Reputation: 993
I am using XML::Simple for parsing a XML file. Code is given below with XML file,
use Tie::IxHash;
tie %$data, "Tie::IxHash";
use XML::Simple;
use Data::Dumper;
$xml = new XML::Simple;
$data = $xml->XMLin("ship_order.xml");
print Dumper($data);
XML file, (ship_order.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<shipment>
<shiptoaddress>
<name>Prasad</name>
<address>AnnaNagar</address>
</shiptoaddress>
<items>
<quantity>5</quantity>
<price>100</price>
</items>
<items>
<quantity>6</quantity>
<price>50</price>
</items>
<num_of_items>2</num_of_items>
</shipment>
Output is not coming in order, even though I am using Tie::IxHash module.
My output:
$VAR1 = {
'num_of_items' => '2',
'shiptoaddress' => {
'name' => 'Prasad',
'address' => 'AnnaNagar'
},
'items' => [
{
'quantity' => '5',
'price' => '100'
},
{
'quantity' => '6',
'price' => '50'
}
]
};
Upvotes: 0
Views: 2687
Reputation: 22560
You could consider subclassing XML::Simple
and overwritting necessary method that creates the hash with Tie::IxHash
.
However seriously consider this answer given by the author of XML::Simple
on the CPAN forum in this thread: how to preserve XML::Simple element order...
Retaining element order is not and never will be a feature of XML::Simple. For some XML document types you might be able to hack it in by subclassing XML::Simple and overriding the new_hashref() method to supply a hashref tied to Tie::IxHash. That could solve the ABC case but it won't solve the ABA case. The short answer is that if you care about element order then you should not use XML::Simple. XML::LibXML is an excellent alternative which for many use cases is really no harder to use than XML::Simple - as described in [1]
And also what he put in his code:
##############################################################################
# Method: new_hashref()
#
# This is a hook routine for overriding in a sub-class. Some people believe
# that using Tie::IxHash here will solve order-loss problems.
#
sub new_hashref {
my $self = shift;
return { @_ };
}
[1] - Stepping up from XML::Simple to XML::LibXML
Upvotes: 3
Reputation: 64919
Ah, but you aren't using Tie::IxHash
. Or more accurately, you start off using Tie::IxHash
and then destroy it:
$data = $xml->XMLin("ship_order.xml");
This line discards the hash reference you created and assigns one from the method call to $data
.
If you care about the order of the items (and you probably shouldn't have to since any decent XML format will include an attribute that tells you the order), you will need to use a parser that returns an object, not a data structure. The object will know the order the items were seen and provide you with a children
method that returns them.
Alternatively, you could build the data structure yourself:
#!/usr/bin/perl
use strict;
use warnings;
use XML::Twig;
my $shipment;
my $t = XML::Twig->new(
twig_handlers => {
shiptoaddress => sub {
my ($t, $elt) = @_;
$shipment->{name} = $elt->first_child("name")->text;
$shipment->{address} = $elt->first_child("address")->text;
$t->purge;
},
items => sub {
my ($t, $elt) = @_;
push @{$shipment->{items}}, {
quantity => $elt->first_child("quantity")->text,
price => $elt->first_child("price")->text,
};
$t->purge;
},
},
);
$t->parse(join "", <DATA>); #FIXME: use parsefile later
use Data::Dumper;
print Dumper $shipment;
__DATA__
<?xml version="1.0" encoding="UTF-8" ?>
<shipment>
<shiptoaddress>
<name>Prasad</name>
<address>AnnaNagar</address>
</shiptoaddress>
<items>
<quantity>5</quantity>
<price>100</price>
</items>
<items>
<quantity>6</quantity>
<price>50</price>
</items>
<num_of_items>2</num_of_items>
</shipment>
Upvotes: 6