Flex Lexer模式匹配句子分隔符/标点符号作为URL路径部分

时间:2017-06-14 01:41:05

标签: c++ regex flex-lexer

我即将使用RE-Flex(flex兼容词法分析器)重构文本片段的空格标记器

我的lexer文件中有以下模式,我只列出了与此问题有关的模式:

// ...

WHITESPACE  \r\n|[ \r\n\t\f]
DOMAIN      "mil"|"info"|"gov"|"edu"|"biz"|"com"|"org"|"net"|"arpa"|"de"|[a-z]{2}
DIGIT       [0-9]
LETTER      [a-zA-Z]
SYMBOL      ({LETTER}|{DIGIT})({LETTER}|{DIGIT}|"_"|"-")*
BARE_URL    {SYMBOL}("."{SYMBOL})*"."{DOMAIN}
URL_PATH    ([!*'();:@&=+$,/?%#_.~]|"-"|"["|"]"|{LETTER}|{DIGIT})+

%%

("." | "?" | "!" | ";")+ { 
     return tokenizer_base::TK_PUNCTUATION; 
}

/* ... other patterns ... */

{BARE_URL} { 
    return tokenizer_base::TK_BARE_URL;
}
(("http"|"https"|"ftp")"://")?{BARE_URL}{URL_PATH}? { 
    return tokenizer_base::TK_FULL_URL;
}    

/* ... */

/** Ignore the rest */
.|{WHITESPACE} { 
    ;
}

%%

这基本上工作正常,但请考虑输入的这种情况:

Please visit http://www.google.de.

上面字符串中的最后一个.是句子分隔符,应该返回TK_PUNCTUATION令牌类型。不幸的是,它没有,它被解释为TK_FULL_URL令牌的一部分并返回http://www.google.de.

思考普通正则表达式我试图将[^!;.]附加到TK_FULL_URL模式但这不起作用。

另一个 - 在我看来是hackish - 解决方案是分析返回的令牌的最后一个 字符和unput字符返回到输入流(如果它与标点符号匹配)。我可以这样做:

size_t last = YY_SCANNER.ptr_matcher()->size() - 1; // similar to YYleng
std::string last_str = YY_SCANNER.ptr_matcher()->text(); // similar to YYtext

try {
    // Check if last character is a '.' and second-last char of type alpha
    if (last_str.at(last) == '.' && ::isalpha(last_str.at(last - 1))) {
        YY_SCANNER.ptr_matcher()->unput(last_str[last]); 
        YY_SCANNER.ptr_matcher()->less(last); // similar to YYless
    }
} catch(const std::out_of_range& e) {
    // we keep silent 
}

到目前为止这是有效的,但我认为这不是很优雅且容易出错。

所以我的基本问题是,我是否可以某种方式调整urlpath模式,以便最后.不被视为URL路径的一部分?我知道http://www.domain.tld/foo/bar/.有效,但http://www.domain.tld/foo/bar.不是。

也许有一个简单的解决方案。欢迎任何建议。谢谢你的努力!

1 个答案:

答案 0 :(得分:1)

绝对清楚你要接受什么是非常重要的。否则,你不能写一个正则表达式来接受它,任何人都不能试图帮助你。

请注意:以下段落中的(损坏的)网址都是故意输入的,因此Markdown的识别算法很明显。

http://www.domain.tld/foo/bar/。和http://www.domain.tld/foo/bar。是有效的网址。但是,URL识别器常见的是避免匹配尾随.(正如您所见,Markdown不匹配),因为在句子末尾写一个URL的常见做法,就像这样http://www.domain.tld/foo? (但使用http://www.domain.tld/foo?search时,Markdown会将?识别为网址的一部分。)

括号和引号也很棘手。 Markdown,继续运行示例,如果它们是平衡的(http://foo.es/?q=(main())),它将接受URL中的括号,但正如您所看到的,仍然可以将URL放在括号内。使用正则表达式无法模拟此行为,因为正则表达式无法计数。

但是,让我们保持简单。我们可以接受一个URL,但如果它在标点符号列表中,则排除最后一个字符。所以最终可能会出现这样的结果:

URL_CHAR  [][a-zA-Z0-9*@&=+$/?%#_~|()'"!:;.,-]
URL_FINAL [][a-zA-Z0-9*@&=+$/?%#_~|-]
URL_PATH  {URL_CHAR}*{URL_FINAL}

关于字符类的注释:在字符类中,如果将其放在开头,则可以将] 用作常规字符。所以[][…]是用括号编写字符类的传统方法。 - 可以写成第一个或最后一个字符,因此您可以编写[-…][…-]来包含短划线,但如果您还有],你需要把破折号放在最后,因为开头已经被占用了。所以你最终得到了[][…-],这就是我编写上述模式的方式。除 - ] \ 之外,字符类中没有特殊字符。因此,您可以自由地包含否则将是正则表达式元字符的字符,例如 | 。除此之外,我尝试编写类,以便明确第二类中缺少哪些字符。

如果你想将http://www.domain.tld/foo/.识别为一个网址(而不是更有可能http://www.domain.tld/foo/后跟一个标点符号),那么你需要更复杂的东西,因为你&# 39; d必须特殊情况下斜杠。这可以做到,但正如我在开始时所说,重要的是要知道完全你想要匹配的东西。