perl 解析xml_使用Perl的高级XML解析技术

perl 解析xml

对于种类繁多的Perl应用程序,选择的XML工具是XML::Simple ,这是本系列第1部分的主题(请参阅参考资料 )。 XML::Simple将XML输入文件转换为易于操作的Perl数据结构,并将这些数据结构写回XML。 但是请记住,这种方法在某些情况下不起作用。

当您需要在内存中构建XML文档的表示形式,然后以相当复杂或不可预测的方式搜索或转换XML文档时, XML::Simple并不是最好的选择,这就是进行树解析的地方。如果XML文档将无法容纳在内存中或者是长度未知的流,因此不能使用XML::Simple 。 您必须使用事件驱动的解析器。 最初,大多数人发现事件驱动的解析器有些奇怪,但是一旦您习惯了这种解析方式,SAX可能会成为您的首选工具。

本文的其余部分将介绍使用Perl解析XML的这两种高级方法。

入门

您将需要一些开源Perl模块才能关注本文。 通常,您会通过以下两种方式之一获得这些信息:如果您使用的是Windows,请使用ppm; 如果您的操作系统是UNIX®或Linux™,请转至CPAN( 有关链接,请参阅参考资料)。 如果您不熟悉这些存储库,那么本系列的第1部分可以向您介绍它们。

清单1显示了如何在UNIX / Linux下获得模块。 当然,最好以root用户身份进行操作,以使这些模块可用于系统上的所有帐户。 这些模块具有依赖关系,其中某些依赖关系可能在您的系统上不存在。 如果cpan配置正确(follow = yes),则依赖项将自动安装。

清单1.从CPAN获取本文中使用的模块
$ perl -MCPAN -e shell
cpan> install XML::LibXML XML::SAX::Base XML::SAX::ExpatXS XML::SAX::Writer
cpan> quit

在Windows下,它甚至更简单,如清单2所示。再次,最好由管理员帐户完成安装。

清单2.使用PPM获取模块
$ppm install XML::LibXML XML::SAX::Base XML::SAX::ExpatXS XML::SAX::Writer

树解析

大多数程序员可能会觉得将XML视为树形结构很舒服。 这种XML视图在经过多年的过程中被正式化为文档对象模型(DOM)。 DOM等级3已于2002年达到。

DOM将XML文档表示为双向链接节点的树,每个级别的第一个子级链接到其父级,再跨到同级。 树上定义了一大套功能,并以主要的编程语言实现。

尽管您可以通过链接来导航DOM树,但是从程序员的角度来讲,使用XPath协议通常更有效。 这是一个子语言,允许导航到节点,检索节点集等。

请参阅相关的主题为指针指向DOM规范本身,更具可读性的介绍,向DOM规范,XPath和相关协议。

许多Perl模块可以将XML文档解析为DOM树。 这些切赫Pajas的的XML::LibXML是最好的(见一个相关主题 )。 它包装了Gnome项目的libxml2,它是一个多方面的包,其中包括DOM解析器,XPath的部分实现和SAX2的实现(如下所述 )。

清单3是您在本系列的第1部分中使用的XML文件(请参阅参考资料 ),在其中使用XML::Simple对其进行了解析,并对表示形式作为Perl数据结构进行了更改,然后使用XML::Simple将其转换为文本形式的XML。

清单3. Rosie的宠物店,pets.xml
<?xml version='1.0'?>
<pets>
  <cat>
    <name>Madness</name>
    <dob>1 February 2004</dob>
    <price>150</price>
  </cat>
  <dog>
    <name>Maggie</name>
    <dob>12 October 2002</dob>
    <price>75</price>
    <owner>Rosie</owner>
  </dog>
  <cat>
    <name>Little</name>
    <dob>23 June 2006</dob>
    <price>25</price>
  </cat>
</pets>

使用XML::LibXML进行解析很简单(请参见清单4清单5中程序的输出)。 一个简单的$parser->parse_file为DOM模型创建XML树结构。 您定义了一个简单的Perl子例程,以将一个子元素添加到树中的节点,然后使用它来构造代表单个宠物的子树。 然后,此子例程addPet()用于向清单中添加一对新宠物,即沙鼠和仓鼠。

清单4. Rosie的股票的XML::LibXML解析
#!/usr/bin/perl -w
use strict;
use XML::LibXML;

my $parser = XML::LibXML->new;
my $doc    = $parser->parse_file('pets.xml')
                or die "can't parse Rosie's stock file: $@";
