【问题标题】:How to find nodes case-insensitive using XML::LibXML如何使用 XML::LibXML 查找不区分大小写的节点
【发布时间】:2016-02-14 16:29:33
【问题描述】:

我需要在需要不区分大小写的 XML 文件中查找节点。以下代码有效,但前提是没有元素为小写:

my $dom = XML::LibXML->new->parse_fh(*DATA);
my $xpc = XML::LibXML->XPathContext->new( $dom->documentElement );
my @invoices = $xpc->findnodes( "/ALLINVOICES/INVOICES/INVOICE" );

__DATA__
<ALLINVOICES>
  <INVOICES>
    <INVOICE number="12345">
       <CUSTOMER>Mr Fubar</CUSTOMER>
    </INVOICE>
  </INVOICES>
</ALLINVOICES>

如何修复它以使其也接受&lt;allinvoices&gt;&lt;invoices&gt;&lt;invoice&gt;

【问题讨论】:

  • 作为变体/*[translate(name(),'alinvoice','ALINVOICES')='ALLINVOICES']...
  • 解决方案是否必须支持混合大小写?如果没有,请使用 (/ALLINVOICES/INVOICES/INVOICE|/allinvoices/invoices/invoice) 作为 XPath 表达式。

标签: xml perl xpath xml-libxml


【解决方案1】:

将元素名称标准化为小写的字符串预处理阶段可能会对您有所帮助:

my $xmlstring = '';
{
    local $/;
    $xmlstring = <DATA>;
}

#
# Turns all element names into lowercase.
# Works as well with uppercase ( replace lc with uc )
#
# !!! The usual caveats wrt processing semistructured data with regexen apply (ie. don't try more complex transformations purely by changing the regex pattern )
#
$xmlstring =~ s#(<[/]?[^/>[:space:]]+)#lc($1)#eg; # all element names

my $dom = XML::LibXML->new->parse_string( $xmlstring);
# ...

注意

所提出的解决方案不正确地处理 cmets 和 cdata 部分(正如@ikegami 所指出的那样)。根据the specs,为了安全起见,元素名称的第一个字符必须属于以下字符类:

  [:_a-zA-Z\x{c0}-\x{d6}\x{d8}-\x{f6}\x{f8}-\x{ff}\x{0370}-\x{037d}\x{037f}-\x{1fff}\x{200c}\x{200d}\x{2070}-\x{218f}\x{2c00}-\x{2fef}\x{3001}-\x{d7ff}\x{f900}-\x{fdcf}\x{fdf0}-\x{fffd}\N{U+10000}-\n{U+EFFFF}]

这个怪物将被插入在上面代码部分的正则表达式模式中的[/]?[^/&gt;[:space:]]* 之间(观察改变的重复修饰符)。

【讨论】:

  • 注意:Clobbers CDATA 部分和 cmets。
  • @ikegami:对。感谢您指出这个问题,将相应地更新答案。
【解决方案2】:

XML 和 XPath 始终区分大小写,因此您需要编写将字符串转换为大写或小写的代码来比较它们。我认为LibXML::XPathContext 允许您注册其他函数,这样您就可以在 Perl 中编写一个函数,您可以从 XPath 调用该函数,其中包含要比较的节点和名称,并根据需要返回 true 或 false:

$xpc->registerFunction('tn', sub { my ($node,$name) = @_; if (lc($node->item(0)->localName) eq $name) { return XML::LibXML::Boolean->True; } else { return XML::LibXML::Boolean->False;} });

my @invoices = $xpath->findnodes('/*[tn(., "allinvoices")]/*[tn(., "invoices")]/*[tn(., "invoice")]');

然而,在编写(大量)长 XPath 表达式时,这仅比在 XPath 中使用 translate 略短,正如评论中所建议的那样。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-23
    • 1970-01-01
    • 1970-01-01
    • 2019-11-27
    • 1970-01-01
    相关资源
    最近更新 更多