PHP DOM加载含未定义实体引用的XML时如何保留实体节点?
解决方案:保留XML中未定义实体引用的两种实现方式
libxml默认遇到未定义实体时会报错,即使开启recover=true也会自动丢弃实体引用节点。要实现加载XML并保留所有实体引用(无论是否定义),可采用以下两种方法:
方法一:通过XMLReader读取后导入DOM
XMLReader对未定义实体的容忍度更高,可保留实体引用节点,再导入到DOMDocument中使用:
<?php libxml_use_internal_errors(true); $xml = "<a>&b;</a>"; // 初始化XMLReader,关闭实体替换 $reader = new XMLReader(); $reader->xml($xml, null, ~XMLReader::SUBST_ENTITIES); // 初始化目标DOMDocument,关闭实体替换 $dom = new DOMDocument(); $dom->substituteEntities = false; // 遍历读取节点并导入到DOM while ($reader->read()) { if ($reader->nodeType !== XMLReader::SIGNIFICANT_WHITESPACE) { $node = $dom->importNode($reader->expand(), true); $dom->appendChild($node); } } // 验证结果:输出5,b (5对应DOMEntityReference节点类型) foreach ($dom->documentElement->childNodes as $node) { echo $node->nodeType . "," . $node->nodeName . " "; }
关键说明
~XMLReader::SUBST_ENTITIES:关闭XMLReader的实体替换功能,保留原始实体引用节点$reader->expand():将当前XMLReader节点转换为DOM节点,再通过importNode导入到目标DOM中
方法二:动态补全未定义实体后用DOMDocument加载
先通过错误捕获提取所有未定义实体,动态添加到DOCTYPE中再重新加载:
<?php $xml = "<a>&b;&c;</a>"; // 第一步:捕获未定义实体的错误 libxml_use_internal_errors(true); $tempDom = new DOMDocument(); $tempDom->loadXML($xml); $errors = libxml_get_errors(); libxml_clear_errors(); // 提取所有未定义的实体名称 $undefinedEntities = []; foreach ($errors as $error) { if (preg_match("/Entity '(\w+)' not defined/", $error->message, $matches)) { $undefinedEntities[] = $matches[1]; } } // 第二步:给XML添加临时实体定义 if (!empty($undefinedEntities)) { $entityDefs = implode('', array_map(function($name) { return "<!ENTITY $name 'TEMP'>"; }, $undefinedEntities)); $xml = "<!DOCTYPE a [$entityDefs]>" . $xml; } // 第三步:重新加载XML,保留实体引用 $dom = new DOMDocument(); $dom->substituteEntities = false; $dom->loadXML($xml); // 验证结果:输出5,b 5,c foreach ($dom->documentElement->childNodes as $node) { echo $node->nodeType . "," . $node->nodeName . " "; }
关键说明
- 先通过临时DOM加载捕获错误,提取未定义实体名称
- 动态生成DOCTYPE实体定义,让DOMDocument能正常加载XML且不替换实体
结论
- 直接使用DOMDocument无法保留未定义实体引用,因为libxml的
recover模式会自动丢弃这类节点 - 优先推荐XMLReader+DOM导入的方法,无需修改原始XML,可靠性更高
- 动态补全实体的方法适合必须使用DOMDocument直接加载的场景,但依赖错误捕获逻辑
内容的提问来源于stack exchange,提问作者André LFS Bacci
相关产品推荐
相关产品推荐

