nenbrian has asked for the wisdom of the Perl Monks concerning the following question:

Hello Perl hackers,

I have an application that I originally wrote using XML::DOM. However, due limitations in XML::XSLT and XML::DOM::XPath (which use XML::DOM objects) I have decided to convert the application to use XML::LibXML and XML::LibXSLT. Since most of the methods are the same, the changes were not too large.

In my application, I need to subclass the XML::LibXML::Element class. My class provides wrapper methods around the DOM methods, which I use to simplify access to data members in my objects (data members in my objects are implemented as subelements in an XML::LibXML::Element). There are a number of reasons for why I am doing this, but I won't get into them here.

My question is, is it ok to do this? I didn't have any problems subclassing XML::DOM::Element, but when I switched over to XML::LibXML, I ran into a strange problem. My application is quite complex, so I can't include code samples, but I have isolated the problem in a simple example (see "readmore" section below).

In my application I bless an XML::LibXML::Element object into my class, which we'll call "Foo". Then I insert it into a document. My problem is that, when I use the parentNode() method to get a, reference to the a Foo object from one of its subelements, I get a reference to an XML::LibXML::Element object, not my application's Foo object. At first I thought that the XML::LibXML package was re-blessing the reference into the XML::LibXML::Element class somewhere, but looking at the references, they are not the same objects (i.e. the references have different values). I think I could work around this if I could just find another way to get from a subelemnt to its parent element, without losing the reference to the original parent element. (note: I also tried using $obj->findnodes('..'), but with no success.)

Thank you and regards.
-brian

CommonObject.pm - base class for most of the classes in my application. Contains methods for accessing the data members of my objects, which are implemented as subelements of an XML::LibXML::Element objects blessed into my class.

