HTML敏捷包 - 如何删除不需要的标签而不会删除内容?

51

我看到了一些相关的问题,但它们并没有涉及到我所面临的问题。

我想使用HTML Agility Pack从我的HTML中删除不需要的标签,同时保留标签内的内容。

例如,在我的场景中,我想保留"b", "i"和"u"这些标签。

对于像这样的输入:

<p>我的段落<div>和我<b>div</b></div>是<i>斜体</i>和<b>粗体</b></p>

结果应该是:

我的段落和我<b>div</b>是<i>斜体</i>和<b>粗体</b>

我尝试使用HtmlNodeRemove方法,但它也会删除我的内容。 有什么建议吗?


2
你尝试过HtmlNode.RemoveChild(HtmlNode oldChild, bool keepGrandChildren)方法吗? - Ichabod Clay
请查看以下链接:https://dev59.com/wWMl5IYBdhLWcg3wwZIq#18154046 - Bidou
@bidou,如果需要强制实施安全措施,我非常怀疑使用正则表达式是否是一个好主意。 - Mathias Lykkegaard Lorenzen
5个回答

67

我根据 Oded 的建议编写了一个算法。这是它的代码。非常好用。

它会删除除strongemu和原始文本节点之外的所有标签。

internal static string RemoveUnwantedTags(string data)
{
    if(string.IsNullOrEmpty(data)) return string.Empty;

    var document = new HtmlDocument();
    document.LoadHtml(data);

    var acceptableTags = new String[] { "strong", "em", "u"};

    var nodes = new Queue<HtmlNode>(document.DocumentNode.SelectNodes("./*|./text()"));
    while(nodes.Count > 0)
    {
        var node = nodes.Dequeue();
        var parentNode = node.ParentNode;

        if(!acceptableTags.Contains(node.Name) && node.Name != "#text")
        {
            var childNodes = node.SelectNodes("./*|./text()");

            if (childNodes != null)
            {
                foreach (var child in childNodes)
                {
                    nodes.Enqueue(child);
                    parentNode.InsertBefore(child, node);
                }
            }

            parentNode.RemoveChild(node);

        }
    }

    return document.DocumentNode.InnerHtml;
}

1
感谢你提供的解决方案!但请注意,它不会删除嵌套在可接受标记内部的不可接受标记--例如,如果允许“P”但不允许“SPAN”,则“SPAN”将不会被删除。我通过递归运行此方法来解决了这个问题,从“var nodes ...”分割成一个单独的方法,该方法在任何在可接受列表中并具有子节点的“node”变量上调用自身。 - Sean Mahan
太好了@SeanMahan。请随意编辑我的帖子并提供你的解决方案 - 我相信它会更好 :) - Mathias Lykkegaard Lorenzen
@SeanMahan 请提供您的解决方案!我很想看看您的解决方案。 - StarQuake
不适用于输入:'10 > 7 <b>但是</b> 10 < 30',输出为:'10 > 7 但是 10'。 - Bartosz Pierzchlewicz
8
但这也是非常无效的HTML。 - Mathias Lykkegaard Lorenzen

17

如何递归地从HTML字符串中删除给定的不需要的HTML标记列表

我采用了@mathias的答案,并改进了他的扩展方法,使您可以提供要排除的标记列表作为List<string>(例如{"a","p","hr"})。 我还修正了逻辑,以便它可以正确递归地工作:

public static string RemoveUnwantedHtmlTags(this string html, List<string> unwantedTags)
    {
        if (String.IsNullOrEmpty(html))
        {
            return html;
        }

        var document = new HtmlDocument();
        document.LoadHtml(html);

        HtmlNodeCollection tryGetNodes = document.DocumentNode.SelectNodes("./*|./text()");

        if (tryGetNodes == null || !tryGetNodes.Any())
        {
            return html;
        }

        var nodes = new Queue<HtmlNode>(tryGetNodes);

        while (nodes.Count > 0)
        {
            var node = nodes.Dequeue();
            var parentNode = node.ParentNode;

            var childNodes = node.SelectNodes("./*|./text()");

            if (childNodes != null)
            {
                foreach (var child in childNodes)
                {
                    nodes.Enqueue(child);                       
                }
            }

            if (unwantedTags.Any(tag => tag == node.Name))
            {               
                if (childNodes != null)
                {
                    foreach (var child in childNodes)
                    {
                        parentNode.InsertBefore(child, node);
                    }
                }

                parentNode.RemoveChild(node);

            }
        }

        return document.DocumentNode.InnerHtml;
    }

