💡 مثال كامل يعمل متوفر على GitHub:
office-metadata-pii-cleanup-nodejs

Introduction

نقطة تحميل تستقبل ملف DOCX من أحد الموظفين وتخزنه مع تذكرة العميل. النص سليم. الخصائص ليست كذلك: الملف يحتوي على اسم الشخص الذي أنشأه، والزميل الذي حفظه آخر مرة، ومدير القسم من القالب المؤسسي، وبسبب أنه جاء من SharePoint، اسم الموافق الذي صادقه.

منظف البيانات الوصفية هو سكريبت صغير يحذف تلك الخصائص قبل تخزين الملف ثم يتحقق من عمله. يبني هذا الدرس سكريبت في Node.js باستخدام GroupDocs.Metadata، في أربع خطوات: اختيار الخصائص حسب الوسم، اختيارها حسب الاسم، مسح كل شيء عندما تتوقف الانتقائية عن الفائدة، والتحقق مما تبقى. كل خطوة تتكون من بضع أسطر، والسكريبت النهائي أقل من مئة سطر.

Why Metadata Sanitization Matters

تتراكم البيانات دون أن يختارها أحد. Word يكتب Author و LastSavedBy من حساب نظام التشغيل في كل حفظ، يحتفظ بعداد المراجعات، يتتبع TotalEditingTime، ويسجل LastPrinted. خوادم المستندات تضيف مسارات سير العمل، معرفات الموافق، وعناوين URI لنوع المحتوى عند الفحص. لا يظهر أي من هذا عند قراءة أو طباعة المستند، لذا لا يلتقطه المراجعة اللغوية.

الهدف من القيام بذلك في Node.js بدلاً من يدويًا هو أن السكريبت يعيد أرقامًا: كل استدعاء حذف يُبلغ عن عدد الخصائص التي حذفها، ويمكن كتابة هذا العدد إلى سجل، أو التحقق منه في اختبار، أو إرفاقه بالسجل الذي ينتمي إليه المستند.

هناك سبب ثانٍ، أقل وضوحًا حتى يبدأ تشغيل مهمة دفعة. التنظيف اليدوي هو قرار يُتخذ مرة واحدة لكل ملف من قبل من يتعامل معه، لذا قد ينتج عن شخصين ينظفان نفس نوع المستند نتائج مختلفة. السكريبت يثبت القاعدة في مكان واحد: نفس الوسوم الأربعة، نفس قوائم السلاسل الفرعية، تُطبق بنفس الطريقة سواء كان الطابور يحتوي على ثلاثة ملفات أو ثلاثة آلاف.

Prerequisites

الحزمة هي Node.js عبر Java، لذا يحتاج الجهاز إلى بيئة تشغيل Java إلى جانب Node.

Installation

npm install @groupdocs/groupdocs.metadata

المشروع النموذجي يثبت الإصدار 26.7 ويضيف إدخال overrides يحدد nan إلى ^2.22.0، مما يحافظ على بناء الربط الأصلي على إصدارات Node الحالية. بدون ملف ترخيص تعمل المكتبة في وضع التقييم، وهو كافٍ لتتبع كل خطوة هنا.

Step 1 - Select properties by what they mean

تختلف أسماء الخصائص بين الصيغ والحزم، لذا القاعدة الأولى تطابق على الوسوم بدلاً من ذلك. ContainsTagSpecification يأخذ وسمًا ويطابق أي خاصية تحمله؛ .or() يدمج المواصفات في واحدة.

const T = groupdocs.Tags;
const spec = new groupdocs.ContainsTagSpecification(T.getPerson().getCreator())
  .or(new groupdocs.ContainsTagSpecification(T.getPerson().getEditor()))
  .or(new groupdocs.ContainsTagSpecification(T.getPerson().getManager()))
  .or(new groupdocs.ContainsTagSpecification(T.getCorporate().getCompany()));
