c# - 在文本 block ( block 元素)末尾截断 HTML 内容

标签 c# html extract truncate

主要是当我们缩短/截断文本内容时,我们通常只是在特定的字符索引处截断它。无论如何,这在 HTML 中已经很复杂了,但我想使用不同的度量截断我的 HTML 内容(使用内容可编辑 div 生成):

  • 我会定义字符索引 N这将作为截断起点 限制
  • 算法将检查内容是否至少为 N字符长(仅文本;不包括标签);如果不是,它只会返回整个内容
  • 然后它会从 N-X 检查至 N+X字符位置(仅文本)并搜索块节点的结尾; X已预定义 偏移值(value)和可能约 N/5N/4 ;
  • 如果有多个区块节点在此范围内结束,算法将选择最接近限制索引N 结束的一个。
  • 如果在此范围内没有块节点结束,它将在同一范围内找到最近的词边界并选择最接近 N 的索引。并在该位置截断。
  • 返回带有有效 HTML 的截断内容(所有标签最后都关闭)

  • 我的内容可编辑生成的内容可能包括段落(带换行符)、预先格式化的代码块、块引号、有序和无序列表、标题、粗体和斜体(它们是内联节点,不应在截断过程中计算在内)等。实现当然会定义哪些元素是可能的截断候选。标题即使它们是块 HTML 元素也不会算作截断点,因为我们不想要寡居的标题。段落、列出单个项目、整个有序和无序列表、块引用、预先格式化的块、空元素等都是很好的。标题和所有内联块元素都不是。

    例子

    让我们以这个 stackoverflow 问题作为我想截断的 HTML 内容的示例。让我们将截断限制设置为 1000 偏移量为 250 字符 (1/4)。

    This DotNetFiddle显示此问题的文本,同时还在其中添加限制标记(|MIN| 代表字符 750,|LIMIT| 代表字符 1000 和 |MAX| 代表字符 1250)。

    从示例中可以看出两个区块节点之间最近的截断边界 到字符 1000 介于 </OL> 之间和 P (我的内容可编辑生成...)。这意味着我的 HTML 应该在这两个标签之间被截断,这将导致内容文本长度少于 1000 个字符,但保持截断的内容有意义,因为它不会只是在某些文本段落中间的某处截断。

    我希望这可以解释与该算法相关的事情应该如何工作。

    问题

    我在这里看到的第一个问题是我正在处理像 HTML 这样的嵌套结构。我还必须检测不同的元素(只有块元素,没有内联元素)。最后但并非最不重要的一点是,我将只需要计算字符串中的某些字符,而忽略那些属于标签的字符。

    可能的解决方案
  • 我可以通过创建一些表示内容节点及其层次结构的对象树来手动解析我的内容
  • 我可以将 HTML 转换为更易于管理的内容,例如 Markdown,然后只需搜索最接近我提供的索引的新行 N并转换回 HTML
  • 使用 HTML Agility Pack 之类的东西并用它替换我的 #1 解析,然后以某种方式使用 XPath 提取块节点并截断内容

  • 第二个想法
  • 我确信我可以通过做 #1 来做到这一点,但感觉我正在重新发明轮子。
  • 我认为 #2 没有任何 C# 库,所以我也应该手动将 HTML 转换为 Markdown 或运行,即 pandoc 作为外部进程。
  • 我可以使用 HAP,因为它非常擅长处理 HTML,但我不确定使用它的截断是否足够简单。恐怕我的自定义代码中的大部分处理仍将在 HAP 之外

  • 应该如何处理这种截断算法?我的头脑似乎太累了,无法达成共识(或解决方案)。

    最佳答案

    这是一些可以截断内部文本的示例代码。它使用 InnerText 的递归能力属性(property)和CloneNode方法。

        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, 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("http://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
    

    注意:我没有在单词边界处截断,只是在字符边界处截断,不,它根本没有遵循我评论中的建议:-)

    关于c# - 在文本 block ( block 元素)末尾截断 HTML 内容,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30926684/

    相关文章:

    c# Dictionary<object, T> 查找值

    javascript - 将条目索引页面存储到 cookie 并将访问者发送回该索引页面,无论文件夹如何

    c++ - 提取基类指针

    javascript - 有没有办法更改悬停时显示的文本?

    javascript - Onclick 函数在 <span> 中不起作用?

    pdf - 从研究论文的 PDF 中提取信息

    c++ - C++的数据记录和提取软件

    c# - 存储对值类型的引用?

    c# - 根据停靠在其中的 webBrowser 控件中的数据调整自定义用户控件的大小

    C#:结构构造函数性能