如何使用 XSLT 获取所有可能的节点组合?

How to get all possible node combinations with XSLT?

https://xsltfiddle.liberty-development.net/bFWR5DY/1

我的目标是将每个 person.type 节点相互相乘:

<?xml version="1.0" encoding="utf-8" ?>
<persons>
    <department>OUTER</department>
    <person>
        <name>john</name>
        <types>
          <type code="A"/>
          <type code="B"/>
        </types>
    </person>
    <person>
        <name>doe</name>
        <types>
          <type code="A"/>
          <type code="C"/>
        </types>
    </person>
    <person>
        <name>jane</name>
        <types>
          <type code="D"/>
          <type code="X"/>
        </types>
    </person>
</persons>

我开始如下提取值:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" omit-xml-declaration="yes" indent="no"/>

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

    <xsl:template match="persons">
        <xsl:for-each select="person">
             <xsl:for-each select="type">
                <xsl:value-of select=".//@code" separator=";"/>
                <xsl:text>&#10;</xsl:text>
            </xsl:for-each>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

结果:

A
B
A
C
D
X

想要的结果:

OUTER;john;A;doe;A;jane;D
OUTER;john;A;doe;A;jane;X
OUTER;john;A;doe;c;jane;D
OUTER;john;A;doe;C;jane;X
OUTER;john;B;doe;A;jane;D
OUTER;john;B;doe;A;jane;X
OUTER;john;B;doe;C;jane;D
OUTER;john;B;doe;C;jane;X

我怎样才能到达那里?

执行此操作的一种方法是使用递归模板。

首先匹配第一个 person 元素下的 type 元素,然后递归调用模板下一个 person 下的 type 元素,传递 "running total" 和你一样。

试试这个 XSLT

<xsl:stylesheet version="3.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" omit-xml-declaration="yes" indent="no"/>

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

    <xsl:param name="quote">"</xsl:param>

    <xsl:template match="persons">
        <xsl:apply-templates select="person[1]/types/type">
          <xsl:with-param name="runningTotal" select="department" />
        </xsl:apply-templates>
    </xsl:template>

    <xsl:template match="type">
      <xsl:param name="runningTotal" />
      <xsl:variable name="newRunningTotal" select="concat($runningTotal, ';', ../../name, ':', @code)" />
      <xsl:variable name="nextPerson" select="../../following-sibling::person[1]" />
      <xsl:choose>
        <xsl:when test="$nextPerson">
          <xsl:apply-templates select="$nextPerson/types/type">
            <xsl:with-param name="runningTotal" select="$newRunningTotal" />
          </xsl:apply-templates>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$newRunningTotal" />
          <xsl:text>&#10;</xsl:text>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/gWEamL3/3

由于 Tim 给我施加了负担和压力,试图找到一些 XSLT 3 compact/complex 方式,我尝试使用累加器和 XPath:

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xsl:output method="text" indent="no"/>

    <xsl:mode on-no-match="shallow-skip" use-accumulators="#all"/>

    <xsl:accumulator name="person-codes" as="xs:string*" initial-value="()">
        <xsl:accumulator-rule match="persons/person" select="()"/>
        <xsl:accumulator-rule match="persons/person/types/type" select="$value, string(@code)"/>
    </xsl:accumulator>

    <xsl:accumulator name="permutations" as="xs:string*" initial-value="()">
        <xsl:accumulator-rule phase="end" match="person"
            select="
                if (empty($value))
                then
                    accumulator-after('person-codes')
                else
                    for $v in $value,
                    $cv in accumulator-after('person-codes')
                    return $v || $cv"/>
    </xsl:accumulator>

    <xsl:template match="persons">
        <xsl:apply-templates/>
        <xsl:value-of select="accumulator-after('permutations')" separator="&#10;"/>
    </xsl:template>

</xsl:stylesheet>

这似乎为 Saxon 9.8 和 9.9 提供了正确的输出,并在 https://xsltfiddle.liberty-development.net/gWEamL3/4 提供了最新输入,但没有考虑要收集的各种其他数据,只有 code属性。

基于累加器的方法甚至适用于 Saxon EE 和流:

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xsl:output method="text" indent="no"/>

    <xsl:mode streamable="yes" on-no-match="shallow-skip" use-accumulators="#all"/>

    <xsl:accumulator name="person-codes" as="xs:string*" initial-value="()" streamable="yes">
        <xsl:accumulator-rule match="persons/person" select="()"/>
        <xsl:accumulator-rule match="persons/person/types/type" select="$value, string(@code)"/>
    </xsl:accumulator>

    <xsl:accumulator name="permutations" as="xs:string*" initial-value="()" streamable="yes">
        <xsl:accumulator-rule phase="end" match="person"
            select="
                if (empty($value))
                then
                    accumulator-after('person-codes')
                else
                    for $v in $value,
                        $cv in accumulator-after('person-codes')
                    return
                        $v || $cv"
        />
    </xsl:accumulator>

    <xsl:template match="persons">
        <xsl:apply-templates/>
        <xsl:value-of select="accumulator-after('permutations')" separator="&#10;"/>
    </xsl:template>

</xsl:stylesheet>

一个也可以捕获部门和名称并且应该在没有流式传输(例如 Saxon 9.8 或更高版本 HE 或 PE)或流式传输(例如 Saxon 9.8 或更高版本 EE)的情况下工作的版本是:

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xsl:param name="sep" as="xs:string" select="';'"/>

    <xsl:output method="text" indent="no"/>

    <xsl:mode on-no-match="shallow-skip" streamable="yes" use-accumulators="#all"/>

    <xsl:accumulator name="department" as="xs:string?" initial-value="()" streamable="yes">
        <xsl:accumulator-rule match="persons/department/text()" select="string()"/>
    </xsl:accumulator>

    <xsl:accumulator name="person-name" as="xs:string?" initial-value="()" streamable="yes">
        <xsl:accumulator-rule match="person" select="()"/>
        <xsl:accumulator-rule match="person/name/text()" select="string()"/>
    </xsl:accumulator>

    <xsl:accumulator name="person-codes" as="xs:string*" initial-value="()" streamable="yes">
        <xsl:accumulator-rule match="persons/person" select="()"/>
        <xsl:accumulator-rule match="persons/person/types/type"
            select="$value, accumulator-before('person-name') || $sep || string(@code)"/>
    </xsl:accumulator>

    <xsl:accumulator name="permutations" as="xs:string*" initial-value="()" streamable="yes">
        <xsl:accumulator-rule phase="end" match="person"
            select="
                if (empty($value))
                then
                    for $iv in accumulator-after('person-codes')
                    return
                        accumulator-before('department') || $sep || $iv
                else
                    for $v in $value,
                        $cv in accumulator-after('person-codes')
                    return
                        $v || $sep || $cv"
        />
    </xsl:accumulator>

    <xsl:template match="persons">
        <xsl:apply-templates/>
        <xsl:value-of select="accumulator-after('permutations')" separator="&#10;"/>
    </xsl:template>

</xsl:stylesheet>