💡 مثال كامل يعمل متوفر على GitHub:
remove-pii-from-office-metadata-java
تحدي الامتثال: لماذا فشل مراجعة البيانات الوصفية اليدوية عند التوسع
فريق السجلات يرسل 200 مستند إلى مدقق خارجي. شخص ما قرأ كل صفحة. لا أحد قرأ الخصائص، والخصائص هي المكان الذي توجد فيه البيانات الشخصية: المحلل في Author، المحلل الثاني في LastSavedBy، رئيس القسم في Manager، الشركة التابعة في Company، طابع زمني LastPrinted من الليلة التي سبقت الموعد النهائي، وعلى أي شيء مر عبر SharePoint، معرف المراجع ومسار سير العمل.
تنقية البيانات الوصفية هي سير عمل GroupDocs.Metadata للـ Java يزيل تلك الخصائص التي تحمل هوية من ملفات Word وExcel وPowerPoint ثم يقرأ النتيجة لتقرير ما تبقى. يشرح هذا المقال سير العمل كما سيبنيه فريق الامتثال: ما مجموعات الخصائص الموجودة، أي قاعدة إزالة تناسب كل مجموعة، متى تستبدل عملية المسح المستهدف بمسح كامل، ولماذا يجب أن تكون عملية التحقق ضمن نفس المهمة بدلاً من قائمة مراجعة.
مشكلة النطاق ليست أن الإزالة صعبة. المشكلة أن المراجعة اليدوية لا تنتج سجلاً. المدقق الذي يسأل “ما الحقول التي أزيلت من هذا الملف ومتى” يحتاج إلى رقم، وحوار الخصائص لا ينتج أي شيء.
لماذا لا تنجح أدوات التنظيف العامة هنا
حوار خصائص Windows يحرّر ملفًا واحدًا في كل مرة ويصل إلى مجموعة فرعية من الحقول. فاحص المستندات في Office يعمل تفاعليًا، مما يجعله غير مناسب لوظيفة ليلية. كلاهما يترك أجزاء OOXML المخصصة دون لمس، ولا يكتب أي شيء يمكن لخط أنابيب قراءته مرة أخرى.
الفرق التي تنزل مستوىً واحدًا إلى الأسفل، وتحرّر docProps/core.xml وdocProps/custom.xml مباشرة، تتحمل عبء صيانة: تعبير XPath لكل حقل، لكل تنسيق، يُعاد مراجعته كلما غير تطبيق منتج اسمًا. هذا العمل أيضًا يخطئ في سؤال التصنيف. أسماء الخصائص تختلف بين الحزم، لذا قائمة الأسماء تصبح قديمة بصمت، وتظهر قاعدة لم تعد تطابق أي شيء كأنها ملف نظيف بالفعل.
الحل: GroupDocs.Metadata في سير عمل السجلات
GroupDocs.Metadata للـ Java يمرّر كل شيء عبر محرك بحث خصائص واحد. كائن Specification يحدد أي الخصائص تتطابق، removeProperties يحذف كل تطابق ويعيد عدد المتأثرين، وfindProperties ينفّذ نفس الشرط للقراءة فقط. تحمل الخصائص وسومًا، لذا Tags.getPerson().getCreator() يحدد الحقول على نمط المؤلف بغض النظر عن التنسيق أو الحزمة التي جاءت منها.
الـ Java لا يدعم lambda overload على removeProperties، وهذا يصبح ميزة هنا: كل قاعدة هي كائن، والكائنات قابلة لإعادة الاستخدام. يمكن تمرير نفس كائن المواصفات الذي يمسح مجموعة إلى فحص التحقق، لذا لا يمكن أن ينحرف الفحص عن عملية التنظيف التي من المفترض أن يختبرها.
تنفيذ خطوة أنابيب التنقية خطوة بخطوة
الخطوة 1 - مسح مجموعة الهوية حسب الوسم
أربع مواصفات وسمية موحدة بـ .or(...) تغطي المؤلف، المحرر، المدير، والشركة. لا شيء آخر يتحرك، لذا يظل العنوان، الموضوع، والكلمات المفتاحية متاحين لفهرس السجلات.
try (Metadata metadata = new Metadata(inputPath)) {
if (metadata.getFileFormat() == FileFormat.Unknown) return 0;
int affected = metadata.removeProperties(
new ContainsTagSpecification(Tags.getPerson().getCreator())
.or(new ContainsTagSpecification(Tags.getPerson().getEditor()))
.or(new ContainsTagSpecification(Tags.getPerson().getManager()))
.or(new ContainsTagSpecification(Tags.getCorporate().getCompany())));
metadata.save(outputPath);
return affected;
}
حارس FileFormat.Unknown مهم أكثر مما يبدو. بدون هذا الحارس، ملف غير قابل للقراءة يُعيد صفر حذف، وهو ما لا يستطيع المستدعي تمييزه عن مستند كان نظيفًا عند الوصول.
الخطوة 2 - مسح عائلات الحقول حسب الاسم
سلاسل التعليقات، عدادات المراجعات، وحقول الخادم لا تحمل وسمًا، لذا يتم مطابقتها بالاسم. فئة Specification صغيرة تأخذ قائمة متغيرة من السلاسل الجزئية، مما يسمح لفئة واحدة أن تخدم ثلاث تمريرات:
public class NameContainsSpec extends Specification {
private final String[] needles;
public NameContainsSpec(String... needles) {
this.needles = needles;
}
@Override
public boolean isSatisfiedBy(MetadataProperty candidate) {
String name = candidate.getName();
if (name == null) return false;
for (String n : needles) {
if (name.contains(n)) return true;
}
return false;
}
}
خط الزمن التحريري هو ما يهم فرق الامتثال أكثر، لأن عدد المراجعات وتاريخ الطباعة الأخير يصفان كيف تم إنتاج المستند:
try (Metadata metadata = new Metadata(inputPath)) {
if (metadata.getFileFormat() == FileFormat.Unknown) return 0;
int affected = metadata.removeProperties(new NameContainsSpec(
"Revision", "TrackedChange", "LastPrinted", "TotalEditingTime", "EditTime"));
metadata.save(outputPath);
return affected;
}
تمرير التعليقات وتمرير SharePoint هما نفس الاستدعاء مع قوائم سلاسل جزئية مختلفة: Comment, Reviewer, Reviewed لتتبع المراجعات، وServer, Workflow, Approver, ContentType, Template لحقول خادم المستند.
الخطوة 3 - مسح كامل عند الحد
عندما يغادر الملف المؤسسة، لا تعود الانتقائية ذات جدوى. استدعاء واحد يمسح كل حزمة بيانات وصفية تكتشفها المكتبة، بما في ذلك أجزاء OOXML المخصصة التي لا تبحث عنها أي قاعدة مستهدفة:
try (Metadata metadata = new Metadata(inputPath)) {
if (metadata.getFileFormat() == FileFormat.Unknown) return 0;
int affected = metadata.sanitize();
metadata.save(outputPath);
return affected;
}
الخطوة 4 - التحقق وتصنيف ما تبقى
الفحص يمرّ على اتحاد كل قاعدة عبر findProperties ويصنّف النتائج إلى قائمتين. تُهمل القيم الفارغة والعدادات الصفرية، وتُعامل الإدخالات التي يبدأ اسمها بـ Comment أو Revision أو Inspection كغلافات لمحتوى النص بدلاً من بيانات وصفية:
String value = "";
if (p.getValue() != null && p.getValue().getRawValue() != null) {
value = String.valueOf(p.getValue().getRawValue());
}
if (value.isEmpty() || value.equals("0") || value.equals("0.0")) continue;
String entry = p.getName() + "=" + value;
String name = p.getName() == null ? "" : p.getName();
if (name.startsWith("Comment") || name.startsWith("Revision")
|| name.startsWith("Inspection")) {
report.contentLevelLeaks.add(entry);
} else {
report.metadataLeaks.add(entry);
}
هذا الفصل هو ما يحافظ على إشارة النجاح/الفشل صادقة. يجب أن تكون تسريبات البيانات الوصفية فارغة. تسريبات مستوى المحتوى تبقى معلوماتية، لأن تعليقات Word ومؤلفو التغييرات المتعقبة تعيش داخل word/document.xml، ومكتبة البيانات الوصفية تُبلغ عنها دون تعديلها؛ إزالة تلك تحتاج مكتبة تحرير محتوى مثل Aspose.Words.
متى تكون العملية المستهدفة أفضل من التنقية الكاملة؟
كلما كان المستند لا يزال يُستخدم في العمل. ملف يتنقل بين المراجعين يحتاج إلى العنوان، الموضوع، والكلمات المفتاحية للبحث وتصنيف السجلات، وsanitize() يزيل الثلاثة. نفّذ تمريرات الهوية والتعليق أثناء التعاون، احتفظ بالحقول الوصفية، واحفظ المسح الكامل للحظة عبور الملف إلى طرف خارجي.
سير عمل حقيقي: مهمة تصدير لمدقق خارجي
تخيل المهمة الليلية. تقرأ قائمة معرفات المستندات، تنسخ كل ملف إلى مسار مؤقت، تطبق تمريرة الهوية وتمرير الخادم، تستدعي sanitize() على أي شيء مُعلم بأنه سيغادر المؤسسة، ثم تجري فحص التسرب على النسخة المحفوظة. كل خطوة تضيف عدد المتأثرين إلى سطر سجل واحد لكل ملف، وقائمة تسريبات بيانات وصفية غير فارغة تُفشل المهمة بدلاً من تسجيل تحذير.
قضيت مرة بعد ظهر على نسخة من هذه المهمة أبلغت عن صفر حذف عبر 40 ملفًا وبدا أنها دفعة نظيفة. كان المجلد الإدخالي يحتوي على ملفات .doc قديمة، وكان حارس التنسيق يُعيد مبكرًا على كل منها، ولم يميز السجل بين “لا شيء للإزالة” و"لم يُقرأ شيء". إضافة تنسيق الملف إلى جانب العدد حَل المشكلة.
تأثير الأعمال: ما الذي يتغيّر
| الجانب | مراجعة يدوية | خط أنابيب GroupDocs.Metadata |
|---|---|---|
| التغطية | الحقول الظاهرة في حوار الخصائص | كل حزمة تكتشفها المكتبة، بما في ذلك أجزاء OOXML المخصصة |
| سجل العمل | ملاحظات، إذا كتبها أحد | عدد المتأثرين لكل ملف ولكل مجموعة خصائص |
| القابلية للتكرار | تعتمد على من قام بها | مواصفة واحدة لكل قاعدة، تُطبق بنفس الطريقة على كل ملف |
| التحقق | إعادة فتح الملف والنظر | فحص findProperties مع تصنيف ثنائي |
| النطاق | ملف واحد في كل مرة | نفس العمليات الست تُنفّذ في حلقة على مجلد تصدير |
سيناريوهات أخرى حيث يناسب GroupDocs.Metadata
محرك الخصائص نفسه يقرأ كما يزيل. مقارنة البيانات الوصفية بين نسختين من مستند تُظهر ما غيرته جولة تحرير، وهو مفيد للنزاعات على الملكية وللكشف عن ملف أعيد تأليفه خارج العملية. العمل مع وسوم البيانات الوصفية بدلاً من الأسماء هو ما يجعل الحالتين قابلتين للنقل عبر DOCX وXLSX وPPTX وPDF وصيغ الصور.
البدء مع GroupDocs.Metadata للـ Java
أضف مستودع GroupDocs للـ Java إلى pom.xml واعتمد على com.groupdocs:groupdocs-metadata. المكتبة تعمل في وضع التقييم بدون ترخيص، وهو كافٍ لتشغيل جميع العمليات الست على عينة DOCX ورؤية الأعداد. ابدأ بتمرير الهوية، أضف تمريرات عائلات الحقول حسب ما تتطلبه مصادر مستنداتك، وضع فحص التسرب قبل أن ينتقل أي شيء إلى الإنتاج.