1

我正在使用 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>所以问题是,我如何选择以第一个非标签结尾的后续兄弟姐妹的子集?

4

2 回答 2

3

假设,从您的示例中,您想要“包装”除了section元素中的第一个元素之外的所有内容,您可以编写一个模板来匹配 section 元素,然后从选择名称与其最前面的兄弟元素不同的子元素开始

<xsl:apply-templates 
     select="*[position() > 1]
              [local-name() != local-name(preceding-sibling::*[1])]" mode="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>
于 2013-05-03T22:39:46.210 回答
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>
于 2013-05-07T19:54:06.207 回答