【问题标题】:How to distinguish between empty element and null-size string in DOMDocument?如何区分 DOMDocument 中的空元素和 null 大小的字符串?
【发布时间】:2014-06-07 14:06:52
【问题描述】:

我无法将 XML 文档加载到 DOM 中,保留空标签和 null 大小的字符串。这里是例子:

$doc = new DOMDocument("1.0", "utf-8");

$root = $doc->createElement("root");
$doc->appendChild($root);

$element = $doc->createElement("element");
$root->appendChild($element);

echo $doc->saveXML();

生成以下 XML:

<?xml version="1.0" encoding="utf-8"?>
<root><element/></root>

空元素,完全符合预期。现在让我们将空文本节点添加到元素中。

$doc = new DOMDocument("1.0", "utf-8");

$root = $doc->createElement("root");
$doc->appendChild($root);

$element = $doc->createElement("element");
$element->appendChild($doc->createTextNode(""));
$root->appendChild($element);

echo $doc->saveXML();

生成以下 XML:

<?xml version="1.0" encoding="utf-8"?>
<root><element></element></root>

具有空大小字符串的非空元素。好的!但是当我想做的时候:

$doc = new DOMDocument();
$doc->loadXML($xml);

echo $doc->saveXML($doc);

在这些 XML 文档上我总是得到

<?xml version="1.0" encoding="utf-8"?>
<root><element/></root>

即删除了空大小的字符串,只加载了空元素。我相信它发生在 loadXML() 上。 有什么方法可以说服 DOMDocument loadXML() 不要将 null 大小的字符串转换为空元素? 如果 DOM 将带有 null 大小字符串的 TextNode 作为元素的子元素,那就更好了。

解决方案需要在 PHP DOM 中,因为加载的数据会进一步发生变化。

【问题讨论】:

  • 顺便问一下你的 $xml 是什么?
  • How to create an XML text node with an empty string value (in Java) 的可能副本。空文本节点(零长度字符串)不是文本节点,见stackoverflow.com/a/3885737/2044940
  • 任意 xhtml 内容。我发现如果文档用作 Content-Type: text/html ,则 xhtml 的完全有效的 元素无法正确呈现。它必须表示为 以便浏览器正确理解。所以我的想法是在交付之前加载 xhtml 以查找标记是否有空的 元素并相应地修复它。更改服务器上的 Content-Type 是没有问题的,因为如果文件保存在本地,浏览器会使用 text/html。
  • @CoDEmanX 不是骗子:我很清楚 XML 解析器认为无字符串和空字符串之间没有区别。我的问题是关于如何通过使用空的 TextNode 来注意加载 DOM 树中的这种差异 - 如我上面的示例所示。我可以创建空子 TextNode。我可以在加载时做同样的事情吗?
  • XML 和 XHTML 与 HTML 不兼容,XSLT 处理器确实不能用于 HTML。我怀疑 PHP 有一个隐藏选项来禁用要使用的自关闭元素,因此您必须遍历整个 DOM 树并添加空文本节点(不确定如何),或者使用 XSLT 处理器来扩展自关闭元素(示例发布在自己的答案中)。

标签: php xml


【解决方案1】:

区分这两者的问题是,当DOMDocument加载XML序列化文档时,它只遵循规范。

按照书本,在&lt;element&gt;&lt;/element&gt; 中,该元素中没有空文本节点 - 这也是其他人已经评论过的。

但是 DOMDocument 如果您在其中插入自己的空文本节点,则完全没问题。然后,您可以轻松区分自闭合标签(无子元素)和空元素(有一个子元素,一个空文本节点)。

那么如何输入那些空的文本节点呢?例如,通过使用来自the XMLReader based XMLReaderIterator library,特别是 DOMReadingIteration,它能够构建文档,同时提供每个当前 XMLReader 节点进行交互:

$doc = new DOMDocument();

$iterator = new DOMReadingIteration($doc, $reader);

foreach ($iterator as $index => $value) {
    // Preserve empty elements as non-self-closing by making them non-empty with a single text-node
    // children that has zero-length text
    if ($iterator->isEndElementOfEmptyElement()) {
        $iterator->getLastNode()->appendChild(new DOMText(''));
    }
}

echo $doc->saveXML();

您可以输入:

<?xml version="1.0" encoding="utf-8"?>
<root><element></element></root>

这个输出:

<?xml version="1.0"?>
<root><element></element></root>

没有附加条件。一个很好的构建DOMDocument。该示例来自examples/read-into-dom.php,并且很好地证明了当您通过 XMLReader 加载文档并处理您拥有的单个特殊情况时没有问题。

【讨论】:

  • 正是我想要的!尝试了您的解决方案,效果很好!我应该赞扬您出色的 PHP 技能 - 阅读您的源代码是一种乐趣!顺便说一句:有没有办法自动将 git repo 拉入 svn? :) 开玩笑!那是另一个问题...谢谢!
  • @user3713667:这个 git repo 由支持 SVN 的 github 托管:Subversion checkout URL:github.com/hakre/XMLReaderIterator
  • 很高兴知道!谢谢!
【解决方案2】:

这对于加载 XML 解析器没有区别。 DOM 完全相同。

如果您加载/保存存在空标签问题的 XML 格式,您可以使用一个选项来避免保存时出现空标签:

$dom = new DOMDocument();
$dom->appendChild($dom->createElement('foo'));

echo $dom->saveXml();
echo "\n";
echo $dom->saveXml(NULL, LIBXML_NOEMPTYTAG);

输出:

<?xml version="1.0"?>
<foo/>

<?xml version="1.0"?>
<foo></foo>

【讨论】:

  • 我保存部分没有问题。将空的 TextNode 作为子元素附加到元素可以避免它以自关闭的方式保存。我的问题是,在将 XML 加载回 DOM 后,我无法取回这个空的 TextNode。或者我希望至少有一些属性来指示元素在原始 XML 文档中是自关闭的还是为空的。
【解决方案3】:

您可以通过假装 xsl:value-of 插入一个变量,但该变量是一个空字符串 '',来欺骗 XSLT 处理器不使用自闭合元素。

输入:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <foo>
    <bar some="value"></bar>
    <self-closing attr="foobar" val="3.5"/>
  </foo>
  <goo>
    <gle>
      <nope/>
    </gle>
  </goo>
</root>

样式表:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

  <xsl:template match="*[not(node())]">
    <xsl:copy>
      <xsl:for-each select="@*">
        <xsl:attribute name="{name()}">
          <xsl:value-of select="."/>
        </xsl:attribute>
      </xsl:for-each>
      <xsl:value-of select="''"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

输出:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <foo>
    <bar some="value"></bar>
    <self-closing attr="foobar" val="3.5"></self-closing>
  </foo>
  <goo>
    <gle>
      <nope></nope>
    </gle>
  </goo>
</root>

为了在不使用 XSLT 处理器的情况下在 PHP 中解决这个问题,我只能考虑向所有没有子元素的元素添加空文本节点(就像您在创建 XML 时所做的那样)。

【讨论】:

  • 看来核心问题是检测XML流中的元素是自封闭还是空元素。查看 libxml 源代码很明显,它不能区分两者,因此 PHP DOM(作为 libxml 的直接用户)无法做到这一点。其他 xml 解析器可能,但我们仅限于 PHP DOM。所以看来我们需要使用一些丑陋的正则表达式来获取自封闭的。幸运的是,我们只需要检测它,不需要转换。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-04-10
  • 2022-11-22
  • 2015-03-10
  • 2018-12-26
  • 2020-01-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多