php 如何使用DOMDocument完全删除名称空间

ldioqlga  于 2023-01-24  发布在  PHP
关注(0)|答案(3)|浏览(172)

给定一些类似下面的XML,如何从每个元素中完全删除特定的名称空间,包括其声明?

<?xml version="1.0" encoding="UTF-8"?>
<document xmlns:my-co="http://www.example.com/2015/co">
  <my-namespace:first xmlns:my-namespace="http://www.example.com/2015/ns">
    <element my-namespace:id="1">
    </element>
  </my-namespace:first>
  <second>
    <my-namespace:element xmlns:my-namespace="http://www.example.com/2015/ns" my-co:id="2">
    </my-namespace:element>
  </second>
</document>

注意,根级别没有xmlns:my-namespace声明,这两个声明位于XML结构的不同部分和级别。
如何高效地只删除名称空间my-namespace,而不必检查代码中的每个节点?
下面是XML应该看起来像:

<?xml version="1.0" encoding="UTF-8"?>
<document xmlns:my-co="http://www.example.com/2015/co">
  <first>
    <element id="1">
    </element>
  </first>
  <second>
    <element my-co:id="2">
    </element>
  </second>
</document>
tyky79it

tyky79it1#

下面的代码可以实现这个功能:

// Removes the namespace $ns from all elements in the DOMDocument $doc
function remove_dom_namespace($doc, $ns) {
  $finder = new DOMXPath($doc);
  $nodes = $finder->query("//*[namespace::{$ns} and not(../namespace::{$ns})]");
  foreach ($nodes as $n) {
    $ns_uri = $n->lookupNamespaceURI($ns);
    $n->removeAttributeNS($ns_uri, $ns);
  }
}

// Usage:
$mydoc = new DOMDocument();
$mydoc->load('test.xml'); // Load "before" XML
remove_dom_namespace($mydoc, 'my-namespace');

// Prints the above "after" XML
echo $mydoc->saveXML(null, LIBXML_NOEMPTYTAG);

XPath查询查找所有具有名称空间节点$ns的节点,而其父节点不具有相同的名称空间。这将查找/document/my-namespace:first/document/second/my-namespace:element,但不会查找/document/my-namespace:first/element,因为其父节点也具有名称空间my-namespace。然后,代码从找到的每个元素中删除指定的名称空间。从元素中删除命名空间也会自动将其从所有子元素中删除。
许多真实的世界中的XML文档都在根元素上声明了xmlns,但是这段代码可以在任何地方处理它们。

zbwhf8kr

zbwhf8kr2#

我们也想移除名称空间(在我们的例子中是所有的名称空间,而不仅仅是一个特定的名称空间),但是上面的解决方案只起了部分作用。如果一个前缀被定义了多次,但是使用了不同的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)的用法。
补充说明:我们只查找第一个名称空间的第一个节点,然后出于性能原因尝试从该节点删除所有名称空间。我们有时不得不处理可能包含100多个不同名称空间并且可能有几MB大的可怕XML。为每个名称空间创建一个xpath在这些文档上非常慢。此解决方案极大地提高了性能,因为它假设大多数如果不是全部,则在同一元素中声明命名空间(通常是根元素)。因此,与其循环并分别为每个名称空间执行xpath,它只是尝试从文档中第一个命名空间的第一个元素中移除所有命名空间,然后重新检查是否还有命名空间,但是如果文档中稍后还有命名空间,它仍然会移除它们。如果名称空间在文档中分布得更广,那么一种不同的方法可能更好。

ercv8c1e

ercv8c1e3#

SimpleXML有一个提取所有名称空间信息的函数,DOMXML有一个删除它的函数,如果你知道要删除什么的话。
下面是一个从DOMDoc中提取名称空间信息的简单函数,方法是将其导入SimpleXMLElement,然后使用名称空间数组从DOMDoc中本机删除所有名称空间内容

function removeNamespaces(DOMDocument $domdoc)
{
    // convert to a SimpleXML element
    $simplexml = simplexml_import_dom($domdoc);

    // get all the namespaces
    $namespaces = $simplexml->getDocNamespaces(true, true);

    // loop through the namespaces
    foreach($namespaces as $prefix => $uri)  
    {
            // remove namespace stuff
            $domdoc->documentElement->removeAttributeNS($uri, $prefix);
    }

    // return cleaned doc
    return $domdoc;
}

相关问题