File Coverage

blib/lib/XML/Parser/Wrapper.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             # -*-perl-*-
2             # Creation date: 2005-04-23 22:39:14
3             # Authors: Don
4             # $Revision: 1599 $
5             #
6             # Copyright (c) 2005-2010 Don Owens
7             #
8             # All rights reserved. This program is free software; you can
9             # redistribute it and/or modify it under the same terms as Perl
10             # itself.
11              
12             =pod
13              
14             =head1 NAME
15              
16             XML::Parser::Wrapper - A simple object wrapper around XML::Parser
17              
18             =cut
19              
20 4     4   70857 use strict;
  4         11  
  4         288  
21 4     4   16322 use XML::Parser ();
  0            
  0            
22              
23             use XML::Parser::Wrapper::SAXHandler;
24              
25             { package XML::Parser::Wrapper;
26              
27             use vars qw($VERSION);
28            
29             $VERSION = '0.15';
30              
31             my %i_data;
32              
33             =pod
34              
35             =head1 VERSION
36              
37             0.14
38              
39             =cut
40              
41             =head1 SYNOPSIS
42              
43             use XML::Parser::Wrapper;
44            
45             my $xml = qq{Hello World!};
46             my $root = XML::Parser::Wrapper->new($xml);
47            
48             my $root2 = XML::Parser::Wrapper->new({ file => '/tmp/test.xml' });
49            
50             my $parser = XML::Parser::Wrapper->new;
51             my $root3 = $parser->parse({ file => '/tmp/test.xml' });
52            
53             my $root4 = XML::Parser::Wrapper->new_sax_parser({ class => 'XML::LibXML::SAX',
54             handler => $handler,
55             start_tag => 'stuff',
56             # start_depth => 2,
57             }, $xml);
58            
59             my $root_tag_name = $root->name;
60             my $roots_children = $root->elements;
61            
62             foreach my $element (@$roots_children) {
63             if ($element->name eq 'head') {
64             my $id = $element->attr('id');
65             my $hello_world_text = $element->text; # eq "Hello World!"
66             }
67             }
68            
69             my $head_element = $root->first_element('head2');
70             my $head_elements = $root->elements('head2');
71             my $test = $root->element('head2')->first_element('test_tag');
72            
73             my $root = XML::Parser::Wrapper->new_doc('root_tag', { root => 'attr' });
74            
75             my $new_element = $root->add_kid('test4', { attr1 => 'val1' });
76            
77             my $kid = $root->update_kid('root_child', { attr2 => 'stuff2' }, 'blah');
78             $kid->update_node({ new_attr => 'new_stuff' });
79            
80             $new_element->add_kid('child', { myattr => 'stuff' }, 'bleh');
81            
82             my $another_element = $root->new_element('foo', { bar => '1' }, 'test');
83             $root->add_kid($another_element);
84            
85             my $new_xml = $root->to_xml;
86              
87             my $doctype_info = $root->get_doctype;
88              
89             my $xml_decl_info = $root->get_xml_decl;
90              
91             =head1 DESCRIPTION
92              
93             XML::Parser::Wrapper provides a simple object around XML::Parser
94             to make it more convenient to deal with the parse tree returned
95             by XML::Parser.
96              
97             For a list of changes in recent versions, see the documentation
98             for L.
99              
100              
101             =head1 METHODS
102              
103             =head2 C, C, C $filename })>
104              
105             Calls XML::Parser to parse the given XML and returns a new
106             XML::Parser::Wrapper object using the parse tree output from
107             XML::Parser.
108              
109             If no parameters are passed, a reusable object is returned
110             -- see the parse() method.
111              
112             =cut
113              
114             # Takes the 'Tree' style output from XML::Parser and wraps in in objects.
115             # A parse tree looks like the following:
116             #
117             # [foo, [{}, head, [{id => "a"}, 0, "Hello ", em, [{}, 0, "there"]],
118             # bar, [ {}, 0, "Howdy", ref, [{}]],
119             # 0, "do"
120             # ]
121             # ]
122             sub new {
123             my $proto = shift;
124             my $self = $proto->_new;
125              
126             unless (scalar(@_) >= 1) {
127             return $self;
128             }
129              
130             return $self->parse(@_);
131             }
132              
133             # adapted from refaddr in Scalar::Util
134             sub refaddr {
135             my $obj = shift;
136             my $pkg = ref($obj) or return undef;
137            
138             bless $obj, 'XML::Parser::Wrapper::Fake';
139            
140             my $i = int($obj);
141            
142             bless $obj, $pkg;
143            
144             return $i . '';
145             }
146              
147             sub _doctype_handler {
148             my ($self, $orig_handler, $expat, $name, $sysid, $pubid, $internal) = @_;
149              
150             $self->{_doctype} = { name => $name, sysid => $sysid,
151             pubid => $pubid, internal => $internal,
152             };
153              
154             return 0 unless defined $orig_handler;
155             return $orig_handler->($expat, $name, $sysid, $pubid, $internal);
156             }
157              
158             sub _xml_decl_handler {
159             my ($self, $orig_handler, $expat, $version, $encoding, $standalone) = @_;
160              
161             $self->{_xml_decl} = { version => $version, encoding => $encoding,
162             standalone => $standalone,
163             };
164            
165             return 0 unless defined $orig_handler;
166             return $orig_handler->($orig_handler, $expat, $version, $encoding, $standalone);
167             }
168            
169             sub _new {
170             my $class = shift;
171             my $parser = XML::Parser->new(Style => 'Tree');
172              
173             my $self = bless { parser => $parser }, ref($class) || $class;
174              
175             # FIXME: use $parser->setHandlers() here to set handlers for doctype, etc.
176             # use the return values to get reference to handlers to call after
177             # so that the Tree style works properly (e.g., knows about any declared entities)
178             # Doctype: (Expat, Name, Sysid, Pubid, Internal)
179             # XMLDecl: (Expat, Version, Encoding, Standalone)
180              
181             my $orig_doctype_handler;
182             my $orig_xml_decl_handler;
183            
184             my $dt_h = sub { $self->_doctype_handler($orig_xml_decl_handler, @_) };
185             my $xd_h = sub { $self->_xml_decl_handler($orig_xml_decl_handler, @_) };
186             my %old_handlers = $parser->setHandlers(Doctype => $dt_h,
187             XMLDecl => $xd_h,
188             );
189              
190             $orig_doctype_handler = $old_handlers{Doctype};
191             $orig_xml_decl_handler = $old_handlers{XMLDecl};
192            
193             # use Data::Dumper;
194             # print STDERR Data::Dumper->Dump([ \%old_handlers ], [ 'old_handlers' ]) . "\n\n";
195             # exit 0;
196              
197             return $self;
198             }
199              
200             =pod
201              
202             =head2 C, C, C $filename })>
203              
204             Experimental support for SAX parsers based on XML::SAX::Base. Valid parameters are
205              
206             =head3 class
207              
208             SAX parser class (defaults to XML::LibXML::SAX)
209              
210             =head3 start_tag
211              
212             SAX tag name starting the section you are looking for if stream parsing.
213              
214             =head3 handler
215              
216             Handler function to call when stream parsing.
217              
218             =head3 start_depth
219              
220             Use this option for picking up sections that occur inside another
221             section with the same tag name. E.g., if you want to get the
222             inside "foo" section in this example:
223              
224             =for pod2rst next-code-block: xml
225              
226             here
227              
228             instead of the one at the top level, set start_depth to 2. This
229             is the number of times your start_tag occurs in the hierarchy
230             before you get to the section you want (not the tag depth).
231              
232             =cut
233             sub new_sax_parser {
234             my $class = shift;
235             my $parse_spec = shift || { };
236              
237             my $parser_class = $parse_spec->{class} || 'XML::LibXML::SAX';
238             my $start_tag = $parse_spec->{start_tag};
239             my $user_cb = $parse_spec->{handler};
240             my $start_depth = $parse_spec->{start_depth};
241              
242             my $self = bless { parser_class => $parser_class, handler => $user_cb,
243             start_tag => $start_tag,
244             },
245             ref($class) || $class;
246              
247             eval "require $parser_class;";
248              
249             my $sax_handler = XML::Parser::Wrapper::SAXHandler->new({ start_tag => $start_tag,
250             handler => $user_cb,
251             start_depth => $start_depth,
252             });
253             $self->{parser} = $parser_class->new({ Handler => $sax_handler,
254             # DeclHandler => $sax_handler,
255             });
256              
257             # DTDHandler => $sax_handler,
258              
259             $self->{sax_handler} = $sax_handler;
260              
261             unless (scalar(@_) >= 1) {
262             return $self;
263             }
264              
265             return $self->parse(@_);
266             }
267              
268             =pod
269              
270             =head2 C, C $filename })>
271              
272             Parses the given XML and returns a new XML::Parser::Wrapper
273             object using the parse tree output from XML::Parser.
274              
275             =cut
276             sub parse {
277             my $self = shift;
278             my $arg = shift;
279              
280             my $parser = $self->{parser};
281              
282             my $tree = [];
283             if (ref($arg) eq 'HASH') {
284             if (exists($arg->{file})) {
285             if ($self->{sax_handler}) {
286             if (UNIVERSAL::isa($arg->{file}, 'GLOB') and *{$arg->{file}}{IO}) {
287             $self->{parser}->parse(Source => { ByteStream => $arg->{file} });
288             }
289             else {
290             $self->{parser}->parse(Source => { SystemId => $arg->{file} });
291             }
292            
293             $tree = $self->{sax_handler}->get_tree;
294             }
295             else {
296             $tree = $parser->parsefile($arg->{file});
297             }
298             }
299             } else {
300             if ($self->{sax_handler}) {
301             if (UNIVERSAL::isa($arg, 'GLOB')) {
302             $self->{parser}->parse(Source => { ByteStream => $arg });
303             }
304             else {
305             $self->{parser}->parse(Source => { String => $arg });
306             }
307             $tree = $self->{sax_handler}->get_tree;
308             }
309             else {
310             $tree = $parser->parse($arg);
311             }
312             }
313              
314             return undef unless defined($tree) and ref($tree);
315            
316             my $obj = bless $tree, ref($self);
317              
318             my $k = refaddr($obj);
319             $i_data{$k} = { doctype => $self->{_doctype}, xmldecl => $self->{_xml_decl} };
320            
321             return $obj;
322             }
323              
324             =pod
325              
326             =head2 C
327              
328             Returns information about the XML declaration at the beginning of
329             the document. E.g., for the declaration
330              
331             =for pod2rst next-code-block: xml
332              
333            
334              
335             The return value is
336              
337             {
338             'version' => '1.0',
339             'standalone' => undef,
340             'encoding' => 'utf-8'
341             }
342              
343              
344             B This does not work for the SAX parser interface.
345              
346              
347             =cut
348             sub get_xml_decl {
349             my ($self) = @_;
350              
351             my $k = refaddr($self);
352             my $data = $i_data{$k};
353              
354             if ($data) {
355             return $data->{xmldecl};
356             }
357             return undef;
358             }
359              
360             =pod
361              
362             =head2 C
363              
364             Returns information about the doctype declaration. E.g., for the declaration
365              
366             =for pod2rst next-code-block: xml
367              
368            
369              
370             The return value is
371              
372             {
373             'pubid' => undef,
374             'sysid' => 'hello.dtd',
375             'name' => 'greeting',
376             'internal' => ''
377             }
378              
379             B This does not work for the SAX parser interface.
380              
381              
382             =cut
383             sub get_doctype {
384             my ($self) = @_;
385              
386             my $k = refaddr($self);
387             my $data = $i_data{$k};
388              
389             if ($data) {
390             return $data->{doctype};
391             }
392             return undef;
393             }
394              
395             sub _new_element {
396             my $proto = shift;
397             my $tree = shift || [];
398              
399             return bless $tree, ref($proto) || $proto;
400             }
401              
402             =pod
403              
404             =head2 C
405              
406             Returns the name of the element represented by this object.
407              
408             Aliases: tag(), getName(), getTag()
409              
410             =cut
411             sub tag {
412             my $tag = shift()->[0];
413             return '' if $tag eq '0';
414             return $tag;
415             }
416             *name = \&tag;
417             *getTag = \&tag;
418             *getName = \&tag;
419              
420             =pod
421              
422             =head2 C
423              
424             Returns a true value if this element is a text element, false
425             otherwise.
426              
427             Aliases: isText()
428              
429             =cut
430             sub is_text {
431             my $self = shift;
432             if (@$self and defined($self->[0])) {
433             return $self->[0] eq '0';
434             }
435             return;
436              
437             # return $self->[0] eq '0';
438             }
439             *isText = \&is_text;
440              
441             =pod
442              
443             =head2 C
444              
445             If this element is a text element, the text is returned.
446             Otherwise, return the text from the first child text element, or
447             undef if there is not one.
448              
449             Aliases: content(), getText(), getContent()
450              
451             =cut
452             sub text {
453             my $self = shift;
454             if ($self->is_text) {
455             return $self->[1];
456             } else {
457             my $kids = $self->kids;
458             foreach my $kid (@$kids) {
459             return $kid->text if $kid->is_text;
460             }
461             return undef;
462             }
463             }
464             *content = \&text;
465             *contents = \&text;
466             *getText = \&text;
467             *getContent = \&text;
468             *getContents = \&text;
469              
470             =pod
471              
472             =head2 C
473              
474             Like text(), except HTML-escape the text (escape &, <, >, and ")
475             before returning it.
476              
477             Aliases: content_html(), getContentHtml()
478              
479             =cut
480             sub html {
481             my $self = shift;
482              
483             return $self->escape_html($self->text);
484             }
485             *content_html = \&html;
486             *getContentHtml = \&html;
487              
488             =pod
489              
490             =head2 C
491              
492             Like text(), except XML-escape the text (escape &, <, >, and ")
493             before returning it.
494              
495             Aliases: content_xml(), getContentXml()
496              
497             =cut
498             sub xml {
499             my $self = shift;
500              
501             return $self->escape_xml_attr($self->text);
502             }
503             *content_xml = \&html;
504             *getContentXml = \&html;
505              
506             =pod
507              
508             =head2 C
509              
510             Converts the node back to XML. The ordering of attributes may
511             not be the same as in the original XML, and CDATA sections may
512             become plain text elements, or vice versa. This assumes the data
513             is encoded in utf-8.
514              
515             Valid options
516              
517             =head3 pretty
518              
519             If pretty is a true value, then whitespace is added to the output
520             to make it more human-readable.
521              
522             =head3 cdata
523              
524             If cdata is defined, any text nodes with length greater than
525             cdata are output as a CDATA section, unless it contains "]]>", in
526             which case the text is XML escaped.
527              
528             Aliases: toXml()
529              
530             =head3 decl
531              
532             If a true value, output an XML declaration before outputing the
533             converted document, i.e.,
534              
535             =for pod2rst next-code-block: xml
536              
537            
538              
539             =cut
540             sub to_xml {
541             my ($self, $options) = @_;
542              
543             unless ($options and ref($options) and UNIVERSAL::isa($options, 'HASH')) {
544             $options = { };
545             }
546              
547             if ($options->{decl}) {
548             my $xml = qq{};
549             if ($options->{pretty}) {
550             $xml .= "\n";
551             }
552            
553             $xml .= $self->_to_xml(0, $options, 0);
554              
555             return $xml;
556             }
557            
558             return $self->_to_xml(0, $options, 0);
559             }
560            
561             sub _to_xml {
562             my ($self, $level, $options, $index) = @_;
563              
564             unless ($options and ref($options) and UNIVERSAL::isa($options, 'HASH')) {
565             $options = { };
566             }
567              
568             if ($self->is_text) {
569             my $text = $self->text;
570              
571             if (defined $options->{cdata}) {
572             if (length($text) >= $options->{cdata}) {
573             unless (index($text, ']]>') > -1) {
574             return '';
575             }
576             }
577             }
578            
579             return $self->escape_xml_body($text);
580             }
581              
582             my $pretty = $options->{pretty};
583              
584             my $attributes = $self->_get_attrs;
585             my $name = $self->name;
586             my $kids = $self->kids;
587              
588             my $indent = $pretty ? (' ' x $level) : '';
589             my $eol = $pretty ? "\n" : '';
590              
591             my $xml = '';
592              
593             if ($pretty and $level >= 1) {
594             $xml .= $eol if $index == 0;
595             }
596            
597             $xml .= qq{$indent<$name};
598             if ($attributes and %$attributes) {
599             my @pairs;
600             foreach my $key (sort keys %$attributes) {
601             my $val = $attributes->{$key} . '';
602            
603             push @pairs, $key . '=' . '"' . $self->escape_xml_attr($val) . '"';
604             }
605             $xml .= ' ' . join(' ', @pairs);
606             }
607            
608             if ($kids and @$kids) {
609             my $cnt = 0;
610             $xml .= '>' . join('', map { $_->_to_xml($level + 1, $options, $cnt++) } @$kids);
611             $xml .= $indent if scalar(@$kids) > 1;
612             $xml .= "$eol";
613             }
614             else {
615             $xml .= "/>$eol";
616             }
617             }
618             *toXml = \&to_xml;
619              
620              
621             sub to_jsonml {
622             my ($self) = @_;
623              
624             return $self->_to_jsonml;
625             }
626              
627             sub _to_jsonml {
628             my ($self) = @_;
629              
630             if ($self->is_text) {
631             return $self->_quote_json_str($self->text);
632             }
633              
634             my $name = $self->name;
635             my $attrs = $self->_get_attrs;
636             my $kids = $self->kids;
637              
638             my $json = '[' . $self->_quote_json_str($name);
639             if ($attrs and %$attrs) {
640             my @keys = sort keys %$attrs;
641             my @pairs =
642             map { $self->_quote_json_str($_) . ':' . $self->_quote_json_str($attrs->{$_}) } @keys;
643             my $attr_str = '{' . join(',', @pairs) . '}';
644             $json .= ',' . $attr_str;
645             }
646              
647             if ($kids and @$kids) {
648             foreach my $kid (@$kids) {
649             $json .= ',' . $kid->_to_jsonml;
650             }
651             }
652              
653             $json .= ']';
654              
655             return $json;
656             }
657              
658             sub _quote_json_str {
659             my ($self, $str) = @_;
660              
661             $str =~ s/\\/\\\\/g;
662             $str =~ s/\"/\\\"/g;
663             $str =~ s/\x00/\\u0000/g;
664              
665             # FIXME: do tabs, etc.
666             $str =~ s/\x08/\\b/g;
667             $str =~ s/\x09/\\t/g;
668             $str =~ s/\x0a/\\n/g;
669             $str =~ s/\x0c/\\f/g;
670             $str =~ s/\x0d/\\r/g;
671             $str =~ s/([\x00-\x1e])/sprintf("\\u%04x", ord($1))/eg;
672            
673             return '"' . $str . '"';
674             }
675              
676             =pod
677              
678             =head2 C, C
679              
680             If no arguments are given, returns a hash of attributes for this
681             element. If arguments are present, an array of corresponding
682             attribute values is returned. Returns an array in array context
683             and an array reference if called in scalar context.
684              
685             E.g., for
686              
687             =for pod2rst next-code-block: xml
688              
689             bar
690              
691             use this to get the attributes:
692              
693             my ($name, $id) = $element->attributes('name', 'id');
694              
695             Aliases: attrs(), getAttributes(), getAttrs()
696              
697             =cut
698             sub attributes {
699             my $self = shift;
700             my $val = $self->[1];
701              
702             if (ref($val) eq 'ARRAY' and scalar(@$val) > 0) {
703             my $attr = $val->[0];
704             if (@_) {
705             my @keys;
706             if (ref($_[0]) eq 'ARRAY') {
707             @keys = @{$_[0]};
708             } else {
709             @keys = @_;
710             }
711             return wantarray ? @$attr{@keys} : [ @$attr{@keys} ];
712             }
713             return wantarray ? %$attr : $attr;
714             } else {
715             return {};
716             }
717             }
718             *attrs = \&attributes;
719             *getAttributes = \&attributes;
720             *getAttrs = \&attributes;
721             *get_attributes = \&attributes;
722             *get_attrs = \&attributes;
723              
724             sub _get_attrs {
725             my $self = shift;
726             my $val = $self->[1];
727              
728             if (ref($val) eq 'ARRAY' and scalar(@$val) > 0) {
729             my $attr = $val->[0];
730             if (@_) {
731             my @keys;
732             if (ref($_[0]) eq 'ARRAY') {
733             @keys = @{$_[0]};
734             } else {
735             @keys = @_;
736             }
737              
738             return wantarray ? @$attr{@keys} : [ @$attr{@keys} ];
739             }
740              
741             return wantarray ? %$attr : $attr;
742             } else {
743             return {};
744             }
745             }
746              
747             =pod
748              
749             =head2 C
750              
751             Similar to attributes(), but only returns one value.
752              
753             Aliases: attr(), getAttribute(), getAttr()
754              
755             =cut
756             sub attribute {
757             my ($self, $attr_name) = @_;
758             my $val = $self->attributes()->{$attr_name};
759              
760             return undef unless defined $val;
761              
762             return $val . '';
763             }
764             *attr = \&attribute;
765             *getAttribute = \&attribute;
766             *getAttr = \&attribute;
767              
768             sub attribute_str {
769             my ($self, $attr_name) = @_;
770              
771             my $attr = $self->attribute($attr_name);
772             if ($attr and ref($attr) eq 'HASH') {
773             return $attr->{Value};
774             }
775             else {
776             return $attr;
777             }
778             }
779              
780             =pod
781              
782             =head2 C, C
783              
784             Returns an array of child elements. If $element_name is passed,
785             a list of child elements with that name is returned.
786              
787             Aliases: getElements(), kids(), getKids(), children(), getChildren()
788              
789             =cut
790             sub kids {
791             my $self = shift;
792             my $tag = shift;
793            
794             my $val = $self->[1];
795             my $i = 1;
796             my $kids = [];
797             if (ref($val) eq 'ARRAY') {
798             my $stop = $#$val;
799             while ($i < $stop) {
800             my $this_tag = $val->[$i];
801             if (defined($tag)) {
802             push @$kids, XML::Parser::Wrapper->_new_element([ $this_tag, $val->[$i + 1] ])
803             if $this_tag eq $tag;
804             } else {
805             push @$kids, XML::Parser::Wrapper->_new_element([ $this_tag, $val->[$i + 1] ]);
806             }
807            
808             $i += 2;
809             }
810             }
811            
812             return wantarray ? @$kids : $kids;
813             }
814             *elements = \&kids;
815             *getKids = \&kids;
816             *getElements = \&kids;
817             *children = \&kids;
818             *getChildren = \&kids;
819              
820             =pod
821              
822             =head2 C, C
823              
824             Returns the first child element of this element. If
825             $element_name is passed, returns the first child element with
826             that name is returned.
827              
828             Aliases: getFirstElement(), kid(), first_kid()
829              
830             =cut
831             sub kid {
832             my $self = shift;
833             my $tag = shift;
834            
835             my $val = $self->[1];
836             if (ref($val) eq 'ARRAY') {
837             if (defined($tag)) {
838             my $i = 1;
839             my $stop = $#$val;
840             while ($i < $stop) {
841             my $kid;
842             my $this_tag = $val->[$i];
843             if ($this_tag eq $tag) {
844             return XML::Parser::Wrapper->_new_element([ $this_tag, $val->[$i + 1] ]);
845             }
846             $i += 2;
847             }
848             return undef;
849             } else {
850             return XML::Parser::Wrapper->_new_element([ $val->[1], $val->[2] ]);
851             }
852             } else {
853             return $val;
854             }
855             }
856             *element = \&kid;
857             *first_element = \&kid;
858             *getFirstElement = \&kid;
859             *first_kid = \&kid;
860              
861             =pod
862              
863             =head2 C
864              
865             Like first_element(), except if there is no corresponding child,
866             return an object that will work instead of undef. This allows
867             for reliable chaining, e.g.
868              
869             my $class = $root->kid_if('field')->kid_if('field')->kid_if('element')
870             ->kid_if('field')->attribute('class');
871              
872             Aliases: getFirstElementIf(), kidIf(), first_kid_if()
873              
874             =cut
875             sub kid_if {
876             my $self = shift;
877             my $tag = shift;
878             my $kid = $self->kid($tag);
879              
880             return $kid if defined $kid;
881              
882             return XML::Parser::Wrapper->_new_element([ undef, [ {} ] ]);
883             }
884             *kidIf = \&kid_if;
885             *first_element_if = \&kid_if;
886             *first_kid_if = \&kid_if;
887             *getFirstElementIf = \&kid_if;
888              
889              
890             =pod
891              
892             =head2 C
893              
894             Create a new XML document.
895              
896             =cut
897             sub new_document {
898             my ($class, $root_tag, $attr) = @_;
899              
900             $attr = { } unless $attr;
901              
902             my $data = [$root_tag, [ { %$attr } ] ];
903            
904             return bless $data, ref($class) || $class;
905             }
906             *new_doc = \&new_document;
907              
908             =pod
909              
910             =head2 C
911              
912             Create a new XML element object. If $text_val is defined, a
913             child text node will be created.
914              
915             =cut
916             sub new_element {
917             my ($class, $tag_name, $attr, $val) = @_;
918              
919             unless (defined($tag_name)) {
920             return undef;
921             }
922              
923             my $attr_to_add;
924             if ($attr and %$attr) {
925             $attr_to_add = $attr;
926             }
927             else {
928             $attr_to_add = { };
929             }
930              
931             my $stuff = [ $attr_to_add ];
932             if (defined($val)) {
933             push @$stuff, '0', $val;
934             }
935              
936             return $class->_new_element([ $tag_name, $stuff ]);
937             }
938              
939             sub new_from_tree {
940             my $class = shift;
941             my $tree = shift;
942            
943             my $obj = bless $tree, ref($class) || $class;
944            
945             return $obj;
946             }
947              
948             =pod
949              
950             =head2 C, C
951              
952             Adds a child to the current node. If $text_value is defined, it
953             will be used as the text between the opening and closing tags.
954             The return value is the newly created node (XML::Parser::Wrapper
955             object) that can then in turn have child nodes added to it.
956             This is useful for loading and XML file, adding an element, then
957             writing the modified XML back out. Note that all parameters
958             must be valid UTF-8.
959              
960             If the first argument is an element object created with the
961             new_element() method, that element will be added as a child.
962              
963             my $root = XML::Parser::Wrapper->new($input);
964            
965             my $new_element = $root->add_kid('test4', { attr1 => 'val1' });
966             $new_element->add_kid('child', { myattr => 'stuff' }, 'bleh');
967            
968             my $foo = $root->new_element('foo', { bar => 1 }, 'some text');
969             $new_element->add_kid($foo);
970              
971             Aliases: addKid(), add_child, addChild()
972              
973             =cut
974             sub add_kid {
975             my ($self, $tag_name, $attr, $val) = @_;
976              
977             unless (defined($tag_name)) {
978             return undef;
979             }
980              
981             if (ref($tag_name) and UNIVERSAL::isa($tag_name, 'XML::Parser::Wrapper')) {
982             push @{$self->[1]}, @$tag_name;
983             return $tag_name;
984             }
985              
986             my $new_element = $self->new_element($tag_name, $attr, $val);
987             push @{$self->[1]}, @$new_element;
988              
989             return $new_element;
990              
991             }
992             *addChild = \&add_kid;
993             *add_child = \&add_kid;
994             *addKid = \&add_kid;
995              
996             =pod
997              
998             =head2 C
999              
1000             Set the value of the attribute given by $name to $val for the
1001             element.
1002              
1003             =cut
1004             sub set_attr {
1005             my ($self, $name, $val) = @_;
1006              
1007             $self->[1][0]->{$name} = $val;
1008              
1009             return $val;
1010             }
1011              
1012             =pod
1013              
1014             =head2 C
1015              
1016             Convenience method that calls set_attr() for each key/value pair
1017             in %attrs.
1018              
1019             =cut
1020             sub set_attrs {
1021             my ($self, $attrs) = @_;
1022              
1023             return undef unless $attrs;
1024              
1025             return 0 unless %$attrs;
1026              
1027             my $cnt = 0;
1028             foreach my $k (keys %$attrs) {
1029             $self->set_attr($k, $attrs->{$k});
1030             $cnt++;
1031             }
1032              
1033             return $cnt;
1034             }
1035              
1036             =pod
1037              
1038             =head2 C
1039              
1040             Replaces all attributes for the element with the provided ones.
1041             That is, the old attributes are all removed and the new ones are
1042             added.
1043              
1044             =cut
1045             sub replace_attrs {
1046             my ($self, $attrs) = @_;
1047              
1048             return undef unless $attrs;
1049            
1050             my %new_attrs = %$attrs;
1051              
1052             $self->[1][0] = \%new_attrs;
1053              
1054             return \%new_attrs;
1055             }
1056              
1057             =pod
1058              
1059             =head2 C
1060              
1061             Removes all child nodes (include text nodes) from this element.
1062              
1063             =cut
1064             sub remove_kids {
1065             my ($self) = @_;
1066              
1067             @{$self->[1]} = ($self->[1][0]);
1068              
1069             return 1;
1070             }
1071              
1072             =pod
1073              
1074             =head2 C
1075              
1076             Removes the first child node with name $name.
1077              
1078             =cut
1079             sub remove_kid {
1080             my ($self, $name_to_remove) = @_;
1081              
1082             return undef unless defined $name_to_remove;
1083              
1084             my $index = 1;
1085             my $found = 0;
1086             my $children = $self->[1];
1087             if (scalar(@$children) > 1) {
1088             while (not $found and $index < scalar(@$children)) {
1089             my $name = $children->[$index];
1090             if ($name eq $name_to_remove) {
1091             $found = 1;
1092             }
1093             else {
1094             $index += 2;
1095             }
1096             }
1097             }
1098              
1099             if ($found) {
1100             splice(@$children, $index, 2);
1101              
1102             return 1;
1103             }
1104              
1105             return 0;
1106             }
1107              
1108             =pod
1109              
1110             =head2 C
1111              
1112             Sets the first text child node to $text_val. If there is no text
1113             child node, one is created. If $text_val is undef, the first
1114             text child node is removed.
1115              
1116             =cut
1117             sub set_text {
1118             my ($self, $text_val) = @_;
1119              
1120             my $index = 1;
1121             my $found = 0;
1122             my $children = $self->[1];
1123             if (scalar(@$children) > 1) {
1124             while (not $found and $index < scalar(@$children)) {
1125             my $name = $children->[$index];
1126             if ($name eq '0') {
1127             $found = 1;
1128             }
1129             else {
1130             $index += 2;
1131             }
1132             }
1133             }
1134              
1135             unless (defined($text_val)) {
1136             return 0 unless $found;
1137              
1138             splice(@$children, $index, 2);
1139              
1140             return 1;
1141             }
1142              
1143             if ($found) {
1144             $children->[$index + 1] = $text_val;
1145             }
1146             else {
1147             push @$children, '0', $text_val . '';
1148             }
1149              
1150             return 1;
1151             }
1152              
1153             =pod
1154              
1155             =head2 C
1156              
1157             Updates the node, setting the attributes to the ones provided in
1158             %attrs, and sets the text child node to $text_val if it is
1159             defined. Note that this removes all child nodes.
1160              
1161             Aliases: updateNode()
1162              
1163             =cut
1164             sub update_node {
1165             my $self = shift;
1166             my $attrs = shift;
1167             my $text_val = shift;
1168              
1169             my $stuff = [ $attrs ];
1170             if (defined($text_val)) {
1171             push @$stuff, '0', $text_val;
1172             }
1173              
1174             @{$self->[1]} = @$stuff;
1175              
1176             return $self;
1177             }
1178             *updateNode = \&update_node;
1179              
1180             =pod
1181              
1182             =head2 C
1183              
1184             Calls update_node() on the first child node with name $tag_name
1185             if it exists. If there is no such child node, one is created by
1186             calling add_kid().
1187              
1188             Aliases: updateKid(), update_child(), updateChild()
1189              
1190             =cut
1191             sub update_kid {
1192             my ($self, $tag_name, $attrs, $text_val) = @_;
1193              
1194             my $kid = $self->kid($tag_name);
1195             if ($kid) {
1196             $kid->update_node($attrs, $text_val);
1197             return $kid;
1198             }
1199              
1200             $kid = $self->add_kid($tag_name, $attrs, $text_val);
1201             return $kid;
1202             }
1203             *updateKid = \&update_kid;
1204             *update_child = \&update_kid;
1205             *updateChild = \&update_kid;
1206              
1207             sub escape_html {
1208             my ($self, $text) = @_;
1209             return undef unless defined $text;
1210            
1211             $text =~ s/\&/\&/g;
1212             $text =~ s/
1213             $text =~ s/>/\>/g;
1214             $text =~ s/\"/\"/g;
1215              
1216             return $text;
1217             }
1218              
1219             # our $Escape_Map = { '&' => '&',
1220             # '<' => '<',
1221             # '>' => '>',
1222             # '"' => '"',
1223             # "'" => ''',
1224             # };
1225              
1226             sub escape_xml {
1227             my ($self, $text) = @_;
1228             return undef unless defined $text;
1229              
1230             # FIXME: benchmark this and test fully
1231             # $text =~ s/([&<>"'])/$Escape_Map->{$1}/eg;
1232             # return $text;
1233            
1234             $text =~ s/\&/\&/g;
1235             $text =~ s/
1236             $text =~ s/>/\>/g;
1237             $text =~ s/\"/\"/g;
1238             $text =~ s/\'/\'/g;
1239              
1240             return $text;
1241             }
1242              
1243             sub escape_xml_attr {
1244             my ($self, $text) = @_;
1245             return undef unless defined $text;
1246              
1247             # FIXME: benchmark this and test fully
1248             # $text =~ s/([&<>"'])/$Escape_Map->{$1}/eg;
1249             # return $text;
1250            
1251             $text =~ s/\&/\&/g;
1252             $text =~ s/
1253             $text =~ s/>/\>/g;
1254             $text =~ s/\"/\"/g;
1255             $text =~ s/\'/\'/g;
1256              
1257             return $text;
1258             }
1259              
1260             sub escape_xml_body {
1261             my ($self, $text) = @_;
1262             return undef unless defined $text;
1263              
1264             # FIXME: benchmark this and test fully
1265             # $text =~ s/([&<>"'])/$Escape_Map->{$1}/eg;
1266             # return $text;
1267            
1268             $text =~ s/\&/\&/g;
1269             $text =~ s/
1270             $text =~ s/>/\>/g;
1271              
1272             return $text;
1273             }
1274              
1275              
1276             =pod
1277              
1278             =head2 C
1279              
1280             Assume a data structure of hashes, arrays, and strings are
1281             represented in the xml with no attributes. Return the data
1282             structure, leaving out the root tag.
1283              
1284             =cut
1285             # Assume a data structure of hashes, arrays, and strings are
1286             # represented in the xml with no attributes. Return the data
1287             # structure, leaving out the root tag.
1288             sub simple_data {
1289             my $self = shift;
1290              
1291             return _convert_xml_node_to_perl($self);
1292             }
1293              
1294             sub _convert_xml_node_to_perl {
1295             my $node = shift;
1296              
1297             my $new_data;
1298             if ($node->is_text) {
1299             $new_data = $node->text;
1300             }
1301             else {
1302             $new_data = {};
1303             my $ignore_whitespace_kids;
1304             my $kids = $node->kids;
1305             my $attr = $node->attributes;
1306              
1307             if (scalar(@$kids) == 0) {
1308             return ($attr and %$attr) ? { %$attr } : undef;
1309             }
1310             elsif (scalar(@$kids) == 1) {
1311             if ($kids->[0]->is_text) {
1312             return $kids->[0]->text;
1313             }
1314             }
1315             else {
1316             $ignore_whitespace_kids = 1;
1317             }
1318              
1319             foreach my $kid (@$kids) {
1320             if ($ignore_whitespace_kids and $kid->is_text and $kid->text =~ /^\s*$/) {
1321             next;
1322             }
1323              
1324             my $kid_data = _convert_xml_node_to_perl($kid);
1325             my $node_name = $kid->name;
1326             if (exists($new_data->{$node_name})) {
1327             unless (ref($new_data->{$node_name}) eq 'ARRAY') {
1328             $new_data->{$node_name} = [ $new_data->{$node_name} ];
1329             }
1330             push @{$new_data->{$node_name}}, $kid_data
1331             }
1332             else {
1333             $new_data->{$node_name} = $kid_data;
1334             }
1335             }
1336              
1337             }
1338              
1339             return $new_data;
1340             }
1341              
1342             =pod
1343              
1344             =head2 C
1345              
1346             The reverse of simple_data() -- return xml representing the data
1347             structure passed.
1348              
1349             =cut
1350             # the reverse of simple_data() -- return xml representing the data structure provided
1351             sub dump_simple_data {
1352             my $self = shift;
1353             my $data = shift;
1354              
1355             my $xml = '';
1356             if (ref($data) eq 'ARRAY') {
1357             foreach my $element (@$data) {
1358             $xml .= $self->dump_simple_data($element);
1359             }
1360             }
1361             elsif (ref($data) eq 'HASH') {
1362             foreach my $key (keys %$data) {
1363             if (ref($data->{$key}) eq 'ARRAY') {
1364             foreach my $element ( @{$data->{$key}} ) {
1365             $xml .= '<' . $key . '>' . $self->dump_simple_data($element)
1366             . '';
1367             }
1368             }
1369             else {
1370             $xml .= '<' . $key . '>' . $self->dump_simple_data($data->{$key})
1371             . '';
1372             }
1373             }
1374             }
1375             else {
1376             return $self->escape_xml_body($data);
1377             }
1378              
1379             return $xml;
1380             }
1381              
1382             sub DESTROY {
1383             my ($self) = @_;
1384            
1385             delete $i_data{refaddr($self)};
1386            
1387             return 1;
1388             }
1389              
1390             }
1391              
1392              
1393             {
1394             package XML::Parser::Wrapper::AttributeVal;
1395              
1396             use overload '""' => \&as_string;
1397              
1398             sub new {
1399             my ($class, $val) = @_;
1400              
1401             return bless { v => $val }, ref($class) || $class;
1402             }
1403              
1404             sub as_string {
1405             my ($self) = @_;
1406              
1407             my $val = $self->{v};
1408            
1409             if ($val and ref($val) and UNIVERSAL::isa($val, 'HASH')) {
1410             return $val->{Value};
1411             }
1412             else {
1413             return $val;
1414             }
1415             }
1416             }
1417              
1418             1;
1419              
1420             __END__