【问题标题】:CakePHP Xml utility library triggers DOMDocument warningCakePHP Xml 实用程序库触发 DOMDocument 警告
【发布时间】:2014-05-22 06:54:51
【问题描述】:

我正在使用 CakePHP 的Xml core library 在视图中生成 XML:

$xml = Xml::build($data, array('return' => 'domdocument'));
echo $xml->saveXML();

View 是从控制器提供的一个数组:

$this->set(
    array(
        'data' => array(
            'root' => array(
                array(
                    '@id' => 'A & B: OK',
                    'name' => 'C & D: OK',
                    'sub1' => array(
                        '@id' => 'E & F: OK',
                        'name' => 'G & H: OK',
                        'sub2' => array(
                            array(
                                '@id' => 'I & J: OK',
                                'name' => 'K & L: OK',
                                'sub3' => array(
                                    '@id' => 'M & N: OK',
                                    'name' => 'O & P: OK',
                                    'sub4' => array(
                                        '@id' => 'Q & R: OK',
                                        '@'   => 'S & T: ERROR',
                                    ),
                                ),
                            ),
                        ),
                    ),
                ),
            ),
        ),
    )
);

不管出于什么原因,CakePHP 发出这样的内部调用:

$dom = new DOMDocument;
$key = 'sub4';
$childValue = 'S & T: ERROR';
$dom->createElement($key, $childValue);

... 触发 PHP 警告:

Warning (2): DOMDocument::createElement(): unterminated entity reference               T [CORE\Cake\Utility\Xml.php, line 292

... 因为 (as documented),DOMDocument::createElement 不会转义值。但是,正如测试用例所示,它仅在某些节点中执行。

是我做错了什么还是我只是在 CakePHP 中遇到了一个错误?

【问题讨论】:

  • $dom->createElement($key, htmlspecialchars($childValue)); 这样的包装值就可以了
  • @Alliswell - 请再次阅读问题。这是一个 CakePHP 问题,我没有直接调用 DOM 函数,只是构建一个数组。而且我无法以这种方式修补 CakePHP 核心,因为有些元素已经被转义,而另一些则没有。 (有关其他详细信息,请参阅接受的答案。)

标签: php xml cakephp domdocument cakephp-2.2


【解决方案1】:

这是 PHP 中的 bug DOMDocument::createElement() 方法。这里有两种方法可以避免这个问题。

创建文本节点

单独创建文本节点并将其附加到元素节点。

$dom = new DOMDocument;
$dom
  ->appendChild($dom->createElement('element'))
  ->appendChild($dom->createTextNode('S & T: ERROR'));

var_dump($dom->saveXml());

输出:

string(58) "<?xml version="1.0"?>
<element>S &amp; T: ERROR</element>
"

这是将文本节点添加到 DOM 的最初预期方式。您始终创建一个节点(元素、文本、cdata、...)并将其附加到其父节点。您可以将多个节点和不同类型的节点添加到一个父节点。就像下面的例子:

$dom = new DOMDocument;
$p = $dom->appendChild($dom->createElement('p'));
$p->appendChild($dom->createTextNode('Hello '));
$b = $p->appendChild($dom->createElement('b'));
$b->appendChild($dom->createTextNode('World!'));

echo $dom->saveXml();

输出:

<?xml version="1.0"?>
<p>Hello <b>World!</b></p>

属性DOMNode::$textContent

DOM Level 3 引入了一个名为textContent 的新节点属性。它根据节点类型抽象节点的内容/值。设置元素节点的$textContent 会将其所有子节点替换为单个文本节点。读取它会返回所有后代文本节点的内容。

$dom = new DOMDocument;
$dom
  ->appendChild($dom->createElement('element'))
  ->textContent = 'S & T: ERROR';

var_dump($dom->saveXml());

【讨论】:

  • 我还没有测试你是否可以在数据数组中插入 DOMDocument 对象但是,如果你事先知道需要修复哪些值¹,这是一个非常复杂的解决方法:) —— (¹) 我不知道我什么时候问的。
  • 其实这不是解决办法。 createElement() 中的第二个参数违反了 W3C DOM 规范。上面的示例是添加文本节点的标准方法。方法中的参数只是一个捷径——一个坏掉的捷径。
【解决方案2】:

问题似乎出在同时具有属性和值的节点中,因此需要使用@ 语法:

'@id' => 'A & B: OK',  // <-- Handled as plain text
'name' => 'C & D: OK', // <-- Handled as plain text
'@' => 'S & T: ERROR', // <-- Handled as raw XML

