我需要使用 XSLT 1.0 从平面 XML 结构创建 HTML 无序列表。输入 XML 由一系列要转换为列表项的节点组成。但是,此系列可能会被不同类型的非列表节点中断:
<input>
<paragraph>abc</paragraph>
<paragraph>def</paragraph>
<listable>123</listable>
<listable>456</listable>
<other-block>
<other-text>Foo</other-text>
</other-block>
<listable>789</listable>
<listable>012</listable>
</input>
我的目标是:
<div class="output">
<p>abc</p>
<p>def</p>
<ul>
<li>123</li>
<li>456</li>
</ul>
<div class="my-block">
<p class="other">Foo</p>
</div>
<ul>
<li>789</li>
<li>012</li>
</ul>
</div>
我找到了一个旧线程,其中包含几乎适合我的解决方案(页面上的最后一个解决方案,由迪米特雷·诺瓦切夫(Dimitre Novatchev(撰写(并对其进行了调整。这是基于该解决方案的最小样式表:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="utf-8" indent="yes" />
<xsl:strip-space elements="*" />
<!-- IDENTITY TRANSFORM: -->
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
<!-- NON-LIST ITEMS: -->
<xsl:template match="input">
<div class="output">
<xsl:apply-templates />
</div>
</xsl:template>
<xsl:template match="paragraph">
<p>
<xsl:apply-templates />
</p>
</xsl:template>
<xsl:template match="other-block">
<div class="my-block">
<xsl:apply-templates select="descendant::other-text" />
</div>
</xsl:template>
<xsl:template match="other-text">
<p class="other">
<xsl:copy-of select="text()" />
</p>
</xsl:template>
<!-- LIST HANDLING: -->
<xsl:key name="kFollowingUL" match="listable"
use="generate-id(preceding-sibling::*[not(self::listable)][1])"/>
<xsl:template match="*[not(self::listable) and following-sibling::*[1][self::listable]]">
<xsl:call-template name="identity" />
<xsl:variable name="vFolUL"
select="key('kFollowingUL',generate-id())"/>
<xsl:if test="$vFolUL">
<ul>
<xsl:apply-templates mode="copy"
select="key('kFollowingUL',generate-id())" />
</ul>
</xsl:if>
</xsl:template>
<xsl:template match="listable" mode="copy">
<li>
<xsl:value-of select="normalize-space()" />
</li>
</xsl:template>
<xsl:template match="listable" />
</xsl:stylesheet>
此方法的问题在于,它不会将转换应用于每个列表之前的最后一个不可列出的节点。输入中的<paragraph>
和<other-block>
节点将直接复制到输出中,尽管模板应用于<other-block>
的后代:
<div class="output">
<p>abc</p>
<paragraph>def</paragraph>
<ul>
<li>123</li>
<li>456</li>
</ul>
<other-block>
<p class="other">Foo</p>
</other-block>
<ul>
<li>789</li>
<li>012</li>
</ul>
</div>
任何人都可以建议一种方法来修改早期的 XSLT 1.0 解决方案并在可列出组之前添加最后一个节点的转换?
我会这样做:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/input">
<div class="output">
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="paragraph">
<p>
<xsl:apply-templates/>
</p>
</xsl:template>
<xsl:template match="other-block">
<div class="my-block">
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="other-text">
<p class="other">
<xsl:apply-templates/>
</p>
</xsl:template>
<xsl:template match="listable">
<xsl:if test="not(preceding-sibling::*[1][self::listable])">
<ul>
<xsl:apply-templates select="." mode="list"/>
</ul>
</xsl:if>
</xsl:template>
<xsl:template match="listable" mode="list">
<li>
<xsl:apply-templates/>
</li>
<xsl:apply-templates select="following-sibling::*[1][self::listable]" mode="list"/>
</xsl:template>
</xsl:stylesheet>
您的问题来自此模板:
<xsl:template match="*[not(self::listable) and following-sibling::*[1][self::listable]]">
<xsl:call-template name="identity" />
<xsl:variable name="vFolUL"
select="key('kFollowingUL',generate-id())"/>
<xsl:if test="$vFolUL">
<ul>
<xsl:apply-templates mode="copy"
select="key('kFollowingUL',generate-id())" />
</ul>
</xsl:if>
</xsl:template>
这将任何具有 listable
元素的元素作为第一个后续同级元素进行匹配。然后在内容模板中,它调用名为 identity
的模板(在本例中为标识规则(。对于other-block
元素,这比其他模板具有更好的默认优先级:
<xsl:template match="other-text">
<p class="other">
<xsl:apply-templates/>
</p>
</xsl:template>
我喜欢michael.hor257k解决方案,这是我在原始答案中给出的方法。 另一种可能的解决方案是遵循相同的原则:
<xsl:template match="*[not(self::listable) and following-sibling::*[1][self::listable]]">
<xsl:call-template name="separator" />
<xsl:variable name="vFolUL"
select="key('kFollowingUL',generate-id())"/>
<xsl:if test="$vFolUL">
<ul>
<xsl:apply-templates mode="copy"
select="key('kFollowingUL',generate-id())" />
</ul>
</xsl:if>
</xsl:template>
<xsl:template match="other-text" name="separator">
<p class="other">
<xsl:apply-templates/>
</p>
</xsl:template>
但是,请注意,这不能很好地扩展。