【问题标题】:How to use global namespace definitions in a fragment creation?如何在片段创建中使用全局命名空间定义?
【发布时间】:2014-10-27 17:37:19
【问题描述】:

根元素有命名空间声明,如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


    【解决方案1】:

    看起来它正在按应有的方式工作。查看bug report #44773。 chregu@php.net 说这不是错误并且可以正常工作。虽然我同意错误报告和其他 cmets,但由于片段是由 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>
    

    【讨论】:

    • 嗯...(感谢错误报告 44773!)是的,这是解决方法,我需要通过正则表达式检测,创建不安全、缓慢和肮脏的代码...我用fragment in a generic function, see this one
    • 我认为我们可以重新打开 PHP 的错误...逻辑和实用行为是接受已经在DomDocument root 定义的任何命名空间...或者为片段提供setAttributeNS() 方法内容(!)。
    • 拥有这样的东西是有意义的。据我所知,除了该错误报告之外,没有提到您能够在字符串中传递命名空间。
    • 参见 2014-08-18 评论:“如果 DocumentFragment 不知道它是为哪些命名空间定义的,则当前行为没有意义”。我同意。
    【解决方案2】:

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

    libxml_use_internal_errors(true);
    

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

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

    【讨论】:

      【解决方案3】:

      这不是错误,它确实是预期的行为。命名空间不是在 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 =&gt; 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>
      

      【讨论】:

      • 嗯...我不同意...您如何解决“典型问题场景”,〜在问题中解释?如果你的$dom&lt;foo xmlns:xlink="..."&gt;,自然是fragment继承了相同的命名空间,因为fragment不是“外部导入节点”,它是从$dom创建的:你为什么不同意这个立场?跨度>
      • 命名空间不是在文档上定义的,而是在文档元素节点(或任何其他元素节点)上定义的。您可以从层次结构中单独创建节点或片段。在追加之前,您不知道前缀是为哪个命名空间定义的。如果创建元素或属性需要提供命名空间,片段也是如此。
      • 好的,你的位置在数学上是正确的,并且证明了 default 行为......但只有 default in our view(参见 2014 年的 cmets) . “错误”是不提供替代方案,即“设置名称空间”选项,因为没有最少的“友好的良好实践”,许多潜在的片段应用程序都会丢失(!)。替代示例:标志createDocumentFragment($importRootNamespaces=false)、引用节点createDocumentFragment($refNamespacesNode=NULL)DOMDocumentFragment::setAttributeNS() 方法。
      • ...想象一个通用应用程序,作为您的 FluentDOM (!),其中用户将 DomDocument 的复制/粘贴片段提供给FluentDOM::replace_innerXML($node,$innerXML) 假设方法...如何管理它?你能预测$innerXML 中的哪个命名空间吗?你的replace_innerXML 通过复杂的正则表达式注入$innerXML 中所有可能的命名空间是否有意义?示例$innerXML='nononono &lt;xref xxlink:href="..."&gt;link1&lt;/xref&gt; nonon nonononono &lt;xref yylink:href="..."&gt;link2&lt;/xref&gt;...'.
      • 我添加了一个示例函数。如您所见,它不必是 DOM API 本身的一部分。实际上,我认为命名空间不应该来自元素节点,而是来自外部定义。在 FluentDOM 中,文档对象有一个命名空间定义。这些不是节点上的命名空间定义,而是用于 API(如 DOMXpath::registerNamespace)。 imho片段的命名空间定义应该来自片段或提供片段的应用程序。实际节点的前缀不应与您的应用程序逻辑相关 - 它们可以来自外部来源。
      【解决方案4】:

      关于问题的说明以及与 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 内容,该内容可以是片段内容。请参阅下面的函数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);
      

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

      一个巨大的解决方案

      “INJECT 命名空间”的唯一方法(今天是 2014 年)是

      $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是因为没有避开这个“大象”...解决“PHP bug”的解决方案?

      PHP的理想解决方案

      PHP RCF to solve the problem 有许多替代方案:标志 createDocumentFragment($importRootNamespaces=false)、引用节点 createDocumentFragment($refNamespacesNode=NULL)DOMDocumentFragment::setAttributeNS() method... 所有的默认行为都等同于通常的 createDocumentFragment()(无参数)。

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

      【讨论】:

        猜你喜欢
        • 2013-08-11
        • 2013-12-13
        • 1970-01-01
        • 1970-01-01
        • 2011-01-13
        • 1970-01-01
        • 2023-01-18
        • 2011-01-15
        • 2021-10-29
        相关资源
        最近更新 更多