如何在片段创建中使用全局名称空间定义?

问题描述 投票:4回答:4

根元素具有名称空间声明,例如xmlns:xlink="http://www.w3.org/1999/xlink" ...,因此,任何附加节点(例如appendChild)都将接受该名称空间。我可以附加<graphic xlink:href=".."/>,因为总体上它是有效的...但是要附加一个片段,我首先需要使用createDocumentFragment()创建该片段。

示例:

    $tmp = $dom->createDocumentFragment();
    $ok = $tmp->appendXML('<graphic xlink:href="file123.ext"/>');

运行时会产生错误, DOMDocumentFragment::appendXML(): namespace error : Namespace prefix xlink for href on inline-graphic is not defined

如何对DOMDocumentFragment::appendXML()方法说“使用DomDocument命名空间”?


注释和上下文

([transfered as an answer,不要在这里污染)

xml domdocument libxml2
4个回答
2
投票

看起来好像按照预期的方式工作。签出bug report #44773[email protected]说这不是错误,可以正常工作。尽管我会同意错误报告和其他评论,但由于该片段是从DOMDocument生成的,并且已定义了名称空间,因此实际上应该知道它们是什么,并且应该可以正常工作。

将名称空间与元素一起传递。它不会显示在输出的XML中,但是会被该片段读取,以便它可以创建属性而不会出现任何错误。

$dom = new DOMDocument('1.0', 'utf-8');
$root = $dom->createElement('MyRoot');
$root->setAttributeNS('http://www.w3.org/2000/xmlns/','xmlns:xlink','http://www.w3.org/1999/xlink');
$dom->appendChild($root);

$tmp = $dom->createDocumentFragment();
$ok = $tmp->appendXML('<graphic xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="file123.ext"/>');
$dom->documentElement->appendChild($tmp);
die($dom->saveXML());

输出

<?xml version="1.0" encoding="utf-8"?>
<MyRoot xmlns:xlink="http://www.w3.org/1999/xlink"><graphic xlink:href="file123.ext"/></MyRoot>

1
投票

这不是错误,确实是预期的行为。命名空间不是在XML文档上或针对XML文档定义的,而是在元素节点上定义的。它们对该节点和所有子节点均有效,直到重新定义。

因此,如果您创建文档片段,则它没有父节点,现在您将添加一些XML片段。查找它找不到命名空间的任何定义,并且您得到一个错误。根据要在文档中添加的位置,名称空间前缀可以用于完全不同的名称空间。

您必须在片段中定义名称空间,如果片段是由DOM生成的,则它应始终具有所有需要的名称空间定义。

如果将其生成为文本,则可以确保名称空间定义包含在需要它的元素中,或者可以添加具有所有所需名称空间定义的包装器元素节点。

$dom = new DOMDocument();
$dom->loadXml('<foo/>');

$fragment = $dom->createDocumentFragment();
$fragment->appendXML(
  '<fragment xmlns:xlink="http://www.w3.org/1999/xlink">
     <graphic xlink:href="file123.ext"/>
   </fragment>'
);
foreach ($fragment->firstChild->childNodes as $child) {
  $dom->documentElement->appendChild($child->cloneNode(TRUE));
}

echo $dom->saveXML();

输出:

<?xml version="1.0"?>
<foo>
  <graphic xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="file123.ext"/>
</foo>

可以从命名空间列表中生成包装器元素。以下函数将使用元素节点或名称空间[prefix => namespace]的列表。

function wrapFragment($namespaces, $xml) {
  if ($namespaces instanceOf DOMElement) {
    $xpath = new DOMXpath($namespaces->ownerDocument);
    $namespaces = $xpath->evaluate('namespace::*', $namespaces);
  }
  $result = '<fragment';
  foreach ($namespaces as $key => $value) {
    if ($value instanceOf DOMNamespaceNode) {
      $prefix = $value->localName;
      $xmlns = $value->nodeValue;
    } else {
      $prefix = $key == '#default' ? '' : $key;
      $xmlns = $value;
    }
    $result .= ' '.htmlspecialchars(empty($prefix) ? 'xmlns' : 'xmlns:'.$prefix);
    $result .= '="'.htmlspecialchars($xmlns).'"';
  }
  return $result.'>'.$xml.'</fragment>';
}

echo wrapFragment(
  $dom->documentElement, '<graphic xlink:href="file123.ext"/>'
);

输出:

<fragment xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:xlink="http://www.w3.org/1999/xlink"><graphic xlink:href="file123.ext"/></fragment>

1
投票

我花了大约4个小时来解决这个问题,如果您关闭libxml错误,该警告就会消失,并且您可以自由使用前缀而不会遇到麻烦。

libxml_use_internal_errors(true);

[我同意这不是设计上的错误,但此解决方法为我节省了很多时间。

编辑:如果您不需要返回并稍后在脚本中引用该片段,则此方法有效。尽管在打印文档时会出现前缀,但似乎抑制警告仍然使项目没有命名空间。


0
投票

关于该问题以及与PHP的关系的注释缺少片段名称空间的资源。

一个PHP错误?

((@ slapyo之后)

PHP bug#44773:不是“良好行为”,是错误(!)。如果您同意,请在此处添加评论,然后在此处投票(!)。


想象您使用replace_innerXML($node,$innerXML)函数或任何其他类似的上下文...请参阅下面的“典型方案”部分。

如何解决?没有大的正则表达式(在示例中为$innerXML)和较慢的算法,就无需设置名称空间声明即可设置每个标签...当然,毕竟appendXML()会将片段变成树的一部分,所以不需要名称空间,因为它已经在根了...所有工作仅是使用越野车片段的appendXML()

典型场景

((在@ThW答案/讨论之后)典型的“盲名称空间”片段使用。

[当片段是具有新名称空间的“外部”时,好的,该片段需要自己声明使用的名称空间...但是这个问题中暴露的问题不是这个奇特的名称,而是一个很常见的名称。 PS:正如我们将要看到的,“ PHP bug”解决方案也是针对此情况的解决方案。

这里,举例说明,片段有两种典型用法其中不存在关于名称空间的知识(由片段的元素使用)>],只有事实是所有这些都已在DOMDocument中声明(无需重新声明)。1)一个​​XSLT call-to-PHP-returning-fragment by XSLTProcessor::registerPHPFunctions()

2)一个“通用DOM库”,它提供了一种处理方法,可以用新的XML内容(可以是片段内容)替换节点的XML内部内容。请参见下面的功能XSLTProcessor::registerPHPFunctions()

replace_innerXML()

如果提供PHP功能,“到INJECT命名空间的算法”(请参见函数中的注释)将很简单...但是,像function replace_innerXML(DOMNode $e, $innerXML='') {
    if ($e && ($innerXML>'' || $e->nodeValue>'')) {
        $e->nodeValue='';   
        if ($innerXML>'') {
            $tmp = $e->ownerDocument->createDocumentFragment();
            // HERE we need to INJECT namespace declarations into $innerXML
            $tmp->appendXML($innerXML);
            $e->appendChild( $tmp );
        }
        return true;
    }
    return false;
}
// This function is only illustrative, for other propuses 
//         see https://stackoverflow.com/q/26029868/287948
// Example of use:
$innerXML='nonoo <xx xx:aa="ww">uuu</xx> nono<a:yy zz:href="...">uu</a:yy>...';
replace_innerXML($someNode,$innerXML);
一样,

PHP有bug,因为没有提供(!)

。]]笨拙的解决方案

