Dividere una stringa html in N parti

c# html-agility-pack htmltidy regex

Domanda

Qualcuno ha un esempio di spaccare una stringa html (proveniente da un piccolo editor di mce) e dividerlo in N parti usando C #?

Ho bisogno di dividere la corda in modo uniforme senza dividere le parole.

Stavo pensando di suddividere l'html e di usare HtmlAgilityPack per provare e correggere i tag danneggiati. Anche se non sono sicuro di come trovare il punto di divisione, come Idealmente dovrebbe essere basato puramente sul testo piuttosto che sull'html.

Qualcuno ha qualche idea su come andare su questo?

AGGIORNARE

Come richiesto, ecco un esempio di input e output desiderato.

INGRESSO:

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

OUTPUT (Quando diviso in 3 colonne):

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>

AGGIORNAMENTO 2:

Ho appena avuto un gioco con Tidy HTML e sembra funzionare bene nel correggere i tag danneggiati, quindi questa può essere una buona opzione se riesco a trovare un modo per localizzare le pinte divise?

AGGIORNAMENTO 3

Usando un metodo simile a questa stringa Truncate su intere parole in .NET C # , ora sono riuscito a ottenere un elenco di parole in testo semplice che compongono ciascuna parte. Quindi, ad esempio usando Tidy HTML, ho una struttura XML valida per l'html e, dato questo elenco di parole, qualcuno ha avuto un'idea su quale sarebbe ora il modo migliore per dividerlo?

AGGIORNAMENTO 4

Qualcuno può vedere un problema con l'utilizzo di una regex per trovare gli indici con l'HTML nel modo seguente:

Data la stringa di testo semplice "sit amet, consectetur", sostituisci tutti gli spazi con la regex "(\ s | <(. | \ N) +?>) *", In teoria trovando quella stringa con qualsiasi combinazione di spazi e / o tag

Potrei quindi usare Tidy HTML per correggere i tag html danneggiati?

Grazie molto

opaco

Risposta accettata

Una soluzione proposta

Amico, questa è una mia maledizione ! A quanto pare non riesco ad allontanarmi da un problema senza spendere troppo tempo, incluso un periodo di tempo irragionevole .

Ci ho pensato. Ho pensato a HTML Tidy, e forse avrebbe funzionato, ma ho avuto dei problemi a girarmela intorno.

Quindi, ho scritto la mia soluzione.

Ho provato questo sul tuo input e su qualche altro input che ho buttato insieme. Sembra funzionare abbastanza bene. Sicuramente ci sono dei buchi, ma potrebbe fornirti un punto di partenza.

Ad ogni modo, il mio approccio era questo:

  1. Incapsula la nozione di una singola parola in un documento HTML usando una classe che include informazioni sulla posizione di quella parola nella gerarchia del documento HTML, fino a un dato "top". Questo ho implementato nella classe HtmlWord seguito.
  2. Crea una classe che sia in grado di scrivere una singola riga composta da queste parole HTML sopra, in modo tale che i tag elemento iniziale e finale vengano aggiunti nelle posizioni appropriate. Questo ho implementato nella classe HtmlLine qui sotto.
  3. Scrivi alcuni metodi di estensione per rendere queste classi immediatamente e intuitivamente accessibili direttamente da un oggetto HtmlAgilityPack.HtmlNode . Questi ho implementato nella classe HtmlHelper seguito.

Sono pazzo di fare tutto questo? Probabilmente sì. Ma, sai, se non riesci a capire in nessun altro modo, puoi fare un tentativo.

Ecco come funziona con il tuo esempio di input:

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());

Produzione:

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

E ora per il codice:

HtmlWord class

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();
    }
}

Classe 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 static class

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();
    }
}

Conclusione

Giusto per ribadire: questa è una soluzione gettata insieme; Sono sicuro che abbia problemi. La presento solo come punto di partenza da prendere in considerazione - di nuovo, se non sei in grado di ottenere il comportamento che desideri attraverso altri mezzi.


Risposta popolare

Questo suggerimento è solo un trucco - si spera che ci sia un modo migliore.

Fondamentalmente, vuoi prendere una porzione di testo formattato in HTML e dividerlo in parti più piccole che conservano ancora il font ecc. Dell'originale. Penso che potresti caricare l'HTML originale in un controllo RTF o in un oggetto Word, dividerlo in parti che mantengono la formattazione e quindi produrre i pezzi come HTML separato.

Potrebbe anche esserci un modo per usare HtmlAgilityPack in questo modo, se fornisce un modo semplice per estrarre testo con informazioni di formattazione dall'HTML originale.



Related

Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché