SimpleXML,请不要扩展实体

时间:2013-11-03 04:38:02

标签: php xml simplexml

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

在将文件内容传递给XML解析器之前,我曾想过“预解析”XML文件以去除<!ENTITY位,但这感觉很麻烦,因为它是一个巨大的文件,我'而宁可尽可能地摆弄它。

(请原谅以上任何错误的术语;我在相当长的一段时间内没有完成这个级别的XML工作。)

1 个答案:

答案 0 :(得分:3)

看起来似乎如此,但事实并非如此(除非你指定了我认为你不会的标志,尽管你没有在代码中显示你做了什么)。只是如果你使用->asXML()方法而不是通过to-string-implementation,SimpleXML只能将它返回给你。

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

<!ENTITY n "noun (common) (futsuumeishi)">

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

$xml = simplexml_load_file($file);
$pos = $xml->entry->sense->pos;

变量$pos现在是<pos>元素节点的SimpleXMLElement。让我们输出它以查看解析器对&n;实体的作用:

echo  "SimpleXML value (string): ", $pos         , "\n"
    , "SimpleXML value (XML)   : ", $pos->asXML(), "\n";

输出是:

SimpleXML value (string): noun (common) (futsuumeishi)
SimpleXML value (XML)   : <pos>&n;</pos>

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

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

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

$xml = simplexml_load_file($file, NULL, LIBXML_NOENT);

这实际上会按照您的假设执行,现在实体已展开,XML输出 不再包含实体:

SimpleXML value (string): noun (common) (futsuumeishi)
SimpleXML value (XML)   : <pos>noun (common) (futsuumeishi)</pos>

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

Mode 1:
DOMDocument Class       : DOMEntityReference
DOMDocument value(XML)  : &n;
DOMDocument ->nodeName  : n

Mode 2 (LIBXML_NOENT):
DOMDocument Class       : DOMText
DOMDocument value(XML)  : noun (common) (futsuumeishi)
DOMDocument ->nodeName  : #text

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

$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";

正如所写,它是一个姊妹图书馆,dom_import_simplexml$pos转变为DOMElement我们需要遍历它的子项,我们知道它是所讨论的实体引用。< / p>

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

否则会有什么方法可以区分

的字符串值
<pos>&n;</pos>
<pos><![CDATA[&n;]]></pos>

?所以你要求的只是有限的意义。然而,这并不意味着我们无法解决这个问题,因此可以通过从中扩展来欺骗SimpleXML。假设每个仅包含单个实体的子元素应该返回。否则应使用标准的SimpleXML stringyfication:

/**
 * 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);
    }
}

让我们从上面开始运行我们的例子:

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";

SimpleXML现在使用扩展类,然后按预期提供:

SimpleXML value (string): &n;
SimpleXML value (XML)   : <pos>&n;</pos>

&n;,因为它是唯一的子项,现在保留在SimpleXMLElement的to-string转换中。但只是因为这个工作并不意味着你应该使用它,它打破了文本形式的解析XML和文档模型意义上的XML之间的编码边界。

可能你只是在寻找DOMDocument?这是一个包含更多细节的模型,如果有的话,你可以使用DOMEntityReference