>我将定义将用作截断起始点限制的字符索引N
>算法将检查内容是否至少为N个字符长(仅文本;不计数标签);如果不是,它只会返回整个内容
>然后,它将从N-X到N X字符位置(仅文本)检查并搜索块节点的末尾; X是预定义的偏移值,可能约为N / 5到N / 4;
>如果几个块节点在此范围内结束,算法将选择最接近极限索引N的一个
>如果没有块节点在此范围内结束,则会在相同范围内找到最接近的单词边界,并选择最接近N的索引,并在该位置截断.
>使用有效的HTML返回截断的内容(所有标签在最后关闭)
我的内容可编辑的生成内容可能包含段落(带换行符),预格式化的代码块,块引号,有序和无序列表,标题,粗体和斜体(它们是内联节点,不应该在截断过程中计数)等.实施当然将定义哪些要素具体是可能的截断候选者.标题尽管它们是块HTML元素不会被视为截断点,因为我们不想要遗ed头.段落,列出单个项目,整个有序和无序列表,预格式化块,空白元素等都是好的.标题和所有内嵌块元素不是.
例
我们来看一下这个非常stackoverflow的问题,作为我要截断的HTML内容的例子.我们将截断限制设置为1000,偏移量为250个字符(1/4).
This DotNetFiddle显示此问题的文本,同时在其中添加极限标记(| MIN |表示字符750,| LIMIT |表示字符1000和| MAX |,表示字符1250).
从示例可以看出,两个块节点之间到字符1000的最近的截断边界在< / OL>之间.和P(我的内容可编辑生成…).这意味着我的HTML应该被截断在这两个标签之间,这将导致一点点不到1000个字符长的内容文本明智,但保留截断的内容有意义,因为它不会只是截断在某些文本段落的某个地方.
我希望这解释了这个算法应该如何工作.
问题
我在这里看到的第一个问题是我正在处理像HTML这样的嵌套结构.我还必须检测不同的元素(只有块元素,没有内联的元素).最后但并非最不重要的是,我只需要计算字符串中的某些字符,并忽略属于标签的字符.
可能的解决方案
>我可以通过创建一些表示内容节点及其层次结构的对象树来手动解析我的内容
>我可以将HTML转换成更容易管理的标记,然后只需搜索我提供的索引N的最近的新行,并转换回HTML
>使用像HTML Agility Pack这样的东西,用我的#1解析替换,然后以某种方式使用XPath来提取块节点并截断内容
第二个想法
我确定我可以做#1,但感觉到我正在重塑轮子.
>我不认为#2中有任何C#库,所以我也应该手动执行Markdown到Markdown,或者运行pandoc作为外部进程.
>我可以使用HAP,因为它是伟大的操纵HTML,但我不知道我的截断是否足够简单使用它.我的自定义代码中恐怕大部分的处理仍然在HAP之外
解决方法
public static HtmlNode TruncateInnerText(HtmlNode node,int length) { if (node == null) throw new ArgumentNullException("node"); // nothing to do? if (node.InnerText.Length < length) return node; HtmlNode clone = node.CloneNode(false); TruncateInnerText(node,clone,length); return clone; } private static void TruncateInnerText(HtmlNode source,HtmlNode root,HtmlNode current,int length) { HtmlNode childClone; foreach (HtmlNode child in source.ChildNodes) { // is expected size is ok? int expectedSize = child.InnerText.Length + root.InnerText.Length; if (expectedSize <= length) { // yes,just clone the whole hierarchy childClone = child.CloneNode(true); current.ChildNodes.Add(childClone); continue; } // is it a text node? then crop it HtmlTextNode text = child as HtmlTextNode; if (text != null) { int remove = expectedSize - length; childClone = root.OwnerDocument.CreateTextNode(text.InnerText.Substring(0,text.InnerText.Length - remove)); current.ChildNodes.Add(childClone); return; } // it's not a text node,shallow clone and dive in childClone = child.CloneNode(false); current.ChildNodes.Add(childClone); TruncateInnerText(child,root,childClone,length); } }
还有一个示例C#控制台应用程序,将把这个问题作为一个例子,并将其截断为500个字符.
class Program { static void Main(string[] args) { var web = new HtmlWeb(); var doc = web.Load("https://stackoverflow.com/questions/30926684/truncating-html-content-at-the-end-of-text-blocks-block-elements"); var post = doc.DocumentNode.SelectSingleNode("//td[@class='postcell']//div[@class='post-text']"); var truncated = TruncateInnerText(post,500); Console.WriteLine(truncated.OuterHtml); Console.WriteLine("Size: " + truncated.InnerText.Length); } }
当它运行它应该显示:
<div class="post-text" itemprop="text"> <p>Mainly when we shorten/truncate textual content we usually just truncate it at specific character index. That's already complicated in HTML anyway,but I want to truncate my HTML content (generated using content-editable <code>div</code>) using different measures:</p> <ol> <li>I would define character index <code>N</code> that will serve as truncating startpoint <em>limit</em></li> <li>Algorithm will check whether content is at least <code>N</code> characters long (text only; not counting tags); if it's not it will just return the whole content</li> <li>It would then</li></ol></div> Size: 500
注意:我没有在字边界截断,只是在字符边界,而不是,根本不符合我的意见建议:-)