Notepad++のXMLファイルをソートして整形して出力する:3発目は不発

id:eel3:20110226:1298686840 にてRubyXMLファイルをソートしたのだが、どうも釈然としないものがある。

XMLを変換するのならXSLTではないだろうか? XSLTではないだろうか?*1

もう5年以上も前になるが、人生で初めてプログラミング言語に触れて4〜5ヶ月ぐらい経った頃にXMLXSLTについて学ぶ機会があった。で、それから半年もしないうちに書籍情報のデータベース代わりとなる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>

さすがXSLTRubyでREXMLを使った場合よりも短くさくっと記述できた。久しぶりにXSLTを使ってみて思ったのだが、XMLというツリー構造のデータを扱うので、やはり再帰に慣れていると記述しやすく感じる。

このコードでも問題ないのだが、気になる点がある。例えばxsl:templateの属性matchでNotepadPlusという要素名を指定しておきながら、出力するタグとしてと直書きしている。今書いているXSLTでは一部ノードの出力順を変更するだけで、出力する内容自体は変わらない。折角matchにNotepadPlusを指定しているのだから、改めてと要素名を記述するのは無駄ではないか? matchに指定した要素名を再び記述することなく流用できないだろうか?

同じことは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プロセッサ独自の機能を使うなどで解決できるかもしれないが、まだそこまで調べていない。

*1:重要なので繰り返し。

*2:まあでもRubyXMLを操作する方法を覚えたのは無駄ではないと思う。

*3:まあ一部の出力順を制御する以上、ある程度はノードの構造に密着したコードになるのは仕方がないのだが。