Search code examples
xmlperlxml-simple

Parse XML data and append a new element using XML::Simple


I'm using XML::Simple and XML::Dumper. I'm well aware of the issues surrounding the former module. If I could use an alternative library, I would.

What I want to achieve is to load the XML, then append lines to it. This is my XML structure before the script has run.

<person>
  <appearance>
     <param name="height" value="6,3"/>
  </appearence>
</person>

The script, or what I intended to code, should load this XML from a file, then append a <param> element onto <appearence>. I've attempted it using this code:

use warnings;

use XML::Simple;
use XML::Dumper;

my $xml = XMLin('xml_import.xml');

$xml->{appearence} .= qq{<param name="age" value="22" />};
my $new_xml = XMLout($xml, noattr => 1, NoEscape => 1);

open(FILE, ">xml_import.xml");
print FILE $new_xml;
close FILE;

Unfortunately the output is this:

<opt>
  <appearence>HASH(0x1722190)<param name="age" value="22" /></appearence>
</opt>

Not only do I lose my original <person> tags to <opt> but that HASH string replaces what I assume what was the existing <param> element. I've read over the documentation for XML::Simple but I cannot spot what argument should be used to prevent this from happening.


Solution

  • The point of using XML::Simple is to use Perl data structures, not XML in your code.

    Your problems show exactly why the module is discouraged. You have to use ForceArray to be able to easily change the number of the elements in a parent, and you have to clear KeyAttr to block a special handling of the name attribute.

    #!/usr/bin/perl
    use warnings;
    use strict;
    
    use XML::Simple;
    
    my $xml = << '__XML__';
    <person>
      <appearance>
         <param name="height" value="6,3"/>
      </appearance>
    </person>
    __XML__
    
    my $simple = XMLin($xml, ForceArray => 1,
                             KeepRoot   => 1,
                             KeyAttr    => [],
                      );
    
    push @{ $simple->{person}[0]{appearance}[0]{param} }, { name  => 'age',
                                                            value => 22,
                                                          };
    print XMLout($simple, KeepRoot => 1);
    

    For comparison, the same task in XML::XSH2, a wrapper around XML::LibXML:

    open file.xml ;
    my $p := insert element param append /person/appearance ;
    set $p/@name 'age' ;
    set $p/@value 22 ;
    save :b ;