“基于关键字匹配的文本过滤系统”配置文件的设计和实现(C/C++源码)

本文原始链接:http://blog.csdn.net/liigo/archive/2009/10/29/4744700.aspx

作者:liigo, 2009/10/29

转载请注明出处:http://blog.csdn.net/liigo

 

  假设有一个基于关键字匹配的文本过滤系统,或类似的系统,需要一个配置文件,用于设定欲过滤的关键字列表。该怎么设计这样一个配置文件呢?又该如何编码实现呢?此文将给出一个可行的方案。这是本人(liigo)重复发明轮子系列文章的新一篇。

  因为是一个小型应用系统,我对配置文件提出的要求是:简单直观,易于实现,同时保持足够的灵活性和可扩展性。

我对配置文件的设计结果如下:

  文件是纯文本的,以行为处理单位;
  行与行的分隔符可以是/r/n的任意组合,即支持WIN/LINUX/UNIX/MAC等多种换行模式;
  行首以 "text:" 或 "literal:" 为前缀的,后面可以跟一个关键字常量,也可以跟“以半角逗号分隔的”多个关键字常量;
  行首以 "regex:" 为前缀的,后面可以跟一个正则表达式文本;
  ":" 或 "," 后面可以有任意多个空白字符,解析时可自动略过;
  如果没有歧义,可以省略行首的 "text:" 或 "literal:"。

根据以上设计结果,下面是一个合法的配置文件内容示例:

hello love,home, java,c++ literal: a,b,c,,,, regex: [A-Z0-9._%-]+@[A-Z0-9.-]+/.[A-Z]{2,4} by liigo

  配置文件内容很简单,没有很特殊的语法,规则也很直观。

下面我说一下编码解析此文件的思路。

  首先把文件读入内存,在末尾添加'/0'确保是合法的C语言文本;然后从头到尾逐字符遍历该文本,遇到回车符或换行等则临时停下来,把这个字符改写为'/0',这样就得到了一行文本(变量line始终指向每一行的行首);继续向后遍历字符,略过连续的回车符或换行符,进入下一行;如此循环下去就依次得到了每一行的内容,可分别交给下面的函数专门处理;遍历到结尾,无论是否遇到回车或换行等,都要取得最后一行文本(如果非空的话)并处理之。

  接下来,写另外一个函数,专门处理文件中的每一行:判断行首如果是 "regex:" 则把后面的文本视为正则表达式,记录下来;判断行首如果是 "text:" 或 "literal:" 或没有此类前缀,则视为文本常量处理之。在文本常量的情况下,又分为“逗号分隔”和“非逗号分隔”两种情况,后者可视为前者的特例,可以统一处理,方法与上面类似,逐字符历遍找到逗号,把逗号字符改写为'/0'就得到了一个关键字文本常量,记录之;如此循环下去。

  解析完成之后的结果是,得到了两个数组,一个是关键字常量文本指针数组,一个是用于匹配关键字的正则表达式对象指针数组。将来做关键字过滤时,只需分别遍历这两个数组,逐一检查关键字匹配情况即可。如需进一步提高执行效率,可在过滤之前事先将关键字常量文本置入哈希表等快速查询容器中。

  这种文本解析方法,避免了文本分隔,避免了子文本的复制以及相应的内存申请和释放。

下面给出完整的文本解析代码(C/C++):

/* 文件是纯文本的, 以行为单位处理(行与行的分隔符可以是/r/n的任意组合). 行首以 "text:" 或 "literal:" 为前缀的, 后面可以跟一个名称, 也可以跟"以半角逗号分隔的"多个名称; 行首以 "regex:" 为前缀的, 后面可以跟一个正则表达式文本; ":" 或 "," 后面可以有任意多个空白字符, 解析时将自动略过; 如果没有歧义, 可以省略行首的 "text:" 或 "literal:"; by liigo, 2009/10/29 */ static bool ParseSymFile(BufferedMem& filedata, BufferedMem& names, BufferedMem& regexs) { filedata.AppendChar('/0'); char* p = (char*) filedata.GetData(); char* line = p; char c; while( (c = *p) != '/0') { if(c == '/r' || c == '/n') { *p = '/0'; ParseLineOfSymFile(line, names, regexs); p++; while(*p == '/r' || *p == '/n') p++; line = p; } else p++; } if(p > line) ParseLineOfSymFile(line, names, regexs); return true; }

static bool ParseLineOfSymFile(char* line, BufferedMem& names, BufferedMem& regexs) { //printf("line: %s/r/n", line); char* p = line; if(strstr(line, "regex:") == line) { line += 6 /* strlen("regex:") */; while(isspace(*line)) line++; CRegexpT<char>* pRegex = new CRegexpT<char>(line, 0); regexs.AppendPointer(pRegex); return true; } if(strstr(line, "text:") == line) line += 5 /* strlen("text:") */; else if(strstr(line, "literal:") == line) line += 8 /* strlen("literal:") */; while(isspace(*line)) line++; char* name = line; p = line; while(*p) { if(*p == ',') { *p = '/0'; names.AppendPointer(name); p++; if(*p == ',') { p++; name = p; continue; } //process ',' immediately follows another ',' while(isspace(*p)) p++; name = p; } p++; } if(p > name) names.AppendPointer(name); return true; }

  以上代码也是今天刚刚完成的,经初步测试可用,但还没有进行完整而严格的单元测试,可能会有一些BUG或缺陷,还请各位及时斧正。代码中出现的 BufferedMem 类,由于不涉及核心的文本解析算法,就不额外提供代码了。另外,由于这是特定于某个应用系统的内部应用,在通用性上可能做的不够(例如我假定了正则表达式不会以空白字符开头、关键字不能是空文本等)。

你可能感兴趣的:(java,c,算法,正则表达式,单元测试,regex)