使用PHP从XML中移除命名空间

22

我有一个长这样的XML文档:

<Data 
  xmlns="http://www.domain.com/schema/data" 
  xmlns:dmd="http://www.domain.com/schema/data-metadata"
>
  <Something>...</Something>
</Data>

我正在使用PHP中的SimpleXML解析信息。我正在处理数组,但命名空间似乎有问题。

我的问题是:如何删除这些命名空间?我从一个XML文件中读取数据。

谢谢!


如果您需要详细信息...我的原始问题已经在这里发布,其中一个用户已经回答了(谢谢!)。但是我发现命名空间导致他的循环无法运行并返回一个空数组。原始问题位于此处:http://stackoverflow.com/questions/1209301/php-simplexml-group-by-element-type - jchimpo
5个回答

21

我发现上面的答案很有帮助,但对我来说并不完全适用。 最终以下方法管用:

// Gets rid of all namespace definitions 
$xml_string = preg_replace('/xmlns[^=]*="[^"]*"/i', '', $xml_string);

// Gets rid of all namespace references
$xml_string = preg_replace('/[a-zA-Z]+:([a-zA-Z]+[=>])/', '$1', $xml_string);

4
我会使用类似以下的方式来消除“所有命名空间引用”: $xml = preg_replace('/(</*)[^>:]+:/', '$1', $xml); - Silas Palmer
我人生中很少会点赞使用正则表达式操作XML的解决方案,但这确实是其中之一。我真的不想注册默认命名空间并且使我的xpath查询变得混乱无序。 - But those new buttons though..
1
几乎完美。需要查找节点名称后面的潜在空格。如果节点内容带有冒号<node>Order:Num</node>,则剥离节点内容,也不会找到数字键<ns:addr2>Content</ns:addr2>。尝试:$xml_string = preg_replace('/(<\/|<)[a-zA-Z]+:([a-zA-Z0-9]+[ =>])/', '$1$2', $xml_string); - M P

20

如果你正在使用XPath,那么这是XPath的限制,而不是PHP。请查看有关XPath和默认名称空间的解释获取更多信息。

更具体地说,问题出在根节点中的xmlns=""属性上。这意味着你需要注册命名空间,然后使用QName来引用元素。

$feed = simplexml_load_file('http://www.sitepoint.com/recent.rdf');
$feed->registerXPathNamespace("a", "http://www.domain.com/schema/data");
$result = $feed->xpath("a:Data/a:Something/...");

重要提示:在registerXPathNamespace函数调用中使用的 URI 必须与实际的 XML 文件中使用的 URI 完全相同。


好的,所以我不是删除...而是注册命名空间。这解决了我的问题!!!你太棒了!谢谢! - jchimpo
不幸的是,这似乎是唯一的方法。 - Alexei Tenitski
2
请注意重要部分。我第一次查看这个答案时错过了它。 - Kevin Schroeder

2
以下 PHP 代码可以自动检测 XML 文件中别名 "default" 下指定的默认命名空间。不需要更新所有 xpath 查询以包括前缀 default:。因此,如果您想读取包含默认 NS 定义或没有默认 NS 定义并且想查询所有 Something 元素的 XML 文件,您可以使用以下代码:
$xml = simplexml_load_file($name);
$namespaces = $xml->getDocNamespaces();
if (isset($namespaces[''])) {
    $defaultNamespaceUrl = $namespaces[''];
    $xml->registerXPathNamespace('default', $defaultNamespaceUrl);
    $nsprefix = 'default:';
} else {
    $nsprefix = '';
}

$somethings = $xml->xpath('//'.$nsprefix.'Something');

echo count($somethings).' times found';

2
当您只想要解析XML以供使用,并且不关心任何命名空间时,只需将其删除。正则表达式很好用,比下面的方法要快得多。
但是,为了更安全地删除命名空间,可以使用SimpleXML解析XML并询问它所具有的命名空间,如下所示:
$xml = '...';
$namespaces = simplexml_load_string($xml)->getDocNamespaces(true);
//The line bellow fetches default namespace with empty key, like this: '' => 'url'
//So we remove any default namespace from the array
$namespaces = array_filter(array_keys($namespaces), function($k){return !empty($k);});
$namespaces = array_map(function($ns){return "$ns:";}, $namespaces);

$ns_clean_xml = str_replace("xmlns=", "ns=", $xml);
$ns_clean_xml = str_replace($namespaces, array_fill(0, count($namespaces), ''), $ns_clean_xml);
$xml_obj = simplexml_load_string($ns_clean_xml);

因此,您只需替换命名空间,避免删除XML可能具有的任何其他内容。
实际上,我将其用作一种方法:
function refined_simplexml_load_string($xml_string) {
  if(false === ($x1 = simplexml_load_string($xml_string)) ) return false;
  
  $namespaces = array_keys($x1->getDocNamespaces(true));
  $namespaces = array_filter($namespaces, function($k){return !empty($k);});
  $namespaces = array_map(function($ns){return "$ns:";}, $namespaces);
  
  return simplexml_load_string($ns_clean_xml = str_replace(
    array_merge(["xmlns="], $namespaces),
    array_merge(["ns="], array_fill(0, count($namespaces), '')),
    $xml_string
  ));
}

非常感谢你分享解决方案。我之前有另一种方法来完成这个(PHP 7.2),而且多年来一直很好用。然而,出于某种奇怪的原因,在 PHP 8.1 中它似乎没有进行任何清理工作。我在版本更新中找不到任何相关信息,但是你的方法在两个 PHP 版本中都有效。 - Oliver Maksimovic

0
要完全删除命名空间,您需要使用正则表达式(RegEx)。例如:
$feed = file_get_contents("http://www.sitepoint.com/recent.rdf");
$feed = preg_replace("/<.*(xmlns *= *[\"'].[^\"']*[\"']).[^>]*>/i", "", $feed); // This removes ALL default namespaces.
$xml_feed = simplexml_load_string($feed);

然后在加载XML之前,您已经剥离了所有的XML命名空间(请小心使用正则表达式,因为如果您的字段中有任何类似于以下内容的内容:

<![CDATA[ <Transfer xmlns="http://redeux.example.com">cool.</Transfer> ]]>

然后它将从CDATA内部剥离xmlns,这可能会导致意外的结果。


不错,但它没有删除闭合标签。 - tnt-rox

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接