💡 Contoh kerja penuh tersedia di GitHub:
metadata-diff-between-docs-using-groupdocs-metadata-dotnet

Pendahuluan

Ketika sebuah kontrak berpindah tangan selama merger, tim hukum harus membuktikan bahwa informasi kepemilikan dokumen – penulis, editor terakhir, nama perusahaan – tetap utuh. Pemeriksaan manual properti setiap file memakan waktu dan rawan kesalahan; satu perubahan yang terlewat dapat membatalkan audit kepatuhan. GroupDocs.Metadata adalah perpustakaan .NET yang mengekstrak dan membandingkan setiap properti tertanam dalam dokumen, memungkinkan analisis forensik otomatis dari 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 selanjutnya.

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.

Mengapa Perbandingan Metadata Penting

Perbandingan metadata yang akurat penting untuk:

  • Legal e‑discovery: Membuktikan asal‑usul dokumen dan mendeteksi manipulasi.
  • Kepatuhan regulasi: Melacak siapa yang membuat atau memodifikasi file untuk audit GDPR atau SOX.
  • Investigasi forensik: Menemukan revisi tersembunyi atau penyuntingan tidak sah.

Menurut dokumentasi GroupDocs.Metadata (2024), lebih dari 30 % kegagalan kepatuhan berasal dari perubahan properti yang tidak terdokumentasi.

Prasyarat

  • .NET 6.0 atau lebih baru
  • GroupDocs.Metadata untuk .NET 24.10+ (lisensi sementara)
  • Dua versi dokumen (misalnya contract_v1.docx dan contract_v2.docx)

Install via NuGet:

dotnet add package GroupDocs.Metadata

Bagaimana cara membandingkan metadata antara dua versi dokumen?

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.

Perbedaan Properti Lengkap

Potongan kode berikut menunjukkan algoritma diff inti. Ia menggunakan pembantu ExtractAllMetadata.Run (lihat nanti) untuk menarik 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:

  • MetadataDiff menyimpan 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.

Mendeteksi Perubahan Kepemilikan

Tag yang berhubungan dengan kepemilikan (Author, LastSavedBy, Manager, Company) sering menjadi yang paling kritis untuk audit hukum. Metode di bawah ini mengisolasi 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.* dan Tags.Corporate.Company.
  • Mengembalikan kamus di mana setiap entri menunjukkan nilai lama → baru.
  • Ideal untuk menghasilkan laporan perubahan kepemilikan yang ringkas.

Pembantu: 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;

Mendeteksi Perubahan Riwayat Revisi

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 delta.

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.

Pembantu: 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;

Mengekspor Perbedaan ke 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:

  • CsvEscape mengutip aman bidang yang mengandung koma atau baris baru.
  • File yang dihasilkan dapat dibuka langsung di Excel atau dimuat ke dalam SIEM.

Pembantu: CsvEscape

if (string.IsNullOrEmpty(s)) return string.Empty;
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
{
    return "\"" + s.Replace("\"", "\"\"") + "\"";
}
return s;

Mengekspor Perbedaan ke 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:

  • WriteMap menulis objek kunci‑nilai sederhana untuk bagian added/removed.
  • Escape memastikan string kompatibel dengan JSON.

Pembantu: 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;

Membandingkan Metode: Kapan Menggunakan Masing‑Masing

Method Best For Key Advantages Limitations
Full‑Property Diff Audit forensik umum Mendeteksi setiap properti yang ditambahkan/dihapus/diubah Mungkin menghasilkan perbedaan besar untuk file kompleks
Ownership Detection Pemeriksaan asal‑usul hukum Fokus hanya pada tag yang memuat identitas Mengabaikan perubahan metadata lainnya
Revision History Detection Analisis aktivitas penyuntingan Menyoroti penghitung revisi dan waktu Memerlukan properti yang ada dalam file
CSV Export Pelaporan berbasis spreadsheet Mudah dibuka di Excel, dapat dibaca manusia Terbatas pada struktur datar
JSON Export Pipeline otomatis, dasbor Terstruktur, dapat dibaca mesin Payload sedikit lebih besar

Pilih perbedaan properti lengkap 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.

Praktik Terbaik dan Tips

  • Dispose MetadataFacade segera: bungkus dalam blok using untuk membebaskan sumber daya native.
  • Batasi ekstraksi ke tag yang diperlukan: penyaringan dengan Tags.Person.* atau Tags.Time.* mengurangi penggunaan memori untuk PDF besar.
  • Validasi format file: metadata.FileFormat == FileFormat.Unknown menandakan file tidak didukung atau rusak.
  • Versi laporan audit Anda: sertakan versi perpustakaan (GroupDocs.Metadata 24.10) di header file yang diekspor untuk jejak ketelusuran.
  • Keamanan: jangan pernah mencatat nilai properti mentah yang mungkin berisi data pribadi; sembunyikan PII sebelum menyimpan.
  • Kinerja: untuk dokumen >50 MB, pertimbangkan streaming ekstraksi metadata (saat ini belum tersedia di API) atau proses file dalam batch paralel.

Kesimpulan

GroupDocs.Metadata menyediakan cara yang kuat dan programatik untuk membandingkan setiap informasi tertanam antara dua versi dokumen. Dengan mengekstrak metadata lengkap, memfokuskan pada tag kepemilikan atau revisi, dan mengekspor perbedaan ke CSV atau JSON, Anda dapat membangun alur kerja forensik yang dapat diulang dan memenuhi persyaratan hukum, kepatuhan, serta keamanan.

Langkah selanjutnya:

Sumber Daya Tambahan