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

Posted

技术标签:

【中文标题】SimpleXMLElement 在 addChild 和 addAttribute 中处理文本值的基本原理【英文标题】:Rationale behind SimpleXMLElement's handling of text values in addChild and addAttribute 【发布时间】:2010-10-07 20:48:09 【问题描述】:

这不是不一致的行为吗? (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 设计决策?

【问题讨论】:

顺便说一下,如果你必须在你的 标签内放置多个 标签,你可以通过 $a->d[0] 和 $ 访问 $a->d 行为a->d[1] 等 谢谢,格雷格。我不知道。 【参考方案1】:

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

    使用 addAttribute 将 & 符号插入属性

    使用 addChild 将 & 符号插入元素

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

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

基于我的直觉,并受到this bug 的鼓舞,这是一个深思熟虑的设计决定。属性重载 ($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();

这就是那个 bug 中的 PHP 开发人员所提倡的。当您需要在文档中插入 & 符号而不被转义时,addChild 的行为旨在提供“不那么简单、更健壮”的支持。

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

    addAttribute 方法对 & 符号进行转义 addChild 方法不会转义 & 符号 此行为有些不一致。用户期望 SimpleXML 上的方法以一致的方式转义是合理的

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

    元素对象上的属性重载会转义 & 符号 属性对象上的属性重载会转义 & 符号 addChild 方法不会转义 & 符号 addAttribute 方法不会转义 & 符号

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

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

团队本可以添加一个 SimpleXMLAttribute 对象,但这增加了一层复杂性。如果您想要一个多对象层次结构,请使用 DomDoument。

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

真正的教训在这里?也许就是这么简单就很难,而在最后期限内简单就更难了。我不知道是不是这样,但是对于 SimpleXML,似乎有人从一个简单的想法开始(使用属性重载使 XML 文档的创建变得容易),然后随着问题/功能请求的出现进行调整.

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

【讨论】:

你,先生,真棒。感谢您对我无法弄清楚的事情的高质量解释。 "addAttribute 方法是添加属性的唯一方法" - 这是不正确$a-&gt;b['attr'] = 'value';。使用数组访问运算符,您可以使用unset 创建、更改和删除属性。但是,它取决于键:当键是整数时,这处理子元素,但是当键是字符串时,它与属性一起使用。 (注意:我做了最少的试验,所以我可能会遗漏一两个细节!) 我在这里看到@Alan 和我来到这里是因为 Magento 错误涉及这个精确问题:Mage_Usa_Model_Shipping_Carrier_Dhl_International::_doRequest() 有相同的错误(例如,如果客户端有一个包含 &amp;companyname请求将被破坏)【参考方案2】:

这是我的解决方案,特别是这解决了添加多个具有相同标记名的孩子

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

【讨论】:

遗憾的是,这种方法虽然可以一次性完成分配,但它失去了分配能力。例如。 $newnode = $job...... 请注意,使用此对象表示法设置值在 PHP 7.0+ 中不再起作用。相反,您可以使用 $job->addChild('industrycode')[0]。例如,请参阅3v4l.org/cliKZ。【参考方案3】:

“假设您有一个文本数据库,其中所有 & 符号都已转义。”

如果你这样做,那你就错了。数据应该以最准确的形式存储,而不是针对您当前使用的任何类型的输出。如果您实际上在数据库中存储(有效)html 的 blob,情况会更糟。使用 addChild() 并再次获取数据会破坏您的 HTML;没有任何明智的图书馆表现出如此可怕的不对称性。

addChild() 不为您的文本编码是完全违反直觉的。 API 中不能保护您免受此影响的意义何在?如果您在其中一个值中使用双引号,这就像 json_encode() 吐槽。

无论如何,回答最初的问题:显然,我也认为这不是一个好的决定。我确实认为这与 PHP 的许多设计决策是一致的,即实现某人“更快”的想法,而不是正确的。

【讨论】:

【参考方案4】:

转义字符 &amp;&amp;lt; 的要求在 Character Data and Markup 部分中提供,而不是在属性值规范化部分中提供,如上一个答案所述

To quote the XML Spec.

“和号字符 (&) 和左尖括号 (&amp; 和 &amp;lt; 进行转义"

【讨论】:

【参考方案5】:

Alan Storm 对这个问题有很好的描述,但是对于他描述的悖论,有一个简单的解决方案。 addChild() 方法可以有一个可选的布尔参数,用于确定是否自动转义字符。所以,我仍然坚信这只是一个(非常)糟糕的设计选择。

addChild() 方法的文档没有提供任何参考,因此问题更加复杂(尽管正在讨论中)。此外,该方法转义了一些字符(即小于号和大于号)。这会误导使用该方法的开发人员认为它通常会转义字符。

【讨论】:

【参考方案6】:

我相信这是由 XML 规范要求的 Attribute-Value Normalization 引起的。

【讨论】:

以上是关于SimpleXMLElement 在 addChild 和 addAttribute 中处理文本值的基本原理的主要内容,如果未能解决你的问题,请参考以下文章

让 SimpleXMLElement 在输出中包含编码

如何在 PHP 中回显此 SimpleXMLElement 中的 OK 属性? [复制]

从PHP SimpleXMLElement中提取数组

Laravel - 未找到 SimpleXMLElement'

获取 SimpleXMLElement 的 XML 内容

未捕获的错误:找不到类“SimpleXMLElement”