使用 Flex-Bison 在标签之间提取数据

Extracting data between tags using Flex-Bison

作为 Flex-Bison 的初学者,我遇到了第一个障碍,似乎无法找到解决方法。

问题陈述:对于给定的html/xml文件,需要在标签之间提取数据。我已经阅读了有关 SO 的相关问题,但似乎并没有触及这个问题的最佳点

(因为这是为了学习如何使用 flex-bison,所以我不想切换到使用任何其他 language/tool)。

输入文件包含以下要提取的字段:

<!DOCTYPE html>
<html charset="utf-8" lang="en">
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<meta content="text/css" http-equiv="Content-Style-Type">
<script src="/commd/jquery.nivo.slider.pack.js"></script>
<link rel="stylesheet" type="text/css" href="/fonts/stylesheet.css"/>
<link rel="stylesheet" type="text/css" href="/commd/stylesheet.css"/>


<!--<legend> DATA TO BE EXTRACTED</legend>--> //relevant data between <legend> tag

我写了下面的扫描器test.l

%option noyywrap
%{
#include "parser.tab.h"
%}
%%
"<!--<legend>"  {return name1;}
(.*?)   {yylval.sval=strdup(yytext); return name2;}
"<\/legend>" {return name3;}
%%

和解析器代码parser.y

%{
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define YYERROR_VERBOSE
extern int yylex();
extern int yyparse();
extern FILE *yyin;

%}

%union {
    char *sval;
}

%token <sval> name1
%token <sval> name2 
%token <sval> name3

%%
names : name1 name2 name3 { printf("%s\n", ); }

%%

int main(int argc, char **argv) {

    // open a file handle to a particular file:
    FILE *myfile = fopen(argv[1], "r");
    // make sure it is valid:
    if (!myfile) {
        printf("I can't open file!");
        return 1;
    }
    // set flex to read from it instead of defaulting to STDIN:
    yyin = myfile;

    // parse through the input until there is no more:
    do {
        yyparse();
    } while (!feof(yyin));

}

void yyerror(char *s) {
    printf("EEK, parse error!  Message:%s",s);
    // might as well halt now:
    exit(1);
}

使用 makefile 编译

all: compile_run

compile_run:
    @bison -d parser.y
    @flex test.l
    @gcc parser.tab.c lex.yy.c -lfl -o run

在执行程序时出现以下错误:

EEK, parse error! Message:**syntax error, unexpected name2, expecting name1***

我理解阅读错误,因为 name2 标记可以无限匹配,而且它出现在语法中预期的标记 name1 之前。

我的问题是,既然我已经定义了首先查找 name1,然后是 name2,然后是 name3 标记的语法,为什么会出现这个错误。

如果我在扫描仪中只定义一个标记 name1

<!--<legend>(.*?)<\/legend> {return name1;} 

我会得到包括标签在内的整个字符串。我可以 post 处理以获取数据,但我真的认为必须有更聪明的方法,我会从这里了解到:)。

您遇到问题的原因是您只为部分输入文件定义了规则,并希望词法分析器和解析器将忽略其余部分。这些工具不是这样工作的;他们尝试匹配所有内容,因此您必须为输入数据的各个方面定义所有内容。

我还注意到您的原始词法分析器文件不是使用 flex 构建的。您的规则顺序错误。您的原始规则集:

%option noyywrap
%{
#include "parser.tab.h"
%}
%%
"<!--<legend>"  {return name1;}
(.*?)   {yylval.sval=strdup(yytext); return name2;}
"<\/legend>" {return name3;}
%%

报错如下:

"test.l", line 8: warning, rule cannot be matched

这是因为 flex 按顺序使用规则,name3 永远不会被 return 编辑,因为 name2 的模式也会匹配 name3。您显然已修复此问题以便能够构建您的测试程序。解决方法是颠倒规则的顺序,如下所示:

%option noyywrap
%{
#include "parser.tab.h"
%}
%%
"<!--<legend>"  {return name1;}
"<\/legend>" {return name3;}
(.*?)   {yylval.sval=strdup(yytext); return name2;}
%%

flex(bison)的一个对调试很有用的特性就是调试模式,这并不奇怪!

如果我们 运行 您的代码启用了调试模式,就像这样:

bison -d parser.y
flex -d test.l
gcc  parser.tab.c lex.yy.c -lfl -o run

然后执行程序,我们现在从词法分析器得到有用的输出:

--(end of buffer or a NUL)
--accepting rule at line 8 ("")
EEK, parse error! Message:syntax error, unexpected name2, expecting name1

您可以看到您的规则 (.*?) 确实匹配任何文本,但不仅在 <legend> 内而且在其他任何地方都匹配。这意味着您的解析器将在看到 name1 之前看到一系列标记 name2name2name2。现在,解析器中的 only 规则在输入中说 mustname1 标记开头,因此您会遇到语法错误。

现在,有几种方法可以解决这个问题。您可以更改 bison 规则以在 name1 之前接受大量 name2 个标记,或者您可以升级整个语法以描述整个 XML/HTML。至少您可能希望升级语法以在一个文件中接受 多个 <legend> 标签!目前你的语法只匹配一个包含一个 <legend> 结构的文件,没有别的 - 记住它不只是忽略其他输入(除非你告诉它)!

