1package XML::XML2JSON;
2use strict;
3our $VERSION = '0.06';
4
5use Carp;
6use XML::LibXML;
7
8our $XMLPARSER ||= XML::LibXML->new();
9
10=head1 NAME
11
12XML::XML2JSON - Convert XML into JSON (and back again) using XML::LibXML
13
14=head1 SYNOPSIS
15
16	use XML::XML2JSON;
17
18	my $XML = '<test><element foo="bar"/></test>';
19
20	my $XML2JSON = XML::XML2JSON->new();
21
22	my $JSON = $XML2JSON->convert($XML);
23
24	print $JSON;
25
26	my $RestoredXML = $XML2JSON->json2xml($JSON);
27
28=head1 DESCRIPTION
29
30I used Google for inspiration: http://code.google.com/apis/gdata/json.html
31
32In short:
33
34=over 4
35
36=item * The response is represented as a JSON object; each nested element or attribute is represented as a name/value property of the object.
37
38=item * Attributes are converted to String properties.
39
40=item * Attribute names are prefixed with "@" so that they dont conflict with child elements of the same name.
41
42=item * Child elements are converted to Object properties.
43
44=item * Text values of tags are converted to $t properties.
45
46=back
47
48Namespace
49
50=over 4
51
52=item * If an element has a namespace alias, the alias and element are concatenated using "$". For example, ns:element becomes ns$element.
53
54=back
55
56XML
57
58=over 4
59
60=item * XML version and encoding attributes are converted to attribute version and encoding of the root element, respectively.
61
62=back
63
64=cut
65
66=head1 METHODS
67
68=head2 new
69
70Creates a new XML::XML2JSON object.
71
72It supports the following arguments:
73
74=head3 module
75
76This is the JSON module that you want to use.
77By default it will use the first one it finds, in the following order: JSON::Syck, JSON::XS, JSON, JSON::DWIW
78
79=head3 private_elements
80
81An arraryref of element names that should be removed after calling the sanitize method.
82Children of the elements will be removed as well.
83
84=head3 empty_elements
85
86An arrayref of element names that should have their attributes and text content
87removed after calling the sanitize method. This leaves any children of the elements intact.
88
89=head3 private_attributes
90
91An arrayref of attribute names that should be removed after calling the sanitize method.
92
93=head3 attribute_prefix
94
95All attributes will be prefixed by this when converting to JSON. This is "@" by default.
96You can set this to "", but if you do, any attributes that conflict with a child element name will be lost.
97
98=head3 content_key
99
100This is the name of the hash key that text content will be added to. This is "$t" by default.
101
102=head3 force_array
103
104If set to true, child elements that appear only once will be added to a one element array.
105If set to false, child elements that appear only once will be assesible as a hash value.
106
107The default is false.
108
109=head3 pretty
110
111If set to true, output will be formatted to be easier to read whenever possible.
112
113=head3 debug
114
115If set to true, will print warn messages to describe what it is doing.
116
117=cut
118
119sub new
120{
121	my $Class = shift;
122	my $Self  = {};
123	bless $Self, $Class;
124	$Self->_init(@_);
125	return $Self;
126}
127
128sub _init
129{
130	my $Self = shift;
131	my %Args = @_;
132
133	#
134	# load JSON module
135	#
136
137	my @Modules = qw(JSON::Syck JSON::XS JSON JSON::DWIW);
138
139	if ( $Args{module} )
140	{
141		my $OK = 0;
142		foreach my $Module ( @Modules )
143		{
144			$OK = 1 if $Module eq $Args{module};
145		}
146		croak "Unsupported module: $Args{module}" unless $OK;
147		@Modules = ( $Args{module} );
148	}
149
150	$Self->{_loaded_module} = "";
151
152	foreach my $Module ( @Modules )
153	{
154		eval "use $Module (); 1;";
155		unless ($@)
156		{
157			$Self->{_loaded_module} = $Module;
158			last;
159		}
160	}
161
162	croak "Cannot find a suitable JSON module" unless $Self->{_loaded_module};
163
164    warn "loaded module: $Self->{_loaded_module}";
165
166	# force arrays (this turns off array folding)
167	$Self->{force_array} = $Args{force_array} ? 1 : 0;
168
169	# use pretty printing when possible
170	$Self->{pretty} = $Args{pretty} ? 1 : 0;
171
172	# debug mode
173	$Self->{debug} = $Args{debug} ? 1 : 0;
174
175	# names
176	$Self->{attribute_prefix} = defined $Args{attribute_prefix} ? $Args{attribute_prefix} : '@';
177	$Self->{content_key}      = defined $Args{content_key}      ? $Args{content_key}      : '$t';
178
179	#
180	# sanitize options
181	#
182	# private_elements
183	$Self->{private_elements} = {};
184	if ($Args{private_elements})
185	{
186		foreach my $private_element ( @{$Args{private_elements}} )
187		{
188			# this must account for the ":" to "$" switch
189			$private_element =~ s/([^^])\:/$1\$/;
190			$Self->{private_elements}->{$private_element} = 1;
191		}
192	}
193	# empty_elements
194	$Self->{empty_elements} = {};
195	if ($Args{empty_elements})
196	{
197		foreach my $empty_element ( @{$Args{empty_elements}} )
198		{
199			# this must account for the ":" to "$" switch
200			$empty_element =~ s/([^^])\:/$1\$/;
201			$Self->{empty_elements}->{$empty_element} = 1;
202		}
203	}
204	# private_attributes
205	$Self->{private_attributes} = {};
206	if ($Args{private_attributes})
207	{
208		foreach my $private_attribute ( @{$Args{private_attributes}} )
209		{
210			# this must account for the attribute_prefix
211			$Self->{private_attributes}->{ $Self->{attribute_prefix} . $private_attribute } = 1;
212		}
213	}
214
215	return;
216}
217
218=head2 convert
219
220Takes an XML string as input.
221Returns a string of sanitized JSON.
222
223Calling this method is the same as:
224
225	my $Obj = $XML2JSON->xml2obj($XML);
226	$XML2JSON->sanitize($Obj);
227	my $JSON = $XML2JSON->obj2json($Obj);
228
229=cut
230
231sub convert
232{
233	my ( $Self, $XML ) = @_;
234
235	my $Obj = $Self->xml2obj($XML);
236
237	if ( %{ $Self->{private_elements} } || %{ $Self->{empty_elements} } || %{ $Self->{private_attributes} } )
238	{
239		$Self->sanitize($Obj);
240	}
241
242	my $JSON = $Self->obj2json($Obj);
243
244	return $JSON;
245}
246
247=head2 xml2json
248
249This is an alias for convert.
250
251=cut
252
253sub xml2json
254{
255	my ( $Self, $XML ) = @_;
256
257	my $JSON = $Self->convert($XML);
258
259	return $JSON;
260}
261
262=head2 obj2json
263
264Takes a perl data object as input.
265Return a string of equivalent JSON.
266
267=cut
268
269sub obj2json
270{
271	my ( $Self, $Obj ) = @_;
272
273	my $JSON = "";
274
275	carp "Converting obj to json using $Self->{_loaded_module}" if $Self->{debug};
276
277	if ( $Self->{_loaded_module} eq 'JSON::Syck' )
278	{
279		# this module does not have a "pretty" option
280		$JSON = JSON::Syck::Dump($Obj);
281	}
282
283	if ( $Self->{_loaded_module} eq 'JSON::XS' )
284	{
285		$JSON = JSON::XS->new->utf8->pretty( $Self->{pretty} )->encode($Obj);
286	}
287
288	if ( $Self->{_loaded_module} eq 'JSON' )
289	{
290		$JSON::UnMapping = 1;
291
292		if ( $Self->{pretty} )
293		{
294			$JSON = JSON::to_json( $Obj, { pretty => 1, indent => 2 } );
295		}
296		else
297		{
298			$JSON = JSON::to_json($Obj);
299		}
300	}
301
302	if ( $Self->{_loaded_module} eq 'JSON::DWIW' )
303	{
304		$JSON = JSON::DWIW->to_json( $Obj, { pretty => $Self->{pretty} } );
305	}
306
307	return $JSON;
308}
309
310=head2 dom2obj
311
312Takes an XML::LibXML::Document object as input.
313Returns an equivalent perl data structure.
314
315=cut
316
317sub dom2obj
318{
319	my ( $Self, $Doc ) = @_;
320
321	# this is the response element
322	my $Root = $Doc->documentElement;
323
324	# set the root element name
325	my $NodeName = $Root->nodeName;
326
327	# replace a ":" in the name with a "$"
328	$NodeName =~ s/([^^])\:/$1\$/;
329
330	# get the version and encoding of the xml doc
331	my $Version  = $Doc->version  || '1.0';
332	my $Encoding = $Doc->encoding || 'UTF-8';
333
334	# create the base objects
335	my $Obj = {};
336	my $RootObj = {    $Self->{attribute_prefix} .
337					  'version' => $Version,
338					$Self->{attribute_prefix} .
339					  'encoding' => $Encoding,
340					$NodeName => $Obj,
341	};
342
343	# grab any text content
344	my $Text = $Root->findvalue('text()');
345	$Text = undef unless $Text =~ /\S/;
346	$Obj->{ $Self->{content_key} } = $Text if defined($Text);
347
348	# process attributes
349	my @Attributes = $Root->findnodes('@*');
350	if (@Attributes)
351	{
352		foreach my $Attr (@Attributes)
353		{
354			my $AttrName  = $Attr->nodeName;
355			my $AttrValue = $Attr->nodeValue;
356
357			$Obj->{ $Self->{attribute_prefix} . $AttrName } = $AttrValue;
358		}
359	}
360	my @Namespaces = $Root->getNamespaces();
361	if (@Namespaces)
362	{
363		foreach my $Ns (@Namespaces)
364		{
365			my $Prefix = $Ns->declaredPrefix;
366			my $URI = $Ns->declaredURI;
367			$Prefix = ":$Prefix" if $Prefix;
368			$Obj->{ $Self->{attribute_prefix} . 'xmlns' . $Prefix } = $URI;
369			warn "xmlns$Prefix=\"$URI\"" if $Self->{debug};
370		}
371	}
372
373	$Self->_process_children( $Root, $Obj );
374
375	return $RootObj;
376}
377
378=head2 xml2obj
379
380Takes an xml string as input.
381Returns an equivalent perl data structure.
382
383=cut
384
385sub xml2obj
386{
387	my ( $Self, $XML ) = @_;
388
389	my $Doc = $XMLPARSER->parse_string($XML);
390
391	my $Obj = $Self->dom2obj($Doc);
392
393	return $Obj;
394}
395
396sub _process_children
397{
398	my ( $Self, $CurrentElement, $CurrentObj ) = @_;
399
400	my @Children = $CurrentElement->findnodes('*');
401
402	foreach my $Child (@Children)
403	{
404		# this will contain the data for the current element (including its children)
405		my $ElementHash = {};
406
407		# set the name of the element
408		my $NodeName = $Child->nodeName;
409
410		# replace a ":" in the name with a "$"
411		$NodeName =~ s/([^^])\:/$1\$/;
412
413		warn "Found element: $NodeName" if $Self->{debug};
414
415		# force array: all children are accessed through an arrayref, even if there is only one child
416		# I don't think I like this, but it's more predictable than array folding
417		if ( $Self->{force_array} )
418		{
419			warn "Forcing \"$NodeName\" element into an array" if $Self->{debug};
420			$CurrentObj->{$NodeName} = [] unless $CurrentObj->{$NodeName};
421			push @{ $CurrentObj->{$NodeName} }, $ElementHash;
422		}
423
424		# otherwise, use array folding
425		else
426		{
427
428			# check to see if a sibling element of this node name has already been added to the current object block
429			if ( exists $CurrentObj->{$NodeName} )
430			{
431				my $NodeType = ref( $CurrentObj->{$NodeName} );
432
433				if ( $NodeType eq 'HASH' )
434				{
435
436					# an element was already added, but it is not in an array
437					# so take the sibling element and wrap it inside of an array
438
439					warn "Found the second \"$NodeName\" child element." . " Now wrapping it into an arrayref"
440					  if $Self->{debug};
441					$CurrentObj->{$NodeName} = [ $CurrentObj->{$NodeName} ];
442				}
443				if ( $NodeType eq '' )
444				{
445
446					# oops, it looks like an attribute of the same name was already added
447					# ($Self->{attribute_prefix} eq "")
448					# the attribute is going to get overwritten :(
449
450					warn "The \"$NodeName\" attribute conflicts with a child element of the same name."
451					  . " The attribute has been lost!"
452					  . " Try setting the attribute_prefix arg to something like '\@' to avoid this"
453					  if $Self->{debug};
454					$CurrentObj->{$NodeName} = [];
455				}
456
457				# add the current element to the array
458				warn "Adding the \"$NodeName\" child element to the array" if $Self->{debug};
459				push @{ $CurrentObj->{$NodeName} }, $ElementHash;
460			}
461
462			# this is the first element found for this node name, so just add the hash
463			# this will simplify data access for elements that only have a single child of the same name
464			else
465			{
466				warn "Found the first \"$NodeName\" child element."
467				  . " This element may be accessed directly through its hashref"
468				  if $Self->{debug};
469				$CurrentObj->{$NodeName} = $ElementHash;
470			}
471		}
472
473		# grab any text content
474		my $Text = $Child->findvalue('text()');
475		$Text = undef unless $Text =~ /\S/;
476		$ElementHash->{ $Self->{content_key} } = $Text if defined($Text);
477
478		# add the attributes
479		my @Attributes = $Child->findnodes('@*');
480		if (@Attributes)
481		{
482			foreach my $Attr (@Attributes)
483			{
484				my $AttrName  = $Self->{attribute_prefix} . $Attr->nodeName;
485				my $AttrValue = $Attr->nodeValue;
486
487				# prefix the attribute name so that the name cannot conflict with child element names
488				warn "Adding attribute to the \"$NodeName\" element: $AttrName" if $Self->{debug};
489				$ElementHash->{$AttrName} = $AttrValue;
490			}
491		}
492		my @Namespaces = $Child->getNamespaces();
493		if (@Namespaces)
494		{
495			foreach my $Ns (@Namespaces)
496			{
497				my $Prefix = $Ns->declaredPrefix;
498				my $URI = $Ns->declaredURI;
499				$Prefix = ":$Prefix" if $Prefix;
500				$ElementHash->{ $Self->{attribute_prefix} . 'xmlns' . $Prefix } = $URI;
501				warn "xmlns$Prefix=\"$URI\"" if $Self->{debug};
502			}
503		}
504
505		# look for more children
506		$Self->_process_children( $Child, $ElementHash );
507	}
508
509	return;
510}
511
512=head2 sanitize
513
514Takes a perl hashref as input.
515(You would normally pass this method the object returned by the xml2obj method.)
516
517This method does not return anything. The object passed into it is directly modified.
518
519Since JSON is often returned directly to a client's browser,
520there are cases where sensitive data is left in the response.
521
522This method allows you to filter out content that you do not want to be included in the JSON.
523
524This method uses the private_elements, empty_elements and private_attributes
525arguments which are set when calling the "new" method.
526
527=cut
528
529sub sanitize
530{
531	my ( $Self, $Obj ) = @_;
532
533	my $ObjType = ref($Obj) || 'scalar';
534	carp "That's not a hashref! ($ObjType)" unless $ObjType eq 'HASH';
535
536	# process each hash key
537  KEYS: foreach my $Key ( keys %$Obj )
538	{
539		my $KeyType = ref( $Obj->{$Key} );
540
541		# this is an element
542		if ( $KeyType eq 'HASH' || $KeyType eq 'ARRAY' )
543		{
544			# check to see if this element is private
545			if ( $Self->{private_elements}->{$Key} )
546			{
547				# this is a private element, so delete it
548				warn "Deleting private element: $Key" if $Self->{debug};
549				delete $Obj->{$Key};
550
551				# the element is gone, move on to the next hash key
552				next KEYS;
553			}
554
555			# the element is a hash
556			if ( $KeyType eq 'HASH' )
557			{
558				# check to see if this element should be blanked out
559				if ( $Self->{empty_elements}->{$Key} )
560				{
561					my @Attributes = keys %{ $Obj->{$Key} };
562
563					foreach my $Attribute (@Attributes)
564					{
565						unless ( ref( $Obj->{$Key}->{$Attribute} ) )
566						{
567							warn "Deleting attribute from \"$Key\" element: $Attribute" if $Self->{debug};
568							delete $Obj->{$Key}->{$Attribute};
569						}
570					}
571				}
572
573				# go deeper
574				$Self->sanitize( $Obj->{$Key} );
575			}
576
577			# this is an array of child elements
578			if ( $KeyType eq 'ARRAY' )
579			{
580				# process each child element
581				foreach my $Element ( @{ $Obj->{$Key} } )
582				{
583					$Self->sanitize($Element);
584				}
585			}
586		}
587		# this is an attribute
588		elsif ( !$KeyType )
589		{
590			# check to see if the attribute is private
591			if ( $Self->{private_attributes}->{$Key} )
592			{
593				# this is a private attribute, so delete it
594				warn "Deleting private attribute: $Key" if $Self->{debug};
595				delete $Obj->{$Key};
596			}
597		}
598		else
599		{
600			croak "Invalid data type for key: $Key (data type: $KeyType)";
601		}
602	}
603
604	return;
605}
606
607=head2 json2xml
608
609Takes a JSON string as input.
610Returns a string of equivalent XML.
611
612Calling this method is the same as:
613
614	my $Obj = $Self->json2obj($JSON);
615	my $XML = $Self->obj2xml($Obj);
616
617=cut
618
619sub json2xml
620{
621	my ( $Self, $JSON ) = @_;
622
623	my $Obj = $Self->json2obj($JSON);
624
625	my $XML = $Self->obj2xml($Obj);
626
627	return $XML;
628}
629
630=head2 json2obj
631
632Takes a json string as input.
633Returns an equivalent perl data structure.
634
635=cut
636
637sub json2obj
638{
639	my ( $Self, $JSON ) = @_;
640
641	my $Obj;
642
643	carp "Converting json to obj using $Self->{_loaded_module}" if $Self->{debug};
644
645	if ( $Self->{_loaded_module} eq 'JSON::Syck' )
646	{
647		$Obj = JSON::Syck::Load($JSON);
648	}
649
650	if ( $Self->{_loaded_module} eq 'JSON::XS' )
651	{
652		$Obj = JSON::XS->new->utf8->decode($JSON);
653	}
654
655	if ( $Self->{_loaded_module} eq 'JSON' )
656	{
657		$Obj = JSON::from_json($JSON);
658	}
659
660	if ( $Self->{_loaded_module} eq 'JSON::DWIW' )
661	{
662		$Obj = JSON::DWIW->from_json($JSON);
663	}
664
665	return $Obj;
666}
667
668=head2 obj2dom
669
670Takes a perl data structure as input. (Must be a hashref.)
671Returns an XML::LibXML::Document object.
672
673This method expects the object to be in the same format as
674would be returned by the xml2obj method.
675
676In short:
677
678=over 4
679
680=item * The root hashref may only have a single hashref key. That key will become the xml document's root.
681
682=item * A hashref will be converted to an element.
683
684=item * An arraysref of hashrefs will be converted into multiple child elements. Their names will be set to the name of the arrayref's hash key.
685
686=item * If an attribute is prefixed by an "@", the "@" will be removed.
687
688=item * A hashkey named "$t" will be converted into text content for the current element.
689
690=back
691
692Namespace
693
694=over 4
695
696=item * If a namespace alias has a "$", it will be replaced using ":". For example, ns$element becomes ns:element.
697
698=back
699
700Caveats:
701
702=over 4
703
704=item * The order of child elements and attributes cannot be determined.
705
706=back
707
708=cut
709
710sub obj2dom
711{
712	my ( $Self, $Obj ) = @_;
713
714	croak "Object must be a hashref" unless ref($Obj) eq 'HASH';
715
716	my $Version  = $Obj->{ $Self->{attribute_prefix} . 'version' }  || $Obj->{'version'}  || '1.0';
717	my $Encoding = $Obj->{ $Self->{attribute_prefix} . 'encoding' } || $Obj->{'encoding'} || 'UTF-8';
718
719	my $Dom = $XMLPARSER->createDocument( $Version, $Encoding );
720
721	my $GotRoot = 0;
722
723 	#delete @$Obj{ grep { /^$Self->{attribute_prefix}/ } keys %$Obj };
724
725	foreach my $Key ( keys %$Obj )
726	{
727	    $Obj->{$Key} = "" unless defined($Obj->{$Key});
728
729		my $RefType = ref( $Obj->{$Key} );
730		warn "Value ref type for $Key is: $RefType (value seems to be $Obj->{$Key})" if $Self->{debug};
731
732		my $Name = $Key;
733
734		# replace a "$" in the name with a ":"
735		$Name =~ s/([^^])\$/$1\:/;
736
737		if ( $RefType eq 'HASH' )
738		{
739			warn "Creating root element: $Name" if $Self->{debug};
740
741			croak "You may only have one root element: $Key" if $GotRoot;
742			$GotRoot = 1;
743
744			my $Root = $Dom->createElement($Name);
745			$Dom->setDocumentElement($Root);
746
747			$Self->_process_element_hash( $Dom, $Root, $Obj->{$Key} );
748		}
749		elsif ( $RefType eq 'ARRAY' )
750		{
751			croak "You cant have an array of root nodes: $Key";
752		}
753		elsif ( !$RefType )
754		{
755            if ( $Obj->{$Key} ne '' )
756            {
757				unless ($GotRoot)
758				{
759					my $Root;
760					eval { $Root = $Dom->createElement($Name) };
761					if ( $@ ) {
762						die "Problem creating root element $Name: $@";
763					}
764					$Dom->setDocumentElement($Root);
765					$Root->appendText( $Obj->{$Key} );
766					$GotRoot = 1;
767				}
768            }
769            else
770            {
771              croak "Invalid data for key: $Key";
772            }
773		}
774		else
775		{
776		    warn "unknown reference: $RefType";
777		}
778	}
779
780	return $Dom;
781}
782
783=head2 obj2xml
784
785This method takes the same arguments as obj2dom.
786Returns the XML as a string.
787
788=cut
789
790sub obj2xml
791{
792	my ( $Self, $Obj ) = @_;
793
794	my $Dom = $Self->obj2dom($Obj);
795
796	my $XML = $Dom->toString( $Self->{pretty} ? 2 : 0 );
797
798	return $XML;
799}
800
801sub _process_element_hash
802{
803	my ( $Self, $Dom, $Element, $Obj ) = @_;
804
805	foreach my $Key ( keys %$Obj )
806	{
807		my $RefType = ref( $Obj->{$Key} );
808
809		my $Name = $Key;
810
811		# replace a "$" in the name with a ":"
812		$Name =~ s/([^^])\$/$1\:/;
813
814		# true/false hacks
815		if ($RefType eq 'JSON::XS::Boolean')
816		{
817		    $RefType = "";
818		    $Obj->{$Key} = 1 if ("$Obj->{$Key}" eq 'true');
819		    $Obj->{$Key} = "" if ("$Obj->{$Key}" eq 'false');
820		}
821		if ($RefType eq 'JSON::true')
822		{
823		    $RefType = "";
824		    $Obj->{$Key} = 1;
825		}
826		if ($RefType eq 'JSON::false')
827		{
828		    $RefType = "";
829		    $Obj->{$Key} = "";
830		}
831
832		if ( $RefType eq 'ARRAY' )
833		{
834			foreach my $ChildObj ( @{ $Obj->{$Key} } )
835			{
836				warn "Creating element: $Name" if $Self->{debug};
837
838				my $Child = $Dom->createElement($Name);
839				$Element->addChild($Child);
840
841				$Self->_process_element_hash( $Dom, $Child, $ChildObj );
842			}
843		}
844		elsif ( $RefType eq 'HASH' )
845		{
846			warn "Creating element: $Name" if $Self->{debug};
847
848			my $Child = $Dom->createElement($Name);
849			$Element->addChild($Child);
850
851			$Self->_process_element_hash( $Dom, $Child, $Obj->{$Key} );
852		}
853		elsif ( !$RefType )
854		{
855			if ( $Key eq $Self->{content_key} )
856			{
857				warn "Appending text to: $Name" if $Self->{debug};
858
859				my $Value = defined($Obj->{$Key}) ? $Obj->{$Key} : q{};
860
861				$Element->appendText( $Value );
862			}
863			else
864			{
865
866				# remove the attribute prefix
867				my $AttributePrefix = $Self->{attribute_prefix};
868				if ( $Name =~ /^\Q$AttributePrefix\E(.+)/ )
869				{
870					$Name = $1;
871				}
872
873				my $Value = defined($Obj->{$Key}) ? $Obj->{$Key} : q{};
874
875				warn "Creating attribute: $Name" if $Self->{debug};
876				$Element->setAttribute( $Name, $Value );
877			}
878		}
879		else
880		{
881			croak "Invalid value for element $Key (reference type: $RefType)";
882		}
883	}
884
885	return;
886}
887
888=head1 CAVEATS
889
890The order of child elements is not always preserved.
891This is because the conversion to json makes use of hashes in the resulting json.
892
893=head1 AUTHOR
894
895Ken Prows - perl(AT)xev.net
896
897=head1 COPYRIGHT & LICENSE
898
899Copyright (C) 2007-2008 Ken Prows
900
901This library is free software; you can redistribute it and/or modify
902it under the same terms as Perl itself.
903
904=cut
905
9061;
907
908