const affected = metadata.removeProperties(spec);
metadata.save(outputPath);

Key points:

  • أربعة وسوم تغطي مجموعة الهوية: المنشئ، المحرر، المدير، وحقل الشركة المؤسسية.
  • العنوان (Title)، الموضوع (Subject)، والكلمات المفتاحية (Keywords) تبقى دون تعديل، لذا يظل فهرس السجلات الذي يعتمد عليها يعمل.
  • removeProperties يُعيد عدد العناصر المتأثرة بدلاً من قيمة منطقية.

احwrap الكود بالكامل داخل try/finally مع metadata.close() داخل الـ finally. الربط يبقي الملف مفتوحًا حتى ذلك الحين، وحلقة بدون إغلاق تنفد من المقابض.

Step 2 - Select properties by name

سلاسل التعليقات، عدادات المراجعات، وحقول الخادم لا تحمل وسمًا. بالنسبة لها، WithNameSpecification(needle, false) يطابق أي خاصية يحتوي اسمها على السلسلة needle، ومُنشئ من أربع أسطر يربط واحدة لكل سلسلة فرعية:

let spec = null;
for (const needle of needles) {
  const s = new groupdocs.WithNameSpecification(needle, false /* fullMatch */);
  spec = spec ? spec.or(s) : s;
}
return spec;

ثلاث تمريرات تعيد استخدام هذا المُنشئ بقوائم مختلفة. التعليقات تُعالج أولًا:

const affected = metadata.removeProperties(
  nameContainsSpec(['Comment', 'Reviewer', 'Reviewed']));
metadata.save(outputPath);

خط الزمن التحريري هو المجموعة التي يُنسى عادةً، وهو ما يصف كيف تم إنتاج المستند:

const affected = metadata.removeProperties(nameContainsSpec([
  'Revision', 'TrackedChange', 'LastPrinted', 'TotalEditingTime', 'EditTime',
]));
metadata.save(outputPath);

تمرير SharePoint هو نفس الاستدعاء مع Server، Workflow، Approver، ContentType، و Template. المطابقة على السلاسل الفرعية مقصودة: فهي تلتقط CommentsCount إلى جانب Comment دون الحاجة إلى قائمة أسماء دقيقة لكل صيغة.

Step 3 - Wipe everything when selectivity stops helping

للنسخة التي تُرسل خارج المؤسسة، استدعاء واحد يستبدل الأربع تمريرات:

const affected = metadata.sanitize();
metadata.save(outputPath);

sanitize() يمسح كل حزمة بيانات وصفية تكتشفها المكتبة، بما في ذلك أجزاء OOXML المخصصة، وعادةً ما يكون عددها أكبر من مجموع العدادات المستهدفة. كما أنه يزيل العنوان والموضوع، لذا يُستَخدم عند الحد الفاصل بدلاً من حلقة مراجعة.

Step 4 - Verify, because a silent miss looks like success

المسح يعيد استخدام نفس المواصفات عبر findProperties، التي تقرأ دون كتابة. النتيجة مجموعة Java، لذا تُستعرض عبر الفهرس:

const props = metadata.findProperties(tagSpec.or(nameSpec));
for (let i = 0; i < props.getCount(); i++) {
  const p = props.get_Item(i);
  const val = p.getValue && p.getValue();
  const value = val ? String(val.getRawValue ? val.getRawValue() : val) : '';
  if (!value || value === '0' || value === '0.0') continue;
  leaks.push(`${p.getName()}=${value}`);
}

مرشح القيم الفارغة والصفرية يكتسب مكانه. أضفته بعد أن فشلت تجربة بسبب عداد مراجعة تم مسحه إلى 0، وكان المسح يُبلغ بأمان عن الخاصية المتبقية.

Complete Working Example

