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的宠物店,l
<?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进⾏解析很简单(请参见和程序的输出)。 ⼀个简单的$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('l')
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_document , end_document , start_element , end_element和characters 。 Perl的SAX 2.1绑定有⼀个完整的清单。 对于任何⽂档,Perl程序员都必须提供⼀组处理程序⽅法,每种事件类型⼀个。
尽管这似乎是乏味和重复的秘诀,但实际上,这是⼀个机会,您很快就会看到。
尽管XML::LibXML具有SAX接⼝,但它仍然是DOM解析器,因此它将整个⽂档读⼊内存,然后为其提供⾯向事件的接⼝。 这通常是有⽤的,但它不能处理⽆法放⼊内存或XML流(例如Jabber / XMPP)的⽂档。 因此,您将使⽤XML::SAX::ExpatXS 。 该模块包装了James Clark古⽼的expat解析器,并且⾮常稳定和快速。
假设您有⼀家新的宠物店,与本系列第1部分中使⽤的类似。 清单6代表了该商店库存的⼀部分。
清单6. Lizzie的Petatorium,l
<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. l的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";xml技术的主要应用
my $parser = XML::SAX::ParserFactory->parser(Handler => $writer);
eval { $parser->parse_file('l') };
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代替的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解析)。
perl 解析xml

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。