XSLT2.0で便利になった機能(55) replace

replaceというのはXPath 2.0から使用できるようになった実に強力な関数です.

7.6.3 fn:replace

何が強力かというとそれは正規表現が使えてしまうからです.関数の定義は

fn:replace( $input     as xs:string?,
            $pattern   as xs:string,
            $replacement as xs:string) as xs:string

というものですけれど、$patternに正規表現を書けます.その中に()つきで抽出要素を指定すれば$replacementでそれを抜き出すことができてしまいます.例えば、画像の大きさがXXmm×YYmmというような表記になって$size変数に格納されていたとします.

<xsl:variable name="size" as="xs:string" select="150mm×90mm"/>

そうすると、この150と90という数値はsubstring-before(after)を使ってあたふたしなくとも

<xsl:variable name="mmSpec" as="xs:string" select="replace($size,'([0-9]+mm)×([0-9]+mm)','$1 $2')"/>
<xsl:variable name="mmSeq" as="xs:string+" select="tokenize($mmSpec,' ')"/>

で一発で取得できてしまいます.あとはXSL-FOに落とすなら

<fo:external-graphic src="~" content-width="{$mmSeq[1]}" content-height="{$mmSeq[2]}"/>

とするだけです.

でも時としてこの正規表現はヤバイときがあります.それは私たちが普段使う文字列置換は必ずしも正規表現を必要としない場合もあるからです.いえ、もっと言えばほとんどの場合、正規表現を使用しないと言った方が良いのかもしれません.

例えば次の例は、ファイルが見つからないというメッセージを組み立てようとしています.

replace('[Error 99] ファイルが見つかりません.パス=%path','%path','C:\Users\Toshi\Sample\diagram.png')

でもこれは次のエラーメッセージで失敗します.

FORX0004: Invalid replacement string in replace(): \ character must be followed by \ or $

理由は簡単です.\(バックスラッシュ)は正規表現で使用されるエスケープ文字だからです.ではどうしたら良いのでしょう?最初私は、"\"を"/"に変えて置換していました.ちょっと姑息ですね.もっと良い方法があります.それは以下のライブラリで公開されている関数を使用することです.

FunctX XQuery Functions

たとえばこのように$replacementに\が入っている場合は置換する前に\を\\にエスケープしてやります.このための関数は次のようになります.

    <!--
     function: escapeForRegx
     param:    prmSrcString
     return:   Escaped xs:string
     note:     Original code by Priscilla Walmsley.
    -->
    <xsl:function name="ahf:escapeForRegxDst" as="xs:string">
        <xsl:param name="prmSrcString" as="xs:string"/>
        <xsl:sequence select="replace($prmSrcString,'(\\|\$)','\\$1')"/>
    </xsl:function>

    <xsl:function name="ahf:escapeForRegxSrc" as="xs:string">
        <xsl:param name="prmSrcString" as="xs:string"/>
        <xsl:sequence select="replace($prmSrcString,'(\.|\[|\]|\\|\||\-|\^|\$|\?|\*|\+|\{|\}|\(|\))','\\$1')"/>
    </xsl:function>

    <!--
        function: Safe replace function
        param: prmStr,prmSrc,prmDst
        note: Result string
    -->
    <xsl:function name="ahf:safeReplace" as="xs:string">
        <xsl:param name="prmStr" as="xs:string"/>
        <xsl:param name="prmSrc" as="xs:string"/>
        <xsl:param name="prmDst" as="xs:string"/>
        <xsl:sequence select="replace($prmStr,ahf:escapeForRegxSrc($prmSrc),ahf:escapeForRegxDst($prmDst))"/>
    </xsl:function>

あとはこれをreplaceの代わりにahf:safeReplaceを適用してやれば済みます.

[Error 99] ファイルが見つかりません.パス=C:\Users\Toshi\Sample\diagram.png

この関数の作者のPriscilla Walmsleyさんは、Definitive XML Schemaの著者でもあります.


FunctXのサイトには便利な関数がいろいろあります.ご覧いただければきっと役に立つでしょう.