מבוא
עמית שולח שני גרסאות של חוזה ומבקש ממך לבצע השוואה ביניהם. אתה מזין את שני הקבצים לשירות ההשוואה שלך, התוצאה חוזרת, והכל נראה רגיל. מה שלא ראית הוא שאחד המסמכים מכיל תמונה מקושרת שמצביעה על כתובת URL, והשרת שלך יצר קשר עם המארח ברגע שהקובץ נפתח. שום דבר בפלט לא מודיע לך שזה קרה.
זה לא תקלה – זה מה שמשמעותו טעינת מסמך באופן אמין. קובץ OOXML יכול להתייחס לתמונה שממוקמת בשרת אינטרנט במקום להיות בתוך החבילה, ושגם Word וגם כל ספרייה הטוענת את המסמך פותרות את ההתייחסות הזאת. GroupDocs.Comparison עבור .NET מציג שני מאפיינים ב‑LoadOptions שמאפשרים לך להחליט אם לבצע זאת: SkipExternalResources ו‑WhitelistedResources.
בין השניים הם מציעים שלוש תצורות, והמאמר הזה משווה את שלושן – ברירת המחדל המאפשרת, חסימת הכל, וחסימת הכל פרט להתייחסויות בשם. בסוף תדע איזו לבחור עבור מקור מסמך נתון, ואת שתי הטעויות שגורמות להגדרות אלו להיראות כאילו אינן עובדות.
💡 דוגמה מלאה עובדת: block-external-resources-on-document-load-dotnet – פרויקט קונסול שניתן להריץ, שמשרת את התמונות המקושרות בעצמו ומתעד כל בקשה, כך שתוכל לראות כל הגדרה בפעולה.
היכן ההתייחסויות החיצוניות מסתתרות
לפני בחירת הגדרה, כדאי לדעת מה בדיוק אתה בוחר. קובץ .docx מכיל התייחסויות חיצוניות בשני מקומות נפרדים, והן קלות להחמיץ מכיוון שאף אחת מהן אינה נראית בטקסט המסמך.
הראשונה היא קשר (relationship) ב‑word/_rels/document.xml.rels שמכיל TargetMode="External" וכתובת URL מוחלטת. התמונה מופיעה בגוף כ‑drawing שמצביע על הקשר לפי מזהה, ולכן ה‑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 ההתייחסות, וכל התאמה בכל מקום ב‑URL מאפשרת את המשאב. זה מה שהופך את הרשימה הלבנה לניידת: "includepicture-field.png" מאפשרת את התמונה ללא קשר לסכמה, למארח ולנתיב שלפניו, ולכן אותה רשימה עובדת בפיתוח ובייצור ללא צורך בעריכה.
המאפיין הזה פועל גם בכיוון ההפוך. קטע קצר או כללי – logo.png, או גרוע יותר, .png – יכול להתאים להתייחסויות שמעולם לא התכוונת לאפשר. בחר קטע ספציפי מספיק כדי לזהות את המשאב היחיד שהתכוונת אליו.
בדוגמת ההתייחסות, תצורה זו מושכת את התמונה המופיעה ברשימה הלבנה ומשאירה את התמונה השנייה, שאין לה ערך ברשימה, חסומה. יומן הבקשות מציג שלוש בקשות, בעוד שהברירת המחדל המאפשרת יצרה חמש, ומציין רק את הקובץ המופיע ברשימה הלבנה.
איזו תצורה לבחור?
התאם את ההגדרה למקור המסמך. מסמכים שהאפליקציה או התבניות שלך ייצרו יכולים לשמור על ברירת המחדל, מכיוון שכל כתובת URL מצביעת על תשתית שאתה כבר מפעיל. כל דבר שמגיע מבחוץ – העלאות משתמשים, קבצים מצורפים במייל, קבצים של צד שלישי – מצריך SkipExternalResources = true. הוסף קטע רשימה לבנה מצומצם רק כאשר יש צורך אמיתי בפתרון של התייחסות מהימנה אחת.
השוואת שלוש התצורות
| נושא | ברירת המחדל | חסימת הכל | חסימת הכל + רשימה לבנה |
|---|---|---|---|
| מאפיינים להגדרה | 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() – ואמת מהצד של השרת במקום מקובץ הפלט.
משאבים נוספים
- הגדרת טעינת משאבים חיצוניים ב‑GroupDocs.Comparison עבור .NET – מדריך מקרה שימוש, עם מטריצת החלטות ושאלות נפוצות
- block-external-resources-on-document-load-dotnet – הדוגמה המלאה שניתן להריץ, כולל מארח תמונות לופבק
- טעינת מסמכים מוגנים בסיסמה –
LoadOptions.Password, ההגדרה האחווה עם אותה כלל תחום לכל מסמך - טעינת גופנים מותאמים אישית – פתרון גופנים לא סטנדרטיים בזמן טעינה עם
LoadOptions.FontDirectories - GroupDocs.Comparison עבור .NET – תיעוד API – פרטים מלאים על
LoadOptionsועל מחלקתComparer - פורום תמיכה חינמי – שאלות על טיפול במשאבים חיצוניים והתנהגות השוואה