根元素具有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,此处不予以反对)
答案 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缺乏片段命名空间资源的关系。
(在@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有一个错误,因为什么都不提供(!)。
唯一的方式(今天是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 RCF to solve the problem有很多替代方案:标记createDocumentFragment($importRootNamespaces=false)
,参考节点createDocumentFragment($refNamespacesNode=NULL)
或DOMDocumentFragment::setAttributeNS() method
...所有默认行为都与通常相同createDocumentFragment()
(无参数)。