package CommonObject; use strict; use XML::LibXML; use vars qw{ @ISA }; @ISA= qw{ XML::LibXML::Element }; sub createTextField { my ($self, $fieldname, $fieldvalue) = @_; my ($newelem, $textnode); if (defined($fieldname)) { $newelem = XML::LibXML::Element->new($fieldname); } else { return undef; } # The Perl XML::LibXML implementation requires that a text node's +data be # set to a defined value, so we set it to an empty string if $fiel +dvalue # is undefined. This will happen if an initial text field value i +s not # specified. # $fieldvalue = '' unless (defined($fieldvalue)); if ($newelem) { $textnode = XML::LibXML::Text->new($fieldvalue); } else { return undef; } if (defined($textnode)) { $newelem->appendChild($textnode); $self->appendChild($newelem); } else { return undef; } return $newelem; } sub setTextFieldValue { my $self = shift; my $fieldname = shift; my $newvalue = shift; if (defined($fieldname)) { my $nodelist = $self->findnodes("./$fieldname"); my $node = $nodelist->item(0) if ($nodelist->size() > 0); return undef unless defined($node); my $textnode = $node->getFirstChild(); if ($textnode->isa('XML::LibXML::Text')) { my $oldvalue = $textnode->getData(); if (defined($oldvalue)) { $oldvalue =~ s/^\s+//; # Cut leading whitespace. $oldvalue =~ s/\s+$//; # Cut trailing whitespace. } else { $oldvalue = ''; } if (defined($newvalue)) { $textnode->setData($newvalue); } else { $textnode->setData(''); } return $oldvalue; } else { return undef; } } return undef; } sub getTextFieldValue { my $self = shift; my $fieldname = shift; if (defined($fieldname)) { my $node; foreach my $child ($self->childNodes()) { if ($child->isa('XML::LibXML::Element')) { my $name = $child->localname(); if ($name eq $fieldname) { $node = $child; last; } } } return undef unless defined($node); my $textnode = $node->getFirstChild(); #printf(qq{Found Text node: $textnode with data "%s"\n}, # $textnode->getData()); if (defined($textnode) && $textnode->isa('XML::LibXML::Text')) + { my $value = $textnode->getData(); if (defined($value)) { $value =~ s/^\s+//; # Cut leading whitespace. $value =~ s/\s+$//; # Cut trailing whitespace. } else { $value = ''; } return $value; } else { return ''; } } return undef; } 1;

Foo.pm - simplified example of one of the classes in my application.

package Foo; use strict; use XML::LibXML; use CommonObject; use vars qw{ @ISA }; @ISA = qw{ CommonObject }; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = XML::LibXML::Element->new('Foo'); #my $doc = XML::LibXML::Document->new(); #$doc->setDocumentElement($self); bless($self, $class); $self->createTextField('Name'); return $self; } sub setName { my $self = shift; my $name = shift; return $self->setTextFieldValue('Name', $name); } sub getName { my $self = shift; return $self->getTextFieldValue('Name'); } 1;

testfoo - sample test script illustrating the problem.

#!/usr/bin/perl -w use strict; use XML::LibXML; use Foo; my $foo = Foo->new(); my $doc = XML::LibXML::Document->new(); print "\$foo is a ", ref($foo), " object reference\n"; my $docelem = XML::LibXML::Element->new('Root'); $doc->setDocumentElement($docelem); $docelem->appendChild($foo); $foo->setName('Bar'); my $name = $foo->getName(); print qq{\$foo->getName() returned "$name"\n\n}; print qq{Notice that my object has type "Foo" here:\n}; my $localname = $foo->localname(); print "My object: $foo ($localname)\n\n"; my $nodes = $doc->findnodes('//Name'); my $s = scalar(@{$nodes}) == 1 ? '' : 's'; print "Found ", scalar(@{$nodes}), " node$s:\n"; foreach my $node (@{$nodes}) { print " ", $node->toString(), "\n"; my $parent = $node->parentNode(); print " From a subelement of my object, I call parentNode().\n" +; print " Now notice that my object is no longer a Foo object:\n\ +n"; my $plm = $parent->localname(); print " My parent: $parent ($plm)\n\n"; print " The reference returned by parantNode() is not the same\ +n"; print " as the original reference:\n\n"; print " ($foo != $parent)\n"; } print "\n", "Document:\n", $doc->toString(), "\n";

Replies are listed 'Best First'.
Re: Is it a bad idea to subclass XML::LibXML ?
by raptnor2 (Beadle) on May 08, 2004 at 14:04 UTC
    Is there a reason you want to subclass vice apply a decorator when you use it? Generally speaking, it is not easy to inject your object into a framework that expects its own objects. Depending on how you are using your object, you may want to use composition over inheritance. Composition is defined as one object that uses another to extend or adapt it's interface (slightly simplified).

    #code doesn't work
    foreach my $node (@{$nodes}){
    	my $ele = new Foo($node);
    	# use ele as a wrapper for $node
    }
    #end code that doesn't work
    
    In this example Foo is used to extend or adapt the functionality and interface of $node, but doesn't confuse the framework by adding additional subclasses. If you are trying to add additional behavior to $node you want to use something called the Decorator pattern. If you are just trying to simplify the interface or change it to meet you expectation you want to use something called the Adaptor pattern. Both of these patterns use composition. Using Composition over inheritance will generally simplify your design.

    Scott Walters has put together a great Perl patterns resource called http://perldesignpatterns.com/?/ and has created a great explanation of what patterns (a highly recommended read) are. The decorator pattern is discussed here and the adaptor pattern is discussed here. Based on your question I think one of these may help.

    Cheers,

    John

      Thanks for your reply, John.

      Yes, I thought about implementing my objects as wrappers around XML::LibXML::Element objects. However, the problem is that I need to be able to link my objects into a hierarchical structure, and then export that structure into an XML document that reflects the structure of this hierarchy. And, of course, to re-import the XML document by parsing it with the XML::LibXML::Parser, which would build a DOM tree. I would then bless the DOM objects into my classes, magically re-creating the hierarchy of my application's data... (can you see how beautiful it is?) The simplest way to accomplish this is to have my objects be the DOM objects themselves, and use the DOM tree's structure to represent my application's structure.

      To wrap the DOM objects would be to require that I link the wrapper objects together to represent my application's structure, and then to link their underlying DOM objects in parallel. This would be a lot of duplication, as well as complexity in keeping the two sets of links consistent. Unfortunately, I might be stuck using a solution like this. It's either that, or revert to using XML::DOM (for which my subclassing scheme worked, but which is sub-optimal for other reasons).

      Thanks again,
      -brian

        I posted a new module called IOC::Lite last week that contains a class that may be useful to you. It's called IOC::Lite::Adaptor. This class's job is to pretend to be another object while providing the means to add methods to it at runtime. It will be greatly enhanced in my next release (due out this weekend), but the current one may help you now (at least in concept).

        Cheers,

        John