💡 ตัวอย่างการทำงานเต็มที่มีบน GitHub:
document-version-metadata-diff-python

What You’ll Build

ในคู่มือนี้คุณจะเปรียบเทียบความแตกต่างของคุณสมบัติมีเดต้าแต่ละรายการระหว่างสองเวอร์ชันของเอกสารและพิมพ์สิ่งที่ถูกเพิ่ม, ถูกลบ, หรือถูกเปลี่ยนแปลงอย่างแม่นยำ การเปรียบเทียบเวอร์ชันของเมตาดาต้าเป็นการเปรียบเทียบระดับคุณสมบัติของสองการแก้ไขของไฟล์เดียวกัน และมันจับสัญญาณที่การเปรียบเทียบข้อความไม่เคยเห็น: ผู้สร้างใหม่, RevisionNumber ที่เพิ่มขึ้น, หรือเซสชันการแก้ไขที่บันทึกหลังจากการตรวจสอบปิดลง เมื่อเสร็จคุณจะมีโซลูชันที่ทำงานได้พร้อมกับตัวตรวจจับสองตัวที่เน้นเฉพาะและรูปแบบการส่งออกสองรูปแบบ ทั้งหมดมาจากรีโพซิทอรีที่สามารถรันได้ซึ่งมีตัวอย่างคู่การแก้ไข

ระดับทักษะ: นักพัฒนา Python ระดับกลาง
สิ่งที่คุณต้องการ: Python 3, pip, และสองเวอร์ชันของเอกสารหนึ่งไฟล์

การรันสคริปต์ครั้งแรกของฉันทำให้พบการเปลี่ยนแปลงค่า Company ที่ไม่มีใครในทีมจำได้ว่าทำ; บรรทัดนั้นทำให้การตั้งค่ามีค่าใช้จ่ายคืนคืน ทุกอย่างด้านล่างพร้อมคัดลอกและวางและมีจำนวนบรรทัดน้อยกว่าเกร็ดหนึ่งร้อยบรรทัด

กระบวนการทำงานถูกออกแบบให้น่าเบื่อ: เปิดไฟล์สองครั้ง, ทำ dict comprehension สามครั้ง, ลูปพิมพ์ น่าเบื่อคือจุดมุ่งหมาย การโต้แย้งเวอร์ชันจะตัดสินโดยวิธีที่อธิบายได้และทำซ้ำได้ และสคริปต์ขนาดเล็กนี้สามารถอ่านได้เต็มที่โดยผู้ที่ท้าทายผลลัพธ์


1. Install

pip install groupdocs-metadata-net==26.5

คอมพานี้รีโพซิทอรี ระบุเวอร์ชันนี้และมาพร้อมกับ document-v1.docx และ document-v2.docx ดังนั้นโค้ดด้านล่างจะทำงานตามที่เป็นอยู่ ให้ระบุเวอร์ชันที่การตรวจสอบของคุณใช้; ความสามารถในการทำซ้ำเป็นส่วนหนึ่งของหลักฐาน


2. The Core Code

อ่านต้นไม้ของคุณสมบัติเจ็มทั้งสอง แล้วจัดประเภทเดลตาด้วยตรรกะเซต นี่คือการเปรียบเทียบทั้งหมด:

# Flatten a file's complete property tree into a dict
def read_props(path):
    props = {}
    with Metadata(path) as metadata:
        for p in metadata.find_properties(lambda p: p.name is not None):
            props[p.name] = (str(p.interpreted_value) if p.interpreted_value is not None
                             else (str(p.value) if p.value is not None else ""))
    return props

v1 = read_props("resources/document-v1.docx")
v2 = read_props("resources/document-v2.docx")

# Classify every key; changed entries keep both values
added = {k: v for k, v in v2.items() if k not in v1}
removed = {k: v for k, v in v1.items() if k not in v2}
changed = {k: (v1[k], v2[k]) for k in v1 if k in v2 and v1[k] != v2[k]}

print(f"added={len(added)} removed={len(removed)} changed={len(changed)}")
for k, (old_v, new_v) in changed.items():
    print(f"  {k}: {old_v} -> {new_v}")

นี่คือสิ่งที่คุณต้องการขั้นต่ำ คาดว่าจะได้จำนวนที่น้อยเมื่อใช้คู่การแก้ไขที่แท้จริง; เดลตาที่มีหลายสิบรายการมักหมายถึงไฟล์ผ่านการเปลี่ยนแปลงเทมเพลตหรือการย้ายที่เก็บข้อมูลระหว่างทาง ส่วนต่อไปจะอธิบายการเรียกสำคัญและแสดงการปรับแต่งที่ทีมส่วนใหญ่เพิ่มเป็นอันดับแรก


