在開發瀏覽器嵌入應用、HTML 編輯器、爬蟲分析工具時,常需將 HTML 文件的 DOM 結構視覺化為 TreeView。本文示範使用 mshtml (Microsoft HTML Object Library) 走訪 DOM 節點,遞迴建構 Windows Forms TreeView。
using System;
using System.Windows.Forms;
using mshtml; // 參考: Microsoft HTML Object Library
using AxSHDocVw; // WebBrowser ActiveX
public class DomTreeBuilder
{
private TreeView _treeView;
private string _pathString = "0";
public DomTreeBuilder(TreeView treeView)
{
_treeView = treeView;
}
// 遞迴插入 DOM 節點
private void InsertDomNodes(IHTMLDOMNode parentNode, TreeNode treeNode)
{
if (!parentNode.hasChildNodes()) return;
IHTMLDOMChildrenCollection children = (IHTMLDOMChildrenCollection)parentNode.childNodes;
int siblingIndex = 0;
for (int i = 0; i < children.length; i++)
{
IHTMLDOMNode childNode = (IHTMLDOMNode)children.item(i);
string nodePath = _pathString + "," + siblingIndex++;
TreeNode childTreeNode = treeNode.Nodes.Add($"{childNode.nodeName}_{nodePath}");
string prevPath = _pathString;
_pathString = nodePath;
InsertDomNodes(childNode, childTreeNode);
_pathString = prevPath;
}
}
// WebBrowser DocumentComplete 事件處理
public void OnDocumentComplete(object sender, DWebBrowserEvents2_DocumentCompleteEvent e)
{
_pathString = "0";
_treeView.Nodes.Clear();
IHTMLDocument3 doc = (IHTMLDocument3)((WebBrowser)sender).Document.DomDocument;
IHTMLDOMNode rootDomNode = (IHTMLDOMNode)doc.documentElement;
TreeNode root = _treeView.Nodes.Add($"HTML_{_pathString}");
InsertDomNodes(rootDomNode, root);
_treeView.ExpandAll();
}
}
| 介面 | 用途 |
|---|---|
IHTMLDOMNode | DOM 節點基底介面,提供 nodeName、childNodes、hasChildNodes() |
IHTMLDOMChildrenCollection | 子節點集合,支援 length、item(index) |
IHTMLDocument3 | 文件介面,提供 documentElement (根元素 <html>) |
public partial class MainForm : Form
{
private DomTreeBuilder _builder;
public MainForm()
{
InitializeComponent();
_builder = new DomTreeBuilder(domTreeView);
webBrowser.DocumentCompleted += _builder.OnDocumentComplete;
}
private void btnNavigate_Click(object sender, EventArgs e)
{
webBrowser.Navigate("https://example.com");
}
}
nodeName 回傳值對照:
"DIV"、"A"、"IMG")"#text""#comment""#document""DOCTYPE"private void InsertDomNodes(IHTMLDOMNode parentNode, TreeNode treeNode)
{
if (!parentNode.hasChildNodes()) return;
var children = (IHTMLDOMChildrenCollection)parentNode.childNodes;
for (int i = 0; i < children.length; i++)
{
var child = (IHTMLDOMNode)children.item(i);
string displayName = child.nodeName;
// 若為元素節點,顯示 id/class
if (child.nodeType == 1) // NodeType.Element
{
var elem = (IHTMLElement)child;
if (!string.IsNullOrEmpty(elem.id)) displayName += $" id={elem.id}";
if (!string.IsNullOrEmpty(elem.className)) displayName += $" class={elem.className}";
}
// 文字節點顯示前 30 字
else if (child.nodeType == 3) // NodeType.Text
{
string text = child.nodeValue?.Trim();
if (!string.IsNullOrEmpty(text))
displayName += $": \"{text.Substring(0, Math.Min(30, text.Length))}...\"";
}
TreeNode tn = treeNode.Nodes.Add(displayName);
InsertDomNodes(child, tn);
}
}
using AngleSharp.Dom;
using AngleSharp.Html.Parser;
public void BuildTree(IElement element, TreeViewItem parent)
{
var item = new TreeViewItem { Header = element.TagName };
parent.Items.Add(item);
foreach (var child in element.ChildNodes)
{
if (child is IElement el) BuildTree(el, item);
else if (child is IText txt && !string.IsNullOrWhiteSpace(txt.Text))
item.Items.Add(new TreeViewItem { Header = $"#text: {txt.Text.Substring(0, 30)}" });
}
}
// 使用
var parser = new HtmlParser();
var doc = await parser.ParseDocumentAsync(htmlString);
BuildTree(doc.DocumentElement, treeViewRoot);
Embed Interop Types = True 可避免部署 Primary Interop Assemblymshtml 為 64 位元版本,或設定 Prefer 32-bit