【问题标题】:Get first 100 characters of HTML content without stripping tags在不剥离标签的情况下获取 HTML 内容的前 100 个字符
【发布时间】:2010-03-29 20:11:39
【问题描述】:

关于如何去除html标签有很多问题,但关于关闭它们的功能/方法的问题并不多。

情况是这样的。我有一个 500 个字符的消息摘要(包括 html 标签),但我只想要前 100 个字符。问题是,如果我截断消息,它可能位于 html 标记的中间……这会弄乱一些东西。

假设 html 是这样的:

<div class="bd">"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. <br/>
 <br/>Some Dates: April 30 - May 2, 2010 <br/>
 <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. <em>Duis aute irure dolor in reprehenderit</em> in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
 </p>
 For more information about Lorem Ipsum doemdloe, visit: <br/>
 <a href="http://www.somesite.com" title="Some Conference">Some text link</a><br/> 
</div>

我将如何获取前 100 个左右的字符? (虽然,理想情况下,这将是“内容”的前大约 100 个字符(在 html 标记之间)

我假设最好的方法是使用递归算法来跟踪 html 标签并附加任何会被截断的标签,但这可能不是最好的方法。

我的第一个想法是使用递归来计算嵌套标签,当我们达到 100 个字符时,寻找下一个“

这样做的原因是对现有文章进行简短摘要,而无需用户返回并提供所有文章的摘要。如果可能,我想保留 html 格式。

注意:请忽略 html 不是完全语义化的。这是我所见即所得的处理方式。

编辑:

我添加了一个潜在的解决方案(这似乎有效)我认为其他人也会遇到这个问题。我不确定它是不是最好的......而且它可能并不完全强大(事实上,我知道它不是),但我会很感激任何反馈

【问题讨论】:

  • 你的算法听起来不错。您可能需要一个不需要关闭的 HTML 标记列表:HR、BR、IMG
  • 不确定这是否适合您,但看起来很有趣:htmlagilitypack.codeplex.com
  • Html Agility 包听起来很有趣......我越想这个算法......我越意识到有很多边缘情况......
  • 边缘情况比你想象的还要多。毫无疑问,HTML 敏捷包是要走的路。

标签: c# asp.net html


【解决方案1】:

这是大多数情况下的解决方案。它不会处理不正确的 html 标签,以及像“ac”这样的情况。但它适用于我的目的,也许对其他人有帮助。

    /// <summary>
    /// Gets first number of characters from the html string without stripping tags
    /// </summary>
    /// <param name="htmlString">The html string, not encoded, pure html</param>
    /// <param name="length">The number of first characters to get</param>
    /// <returns>The html string</returns>
    public static string GetFirstCharacters(string htmlString, int length)
    {
        if (htmlString == null)
            return string.Empty;

        if(htmlString.Length < length)
            return htmlString;

        // regex to separate string on parts: tags, texts
        var separateRegex = new Regex("([^>][^<>]*[^<])|[\\S]{1}");
        // regex to identify tags
        var tagsRegex = new Regex("^<[^>]+>$");

        // separate string on tags and texts
        var matches = separateRegex.Matches(htmlString);

        // looping by mathes
        // if it's a tag then just append it to resuls,
        // if it's a text then append substing of it (considering the number of characters)
        var counter = 0;
        var sb = new StringBuilder();
        for (var i = 0; i < matches.Count; i++)
        {
            var m = matches[i].Value;

            // check if it's a tag
            if (tagsRegex.IsMatch(m))
            {
                sb.Append(m);
            }
            else
            {
                var lengthToCut = length - counter;

                var sub = lengthToCut >= m.Length
                    ? m
                    : m.Substring(0, lengthToCut);

                counter += sub.Length;
                sb.Append(sub);
            }
        }

        return sb.ToString();
    }

【讨论】:

  • 非常感谢,正是我需要的。
【解决方案2】:

如果您将 HTML 解析为 DOM 结构,然后开始遍历广度优先或深度优先(随心所欲),收集节点文本直到达到 100 个字符,该怎么办?

【讨论】:

  • 是的,这与我的想法一致......只是试图想象我的代码会是什么样子。我正在测试一些想法。
【解决方案3】:

我的建议是找到一个对 HTML 友好的遍历器(它可以让您像 XML 一样遍历 HTML),然后从头开始标签忽略标签本身,只计算标签中的数据。将其计入您的限制,然后一旦达到,只需关闭每个标签(我想不出任何不只是 /whatever 作为标签的标签)。

这应该工作得相当好,并且与您正在寻找的内容相当接近。

它完全不在 ol'noggin 的顶部,所以我假设会有一些棘手的部分,例如显示的属性值(例如链接标签值)。

【讨论】:

  • 同意,并且您可能希望为此目的查看 HTML Agility Pack。
  • 我最喜欢这个解决方案。虽然我结束了另一个项目,但 HTMLAgilityPack 肯定在我的列表中。一个使用它的例子会很棒,请注意.... ^_-
【解决方案4】:

过去我用正则表达式完成了这个。抓取内容,通过正则表达式去除标签,然后将其修剪到您想要的长度。

当然,这会删除所有 HTML,这正是我想要的。如果您希望保留 HTML,我会考虑不关闭打开的标签,而是删除打开的标签。

【讨论】:

  • 我从没想过删除打开的标签。嗯......不过,不确定它是否会按预期工作。我的第一直觉(阅读:懒惰)只是显示文本......用正则表达式去除标签......但我真的想要一个好的解决方案来尽可能保留内容。
  • "不确定它会按预期工作" 好吧,这就是问题所在。无论解决方案如何,都不会满足某些人的期望。我会提倡无标签选项,因为 sn-ps 列表通常希望拥有独立于源的自己的样式。
【解决方案5】:

我决定推出自己的解决方案……只是为了迎接挑战。

如果有人发现任何逻辑错误或效率低下,请告诉我。

我不知道这是否是最好的方法......但它似乎有效。可能在某些情况下它不起作用......如果 html 不正确,它可能会失败。

/// <summary>
/// Get the first n characters of some html text
/// </summary>
private string truncateTo(string s, int howMany, string ellipsis) {

    // return entire string if it's more than n characters
    if (s.Length < howMany)
        return s;

    Stack<string> elements = new Stack<string>();
    StringBuilder sb = new StringBuilder();
    int trueCount = 0;

    for (int i = 0; i < s.Length; i++) {
        if (s[i] == '<') {

            StringBuilder elem = new StringBuilder();
            bool selfclosing = false;

            if (s[i + 1] == '/') {

                elements.Pop(); // Take the previous element off the stack
                while (s[i] != '>') {
                    i++;
                }
            }
            else { // not a closing tag so get the element name

                while (i < s.Length && s[i] != '>') {

                    if ((s[i] >= 'a' && s[i] <= 'z') || (s[i] >= 'A' && s[i] <= 'Z')) {
                        elem.Append(s[i]);
                    }
                    else if (s[i] == '/' || s[i] == ' ') {

                        // self closing tag or end of tag name. Find the end of tag
                        do {
                            if (s[i] == '/' && s[i + 1] == '>') {
                                // at the end of self-closing tag. Don't store
                                selfclosing = true;
                            }

                            i++;
                        } while (i < s.Length && s[i] != '>');
                    }
                    i++;
                } // end while( != '>' )

                if (!selfclosing)
                    elements.Push(elem.ToString());
            } 
        }
        else {
            trueCount++;
            if (trueCount > howMany) {
                sb.Append(s.Substring(0, i - 1));
                sb.Append(ellipsis);
                while (elements.Count > 0) {
                    sb.AppendFormat("</{0}>", elements.Pop());
                }
            }
        }
    }

    return sb.ToString();
}

【讨论】:

  • Noooooooo 不要这样做,现在回头,以免为时已晚。给你自己一个 HTML 解析器库,除非有一些你不能使用的命令。
  • 听起来您是根据经验说话...别担心,我还没有完全致力于此解决方案...但这是一种相对有趣的写作方法。
【解决方案6】:

我使用 XmlReader 和 XmlWriter 来执行此操作: https://gist.github.com/2413598

正如其他人在这里提到的,您可能应该使用 SgmlReader 或 HtmlAgilityPack 来清理传入的字符串。

【讨论】:

    【解决方案7】:

    我看到了你的问题。在do while循环中有一个错误:

     } while (i < s.Length && s[i] != '>');
    

    应该替换为

     } while (i < s.Length && ***s[i+1]*** != '>');
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-01-18
      • 2011-09-11
      • 1970-01-01
      • 2015-11-07
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多