2010-11-12 12 views
5

J'ai déjà vu quelques réponses sur différents endroits, en ce qui concerne la définition de l'ordre des éléments XML renvoyés par XMLout. Cependant, je ne suis pas capable de résoudre un problème en utilisant ces réponses/exemples.XML :: Ordre simple des éléments de sortie du hachage complexe

J'ai un script qui doit générer des données XML, et certains éléments doivent être imprimés dans un certain ordre. Hash est assez complexe, et je n'ai pas pu obtenir de résultats en remplaçant sorted_keys dans l'objet XML::Simple. Eh bien, je l'ai fait, mais pas de la façon dont je le voulais.

Exemple de code ci-dessous, les détails sur le problème sont en dessous du 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"; 

Ainsi, la sortie de ce script sera la suivante:

<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 éléments sont imprimés, et l'ordre de sortie est par ordre alphabétique, par le tri sur name attribut. L'ordre de sortie est AAA, GGG, ZZZ.

Cependant, ce dont j'ai besoin est d'avoir la sortie tout en étant triée (numériquement, du plus bas au plus haut) sur l'élément i. Donc, cette sortie sera dans l'ordre ZZZ, AAA, GGG.

Je n'ai aucun contrôle sur l'ordre dans le hachage (pas sans utiliser le module Tie::...), donc je ne peux pas le faire de cette façon. Si j'utilise NoSort => 1, la sortie ne sera pas triée par quelque chose en particulier, donc je finirai par avoir une sortie aléatoire. Donc, je suis assez sûr qu'il doit y avoir un moyen de régler cela comme je le veux en remplaçant le sous-programme sorted_keys. Cependant, je n'ai pas pu obtenir les résultats voulus, car sorted_keys est invoqué pour chaque instance de item_list. Lorsque sorted_keys est invoqué pour l'élément opt, alors j'ai simplement accès à la référence de hachage entier, mais encore une fois aucun moyen de garantir l'ordre de sortie sans compter sur le module Tie::.

Maintenant, j'ai réussi à obtenir que cela fonctionne comme je le veux, en utilisant le module Tie::IxHash, puis remplaçant sorted_keys et (re) créer un Subhash item_list, en réinsérant les valeurs de hachage d'origine dans la nouvelle (ordonnée) un, puis supprimer subhash dans le hash d'origine et le remplacer par un nouveau hash ordonné.

Quelque chose comme ceci:

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); 
} 

Bien que cela fonctionne (et donc semble bien fonctionner de manière fiable), je ne crois qu'il doit y avoir un moyen d'y parvenir sans utiliser le module Tie::IxHash et de faire tout ce que les loisirs de hachage/réorganisation, et seulement en triant/retournant certaines données de sorted_keys. Je ne peux pas comprendre, et je ne comprends pas vraiment comment sorted_keys est censé fonctionner (surtout quand vous obtenez des résultats différents avec des ensembles différents/complexes de données d'entrée;), mais j'espère qu'il y a quelqu'un là-bas qui sait cela. Je veux dire, j'ai essayé de modifier XML/Simple.pm lui-même et de changer l'ordre de tri dans la dernière ligne de retour du sous-programme sorted_keys, mais je recevais toujours la sortie triée alphanumérique. J'ai peur de ne pas savoir comment je le modifierais donc il ne triera pas sur name mais sur i.

Toute aide est très appréciée :)

+0

TLDR, bit +1 pour la minutie de (à mon humble avis, futile) effort - aurait fait +2 si je pouvais :) – DVK

Répondre

1

Je crois qu'à ce stade, vous avez trop grand XML :: Simple. Si vous vous souciez de l'ordre des enfants dans un élément, alors il est temps d'utiliser un module plus XML-ish. Pour le style de création XML que vous voulez, peut-être XML::TreeBuilder, regardez la méthode new_from_lol. Ou XML :: LibXML, XML :: Twig, XML :: Writer ...

J'ai aussi essayé de mélanger Tie :: IxHash et XML :: Simple dans le passé, et ce n'était pas joli. vous êtes en fait assez loin ici. Mais je crois que cette façon de faire est la folie

0

Peut-être jeter un oeil à surcharger hash_to_array? Travaillé pour moi. Je voulais effectuer un tri naturel par rapport à une clé d'étiquette et réussir à l'obtenir en remplaçant XML :: Simple hash_to_array. Je essentiellement copié la méthode de XML :: Simple.pm et fait une petite modification pour le tri naturel - comme ceci:

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 => { ... 

Bien sûr, il est laid, sentinelle aurait besoin de mettre en « i » comme KeyAttr et ses 5 années trop tard mais ça marche et maintenant je peux aller faire autre chose :)

+0

Alors que ce lien peut répondre la question, il est préférable d'inclure les parties essentielles de la réponse ici et de fournir le lien pour référence. Les réponses à lien uniquement peuvent devenir invalides si la page liée est modifiée – abarisone