Dividir una cadena html en N partes

c# html-agility-pack htmltidy regex

Pregunta

¿Alguien tiene un ejemplo de división de una cadena html (proveniente de un editor mce pequeño) y división en N partes usando C #?

Necesito dividir la cadena uniformemente sin dividir las palabras.

Estaba pensando en simplemente dividir el html y usar el HtmlAgilityPack para intentar arreglar las etiquetas rotas. Aunque no estoy seguro de cómo encontrar el punto de división, como idealmente, debería basarse únicamente en el texto en lugar de html.

¿Alguien tiene alguna idea sobre cómo hacer esto?

ACTUALIZAR

Según lo solicitado, aquí hay un ejemplo de entrada y salida deseada.

ENTRADA:

<p><strong>Lorem ipsum dolor sit amet, <em>consectetur adipiscing</em></strong> elit.</p>

SALIDA (cuando se divide en 3 cols):

Part1: <p><strong>Lorem ipsum dolor</strong></p>
Part2: <p><strong>sit amet, <em>consectetur</em></strong></p>
Part3: <p><strong><em>adipiscing</em></strong> elit.</p>

ACTUALIZACIÓN 2:

Acabo de jugar con Tidy HTML y parece que funciona bien para corregir etiquetas rotas, por lo que esta podría ser una buena opción si puedo encontrar una manera de localizar las pintas divididas.

ACTUALIZACIÓN 3

Usando un método similar a este Truncar cadena en palabras completas en .NET C # , ahora he logrado obtener una lista de palabras de texto sin formato que conformarán cada parte. Entonces, digamos que al usar Tidy HTML tengo una estructura XML válida para el html y, dada esta lista de palabras, ¿alguien tiene una idea de cuál sería la mejor manera de dividirlo?

ACTUALIZACIÓN 4

¿Alguien puede ver un problema con el uso de una expresión regular para encontrar los índices con el HTML de la siguiente manera?

Dada la cadena de texto plano "sit amet, consectetur", reemplace todos los espacios con la expresión regular "(\ s | <(. | \ N) +?>) *", En teoría, encontrando esa cadena con cualquier combinación de espacios y / o etiquetas

¿Podría simplemente usar Tidy HTML para arreglar las etiquetas html rotas?

Muchas gracias

Mate

Respuesta aceptada

Una solución propuesta

Hombre, esto es una maldición mía! Al parecer, no puedo alejarme de un problema sin gastar hasta e incluir una cantidad irrazonable de tiempo en él.

Pensé en esto. Pensé en HTML Tidy, y tal vez funcionaría, pero tuve problemas para rodearlo.

Entonces, escribí mi propia solución.

Probé esto en tu aporte y en algún otro aporte que junté yo mismo. Parece funcionar bastante bien. Seguramente hay agujeros en él, pero puede proporcionarle un punto de partida.

De todos modos, mi enfoque fue este:

  1. Encapsule la noción de una sola palabra en un documento HTML utilizando una clase que incluya información sobre la posición de esa palabra en la jerarquía de documentos HTML, hasta un "top" dado. Esto lo he implementado en la clase HtmlWord continuación.
  2. Cree una clase que sea capaz de escribir una sola línea compuesta de estas palabras HTML anteriores, de manera que las etiquetas de elemento inicial y elemento final se agreguen en los lugares apropiados. Esto lo he implementado en la clase HtmlLine continuación.
  3. Escriba algunos métodos de extensión para hacer que estas clases sean accesibles de forma inmediata e intuitiva directamente desde un objeto HtmlAgilityPack.HtmlNode . Estos los he implementado en la clase HtmlHelper continuación.

¿Estoy loco por hacer todo esto? Probablemente si. Pero, sabes, si no puedes resolverlo de otra manera, puedes intentarlo.

Así es como funciona con su entrada de muestra:

var document = new HtmlDocument();
document.LoadHtml("<p><strong>Lorem ipsum dolor sit amet, <em>consectetur adipiscing</em></strong> elit.</p>");

var nodeToSplit = document.DocumentNode.SelectSingleNode("p");
var lines = nodeToSplit.SplitIntoLines(3);

foreach (var line in lines)
    Console.WriteLine(line.ToString());

Salida:

<p><strong>Lorem ipsum dolor </strong></p>
<p><strong>sit amet, <em>consectetur </em></strong></p>
<p><strong><em>adipiscing </em></strong>elit. </p>

Y ahora para el código:

Clase HtmlWord

using System;
using System.Collections.Generic;
using System.Linq;

using HtmlAgilityPack;

public class HtmlWord {
    public string Text { get; private set; }
    public HtmlNode[] NodeStack { get; private set; }

    // convenience property to display list of ancestors cleanly
    // (for ease of debugging)
    public string NodeList {
        get { return string.Join(", ", NodeStack.Select(n => n.Name).ToArray()); }
    }

    internal HtmlWord(string text, HtmlNode node, HtmlNode top) {
        Text = text;
        NodeStack = GetNodeStack(node, top);
    }

    private static HtmlNode[] GetNodeStack(HtmlNode node, HtmlNode top) {
        var nodes = new Stack<HtmlNode>();

        while (node != null && !node.Equals(top)) {
            nodes.Push(node);
            node = node.ParentNode;
        };

        return nodes.ToArray();
    }
}

