最近写博客程序,要截取用户转贴过来的HTML内容作为文章的摘要,网上搜了半天也没有好的解决方案,自己大致写了一个,用整则表达式来找到所有的标签,然后把这些开始的标签放入栈中{stack},在后续的处理中如果有闭合的Tag则出栈,没有闭合的Tag,最后以出栈的形式给补上。这样就不会在截取的时候出现无法闭合的HTML了。
下面的代码是Winform的,函数可以用在任何地方
private void button1_Click(object sender, EventArgs e)
{
string str = "<div class='tit'><a href='/coollzh/blog/item/fbe81009e59ad1ca3ac763e7.html'>冯大辉谈数据库架构[转自InfoQ]</a></div>" +
"<div class=date>2008-06-10 10:05</div><br>" +
"<table style='table-layout:fixed'><tr><td><div class='cnt'><span >InfoQ中文站的读者们,大家好,今天我们有幸请到冯大辉先生参加我们的这个采访,大辉你好," +
"请跟大家介绍一下你是谁,在做些什么?</span> <a style='margin-left: 3px' href='http://www.infoq.com/cn/interviews/fengdahui-database-architecture#'>" +
"<img src='http://www.infoq.com/styles/cn/i/icon-collapse.gif;jsessionid=C0EB0B9C3E6AB8EAB137E6BF8E173474' border=0></a>" +
"<div style='display: block; color: #666666'>大家好,我叫冯大辉,是支付宝网络中国科技有限公司的DBA。现在主要是负责支付宝相关的数据库</div></div></td></tr></table>";
int count = 200;
MessageBox.Show(RegSubHtm(str,count));
}
private string RegSubHtm(string html, int length)
{
String subHtml = html.Substring(0, length);
// MessageBox.Show("subHtml:" + subHtml);
//判断被截取的html最后部分时候包含没有闭合的标签,如"</tb" "<div id="
bool isTagOpen = false;
foreach (char c in subHtml.ToCharArray().Reverse())
{
if (c == '<')
{
isTagOpen = true;
break;
}
if (c == '>')
{
isTagOpen = false; break;
}
}
if (isTagOpen)
{//如果包含没有闭合的标签,则继续获取内容,直到获取到闭合标签,即获取到 ">" 字符
string rightStr = html.Substring(length);
int idxTagEnd = rightStr.IndexOf(">");
subHtml += rightStr.Substring(0, idxTagEnd+1);
}
Stack<string> stack = new Stack<string>();
Regex reg = new Regex(@"(<.[^>]*>)|(<//.[^>]*>)|<br/>|<br>|<hr>|<hr/>|<img", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
MatchCollection mc = reg.Matches(subHtml);
foreach (var item in mc)
{
//对<br><hr>这类标签特殊处理
string toLowerItem = item.ToString().ToLower().Replace(" ", "");
if (toLowerItem==“<img” || toLowerItem == "<br>" || toLowerItem == "<br/>"||toLowerItem=="<hr>"||toLowerItem=="<hr/>")
continue;
if (item.ToString().StartsWith("</"))
{//碰到闭合的,则出栈
if (stack.Peek().Replace("<", "</") == item.ToString())
stack.Pop();
}
else
{//否则入栈
stack.Push(GetBeginTag(item.ToString()));
}
// MessageBox.Show(item.ToString());
}
while (true)
{//最后把所有没有闭合的标签出栈,补闭合标签,完成截取操作
if (stack.Count > 0)
{
subHtml += stack.Pop().Replace("<", "</");
}
else
{
break;
}
}
return subHtml;
}
private string GetBeginTag(string input)
{
int idx = input.IndexOf(" ");
if (idx > 0)
{
return input.Substring(0, idx) + ">";
}else
{
return input;
}
}
=======================
HTML文章中截取摘要的问题
博客系统通常的做法是,在博客的首页只显示文章的摘要,点击标题进入以后查看全文。显示哪一部分作为摘要是个比较特殊的问题,不同的系统都有自己不同的处理方式,有的是将摘要和扩展部分作为两个输入框,由用户自己决定哪些部分作为摘要,而且上下两部分都是完整的HTML,不存在截取的问题,就像本站使用的Serendipity系统。还有一种像WordPress,你可以自己在正文区中插入一个<!–More–>的标记,显示博客列表页的时候,会对文章以这个标记进行截取。不过我没有试过在一个Table的中间插入这个标记会怎么样,不过这种做法也是完全让用户自主决定如何截取文章,即使截错了,用户重新设置一下就OK了。
而我们公司的博客系统在设计之初,被很多人的意见所左右,(主要是许多没有真正写过博客的人的意见),意见是使用两个框的方式太麻烦,插入标记大部分恐怕也搞不懂,我们要给用户提供最简单的操作,用户只要写他的文章,如何截取交给系统就可以了。由此,让我陷入了一个疯狂改Bug的循环。
从整段的HTML中截取400个字作为摘要,如何处理?也许删除所有<>标签是最简单的办法,但是,博客的文字大家还是想要保留个性,而且有人想使用大字体,或者想对某篇文章使用大字体,以便更加显著,等等诸如此类的需求。总之,用户只想让自己的博客更漂亮,才不会管你怎么截取。
如果只是简单的按照400个字来截取,那么很可能会截到一个标签的中间,这半个标签可能就会导致后面的大段文本被作为标签内文字而不显示,直到遇到下一个结束符为止。于是首先要根据<>标记符,确保要截取的位置不在这两个标记中间。具体的做法是,找到一个<符号,看看里面的标记,去找它所对应的末尾标记,比如找到<p,就去找</p>,找到<div,就去找</div>。随后又发现,如果截取了半个Table,整个页面的布局就会错位,博客的侧边栏都跑到正文的下面去了,因为被作为Table里面的单元格来处理了。想来想去,决定如果出现Table的话,就在Table之前截断,结果导致许多文章的摘要里一个字都没有。然后又发现,不止Table会破坏布局,如果div的前后不对应,布局也会错位,因为我们的博客模板使用的是Div布局。用前面的截取方法存在一个问题,因为Div是可以嵌套的,连续的两个<div><div>标记,就会出现遇到第一个div标记的时候,直接跳到了第一个结尾的</div>标记上去,结果第二个<div>就没有结束符了。除此之外,font, span等等都是可以嵌套的。
后来又想了个办法,干脆把所有的div, font, span全部过滤掉,所<p></p>全部换成br,终于,基本上不会出现截错的问题了,但是,许多文章的分段和缩进是在div里面使用style来定义的(不知道这些用户的文章是从哪里拷过来的),结果许多人的博客首页文字都变成了一样的12px文字,而且密密麻麻挤在一起,极其不美观。再后来,还是有个用户报怨自己博客页面出错,无法打开。进去一看,他的博客文章里居然有N多<!–[IF[之类的标记,出现这种符号以后,用来截取的正则表达式就会报错。于是又在截取之前将注释类符号全部删掉。过了两天,他又报错,原来文章里还有<![IF这样的标签。God,你从哪里复制来的文章啊?
没办法了,即要保留格式,又要兼容各种不可预期的标签结构,这个问题一定要解决。
思路:想在不破坏格式的情况下截取文字,最保险的方法是,保留所有的格式。于是想到一个办法,只对文本内容使用字数限制,而留下所有的HTML标记。比如要取400个字,那么,从正文的第一个字符开始算,如果是普通的文本,放入结果变量,并将记数器加1。如果记数器已经到了400,就忽略这个字。如果字符是<,那么将其后所有的文本放入结果变量,直到>为止。这样,最后截取出来的摘要包括了400个文字,和一堆格式完整的HTML标签。(前提是他贴进来的时候HTML本来就是完整的)。
但是,如果在达到400字以后后面还有一些br, p, li的话,摘要后面就会出现很大的一段空白,那当然是不行的。因此还要把这些东西处理掉,数p的个数太麻烦,把p变成br,如果记数器已经到了400,忽略掉所有的br标签。对li也是,但是处理要特殊一点,因为不能在两个li中间截断。同理,可以对<tr><td>做同样的处理,这样就不会出现半个空白的表格了。
总体使用下来,这个效果还是可以接受的。虽然源代码里多了很多无用的代码(特别是那些从Word里面粘贴文章过来的人),但是从表面上看,截取是比较完美的。
附完整的C#代码:点击浏览或右键下载 (07年6月28号版本)
======================
/// <summary> /// 提取摘要,是否清除HTML代码 /// </summary> /// <param name="content"></param> /// <param name="length"></param> /// <param name="StripHTML"></param> /// <returns></returns> public static string GetContentSummary(string content, int length, bool StripHTML) { if (string.IsNullOrEmpty(content) || length == 0) return ""; if (StripHTML) { Regex re = new Regex("<[^>]*>"); content = re.Replace(content, ""); content = content.Replace(" ", "").Replace(" ", ""); if (content.Length <= length) return content; else return content.Substring(0, length) + "……"; } else { if (content.Length <= length) return content; int pos = 0, npos = 0, size = 0; bool firststop = false, notr = false, noli = false; StringBuilder sb = new StringBuilder(); while (true) { if (pos >= content.Length) break; string cur = content.Substring(pos, 1); if (cur == "<") { string next = content.Substring(pos + 1, 3).ToLower(); if (next.IndexOf("p") == 0 && next.IndexOf("pre") != 0) { npos = content.IndexOf(">", pos) + 1; } else if (next.IndexOf("/p") == 0 && next.IndexOf("/pr") != 0) { npos = content.IndexOf(">", pos) + 1; if (size < length) sb.Append("<br/>"); } else if (next.IndexOf("br") == 0) { npos = content.IndexOf(">", pos) + 1; if (size < length) sb.Append("<br/>"); } else if (next.IndexOf("img") == 0) { npos = content.IndexOf(">", pos) + 1; if (size < length) { sb.Append(content.Substring(pos, npos - pos)); size += npos - pos + 1; } } else if (next.IndexOf("li") == 0||next.IndexOf("/li") == 0) { npos = content.IndexOf(">", pos) + 1; if (size < length) { sb.Append(content.Substring(pos, npos - pos)); } else { if (!noli && next.IndexOf("/li") == 0) { sb.Append(content.Substring(pos, npos - pos)); noli = true; } } } else if (next.IndexOf("tr") == 0 || next.IndexOf("/tr") == 0) { npos = content.IndexOf(">", pos) + 1; if (size < length) { sb.Append(content.Substring(pos, npos - pos)); } else { if (!notr && next.IndexOf("/tr") == 0) { sb.Append(content.Substring(pos, npos - pos)); notr = true; } } } else if (next.IndexOf("td") == 0 || next.IndexOf("/td") == 0) { npos = content.IndexOf(">", pos) + 1; if (size < length) { sb.Append(content.Substring(pos, npos - pos)); } else { if (!notr) { sb.Append(content.Substring(pos, npos - pos)); } } } else { npos = content.IndexOf(">", pos) + 1; sb.Append(content.Substring(pos, npos - pos)); } if (npos <= pos) npos = pos+1; pos = npos; } else { if (size < length) { sb.Append(cur); size++; } else { if (!firststop) { sb.Append("……"); firststop = true; } } pos++; } } return sb.ToString(); } }
=====================
/// <summary>
/// 截取部分Html内容
/// </summary>
/// <param name="strHtml"></param>
/// <returns></returns>
public static string GetSubContent(string strHtml)
{
if (strHtml.Length > 1000)
strHtml = strHtml.Substring(0, 1000);
//最新版本把此行移到此处,效果待测
//strHtml = Server.HtmlDecode( strHtml );
if (strHtml.LastIndexOf(">") > -1)
{
strHtml = strHtml.Substring(0, strHtml.LastIndexOf(">"));
}
if (strHtml.LastIndexOf("<") > strHtml.LastIndexOf(">"))
{
int cnt = 0;
int pos = 0;
string tmpstr = strHtml.Substring(strHtml.LastIndexOf("<"));
while (true)
{
if (tmpstr.IndexOf("/"", pos) < 0)
{
break;
}
cnt++;
if (pos < tmpstr.Length)
{
pos++;
}
}
if (cnt % 2 > 0)
{
strHtml = strHtml + "/"";
}
cnt = 0;
pos = 0;
tmpstr = strHtml.Substring(strHtml.LastIndexOf("<"));
while (true)
{
if (tmpstr.IndexOf("'", pos) < 0)
{
break;
}
cnt++;
if (pos < tmpstr.Length)
{
pos++;
}
}
if (cnt % 2 > 0)
{
strHtml = strHtml + "'";
}
strHtml = strHtml + ">";
}
strHtml = AddEnding(strHtml, "<td>", "</td>");
strHtml = AddEnding(strHtml, "<td ", "</td>");
strHtml = AddEnding(strHtml, "<tr>", "</tr>");
strHtml = AddEnding(strHtml, "<tr ", "</tr>");
//strHtml = AddEnding(strHtml, "<thead>", "</thead>");
strHtml = AddEnding(strHtml, "<thead", "</thead>");
//strHtml = AddEnding(strHtml, "<table>", "</table>");
strHtml = AddEnding(strHtml, "<table", "</table>");
//strHtml = AddEnding(strHtml, "<span>", "</span>");
strHtml = AddEnding(strHtml, "<span", "</span>");
//strHtml = AddEnding(strHtml, "<div>", "</div>");
strHtml = AddEnding(strHtml, "<div", "</div>");
strHtml = AddEnding(strHtml, "<param", "</param>");
strHtml = AddEnding(strHtml, "<object", "</object>");
strHtml = AddEnding(strHtml, "<font", "</font>");
strHtml = AddEnding(strHtml, "<strong", "</strong>");
//strHtml = AddEnding(strHtml, "<p>", "</p>");
strHtml = AddEnding(strHtml, "<p ", "</p>");
//最新版本把此行移到上面,效果待测
//strHtml = Server.HtmlDecode(strHtml);
//divContent.InnerHtml = strHtml + "......[<a href=/"Articles/Article.aspx?ArticleID=" + article.ArticleID + "/"><font color=red>查看全文</font></a>]";
//divViewMore.InnerHtml = "";
return strHtml;
==================
最近开发一个项目,其中用到截取带html标记得字符串得功能,于是就参考一些文档写了这样一个方法来截取字符串,希望对大家的项目开发有帮助,全部代码如下:
/*
* 按字节长度截取字符串(支持截取带HTML代码样式的字符串)
* @param param 将要截取的字符串参数
* @param length 截取的字节长度
* @param end 字符串末尾补上的字符串
* @return 返回截取后的字符串
*/
public static string subStringHTML(string param,int length,string end)
{
string Pattern = null;
MatchCollection m = null;
StringBuilder result = new StringBuilder();
int n = 0;
char temp;
bool isCode = false; //是不是HTML代码
bool isHTML = false; //是不是HTML特殊字符,如
char[] pchar = param.ToCharArray();
for (int i = 0; i < pchar.Length; i++)
{
temp = pchar[i];
if (temp == '<')
{
isCode = true;
}
else if (temp == '&')
{
isHTML = true;
}
else if (temp == '>' && isCode)
{
n = n - 1;
isCode = false;
}
else if (temp == ';' && isHTML)
{
isHTML = false;
}if (!isCode && !isHTML)
{
n = n + 1;
//UNICODE码字符占两个字节
if (System.Text.Encoding.Default.GetBytes(temp + "").Length > 1)
{
n = n + 1;
}
}result.Append(temp);
if (n >= length)
{
break;
}
}
result.Append(end);
//取出截取字符串中的HTML标记
string temp_result = result.ToString().Replace("(>)[^<>]*(<?)","$1$2");
//去掉不需要结素标记的HTML标记
temp_result = temp_result.Replace(@"</?(AREA|BASE|BASEFONT|BODY|BR|COL|COLGROUP|DD|DT|FRAME|HEAD|HR|HTML|IMG|INPUT|ISINDEX|LI|LINK|META|OPTION|P|PARAM|TBODY|TD|TFOOT|TH|THEAD|TR|area|base|basefont|body|br|col|colgroup|dd|dt|frame|head|hr|html|img|input|isindex|li|link|meta|option|p|param|tbody|td|tfoot|th|thead|tr)[^<>]*/?>",
"");
//去掉成对的HTML标记
temp_result=temp_result.Replace(@"<([a-zA-Z]+)[^<>]*>(.*?)<//1>","$2");
//用正则表达式取出标记
Pattern = ("<([a-zA-Z]+)[^<>]*>");
m = Regex.Matches(temp_result,Pattern);ArrayList endHTML = new ArrayList();
foreach(Match mt in m)
{
endHTML.Add(mt.Result("$1"));
}
//补全不成对的HTML标记
for (int i = endHTML.Count - 1; i >= 0; i--)
{
result.Append("</");
result.Append(endHTML[i]);
result.Append(">");
}return result.ToString();
}如果对大家有帮助,请多支持,谢谢!