これはMoritz Lenz氏のWebサイトPerlgeek.deで公開されているブログ記事"Perl 5 to 6" Lesson 20 - A grammer for (pseudo) XMLの日本語訳です。
原文はCreative Commons Attribution 3.0 Germanyに基づいて公開されています。
本エントリにはCreative Commons Attribution 3.0 Unportedを適用します。
Original text: Copyright© 2008-2010 Moritz Lenz
Japanese translation: Copyright© 2011 SATOH Koichi
注記: XMLに関する用語の誤用(「整形式」と「妥当」を混同しているなど)がありますが、あくまで例なので原文通りに残しています。
NAME
"Perl 5 to 6" Lesson 20 - (似非)XMLのグラマー
SYNOPSIS
grammar XML {
token TOP { ^ <xml> $ };
token xml { <text> [ <tag> <text> ]* };
token text { <-[<>&]>* };
rule tag {
'<'(\w+) <attributes>*
[
| '/>' # 空タグ
| '>'<xml>'</' $0 '>' # 開始タグと終了タグ
]
};
token attributes { \w+ '="' <-["<>]>* '"' };
};
DESCRIPTION
これまでの連載記事の焦点はPerl6言語であり、実装状況は気にしていませんでした。 これが空想上の言語でないことを示すため、またグラマーの能力を証明するために、このレッスンでは基本的なXMLを解析する、Rakudoで実行できるグラマーの開発をお見せします。
Rakudoの入手とビルドはhttp://rakudo.org/how-to-get-rakudoの指示にしたがって自分で行って下さい。
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++;
}
これは「良い」XMLと「悪い」XMLのリスト、そしてXML.parse($string)
を呼び出してテストを走らせる小さなスクリプトです。
言語全体にマッチするルールはTOP
という名前にする約束になっています。
(テスト1で分かるように単一のルートタグを必須にしていませんが、この制限を追加するのは些細なことです)
グラマーの開発
XMLのキモは当然タグの入れ子構造ですから、まず2番目のテストに着目することにしましょう。 テストスクリプトの先頭に以下のコードを置いて下さい:
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 />'
つまりこれは一対の開始タグと終了タグのペアを解析する単純なルールであり、妥当でない4つのXMLをきちんと排除できています。
1番目のテストも同様に簡単に通るように、次のコードを試して下さい:
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>'
(残りはさっきと同じ)
どうして2番目のテストは動かなくなったのでしょう? その理由はRakudoが最長トークンマッチをまだ実装しておらず、順番にマッチングを行っているからです。
<text>
は空文字列に(つまりいつでも)マッチするので、<text> | <tag>
は<tag>
とのマッチングを試しません。選択肢の順番を入れ替えると動きます。
しかし我々はプレーンテキストかタグだけを適当にマッチさせたいのではなく、両者のランダムな組合わせをマッチさせたいのでした:
token xml { <text> [ <tag> <text> ]* };
([...]
はキャプチャしないグループであり、Perl5の(?: ...)
と同様です)
いやはや驚くことに、これは最初の2つのテストを両方とも通過します。
3番目のテスト.<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>'
(残りはさっきと同じ)
これで属性(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+ '="' <-["<>]>* \"
};
これで残るテストは最後の2つになりました:
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'>'
]
};
全テストが通るようになりました。やった、はじめて作ったグラマーはちゃんと動きます。
さらなるハッキング
グラマーで遊ぶのは遊び方を読むよりずっと楽しいので、これから実装できるものの例を挙げておきます:
&
のような実体参照を含むことができるプレーンテキスト- XMLタグ名が数字で開始して良いのかどうか分かりませんが、現在のグラマーはこれを許しています。必要ならXMLの仕様書を調べてグラマーを改造するのも良いでしょう
<![CDATA[ ... ]]>
を含むことができるプレーンテキスト。このXML風タグは無視され、<
のような文字はエスケープする必要がありません<?xml version="0.9" encoding="utf-8"?>
のようなXML宣言を許容し、すべてを包含する単一のルートタグを要求する本物のXML(テストケースをいくつか修正する必要があります)- マッチオブジェクト
$/
を再帰的に走査することでXML用のプリティプリンタを実装できます(これは生半可にはいきません; いくつかRakudoのバグを回避しなければいけないかも知れませんし、キャプチャも新しく導入する必要があるかも知れません)
(解答をこのブログのコメント欄に書かないで下さい; 他の人にも楽しませてあげましょう;-)
ハッキングを楽しんで下さい。
MOTIVATION
強力だし、楽しい
SEE ALSO
正規表現はS05で詳細に規定されています: http://perlcabal.org/syn/S05.html
正規表現とグラマーの動作している(!)例を、Perl6で書かれたWikiエンジンであるNovember projectでもっと見つけることができます。http://github.com/viklund/november/をご覧下さい。
コメント
コメントを投稿