Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using XML::Simple to build XML from an array of hashes with some keys as attributes

Tags:

xml

perl

I am using XML::Simple and I wish to convert this data to XML:

@rooms = (
   {
      id => 4,
      is_key => 0,
      name => B507,
      capacity => 35
   },
   {
      id => 5,
      is_key => 1,
      name => B502,
      capacity => 24
   }
);

I want to output this:

<rooms>
   <room id=4 is_key=0>
      <name>B507</name>
      <capacity>35</capacity>
   </room>
   <room id=5 is_key=1>
      <name>B502</name>
      <capacity>24</capacity>
   </room>
</rooms>

I don't see a way to do this with XML::Simple::XMLout. Am I missing something?

like image 725
hayesk Avatar asked Feb 19 '23 19:02

hayesk


2 Answers

I find XML::Simple unintuitive and very awkward to use. It is easy to end up just throwing random options at it to try to get it to work.

However if you are stuck with it there is a way. First of all the ForceArray option is very useful and, as the documentation says,

Check out "ForceArray" because you'll almost certainly want to turn it on

So you need to adjust your data so that it looks like ForceArray was in effect when the original XML was parsed. This just involves putting in an anonymous array all data that should be the contents of an element instead of an attribute value.

This code does what you need. The KeepRoot option just tells XMLout that the top-level hash is the root element and it doesn't have to wrap the whole thing in another element.

use strict;
use warnings;

use XML::Simple;

my @rooms = (
   {
      id => 4,
      is_key => 0,
      name => 'B507',
      capacity => 35
   },
   {
      id => 5,
      is_key => 1,
      name => 'B502',
      capacity => 24
   }
);

for my $room (@rooms) {
  for my $k (keys %$room) {
      $room->{$k} = [ $room->{$k} ] unless grep $k eq $_, qw/ is_key id /;
  }
}

my $xml = {rooms => {room => \@rooms} };

print XMLout($xml, KeepRoot => 1);

output

<rooms>
  <room id="4" is_key="0">
    <name>B507</name>
    <capacity>35</capacity>
  </room>
  <room id="5" is_key="1">
    <name>B502</name>
    <capacity>24</capacity>
  </room>
</rooms>

Update

You may prefer a solution using XML::Smart, which allows you to specify which nodes are elements and which are tags. This allows you to leave the original data in @rooms untouched.

This program accepts a similar hash reference to the XML::Simple solution, and them loops through all /rooms/room elements, setting all name and capacity child nodes to elements using set_tag.

Note that the XML is output using scalar $smart->data() because when called in list context the data method will return a second value: a boolean flag indicating whether the XML is Unicode-encoded. This doesn't appear to be documented in the POD.

You may omit the call to $smart->set_order if you're not bothered about the order in which the attributes and elements appear in the XML.

use strict;
use warnings;

use XML::Smart;

my @rooms = (
   {
      id => 4,
      is_key => 0,
      name => 'B507',
      capacity => 35
   },
   {
      id => 5,
      is_key => 1,
      name => 'B502',
      capacity => 24
   }
);

my $smart = XML::Smart->new;
$smart->{rooms} = { room => \@rooms };

for my $room (@{$smart->{rooms}{room}}) {
  $room->set_order(qw/ id is_key name capacity /);
  $room->{name}->set_tag;
  $room->{capacity}->set_tag;
}

print scalar $smart->data(noheader => 1, nometagen => 1);

output

<rooms>
  <room id="4" is_key="0">
    <name>B507</name>
    <capacity>35</capacity>
  </room>
  <room id="5" is_key="1">
    <name>B502</name>
    <capacity>24</capacity>
  </room>
</rooms>
like image 159
Borodin Avatar answered Apr 27 '23 15:04

Borodin


Hash value arrayrefs become XML element content, simple hash values become XML attribute values.

use strictures;
use XML::Simple qw(:strict);

print XMLout(
    {
        room => [
            {
                id       => 4,
                is_key   => 0,
                name     => ['B507'],
                capacity => [35],
            },
            {
                id       => 5,
                is_key   => 1,
                name     => ['B502'],
                capacity => [24],
            }
        ]
    },
    KeyAttr  => [],
    RootName => 'rooms'
);

<rooms>
  <room id="4" is_key="0">
    <capacity>35</capacity>
    <name>B507</name>
  </room>
  <room id="5" is_key="1">
    <capacity>24</capacity>
    <name>B502</name>
  </room>
</rooms>
like image 38
daxim Avatar answered Apr 27 '23 17:04

daxim