NAME
XML::LibXML::Ferry - Marshall LibXML nodes and native objects
SYNOPSIS
use XML::LibXML::Ferry; # Implies use XML::LibXML
DESCRIPTION
Adds higher-level methods to XML::LibXML::Element to very expressively traverse and create XML fragments to/from your custom objects.
METHODS
XML::LibXML::Element::attr( [%attributes] )
-
If
%attributes
is not empty: each key/value pair is added/replaced in the element. Undefined values are skipped. Returns the element itself, for possible chaining.If
%attributes
is missing/empty: returns a hashref of all of the element's attributes (although that is redundant with simply using the element as a hashref directly, as explained in "OVERLOADING" in XML::LibXML::Element). XML::LibXML::Element::create( $name, [$text], [%attributes] )
-
Create a stand-alone element named
$name
with%attributes
set, in the same document as the current element.To create an element with attributes but no text content, specify an undefined
$text
.Returns the new element.
XML::LibXML::Element::add( $name, [$text], [%attributes] )
-
Wrapper around "XML::LibXML::Element::create()", which also appends the new element to the children of the current element. Returns the new element.
XML::LibXML::Element::add( $node )
-
With an element as its only argument, convenience wrapper for
$node->appendChild()
. XML::LibXML::Node::textNodeContent()
-
Iterate through each of the element's immediate children and create a string from text nodes found. The result is stripped of leading and trailing whitespace.
XML::LibXML::Element::ferry( $obj, [$exceptions] )
-
Iterate through each of the element's attributes (including namespaced ones), then each of its child nodes.
The lowercased attribute or node name is matched against
$obj
's methods. Without a match, a second try is made with an appends
. With still no match, the same two names are matched against$obj
's hash keys. With still no match, the value is ignored.If we matched a method, it is passed the attribute's text or node's text content as single argument. (Or your subroutine results or new class instance, see below.) If we matched a direct hash key, it is overwritten with that new content, or if the existing hash value is an arrayref, the new content is pushed to it instead.
$exceptions
is a hashref associating attributes and child node names with one of three possible types:- String property name
-
Alternative method/key name to use instead of the lowercased, possibly plural form of the attribute/node name. Great for shortening verbose names in your API.
Since non-existent property names are safely ignored, you can make sure that a node or attribute will be ignored by specifying an unknown name. To keep Ferry-using code consistent and explicit, parts of the DTD you're working with, but which you are not implementing, should be set to
__UNIMPLEMENTED
, parts which are ignored because they are obsolete to__OBSOLETE
and parts which are skipped for any other reason to__IGNORE
. - Arrayref
-
Two items are expected:
- String property name
-
As above, alternative name.
- Subroutine reference OR String class name
-
If a subroutine is given, it is called with two arguments:
$obj
and the current XML node or attribute string. If your subroutine returns something, it will be used as the value to save. It is thus impossible to storeundef
by returning it. Note that if a subroutine is associated with an unknown property name (i.e.__IGNORE
), it will still be invoked and its return value ignored, which is useful for cases where you have nothing meaningful to return.If a class name is given, a new instance of it is created with:
$classname->new($val)
where$val
is either an attribute's string content or a XML::LibXML::Element. The created object will be used as the value to save. This is key to allow creating various classes representing different parts of a DTD: with each class creator internally calling$node->ferry(...)
, one can end up with any arbitrary structure matching that of the XML document.
- Hashref
-
Recursion: the hashref will be treated like
$exceptions
into the current$obj
. This is useful to flatten small but deep structures without having to use multiple classes. An empty hashref still triggers this behavior.
Optional key
__text
should contain a property name. This is necessary for getting the direct text content of a node along with any attributes.Optional key
__meta_name
alters the above behavior slightly: the element being processed is handled like a key-value tag. (Like HTML'sMETA
or cXML'sExtrinsic
.) The key to search in$exceptions
will be the content of its attribute named in__meta_name
instead of itsnodeName
. So a hypothetical<meta property="foo">bar</meta>
with a meta nameproperty
would be treated as if it actually were<foo>bar</foo>
.Optional key
__meta_content
works in conjunction with__meta_name
above, and adds that the value will also come from the specified attribute. For example, a meta nameproperty
and contentvalue
would treat<meta property="foo" value="bar">ignored</meta>
as if it actually were<foo>bar</foo>
.See "EXAMPLES" for a detailed example.
XML::LibXML::Element::toHash()
-
Convert an XML element tree into a recursive hash. Each attribute is in a key
__attributes
and each child node is recursively put in an array in its name. Key__text
contains the merged text nodes directly in the element, with intial and trailing whitespace stripped.The resulting format is a bit verbose, but ideal for using Test::Deep to compare XML fragments and for quick inspections. (See "EXAMPLES".)
XML::LibXML::Document::toHash()
-
Convenience wrapper which invokes XML::LibXML::Element::toHash() above on the document's
documentElement
.
EXAMPLES
ferry():
Given the following XML fragment as $root
, an XML::LibXML::Element:
<Example weirdName="test-example">
<Attribute name="location">1234 Main St</Attribute>
<Attribute name="phone">1-800-555-1212</Attribute>
<Bars>
<Bar name="first bar">
<Description>
<Text>This is the first bar!</Text>
</Description>
</Bar>
<Bar>
<Description>
<Text>The second bar is unnamed.</Text>
</Description>
</Bar>
</Bars>
</Example>
We could write the following to clearly map Example
to a Mystuff::Thingy
also containing some Mystuff::Otherthing
s:
use XML::LibXML::Ferry;
my $thing = new Mystuff::Thing 'thingy', $root;
package Mystuff::Thing
sub new {
my ($class, $name, $node) = @_;
my $self = {
foo => undef,
location => undef,
phone_number => undef,
bar => [],
};
bless $self, $class;
$node->ferry($self, {
__attributes => {
weirdName => 'foo',
},
Attribute => {
__meta_name => 'name',
# 'location' will implicitly match our property
phone => 'phone_number',
},
Bars => {
Bar => [ 'bars', 'Mystuff::Otherthing' ],
},
});
return $self;
}
package Mystuff::Otherthing;
sub new {
my ($class, $name, $node) = @_;
my $self = {
name => undef,
description => undef,
};
bless $self, $class;
$node->ferry($self, {
# Attribute 'name' will implicitly match our property
Description => {
Text => 'description',
},
});
return $self;
}
This would make $thing
contain:
$VAR1 = bless( {
'foo' => 'test-example',
'location' => '1234 Main St',
'phone_number' => '1-800-555-1212',
'bar' => [
bless( {
'name' => 'first bar',
'description' => 'This is the first bar!'
}, 'Mystuff::Otherthing' ),
bless( {
'name' => undef,
'description' => 'The second bar is unnamed.'
}, 'Mystuff::Otherthing' )
],
}, 'Mystuff::Thing' );
toHash():
Given the following XML fragment:
<Example weirdName="test-example">
<Attribute name="location">1234 Main St</Attribute>
<Attribute name="phone">1-800-555-1212</Attribute>
</Example>
"toHash()" would return:
$VAR1 = {
'__attributes' => {
'weirdName' => 'test-example',
},
'Attribute' => [
{
'__attributes' => {
'name' => 'location',
},
'__text' => '1234 Main St',
},
{
'__attributes' => {
'name' => 'phone',
},
'__text' => '1-800-555-1212',
},
],
};
AUTHOR
Stéphane Lavergne https://github.com/vphantom
ACKNOWLEDGEMENTS
Graph X Design Inc. https://www.gxd.ca/ sponsored this project.
COPYRIGHT & LICENSE
Copyright (c) 2017-2018 Stéphane Lavergne https://github.com/vphantom
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.