XSLTプログラミング

年が明けたからといって単純に「あけおめ」とかブログに書くのは私のキャラでない。しかし、たまには季節感も尊重しようではないかと思う。そこで今回のお題はカレンダー。タイトルでネタバレしてる通り、XSLTを用いて実現する。XSLTについて深くは説明しないが、要はXMLで書かれたデータを変換するルールの書式だ。実は何年か前に書いたのを引っぱり出してきたわけだが。
XSLTプログラミング言語ではないとXML界の人は主張しているとも聞く。でも。やはりプログラミング言語だよ。少なくともそう使い得ると思う。
さて、まず用意するのはXML。カレンダーを作成するには何年のカレンダーを作成するのか情報が必要だ。

<?xml version="1.0" encoding="Shift_JIS" standalone="no" ?>
<?xml-stylesheet type="text/xsl" href="calendar.xsl" ?>
<year> 2007 </year>

とりあえず今年のカレンダーを作ることにする。で、このXMLに適用するとカレンダーを出力するようなXSLTを考えるわけだ。

<?xml version="1.0" encoding="Shift_JIS"?>
<xsl:stylesheet
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 version="1.0"
 xmlns="http://www.w3.org/1999/xhtml">

<xsl:template match="/">
<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS" />
  <link rel="stylesheet" type="text/css" />
  <title>カレンダー</title>
 </head>

 <body>
  <xsl:variable name="firstday">
   <xsl:call-template name="getfirst">
    <xsl:with-param name="x" select="year" />
   </xsl:call-template>
  </xsl:variable>

  <xsl:variable name="leapyear">
   <xsl:call-template name="isleap">
    <xsl:with-param name="x" select="year" />
   </xsl:call-template>
  </xsl:variable>

  <table>
   <caption> <h1><xsl:value-of select="year" /></h1> </caption>
   <tr valign="top">
    <td>
     <xsl:call-template name="calendermonth">
      <xsl:with-param name="x" select="$firstday" />
      <xsl:with-param name="y" select="31" />
      <xsl:with-param name="z"> 1月 </xsl:with-param>
     </xsl:call-template>
    </td>
    <td>
     <xsl:call-template name="calendermonth">
      <xsl:with-param name="x" select="($firstday+3)mod 7" />
      <xsl:with-param name="y" select="28 + $leapyear" />
      <xsl:with-param name="z"> 2月 </xsl:with-param>
     </xsl:call-template>
    </td>
    <td>
     <xsl:call-template name="calendermonth">
      <xsl:with-param name="x" select="($firstday+3+$leapyear)mod 7" />
      <xsl:with-param name="y" select="31" />
      <xsl:with-param name="z"> 3月 </xsl:with-param>
     </xsl:call-template>
    </td>
   </tr>
   <tr valign="top">
    <td>
     <xsl:call-template name="calendermonth">
      <xsl:with-param name="x" select="($firstday+6+$leapyear)mod 7" />
      <xsl:with-param name="y" select="30" />
      <xsl:with-param name="z"> 4月 </xsl:with-param>
     </xsl:call-template>
    </td>
    <td>
     <xsl:call-template name="calendermonth">
      <xsl:with-param name="x" select="($firstday+1+$leapyear)mod 7" />
      <xsl:with-param name="y" select="31" />
      <xsl:with-param name="z"> 5月 </xsl:with-param>
     </xsl:call-template>
    </td>
    <td>
     <xsl:call-template name="calendermonth">
      <xsl:with-param name="x" select="($firstday+4+$leapyear)mod 7" />
      <xsl:with-param name="y" select="30" />
      <xsl:with-param name="z"> 6月 </xsl:with-param>
     </xsl:call-template>
    </td>
   </tr>
   <tr valign="top">
    <td>
     <xsl:call-template name="calendermonth">
      <xsl:with-param name="x" select="($firstday+6+$leapyear)mod 7" />
      <xsl:with-param name="y" select="31" />
      <xsl:with-param name="z"> 7月 </xsl:with-param>
     </xsl:call-template>
    </td>
    <td>
     <xsl:call-template name="calendermonth">
      <xsl:with-param name="x" select="($firstday+2+$leapyear)mod 7" />
      <xsl:with-param name="y" select="31" />
      <xsl:with-param name="z"> 8月 </xsl:with-param>
     </xsl:call-template>
    </td>
    <td>
     <xsl:call-template name="calendermonth">
      <xsl:with-param name="x" select="($firstday+5+$leapyear)mod 7" />
      <xsl:with-param name="y" select="30" />
      <xsl:with-param name="z"> 9月 </xsl:with-param>
     </xsl:call-template>
    </td>
   </tr>
   <tr valign="top">
    <td>
     <xsl:call-template name="calendermonth">
      <xsl:with-param name="x" select="($firstday+$leapyear)mod 7" />
      <xsl:with-param name="y" select="31" />
      <xsl:with-param name="z"> 10月 </xsl:with-param>
     </xsl:call-template>
    </td>
    <td>
     <xsl:call-template name="calendermonth">
      <xsl:with-param name="x" select="($firstday + 3 + $leapyear)mod 7" />
      <xsl:with-param name="y" select="30" />
      <xsl:with-param name="z"> 11月 </xsl:with-param>
     </xsl:call-template>
    </td>
    <td>
     <xsl:call-template name="calendermonth">
      <xsl:with-param name="x" select="($firstday + 5 + $leapyear)mod 7" />
      <xsl:with-param name="y" select="31" />
      <xsl:with-param name="z"> 12月 </xsl:with-param>
     </xsl:call-template>
    </td>
   </tr>
  </table>
 </body> 