my $root = $doc->documentElement();
sub addSubElm($$$) {
    my ($pet, $name, $body) = @_;
    my $subElm = $pet->addNewChild('', $name);
    $subElm->addChild( $doc->createTextNode($body) );
}
sub addPet($$$$) {
    my ($type, $name, $dob, $price) = @_;
    # addNewChild is non-compliant; could use addSibling instead
    my $pet = $root->addNewChild('', $type);
    addSubElm ( $pet, 'name', $name );
    addSubElm ( $pet, 'dob',  $dob  );
    addSubElm ( $pet, 'price', $price );
}    
addPet('gerbil',  'nasty', '15 February 2006', '5');
addPet('hamster', 'boris', '5 July 2006',      '7.00');

my @nodeList = $doc->getElementsByTagName('price');
foreach my $priceNode (@nodeList) {
    my $curPrice = $priceNode->textContent;
    my $newPrice = sprintf "%6.2f", $curPrice * 1.2;
    my $parent = $priceNode->parentNode;
    my $newPriceNode = XML::LibXML::Element->new('price');
    $newPriceNode->addChild ( $doc->createTextNode( $newPrice ) );
    $parent->replaceChild ( $newPriceNode, $priceNode );
}
print $doc->toString(1);        # pretty print

为了说明您对DOM的掌握,然后获取树中价格节点的引用列表,并将每个价格提高20%。 因为您可以用一个以上的文本节点表示一个元素中的文本(价格),所以最简单的方法是从该节点获取价格,对其进行重新格式化并重新格式化,然后完全替换原始节点,而不是尝试将其更改到位。 当然,这比第1部分中的Perl代码中的相同转换要复杂得多。

清单5.整理后的Tree Parser输出
<?xml version="1.0"?>
<pets>
  <cat>
    <name>Madness</name> <dob>1 February 2004</dob> 
<price>180.00</price>
  </cat>
  <dog>
    <name>Maggie</name> <dob>12 October 2002</dob> <price> 
90.00</price>
    <owner>Rosie</owner>
  </dog>
  <cat>
    <name>Little</name> <dob>23 June 2006</dob> <price> 
30.00</price>
  </cat>
  <gerbil>
    <name>nasty</name><dob>15 February 2006</dob><price>  
6.00</price>
  </gerbil>
  <hamster>
    <name>boris</name><dob>5 July 2006</dob><price>  
8.40</price>
  </hamster>
</pets>

当您使用更常规的树解析器处理XML时,这是典型的。 将XML格式的源文本形式转换为DOM树。 要浏览树,请遍历节点,从一个链接到另一个链接,或使用类似XPath的命令来检索对节点的引用集。 然后,您可以使用这些引用来编辑节点。 然后,您可以将树写回到磁盘或漂亮地打印它。

对于更小和更简单的树,就工程成本而言,使用XML::Simple通常更便宜。 但是,如果XML文档非常复杂,则诸如getElementsByTagName之类的方法的可用性会偏向于XML::LibXML 。 尽管此方法的运行速度可能比手工制作的Perl和XML::Simple慢,但您不必编写它,也不必调试它。

基于事件的解析:SAX

XML的简单API(SAX)采用了完全不同的解析方法,该方法最初开销较大。 SAX将文档视为一系列事件,并要求您告诉它如何响应每个事件。 此类事件包括start_documentend_documentstart_elementend_elementcharacters 。 Perl的SAX 2.1绑定相关信息有一个完整的清单。 对于任何文档,Perl程序员都必须提供一组处理程序方法,每种事件类型一个。

尽管这似乎是乏味和重复的秘诀,但实际上,这是一个机会,您很快就会看到。

尽管XML::LibXML具有SAX接口,但它仍然是DOM解析器,因此它将整个文档读入内存,然后为其提供面向事件的接口。 这通常是有用的,但它不能处理无法放入内存或XML流(例如Jabber / XMPP)的文档。 因此,您将使用XML::SAX::ExpatXS 。 该模块包装了James Clark古老的expat解析器,并且非常稳定和快速。

假设您有一家新的宠物店,与本系列第1部分中使用的类似。 清单6代表了该商店库存的一部分。

清单6. Lizzie的Petatorium,pets2.xml
<stock>
<item type="iguana" cost="124.42" location="stockroom" age="1"/>
<item type="pig" cost="15" location="floor" age="0.5"/>
<item type="parrot" cost="700" location="cage" age="6"/>
<item type="pig" cost="117.50" location="floor" age="3.2"/>
</stock>

要使用SAX2对此进行解析,您需要一些东西来处理解析器产生的事件。 最简单的事件处理程序是编写器,它在每个事件上输出一些文本。 清单7中的代码解析您的新XML。

清单7. pets2.xml的SAX解析
#!/usr/bin/perl -w
#use strict;
use XML::SAX::ParserFactory;
use XML::SAX::Writer;
my $writer = XML::SAX::Writer->new;

