ノードを、いくつかの固まりごとに処理する
たとえば、XML形式の住所録データがあるとする。そこから宛名ラベル印刷用のデータを作る(住所録データ→XSL-FO→PDFといった形で印刷可能なデータにするとか)。宛名ラベル印刷用紙は用紙1枚につき10宛先をできるとする。そうなると、住所録データ内の住所データ(氏名、住所などなどを子としてもつ要素とする)を、10ずつ扱うとかしたい。
これは手続き型言語のように単純な繰り返し構文(C言語のforみたいなの)があれば話は簡単なのだけれども、そういうのはない。xsl:for-eachの使い方を工夫して実現する必要がある。10ずつに区切ったノードの先頭(Character[position() mod 10 = 1] または Character[(position() - 1) mod 10 = 0])をxsl:for-eachで処理するようにして、そのノード自身と、あとに続く兄弟ノード(最大9個)を処理するといったふうにやるとよい。
住所録データじゃないけど、下にマンガの登場人物データベース(しごく、単純なもの)の登場人物要素(Character要素)を、いくつかの固まりで処理するサンプルを書いた。HTMLとして出力し、各データは表形式で出力される。
表の列数(あとのサンプルだと列数も)を無理にあわせるために、ちょっとめんどうな処理を入れている部分があり、サンプルとしては冗長だけど、そのへんはご容赦のほどを。
もっと良いやり方があれば、コメント欄などでお教えください。
- "sample.xml"
- 元データとなる登場人物データベース。
- "sample1.xsl"
- 変数 range の大きさをひとかたまりとしてCharacter要素を扱う。
- "sample2.xsl"
- "sample1.xsl"を、ちょっと複雑にしたもの。変数 columnSize x 変数 rowSize の表を作る(= 変数 columnSize x 変数 rowSize の大きさをひとかたまりとしてCharacter要素を扱う。)
XSLTは、.NET Framework(System.Xml.Xsl.XslCompiledTransform クラス。PowerShellから利用する)とSAXONで処理した。
こちらの動作環境は以下のもの。
- OS
- Windows XP SP3 x86
- PowerShell
- PowerShell Ver. 2.0
- Java
- Java 6 Update 22
- SAXON
- SAXON-HE 9.3.0.4 (公式サイト:The SAXON XSLT and XQuery Processor)
各種ファイル
"sample.xml"
<?xml version="1.0" encoding="UTF-8"?> <Characters> <!-- けいおん! --> <Character> <FamilyName>平沢</FamilyName> <FirstName>唯</FirstName> <Race>人間</Race> <Sex>女</Sex> <Title>けいおん!</Title> </Character> <Character> <FamilyName>秋山</FamilyName> <FirstName>澪</FirstName> <Race>人間</Race> <Sex>女</Sex> <Title>けいおん!</Title> </Character> <Character> <FamilyName>田井中</FamilyName> <FirstName>律</FirstName> <Race>人間</Race> <Sex>女</Sex> <Title>けいおん!</Title> </Character> <Character> <FamilyName>琴吹</FamilyName> <FirstName>紬</FirstName> <Race>人間</Race> <Sex>女</Sex> <Title>けいおん!</Title> </Character> <Character> <FamilyName>中野</FamilyName> <FirstName>梓</FirstName> <Race>人間</Race> <Sex>女</Sex> <Title>けいおん!</Title> </Character> <Character> <FamilyName>山中</FamilyName> <FirstName>さわ子</FirstName> <Race>人間</Race> <Sex>女</Sex> <Title>けいおん!</Title> </Character> <!-- セキレイ --> <Character> <FamilyName>佐橋</FamilyName> <FirstName>皆人</FirstName> <Sex>男</Sex> <Race>人間</Race> <Title>セキレイ</Title> </Character> <Character> <FirstName>結</FirstName> <Race>セキレイ</Race> <Sex>女</Sex> <Title>セキレイ</Title> </Character> <Character> <FirstName>草野</FirstName> <Race>セキレイ</Race> <Sex>女</Sex> <Title>セキレイ</Title> </Character> <Character> <FirstName>松</FirstName> <Race>セキレイ</Race> <Sex>女</Sex> <Title>セキレイ</Title> </Character> <Character> <FirstName>月海</FirstName> <Race>セキレイ</Race> <Sex>女</Sex> <Title>セキレイ</Title> </Character> <Character> <FirstName>風花</FirstName> <Race>セキレイ</Race> <Sex>女</Sex> <Title>セキレイ</Title> </Character> <Character> <FirstName>篝</FirstName> <Race>セキレイ</Race> <Sex>?</Sex> <Title>セキレイ</Title> </Character> </Characters>
"sample1.xsl"
<?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" indent="yes" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN" doctype-system="http://www.w3.org/TR/html4/loose.dtd" /> <xsl:variable name="range" select="4"/> <xsl:template match="/"> <html> <head> <title>いろいろな作品の登場人物</title> <style> <![CDATA[div { margin-bottom: 8px; } table { margin: 0px; padding: 0px; border-collapse: collapse; } tr { margin: 0px; padding: 0px; } td { margin: 0px; padding: 0px; width: 180px; height: 180px; border: solid 1px black; } td div { margin: 0px; padding: 4px; } td dl { margin: 0; } td dl dt { margin: 0 0 2px 0; width: 5em; float:left; } td dl dd { margin: 0 0 2px 5em; border-bottom: solid 1px black; } td.pattern1 { background: #FFFFFF;} td.pattern2 { background: #DDDDFF;}]]> </style> </head> <body> <xsl:apply-templates /> </body> </html> </xsl:template> <xsl:template match="Characters"> <xsl:for-each select="Character[(position() - 1) mod $range = 0]"> <div> <table> <tr> <!-- カレントノードと、カレントノードに続く兄弟ノード。 --> <xsl:apply-templates select=".|following-sibling::Character[position() < $range]" /> <!-- テーブル内のセル数が $range になるようにする。足りない文、空のセルを追加する。 --> <xsl:call-template name="emptyCell"> <!-- 0〜($range-1) --> <xsl:with-param name="restNodeCount" select="$range - count(following-sibling::Character[position() < $range]) - 1" /> </xsl:call-template> </tr> </table> </div> </xsl:for-each> </xsl:template> <!-- Character 要素をテーブルのセルに。 --> <!-- 最大 $range 個の Character 要素のノードセットを処理する。 --> <xsl:template match="Character"> <td> <xsl:attribute name="class"> <xsl:choose> <xsl:when test="position() mod 2 = 1">pattern1</xsl:when> <xsl:otherwise>pattern2</xsl:otherwise> </xsl:choose> </xsl:attribute> <div> <dl> <dt>氏名</dt> <dd><xsl:if test="count(FamilyName)>0"><xsl:value-of select="FamilyName" /><xsl:text> </xsl:text></xsl:if><xsl:value-of select="FirstName" /></dd> <dt>種族</dt> <dd><xsl:value-of select="Race" /></dd> <dt>性別</dt> <dd><xsl:value-of select="Sex" /></dd> <dt>作品</dt> <dd><xsl:value-of select="Title" /></dd> </dl> </div> </td> </xsl:template> <!-- テーブルのセル数を $range の数にあわせるために、空のセルを追加する。 --> <xsl:template name="emptyCell"> <xsl:param name="restNodeCount" /> <xsl:if test="$restNodeCount >= 1"> <td> <xsl:attribute name="class"> <xsl:choose> <xsl:when test="$restNodeCount mod 2 = ($range mod 2)">pattern1</xsl:when> <xsl:otherwise>pattern2</xsl:otherwise> </xsl:choose> </xsl:attribute> <div></div> </td> <!-- 再帰呼び出し。 --> <xsl:call-template name="emptyCell"> <xsl:with-param name="restNodeCount" select="$restNodeCount - 1" /> </xsl:call-template> </xsl:if> </xsl:template> </xsl:stylesheet>
"sample1.xsl"を処理するPowerShellスクリプト。
try { $xslPath = ".\sample1.xsl"; $xmlPath = ".\sample.xml"; $htmlPath = ".\sample1result_a.html"; $xslt = new-object System.Xml.Xsl.XslCompiledTransform; $xslt.Load($xslPath); $xslt.Transform($xmlPath, $htmlPath); write "無事終了した。"; exit 0; } catch { write-error $_; exit -1; }
"sample1.xsl"をSAXONで処理するコマンド。SAXON-HEのjarファイルの位置は c:\opt\saxon\saxon9he.jar と想定している。
java -cp c:\opt\saxon\saxon9he.jar net.sf.saxon.Transform -t -strip:all "-s:.\sample.xml" "-xsl:.\sample1.xsl" "-o:.\sample1result_b.html"
"sample2.xsl"
<?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" indent="yes" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN" doctype-system="http://www.w3.org/TR/html4/loose.dtd" /> <xsl:variable name="rowSize" select="2" /> <xsl:variable name="columnSize" select="3" /> <xsl:variable name="range" select="$rowSize * $columnSize" /> <xsl:template match="/"> <html> <head> <title>いろいろな作品の登場人物</title> <style> <![CDATA[div { margin-bottom: 8px; } table { margin: 0px; padding: 0px; border-collapse: collapse; } tr { margin: 0px; padding: 0px; } td { margin: 0px; padding: 0px; width: 180px; height: 180px; border: solid 1px black; } td div { margin: 0px; padding: 4px; } td dl { margin: 0; } td dl dt { margin: 0 0 2px 0; width: 5em; float:left; } td dl dd { margin: 0 0 2px 5em; border-bottom: solid 1px black; } td.pattern1 { background: #FFFFFF;} td.pattern2 { background: #DDDDFF;}]]> </style> </head> <body> <xsl:apply-templates /> </body> </html> </xsl:template> <xsl:template match="Characters"> <!-- 表。rowSize x columnSize --> <xsl:for-each select="Character[(position() - 1) mod $range = 0]"> <div> <table> <!-- 行。columnSize --> <xsl:for-each select=".|following-sibling::Character[(position() < $range) and (position() mod $columnSize = 0)]"> <tr> <!-- カレントノードと、カレントノードに続く兄弟ノード。 --> <xsl:apply-templates select=".|following-sibling::Character[position() < $columnSize]"> <xsl:with-param name="rowNum" select="position()" /> </xsl:apply-templates> <!-- テーブル内のセル数が $columnSize になるようにする。足りない文、空のセルを追加する。 --> <xsl:call-template name="emptyCell"> <!-- 0〜$columnSize --> <xsl:with-param name="rowNum" select="position()" /> <xsl:with-param name="restNodeCount" select="$columnSize - count(following-sibling::Character[position() < $columnSize]) - 1" /> </xsl:call-template> </tr> </xsl:for-each> <!-- 必要なら数あわせの行を追加する。 --> <xsl:call-template name="emptyRow"> <!-- 0〜($rowSize - 1) --> <xsl:with-param name="restRowCount" select="$rowSize - ceiling((count(following-sibling::Character[position() < $range]) + 1) div $columnSize)" /> </xsl:call-template> </table> </div> </xsl:for-each> </xsl:template> <!-- Character 要素をテーブルのセルに。 --> <!-- 最大 $columnSize 個の Character 要素のノードセットを処理する。 --> <xsl:template match="Character"> <xsl:param name="rowNum" /> <xsl:comment><xsl:value-of select="$rowNum"/></xsl:comment> <td> <xsl:attribute name="class"> <xsl:choose> <xsl:when test="position() mod 2 = ($rowNum mod 2)">pattern1</xsl:when> <xsl:otherwise>pattern2</xsl:otherwise> </xsl:choose> </xsl:attribute> <div> <dl> <dt>氏名</dt> <dd><xsl:if test="count(FamilyName)>0"><xsl:value-of select="FamilyName" /><xsl:text> </xsl:text></xsl:if><xsl:value-of select="FirstName" /></dd> <dt>種族</dt> <dd><xsl:value-of select="Race" /></dd> <dt>性別</dt> <dd><xsl:value-of select="Sex" /></dd> <dt>作品</dt> <dd><xsl:value-of select="Title" /></dd> </dl> </div> </td> </xsl:template> <!-- テーブルの列数を $rowSize の数にあわせるために、空の行を追加する。 --> <xsl:template name="emptyRow"> <xsl:param name="restRowCount" /> <xsl:if test="$restRowCount >= 1"> <tr> <xsl:call-template name="emptyCell"> <xsl:with-param name="rowNum" select="$rowSize - $restRowCount + 1" /> <xsl:with-param name="restNodeCount" select="$columnSize" /> </xsl:call-template> </tr> <!-- 再帰呼び出し。 --> <xsl:call-template name="emptyRow"> <xsl:with-param name="restRowCount" select="$restRowCount - 1" /> </xsl:call-template> </xsl:if> </xsl:template> <!-- テーブルのセル数を $columnSize の数にあわせるために、空のセルを追加する。 --> <xsl:template name="emptyCell"> <xsl:param name="rowNum" /> <xsl:param name="restNodeCount" /> <xsl:if test="$restNodeCount >= 1"> <td> <xsl:attribute name="class"> <xsl:choose> <xsl:when test="$restNodeCount mod 2 = (((($rowNum - 1) mod 2) + $columnSize) mod 2)">pattern1</xsl:when> <xsl:otherwise>pattern2</xsl:otherwise> </xsl:choose> </xsl:attribute> <div></div> </td> <!-- 再帰呼び出し。 --> <xsl:call-template name="emptyCell"> <xsl:with-param name="rowNum" select="$rowNum" /> <xsl:with-param name="restNodeCount" select="$restNodeCount - 1" /> </xsl:call-template> </xsl:if> </xsl:template> </xsl:stylesheet>
"sample2.xsl"を処理するPowerShellスクリプト。
try { $xslPath = ".\sample2.xsl"; $xmlPath = ".\sample.xml"; $htmlPath = ".\sample2result_a.html"; $xslt = new-object System.Xml.Xsl.XslCompiledTransform; $xslt.Load($xslPath); $xslt.Transform($xmlPath, $htmlPath); write "無事終了した。"; exit 0; } catch { write-error $_; exit -1; }
"sample2.xsl"をSAXONで処理するコマンド。SAXON-HEのjarファイルの位置は c:\opt\saxon\saxon9he.jar と想定している。
java -cp c:\opt\saxon\saxon9he.jar net.sf.saxon.Transform -t -strip:all "-s:.\sample.xml" "-xsl:.\sample2.xsl" "-o:.\sample2result_b.html"