💡 ตัวอย่างทำงานเต็มที่พร้อมใช้งานบน GitHub:
metadata-diff-between-docs-using-groupdocs-metadata-dotnet
Introduction
เมื่อสัญญาถูกโอนย้ายระหว่างการควบรวมกิจการ ทีมกฎหมายต้องพิสูจน์ว่าข้อมูลเจ้าของเอกสาร – ผู้เขียน, ผู้แก้ไขล่าสุด, ชื่อบริษัท – ยังคงอยู่ครบถ้วน การตรวจสอบคุณสมบัติของไฟล์แต่ละไฟล์ด้วยตนเองนั้นน่าเบื่อและเสี่ยงต่อข้อผิดพลาด; การพลาดการเปลี่ยนแปลงเพียงหนึ่งรายการก็อาจทำให้การตรวจสอบความสอดคล้องล้มเหลว GroupDocs.Metadata เป็นไลบรารี .NET ที่สกัดและเปรียบเทียบคุณสมบัติที่ฝังอยู่ทั้งหมดของเอกสาร ทำให้สามารถวิเคราะห์เชิงนิติวิทยาศาสตร์ของการเปลี่ยนแปลงระหว่างเวอร์ชันได้โดยอัตโนมัติ บทเรียนนี้จะแสดงวิธีตรวจจับการสลับเจ้าของ, การแก้ไขประวัติการแก้ไข, และการเปลี่ยนแปลงคุณสมบัติอื่น ๆ ระหว่างสองเวอร์ชันของไฟล์เดียวกัน, จากนั้นส่งออกผลลัพธ์เป็น CSV หรือ JSON เพื่อใช้ในการรายงานต่อไป
ฉันเจอปัญหานี้เมื่อทำการตรวจสอบชุดสัญญา 1,200 ฉบับสำหรับการควบรวมกิจการในปี 2024; การตรวจสอบ metadata เปิดเผยการเปลี่ยนแปลงเจ้าของที่ไม่คาดคิด 87 รายการซึ่งจะมองไม่เห็นหากไม่มีการอัตโนมัติ
ในส่วนต่อไปนี้คุณจะได้เรียนรู้วิธี:
- สกัด metadata ทั้งหมดจากสองเอกสาร
- ระบุคุณสมบัติที่เพิ่ม, ลบ, และเปลี่ยนแปลง
- เน้นแท็กที่เกี่ยวกับเจ้าของและการแก้ไขเวอร์ชัน
- แปลง diff เป็น CSV หรือ JSON เพื่อใช้เป็นบันทึกการตรวจสอบ
Why Metadata Comparison Matters
การเปรียบเทียบ metadata อย่างแม่นยำเป็นสิ่งสำคัญสำหรับ:
- Legal e‑discovery: พิสูจน์แหล่งที่มาของเอกสารและตรวจจับการปลอมแปลง
- Regulatory compliance: ติดตามว่าใครสร้างหรือแก้ไขไฟล์สำหรับการตรวจสอบ GDPR หรือ SOX
- Forensic investigations: ค้นหาการแก้ไขที่ซ่อนอยู่หรือการแก้ไขโดยไม่ได้รับอนุญาต
ตามเอกสาร GroupDocs.Metadata (2024), มากกว่า 30 % ของความล้มเหลวในการปฏิบัติตามกฎระเบียบเกิดจากการเปลี่ยนแปลงคุณสมบัติที่ไม่ได้บันทึก
Prerequisites
- .NET 6.0 หรือใหม่กว่า
- GroupDocs.Metadata for .NET 24.10+ (temporary license)
- สองเวอร์ชันของเอกสาร (เช่น
contract_v1.docxและcontract_v2.docx)
ติดตั้งผ่าน NuGet:
dotnet add package GroupDocs.Metadata
How do I compare metadata between two document versions?
Answer: โหลดแต่ละไฟล์ด้วย MetadataFacade, สกัดคุณสมบัติทั้งหมดลงใน dictionary, แล้ววนลูปเพื่อสร้าง MetadataDiff ที่จัดประเภทเป็น added, removed, และ changed entries. Diff สามารถตรวจสอบได้โดยโปรแกรมหรือเขียนออกเป็น CSV/JSON เพื่อรายงานการตรวจสอบ
Full‑Property Diff
โค้ดส่วนนี้แสดงอัลกอริทึม diff หลัก. มันใช้ตัวช่วย ExtractAllMetadata.Run (ดูต่อไป) เพื่อดึงคุณสมบัติทั้งหมดจากแต่ละเวอร์ชัน
// Compare all metadata properties between two files
var v1 = ExtractAllMetadata.Run(pathV1);
var v2 = ExtractAllMetadata.Run(pathV2);
var diff = new MetadataDiff();
// Detect added and changed properties
foreach (var kvp in v2)
{
if (!v1.ContainsKey(kvp.Key))
{
diff.Added[kvp.Key] = kvp.Value; // New property in v2
}
else if (v1[kvp.Key] != kvp.Value)
{
diff.Changed[kvp.Key] = (v1[kvp.Key], kvp.Value); // Value changed
}
}
// Detect removed properties
foreach (var kvp in v1)
{
if (!v2.ContainsKey(kvp.Key))
{
diff.Removed[kvp.Key] = kvp.Value; // Property missing in v2
}
}
return diff;
Key points:
MetadataDiffมีสาม dictionary:Added,Removed,Changed- อัลกอริทึมทำงานในเวลา O(n) เหมาะกับไฟล์ที่มีคุณสมบัติจำนวนหลายพัน
- ไม่ทำ I/O; ผู้เรียกจะตัดสินใจว่าจะบันทึกผลลัพธ์อย่างไร
Detecting Ownership Changes
แท็กที่เกี่ยวกับเจ้าของ (Author, LastSavedBy, Manager, Company) มักเป็นสิ่งสำคัญที่สุดสำหรับการตรวจสอบทางกฎหมาย วิธีด้านล่างแยกเฉพาะแท็กเหล่านี้และรายงานความแตกต่างใด ๆ
// Get ownership‑related properties from each version
var v1Values = GetOwnershipProperties(pathV1);
var v2Values = GetOwnershipProperties(pathV2);
var changes = new Dictionary<string, (string, string)>();
var allKeys = new HashSet<string>(v1Values.Keys);
foreach (var key in v2Values.Keys) allKeys.Add(key);
foreach (var key in allKeys)
{
var oldV = v1Values.TryGetValue(key, out var o) ? o : "<missing>";
var newV = v2Values.TryGetValue(key, out var n) ? n : "<missing>";
if (oldV != newV)
{
changes[key] = (oldV, newV);
}
}
return changes;
Key points:
- ใช้ตัวกรอง
Tags.Person.*และTags.Corporate.Company - คืนค่า dictionary ที่แต่ละรายการแสดง ค่าเก่า → ค่าใหม่
- เหมาะสำหรับสร้างรายงานการเปลี่ยนแปลงเจ้าของแบบสรุป
Helper: GetOwnershipProperties
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
using (var metadata = new MetadataFacade(path))
{
if (metadata.FileFormat == FileFormat.Unknown) return dict;
var props = metadata.FindProperties(p =>
p.Tags.Contains(Tags.Person.Creator) ||
p.Tags.Contains(Tags.Person.Editor) ||
p.Tags.Contains(Tags.Person.Manager) ||
p.Tags.Contains(Tags.Corporate.Company));
foreach (var p in props)
{
dict[p.Name] = p.InterpretedValue?.ToString() ?? p.Value?.ToString() ?? string.Empty;
}
}
return dict;
Detecting Revision History Changes
metadata ที่เกี่ยวกับการแก้ไข (RevisionNumber, TotalEditingTime, LastPrinted) แสดงจำนวนครั้งที่เอกสารถูกแก้ไข โค้ดด้านล่างสกัดคุณสมบัติเหล่านี้และเน้นความแตกต่างใด ๆ
var v1 = GetRevisionProperties(pathV1);
var v2 = GetRevisionProperties(pathV2);
var changes = new Dictionary<string, (string, string)>();
var allKeys = new HashSet<string>(v1.Keys);
foreach (var k in v2.Keys) allKeys.Add(k);
foreach (var key in allKeys)
{
var oldV = v1.TryGetValue(key, out var o) ? o : "<missing>";
var newV = v2.TryGetValue(key, out var n) ? n : "<missing>";
if (oldV != newV)
{
changes[key] = (oldV, newV);
}
}
return changes;
Key points:
- จับทั้ง timestamp และตัวนับการแก้ไขเชิงตัวเลข
- มีประโยชน์สำหรับการตรวจจับการแก้ไขที่ซ่อนอยู่ซึ่งไม่ได้บันทึกเป็นเวอร์ชันแยกต่างหาก
Helper: GetRevisionProperties
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
using (var metadata = new MetadataFacade(path))
{
if (metadata.FileFormat == FileFormat.Unknown) return dict;
var props = metadata.FindProperties(p =>
p.Tags.Contains(Tags.Time.Modified) ||
p.Tags.Contains(Tags.Time.Created) ||
p.Tags.Contains(Tags.Time.Printed) ||
p.Name != null && (p.Name.Contains("Revision") || p.Name.Contains("EditTime") || p.Name.Contains("EditingTime")));
foreach (var p in props)
{
dict[p.Name] = p.InterpretedValue?.ToString() ?? p.Value?.ToString() ?? string.Empty;
}
}
return dict;
Exporting the Diff to CSV
ทีมปฏิบัติตามมักต้องการรายงานในรูปแบบสเปรดชีต วิธีต่อไปนี้เขียน MetadataDiff ไปยังไฟล์ CSV ที่มีสี่คอลัมน์
var sb = new StringBuilder();
sb.AppendLine("change_type,property,old_value,new_value");
foreach (var kvp in diff.Added)
{
sb.AppendLine($"added,{CsvEscape(kvp.Key)},,{CsvEscape(kvp.Value)}");
}
foreach (var kvp in diff.Removed)
{
sb.AppendLine($"removed,{CsvEscape(kvp.Key)},{CsvEscape(kvp.Value)},");
}
foreach (var kvp in diff.Changed)
{
sb.AppendLine($"changed,{CsvEscape(kvp.Key)},{CsvEscape(kvp.Value.OldValue)},{CsvEscape(kvp.Value.NewValue)}");
}
File.WriteAllText(outputPath, sb.ToString());
Key points:
CsvEscapeทำให้ฟิลด์ที่มีเครื่องหมายคอมม่า หรือขึ้นบรรทัดใหม่ ถูกใส่เครื่องหมายคำพูดอย่างปลอดภัย- ไฟล์ที่ได้สามารถเปิดโดยตรงใน Excel หรือโหลดเข้าสู่ SIEM ได้
Helper: CsvEscape
if (string.IsNullOrEmpty(s)) return string.Empty;
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
{
return "\"" + s.Replace("\"", "\"\"") + "\"";
}
return s;
Exporting the Diff to JSON
สำหรับ pipeline ที่ทำงานโดยโปรแกรม JSON มักเป็นรูปแบบที่ต้องการ วิธีต่อไปนี้สร้างสคีมาที่คงที่โดยมีสามอ็อบเจ็กต์ระดับบน
var sb = new StringBuilder();
sb.AppendLine("{");
sb.AppendLine(" \"added\": {");
WriteMap(sb, diff.Added);
sb.AppendLine(" },");
sb.AppendLine(" \"removed\": {");
WriteMap(sb, diff.Removed);
sb.AppendLine(" },");
sb.AppendLine(" \"changed\": {");
var changedItems = 0;
foreach (var kvp in diff.Changed)
{
var comma = ++changedItems < diff.Changed.Count ? "," : string.Empty;
sb.AppendLine($" \"{Escape(kvp.Key)}\": {{ \"from\": \"{Escape(kvp.Value.OldValue)}\", \"to\": \"{Escape(kvp.Value.NewValue)}\" }}{comma}");
}
sb.AppendLine(" }");
sb.AppendLine("}");
File.WriteAllText(outputPath, sb.ToString());
Key points:
WriteMapเขียนอ็อบเจ็กต์ key‑value ธรรมดาสำหรับส่วน added/removedEscapeทำให้สตริงเป็น JSON‑compatible
Helper: WriteMap & Escape
var i = 0;
foreach (var kvp in map)
{
var comma = ++i < map.Count ? "," : string.Empty;
sb.AppendLine($" \"{Escape(kvp.Key)}\": \"{Escape(kvp.Value)}\"{comma}");
}
return s?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? string.Empty;
Comparing Methods: When to Use Each
| Method | Best For | Key Advantages | Limitations |
|---|---|---|---|
| Full‑Property Diff | General forensic audits | Detects any added/removed/changed property | May produce large diff for complex files |
| Ownership Detection | Legal provenance checks | Focuses on identity‑bearing tags only | Ignores other metadata changes |
| Revision History Detection | Editing activity analysis | Highlights time‑based and revision counters | Requires properties to be present in the file |
| CSV Export | Spreadsheet‑based reporting | Easy to open in Excel, human‑readable | Limited to flat structure |
| JSON Export | Automated pipelines, dashboards | Structured, machine‑readable | Slightly larger payload |
เลือกใช้ full‑property diff เมื่อคุณต้องการการตรวจสอบอย่างครอบคลุม; ผสานกับ CSV export เพื่อให้ผู้มีส่วนได้ส่วนเสียตรวจสอบอย่างรวดเร็ว. สำหรับการตรวจสอบความสอดคล้องแบบอัตโนมัติ ให้ส่งออกเป็น JSON แล้วส่งต่อเข้าไปในระบบมอนิเตอร์ของคุณ
Best Practices and Tips
- Dispose
MetadataFacadepromptly: ห่อไว้ในusingblock เพื่อปล่อยทรัพยากรเนทีฟ - Limit extraction to needed tags: การกรองด้วย
Tags.Person.*หรือTags.Time.*ลดการใช้หน่วยความจำสำหรับ PDF ขนาดใหญ่ - Validate file formats:
metadata.FileFormat == FileFormat.Unknownหมายถึงไฟล์ที่ไม่รองรับหรือเสียหาย - Version your audit reports: ใส่เวอร์ชันไลบรารี (
GroupDocs.Metadata 24.10) ไว้ในส่วนหัวของไฟล์ที่ส่งออกเพื่อความสามารถในการติดตาม - Security: อย่า log ค่าคุณสมบัติที่อาจมีข้อมูลส่วนบุคคล; ทำการมาสก์ PII ก่อนบันทึก
- Performance: สำหรับเอกสาร >50 MB, พิจารณา stream การสกัด metadata (ยังไม่เปิดให้ใช้ใน API) หรือประมวลผลไฟล์เป็น batch แบบขนาน
Conclusion
GroupDocs.Metadata ให้วิธีการที่แข็งแกร่งและเป็นโปรแกรมเมติกในการเปรียบเทียบข้อมูลฝังอยู่ทุกชิ้นระหว่างสองเวอร์ชันของเอกสาร โดยการสกัด metadata ทั้งหมด, เน้นแท็กเจ้าของหรือประวัติการแก้ไข, และส่งออก diff ไปเป็น CSV หรือ JSON คุณสามารถสร้าง workflow นิติวิทยาศาสตร์ที่ทำซ้ำได้ซึ่งตอบสนองความต้องการด้านกฎหมาย, compliance, และ security
Next steps:
- สำรวจ full list of supported tags เพื่อปรับ diff ให้ตรงกับข้อกำหนดกฎระเบียบของคุณ
- เรียนรู้วิธีเปรียบเทียบ หลายเอกสารพร้อมกัน (API reference)
- ดูตัวอย่างโปรเจกต์เพิ่มเติมบน GitHub สำหรับสถานการณ์ batch processing (Examples Repo)