Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XML::Simple output element order from complex hash

I have already seen few answers on various places, in regards to setting the order of XML elements being returned by XMLout. However, I am not able to solve a problem using those answers/examples.

I have a script that needs to output some XML data, and certain elements need to be printed in certain order. Hash is pretty complex, and I was not able to achieve any results by overriding sorted_keys in XML::Simple object. Well, I did, but not in the way I wanted.

Sample code is below, details on the problem are below the code.

#!/usr/bin/perl

use strict;
use warnings;
use XML::Simple;

package MyXMLSimple;
use base 'XML::Simple';

sub sorted_keys
{
 my ($self, $name, $hashref) = @_;
 # ... 
 return $self->SUPER::sorted_keys($name, $hashref);
}

package main;

my $xmlParser = MyXMLSimple->new;

my $items = {
 'status' => 'OK',
 'fields' => {
  'i1' => {
   'header' => 'Header 1',
   'max_size' => '3'
  },
  'i2' => {
   'header' => 'Header 2',
   'max_size' => '8'
  }
 },
 'item_list' => {
  'GGG' => {
   'index' => '3',
   'i' => 3,
   'points' => {
    'p5' => {
     'data' => '10',
    }
   },
  },
  'AAA' => {
   'index' => '1',
   'i' => 2,
   'points' => {
    'p7' => {
     'data' => '22',
    }
   },
  },
  'ZZZ' => {
   'index' => '2',
   'i' => 1,
   'points' => {
    'p6' => {
     'data' => '15',
    }
   },
  }
 }
};

my $xml = $xmlParser->XMLout($items);
print "$xml";

So, the output of this script will be this:

<opt status="OK">
  <fields name="i1" header="Header 1" max_size="3" />
  <fields name="i2" header="Header 2" max_size="8" />
  <item_list name="AAA" i="2" index="1">
    <points name="p7" data="22" />
  </item_list>
  <item_list name="GGG" i="3" index="3">
    <points name="p5" data="10" />
  </item_list>
  <item_list name="ZZZ" i="1" index="2">
    <points name="p6" data="15" />
  </item_list>
</opt>

item_list elements are printed out, and output order is alphabetically, by sorting on name attribute. Output order is AAA, GGG, ZZZ.

However, what I would need is to have the output while being sorted (numerically, from lowest to highest) on i element. So that output will be in order ZZZ, AAA, GGG.

I have no control over order in the hash (not without using Tie::... module), so I can not do it that way. If I use NoSort => 1, output will not be sorted by anything in particular, so I'll end up getting random output.

So, I am pretty sure that there must be a way to sort this out the way I want it by overriding sorted_keys subroutine. However, I couldn't get results I wanted, because sorted_keys gets invoked for each instance of item_list. When sorted_keys is invoked for opt element, then I simply have access to whole hash reference, but again no means to guarantee output ordering without relying on Tie:: module.

Now, I have managed to get this to work the way I want, by using Tie::IxHash module, then overriding sorted_keys and (re)creating a subhash item_list, by reinserting values from original hash into new (ordered) one, then deleting subhash in original hash, and substituting it with new ordered hash.

Something like this:

sub sorted_keys
{
 my ($self, $name, $hashref) = @_;
 if ($name eq "opt")
 {
  my $clist = { };
  tie %{$clist}, "Tie::IxHash";

  my @sorted_keys = sort { $hashref->{item_list}->{$a}->{i} <=> $hashref->{item_list}->{$b}->{i} } keys %{$hashref->{item_list}};
  foreach my $sorted_key (@sorted_keys)
  {
   $clist->{$sorted_key} = $hashref->{item_list}->{$sorted_key};
  }

  delete $hashref->{item_list};
  $hashref->{item_list} = $clist;
 }
 return $self->SUPER::sorted_keys($name, $hashref);
}

Although this works (and so far seems to work reliably), I do believe that there must be a way to achieve this without using Tie::IxHash module and doing all that hash recreation/reordering, and only by somehow sorting/returning certain data from within sorted_keys.

I just can't figure it out, and I don't really understand how sorted_keys is supposed to work (especially when you get different results with different/complex sets of input data ;), but I hope there is someone out there who knows this.

I mean, I have tried modifying XML/Simple.pm itself and changing sort order in the last return line of sorted_keys subroutine, but I was still getting alphanumerically sorted output. I am afraid I can't figure out how I would modify it so it doesn't sort on name but on i.

like image 220
sentinel Avatar asked Nov 12 '10 12:11

sentinel


1 Answers

I believe at this point you have outgrown XML::Simple. If you care about the order of children in an element, then it's time to use a more XML-ish module. For the style of XML creation you want, maybe XML::TreeBuilder, look at the new_from_lol method. Or XML::LibXML, XML::Twig, XML::Writer...

I also have tried mixing Tie::IxHash and XML::Simple in the past, and it wasn't pretty. you actually got pretty far here. But I believe this way lies madness

like image 129
mirod Avatar answered Nov 12 '22 14:11

mirod