PHP实现同结构未知XML对比并生成带缺失标记的XML
PHP对比XML并修复节点过早闭合问题
需求
熟悉PHP但对XML陌生,需实现:
- 对比两个结构相同的未知XML文件
- 生成两个保留原节点顺序的XML文件,标记对方缺失的节点
- 最终用于HTML页面可视化差异
示例XML
示例XML1
<start> <eins> <zwei> <drei>test1</drei> <vier>test2</vier> <fuenf>test3</fuenf> </zwei> <sechs> <sieben> <acht>test4</acht> <neun>tets5</neun> </sieben> </sechs> </eins> </start>
示例XML2
<start> <eins> <zwei> <fuenf>test3</fuenf> <zehn>hmm</zehn> <drei>testFail</drei> </zwei> <sechs> <sieben> <neun>tets5</neun> <acht>test3</acht> </sieben> </sechs> </eins> </start>
期望输出
newXML1(基于XML1,标记XML2缺失的节点)
<start> <eins> <zwei> <drei>test1</drei> <vier>test2</vier> <fuenf>test3</fuenf> <missing>missing</missing> </zwei> <sechs> <sieben> <acht>test4</acht> <neun>tets5</neun> </sieben> </sechs> </eins> </start>
newXML2(基于XML2,标记XML1缺失的节点)
<start> <eins> <zwei> <drei>testFail</drei> <missing>missing</missing> <fuenf>test3</fuenf> <zehn>hmm</zehn> </zwei> <sechs> <sieben> <acht>test3</acht> <neun>tets5</neun> </sieben> </sechs> </eins> </start>
现有代码及问题
现有代码
function checkXML($xml, $xml2, &$xmlNew, &$nodeCount) { foreach($xml as $key => $xmlpos) { if (isset($xml2->$key)) { if($xmlpos->count()) { xmlwriter_start_element($xmlNew,$key); $nodeCount = $xmlpos->count(); checkXML($xmlpos, $xml2->$key, $xmlNew, $nodeCount); $nodeCount--; } else { xmlwriter_write_element($xmlNew,$key,$xml2->$key); } } else { //echo $key . " - this is missing"; xmlwriter_write_element($xmlNew,"missing","missing"); } } xmlwriter_end_document($xmlNew); } $xmlStr1 = '<?xml version="1.0" encoding="UTF-8"?> <start> <eins> <zwei> <drei>test1</drei> <vier>test2</vier> <fuenf>test3</fuenf> </zwei> <sechs> <sieben> <acht>test4</acht> <neun>tets5</neun> </sieben> </sechs> </eins> </start>'; $xmlStr2 = '<?xml version="1.0" encoding="UTF-8"?> <start> <eins> <zwei> <fuenf>test3</fuenf> <zehn>hmm</zehn> <drei>testFail</drei> </zwei> <sechs> <sieben> <neun>tets5</neun> <acht>test3</acht> </sieben> </sechs> </eins> </start>'; $xml1 = new SimpleXMLElement($xmlStr1); $xml2 = new SimpleXMLElement($xmlStr2); $newXML = xmlwriter_open_memory(); xmlwriter_start_document($newXML,"1.0"); $nodeCount[0] = 0; checkXML($xml1, $xml2, $newXML, $nodeCount); //Header('Content-type: text/xml'); echo "<xmp>"; $xml_data=xmlwriter_output_memory($newXML); echo $xml_data; echo "</xmp>";
当前输出(问题表现)
<?xml version="1.0"?> <eins> <zwei> <drei>testFail</drei> <missing>missing</missing> <fuenf>test3</fuenf> </zwei> </eins> <sechs> <sieben> <acht>test3</acht> <neun>tets5</neun> </sieben> </sechs>
问题根源
- 错误提前结束文档:递归函数中调用
xmlwriter_end_document,导致第一次递归完成后直接关闭整个XML文档,后续节点被当作新的根节点输出 - 未正确闭合元素节点:调用
xmlwriter_start_element后没有对应的xmlwriter_end_element,节点嵌套逻辑混乱 - 节点对比逻辑缺陷:仅检查XML1的节点在XML2中是否存在,未处理XML2独有的节点,也未按实际节点顺序对比(SimpleXMLElement的
$xml2->$key会匹配第一个同名节点,忽略顺序)
修复后的代码
/** * 对比两个XML节点,生成带缺失标记的XML * @param DOMNode $node1 基准节点 * @param DOMNode $node2 对比节点 * @param XMLWriter $writer XML写入器 */ function compareNodes(DOMNode $node1, DOMNode $node2, XMLWriter $writer) { // 处理元素节点 if ($node1 instanceof DOMElement) { $writer->startElement($node1->tagName); // 获取两个节点的子元素列表(只取元素节点,忽略文本/注释等) $children1 = []; foreach ($node1->childNodes as $child) { if ($child instanceof DOMElement) $children1[] = $child; } $children2 = []; foreach ($node2->childNodes as $child) { if ($child instanceof DOMElement) $children2[] = $child; } $i = 0; $j = 0; // 双指针按顺序对比子节点 while ($i < count($children1) || $j < count($children2)) { if ($i < count($children1) && $j < count($children2)) { $child1 = $children1[$i]; $child2 = $children2[$j]; if ($child1->tagName === $child2->tagName) { // 节点名相同,递归对比 compareNodes($child1, $child2, $writer); $i++; $j++; } else { // 检查当前child1是否在children2后续存在 $found = false; for ($k = $j; $k < count($children2); $k++) { if ($children2[$k]->tagName === $child1->tagName) { $found = true; break; } } if (!$found) { // child1在child2中不存在,保留并标记缺失 compareNodes($child1, new DOMElement('missing'), $writer); $i++; } else { // child2在child1中不存在,添加缺失标记 $writer->writeElement('missing', 'missing'); $j++; } } } elseif ($i < count($children1)) { // child1还有剩余,全部保留(对方缺失) compareNodes($children1[$i], new DOMElement('missing'), $writer); $i++; } else { // child2还有剩余,添加缺失标记 $writer->writeElement('missing', 'missing'); $j++; } } $writer->endElement(); } elseif ($node1 instanceof DOMText) { // 处理文本节点,保留基准节点的文本 $writer->text($node1->nodeValue); } } /** * 生成对比后的XML字符串 * @param string $xmlStr1 基准XML字符串 * @param string $xmlStr2 对比XML字符串 * @return string */ function generateDiffXML($xmlStr1, $xmlStr2) { $dom1 = new DOMDocument(); $dom1->loadXML($xmlStr1); $dom2 = new DOMDocument(); $dom2->loadXML($xmlStr2); $writer = new XMLWriter(); $writer->openMemory(); $writer->startDocument('1.0', 'UTF-8'); // 从根节点开始对比 compareNodes($dom1->documentElement, $dom2->documentElement, $writer); $writer->endDocument(); return $writer->outputMemory(); } // 测试数据 $xmlStr1 = '<?xml version="1.0" encoding="UTF-8"?> <start> <eins> <zwei> <drei>test1</drei> <vier>test2</vier> <fuenf>test3</fuenf> </zwei> <sechs> <sieben> <acht>test4</acht> <neun>tets5</neun> </sieben> </sechs> </eins> </start>'; $xmlStr2 = '<?xml version="1.0" encoding="UTF-8"?> <start> <eins> <zwei> <fuenf>test3</fuenf> <zehn>hmm</zehn> <drei>testFail</drei> </zwei> <sechs> <sieben> <neun>tets5</neun> <acht>test3</acht> </sieben> </sechs> </eins> </start>'; // 生成newXML1和newXML2 $newXML1 = generateDiffXML($xmlStr1, $xmlStr2); $newXML2 = generateDiffXML($xmlStr2, $xmlStr1); // 输出结果 echo "<h3>newXML1</h3><xmp>{$newXML1}</xmp>"; echo "<h3>newXML2</h3><xmp>{$newXML2}</xmp>";
修复说明
- 改用DOMDocument处理XML:相比SimpleXMLElement,DOM能更精准地按顺序获取子节点,避免同名节点匹配错误
- 双指针顺序对比:通过双指针遍历两个节点的子元素,确保按原顺序处理节点,正确识别缺失项
- 正确管理节点开闭:每个
startElement对应endElement,确保XML结构嵌套正确 - 移除错误的文档结束调用:仅在所有节点处理完成后调用
endDocument - 支持双向生成:通过调换基准和对比节点,可同时生成两个方向的差异XML
内容的提问来源于stack exchange,提问作者MajorTom
相关产品推荐
相关产品推荐

