`
lonestone
  • 浏览: 90826 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

转Flex:让CS2支持中文搜索,并带源码下载,经过测试有效,效果不错

阅读更多
前面几节里有朋友提到CS2对中文搜索支持的不好,那么这一节就提前到这里来讲讲怎样解决CS2对中文搜索的问题。

我们都知道,英文和中文语言上的不同导致了处理英文和中文的不同方法,最明显的不同就是英文是以单词为最小单位,而中文则是以字为最小单位,这样造成了程序上的不一样,而在CS2中默认的搜索模块只考虑到英文这一方面,对中文相当于不认识了,怎样让其认识中文呢,这就需要我们来分析CS2处理搜索的机制。
虽然CS2中使用的分词搜索,但其技术本身并不是很复杂,我们完全可以自己动手来改造其搜索效果。简单一点我就直截了当的介绍其分词搜索的原理吧。与我们常见的一些使用SQL语句在数据结构里搜索不同,分词搜索把需要搜索的数据预先进行索引(这里的索引不是通常所指的数据库本身的索引),在搜索引擎里的文章最小单位是词,英文按照单词,也就是按空格分开,中文就需要用到分词技术了,把一篇文章智能的分成多个词语的组合的技术,这也是搜索技术的核心,搜索结果的理想程度很大程度取决于分词的理想程度,把一篇文章分开为词语的组合后,将其内容逐个记录并保存权重值,当然这里也涉及到一些高级技术,在CS中使用了较简单的方式,直接记录词在其文章中的信息。搜索文章的时候在此即可快速定位到需要的文章。说了这么多感觉比较抽象,还是结合实例来说吧。
打开解决方案我们会看到有CommunityServerSearchBarrel这样的项目,在这个项目里都是一些job和Item形式的类,这些类就是完成索引的关键类,针对不同应用都有不同的job,说到Job,在之前的系列已经介绍过其工作方式就是在后台单独的进程里运行的组件,在CommunityServer.config的jobs节点里可以对这些Job进行配置,我们可以在这里配置索引的间隔时间。其原理即为,当到了设定的时间间隔,针对不同应用的SearchJob在后台独立的线程开始工作,先判断是否出现了未进行索引的文章如果有新的文章出现则读取出来进行索引(一般为几十条数据),索引完后进行记录,下次就不再重复进行了,直到文章被修改后再次进行。
简单了解了这么多之后让我们来解决问题吧,CS2不支持中文究竟问题出在哪里呢,让我们打开数据库的cs_SearchBarrel表,我们可以看到这里就是保存分词结果的地方,不过可以看到正确的一个个英文单词却很难看到一个正确的中文词语,这就是为什么CS2对中文支持的这么不好了,CS2在搜索时是检索这个表的数据的,这个表数据有问题当然就没有办法检索到正确的信息了。好了,现在已经很明确了,我们就是需要修改CS2对中文的分词,分词是一个复杂的技术,我们可以利用现有的分词组件来帮助我们。
博客园真是个好地方,在正需要分词组件的时候Eunge兄就发布了免费版本(虽然没开放源码,不过能用就行,何必太叫真呢),真是及时雨啊,无论分词效果怎样,这总归是个解决方案,我试用了它的组件,感觉还不错,能胜任一般的应用了,于是就拿了过来,发布网址参见:http://lovinger2000.cnblogs.com/archive/2006/03/02/ChineseTokenizerDll.HTML 那到这个好东东之后就可以开始我们的改造了:

那么我们应该如何改造呢,对,把系统分词的方法替换成中文组件的方法即可,那么怎样嵌入我们的中文分词组件呢,当然是顺潮流使用代理模式了,这样如果我们有更好的分词组件扩展起来是非常容易的。首先来看看CommunityServerSearchBarrel这个项目,这个项目就是处理搜索相关的地方。找到核心的调用让我们看看SearchJob.cs的Index方法,这个方面就是索引文章的方法了,在这里我们可以看到string[] wordsToIndex = SearchTerms.CleanSearchTerms(contentToIndex);这样的语句,不难理解,这个就是把文档的各部分转换为词的字符串数组了。SearchTerms.CleanSearchTerms这个方法在CommunityServerComponents项目的Search/SearchTerms.cs下,在这里就是要修改的核心了,我们可以看到其中都是对英文单词的处理,当然,我们只要替换掉对英文的处理为中文分词原则上就可以,主要的修改为:

CleanSearchTerms
<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->publicstaticstring[]CleanSearchTerms(stringsearchTerms)
{

if(searchTerms==null)
returnnewstring[0];

//ForcethesearchTermstolowercase
//
searchTerms=searchTerms.ToLower();
try
{

//Stripanymarkupcharacters
//
searchTerms=Transforms.StripHTMLXMLTags(searchTerms).Replace("&nbsp;","");

//Removenon-alpha/numericcharacters
//Editbylf没必要去掉特殊字符,有些时候仍然需要搜索它
//searchTerms=Regex.Replace(searchTerms,"[^\\w]","",RegexOptions.Compiled|RegexOptions.Multiline);

//Replacespecialwordswithsymbols
/**/////Editbylf这几句要出错
//searchTerms=Regex.Replace(searchTerms,"\\bor\\b","||",RegexOptions.Compiled|RegexOptions.Multiline);
//searchTerms=Regex.Replace(searchTerms,"\\band\\b","&&",RegexOptions.Compiled|RegexOptions.Multiline);

//Finallyremoveanyextraspacesfromthestring
//空格也不需要替换,中文分词自动替换
//searchTerms=Regex.Replace(searchTerms,"{1,}","",RegexOptions.IgnoreCase|RegexOptions.Compiled|RegexOptions.Multiline);

}
catch(System.Exceptionex)
{
try
{
CSExceptioncsEx=newCSException(CSExceptionType.SearchUnknownError,"中文分词异常,这个是在替换特殊字符的时候的异常",ex);
csEx.Log();
}
catch{}
}
if(searchTerms.Trim()!=string.Empty)
{
//Editbylf设置为中文搜索
try{returnChineseTokenizeProvider.Instance().ChineseTokenize(searchTerms);}
catch(System.Exceptionex)
{
try
{
CSExceptioncsEx=newCSException(CSExceptionType.SearchUnknownError,"中文分词异常",ex);
csEx.Log();
}
catch{}
}
}

returnsearchTerms.Split('');
}

怎样引入我们的分词组件呢,让我们先建立一个抽象的Provider来作为代理的基础类,在这里建立名为ChineseTokenizeProvider,当然是在CommunityServerComponents项目里建立了。代码如下:

ChineseTokenizeProvider
<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingCommunityServer.Components;
usingCommunityServer.Configuration;

namespaceCommunityServer.Components
{
/**////<summary>
///中文分词的支持
///</summary>
///<remarks>Editbylf</remarks>
publicabstractclassChineseTokenizeProvider
{

Instance#regionInstance

privatestaticChineseTokenizeProvider_defaultInstance=null;

staticChineseTokenizeProvider()
{
CreateDefaultCommonProvider();
}


publicstaticChineseTokenizeProviderInstance()
{
return_defaultInstance;
}


privatestaticvoidCreateDefaultCommonProvider()
{

CSConfigurationconfig=CSConfiguration.GetConfig();

ProviderchineseProviders=(Provider)config.Providers["ChineseTokenizeProvider"];

_defaultInstance=Activator.CreateInstance(Type.GetType(chineseProviders.Type))asChineseTokenizeProvider;
}

#endregion

publicabstractstring[]ChineseTokenize(stringinput);

}//class
}

代码沿袭了CS中处理数据提供者的方式,只是这里使用的是分词提供方法,建立了基类后让我们建立一个扩展的代理层吧,这里我命名为Felix.NET.ChineseTokenWraper的项目其实就一个方法,也就是重写继承基类的抽象函数。如下:

Felix.NET.ChineseTokenWraper
<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingCommunityServer.Components;
usingSj110.Com.Chinese;

namespaceFelix.NET.ChineseTokenWraper
{
publicclassChineseTokenizer:ChineseTokenizeProvider
{

ChineseTokenizeProvider成员#regionChineseTokenizeProvider成员

publicoverridestring[]ChineseTokenize(stringinput)
{
List<string>resultList=Tokenizer.Tokenize(input);
string[]mystring=newstring[resultList.Count];
resultList.CopyTo(mystring);
returnmystring;
}

#endregion
}
}

当然完成了这些后还需要在配置文件加上这个Provider的配置,打开CommunityServer.config文件,在Providers配置节里添加如下代码:

<!--Editbylf加入对中文分词的支持-->
<add
name="ChineseTokenizeProvider"
type="Felix.NET.ChineseTokenWraper.ChineseTokenizer,Felix.NET.ChineseTokenWraper"
/>

处理完这些后就可以说大功告成了一半了,只是还有很多语言方面的问题需要我们在调试的时候处理,具体的处理细节我就不细说了,自己调试调试就ok了,我也放上自己的CommunityServerSearchBarrel项目,主要修改也就是这个项目了。
发现写这个文档怎么这么费力,好像还没说得很清楚,不过实在精力有限,还有很多事情等着我做,要完善这个功能还需要你细心调试,这里也就起到引个路子的作用,如果它能帮到你,我花这些功夫就没白费了。

源代码下载(修正了本文一个遗漏点,即2字词的索引,英文对于3个字母不处理)

这个方法是我测试过,可以使用的,并且效果不错,可以到 http://bbs.cnfdc.com.cn 测试。

点这里直接测试

不过修正后需要将cs_posts表的isindexed字段置为0,还有cs_sections表issearchable字段置为1,然后删除cs_SearchBarrel里面的所有记录。重起cs,过15分钟后,cs就开始建立索引了,这个时候你打开cs_SearchBarrel表,如果看到的都是汉字单词,那么就恭喜你,成功了。根据你的帖子量,很快你的搜索结果就很可观了。

补充:改进cs搜索结果的高亮显示

首先保证你的中文搜索按照上面的说明修改成功,然后打开Skin-SearchResults.ascx,修改下面这段

<atarget="_blank"href="<%#DataBinder.Eval(Container.DataItem,"Url")%>"><%...#Formatter.GetBodySummary(DataBinder.Eval(Container.DataItem,"BestMatch").ToString(),350,earchTextTop.Text,System.Drawing.Color.Black,System.Drawing.Color.Yellow)%></a>
<atarget="_blank"href="<%#DataBinder.Eval(Container.DataItem,"Url")%>"><%...#Formatter.GetBodySummary(DataBinder.Eval(Container.DataItem,"BestMatch").ToString(),350,ChineseTokenizeProvider.Instance().ChineseTokenize(SearchTextTop.Text),System.Drawing.Color.Black,System.Drawing.Color.Yellow)%></a>

然后修改\Components\Components\Formatter.cs里面的

public static string GetBodySummary(string text, int size, string[] highlightWords, Color color, Color bgColor)

方法,不知道官方的想法,这里对于字符串数组高亮的显示处理不能理解,修改为

publicstaticstringGetBodySummary(stringtext,intsize,string[]highlightWords,Colorcolor,ColorbgColor)
...{
//EAD6/27/04:NewfunctionIwroteamonthagotostripall
//tagsoutandreplacewithlinebreaks.Inthiscase,weneedto
//insert<br/>sinplaceoflinebreakssoitlookssomewhatneat.
//
//LN6/23/04:DonotHtmlencodethebody
//postBody=Globals.HtmlEncode(Transforms.StripHtmlXmlTags(postBody));
//postBody=Transforms.StripHtmlXmlTags(postBody);
stringpostBody=Formatter.StripAllTags(text);

//Weonlywanttodisplaysomeofthebody
//
if(size>0&&postBody.Length>size)
...{
intwhitespace=0;
//Clipthebody
postBody=postBody.Substring(0,size);

//Findthelastoccurenceofaspace
whitespace=postBody.LastIndexOf("");

if(whitespace==-1)
whitespace
=size;

//RebuildpostBodystring
postBody=postBody.Substring(0,whitespace)+"...";
}


//Doanywordhighlighting
//
if(highlightWords.Length>0)
...{
//注释:王勇,修改高亮显示,支持中文分词
//stringdelimitedString;

/**/////Splitanddelimitstring
////

//delimitedString=Transforms.ToDelimitedString(highlightWords,"|");

/**/////TDD7/19/2004
////thisstringisusedastheregexpattern.ifitcontainsspecialcharacterslike(,*$it
////throwanerrorsincethesearespecialinstructioncharacterstoRegex.Wemustescapethese
////specialcharactersiftheyareusedassearchstrings.

//delimitedString=Regex.Escape(delimitedString);
/**/////Performreplacement
////

foreach(stringdelimitedStringinhighlightWords)
...{
postBody
=Regex.Replace(postBody,delimitedString,"<spanstyle="color:"+color.Name+";background-color:"+bgColor.Name+""><b>$0</b></span>",RegexOptions.IgnoreCase|RegexOptions.Compiled|RegexOptions.Multiline);
}

}


returnpostBody;
}

重新编译,测试,OK,这样你的cs就可以自动按照分词来高亮每一个关键字了。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics