File Coverage

blib/lib/XML/Constructor.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package XML::Constructor;
2              
3 1     1   53752 use warnings;
  1         11  
  1         48  
4 1     1   7 use strict;
  1         2  
  1         221  
5 1     1   837 use XML::LibXML;
  0            
  0            
6             use Scalar::Util qw/blessed/;
7             use Carp qw/cluck croak carp/;
8              
9             =head1 NAME
10              
11             XML::Constructor - Generate XML from a markup syntax allowing for the abstraction of markup from code
12              
13             =cut
14              
15             our $VERSION = '0.01';
16              
17              
18             sub generate {
19             my ( $class, %args ) = @_;
20             my ( $parent_node, $data ) =
21             @args{ qw( parent_node data ) };
22              
23             my $parent = $class->_get_parent_node($parent_node);
24              
25             return $parent unless (ref $data eq 'ARRAY');
26              
27             return $class->_generate(parent => $parent, data => $data);
28             }
29              
30             sub toString { shift->generate( @_ )->toString }
31              
32             sub _get_parent_node {
33             my ( $class, $parent_node ) = @_;
34              
35             if(blessed $parent_node) {
36             return $parent_node
37             if $class->_validate_parent_object($parent_node);
38             }
39              
40             return ( ref $parent_node eq 'ARRAY' )
41             ? $class->_create_parent_from_arrayref( element => $parent_node )
42             : $class->_create_parent_node( $parent_node );
43             }
44              
45             sub _generate {
46             my ( $class, %args ) = @_;
47             my ($parent, $data ) = @args{ qw( parent data ) };
48              
49             for my $element ( @$data ) {
50             my $method = $class->_get_dispatch_method($element);
51              
52             next unless $method;
53              
54             $class->$method( parent => $parent, element => $element );
55             }
56             return $parent;
57             }
58              
59             sub _validate_parent_object {
60             my ( $class, $parent_element ) = @_;
61             croak "parent element not an object"
62             unless blessed $parent_element;
63              
64             croak "parent element not decendant of XML::LibXML::(Element|Document)"
65             unless ($parent_element->isa("XML::LibXML::Node") ||
66             $parent_element->isa("XML::LibXML::Document"));
67            
68            
69             return 1;
70             }
71              
72             sub _create_parent_node {
73             my ( $class, $parent_node_name ) = @_;
74             cluck "creating an empty parent node"
75             unless(defined $parent_node_name && $parent_node_name =~/\w/);
76              
77             return $class->_create_element($parent_node_name);
78             }
79              
80             sub _get_dispatch_method {
81             my ( $class, $element) = @_;
82             my $method;
83              
84             for( ref $element ) {
85             $_ eq 'HASH' && do{$method = '_from_hash'; last};
86             $_ eq 'ARRAY' && do{$method = '_from_array'; last};
87             $_ eq 'SCALAR' && do{$method = '_from_scalar'; last};
88             }
89              
90             # is element a XML::LibXML::Element object?
91             if(!$method && (blessed $element)) {
92             $method = '_from_libxml'
93             if $element->isa("XML::LibXML::Element");
94             }
95              
96             carp "cannot process an element in markup [$element]"
97             if(!$method);
98              
99             return $method;
100             }
101              
102             sub _create_parent_from_arrayref {
103             my $class = shift;
104             my %args = @_;
105             my( $element )
106             = @args{qw/element/};
107              
108             my $root = XML::LibXML::Element->new("");
109             # save attribute_title
110             my $attribute_title = $element->[0];
111              
112             $class->_from_array( parent => $root, element => $element );
113              
114             my $parent = $root->getChildrenByTagName( $attribute_title );
115              
116             die "could not create parent node from ARRAYREF named ".$element->[0]
117             unless ( (ref $parent ) && $parent->[0] );
118              
119             # return 1st node found
120             return $parent->[0];
121             }
122              
123             sub _create_element{
124             my $class =shift;
125             return XML::LibXML::Element->new( shift || "" )
126             }
127              
128             sub _from_hash {
129             my ( $class, %args ) = @_;
130             my ( $parent, $element ) =
131             @args{ qw( parent element ) };
132              
133             foreach my $attribute ( keys %$element ) {
134             my $value = $element->{$attribute};
135             my $obj = $class->_create_element($attribute);
136              
137             if( $value ) {
138             if( ref $value ) {
139             # kick back to generate
140             $class->_generate( parent => $obj, data => [ $value ] );
141             }
142             else {
143             $obj->appendText( $value );
144             }
145             }
146             $parent->addChild( $obj );
147             }
148             }
149              
150             sub _from_array {
151             my ( $class, %args ) = @_;
152             my ( $parent, $array ) =
153             @args{ qw( parent element ) };
154              
155             my $node = $class->_create_element( shift @$array );
156              
157             while( my $attribute = shift @$array ) {
158             if( ref $attribute ) {
159             $class->_generate( parent => $node, data => [ $attribute ] );
160             }
161             else {
162             # next element in array becomes attribute value
163             $node->setAttribute( $attribute, shift @$array );
164             }
165             }
166             $parent->addChild( $node );
167             }
168              
169             sub _from_libxml {
170             my ( $class, %args ) = @_;
171             my ( $parent, $element ) =
172             @args{ qw( parent element ) };
173             $parent->addChild( $element );
174             }
175              
176             sub _from_scalar {
177             my ( $class, %args ) = @_;
178             my ( $parent, $element ) =
179             @args{ qw( parent element ) };
180              
181             if ( $$element ) {
182             my $string = $$element;
183             # removed doubly encoded entites
184             # et al XML::DoubleEncodedEntities et al XML::Tiny
185             if($string =~ /&(amp|lt|gt|quot|apos);/) {
186             $string =~ s/&(lt;|gt;|quot;|apos;|amp;)/
187             $1 eq 'lt;' ? '<' :
188             $1 eq 'gt;' ? '>' :
189             $1 eq 'apos;' ? "'" :
190             $1 eq 'quot;' ? '"' :
191             '&'
192             /ge;
193             $element = \$string;
194             }
195             $parent->appendText( $$element );
196             }
197             }
198             1;
199              
200             __END__