XSLT20 の変更点
- 追加された行はこの色です。
- 削除された行はこの色です。
- XSLT20 へ行く。
- XSLT20 の差分を削除
--- title: XSL Transformations (XSLT) Version 2.0 description: XSL Transformations (XSLT) Version 2.0 に関するメモ書き author: aterai pubdate: 2016-03-24T16:35:29+09:00 --- #contents * 概要 [#summary] `XSL Transformations (XSLT) Version 2.0`関連のメモ書きです。 ** XSL Transformations (XSLT) Version 2.0 とは [#l44110f7] - [https://www.w3.org/TR/xslt20/ XSL Transformations (XSLT) Version 2.0] * 関数など [#function] ** fn:doc-available() [#x03c1df3] #code{{ @echo off setlocal set SRC=C:/tmp/aaa/word/document.xml set DST=C:/output/aaa/xxx.xml set LIB=C:/lib/saxon-9.1.0.8.jar set SAXON=net.sf.saxon.Transform set XSLT=C:/xslt/docx2xxx.xsl "%JAVA_HOME%\bin\java" -cp "%LIB%" %SAXON% -s:"%SRC%" -xsl:"%XSLT%" -o:"%DST%" }} #code{{ <!-- C:/tmp/aaa/word/numbering.xml が存在するかをチェックしたい --> <xsl:if test="fn:doc-available(fn:replace(fn:base-uri(),'document\.xml$','numbering.xml'))"> <xsl:variable name="numbering" select="fn:document('numbering.xml',/)/w:numbering" /> </xsl:if> }} - `fn:document('numbering.xml',/)`のように第二引数を指定して入力ファイル相対パスで`fn:doc-available`を使用することは出来ない - `fn:base-uri()` -> `C:/tmp/aaa/word/document.xml` - `fn:resolve-uri('')` -> `C:/xslt/docx2xxx.xsl` ** xsl:analyze-string [#ic1c9eb9] `docx`の`w:lvlText`のフォーマット(例えば`%1.%2.%3`)に従って、実際の段落番号文字列(例えば、`1.5.10`)に変換する場合、`xsl:analyze-string`が便利。 - [https://msdn.microsoft.com/ja-jp/library/office/ee922775(v=office.14).aspx Open XML WordprocessingML の段落番号を操作する] #code{{ <!-- w:lvlRestartの処理などは省略 --> <xsl:variable name="numberText"> <xsl:analyze-string select="$lvl/w:lvlText/@w:val" regex="%\d"> <xsl:matching-substring> <xsl:variable name="pos" select="number(replace(.,'%',''))" /> <xsl:variable name="i" select="$pos - 1" /> <xsl:variable name="ii" select="$i - 1" /> <xsl:variable name="parentList" select="$list[(w:pPr/w:numPr/w:ilvl and w:pPr/w:numPr/w:ilvl/@w:val=$ii) or ($ii=0 and not(w:pPr/w:numPr/w:ilvl))]" /> <xsl:variable name="pstl" select="if(empty($parentList)) then '' else $parentList[1]/w:pPr/w:pStyle/@w:val" /> <xsl:variable name="next" select="if(empty($psList) or empty($pstl)) then () else index-of($psList,$pstl)" /> <xsl:variable name="list2" select="if(empty($next)) then $list else subsequence($list,$next[last()])" /> <xsl:value-of select="fn:count($list2[(w:pPr/w:numPr/w:ilvl and w:pPr/w:numPr/w:ilvl/@w:val=$i) or ($i=0 and w:pPr/w:numPr and not(w:pPr/w:numPr/w:ilvl))])" /> </xsl:matching-substring> <xsl:non-matching-substring> <xsl:value-of select="." /> </xsl:non-matching-substring> </xsl:analyze-string> </xsl:variable> }} - `Excel`の日付書式(`YYYY/MM/DD`)を`XSLT 2.0`の日付書式(`[Y0001]/[M01]/[D01]`)に変換 -- 条件付き書式や`\`エスケープなどには未対応 #code{{ <!-- 関数: Excelの日付書式をXSLTの日付書式に変換 --> <xsl:function as="xs:string" name="kjxf:convertDateFormat"> <xsl:param name="formatCode" /> <xsl:variable name="ret"> <xsl:analyze-string select="$formatCode" regex="([Y|y]+)|([M|m]+)|([D|d])+"> <xsl:matching-substring> <xsl:variable name="len" select="fn:string-length(.)" /> <xsl:choose> <xsl:when test="regex-group(1)"><!-- Y --> <xsl:value-of select="'[Y',substring('0000',1,$len - 1),'1]'" separator="" /> </xsl:when> <xsl:when test="regex-group(2)"><!-- M --> <xsl:choose> <xsl:when test="$len = 5"> <xsl:value-of select="'[MN,*-1]'" /> </xsl:when> <xsl:when test="$len = 4"> <xsl:value-of select="'[MNn]'" /> </xsl:when> <xsl:when test="$len = 3"> <xsl:value-of select="'[MNn,*-3]'" /> </xsl:when> <xsl:when test="$len = 2"> <xsl:value-of select="'[M01]'" /> </xsl:when> <xsl:when test="$len = 1"> <xsl:value-of select="'[M]'" /> </xsl:when> </xsl:choose> </xsl:when> <xsl:when test="regex-group(3)"><!-- D --> <xsl:choose> <xsl:when test="$len = 4"> <xsl:value-of select="'[FNn]'" /> </xsl:when> <xsl:when test="$len = 3"> <xsl:value-of select="'[FNn,*-3]'" /> </xsl:when> <xsl:when test="$len = 2"> <xsl:value-of select="'[D01]'" /> </xsl:when> <xsl:when test="$len = 1"> <xsl:value-of select="'[D]'" /> </xsl:when> </xsl:choose> </xsl:when> </xsl:choose> </xsl:matching-substring> <xsl:non-matching-substring> <xsl:value-of select="." /> </xsl:non-matching-substring> </xsl:analyze-string> </xsl:variable> <xsl:value-of select="$ret" /> </xsl:function> }} ** to演算子 [#y5a69778] `<xsl:for-each select="1 to $gridBefore">`のように、指定した回数でループすることが可能で、以下の例では`w:trPr/w:gridBefore`で指定された数だけ空の`td`を作成している。 `<xsl:for-each select="1 to $gridBefore">`のように、指定した回数でのループが可能で、以下の例では`w:trPr/w:gridBefore`で指定された数だけ空の`td`を作成している。 #code{{ <xsl:template match="w:tr"> <tr> <xsl:variable name="gridBefore" as="xs:integer" select="if(w:trPr/w:gridBefore) then w:trPr/w:gridBefore/@w:val else 0" /> <xsl:if test="$gridBefore > 0"> <xsl:for-each select="1 to $gridBefore"> <td></td> </xsl:for-each> </xsl:if> <xsl:apply-templates select="w:tc[not(w:tcPr/w:vMerge) or w:tcPr/w:vMerge/@w:val='restart']" /> <tr> </xsl:template> }} - [http://xsltbyexample.blogspot.jp/2010/05/obtain-position-from-for-expression-in.html XSLT by Example: Obtain position() from “for” expression in Xpath 2]のように、`<xsl:variable name="aaa" select="for $i in (1 to 9) return $i" />`で`<xsl:variable name="bbb" as="xs:integer*" select="(1,2,3,4,5,6,7,8,9)" />`と同じシーケンスが作成可能 #code{{ <xsl:variable name="aaa" select="for $i in (1 to 9) return $i" /> <xsl:variable name="bbb" as="xs:integer*" select="(1,2,3,4,5,6,7,8,9)" /> <xsl:value-of select="deep-equal($aaa,$bbb)" /> <!-- true --> }} - `<xsl:value-of select="for $i in (0 to 2) return $i" />`は、`(0,1,2)` - `<xsl:value-of select="for $i in (3 to 1) return $i" />`は、`()` - `<xsl:value-of select="for $i in reverse(1 to 3) return $i" />`は、`(3,2,1)` - `<xsl:value-of select="for $i in (1 to 1) return $i" />`は、`(1)` - `<xsl:value-of select="for $i in (-1 to 1) return $i" />`は、`(-1,0,1)` - `<xsl:value-of select="for $i in (1, 3 to 5, 7) return $i" />`は、`(1,3,4,5,7)` ** for~in~return 式 [#gd038dff] `for~in~return`式は、二重ループなども可能。 #code{{ <xsl:function as="xs:string*" name="myfn:get-merge-cells"> <xsl:param name="ref" as="xs:string" /> <xsl:variable name="a" as="xs:integer*"> <xsl:analyze-string select="$ref" regex="([A-Z]+)([0-9]+):([A-Z]+)([0-9]+)"> <xsl:matching-substring> <xsl:sequence select="myfn:column-index(regex-group(1))" /> <xsl:sequence select="xs:integer(regex-group(2))" /> <xsl:sequence select="myfn:column-index(regex-group(3))" /> <xsl:sequence select="xs:integer(regex-group(4))" /> </xsl:matching-substring> </xsl:analyze-string> </xsl:variable> <!-- xsl:sequence select="remove(for $ri in ($a[2] to $a[4]) return for $ci in ($a[1] to $a[3]) return concat(myfn:column-label($ci),$ri), 1)" / --> <xsl:sequence select="remove(for $ri in ($a[2] to $a[4]), $ci in ($a[1] to $a[3]) return concat(myfn:column-label($ci),$ri), 1)" /> </xsl:function> }} - `myfn:get-merge-cells('A18:D20')`は、`(B18, C18, D18, A19, B19, C19, D19, A20, B20, C20, D20)` ** fn:codepoints-to-string, fn:string-to-codepoints [#cb578b10] - `Excel`などの列名(アルファベット`26`進数`bijective base-26`の文字列)を`10`進数の列番号に変換する -- `<xsl:value-of select="myfn:column-index('AA')" />`は、`27` -- `<xsl:value-of select="myfn:column-index('XFD')" />`は、`16384` #code{{ <!-- 関数: アルファベット26進数(bijective base-26)の文字列を10進数のxs:integerに変換 --> <!-- べき乗が計算できない?ので、あらかじめ用意する。最大列はXFD(16384)なので、26^3(17576)は不要 --> <xsl:variable name="power" as="xs:integer+" select="(1, 26, 676)" /> <!-- xsl:variable name="AtoZ" as="xs:string+" select="('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z')" / --> <xsl:variable name="AtoZ" as="xs:string+" select="for $i in (0 to 26 - 1) return codepoints-to-string(string-to-codepoints('A') + $i)" /> <xsl:function as="xs:integer" name="myfn:column-index"> <xsl:param name="str" as="xs:string" /> <xsl:variable name="len" select="fn:string-length($str)" /> <!-- 最大3桁 --> <xsl:value-of select="sum(for $i in (1 to $len) return index-of($AtoZ, substring($str, $i, 1)) * $power[$len - $i + 1])" /> </xsl:function> }} - 列番号の数値を列名に変換 -- `<xsl:value-of select="myfn:column-label(702)" />`は、`ZZ` -- `<xsl:value-of select="myfn:column-label(703)" />`は、`AAA` #code{{ <xsl:function as="xs:string" name="myfn:column-label"> <xsl:param name="num" as="xs:integer" /> <xsl:variable name="cpa" as="xs:integer" select="string-to-codepoints('A')" /> <xsl:variable name="dv3" as="xs:integer" select="($num - 26 - 1) idiv 676" /> <xsl:variable name="st3" as="xs:string" select="if($dv3 != 0) then codepoints-to-string($cpa + $dv3 - 1) else ''" /> <xsl:variable name="md3" as="xs:integer" select="$num - ($dv3 * 676)" /> <xsl:variable name="dv2" as="xs:integer" select="($md3 - 1) idiv 26" /> <xsl:variable name="st2" as="xs:string" select="if($dv2 != 0) then codepoints-to-string($cpa + $dv2 - 1) else ''" /> <xsl:variable name="md2" as="xs:integer" select="$md3 - ($dv2 * 26)" /> <xsl:variable name="st1" as="xs:string" select="codepoints-to-string($cpa + $md2 - 1)" /> <xsl:value-of select="concat($st3,$st2,$st1)" /> </xsl:function> }} ** fn:format-date関数、xs:dayTimeDurationデータタイプ [#q5c8e46f] - `Excel`の日付のシリアル値を指定された書式で日付に変換する -- シリアル値は、`1900-01-00`からの日数 -- `1900-01-01`: `1` -- `1900-02-29`: `60`(うるう年計算のバグ、`Leap year bug`) -- `2016-07-27`: `42577` - `xs:date('1899-12-31') + (シリアル値 - 1) * xs:dayTimeDuration('P1D')` -- シリアル値から`1`引いて計算するのは、うるう年計算のバグで本来存在しないはずの`1900-02-29`日分を除くため -- [https://support.microsoft.com/en-us/kb/214326 Excel incorrectly assumes that the year 1900 is a leap year] - `Excel`の日付書式(例:`YYYY/MM/DD`)を`fn:format-date`関数に与える書式(例:`[Y0001]/[M01]/[D01]`)に変換する必要がある(以下のサンプルテンプレートでは省略) #code{{ <xsl:variable name="styleSheetNumFmts" select="document('styles.xml',/)/styleSheet/numFmts/numFmt" /> <xsl:template match="c"> <xsl:variable name="xf" select="..." /> <xsl:variable name="numFmt" select="$styleSheetNumFmts[@numFmtId = $xf/@numFmtId]" /> <xsl:variable name="formatCode" select="if(fn:exists($numFmt)) then fn:replace(fn:tokenize($numFmt/@formatCode,';')[1], '\[.+\]|_\)|\\(.)', '$1') else ''" /> <td> <xsl:value-of select="fn:format-date(xs:date('1900-01-01') + xs:integer(v - 2) * xs:dayTimeDuration('P1D'), $formatCode)" /> </td> </xsl:template> }} ** fn:tokenize関数 [#u62a28d9] - 文字列を分割 #code{{ <xsl:value-of select="fn:tokenize('aaa;bbb;ccc', ';')[1]" /> <!-- aaa --> <xsl:value-of select="fn:tokenize('aaa"bbb";ccc', ';')[1]" /> <!-- aaa"bbb" --> <xsl:value-of select="fn:tokenize('aaa"', ';')[1]" /> <!-- aaa" --> }} ** separator属性 [#p66433ee] - `xsl:value-of`要素で、`separator`属性が使用できる - `separator=""`で区切り文字なしで連結(`fn:concat`の代わりになる) #code{{ <xsl:variable name="hex" select="'0123456789ABCDEF'" /> <xsl:function as="xs:string?" name="myfn:decimalToHex"> <xsl:param as="xs:integer" name="dec" /> <xsl:if test="$dec gt 0"> <xsl:value-of select="myfn:decimalToHex($dec idiv 16), substring($hex, ($dec mod 16) + 1, 1)" separator="" /> </xsl:if> </xsl:function> }} ** シーケンスに要素が含まれているかをチェックする [#cd7d3228] - `XSLT 1.0`では一旦文字列にして`contains`関数などを使用する必要があったが、`XSLT 2.0`では、`=`でシーケンス内に要素が存在するかをチェックすることが可能 - `XSLT 1.0`では一旦文字列にして`contains`関数などを使用する必要があったが、`XSLT 2.0`では、`=`でシーケンス内に要素が存在するかをチェックできる -- 参考: [https://stackoverflow.com/questions/4810872/how-to-check-if-a-value-is-in-a-sequence-of-values xml - How to check if a value is in a sequence of values? - Stack Overflow] -- `2 = (1 to 3): <xsl:value-of select="2 = (1 to 3)" /> <!-- true -->` -- `7 = (8, 6, 4): <xsl:value-of select="7 = (8, 6, 4)" /> <!-- false -->` -- `(1 to 3) = 2: <xsl:value-of select="(1 to 3) = 2" /> <!-- true -->` #code{{ <!-- 1~3番目のシートのみ処理する --> <xsl:variable name="output" as="xs:integer+" select="1 to 3" /> <xsl:for-each select="sheets/sheet[position() = $output]"> <xsl:variable name="num" as="xs:integer" select="position()" /> <xsl:variable name="sheet" as="xs:string" select="concat('worksheets/sheet',$num,'.xml')" /> <xsl:variable name="worksheet" select="document($sheet,/)/worksheet" /> ... }} ** xsl:for-each-group [#sbfd6336] * Office Open XML [#OOXML] ** DrawingML [#DrawingML] ** English Metric Units [#emu] - [http://archive.oreilly.com/pub/post/what_is_an_emu.html Why EMUs?] - [https://startbigthinksmall.wordpress.com/2010/01/04/points-inches-and-emus-measuring-units-in-office-open-xml/ Points, inches and Emus: Measuring units in Office Open XML – Lars Corneliussen] -- インチやミリの橋渡しをする高精度の内部単位として使用 -- [https://ja.wikipedia.org/wiki/Edian Edian]の内部単位`neu`とその理由なども完全に同じ -- 上限は`int`(`Integer.MAX_VALUE`,`2147483647`)で`50`メートル程度表現可能 1in = 914400emu = 127 * 3 * 2400emu 1pt = 12700emu = 127 * 100emu 1mm = 36000emu 1級 = 9000emu // 1in = 25.4mm // 72pt = 1in * コメント [#comment] #comment #comment