SimpleXMLElement 在 addChild 和 addAttribute 中处理文本值的基本原理

2022-08-30 11:36:42

这难道不是一种不一致的行为吗?(PHP 5.2.6)

<?php

$a = new SimpleXMLElement('<a/>');

$a->addAttribute('b', 'One & Two');
//$a->addChild('c', 'Three & Four'); -- results in "unterminated entity reference" warning!
$a->addChild('c', 'Three &amp; Four');
$a->d = 'Five & Six';

print($a->asXML());

呈现:

<?xml version="1.0"?>
<a b="One &amp; Two">
    <c>Three &amp; Four</c>
    <d>Five &amp; Six</d>
</a>

在 bugs.php.net 他们拒绝了所有关于此的提交,称这是一项功能。为什么会这样呢?顺便说一句,文档中没有任何关于SimpleXMLElement转义文本值的差异。

谁能说服我这是最好的API设计决策?


答案 1

为了确保我们在同一页面上,您有三种情况。

  1. 使用 addAttribute 将 & 符号插入到属性中

  2. 使用 addChild 将 & 符号插入到元素中

  3. 通过属性重载将 & 符号插入元素

这是2和3之间的差异让你感到困惑。为什么 addChild 不会自动转义 & 符号,而向对象添加属性并设置其值会自动转义 & 符号?

根据我的直觉,并受到这个错误的支持,这是一个深思熟虑的设计决定。属性重载 ($a->d = 'Five & Six';)旨在成为“为我转义与号”的做事方式。addChild 方法的意思是“添加我告诉您添加的内容”方法。因此,无论您需要哪种行为,SimpleXML都可以满足您的需求。

假设您有一个文本数据库,其中所有 & 符号都已转义。自动转义在这里对您不起作用。这就是你使用addChild的地方。或者假设您需要在文档中插入实体

$a = simplexml_load_string('<root></root>');
$a->b = 'This is a non-breaking space &nbsp;';
$a->addChild('c','This is a non-breaking space &nbsp;');    
print $a->asXML();

这就是该错误中的PHP开发人员所提倡的。addChild 的行为旨在当您需要在文档中插入 & 符号而不对其进行转义时提供“更简单,更可靠”的支持。

当然,这确实给我们留下了我提到的第一种情况,即addAttribute方法。addAttribute 方法转义与号。因此,我们现在可以将不一致声明为

  1. addAttribute 方法转义与号
  2. addChild 方法转义与号
  3. 此行为有些不一致。用户期望 SimpleXML 上的方法以一致的方式转义是合理的。

这就暴露了 SimpleXML api 的真正问题。这里的理想情况是

  1. 元素对象上的属性重载对 & 符号进行转义
  2. 属性对象上的属性重载对 & 符号进行转义
  3. addChild 方法不转义与号
  4. addAttribute 方法不转义与号

但这是不可能的,因为 SimpleXML 没有属性对象的概念。addAttribute 方法是(似乎是?)添加属性的唯一方法。正因为如此,事实证明(似乎?SimpleXML 无法使用实体创建属性。

所有这些都揭示了简单XML 的悖论。这个API背后的想法是提供一种与复杂事物交互的简单方法。

该团队本可以添加一个 SimpleXMLAttribute 对象,但这是一个额外的复杂性层。如果需要多对象层次结构,请使用 DomDoument。

该团队本可以向 addAttribute 和 addChild 方法添加标志,但标志会使 API 更加复杂。

真正的教训是什么?也许简单很难,而在截止日期前简单更难。我不知道情况是否如此,但是对于SimpleXML,似乎有人从一个简单的想法开始(使用属性重载使XML文档的创建变得容易),然后随着问题/功能请求的出现进行调整。

实际上,我认为这里真正的教训是只使用JSON;)


答案 2

这是我的解决方案,特别是这解决了添加几个具有相同标签名称的孩子的问题

$job->addChild('industrycode')->{0} = $entry1;
$job->addChild('industrycode')->{0} = $entry2;
$job->addChild('industrycode')->{0} = $entry3;

推荐