File Coverage

blib/lib/CAM/XML.pm
Criterion Covered Total %
statement 28 336 8.3
branch 1 150 0.6
condition 0 72 0.0
subroutine 7 37 18.9
pod 19 19 100.0
total 55 614 8.9


line stmt bran cond sub pod time code
1             package CAM::XML;
2              
3 1     1   25167 use 5.006;
  1         4  
  1         40  
4 1     1   6 use strict;
  1         2  
  1         33  
5 1     1   4 use warnings;
  1         2  
  1         43  
6 1     1   528 use CAM::XML::Text;
  1         2  
  1         31  
7 1     1   943 use English qw(-no_match_vars);
  1         4754  
  1         6  
8 1     1   403 use Carp;
  1         5  
  1         4207  
9              
10             our $VERSION = '1.14';
11              
12             =for stopwords XHTML XPath pre-formatted attr1 attr2
13              
14             =head1 NAME
15              
16             CAM::XML - Encapsulation of a simple XML data structure
17              
18             =head1 LICENSE
19              
20             Copyright 2006 Clotho Advanced Media, Inc.,
21              
22             This library is free software; you can redistribute it and/or modify it
23             under the same terms as Perl itself.
24              
25             =head1 SYNOPSIS
26              
27             my $pollTag = CAM::XML->new('poll');
28            
29             foreach my $q (@questions) {
30             my $questionTag = CAM::XML->new('question');
31            
32             $questionTag->add(-text => $q->{text});
33             my $choicesTag = CAM::XML->new('choices');
34            
35             foreach my $c (@{$q->{choices}}) {
36             my $choiceTag = CAM::XML->new('choice');
37             $choiceTag->setAttributes('value', $c->{value});
38             $choiceTag->add(-text => $c->{text});
39             $choicesTag->add($choiceTag);
40             }
41             $questionTag->add($choicesTag);
42             $pollTag->add($questionTag);
43             }
44             print CAM::XML->header();
45             print $pollTag->toString();
46              
47             =head1 DESCRIPTION
48              
49             This module reads and writes XML into a simple object model. It is
50             optimized for ease of creating code that interacts with XML.
51              
52             This module is not as powerful or as standards-compliant as say
53             XML::LibXML, XML::SAX, XML::DOM, etc, but it's darn easy to use. I
54             recommend it to people who want to just read/write a quick but valid
55             XML file and don't want to bother with the bigger modules.
56              
57             In our experience, this module is actually easier to use than
58             XML::Simple because the latter makes some assumptions about XML
59             structure that prevents it from handling all XML files well. YMMV.
60              
61             However, one exception to the simplicity claimed above is our
62             implementation of a subset of XPath. That's not very simple. Sorry.
63              
64             =head1 CLASS METHODS
65              
66             =over
67              
68             =item $pkg->parse($xmlstring)
69              
70             =item $pkg->parse(-string => $xmlstring)
71              
72             =item $pkg->parse(-filename => $xmlfilename)
73              
74             =item $pkg->parse(-filehandle => $xmlfilehandle)
75              
76             Parse an incoming stream of XML into a CAM::XML hierarchy. This
77             method just hands the first argument off to XML::Parser, so it can
78             accept any style of argument that XML::Parser can. Note that XML::Parser
79             says the filehandle style should pass an IO::Handle object. This can
80             be called as a class method or an instance method.
81              
82             Additional meaningful flags:
83              
84             -cleanwhitespace => 1
85              
86             Traverse the document and remove non-significant whitespace, as per
87             removeWhitespace().
88              
89             -xmlopts => HASHREF
90              
91             Any options in this hash are passed directly to XML::Parser.
92              
93             NOTE: this method does NOT work well on subclasses. I tried, but
94             failed to fix it up. The problems is that CAM::XML::XMLTree has to be
95             able to instantiate one of this class, but there's no really good way
96             to communicate with it yet.
97              
98             =cut
99              
100             sub parse
101             {
102 1     1 1 13 my $pkg_or_self = shift;
103 1         3 my $mode;
104 1 50       12 if ($_[0] =~ m/\A-/xms) # If no mode was specified, imply one
105             {
106 1         3 $mode = shift;
107             }
108             else
109             {
110 0         0 $mode = '-string';
111             }
112 1         1 my $xml = shift;
113 1         4 my %flags = (@_);
114              
115 1         6 local $SIG{__DIE__};
116 1         4 local $SIG{__WARN__};
117 1         1191 require CAM::XML::XMLTree;
118 1         736 require XML::Parser;
119 0           my $pkg = ref $pkg_or_self;
120 0 0         if (!$pkg)
121             {
122 0           $pkg = $pkg_or_self;
123             }
124 0           my $p = XML::Parser->new(Style => $pkg.'::XMLTree',
125 0 0         $flags{-xmlopts} ? %{$flags{xmlopts}} : ());
126 0           my $self;
127 0 0         if ($mode eq '-filename')
128             {
129 0 0         if (open my $fh, '<', $xml)
130             {
131 0           local $INPUT_RECORD_SEPARATOR = undef;
132 0           eval {
133 0           $self = $p->parse(<$fh>);
134             };
135 0           close $fh;
136             }
137             }
138             else
139             {
140 0           eval {
141 0           $self = $p->parse($xml);
142             };
143             }
144 0 0 0       if ($self && $flags{-cleanwhitespace})
145             {
146 0           $self->removeWhitespace();
147             }
148 0           return $self;
149             }
150              
151             =item $pkg->new($tagname)
152              
153             =item $pkg->new($tagname, attr1 => $value1, attr2 => $value2, ...)
154              
155             Create a new XML tag. Optionally, you can set tag attributes at the
156             same time.
157              
158             =cut
159              
160             sub new
161             {
162 0     0 1   my $pkg = shift;
163 0           my $name = shift;
164              
165 0 0         if (!$name)
166             {
167 0           croak 'No XML tag name specified';
168             }
169              
170 0           my $self = bless {
171             name => $name,
172             attributes => {},
173             children => [],
174             }, $pkg;
175              
176 0           return $self->setAttributes(@_);
177             }
178              
179             =item $pkg->header()
180              
181             =item $self->header()
182              
183             Return a string containing the following message, suffixed by a newline:
184              
185            
186              
187             =cut
188              
189             sub header
190             {
191 0     0 1   return qq[\n];
192             }
193              
194             =back
195              
196             =head1 INSTANCE METHODS
197              
198             =over
199              
200             =item $self->getName()
201              
202             Returns the name of the node.
203              
204             =cut
205              
206             sub getName
207             {
208 0     0 1   my $self = shift;
209 0           return $self->{name};
210             }
211              
212             =item $self->setAttributes(attr1 => $value1, attr2 => $value2, ...)
213              
214             Set the value of one or more XML attributes. If any keys are
215             duplicated, only the last one set is recorded.
216              
217             =cut
218              
219             sub setAttributes
220             {
221 0     0 1   my $self = shift;
222              
223 0           while (@_ > 0)
224             {
225 0           my $key = shift;
226 0           my $value = shift;
227 0 0 0       if (!defined $key || $key eq q{})
228             {
229 0           croak 'Invalid key specified';
230             }
231 0           $self->{attributes}->{$key} = $value;
232             }
233 0           return $self;
234             }
235              
236             =item $self->deleteAttribute($key)
237              
238             Remove the specified attribute if it exists.
239              
240             =cut
241              
242             sub deleteAttribute
243             {
244 0     0 1   my $self = shift;
245 0           my $key = shift;
246              
247 0           delete $self->{attributes}->{$key};
248 0           return $self;
249             }
250              
251             =item $self->getAttributeNames()
252              
253             Returns a list of the names of all the attributes of this node. The
254             names are returned in arbitrary order.
255              
256             =cut
257              
258             sub getAttributeNames
259             {
260 0     0 1   my $self = shift;
261              
262 0           return keys %{ $self->{attributes} };
  0            
263             }
264              
265             =item $self->getAttributes()
266              
267             Returns a hash of all attributes.
268              
269             =cut
270              
271             sub getAttributes
272             {
273 0     0 1   my $self = shift;
274              
275 0           return %{ $self->{attributes} };
  0            
276             }
277              
278             =item $self->getAttribute($key)
279              
280             Returns the value of the named attribute, or undef if it does not exist.
281              
282             =cut
283              
284             sub getAttribute
285             {
286 0     0 1   my $self = shift;
287 0           my $key = shift;
288              
289 0 0         return $key ? $self->{attributes}->{$key} : undef;
290             }
291              
292             =item $self->getChildren()
293              
294             Returns an array of XML nodes and text objects contained by this node.
295              
296             =cut
297              
298             sub getChildren
299             {
300 0     0 1   my $self = shift;
301 0           return @{ $self->{children} };
  0            
302             }
303              
304             =item $self->getChild($index)
305              
306             Returns a child of this node. The argument is a zero-based index.
307             Returns undef if the index is not valid.
308              
309             =cut
310              
311             sub getChild
312             {
313 0     0 1   my $self = shift;
314 0           my $index = shift;
315              
316 0 0 0       return if (!defined $index || $index !~ m/\A\d+\z/xms);
317 0           return $self->{children}->[$index];
318             }
319              
320             =item $self->getChildNodes()
321              
322             Returns an array of XML nodes contained by this node (that is, unlike
323             getChildren(), text nodes are ignored).
324              
325             =cut
326              
327             sub getChildNodes
328             {
329 0     0 1   my $self = shift;
330              
331 0           return grep { $_->isa(__PACKAGE__) } @{ $self->{children} };
  0            
  0            
332             }
333              
334             =item $self->getChildNode($index)
335              
336             Returns a CAM::XML child of this node (that is, unlike getChild(),
337             text nodes are ignored. The argument is a zero-based index. Returns
338             undef if the index is not valid.
339              
340             =cut
341              
342             sub getChildNode
343             {
344 0     0 1   my $self = shift;
345 0           my $index = shift;
346              
347 0 0 0       return if (!defined $index || $index !~ m/\A\d+\z/xms);
348 0           my @kids = grep { $_->isa(__PACKAGE__) } @{ $self->{children} };
  0            
  0            
349 0           return $kids[$index];
350             }
351              
352             =item $self->setChildren($node1, $node2, ...)
353              
354             Removes all the children from this node and replaces them with the
355             supplied values. All of the values MUST be CAM::XML or CAM::XML::Text
356             objects, or this method will abort and return false before any changes
357             are made.
358              
359             =cut
360              
361             sub setChildren
362             {
363 0     0 1   my $self = shift;
364              
365 0 0 0       my @good = grep { defined $_ && ref $_ &&
  0   0        
366             ($_->isa(__PACKAGE__) || $_->isa('CAM::XML::Text')) } @_;
367 0 0         if (@good != @_)
368             {
369 0           croak 'Attempted to add bogus XML node';
370             }
371              
372 0           @{ $self->{children} } = @good;
  0            
373 0           return $self;
374             }
375              
376             =item $self->add(CAM::XML instance)
377              
378             =item $self->add(-text => $text)
379              
380             =item $self->add(-cdata => $text)
381              
382             =item $self->add(-xml => $rawxml)
383              
384             =item $self->add()
385              
386             Add content within the current tag. Order of addition may be
387             significant. This content can be any one of 1) subsidiary XML tags
388             (CAM::XML), 2) literal text content (C<-text> or C<-cdata>), or 3)
389             pre-formatted XML content (C<-xml>).
390              
391             In C<-text> and C<-cdata> content, any reserved characters will be
392             automatically escaped. Those two modes differ only in their XML
393             representation: C<-cdata> is more human-readable if there are a lot of
394             "&", "<" and ">" characters in your text, where C<-text> is usually more
395             compact for short strings. These strings are not escaped until
396             output.
397              
398             Content in C<-xml> mode is parsed in as CAM::XML objects. If it is not
399             valid XML, a warning will be emitted and the add will fail.
400              
401             =cut
402              
403             sub add
404             {
405 0     0 1   my $self = shift;
406              
407 0           while (@_ > 0)
408             {
409 0           my $add = shift;
410              
411             # Test different kinds of input
412 0           !$add ? croak 'Undefined object'
413 0           : ref $add && $add->isa(__PACKAGE__) ? push @{ $self->{children} }, $add
414 0 0 0       : ref $add && $add->isa('CAM::XML::Text') ? push @{ $self->{children} }, $add
    0 0        
    0          
    0          
    0          
    0          
415             : ref $add ? croak 'Invalid object type to add to a CAM::XML node'
416             : $add =~ m/\A-(text|cdata)\z/xms ? $self->_add_text($1, shift)
417             : $add eq '-xml' ? $self->_add_xml(shift)
418             : croak "Unknown flag '$add'. Expected '-text' or '-cdata' or '-xml'";
419             }
420              
421 0           return $self;
422             }
423              
424             sub _add_text
425             {
426 0     0     my $self = shift;
427 0           my $type = shift;
428 0           my $text = shift;
429              
430 0 0         if (!defined $text)
431             {
432 0           $text = q{};
433             }
434            
435             # If the previous element was the same kind of text item
436             # then merge them. Otherwise append this text item.
437            
438 0 0 0       if (@{ $self->{children} } > 0 &&
  0   0        
439             $self->{children}->[-1]->isa('CAM::XML::Text') &&
440             $self->{children}->[-1]->{type} eq $type)
441             {
442 0           $self->{children}->[-1]->{text} .= $text;
443             }
444             else
445             {
446 0           push @{ $self->{children} }, CAM::XML::Text->new($type => $text);
  0            
447             }
448 0           return;
449             }
450              
451             sub _add_xml
452             {
453 0     0     my $self = shift;
454 0           my $xml = shift;
455              
456 0           my $parsed = $self->parse($xml);
457 0 0         if ($parsed)
458             {
459 0           $self->add($parsed);
460             }
461             else
462             {
463 0           croak 'Tried to add invalid XML content';
464             }
465 0           return;
466             }
467              
468             =item $self->removeWhitespace()
469              
470             Clean out all non-significant whitespace. Whitespace is deemed
471             non-significant if it is bracketed by tags. This might not be true in
472             some data formats (e.g. HTML) so don't use this function carelessly.
473              
474             =cut
475              
476             sub removeWhitespace
477             {
478 0     0 1   my $self = shift;
479              
480 0           my @delete_indices = ();
481 0           my $lasttag = -1;
482 0           foreach my $i (0 .. $#{ $self->{children} })
  0            
483             {
484 0           my $child = $self->{children}->[$i];
485 0 0         if ($child->isa(__PACKAGE__))
    0          
486             {
487 0 0         if (defined $lasttag)
488             {
489 0           push @delete_indices, ($lasttag + 1) .. ($i - 1);
490             }
491 0           $child->removeWhitespace();
492 0           $lasttag = $i;
493             }
494             elsif ($child->{text} =~ m/\S/xms) # CAM::XML::Text instance
495             {
496 0           $lasttag = undef;
497             }
498             }
499 0 0         if (defined $lasttag)
500             {
501 0           push @delete_indices, ($lasttag + 1) .. $#{ $self->{children} };
  0            
502             }
503 0           while (@delete_indices > 0)
504             {
505 0           my $node_index = pop @delete_indices;
506 0           splice @{ $self->{children} }, $node_index, 1;
  0            
507             }
508 0           return $self;
509             }
510              
511             =item $self->getInnerText()
512              
513             For the given node, descend through all of its children and
514             concatenate all the text values that are found. If none, this method
515             returns an empty string (not undef).
516              
517             =cut
518              
519             sub getInnerText
520             {
521 0     0 1   my $self = shift;
522              
523 0           my $text = q{};
524 0           my @stack = ([@{ $self->{children} }]);
  0            
525 0           while (@stack > 0)
526             {
527 0           my $list = $stack[-1];
528 0           my $child = shift @{$list};
  0            
529 0 0         if ($child)
530             {
531 0 0         if ($child->isa(__PACKAGE__))
532             {
533 0           push @stack, [@{ $child->{children} }];
  0            
534             }
535             else # CAM::XML::Text
536             {
537 0           $text .= $child->{text};
538             }
539             }
540             else
541             {
542 0           pop @stack;
543             }
544             }
545 0           return $text;
546             }
547              
548             =item $self->getNodes(-tag => $tagname)
549              
550             =item $self->getNodes(-attr => $attrname, -value => $attrvalue)
551              
552             =item $self->getNodes(-path => $path)
553              
554             Return an array of CAM::XML objects representing nodes that match the
555             requested properties.
556              
557             A path is a syntactic path into the XML doc something like an XPath
558              
559             '/' divides nodes
560             '//' means any number of nodes
561             '/[n]' means the nth child of a node (1-based)
562             '[n]' means the nth instance of this node
563             '/[-n]' means the nth child of a node, counting backward
564             '/[last()]' means the last child of a node (same as [-1])
565             '/[@attr="value"]' means a node with this attribute value
566             '/text()' means all of the text data inside a node
567             (note this returns just one node, not all the nodes)
568              
569             For example, C
570             searches an XHTML body for all tables, and returns all anchor nodes in
571             the first row which pop new windows.
572              
573             Please note that while this syntax resembles XPath, it is FAR from a
574             complete (or even correct) implementation. It's useful for basic
575             delving into an XML document, however.
576              
577             =cut
578              
579             sub getNodes
580             {
581 0     0 1   my $self = shift;
582 0           my %criteria = (@_);
583              
584 0 0         if ($criteria{-path})
585             {
586             # This is a very different beast. Handle it separately.
587 0           return $self->_get_path_nodes($criteria{-path}, [$self]);
588             }
589              
590 0           my @list = ();
591 0           my @stack = ([$self]);
592 0           while (@stack > 0)
593             {
594 0           my $list = $stack[-1];
595 0           my $obj = shift @{$list};
  0            
596 0 0         if ($obj)
597             {
598 0 0         if ($obj->isa(__PACKAGE__))
599             {
600 0           push @stack, [@{ $obj->{children} }];
  0            
601 0 0 0       if (($criteria{-tag} && $criteria{-tag} eq $obj->{name}) ||
      0        
      0        
      0        
602             ($criteria{-attr} && exists $obj->{attributes}->{$criteria{-attr}} &&
603             $obj->{attributes}->{$criteria{-attr}} eq $criteria{-value}))
604             {
605 0           push @list, $obj;
606             }
607             }
608             }
609             else
610             {
611 0           pop @stack;
612             }
613             }
614 0           return @list;
615             }
616              
617             # Internal use only
618              
619             sub _get_path_nodes
620             {
621 0     0     my $self = shift;
622 0           my $path = shift;
623 0   0       my $kids = shift || $self->{children};
624              
625 0 0         my @list = !$path ? $self
    0          
    0          
    0          
    0          
    0          
626             : $path =~ m,\A /?text\(\) \z,xms ? $self->_get_path_nodes_text()
627             : $path =~ m,\A /?\[(\d+)\](.*) \z,xms ? $self->_get_path_nodes_easyindex($kids, $1, $2)
628             : $path =~ m,\A /?\[([^\]]+)\](.*) \z,xms ? $self->_get_path_nodes_index($kids, $1, $2)
629             : $path =~ m,\A //+ \z,xms ? $self->_get_path_nodes_all($kids, $path)
630             : $path =~ m, (/?)(/?)([^/]+)(.*) \z,xms ? $self->_get_path_nodes_match($kids, $path, $1, $2, $3, $4)
631             : croak "path not understood: '$path'";
632              
633 0           return @list;
634             }
635              
636              
637             sub _get_path_nodes_text
638             {
639 0     0     my $self = shift;
640              
641 0           return CAM::XML::Text->new(text => $self->getInnerText());
642             }
643              
644             sub _get_path_nodes_easyindex
645             {
646 0     0     my $self = shift;
647 0           my $kids = shift;
648 0           my $num = shift;
649 0           my $rest = shift;
650            
651             # this is a special case of _get_path_nodes_index
652             # it's higher performance since we can go straight to the
653             # index instead of looping
654              
655 0           my $match = $kids->[$num - 1];
656 0 0         return $match ? $match->_get_path_nodes($rest) : ();
657             }
658              
659             sub _get_path_nodes_index
660             {
661 0     0     my $self = shift;
662 0           my $kids = shift;
663 0           my $limit = shift;
664 0           my $rest = shift;
665              
666 0           my $index = 0;
667 0           my @list;
668 0           foreach my $node (@{$kids})
  0            
669             {
670 0           ++$index; # one-based
671 0 0         if ($self->_match($node, undef, $limit, $index, scalar @{$kids}))
  0            
672             {
673 0           push @list, $node->_get_path_nodes($rest);
674             }
675             }
676 0           return @list;
677             }
678              
679             sub _get_path_nodes_all
680             {
681 0     0     my $self = shift;
682 0           my $kids = shift;
683 0           my $path = shift;
684              
685 0           my @list;
686 0           foreach my $node (@{$kids})
  0            
687             {
688 0 0         if ($node->isa(__PACKAGE__))
689             {
690 0           push @list, $node, $node->_get_path_nodes($path);
691             }
692             }
693 0           return @list;
694             }
695              
696             sub _get_path_nodes_match
697             {
698 0     0     my $self = shift;
699 0           my $kids = shift;
700 0           my $path = shift;
701 0           my $base = shift;
702 0           my $any = shift;
703 0           my $match = shift;
704 0           my $rest = shift;
705              
706 0           my @list;
707 0           my $limit = undef;
708 0 0         if ($match =~ s,\[([^\]]+)\]\z,,xms)
709             {
710 0           $limit = $1;
711 0 0         if (!$limit)
712             {
713 0           croak "bad index in path (indices are one-based): '$path'";
714             }
715             }
716 0 0 0       if ($match && $limit)
    0 0        
