我们也想删除命名空间(在我们的例子中是所有命名空间,而不仅仅是一个特定的命名空间),但上述解决方案只能部分工作。如果一个前缀被多次定义但使用不同的 URI,第一个答案不会将它们全部删除。
在所有用例中都适用于我们的解决方案是使用 SimpleXMLElement 搜索命名空间并使用 SimpleXMLElement->xpath() 搜索该命名空间的节点,然后转换为 DOMElement 以删除命名空间。对我们来说,使用这种方法的内存管理比在 DOM 中加载 XML 和使用 DOMXPath 更好。
要测试的示例 XML:
<xml xmlns="http://foo" xmlns:bar="http://bar" xmlns:baz="http://baz">
<foo bam="hoi">Hello World</foo>
<foo baz:bam="hoi">Hello World</foo>
<bar:foo bam="hoi">Hello World</bar:foo>
<bar:foo bar:bam="hoi">Hello World</bar:foo>
<bar:foo baz:bam="hoi">Hello World</bar:foo>
<baz:foo bar:bam="hoi">Hello World</baz:foo>
<plop:foo xmlns:plop="http://plop" xmlns:bar="http://baasdr">
<bar:foo>
<bar:foo xmlns:plop="http://plop">
<plop:foo>
<plop:foo>
<plop:foo xmlns:bar="http://bar">
<bar:baz>Hello World</bar:baz>
</plop:foo>
</plop:foo>
</plop:foo>
</bar:foo>
</bar:foo>
</plop:foo>
</xml>
删除命名空间的示例代码:
function removeNamespaces(SimpleXMLElement $xml) {
while($namespaces = $xml->getDocNamespaces(true, true)) {
$uri = reset($namespaces);
$prefix = key($namespaces);
$elements = $xml->xpath("//*[namespace::*[name() = '{$prefix}' and . = '{$uri}'] and not (../namespace::*[name() = '{$prefix}' and . = '{$uri}'])]");
$element = dom_import_simplexml($elements[0]);
foreach($namespaces as $prefix => $uri) {
$element->removeAttributeNS($uri, $prefix);
}
$xml = new SimpleXMLElement($xml->asXML());
}
return $xml;
}
SimpleXMLElement 被重新创建是因为在某些情况下,如果您在使用 DOM 删除命名空间后尝试访问或操作 SimpleXMLElement,PHP (5.6) 会因分段错误而崩溃。幸运的是,尽管 asXML() 继续运行以允许这种解决方法,因为新创建的对象不会导致崩溃。
如果您想删除特定的命名空间,您可以重写函数和/或 xpath,使其只搜索特定的命名空间。请注意,您还必须更改 SimpleXMLElement->getDocNamespaces(true, true) 的使用。
附加说明,我们只查找第一个命名空间的第一个节点,然后出于性能原因尝试从该节点中删除所有命名空间。我们有时不得不处理可怕的 XML,这些 XML 可能包含 100 多个不同的命名空间,并且可能有几 MB 大。在这些文档上为每个命名空间执行 xpath 非常慢。该解决方案极大地提高了性能,因为它在假设大多数(如果不是全部)命名空间都在同一元素(通常是根元素)中声明的情况下工作。因此,它不是循环遍历每个命名空间并单独为每个命名空间执行 xpath,它只是尝试从为文档中的第一个命名空间找到的第一个元素中删除所有命名空间,然后重新检查是否还有命名空间。但是,如果文档中稍后有命名空间,它仍然会删除这些命名空间。如果命名空间在文档中分布得更广,那么另一种方法可能会更好。