【问题标题】:PHP change xmlns in XML filePHP更改XML文件中的xmlns
【发布时间】:2013-03-03 17:49:59
【问题描述】:

我有以下结构的xml:

<?xml version="1.0"?>
<ONIXMessage xmlns="http://test.com/test">
 ...data...
</ONIXMessage>

我需要用我自己的值更改 xmlns 属性。我该怎么做?最好使用 DOMDocument 类。

【问题讨论】:

  • 感谢您的详细回答。

标签: php xml xml-namespaces


【解决方案1】:

我需要用我自己的值更改 xmlns 属性。我该怎么做?最好使用DOMDocument 类。

这在设计上是不可能的。每个DOMDocument 都有一个根/文档元素。

在您的示例 XML 中,根元素是:

{http://test.com/test}ONIXMessage

我将元素名称写为 expanded-name,约定将命名空间 URI 放在尖括号中。

将元素名称写成显示其完整的形式expanded-name也表明您不仅要在此处更改属性的值,而且要更改名称空间的URI一个特定的元素。所以你想改变元素名称。如果子元素在同一个命名空间中,它还可能包含它包含的任何子元素名称。

由于xmlns attribute 只反映了元素本身的命名空间URI,你不能改变它。一旦在DOMDocument 中设置,您将无法更改它。

你可以替换整个元素,但是子元素的命名空间也不会改变。这里有一个类似于你的 XML 的示例,只有 textnode 子节点(没有命名空间):

$xml = <<<EOD
<?xml version="1.0"?>
<ONIXMessage xmlns="uri:old">
 ...data...
</ONIXMessage>
EOD;

$doc = new DOMDocument();
$doc->loadXML($xml);
$newNode = $doc->createElementNS('uri:new', $doc->documentElement->tagName);
$oldNode = $doc->replaceChild($newNode, $doc->documentElement);

foreach(iterator_to_array($oldNode->childNodes, true) as $child) {
    $doc->documentElement->appendChild($child);
}

生成的 XML 输出为:

<?xml version="1.0"?>
<ONIXMessage xmlns="uri:new">
 ...data...
</ONIXMessage>

现在将输入 XML 更改为包含类似子项的内容

<?xml version="1.0"?>
<ONIXMessage xmlns="uri:old">
   <data>
     ...data...
   </data>
</ONIXMessage>

然后将创建以下输出,注意现在再次弹出的旧命名空间 URI:

<?xml version="1.0"?>
<ONIXMessage xmlns="uri:new">
   <default:data xmlns:default="uri:old">
     ...data...
   </default:data>
</ONIXMessage>

如您所见,DOMDocument 不提供开箱即用替换现有元素的命名空间 URI 的功能。但是希望到目前为止,通过此答案中提供的信息,可以更清楚地说明为什么如果该属性值已经存在,则无法更改它。

expat based parser in the libxml based PHP extension 确实允许“更改”现有属性值,无论它是否为 xmlns* 属性 - 因为它只是解析数据,您可以使用它即时处理它。

一个工作示例是:

$xml = <<<EOD
<?xml version="1.0" encoding="utf-8"?>
<ONIXMessage xmlns="uri:old">
  <data>
    ...data...
  </data>
</ONIXMessage>
EOD;

$uriReplace = [
    'uri:old' => 'uri:new',
];

$parser = xml_parser_create('UTF-8');
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_set_default_handler($parser, function ($parser, $data) {
    echo $data;
});
xml_set_element_handler($parser, function ($parser, $name, $attribs) use ($xml, $uriReplace) {
    $selfClosing = '/>' === substr($xml, xml_get_current_byte_index($parser), 2);
    echo '<', $name;
    foreach ($attribs as $name => $value) {
        if (substr($name, 0, 5) === 'xmlns' && isset($uriReplace[$value])) {
            $value = $uriReplace[$value];
        }
        printf(' %s="%s"', $name, htmlspecialchars($value, ENT_COMPAT | ENT_XML1));
    }
    echo $selfClosing ? '/>' : '>';
}, function ($parser, $name) use ($xml) {
    $selfClosing = '/>' === substr($xml, xml_get_current_byte_index($parser) - 2, 2);
    if ($selfClosing) return;
    echo '</', $name, '>';
});

xml_parse($parser, $xml, true);
xml_parser_free($parser);

然后输出透明地将命名空间 URI 从 uri:old 更改为 uri:new

<ONIXMessage xmlns="uri:new">
   <data>
     ...data...
   </data>
</ONIXMessage>

正如本例所示,您在 XML 中使用的每个 XML 特性都需要由解析器处理。例如,缺少 XML 声明。然而,这些可以通过实现缺失的处理程序类回(例如for CDATA sections)或通过输出缺失的输出(例如“缺失”的 XML 声明)来添加。我希望这会有所帮助,并向您展示如何更改这些不打算更改的值的替代方法。

【讨论】:

  • 如何使用 uri:new 创建一个新文档并从旧文档中复制节点?
  • @michi:这应该也是可能的。一旦设置了新的文档元素,我认为当您导入节点时,它们会采用已经存在的前缀。但我手头没有一个可行的例子,因为这应该首先验证。
  • @michi,我尝试使用新的根节点和相应的 xmlns 创建一个新文档,但是从旧文档中导入任何子节点都会导致同样的问题,即子节点的 xlmns将指向旧的 xmlns。
  • @moz:不要导入。只需使用您拥有的旧文档中的本地名称创建新元素。如果您导入节点,则命名空间是您导入的节点的一部分(因此称为“导入”)。复制迭代也许可以使用像 github.com/hakre/Iterator-Garden/blob/development/src/DOM/… 这样的迭代器来简化
  • @hakre,感谢您的提示。我会试一试,虽然我现在用了一个很好的旧 preg_replace
猜你喜欢
  • 1970-01-01
  • 2016-01-11
  • 2018-03-12
  • 1970-01-01
  • 1970-01-01
  • 2019-07-28
  • 1970-01-01
  • 2013-08-10
  • 1970-01-01
相关资源
最近更新 更多