File Coverage

blib/lib/XML/MyXML.pm
Criterion Covered Total %
statement 294 352 83.5
branch 108 180 60.0
condition 36 62 58.0
subroutine 26 28 92.8
pod 7 7 100.0
total 471 629 74.8


line stmt bran cond sub pod time code
1             package XML::MyXML;
2              
3 2     2   180460 use 5.008001;
  2         18  
4 2     2   11 use strict;
  2         4  
  2         45  
5 2     2   9 use warnings;
  2         3  
  2         81  
6              
7 2     2   856 use XML::MyXML::Object;
  2         5  
  2         82  
8              
9 2     2   17 use Encode;
  2         4  
  2         149  
10 2     2   11 use Carp;
  2         5  
  2         94  
11 2     2   10 use Scalar::Util qw/ weaken /;
  2         4  
  2         9594  
12              
13             require Exporter;
14             our @ISA = qw(Exporter);
15             our @EXPORT_OK = qw(tidy_xml object_to_xml xml_to_object simple_to_xml xml_to_simple check_xml xml_escape);
16             our %EXPORT_TAGS = (all => [@EXPORT_OK]);
17              
18             our $VERSION = "1.07";
19              
20             my $DEFAULT_INDENTSTRING = ' ' x 4;
21              
22             =encoding utf-8
23              
24             =head1 NAME
25              
26             XML::MyXML - A simple-to-use XML module, for parsing and creating XML documents
27              
28             =head1 SYNOPSIS
29              
30             use XML::MyXML qw(tidy_xml xml_to_object);
31             use XML::MyXML qw(:all);
32              
33             my $xml = "Table10.008.50";
34             print tidy_xml($xml);
35              
36             my $obj = xml_to_object($xml);
37             print "Price in Euros = " . $obj->path('price/eur')->text;
38              
39             $obj->simplify is hashref { item => { name => 'Table', price => { usd => '10.00', eur => '8.50' } } }
40             $obj->simplify({ internal => 1 }) is hashref { name => 'Table', price => { usd => '10.00', eur => '8.50' } }
41              
42             =head1 EXPORTABLE
43              
44             xml_escape, tidy_xml, xml_to_object, object_to_xml, simple_to_xml, xml_to_simple, check_xml
45              
46             =head1 FEATURES & LIMITATIONS
47              
48             This module can parse XML comments, CDATA sections, XML entities (the standard five and numeric ones) and simple non-recursive C<< >>s
49              
50             It will ignore (won't parse) C<< >>, C<< >> and other C<< >> special markup
51              
52             All strings (XML documents, attribute names, values, etc) produced by this module or passed as parameters to its functions, are strings that contain characters, rather than bytes/octets. Unless you use the C function flag (see below), in which case the XML documents (and just the XML documents) will be byte/octet strings.
53              
54             XML documents to be parsed may not contain the C<< > >> character unencoded in attribute values
55              
56             =head1 OPTIONAL FUNCTION FLAGS
57              
58             Some functions and methods in this module accept optional flags, listed under each function in the documentation. They are optional, default to zero unless stated otherwise, and can be used as follows: S 1, flag2 => 1 } ) >>>. This is what each flag does:
59              
60             C : the function will strip initial and ending whitespace from all text values returned
61              
62             C : the function will expect the path to a file containing an XML document to parse, instead of an XML string
63              
64             C : the function's XML output will include an XML declaration (C<< >>) in the beginning
65              
66             C : the function will only return the contents of an element in a hashref instead of the element itself (see L for example)
67              
68             C : the function will return tidy XML
69              
70             C : when producing tidy XML, this denotes the string with which child elements will be indented (Default is a string of 4 spaces)
71              
72             C : the function (apart from doing what it's supposed to do) will also save its XML output in a file whose path is denoted by this flag
73              
74             C : strip the namespaces (characters up to and including ':') from the tags
75              
76             C : will add a link in the XML that's being output, of type 'text/xsl', pointing to the filename or URL denoted by this flag
77              
78             C : the function will create a simple arrayref instead of a simple hashref (which will preserve order and elements with duplicate tags)
79              
80             C : the XML document string which is parsed and/or produced by this function, should contain bytes/octets rather than characters
81              
82             =head1 FUNCTIONS
83              
84             =cut
85              
86             sub _encode {
87 91     91   149 my $string = shift;
88 91   50     256 my $entities = shift || {};
89 91 50       174 defined $string or $string = '';
90 91         320 my %replace = (
91             '<' => '<',
92             '>' => '>',
93             '&' => '&',
94             '\'' => ''',
95             '"' => '"',
96             );
97 91         335 my $keys = "(".join("|", sort {length($b) <=> length($a)} keys %replace).")";
  728         1083  
98 91         1840 $string =~ s/$keys/$replace{$1}/g;
99 91         531 return $string;
100             }
101              
102             =head2 xml_escape($string)
103              
104             Returns the same string, but with the C<< < >>, C<< > >>, C<< & >>, C<< " >> and C<< ' >> characters replaced by their XML entities (e.g. C<< & >>).
105              
106             =cut
107              
108             sub xml_escape {
109 1     1 1 3 my ($string) = @_;
110              
111 1         2 return _encode($string);
112             }
113              
114             sub _decode {
115 130     130   208 my $string = shift;
116 130   100     256 my $entities = shift || {};
117 130   50     373 my $flags = shift || {};
118 130 50       251 defined $string or $string = '';
119 130         590 my %replace = ( %$entities, reverse(
120             '<' => '<',
121             '>' => '>',
122             '&' => '&',
123             '\'' => ''',
124             '"' => '"',
125             ));
126 130         670 my @capture = map "\Q$_\E", keys %replace;
127 130         333 push @capture, '&#x[0-9A-Fa-f]+;', '&#[0-9]+;';
128 130         375 my $capture = "(".join("|", @capture).")";
129 130         5503 my @captured = $string =~ /$capture/g;
130 130 100       1029 @captured or return $string;
131 6         15 my %conv;
132 6         14 foreach my $e (@captured) {
133 6 50       20 if (exists $conv{$e}) { next; }
  0         0  
134 6 100       20 if (exists $replace{$e}) {
    50          
    0          
135 5         19 $conv{$e} = $replace{$e};
136             } elsif ($e =~ /\A&#x([0-9a-fA-F]+);\z/) {
137 1         9 $conv{$e} = chr(hex($1));
138             } elsif ($e =~ /\A&#([0-9]+);\z/) {
139 0         0 $conv{$e} = chr($1);
140             }
141             }
142 6         35 my $keys = "(".join("|", map "\Q$_\E", keys %conv).")";
143 6         95 $string =~ s/$keys/$conv{$1}/g;
144 6         48 return $string;
145             }
146              
147             sub _strip {
148 2     2   4 my $string = shift;
149              
150             # NOTE: Replace this with the 'r' flag of the substitution operator
151 2 50       16 return defined $string ? ($string =~ /\A\s*(.*?)\s*\z/s)[0] : $string;
152             }
153              
154             sub _strip_ns {
155 1     1   3 my $string = shift;
156              
157             # NOTE: Replace this with the 'r' flag of the substitution operator
158 1 50       11 return defined $string ? ($string =~ /\A(?:.+\:)?(.*)\z/s)[0] : $string;
159             }
160              
161             =head2 tidy_xml($raw_xml)
162              
163             Returns the XML string in a tidy format (with tabs & newlines)
164              
165             Optional flags: C, C, C, C, C
166              
167             =cut
168              
169              
170             sub tidy_xml {
171 3     3 1 7 my $xml = shift;
172 3   50     15 my $flags = shift || {};
173              
174 3         11 my $object = xml_to_object($xml, $flags);
175 3 50       11 defined $object or return $object;
176 3         11 _tidy_object($object, undef, $flags);
177 3         24 my $return = $object->to_xml({ %$flags, tidy => 0 }) . "\n";
178 3         26 return $return;
179             }
180              
181              
182             =head2 xml_to_object($raw_xml)
183              
184             Creates an 'XML::MyXML::Object' object from the raw XML provided
185              
186             Optional flags: C, C
187              
188             =cut
189              
190             sub xml_to_object {
191 40     40 1 4295 my $xml = shift;
192 40   100     146 my $flags = shift || {};
193              
194 40 100       106 if ($flags->{file}) {
195 1 50       43 open my $fh, '<', $xml or croak "Error: The file '$xml' could not be opened for reading: $!";
196 1         27 $xml = join '', <$fh>;
197 1         14 close $fh;
198             }
199              
200 40 100 100     159 if ($flags->{bytes} or $flags->{file}) {
201 5         26 my (undef, undef, $encoding) = $xml =~ /<\?xml(\s[^>]+)?\sencoding=(['"])(.*?)\2/g;
202 5 50       88 $encoding = 'UTF-8' if ! defined $encoding;
203 5 50       56 if ($encoding =~ /\Autf-?8\z/i) { $encoding = 'UTF-8'; }
  5         13  
204 5         10 eval {
205 5         28 $xml = decode($encoding, $xml, Encode::FB_CROAK);
206             };
207 5 100       715 ! $@ or croak 'Error: Input string is invalid UTF-8';
208             }
209              
210 39         77 my $entities = {};
211              
212             # Parse CDATA sections
213 39         123 $xml =~ s/<\!\[CDATA\[(.*?)\]\]>/_encode($1)/egs;
  0         0  
214 39         526 my @els = $xml =~ /(|$)|<[^>]*?>|[^<>]+)/sg;
215             # Remove comments, special markup and initial whitespace
216             {
217 39         71 my $init_ws = 1;
  39         71  
218 39         101 foreach my $el (@els) {
219 304 50       1074 if ($el =~ /\A\z/) { croak encode_utf8("Error: unclosed XML comment block - '$el'"); }
  0         0  
221 0         0 undef $el;
222             } elsif ($el =~ /\A<\?/) { # like or
223 0 0       0 if ($el !~ /\?>\z/) { croak encode_utf8("Error: Erroneous special markup - '$el'"); }
  0         0  
224 0         0 undef $el;
225             } elsif (my ($entname, undef, $entvalue) = $el =~ /\A\z/g) {
226 2         40 $entities->{"&$entname;"} = _decode($entvalue);
227 2         6 undef $el;
228             } elsif ($el =~ / or or
229 0         0 undef $el;
230             } elsif ($init_ws) {
231 42 100       163 if ($el =~ /\S/) {
232 39         77 $init_ws = 0;
233             } else {
234 3         58 undef $el;
235             }
236             }
237             }
238 39         81 @els = grep { defined $_ } @els;
  304         559  
239 39 50       95 if (! @els) { croak "Error: No elements in XML document"; }
  0         0  
240             }
241 39         61 my @stack;
242 39         147 my $object = bless ({ content => [] }, 'XML::MyXML::Object');
243 39         57 my $pointer = $object;
244 39         70 foreach my $el (@els) {
245 299 50       1577 if ($el =~ /\A<\/?>\z/) {
    100          
    100          
    100          
    50          
246 0         0 croak encode_utf8("Error: Strange element: '$el'");
247             } elsif ($el =~ /\A<\/[^\s>]+>\z/) {
248 97         359 my ($element) = $el =~ /\A<\/(\S+)>\z/g;
249 97 50       274 if (! length($element)) { croak encode_utf8("Error: Strange element: '$el'"); }
  0         0  
250 97 50       235 if ($stack[-1]{element} ne $element) { croak encode_utf8("Error: Incompatible stack element: stack='".$stack[-1]{element}."' element='$el'"); }
  0         0  
251 97         153 my $stackentry = pop @stack;
252 97 100       125 if ($#{$stackentry->{content}} == -1) {
  97         222  
253 1         3 delete $stackentry->{content};
254             }
255 97         209 $pointer = $stackentry->{parent};
256             } elsif ($el =~ /\A<[^>]+\/>\z/) {
257 7         42 my ($element) = $el =~ /\A<([^\s>\/]+)/g;
258 7 50       20 if (! length($element)) { croak encode_utf8("Error: Strange element: '$el'"); }
  0         0  
259 7         78 $el =~ s/\A<\Q$element\E//;
260 7         31 $el =~ s/\/>\z//;
261 7         16 my @attrs = $el =~ /\s+(\S+=(['"]).*?\2)/g;
262 7         13 my $i = 1;
263 7         17 @attrs = grep {$i++ % 2} @attrs;
  0         0  
264 7         11 my %attr;
265 7         18 foreach my $attr (@attrs) {
266 0         0 my ($name, undef, $value) = $attr =~ /\A(\S+?)=(['"])(.*?)\2\z/g;
267 0 0 0     0 if (! length($name) or ! defined($value)) { croak encode_utf8("Error: Strange attribute: '$attr'"); }
  0         0  
268 0         0 $attr{$name} = _decode($value, $entities);
269             }
270 7         25 my $entry = { element => $element, attrs => \%attr, parent => $pointer };
271 7         74 weaken( $entry->{parent} );
272 7         18 bless $entry, 'XML::MyXML::Object';
273 7         11 push @{$pointer->{content}}, $entry;
  7         27  
274             } elsif ($el =~ /\A<[^\s>\/][^>]*>\z/) {
275 98         357 my ($element) = $el =~ /\A<([^\s>]+)/g;
276 98 50       250 if (! length($element)) { croak encode_utf8("Error: Strange element: '$el'"); }
  0         0  
277 98         1255 $el =~ s/\A<\Q$element\E//;
278 98         470 $el =~ s/>\z//;
279 98         296 my @attrs = $el =~ /\s+(\S+=(['"]).*?\2)/g;
280 98         139 my $i = 1;
281 98         173 @attrs = grep {$i++ % 2} @attrs;
  60         119  
282 98         122 my %attr;
283 98         183 foreach my $attr (@attrs) {
284 30         165 my ($name, undef, $value) = $attr =~ /\A(\S+?)=(['"])(.*?)\2\z/g;
285 30 50 33     135 if (! length($name) or ! defined($value)) { croak encode_utf8("Error: Strange attribute: '$attr'"); }
  0         0  
286 30         63 $attr{$name} = _decode($value, $entities);
287             }
288 98         341 my $entry = { element => $element, attrs => \%attr, content => [], parent => $pointer };
289 98         359 weaken( $entry->{parent} );
290 98         158 bless $entry, 'XML::MyXML::Object';
291 98         160 push @stack, $entry;
292 98         121 push @{$pointer->{content}}, $entry;
  98         189  
293 98         238 $pointer = $entry;
294             } elsif ($el =~ /\A[^<>]*\z/) {
295 97         208 my $entry = { value => _decode($el, $entities), parent => $pointer };
296 97         356 weaken( $entry->{parent} );
297 97         161 bless $entry, 'XML::MyXML::Object';
298 97         146 push @{$pointer->{content}}, $entry;
  97         264  
299             } else {
300 0         0 croak encode_utf8("Error: Strange element: '$el'");
301             }
302             }
303 39 100       167 if (@stack) { croak encode_utf8("Error: The <$stack[-1]{element}> element has not been closed in XML"); }
  1         9  
304 38         80 $object = $object->{content}[0];
305 38         74 $object->{parent} = undef;
306 38         273 return $object;
307             }
308              
309             sub _objectarray_to_xml {
310 60     60   85 my $object = shift;
311              
312 60         82 my $xml = '';
313 60         97 foreach my $stuff (@$object) {
314 78 100 66     222 if (! defined $stuff->{element} and defined $stuff->{value}) {
315 32         64 $xml .= _encode($stuff->{value});
316             } else {
317 46         104 $xml .= "<".$stuff->{element};
318 46         55 foreach my $attrname (keys %{$stuff->{attrs}}) {
  46         120  
319 1         6 $xml .= " ".$attrname.'="'._encode($stuff->{attrs}{$attrname}).'"';
320             }
321 46 100 100     106 if (! defined $stuff->{content} or ! @{ $stuff->{content} }) {
322 7         19 $xml .= "/>"
323             } else {
324 39         66 $xml .= ">";
325 39         92 $xml .= _objectarray_to_xml($stuff->{content});
326 39         111 $xml .= "{element}.">";
327             }
328             }
329             }
330 60         134 return $xml;
331             }
332              
333             =head2 object_to_xml($object)
334              
335             Creates an XML string from the 'XML::MyXML::Object' object provided
336              
337             Optional flags: C, C, C, C, C
338              
339             =cut
340              
341             sub object_to_xml {
342 0     0 1 0 my $object = shift;
343 0   0     0 my $flags = shift || {};
344              
345 0         0 return $object->to_xml( $flags );
346             }
347              
348             sub _tidy_object {
349 19     19   28 my $object = shift;
350 19   100     39 my $tabs = shift || 0;
351 19   50     33 my $flags = shift || {};
352              
353 19 100       40 my $indentstring = exists $flags->{indentstring} ? $flags->{indentstring} : $DEFAULT_INDENTSTRING;
354              
355 19 100 66     41 if (! defined $object->{content} or ! @{$object->{content}}) { return; }
  9         25  
  10         20  
356 9         10 my $hastext;
357 9         11 my @children = @{$object->{content}};
  9         18  
358 9         24 foreach my $i (0..$#children) {
359 11         15 my $child = $children[$i];
360 11 100       27 if (defined $child->{value}) {
361 5 50       28 if ($child->{value} =~ /\S/) {
362 5         11 $hastext = 1;
363 5         12 last;
364             }
365             }
366             }
367 9 100       18 if ($hastext) { return; }
  5         11  
368              
369 4   33     6 @{$object->{content}} = grep { ! defined $_->{value} or $_->{value} !~ /\A\s*\z/ } @{$object->{content}};
  4         10  
  6         20  
  4         10  
370              
371 4         6 @children = @{$object->{content}};
  4         9  
372 4         9 $object->{content} = [];
373 4         8 for my $i (0..$#children) {
374 6         26 my $whitespace = bless ({ value => "\n".($indentstring x ($tabs+1)), parent => $object }, 'XML::MyXML::Object');
375 6         17 weaken( $whitespace->{parent} );
376 6         8 push @{$object->{content}}, $whitespace;
  6         11  
377 6         9 push @{$object->{content}}, $children[$i];
  6         28  
378             }
379 4         17 my $whitespace = bless ({ value => "\n".($indentstring x ($tabs)), parent => $object }, 'XML::MyXML::Object');
380 4         12 weaken( $whitespace->{parent} );
381 4         6 push @{$object->{content}}, $whitespace;
  4         8  
382              
383 4         7 for my $i (0..$#{$object->{content}}) {
  4         9  
384 16         45 _tidy_object($object->{content}[$i], $tabs+1, $flags);
385             }
386             }
387              
388              
389             =head2 simple_to_xml($simple_array_ref)
390              
391             Produces a raw XML string from either an array reference, a hash reference or a mixed structure such as these examples:
392              
393             { thing => { name => 'John', location => { city => 'New York', country => 'U.S.A.' } } }
394             # JohnU.S.A.New York
395              
396             [ thing => [ name => 'John', location => [ city => 'New York', country => 'U.S.A.' ] ] ]
397             # JohnU.S.A.New York
398              
399             { thing => { name => 'John', location => [ city => 'New York', city => 'Boston', country => 'U.S.A.' ] } }
400             # JohnNew YorkBostonU.S.A.
401              
402             Here's a mini-tutorial on how to use this function, in which you'll also see how to set attributes.
403              
404             The simplest invocations are these:
405              
406             simple_to_xml({target => undef})
407             #
408              
409             simple_to_xml({target => 123})
410             # 123
411              
412             Every set of sibling elements (such as the document itself, which is a single top-level element, or a pack of
413             5 elements all children to the same parent element) is represented in the $simple_array_ref parameter as
414             key-value pairs inside either a hashref or an arrayref (you can choose which).
415              
416             Keys represent tags+attributes of the sibling elements, whereas values represent the contents of those elements.
417              
418             Eg:
419              
420             [
421             first => 'John',
422             last => 'Doe,'
423             ]
424              
425             ...and...
426              
427             {
428             first => 'John',
429             last => 'Doe',
430             }
431              
432             both translate to:
433              
434             JohnDoe
435              
436             A value can either be undef (to denote an empty element), or a string (to denote a string), or another
437             hashref/arrayref to denote a set of children elements, like this:
438              
439             {
440             person => {
441             name => {
442             first => 'John',
443             last => 'Doe'
444             }
445             }
446             }
447              
448             ...becomes:
449              
450            
451            
452             John
453             Doe
454            
455            
456              
457              
458             The only difference between using an arrayref or using a hashref, is that arrayrefs preserve the
459             order of the elements, and allow repetition of identical tags. So a person with many addresses, should choose to
460             represent its list of addresses under an arrayref, like this:
461              
462             {
463             person => [
464             name => {
465             first => 'John',
466             last => 'Doe',
467             },
468             address => {
469             country => 'Malta',
470             },
471             address => {
472             country => 'Indonesia',
473             },
474             address => {
475             country => 'China',
476             }
477             ]
478             }
479              
480             ...which becomes:
481              
482            
483            
484             Doe
485             John
486            
487            
488             Malta
489            
490            
491             Indonesia
492            
493            
494             China
495            
496            
497              
498             Finally, to set attributes to your elements (eg id="12") you need to replace the key with either
499             a string containing attributes as well (eg: C<'address id="12"'>), or replace it with a reference, as the many
500             items in the examples below:
501              
502             {thing => [
503             'item id="1"' => 'chair',
504             [item => {id => 2}] => 'table',
505             [item => [id => 3]] => 'door',
506             [item => id => 4] => 'sofa',
507             {item => {id => 5}} => 'bed',
508             {item => [id => 6]} => 'shirt',
509             [item => {id => 7, other => 8}, [more => 9, also => 10, but_not => undef]] => 'towel'
510             ]}
511              
512             ...which becomes:
513              
514            
515             chair
516             table
517             door
518             sofa
519             bed
520             shirt
521             towel
522            
523              
524             As you see, attributes may be represented in a great variety of ways, so you don't need to remember
525             the "correct" one.
526              
527             Of course if the "simple structure" is a hashref, the key cannot be a reference (because hash keys are always
528             strings), so if you want attributes on your elements, you either need the enclosing structure to be an
529             arrayref as in the example above, to allow keys to be refs which contain the attributes, or you need to
530             represent the key (=tag+attrs) as a string, like this (also in the previous example): C<'item id="1"'>
531              
532             This concludes the mini-tutorial of the simple_to_xml function.
533              
534             All the strings in C<$simple_array_ref> need to contain characters, rather than bytes/octets. The C optional flag only affects the produced XML string.
535              
536             Optional flags: C, C, C, C, C, C
537              
538             =cut
539              
540             sub simple_to_xml {
541 21     21 1 6930 my $arref = shift;
542 21   100     89 my $flags = shift || {};
543              
544 21         40 my $xml = '';
545 21 100       97 my ($key, $value, @residue) = (ref $arref eq 'HASH') ? %$arref : @$arref;
546 21         51 $key = _key_to_string($key);
547 21 50       58 if (@residue) { croak "Error: the provided simple ref contains more than 1 top element"; }
  0         0  
548 21         118 my ($tag) = $key =~ /\A(\S+)/g;
549 21 50       55 croak encode_utf8("Error: Strange key: $key") if ! defined $tag;
550              
551 21 100       49 if (! ref $value) {
552 12 50 33     56 if ($key eq '!as_is') {
    50          
553 0         0 $xml .= $value;
554             } elsif (defined $value and length $value) {
555 12         30 $xml .= "<$key>"._encode($value)."";
556             } else {
557 0         0 $xml .= "<$key/>";
558             }
559             } else {
560 9         36 $xml .= "<$key>"._arrayref_to_xml($value, $flags)."";
561             }
562 21 0       63 if ($flags->{tidy}) { $xml = tidy_xml($xml, { exists $flags->{indentstring} ? (indentstring => $flags->{indentstring}) : () }); }
  0 50       0  
563 21 50       53 my $decl = $flags->{complete} ? ''."\n" : '';
564 21 50       48 $decl .= "{xslt}\"?>\n" if $flags->{xslt};
565 21         43 $xml = $decl . $xml;
566              
567 21 100       51 if (defined $flags->{save}) {
568 1 50       63 open my $fh, '>', $flags->{save} or croak "Error: Couldn't open file '$flags->{save}' for writing: $!";
569 1     1   13 binmode $fh, ':encoding(UTF-8)';
  1         2  
  1         9  
  1         51  
570 1         1372 print $fh $xml;
571 1         167 close $fh;
572             }
573              
574 21 100       51 $xml = encode_utf8($xml) if $flags->{bytes};
575 21         97 return $xml;
576             }
577              
578             sub _flatten {
579 127     127   181 my ($thing) = @_;
580              
581 127 100       221 if (!ref $thing) { return $thing; }
  90 100       217  
    50          
582 13         34 elsif (ref $thing eq 'HASH') { return map _flatten($_), %$thing; }
583 24         53 elsif (ref $thing eq 'ARRAY') { return map _flatten($_), @$thing; }
584 0         0 else { croak 'Error: reference of invalid type in simple_to_xml: '.(ref $thing); }
585             }
586              
587             sub _key_to_string {
588 37     37   69 my ($key) = @_;
589              
590 37 100       83 if (! ref $key) {
591 19         40 return $key;
592             } else {
593 18         33 my ($tag, %attrs) = _flatten($key);
594 18         52 return $tag . join('', map ' '.$_.'="'._encode($attrs{$_}).'"', grep {defined $attrs{$_}} keys %attrs);
  36         98  
595             }
596             }
597              
598             sub _arrayref_to_xml {
599 11     11   19 my $arref = shift;
600 11   50     28 my $flags = shift || {};
601              
602 11         19 my $xml = '';
603              
604 11 100       27 if (ref $arref eq 'HASH') { return _hashref_to_xml($arref, $flags); }
  4         13  
605              
606 7         20 foreach (my $i = 0; $i <= $#$arref; ) {
607 16         34 my $key = $arref->[$i++];
608 16         26 $key = _key_to_string($key);
609 16         65 my ($tag) = $key =~ /\A(\S+)/g;
610 16 50       40 croak encode_utf8("Error: Strange key: $key") if ! defined $tag;
611 16         30 my $value = $arref->[$i++];
612              
613 16 50       101 if ($key eq '!as_is') {
    100          
614 0 0       0 $xml .= $value if check_xml($value);
615             } elsif (! ref $value) {
616 14 50 33     60 if (defined $value and length $value) {
617 14         38 $xml .= "<$key>"._encode($value)."";
618             } else {
619 0         0 $xml .= "<$key/>";
620             }
621             } else {
622 2         10 $xml .= "<$key>"._arrayref_to_xml($value, $flags)."";
623             }
624             }
625 7         39 return $xml;
626             }
627              
628              
629             sub _hashref_to_xml {
630 4     4   8 my $hashref = shift;
631 4   50     9 my $flags = shift || {};
632              
633 4         10 my $xml = '';
634              
635 4         18 while (my ($key, $value) = each %$hashref) {
636 4         15 my ($tag) = $key =~ /\A(\S+)/g;
637 4 50       11 croak encode_utf8("Error: Strange key: $key") if ! defined $tag;
638              
639 4 50       16 if ($key eq '!as_is') {
    50          
640 0 0       0 $xml .= $value if check_xml($value);
641             } elsif (! ref $value) {
642 4 100 100     19 if (defined $value and length $value) {
643 2         11 $xml .= "<$key>"._encode($value)."";
644             } else {
645 2         10 $xml .= "<$key/>";
646             }
647             } else {
648 0         0 $xml .= "<$key>"._arrayref_to_xml($value, $flags)."";
649             }
650             }
651 4         17 return $xml;
652             }
653              
654             =head2 xml_to_simple($raw_xml)
655              
656             Produces a very simple hash object from the raw XML string provided. An example hash object created thusly is this:
657             S { name => 'John', location => { city => 'New York', country => 'U.S.A.' } } } >>>
658              
659             B This function only works on very simple XML strings, i.e. children of an element may not consist of both
660             text and elements (child elements will be discarded in that case). Also attributes in tags are ignored.
661              
662             Since the object created is a hashref (unless used with the C optional flag), duplicate keys will be
663             discarded.
664              
665             All strings contained in the output simple structure will always contain characters rather than octets/bytes,
666             regardless of the C optional flag.
667              
668             Optional flags: C, C, C, C, C, C
669              
670             =cut
671              
672             sub xml_to_simple {
673 7     7 1 2396 my $xml = shift;
674 7   100     39 my $flags = shift || {};
675              
676 7         26 my $object = xml_to_object($xml, $flags);
677              
678 7 50       50 my $return = defined $object ? $object->simplify($flags) : $object;
679              
680 7         54 return $return;
681             }
682              
683             sub _objectarray_to_simple {
684 62     62   77 my $object = shift;
685 62   50     115 my $flags = shift || {};
686              
687 62 50       103 if (! defined $object) { return undef; }
  0         0  
688              
689 62 50       96 if ($flags->{arrayref}) {
690 0         0 return _objectarray_to_simple_arrayref($object, $flags);
691             } else {
692 62         117 return _objectarray_to_simple_hashref($object, $flags);
693             }
694             }
695              
696             sub _objectarray_to_simple_hashref {
697 62     62   78 my $object = shift;
698 62   50     106 my $flags = shift || {};
699              
700 62 50       102 if (! defined $object) { return undef; }
  0         0  
701              
702 62         83 my $hashref = {};
703              
704 62         108 foreach my $stuff (@$object) {
705 113 100       229 if (defined $stuff->{element}) {
    50          
706 48         82 my $key = $stuff->{element};
707 48 100       84 if ($flags->{strip_ns}) { $key = _strip_ns($key); }
  1         5  
708 48         87 $hashref->{ $key } = _objectarray_to_simple($stuff->{content}, $flags);
709             } elsif (defined $stuff->{value}) {
710 65         97 my $value = $stuff->{value};
711 65 50       108 if ($flags->{strip}) { $value = _strip($value); }
  0         0  
712 65 100       283 return $value if $value =~ /\S/;
713             }
714             }
715              
716 35 50       81 if (keys %$hashref) {
717 35         99 return $hashref;
718             } else {
719 0         0 return undef;
720             }
721             }
722              
723             sub _objectarray_to_simple_arrayref {
724 0     0   0 my $object = shift;
725 0   0     0 my $flags = shift || {};
726              
727 0 0       0 if (! defined $object) { return undef; }
  0         0  
728              
729 0         0 my $arrayref = [];
730              
731 0         0 foreach my $stuff (@$object) {
732 0 0       0 if (defined $stuff->{element}) {
    0          
733 0         0 my $key = $stuff->{element};
734 0 0       0 if ($flags->{strip_ns}) { $key = _strip_ns($key); }
  0         0  
735 0         0 push @$arrayref, ( $key, _objectarray_to_simple($stuff->{content}, $flags) );
736             } elsif (defined $stuff->{value}) {
737 0         0 my $value = $stuff->{value};
738 0 0       0 if ($flags->{strip}) { $value = _strip($value); }
  0         0  
739 0 0       0 return $value if $value =~ /\S/;
740             }
741             }
742              
743 0 0       0 if (@$arrayref) {
744 0         0 return $arrayref;
745             } else {
746 0         0 return undef;
747             }
748             }
749              
750              
751             =head2 check_xml($raw_xml)
752              
753             Returns true if the $raw_xml string is valid XML (valid enough to be used by this module), and false otherwise.
754              
755             Optional flags: C, C
756              
757             =cut
758              
759             sub check_xml {
760 2     2 1 348 my $xml = shift;
761 2   50     11 my $flags = shift || {};
762              
763 2         5 my $obj = eval { xml_to_object($xml, $flags) };
  2         6  
764 2         133 return ! $@;
765             }
766              
767             1; # End of XML::MyXML
768              
769             __END__