I. これは、10 年以上前に私が書いた解決策です。
この変換 (FXSL ライブラリから):
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:str-split2lines-func="f:str-split2lines-func"
exclude-result-prefixes="f str-split2lines-func">
<xsl:import href="str-foldl.xsl"/>
<xsl:output method="text"/>
<str-split2lines-func:str-split2lines-func/>
<xsl:template match="/">
<xsl:call-template name="str-split-to-lines">
<xsl:with-param name="pStr" select="/*"/>
<xsl:with-param name="pLineLength" select="64"/>
<xsl:with-param name="pDelimiters" select="' 	 '"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="str-split-to-lines">
<xsl:param name="pStr"/>
<xsl:param name="pLineLength" select="60"/>
<xsl:param name="pDelimiters" select="' 	 '"/>
<xsl:variable name="vsplit2linesFun"
select="document('')/*/str-split2lines-func:*[1]"/>
<xsl:variable name="vrtfParams">
<delimiters><xsl:value-of select="$pDelimiters"/></delimiters>
<lineLength><xsl:copy-of select="$pLineLength"/></lineLength>
</xsl:variable>
<xsl:variable name="vResult">
<xsl:call-template name="str-foldl">
<xsl:with-param name="pFunc" select="$vsplit2linesFun"/>
<xsl:with-param name="pStr" select="$pStr"/>
<xsl:with-param name="pA0" select="$vrtfParams"/>
</xsl:call-template>
</xsl:variable>
<xsl:for-each select="$vResult/line">
<xsl:for-each select="word">
<xsl:value-of select="concat(., ' ')"/>
</xsl:for-each>
<xsl:value-of select="' '"/>
</xsl:for-each>
</xsl:template>
<xsl:template match="str-split2lines-func:*" mode="f:FXSL">
<xsl:param name="arg1" select="/.."/>
<xsl:param name="arg2"/>
<xsl:copy-of select="$arg1/*[position() < 3]"/>
<xsl:copy-of select="$arg1/line[position() != last()]"/>
<xsl:choose>
<xsl:when test="contains($arg1/*[1], $arg2)">
<xsl:if test="string($arg1/word)">
<xsl:call-template name="fillLine">
<xsl:with-param name="pLine" select="$arg1/line[last()]"/>
<xsl:with-param name="pWord" select="$arg1/word"/>
<xsl:with-param name="pLineLength" select="$arg1/*[2]"/>
</xsl:call-template>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$arg1/line[last()]"/>
<word><xsl:value-of select="concat($arg1/word, $arg2)"/></word>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Test if the new word fits into the last line -->
<xsl:template name="fillLine">
<xsl:param name="pLine" select="/.."/>
<xsl:param name="pWord" select="/.."/>
<xsl:param name="pLineLength" />
<xsl:variable name="vnWordsInLine" select="count($pLine/word)"/>
<xsl:variable name="vLineLength" select="string-length($pLine) + $vnWordsInLine"/>
<xsl:choose>
<xsl:when test="not($vLineLength + string-length($pWord) > $pLineLength)">
<line>
<xsl:copy-of select="$pLine/*"/>
<xsl:copy-of select="$pWord"/>
</line>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$pLine"/>
<line>
<xsl:copy-of select="$pWord"/>
</line>
<word/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
次の XML ドキュメントに適用した場合:
<text>
Dec. 13 — As always for a presidential inaugural, security and surveillance were
extremely tight in Washington, DC, last January. But as George W. Bush prepared to
take the oath of office, security planners installed an extra layer of protection: a
prototype software system to detect a biological attack. The U.S. Department of
Defense, together with regional health and emergency-planning agencies, distributed
a special patient-query sheet to military clinics, civilian hospitals and even aid
stations along the parade route and at the inaugural balls. Software quickly
analyzed complaints of seven key symptoms — from rashes to sore throats — for
patterns that might indicate the early stages of a bio-attack. There was a brief
scare: the system noticed a surge in flulike symptoms at military clinics.
Thankfully, tests confirmed it was just that — the flu.
</text>
最大 64 行の長さ (パラメーターの値として任意の長さを指定できます)に収まるようにテキストを調整し$pLineLength
、結果は次のようになります。
Dec. 13 — As always for a presidential inaugural, security and
surveillance were extremely tight in Washington, DC, last
January. But as George W. Bush prepared to take the oath of
office, security planners installed an extra layer of
protection: a prototype software system to detect a biological
attack. The U.S. Department of Defense, together with regional
health and emergency-planning agencies, distributed a special
patient-query sheet to military clinics, civilian hospitals and
even aid stations along the parade route and at the inaugural
balls. Software quickly analyzed complaints of seven key
symptoms — from rashes to sore throats — for patterns that might
indicate the early stages of a bio-attack. There was a brief
scare: the system noticed a surge in flulike symptoms at
military clinics. Thankfully, tests confirmed it was just that —
the flu.
上記の変換でインポートされる個別のスタイルシートは次のとおりです。
str-foldl.xsl:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
exclude-result-prefixes="f">
<xsl:template name="str-foldl">
<xsl:param name="pFunc" select="/.."/>
<xsl:param name="pA0"/>
<xsl:param name="pStr"/>
<xsl:choose>
<xsl:when test="not(string($pStr))">
<xsl:copy-of select="$pA0"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vFunResult">
<xsl:apply-templates select="$pFunc[1]" mode="f:FXSL">
<xsl:with-param name="arg0" select="$pFunc[position() > 1]"/>
<xsl:with-param name="arg1" select="$pA0"/>
<xsl:with-param name="arg2" select="substring($pStr,1,1)"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:call-template name="str-foldl">
<xsl:with-param name="pFunc" select="$pFunc"/>
<xsl:with-param name="pStr"
select="substring($pStr,2)"/>
<xsl:with-param name="pA0" select="$vFunResult"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
これは本質的に XSLT 1.0 ソリューションであることに注意してください。正規表現処理の XSLT 2.0 の機能を使用すると、より短い XSLT 2.0 ソリューションが可能です。
Ⅱ.XSLT 2.0 正規表現の使用
これは関数です -- f:getLine()
-- 文字列と最大行長を渡すと、単語境界で終わる (最初の最大行長チャンクの) 開始部分文字列の中で最も長い文字列の最初の行を返します。以下の変換では、この関数を使用して、必要な複数行の結果の最初の行を生成します。
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="my:f" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/*/text()">
<xsl:sequence select="f:getLine(., 64)"/>
</xsl:template>
<xsl:function name="f:getLine" as="xs:string?">
<xsl:param name="pText" as="xs:string?"/>
<xsl:param name="pLength" as="xs:integer"/>
<xsl:variable name="vChunk" select="substring($pText, 1, $pLength)"/>
<xsl:choose>
<xsl:when test="not(string-length($pText) > $pLength)
or matches(substring($pText, $pLength+1, 1), '\W')">
<xsl:sequence select="$vChunk"/>
</xsl:when>
<xsl:otherwise>
<xsl:analyze-string select="$vChunk"
regex="^((\W*\w*)*?)(\W+\w*)$">
<xsl:matching-substring>
<xsl:sequence select="regex-group(1)"/>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
</xsl:stylesheet>
この変換が同じ XML ドキュメントに適用されると、正しい最初の行が生成されます。
Dec. 13 — As always for a presidential inaugural, security and
最後に、RegEx を使用した完全な XSLT 2.0 変換:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="my:f" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/*/text()" name="reformat">
<xsl:param name="pText" select="translate(., '
', ' ')"/>
<xsl:param name="pMaxLength" select="64"/>
<xsl:param name="pTotalLength" select="string-length(.)"/>
<xsl:param name="pLengthFormatted" select="0"/>
<xsl:if test="not($pLengthFormatted >= $pTotalLength)">
<xsl:variable name="vNextLine"
select="f:getLine(substring($pText, $pLengthFormatted+1), $pMaxLength)"/>
<xsl:sequence select="concat($vNextLine, '
')"/>
<xsl:call-template name="reformat">
<xsl:with-param name="pText" select="$pText"/>
<xsl:with-param name="pMaxLength" select="$pMaxLength"/>
<xsl:with-param name="pTotalLength" select="$pTotalLength"/>
<xsl:with-param name="pLengthFormatted"
select="$pLengthFormatted + string-length($vNextLine)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:function name="f:getLine" as="xs:string?">
<xsl:param name="pText" as="xs:string?"/>
<xsl:param name="pLength" as="xs:integer"/>
<xsl:variable name="vChunk" select="substring($pText, 1, $pLength)"/>
<xsl:choose>
<xsl:when test="not(string-length($pText) > $pLength)
or matches(substring($pText, $pLength+1, 1), '\W')">
<xsl:sequence select="$vChunk"/>
</xsl:when>
<xsl:otherwise>
<xsl:analyze-string select="$vChunk"
regex="^((\W*\w*)*?)(\W+\w*)$">
<xsl:matching-substring>
<xsl:sequence select="regex-group(1)"/>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
</xsl:stylesheet>