【问题标题】:Building a hash tree using XML::DOM in Perl在 Perl 中使用 XML::DOM 构建哈希树
【发布时间】:2013-01-08 11:33:03
【问题描述】:

我想使用 XML::DOM 模块解析一个简单的 XML 文档以进行哈希处理。

<?xml version ="1.0"?>
<Select>
  <book>
    <prop Name = "prop1" Title = "title1" />
    <prop Name = "prop2" Title = "title2" />
  </book>
  <fruit>
    <prop Name = "prop3" Title = "title3" />
    <prop Name = "prop4" Title = "title4" />
  </fruit>
</Select>

预期的输出是-

$VAR1 = {
  Select => {
    book  => {
               prop => [
                 { Name => "prop1", Title => "title1" },
                 { Name => "prop2", Title => "title2" },
               ],
             },
    fruit => {
               prop => [
                 { Name => "prop3", Title => "title3" },
                 { Name => "prop4", Title => "title4" },
               ],
             },
  },
}

我写的代码是:

use strict;
use XML::DOM;
use Data::Dumper;

my @stack;
my %hash;
push @stack,\%hash;

my $parser = new XML::DOM::Parser;
my $doc = $parser -> parsefile('demo.xml');
my $root = $doc->getDocumentElement();
my $rootnode = $root->getTagName;

################################################################