[唯一的方式(到2014年今天)是[INJECT名称空间]是

...因此,做一件简单的事情是一件大事:仅

接受预先存在的DOMDocument命名空间。 PHP有一个bug,因为不能避免这个“大笨蛋” ...解决“ PHP bug”的解决方案?

PHP的理想解决方案

$innerXML = preg_replace_callback( "/([<\s])($namespacesJoinByPipe):([^\s>]+)/s", function ($m) use($namespacesAssociative) { $nsdecl = "xmlns:$m[2]=\"".$namespacesAssociative[$m[2]].'"'; return ($m[1]=='<') ? "<$m[1]$m[2]:$m[3] $nsdecl " // tag like "<a:yy" : " $nsdecl $m[1]$m[2]:$m[3]"; // attribute like " xx:aa" }, $innerXML ); 有很多替代方案:标志PHP RCF to solve the problem,参考节点createDocumentFragment($importRootNamespaces=false)createDocumentFragment($refNamespacesNode=NULL) ...全部具有与常规DOMDocumentFragment::setAttributeNS() method等效的默认行为(无参数)。] >

PS:这些解决方案中的任何一个也有助于解决其他问题,例如第一个问题,即:“……当片段是具有新命名空间的'外肾'时……”。

© www.soinside.com 2019 - 2024. All rights reserved.