💡 Full working example available on GitHub: document-version-metadata-diff-python
Những gì bạn sẽ xây dựng
Trong hướng dẫn này, bạn sẽ so sánh mọi thuộc tính metadata giữa hai phiên bản của một tài liệu và in ra chính xác những gì đã được thêm, xóa hoặc thay đổi. Một diff phiên bản metadata là so sánh ở mức thuộc tính giữa hai bản sửa đổi của cùng một tệp, và nó bắt được những tín hiệu mà so sánh văn bản không bao giờ thấy: một Creator mới, một RevisionNumber tăng lên, một phiên chỉnh sửa được ghi lại sau khi đánh giá đã đóng. Khi kết thúc, bạn sẽ có một giải pháp hoạt động cùng hai bộ phát hiện tập trung và hai định dạng xuất, tất cả được rút ra từ một kho lưu trữ có thể chạy được và đã được cung cấp một cặp phiên bản mẫu.
Skill level: nhà phát triển Python trung cấp
What you need: Python 3, pip, và hai phiên bản của một tài liệu
Lần chạy đầu tiên của tôi với script này đã phát hiện một thay đổi giá trị Company mà không ai trong nhóm nhớ đã thực hiện; một dòng mã đó đã trả tiền cho việc thiết lập. Mọi thứ dưới đây đã sẵn sàng để sao chép và dán và tổng cộng chưa tới một trăm dòng.
Quy trình được thiết kế cố ý đơn giản: hai lần mở tệp, ba dict comprehension, một vòng lặp in. Sự đơn giản là mục tiêu. Các tranh chấp về phiên bản được quyết định dựa trên việc phương pháp có thể giải thích và lặp lại được không, và một script nhỏ như vậy có thể được đọc toàn bộ bởi bất kỳ ai thách thức kết quả.
1. Cài đặt
pip install groupdocs-metadata-net==26.5
companion repository đã cố định phiên bản này và cung cấp document-v1.docx và document-v2.docx nên mã dưới đây chạy ngay như vậy. Hãy cố định phiên bản mà cuộc kiểm toán của bạn đã sử dụng; khả năng tái tạo là một phần của bằng chứng.
2. Mã lõi
Đọc cả hai cây thuộc tính, sau đó phân loại delta bằng logic tập hợp. Đây là toàn bộ diff:
# 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}")
Đó là mức tối thiểu bạn cần. Mong đợi số lượng nhỏ trên các cặp phiên bản thực tế; một delta hàng chục thường có nghĩa là tệp đã đi qua một thay đổi mẫu hoặc một di chuyển lưu trữ trên đường. Các phần tiếp theo sẽ giải thích các lời gọi chính và hiển thị các tùy chỉnh mà hầu hết các nhóm thường thêm vào đầu tiên.
3. Cách hoạt động
Metadata: trình quản lý ngữ cảnh mở tệp và giải phóng nó khi thoát; một thể hiện cho mỗi phiên bản.find_properties: duyệt các trường tích hợp, thuộc tính tùy chỉnh và XMP trong một lần, trả về mọi thứ mà predicate chấp nhận.interpreted_value: dạng giá trị dễ đọc cho con người; ưu tiên nó có nghĩa là ngày tháng và các enum được so sánh dưới dạng chuỗi mà bạn có thể in trong báo cáo.- Qualified names as keys: các trường tích hợp và tùy chỉnh không thể trùng nhau trong dict, vì vậy logic tập hợp vẫn an toàn.
Không có gì ở đây phân tích cấu trúc DOCX. [product documentation] (https://docs.groupdocs.com/metadata/python-net/) liệt kê hơn 170 định dạng phía sau cùng một lời gọi, vì vậy script giống hệt này cũng diff được các cặp PDF hoặc XLSX.
Một thuộc tính nữa của thiết kế đáng đề cập: ranh giới API kết thúc ở hai lời gọi read_props. Mọi thứ sau chúng là Python chuẩn, vì vậy các unit test, ngưỡng và quy tắc cảnh báo không bao giờ chạm tới lớp tài liệu. Các nhóm bọc đoạn này trong một dịch vụ thường cache các dict đã trích xuất cho mỗi phiên bản và cho phép mọi kiểm tra hạ nguồn tái sử dụng chúng, giữ việc I/O tệp ở một lần mở cho mỗi phiên bản bất kể có bao nhiêu câu hỏi được đặt ra.
4. Tùy chỉnh thường gặp
Phát hiện chỉ thay đổi quyền sở hữu
Khi câu hỏi là “ai đã chạm vào tệp này”, lọc ngay khi đọc bằng các predicate tag thay vì lọc sau khi có diff đầy đủ:
# 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
Chạy cùng vòng lặp delta trên hai dict này, sử dụng <missing> làm giá trị mặc định để một trường biến mất vẫn được hiện ra. Predicate không đặt tên trường, đó là lý do một bộ phát hiện có thể phục vụ mọi định dạng mà thư viện đọc.
Theo dõi dòng thời gian chỉnh sửa
Thay đổi predicate thành Tags.time cộng với các quy tắc tên bộ đếm và bộ phát hiện sẽ báo cáo các di chuyển của RevisionNumber, TotalEditingTime và 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)))
Xuất báo cáo kiểm toán
Những phát hiện chỉ ở console sẽ chết ở đó. Bốn cột bao phủ bảng tính và trường hợp 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])
Kho lưu trữ cũng bao gồm một exporter JSON với schema ba bản đồ ổn định cho các bảng điều khiển và API quản lý trường hợp.
Nơi áp dụng thực tế
Ba triển khai liên tục xuất hiện. Các pipeline tiếp nhận diff mỗi tài liệu đến với bản sao đã có trong hồ sơ và cách ly các cặp có thay đổi quyền sở hữu. Các công việc tuân thủ chạy diff theo lịch và lưu trữ CSV cho mỗi cặp, xây dựng một dòng thời gian thuộc tính mà không ai phải tái tạo sau này. Và công cụ tranh chấp chạy cả hai bộ phát hiện theo yêu cầu, vì khi một khiếu nại xuất hiện câu hỏi mở đầu luôn là ai đã chạm vào tệp và khi nào, không phải thay đổi gì ở đoạn văn thứ tư.
Mẫu thứ tư, diff một tệp với bản snapshot cuối cùng được biết là tốt, tái sử dụng cùng mã với một dict đã lưu ở một phía. Trong tất cả các trường hợp, tệp xuất là sản phẩm cuối; output console chỉ là tiếng ồn tiến trình. Mẫu exit-code của script tuân theo main.py trong kho, vì vậy các scheduler và CI coi một assert thất bại là một lần chạy thất bại mà không cần dây nối thêm. Không có trường hợp nào cần code ngoài những gì trang này hiển thị.
Điều gì được coi là thay đổi đáng chú ý?
Bất kỳ gì diff phân loại cộng với ngữ cảnh bạn thêm vào. Các thuộc tính được thêm và xóa luôn đáng xem xét vì chúng cho thấy cấu trúc đã thay đổi chứ không chỉ giá trị. Đối với các mục đã thay đổi, hầu hết các nhóm cảnh báo trước tiên trên các nhóm danh tính và phiên bản, còn phần còn lại chỉ là thông tin. Các bộ phát hiện tồn tại để lần đầu tiên chỉ tốn một lời gọi hàm.
5. Tham chiếu nhanh: Các lời gọi chính
| Lời gọi | Mô tả |
|---|---|
Metadata(path) |
Mở tệp; trình quản lý ngữ cảnh xử lý giải phóng |
find_properties(predicate) |
Trả về mọi thuộc tính mà predicate chấp nhận, trên tất cả các lớp |
p.interpreted_value |
Giá trị dễ đọc; nếu không có sẽ dùng p.value |
Tags.person.* / Tags.corporate.company |
Phân loại danh tính, không phụ thuộc vào định dạng |
Tags.time.* |
Phân loại thời gian cho bộ phát hiện phiên bản |
Xem [complete API reference] (https://reference.groupdocs.com/metadata/python-net/) để biết toàn bộ khả năng tìm kiếm và gắn thẻ. Từ vựng thẻ lớn hơn các hàng này; các nhóm thẻ origin, content và legal tuân theo cùng một kiểm tra thành viên.
6. Vấn đề thường gặp & Giải pháp
Diff quá lớn và đọc giống như nhiễu
→ Hai đường dẫn có thể không phải là các phiên bản của cùng một tài liệu. Sửa: xác thực nguồn gốc trước khi diff; các tệp không liên quan sẽ tạo ra delta vô nghĩa.
Trường tác giả đã biết không bao giờ xuất hiện trong bộ phát hiện quyền sở hữu
→ Một số nhà sản xuất lưu danh tính trong các trường tùy chỉnh không được gắn thẻ. Sửa: chạy diff đầy đủ một lần, tìm tên trường thực tế, và mở rộng predicate bằng quy tắc tên.
Console shows an evaluation-mode warning
→ Không tìm thấy tệp giấy phép. Sửa: chỉ định LICENSE_PATH trong main.py tới tệp .lic của bạn, hoặc giữ chế độ đánh giá cho phát triển; logic vẫn giống nhau.
Dates print as raw serial numbers
→ p.value thô đã lọt vào một phần đọc nào đó. Sửa: giữ mẫu interpreted_value-first trong read_props; đó là lý do báo cáo vẫn đọc được.
Tiếp theo là gì?
Bạn đã có một diff metadata hoạt động. Dưới đây là các bước tiếp theo:
- Batch it: lặp script qua các cặp tài liệu và lưu CSV cho mỗi cặp; chi phí mỗi cặp là hai lần mở tệp, và các CSV có thể nối liền cho một cái nhìn toàn thư viện.
- Schedule it:
main.pytrong kho khẳng định mỗi bước và trả về mã thoát thích hợp, dễ dàng tích hợp vào CI hoặc scheduler. - Walk the tutorial version: [use case guide] (https://docs.groupdocs.com/metadata/python-net/use-cases/compare-metadata-between-document-versions/) xây dựng cùng pipeline trong ba tutorial có cấp độ.
- See the whole project: [document-version-metadata-diff-python] (https://github.com/groupdocs-metadata/document-version-metadata-diff-python) với cặp revision mẫu.