المقدمة
عندما تحتاج الفرق القانونية أو المحللون الجنائيون إلى إثبات أن المستند لم يتم العبث به، فإن النظر إلى المحتوى الظاهر لا يكفي. يمكن للخصائص المخفية — مثل المؤلف، تاريخ الإنشاء، أو رقم المراجعة — أن تكشف من قام بلمس الملف ومتى. اكتشاف هذه التغييرات الدقيقة عبر إصدارات المستند هو نقطة ألم شائعة تتطلب غالبًا فحصًا يدويًا لكل خاصية، وهو أمر مستهلك للوقت وعرضة للأخطاء.
توفر GroupDocs.Metadata للـ Java طريقة برمجية لاستخراج كل حقل من بيانات التعريف وحساب فرق منظم بين نسختين. في هذا البرنامج التعليمي سنقارن بين ثلاث طرق عملية: فرق بيانات التعريف الكامل، الكشف عن تغيّر الملكية، وتحليل تاريخ المراجعات. يتم توضيح كل طريقة بكود مختصر يمكن نسخه ولصقه، وسنظهر أيضًا كيفية تصدير النتائج إلى CSV أو JSON لتقارير التدقيق.
صادفت هذا عندما كنت أراجع عقدًا تم تحريره من قبل عدة أطراف على مدار أشهر؛ كان النص الظاهر متطابقًا، لكن حقول الملكية تغيرت بصمت.
كيف يمكنني معرفة أي حقول بيانات التعريف تغيرت بين نسختين من المستند؟
يقوم GroupDocs.Metadata بتحميل كل ملف، استخراج جميع الخصائص القابلة للوصول إلى خريطة، ثم يت iterates على المفاتيح لتصنيف الإضافات والإزالة والتعديلات. تتعامل المكتبة مع العلامات المدمجة والمخصصة، لذا تحصل على صورة كاملة دون الحاجة إلى كتابة محللات خاصة بالصيغة. النتيجة هي كائن MetadataDiff يمكنك استعلامه أو تسلسله لتقارير الامتثال.
المتطلبات المسبقة
- Java 8 أو أحدث
- GroupDocs.Metadata للـ Java 24.7 ([رخصة مؤقتة][TEMP_LICENSE_URL])
- ملفا مستند تريد مقارنتهما (مثال:
contract_v1.pdfوcontract_v2.pdf)
التثبيت
أضف الاعتماد عبر Maven:
<dependency>
<groupId>com.groupdocs</groupId>
<artifactId>groupdocs-metadata</artifactId>
<version>24.7</version>
</dependency>
الطريقة 1 – فرق بيانات التعريف الكامل
تستخرج هذه الطريقة كل خاصية من بيانات التعريف من كلا الإصدارين وتبلغ عن الإدخالات المضافة، المحذوفة، والمغيّرة.
// CompareMetadataSets.run – returns a MetadataDiff object
Map<String, String> v1 = ExtractAllMetadata.run(pathV1);
Map<String, String> v2 = ExtractAllMetadata.run(pathV2);
MetadataDiff diff = new MetadataDiff();
// Detect added and changed properties
for (Map.Entry<String, String> e : v2.entrySet()) {
String key = e.getKey();
String val = e.getValue();
if (!v1.containsKey(key)) {
diff.added.put(key, val); // New property in v2
} else if (!v1.get(key).equals(val)) {
diff.changed.put(key, new String[]{v1.get(key), val}); // Value changed
}
}
// Detect removed properties
for (Map.Entry<String, String> e : v1.entrySet()) {
if (!v2.containsKey(e.getKey())) {
diff.removed.put(e.getKey(), e.getValue());
}
}
return diff;
نقاط رئيسية:
- شاملة: تلتقط جميع العلامات، بما في ذلك المخصصة.
- منطق خريطة بسيط: لا تحتاج إلى مكتبة فرق خارجية.
- كائن النتيجة: خرائط
addedوremovedوchangedجاهزة للمعالجة الإضافية.
💡 نصيحة: استخدم هذه الطريقة عندما تحتاج إلى سجل تدقيق كامل للامتثال التنظيمي.
الطريقة 2 – الكشف عن تغيّر الملكية
غالبًا ما تعتمد النزاعات القانونية على من أنشأ أو حرّر المستند. تركز هذه الطريقة على العلامات المتعلقة بالأشخاص مثل Creator، Editor، Manager، و Company.
// DetectOwnershipChanges.run – returns a map of changed ownership fields
Map<String, String> v1 = readOwnership(pathV1);
Map<String, String> v2 = readOwnership(pathV2);
Set<String> allKeys = new HashSet<>(v1.keySet());
allKeys.addAll(v2.keySet());
Map<String, String[]> changes = new LinkedHashMap<>();
for (String key : allKeys) {
String oldVal = v1.getOrDefault(key, "<missing>");
String newVal = v2.getOrDefault(key, "<missing>");
if (!oldVal.equals(newVal)) {
changes.put(key, new String[]{oldVal, newVal});
}
}
return changes;
readOwnership يجلب فقط العلامات ذات الصلة:
Map<String, String> result = new LinkedHashMap<>();
try (Metadata metadata = new Metadata(path)) {
if (metadata.getFileFormat() == FileFormat.Unknown) return result;
for (MetadataProperty p : metadata.findProperties(
new ContainsTagSpecification(Tags.getPerson().getCreator())
.or(new ContainsTagSpecification(Tags.getPerson().getEditor()))
.or(new ContainsTagSpecification(Tags.getPerson().getManager()))
.or(new ContainsTagSpecification(Tags.getCorporate().getCompany())))) {
String value = "";
if (p.getValue() != null && p.getValue().getRawValue() != null) {
value = String.valueOf(p.getValue().getRawValue());
}
result.put(p.getName(), value);
}
}
return result;
نقاط رئيسية:
- مستهدفة: يتم فحص الخصائص التي تحمل هوية الأشخاص فقط.
- إخراج واضح: تُعيد خريطة حيث يُظهر كل إدخال قيم
[old, new]. - جاهزة للامتثال: مثالية للاكتشاف الإلكتروني أو نزاعات ملكية العقود.
💡 نصيحة: اجمع هذه الطريقة مع الفرق الكامل إذا كنت بحاجة إلى كل من العرض الواسع والعمق.
الطريقة 3 – الكشف عن تغيّر تاريخ المراجعات
أرقام المراجعات، طوابع الوقت للتعديل، وتواريخ الطباعة غير مرئية للمستخدمين النهائيين لكنها حاسمة للجداول الزمنية الجنائية. تعزل هذه الطريقة العلامات المتعلقة بالوقت.
Map<String, String> v1 = readRevision(pathV1);
Map<String, String> v2 = readRevision(pathV2);
Set<String> allKeys = new HashSet<>(v1.keySet());
allKeys.addAll(v2.keySet());
Map<String, String[]> changes = new LinkedHashMap<>();
for (String key : allKeys) {
String oldVal = v1.getOrDefault(key, "<missing>");
String newVal = v2.getOrDefault(key, "<missing>");
if (!oldVal.equals(newVal)) {
changes.put(key, new String[]{oldVal, newVal});
}
}
return changes;
readRevision يستخرج طوابع الوقت Modified، Created، و Printed:
Map<String, String> result = new LinkedHashMap<>();
try (Metadata metadata = new Metadata(path)) {
if (metadata.getFileFormat() == FileFormat.Unknown) return result;
for (MetadataProperty p : metadata.findProperties(
new ContainsTagSpecification(Tags.getTime().getModified())
.or(new ContainsTagSpecification(Tags.getTime().getCreated()))
.or(new ContainsTagSpecification(Tags.getTime().getPrinted())))) {
String value = "";
if (p.getValue() != null && p.getValue().getRawValue() != null) {
value = String.valueOf(p.getValue().getRawValue());
}
result.put(p.getName(), value);
}
}
return result;
نقاط رئيسية:
- إعادة بناء الخط الزمني: تُظهر عدد مرات تعديل أو طباعة الملف.
- فروقات رقمية: مفيدة لاكتشاف مراجعات سريعة مريبة.
- خفيفة الوزن: يتم الاستعلام عن ثلاث علامات فقط، مما يبقي التنفيذ سريعًا.
💡 نصيحة: استخدم هذه الطريقة عندما تحتاج إلى إثبات أن المستند لم يتغير بعد موعد نهائي محدد.