🪣
変換後XML内の日本語から改行由来のスペースを除く、たった1つじゃない冴えないXSLT
記事題は『よくわかる現代魔法』からですね。突発でこのタイトルにしましたが、そのうちちゃんとバリエーションを追加するかもしれない。
とりあえず、前提と要求は次:
- これから書く最終段以外のテンプレートでは、テキストは
<xsl:value-of>
で取り出さず、<xsl:apply-templates>
で処理される - 日本語であることが
fn:lang()
で取得できるよう明示されている - 日本語以外では通常の改行用の処理が走る
- preの子孫では改行を残す
- とりあえず改行文字は'\n'で取れるとする
XSLTバージョンは3.0。
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
...>...
<xsl:template match="//pre//text()" priority="5">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="text()" priority="1">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="text()[fn:lang('ja')]" priority="3">
<xsl:sequence
select=". => fn:tokenize('\n')
=> fn:fold-left('',
function($head, $cont){fn:concat($head,$cont)})
=> fn:normalize-space()"/>
</xsl:template>
</xsl:stylesheet>
ということで、テキストの述部で言語判定をして日本語だったらトークン化してfold-leftでconcatを繰り返しています。改行を消すだけならfn:normalize-space()
は要りませんね。逆にnormalize-space()を先に書いてしまうと、改行文字もnormalize-space対象になり面倒になったと思います。
XPathでも結構バリエーションは考えられますね。後はfor-eachを回してみるとか。
'\n'を前提としましたが、正規表現で'\r\n'も対応できるreplaceを書いた方がスマートな気はする。'\r'だけのコードは無いものとする。
<xsl:sequence
select=". => fn:replace('\r?\n', '') => fn:normalize-space()"/>
XSLT 1.0
translate()
<xsl:value-of select="translate(.,'
', '')"/>
そういえばあったね。translate()
はコンテキスト中の単一文字を置換する処理です。動作は後で確認しておきます。
出力XMLにXSLT由来のインデントを含めないようにする
「出力XMLに謎のアキがある」というとき、XSLTテンプレート中のインデントが影響している場合があります。<xsl:output>
の@indent
がyes
であると、XSLTテンプレート中のインデントが保持されます。インラインの処理用に書いたテンプレートなどで顕在化します。
これは<xsl:output>
で@indent="no"
とすれば解決します。
<xsl:output method="xml" indent="no"/>
当然出力XMLは見辛くなります。
参考資料
Discussion
fn:fold-left()
部分は、fn:string-join()
のほうが一見でわかりやすそうですね。ありがとうございます。記事タイトルに「たった1つじゃない冴えない」としているので、時間のあるときに他の無駄の多い方法も含め(そちらが大半ですが)追記します。