このあいだの勉強会でやったXML入門の復習。
今夜のお題は、pearのpackage.xmlをXSLTを使ってul, liのリストで見やすく(?)表示します。
XSLTって?
XMLにスタイルシート(XSL, eXtensible Stylesheet Language)を適用させて、HTMLとかSVGとか、あるいはplain textとかに変換(Transform)することらしいです。
書いている途中はsmartyなんかのテンプレートと似た感覚の場面もあるんだけれど、全体としてはnode単位で切り出す .l(lex) に対してprintしまくる .y(yacc) を書いている感じなのかなぁ? (すごい適当)
簡単な例
- 入力(test.xml):
<?xml version="1.0"?> <?xml-stylesheet href="test.xsl" type="text/xsl"?> <hoge foo="bar">hello xslt!</hoge>
- スタイルシート(test.xsl):
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="text"/> <xsl:template match="/hoge"> element=<xsl:value-of select="name()"/> attr_foo=<xsl:value-of select="@foo"/> value=<xsl:value-of select="."/> </xsl:template> </xsl:stylesheet>
これで、top-levelにある
element=hoge attr_foo=bar value=hello xslt!
と出力してくれる。はず。
重要っぽいのは
- select=, match= の部分にかくもの (XPathというらしい)
- xsl:hogehoge の部分
の2つで、ここに何が書けるのかは http://www.w3.org/TR/xslt とかを見ればOK。
ただ、xmlにしてもxslにしても、どのようにparseされて、どのようにmatchするtemplateが適用されるのかはさっぱりわからず(調べようともしていない)、こう書くとこうなるのかー、という試行錯誤から感覚をつかんでいこうとしてます。
目標
DTDを見てどんな構造になっているかを知っておくとかしたくないので、
<hoge foo="foo" bar="bar"/>
hoge (foo=foo, bar=bar)
<hoge><fuga/><hoge/>
- hoge
- fuga
というかんじに変換するだけ。とりあえず、ね。
考えたこと。
DTDは見ない以上、どんな構造になっているかさっぱり分からないので、XPathに "*" とか相対指定とかを書きまくるんだろうなー。
で、すごいヒントになる、いやむしろそのままコピペでできるんじゃないか的なサンプルが、勉強会で出てきた doNothing.xsl という例でした。つまり "何もしない" transformです。Cで「printfを実装する」的な、よくある課題だそうで。(xslt + donothing とか identity とか identical とかで検索すればきっとどっかにあると思います。)
ということでできたぞ
かなり中略ですができました。
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="html" encoding="UTF-8"/> <xsl:template match="/"> <!-- (1) --> <html> <body> <ul> <xsl:apply-templates select="child::*"/> <!-- (2) --> </ul> </body> </html> </xsl:template> <xsl:template match="*"> <!-- (3) --> <li> <xsl:value-of select="name()"/> <!-- (4) --> <xsl:if test="attribute::*"> (<xsl:apply-templates select="attribute::*"/>) </xsl:if> <ul> <xsl:choose> <!-- (5) --> <xsl:when test="child::*"> <xsl:apply-templates select="child::*"/> <!-- (6) --> </xsl:when> <xsl:otherwise> <xsl:value-of select="."/> <!-- (7) --> </xsl:otherwise> </xsl:choose> </ul> </li> </xsl:template> <xsl:template match="@*"> <xsl:value-of select="name()"/>=<xsl:value-of select="."/> <xsl:if test="not(position()=last())">, </xsl:if> <!-- (8) --> </xsl:template> </xsl:stylesheet>
- トップレベルで、, で外側を囲む指示
- ぶら下がっている各ノード(ここではpackageだけ)に対してテンプレート適用を指示。
- これを書かないと何も表示されません。
- なので、たぶんdepth firstな再帰関数みたいなのが裏に存在して、最初に "/" を突っ込んで実行している感覚なんだと思います。
- なんにでも ("*") マッチするテンプレート。
- トップレベルは先に定義してある "/" にマッチするのでこっちには来ないんだろうと推測。
- 最初に name() 関数を使ってタグ名を出力する
- 条件分岐。
- ifはあるんだけれどelseがないんですね!! やばいよキレイすぎ。
- switch-caseみたいなやつです。
- 子ノードがあるときは子ノードに処理を移す(再帰呼び出し)
- 子ノードがないなら、テキスト要素を出力
- smartyでも使えた気がする、join(glue, array)的なやりかた。
- このテンプレート定義はattribute全部じゃなくて、各attributeに対して実行されることに注意。