$XML::SAX::ParserPackage = "XML::SAX::ExpatXS";
my $parser = XML::SAX::ParserFactory->parser(Handler => $writer);

eval { $parser->parse_file('pets2.xml') };
die "can't parse Lizzie's stock file: $@"   if $@;

然后,XML产生清单8所示的输出。

清单8. SAX解析器输出
<?xml version='1.0'?><stock>
  <item cost='124.42' location='stockroom' type='iguana' age='1' />
  <item cost='15' location='floor' type='pig' age='0.5' />
  <item cost='700' location='cage' type='parrot' age='6' />
  <item cost='117.50' location='floor' type='pig' age='3.2' />
</stock>

使用ExpatXS时需要注意以下几点:

  • 确保所有工具均为SAX或SAX2:请勿尝试混用。 如果您使用XML::Handler::YAWriter代替清单7中XML::SAX::Writer ,那么您将不会收到任何错误消息,但是输出将大部分是垃圾。 由于ExpatXS是SAX2解析器,因此必须将其与SAX2编写器一起使用。
  • 要检查解析器错误,请将解析包装为eval ,然后测试$@而不是$!
  • 您必须先设置处理程序,然后再使用它们。 重要的是要理解,尽管程序员将SAX解析器可视化为从左到右执行的管道( 下面详细介绍了这一点),但初始化必须从右到左完成。 也就是说,管道看起来像P> W,因此您需要以相反的顺序进行初始化,先是W,然后是P。

驱动程序和过滤器

SAX的天才从这里开始。 SAX定义了一个事件流:解析器生成一系列事件,并将每个事件传递给处理程序。 想象一下一个看起来像一个或两个都一样的抽象模块。 像解析器一样,它可以生成SAX事件。 但是它也是一个处理程序,可以通过切换帽子,担当其解析器角色并将事件传递给下一个处理程序来处理任何标准SAX事件。 也就是说,它定义了一组仅传递事件的默认方法。 处理这些方法的模块是XML::SAX::Base

为了定义任何可能的SAX事件处理程序,程序员扩展了XML::SAX::Base并覆盖了任何感兴趣的方法。 其他事件刚刚传递。 可以将此类事件处理程序链接在一起,以便您可以像UNIX命令行一样构建管道。 处理程序上有定义明确的接口,内容非常明确:XML。

而且,您可以在管道的两端采用相同的方法。 首先,生成器是SAX2解析器,它使用XML文档并生成事件。 实际上,生成器可以是生成SAX事件的任何东西。 例如,您可以编写一个模块,该模块读取数据库中的表并输出SAX事件流。 (这也以XML::Generator::DBI 。)

按照惯例,管道的另一端使用SAX事件并输出文档。 XML::SAX::Writer就是这样做的。 但是处理程序可以轻松地写入数据库( XML::SAX::DBI )。

所有这些提供了两个主要好处。 首先,它鼓励开发以简单方式转换事件流的SAX处理程序。 这已经发生了; 现在有数百种实现SAX 2.1绑定的开源Perl模块(请参阅参考资料 )。 其次,这意味着设计人员可以将精力集中在指定处理程序上,这些处理程序提供与现有处理程序一起完成工作所需的最小功能。 两者都为廉价的程序员时间交换了廉价的机器资源。

XML::SAX::Base更详细

使用Kip Hampton的XML::SAX::Base设计处理程序涉及两个简单步骤。 首先,处理程序必须扩展基类。 其次,程序员必须根据需要重写基本方法。 然后,您可以放弃事件,或者必须调用在基类中重写的方法。 处理程序必须调用超类中的方法,而不是重写模块中的方法(请参见清单9)。

清单9.使用XML::SAX::Base
package XyzHandler;
  use base qw(XML::SAX::Base); # extend it

  sub start_element {          # override methods as necessary
    my $self = shift;
    my $data = shift;          # parameter is a reference to a hash
    # transform/extract/copy data
    $self->SUPER::start_element($data);
  }

摘要

在这个由三部分组成的系列文章的第2部分中,我向您简要介绍了XML解析这个非常复杂的世界。

首先,它向您展示了如何将XML文档转换为内存中的对象树。 最初,大多数程序员发现此方法更自然,并且只要数据适合内存,它在许多方面确实更加方便。

然后,它向您介绍了SAX和基于事件的解析,这是您的XML文档非常大或无休止的流时必须采取的方法。 事实证明,为处理这些情况而开发的工具使它们具有完全不同的编程风格,事实证明该编程风格非常丰富:SAX管道。

本系列的下一篇文章将展示如何在更复杂的应用程序中使用这两种方法(DOM和SAX解析)。


翻译自: https://www.ibm.com/developerworks/web/library/x-xmlperl2/index.html

perl 解析xml

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值