我有一个有描述节点的xml:
<config>
<desc>A <b>first</b> sentence here. The second sentence with some link <a href="myurl">The link</a>. The <u>third</u> one.</desc>
</config>
我正在尝试使用点作为分隔符来分割句子,但是在HTML输出中同时保留最终的HTML标记。 到目前为止,我所拥有的是一个分割描述的模板,但由于normalize-space和substring-before函数,HTML标记在输出中丢失。 我目前的模板如下:
<xsl:template name="output-tokens">
<xsl:param name="sourceText" />
<!-- Force a . at the end -->
<xsl:variable name="newlist" select="concat(normalize-space($sourceText), ' ')" />
<!-- Check if we have really a point at the end -->
<xsl:choose>
<xsl:when test ="contains($newlist, '.')">
<!-- Find the first . in the string -->
<xsl:variable name="first" select="substring-before($newlist, '.')" />
<!-- Get the remaining text -->
<xsl:variable name="remaining" select="substring-after($newlist, '.')" />
<!-- Check if our string is not in fact a . or an empty string -->
<xsl:if test="normalize-space($first)!='.' and normalize-space($first)!=''">
<p><xsl:value-of select="normalize-space($first)" />.</p>
</xsl:if>
<!-- Recursively apply the template for the remaining text -->
<xsl:if test="$remaining">
<xsl:call-template name="output-tokens">
<xsl:with-param name="sourceText" select="$remaining" />
</xsl:call-template>
</xsl:if>
</xsl:when>
<!--If no . was found -->
<xsl:otherwise>
<p>
<!-- If the string does not contains a . then display the text but avoid
displaying empty strings
-->
<xsl:if test="normalize-space($sourceText)!=''">
<xsl:value-of select="normalize-space($sourceText)" />.
</xsl:if>
</p>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
我正在以下列方式使用它:
<xsl:template match="config">
<xsl:call-template name="output-tokens">
<xsl:with-param name="sourceText" select="desc" />
</xsl:call-template>
</xsl:template>
预期输出为:
<p>A <b>first</b> sentence here.</p>
<p>The second sentence with some link <a href="myurl">The link</a>.</p>
<p>The <u>third</u> one.</p>
答案 0 :(得分:4)
一个好问题,而不是一个容易解决的问题。特别是,当然,如果你正在使用XSLT 1.0(你真的需要告诉我们是否是这种情况)。
我已经看到了解决这个问题的两种方法。两者都涉及将其分解为更小的问题。
第一种方法是将标记转换为文本(例如将<b>first</b>
替换为[b]first[/b]
),然后使用文本操作操作(xsl:analyze-string)将其拆分为句子,然后重构句子中的标记。
第二种方法(我个人更喜欢)是将文本分隔符转换为标记(将“。”转换为<stop/>
),然后使用位置分组技术(通常&lt; xsl:for-each-group group-ending-with="stop"/>
转换为段落中的句子。)
答案 1 :(得分:3)
以下是使用XSLT 2实现第二种方法suggested by Michael Kay的一种方法。
此样式表演示了一个两遍变换,其中第一个传递在每个句子后引入<stop/>
个标记,第二个传递包含段落中以<stop/>
结尾的所有组。
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<!-- two-pass processing -->
<xsl:template match="/">
<xsl:variable name="intermediate">
<xsl:apply-templates mode="phase-1"/>
</xsl:variable>
<xsl:apply-templates select="$intermediate" mode="phase-2"/>
</xsl:template>
<!-- identity transform -->
<xsl:template match="@*|node()" mode="#all" priority="-1">
<xsl:copy>
<xsl:apply-templates select="@*|node()" mode="#current"/>
</xsl:copy>
</xsl:template>
<!-- phase 1 -->
<!-- insert <stop/> "milestone markup" after each sentence -->
<xsl:template match="text()" mode="phase-1">
<xsl:analyze-string select="." regex="\.\s+">
<xsl:matching-substring>
<xsl:value-of select="regex-group(0)"/>
<stop/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
<!-- phase 2 -->
<!-- turn each <stop/>-terminated group into a paragraph -->
<xsl:template match="*[stop]" mode="phase-2">
<xsl:copy>
<xsl:for-each-group select="node()" group-ending-with="stop">
<p>
<xsl:apply-templates select="current-group()" mode="#current"/>
</p>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<!-- remove the <stop/> markers -->
<xsl:template match="stop" mode="phase-2"/>
</xsl:stylesheet>
答案 2 :(得分:2)
这是我谦虚的解决方案,基于@Michael Kay回答的第二个建议。
与@Jukka回答不同(确实非常优雅)我没有使用xsl:analyse-string
,因为XPath 1.0函数contains
和substring-after
足以完成拆分。我还从config
开始了匹配模式。
这是变换:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<!-- two pass processing -->
<xsl:template match="config">
<xsl:variable name="pass1">
<xsl:apply-templates select="node()"/>
</xsl:variable>
<xsl:apply-templates mode="pass2" select="$pass1/*"/>
</xsl:template>
<!-- 1. Copy everything as is (identity) -->
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<!-- 1. Replace "text. text" with "text<dot/> text" -->
<xsl:template match="text()[contains(.,'. ')]">
<xsl:value-of select="substring-before(.,'. ')"/>
<dot/>
<xsl:value-of select="substring-after(.,'. ')"/>
</xsl:template>
<!-- 2. Group by examining in population order ending with dot -->
<xsl:template match="desc" mode="pass2">
<xsl:for-each-group select="node()"
group-ending-with="dot">
<p><xsl:apply-templates select="current-group()" mode="pass2"/></p>
</xsl:for-each-group>
</xsl:template>
<!-- 2. Identity -->
<xsl:template match="node()|@*" mode="pass2">
<xsl:copy>
<xsl:apply-templates select="node()|@*" mode="pass2"/>
</xsl:copy>
</xsl:template>
<!-- 2. Replace dot with mark -->
<xsl:template match="dot" mode="pass2">
<xsl:text>.</xsl:text>
</xsl:template>
</xsl:stylesheet>
应用于问题中显示的输入,产生:
<p>A <b>first</b> sentence here.</p>
<p>The second sentence with some link <a href="myurl">The link</a>.</p>
<p>The <u>third</u> one.</p>
答案 3 :(得分:0)