1
注意:如果我理解正确,这个函数会删除一系列不需要的标签,而@mathias发布的函数则保留了可接受的标签。这是非常不同的。 - StarQuake
@StarQuake 这是对原始问题的回答,即删除不需要的标签。我只是改编了Mathias的代码,使其更加灵活。 - theyetiman
1
原始问题询问如何通过提供allowedTags来删除不需要的标签。原始问题的标题是误导性的。给出的示例还说明了一个将allowedTags保留在内而不是删除不允许的标签的函数。 - StarQuake
1
@StarQuake 这个问题并没有提到实现需要一个“allowedTags”列表,它只是给出了一个要在一个实例中保留的标签示例。Mathias的答案通过硬编码白名单方法提供了一种解决方案。我的答案通过可变黑名单方法提供了一种解决方案,以增加灵活性。两者都没有对错之分,只是原始问题的替代解决方案。 - theyetiman

11
尝试以下方法,你可能会发现它比其他提议的解决方案更整洁:
public static int RemoveNodesButKeepChildren(this HtmlNode rootNode, string xPath)
{
    HtmlNodeCollection nodes = rootNode.SelectNodes(xPath);
    if (nodes == null)
        return 0;
    foreach (HtmlNode node in nodes)
        node.RemoveButKeepChildren();
    return nodes.Count;
}

public static void RemoveButKeepChildren(this HtmlNode node)
{
    foreach (HtmlNode child in node.ChildNodes)
        node.ParentNode.InsertBefore(child, node);
    node.Remove();
}

public static bool TestYourSpecificExample()
{
    string html = "<p>my paragraph <div>and my <b>div</b></div> are <i>italic</i> and <b>bold</b></p>";
    HtmlDocument document = new HtmlDocument();
    document.LoadHtml(html);
    document.DocumentNode.RemoveNodesButKeepChildren("//div");
    document.DocumentNode.RemoveNodesButKeepChildren("//p");
    return document.DocumentNode.InnerHtml == "my paragraph and my <b>div</b> are <i>italic</i> and <b>bold</b>";
}

5

在删除节点之前,获取其父节点及其InnerText,然后删除该节点并将InnerText重新赋值给父节点。

var parent = node.ParentNode;
var innerText = parent.InnerText;
node.Remove();
parent.AppendChild(doc.CreateTextNode(innerText));

InnerText 破坏了层次结构。想象一下更深的嵌套,例如:<p>我的<b>段落<div>和我的<b><span><i>div</i></span></b></div>是<i>斜体</i>和<b>粗体</b></b></p>。InnerText 搞乱了 HTML 并将其转换为文本,但这并不是我在所有情况下想要的。我仍然希望保留允许的标签的 HTML。 - Mathias Lykkegaard Lorenzen
@MathiasLykkegaardLorenzen - 我的意思是你只需要处理那些不需要的元素。 - Oded
是的。但如果您浏览此处的数据。lalala <span>omg <b>this</b> is bold</span>。首先,您会发现span元素。它不被允许,因此您将其替换为其InnerText值。然而,这破坏了我仍然需要的内部b标记。希望这样解释更清楚了。 - Mathias Lykkegaard Lorenzen
1
@MathiasLykkegaardLorenzen - 当然,你可以简单地使用节点的 InnerHtml 而不是父级来执行相同的操作(也就是说,获取节点的 InnerHtml,在删除节点后替换父级的 InnerHtml)。你也可以递归地使用节点的 InnerHtml 来实现这一点。 - Oded
谢谢 - 我会尝试并回复你。 - Mathias Lykkegaard Lorenzen

3
如果您不想使用Html Agility Pack但仍希望删除不需要的HTML标签,您可以按照以下方法进行操作。
public static string RemoveHtmlTags(string strHtml)
    {
        string strText = Regex.Replace(strHtml, "<(.|\n)*?>", String.Empty);
        strText = HttpUtility.HtmlDecode(strText);
        strText = Regex.Replace(strText, @"\s+", " ");
        return strText;
    }

12
不要使用正则表达式来解析或分析HTML。 - Mathias Lykkegaard Lorenzen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接