返回

PHP SimpleXML 自闭合标签问题及解决方法

php

阻止PHP SimpleXML中的自闭合标签

使用PHP的SimpleXML生成XML时,有时会遇到一个问题,那就是当某个元素没有内容时,它会以自闭合标签的形式出现,比如<noValue/>,而不是我们期望的 <noValue></noValue>。这种行为在一些XML解析器或应用场景中可能导致问题,因此需要一种方法来控制 SimpleXML 的输出,确保生成预期格式的 XML。

理解问题根源

SimpleXML 在处理没有内容的标签时,会为了简化 XML 结构并减少文件大小,将其表示为自闭合标签。这是一个符合 XML 标准的有效表示形式,但在需要显式空标签时则无法满足需求。LIBXML_NOEMPTYTAG 是一个 libxml 库中的常量,用于控制 XML 文档中的空标签输出行为。然而,这个常量并不能直接在SimpleXMLElement构造函数中应用。问题在于SimpleXMLElement构造函数只接收 XML 字符串,并且无法控制空标签的呈现形式,这种呈现由asXML()方法完成。

解决方案一:手动替换自闭合标签

一个简单直接的方法是,在生成 XML 后,使用字符串替换函数将自闭合标签替换为完整的空标签。这种方法虽有些“粗暴”,但可以快速解决问题。

步骤:

  1. 使用SimpleXML生成 XML 。
  2. 获取XML的字符串表示。
  3. 使用正则表达式,查找所有的自闭合标签(如/<(\w+)\/>/)。
  4. 使用 preg_replace 函数将这些标签替换成完整标签(如 <$1></$1>)。

代码示例:

<?php
$xml = new SimpleXMLElement('<xml/>');

$output = $xml->addChild('child1');
$output->addChild('child2', "value");
$output->addChild('noValue', '');

$xmlString = $xml->asXML();
$xmlString = preg_replace('/<(\w+)\/>/', '<$1></$1>', $xmlString);

Header('Content-type: text/xml');
print($xmlString);
?>

解释:

上述代码首先按照原方式创建了SimpleXML对象并添加了相应的元素,接下来调用asXML()方法获取XML的字符串形式,使用preg_replace 函数查找所有自闭合标签<tag/>并将它替换为 <tag></tag>

这种方式优点在于实现简单快速,缺点是需要进行一次字符串操作,当XML结构较大或者非常频繁的进行 XML 操作的时候,可能会影响到程序的性能。

解决方案二:使用 DOMDocument

更好的办法是使用 DOMDocumentDOMElement,这是 PHP 的另一个 XML 处理扩展。 DOMDocument 可以更细致地控制 XML 结构,并使用 createElementappendChild 方法添加元素,并且saveXML 函数可以根据我们设置参数来控制标签的输出形式,更直接有效。

步骤:

  1. 创建 DOMDocument 对象。
  2. 创建 XML 根元素,并使用 appendChild 方法附加到文档。
  3. 创建子元素,如果值为空字符串,直接调用appendChild追加,否则先调用appendChild添加值节点再添加值节点。

代码示例:

<?php

$doc = new DOMDocument('1.0', 'UTF-8');

$xml = $doc->appendChild($doc->createElement('xml'));
$child1 = $xml->appendChild($doc->createElement('child1'));
$child2 = $child1->appendChild($doc->createElement('child2'));
$child2->appendChild($doc->createTextNode('value'));

$noValue = $child1->appendChild($doc->createElement('noValue'));


Header('Content-type: text/xml');
$doc->formatOutput = true;
print $doc->saveXML();

?>

解释:

首先创建DOMDocument实例。createElement() 方法用于创建元素,createTextNode方法用于创建文本节点。使用appendChild()方法将创建的元素添加到父元素。需要强调的一点是,因为我们只创建空标签的时候并没有为标签创建内容节点,这也就是DOM可以生成<noValue></noValue>的原因。通过设置$doc->formatOutput = true,可以格式化XML输出,提高可读性。这种方法相比字符串替换方法,提供了更准确的 XML 控制,是一种更优选的解决方案。

额外的安全建议

无论是采用哪种方案,处理用户提供的 XML 数据时,请务必进行输入验证和转义,以避免 XML 注入攻击。可以使用 PHP 的内置函数,例如 htmlspecialchars()DOMDocument 类的方法来确保 XML 内容的安全。永远不要相信任何来自外部来源的XML数据。

在处理 XML 输出的时候也要小心转义可能被误解的特殊字符,例如 “<”, “>”, “&”, 这些字符如果不经过适当转义会被浏览器解释成其他的HTML标签导致潜在的跨站脚本风险(XSS),所以永远不要在网页端直接输出xml内容。