You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#中比较TinyMce与DOM返回的等效HTML字符串?

用C#实现等效HTML内容比较的正确姿势

首先得说,绝对不要用正则来处理HTML比较——HTML是嵌套的结构化文档,正则根本没法覆盖所有边缘情况(比如嵌套标签、不同的属性顺序、自闭合标签差异等等),用正则迟早会踩坑。

最靠谱的方案是用专业的HTML解析库把两个字符串解析成DOM树,然后做标准化处理,再递归比较节点结构和内容。C#里最常用的就是HtmlAgilityPack,下面给你一步步讲怎么实现:

步骤1:安装HtmlAgilityPack

先通过NuGet安装这个包,在Visual Studio里右键项目→管理NuGet程序包,搜索HtmlAgilityPack安装就行。

步骤2:解析HTML字符串到DOM对象

把两个HTML字符串分别加载进HtmlDocument对象:

string tinyMceHtml = "<p>Test<img src=\"../../../files/a54f58ca-56e1-4e6a-acf5-1cccebe51dc1/FY17/img/21c17e0e-b433-42de-97c7-cf0194fc7ac4\" /></p>";
string domHtml = "<p>Test<img src=\"/files/a54f58ca-56e1-4e6a-acf5-1cccebe51dc1/FY17/img/21c17e0e-b433-42de-97c7-cf0194fc7ac4\"></p>";

var doc1 = new HtmlAgilityPack.HtmlDocument();
doc1.LoadHtml(tinyMceHtml);

var doc2 = new HtmlAgilityPack.HtmlDocument();
doc2.LoadHtml(domHtml);

步骤3:标准化处理DOM

这一步是关键,要消除那些“语义无关”的差异:比如路径格式(相对→绝对)、空白文本节点、自闭合标签格式等。

3.1 统一图片路径格式

把相对路径转成绝对路径(或者反过来,根据你的网站根目录调整):

private static void NormalizeImageSrc(HtmlNode node, string baseWebsiteUrl)
{
    if (node.NodeType == HtmlNodeType.Element && node.Name.Equals("img", StringComparison.OrdinalIgnoreCase))
    {
        var srcAttr = node.Attributes["src"];
        if (srcAttr != null)
        {
            // 把相对路径转为网站根目录的绝对路径
            Uri absoluteUri = new Uri(new Uri(baseWebsiteUrl), srcAttr.Value);
            srcAttr.Value = absoluteUri.AbsolutePath;
        }
    }

    // 递归处理所有子节点
    foreach (HtmlNode child in node.ChildNodes.ToList())
    {
        NormalizeImageSrc(child, baseWebsiteUrl);
    }
}

// 假设你的网站根URL是"https://your-site.com/"
NormalizeImageSrc(doc1.DocumentNode, "https://your-site.com/");
NormalizeImageSrc(doc2.DocumentNode, "https://your-site.com/");

3.2 移除无关的空白文本节点

DOM和TinyMce返回的HTML可能包含多余的换行、空格,这些不影响语义,直接删掉:

private static void RemoveWhitespaceTextNodes(HtmlNode node)
{
    var childNodes = node.ChildNodes.ToList();
    foreach (HtmlNode child in childNodes)
    {
        if (child.NodeType == HtmlNodeType.Text && string.IsNullOrWhiteSpace(child.InnerText))
        {
            node.RemoveChild(child);
        }
        else
        {
            RemoveWhitespaceTextNodes(child);
        }
    }
}

RemoveWhitespaceTextNodes(doc1.DocumentNode);
RemoveWhitespaceTextNodes(doc2.DocumentNode);

步骤4:递归比较DOM节点

写一个递归函数,逐节点比较类型、标签名、属性、子节点和文本内容:

private static bool AreNodesEquivalent(HtmlNode node1, HtmlNode node2)
{
    // 节点类型不同直接不等
    if (node1.NodeType != node2.NodeType)
        return false;

    // 处理元素节点
    if (node1.NodeType == HtmlNodeType.Element)
    {
        // 标签名不区分大小写
        if (!string.Equals(node1.Name, node2.Name, StringComparison.OrdinalIgnoreCase))
            return false;

        // 比较属性:数量一致,且每个属性的键(不区分大小写)和值都相同
        if (node1.Attributes.Count != node2.Attributes.Count)
            return false;

        foreach (var attr in node1.Attributes)
        {
            var matchingAttr = node2.Attributes[attr.Name];
            if (matchingAttr == null || !string.Equals(attr.Value, matchingAttr.Value, StringComparison.Ordinal))
                return false;
        }
    }
    // 处理文本节点:忽略前后空白
    else if (node1.NodeType == HtmlNodeType.Text)
    {
        return string.Equals(node1.InnerText.Trim(), node2.InnerText.Trim(), StringComparison.Ordinal);
    }

    // 子节点数量不一致直接不等
    if (node1.ChildNodes.Count != node2.ChildNodes.Count)
        return false;

    // 递归比较每个子节点
    for (int i = 0; i < node1.ChildNodes.Count; i++)
    {
        if (!AreNodesEquivalent(node1.ChildNodes[i], node2.ChildNodes[i]))
            return false;
    }

    return true;
}

// 调用比较
bool isEquivalent = AreNodesEquivalent(doc1.DocumentNode, doc2.DocumentNode);

额外考虑

如果TinyMce还会生成一些额外的属性(比如data-mce-*类的私有属性),你可以在标准化步骤里把这些无关属性删掉,避免影响比较结果。


内容的提问来源于stack exchange,提问作者sdfsd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 17:42:37