我写了一个小辅助函数:

protected function escapeXmlValue($value){
    return is_null($value) ? null : htmlspecialchars($value, ENT_XML1, 'UTF-8');
}

...并在我创建数组时手动调用它:

'@id' => 'A & B: OK',
'name' => 'C & D: OK',
'@' => $this->escapeXmlValue('S & T: NOW WORKS FINE'),

很难说这是错误还是功能,因为documentation 没有提到它。

【讨论】:

    【解决方案3】:

    这实际上是因为 DOMDocument 方法希望在 html 中输出正确的字符;即&amp;等字符会破坏内容并产生unterminated entity reference错误

    在使用它创建元素之前只需 htmlentities() :

    $dom = new DOMDocument;
    $key = 'sub4';
    $childValue = htmlentities('S & T: ERROR');
    $dom->createElement($key ,$childValue);
    

    【讨论】:

      【解决方案4】:

      因为这个字符:&amp;amp; 你需要用相关的 HTML 实体替换它。 &amp;amp; 要执行翻译,您可以使用 htmlspecialchars 函数。在写入 nodeValue 属性时,您必须转义该值。引用自 2005 年位于 here 的错误报告

      & 符号在设置时被正确编码 属性文本内容。不幸的是,当 文本字符串作为可选的第二个参数传递给 DOMElement::createElement 您必须创建一个文本节点,设置 textContent,然后附加文本 新元素的节点。

      htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
      

      这是翻译表:

      '&' (ampersand) becomes '&amp;'
      '"' (double quote) becomes '&quot;' when ENT_NOQUOTES is not set.
      "'" (single quote) becomes '&#039;' (or &apos;) only when ENT_QUOTES is set.
      '<' (less than) becomes '&lt;'
      '>' (greater than) becomes '&gt;'
      

      这个脚本会递归地进行翻译:

      <?php
      function clean($type) {
        if(is_array($type)) {
          foreach($type as $key => $value){   
           $type[$key] = clean($value);
          }
          return $type;
        } else {
          $string = htmlspecialchars($type, ENT_QUOTES, 'UTF-8');
          return $string;
        }
      }
      
      $data = array(
          'data' => array(
              'root' => array(
                  array(
                      '@id' => 'A & B: OK',
                      'name' => 'C & D: OK',
                      'sub1' => array(
                          '@id' => 'E & F: OK',
                          'name' => 'G & H: OK',
                          'sub2' => array(
                              array(
                                  '@id' => 'I & J: OK',
                                  'name' => 'K & L: OK',
                                  'sub3' => array(
                                      '@id' => 'M & N: OK',
                                      'name' => 'O & P: OK',
                                      'sub4' => array(
                                          '@id' => 'Q & R: OK',
                                          '@' => 'S & T: ERROR',
                                      ) ,
                                  ) ,
                              ) ,
                          ) ,
                      ) ,
                  ) ,
              ) ,
          ) ,
      );
      
      $data = clean($data);
      

      输出

      Array
      (
          [data] => Array
              (
                  [root] => Array
                      (
                          [0] => Array
                              (
                                  [@id] => A &amp; B: OK
                                  [name] => C &amp; D: OK
                                  [sub1] => Array
                                      (
                                          [@id] => E &amp; F: OK
                                          [name] => G &amp; H: OK
                                          [sub2] => Array
                                              (
                                                  [0] => Array
                                                      (
                                                          [@id] => I &amp; J: OK
                                                          [name] => K &amp; L: OK
                                                          [sub3] => Array
                                                              (
                                                                  [@id] => M &amp; N: OK
                                                                  [name] => O &amp; P: OK
                                                                  [sub4] => Array
                                                                      (
                                                                          [@id] => Q &amp; R: OK
                                                                          [@] => S &amp; T: ERROR
                                                                      )
      
                                                              )
      
                                                      )
      
                                              )
      
                                      )
      
                              )
      
                      )
      
              )
      
      )
      

      【讨论】:

      • OP 明确指出这是一个警告 - 但想了解其原因。简单地忽略警告是一个非常的坏主意。
      • 抑制消息并不能解决根本问题。这就像把你的手指伸进你的耳朵里,然后大声唱歌。
      • 它确实在某些情况下修复了它。
      • 你确定吗? Docs 不要提到手动预处理数据的必要性,我的示例在其他所有值中都使用与号做正确的事情......
      • DOMDocument 是原生的 PHP 类,所以我认为 CakePHP 和 PHP 编程语言在这里是互斥的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多