使用 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
之前看到一系列标记 name2
、name2
、name2
。现在,解析器中的 only 规则在输入中说 must 以 name1
标记开头,因此您会遇到语法错误。
现在,有几种方法可以解决这个问题。您可以更改 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。至少我已经解释了,教程时尚,发生了什么,以及让它与您的示例数据集一起工作的一种方法。
作为 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
之前看到一系列标记 name2
、name2
、name2
。现在,解析器中的 only 规则在输入中说 must 以 name1
标记开头,因此您会遇到语法错误。
现在,有几种方法可以解决这个问题。您可以更改 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。至少我已经解释了,教程时尚,发生了什么,以及让它与您的示例数据集一起工作的一种方法。