- 论坛徽章:
- 1
|
"Perl 5 to 6" Lesson 20 - 处理 XML 的 grammar
概要- grammar XML {
- token TOP { ^ <xml> $ };
- token xml { <text> [ <tag> <text> ]* };
- token text { <-[<>&]>* };
- rule tag {
- '<'(\w+) <attributes>*
- [
- | '/>' # a single tag
- | '>'<xml>'</' $0 '>' # an opening and a closing tag
- ]
- };
- token attributes { \w+ '="' <-["<>]>* '"' };
- };
复制代码 描述
到目前为止, 我们看到的都是 Perl 6 语言本身的一些实现的特性. 现在需要告诉你这不是一个纯粹 yy 的语言, 所以讲一些真实的应用, 来向你展示 Perl 强大的语法, 我们这课是使用 Rakudo 上的 grammar 来解析基本的 XML.
你可以看看 http://rakudo.org/how-to-get-rakudo/ 来怎么样安装这个.
Our idea of XML
对于我们的目标 XML 解析是非常简单的: 它是包含了一些纯文本和一些嵌套的的标签并能包含一些可选的属性. 所以我们先写几个可用和不可用的 "XML" 的测试.- my @tests = (
- [1, 'abc' ], # 1
- [1, '<a></a>' ], # 2
- [1, '..<ab>foo</ab>dd' ], # 3
- [1, '<a><b>c</b></a>' ], # 4
- [1, '<a href="foo"><b>c</b></a>'], # 5
- [1, '<a empty="" ><b>c</b></a>' ], # 6
- [1, '<a><b>c</b><c></c></a>' ], # 7
- [0, '<' ], # 8
- [0, '<a>b</b>' ], # 9
- [0, '<a>b</a' ], # 10
- [0, '<a>b</a href="">' ], # 11
- [1, '<a/>' ], # 12
- [1, '<a />' ], # 13
- );
- my $count = 1;
- for @tests -> $t {
- my $s = $t[1];
- my $M = XML.parse($s);
- if !($M xor $t[0]) {
- say "ok $count - '$s'";
- } else {
- say "not ok $count - '$s'";
- }
- $count++;
- }
复制代码 这会列出 "good" 和 "bad" 的 XML. 只需要调用 XML.parse($string) 来运行这些测试的的列表. 根据惯例这些匹配的 grammar 的语法, 需要符合 "TOP" 的该规则才行.
( 你可以看测试 1, 这个我们并没有 root 标签, 但我们会很容易来加入此限制 ).
grammar 开发
XML 的本质必然是标签嵌套, 所以我们给重点放在了第二个测试上面. 这个上面能使用这个测试脚本中的 top .- grammar XML {
- token TOP { ^ <tag> $ }
- token tag {
- '<' (\w+) '>'
- '</' $0 '>'
- }
- };
复制代码 现在运行测试脚本:- $ ./perl6 xml-01.pl
- not ok 1 - 'abc'
- ok 2 - '<a></a>'
- not ok 3 - '..<ab>foo</ab>dd'
- not ok 4 - '<a><b>c</b></a>'
- not ok 5 - '<a href="foo"><b>c</b></a>'
- not ok 6 - '<a empty="" ><b>c</b></a>'
- not ok 7 - '<a><b>c</b><c></c></a>'
- ok 8 - '<'
- ok 9 - '<a>b</b>'
- ok 10 - '<a>b</a'
- ok 11 - '<a>b</a href="">'
- not ok 12 - '<a/>'
- not ok 13 - '<a />'
复制代码 所以这是使用了简单的规则来解析出了开始标签和结束标签对, 也正确的拒绝掉了无效的 XML 中的四个例子.
第一个测试应该很容易通过,所以让我们试试这个:- grammar XML {
- token TOP { ^ <xml> $ };
- token xml { <text> | <tag> };
- token text { <-[<>&]>* };
- token tag {
- '<' (\w+) '>'
- '</' $0 '>'
- }
- };
复制代码 (记住, <-[...]> 是一个用于否定的字符类.)
现在运行:
- $ ./perl6 xml-03.pl
- ok 1 - 'abc'
- not ok 2 - '<a></a>'
- (rest unchanged)
复制代码 为什么处理第第二个测试就工作了? 答案是因为 Rakudo 还不支持 token 的长匹配 ( update 2013=01: 现在支持 ), 这个匹配是顺序的. <text> 匹配空字符串命中, 所以 <text> | <tag> 从来没有尝试匹配 <tag>, 给这二个顺序修改一下会有所帮助.
但我们并不只想无论怎么样都能匹配到纯文本或标签, 我们希望这二个能随机组合起来:- token xml { <text> [ <tag> <text> ]* };
复制代码 ([...] 是不会捕捉内容, 象在 Perl 5 中的 (?: ... ) ).
现在这前二个测试都能通过.
第三个测试, ..<ab>foo</ab>dd, 找有开头和结构标记之间的文本, 还要允许有多个标记, 因为不只是唯一中间可以放文本, 还要可以放任意的 XML. 因为这叫 <xml>.- token tag {
- '<' (\w+) '>'
- <xml>
- '</' $0 '>'
- }
复制代码- ./perl6 xml-05.pl
- ok 1 - 'abc'
- ok 2 - '<a></a>'
- ok 3 - '..<ab>foo</ab>dd'
- ok 4 - '<a><b>c</b></a>'
- not ok 5 - '<a href="foo"><b>c</b></a>'
- (rest unchanged)
复制代码 我们现在可以专注于属性的实现 ( href="foo" ):- token tag {
- '<' (\w+) <attribute>* '>'
- <xml>
- '</' $0 '>'
- };
- token attribute {
- \w+ '="' <-["<>]>* \"
- };
复制代码 这并不会有新的测试通过. 原因是因为签名和属性之间的有空白. 我们不使用 \s+ 或 \s* 而是使用 token 转到 rule, 所以这会有 :sigspace 的修饰符:- rule tag {
- '<'(\w+) <attribute>* '>'
- <xml>
- '</'$0'>'
- };
- token attribute {
- \w+ '="' <-["<>]>* \"
- };
复制代码 现在,所有的测试都通过了,除了最后两个:- ok 1 - 'abc'
- ok 2 - '<a></a>'
- ok 3 - '..<ab>foo</ab>dd'
- ok 4 - '<a><b>c</b></a>'
- ok 5 - '<a href="foo"><b>c</b></a>'
- ok 6 - '<a empty="" ><b>c</b></a>'
- ok 7 - '<a><b>c</b><c></c></a>'
- ok 8 - '<'
- ok 9 - '<a>b</b>'
- ok 10 - '<a>b</a'
- ok 11 - '<a>b</a href="">'
- not ok 12 - '<a/>'
- not ok 13 - '<a />'
复制代码 这些包含在封闭的标签中有单个斜线 /. 这个很容易, 我们只要在我们的 rule 的 tag 中加入这个就好:- rule tag {
- '<'(\w+) <attribute>* [
- | '/>'
- | '>' <xml> '</'$0'>'
- ]
- };
复制代码 所有的测试都通过了, 我们很高兴, 我们的第一个 grammar 效果很好.
More hacking
Playing with grammars is much more fun that reading about playing, so here's what you could implement:
plain text can contain entities like &
I don't know if XML tag names are allowed to begin with a number, but the current grammar allows that. You might look it up in the XML specification, and adapt the grammar if needed.
plain text can contain <![CDATA[ ... ]]> blocks, in which xml-like tags are ignored and < and the like don't need to be escaped
Real XML allows a preamble like <?xml version="0.9" encoding="utf-8"?> and requires one root tag which contains the rest (You'd have to change some of the existing test cases)
You could try to implement a pretty-printer for XML by recursively walking through the match object $/. (This is non-trivial; you might have to work around a few Rakudo bugs, and maybe also introduce some new captures).
(Please don't post solutions to this as comments in this blog; let others have the same fun as you had ;-).
Have fun hacking.
动机
这个很强大并且有意思
另请参阅
Regexes are specified in great detail in S05: http://perlcabal.org/syn/S05.html.
More working examples for grammars can be found at https://github.com/moritz/json/ (check file lib/JSON/Tiny/Grammar.pm). |
|