Apr 10

[原]perl的XML::Twig模块 晴

linuxing , 14:31 , 编程 » Perl , 评论(0) , 引用(0) , 阅读(23590) , Via 本站原创 | |
    CPAN上的XML模块大概可以分三类:对XML数据提供独特接口的模块(通常有关在XML实例和Perl数据之间的转换),实现某一标准XML API的模块,和对一些特定的XML相关任务进行简化等特殊用途的模块。其中,第一种模块也可以理解为自定义标准来解析XML,接下来要讲述的就是这一种类的其中一个模块。在对比了该种类多个模块后,我决定使用XML::Twig,原因是它功能比较强大,而且简单易用,对比XML::Simple要强,既可以输入也可以输出。
    虽然XML的用途非常多,但大部分的任务可以分两组:一、从已有的XML文档中提取数据,二使用其他资源的数据创建一个新的XML文档。

一、简介
如果您想寻找一种快速、节省内存资源的方式来处理XML文档,但使用SAX接口又觉得太复杂的话,XML::Twig是其中一种解决方案。XML::Twig模块比较类似DOM接口,在其中可以找到很多类似的DOMish特征,就像一个使用XPath节点语法来实现的小型SAX处理过程。
我们同样可以参考DOM的语法,把XML文档中的内容看成树目录结构,底下是各个节点,节点也分开元素节点、属性节点和文本节点几种类型。例如:
引用
          html
          /   \
       head    body
        / \      \
   script title  div
                 / \
               h1   p


在这里head、 body节点就是html的分支(或称twig 小枝)。XML::Twig通过使用XPath的语法来具体指定某一节点位置,然后对其进行操作。

二、简单操作
1、示例XML文件
这里,为了说明具体的概念,我使用博客上提供的RSS XML文件来做源文件。通过对该文件的解析来输入和处理、输出XML。
获取该文件的方式很简单,点击首页右下角的RSS:日志
下载后,把该文件保存为feed.xml文件。
其大概的结构如下:
引用
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
  <title>博客网站名称</title>
  <link>博客网站链接</link>
  <description>简要描述</description>
  <language>zh-cn</language>
  <copyright>版权信息</copyright>
  <item>
    <link>每个日志的具体链接</link>
    <title>每个日志的标题</title>
    <author>作者信息</author>
    <category>分类信息</category>
    <pubDate>发布时间</pubDate>
    <guid>一个唯一链接标记</guid>
    <description>每个日志的简要描述</description>
  </item>
  <item>
    ......
  </item>
</channel>
</rss>

2、定义TwigRoots
当使用一个XML文件创建XML::Twig实例的时候,TwigRoots就用于接受单个has的引用,该值使用XPath语法结构来表示节点在输入XML文档中的具体问题。如果定义多个TwgRoots,则只有所有定义的节点的根节点作为最后的TwigRoots的值。
举例,把TwigRoots定义在title节点的问题,则从该节点开始开始处理:

#!/bin/perl -w
use encoding "utf-8";
use XML::Twig;

my $file = "./feed.xml";
# 定义一个XML::Twig实例
my $twig = new XML::Twig(TwigRoots => {'title' => 1},pretty_print => 'indented');
# 打开指定的XML文件
$twig->parsefile($file);
$twig->print;
print "\n";

结果:
(如果没有pretty_print => 'indented',则不会分行输出)
引用
$ perl printTwigRoots.pl
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <title><![CDATA[linuxの飘扬]]></title>
  <title><![CDATA[[转]heartbeat CRM管理程序功能示例]]></title>
  <title><![CDATA[[原]heartbeat 2.x简单配置[2]]]></title>
  <title><![CDATA[[原]heartbeat 2.x简单配置[1]]]></title>
  <title><![CDATA[[原]heartbeat 2.x基础概念]]></title>
  <title><![CDATA[[转]关于脑中风急救--放血救命法]]></title>
  <title><![CDATA[清明节的起源和习俗]]></title>
  <title><![CDATA[[原]关闭Apache的目录浏览功能]]></title>
  <title><![CDATA[[转]用好sudo]]></title>
  <title><![CDATA[[原]发布ExtMail 1.0.8/ Extman 1.0.0安装及升级rpm ]]></title>
  <title><![CDATA[[原]流量监控脚本 v1.3]]></title>
