我即将使用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.
不是。
也许有一个简单的解决方案。欢迎任何建议。谢谢你的努力!
答案 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必须特殊情况下斜杠。这可以做到,但正如我在开始时所说,重要的是要知道完全你想要匹配的东西。