04 簡易XMLパーザ
■ XML
XMLです。だいたいこんなかんじのHTMLで書くと<>がどっさりでめんどくさいやつですね。
<MResourceFile version="1.00"> <Description> test description </Description> <!-- comment --> <Window name="testwin1" width="320" height="240"> <Editbox name="edit1" left="16" top="16" width="160" height="16"/> <StaticText name="stext1" left="32" top="64" text="StaticText1"/> <Listbox name="list" left="16" top="96" width="240" height="120"> <String text="test"/> <Icon icon="hoge"/> </Listbox> </Window> <Window name="testwin2" width="60%" height="30%"> </Window> </MResourceFile>
"簡易"とあるのはXMLのEBNF見てみたら思ったよりも相当複雑だったので、上に書いてあるようなのが解析できて読み込めたらOKということにするってことで"簡易"としました。宣言とかDTDとかはナシで。それでは今回読み込む構文を1つづつ見ていきます。
まずこういうやつ。
<tag> </tag>
HTMLとかと同じですね。なんて呼ぶのか実はよく知らないんですがなんか要素っぽいのでとりあえず要素と呼ぶことにします。この要素には以下のようにして属性をつけることが可能です。
<tag attribute="attribute"> </tag>属性は何個でもつけることが可能です。また、要素は階層構造にすることも可能です。
<tag attribute="attribute">
<tag2>
<tag2>
</tag>
で、タグとタグの間にデータをかいたり。
<tag attribute="attribute">
<tag2>
description
<tag2>
</tag>
ここまではHTMLといっしょですが、HTMLの水平線のタグ<HR>のような、1つだけでそれをあらわして、閉じるタグのないもの、こういうものはXMLでは次のように書きます。XMLだと任意のタグが作れるのでミスって開きっぱなしなのか1つの<HR>なのかわからないからでしょうか。
<HR/>
あとはコメントとか。
<!-- comment -->
とりあえずこんだけにしときます。でもこれだけが読み込めても便利そうだしboost::spiritで簡単に記述できそうです。
■ 構文解析
まずセマンティックアクションは考えずにXMLをパージングするプログラムをboost::spiritで書いてみましょう。
s = +space_p;
string_r = '\"' >> *(anychar_p - '\"') >> '\"';
name_r = (alpha_p | ch_p('_')) >> *( (alnum_p | ch_p('_')) );
desc_r = +(anychar_p[&AckDescription] - '<');
stag = '<' >> name_r >> *(s >> attribute) >> '>';
etag = "</" >> name_r >> '>';
emptyelem = '<' >> name_r >> *(s >> attribute) >> "/>";
attribute = name_r >> '=' >> string_r;
description = desc_r;
comment = "<!--" >> *(anychar_p - "-->") >> "-->";
content = element | description;
element = comment | emptyelem | (stag >> *content >> etag);
xml = *space_p >> *element >> *space_p;
コメントのとこなど(anychar_p - "-->")のような記述ができるのがすばらしいですね。というふうにパージングまでは簡単にいくのですが。
■ セマンティックアクション
というわけでセマンティックアクションを付加していきましょう。と、いいたいのですがこれがなかなかうまくいきません。結果的に間違ってるプログラムなので詳細は書きませんが、なんだか要素によって2回読みこまれたりちゃんと1回だけよみこまれたりとよくわからない結果になってしまいます。例えば以下のようなファイルをパージングさせてみた場合。
<Window name="testwin1" width="320" height="240"> <Editbox name="edit1" left="16" top="16" width="160" height="16"/> <StaticText name="stext1" left="32" top="64" text="StaticText1"/> <Listbox name="list" left="16" top="96" width="240" height="120"> <String text="test"/> <Icon icon="hoge"/> </Listbox> </Window>
セマンティックアクションで、タグが読み込まれたときにそのタグを表示するように書いてみます。
vector<char> r_name; // name_r が読み込まれたときに名前が入るバッファ
void StartTag(const char* const, const char* cosnt)
{
printf("<%s>\n", r_name.begin());
}
void EndTag(const char* const, const char* cosnt)
{
printf("</%s>\n", r_name.begin());
}
void EmptyTag(const char* const, const char* cosnt)
{
printf("<%s>\n", r_name.begin());
}
stag = '<' >> name_r[&StartTag] >> *(s >> attribute) >> '>';
etag = "</" >> name_r[&EndTag] >> '>';
emptyelem = '<' >> name_r[&EmptyTag] >> *(s >> attribute) >> "/>";
これで実行すると、以下のような結果になります。
<Window/> <Window> <Editbox/> <StaticText/> <Listbox/> <Listbox> <String/> <Icon/> </Listbox> </Window>
うまくいってません。これだと、
<Window/>
<Window>
<Editbox/>
<StaticText/>
<Listbox/>
<Listbox>
<String/>
<Icon/>
</Listbox>
</Window>
のような構造ということになってしまいます。なんででしょう。とりあえずBNFの怪しそうなところをみてみましょう。
element = comment | emptyelem | (stag >> *content >> etag);
なんとなくわかりましたね?この場合、一番最初にコメントのマッチングが試行され、失敗すると次に<tag/>のマッチングが試行され、それも失敗すると最後に<tag></tag>のマッチングが試行されるわけです。マッチングしようとして失敗したらその時点でどうもboost::spiritはバックトラックを行うみたいなのです。なんだかprologみたいですね。バックトラックは行われるんだけど、セマンティックアクションは実行されてしまったものだから、うまくXMLが読み込めてないのです。そもそもXMLでは「<name」まで読みこんでも、それが「<name/>」「<name>」であるかどうかはそこまで読み込んでみなければわからないのです。前回「"string"」のダブルクォーテーションで囲まれた中身だけを読み込もうとして、なぜか「string"」まで読み込まれてしまう、っていうのも考えてみたらこういうことが起こってたんですね。
さてどうしましょう。いろいろ考えてみましたが、いったんタグの名前、属性読み込んでそれをバッファにためておいてタグの最後をよんだときにそれがどの種のタグなのかを決定すればよさそうです。幸いタグの中でのタグのネストはありませんのでどうにかなりそうです。ではそうやって書いてみましょう。
s = +space_p;
string_r = '\"' >> eps_p[&BeginString] >> *(anychar_p[&AckString] - '\"') >> '\"' >> eps_p[&EndString];
name_r = (alpha_p[&BeginName] | ch_p('_')[&BeginName]) >> *( (alnum_p[&MidName] | ch_p('_')[&MidName]) ) >> eps_p[&EndName];
desc_r = eps_p[&BeginDescription] >> +(anychar_p[&AckDescription] - '<') >> eps_p[&EndDescription];
attribute = name_r >> '=' >> string_r >> eps_p[&AddAttribute];
description = desc_r[&Description];
stag = '<' >> eps_p[&ClearBuf] >> name_r[&SetNodeName] >> *(s >> attribute) >> '>' >> eps_p[&PushBuf];
etag = "</" >> eps_p[&ClearBuf] >> name_r[&SetNodeName] >> '>' >> eps_p[&PopBuf];
emptyelem = '<' >> eps_p[&ClearBuf] >> name_r[&SetNodeName] >> *(s >> attribute) >> "/>" >> eps_p[&PushBuf] >> eps_p[&PopBuf];
comment = "<!--" >> *(anychar_p - "-->") >> "-->";
content = element | description;
element = comment | emptyelem | (stag >> *content >> etag);
xml = *space_p >> *element >> *space_p;
なんだかごちゃごちゃしてますが、前述の通り、いったんタグの名前、属性を読み込みながらバッファにためておき、タグの最後の「>」「/>」を見分けてからタグの種類を決定していってます。これに一番最初のXMLの例でだした以下のようなファイルを読み込ませてみます。
<MResourceFile version="1.00"> <Description> test description </Description> <!-- comment --> <Window name="testwin1" width="320" height="240"> <Editbox name="edit1" left="16" top="16" width="160" height="16"/> <StaticText name="stext1" left="32" top="64" text="StaticText1"/> <Listbox name="list" left="16" top="96" width="240" height="120"> <String text="test"/> <Icon icon="hoge"/> </Listbox> </Window> <Window name="testwin2" width="60%" height="30%"> </Window> </MResourceFile>
これの出力結果は以下の通り。
PushNode : <MResourceFile>
Attribute : version="1.00"
Description : "
"
PushNode : <Description>
Description : "
test description
"
PopNode : </Description>
Description : "
"
Description : "
"
PushNode : <Window>
Attribute : name="testwin1"
Attribute : width="320"
Attribute : height="240"
Description : "
"
PushNode : <Editbox>
Attribute : name="edit1"
Attribute : left="16"
Attribute : top="16"
Attribute : width="160"
Attribute : height="16"
PopNode : </Editbox>
Description : "
"
PushNode : <StaticText>
Attribute : name="stext1"
Attribute : left="32"
Attribute : top="64"
Attribute : text="StaticText1"
PopNode : </StaticText>
Description : "
"
PushNode : <Listbox>
Attribute : name="list"
Attribute : left="16"
Attribute : top="96"
Attribute : width="240"
Attribute : height="120"
Description : "
"
PushNode : <String>
Attribute : text="test"
PopNode : </String>
Description : "
"
PushNode : <Icon>
Attribute : icon="hoge"
PopNode : </Icon>
Description : "
"
PopNode : </Listbox>
Description : "
"
PopNode : </Window>
Description : "
"
PushNode : <Window>
Attribute : name="testwin2"
Attribute : width="60%"
Attribute : height="30%"
Description : "
"
PopNode : </Window>
Description : "
"
PopNode : </MResourceFile>
ok
というわけです。というわけで今回のサンプルプログラム。
読み込む部分しか作ってませんが、適当なクラスを作ってやればデータの保持、取り出しは簡単にいけるんじゃないかと思います。
更新日 2003/06/27