المستودع يربط الست وظائف في index.js، الذي يطبق الترخيص، يشغّل كل تمريرة على resources/pii-sample.docx، يتحقق من وجود كل ملف ناتج، وينتهي بالتحقق من أن قائمة التسرب فارغة. فشل التحقق يخرج برمز غير صفري، لذا يعمل كله كفحص في CI بدلاً من عرض توضيحي تقرأه.

تفصيل يستحق النسخ إلى نسختك الخاصة: كل تمريرة تقرأ نفس ملف المصدر وتكتب مخرجًا منفصلًا، بدلاً من ربط ملف مُنظّف بآخر. هذا يبقي عدادات المتأثرين مستقلة، لذا سطر السجل لتمرير التعليقات يُظهر ما وجده قاعدة التعليقات وليس ما تبقى بعد تشغيل قاعدة الهوية.

When should I run a targeted pass instead of sanitize()?

كلما كان المستند لا يزال قيد الاستخدام. الملفات التي تتنقل بين المراجعين تعتمد على العنوان، الموضوع، والكلمات المفتاحية للبحث والتصنيف، وsanitize() يزيل الثلاثة مع البيانات الشخصية. شغّل تمريرات الهوية والتعليقات أثناء التعاون، احتفظ بالحقول الوصفية، واستخدم المسح الكامل للنسخة التي تُرسل فعليًا خارج المؤسسة.

Real-World Applications

Upload handler

مسار Express ينظف المرفق قبل كتابته إلى التخزين، يسجل عدد المتأثرين على التذكرة، ويرفض التحميل عندما تكون قائمة التسرب غير فارغة.

Nightly export job

عامل يمشي مجلد التصدير، يطبق تمريرات الهوية والخادم، ويفشل المهمة بدلاً من تسجيل تحذير عندما لا يزال المستند يُظهر بيانات شخصية متبقية.

Pre-publication gate

خطوة بناء تنظف مرفقات الوثائق قبل الإصدار، باستخدام sanitize() لأن لا شيء في تلك الملفات يحتاج إلى الحفاظ على بياناته الوصفية.

Best Practices and Tips

  • دائمًا اكتب إلى مسار جديد حتى يبقى الأصلي متاحًا لحل النزاعات.
  • أغلق كائن البيانات الوصفية في كتلة finally، خاصةً داخل الحلقات.
  • دمج المواصفات باستخدام .or() في وظائف الدفعة؛ فتح واحد وحفظ واحد أفضل من أربعة.
  • سجّل عدد المتأثرين لكل تمريرة، بما في ذلك الصفر، حتى يكون الصيغة غير المعروفة مرئية.

Troubleshooting Common Issues

The affected count is zero on a document you know is dirty
تحقق من أن صيغة الإدخال مُعترف بها قبل الاستنتاج بأن الملف نظيف؛ ملف غير قابل للقراءة وملف نظيف ينتجان نفس الصفر.

The leak check reports properties you just removed
وجّه الفحص إلى مسار الإخراج المحفوظ، وليس إلى مسار الإدخال. المسح يقرأ أي ملف يُعطى له.

Comment balloons are still visible in Word
نص التعليق يعيش في جسم المستند، وليس في حزمة البيانات الوصفية. GroupDocs.Metadata يمسح الخصائص المتعلقة بالتعليقات؛ إزالة الفقاعات نفسها تحتاج مكتبة تحرير محتوى مثل Aspose.Words.

Conclusion

أربع خطوات، ست وظائف، سكريبت واحد يُبلغ عما فعل. مواصفات الوسوم تدير مجموعة الهوية عبر الصيغ، مواصفات الأسماء تغطي العائلات التي لا تصنفها الوسوم، sanitize() يدير الحد الفاصل، وفحص التسرب يحول كل ذلك إلى فحص. استنسخ المستودع، شغّله على مستند مر بعملية مراجعة حقيقية، وانظر إلى العدادات قبل أن تقرر أي تمريرات يحتاجها خط أنابيبك.

Additional Resources