介紹
同事傳送兩個版本的合約給你,請你比較差異。你把兩個檔案都放入比較服務,結果回傳,所有看起來都正常。你沒注意到的是,其中一個文件包含指向 URL 的連結圖片,當檔案被開啟的瞬間,你的伺服器就向該主機發出了請求。輸出結果並未告訴你這件事發生了。
這不是缺陷——這正是「忠實載入文件」的意義。OOXML 檔案可以引用位於網路伺服器上的圖片,而不是封裝在檔案內,Word 與任何正確載入文件的程式庫都會解析這個引用。GroupDocs.Comparison for .NET 在 LoadOptions 上提供兩個屬性讓你決定是否要這樣做:SkipExternalResources 與 WhitelistedResources。
這兩個屬性組合出三種設定方式,本文將比較這三種——寬鬆的預設、阻止全部、以及阻止全部但允許特定名稱的引用。閱讀完畢後,你將知道在不同文件來源下應選擇哪一種設定,以及兩個常見錯誤為何會讓這些設定看似失效。
💡 完整可執行範例: block-external-resources-on-document-load-dotnet - 一個可執行的主控台專案,會自行提供參考圖片並記錄每一次請求,讓你可以觀察每個設定的實際效果。
外部參考隱藏位置
在選擇設定之前,先了解你到底在選擇什麼。一個 .docx 會在兩個不同的地方保存外部參考,且這兩個位置都不會在文件文字中直接顯示,容易被忽略。
-
第一個位置是
word/_rels/document.xml.rels中的關係(relationship),其TargetMode="External"以及絕對 URL。圖片會以 drawing 形式出現在正文中,透過 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:阻止所有外部資源
只要將此屬性設為 true,即可關閉該文件的遠端參考解析:
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。只有在確實需要允許單一可信參考時,才加入極窄的 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() 呼叫——並從服務端而非輸出檔案驗證效果。
其他資源
- 在 GroupDocs.Comparison for .NET 中設定外部資源載入 - 使用情境指南,包含決策矩陣與 FAQ
- block-external-resources-on-document-load-dotnet - 完整可執行範例,包含回環圖片主機
- 載入受密碼保護的文件 -
LoadOptions.Password,與前述選項同屬每文件範圍規則的兄弟設定 - 載入自訂字型 - 使用
LoadOptions.FontDirectories在載入時解析非標準字型 - GroupDocs.Comparison for .NET API 參考文件 -
LoadOptions與Comparer類別的完整說明 - Free support forum - 關於外部資源處理與比較行為的問題討論