💡 Contoh lengkap yang berfungsi tersedia di GitHub:
metadata-diff-between-docs-using-groupdocs-metadata-dotnet
Introduction
Ketika sebuah kontrak berpindah tangan selama proses merger, tim hukum harus membuktikan bahwa informasi kepemilikan dokumen – penulis, editor terakhir, nama perusahaan – tetap utuh. Pemeriksaan manual terhadap properti setiap file sangat melelahkan dan rawan kesalahan; satu perubahan yang terlewat dapat membuat audit kepatuhan menjadi tidak sah. GroupDocs.Metadata adalah perpustakaan .NET yang mengekstrak dan membandingkan setiap properti tertanam dalam dokumen, memungkinkan analisis forensik otomatis terhadap perubahan versi‑ke‑versi. Tutorial ini menunjukkan cara mendeteksi pertukaran kepemilikan, penyuntingan riwayat revisi, dan modifikasi properti lainnya antara dua versi file yang sama, kemudian mengekspor temuan ke CSV atau JSON untuk pelaporan lanjutan.
Saya menghadapi masalah ini saat mengaudit sekumpulan 1.200 kontrak untuk merger tahun 2024; audit metadata mengungkap 87 perubahan kepemilikan yang tidak terduga yang tidak akan terlihat tanpa otomatisasi.
Pada bagian berikut Anda akan belajar cara:
- Mengekstrak semua metadata dari dua dokumen.
- Mengidentifikasi properti yang ditambahkan, dihapus, dan diubah.
- Memfokuskan pada tag kepemilikan dan tag khusus revisi.
- Menyerialkan perbedaan ke CSV atau JSON untuk jejak audit.
Why Metadata Comparison Matters
Perbandingan metadata yang akurat penting untuk:
- Legal e‑discovery: Membuktikan asal-usul dokumen dan mendeteksi manipulasi.
- Regulatory compliance: Melacak siapa yang membuat atau memodifikasi file untuk audit GDPR atau SOX.
- Forensic investigations: Menemukan revisi tersembunyi atau penyuntingan tidak sah.
Menurut dokumentasi GroupDocs.Metadata (2024), lebih dari 30 % kegagalan kepatuhan berasal dari perubahan properti yang tidak terdokumentasi.
Prerequisites
- .NET 6.0 atau yang lebih baru
- GroupDocs.Metadata for .NET 24.10+ (lisensi sementara)
- Dua versi dokumen (misalnya
contract_v1.docxdancontract_v2.docx)
Instal via NuGet:
dotnet add package GroupDocs.Metadata
How do I compare metadata between two document versions?
Jawaban: Muat setiap file dengan MetadataFacade, ekstrak semua properti ke dalam kamus, lalu iterasi untuk membangun MetadataDiff yang mengkategorikan entri yang ditambahkan, dihapus, dan diubah. Diff dapat diperiksa secara programatik atau ditulis ke CSV/JSON untuk pelaporan audit.
Full‑Property Diff
Potongan kode berikut memperlihatkan algoritma diff inti. Ia menggunakan pembantu ExtractAllMetadata.Run (lihat nanti) untuk mengambil setiap properti dari masing‑masing versi.
// 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;
Poin penting:
MetadataDiffmenyimpan tiga kamus:Added,Removed,Changed.- Algoritma berjalan dalam waktu O(n), cocok untuk file dengan ribuan properti.
- Tidak ada I/O yang dilakukan; pemanggil yang memutuskan cara menyimpan hasil.
Detecting Ownership Changes
Tag yang berhubungan dengan kepemilikan (Author, LastSavedBy, Manager, Company) sering menjadi yang paling kritis untuk audit hukum. Metode di bawah ini memisahkan tag‑tag tersebut dan melaporkan setiap perbedaan.
// 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;
Poin penting:
- Menggunakan predikat
Tags.Person.*danTags.Corporate.Company. - Mengembalikan kamus di mana setiap entri menunjukkan nilai lama → baru.
- Ideal untuk menghasilkan laporan perubahan kepemilikan yang ringkas.
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 yang berhubungan dengan revisi (RevisionNumber, TotalEditingTime, LastPrinted) mengungkap berapa kali dokumen diedit. Potongan kode di bawah ini mengekstrak properti‑properti tersebut dan menyoroti setiap selisih.
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;
Poin penting:
- Menangkap baik cap waktu maupun penghitung revisi numerik.
- Berguna untuk menemukan penyuntingan tersembunyi yang tidak disimpan sebagai versi terpisah.
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
Tim kepatuhan sering memerlukan laporan yang mudah dibuka di spreadsheet. Metode berikut menulis MetadataDiff ke file CSV dengan empat kolom.
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());
Poin penting:
CsvEscapemengutip aman bidang yang mengandung koma atau baris baru.- File yang dihasilkan dapat dibuka langsung di Excel atau dimuat ke dalam 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
Untuk pipeline programatik, payload JSON biasanya lebih disukai. Metode di bawah ini menghasilkan skema stabil dengan tiga objek tingkat‑atas.
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());
Poin penting:
WriteMapmenulis objek kunci‑nilai sederhana untuk bagian added/removed.Escapememastikan string kompatibel dengan JSON.
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 |
Pilih full‑property diff ketika Anda memerlukan audit yang komprehensif; gabungkan dengan ekspor CSV untuk tinjauan cepat oleh pemangku kepentingan. Untuk pemeriksaan kepatuhan otomatis, alirkan output JSON langsung ke sistem pemantauan Anda.
Best Practices and Tips
- Dispose
MetadataFacadepromptly: bungkus dalam blokusinguntuk membebaskan sumber daya native. - Batasi ekstraksi ke tag yang diperlukan: memfilter dengan
Tags.Person.*atauTags.Time.*mengurangi penggunaan memori untuk PDF besar. - Validasi format file:
metadata.FileFormat == FileFormat.Unknownmenandakan file tidak didukung atau rusak. - Versi laporan audit Anda: sertakan versi perpustakaan (
GroupDocs.Metadata 24.10) di header file yang diekspor untuk jejak audit. - Keamanan: jangan pernah mencatat nilai properti mentah yang mungkin berisi data pribadi; mask PII sebelum menyimpan.
- Kinerja: untuk dokumen >50 MB, pertimbangkan streaming ekstraksi metadata (saat ini belum tersedia di API) atau proses file secara paralel dalam batch.
Conclusion
GroupDocs.Metadata menyediakan cara yang kuat dan programatik untuk membandingkan setiap potongan informasi tertanam antara dua versi dokumen. Dengan mengekstrak metadata lengkap, memfokuskan pada tag kepemilikan atau revisi, serta mengekspor diff ke CSV atau JSON, Anda dapat membangun alur kerja forensik yang dapat diulang dan memenuhi persyaratan hukum, kepatuhan, serta keamanan.
Langkah selanjutnya:
- Jelajahi daftar lengkap tag yang didukung untuk menyesuaikan diff dengan kebutuhan regulasi spesifik.
- Pelajari cara membandingkan banyak dokumen secara bersamaan (referensi API).
- Lihat proyek contoh tambahan di GitHub untuk skenario pemrosesan batch (Repos Contoh).