File Coverage

blib/lib/XML/MyXML.pm
Criterion Covered Total %
statement 269 303 88.7
branch 108 170 63.5
condition 35 57 61.4
subroutine 25 27 92.5
pod 7 7 100.0
total 444 564 78.7


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