XML 文档的词法分析器——XML 元素数据的正则表达式隐藏了空白的正则表达式——如何修复它?

Lexer for an XML document -- the regex for XML element data is hiding the regex for whitespace -- how to fix it?

我正在为 XML 文档创建词法分析器。这是我的 XML 文档(注意实际的 XML 文档要复杂得多,这是一个简单的 XML 文档来说明问题):

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:noNamespaceSchemaLocation="test.xsd"
          version="1.0">
    <message>Hello, world</message>
</Document>

我希望词法分析器产生这个:

DOCUMENT_START_TAG
ATTRIBUTE_NAME = version
ATTRIBUTE_VALUE = "1.0"
MESSAGE_START_TAG
ELEMENT_VALUE = Hello, world
MESSAGE_END_TAG
DOCUMENT_END_TAG

也就是说,我希望词法分析器忽略第一行(XML 声明)、元素之间的白色space 和两个名称space 声明。

但是,词法分析器却产生了这个:

ELEMENT_VALUE =

DOCUMENT_START_TAG
ATTRIBUTE_NAME = version
ATTRIBUTE_VALUE = "1.0"
ELEMENT_VALUE =

MESSAGE_START_TAG
ELEMENT_VALUE = Hello, world
MESSAGE_END_TAG
ELEMENT_VALUE =

DOCUMENT_END_TAG

whitespace 的词法分析器规则未触发。相反,元素值的规则是触发。所以我知道问题出在哪里:元素值的正则表达式不正确。但我不知道正确的正则表达式是什么。如果您能提供任何帮助,我们将不胜感激。

底部是我的整个 .l 文件。下面是对里面规则的解释:

第一行——XML 声明行——是我希望词法分析器简单丢弃的内容。这是它的词法分析器规则:

"<?"[^?>]+"?>"

XML 声明以 <? 开始并以 ?> 结束,中间的内容是除 ?>

之外的任何内容

我希望词法分析器丢弃 XML 元素之间的白色 space。这是 whitespace:

的词法分析器规则
[ \t\n]+

吞噬了 spaces、制表符和换行符。

我希望词法分析器忽略两个 namespace 声明。以下是它们的词法分析器规则:

[ \t\n]+xmlns:xsi=\"http:\/\/www\.w3\.org\/2001\/XMLSchema-instance\"
[ \t\n]+xsi:noNamespaceSchemaLocation=\"test.xsd\"

名称space 声明前面总是至少有一个白色space 字符。

我希望词法分析器 return <Document> 元素的标记 DOCUMENT_START_TAG<Document> 元素内部捆绑了属性,因此需要特别注意:

"<Document"[^>]*">"         { yyless(9); return(DOCUMENT_START_TAG); }

<Document> 元素以 <Document 开头,然后是一些内容,最后以 > 结尾。该操作放回 <Document 和 return 标记 DOCUMENT_START_TAG.

之后的所有内容

我希望词法分析器 return DOCUMENT_END_TAG </Document>。这是词法分析器规则:

"</Document>"               { return(DOCUMENT_END_TAG); }

消息开始和结束标记的词法分析器规则如下:

"<message>"                 { return(MESSAGE_START_TAG); }
"</message>"                { return(MESSAGE_END_TAG); }

XML 属性有名称、等号和用引号引起来的值。这是名称的词法分析器规则:

[^ \t\n=]+/=[ \t\n]*" { return(ATTRIBUTE_NAME); }

名称不包含 space、制表符、换行符或等号。 (使用先行运算符)名称后面是一个等号,可能是一些白色 space 和一个引号。

属性值是引号内的内容:

\"[^"]*\"                   { return(ATTRIBUTE_VALUE); }

我不希望属性值包含引号 - 如何删除它们?

我希望词法分析器 return 元素的值(例如,Hello, world)。元素值不包含 <>

[^<>]+/<                    { return(ELEMENT_VALUE); }

我用lookahead表示值后面总是跟着<

