💡 ตัวอย่างการทำงานเต็มที่มีบน GitHub:
document-version-metadata-diff-python
What You’ll Build
ในคู่มือฉบับนี้คุณจะเปรียบเทียบความแตกต่างของคุณสมบัติเมตาดาต้าทุกอย่างระหว่างสองเวอร์ชันของเอกสารและพิมพ์ผลลัพธ์ที่ถูกเพิ่ม, ถูกลบ, หรือถูกเปลี่ยนแปลงอย่างแม่นยำ การเปรียบเทียบเวอร์ชันเมตาดาต้าเป็นการเปรียบเทียบระดับคุณสมบัติของสองการแก้ไขของไฟล์เดียวกัน และจะจับสัญญาณที่การเปรียบเทียบข้อความไม่เคยเห็น: ผู้สร้างใหม่, หมายเลข RevisionNumber ที่เพิ่มขึ้น, หรือเซสชันการแก้ไขที่บันทึกหลังจากการตรวจสอบปิดลง เมื่อเสร็จคุณจะมีโซลูชันที่ทำงานได้พร้อมกับตัวตรวจจับสองตัวที่เน้นเฉพาะและรูปแบบการส่งออกสองรูปแบบ ทั้งหมดมาจากรีโพซิทอรีที่สามารถรันได้และมีตัวอย่างคู่เวอร์ชันให้ทดลอง
Skill level: นักพัฒนา Python ระดับกลาง
What you need: Python 3, pip, และสองเวอร์ชันของเอกสารไฟล์เดียวกัน
การรันสคริปต์ครั้งแรกของฉันทำให้พบการเปลี่ยนแปลงค่า Company ที่ไม่มีใครในทีมจำได้ว่าทำไว้; บรรทัดเดียวนั้นทำให้การตั้งค่าเป็นจริง ทุกอย่างด้านล่างพร้อมคัดลอกและวางและรวมทั้งหมดไม่ถึงร้อยบรรทัด
กระบวนการทำงานถูกออกแบบให้เรียบง่าย: เปิดไฟล์สองครั้ง, ใช้การทำความเข้าใจ dict สามครั้ง, วนลูปพิมพ์ ผลลัพธ์ที่เรียบง่ายคือจุดมุ่งหมาย การโต้แย้งเรื่องเวอร์ชันจึงตัดสินได้จากว่าวิธีการสามารถอธิบายและทำซ้ำได้หรือไม่ และสคริปต์ขนาดเล็กนี้สามารถอ่านได้เต็มที่โดยผู้ที่ท้าทายผลลัพธ์
1. Install
pip install groupdocs-metadata-net==26.5
companion repository ระบุเวอร์ชันนี้และมีไฟล์ 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 มาตรฐาน, ดังนั้นการทดสอบหน่วย, เกณฑ์, และกฎการแจ้งเตือนจะไม่ต้องสัมผัสชั้นเอกสาร ทีมที่ห่อหุ้มโค้ดนี้เป็นบริการมักจะแคช 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
รันลูป delta เดียวกันบน 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
มีการใช้งานสามรูปแบบที่พบบ่อย: ระบบรับข้อมูลเปรียบเทียบเอกสารที่เข้ามากับสำเนาที่บันทึกไว้แล้วและกักไฟล์ที่มีการเปลี่ยนแปลงตัวตน งานปฏิบัติตามกฎจะรัน diff ตามกำหนดเวลาและเก็บ CSV แยกตามคู่, สร้างไทม์ไลน์คุณสมบัติที่ไม่มีใครต้องสร้างใหม่ในภายหลัง และเครื่องมือโต้แย้งจะรันตัวตรวจจับทั้งสองตามคำขอ, เพราะเมื่อมีการอ้างสิทธิ์ คำถามแรกเสมอคือใครได้แตะไฟล์และเมื่อไหร่, ไม่ใช่ว่าอะไรเปลี่ยนในย่อหน้าที่สี่
รูปแบบที่สี่คือการเปรียบเทียบไฟล์กับสแนปชอตที่ถือว่าเป็นเวอร์ชันที่ดีล่าสุด, ใช้โค้ดเดียวกันกับ dict ที่เก็บไว้ด้านหนึ่ง ในทุกกรณีไฟล์ส่งออกคือผลลัพธ์ที่ส่งมอบ; คอนโซลเป็นเพียงเสียงรบกวนของความคืบหน้า รูปแบบการคืนค่า exit-code ของสคริปต์สอดคล้องกับ main.py ของรีโพซิทอรี, ทำให้ตัวจัดตารางและ CI ถือว่าการ assert ล้มเหลวเป็นการรันที่ล้มเหลวโดยไม่ต้องตั้งค่าเพิ่มเติม ไม่มีกรณีใดที่ต้องเขียนโค้ดเพิ่มเติมนอกจากที่แสดงในหน้านี้
What counts as a change worth flagging?
ทุกอย่างที่ diff จัดประเภทบวกกับบริบทที่คุณเพิ่มเข้ามา คุณสมบัติที่เพิ่มและลบมักจะต้องตรวจสอบเสมอเพราะหมายถึงโครงสร้างเปลี่ยนแปลง ไม่ใช่แค่ค่า สำหรับรายการที่เปลี่ยน, ทีมส่วนใหญ่จะเตือนบนกลุ่มตัวตนและเวอร์ชันก่อน แล้วถือส่วนที่เหลือเป็นข้อมูลเพิ่มเติม ตัวตรวจจับถูกสร้างขึ้นเพื่อให้การตรวจสอบครั้งแรกใช้ฟังก์ชันเดียว
5. Quick Reference: Key Calls
| Call | What It Does |
|---|---|
Metadata(path) |
เปิดไฟล์; ตัวจัดการบริบทดูแลการปล่อยทรัพยากร |
find_properties(predicate) |
คืนค่าทุกคุณสมบัติที่เงื่อนไขรับ, ครอบคลุมทุกชั้น |
p.interpreted_value |
ค่าที่มนุษย์อ่านได้; หากไม่มีจะใช้ p.value |
Tags.person.* / Tags.corporate.company |
การจัดประเภทตัวตน, ไม่ขึ้นกับรูปแบบไฟล์ |
Tags.time.* |
การจัดประเภทเวลาเพื่อใช้กับตัวตรวจจับเวอร์ชัน |
ดู complete API reference เพื่อดูการค้นหาและแท็กทั้งหมด คำศัพท์แท็กมีมากกว่าที่แสดงในตารางนี้; กลุ่มแท็ก origin, content, และ legal ใช้การทดสอบสมาชิกแบบเดียวกัน
6. Common Issues & Fixs
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ของรีโพซิทอรีทำการ assert ทุกขั้นตอนและคืนค่า exit code ที่เหมาะสม, สามารถต่อเข้ากับ CI หรือ scheduler ได้โดยตรง - Walk the tutorial version: use case guide สร้าง pipeline เดียวกันในสามบทเรียนระดับต่างกัน
- See the whole project: document-version-metadata-diff-python พร้อมคู่เวอร์ชันที่เตรียมไว้