HTML Agility Pack removeChild — работает не так, как ожидалось

#c# #html-agility-pack

#c# #html-agility-pack

Вопрос:

Допустим, я хочу удалить тег span из этого html:

 <html><span>we do like <b>bold</b> stuff</span></html>
  

Я ожидаю, что этот фрагмент кода будет делать то, что мне нужно

 string html = "<html><span>we do like <b>bold</b> stuff</span></html>";
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(html);

HtmlNode span = doc.DocumentNode.Descendants("span").First();
span.ParentNode.RemoveChild(span, true); //second parameter is 'keepGrandChildren'
  

Но результат выглядит так:

 <html> stuff<b>bold</b>we do like </html>
  

Похоже, что он меняет дочерние узлы в пределах диапазона. Я делаю что-то не так?

Ответ №1:

Похоже на ошибку в HtmlAgilityPack — см. Их реестр проблем:

http://htmlagilitypack.codeplex.com/workitem/9113

Интересно, что это было поднято 4 года назад…

Вот фрагмент, который удалит все теги span (или любой другой указанный вами тег) и сохранит другие узлы в правильном порядке.

 void Main()
{
    string html = "<html><span>we do like <b>bold</b> stuff</span></html>";
    HtmlDocument doc = new HtmlDocument();
    doc.LoadHtml(html);
    RemoveTags(doc, "span");
    Console.WriteLine(doc.DocumentNode.OuterHtml);
}

public static void RemoveTags(HtmlDocument html, string tagName)
{
    var tags = html.DocumentNode.SelectNodes("//"   tagName);
    if (tags!=null)
    {
        foreach (var tag in tags)
        {
            if (!tag.HasChildNodes)
            {
                tag.ParentNode.RemoveChild(tag);
                continue;
            }

            for (var i = tag.ChildNodes.Count - 1; i >= 0; i--)
            {
                var child = tag.ChildNodes[i];
                tag.ParentNode.InsertAfter(child, tag);
            }
            tag.ParentNode.RemoveChild(tag);
        }
    }
}
  

Комментарии:

1. Интересно, что это все еще происходит в 2017 году

2. …и в 2019 году. Спасибо за отличное предложение и код. Отлично работает!

Ответ №2:

 foreach (HtmlNode child in tag.ChildNodes)
{
    tag.ParentNode.InsertBefore(child, tag);
}

tag.Remove();
  

Ответ №3:

Просто для записи, это моя версия, основанная на ответах на этот вопрос:

 using HtmlAgilityPack;

internal static class HtmlAgilityPackExtensions
{
    public static void RemoveNodeKeepChildren(this HtmlNode node)
    {
        foreach (var child in node.ChildNodes)
        {
            node.ParentNode.InsertBefore(child, node);
        }
        node.Remove();
    }
}