Dividir la cadena HTML en dos partes con HtmlAgilityPack

c# dom html html-agility-pack parsing

Pregunta

Estoy buscando la mejor manera de dividir un documento HTML sobre una etiqueta en C # usando HtmlAgilityPack. Quiero preservar el marcado previsto ya que estoy haciendo la división. Aquí hay un ejemplo.

Si el documento es así:

<p>
<div>
    <p>
        Stuff
    </p>
    <p>
        <ul>
            <li>Bullet 1</li>
            <li><a href="#">link</a></li>
            <li>Bullet 3</li>
        </ul>
    </p>
            <span>Footer</span>
</div>
</p>

Una vez que se divide, debe verse así:

Parte 1

<p>
<div>
    <p>
        Stuff
    </p>
    <p>
        <ul>
            <li>Bullet 1</li>
        </ul>
    </p>
</div>
</p>

Parte 2

<p>
<div>
    <p>
        <ul>
            <li>Bullet 3</li>
        </ul>
    </p>
            <span>Footer</span>
</div>

</p>

¿Cuál sería la mejor manera de hacer algo así?

Respuesta aceptada

Aquí es lo que se me ocurrió. Esto hace la división y elimina los elementos "vacíos" del elemento donde ocurre la división.

    private static void SplitDocument()
    {
        var doc = new HtmlDocument();
        doc.Load("HtmlDoc.html");
        var links = doc.DocumentNode.SelectNodes("//a[@href]");
        var firstPart = GetFirstPart(doc.DocumentNode, links[0]).DocumentNode.InnerHtml;
        var secondPart = GetSecondPart(links[0]).DocumentNode.InnerHtml;
    }

    private static HtmlDocument GetFirstPart(HtmlNode currNode, HtmlNode link)
    {
        var nodeStack = new Stack<Tuple<HtmlNode, HtmlNode>>();        
        var newDoc = new HtmlDocument();
        var parent = newDoc.DocumentNode;

        nodeStack.Push(new Tuple<HtmlNode, HtmlNode>(currNode, parent));

        while (nodeStack.Count > 0)
        {
            var curr = nodeStack.Pop();
            var copyNode = curr.Item1.CloneNode(false);
            curr.Item2.AppendChild(copyNode);

            if (curr.Item1 == link)
            {
                var nodeToRemove = NodeAndEmptyAncestors(copyNode);
                nodeToRemove.ParentNode.RemoveChild(nodeToRemove);
                break;
            }

            for (var i = curr.Item1.ChildNodes.Count - 1; i >= 0; i--)
            {
                nodeStack.Push(new Tuple<HtmlNode, HtmlNode>(curr.Item1.ChildNodes[i], copyNode));
            }
        }

        return newDoc;
    }

    private static HtmlDocument GetSecondPart(HtmlNode link)
    {
        var nodeStack = new Stack<HtmlNode>();
        var newDoc = new HtmlDocument();

        var currNode = link;
        while (currNode.ParentNode != null)
        {
            currNode = currNode.ParentNode;
            nodeStack.Push(currNode.CloneNode(false));
        }

        var parent = newDoc.DocumentNode;
        while (nodeStack.Count > 0)
        {
            var node = nodeStack.Pop();
            parent.AppendChild(node);
            parent = node;
        }

        var newLink = link.CloneNode(false);
        parent.AppendChild(newLink);

        currNode = link;
        var newParent = newLink.ParentNode;

        while (currNode.ParentNode != null)
        {
            var foundNode = false;
            foreach (var child in currNode.ParentNode.ChildNodes)
            {
                if (foundNode) newParent.AppendChild(child.Clone());
                if (child == currNode) foundNode = true;
            }

            currNode = currNode.ParentNode;
            newParent = newParent.ParentNode;
        }

        var nodeToRemove = NodeAndEmptyAncestors(newLink);
        nodeToRemove.ParentNode.RemoveChild(nodeToRemove);

        return newDoc;
    }

    private static HtmlNode NodeAndEmptyAncestors(HtmlNode node)
    {
        var currNode = node;
        while (currNode.ParentNode != null && currNode.ParentNode.ChildNodes.Count == 1)
        {
            currNode = currNode.ParentNode;
        }

        return currNode;
    }

Respuesta popular

Definitivamente no por . (Nota: esta era originalmente una etiqueta en la pregunta, ahora eliminada). Generalmente no soy uno para saltar al carro de The Pony is Coming, pero este es un caso en el que las expresiones regulares serían particularmente malas.

Primero, escribiría una función recursiva que elimine a todos los hermanos de un nodo que sigue a ese nodo, RemoveSiblingsAfter(node) y luego se llame a sí mismo a su padre , para que todos los hermanos que siguen al padre también se eliminen (y todos los hermanos siguiendo al abuelo, y así sucesivamente). Puede usar un XPath para encontrar los nodos en los que desea dividir, por ejemplo , doc.DocumentNode.SelectNodes("//a[@href='#']") , y llamar a la función en ese nodo. Cuando haya terminado, eliminaría el nodo de división en sí mismo, y eso es todo. Repetiría estos pasos para una copia del documento original, excepto que implementaría RemoveSiblingsBefore(node) para eliminar los hermanos que preceden a un nodo.

En su ejemplo, RemoveSiblingsBefore actuaría de la siguiente manera:

  1. <a href="#"> no tiene hermanos, así que recurra a los padres, <li> .
  2. <li> tiene un hermano precedente, <li>Bullet 1</li> así que elimínelo y hágalo a un padre, <ul> .
  3. <ul> no tiene hermanos, por lo tanto recuérdese sobre el padre, <p> .
  4. <p> tiene un hermano precedente— <p>Stuff</p> —así que lo elimine, y haga lo mismo con el padre, <div> .
  5. y así.


Related

Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow