💡 Tam çalışan örnek GitHub’da mevcuttur:
metadata-diff-between-docs-using-groupdocs-metadata-dotnet

Giriş

Bir birleşme sırasında bir sözleşme el değiştirdiğinde, hukuk ekibi belgenin sahiplik bilgilerinin – yazar, son düzenleyici, şirket adı – değişmediğini kanıtlamak zorundadır. Her dosyanın özelliklerini manuel olarak incelemek zahmetli ve hata yapmaya açıktır; tek bir kaçırılan değişiklik uyum denetimini geçersiz kılabilir. GroupDocs.Metadata, bir .NET kütüphanesi olarak bir belgenin gömülü tüm özelliklerini çıkarır ve karşılaştırır, sürüm‑sürüm değişikliklerinin otomatik adli analizini mümkün kılar. Bu öğreticide aynı dosyanın iki sürümü arasındaki sahiplik değişimlerini, revizyon‑geçmişi düzenlemelerini ve diğer tüm özellik değişikliklerini nasıl tespit edeceğinizi, ardından bulguları CSV veya JSON olarak dışa aktararak raporlama sürecine nasıl dahil edeceğinizi göstereceğiz.

Bu sorunu, 2024 birleşmesi için 1.200 sözleşme setini denetlerken yaşadım; metadata denetimi, otomasyon olmadan görünmez olabilecek 87 beklenmedik sahiplik değişikliği ortaya çıkardı.

Aşağıdaki bölümlerde şunları öğreneceksiniz:

  • İki belgeden tüm metadata’yı çıkarma.
  • Eklenen, kaldırılan ve değişen özellikleri belirleme.
  • Sahiplik ve revizyon‑özel etiketlere odaklanma.
  • Denetim izleri için farkı CSV veya JSON olarak serileştirme.

Why Metadata Comparison Matters

Doğru metadata karşılaştırması şunlar için kritiktir:

  • Yasal e‑keşif: Belge kökenini kanıtlayın ve sahteciliği tespit edin.
  • Regülasyon uyumu: GDPR veya SOX denetimleri için dosyayı kimin oluşturduğunu veya değiştirdiğini izleyin.
  • Adli araştırmalar: Gizli revizyonları veya yetkisiz düzenlemeleri ortaya çıkarın.

GroupDocs.Metadata belgelerine (2024) göre, uyumsuzluk hatalarının %30 ‘undan fazlası belgelenmemiş özellik değişikliklerinden kaynaklanmaktadır.

Prerequisites

  • .NET 6.0 veya üzeri
  • GroupDocs.Metadata for .NET 24.10+ (geçici lisans)
  • İki belge sürümü (ör. contract_v1.docx ve contract_v2.docx)

NuGet üzerinden kurun:

dotnet add package GroupDocs.Metadata

İki belge sürümü arasında metadata nasıl karşılaştırılır?

Cevap: Her dosyayı MetadataFacade ile yükleyin, tüm özellikleri sözlüklere çıkarın, ardından eklenen, kaldırılan ve değişen girdileri sınıflandıran bir MetadataDiff oluşturmak için yineleyin. Fark programatik olarak incelenebilir veya denetim raporlaması için CSV/JSON olarak yazılabilir.

Tam‑Özellik Farkı

Aşağıdaki kod parçacığı temel fark algoritmasını gösterir. Her sürümden tüm özellikleri çekmek için yardımcı ExtractAllMetadata.Run (daha sonra) kullanılır.

// 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;

Ana noktalar:

  • MetadataDiff üç sözlük tutar: Added, Removed, Changed.
  • Algoritma O(n) zaman karmaşıklığına sahiptir, binlerce özellik içeren dosyalar için uygundur.
  • Giriş/Çıkış (I/O) yapılmaz; sonucu nasıl kalıcı hale getireceğiniz size kalmıştır.

Sahiplik Değişikliklerini Algılama

Sahiplik‑ile ilgili etiketler (Author, LastSavedBy, Manager, Company) genellikle yasal denetimler için en kritik olanlardır. Aşağıdaki yöntem bu etiketleri izole eder ve farkları raporlar.

// 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;

Ana noktalar:

  • Tags.Person.* ve Tags.Corporate.Company öngörüleri kullanılır.
  • Her giriş eski → yeni değerlerini gösteren bir sözlük döndürür.
  • Kısa bir sahiplik‑değişikliği raporu oluşturmak için idealdir.

Yardımcı: 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;

