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

如何用C#比较属性顺序不同但效果一致的HTML字符串?

C#实现渲染一致的HTML字符串比较逻辑

对于HTML字符串,普通字符串比较会因为标签属性(如style内的样式顺序)不同判定为不等,但实际浏览器渲染效果完全一致。以下是基于C#的解决方案,通过HTML结构化解析实现精准比较:

步骤1:安装HtmlAgilityPack

通过NuGet安装专业HTML解析库:

Install-Package HtmlAgilityPack
# 或使用.NET CLI
dotnet add package HtmlAgilityPack

步骤2:实现比较逻辑

核心思路是将HTML解析为DOM节点,递归比较节点的关键特征:标签名、标准化后的文本内容、排序后的属性(尤其是style)、子节点顺序与内容。

辅助方法:标准化Style属性

将style内的样式规则按属性名排序,消除顺序差异影响:

private static string NormalizeStyle(string style)
{
    if (string.IsNullOrWhiteSpace(style))
        return string.Empty;

    var styleRules = style.Split(';')
        .Select(rule => rule.Trim())
        .Where(rule => !string.IsNullOrEmpty(rule))
        .Select(rule =>
        {
            var parts = rule.Split(':')
                .Select(p => p.Trim())
                .ToArray();
            return parts.Length == 2 ? $"{parts[0]}: {parts[1]}" : rule;
        })
        .OrderBy(rule => rule.Split(':')[0].Trim())
        .ToList();

    return string.Join("; ", styleRules) + ";";
}

递归节点比较方法

private static bool AreNodesEqual(HtmlNode node1, HtmlNode node2)
{
    // 节点类型不一致直接返回false
    if (node1.NodeType != node2.NodeType)
        return false;

    // 文本节点:标准化空白后比较
    if (node1.NodeType == HtmlNodeType.Text)
    {
        var text1 = string.Join(" ", node1.InnerText.Split(new[] {' ', '\t', '\n', '\r'}, StringSplitOptions.RemoveEmptyEntries));
        var text2 = string.Join(" ", node2.InnerText.Split(new[] {' ', '\t', '\n', '\r'}, StringSplitOptions.RemoveEmptyEntries));
        return text1.Equals(text2, StringComparison.OrdinalIgnoreCase);
    }

    // 元素节点:比较标签名
    if (node1.Name != node2.Name)
        return false;

    // 处理属性:对style标准化,其他属性直接比较键值对
    var attrs1 = node1.Attributes.ToDictionary(a => a.Name, a =>
        a.Name.Equals("style", StringComparison.OrdinalIgnoreCase) ? NormalizeStyle(a.Value) : a.Value);
    var attrs2 = node2.Attributes.ToDictionary(a => a.Name, a =>
        a.Name.Equals("style", StringComparison.OrdinalIgnoreCase) ? NormalizeStyle(a.Value) : a.Value);

    if (attrs1.Count != attrs2.Count)
        return false;

    foreach (var attr in attrs1)
    {
        if (!attrs2.TryGetValue(attr.Key, out var value2) || !attr.Value.Equals(value2, StringComparison.OrdinalIgnoreCase))
            return false;
    }

    // 比较子节点数量与内容
    if (node1.ChildNodes.Count != node2.ChildNodes.Count)
        return false;

    for (int i = 0; i < node1.ChildNodes.Count; i++)
    {
        if (!AreNodesEqual(node1.ChildNodes[i], node2.ChildNodes[i]))
            return false;
    }

    return true;
}

主比较方法

public static bool AreHtmlRenderedEqual(string html1, string html2)
{
    var doc1 = new HtmlAgilityPack.HtmlDocument();
    doc1.LoadHtml(html1);

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

    // 优先比较body节点内容,无body则比较根节点
    var body1 = doc1.DocumentNode.SelectSingleNode("//body") ?? doc1.DocumentNode;
    var body2 = doc2.DocumentNode.SelectSingleNode("//body") ?? doc2.DocumentNode;

    return AreNodesEqual(body1, body2);
}

测试示例

string str1 = @"<p><strong style=""font-size: 36px; color: rgb(153, 51, 255);"">Hello </strong><em><u>World</u></em></p>";
string str2 = @"<p><strong style=""color: rgb(153, 51, 255); font-size: 36px;"">Hello </strong><em><u>World</u></em></p>";

bool isEqual = AreHtmlRenderedEqual(str1, str2);
Console.WriteLine(isEqual); // 输出: True

扩展说明

  • 若需处理class属性顺序问题(class顺序不影响渲染),可添加类似NormalizeStyle的NormalizeClass方法,将类名排序后比较
  • 文本节点的空白标准化可根据需求调整,比如保留原始换行(若业务需要)
  • HtmlAgilityPack支持解析不规范的HTML,兼容性较强

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 12:15:11