XML::复杂散列的简单输出元素顺序

发布于 2024-10-02 11:17:42 字数 3496 浏览 2 评论 0原文

关于设置 XMLout 返回的 XML 元素的顺序,我已经在各个地方看到了很少的答案。但是,我无法使用这些答案/示例来解决问题。

我有一个脚本需要输出一些 XML 数据,并且某些元素需要按特定顺序打印。哈希非常复杂,我无法通过重写 XML::Simple 对象中的 sorted_keys 来获得任何结果。嗯,我做到了,但不是以我想要的方式。

示例代码如下,问题的详细信息位于代码下方。

#!/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 的每个实例调用。当为 opt 元素调用 sorted_keys 时,我只需访问整个哈希引用,但同样无法在不依赖 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 排序。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

慈悲佛祖 2024-10-09 11:17:42

我相信此时您已经无法满足 XML::Simple 的需求了。如果您关心元素中子元素的顺序,那么是时候使用更接近 XML 的模块了。对于您想要的 XML 创建样式,可能是 XML::TreeBuilder,请查看new_from_lol 方法。或者 XML::LibXML、XML::Twig、XML::Writer...

过去我也尝试过混合 Tie::IxHash 和 XML::Simple,但效果并不好。你实际上已经走了很远了。但我相信这条路充满了疯狂

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

北城挽邺 2024-10-09 11:17:42

也许看看重写 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 年,但它可以工作并且现在我可以去做别的事了:)

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 :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文