这是我的完整 .l 文件:

%{
  enum yytokentype {
    DOCUMENT_START_TAG = 258,
    DOCUMENT_END_TAG = 259,
    MESSAGE_START_TAG = 260,
    MESSAGE_END_TAG = 261,
    ELEMENT_VALUE = 262,
    ATTRIBUTE_NAME = 263,
    ATTRIBUTE_VALUE = 264,
    JUNK = 265
  };
  int yyval;
%}
%%
"<?"[^?>]+"?>"
[ \t\n]+
">"
"="
[ \t\n]+xmlns:xsi=\"http:\/\/www\.w3\.org\/2001\/XMLSchema-instance\"
[ \t\n]+xsi:noNamespaceSchemaLocation=\"test.xsd\"
"<Document"[^>]*">"         { yyless(9); return(DOCUMENT_START_TAG); }
"</Document>"               { return(DOCUMENT_END_TAG); }
"<message>"                 { return(MESSAGE_START_TAG); }
"</message>"                { return(MESSAGE_END_TAG); }
[^ \t\n=]+/=[ \t\n]*\"      { return(ATTRIBUTE_NAME); }
\"[^"]*\"                   { return(ATTRIBUTE_VALUE); }
[^<>]+/<                    { return(ELEMENT_VALUE); }
.                           { return(JUNK);  }
%%

int yywrap(){ return 1;}
int main(int argc, char *argv[])
{
    yyin = fopen(argv[1], "r");
    int tok;
    while (tok = yylex()) {
       switch (tok){
          case 258:
             printf("DOCUMENT_START_TAG\n");
             break;
          case 259:
             printf("DOCUMENT_END_TAG\n");
             break;
          case 260:
             printf("MESSAGE_START_TAG\n");
             break;
          case 261:
             printf("MESSAGE_END_TAG\n");
             break;
          case 262:
             printf("ELEMENT_VALUE = %s\n", yytext);
             break;
          case 263:
             printf("ATTRIBUTE_NAME = %s\n", yytext);
             break;
          case 264:
             printf("ATTRIBUTE_VALUE = %s\n", yytext);
             break;
          case 265:
             printf("JUNK = %s\n", yytext);
             break;
          default:
             printf(" = invalid token, value = %s\n", yytext);
       }
    }
    
    fclose(yyin);
    
    return 0;
}

你的元素值规则总是胜过你的空白规则,因为它有更长的匹配。这是因为尾随上下文算作匹配的一部分,即使词法分析器在触发操作之前回溯了尾随上下文。

这在 Flex 手册中有记载,但很容易遗漏。

我不清楚为什么您觉得需要尾随上下文。 [^<>]+ 后面的字符只有 <>;如果您想将 > 视为错误,那么在 > 发生的位置标记错误比在最终包含有问题的元素值的开头标记错误更有意义特点。但静静地接受 > 作为一个普通字符可能更有意义。无论哪种方式,都不需要尾随上下文,如果没有尾随上下文,您的空白规则将在适用的情况下获胜。

但请注意,如果 XML 文档使用了 CRLF 行结尾,则空白规则不会捕获它们。我总是建议使用 [[:space:]] 而不是列出空白字符,尽管它匹配了一些可能被认为是错误的字符。

同样,扫描标签直到结束 > 然后回溯到标签名是完全没有意义的。要么标签被正确终止并且您最终将到达 >,要么您将到达文档的末尾,此时您可以抛出错误。但是,您应该做的是捕获其标记名以 Document 开头的标记,例如 <Documentary>(您当前的模式将接受)。这会建议类似:

<Document        { return DOCUMENT_START_TAG; }
<message         { return MESSAGE_START_TAG; }
</Document       { return DOCUMENT_END_TAG; }
</message        { return MESSAGE_END_TAG; }
</[^[:space:]>]+ { return UNKNOWN_END_TAG; }
<[^[:space:]>]+  { return UNKNOWN_START_TAG; }