You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

问题根源

  1. 错误提前结束文档:递归函数中调用xmlwriter_end_document,导致第一次递归完成后直接关闭整个XML文档,后续节点被当作新的根节点输出
  2. 未正确闭合元素节点:调用xmlwriter_start_element后没有对应的xmlwriter_end_element,节点嵌套逻辑混乱
  3. 节点对比逻辑缺陷:仅检查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>";

修复说明

  1. 改用DOMDocument处理XML:相比SimpleXMLElement,DOM能更精准地按顺序获取子节点,避免同名节点匹配错误
  2. 双指针顺序对比:通过双指针遍历两个节点的子元素,确保按原顺序处理节点,正确识别缺失项
  3. 正确管理节点开闭:每个startElement对应endElement,确保XML结构嵌套正确
  4. 移除错误的文档结束调用:仅在所有节点处理完成后调用endDocument
  5. 支持双向生成:通过调换基准和对比节点,可同时生成两个方向的差异XML

内容的提问来源于stack exchange,提问作者MajorTom

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 05:05:23