Revizyon Geçmişi Değişikliklerini Algılama

Revizyon‑ile ilgili metadata (RevisionNumber, TotalEditingTime, LastPrinted) belgenin kaç kez düzenlendiğini gösterir. Aşağıdaki kod bu özellikleri çıkarır ve farkları vurgular.

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;

Ana noktalar:

  • Hem zaman damgalarını hem de sayısal revizyon sayaçlarını yakalar.
  • Ayrı ayrı kaydedilmemiş gizli düzenlemeleri tespit etmek için kullanışlıdır.

Yardımcı: 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;

Farkı CSV’ye Aktarma

Uyum ekipleri genellikle bir elektronik tablo‑dostu rapor ister. Aşağıdaki yöntem MetadataDiff i dört sütunlu bir CSV dosyasına yazar.

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());

Ana noktalar:

  • CsvEscape, virgül veya satır sonu içeren alanları güvenli bir şekilde tırnak içine alır.
  • Oluşan dosya doğrudan Excel’de açılabilir veya bir SIEM’e yüklenebilir.

Yardımcı: CsvEscape

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

Farkı JSON’a Aktarma

Programatik boru hatları için JSON genellikle tercih edilir. Aşağıdaki yöntem üç üst‑seviye nesne içeren sabit bir şema üretir.

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());

Ana noktalar:

  • WriteMap, eklenen/kaldırılan bölümler için basit anahtar‑değer nesneleri yazar.
  • Escape, JSON uyumlu dizgileri garanti eder.

Yardımcı: 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;

Yöntemleri Karşılaştırma: Hangi Durumda Hangi Yöntemi Kullanmalı

Yöntem En İyi Kullanım Durumu Temel Avantajlar Sınırlamalar
Full‑Property Diff Genel adli denetimler Eklenen/kaldırılan/değişen tüm özellikleri tespit eder Karmaşık dosyalar için büyük fark çıktısı üretebilir
Ownership Detection Yasal köken kontrolleri Sadece kimlik‑taşıyan etiketlere odaklanır Diğer metadata değişikliklerini görmez
Revision History Detection Düzenleme aktivitesi analizi Zaman‑bazlı ve revizyon sayaçlarını vurgular Özelliklerin dosyada bulunması gerekir
CSV Export Elektronik tablo‑tabanlı raporlama Excel’de kolay açılır, insan‑okunur Düz yapı nedeniyle sınırlı
JSON Export Otomatik boru hatları, panolar Yapılandırılmış, makine‑okunur Biraz daha büyük payload

Tam‑özellik farkını, kapsamlı bir denetim gerektiğinde seçin; hızlı paydaş incelemeleri için CSV dışa aktarımını birleştirin. Otomatik uyum kontrolleri için JSON çıktısını doğrudan izleme sisteminize yönlendirin.

En İyi Uygulamalar ve İpuçları

  • MetadataFacade‘i hemen serbest bırakın: yerel kaynakları temizlemek için using bloğu kullanın.
  • Gerekli etiketlerle sınırlı çıkarım yapın: Tags.Person.* veya Tags.Time.* filtrelemesi, büyük PDF’lerde bellek kullanımını azaltır.
  • Dosya formatlarını doğrulayın: metadata.FileFormat == FileFormat.Unknown desteklenmeyen veya bozuk bir dosyayı gösterir.
  • Denetim raporlarınızı sürümleyin: dışa aktarılan dosya başlığına kütüphane sürümünü (GroupDocs.Metadata 24.10) ekleyerek izlenebilirliği sağlayın.
  • Güvenlik: Kişisel veri içerebilecek ham özellik değerlerini asla loglamayın; saklamadan önce PII’yı maskeleyin.
  • Performans: 50 MB üzerindeki belgeler için metadata çıkarımını akış (stream) olarak yapmayı (API’da henüz mevcut değil) düşünün veya dosyaları paralel toplu işlerde işleyin.

Sonuç

GroupDocs.Metadata, iki belge sürümü arasındaki gömülü tüm bilgileri karşılaştırmak için sağlam ve programatik bir yol sunar. Tam metadata çıkarımı, sahiplik veya revizyon etiketlerine odaklanma ve farkı CSV veya JSON olarak dışa aktarma sayesinde, yasal, uyum ve güvenlik gereksinimlerini karşılayan tekrarlanabilir adli iş akışları oluşturabilirsiniz.

Sonraki adımlar:

Ek Kaynaklar