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

时间:2014-10-27 17:37:20

标签: xml domdocument libxml2

根元素具有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

如何说&#34;使用DomDocument命名空间&#34;到DOMDocumentFragment::appendXML()方法?


注释和背景

transfered as an answer,此处不予以反对)

4 个答案:

答案 0 :(得分:2)

看起来它按照它应该的方式工作。查看bug report #44773。 chregu@php.net说它不是一个bug而且工作正常。虽然我同意错误报告和其他注释,但由于片段是由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 :(得分: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>

答案 2 :(得分:1)

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

libxml_use_internal_errors(true);

我同意这不是设计上的错误,但是这种解决方法为我节省了一天。

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

答案 3 :(得分:0)

注意问题以及与PHP缺乏片段命名空间资源的关系。

PHP错误?

(在@slapyo之后)

有一个PHP bug#44773:不是&#34;良好的行为&#34;,是一个错误(!)。如果您同意,请在那里添加评论,并在此处投票(!)。


想象一下,您使用replace_innerXML($node,$innerXML)函数或任何其他类似的上下文...请参阅&#34;典型场景&#34;以下部分。

如何解决? 如果没有大的正则表达式(在示例中超过$innerXML)和慢速算法,则设置每个标签而不使用名称空间声明......当然,appendXML()将片段转换为组件树,因此它不需要名称空间,因为它已经在根...所有工作只是使用有问题的片段appendXML()

典型场景

(在@ThW回答/讨论之后)典型的&#34;盲命名空间&#34;片段使用。

当片段是&#34; extraterrenal&#34;使用新的命名空间,好吧,片段需要自己声明使用的命名空间......但是在这个问题中暴露的问题不是这种奇特的问题,而是另一个问题。
PS:正如我们所知,&#34; PHP bug&#34; -solution也是这种情况的解决方案。

这里,为了说明,片段有两种典型用法,其中没有关于命名空间的先验知识(由片段元素使用),仅所有已在DOMDocument中声明的事实(不需要重新声明)。

1)a XSLT call-to-PHP-returning-fragment by XSLTProcessor::registerPHPFunctions();

2)&#34;通用DOM库&#34;它提供了一种处理方法,用于通过新的XML内容替换节点的XML内部内容,该内容可以是片段内容。请参阅下面的函数replace_innerXML()

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);

算法&#34;到INJECT命名空间&#34; (如函数中的注释)如果提供PHP功能会很简单......但是,作为we insist PHP有一个错误,因为什么都不提供(!)

Elephantic solution

唯一的方式(今天是2014年)到&#34; INJECT命名空间&#34;是

$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
);

...所以,做一个如此简单的事情是一个大象:只有接受预先存在的DOMDocument命名空间

PHP有一个bug,因为不能避免这个&#34;大象&#34; ...解决问题&#34; PHP bug&#34;?

PHP的理想解决方案

PHP RCF to solve the problem有很多替代方案:标记createDocumentFragment($importRootNamespaces=false),参考节点createDocumentFragment($refNamespacesNode=NULL)DOMDocumentFragment::setAttributeNS() method ...所有默认行为都与通常相同createDocumentFragment()(无参数)。

PS:这些解决方案中的任何一个也有助于处理其他问题,例如第一个解决的问题,&#34; ...当片段是“外部的”时。使用新的命名空间......&#34;。