我正在使用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>
我需要能够评估selector
中包含的XPath表达式,其中变量的值是its:param
元素的内容。我不知道该怎么做。XML::XPath的文档提到了变量(我认为应该是上下文的一部分),它甚至有一个类来表示它们,但文档没有说明如何在上下文中指定变量。如果可能的话,我甚至不确定如何从XML::Twig访问这样的功能。
有人知道怎么做吗?或者,你能举一个例子,说明如何将这种功能与另一个模块(如XML::LibXML)一起使用吗?
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);
这里有一个完整的解决方案。
我避开了"what's a Qname"部分,从已经找到的参数名称构建了一个正则表达式。如果有很多参数,这可能会很慢,但它在W3C的例子中运行良好;构建regexp意味着转义\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"};
}
如果您使用的引擎只支持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-zxC0-xD6xD8-xF6xF8-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-9xB7x{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 quoten");
$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;
}
未测试。
在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. FOUNDnn",
XML::XPath::XMLParser::as_string($node),
"nn";
}
#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. FOUNDnn",
XML::XPath::XMLParser::as_string($node),
"nn";
}