我正在使用XSLT将XML文档从一种文档类型转换为另一种文档类型。简化形式,我有一些看起来像这样的东西:
<section>
<title>Title</title>
<a>content1</a>
<a>content2</a>
<a>content3</a>
<a>content4</a>
<b>other content</b>
<a>content5</a>
<a>content6</a>
<a>content7</a>
<c>other content</c>
<a>content8</a>
<a>content9</a>
<a>content10</a>
</section>
我想要的是在<a>
元素组周围放置一个包装器,因此翻译结果如下所示:
<section>
<title>Title</title>
<a-wrapper>
<a>content1</a>
<a>content2</a>
<a>content3</a>
<a>content4</a>
</a-wrapper>
<b>other content</b>
<a-wrapper>
<a>content5</a>
<a>content6</a>
<a>content7</a>
</a-wrapper>
<c>other content</c>
<a-wrapper>
<a>content8</a>
<a>content9</a>
<a>content10</a>
</a-wrapper>
</section>
我目前的模板如下所示:
<xsl:template match="a">
<xsl:choose>
<xsl:when test="not(preceding-sibling::a)">
<xsl:element name="a-wrapper">
<xsl:element name="a">
<xsl:apply-templates/>
</xsl:element>
<xsl:apply-templates select="following-sibling::a[generate-id(preceding-sibling::a[last()]) = generate-id(current())]"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="a">
<xsl:apply-templates/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
这让我在那里。问题是apply-templates标记中的select子句获取所有<a>
标记,当我希望它停在第一个非<a>
标记时,即第一个处理当我只想要前四个时,<a>
标记会将所有10个<a>
标记放在第一个<a-wrapper>
内。这也有插入嵌套<a-wrapper>
标记的效果,就像第五个<a>
标记生成的标记一样,并且DTD不允许这样做。
所以问题是,如何选择以第一个非<a>
标签结尾的后续兄弟的子集?
答案 0 :(得分:2)
假设,从您的示例中,您想要“包装”除部分元素中的第一个元素之外的所有内容,您可以编写模板以匹配section元素,然后选择名字与其最早的兄弟姐妹不同的子元素
<xsl:apply-templates
select="*[position() > 1]
[local-name() != local-name(preceding-sibling::*[1])]" mode="wrapper" />
然后,在这个'wrapper'模板中,你可以创建你的包装元素,然后通过选择要包装的第一个元素开始
<xsl:element name="{local-name()}-wrapper">
<xsl:apply-templates select="self::*" mode="copy"/>
</xsl:element>
在这个“复制”模板中,您将复制该元素,然后以递归方式选择下一个元素,如果它具有相同的名称
<xsl:apply-templates
select="following-sibling::*[1][local-name() = local-name(current())]" mode="copy"/>
这是完整的XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="@*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="*[1]"/>
<xsl:apply-templates select="*[position() > 1][local-name() != local-name(preceding-sibling::*[1])]" mode="wrapper"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="wrapper">
<xsl:element name="{local-name()}-wrapper">
<xsl:apply-templates select="self::*" mode="copy"/>
</xsl:element>
</xsl:template>
<xsl:template match="*" mode="copy">
<xsl:call-template name="identity"/>
<xsl:apply-templates select="following-sibling::*[1][local-name() = local-name(current())]" mode="copy"/>
</xsl:template>
</xsl:stylesheet>
应用于XML时,输出以下内容
<section>
<title>Title</title>
<a-wrapper>
<a>content1</a>
<a>content2</a>
<a>content3</a>
<a>content4</a>
</a-wrapper>
<b-wrapper>
<b>other content</b>
</b-wrapper>
<a-wrapper>
<a>content5</a>
<a>content6</a>
<a>content7</a>
</a-wrapper>
<c-wrapper>
<c>other content</c>
</c-wrapper>
<a-wrapper>
<a>content8</a>
<a>content9</a>
<a>content10</a>
</a-wrapper>
</section>
请注意,此解决方案中没有任何特定元素名称的硬编码,这有望为您提供更大的灵活性。
编辑:因为你实际上只想包装'a'元素,这使它实际上更简单。你可以有一个模板来匹配一组'a'元素的第一次出现,如此
<xsl:template match="a[preceding-sibling::*[1][not(self::a)]]">
然后,您将以与之前类似的方式复制元素和以下“a”元素。
尝试使用此XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="@*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a[preceding-sibling::*[1][not(self::a)]]">
<a-wrapper>
<xsl:apply-templates select="self::a" mode="copy"/>
</a-wrapper>
</xsl:template>
<xsl:template match="a" />
<xsl:template match="a" mode="copy">
<xsl:call-template name="identity"/>
<xsl:apply-templates select="following-sibling::*[1][self::a]" mode="copy"/>
</xsl:template>
</xsl:stylesheet>
答案 1 :(得分:0)
我非常感谢您代表我的努力。现在轮到我道歉,因为问题陈述不完整。我忽略了注意到我正在将[a]转换为另一个标签 - 让我们称之为[a2]。其他转换也在发生,包括[section]到[sect1]。
与此同时,一个不同的消息来源提出了一个适合我的解决方案。它看起来像片段形式:
<xsl:template match="section">
<xsl:element name="sect1">
<xsl:for-each-group select="*" group-adjacent="boolean(self::a)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:element name="a-wrapper">
<xsl:apply-templates select="current-group()"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:element>
</xsl:template>