在XSLT中将包装器放在一组元素周围

时间:2013-05-03 20:11:07

标签: xslt

我正在使用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>标签结尾的后续兄弟的子集?

2 个答案:

答案 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() &gt; 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>