XSLT2.0で便利になった機能(56) xsl:analyze-string

すでに取り上げていますが、xsl:analyze-stringをもう一度使ってみる機会がありましたので紹介します.

目的は"($Index_Indent_Value) * %level + 2mm"のようなスタイル記述の文字列で、"$"で始まっている変数的なものを取り出して値で置換する、他はそのままというものです.例えば"$Index_Indexnt_Value"は"7mm"に変換します.”%level"という変数的な記述もありますが、これはスタイル適用時に動的に値を変えたい主旨です.

サンプルはこのようなものになります.結果がわかりやすいようにマッチングした箇所は<match>~</match>、マッチングしない箇所は<unmatch>~</unmatch>で出力します.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
 exclude-result-prefixes="xs">
    <xsl:output indent="yes"/>
    <xsl:template match="/">
        <xsl:variable name="exp" as="xs:string" select="'($Index_Indent_Value) * (%level - 1)'"/>
        <xsl:variable name="expandExpRegX" as="xs:string" select="'[\s\(\),\*\+\-]+'"/>
        <result>
            <xsl:analyze-string select="$exp" regex="$expandExpRegX">
                <xsl:matching-substring>
                    <match>
                        <xsl:value-of select="."/>
                    </match>
                </xsl:matching-substring>
                <xsl:non-matching-substring>
                    <unmatch>
                        <xsl:choose>
                            <xsl:when test=". eq '$Index_Indent_Value'">
                                <xsl:text>7mm</xsl:text>
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:value-of select="."/>
                            </xsl:otherwise>
                        </xsl:choose>
                    </unmatch>
                </xsl:non-matching-substring>
            </xsl:analyze-string>
        </result>
    </xsl:template>
</xsl:stylesheet>

で一発で動いてくれるものと自信満々だったのですが、結果は敢え無く撃沈で

<?xml version="1.0" encoding="UTF-8"?>
<result>
   <unmatch>($Index_Indent_Value) * (%level - 1)</unmatch>
</result>

となってしまいました.置換も何もあったものではありません.最近xsl:analyze-stringは使っていなかったので必死に過去の例を引っ張り出して見直したら、

<xsl:analyze-string select="$exp" regex="{$expandExpRegX}">

でした.そうregexには正規表現をそのまま書くので"{"と"}"で囲んで$expandExpRegXを式として解釈させないといけないのでした.これを修正すると次のような結果になります.

<?xml version="1.0" encoding="UTF-8"?>
<result>
   <match>(</match>
   <unmatch>7mm</unmatch>
   <match>) * (</match>
   <unmatch>%level</unmatch>
   <match> - </match>
   <unmatch>1</unmatch>
   <match>)</match>
</result>

バッチリ置換していてくれます.構文解析まではとても行きませんが、置換には非常に有用です.これを一般的なスタイル記述で使えるようにするともっと動的にスタイルを当てることができるようになります.

さて<xsl:variable name="expandExpRegX" as="xs:string" select="'[\s\(\),\*\+\-]+'"/>に注目していただきたいのですが、正規表現で使用する文字はすべてバックスラッシュででエスケープします.ここがポイントです.あと<xsl:variable name="expandExpRegX" as="xs:string" select="'[\s\(\),\*\+\-]+?'"/>と変えると最少範囲でマッチングしてくれるようになります.これも時として必要になります.

<?xml version="1.0" encoding="UTF-8"?>
<result>
   <match>(</match>
   <unmatch>7mm</unmatch>
   <match>)</match>
   <match> </match>
   <match>*</match>
   <match> </match>
   <match>(</match>
   <unmatch>%level</unmatch>
   <match> </match>
   <match>-</match>
   <match> </match>
   <unmatch>1</unmatch>
   <match>)</match>
</result>

xsl:analyze-stringは正規表現を駆使すればテキスト処理で非常に強力な武器になります.