4

我正在使用XML::Twig::XPath处理 ITS 数据,并试图弄清楚如何使用其中的变量解析 XPath 表达式。这是我需要从 ITS规范中使用的示例:

<its:rules version="2.0">
  <its:param name="LCID">0x0409</its:param>
  <its:translateRule selector="//msg[@lcid=$LCID]" translate="yes"/>
</its:rules>

我需要能够评估 中包含的 XPath 表达式selector,变量的值是its:param元素的内容。我不知道如何做到这一点。XML::XPath的文档提到了变量(我假设它应该是上下文的一部分),它甚至有一个类来表示它们,但是文档没有说明如何在上下文中指定变量。如果可能的话,我会更加不确定如何从 XML::Twig 访问此类功能。

有谁知道如何做到这一点?或者,您能否举例说明如何将此类功能与另一个模块(例如 XML::LibXML)一起使用(其中广泛提及变量,但让我有点不确定如何使用字符串变量执行此操作)?

4

4 回答 4

3

libxml2 和 XML::LibXML 支持 XPath 2.0 路径及其变量。

use XML::LibXML               qw( );
use XML::LibXML::XPathContext qw( );

sub dict_lookup {
   my ($dict, $var_name, $ns) = @_;
   $var_name = "{$ns}$var_name" if defined($ns);
   my $val = $dict->{$var_name};
   if (!defined($val)) {
      warn("Unknown variable \"$var_name\"\n");
      $val = '';
   }

   return $val;
}

my $xml = <<'__EOI__';
<r>
<e x="a">A</e>
<e x="b">B</e>
</r>
__EOI__

my %dict = ( x => 'b' );

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

my $xpc = XML::LibXML::XPathContext->new();
$xpc->registerVarLookupFunc(\&dict_lookup, \%dict);

say $_->textContent() for $xpc->findnodes('//e[@x=$x]', $doc);
于 2013-06-25T07:46:38.953 回答
2

如果您使用的引擎仅支持 XPath 1.0 路径,则可以将该值视为语法为:

start : parts EOI
parts : part parts |
part  : string_literal | variable | other

下面从 XPath 模板生成 XPath。

sub text_to_xpath_lit {
   my ($s) = @_;
   return qq{"$s"} if $s !~ /"/;
   return qq{'$s'} if $s !~ /'/;

   $s =~ s/"/", '"', "/g;
   return qq{concat("$s")};
}

my $NCNameStartChar_class = '_A-Za-z\xC0-\xD6\xD8-\xF6\xF8-\x{2FF}\x{370}-\x{37D}\x{37F}-\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}\x{10000}-\x{EFFFF}';
my $NCNameChar_class = $NCNameStartChar_class . '\-.0-9\xB7\x{300}-\x{36F}\x{203F}-\x{2040}';
my $NCName_pat = "[$NCNameStartChar_class][$NCNameChar_class]*+";

my $xpath = '';
for ($xpath_template) {
   while (1) {
      if (/\G ( [^'"\$]++ ) /xgc) {
         $xpath .= $1;
      }
      elsif (/\G (?=['"]) /xgc) {
         /\G ( ' [^\\']*+ ' | " [^\\"]*+ " ) /sxgc
            or die("Unmatched quote\n");

         $xpath .= $1;
      }
      elsif (/\G \$ /xgc) {
         /\G (?: ( $NCName_pat ) : )?+ ( $NCName_pat ) /xgc
            or die("Unexpected '\$'\n");

         my ($prefix, $var_name) = ($1, $2);
         my $ns = $ns_map{$prefix}
            or die("Undefined prefix '$prefix'\n");

         $xpath .= text_to_xpath_lit(var_lookup($ns, $var_name));
      }
      elsif (/\G \z /xgc) {
         last;
      }
   }    
}

样品var_lookup

sub var_lookup {
   my ($ns, $var_name) = @_;
   $var_name = "{$ns}$var_name" if defined($ns);
   my $val = $params{$var_name};
   if (!defined($val)) {
      warn("Unknown variable \"$var_name\"\n");
      $val = '';
   }

   return $val;
}

未经测试。

于 2013-06-25T05:28:36.240 回答
2

这是一个完整的解决方案。

我通过从已经找到的参数名称构建一个正则表达式来回避“什么是 Qname”部分。如果有很多参数,这可能会很慢,但它在 W3C 的示例中运行良好;构建正则表达式意味着在 \Q/\E 之间转义每个名称,因此忽略名称中的元字符,按长度对名称进行排序,以便较短的名称不匹配而不是较长的名称,然后通过“|”连接它们,

限制:

  • 如果您使用之前未定义的参数,则没有错误处理,
  • 选择器中的命名空间没有处理,如果你有真实数据很容易添加,只需添加适当的map_xmlns声明,
  • 整个文档都加载到内存中,如果您想使用通用 XPath 选择器,这很难避免

这里是:

#!/usr/bin/perl

use strict;
use warnings;

use XML::Twig::XPath;

my %param;
my $mparam;
my @selectors;

my $t= XML::Twig::XPath->new( 
  map_xmlns     => { 'http://www.w3.org/2005/11/its' => 'its' },
  twig_handlers => { 'its:param' => sub { $param{$_->att( 'name')}= $_->text; 
                                          $match_param= join '|', 
                                                         map { "\Q$_\E" }
                                                         sort { lenght($b) <=> length($a) } keys %param;
                                        },
                     'its:translateRule[@translate="yes"]' =>
                                   sub { my $selector= $_->att( 'selector');
                                         $selector=~ s{\$($mparam)}{quote($param{$1})}eg;
                                         push @selectors, $selector;
                                       },
                   },
                            )
                       ->parse( \*DATA);

foreach my $selector (@selectors)
  { my @matches= $t->findnodes( $selector);
    print "$selector: ";
    foreach my $match (@matches) { $match->print; print "\n"; }
  }

sub quote
  { my( $param)= @_;
    return $param=~ m{"} ? qq{'$param'} : qq{"$param"}; 
  }
于 2013-06-25T07:47:15.597 回答
0

在 XML::XPath 中,您可以在 XML::XPath::Parser 对象上设置变量。它似乎不能通过 XML::XPath 对象直接访问;您必须使用$xp->{path_parser}未记录的 来获取它。这是一个带有字符串变量和节点集变量的示例:

use XML::XPath;
use XML::XPath::Parser;
use XML::XPath::Literal;

my $xp = XML::XPath->new(xml => <<'ENDXML');
<?xml version="1.0"?>
<xml>
    <a>
        <stuff foo="bar">
            junk
        </stuff>
    </a>
</xml>
ENDXML

#set the variable to the literal string 'bar'
$xp->{path_parser}->set_var('foo_att', XML::XPath::Literal->new('bar'));
my $nodeset = $xp->find('//*[@foo=$foo_att]');

foreach my $node ($nodeset->get_nodelist) {
    print "1. FOUND\n\n",
        XML::XPath::XMLParser::as_string($node),
        "\n\n";
}

#set the variable to the nodeset found from the previous query
$xp->{path_parser}->set_var('stuff_el', $nodeset);
$nodeset = $xp->find('/*[$stuff_el]');

foreach my $node ($nodeset->get_nodelist) {
    print "2. FOUND\n\n",
        XML::XPath::XMLParser::as_string($node),
        "\n\n";
}
于 2013-06-25T20:28:28.313 回答