C语言中基于正则表达式的strstr函数

时间:2011-05-05 04:14:00

标签: c regex tokenize

我需要找到一种方法来获取指向子串的指针(如strstr,第一次出现)到多个可能的针(模式)大字符串。 C的标准strstr()只支持一针,我需要2针甚至3针。 为什么这一切?我需要能够将html文档“标记”成部分以进一步解析这些“片段”。我需要用于标记化的“锚”可以变化,例如<div class="blub"><span id="bla,并且用作标记的html标记可以包含id / class属性值中的数字(我可以使用\d+或类似的过滤器。)

所以我想用posix regex写一个函数。

该功能如下所示:

char * reg_strstr(const char *str, const char *pattern) {
    char *result = NULL;
    regex_t re;
    regmatch_t match[REG_MATCH_SIZE];

    if (str == NULL)
        return NULL;

    if (regcomp( &re, pattern, REG_ICASE | REG_EXTENDED) != 0) {
        regfree( &re );         
        return NULL;
    }

    if (!regexec(&re, str, (size_t) REG_MATCH_SIZE, match, 0)) {

        fprintf( stdout, "Match from %2d to %2d: \"%s\"\n",
             match[0].rm_so,
             match[0].rm_eo,
             str + match[0].rm_so);
        fflush(stdout);

        if ((str + match[0].rm_so) != NULL) {
            result = strndup(str + match[0].rm_so, strlen(str + match[0].rm_so));
        }
    }

    regfree( &re );

    return result;
}

常数REG_MATCH_SIZE为10

首先,使用正则表达式作为扩展strstr函数的想法是否有意义?

在简单的测试用例中,函数似乎工作正常:

char *str_result = reg_strstr("<tr class=\"i10\"><td><div class=\"xyz\"><!--DDDD-1234--><div class=\"xx21\">", "<div class=\"xyz\">|<div class=\"i10 rr");

printf( "\n\n"
    "reg_strstr result: '%s' ..\n", str_result);

free( str_result) ;

