xmlperlhash

How do I maintain the order of values in an XML file with I read it with XML::Simple?


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'
                     }
                   ]
        };

Solution

  • 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>