爬蟲、郵件範本處理、CMS 內容儲存、離線瀏覽器等場景中,HTML 內容常包含相對路徑(如 /images/logo.png、../style.css)。儲存或轉發前,需將其解析為絕對 URL,確保資源正確載入。
using System;
using mshtml; // 參考 COM: Microsoft HTML Object Library
public static string ConvertToAbsoluteUrls(string html, Uri baseUri)
{
// 建立 DOM 文件
IHTMLDocument2 doc = new HTMLDocumentClass();
doc.write(new object[] { html });
doc.close();
// 處理所有 <a href>
foreach (IHTMLAnchorElement anchor in doc.links)
{
IHTMLElement elem = (IHTMLElement)anchor;
string href = (string)elem.getAttribute("href", 2);
if (!string.IsNullOrEmpty(href))
{
Uri absolute = new Uri(baseUri, href);
anchor.href = absolute.AbsoluteUri;
}
}
// 處理所有 <img src>
foreach (IHTMLImgElement img in doc.images)
{
IHTMLElement elem = (IHTMLElement)img;
string src = (string)elem.getAttribute("src", 2);
if (!string.IsNullOrEmpty(src))
{
Uri absolute = new Uri(baseUri, src);
img.src = absolute.AbsoluteUri;
}
}
return doc.body.innerHTML;
}
string html = @"<a href='./page.html'>連結</a><img src='../images/logo.png' />";
Uri baseUri = new Uri("https://example.com/folder/");
string result = ConvertToAbsoluteUrls(html, baseUri);
// 結果:
// <a href="https://example.com/folder/page.html">連結</a>
// <img src="https://example.com/images/logo.png" />
mshtml.dll),屬於非受控 COM 元件mshtml.dll(IE 安裝自動註冊)mshtml.dll 位元數一致,或勾選「Prefer 32-bit」using HtmlAgilityPack;
public static string ConvertUrlsModern(string html, Uri baseUri)
{
var doc = new HtmlDocument();
doc.LoadHtml(html);
foreach (var a in doc.DocumentNode.SelectNodes("//a[@href]") ?? Enumerable.Empty())
{
string href = a.GetAttributeValue("href", "");
if (Uri.TryCreate(baseUri, href, out Uri abs))
a.SetAttributeValue("href", abs.AbsoluteUri);
}
foreach (var img in doc.DocumentNode.SelectNodes("//img[@src]") ?? Enumerable.Empty())
{
string src = img.GetAttributeValue("src", "");
if (Uri.TryCreate(baseUri, src, out Uri abs))
img.SetAttributeValue("src", abs.AbsoluteUri);
}
return doc.DocumentNode.OuterHtml;
}
優點:純 .NET、跨平台、無 COM 依賴、效能佳、支援 .NET Core / .NET 5+ / Linux Docker。
using AngleSharp;
using AngleSharp.Dom;
public static async Task ConvertUrlsAsync(string html, Uri baseUri)
{
var config = Configuration.Default;
var context = BrowsingContext.New(config);
var doc = await context.OpenAsync(req => req.Content(html));
foreach (var a in doc.QuerySelectorAll("a[href]"))
{
if (Uri.TryCreate(baseUri, a.GetAttribute("href"), out Uri abs))
a.SetAttribute("href", abs.AbsoluteUri);
}
foreach (var img in doc.QuerySelectorAll("img[src]"))
{
if (Uri.TryCreate(baseUri, img.GetAttribute("src"), out Uri abs))
img.SetAttribute("src", abs.AbsoluteUri);
}
return doc.DocumentElement.OuterHtml;
}
| 相對路徑 | 基底 URI | 解析結果 |
|---|---|---|
page.html | https://site.com/dir/ | https://site.com/dir/page.html |
/page.html | https://site.com/dir/ | https://site.com/page.html |
../page.html | https://site.com/dir/sub/ | https://site.com/dir/page.html |
https://other.com/x.jpg | https://site.com/ | https://other.com/x.jpg(已為絕對,保持不變) |
try
{
return ConvertToAbsoluteUrls(html, baseUri);
}
catch (UriFormatException ex)
{
_logger.LogWarning(ex, "Invalid base URI or malformed HTML");
return html; // 回傳原始內容
}
catch (COMException ex) when (ex.ErrorCode == -2147221164) // CLASS_E_CLASSNOTAVAILABLE
{
_logger.LogError(ex, "mshtml.dll not registered");
throw new InvalidOperationException("HTML parser unavailable", ex);
}