💡 مثال عملي كامل متاح على GitHub:
metadata-diff-between-docs-using-groupdocs-metadata-dotnet

المقدمة

عند انتقال عقد بين جهتين أثناء الاندماج، يجب على الفريق القانوني إثبات أن معلومات ملكية المستند – المؤلف، آخر محرر، اسم الشركة – ظلت سليمة. الفحص اليدوي لخصائص كل ملف مرهق وعرضة للأخطاء؛ أي تغيير مفقود قد يبطل تدقيق الامتثال. GroupDocs.Metadata هي مكتبة .NET تستخرج وتقارن كل خاصية مدمجة في المستند، مما يتيح تحليلًا جنائيًا آليًا للتغييرات بين الإصدارات. يوضح هذا البرنامج التعليمي كيفية اكتشاف تبديل الملكية، وتعديلات سجل المراجعات، وأي تعديل آخر للخصائص بين نسختين من نفس الملف، ثم تصدير النتائج إلى CSV أو JSON لتقارير لاحقة.

واجهت هذه المشكلة أثناء تدقيق مجموعة من 1,200 عقد لاندماج عام 2024؛ كشف تدقيق البيانات الوصفية عن 87 تغيير ملكية غير متوقع كان سيبقى غير مرئي بدون الأتمتة.

في الأقسام التالية ستتعلم كيفية:

  • استخراج جميع البيانات الوصفية من مستندين.
  • تحديد الخصائص المضافة، المحذوفة، والمعدلة.
  • التركيز على العلامات المتعلقة بالملكية وسجل المراجعات.
  • تسلسل الفرق إلى CSV أو JSON لسلاسل التدقيق.

لماذا مقارنة البيانات الوصفية مهمة

المقارنة الدقيقة للبيانات الوصفية أساسية لـ:

  • الاكتشاف القانوني الإلكتروني: إثبات أصل المستند واكتشاف التلاعب.
  • الامتثال التنظيمي: تتبع من أنشأ أو عدل ملفًا لتدقيقات GDPR أو SOX.
  • التحقيقات الجنائية: اكتشاف مراجعات مخفية أو تعديلات غير مصرح بها.

وفقًا لتوثيق GroupDocs.Metadata (2024)، أكثر من 30 % من فشل الامتثال ينجم عن تغييرات خصائص غير موثقة.

المتطلبات المسبقة

  • .NET 6.0 أو أحدث
  • GroupDocs.Metadata لـ .NET 24.10+ ([رخصة مؤقتة][TEMP_LICENSE_URL])
  • نسختان من المستند (مثال: contract_v1.docx و contract_v2.docx)

التثبيت عبر NuGet:

dotnet add package GroupDocs.Metadata

كيف أقارن البيانات الوصفية بين نسختين من المستند؟

الإجابة: حمّل كل ملف باستخدام MetadataFacade، استخرج جميع الخصائص إلى قواميس، ثم كرّر لبناء MetadataDiff يصنّف الخصائص المضافة، المحذوفة، والمعدلة. يمكن فحص الفرق برمجيًا أو كتابته إلى CSV/JSON لتقارير التدقيق.

فرق الخصائص الكاملة

المقتطف التالي يوضح خوارزمية الفرق الأساسية. يستخدم المساعد 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;

نقاط رئيسية:

  • MetadataDiff يحتوي على ثلاثة قواميس: Added، Removed، Changed.
  • الخوارزمية تعمل في زمن O(n)، مناسبة للملفات التي تحتوي على آلاف الخصائص.
  • لا يتم إجراء أي I/O؛ يقرر المستدعي كيفية حفظ النتيجة.

اكتشاف تغييرات الملكية

العلامات المتعلقة بالملكية (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;

نقاط رئيسية:

  • يستخدم عوامل Tags.Person.* و Tags.Corporate.Company.
  • يُعيد قاموسًا حيث يُظهر كل إدخال القيمة القديمة → القيمة الجديدة.
  • مثالي لإنشاء تقرير مختصر لتغييرات الملكية.

المساعد: 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;

اكتشاف تغييرات سجل المراجعات

البيانات الوصفية المتعلقة بالمراجعات (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;

نقاط رئيسية:

  • يلتقط كلًا من الطوابع الزمنية والعدادات الرقمية للمراجعات.
  • مفيد لاكتشاف تعديلات مخفية لم تُحفظ كإصدارات منفصلة.

المساعد: 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