SimpleXML,请不要展开实体

Gar*_*ght 4 php xml simplexml

我正在使用 SimpleXML 尝试解析带有声明的大型 XML 文件<!ENTITY。不幸的是,SimpleXML 似乎太急于继续扩展这些实体,而我宁愿它没有,因为实体符号很短,易于解析,并且理论上不会在较新版本的文件中改变,而扩展的实体是可能会改变的英语句子。有没有办法告诉 SimpleXML 取消它?

我想过<!ENTITY在将文件内容传递给 XML 解析器之前“预解析”XML 文件以去除这些位,但这感觉很糟糕,而且由于它是一个巨大的文件,我宁愿对它做一点点摆弄尽可能。

(请原谅上述任何错误的术语;我已经有一段时间没有完成这种级别的 XML 工作了。)

hak*_*kre 6

看起来似乎是这样,但事实并非如此(除非您指定了标志,我猜您不会在代码中显示您所做的事情)。只是 SimpleXML 只能在您使用该->asXML()方法而不是通过 to-string-implementation返回给您。

让我们做一些例子来演示它是如何工作的。我从 DTD 中选择了这个简单的实体:

<!ENTITY n "noun (common) (futsuumeishi)">
Run Code Online (Sandbox Code Playgroud)

所以让我们选择第一个<pos>元素,因为它包含一个&n;实体:

$xml = simplexml_load_file($file);
$pos = $xml->entry->sense->pos;
Run Code Online (Sandbox Code Playgroud)

该变量$pos现在是<pos>元素节点的 SimpleXMLElement 。让我们输出它看看解析器对&n;实体做了什么:

echo  "SimpleXML value (string): ", $pos         , "\n"
    , "SimpleXML value (XML)   : ", $pos->asXML(), "\n";
Run Code Online (Sandbox Code Playgroud)

输出是:

<!ENTITY n "noun (common) (futsuumeishi)">
Run Code Online (Sandbox Code Playgroud)

如本例所示,&n;仍然存在 ( <pos>&n;</pos>),只是当您将其作为字符串值 ( noun (common) (futsuumeishi))访问时,它将被扩展。

顺便说一下,这完全没问题,XML 规范在这里说,是否扩展这些实体取决于解析器。对于 SimpleXML 的设计目的,在读取字符串值时,这完全可以扩展。

您甚至可以通过指定LIBXML_NOENT选项来控制此行为:

$xml = simplexml_load_file($file, NULL, LIBXML_NOENT);
Run Code Online (Sandbox Code Playgroud)

这实际上会执行您当时假设的操作,现在扩展实体,XML 输出不再包含实体:

$xml = simplexml_load_file($file);
$pos = $xml->entry->sense->pos;
Run Code Online (Sandbox Code Playgroud)

所以现在双问号如何做你正在寻找的东西?好吧,实际上具有实体模型的 PHP 中的 XML 解析器是 DOMDocument。它是 SimpleXML 的姊妹库,内部共享相同的内存对象。这是不带和带这两种模式的同一对象(更准确地说:它唯一的子节点)的输出LIBXML_NOENT

echo  "SimpleXML value (string): ", $pos         , "\n"
    , "SimpleXML value (XML)   : ", $pos->asXML(), "\n";
Run Code Online (Sandbox Code Playgroud)

这是由以下代码创建的,它应该使给定输出后面的内容更加可见:

$node   = dom_import_simplexml($pos);
$doc    = $node->ownerDocument;
$entity = $node->firstChild;

echo  "DOMDocument Class       : ", get_class($entity)    , "\n"
    , "DOMDocument value(XML)  : ", $doc->saveXML($entity), "\n"
    , "DOMDocument ->nodeName  : ", $entity->nodeName     , "\n";
Run Code Online (Sandbox Code Playgroud)

正如所写的那样,它是一个姊妹库,dom_import_simplexml变成$pos了一个DOMElement,我们需要遍历它的子库,我们知道这是有问题的实体引用。

所以现在这开始变得非常有意义:因为 SimpleXML 不能表示实体引用,它只能提供扩展的字符串值包含实体的 XML。

否则有什么方法可以改变字符串的值

<pos>&n;</pos>
<pos><![CDATA[&n;]]></pos>
Run Code Online (Sandbox Code Playgroud)

? 所以你所要求的只是有限的意义。然而,这并不意味着我们无法处理它,因此可以通过扩展 SimpleXML 来欺骗它来做到这一点。假设每个只包含单个实体的子元素都应该返回 so。否则应该使用标准的 SimpleXML 字符串化:

/**
 * Class EntityPreserveXML
 */
class EntityPreserveXML extends SimpleXMLElement
{
    /**
     * @return string
     */
    public function __toString()
    {
        $dom = dom_import_simplexml($this);
        if (
            !$dom instanceof DOMElement
            || $dom->childNodes->length !== 1
            || ! $dom->firstChild instanceof DOMEntityReference
        ) {
            return parent::__toString();
        }

        return $dom->ownerDocument->saveXML($dom->firstChild);
    }
}
Run Code Online (Sandbox Code Playgroud)

让我们在上面的例子中运行它:

require('EntityPreserveXML.php');
$xml = simplexml_load_file($file, 'EntityPreserveXML');
$pos = $xml->entry->sense->pos;

echo  "SimpleXML value (string): ", $pos         , "\n"
    , "SimpleXML value (XML)   : ", $pos->asXML(), "\n";
Run Code Online (Sandbox Code Playgroud)

SimpleXML 现在使用扩展类,然后按预期给出:

SimpleXML value (string): noun (common) (futsuumeishi)
SimpleXML value (XML)   : <pos>&n;</pos>
Run Code Online (Sandbox Code Playgroud)

&n;,因为它是唯一的孩子现在被保存在到字符串转换为SimpleXMLElement的。但仅仅因为这有效并不意味着您应该使用它,它打破了文本形式的解析 XML 和文档模型含义中的 XML 之间的编码边界。

可能您只是在寻找 DOMDocument?这是一个包含更多细节的模型,您可以从中使用DOMEntityReferences(如果有的话)。

  • 对一个坦率而相当模糊的问题的非常彻底的回答!在某些方面,它是 [我不久前写的这个](http://stackoverflow.com/a/13981917/157957) 的一个很好的伴侣,关于“LIBXML_NOCDATA”的实际含义,以及*它*与实体处理的关系. (2认同)
  • @GarrettAlbright:您可以根据需要在此处同时使用 SimpleXML 和 DOMDocument。而且我认为不需要正则表达式,您应该更彻底地考虑具体需要什么。这是我能给出的最好的建议。答案只是解释了它是如何工作的。 (2认同)