File Coverage

blib/lib/XML/Struct/Simple.pm
Criterion Covered Total %
statement 61 61 100.0
branch 32 34 94.1
condition 11 12 91.6
subroutine 9 9 100.0
pod 2 3 66.6
total 115 119 96.6


line stmt bran cond sub pod time code
1             package XML::Struct::Simple;
2 6     6   168319 use strict;
  6         18  
  6         190  
3 6     6   545 use Moo;
  6         11112  
  6         32  
4 6     6   3003 use List::Util qw(first);
  6         13  
  6         452  
5 6     6   39 use Scalar::Util qw(reftype blessed);
  6         13  
  6         302  
6 6     6   36 use Carp qw(carp);
  6         10  
  6         4941  
7              
8             our $VERSION = '0.27';
9              
10             has root => (
11             is => 'rw',
12             default => sub { 0 },
13             );
14              
15             has attributes => (
16             is => 'rw',
17             default => sub { 1 },
18             coerce => sub { !defined $_[0] or ($_[0] and $_[0] ne 'remove') },
19             );
20              
21             has content => (
22             is => 'rw',
23             default => sub { 'content' },
24             );
25              
26             has depth => (
27             is => 'rw',
28             coerce => sub { (defined $_[0] and $_[0] >= 0) ? $_[0] : undef },
29             );
30              
31             sub transform {
32 40     40 1 330 my ($self, $element) = @_;
33              
34 40         94 my $simple = $self->transform_content($element,0);
35              
36             # enforce root for special case <root>text</root>
37 40 100 100     202 if ($self->root or !ref $simple) {
38 23 100       142 my $root = $self->root !~ /^[+-]?[0-9]+$/ ? $self->root : $element->[0];
39 23         159 return { $root => $simple };
40             } else {
41 17         118 return $simple;
42             }
43             }
44              
45             # returns a (possibly empty) hash or a scalar
46             sub transform_content {
47 121     121 1 223 my ($self, $element, $depth) = @_;
48 121 50       236 $depth = 0 if !defined $depth;
49              
50 121 100 100     2106 if (defined $self->depth and $depth >= $self->depth) {
    100          
51 14         348 return $element;
52             } elsif ( @$element == 1 ) { # empty tag
53 2         17 return { };
54             }
55              
56 105         954 my $attributes = {};
57 105         138 my $children;
58              
59 105 100       352 if ( reftype $element->[1] eq 'HASH' ) { # [ $tag, \%attributes, \@children ]
60 97 100       1588 $attributes = $element->[1] if $self->attributes;
61 97         658 $children = $element->[2];
62             } else { # [ $tag, \@children ]
63 8         20 $children = $element->[1];
64             }
65              
66             # no element children
67 105 100   93   524 unless ( first { ref $_ } @$children ) {
  93         284  
68 57         134 my $content = join "", @$children;
69 57 100       147 if ($content eq '') {
    100          
70 20         82 return { %$attributes };
71             } elsif (!%$attributes) {
72 32         92 return $content;
73             } else {
74 5         31 return { %$attributes, $self->content => $content };
75             }
76             }
77              
78 48         186 my $simple = { map {$_ => [$attributes->{$_}] } keys %$attributes };
  6         36  
79              
80 48         111 foreach my $child ( @$children ) {
81 89 100       192 next unless ref $child; # skip mixed content text
82              
83 81         125 my $name = $child->[0];
84 81         198 my $content = $self->transform_content($child, $depth+1);
85              
86 81 100       215 if ( $simple->{$name} ) {
87 14         22 push @{$simple->{$name}}, $content;
  14         45  
88             } else {
89 67         207 $simple->{$name} = [$content];
90             }
91             }
92              
93 48         154 foreach my $name (keys %$simple) {
94 73 100       129 next if @{$simple->{$name}} != 1;
  73         198  
95 60         112 my $c = $simple->{$name}->[0];
96 60 100 66     267 if (!ref $c or (!blessed $c and reftype $c eq 'HASH')) {
      100        
97 52         119 $simple->{$name} = $c;
98             }
99             }
100              
101 48         99 return $simple;
102             }
103              
104             sub removeXMLAttr {
105 4     4 0 803 my $node = shift;
106              
107 4 100       10 carp "removeXMLAttr is deprecated"
108             if 'XML::Struct::Simple::removeXMLAttr' ne (caller(1))[3];
109              
110             ref $node
111             ? ( $node->[2]
112 4 50       797 ? [ $node->[0], [ map { removeXMLAttr($_,1) } @{$node->[2]} ] ]
  3 100       9  
  2         5  
113             : [ $node->[0] ] ) # empty element
114             : $node; # text node
115             }
116              
117             1;
118             __END__
119              
120             =encoding UTF-8
121              
122             =head1 NAME
123              
124             XML::Struct::Simple - Transform MicroXML data structures into simple (unordered) form
125              
126             =head1 SYNOPSIS
127              
128             my $micro = [
129             root => { xmlns => 'http://example.org/' },
130             [ '!', [ x => {}, [42] ] ]
131             ];
132             my $converter = XML::Struct::Simple->new( root => 'record' );
133             my $simple = $converter->transform( $micro );
134             # { record => { xmlns => 'http://example.org/', x => 42 } }
135              
136             =head1 DESCRIPTION
137              
138             This module implements a transformation from structured XML (MicroXML) to
139             simple key-value format (SimpleXML) as known from L<XML::Simple>: Attributes
140             and child elements are treated as hash keys with their content as value. Text
141             elements without attributes are converted to text and empty elements without
142             attributes are converted to empty hashes.
143              
144             L<XML::Struct> can export the function C<simpleXML> for easy use. Function
145             C<readXML> and L<XML::Struct::Reader> apply transformation to SimpleXML with
146             option C<simple>.
147              
148             =head1 METHODS
149              
150             =head2 transform( $element )
151              
152             Transform XML given as array reference (MicroXML) to XML as hash reference
153             (SimpleXML) as configured.
154              
155             =head2 transform_content( $element [, $depth ] )
156              
157             Transform child nodes and attributes of an XML element given as array reference
158             at a given depth (C<0> by default). Returns a hash reference, a scalar, or the
159             element unmodified.
160              
161             =head1 CONFIGURATION
162              
163             =over
164              
165             =item root
166              
167             Keep the root element instead of removing. This corresponds to option
168             C<KeepRoot> in L<XML::Simple>. In addition a non-numeric value can be used to
169             override the name of the root element. Disabled by default.
170              
171             =item attributes
172              
173             Include XML attributes. Enabled by default. The special value C<remove> is
174             equivalent to false. Corresponds to option C<NoAttr> in L<XML::Simple>.
175              
176             =item content
177              
178             Name of a field to put text content in. Set to "C<content> by default.
179             Corresponds to option C<ContentKey> in L<XML::Simple>.
180              
181             =item depth
182              
183             Only transform up to a given depth. Set to a negative value by default for
184             unlimited depth. Elements below depth are not cloned but copied by reference.
185             Depth 0 will return the element unmodified.
186              
187             =back
188              
189             Option C<KeyAttr>, C<ForceArray>, and other fetures of L<XML::Simple> not
190             supported. Options C<NsExpand> and C<NsStrip> supported in
191             L<XML::LibXML::Simple> are not supported yet.
192              
193             =cut