面向 Perl 开发人员的 XML,第 2 部分: 使用到 Perl 的高级 XML 解析技术

面向 Perl 开发人员的 XML,第 2 部分: 使用到 Perl 的高级 XML 解析技术

树解析和事件驱动的解析

本系列文章是为那些需要一种快捷的 XML/Perl 解决方案的人编写的。第 1 部分考察了 XML::Simple,把 XML 集成到 Perl 应用程序中的一种工具。这是本系列第二篇文章,向 Perl 程序员介绍两种主要的 XML 解析技术:树解析和事件驱动的解析。

Jim Dixon (jddixon@gmail.com), 自由作家, Freelance


Jim Dixon 是一位独立承包商,他最近回到旧金山,在那里推广用 Perl 和 Ruby 实现 Web 2.0。以前,他在一家英美互联网服务提供商担任技术主管有 7 年时间,开发了许多 Java/Java EE 软件。


简介

对于很多 Perl 应用程序来说,首选的 XML 工具是 XML::Simple,这是本系列文章第 1 部分的主题(请参阅 参考资料)。XML::Simple 将 XML 输入文件转化成易于操作的 Perl 数据结构,然后将这种数据结构作为 XML 写回。但是要记住,有些情况下不能使用这种方法。

请访问 面向 Perl 和 PHP 开发人员的 XML:您可以通过该专题来了解更多与 Perl 和 PHP 相关的 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 所示。同样,最好用 admin 账户进行安装。

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

树解析

多数程序员可能发现把 XML 看成一种树结构更方便。经过数年努力,这种 XML 观点被规范化为文档对象模型(DOM),2002 年发布了 DOM Level 3。

DOM 把 XML 文档表示成双链接节点组成的树,每一层上的第一个孩子链接到父节点和所有的兄弟节点。大部分函数都在树上定义,主流编程语言都有实现。

虽然可以沿着链接遍历树,但从节约程序员时间上来说,使用 XPath 协议通常效率更高。这是一种用于遍历节点、检索节点组等的子语言。

关于 DOM 规范本身以及其他更容易阅读的 DOM 规范、XPath 及相关协议的介绍性文章,请参阅 参考资料

很多 Perl 模块能够把 XML 文档解析成 DOM 树。其中 Petr 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. XML::LibXML 解析 Rosie 的库存文件
#!/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. 树解析器输出(经过整理)
<?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

Simple API for XML (SAX) 采用了完全不同的解析方法,这种方法在一开始开销更大一些。SAX 把文档看作一系列事件,要求您告诉它如何响应每种事件。比如 start_documentend_documentstart_elementend_elementcharacters参考资料 中的 Perl SAX 2.1 Binding 提供了完整的清单。对于任何文档,Perl 程序员都必须使用一组处理程序方法,分别对应每种事件。

虽然看起来非常罗嗦,有不少重复,但实际上也是一种机会,后面您将看到。

虽然 XML::LibXML 提供了 SAX 接口,但仍然是一种 DOM 解析器,因此要把整个文档都读入内存然后再提供面向事件的接口。虽然可能有用,但是不能处理超出内存容量的 XML 文档或者 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. SAX 解析 pets2.xml
#!/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,但不要混合使用。如果 清单 7 中使用 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 处理程序。这一点已经实现,现在有数百个开放源码的 Perl 模块实现了 SAX 2.1 绑定(请参阅 参考资料)。其次,它意味着设计人员可以集中精力定义仅提供必需功能的处理程序,与其他现有的处理程序结合起来完成工作。两者都是用低廉的机器资源替代高昂的程序员工作时间。


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 解析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
4S店客户管理小程序-毕业设计,基于微信小程序+SSM+MySql开发,源码+数据库+论文答辩+毕业论文+视频演示 社会的发展和科学技术的进步,互联网技术越来越受欢迎。手机也逐渐受到广大人民群众的喜爱,也逐渐进入了每个用户的使用。手机具有便利性,速度快,效率高,成本低等优点。 因此,构建符合自己要求的操作系统是非常有意义的。 本文从管理员、用户的功能要求出发,4S店客户管理系统中的功能模块主要是实现管理员服务端;首页、个人中心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、系统管理,用户客户端:首页、车展、新闻头条、我的。门店客户端:首页、车展、新闻头条、我的经过认真细致的研究,精心准备和规划,最后测试成功,系统可以正常使用。分析功能调整与4S店客户管理系统实现的实际需求相结合,讨论了微信开发者技术与后台结合java语言和MySQL数据库开发4S店客户管理系统的使用。 关键字:4S店客户管理系统小程序 微信开发者 Java技术 MySQL数据库 软件的功能: 1、开发实现4S店客户管理系统的整个系统程序; 2、管理员服务端;首页、个人中心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、系统管理等。 3、用户客户端:首页、车展、新闻头条、我的 4、门店客户端:首页、车展、新闻头条、我的等相应操作; 5、基础数据管理:实现系统基本信息的添加、修改及删除等操作,并且根据需求进行交流信息的查看及回复相应操作。
现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本微信小程序医院挂号预约系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理人员提高事务处理效率,达到事半功倍的效果。此微信小程序医院挂号预约系统利用当下成熟完善的SSM框架,使用跨平台的可开发大型商业网站的Java语言,以及最受欢迎的RDBMS应用软件之一的MySQL数据库进行程序开发。微信小程序医院挂号预约系统有管理员,用户两个角色。管理员功能有个人中心,用户管理,医生信息管理,医院信息管理,科室信息管理,预约信息管理,预约取消管理,留言板,系统管理。微信小程序用户可以注册登录,查看医院信息,查看医生信息,查看公告资讯,在科室信息里面进行预约,也可以取消预约。微信小程序医院挂号预约系统的开发根据操作人员需要设计的界面简洁美观,在功能模块布局上跟同类型网站保持一致,程序在实现基本要求功能时,也为数据信息面临的安全问题提供了一些实用的解决方案。可以说该程序在帮助管理者高效率地处理工作事务的同时,也实现了数据信息的整体化,规范化与自动化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值