如何使用PHP将XML转JSON 修复CDATA内容截断异常
PHP XML转JSON时CDATA文本截断问题修复方案
问题根因
该CDATA截断问题是libxml2在使用LIBXML_NOCDATA参数时的已知解析缺陷:当CDATA块内包含XML1.0规范不允许的非法控制字符(即0x00-0x1F范围内除制表符、换行、回车外的不可见字符,多数系统导出的XML会隐式带入这类字符),libxml在将CDATA节点合并为普通文本节点的过程中,会在第一个非法字符位置直接终止文本读取,且不会抛出显性解析错误。单独提取CDATA片段测试无法复现的原因是,文本编辑器复制粘贴时会自动过滤掉这类不可见控制字符。
此前尝试手动转换CDATA节点未生效的原因是:代码中仅给循环临时变量$node重新赋值,没有实际修改DOM树的节点结构,改动不会作用到原文档对象上。
修复方案
方案1:预处理过滤非法XML字符(改动最小,兼容性最优)
在加载XML前先过滤掉所有不符合XML1.0规范的非法字符,从源头规避libxml解析截断问题,原有转换逻辑不需要做任何调整:
$rawXml = FileHelpers::fileGetContents($file); // 匹配所有XML1.0不允许的非法控制字符 $invalidCharPattern = '/[^\x{9}\x{A}\x{D}\x{20}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]/u'; $cleanXml = preg_replace($invalidCharPattern, '', $rawXml); $dom = new \DOMDocument(); $dom->loadXML($cleanXml, LIBXML_NOCDATA); // 原有jsonPrepareXml、simplexml加载、JSON编码逻辑保持不变即可
注意:该方案会直接丢弃不可见的非法控制字符,如果业务场景需要保留这类特殊字符,请使用方案2。
方案2:移除LIBXML_NOCDATA,手动替换CDATA节点
放弃使用存在缺陷的LIBXML_NOCDATA自动转换逻辑,加载XML后手动遍历DOM树,将所有CDATA节点正式替换为普通文本节点,绕开libxml的解析bug,不会丢失任何字符:
/** * 递归将DOM树中所有CDATA节点替换为普通文本节点 */ function replaceCdataWithText(\DOMNode $node): void { // 从后往前遍历子节点,避免节点替换导致的遍历偏移问题 for ($i = $node->childNodes->length - 1; $i >= 0; $i--) { $childNode = $node->childNodes->item($i); if ($childNode instanceof \DOMCdataSection) { // 直接操作DOM树完成节点替换,仅修改临时变量不会生效 $textNode = $node->ownerDocument->createTextNode($childNode->nodeValue); $node->replaceChild($textNode, $childNode); } elseif ($childNode->hasChildNodes()) { replaceCdataWithText($childNode); } } } $dom = new \DOMDocument(); // 移除LIBXML_NOCDATA,添加错误抑制参数避免非标准XML抛出警告 $dom->loadXML(FileHelpers::fileGetContents($file), LIBXML_NOERROR | LIBXML_NOWARNING); replaceCdataWithText($dom); // 原有jsonPrepareXml、simplexml加载、JSON编码逻辑保持不变即可
辅助排查手段
如果需要确认截断位置的具体异常字符,可以开启libxml内部错误捕获,查看解析过程中的隐式警告:
libxml_use_internal_errors(true); $dom->loadXML($rawXml); $parseErrors = libxml_get_errors(); // 打印$parseErrors即可定位到非法字符所在的行号、具体错误类型 libxml_clear_errors();
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

