🪣

変換後XML内の日本語から改行由来のスペースを除く、たった1つじゃない冴えないXSLT

2023/03/30に公開
2

記事題は『よくわかる現代魔法』からですね。突発でこのタイトルにしましたが、そのうちちゃんとバリエーションを追加するかもしれない。

とりあえず、前提と要求は次:

  • これから書く最終段以外のテンプレートでは、テキストは<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(.,'&#10', '')"/>

そういえばあったね。translate()はコンテキスト中の単一文字を置換する処理です。動作は後で確認しておきます。

出力XMLにXSLT由来のインデントを含めないようにする

「出力XMLに謎のアキがある」というとき、XSLTテンプレート中のインデントが影響している場合があります。<xsl:output>@indentyesであると、XSLTテンプレート中のインデントが保持されます。インラインの処理用に書いたテンプレートなどで顕在化します。

これは<xsl:output>@indent="no"とすれば解決します。

<xsl:output method="xml" indent="no"/>

当然出力XMLは見辛くなります。

参考資料

https://atmarkit.itmedia.co.jp/ait/articles/0102/28/news001.html

組版・ドキュメンテーション勉強会

Discussion

AQAQ

fn:fold-left()部分は、fn:string-join()のほうが一見でわかりやすそうですね。

hidarumahidaruma

ありがとうございます。記事タイトルに「たった1つじゃない冴えない」としているので、時間のあるときに他の無駄の多い方法も含め(そちらが大半ですが)追記します。