eaglet

本博专注于基于微软技术的搜索相关技术
posts - 189, comments - 3705, trackbacks - 26, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

公告

通过盘古分词自定义规则功能实现软件版本号的提取

Posted on 2010-08-20 14:48 eaglet 阅读(...) 评论(...) 编辑 收藏

作者:eaglet

在某些软件下载网站的全文搜索应用中往往需要根据部分或者全部的版本号来匹配查询。然而对于版本信息的提取,无论是采用增加单词还是其他什么办法都无法很好的实现。好在盘古分词提供了自定义规则的接口,我们可以通过实现自己的自定义规则来完成一些特殊信息的提取。这篇文章也作为盘古分词自定义规则使用的一个示例文章,希望大家看了这篇文章后可以举一反三,实现诸如IP地址提取,Email 提取,网址提取等等特殊提取功能的自定义规则。

首先先说一下需求。

我们希望将版本信息字符串 比如 V1.2.1.0 从文章中提取出来,分词为 v/1.2.1.0/1.2.1/1.2 这种形式,这样无论搜索用户输入 1.2.1.0 还是1.2 或者 v 1.2.1 都可以匹配到相应的结果。

下面我来谈谈如何来完成这个功能。

首先 我们需要将盘古分词版本升级到 V2.3.1.0 或以上版本。

第二 我们需要将英文多元分词开关打开,打开方法是在 Pangu.xml 中做如下设置:

<EnglishMultiDimensionality>true</EnglishMultiDimensionality>

接下来 我们要实现自定义规则接口,其接口定义如下:

    /// <summary>
    /// 用户自定义规则接口
    /// </summary>
    public interface ICustomRule
    {
        string Text {get; set;}
        void AfterSegment(SuperLinkedList<WordInfo> result);
    }

其中

Text 为输入给分词器的原始字符串,盘古分词会将这个原始字符串带入到接口中。

AfterSegement 函数在盘古分词完成了分词工作后调用,输入的参数是分词的结果,这个结果以链表形势展现。

 

下面看提取版本号的接口实现,实现方法是遍历整个分词结果,发现单词分量为v 或者 V 时,就假设为版本号的开始,然后判断后面是否出现数字,如果出现数字

且位置和V靠的很近,则认为是版本号信息,进入提取版本号的状态。提取完后,删除中间的一些不必要的单词分量,然后将版本号的单词分量加入到v 这个单词后面。

具体可以看代码。代码也可以在 盘古分词主页 中下载,下载位置:主页中的 Download 页面 PanGu_SourceCode_V2.3.1.0.zip 这个文件。

PickupVersion.cs 这个文件在 Example\CustomRuleExample 这个目录下。

 

实现了这个接口后,我们编译成一个动态库,这里假设为 CustomRuleExample.dll

最后 我们需要将这个动态库拷贝到和PanGu.dll 相同的目录中,然后修改 PanGu.xml 配置文件,打开自定义规则开关,方法如下:

在 MatchOption 中将 CustomRule 这个开关设置为 true

  <MatchOptions>
   ....
      <CustomRule>true</CustomRule>
  </MatchOptions>

 

 
在 Parameters 中设置自定义程序集的文件名和类名
  <Parameters>
    ....
    <CustomRuleAssemblyFileName>CustomRuleExample.dll</CustomRuleAssemblyFileName>
    <CustomRuleFullClassName>CustomRuleExample.PickupVersion</CustomRuleFullClassName>
  </Parameters>

 

这些全部做完后,一个版本号提取的自定义规则就完成了。下面让我们看看分词结果

image

 

当然这个只是一个例子,不可能考虑所有的情况。比如在实际应用中,可能版本号不是以 v 起始,这个没有关系,因为盘古分词已经通过接口把所有信息都给了出来,大家可以根据这个例子举一反三,编写适合于自己应用的自定义规则程序。

 

PickupVersion 类的源码如下:

 

using System;
using System.Collections.Generic;
using System.Text;
using PanGu;
 
namespace CustomRuleExample
{
    /// <summary>
    /// 这个规则用于将文章中的版本号单独提出来
    /// V1.2.3.4 分词结果为
    /// v/1.2/1.2.3/1.2.3.4
    /// 这个规则要工作正常,需要将 EnglishMultiDimensionality 开关打开
    /// </summary>
    public class PickupVersion : ICustomRule
    {
        private string _Text;
 
        #region ICustomRule Members
 
