PHP中不区分大小写的XPath搜索

8

我有一个像这样的xml文件:

<volume name="Early">
<book name="School Years">
<chapter number="1">
<line number="1">Here's the first line with Chicago in it.</line>
<line number="2">Here's a line that talks about Atlanta</line>
<line number="3">Here's a line that says chicagogo </line>
</chapter>
</book>
</volume>

我正在尝试使用PHP进行简单的关键字搜索,找到该词并显示它所在的行。我已经成功实现了这个功能。

$xml = simplexml_load_file($data);
$keyword = $_GET['keyword'];
$kw=$xml->xpath("//line[contains(text(),'$keyword')]");
...snip...

echo $kw[0]." is the first returned item";

然而,使用这种技术,用户必须搜索'Chicago'而不是'chicago',否则搜索将返回空结果。

我知道我需要使用翻译函数,但是所有的尝试都徒劳无功。

我已经尝试过:

$upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$lower = "abcdefghijklmnopqrstuvwxyz";
$kw = $xml->xpath("line[contains(text(),'translate('$keyword','$upper','$lower'))]");

但是似乎什么都不起作用。有什么建议吗?

你的最后一个代码示例中,在 translate 前面似乎多了一个单引号。 - Charles
3个回答

10

如果你选择使用Gordon推荐的从XPath中调用PHP函数的方法,它将更加灵活。然而,与他的答案相反,在XPath 1.0中确实translate字符串函数是可用的,这意味着你可以使用它。您的问题是如何使用它。

首先,有明显的拼写错误,正如Charles在评论中指出的那样。然后是您尝试匹配文本值的逻辑。


以文字形式,您当前正在问:“文本是否包含关键字的小写形式?” 这实际上不是您想要问的内容。相反,应该问:“小写文本是否包含小写关键字?” 将其翻译回XPath语言即为:

(注意:为了易读性而截断字母表)

//line[contains(translate(text(),'ABC...Z','abc...z'),'chicago')]

上面的代码将line节点中包含的文本转换为小写,然后检查(小写后的)文本是否包含关键词chicago


以下是必要的代码片段(但实际上,上面的思路才是你真正需要掌握的):

$xml    = simplexml_load_file($data);
$search = strtolower($keyword);
$nodes  = $xml->xpath("//line[contains(translate(text(), 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghjiklmnopqrstuvwxyz'), '$search')]");

echo 'Got ' . count($nodes) . ' matches!' . PHP_EOL;
foreach ($nodes as $node){
   echo $node . PHP_EOL;
}

编辑:在 dijon 的评论

在 foreach 循环内,你可以像下面这样访问行号、章节号和书名。

行号 —— 这只是 <line> 元素上的一个属性,使得访问它变得超级容易。有两种使用 SimpleXML 访问它的方式:$node['number'] 或者 $node->attributes()->number(我更喜欢前者)。

章节号 —— 要获取它,就像你所说的一样,我们需要沿着树向上遍历。如果我们使用 DOM 类,我们将有一个方便的 $node->parentNode 属性,直接将我们引导到 <line> 的直接祖先 <chapter> 上。SimpleXML 没有这样一个方便的属性,但是我们可以使用相对 XPath 查询来获取它。 parent axis 允许我们向上遍历树。

由于 xpath() 返回一个数组,我们可以作弊并使用 current() 来访问从中返回的第一个(也是唯一的)项目。然后就只需要像上面那样访问 number 属性即可。

// In the near future we can use: current(...)['number'] but not yet
$chapter = current($node->xpath('./parent::chapter'))->attributes()->number;

书名 -- 这个过程与访问章节号的过程相同。可以从<line>中使用相对XPath查询,利用祖先轴,例如:./ancestor::book(或者./parent:chapter/parent::book)。希望您能够找到如何访问其name属性。


感谢详细解释其工作原理以及代码片段。正是我所需要的!在这个项目中,我一直主要使用SimpleXML,但很高兴有Gordon在下面的答案可以进行比较。 - dijon
我非常想知道的一件事是:在foreach子句中,如何列出行号、章节号和书名?我相信这也是基于当前节点的xpath,并向上遍历树?例如,(来自第一个XML示例)我想搜索“亚特兰大”,并收到:学校年度,第1章:这里有一行谈论亚特兰大。再次说明,试错法一直让我感到困扰! - dijon
再次感谢!由于我正在使用simpleXML,我永远不会偶然发现您所说的“cheat”的'current()'。我知道轴,但无法弄清楚如何描述起点。我总是很欣赏代码和解释一起给出。这样我就能学到东西了! - dijon

3

请参考salathe的答案,了解如何使用SimpleXml和translate()进行操作。

作为XPath函数的替代/附加选项,您可以在使用DOM时在XPath表达式中使用任何PHP函数,包括自定义函数。我不确定SimpleXml是否也支持这一功能。

// create a DOMDocument and load your XML string into it
$dom = new DOMDocument;
$dom->loadXML($xml);

// create a new Xpath and register PHP functions as XPath functions
$xPath = new DOMXPath($dom);
$xPath->registerNamespace("php", "http://php.net/xpath");
$xPath->registerPHPFunctions();

// Setup the query
$keyword = 'chicago';
$q = "//line[php:functionString('stripos', text(), '$keyword')]";
$nodes = $xPath->query($q);

// Iterate the resulting NodeList
foreach($nodes as $node) {
    echo $node->nodeValue, PHP_EOL;
}

这将输出
Here's the first line with Chicago in it.
Here's a line that says chicagogo

更多细节请参见@salathes博客文章PHP手册。


+1 表示能够在 XPath 查询中使用 PHP-land 函数(并链接到我的博客!)的提示。 :) - salathe
@salathe 出于好奇:您是否知道是否有任何函数可以让我像在array_map中使用数组或在iterator_apply中使用迭代器一样使用DOMNodeList?除了使用 $xpath->query('//book[php:function("callback", author)]'); 之外? - Gordon
我不确定SimpleXml中是否有相同的内容。但是,没有什么可以阻止人们混合和匹配DOM/SimpleXML类。 :) - salathe
@Gordon(的评论)-您可以将DOMNodeList包装在IteratorIterator中,并在其上使用iterator_apply - salathe
@salathe 我说过那个吗?哇?什么时候说的?30岁以上真是痛苦啊,总是会忘记东西。 :D - Gordon
谢谢你的回复,Gordon。我使用了Salathe上面的答案,但是你给了我另一种方法,我会仔细研究! - dijon

0
我可能漏掉了什么...但这里有另一种方法,我认为更简单。在通过simplexml_load_string()将XML加载到SimpleXML之前,如何使用PHP的strtolower()

$xml = simplexml_load_string(strtolower(file_get_contents($xml_file_path)));
$keyword = strtolower($_GET['keyword']); //Make sure you sanitize this!
$kw = $xml->xpath("//line[contains(text(),'$keyword')]");

这样,你正在比较小写字母::小写字母


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接