NAME XML::LibXML::Augment - extend XML::LibXML::{Attr,Element,Document} on a per-namespace/element basis SYNOPSIS { package Local::Element::Bar; use 5.010; use strict; use warnings; use XML::LibXML::Augment -names => ['{http://example.com/}bar']; sub tellJoke { say q{A man walked into a bar.}; say q{"Ouch," he said.}; say q{It was an iron bar.}; } } { package main; use 5.010; use strict; use warnings; use XML::LibXML::Augment; my $doc = XML::LibXML->load_xml(string => <<'XML'); XML XML::LibXML::Augment->upgrade($doc); $doc->findnodes('//*[@baz]')->shift->tellJoke; } DESCRIPTION XML::LibXML is super-awesome. However, I don't know about you, but sometimes I wish it had some domain-specific knowledge. For example, if I have an XML::LibXML::Element which represents an HTML `
` element, why can't it have a `submit` method? OK, so I can subclass XML::LibXML::Element, but then I call `childNodes` on my subclass, and get back plain, non-subclassed objects, and I'm back where I first began. XML::LibXML::Augment is the package I've been meaning to write for quite some time, to take care of all those issues. The magic is in the import method. You write a package which imports XML::LibXML::Augment with particular settings. Then, once you've parsed the document, you call `XML::LibXML::Augment->upgrade` on the root node. `import %args` Currently three options are supported. Each has a leading hyphen. `-names => \@list` Each item on the list is the name of an element you want to override. If the element is in a namespace, use Clark notation: {http://www.example.com/namespace}localname If the element is not in a namespace, then leave out the curly braces. Because of clashes (see "Conflict resolution" in CAVEATS below) it seems like a bad idea to try to augment non-namespaced elements based entirely on their localname. But, hey, you want to do it? It's your funeral. You can use "*" as the localname to indicate that you wish to subclass all elements in a particular namespace. (Again, see "Conflict resolution".) Yes, this is a list, because it might make sense to use the same package to cover, say, HTML ``, `` and `` elements. `-type => $type` $type can be either 'Element', 'Attr' or 'Document', but defaults to 'Element'. This indicates what sort of thing you're subclassing. Only elements, attributes and documents are supported. (Document subclassing is based on the namespace and localname of its root element.) Elements, attributes and documents are pairwise disjoint classes, so you cannot (for example) subclass elements and attributes in the same package. `-isa => \@packages` Normally the import routine will automatically establish your package as a subclass of XML::LibXML::Augment::Attr or XML::LibXML::Augment::Element by monkeying around with your @ISA. There are times when you want more precise control over @ISA though, such as inheritance heirarchies: Local::HTML::TableHeader -> Local::HTML::TableCell -> Local::HTML::Element -> XML::LibXML::Augment::Element In this case, you probably want to use the automatic subclassing for Local::HTML::Element, but not for the other two classes, which would work better with explicit subclassing: { package Local::HTML::TableCell; use XML::LibXML::Augment -names => ['{http://www.w3.org/1999/xhtml}td'], -isa => ['Local::HTML::Element']; } `upgrade(@things)` This is a function, not a method. It's not exported, so call it with its full name: XML::LibXML::Augment::upgrade(@things); Upgrades the things in-place, skipping over things that cannot be upgraded, and returns the things as a list. You can of course call it like this: @upgraded = XML::LibXML::Augment->upgrade(@things); But bear in mind that because of the way Perl method calls work, this is effectively the same as: @upgraded = ( 'XML::LibXML::Augment', XML::LibXML::Augment::upgrade(@things), ); What is upgrading? Things that are not blessed objects are not upgradable. Blessed objects that XML::LibXML::Augment can find an appropriate subclass for are reblessed into that package (e.g. XML::LibXML::Comment is reblessed into XML::LibXML::Augment::Comment). The nodes in XML::LibXML::NodeLists are reblessed. `rebless($thing)` This is basically a single-argument version `upgrade` but designed to be called as a class method, and doesn't recurse into nodelists. my $upgraded = XML::LibXML::Augment->rebless($element); Note that $element is actually upgraded in-place. refaddr($upgraded) == refaddr($element); # true `ideal_class_for_object($object)` Calculates the class that `rebless` would bless the object into, but doesn't actually do the reblessing. `make_class(@superclasses)` Constructs a new class that is a subclass of the given classes. Call this as a class method. Returns the class name. This is a method used internally by XML::LibXML::Augment, documented in case anybody else wants to use it. `BLESS` XML::LibXML::Augment doesn't actually have a method called `BLESS`, but your package can do. Inspired by Moose's `BUILD` method, your package's `BLESS` method will be called (if it exists) just after an XML::LibXML node is reblessed into your package. Unlike Moose's `BUILD` method, the inheritance chain isn't automatically walked. It is your package's responsibility to call `SUPER::BLESS` if required. Do bear in mind that XML::LibXML's node objects are little more than pointers to the "real" XML nodes that live on the C side of the XS divide. As such, XML::LibXML can (and frequently does) destroy and re-instantiate the pointers willy-nilly. This may limit the usefulness of `BLESS`. HOW DOES IT WORK? Mostly just careful use of inheritance. CAVEATS Conflict resolution Only one class can handle any given element. If two different modules want to subclass, say, XHTML `` elements, there can be only one winner. So, which one wins? Neither. XML::LibXML::Augment creates a brand, spanking new class which inherits from both, and that new class wins. This will usually work, but may trip up sometimes. The `joke.pl` example bundled with the XML-LibXML-Augment release gives a demonstration of this feature. Note that packages which use an element wildcard, for instance: package Local::HTML::Element; use XML::LibXML::Augment -names => ['{http://www.w3.org/1999/xhtml}*']; are treated purely as fallbacks. If there exists a non-wildcard class to handle an element, then the wildcard class will be ignored altogether - it won't be included in the funky on-the-fly class generation described above. For these reasons, wildcards are perhaps best avoided. It's usually better to do something like: package Local::HTML::Element; our @ELEMENTS; BEGIN { @ELEMENTS = map { "{http://www.w3.org/1999/xhtml}$_" } qw/a area b col colgroup ... u/; } use XML::LibXML::Augment -names => \@ELEMENTS; XML::LibXML::XPathContext We don't touch XML::LibXML::XPathContext. Results from calling, e.g. `findnodes` on an XPath context will return plain old XML::LibXML nodes. (You can of course upgrade them.) That doesn't render XPath completely unusable though - XML::LibXML::Node also has a `findnodes` method, which will return upgraded objects. Subclassing all elements (or all attributes) XML::LibXML::Augment requires you to specify the namespace URI and localname of the elements/attributes you wish to subclass. If you want to provide additional methods to all XML::LibXML::Elements, then perhaps XML::LibXML::Augment is not for you. Try: sub XML::LibXML::Element::myAwesomeMethod { ... } If you think adding methods to other peoples' classes is evil, then go write some Java and quit complaining. BUGS Please report any bugs to . SEE ALSO XML::LibXML. AUTHOR Toby Inkster . COPYRIGHT AND LICENCE This software is copyright (c) 2012, 2014 by Toby Inkster. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. DISCLAIMER OF WARRANTIES THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.