无法使用已注册的命名空间解析xml文件

时间:2016-03-16 17:56:15

标签: perl xslt xml-libxml

我使用XML::LibXML来解析XML文件。在访问节点元素时使用已注册的命名空间似乎存在一些问题。我打算将这个xml数据转换为CSV文件。我试图访问这里的每个元素。首先,我尝试提取<country><state>标记的属性值。以下是我附带的代码。但我收到错误XPath error : Undefined namespace prefix

use strict;
use warnings;
use Data::Dumper;
use XML::LibXML;

my $XML=<<EOF;
<DataSet xmlns="http://www.w3schools.com" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3schools.com note.xsd">
    <exec>
        <survey_region ver="1.1" type="x789" date="20160312"/>
        <survey_loc ver="1.1" type="x789" date="20160312"/>
        <note>Population survey</note>
    </exec>
    <country name="ABC" type="MALE">
        <state name="ABC_state1" result="PASS">
            <info>
                <type>literacy rate comparison</type>
            </info>
            <comment><![CDATA[
Some random text
contained here
]]></comment>
        </state>
    </country>
    <country name="XYZ" type="MALE">
        <state name="XYZ_state2" result="FAIL">
            <info>
                <type>literacy rate comparison</type>
            </info>
            <comment><![CDATA[
any random text data
]]></comment>
        </state>
    </country>
</DataSet>
EOF




my $parser = XML::LibXML->new();
my $doc  = $parser->parse_string($XML);


my $xc     = XML::LibXML::XPathContext->new($doc);
$xc->registerNs('x','http://www.w3schools.com');



foreach my $camelid ($xc->findnodes('//x:DataSet')) {

    my $country_name = $camelid->findvalue('./x:country/@name');
    my $country_type = $camelid->findvalue('./x:country/@type');

    my $state_name =  $camelid->findvalue('./x:state/@name');
    my $state_result =  $camelid->findvalue('./x:state/@result');
    print "state_name ($state_name)\n";
    print "state_result ($state_result)\n";
    print "country_name ($country_name)\n";
    print "country_type ($country_type)\n";
}

更新 如果我从XML中删除名称空间并稍微改变我的XPath似乎工作。有人可以帮助我理解差异。

foreach my $camelid ($xc->findnodes('//DataSet')) {
    my $country_name = $camelid->findvalue('./country/@name');
    my $country_type = $camelid->findvalue('./country/@type');

    my $state_name =  $camelid->findvalue('./country/state/@name');
    my $state_result =  $camelid->findvalue('./country/state/@result');
    print "state_name ($state_name)\n";
    print "state_result ($state_result)\n";
    print "country_name ($country_name)\n";
    print "country_type ($country_type)\n";
}

2 个答案:

答案 0 :(得分:1)

这将是我的方法

#!/usr/bin/perl

use strict;
use warnings;
use XML::LibXML;

my $XML=<<EOF;
<DataSet xmlns="http://www.w3schools.com" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3schools.com note.xsd">
    <exec>
        <survey_region ver="1.1" type="x789" date="20160312"/>
        <survey_loc ver="1.1" type="x789" date="20160312"/>
        <note>Population survey</note>
    </exec>
    <country name="ABC" type="MALE">
        <state name="ABC_state1" result="PASS">
            <info>
                <type>literacy rate comparison</type>
            </info>
            <comment><![CDATA[
Some random text
contained here
]]></comment>
        </state>
    </country>
    <country name="XYZ" type="MALE">
        <state name="XYZ_state2" result="FAIL">
            <info>
                <type>literacy rate comparison</type>
            </info>
            <comment><![CDATA[
any random text data
]]></comment>
        </state>
    </country>
</DataSet>
EOF


my $parser = XML::LibXML->new();
my $tree = $parser->parse_string($XML);
my $root = $tree->getDocumentElement;
my @country = $root->getElementsByTagName('country');


foreach my $citem(@country){
    my $country_name = $citem->getAttribute('name');
    my $country_type = $citem->getAttribute('type');
    print "Country Name -- $country_name\nCountry Type -- $country_type\n";
    my @state = $citem->getElementsByTagName('state');
    foreach my $sitem(@state){
        my @info = $sitem->getElementsByTagName('info');
        my $state_name = $sitem->getAttribute('name');
        my $state_result = $sitem->getAttribute('result');
        print "State Name -- $state_name\nState Result -- $state_result\n";
        foreach my $i (@info){
            my $text = $i->getElementsByTagName('type');
            print "Info --- $text\n";
        }
    }
    print "\n";
}

当然,无论如何你都可以操纵数据。如果要从文件解析,请将 parse_string 更改为 parse_file

对于xml中的各个元素,使用 getElementsByTagName 来获取标记中的元素。这应该足以让你前进

答案 1 :(得分:1)

这里似乎有两个小错误 1.以上下文节点作为参数调用XPathContext文档的findvalue 2. name是国家/地区中没有节点的属性。

因此尝试:

   my $country_name = $xc->findvalue('./x:country/@name', $camelid );

更新以更新问题如果我从XML中删除名称空间并稍微更改我的XPath似乎可以正常工作。有人可以帮助我理解其中的差异。

要了解此处发生的事情,请查看NOTE ON NAMESPACES AND XPATH

在您的情况下$camelid->findvalue('./x:state/@name');调用为节点调用findvalue。

但是:推荐的方法是使用XML :: LibXML :: XPathContext模块为XPath评估定义显式上下文,其中可以定义文档无关的前缀到命名空间映射。我上面做了什么。

<强>结论: 在节点上调用find只能起作用:如果根元素没有名称空间
(或者如果你使用与xml doucment相同的前缀,如果有的话)

相关问题