المقدمة

زميلك يرسل نسختين من عقد ويطلب منك مقارنة الفروقات بينهما. تضع كلا الملفين في خدمة المقارنة، تعود النتيجة، ويبدو كل شيء طبيعياً. ما لم تلاحظه هو أن أحد المستندين يحتوي على صورة مرتبطة تشير إلى عنوان URL، وقد تواصل خادمك مع ذلك المضيف لحظة فتح الملف. لا شيء في الناتج يخبرك بحدوث ذلك.

هذا ليس عيبًا – إنه ما يعنيه تحميل المستند بأمانة. يمكن لملف OOXML أن يشير إلى صورة موجودة على خادم ويب بدلاً من داخل الحزمة، وكل من Word وأي مكتبة تقوم بتحميل المستند بشكل صحيح تحل هذا المرجع. تُظهر GroupDocs.Comparison for .NET خاصيتين في LoadOptions تسمحان لك بتحديد ما إذا كان سيتم ذلك أم لا: SkipExternalResources و WhitelistedResources.

معًا، توفران ثلاث تكوينات، وتقارن هذه المقالة بين الثلاثة – الإعداد الافتراضي المتساهل، حظر كل شيء، وحظر كل شيء ما عدا المراجع المسماة. في النهاية ستعرف أي إعداد تختار لمصدر المستند المعين، وما الخطأين الذين يجعلان هذه الإعدادات تبدو كما لو أنها لا تعمل.

💡 مثال كامل يعمل: block-external-resources-on-document-load-dotnet – مشروع وحدة تحكم قابل للتنفيذ يُقدِّم الصور المرجعية بنفسه ويسجل كل طلب، بحيث يمكنك مشاهدة تأثير كل إعداد.

أين تُخفى المراجع الخارجية

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

الأول هو علاقة في word/_rels/document.xml.rels تحمل TargetMode="External" وعنوان URL مطلق. تظهر الصورة في النص كرسمة تشير إلى العلاقة عبر المعرف (ID)، لذا لا يظهر عنوان URL نفسه بالقرب من المحتوى المتأثر.

الثاني هو رمز حقل INCLUDEPICTURE في جسم المستند، يحمل عنوان URL داخل تعليمات الحقل. يقوم Word بحله عندما تُعرض الصفحة؛ وتقوم مكتبة المقارنة بحله عندما يُحمَّل المستند.

كلا الآليتين تحترمان خيارَي التحميل المذكورين أدناه، وهذا مهم لأن المستند قد يستخدم إحداهما أو كلاهما. المرجع الذي تلاحظه في ملف العلاقات ليس دليلًا على عدم وجود مرجع ثانٍ في رمز الحقل.

النهج 1: الإعداد الافتراضي – حل المراجع

SkipExternalResources تكون قيمتها الافتراضية false، لذا عندما يُحمَّل مستند دون تكوين يتم حل مراجعّه البعيدة:

LoadOptions loadOptions = new LoadOptions
{
    SkipExternalResources = false
};

using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
    comparer.Add(targetPath, loadOptions);
    comparer.Compare(outputPath);
}

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

التكلفة هي أن كل مرجع يتم الاتصال به، بغض النظر عن من وضعه هناك. هناك أيضًا تكلفة زمنية لا علاقة لها بالثقة: عنوان URL لا يُحلّ بعد الآن يجعل عملية التحميل تنتظر محاولة الاتصال بالكامل، في كل مقارنة.

النهج 2: حظر كل مورد خارجي

خاصية واحدة تُوقف حل المراجع البعيدة لهذا المستند:

LoadOptions loadOptions = new LoadOptions
{
    SkipExternalResources = true
};

using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
    comparer.Add(targetPath, loadOptions);
    comparer.Compare(outputPath);
}

لا يُرسل أي طلب. الصور المرجعية غائبة عن النتيجة، - وهذا هو الجزء الذي يجب توضيحه - لا يتغير شيء آخر. الإعداد يتحكم فيما يتم تحميله، وليس في كيفية اكتشاف الفروقات، لذا تُكتشف التغييرات النصية والهيكلية بين المستندين كما كانت من قبل. الشيء الوحيد الذي تفقده هو القدرة على اكتشاف تغيير داخل صورة مرجعية، لم تُحمَّل أصلاً.

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

النهج 3: حظر كل شيء ما عدا المراجع المسماة

التكوين الثالث هو الذي يكافئ القراءة المتأنية. WhitelistedResources تستقبل List<string> وتُستشار فقط عندما تكون SkipExternalResources مساوية لـ true:

LoadOptions loadOptions = new LoadOptions
{
    SkipExternalResources = true,
    WhitelistedResources = new List<string> { "includepicture-field.png" }
};

using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
    comparer.Add(targetPath, loadOptions);
    comparer.Compare(outputPath);
}