重写通用化 XML 结构的语法会是一项更大的工作,但可以做的是指示 flex lexer 忽略其他输入,以便 name2 模式是未 returned。我们只需要为输入数据文件中的其他内容编写模式。我们需要匹配其他 XML 标签,注释行和白色 space 并告诉 flex 忽略它们。

这样做的一个例子可能是:

%{
#include "parser.tab.h"
%}
%%
"<!--<legend>"           {return name1;}
"<\/legend>"             {return name3;}
"<".[^-](.|[ \t])*">"    ; /* Skip other tags */
"//".*[\r\n]+            ; /* Skip comments */
[\r\n\t ]+               ; /* Skip unused whitespace */
(.*?)                    {yylval.sval=strdup(yytext); return name2;}
%%

当我们 运行 这段代码时,我们确实设法跳过了一些不需要的标签:

--(end of buffer or a NUL)
--accepting rule at line 7 ("<!DOCTYPE html>")
--accepting rule at line 9 ("
")
--accepting rule at line 7 ("<html charset="utf-8" lang="en">")
--accepting rule at line 9 ("
")
--accepting rule at line 7 ("<head>")
--accepting rule at line 9 ("
")
--accepting rule at line 7 ("<meta content="text/html; charset=UTF-8" http-equiv
="content-type">")
--accepting rule at line 9 ("
")
--accepting rule at line 7 ("<meta content="text/css" http-equiv="Content-Style-
Type">")
--accepting rule at line 9 ("
")
--accepting rule at line 7 ("<script src="/commd/jquery.nivo.slider.pack.js"></s
cript>")
--accepting rule at line 9 ("
")
--accepting rule at line 7 ("<link rel="stylesheet" type="text/css" href="/fonts
/stylesheet.css"/>")
--accepting rule at line 9 ("
")
--accepting rule at line 7 ("<link rel="stylesheet" type="text/css" href="/commd
/stylesheet.css"/>")
--accepting rule at line 9 ("


")
--accepting rule at line 10 ("<!--<legend> DATA TO BE EXTRACTED</legend>--> //re
levant data between <legend> tag")
EEK, parse error!  Message:syntax error, unexpected name2, expecting name1

我们遇到了另一个问题。谜底是,为什么和returnname1不匹配?这是由于匹配算法是 greedy 并找到匹配最长标记的规则。为了克服这个问题,我们必须使用 flex 的 start condition 特性,并且只匹配 <legend> 结构中的一般文本。在 match XML 中使用开始条件时,我们必须小心,因为 < 符号用于表示状态更改以及引入 XML 标记。我们可以像这样重新编码以切换状态:

%{
#include "parser.tab.h"
%}
%x legends 
%x finishd
%%
<INITIAL>"<!--<legend>"         {BEGIN(legends); return name1;}
<finishd>"</legend>-->"         {BEGIN(INITIAL); return name3;}
<INITIAL>"<".[^-](.|[ \t])*">"  ; /* Skip other tags */
<INITIAL>"//".*[\r\n]+          ; /* Skip comments */
<INITIAL>[\r\n\t ]+             ; /* Skip unused whitespace */
<legends>[^<>]+                 {BEGIN(finishd); yylval.sval=strdup(yytext); return name2;}
%%

然后我们神奇地得到以下内容:

--(end of buffer or a NUL)
--accepting rule at line 9 ("<!DOCTYPE html>")
--accepting rule at line 11 ("
")
--accepting rule at line 9 ("<html charset="utf-8" lang="en">")
--accepting rule at line 11 ("
")
--accepting rule at line 9 ("<head>")
--accepting rule at line 11 ("
")
--accepting rule at line 9 ("<meta content="text/html; charset=UTF-8" http-equiv
="content-type">")
--accepting rule at line 11 ("
")
--accepting rule at line 9 ("<meta content="text/css" http-equiv="Content-Style-
Type">")
--accepting rule at line 11 ("
")
--accepting rule at line 9 ("<script src="/commd/jquery.nivo.slider.pack.js"></s
cript>")
--accepting rule at line 11 ("
")
--accepting rule at line 9 ("<link rel="stylesheet" type="text/css" href="/fonts
/stylesheet.css"/>")
--accepting rule at line 11 ("
")
--accepting rule at line 9 ("<link rel="stylesheet" type="text/css" href="/commd
/stylesheet.css"/>")
--accepting rule at line 11 ("


")
--accepting rule at line 7 ("<!--<legend>")
--accepting rule at line 12 (" DATA TO BE EXTRACTED")
--accepting rule at line 8 ("</legend>-->")
 DATA TO BE EXTRACTED
--accepting rule at line 11 (" ")
--(end of buffer or a NUL)
--accepting rule at line 10 ("//relevant data between <legend> tag
")
--(end of buffer or a NUL)
--EOF (start condition 0

现在,如果您关闭 flex 调试,您将得到所需的输出:

DATA TO BE EXTRACTED

如果要提取的数据不止一组,您仍然需要更新 bison 语法;实际上你应该升级整个 bison 语法以更好地匹配更多 XML。至少我已经解释了,教程时尚,发生了什么,以及让它与您的示例数据集一起工作的一种方法。