id:eel3:20110226:1298686840 にてRubyでXMLファイルをソートしたのだが、どうも釈然としないものがある。
XMLを変換するのならXSLTではないだろうか? XSLTではないだろうか?*1
もう5年以上も前になるが、人生で初めてプログラミング言語に触れて4〜5ヶ月ぐらい経った頃にXMLとXSLTについて学ぶ機会があった。で、それから半年もしないうちに書籍情報のデータベース代わりとなるXMLファイルとそれをXHTMLに変換するXSLTを書いた。それらは今でも使用している。
それ以降XSLTを書く機会は無かったとはいえ、今までのキャリアでXMLを変換する技術を身に付けていたのは間違いない。なのに何故使わなかったのだろう?*2
ということで久しぶりにXSLTで書いてみた。
<?xml version="1.0" encoding="Windows-1252" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="Windows-1252" indent="yes" /> <xsl:template match="/"> <xsl:apply-templates /> </xsl:template> <xsl:template match="NotepadPlus"> <NotepadPlus> <xsl:apply-templates /> </NotepadPlus> </xsl:template> <xsl:template match="AutoComplete"> <xsl:element name="AutoComplete"> <xsl:attribute name="language"> <xsl:value-of select="@language" /> </xsl:attribute> <xsl:copy-of select="Environment" /> <xsl:for-each select="KeyWord"> <xsl:sort select="@name" data-type="text" order="ascending" case-order="upper-first" /> <xsl:copy-of select="." /> </xsl:for-each> </xsl:element> </xsl:template> </xsl:stylesheet>
さすがXSLT。RubyでREXMLを使った場合よりも短くさくっと記述できた。久しぶりにXSLTを使ってみて思ったのだが、XMLというツリー構造のデータを扱うので、やはり再帰に慣れていると記述しやすく感じる。
このコードでも問題ないのだが、気になる点がある。例えばxsl:templateの属性matchでNotepadPlusという要素名を指定しておきながら、出力するタグとして
同じことはAutoComplete要素とその属性languageにも言える。基本的にノードをコピーするだけなのに、コピーするノードの構造に必要以上に密着している気がしてならない*3。これは悪い兆候だ。ノードの構造が少し変化しただけで、このXSLTは使えなくなる可能性がある。
まあ平均よりも下のプログラマだろう私でも気が付いた問題点なのだから、XSLT自体に解決の為の機能が備わっているはずだ。少し調べてみたらxsl:copyが使えそうだったので、書き直してみた。
<?xml version="1.0" encoding="Windows-1252" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="Windows-1252" indent="yes" /> <xsl:template match="/"> <xsl:apply-templates /> </xsl:template> <xsl:template match="NotepadPlus"> <xsl:copy> <xsl:apply-templates /> </xsl:copy> </xsl:template> <xsl:template match="AutoComplete"> <xsl:copy> <xsl:for-each select="@*"> <xsl:copy /> </xsl:for-each> <xsl:copy-of select="Environment" /> <xsl:for-each select="KeyWord"> <xsl:sort select="@name" data-type="text" order="ascending" case-order="upper-first" /> <xsl:copy-of select="." /> </xsl:for-each> </xsl:copy> </xsl:template> </xsl:stylesheet>
さて、このXSLTで変換したXMLファイルだが……実はNotepad++では使えない。コールチップ機能は正常に動作するのだが、関数補完の機能が動作しない。
この原因はxsl:sortのソート順にある。KeyWord要素の属性nameの値でソートしている訳だが、例えば以下の値があるとする。
CCC AAA bbb
これをxsl:sortで文字列として昇順でソートすると、次の順序になる。
AAA bbb CCC
しかしNotepad++で関数補完機能を使うためには、次の順序にならなければならない。
AAA CCC bbb
恐らくNotepad++では、属性nameの値がASCIIコードの昇順になるようにソートされている必要がある。実際、前回Rubyで書いたスクリプトではその順番にソートされて、Notepad++で何の問題もなく使用できている。
ソート順についてはXSLTプロセッサ独自の機能を使うなどで解決できるかもしれないが、まだそこまで調べていない。