6

有没有人有拆分 html 字符串(来自小型 mce 编辑器)并使用 C# 将其拆分为 N 部分的示例?

我需要在不拆分单词的情况下均匀拆分字符串。

我正在考虑拆分 html 并使用 HtmlAgilityPack 尝试修复损坏的标签。虽然我不确定如何找到分割点,但理想情况下它应该基于文本而不是 html 的purley。

有人对如何解决这个问题有任何想法吗?

更新

根据要求,这里是输入和所需输出的示例。

输入:

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

输出(分成 3 列时):

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>

更新 2:

我刚刚玩过 Tidy HTML,它似乎可以很好地修复损坏的标签,所以如果我能找到一种方法来定位拆分品脱,这可能是一个不错的选择?

更新 3

在 .NET C# 中使用类似于此Truncate string on whole words的方法,我现在设法获得了构成每个部分的纯文本单词列表。因此,假设使用 Tidy HTML,我有一个有效的 html 结构,并且给出了这个单词列表,任何人都知道现在拆分它的最佳方法是什么?

更新 4

任何人都可以看到使用正则表达式以下列方式查找带有 HTML 的索引的问题:

给定纯文本字符串“sit amet, consectetur”,用正则表达式“(\s|<(.|\n)+?>)*”替换所有空格,理论上可以找到具有空格和/或任意组合的字符串标签

然后我可以使用 Tidy HTML 来修复损坏的 html 标签吗?

非常感谢

马特

4

2 回答 2

17

建议的解决方案

伙计,这是我的诅咒显然,如果不花费不合理的时间来解决问题,我就无法摆脱问题。

我想过这个。我想到了 HTML Tidy,也许它会起作用,但我很难理解它。

所以,我写了自己的解决方案。

我根据您的输入和我自己拼凑的其他输入对此进行了测试。它似乎工作得很好。当然其中有漏洞,但它可能会为您提供一个起点。

无论如何,我的方法是这样的:

  1. 使用一个类将单个单词的概念封装在 HTML 文档中,该类包含有关该单词在 HTML 文档层次结构中的位置的信息,直到给定的“顶部”。这是我在HtmlWord下面的课程中实现的。
  2. 创建一个能够编写由上述 HTML 单词组成的单行的类,以便在适当的位置添加开始元素和结束元素标签。这是我在HtmlLine下面的课程中实现的。
  3. 编写一些扩展方法,使这些类可以直接从HtmlAgilityPack.HtmlNode对象中直接、直观地访问。这些我已经在HtmlHelper下面的课程中实现了。

我为做这一切而疯狂吗?大概是。但是,你知道,如果你想不出任何其他方法,你可以试试这个。

以下是它如何与您的示例输入一起使用:

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

输出:

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

现在是代码:

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

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 静态类

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

结论

重申一下:这是一个综合解决方案;我确定它有问题。我仅将其作为您考虑的起点——同样,如果您无法通过其他方式获得您想要的行为。

于 2010-05-01T14:20:57.030 回答
0

这个建议只是一个技巧 - 希望有更好的方法。

基本上,您希望获取一大块 HTML 格式的文本并将其拆分为仍保留原始字体等的较小部分。我认为您可以将原始 HTML 加载到 RTF 控件或 Word 对象中,将其拆分为保留格式的片段,然后将这些片段作为单独的 HTML 输出。

如果 HtmlAgilityPack 提供了一种从原始 HTML 中提取带有格式信息的文本的简单方法,则可能还有一种方法可以像这样使用 HtmlAgilityPack。

于 2010-05-01T14:30:29.287 回答