2015-05-15 5 views
0

Учитывая некоторые XML, как показано ниже, как вы можете полностью удалить определенное пространство имен, включая его объявление, из каждого элемента?Как полностью удалить пространство имен с помощью DOMDocument

<?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> 

ответ

2

Следующий код делает трюк:

// 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 на корневом элементе, но этот код обрабатывает их в любом месте.

0

Мы также хотели удалить пространства имен (в нашем случае все пространства имен, а не только конкретные), но вышеупомянутое решение работало частично. Если префикс определен несколько раз, но с другим URI, первый ответ не удаляет их всех.

Решение, которое работало для нас во всех случаях использования, должен был использовать SimpleXMLElement для поиска пространств имен и использовать SimpleXMLElement->xpath() для поиска узлов этого пространства имен, а затем преобразовать в DOMElement удалить пространство имен. Для нас управление памятью было лучше использовать этот подход, а не загрузку XML в DOM и использование 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 воссоздана, потому что в некоторых случаях, если вы пытаетесь получить доступ или манипулировать SimpleXMLElement после использования DOM для удаления пространств имен PHP (5.6) с ошибкой сегментации. К счастью, хотя asXML() продолжал работать, чтобы разрешить это обходное решение, поскольку вновь созданный объект не вызывал сбоев.

Если вы хотите удалить определенные пространства имен, вы можете переписать функцию и/или xpath таким образом, чтобы она выполняла поиск только в конкретных пространствах имен. Обратите внимание, что вам также придется изменить использование SimpleXMLElement->getDocNamespaces(true, true).

Обратите внимание, что мы только ищем первый узел первого пространства имен, а затем попытаемся удалить все пространства имен из этого узла по соображениям производительности. Иногда нам приходится работать с ужасными XML-файлами, которые могут содержать более 100 различных пространств имен и могут иметь несколько мегабайт. Выполнение xpath для каждого пространства имен было очень медленным в этих документах.Это решение значительно повышает производительность, поскольку оно работает в предположении, что большинство, если не все, пространства имен объявлены в одном и том же элементе (обычно это корневой элемент). Поэтому вместо того, чтобы перебирать и выполнять xpath для каждого пространства имен отдельно, он просто пытается удалить все пространства имен из первого элемента, найденного для первого пространства имен в документе, а затем повторно проверяет, осталось ли еще пространство имён. Но если в документе уже есть пространства имен, оно все равно удаляет их. Если пространства имен более распространены через документ, другой подход может быть лучше.

 Смежные вопросы

  • Нет связанных вопросов^_^