</rss>

结果输出每个title节点的内容。
当定义TwigRoots的时候,可以使用Xpath语法来指定具体的问题,处理的内容也包括该节点后的所有子节点。
例如把脚本该为下面的内容:

my $twig = new XML::Twig(TwigRoots => {'item' => 1});
my $twig = new XML::Twig(TwigRoots => {'item/title' => 1});

第一个会输出item节点下的所有内容(包括子节点信息),第二个把TwigRoots定义在<item><title>的位置,输出就不包括博客网站标题的title信息了。

3、定义TwigHandlers
上面的输出中并没有对捕捉到的XML内容进行处理,仅是简单的完全输出,而TwigHandlers则允许我们对这些内容进行加工。TwigHandlers接受一个函数的引用,对特定的节点进行修改,而不满足条件的节点不做处理,保留输出。(除非你强制删掉不符合要求的节点内容)

#!/bin/perl -w
use XML::Twig;

# 定义对item/title节点的处理函数为&item_title,接受的是函数引用
my $twig_handlers = {'item/title' => \&item_title};
my $file = "./feed.xml";
# 创建实例时,增加TwigHandlers属性的定义
my $twig = new XML::Twig(TwigRoots => {'title' => 1},
  TwigHandlers => $twig_handlers);
$twig->parsefile($file);
$twig->print;
print "\n";