الإدخالات هي جزء من عنوان URL، وليس أسماء ملفات. يُطابق كل جزء مع عنوان URL للمرجع، وأي تطابق في أي موضع يسمح بذلك المورد. هذا ما يجعل القائمة البيضاء قابلة للنقل: "includepicture-field.png" يسمح بالصورة بغض النظر عن المخطط أو المضيف أو المسار الذي يسبقها، لذا تعمل القائمة نفسها في بيئة التطوير والإنتاج دون تعديل.

العكس صحيح أيضًا. جزء قصير أو عام – logo.png، أو أسوأ، .png – يمكن أن يطابق مراجع لم تكن تقصد السماح بها. اختر جزءًا محددًا بما يكفي لتحديد المورد الوحيد الذي تقصده.

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

أي تكوين يجب أن تستخدمه؟

طابق الإعداد مع مصدر المستند. المستندات التي تُنتجها تطبيقك أو القوالب الخاصة بك يمكنها الاحتفاظ بالإعداد الافتراضي، لأن كل عنوان URL يشير إلى بنية تحتية تديرها بالفعل. أي شيء يأتي من الخارج – تحميلات المستخدم، مرفقات البريد الإلكتروني، ملفات الطرف الثالث – يستدعي SkipExternalResources = true. أضف جزءًا ضيقًا إلى WhitelistedResources فقط عندما يكون هناك مرجع موثوق واحد يحتاج فعليًا إلى الحل.

مقارنة الثلاثة

القلق الافتراضي حظر الكل حظر + القائمة البيضاء
الخصائص التي يجب ضبطها 0 1 2
الطلبات الصادرة جميع المراجع لا شيء المراجع المسموح بها فقط
التحكم في كل مرجع لا لا نعم
تكلفة عنوان URL غير صالح على وقت التحميل نعم لا المسموح به فقط
الأنسب لـ المستندات التي أنت أنشأتها المستندات من أي مصدر آخر القوالب الموثوقة بين محتوى غير موثوق

القرار يتبع أصل المستند وليس الأداء. المستندات التي تُنشئها أنظمتك يمكنها الاحتفاظ بالإعداد الافتراضي. المستندات الواردة من الخارج تستدعي الحظر. استخدم القائمة البيضاء فقط عندما يحتاج مرجع محدد حقًا إلى الحل – مثل قالب شركة يجلب صورة الرأس من عنوان URL داخلي، ضمن تقارير يلصق مؤلفوها صورًا من أي مكان.

الخطأان

كلاهما ينتج نفس العَرَض: تضبط الخيار، ويبدو أنه لا يفعل شيئًا.

قائمة بيضاء بدون تشغيل المفتاح. WhitelistedResources تُستشار فقط عندما تكون SkipExternalResources مساوية لـ true. إذا ضبطتها بمفردها، فإنها لا تفعل شيئًا على الإطلاق – لا يوجد حظر لتستثني منه. إذا بدت القائمة البيضاء متجاهلة، تحقق من هذا أولًا.

الخيارات على المصدر فقط. هذا هو الخطأ الأكثر دقة. خيارات التحميل تصف كيفية تحميل مستند واحد. مُنشئ Comparer يأخذ الخيارات للمصدر؛ كل استدعاء Add() يأخذ الخيارات للهدف:

using (Comparer comparer = new Comparer(sourcePath, loadOptions))
{
    comparer.Add(targetPath, loadOptions);
    comparer.Compare(outputPath);
}

إذا مررت الخيارات إلى المُنشئ ونستُها في استدعاء Add()، فإن المصدر يُحمى بينما لا يزال كل هدف يجلب مراجعَه. تنجح المقارنة، وتبدو النتيجة معقولة، ولا يزال نصف مستنداتك يتواصل مع الشبكة. عندما يحتاج المصدر والهدف إلى معالجة مختلفة، مرّر كائنات LoadOptions منفصلة – وهذا هو السبب بالضبط في أن الـ API يأخذها لكل مستند.

التحقق من أن الإعداد فعّال فعلاً

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

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

الخلاصة

ثلاثة تكوينات، قاعدة قرار واحدة: دع المستندات التي أنت أنشأتها تحتفظ بالإعداد الافتراضي، اضبط SkipExternalResources = true لكل شيء آخر، وضع قائمة بيضاء لجزء URL ضيق فقط حيث يحتاج مرجع موثوق محدد إلى الحل.

ثم تحقق من الأمرين الذين يلغي العمل بصمت – قائمة بيضاء بدون SkipExternalResources = true، والخيارات التي تُمرَّر إلى مُنشئ Comparer دون تمريرها إلى كل استدعاء Add() – وتحقق من الجانب الخادمي بدلاً من ملف الإخراج.

موارد إضافية