在 XSLT 2.0 的分组元素中转换 XML 时出现重复元素

Duplicate elements occuring while transforming XML in grouping elements in XSLT 2.0

在多处转换此元素时 <p content-type="paratext">paratext 2_1</p>

XML 输入:

<?xml version="1.0" encoding="UTF-8"?>
<body>
    <p content-type="heading-01">heading-01</p>
    <p content-type="paratext">paratext 1</p>
    <p content-type="paratext">paratext 1</p>
    <p content-type="heading-01">heading-01</p>
    <p content-type="heading-02">heading-02</p>
    <p content-type="paratext">paratext 2_1</p>
    <p content-type="heading-02">heading-02</p>
    <p content-type="paratext">paratext 2_2</p>
    <p content-type="heading-01">heading-01</p>
    <p content-type="paratext">paratext 3</p>
    <p content-type="paratext">paratext 3</p>
    <p content-type="heading-01">heading-01</p>
    <p content-type="paratext">paratext 4</p>
    <p content-type="paratext">paratext 4</p>
</body>

所需输出:

<?xml version="1.0" encoding="UTF-8"?>
<body>
    <sec sec-type="heading-01">
        <title>heading-01</title>
        <p content-type="paratext">paratext 1</p>
        <p content-type="paratext">paratext 1</p>
    </sec>
    <sec sec-type="heading-01">
        <title>heading-01</title>
        <sec sec-type="heading-02">
            <title>heading-02</title>
            <p content-type="paratext">paratext 2_1</p>
        </sec>
        <sec sec-type="heading-02">
            <title>heading-02</title>
            <p content-type="paratext">paratext 2_2</p>
        </sec>
    </sec>
    <sec sec-type="heading-01">
        <title>heading-01</title>
        <p content-type="paratext">paratext 3</p>
        <p content-type="paratext">paratext 3</p>
    </sec>
    <sec sec-type="heading-01">
        <title>heading-01</title>
        <p content-type="paratext">paratext 4</p>
        <p content-type="paratext">paratext 4</p>
    </sec>
</body>

当前输出:

<?xml version="1.0" encoding="UTF-8"?>
<body>
    <sec sec-type="heading-01">
        <title>heading-01</title>
        <p content-type="paratext">paratext 1</p>
        <p content-type="paratext">paratext 1</p>
    </sec>
    <sec sec-type="heading-01">
        <title>heading-01</title>
        <sec sec-type="heading-02">
            <title>heading-02</title>
            <p content-type="paratext">paratext 2_1</p>
        </sec>
        <p content-type="paratext">paratext 2_1</p>
        <sec sec-type="heading-02">
            <title>heading-02</title>
            <p content-type="paratext">paratext 2_2</p>
        </sec>
        <p content-type="paratext">paratext 2_2</p>
    </sec>
    <sec sec-type="heading-01">
        <title>heading-01</title>
        <p content-type="paratext">paratext 3</p>
        <p content-type="paratext">paratext 3</p>
    </sec>
    <sec sec-type="heading-01">
        <title>heading-01</title>
        <p content-type="paratext">paratext 4</p>
        <p content-type="paratext">paratext 4</p>
    </sec>
</body>

XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">

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

    <xsl:template match="body">
        <body>
            <xsl:for-each select="p[@content-type = 'heading-01']">
                <sec sec-type="{@content-type}">
                    <title>
                        <xsl:value-of select="."/>
                    </title>
                    <xsl:apply-templates select="following-sibling::node() except (following-sibling::p[@content-type = ('heading-01', 'referencetitle')], following-sibling::p[@content-type = ('heading-01', 'referencetitle')]/following-sibling::node())"/>
                </sec>
            </xsl:for-each>
        </body>
    </xsl:template>

    <xsl:template match="p[@content-type = 'heading-02']">
        <sec sec-type="{@content-type}">
            <title>
                <xsl:value-of select="."/>
            </title>
            <xsl:apply-templates select="following-sibling::node() except (following-sibling::p[@content-type = ('heading-01', 'heading-02', 'referencetitle')], following-sibling::p[@content-type = ('heading-01', 'heading-02', 'referencetitle')]/following-sibling::node())"/>
        </sec>
    </xsl:template>

</xsl:stylesheet>

