萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> 程式語言綜合

Python XML 比較工具:ElementTree 遞迴逐節點差異檢測

比較兩份 XML 文件的結構與內容差異是測試、版本控制、配置同步的常見需求。本文基於標準庫 `xml.etree.ElementTree` 實作遞迴比較器,檢測標籤差異、文本差異、屬性增減、子節點數量與順序差異。

比較器核心邏輯


from xml.etree import ElementTree

OK = True
MAIN_PID = 10000
LOOP_DEPTH = 0

def compare_xml(left: ElementTree.Element, right: ElementTree.Element, key_info: str = '.'):
    global LOOP_DEPTH, OK
    LOOP_DEPTH += 1
    
    if LOOP_DEPTH == 1:
        print()  # 頂層換行
    
    # 1. 標籤比較
    if left.tag != right.tag:
        print_diff(MAIN_PID, key_info, 'difftag', left.tag, right.tag)
        LOOP_DEPTH -= 1
        return
    
    # 2. 文本內容比較
    if (left.text or '').strip() != (right.text or '').strip():
        print_diff(MAIN_PID, key_info, 'difftext', left.text, right.text)
        # 不 return,繼續比較子節點
    
    # 3. 屬性比較
    left_attrs = dict(left.items())
    right_attrs = dict(right.items())
    
    for k, v in left_attrs.items():
        if k not in right_attrs:
            path = f'{key_info}/{left.tag}'
            print_diff(MAIN_PID, path, 'lostattr', k, '')
    
    for k, v in right_attrs.items():
        if k not in left_attrs:
            path = f'{key_info}/{right.tag}'
            print_diff(MAIN_PID, path, 'extraattr', '', k)
    
    # 4. 子節點比較 (按順序配對)
    left_children = list(left)
    right_children = list(right)
    
    if len(left_children) != len(right_children):
        path = f'{key_info}/{right.tag}'
        print_diff(MAIN_PID, path, 'difflen', len(left_children), len(right_children))
        LOOP_DEPTH -= 1
        return
    
    # 同名子節點計數器,處理同名同級節點
    tag_counter = {}
    for i in range(len(left_children)):
        lchild = left_children[i]
        rchild = right_children[i]
        
        # 產生唯一路徑標識
        if lchild.tag not in tag_counter:
            tag_counter[lchild.tag] = 1
            tag_path = lchild.tag
        else:
            tag_counter[lchild.tag] += 1
            tag_path = f'{lchild.tag}{tag_counter[lchild.tag]}'
        
        child_path = f'{key_info}/{tag_path}'
        compare_xml(lchild, rchild, child_path)
    
    LOOP_DEPTH -= 1

def print_diff(pid: int, key_info: str, msg: str, base_val, test_val):
    global OK
    info = f'[ {msg.upper():-5s} ] {pid} -> {key_info.strip("./"):-40s} [ {base_val} != {test_val} ]'
    print(info)
    OK = False

使用範例


if __name__ == '__main__':
    xml1 = '''

    
        linux
        30
    
    
        windows
        20
    
'''

    xml2 = '''

    
        windows
        20
    
    
        linux
        30
    
'''

    left_root = ElementTree.fromstring(xml1)
    right_root = ElementTree.fromstring(xml2)
    
    compare_xml(left_root, right_root)
    
    if OK:
        print("XML 內容完全一致")
    else:
        print("XML 存在差異 (見上方詳細輸出)")

輸出範例


[ DIFFTAG ] 10000 -> employees/employee1                      [ employee != employee ]
[ LOSTATTR ] 10000 -> employees/employee1                      [ id !=  ]
[ EXTRAATTR ] 10000 -> employees/employee1                      [  != id ]
[ DIFFTEXT ] 10000 -> employees/employee1/name                 [ linux != windows ]
[ DIFFTEXT ] 10000 -> employees/employee1/age                  [ 30 != 20 ]
[ DIFFTEXT ] 10000 -> employees/employee2/name                 [ windows != linux ]
[ DIFFTEXT ] 10000 -> employees/employee2/age                  [ 20 != 30 ]

差異類型代碼說明

代碼含義
`difftag`同層級對應位置標籤名不同
`difftext`節點文本內容不同 (已 strip 空白)
`lostattr`左側有、右側無此屬性
`extraattr`右側有、左側無此屬性
`difflen`子節點數量不同

進階擴展方向

  • 語義比較 (忽略順序):對子節點按 tag+關鍵屬性排序後再配對比較。
  • 數值容差比較:數值型文本允許相對/絕對誤差 (如浮點數)。
  • 白名單忽略:指定路徑模式跳過比較 (如時間戳、ID)。
  • 報告格式化:輸出 JSON/HTML 差異報告,支援前端可視化。
  • 大檔案流式比較:使用 `iterparse` 邊讀邊比,避免全量載入記憶體。

替代方案

  • xmldiff:專業 XML 差異庫,支援樹編輯距離、補丁生成 (`pip install xmldiff`)。
  • lxml.etree:更快的 C 實現,支援 XPath、XSLT、Schema 驗證。
  • difflib:文本級差異 (行級),適用於格式化後的 XML 字串比較。

最佳實踐

  • 比較前**規範化**:統一屬性順序、移除無意義空白、展開實體引用。
  • 大量比較任務**並行化** (多進程池),單進程受限於 GIL。
  • 建立**基準線** (Golden Master) 進行回歸測試。
  • CI/CD 流程整合:PR 檢查配置檔 XML 無意外變更。
copyright © 萬盛學電腦網 all rights reserved