XML :: Простой порядок вывода элементов из сложного хэша - PullRequest
6 голосов
/ 12 ноября 2010

Я уже видел несколько ответов в разных местах, касающихся установки порядка элементов XML, возвращаемых XMLout. Однако я не могу решить проблему, используя эти ответы / примеры.

У меня есть скрипт, который должен выводить некоторые XML-данные, и некоторые элементы должны быть напечатаны в определенном порядке. Хеш довольно сложен, и я не смог достичь каких-либо результатов, переопределив sorted_keys в XML::Simple объекте. Ну, я сделал, но не так, как я хотел.

Пример кода приведен ниже, подробности проблемы приведены ниже кода.

#!/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";

Итак, вывод этого скрипта будет таким:

<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 элементы распечатываются, а порядок вывода в алфавитном порядке, путем сортировки по атрибуту name. Порядок вывода: AAA, GGG, ZZZ.

Тем не менее, мне нужно, чтобы выходные данные были отсортированы (численно, по убыванию) по элементу i. Так что вывод будет в порядке ZZZ, AAA, GGG.

У меня нет контроля над порядком в хэше (не без использования модуля Tie::...), поэтому я не могу сделать это таким образом. Если я использую NoSort => 1, выходные данные не будут сортироваться по чему-либо конкретному, поэтому я получу случайный вывод.

Итак, я почти уверен, что должен быть способ уладить это так, как я хочу, переопределив подпрограмму sorted_keys. Однако я не смог получить желаемые результаты, потому что sorted_keys вызывается для каждого экземпляра item_list. Когда sorted_keys вызывается для элемента opt, тогда у меня просто есть доступ ко всей ссылке на хэш, но опять же нет средств гарантировать порядок вывода без использования модуля Tie::.

Теперь мне удалось заставить это работать так, как я хочу, используя модуль Tie::IxHash, затем переопределяя sorted_keys и (повторно) создавая хеш item_list, повторно вставляя значения из исходного хэша в новый ( упорядоченный) один, затем удаляя вложенный хеш в исходном хеше и заменяя его новым упорядоченным хешем.

Примерно так:

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

Хотя это работает (и, похоже, пока работает надежно), я считаю, что должен быть способ достичь этого без использования модуля Tie::IxHash и выполнения всего этого хэша, воссоздания / переупорядочения и только путем какой-либо сортировки / возврата определенные данные изнутри sorted_keys.

Я просто не могу понять, и я не совсем понимаю, как sorted_keys должен работать (особенно когда вы получаете разные результаты с разными / сложными наборами входных данных;), но я надеюсь, что есть кто-то там, кто знает это.

Я имею в виду, я пытался изменить сам XML/Simple.pm и изменить порядок сортировки в последней строке возврата подпрограммы sorted_keys, но я все еще получал буквенно-отсортированный вывод. Боюсь, я не могу понять, как бы я изменил его, чтобы он не сортировался по name, а по i.

Ответы [ 2 ]

2 голосов
/ 12 ноября 2010

Полагаю, на данный момент вы переросли XML :: Simple.Если вы заботитесь о порядке дочерних элементов, тогда самое время использовать модуль XML-ish.Для желаемого стиля создания XML, возможно XML :: TreeBuilder , посмотрите метод new_from_lol.Или XML :: LibXML, XML :: Twig, XML :: Writer ...

В прошлом я также пытался смешивать Tie :: IxHash и XML :: Simple, и это было не красиво.Вы на самом деле довольно далеко здесь.Но я верю, что в этом и заключается безумие

0 голосов
/ 07 июня 2016

Может быть, посмотрите на переопределение hash_to_array? Получилось для меня. http://perlmaven.com/xml-simple-sorting

Я хотел выполнить естественную сортировку ключа тега, и мне удалось добиться этого путем переопределения XML :: Simple hash_to_array. Я в основном скопировал метод из XML :: Simple.pm и сделал небольшое редактирование для естественной сортировки, например:

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

Конечно, это уродливо, стражу нужно было бы установить 'i' в качестве KeyAttr и на 5 лет опоздать, но это работает, и теперь я могу пойти и сделать что-то еще:)

...