Clase HtmlLine

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;

using HtmlAgilityPack;

[Flags()]
public enum NodeChange {
    None = 0,
    Dropped = 1,
    Added = 2
}

public class HtmlLine {
    private List<HtmlWord> _words;
    public IList<HtmlWord> Words {
        get { return _words.AsReadOnly(); }
    }

    public int WordCount {
        get { return _words.Count; }
    }

    public HtmlLine(IEnumerable<HtmlWord> words) {
        _words = new List<HtmlWord>(words);
    }

    private static NodeChange CompareNodeStacks(HtmlWord x, HtmlWord y, out HtmlNode[] droppedNodes, out HtmlNode[] addedNodes) {
        var droppedList = new List<HtmlNode>();
        var addedList = new List<HtmlNode>();

        // traverse x's NodeStack backwards to see which nodes
        // do not include y (and are therefore "finished")
        foreach (var node in x.NodeStack.Reverse()) {
            if (!Array.Exists(y.NodeStack, n => n.Equals(node)))
                droppedList.Add(node);
        }

        // traverse y's NodeStack forwards to see which nodes
        // do not include x (and are therefore "new")
        foreach (var node in y.NodeStack) {
            if (!Array.Exists(x.NodeStack, n => n.Equals(node)))
                addedList.Add(node);
        }

        droppedNodes = droppedList.ToArray();
        addedNodes = addedList.ToArray();

        NodeChange change = NodeChange.None;
        if (droppedNodes.Length > 0)
            change &= NodeChange.Dropped;
        if (addedNodes.Length > 0)
            change &= NodeChange.Added;

        // could maybe use this in some later revision?
        // not worth the effort right now...
        return change;
    }

    public override string ToString() {
        if (WordCount < 1)
            return string.Empty;

        var lineBuilder = new StringBuilder();

        using (var lineWriter = new StringWriter(lineBuilder))
        using (var xmlWriter = new XmlTextWriter(lineWriter)) {
            var firstWord = _words[0];
            foreach (var node in firstWord.NodeStack) {
                xmlWriter.WriteStartElement(node.Name);
                foreach (var attr in node.Attributes)
                    xmlWriter.WriteAttributeString(attr.Name, attr.Value);
            }
            xmlWriter.WriteString(firstWord.Text + " ");

            for (int i = 1; i < WordCount; ++i) {
                var previousWord = _words[i - 1];
                var word = _words[i];

                HtmlNode[] droppedNodes;
                HtmlNode[] addedNodes;

                CompareNodeStacks(
                    previousWord,
                    word,
                    out droppedNodes,
                    out addedNodes
                );

                foreach (var dropped in droppedNodes)
                    xmlWriter.WriteEndElement();
                foreach (var added in addedNodes) {
                    xmlWriter.WriteStartElement(added.Name);
                    foreach (var attr in added.Attributes)
                        xmlWriter.WriteAttributeString(attr.Name, attr.Value);
                }

                xmlWriter.WriteString(word.Text + " ");

                if (i == _words.Count - 1) {
                    foreach (var node in word.NodeStack)
                        xmlWriter.WriteEndElement();
                }
            }
        }

        return lineBuilder.ToString();
    }
}

HtmlHelper clase estática

using System;
using System.Collections.Generic;
using System.Linq;

using HtmlAgilityPack;

public static class HtmlHelper {
    public static IList<HtmlLine> SplitIntoLines(this HtmlNode node, int wordsPerLine) {
        var lines = new List<HtmlLine>();

        var words = node.GetWords(node.ParentNode);

        for (int i = 0; i < words.Count; i += wordsPerLine) {
            lines.Add(new HtmlLine(words.Skip(i).Take(wordsPerLine)));
        }

        return lines.AsReadOnly();
    }

    public static IList<HtmlWord> GetWords(this HtmlNode node, HtmlNode top) {
        var words = new List<HtmlWord>();

        if (node.HasChildNodes) {
            foreach (var child in node.ChildNodes)
                words.AddRange(child.GetWords(top));
        } else {
            var textNode = node as HtmlTextNode;
            if (textNode != null && !string.IsNullOrEmpty(textNode.Text)) {
                string[] singleWords = textNode.Text.Split(
                    new string[] {" "},
                    StringSplitOptions.RemoveEmptyEntries
                );
                words.AddRange(
                    singleWords
                        .Select(w => new HtmlWord(w, node.ParentNode, top)
                    )
                );
            }
        }

        return words.AsReadOnly();
    }
}

Conclusión

Solo para reiterar: esta es una solución combinada; Estoy seguro de que tiene problemas. Lo presento solo como un punto de partida para que lo tenga en cuenta, nuevamente, si no puede obtener el comportamiento que desea por otros medios.


Respuesta popular

Esta sugerencia es solo un truco, espero que haya una mejor manera.

Básicamente, usted desea tomar un trozo de texto con formato HTML y dividirlo en partes más pequeñas que aún conservan la fuente, etc. del original. Creo que podría cargar el HTML original en un control RTF o en un objeto de Word, dividirlo en partes que conserven el formato y luego generar las piezas como HTML separado.

También puede haber una forma de usar HtmlAgilityPack de esta manera, si proporciona una forma sencilla de extraer texto con información de formato del HTML original.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué