XSLT 2:如何在 XML-tag 的第一次出现和最后一次出现之间包装所有内容?

XSLT 2: How to wrap all content between first and last occurrence of a XML-tag?

我正在使用 XSLT 2.0 转换 XML。 重要的是,输入 XML 中的所有文本节点都包含在结果 XML 中,并且顺序与它们在输入中出现的顺序相同。对于元素节点,在大多数情况下我只想更改标签的名称,或者添加一些层次结构将某些节点包装在新节点中。

对于这个问题,我想知道如何将某个标签从(含)第一个 child 出现到(含)最后一个 "unit" 出现的所有内容视为一个 "unit" child 出现相同的标签,包括 文本和中间的其他标签。同时,我希望也能把这个选择之前的所有children当作一个单独的"unit",把这个选择之后的所有children当作另一个单独的"unit".

我已经包含了一个我想要的虚拟示例。假设 "current node" 是 <c>,例如如果我们在 <xsl:template match="//c"> 中。 我想把从第一个 <e> 节点到最后一个 <e> 节点(含)的所有内容(在 <c> 下)包装起来,包括 <f> 节点,在一个节点 <es>。此外,我(仍然只在上下文 <c> 中)喜欢在 as-is 之前保留所有内容,但将所有内容包装在节点 <after-es> 之后。我希望此操作在 <c> 之外没有任何副作用,例如将内容移入或移出 <c> 节点。

输入XML:

<a>
  alfred
  <b>bob</b>
  charlie
  <c>
    dover
    <d>elon</d>
    fabio
    <e>grant</e>
    hugh
    <f>illinois</f>
    jacob
    <e>kathy</e>
    lombard
    <e>
      moby
      <g>narwhal</g>
      obi-wan
    </e>
    pontiac
    <h>quantas</h>
    rhino
  </c>
  xenu
  <z>yoga</z>
  zombie
</a>

预期输出XML:

<a>
  alfred
  <b>bob</b>
  charlie
  <c>
    dover
    <d>elon</d>
    fabio
    <es>
      <e>grant</e>
      hugh
      <f>illinois</f>
      jacob
      <e>kathy</e>
      lombard
      <e>
        moby
        <g>narwhal</g>
        obi-wan
      </e>
    </es>
    <after-es>
      pontiac
      <h>quantas</h>
      rhino
    </after-es>
  </c>
  xenu
  <z>yoga</z>
  zombie
</a>

如何做到这一点?最好使用 XSLT 2.0。解决方案越简洁越好。

这是您可以查看的一种方式:

XSLT 2.0

<xsl:stylesheet version="2.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="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="c">
    <xsl:variable name="nodes-before" select="node()[. &lt;&lt; ../e[1]]"/>
    <xsl:variable name="nodes-after" select="node()[. >> ../e[last()]]"/>
    <xsl:copy>
        <xsl:apply-templates select="$nodes-before"/>
        <es>
            <xsl:apply-templates select="node() except ($nodes-before|$nodes-after)"/>
        </es>
        <xsl:if test="$nodes-after">
            <after-es>
                <xsl:apply-templates select="$nodes-after"/>
            </after-es>
        </xsl:if>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

请注意,这里有一个潜在的假设,即 c 至少有一个 e child。

我自己解决这个问题的方法是在我看到@michael.hor257k 的回答之前开始的。

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">

  <xsl:output omit-xml-declaration="yes" />

  <!-- identity transform -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="c">
    <xsl:variable name="first-e" select="count(e[     1]/preceding-sibling::node()) + 1"/>
    <xsl:variable name="last-e"  select="count(e[last()]/preceding-sibling::node()) + 1"/>

    <xsl:copy>
      <xsl:apply-templates select="e[1]/preceding-sibling::node()"/>
      <es>
        <xsl:apply-templates select="node()[$first-e &lt;= position() and position() &lt;= $last-e]"/>
      </es>
      <after-es>
        <xsl:apply-templates select="e[last()]/following-sibling::node()"/>
      </after-es>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>