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 on开发者_JAVA百科ly 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
.
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
Maybe take a look at overriding hash_to_array? Worked out for me. http://perlmaven.com/xml-simple-sorting
I wanted to perform a natural sort against a tag key and managed to achieve by overriding XML::Simple hash_to_array. I basically copied the method from XML::Simple.pm and made a small edit for the natural sort - like this:
package MyXMLSimple; # my XML::Simple subclass
use base 'XML::Simple';
sub hash_to_array {
my $self = shift;
my $parent = shift;
my $hashref = shift;
my $arrayref = [];
my($key, $value);
if ( $parent eq "mytag" ) {
my @keys = $self->{opt}->{nosort} ? keys %$hashref : sort {$a<=>$b} keys %$hashref;
foreach $key (@keys) {
$value = $hashref->{$key};
return($hashref) unless(UNIVERSAL::isa($value, 'HASH'));
if(ref($self->{opt}->{keyattr}) eq 'HASH') {
return($hashref) unless(defined($self->{opt}->{keyattr}->{$parent}));
push @$arrayref, $self->copy_hash(
$value, $self->{opt}->{keyattr}->{$parent}->[0] => $key
);
}
else {
push(@$arrayref, { $self->{opt}->{keyattr}->[0] => $key, %$value });
}
}
} else {
my @keys = $self->{opt}->{nosort} ? keys %$hashref : sort keys %$hashref;
foreach $key (@keys) {
$value = $hashref->{$key};
return($hashref) unless(UNIVERSAL::isa($value, 'HASH'));
if(ref($self->{opt}->{keyattr}) eq 'HASH') {
return($hashref) unless(defined($self->{opt}->{keyattr}->{$parent}));
push @$arrayref, $self->copy_hash(
$value, $self->{opt}->{keyattr}->{$parent}->[0] => $key
);
}
else {
push(@$arrayref, { $self->{opt}->{keyattr}->[0] => $key, %$value });
}
}
}
return($arrayref);
}
my $xmlParser = MyXMLSimple->new(KeepRoot => 1);
$xmlParser->XMLout($self->{_xml}, KeyAttr => { ...
Sure, it's ugly, sentinel would need to set 'i' as KeyAttr and its 5 years too late but it works and now I can go do something else :)
精彩评论