php中不区分大小写的xpath搜索

case insensitive xpath searching in php

我有一个这样的 xml 文件:

1
2
3
4
5
6
7
8
9
<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 进行简单的关键字搜索,以找到单词并显示它所在的行。我有这个工作

1
2
3
4
5
6
$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",否则搜索将不会返回任何内容。

我知道我需要使用翻译功能,但我所有的尝试和错误都是徒劳的。

我试过了:

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

但似乎没有任何效果。有小费吗?


如果您选择使用 XPath 中的 PHP 函数,Gordon 的建议将证明更加灵活。然而,与他的回答相反,translate 字符串函数在 XPath 1.0 中可用,这意味着您可以使用它;你的问题是如何。

首先,查尔斯在对问题的评论中指出了明显的错字。然后是您如何尝试匹配文本值的逻辑。

在 word 形式中,您目前在问,"文本是否包含关键字的小写形式?"这不是你真正想要问的。相反,问,"小写文本是否包含小写关键字?"翻译(原谅双关语)回到 XPath 领域将是:

(注意:为了便于阅读,字母被截断)

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

上面将 line 节点中包含的文本小写,然后检查它(小写文本)是否包含关键字 chicago.

现在是强制性的代码片段(但实际上,上面的想法是你真正需要带回家的):

1
2
3
4
5
6
7
8
$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;
}

在第戎的评论后编辑

在 foreach 中,您可以访问如下所示的行号、章节号和书名。

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

章节编号——正如你所说的那样,为了解决这个问题,我们需要遍历树。如果我们使用 DOM 类,我们将有一个方便的 $node->parentNode 属性将我们直接引导到 <chapter>(因为它是我们的 <line> 的直接祖先)。 SimpleXML 没有这么方便的属性,但我们可以使用相对 XPath 查询来获取它。父轴允许我们向上遍历树。

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

1
2
// 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 属性。


请参阅 salathe 的回答,了解如何使用 SimpleXml 和 translate()。

作为使用 XPath 函数的替代/添加选项,您可以在使用 DOM 时在 XPath 表达式中使用 PHP5.3 起的任何 PHP 函数,包括自定义函数。我不确定 SimpleXml 中是否有相同的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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;
}

这将输出

1
2
Here's the first line with Chicago in it.
Here'
s a line that says chicagogo

有关更多详细信息,请参阅@salathes 博客条目和 PHP 手册。


我可能遗漏了一些东西......但恕我直言,这是另一种方法 - 更简单。
在通过 simplexml_load_string() 将 XML 加载到 SimpleXML 之前使用 PHP 的 strtolower() 怎么样?

IE

1
2
3
$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')]");

这样,你是在比较小写::小写