以下是 XSLT 3,但如果您使用 concat 代替 ||xsl:value-of,那么使用 for-each-group 的递归函数也可以在 XSLT 2 中使用文本值模板 {.}:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">

  <xsl:function name="mf:nest" as="element()*">
      <xsl:param name="input" as="element(p)*"/>
      <xsl:param name="level" as="xs:integer"/>
      <xsl:for-each-group select="$input" group-starting-with="p[@content-type = 'heading-' || format-number($level, '00')]">
          <xsl:choose>
              <xsl:when test="self::p[@content-type = 'heading-' || format-number($level, '00')]">
                  <sec sec-type="{@content-type}">
                      <title>{.}</title>
                      <xsl:apply-templates select="mf:nest(tail(current-group()), $level + 1)"/>
                  </sec>
              </xsl:when>
              <xsl:otherwise>
                  <xsl:apply-templates select="current-group()"/>
              </xsl:otherwise>
          </xsl:choose>
      </xsl:for-each-group>
  </xsl:function>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="body">
      <xsl:copy>
          <xsl:apply-templates select="mf:nest(*, 1)"/>
      </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/6rewNxB/1

XSLT 2:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="#all">

  <xsl:function name="mf:nest" as="element()*">
      <xsl:param name="input" as="element(p)*"/>
      <xsl:param name="level" as="xs:integer"/>
      <xsl:for-each-group select="$input" group-starting-with="p[@content-type = concat('heading-', format-number($level, '00'))]">
          <xsl:choose>
              <xsl:when test="self::p[@content-type = concat('heading-', format-number($level, '00'))]">
                  <sec sec-type="{@content-type}">
                      <title>
                          <xsl:value-of select="."/>
                      </title>
                      <xsl:apply-templates select="mf:nest(subsequence(current-group(), 2), $level + 1)"/>
                  </sec>
              </xsl:when>
              <xsl:otherwise>
                  <xsl:apply-templates select="current-group()"/>
              </xsl:otherwise>
          </xsl:choose>
      </xsl:for-each-group>
  </xsl:function>

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

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="body">
      <xsl:copy>
          <xsl:apply-templates select="mf:nest(*, 1)"/>
      </xsl:copy>
  </xsl:template>

</xsl:transform>

http://xsltransform.net/pNEhB3u

怎么样:

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:template match="/body">
    <xsl:copy>
        <xsl:for-each-group select="p" group-starting-with="p[@content-type='heading-01']">
            <sec sec-type="heading-01">
                <title>
                    <xsl:value-of select="."/>
                </title>
                <xsl:choose>
                    <xsl:when test="current-group()[@content-type='heading-02']">
                        <xsl:for-each-group select="current-group()[position() > 1]" group-starting-with="p[@content-type='heading-02']">
                            <sec sec-type="heading-02">
                                <title>
                                    <xsl:value-of select="."/>
                                </title>
                                <xsl:copy-of select="current-group()[@content-type='paratext']"/>
                            </sec>
                        </xsl:for-each-group>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:copy-of select="current-group()[@content-type='paratext']"/>
                    </xsl:otherwise>
                </xsl:choose>
            </sec>  
        </xsl:for-each-group>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

演示https://xsltfiddle.liberty-development.net/bFWR5DF

感谢大家的回复,我通过下面的代码用for-each-group解决了这个问题:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">

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

    <xsl:template match="body">
        <body>
            <xsl:for-each-group select="*" group-starting-with="p[@content-type='heading-01']">
                <xsl:choose>
                    <xsl:when test="@content-type='heading-01'">
                        <sec content-type="{@content-type}">
                            <xsl:for-each-group select="current-group()" group-starting-with="p[@content-type='heading-02']">
                                <xsl:choose>
                                    <xsl:when test="@content-type='heading-02'">
                                        <sec content-type="{@content-type}">
                                            <xsl:for-each-group select="current-group()" group-starting-with="p[@content-type='heading-03']">
                                                <xsl:choose>
                                                    <xsl:when test="@content-type='heading-03'">
                                                        <sec content-type="{@content-type}">
                                                            <xsl:apply-templates select="current-group()"/>
                                                        </sec>
                                                    </xsl:when>
                                                    <xsl:otherwise>
                                                        <xsl:apply-templates select="current-group()"/>
                                                    </xsl:otherwise>
                                                </xsl:choose>
                                            </xsl:for-each-group>
                                        </sec>
                                    </xsl:when>
                                    <xsl:otherwise>
                                        <xsl:apply-templates select="current-group()"/>
                                    </xsl:otherwise>
                                </xsl:choose>
                            </xsl:for-each-group>
                        </sec>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:apply-templates select="current-group()"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each-group>
        </body>
    </xsl:template>

    <xsl:template match="p">
        <xsl:choose>
            <xsl:when test="starts-with(@content-type, 'heading')">
                <title>
                    <xsl:apply-templates/>
                </title>
            </xsl:when>
            <xsl:otherwise>
                <p>
                    <xsl:apply-templates select="@* | node()"/>
                </p>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

</xsl:stylesheet>