2008-09
03

在Perl中解析XML的方法最常见的就是使用 XML::DOM 和 XML::Simple了。 XML::DOM过于庞大,而且解析结果是一个DOM树,操作也不方便。 对于小型且不复杂的XML文件,XML::DOM真是杀鸡用牛刀。 这时就轮到轻便的XML::Simple上场了。

XML::Simple如其名,真的很简单。假设XML内容如下:

<opt>
    <user login="grep" fullname="Gary R Epstein" />
    <user login="stty" fullname="Simon T Tyson" >
        <session pid="12345"/>
    </user>
    <text>This is a test.</text>
</opt>

那么只需这样写:

use XML::Simple;
use Data::Dumper;

$xml = XMLin('sample.xml');
print Dumper($xml);

就可以轻而易举地将XML解析成一个hash,然后用foreach依次处理即可。

$VAR1 = {
          'text' => 'This is a test.',
          'user' => [
                    {
                      'fullname' => 'Gary R Epstein',
                      'login' => 'grep'
                    },
                    {
                      'session' => {
                                   'pid' => '12345'
                                 },
                      'fullname' => 'Simon T Tyson',
                      'login' => 'stty'
                    }
                  ]
        };

可以发现如下规律:

  • 元素的标签名被用于hash的key。
  • 单个元素的内容作为hash的value,多个重复的元素的内容被放到一个数组引用中作为hash的value
  • 属性和子元素都以hash的key=>value对出现在元素的内容中

一个问题是,对单个元素和多个重复元素的处理结果不一致,就会导致foreach处理时比较麻烦 (需要区分是标量还是数组引用),如上面的 text 和 user 的值。 解决方法是添加选项 ForceArray => 1,就可以强制单个元素也放到数组引用中。

$xml = XMLin('sample.xml', ForceArray => 1);
print Dumper($xml);

运行结果(部分):

$VAR1 = {
          'text' => [
                    'This is a test.'
                  ],
          'user' => [
......

另一个问题是,如果你的元素属性中包含id、name或key,那么元素就不再放到数组引用中,而是放到 hash引用中。比如下面的XML,注意与上面的结果的区别:

<opt>
    <user id="grep" fullname="Gary R Epstein" />
    <user id="stty" fullname="Simon T Tyson">
        <session pid="12345"/>
    </user>
    <text>This is a test.</text>
</opt>
$VAR1 = {
          'text' => [
                    'This is a test.'
                  ],
          'user' => {
                    'grep' => {
                              'fullname' => 'Gary R Epstein'
                            },
                    'stty' => {
                              'session' => [
                                           {
                                             'pid' => '12345'
                                           }
                                         ],
                              'fullname' => 'Simon T Tyson'
                            }
                  }
        };

user的内容不再是数组引用,而是hash引用,而id='grep'也变成了key存在。

要想禁用这个功能,应当指定选项 KeyAttr => ''。这个选项就是说,解析时应该把哪些属性作为hash的key来使用, 默认值是['id', 'name', 'key']。

在XML::Simple的文档中, 所有的选项都有详细说明,而KeyAttr和ForceArray选项被标为important,可见它们是多么常用了。




这篇文章有 12 条评论了,快来一起讨论讨论吧!
#1
yisud
2008-10-28 15:32

遇到encoding=”gb2312″中文 dumper出来都是\x{4e3a}\x{4e66}这种样子的,不知道有没有XML自带的编码函数? 现在都用use encoding来处理 很麻烦 谢谢~

#2
zhangxiaoqiang
2008-12-04 18:19

我在windows系统上装了一个perl解释器,运行里边cmd,然后cd 到d:\perl\bin 然后按照你上边说的在一个目录建立一个perl文件和一个xml文件然后d:\perl\bin>perl e:\perl\sample.pl 提示错误说xml文件不存在
File does not exist: perl.xml
能告诉我这是为什么吗?

#3
charlee
2008-12-04 20:24

@zhangxiaoqiang 如果你用的程序和我的完全一样的话,那么程序里打开文件用的是’sample.xml’,没有指定路径,所以它指的是当前目录下的sample.xml。而你的当前目录是 D:\perl\bin,所以它会去找 D:\perl\bin\sample.xml,当然找不到了。
你应当先cd到 e:\perl,然后再运行 perl sample.pl

#4
zhangxiaoqiang
2008-12-15 09:51

呵呵,谢谢啦,是像你说的那样!

#5
zhangxiaoqiang
2008-12-15 10:05

顺便问一下,将一个xml文件的节点设成一个表的字段,节点的属性值作为字段的内容
格式: 字段:A|B|C|D
内容:a|b|c|d

没有思路啊!

#6
charlee
2008-12-15 11:31

@zhangxiaoqiang 你说的是不是数据库里面那种表?可以把字段作为属性名称,内容作为属性值;也可以把字段作为节点名称,内容作为节点的文本内容。如:

1) 字段作为属性名称,内容作为属性值
[library]
[book author=”aaa” year=”2008″ title=”Hello”/]
[book author=”bbb” year=”2007″ title=”World”/]
[/library]

2) 字段作为节点名称,内容作为节点的文本内容
[library]
[book]
[author]aaa[/author]
[year]2008[/year]
[title]Hello[/title]
[book]
[book]
[author]bbb[/author]
[year]2007[/year]
[title]World[/title]
[book]
[/library]

将上面的方括号看作尖括号。不知道是否回答了你的问题?

#7
zhangxiaoqiang
2008-12-15 14:05

是这样的,我得到一个xml,需要把他解析,然后把解析完的文件再入库!库结构已经有了比如一张表的字段:
colum1
colum2
colum3
这三个字段可能是xml里边的节点也可能是属性
123
123

我需要解析的文件格式:123|123|3
不知道我的需求有没有什么好的办法,谢谢啦!

#8
zhangxiaoqiang
2008-12-15 14:08

尖括号的内容都屏蔽掉了~~

#9
zhangxiaoqiang
2008-12-15 14:13

[colum1]123[/colum1]
[colum2]321[/colum2]
[value colum3=”3″/]

输出的结果我想让他这样123|321|3

#10
zhangxiaoqiang
2008-12-15 14:22

另外还有一个问题请大侠赐教:用xml::sample的出来的那个
$VAR1={……}这个怎么用foreach遍历啊,能否举个例子?

#11
charlee
2008-12-16 13:27

@zhangxiaoqiang 那就直接用XML::Simple解析之后用foreach循环结果,依次取出每个结果就行了啊。取出之后输出也好return也好直接组成SQL执行也好,可以随便使用。

foreach遍历hash:
foreach my $key (keys %$hash) {
print $hash->{$key};
}
foreach遍历array:
foreach my $value (@$array) {
print $value;
}

至于应该用哪个,用Data::Dumper把解析结果dump出来看看,{}的部分就用hash,[]部分就是数组。

#12
zhangxiaoqiang
2008-12-19 08:54

好的,我知道了,谢谢啊!

添加评论