foreach my $node ($doc->getElementsByTagName($rootnode)){
    push @stack,$stack[$#stack]->{$rootnode};
    my @childnode = $node->getChildNodes();

    foreach my $child(@childnode){
        if($child->isElementNode){
            my $childname = $child->getNodeName();
            pop(@stack);
            push @stack,$stack[$#stack]->{$rootnode} = {$childname,{}};
            my @childnodes2 = $child->getChildNodes();

            foreach my $subchild(@childnodes2){
                if($subchild->isElementNode){
                    my $subchildname = $subchild->getNodeName();

                    my $name = $subchild->getAttributes->getNamedItem('Name')->getNodeValue;
                    my $title = $subchild->getAttributes->getNamedItem('Title')->getNodeValue;
                    pop(@stack);
                    push @stack,$stack[$#stack]->{$rootnode}->{$child->getNodeName()} = {$subchildname,{}};    #{} contains $name or $title
                }
            }
        }
    }
}

print Dumper(\%hash);

我认为,我无法正确推送和弹出数组。另外,我不想使用XML::Simple 和递归。

如何在 Perl 中做到这一点?

【问题讨论】:

  • 您能否定义(用散文或伪代码),哪些规则会产生所需的输出?似乎您希望将属性视为子标签(&lt;foo bar="baz" /&gt;&lt;foo&gt;&lt;bar&gt;baz&lt;/bar&gt;&lt;/foo&gt;),并且您希望将多个标签分组到一个数组中,如果它们是同名的兄弟姐妹。或者这个解析的目的只是为了填写prop字段,而结构的其余部分是不变的?
  • 另外请详细说明“XML::Simple and recursion”是什么意思,以及为什么要避免它。
  • @amon 是的,你是对的。所需的输出是在 XML::Simple 库的帮助下生成的。我只是想在 XML::Dom 的帮助下找到一种方法来做到这一点,而不使用任何递归函数,因为当 xml 变大时会受到内存限制
  • @user2001559 编写良好的递归解决方案不太可能比迭代解决方案消耗那么多的内存。以易于理解的递归方式编写代码通常很有用,然后将代码转换为迭代(请参阅 Chapter 5 of Higher Order Perl 以获得灵感和广泛讨论)。
  • @user2001559:我假设您在使用XML::Simple 时遇到了内存问题?问题很可能是由于将 整个 XML 树保存在内存中,因为递归解析使用的任何额外内存都相对无关紧要。当然,任何创建相同散列结构的解决方案都将使用相同数量的内存,因此您将不得不考虑像XML::Twig 这样的流式 XML 解析器。不幸的是,任何其他依赖 XML::Simple 数据格式的软件都必须重写。

标签: perl hash xml-parsing xmldom


【解决方案1】:

这是一个可能的解决方案,假设整个文档遵循严格的架构,其中一个 Select 作为根,任何不同名称的子节点(不会处理冲突),以及任意数量的 props这些子节点,其中NameTitle 字段很有趣。

这是序言,我还使用了Carp 来更好地处理错误。

#!/usr/bin/perl

use strict; use warnings; use 5.012;
use XML::DOM;
use Data::Dumper;
use Carp;

这里是主要代码。它启动一个解析器(假设文档在特殊的DATA 文件句柄中),并将生成的文档传递给make_data_structure 子例程。我经常考虑让脚本die,尽早发现错误。

{
    my $xml_parser = XML::DOM::Parser->new;
    my $document_string = do{ local $/=undef; <DATA> };
    my $document = $xml_parser->parse($document_string) or die;

    my $data_structure = make_data_structure($document) or die;
    print Dumper $data_structure;
}

这是完成所有工作的子程序。它接受一个文档并返回一个符合您格式的 hashref。

sub make_data_structure {
    my ($document) = @_;
    my $root = $document->getDocumentElement;
    my $rootname = $root->getTagName // "undef";

    didnt_expect_anything(but=> "Select", as=> "the root tag", got=> $rootname)
        unless $rootname eq "Select";

    my $dsc = +{ $rootname => +{} };
    CHILD:
    for my $child ($root->getChildNodes) {
        next CHILD unless $child->isElementNode;

        my $childname = $child->getTagName
            // couldnt_get("the tag name", of=> "a $rootname child");

        $dsc->{$rootname}{$childname} = undef; # unneccessary iff we have props
        PROP:
        for my $prop ($child->getChildNodes) {
            next PROP unless $prop->isElementNode;

            my $propname = $prop->getTagName // "undef";

            die didnt_expect_anything(but=> "prop", got=> $propname)
                unless $propname eq "prop";

            my $attributes = $prop->getAttributes
                // couldnt_get("the attributes", of=> "a prop node");

            # for minimum code duplication, and maximum error handling,
            # use dataflow programming, and `map`. 
            my ($Name, $Title) =
                map { $_->getNodeValue // couldnt_get("the node value", of=>"the attribute") }
                map { $attributes->getNamedItem($_) // couldnt_get("the named item $_", of=> "the prop attributes") }
                    qw/Name Title/;
            my $propvalue = +{
                Name    => $Name,
                Title   => $Title,
            };

            push @{ $dsc->{$rootname}{$childname}{$propname} }, $propvalue;
        }
    }
    return $dsc;
}

以下是自定义错误处理子例程,以使上述代码更具表现力。

sub didnt_expect_anything {
    my %args = @_;
    my $expected = $args{but} // croak qq(required named argument "but" missing);
    my $role     = $args{as}  // "a tag name";
    my $instead  = $args{got} // croak qq(required named argument "got" missing);
    croak qq(Didn't expect anything but "$expected" as $role here, got "$instead");
}
sub couldnt_get {
    my ($what, %args) = @_;
    my $of_what = $args{of} // croak qq(required named argument "of" missing);
    croak qq(Couldn't get $what of $of_what);
}

当然,会产生正确的输出,但这不是到达那里的正确方法 - 使用 CPAN。

您的实现的部分问题是(除了缺少错误处理),您使用“堆栈”做了一些复杂的体操。

在外循环的第一次迭代之前,@stack+{}(对空哈希的引用)。

$stack[$#stack]-&gt;{$rootnode} 行访问堆栈的最后一个元素(最好写为$stack[-1]),将值视为哈希引用,并查找名为$rootnode 的条目。这评估为undef。然后你将这个 value 压入堆栈。混乱随之而来。

【讨论】:

  • 是的,因为我是 perl 新手,这些哈希数组让我感到害怕。正如您所说,代码有些硬编码。顺便谢谢...我会尝试一些改进
猜你喜欢
  • 2011-06-05
  • 2015-03-26
  • 2015-09-28
  • 2015-02-04
  • 1970-01-01
  • 1970-01-01
  • 2014-02-27
  • 1970-01-01
  • 2013-01-06
相关资源
最近更新 更多