package.xml に XSLT

このあいだの勉強会でやったXML入門の復習。

今夜のお題は、pearのpackage.xmlXSLTを使って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>
<?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/>

というかんじに変換するだけ。とりあえず、ね。

考えたこと。

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>
  1. トップレベルで、, で外側を囲む指示
  2. ぶら下がっている各ノード(ここではpackageだけ)に対してテンプレート適用を指示。
    • これを書かないと何も表示されません。
    • なので、たぶんdepth firstな再帰関数みたいなのが裏に存在して、最初に "/" を突っ込んで実行している感覚なんだと思います。
  3. なんにでも ("*") マッチするテンプレート。
    • トップレベルは先に定義してある "/" にマッチするのでこっちには来ないんだろうと推測。
  4. 最初に name() 関数を使ってタグ名を出力する
  5. 条件分岐。
    • ifはあるんだけれどelseがないんですね!! やばいよキレイすぎ。
    • switch-caseみたいなやつです。
  6. 子ノードがあるときは子ノードに処理を移す(再帰呼び出し)
  7. 子ノードがないなら、テキスト要素を出力
  8. smartyでも使えた気がする、join(glue, array)的なやりかた。
    • このテンプレート定義はattribute全部じゃなくて、各attributeに対して実行されることに注意。

はまりどころ

やっぱり、感覚をつかむのに時間がかかりますね。xslのコード屋さんはかなり専門職的な扱いをうけることもあるらしいです。

はまったところとしては、上の(8)みたいな時に、for-eachを中に書いてしまう的なものでした。あとはparse順がわかんなくて、breadth firstな感じで書いたほうがいいのかなー、みたいに迷ってしまったりとか。

さらにいうと、出力はhoge.{xml,xsl}を同じディレクトリにおいてブラウザで開いて確認するわけですが、ソースを表示しても元のxmlが表示されるだけなんですよね。これがかなりうざくて手間取りやすい要因だと思う。

まとめ

ということで、package.xml をブラウザで開いてもそれなりに整形されて見れるようになりました。しかも pear package でうるさいことを言われたりすることもありませんよ!! これでパッケージ作るときに「yamlだったらなー」なんて不満も吹っ飛びます!!

いや、ウソです。yamlだったらなー。