717             {
718             # This is a special case that arose from a bug in _match()
719             # TODO: move the @group and $index logic into _match()
720 0           my @group;
721 0           my $index = 0;
722 0           my $max = 0;
723 0           foreach my $node (@{$kids})
  0            
724             {
725 0           ++$index; # one-based
726 0 0         if ($self->_match($node, $match, undef, $index, scalar @{$kids}))
  0            
727             {
728 0           push @group, 1;
729 0           $max++;
730             }
731             else
732             {
733 0           push @group, 0;
734             }
735             }
736 0           $index = 0;
737 0           foreach my $i (0 .. $#{$kids})
  0            
738             {
739 0           my $node = $kids->[$i];
740 0 0         if ($group[$i])
741             {
742 0           ++$index; # one-based
743 0 0         if ($self->_match($node, undef, $limit, $index, $max))
744             {
745 0           push @list, $node->_get_path_nodes($rest);
746             }
747             }
748 0 0         if ($any)
749             {
750 0           push @list, $node->_get_path_nodes($path);
751             }
752             }
753             }
754             elsif ($match || $limit)
755             {
756 0           my $index = 0;
757 0           foreach my $node (@{$kids})
  0            
758             {
759 0           ++$index; # one-based
760 0 0         if ($self->_match($node, $match, $limit, $index, scalar @{$kids}))
  0            
761             {
762 0           push @list, $node->_get_path_nodes($rest);
763             }
764 0 0         if ($any)
765             {
766 0           push @list, $node->_get_path_nodes($path);
767             }
768             }
769             }
770             else
771             {
772 0           die 'Internal error: neither match nor limit were true';
773             }
774 0           return @list;
775             }
776              
777             sub _match
778             {
779 0     0     my $self = shift;
780 0           my $node = shift;
781 0           my $tag = shift;
782 0           my $limit = shift;
783 0           my $index = shift; # one-based
784 0           my $max = shift;
785              
786 0 0 0       if ($tag && $limit)
787             {
788 0           die 'Internal error: _match() is broken for simultaneous tag and index matches';
789             # currently, the $tag && $limit case is handled externally.
790             # TODO: handle this better
791             }
792              
793 0           my $is_element = $node->isa(__PACKAGE__);
794 0 0         if ($tag)
795             {
796 0 0         return if (!$is_element);
797 0 0         return if ($node->{name} ne $tag);
798             }
799 0 0         if ($limit)
800             {
801             # massaging
802 0 0         if ($limit eq 'last()')
803             {
804 0           $limit = -1;
805             }
806              
807 0 0 0       if ($limit =~ m/\A\-\d+/xms)
    0          
    0          
808             {
809 0 0         return if ($max + $limit + 1 != $index);
810             }
811             elsif ($limit =~ m/\A\d+/xms)
812             {
813 0 0         return if ($limit != $index);
814             }
815             elsif ($limit =~ m/\A\@(\w+)=\"([^\"]*)\"\z/xms ||
816             $limit =~ m/\A\@(\w+)=\'([^\']*)\'\z/xms)
817             {
818 0 0         return if (!$is_element);
819 0           my $attr = $1;
820 0           my $val = $2;
821 0           my $cmp = $node->{attributes}->{$attr};
822 0 0 0       return if (!defined $cmp || $val ne $cmp);
823             }
824             else
825             {
826 0           croak "path predicate not understood: '$limit'";
827             }
828             }
829 0           return $self;
830             }
831              
832             =item $self->toString([OPTIONS])
833              
834             Serializes the tag and all subsidiary tags into an XML string. This
835             is called recursively on any subsidiary CAM::XML objects. Note that
836             the XML header is not prepended to this output.
837              
838             The following optional arguments apply:
839              
840             -formatted => boolean
841             If true, the XML is indented nicely. Otherwise, no whitespace
842             is inserted between tags.
843             -textformat => boolean
844             Only relevent if -formatted is true. If false, this prevents
845             the formatting of pure text values.
846             -level => number
847             Indents this tag by the number of levels indicated. This implies
848             -formatted => 1
849             -indent => number
850             The number of spaces to indent per level if the output is
851             formatted. By default, this is 2 (i.e. two spaces).
852              
853             Example: -formatted => 0
854              
855             Baz
856              
857             Example: -formatted => 1
858              
859            
860            
861             Baz
862            
863            
864              
865             Example: C<-formatted =E 1, textformat =E 0>
866              
867            
868             Baz
869            
870              
871             Example: C<-formatted =E 1, textformat =E 0, -indent =E 4>
872              
873            
874             Baz
875            
876              
877             =cut
878              
879             sub toString
880             {
881 0     0 1   my $self = shift;
882 0           my %args = (@_);
883              
884 0 0 0       if ($args{'-formatted'} && !exists $args{'-level'})
885             {
886 0           $args{'-level'} = 0;
887 0 0         if (!exists $args{'-textformat'})
888             {
889 0           $args{'-textformat'} = 1;
890             }
891             }
892 0 0 0       if (!defined $args{'-indent'} || $args{'-indent'} =~ m/\D/xms)
893             {
894 0           $args{'-indent'} = 2;
895             }
896              
897 0           return join q{}, $self->_to_string(%args);
898             }
899              
900             sub _to_string
901             {
902 0     0     my $self = shift;
903 0           my %args = (@_);
904              
905 0           my $level = $args{'-level'};
906 0 0         my $indent = defined $level ? q{ } x $args{'-indent'} : q{};
907 0 0         my $begin = defined $level ? $indent x $level : q{};
908 0 0         my $end = defined $level ? "\n" : q{};
909              
910             # open tag
911 0           my @ret = ( $begin, '<', $self->_XML_escape($self->{name}) );
912              
913             # attributes
914 0           foreach my $key (sort keys %{ $self->{attributes} })
  0            
915             {
916 0           push @ret, (
917             q{ }, $self->_XML_escape($key), q{=},
918             q{"}, $self->_XML_escape($self->{attributes}->{$key}), q{"},
919             );
920             }
921              
922             # Empty tag?
923 0 0 0       if (@{ $self->{children} } == 0)
  0 0 0        
924 0           {
925 0           push @ret, '/>', $end;
926             }
927              
928             # Body is pure text?
929             elsif ($args{'-formatted'} && !$args{'-textformat'}
930 0           && 0 == scalar grep {$_->isa(__PACKAGE__)} @{$self->{children}})
931             {
932 0           push @ret, '>';
933 0           push @ret, map { $_->toString() } @{ $self->{children} };
  0            
  0            
934 0           push @ret, '{name}, '>', $end;
935             }
936              
937             # Body has elements
938             else
939             {
940 0           push @ret, '>', $end;
941 0           foreach my $child (@{ $self->{children} })
  0            
942             {
943 0 0         if ($child->isa(__PACKAGE__))
944             {
945 0 0         push @ret, $child->_to_string(
946             %args, -level => defined $level ? $level+1 : undef,
947             );
948             }
949             else
950             {
951 0           push @ret, $begin, $indent, $child->toString(), $end;
952             }
953             }
954 0           push @ret, $begin, '{name}, '>', $end;
955             }
956              
957 0           return @ret;
958             }
959              
960             # Private function
961             sub _XML_escape
962             {
963 0     0     my $pkg_or_self = shift;
964 0           my $text = shift;
965              
966 0 0         if (!defined $text)
967             {
968 0           $text = q{};
969             }
970 0           $text =~ s/&/&/gxms;
971 0           $text =~ s/
972 0           $text =~ s/>/>/gxms;
973 0           $text =~ s/\"/"/gxms;
974 0           return $text;
975             }
976              
977             # Private function
978             sub _CDATA_escape
979             {
980 0     0     my $pkg_or_self = shift;
981 0           my $text = shift;
982              
983             # Escape illegal "]]>" strings by ending and restarting the CDATA section
984 0           $text =~ s/ ]]> /]]>]]>
985              
986 0           return "";
987             }
988              
989             1;
990             __END__