</html>

</xsl:template>

<!-- 1年の最初の曜日を取得する -->
<xsl:template name="getfirst">
 <xsl:param name="x" />
 <xsl:variable name="first"
 select="($x+floor(($x -1)div 4)-floor(($x -1)div 100)+floor(($x -1)div 400)) mod 7" />
 <xsl:value-of select="$first" />
</xsl:template>

<!-- その年が閏年かどうか判定する -->
<xsl:template name="isleap">
 <xsl:param name="x" />
 <xsl:variable name="leap"
 select="number(($x mod 4=0 and $x mod 100!=0)or($x mod 400=0))" />
 <xsl:value-of select="$leap" />
</xsl:template>

<!-- 一月分のカレンダーを出力 -->
<xsl:template name="calendermonth">
 <xsl:param name="x" />
 <xsl:param name="y" />
 <xsl:param name="z" />
 <table border="1">
 <caption> <xsl:value-of select="$z" /> </caption>
 <xsl:call-template name="calendermonthbody">
 <xsl:with-param name="y" select="$y" />
 <xsl:with-param name="z" select="- $x + 1" />
 </xsl:call-template>
 </table>
</xsl:template>

<xsl:template name="calendermonthbody">
 <xsl:param name="y" />
 <xsl:param name="z" />
 <tr>
 <xsl:call-template name="calenderweek">
 <xsl:with-param name="x" select="0" />
 <xsl:with-param name="y" select="$y" />
 <xsl:with-param name="z" select="$z" />
 </xsl:call-template>
 </tr>
 <xsl:if test="$z + 7 &lt;= $y">
 <xsl:call-template name="calendermonthbody">
 <xsl:with-param name="y" select="$y" />
 <xsl:with-param name="z" select="$z+7" />
 </xsl:call-template>
 </xsl:if>

</xsl:template>

<!-- 一週間のカレンダーを出力 -->
<xsl:template name="calenderweek">
 <xsl:param name="x" />
 <xsl:param name="y" />
 <xsl:param name="z" />

 <xsl:choose>
 <xsl:when test="$z &lt;1 or $z &gt; $y">
   <td>.</td>
 </xsl:when>
 <xsl:otherwise>
   <td>
     <xsl:value-of select="$z" />
   </td>
 </xsl:otherwise>
 </xsl:choose>
 <xsl:if test="$x &lt; 6">
 <xsl:call-template name="calenderweek">
   <xsl:with-param name="x" select="$x + 1" />
   <xsl:with-param name="y" select="$y" />
   <xsl:with-param name="z" select="$z + 1" />
 </xsl:call-template>
 </xsl:if>
</xsl:template>

</xsl:stylesheet>

多少冗長なところもあるが、所詮はネタなので作り込む気はない。特に理由がなければ一般的なプログラミング言語を使った方がより簡単にカレンダーを作成することができるし、カレンダーに限らず大抵の場合にはJavaなりPerlなりのXMLライブラリを使った方がより柔軟にXMLを操作することが出来る。XSLTの存在意義は形式的定義に使えるとか、そんなことなんだろう。
Document ID: a3e9f8f89e37557527a9ee8c0872576b