3. How It Works

  • Metadata: ตัวจัดการบริบทที่เปิดไฟล์และปล่อยเมื่อออก; หนึ่งอินสแตนซ์ต่อการแก้ไขหนึ่งครั้ง
  • find_properties: เดินผ่านฟิลด์ในตัว, คุณสมบัติที่กำหนดเอง, และ XMP ในหนึ่งรอบ, คืนค่าทุกอย่างที่พรีดิเกตยอมรับ
  • interpreted_value: รูปแบบที่มนุษย์อ่านได้ของคุณสมบัติ; การให้ความสำคัญกับมันหมายความว่าข้อมูลวันที่และ enumeration จะเปรียบเทียบเป็นสตริงที่คุณสามารถพิมพ์ในรายงานได้
  • Qualified names as keys: ฟิลด์ในตัวและฟิลด์ที่กำหนดเองไม่สามารถชนกันใน dict, ดังนั้นตรรกะเซตจึงปลอดภัย

ไม่มีการแยกโครงสร้าง DOCX ที่นี่ เอกสารผลิตภัณฑ์ (product documentation) ระบุว่ามีรูปแบบกว่า 170+ ที่ทำงานผ่านการเรียกเดียวกัน ดังนั้นสคริปต์เดียวกันสามารถเปรียบเทียบคู่ PDF หรือ XLSX ได้

อีกหนึ่งคุณสมบัติของการออกแบบที่ควรกล่าวถึงคือ: ขอบเขต API สิ้นสุดที่การเรียก read_props สองครั้ง ทุกอย่างหลังจากนั้นเป็น Python มาตรฐาน‑library, ดังนั้นการทดสอบหน่วย, เกณฑ์, และกฎการแจ้งเตือนจะไม่ต้องสัมผัสชั้นเอกสาร ทีมที่ห่อหุ้มสคริปต์นี้ในบริการมักแคช dict ที่สกัดออกมาต่อการแก้ไขและให้การตรวจสอบต่อเนื่องใช้ซ้ำ, ทำให้การเปิดไฟล์อยู่ที่หนึ่งครั้งต่อเวอร์ชันไม่ว่ามีคำถามกี่ข้อ


4. Common Customizations

Detect ownership changes only

เมื่อคำถามคือ “ใครสัมผัสไฟล์นี้” ให้กรองขณะอ่านด้วยพรีดิเกตแท็กแทนการกรองหลังจากเปรียบเทียบเต็มรูปแบบ:

# Identity fields only, whatever the format calls them
def read_ownership(path):
    result = {}
    with Metadata(path) as metadata:
        props = metadata.find_properties(lambda p:
            Tags.person.creator in list(p.tags)
            or Tags.person.editor in list(p.tags)
            or Tags.person.manager in list(p.tags)
            or Tags.corporate.company in list(p.tags))
        for prop in props:
            result[prop.name] = (str(prop.interpreted_value)
                                 if prop.interpreted_value is not None
                                 else (str(prop.value) if prop.value is not None else ""))
    return result

รันลูปเดลตาเดียวกันบน dict สองชุดนี้, ใช้ <missing> เป็นค่าเริ่มต้นเพื่อให้ฟิลด์ที่หายไปยังปรากฏ พรีดิเกตไม่มีการระบุฟิลด์ใด, ซึ่งทำให้ตัวตรวจจับหนึ่งตัวสามารถให้บริการทุกฟอร์แมตที่ไลบรารีอ่านได้

Track the editing timeline

สลับพรีดิเกตเป็น Tags.time พร้อมกฎชื่อคานเตอร์และตัวตรวจจับจะรายงานการเคลื่อนที่ของ RevisionNumber, TotalEditingTime, และ LastPrinted:

props = metadata.find_properties(lambda p:
    Tags.time.modified in list(p.tags)
    or Tags.time.created in list(p.tags)
    or Tags.time.printed in list(p.tags)
    or (p.name is not None and ("Revision" in p.name
        or "EditTime" in p.name or "EditingTime" in p.name)))

Export an audit report

ผลลัพธ์ที่อยู่ในคอนโซลจะหายไปที่นั่น คอลัมน์สี่คอลัมน์ครอบคลุมสเปรดชีตและกรณี SIEM:

with open("output/diff.csv", "w", encoding="utf-8", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["change_type", "property", "old_value", "new_value"])
    for k, v in added.items():
        writer.writerow(["added", k, "", v])
    for k, v in removed.items():
        writer.writerow(["removed", k, v, ""])
    for k, (old_v, new_v) in changed.items():
        writer.writerow(["changed", k, old_v, new_v])

รีโพซิทอรียังมีตัวส่งออก JSON พร้อมสคีมาสามแผนที่ที่เสถียรสำหรับแดชบอร์ดและ API การจัดการกรณี


Where This Runs in Practice

มีการปรับใช้สามแบบที่พบบ่อย พายป์ไลน์รับข้อมูลเปรียบเทียบแต่ละเอกสารที่เข้ามากับสำเนาที่บันทึกไว้แล้วและกักกันคู่ที่มีการเปลี่ยนแปลงตัวตน งานปฏิบัติตามกฎจะรันเปรียบเทียบตามกำหนดเวลาและเก็บ CSV ต่อคู่, สร้างไทม์ไลน์ของคุณสมบัติที่ไม่มีใครต้องสร้างใหม่ในภายหลัง และเครื่องมือโต้แย้งจะรันตัวตรวจจับทั้งสองตามคำขอ, เพราะเมื่อมีการอ้างสิทธิ์คำถามแรกคือใครสัมผัสไฟล์และเมื่อไหร่, ไม่ใช่ว่าอะไรเปลี่ยนแปลงในย่อหน้าที่สี่

