【发布时间】:2016-09-08 13:16:11
【问题描述】:
我正在处理一些我想在实际使用它们之前修改的 XPath 字符串。对于那些不熟悉 XPath 的人来说,XPath 是——简而言之——一种类似于 XML 结构的方式,它经常被用作基于 XPath/XQuery 的搜索引擎的正式输入。
目标
要查看下面 XPath sn-ps 的扩展/美化版本,我可以将您定向到 the following beautifier。免责声明,我是该工具的作者。
我的 XPath 字符串可以很简单
//node[@cat="smain" and node[@rel="su" and @pt="vnw"] and node[@rel="hd" and @pt="ww"] and node[@rel="predc" and @cat="np" and node[@rel="det" and @pt="lid"] and node[@rel="hd" and @pt="n"]]]
但也很精致
//node[@cat="top" and node[@rel="--" and @cat="smain" and node[@rel="su" and @pt="vnw" and @word="Dit" and @lemma="dit" and number(@begin) < ../node[@rel="hd" and @pt="ww" and @lemma="zijn"]/number(@begin)] and node[@rel="hd" and @pt="ww" and @lemma="zijn" and number(@begin) < ../node[@rel="predc" and @cat="np"]/node[@rel="det" and @pt="lid" and @word="een" and @cs="no" and @lemma="een"]/number(@begin)] and node[@rel="predc" and @cat="np" and node[@rel="det" and @pt="lid" and @word="een" and @cs="no" and @lemma="een" and number(@begin) < ../node[@rel="hd" and @pt="n" and @cs="no" and @lemma="zin"]/number(@begin)] and node[@rel="hd" and @pt="n" and @cs="no" and @lemma="zin" and number(@begin) < ../../../node[@rel="--" and @pt="let"]/number(@begin)]]] and node[@rel="--" and @pt="let"]]
您可能已经注意到,node 是使用的基本元素。没有其他元素名称。但是,属性不同。我感兴趣的属性是@cs="no",这意味着在未来对属性@word 和/或@lemma 的搜索请求中不需要区分大小写。为了实现区分大小写,我想将transform these two attributes 转换为lower-case(@attr)。问题是我只想要包含 @cs="no" 的节点。
到目前为止我尝试了什么
在 PHP 中,我认为我会成为一个聪明的人并做这样的事情:
- 检查 (XPath) 字符串是否匹配
@cs="no" -
如果有,用正则表达式查找所有单个节点
preg_match_all("/(?<=node\[).*?(?=node\[|\])/", $xpath, $matches); 遍历所有这些匹配(字符串),并再次检查它们是否包含
@cs="no"- 如果是这样,请删除该属性,并将
@word和@lemma标记替换为等效的小写字母。将结果放入虚拟变量中。
现在是棘手的部分:
- 在原始 XPath 字符串中,找到匹配的子字符串并将其替换为虚拟变量。
您可以在here 中看到这一点,但我也复制了下面的 PHP 代码。
<?php
$xpath = '//node[@cat="top" and node[@rel="--" and @cat="smain" and node[@rel="su" and @pt="vnw" and @word="Dit" and @lemma="dit" and number(@begin) < ../node[@rel="hd" and @pt="ww" and @lemma="zijn"]/number(@begin)] and node[@rel="hd" and @pt="ww" and @lemma="zijn" and number(@begin) < ../node[@rel="predc" and @cat="np"]/node[@rel="det" and @pt="lid" and @word="een" and @cs="no" and @lemma="een"]/number(@begin)] and node[@rel="predc" and @cat="np" and node[@rel="det" and @pt="lid" and @word="een" and @cs="no" and @lemma="een" and number(@begin) < ../node[@rel="hd" and @pt="n" and @cs="no" and @lemma="zin"]/number(@begin)] and node[@rel="hd" and @pt="n" and @cs="no" and @lemma="zin" and number(@begin) < ../../../node[@rel="--" and @pt="let"]/number(@begin)]]] and node[@rel="--" and @pt="let"]]';
$xpath = applyCs($xpath);
var_dump($xpath);
function applyCs($xpath) {
if (strpos($xpath, '@cs="no"') !== false) {
preg_match_all("/(?<=node\[).*?(?=node\[|\])/", $xpath, $matches);
foreach ($matches as $match) {
var_dump($match);
if (strpos($match, '@cs="no"') !== false) {
$dummyMatch = preg_replace('/(?:and )?@cs="no"/', '', $match);
if (strpos($dummyMatch, '@word="') !== false) {
$dummyMatch = str_replace('@word="', 'lower-case(@word)="', $dummyMatch);
}
if (strpos($dummyMatch, '@lemma="') !== false) {
$dummyMatch = str_replace('@lemma="', 'lower-case(@lemma)="', $dummyMatch);
}
$xpath = str_replace($match, $dummyMatch, $xpath);
}
}
}
return $xpath;
}
我的功能有问题
首先,您将在通过上面的链接提供的 Ideone 示例中看到,具有word 属性的第一个节点确实不 具有@cs="no" 属性,但在生成的 XPath 中它确实得到lower-case()'d。其次,在示例中您可能看不到重现的内容:因为我只是用新的虚拟对象查找并替换旧匹配项,所以很可能我替换了原始 XPath 节点中没有 @ 的值987654342@ 属性可用。我显然不希望那样。最后我不确定这是最好的方法。效率对我来说很重要,因此我大多不喜欢使用正则表达式。这就是我尽可能多地使用strpos 和str_replace 的原因。但是,如果有一种“解析”XPath 的方法(类似于在 Perl 中使用 Twig 解析 XML 的方法),并以快速的方式相应地操作 XPath,那也很好。然而,效率高于效率。
Tl;dr:在 XPath 字符串中,如果使用 PHP 不使用其他模块将其姊妹属性设置为(特定值),我如何将一个属性替换为另一个字符串。
想法
- 找到一个可以匹配每个节点而不会留下任何间隙的正则表达式,并在必要时编辑匹配后将所有节点重新粘合在一起
- 使用 PREG_OFFSET_CAPTURE 在输入 XPath 中查找匹配的索引,然后以一种或另一种方式替换您从该索引获得的第一个匹配项。
【问题讨论】: