Как получить все возможные комбинации узлов с помощью 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 membersound    schedule 15.10.2019    source источник


Ответы (2)


Один из способов сделать это — использовать рекурсивный шаблон.

Начните с сопоставления type элементов под первым person элементом, затем рекурсивно вызовите шаблон type элементов под следующим person, передав "промежуточный итог", как и вы.

Попробуйте этот 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.

person Tim C    schedule 15.10.2019
comment
Я почти уверен, что должно быть более элегантное решение (особенно в XSLT 3.0, так что, надеюсь, Мартин Хоннен появится через минуту....) - person Tim C; 15.10.2019
comment
Это очень сильно! Спасибо. В любом случае, меня бы заинтересовало предложение @MartinHonnen для xslt-3. - person membersound; 15.10.2019
comment
/edit: что, если у меня есть более сложный сценарий, когда я хочу включить поля вне списка types? См. мой отредактированный xml выше. я изменил type полей в xslt на type/types, но этого кажется недостаточно: xsltfiddle.liberty-development. сеть/gWEamL3/1 - person membersound; 15.10.2019
comment
Вы можете включить дополнительные name в общую сумму. Я обновил свой ответ, чтобы обработать ваш новый XML. - person Tim C; 15.10.2019
comment
Если бы у меня было около 10 атрибутов на месте <name>, мог бы я как-то кэшировать их в родительской переменной, чтобы мне не приходилось concat этих атрибутов в каждом цикле рекурсии? Или в любом типе Map<Integer, 10-columns-csv>, который я мог бы искать по индексу текущего сегмента? - person membersound; 15.10.2019
comment
Вы могли бы потенциально использовать карту. Если вы не можете заставить его работать, вам действительно следует задать новый вопрос. Благодарю вас! - person Tim C; 15.10.2019

Поскольку Тим возлагал на меня бремя и давление, чтобы попытаться найти какой-нибудь компактный/комплексный способ XSLT 3, я попытался использовать аккумуляторы плюс 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>
person Martin Honnen    schedule 15.10.2019
comment
Что ж, в вашем профиле написано Теперь, пожалуйста, начните использовать XSLT 3.0 и задавайте вопросы по xslt-3.0, чтобы мне не было скучно. ;) - person Tim C; 15.10.2019
comment
@TimC, да, но я пытаюсь разработать и развернуть скрипку на чистом XSLT 3 на основе Saxon 9.9 с поддержкой xsl:result-document, и цейтнот, который вы мне оказываете (в течение минуты), не то, что я могу угнаться за такими вещами, как что не вписываясь в обычную группировку, которую мы все слишком много раз проделывали. - person Martin Honnen; 15.10.2019
comment
Прошу прощения за свой легкомысленный комментарий. Я не собирался оказывать на вас давление. - person Tim C; 15.10.2019
comment
@MartinHonnen для любого последователя XSLT в Интернете разумно прийти к выводу, что у вас есть бесконечное количество времени (и, действительно, терпение) или что вы работаете с бесконечной скоростью ... - person Michael Kay; 16.10.2019