รูปแบบที่สี่, การเปรียบเทียบไฟล์กับสแนปช็อตที่ดีสุดที่บันทึกไว้ก่อนหน้า, ใช้โค้ดเดียวกันกับ dict ที่เก็บไว้ด้านหนึ่ง ในทั้งหมดนี้ไฟล์ส่งออกคือผลลัพธ์; คอนโซลเป็นเพียงเสียงรบกวนของความคืบหน้า รูปแบบการคืนค่า exit‑code ของสคริปต์ตาม main.py ของรีโพซิทอรี, ดังนั้นตัวจัดตารางเวลาและ CI จะถือการอ้างอิงล้มเหลวเป็นการรันล้มเหลวโดยไม่ต้องเชื่อมต่อเพิ่มเติม ไม่มีกรณีใดที่ต้องใช้โค้ดเกินกว่าที่หน้านี้แสดง


What counts as a change worth flagging?

อะไรก็ตามที่ diff จัดประเภทบวกกับบริบทที่คุณเพิ่มเข้ามา คุณสมบัติที่เพิ่มและลบมักจะต้องตรวจสอบเสมอเพราะหมายถึงโครงสร้างเปลี่ยนแปลง ไม่ใช่แค่ค่า สำหรับรายการที่เปลี่ยนแปลง, ทีมส่วนใหญ่จะเตือนบนกลุ่มตัวตนและเวอร์ชันก่อนและถือส่วนที่เหลือเป็นข้อมูล ตัวตรวจจับมีไว้เพื่อให้การตรวจสอบครั้งแรกใช้ฟังก์ชันเดียว


5. Quick Reference: Key Calls

การเรียก ทำอะไร
Metadata(path) เปิดไฟล์; ตัวจัดการบริบทดูแลการปล่อย
find_properties(predicate) คืนค่าทุกคุณสมบัติที่พรีดิเกตยอมรับ, ครอบคลุมทุกชั้น
p.interpreted_value ค่าที่มนุษย์อ่านได้; หากไม่มีจะใช้ p.value
Tags.person.* / Tags.corporate.company การจัดประเภทตัวตน, ไม่ขึ้นกับฟอร์แมต
Tags.time.* การจัดประเภทเวลาเพื่อใช้กับตัวตรวจจับเวอร์ชัน

ดู complete API reference สำหรับการค้นหาและแท็กทั้งหมด คำศัพท์แท็กมีมากกว่าที่แสดงในแถวเหล่านี้; กลุ่มต้นกำเนิด, เนื้อหา, และกฎหมายใช้การทดสอบสมาชิกแบบเดียวกัน


6. Common Issues & Fixes

The diff is enormous and reads like noise
→ เส้นทางสองอันอาจไม่ใช่การแก้ไขของเอกสารเดียวกัน แก้: ตรวจสอบแหล่งที่มาก่อนทำ diff; ไฟล์ที่ไม่เกี่ยวข้องจะให้เดลตาที่ไม่มีความหมาย

A known author field never shows up in the ownership detector
→ ผู้ผลิตบางรายเก็บตัวตนในฟิลด์ที่กำหนดเองโดยไม่มีแท็ก แก้: รัน diff เต็มครั้งหนึ่งครั้ง, ค้นหาชื่อฟิลด์จริง, แล้วขยายพรีดิเกตด้วยกฎชื่อ

Console shows an evaluation-mode warning
→ ไม่พบไฟล์ลิขสิทธิ์ แก้: ตั้งค่า LICENSE_PATH ใน main.py ให้ชี้ไปที่ไฟล์ .lic ของคุณ, หรือใช้โหมดประเมินผลสำหรับการพัฒนา; ลอจิกเหมือนเดิม

Dates print as raw serial numbers
p.value ดิบหลุดเข้าที่อ่านบางที่ แก้: รักษาแพทเทิร์น interpreted_value‑first จาก read_props; นั่นคือเหตุผลที่รายงานยังอ่านได้


What’s Next?

คุณมี diff ของเมตาดาต้าที่ทำงานได้แล้ว ต่อไปนี้คือขั้นตอนต่อไป:

  • Batch it: วนลูปสคริปต์บนคู่เอกสารและเก็บ CSV ต่อคู่; ค่าใช้จ่ายต่อคู่คือการเปิดไฟล์สองครั้ง, และ CSV สามารถต่อเนื่องกันได้เพื่อมุมมองระดับไลบรารี
  • Schedule it: main.py ของรีโพซิทอรีตรวจสอบทุกขั้นตอนและคืนค่า exit code ที่เหมาะสม, ซึ่งสามารถเชื่อมต่อกับ CI หรือ scheduler ได้โดยตรง
  • Walk the tutorial version: use case guide สร้าง pipeline เดียวกันในสามบทเรียนระดับต่างกัน
  • See the whole project: document-version-metadata-diff-python พร้อมคู่การแก้ไขที่เตรียมไว้

Resources