基于前置元素分组的XSLT转换问题及解决方案
问题描述
环境:使用Apache FOP 2.8,搭配SaxonHE12。
XML输入
<viewport> <id name="123"/> <font family="sans-serif" style="normal" weight="400" variant="normal" size="10000" color="#000000"/> <text>foo 1</text> <id name="#321"/> <text>foo 2</text> <id name="123"/> <text>foo 3</text> <text>foo 4</text> <text>foo 5</text> <id name="456"/> <text>foo 6</text> <text>foo 7</text> <id name="789"/> <text>foo 8</text> <text>foo 9</text> <text>foo 10</text> </viewport>
转换规则
- 所有前置存在**无前缀#**的
id元素的text,归属于该id标识的文本段; - 若
text的直接前置id带#号,则该text需额外保留此id属性。
示例:foo 1-5归属于id为123的文本段,foo 2同时需保留#321的id属性。
期望输出HTML
<div id="123"> <span>foo 1</span> <span id="#321">foo 2</span> <span>foo 3</span> <span>foo 4</span> <span>foo 5</span> </div> <div id="456"> <span>foo 6</span> <span>foo 7</span> </div> <div id="789"> <span>foo 8</span> <span>foo 9</span> <span>foo 10</span> </div>
尝试的XSLT及问题
最初编写的XSLT如下,存在text模板被多次调用、生成重复内容的问题,调整viewport的apply-templates选择器后仍未解决重复id的问题:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions"> <xsl:template match="*|/"> <xsl:apply-templates select="./*"/> </xsl:template> <xsl:template match="viewport"> <xsl:apply-templates select="./*"/> </xsl:template> <xsl:template match="id"> <xsl:variable name="name"><xsl:value-of select="@name" /></xsl:variable> <xsl:if test="not(fn:starts-with(@name, '#'))"> <div class="element-container" id="{$name}"> <!-- all following text elements that have an id element with the current id --> <xsl:apply-templates select="following::text[preceding::id[not(fn:starts-with($name, '#'))]/@name = $name]"/> </div> </xsl:if> </xsl:template> <xsl:template match="text"> <xsl:variable name="special-id"><xsl:value-of select="preceding-sibling::id[1][fn:starts-with(@name, '#')]/@name"/></xsl:variable> <span> <xsl:if test="$special-id != ''"> <xsl:attribute name="id"><xsl:value-of select="$special-id"/></xsl:attribute> </xsl:if> <xsl:value-of select="text()"/> </span> </xsl:template> </xsl:stylesheet>
可行解决方案
使用xsl:for-each-group按文本所属的无前缀id分组,解决重复渲染问题,最终XSLT如下:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions"> <xsl:template match="*|/"> <xsl:apply-templates select="./*"/> </xsl:template> <xsl:template match="viewport"> <xsl:for-each-group select="./*" group-by="if(preceding-sibling::id[not(fn:starts-with(@name, '#'))][1]/@name != '') then (preceding-sibling::id[not(fn:starts-with(@name, '#'))][1]/@name) else ''"> <xsl:choose> <xsl:when test="current-grouping-key() != ''"> <div class="report-element" id="{current-grouping-key()}"> <xsl:apply-templates select="current-group()"/> </div> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="current-group()"/> </xsl:otherwise> </xsl:choose> </xsl:for-each-group> </xsl:template> <xsl:template match="text"> <xsl:variable name="special-id"><xsl:value-of select="preceding-sibling::id[1][fn:starts-with(@name, '#')]/@name"/></xsl:variable> <span> <xsl:if test="$special-id != ''"> <xsl:attribute name="id"><xsl:value-of select="$special-id"/></xsl:attribute> </xsl:if> <xsl:value-of select="text()"/> </span> </xsl:template> </xsl:stylesheet>
说明:该方案通过分组将同属一个无前缀id的元素归为一组,仅渲染一次;同时保留了对带#前缀id的处理逻辑,确保对应text元素带上特殊id属性。
内容的提问来源于stack exchange,提问作者CrazyEight
相关产品推荐
相关产品推荐