# 对item/title节点的处理函数
sub item_title {
  my ($twig,$title) = @_;
  my $title_text = $title -> text;
  $title_text =~ s/<![CDATA[(.*)]>/$1/;
  # 设置其的文本节点内容
  $title -> set_text($title_text);
  # 设置其的属性节点内容
  $title -> set_att('type','blog');
}

结果:
引用
$ perl printTwigHandlers.pl
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <title><![CDATA[linuxの飘扬]]></title>
  <title type="blog">[转]heartbeat CRM管理程序功能示例</title>
  <title type="blog">[原]heartbeat 2.x简单配置[2]</title>
  <title type="blog">[原]heartbeat 2.x简单配置[1]</title>
  <title type="blog">[原]heartbeat 2.x基础概念</title>
  <title type="blog">[转]关于脑中风急救--放血救命法</title>
  <title type="blog">清明节的起源和习俗</title>
  <title type="blog">[原]关闭Apache的目录浏览功能</title>
  <title type="blog">[转]用好sudo</title>
  <title type="blog">[原]发布ExtMail 1.0.8/ Extman 1.0.0安装及升级rpm </title>
  <title type="blog">[原]流量监控脚本 v1.3</title>
</rss>

请留意,item_title函数对不属于item/title的博客网站标题节点并没有处理。

◎ 如果你需要定义多个TwigHandlers,可以这样做:
a、格式一

my $twig_handlers = {'item/title' => \&item_title,
  'item2/title' => \&item2_title};
my $twig = new XML::Twig(TwigRoots => {'title' => 1},
  TwigHandlers => $twig_handlers);

b、格式二

my $twig = new XML::Twig(TwigRoots => {'title' => 1},
  TwigHandlers => {'item/title' => \&item_title,
  'item2/title' => \&item2_title});

4、描述子节点
与DOM相同,除了可以过滤节点内容,XML::Twig模块还支持子节点的定义。
◎ 示例1:

$ cat printChild.pl
#!/bin/perl -w
use XML::Twig;

# 定义对item节点的处理函数为&item函数
my $twig_handlers = {'item' => \&item};
my $file = "./feed.xml";
# 创建实例时,增加TwigHandlers属性的定义
my $twig = new XML::Twig(TwigRoots => {'item' => 1},
  TwigHandlers => $twig_handlers);
# 打开指定的XML文件
$twig->parsefile($file);
#不要输出整个树节点
#$twig->print;
print "\n";

# 对item节点的处理函数
sub item {
  my ($twig,$item) = @_;
  # 获得item节点下的第一个子节点,并且tag是title的,如果不指定tag,则获得全部第一个子节点
  my $title = $item -> first_child('title');
  # 同样的,这次获得的是title节点的文本节点值
  my $title_text = $item -> first_child_text('title');
  $title_text =~ s/<![CDATA[(.*)]>/$1/;
  $title -> set_text($title_text);
  $title -> set_att('type','blog');
  # 输出
  $title -> print;
}

结果:
引用
$ perl printChild.pl
<title type="blog">[转]heartbeat CRM管理程序功能示例</title>
<title type="blog">[原]heartbeat 2.x简单配置[2]</title>
<title type="blog">[原]heartbeat 2.x简单配置[1]</title>
<title type="blog">[原]heartbeat 2.x基础概念</title>
<title type="blog">[转]关于脑中风急救--放血救命法</title>
<title type="blog">清明节的起源和习俗</title>
<title type="blog">[原]关闭Apache的目录浏览功能</title>
<title type="blog">[转]用好sudo</title>
<title type="blog">[原]发布ExtMail 1.0.8/ Extman 1.0.0安装及升级rpm </title>
<title type="blog">[原]流量监控脚本 v1.3</title>

◎ 示例2:
使用循环来对子节点进行遍历,只修改tag为title的节点并输出,其他不输出:

#!/bin/perl -w
use XML::Twig;
use encoding "utf-8";

# 定义对item/title节点的处理函数为&item_title函数
my $twig_handlers = {'item' => \&item};
my $file = "./feed.xml";
# 创建实例时,增加TwigHandlers属性的定义B
my $twig = new XML::Twig(TwigRoots => {'item' => 1},
  TwigHandlers => $twig_handlers,
  pretty_print => 'indented');
$twig->parsefile($file);
# 打开指定的XML文件
$twig->print;
print "\n";

# 对item/title节点的处理函数A
sub item {
  my ($twig,$item) = @_;
  # 对子节点进行遍历,需要使用children方法获得所有的子节点(数组)
  # 但不能使用first_child,因为其不带参数时,返回的是第一个子节点对象
  for my $child ($item -> children) {
    # 获得该节点的tag名称,可以使用gi、name、tag方法,是同义的
    my $child_name = $child -> name;
    if ( $child_name eq 'title' ) {
  my $child_text = $child -> text;
  $child_text =~ s/<![CDATA[(.*)]>/$1/;
  $child -> set_text($child_text);
  $child -> set_att('type','blog');
    }
  else {
    $child -> delete;
  }
  }
}

输出结果:
引用
$ perl printChild2.pl
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <item>
    <title type="blog">[转]heartbeat CRM管理程序功能示例</title>
  </item>
  <item>
    <title type="blog">[原]heartbeat 2.x简单配置[2]</title>
  </item>
  <item>
    <title type="blog">[原]heartbeat 2.x简单配置[1]</title>
  </item>
  <item>
    <title type="blog">[原]heartbeat 2.x基础概念</title>
  </item>
  <item>
    <title type="blog">[转]关于脑中风急救--放血救命法</title>
  </item>
  <item>
    <title type="blog">清明节的起源和习俗</title>
  </item>
  <item>
    <title type="blog">[原]关闭Apache的目录浏览功能</title>
  </item>
  <item>
    <title type="blog">[转]用好sudo</title>
  </item>
  <item>
    <title type="blog">[原]发布ExtMail 1.0.8/ Extman 1.0.0安装及升级rpm </title>
  </item>
  <item>
    <title type="blog">[原]流量监控脚本 v1.3</title>
  </item>
</rss>

可以对比一下两个示例输出信息的差异。

5、使用XML::Twig::Elt方法创建元素节点
与上面的类似,XML:Twig也可以根据需要创建元素节点,并给其赋予属性节点和文本节点:

$ cat printTwigElt.pl
#!/bin/perl -w
use XML::Twig;

# 定义对item节点的处理函数为&item函数
my $twig_handlers = {'item' => \&item};
my $file = "./feed.xml";
# 创建实例时,增加TwigHandlers属性的定义
my $twig = new XML::Twig(TwigRoots => {'item' => 1},
  TwigHandlers => $twig_handlers);
# 打开指定的XML文件
$twig->parsefile($file);
#不要输出整个树节点
#$twig->print;
print "\n";

# 对item节点的处理函数
sub item {
  my ($twig,$item) = @_;
  # 获得item节点下的第一个子节点,并且tag是title的,如果不指定tag,则获得全部第一个子节点
  my $title = $item -> first_child('title');
  # 同样的,这次获得的是title节点的文本节点值
  my $title_text = $item -> first_child_text('title');
  my $link_text = $item -> first_child_text('link');
  $title_text =~ s/<![CDATA[(.*)]>/$1/;
  $title -> set_text('');
  # 创建一个新a节点
  my $newlink = XML::Twig::Elt -> new('a');
  # 赋予属性节点
  $newlink -> set_att('href',$link_text);
  # 赋予文本节点
  $newlink -> set_text($title_text);
  $newlink -> paste('first_child',$title);
  # 输出
  $title -> print;
}

结果:
引用
$ perl printTwigElt.pl
<title><a href="http://www.linuxfly.org/post/365/">[转]heartbeat CRM管理程序功能示例</a></title>
<title><a href="http://www.linuxfly.org/post/364/">[原]heartbeat 2.x简单配置[2]</a></title>
<title><a href="http://www.linuxfly.org/post/363/">[原]heartbeat 2.x简单配置[1]</a></title>
<title><a href="http://www.linuxfly.org/post/362/">[原]heartbeat 2.x基础概念</a></title>
<title><a href="http://www.linuxfly.org/post/361/">[转]关于脑中风急救--放血救命法</a></title>
<title><a href="http://www.linuxfly.org/post/360/">清明节的起源和习俗</a></title>
<title><a href="http://www.linuxfly.org/post/354/">[原]关闭Apache的目录浏览功能</a></title>
<title><a href="http://www.linuxfly.org/post/353/">[转]用好sudo</a></title>
<title><a href="http://www.linuxfly.org/post/352/">[原]发布ExtMail 1.0.8/ Extman 1.0.0安装及升级rpm </a></title>
<title><a href="http://www.linuxfly.org/post/347/">[原]流量监控脚本 v1.3</a></title>

这里介绍的仅是基本的使用说明,更多的模块可用属性和方法请看perldoc XML::Twig,或CPAN上的介绍。

本次示例下载:
三、参考资料
快速开始Perl XML:接口篇
Using XML::Twig
CPAN 上的XML::Twig模块说明文档
一些更具体的示例

四、补充说明
1、处理输出格式问题
原输出时,所有XML文件为单行,加入pretty_print => 'indented'可优化格式输出结果:

my $twig = new XML::Twig(TwigRoots => {'title' => 1},pretty_print => 'indented');

2、解决源代码运行时的一个警告
信息为:
引用
Wide character in print at /usr/lib/perl5/vendor_perl/5.8.8/XML/Twig.pm line 2706.

在代码中加入:

use encoding "utf-8";

问题解决,参考为:点击

3、去掉CDATA信息
上面的代码是用于举例,实际中,若不希望捕获的信息中包含CDATA信息,可以在创建实例时,给remove_cdata一个真值,例如:

my $twig = new XML::Twig(TwigRoots => {'item/title' => 1},
  pretty_print => 'indented',
  remove_cdata => 'true');

则结果为:
引用
$ perl printTwigRoots.pl
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <title>[转]heartbeat CRM管理程序功能示例</title>
  <title>[原]heartbeat 2.x简单配置[2]</title>
  <title>[原]heartbeat 2.x简单配置[1]</title>
  <title>[原]heartbeat 2.x基础概念</title>
  <title>[转]关于脑中风急救--放血救命法</title>
  <title>清明节的起源和习俗</title>
  <title>[原]关闭Apache的目录浏览功能</title>
  <title>[转]用好sudo</title>
  <title>[原]发布ExtMail 1.0.8/ Extman 1.0.0安装及升级rpm </title>
  <title>[原]流量监控脚本 v1.3</title>
</rss>

同理,XML::Twig模块也提供了创建和删除CDATA等标签的方案。
创建:

my $elt= XML::Twig::Elt->new( 'p' => { #CDATA => 1}, 'foo < bar');

结果:
引用
<p><![CDATA[foo < bar]]></>

删除:
(以代替上面的正规表达式为例)

sub item_title {
  my ($twig,$title) = @_;
  $title -> set_remove_cdata ('#CDATA');
  # 设置其的属性节点内容
  $title -> set_att('type','blog');
}

关于CDATA的解析,见:这里
Tags: ,
发表评论
表情
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
打开HTML
打开UBB
打开表情
隐藏
记住我
昵称   密码   游客无需密码
网址   电邮   [注册]