HtmlAgilityPack所有子串的长度

时间:2012-12-09 23:45:49

标签: c# html-agility-pack

我有嵌套元素的html(大多只是 div p 元素) 我需要返回相同的html,但子字符串由给定数量的字母组成。显然字母计数不应该通过html标签枚举,而只计算每个html元素的InnerText字母。 Html结果应该保留适当的结构 - 任何结束标记,以保持有效的HTML。

示例输入:

<div>
    <p>some text</p>
    <p>some more text some more text some more text some more text some more text</p>
    <div>
        <p>some more text some more text some more text some more text some more text</p>
        <p>some more text some more text some more text some more text some more text</p>
    </div>
</div>

给定int length = 16输出应如下所示:

<div>
    <p>some text</p> // 9 characters in the InnerText here
    <p>some mo</p> // 7 characters in the InnerText here; 9 + 7 = 16;
</div>

请注意,字母数(包括空格)为16.随后的<div>被删除,因为字母数已达到变量length。请注意,输出html仍然有效。

我尝试了以下内容,但这并没有真正起作用。输出不符合预期:一些html元素会重复出现。

public static string SubstringHtml(this string html, int length)
{
    HtmlDocument doc = new HtmlDocument();
    doc.LoadHtml(html);
    int totalLength = 0;
    StringBuilder output = new StringBuilder();
    foreach (var node in doc.DocumentNode.Descendants())
    {
        totalLength += node.InnerText.Length;
        if(totalLength >= length)
        {
            int difference = totalLength - length;
            string lastPiece = node.InnerText.ToString().Substring(0, difference);
            output.Append(lastPiece);
            break;
        }
        else
        {
            output.Append(node.InnerHtml);
        }
    }
    return output.ToString();
}

更新

@SergeBelov提供了适用于第一个样本输入的解决方案,但是进一步的测试显示了输入问题,如下所示。

示例输入#2:

some more text some more text 
<div>
    <p>some text</p>
    <p>some more text some more text some more text some more text some more text</
</div>

鉴于变量int maxLength = 7;,输出应等于 some mo 。 由于此代码ParentNode = null

,因此无效
lastNode
    .Node
    .ParentNode
    .ReplaceChild(HtmlNode.CreateNode(lastNodeText.InnerText.Substring(0, lastNode.NodeLength - lastNode.TotalLength + maxLength)), lastNode.Node);

创建一个新的HtmlNode似乎没有帮助,因为它的InnterText属性是只读的。

1 个答案:

答案 0 :(得分:4)

下面的小型控制台程序说明了一种可能的方法,即:

  1. 选择相关的文本节点并计算它们的运行总长度;
  2. 根据需要获取尽可能多的节点以达到超过最大长度的运行总计;
  3. 从文档中删除除了在步骤## 1,2中选择的节点的祖先之外的所有元素节点;
  4. 剪切列表最后一个节点中的文本以适应最大长度。
  5. 更新:这应该仍然适用于第一个文本节点;可能需要Trim()从中移除空格,如下所示。

        static void Main(string[] args)
        {
            int maxLength = 9;
            string input = @"
                some more text some more text 
                <div>
                    <p>some text</p>
                    <p>some more text some more text some more text some more text some more text</
                </div>";
    
            var doc = new HtmlDocument();
            doc.LoadHtml(input);
    
            // Get text nodes with the appropriate running total
            var acc = 0;
            var nodes = doc.DocumentNode
                .Descendants()
                .Where(n => n.NodeType == HtmlNodeType.Text && n.InnerText.Trim().Length > 0)
                .Select(n => 
                {
                    var length = n.InnerText.Trim().Length;
                    acc += length;
                    return new { Node = n, TotalLength = acc, NodeLength = length }; 
                })
                .TakeWhile(n => (n.TotalLength - n.NodeLength) < maxLength)
                .ToList();
    
            // Select element nodes we intend to keep
            var nodesToKeep = nodes
                .SelectMany(n => n.Node.AncestorsAndSelf()
                    .Where(m => m.NodeType == HtmlNodeType.Element));
    
            // Select and remove element nodes we don't need
            var nodesToDrop = doc.DocumentNode
                .Descendants()
                .Where(m => m.NodeType == HtmlNodeType.Element)
                .Except(nodesToKeep)
                .ToList();
    
            foreach (var r in nodesToDrop)
                r.Remove();
    
            // Shorten the last node as required
            var lastNode = nodes.Last();
            var lastNodeText = lastNode.Node;
            var text = lastNodeText.InnerText.Trim().Substring(0,
                    lastNode.NodeLength - lastNode.TotalLength + maxLength);
            lastNodeText
                .ParentNode
                .ReplaceChild(HtmlNode.CreateNode(text), lastNodeText);
    
            doc.Save(Console.Out);
        }