在N个部分中拆分html字符串

时间:2010-05-01 13:41:12

标签: c# regex html-agility-pack htmltidy

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

我需要均匀地分割字符串而不分割单词。

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

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

更新

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

INPUT:

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

OUTPUT(当分成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>

更新2:

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

更新3

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

更新4

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

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

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

非常感谢

马特

2 个答案:

答案 0 :(得分:17)

建议的解决方案

伙计,这是我的诅咒!我显然无法摆脱问题而不花费大量时间和无理的时间。

我想到了这个。我想到了HTML Tidy,也许它会起作用,但我无法绕过它。

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

我在你的输入和我自己拼凑的其他一些输入上测试了这个。它似乎工作得很好。肯定会有漏洞,但它可能会为你提供一个起点。

无论如何,我的方法是:

  1. 使用包含有关该单词在HTML文档层次结构中的位置的信息的类,在HTML文档中封装单个单词的概念,直到给定的“top”。我已经在下面的HtmlWord课程中实现了这个。
  2. 创建一个能够编写由上述HTML单词组成的单行的类,以便在适当的位置添加start-element和end-element标记。我已经在下面的HtmlLine课程中实现了这个。
  3. 编写一些扩展方法,以便直接从HtmlAgilityPack.HtmlNode对象直接访问这些类。我在下面的HtmlHelper课程中实现了这些。
  4. 我为这一切而疯狂吗?可能是。但是,你知道,如果你无法弄清楚任何其他方式,你可以尝试一下。

    以下是您的示例输入的工作原理:

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

    结论

    重申:这是一个综合解决方案;我确定它有问题。我只是把它作为你考虑的起点 - 再次,如果你不能通过其他方式得到你想要的行为。

答案 1 :(得分:0)

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

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

如果它提供了一种使用原始HTML格式信息提取文本的简单方法,也可能有这种方法使用HtmlAgilityPack。