在使用完整HTML文档的真实案例环境中使用该函数不会像预期的那样工作。它没有找到模式。在内存映射字符串上使用此函数(我在解析HTML文档数据时使用mmap'ed文件作为tmp。存储的缓存)。

修改

这里有一个像所用的循环:

变量:parse_tag->firsttokenparse_tag->nexttoken是我尝试匹配的html锚点,就像上面说明的那样。 doc是输入文档,从mmap的缓存中分配一个'\ 0'终止字符串(带strndup())。 下面的代码与预期的strstr()一起使用。如果我发现,使用正则表达式strstr的想法对我来说真的有用,我可以重写循环,也许可以从reg_strstr返回所有匹配(作为字符串列表等)。所以现在我只是想...


...
char *tokfrom = NULL, *tokto = NULL;
char *listend = NULL;

/* first token found ? */ if ((tokfrom = strstr(doc, parse_tag->firsttoken)) != NULL) { /* is skipto_nexttoken set ? */ if (!parse_tag->skipto_nexttoken) tokfrom += strlen(parse_tag->firsttoken); else { /* ignore string between firsttoken and first nexttoken */ if ((tokfrom = strstr(tokfrom, parse_tag->nexttoken)) == NULL) goto end_parse; }

/* no listend tag found ? */
if (parse_tag->listend == NULL ||
    (listend = reg_strstr(tokfrom, parse_tag->listend)) == NULL) {
    listend = doc + strlen(doc);
}

*listend = '\0';        /* truncate */

do {
    if((tokto = reg_strstr(tokfrom + 1, parse_tag->nexttoken)) == NULL)
        tokto = listend;
    tokto--;  /* tokto-- : this token up to nexttoken */

    if (tokto <= tokfrom)
        break;

    /* do some filtering with current token here ... */
    /* ... */

} while ((tokfrom = tokto + 1) < listend);

} ...

编辑结束

我在这里想念一下吗?如上所述,这是否有可能实现我的目标?正则表达式模式是否错误?

欢迎提出建议!

安德烈亚斯

3 个答案:

答案 0 :(得分:1)

我尝试在一个测试HTML文件上编写代码,我只是通过stdin通过重定向从文本文件输入,这似乎与重复读取fgets()一样正常。我会怀疑问题是在内存映射文件中的字符串数据格式化的某处。我怀疑你的内存映射文件中有一个空终止字符,这样如果你只是将内存映射文件本身用作一个char缓冲区,它就会比你期望的更早地结束字符串。

其次,您只返回第一个匹配以及字符串的其余部分,这意味着如果您使用指向内存映射文件的指针作为str参数,则从第一个匹配开始的整个文件。如果你想要标记文件,我怀疑你的“真实”实现有点不同吗?


修改

我一直在看你的概念代码,它看起来似乎总体上起作用。我做了一些修改只是为了帮助我打印出来,但这是我正在编译的内容(文件内存映射非常简单,只是为了检查正则表达式代码是否正常工作):

#include <regex.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>

#define REG_MATCH_SIZE 10
#define FILE_SIZE 60000

static int total_matches = 0;

char * reg_strstr(const char *str, const char *pattern) 
{
    char *result = NULL;
    regex_t re;
    regmatch_t match[REG_MATCH_SIZE];

    if (str == NULL)
        return NULL;

    if (regcomp( &re, pattern, REG_ICASE | REG_EXTENDED) != 0) {
        regfree( &re );         
        return NULL;
    }

    if (!regexec(&re, str, (size_t) REG_MATCH_SIZE, match, 0)) {

        fprintf( stderr, "@@@@@@ Match from %2d to %2d @@@@@@@@@\n",
             match[0].rm_so,
             match[0].rm_eo);

    total_matches++;

        if ((str + match[0].rm_so) != NULL) {
            result = strndup(str + match[0].rm_so, strlen(str + match[0].rm_so));
        }
    }

    regfree( &re );

    return result;
}


int main()
{   
    int filedes = open("testhtml.txt", O_RDONLY);

    void* buffer = mmap(NULL, FILE_SIZE, PROT_READ, MAP_PRIVATE, filedes, 0); 

    char* str_result;
    char* temp_buffer = strdup((char*)buffer);
    while(str_result = reg_strstr(temp_buffer, "<div"))
    {
        char* temp_print = strndup(str_result, 30);
        fprintf(stderr, "reg_strstr result: '%s' ..\n\n", temp_print);
        free(temp_print);
        free(temp_buffer);
        temp_buffer = strdup(str_result + 1);
        free( str_result) ;
    }

    fprintf(stderr, "Total Matches: %d\n", total_matches);

    return 0;
}

只使用"<div"的简单匹配,如果我在Bloomberg这样的页面的整个HTML源上运行它,我总共得到87个匹配,我得到了一些东西这相当于重复调用标准strstr()时的结果。例如,示例输出看起来像(注意:为了理智,我在30个字符后切断了返回字符串上的匹配):

@@@@@@ Match from 5321 to 5325 @@@@@@@@@
reg_strstr result: '<div id="noir_dialog" class="p' ..

@@@@@@ Match from 362 to 366 @@@@@@@@@
reg_strstr result: '<div id="container" class="mod' ..

匹配索引当然会改变,因为新输入字符串比前一个输入字符串短,所以这就是为什么你看到一个匹配从5321开始,但是下一个匹配是362 ...总偏移量将是在原始字符串中的5683。使用不同的正则表达式我相信你会得到不同的结果,但总的来说,似乎你的概念正在运行,或者至少像strstr()一样工作,也就是说它从匹配开始返回整个字符串到子串一直到字符串的结尾。

如果你没有得到你期望的结果(我不确定你到底得到了什么),那么我会说问题出在正则表达式本身或循环中,您可以关闭索引(例如,使用totok--,您可以为自己创建一个循环,只需在字符串中的同一点返回匹配项。)

答案 1 :(得分:0)

确保您加载的数据以空值终止。 regexec的Arg 2必须是以空字符结尾的字符串。

答案 2 :(得分:0)

为什么不简单地使用一次处理一个字符并解析HTML标记的有限状态机。这样,您还可以摆脱HTML注释的问题。想想以下情况:

HTML评论中的锚标记:

<!-- <my anchortag="foo"> -->

HTML属性中的注释:

<some tag="<!--"> <my anchortag="foo"> </some tag="-->">

使用正则表达式,您将很难处理这些情况。

  

有些人在面对问题时会想“我知道,我会使用正则表达式”。现在他们有两个问题。   (Jamie Zawinski)