Fractionner une chaîne html en N parties

c# html-agility-pack htmltidy regex

Question

Quelqu'un a-t-il un exemple de fractionnement d'une chaîne HTML (provenant d'un petit éditeur de MCE) et de son fractionnement en N parties à l'aide de C #?

J'ai besoin de diviser la chaîne de manière uniforme sans diviser les mots.

Je pensais simplement scinder le code HTML et utiliser HtmlAgilityPack pour essayer de réparer les balises brisées. Bien que je ne sois pas sûr de savoir comment trouver le point de partage, idéalement, il devrait reposer purley sur le texte plutôt que sur le langage HTML.

Quelqu'un a des idées sur la façon de s'y prendre?

METTRE À JOUR

Comme demandé, voici un exemple d'entrée et de sortie souhaitée.

CONTRIBUTION:

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

SORTIE (lorsque divisé en 3 colonnes):

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>

MISE À JOUR 2:

Je viens de jouer à Tidy HTML et cela semble bien fonctionner pour la correction des balises brisées. Ce serait donc une bonne option si je pouvais trouver un moyen de localiser les pintes divisées?

MISE À JOUR 3

En utilisant une méthode similaire à cette chaîne Truncate sur des mots entiers dans .NET C # , j'ai maintenant réussi à obtenir une liste de mots en texte brut qui constitueront chaque partie. Donc, disons qu'en utilisant Tidy HTML, j'ai une structure XML valide pour le HTML, et étant donné cette liste de mots, quiconque a une idée de ce qui serait maintenant le meilleur moyen de la scinder?

MISE À JOUR 4

Est-ce que n'importe qui peut voir un problème avec l'utilisation d'une regex pour trouver les index avec le HTML de la manière suivante:

Étant donné la chaîne de texte en clair "sit amet, consectetur", remplacez tous les espaces par l'expression rationnelle "(\ s | <(. | N) +?>) *" ", En théorie, trouver cette chaîne avec une combinaison d'espaces et / ou Mots clés

Je pourrais alors juste utiliser Tidy HTML pour réparer les balises HTML cassées?

Merci beaucoup

Mat

Réponse acceptée

Une solution proposée

Mec, c'est ma malédiction ! Apparemment, je ne peux pas surmonter un problème sans y consacrer un temps déraisonnable .

J'ai pensé à ça. J'ai pensé à HTML Tidy, et peut-être que ça marcherait, mais j'avais du mal à comprendre.

J'ai donc écrit ma propre solution.

Je l'ai testé sur votre contribution et sur une autre contribution que j'ai jeté moi-même. Cela semble bien fonctionner. Il y a sûrement des trous, mais cela pourrait vous fournir un point de départ.

En tout cas, mon approche était la suivante:

  1. Encapsulez la notion d'un seul mot dans un document HTML à l'aide d'une classe incluant des informations sur la position de ce mot dans la hiérarchie des documents HTML, jusqu'à un "sommet" donné. J'ai implémenté cela dans la classe HtmlWord ci-dessous.
  2. Créez une classe capable d'écrire une seule ligne composée de ces mots HTML ci-dessus, de telle sorte que les balises élément-début et élément-fin soient ajoutées aux emplacements appropriés. J'ai implémenté cela dans la classe HtmlLine ci-dessous.
  3. Écrivez quelques méthodes d'extension pour rendre ces classes immédiatement et intuitivement accessibles directement à partir d'un objet HtmlAgilityPack.HtmlNode . Celles-ci que j'ai implémentées dans la classe HtmlHelper ci-dessous.

Suis-je fou de faire tout cela? Probablement oui. Mais, vous savez, si vous ne pouvez pas trouver un autre moyen, vous pouvez essayer ceci.

Voici comment cela fonctionne avec votre exemple d'entrée:

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

Sortie:

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

Et maintenant pour le code:

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

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

Classe statique HtmlHelper

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

Conclusion

Juste pour répéter: c’est une solution globale; Je suis sûr que ça a des problèmes. Je le présente uniquement comme point de départ à prendre en compte - encore une fois, si vous êtes incapable d’obtenir le comportement que vous souhaitez par d’autres moyens.


Réponse populaire

Cette suggestion n’est qu’un bidouillage - avec un peu de chance, il existe un meilleur moyen.

En gros, vous voulez prendre un morceau de texte au format HTML et le scinder en morceaux plus petits tout en préservant la police, etc. de l'original. Je pense que vous pouvez charger le code HTML d'origine dans un contrôle RTF ou un objet Word, le diviser en plusieurs parties afin de préserver la mise en forme, puis exporter les éléments sous forme de code HTML séparé.

Il peut également y avoir un moyen d'utiliser HtmlAgilityPack comme celui-ci, si cela fournit un moyen simple d'extraire du texte avec les informations de formatage à partir du code HTML d'origine.




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi