File Coverage

blib/lib/XML/MyXML.pm
Criterion Covered Total %
statement 268 302 88.7
branch 107 170 62.9
condition 35 57 61.4
subroutine 25 27 92.5
pod 7 7 100.0
total 442 563 78.5


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