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