File Coverage

blib/lib/XML/MyXML.pm
Criterion Covered Total %
statement 294 351 83.7
branch 107 178 60.1
condition 36 62 58.0
subroutine 26 28 92.8
pod 7 7 100.0
total 470 626 75.0


line stmt bran cond sub pod time code
1             package XML::MyXML;
2              
3 2     2   169992 use 5.008001;
  2         17  
4 2     2   13 use strict;
  2         3  
  2         41  
5 2     2   12 use warnings;
  2         3  
  2         59  
6              
7 2     2   877 use XML::MyXML::Object;
  2         6  
  2         72  
8              
9 2     2   12 use Encode;
  2         4  
  2         136  
10 2     2   13 use Carp;
  2         4  
  2         99  
11 2     2   11 use Scalar::Util qw/ weaken /;
  2         5  
  2         9427  
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.06";
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   139 my $string = shift;
88 91   50     255 my $entities = shift || {};
89 91 50       168 defined $string or $string = '';
90 91         288 my %replace = (
91             '<' => '<',
92             '>' => '>',
93             '&' => '&',
94             '\'' => ''',
95             '"' => '"',
96             );
97 91         316 my $keys = "(".join("|", sort {length($b) <=> length($a)} keys %replace).")";
  728         1063  
98 91         2042 $string =~ s/$keys/$replace{$1}/g;
99 91         553 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         4 return _encode($string);
112             }
113              
114             sub _decode {
115 130     130   206 my $string = shift;
116 130   100     289 my $entities = shift || {};
117 130   50     369 my $flags = shift || {};
118 130 50       244 defined $string or $string = '';
119 130         567 my %replace = ( %$entities, reverse(
120             '<' => '<',
121             '>' => '>',
122             '&' => '&',
123             '\'' => ''',
124             '"' => '"',
125             ));
126 130         648 my @capture = map "\Q$_\E", keys %replace;
127 130         279 push @capture, '&#x[0-9A-Fa-f]+;', '&#[0-9]+;';
128 130         380 my $capture = "(".join("|", @capture).")";
129 130         5265 my @captured = $string =~ /$capture/g;
130 130 100       996 @captured or return $string;
131 6         11 my %conv;
132 6         13 foreach my $e (@captured) {
133 6 50       18 if (exists $conv{$e}) { next; }
  0         0  
134 6 100       19 if (exists $replace{$e}) {
    50          
    0          
135 5         15 $conv{$e} = $replace{$e};
136             } elsif ($e =~ /\A&#x([0-9a-fA-F]+);\z/) {
137 1         8 $conv{$e} = chr(hex($1));
138             } elsif ($e =~ /\A&#([0-9]+);\z/) {
139 0         0 $conv{$e} = chr($1);
140             }
141             }
142 6         34 my $keys = "(".join("|", map "\Q$_\E", keys %conv).")";
143 6         78 $string =~ s/$keys/$conv{$1}/g;
144 6         65 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   2 my $string = shift;
156              
157             # NOTE: Replace this with the 'r' flag of the substitution operator
158 1 50       10 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     13 my $flags = shift || {};
173              
174 3         8 my $object = xml_to_object($xml, $flags);
175 3 50       10 defined $object or return $object;
176 3         13 _tidy_object($object, undef, $flags);
177 3         23 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 3752 my $xml = shift;
192 40   100     122 my $flags = shift || {};
193              
194 40 100       103 if ($flags->{file}) {
195 1 50       37 open my $fh, '<', $xml or croak "Error: The file '$xml' could not be opened for reading: $!";
196 1         20 $xml = join '', <$fh>;
197 1         12 close $fh;
198             }
199              
200 40 100 100     137 if ($flags->{bytes} or $flags->{file}) {
201 5         24 my (undef, undef, $encoding) = $xml =~ /<\?xml(\s[^>]+)?\sencoding=(['"])(.*?)\2/g;
202 5 50       15 $encoding = 'UTF-8' if ! defined $encoding;
203 5 50       33 if ($encoding =~ /\Autf-?8\z/i) { $encoding = 'UTF-8'; }
  5         10  
204 5         9 eval {
205 5         20 $xml = decode($encoding, $xml, Encode::FB_CROAK);
206             };
207 5 100       593 ! $@ or croak 'Error: Input string is invalid UTF-8';
208             }
209              
210 39         64 my $entities = {};
211              
212             # Parse CDATA sections
213 39         108 $xml =~ s/<\!\[CDATA\[(.*?)\]\]>/_encode($1)/egs;
  0         0  
214 39         481 my @els = $xml =~ /(|$)|<[^>]*?>|[^<>]+)/sg;
215             # Remove comments, special markup and initial whitespace
216             {
217 39         69 my $init_ws = 1;
  39         50  
218 39         75 foreach my $el (@els) {
219 304 50       1033 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         45 $entities->{"&$entname;"} = _decode($entvalue);
227 2         7 undef $el;
228             } elsif ($el =~ / or or
229 0         0 undef $el;
230             } elsif ($init_ws) {
231 42 100       131 if ($el =~ /\S/) {
232 39         74 $init_ws = 0;
233             } else {
234 3         42 undef $el;
235             }
236             }
237             }
238 39         76 @els = grep { defined $_ } @els;
  304         551  
239 39 50       98 if (! @els) { croak "Error: No elements in XML document"; }
  0         0  
240             }
241 39         56 my @stack;
242 39         116 my $object = bless ({ content => [] }, 'XML::MyXML::Object');
243 39         57 my $pointer = $object;
244 39         62 foreach my $el (@els) {
245 299 50       1540 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         325 my ($element) = $el =~ /\A<\/(\S+)>\z/g;
249 97 50       237 if (! length($element)) { croak encode_utf8("Error: Strange element: '$el'"); }
  0         0  
250 97 50       229 if ($stack[-1]{element} ne $element) { croak encode_utf8("Error: Incompatible stack element: stack='".$stack[-1]{element}."' element='$el'"); }
  0         0  
251 97         148 my $stackentry = pop @stack;
252 97 100       129 if ($#{$stackentry->{content}} == -1) {
  97         213  
253 1         3 delete $stackentry->{content};
254             }
255 97         219 $pointer = $stackentry->{parent};
256             } elsif ($el =~ /\A<[^>]+\/>\z/) {
257 7         32 my ($element) = $el =~ /\A<([^\s>\/]+)/g;
258 7 50       22 if (! length($element)) { croak encode_utf8("Error: Strange element: '$el'"); }
  0         0  
259 7         74 $el =~ s/\A<\Q$element\E//;
260 7         28 $el =~ s/\/>\z//;
261 7         18 my @attrs = $el =~ /\s+(\S+=(['"]).*?\2)/g;
262 7         11 my $i = 1;
263 7         13 @attrs = grep {$i++ % 2} @attrs;
  0         0  
264 7         9 my %attr;
265 7         15 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         21 my $entry = { element => $element, attrs => \%attr, parent => $pointer };
271 7         25 weaken( $entry->{parent} );
272 7         9 bless $entry, 'XML::MyXML::Object';
273 7         21 push @{$pointer->{content}}, $entry;
  7         24  
274             } elsif ($el =~ /\A<[^\s>\/][^>]*>\z/) {
275 98         338 my ($element) = $el =~ /\A<([^\s>]+)/g;
276 98 50       256 if (! length($element)) { croak encode_utf8("Error: Strange element: '$el'"); }
  0         0  
277 98         1198 $el =~ s/\A<\Q$element\E//;
278 98         373 $el =~ s/>\z//;
279 98         282 my @attrs = $el =~ /\s+(\S+=(['"]).*?\2)/g;
280 98         153 my $i = 1;
281 98         152 @attrs = grep {$i++ % 2} @attrs;
  60         112  
282 98         130 my %attr;
283 98         166 foreach my $attr (@attrs) {
284 30         156 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         58 $attr{$name} = _decode($value, $entities);
287             }
288 98         358 my $entry = { element => $element, attrs => \%attr, content => [], parent => $pointer };
289 98         325 weaken( $entry->{parent} );
290 98         145 bless $entry, 'XML::MyXML::Object';
291 98         152 push @stack, $entry;
292 98         122 push @{$pointer->{content}}, $entry;
  98         189  
293 98         243 $pointer = $entry;
294             } elsif ($el =~ /\A[^<>]*\z/) {
295 97         222 my $entry = { value => _decode($el, $entities), parent => $pointer };
296 97         342 weaken( $entry->{parent} );
297 97         191 bless $entry, 'XML::MyXML::Object';
298 97         132 push @{$pointer->{content}}, $entry;
  97         269  
299             } else {
300 0         0 croak encode_utf8("Error: Strange element: '$el'");
301             }
302             }
303 39 100       83 if (@stack) { croak encode_utf8("Error: The <$stack[-1]{element}> element has not been closed in XML"); }
  1         7  
304 38         66 $object = $object->{content}[0];
305 38         64 $object->{parent} = undef;
306 38         236 return $object;
307             }
308              
309             sub _objectarray_to_xml {
310 60     60   78 my $object = shift;
311              
312 60         112 my $xml = '';
313 60         84 foreach my $stuff (@$object) {
314 78 100 66     216 if (! defined $stuff->{element} and defined $stuff->{value}) {
315 32         58 $xml .= _encode($stuff->{value});
316             } else {
317 46         106 $xml .= "<".$stuff->{element};
318 46         54 foreach my $attrname (keys %{$stuff->{attrs}}) {
  46         111  
319 1         5 $xml .= " ".$attrname.'="'._encode($stuff->{attrs}{$attrname}).'"';
320             }
321 46 100 100     108 if (! defined $stuff->{content} or ! @{ $stuff->{content} }) {
322 7         14 $xml .= "/>"
323             } else {
324 39         70 $xml .= ">";
325 39         82 $xml .= _objectarray_to_xml($stuff->{content});
326 39         107 $xml .= "{element}.">";
327             }
328             }
329             }
330 60         133 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   30 my $object = shift;
350 19   100     39 my $tabs = shift || 0;
351 19   50     34 my $flags = shift || {};
352              
353 19 100       39 my $indentstring = exists $flags->{indentstring} ? $flags->{indentstring} : $DEFAULT_INDENTSTRING;
354              
355 19 100 66     40 if (! defined $object->{content} or ! @{$object->{content}}) { return; }
  9         26  
  10         23  
356 9         14 my $hastext;
357 9         10 my @children = @{$object->{content}};
  9         21  
358 9         23 foreach my $i (0..$#children) {
359 11         50 my $child = $children[$i];
360 11 100       29 if (defined $child->{value}) {
361 5 50       21 if ($child->{value} =~ /\S/) {
362 5         8 $hastext = 1;
363 5         9 last;
364             }
365             }
366             }
367 9 100       16 if ($hastext) { return; }
  5         11  
368              
369 4   33     7 @{$object->{content}} = grep { ! defined $_->{value} or $_->{value} !~ /\A\s*\z/ } @{$object->{content}};
  4         9  
  6         20  
  4         9  
370              
371 4         7 @children = @{$object->{content}};
  4         7  
372 4         9 $object->{content} = [];
373 4         10 for my $i (0..$#children) {
374 6         58 my $whitespace = bless ({ value => "\n".($indentstring x ($tabs+1)), parent => $object }, 'XML::MyXML::Object');
375 6         21 weaken( $whitespace->{parent} );
376 6         7 push @{$object->{content}}, $whitespace;
  6         13  
377 6         8 push @{$object->{content}}, $children[$i];
  6         14  
378             }
379 4         24 my $whitespace = bless ({ value => "\n".($indentstring x ($tabs)), parent => $object }, 'XML::MyXML::Object');
380 4         13 weaken( $whitespace->{parent} );
381 4         6 push @{$object->{content}}, $whitespace;
  4         7  
382              
383 4         6 for my $i (0..$#{$object->{content}}) {
  4         13  
384 16         43 _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 6402 my $arref = shift;
542 21   100     89 my $flags = shift || {};
543              
544 21         37 my $xml = '';
545 21 100       87 my ($key, $value, @residue) = (ref $arref eq 'HASH') ? %$arref : @$arref;
546 21         51 $key = _key_to_string($key);
547 21 50       59 if (@residue) { croak "Error: the provided simple ref contains more than 1 top element"; }
  0         0  
548 21         112 my ($tag) = $key =~ /\A(\S+)/g;
549 21 50       53 croak encode_utf8("Error: Strange key: $key") if ! defined $tag;
550              
551 21 100       46 if (! ref $value) {
552 12 50 33     45 if (defined $value and length $value) {
553 12         30 $xml .= "<$key>"._encode($value)."";
554             } else {
555 0         0 $xml .= "<$key/>";
556             }
557             } else {
558 9         28 $xml .= "<$key>"._arrayref_to_xml($value, $flags)."";
559             }
560 21 0       56 if ($flags->{tidy}) { $xml = tidy_xml($xml, { exists $flags->{indentstring} ? (indentstring => $flags->{indentstring}) : () }); }
  0 50       0  
561 21 50       50 my $decl = $flags->{complete} ? ''."\n" : '';
562 21 50       48 $decl .= "{xslt}\"?>\n" if $flags->{xslt};
563 21         40 $xml = $decl . $xml;
564              
565 21 100       50 if (defined $flags->{save}) {
566 1 50       62 open my $fh, '>', $flags->{save} or croak "Error: Couldn't open file '$flags->{save}' for writing: $!";
567 1     1   10 binmode $fh, ':encoding(UTF-8)';
  1         4  
  1         8  
  1         38  
568 1         1301 print $fh $xml;
569 1         152 close $fh;
570             }
571              
572 21 100       47 $xml = encode_utf8($xml) if $flags->{bytes};
573 21         74 return $xml;
574             }
575              
576             sub _flatten {
577 127     127   177 my ($thing) = @_;
578              
579 127 100       210 if (!ref $thing) { return $thing; }
  90 100       228  
    50          
580 13         31 elsif (ref $thing eq 'HASH') { return map _flatten($_), %$thing; }
581 24         56 elsif (ref $thing eq 'ARRAY') { return map _flatten($_), @$thing; }
582 0         0 else { croak 'Error: reference of invalid type in simple_to_xml: '.(ref $thing); }
583             }
584              
585             sub _key_to_string {
586 37     37   68 my ($key) = @_;
587              
588 37 100       70 if (! ref $key) {
589 19         41 return $key;
590             } else {
591 18         32 my ($tag, %attrs) = _flatten($key);
592 18         50 return $tag . join('', map ' '.$_.'="'._encode($attrs{$_}).'"', grep {defined $attrs{$_}} keys %attrs);
  36         94  
593             }
594             }
595              
596             sub _arrayref_to_xml {
597 11     11   20 my $arref = shift;
598 11   50     25 my $flags = shift || {};
599              
600 11         21 my $xml = '';
601              
602 11 100       29 if (ref $arref eq 'HASH') { return _hashref_to_xml($arref, $flags); }
  4         13  
603              
604 7         18 foreach (my $i = 0; $i <= $#$arref; ) {
605 16         32 my $key = $arref->[$i++];
606 16         28 $key = _key_to_string($key);
607 16         63 my ($tag) = $key =~ /\A(\S+)/g;
608 16 50       43 croak encode_utf8("Error: Strange key: $key") if ! defined $tag;
609 16         27 my $value = $arref->[$i++];
610              
611 16 50       43 if ($key eq '!as_is') {
    100          
612 0 0       0 $xml .= $value if check_xml($value);
613             } elsif (! ref $value) {
614 14 50 33     55 if (defined $value and length $value) {
615 14         31 $xml .= "<$key>"._encode($value)."";
616             } else {
617 0         0 $xml .= "<$key/>";
618             }
619             } else {
620 2         9 $xml .= "<$key>"._arrayref_to_xml($value, $flags)."";
621             }
622             }
623 7         32 return $xml;
624             }
625              
626              
627             sub _hashref_to_xml {
628 4     4   7 my $hashref = shift;
629 4   50     10 my $flags = shift || {};
630              
631 4         7 my $xml = '';
632              
633 4         19 while (my ($key, $value) = each %$hashref) {
634 4         13 my ($tag) = $key =~ /\A(\S+)/g;
635 4 50       11 croak encode_utf8("Error: Strange key: $key") if ! defined $tag;
636              
637 4 50       12 if ($key eq '!as_is') {
    50          
638 0 0       0 $xml .= $value if check_xml($value);
639             } elsif (! ref $value) {
640 4 100 100     19 if (defined $value and length $value) {
641 2         10 $xml .= "<$key>"._encode($value)."";
642             } else {
643 2         10 $xml .= "<$key/>";
644             }
645             } else {
646 0         0 $xml .= "<$key>"._arrayref_to_xml($value, $flags)."";
647             }
648             }
649 4         16 return $xml;
650             }
651              
652             =head2 xml_to_simple($raw_xml)
653              
654             Produces a very simple hash object from the raw XML string provided. An example hash object created thusly is this:
655             S { name => 'John', location => { city => 'New York', country => 'U.S.A.' } } } >>>
656              
657             B This function only works on very simple XML strings, i.e. children of an element may not consist of both
658             text and elements (child elements will be discarded in that case). Also attributes in tags are ignored.
659              
660             Since the object created is a hashref (unless used with the C optional flag), duplicate keys will be
661             discarded.
662              
663             All strings contained in the output simple structure will always contain characters rather than octets/bytes,
664             regardless of the C optional flag.
665              
666             Optional flags: C, C, C, C, C, C
667              
668             =cut
669              
670             sub xml_to_simple {
671 7     7 1 2586 my $xml = shift;
672 7   100     34 my $flags = shift || {};
673              
674 7         19 my $object = xml_to_object($xml, $flags);
675              
676 7 50       36 my $return = defined $object ? $object->simplify($flags) : $object;
677              
678 7         80 return $return;
679             }
680              
681             sub _objectarray_to_simple {
682 62     62   78 my $object = shift;
683 62   50     104 my $flags = shift || {};
684              
685 62 50       97 if (! defined $object) { return undef; }
  0         0  
686              
687 62 50       100 if ($flags->{arrayref}) {
688 0         0 return _objectarray_to_simple_arrayref($object, $flags);
689             } else {
690 62         104 return _objectarray_to_simple_hashref($object, $flags);
691             }
692             }
693              
694             sub _objectarray_to_simple_hashref {
695 62     62   70 my $object = shift;
696 62   50     97 my $flags = shift || {};
697              
698 62 50       93 if (! defined $object) { return undef; }
  0         0  
699              
700 62         84 my $hashref = {};
701              
702 62         90 foreach my $stuff (@$object) {
703 113 100       235 if (defined $stuff->{element}) {
    50          
704 48         66 my $key = $stuff->{element};
705 48 100       84 if ($flags->{strip_ns}) { $key = _strip_ns($key); }
  1         4  
706 48         75 $hashref->{ $key } = _objectarray_to_simple($stuff->{content}, $flags);
707             } elsif (defined $stuff->{value}) {
708 65         92 my $value = $stuff->{value};
709 65 50       116 if ($flags->{strip}) { $value = _strip($value); }
  0         0  
710 65 100       235 return $value if $value =~ /\S/;
711             }
712             }
713              
714 35 50       83 if (keys %$hashref) {
715 35         90 return $hashref;
716             } else {
717 0         0 return undef;
718             }
719             }
720              
721             sub _objectarray_to_simple_arrayref {
722 0     0   0 my $object = shift;
723 0   0     0 my $flags = shift || {};
724              
725 0 0       0 if (! defined $object) { return undef; }
  0         0  
726              
727 0         0 my $arrayref = [];
728              
729 0         0 foreach my $stuff (@$object) {
730 0 0       0 if (defined $stuff->{element}) {
    0          
731 0         0 my $key = $stuff->{element};
732 0 0       0 if ($flags->{strip_ns}) { $key = _strip_ns($key); }
  0         0  
733 0         0 push @$arrayref, ( $key, _objectarray_to_simple($stuff->{content}, $flags) );
734             } elsif (defined $stuff->{value}) {
735 0         0 my $value = $stuff->{value};
736 0 0       0 if ($flags->{strip}) { $value = _strip($value); }
  0         0  
737 0 0       0 return $value if $value =~ /\S/;
738             }
739             }
740              
741 0 0       0 if (@$arrayref) {
742 0         0 return $arrayref;
743             } else {
744 0         0 return undef;
745             }
746             }
747              
748              
749             =head2 check_xml($raw_xml)
750              
751             Returns true if the $raw_xml string is valid XML (valid enough to be used by this module), and false otherwise.
752              
753             Optional flags: C, C
754              
755             =cut
756              
757             sub check_xml {
758 2     2 1 338 my $xml = shift;
759 2   50     12 my $flags = shift || {};
760              
761 2         4 my $obj = eval { xml_to_object($xml, $flags) };
  2         5  
762 2         132 return ! $@;
763             }
764              
765             1; # End of XML::MyXML
766              
767             __END__