PHP DOMDocument获取XML自定义实体值及导入节点失效问题
问题核心原因
PHP的DOM扩展基于libxml实现,DTD实体解析存在几个已知的行为特性,是导致问题的根源:
DOMDocument默认关闭substituteEntities配置,解析XML时不会将&实体名;格式的引用替换为实际值,仅保留实体引用节点DOMEntity对象的nodeValue、textContent属性返回空值属于libxml的历史实现逻辑,实体的实际替换值没有直接挂载在这两个属性上- 跨文档调用
importNode()+appendChild()时,导入的节点不会继承目标文档的DTD实体定义,原文档中的实体引用如果在目标文档无对应定义,会被解析为空值 - 属性值中出现
https://前缀丢失的情况,是libxml处理属性内实体替换时,对URI类内容做默认解析截断导致的已知历史bug。
可行解决方案
方案1:正确配置解析参数,规避跨文档导入问题
如果不需要拆分多文档做节点导入,只要在加载XML前开启正确配置,即可直接拿到替换完成的实体值:
$xml = new DOMDocument(); // 开启实体自动替换 $xml->substituteEntities = true; // 开启内部DTD解析,同时禁止加载外部网络资源规避安全风险 $xml->resolveExternals = true; $xml->loadXML($yourXmlContent, LIBXML_NOCDATA | LIBXML_NONET);
如果必须做跨文档节点导入,需要先在原文档内将所有实体引用替换为纯文本内容,再执行导入操作,避免实体上下文丢失:
// 遍历原文档所有文本节点和属性节点,触发实体替换为纯文本 $xpath = new DOMXPath($sourceDoc); foreach ($xpath->query('//text() | //@*') as $node) { $node->nodeValue = $node->nodeValue; } // 此时再导入节点不会出现空值问题 $importedNode = $targetDoc->importNode($sourceDoc->documentElement, true); $targetDoc->documentElement->appendChild($importedNode);
方案2:手动提取实体映射表做替换(稳定性最高)
如果不想依赖libxml的实体替换逻辑,可以直接提取内部DTD的实体值,自行完成字符串替换,完全绕开DOM解析实体的各类坑。
不需要手动裁剪<!ENTITY>定义字符串,DOMEntity存在未写入官方文档的data属性,直接存储了实体对应的实际替换值:
$entityMap = []; $xml = new DOMDocument(); $xml->loadXML($yourXmlContent); foreach ($xml->doctype->entities as $entity) { $entityMap['&' . $entity->nodeName . ';'] = $entity->data; } // 后续读取到属性值、文本内容后,直接替换即可得到最终结果 $rawAttrValue = $xml->documentElement->getAttribute('url'); $realValue = strtr($rawAttrValue, $entityMap);
该方案不受libxml版本、跨文档操作、解析bug的影响,是生产环境最稳定的实现方式。
注意事项
- 生产环境建议始终加
LIBXML_NONET参数禁止加载外部DTD资源,避免引入安全风险,用到的实体尽量全部定义在XML的内部DTD子集中 - 如果存在实体嵌套定义(即某个实体的值中引用了其他实体),手动替换时需要递归执行替换操作,直到文本中不存在可匹配的实体标记为止。
内容的提问来源于stack exchange,提问作者MrInitialMan
相关产品推荐
相关产品推荐

