将一个HTML字符串分成N个部分

6
有人有用C#将来自tiny mce编辑器的html字符串平均分成N个部分的示例吗?我需要平均分割字符串而不分割单词。我想只是分割html并使用HtmlAgilityPack尝试修复破损的标签。但我不确定如何找到分割点,因为理想情况下它应该仅基于文本而不是html。有人有任何关于如何处理此问题的想法吗?
更新:如请求所示,以下是输入和期望输出的示例。
输入:
<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#中按整个单词截断字符串的方法,我现在已经成功获取了每个部分组成的纯文本单词列表。那么,假设我使用Tidy HTML为html创建了一个有效的XML结构,并且给出了这个单词列表,有人知道现在拆分它的最佳方法吗?

更新4:

有人能看出在以下方式中使用正则表达式查找HTML的索引是否有问题吗:

给定纯文本字符串“sit amet,consectetur”,使用正则表达式“(\ s |&lt;(.| \ n)+?&gt;)*”替换所有空格,理论上可以找到任何带有空格和/或标签的字符串。

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

非常感谢

Matt


将内容均匀分布到列中。是的,我知道你可以使用JavaScript和CSS,但我想要一种C#的方法。 - Matt Brailsford
4
可以给一个输入和期望输出的例子吗? - Avitus
已更新并附上示例。 - Matt Brailsford
无论你做什么,都不要使用正则表达式来处理这个。参见:https://dev59.com/X3I-5IYBdhLWcg3wq6do#1732454 - MusiGenesis
这不是关于定位分割点,而是关于您决定分割点将在哪里。因此,首先要做的是决定规则是什么。 - MusiGenesis
我可以使用类似于这个方法来在纯文本中(即去除HTML标签后)找到块的位置:https://dev59.com/QnI-5IYBdhLWcg3w6tFR现在的问题是如何将这些位置转换为HTML字符串中的位置? - Matt Brailsford
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();
    }
}

结论

再次强调:这只是一个临时解决方案;我相信它存在问题。我提供它只是为了让您考虑 -- 如果您无法通过其他方式获得所需的行为。


他并不是在分段落,而是试图将一个段落拆分成多个段落,同时保留原始字体格式。 - MusiGenesis
@MusiGenesis:是的,我在发布原始答案后才注意到这一点(我真丢人)。这是一个更棘手的问题!现在我正在认真考虑它。 - Dan Tao
是的,这是一个有趣的问题。祝你好运。 - MusiGenesis
哇塞!太棒了。我得试一试 =) 非常感谢你的帮助。 - Matt Brailsford
仅基于数量+1。我感同身受,伙计——不久前,我花了几天时间试图想出如何将动画渲染到Windows桌面(在图标下面)。在一个罕见的清醒时刻,我放弃了。 - MusiGenesis
@MusiGenesis:对我来说,那些偶尔的理智时刻确实非常罕见。 - Dan Tao

0

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

基本上,您想要将一块HTML格式的文本分割成较小的部分,仍然保留原始字体等。我认为您可以将原始HTML加载到RTF控件或Word对象中,在那里将其分成保留格式的片段,然后将这些片段作为单独的HTML输出。

如果HtmlAgilityPack提供了从原始HTML中提取带有格式信息的文本的简单方法,则还可以使用类似于此的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接