        public string Text
        {
            get
            {
                return _Text;
            }
            set
            {
                _Text = value;
            }
        }
 
        /// <summary>
        /// 提取版本号
        /// </summary>
        /// <param name="result">盘古分词的结果</param>
        /// <param name="vWordNode">V 这个字符的第一个出现位置</param>
        /// <param name="lastNode">版本号的最后一个词</param>
        /// <param name="versionBeginPosition">版本号第一个词的起始位置</param>
        private void Pickup(SuperLinkedList<WordInfo> result, SuperLinkedListNode<WordInfo> vWordNode,
            SuperLinkedListNode<WordInfo> lastNode, int versionBeginPosition)
        {
            SuperLinkedListNode<WordInfo> node = vWordNode.Next;
            int lastPosition = lastNode.Value.Position + lastNode.Value.Word.Length;
 
            SuperLinkedListNode<WordInfo> end = lastNode.Next;
 
            while (node != end)
            {
                result.Remove(node);
                node = vWordNode.Next;
            }
 
            if (vWordNode.Value.Word == "V")
            {
                vWordNode.Value.Word = "v";
            }
 
            string version = _Text.Substring(versionBeginPosition, lastPosition - versionBeginPosition);
 
            int dotPosition = 0;
            int dotCount = 0;
 
            WordInfo verWord = null;
            dotPosition = version.IndexOf('.', dotPosition);
 
            while (dotPosition > 0)
            {
                verWord = null;
 
                if (dotCount > 0) //第一个点之前的版本号不提取
                {
                    //提取前n个子版本号
                    verWord = new WordInfo(version.Substring(0, dotPosition), POS.POS_D_K, 0);
                    verWord.Rank = 1; //这里设置子版本号的权重
                    verWord.Position = versionBeginPosition;
                    verWord.WordType = WordType.None;
                }
 
                dotCount++;
 
                dotPosition = version.IndexOf('.', dotPosition + 1);
 
                if (verWord != null)
                {
                    result.AddAfter(vWordNode, verWord);
                }
            }
 
            //提取完整版本号
            verWord = new WordInfo(version, POS.POS_D_K, 0);
            verWord.Rank = 5; //这里设置完整版本号的权重
            verWord.Position = versionBeginPosition;
            verWord.WordType = WordType.None;
            result.AddAfter(vWordNode, verWord);
 
        }
 
        public void AfterSegment(SuperLinkedList<WordInfo> result)
        {
            SuperLinkedListNode<WordInfo> node = result.First;
 
            SuperLinkedListNode<WordInfo> vWordNode = null;
            SuperLinkedListNode<WordInfo> lastNode = null;
            bool isVersion = false;
            int versionBeginPosition = -1;
 
            while (node != null)
            {
                if (vWordNode == null)
                {
                    if (node.Value.WordType == WordType.English)
                    {
                        //匹配 V 这个字符,作为版本号的开始
                        if (node.Value.Word.Length == 1)
                        {
                            if (node.Value.Word[0] == 'v' || node.Value.Word[0] == 'V')
                            {
                                vWordNode = node;
                                lastNode = node;
                            }
                        }
                    }
                }
                else if (vWordNode != null)
                {
                    //如果V有多元分词情况,忽略,跳到下一个
                    if (node.Value.Position == vWordNode.Value.Position)
                    {
                        node = node.Next;
                        continue;
                    }
 
                    //匹配数字或点
                    if (node.Value.WordType == WordType.Numeric ||
                        node.Value.Word == ".")
                    {
                        if (node.Value.Position - (lastNode.Value.Position + lastNode.Value.Word.Length) <= 1)
                        {
                            if (versionBeginPosition < 0)
                            {
                                versionBeginPosition = node.Value.Position;
                            }
 
                            isVersion = true;
                            lastNode = node;
 
                            node = node.Next;
                            continue;
                        }
                    }
 
                    if (isVersion)
                    {
                        //如果是版本号,提取版本号
                        Pickup(result, vWordNode, lastNode, versionBeginPosition);
                        vWordNode = null;
                        lastNode = null;
                        versionBeginPosition = -1;
                        isVersion = false;
                        continue;
                    }
                }
 
                node = node.Next;
            }
 
            if (isVersion)
            {
                //如果是版本号,提取版本号
                Pickup(result, vWordNode, lastNode, versionBeginPosition);
            }
        }
 
        #endregion
 
    }
}