SimpleXML 的 Xpath 不太适合完成整个工作。尤其是不区分大小写的搜索非常尴尬-实际上您在相关问题中遇到了太多问题。
简化工作的一种方法是将其划分。例如,首先获取所有有趣元素/属性的列表,然后过滤它们,然后获取它们的所有父元素。
这可以通过将 xpath 结果(它是一个数组)转换为Iterator
$string = "sample";
$names = $xml->xpath('//ITEM/@NAME');
$filtered = new LaxStringFilterIterator($names, $string);
$items = new SimpleXMLParentNodesIterator($filtered);
foreach ($items as $item) {
echo $item->asXML(), "\n";
}
这将输出搜索到的节点(示例):
<ITEM NAME="Sample">
..some other node here
</ITEM>
<ITEM NAME="SamPlE lorem">
..some other node here
</ITEM>
<ITEM NAME="Sam Ple lorem ipsum">
..some other node here
</ITEM>
<ITEM NAME="sample">
..some other node here
</ITEM>
<ITEM NAME="SAMPLE">
..some other node here
</ITEM>
以及基于字符串值过滤数组的分离解决方案:
/**
* Class LaxStringFilterIterator
*
* Search for needle in case-insensitive manner on a subject
* with spaces removed.
*/
class LaxStringFilterIterator extends FilterIterator
{
private $quoted;
/**
* @param Traversable|Array|Object $it
* @param string $needle
*/
public function __construct($it, $needle) {
parent::__construct($it instanceof Traversable ? new IteratorIterator($it) : new ArrayIterator($it));
$this->quoted = preg_quote($needle);
}
public function accept() {
$pattern = sprintf('/%s/i', $this->quoted);
$subject = preg_replace('/\s+/', '', trim(parent::current()));
return preg_match($pattern, $subject);
}
}
和父节点装饰器:
/**
* Class SimpleXMLParentNodesIterator
*
* Return parent nodes instead of current SimpleXMLElement Nodes,
* for example the element of an attribute.
*/
class SimpleXMLParentNodesIterator extends IteratorIterator
{
public function current() {
$current = parent::current();
list($parent) = $current[0]->xpath('..');
return $parent;
}
}