Поток XSLT3 для добавления целочисленной позиции узла - PullRequest
1 голос
/ 12 ноября 2019

У меня большой XML-файл для преобразования с использованием XSLT для добавления целочисленной позиции узла-брата. Я использую потоковое XSLT3 и аккумуляторы. Я получил желаемый результат. Однако мой код выглядит настолько длинным, что я не могу упростить мой код. Мне также нужно сгруппировать одинаковые узлы-братья, так как узлы-братья в исходном XML не всегда группируются. Может ли кто-нибудь помочь мне здесь, пожалуйста?

Требование: к узлам-родителям, таким как Позиции, Платежи и т. Д., Необходимо добавлять соответствующие целочисленные позиции, такие как <Locations1>, <Locations2> и т. Д. <Payments1>, < Payments2> и т. Д.

Теперь, когда я объявил два аккумулятора, каждый для каждого узла-брата. Тем не менее, мой исходный XML содержит много узлов-братьев. Я не уверен, нужно ли мне использовать столько аккумуляторов и шаблонов, сколько моих узлов-братьев.

Входной XML

``

<?xml version="1.0" encoding="UTF-8"?> 
<Members>
    <Member>
        <Name>
            <fname>Fred</fname>
            <id>1234</id>
        </Name>
        <Locations>
            <name>Chicago</name>
            <days>3</days>
            <hours>24</hours>
        </Locations>
        <Locations>
            <name>Chicago</name>
            <days>3</days>
            <hours>24</hours>
        </Locations>
        <Payments>
            <amount>1000</amount>
            <currency>USD</currency>
        </Payments>
        <Payments>
            <amount>1000</amount>
            <currency>USD</currency>
        </Payments>
        <Locations>
            <name>New York</name>
            <days>5</days>
            <hours>40</hours>
        </Locations>
        <Locations>
            <name>Boston</name>
            <days>4</days>
            <hours>32</hours>
        </Locations>
    </Member>
    <Member>
        <Name>
            <fname>Jack</fname>
            <id>4567</id>
        </Name>
        <Locations>
            <name>New York</name>
            <days>5</days>
            <hours>30</hours>
        </Locations>
        <Locations>
            <name>Chicago</name>
            <days>3</days>
            <hours>24</hours>
        </Locations>
        <Payments>
            <amount>1500</amount>
            <currency>USD</currency>
        </Payments>
        <Payments>
            <amount>1800</amount>
            <currency>USD</currency>
        </Payments>
    </Member>
</Members>

``

Ожидаемый вывод

``

<?xml version="1.0" encoding="UTF-8"?>
<Members>
    <Member>
        <Name>
            <fname>Fred</fname>
            <id>1234</id>
        </Name>
        <Locations_1>
            <name>Chicago</name>
            <days>3</days>
            <hours>24</hours>
        </Locations_1>
        <Locations_2>
            <name>Chicago</name>
            <days>3</days>
            <hours>24</hours>
        </Locations_2>
        <Locations_3>
            <name>New York</name>
            <days>5</days>
            <hours>40</hours>
        </Locations_3>
        <Locations_4>
            <name>Boston</name>
            <days>4</days>
            <hours>32</hours>
        </Locations_4>
        <Payments_1>
            <amount>1000</amount>
            <currency>USD</currency>
        </Payments_1>
        <Payments_2>
            <amount>1000</amount>
            <currency>USD</currency>
        </Payments_2>     
            </Member>
    <Member>
        <Name>
            <fname>Jack</fname>
            <id>4567</id>
        </Name>
        <Locations_1>
            <name>New York</name>
            <days>5</days>
            <hours>30</hours>
        </Locations_1>
        <Locations_2>
            <name>Chicago</name>
            <days>3</days>
            <hours>24</hours>
        </Locations_2>
        <Payments_1>
            <amount>1500</amount>
            <currency>USD</currency>
        </Payments_1>
        <Payments_2>
            <amount>1800</amount>
            <currency>USD</currency>
        </Payments_2>
    </Member>
</Members>

``

Текущий код ``

<?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="3.0">

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

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

    <xsl:accumulator name="loc-count" as="xs:integer" initial-value="0" streamable="yes">
        <xsl:accumulator-rule match="Member" select="0"/>
        <xsl:accumulator-rule match="Member/Locations" select="$value + 1"/>
    </xsl:accumulator>

    <xsl:accumulator name="pay-count" as="xs:integer" initial-value="0" streamable="yes">
        <xsl:accumulator-rule match="Member" select="0"/>
        <xsl:accumulator-rule match="Member/Payments" select="$value + 1"/>
    </xsl:accumulator>

    <xsl:template match="Locations">
        <xsl:element name="Locations_{accumulator-before('loc-count')}">
            <xsl:copy-of select="@* | node()"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="Payments">
        <xsl:element name="Payments_{accumulator-before('pay-count')}">
            <xsl:copy-of select="@* | node()"/>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

``

Токовый выход

<?xml version="1.0" encoding="UTF-8"?>
<Members>
    <Member>
        <Name>
            <fname>Fred</fname>
            <id>1234</id>
        </Name>
        <Locations_1>
            <name>Chicago</name>
            <days>3</days>
            <hours>24</hours>
        </Locations_1>
        <Locations_2>
            <name>Chicago</name>
            <days>3</days>
            <hours>24</hours>
        </Locations_2>
        <Payments_1>
            <amount>1000</amount>
            <currency>USD</currency>
        </Payments_1>
        <Payments_2>
            <amount>1000</amount>
            <currency>USD</currency>
        </Payments_2>
        <Locations_3>
            <name>New York</name>
            <days>5</days>
            <hours>40</hours>
        </Locations_3>
        <Locations_4>
            <name>Boston</name>
            <days>4</days>
            <hours>32</hours>
        </Locations_4>
    </Member>
    <Member>
        <Name>
            <fname>Jack</fname>
            <id>4567</id>
        </Name>
        <Locations_1>
            <name>New York</name>
            <days>5</days>
            <hours>30</hours>
        </Locations_1>
        <Locations_2>
            <name>Chicago</name>
            <days>3</days>
            <hours>24</hours>
        </Locations_2>
        <Payments_1>
            <amount>1500</amount>
            <currency>USD</currency>
        </Payments_1>
        <Payments_2>
            <amount>1800</amount>
            <currency>USD</currency>
        </Payments_2>
    </Member>
</Members>

Ответы [ 2 ]

2 голосов
/ 12 ноября 2019

Если вы хотите сгруппировать дочерние элементы Member по node-name(), тогда я думаю, что вам нужно обернуть xsl:for-each-group в xsl:fork:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="#all" version="3.0">

    <xsl:strip-space elements="*"/>
    <xsl:output indent="yes"/>

    <xsl:mode on-no-match="shallow-copy" streamable="yes" use-accumulators="counters"/>

    <xsl:accumulator name="counters" as="map(xs:QName, xs:integer)" initial-value="map{}" streamable="yes">
        <xsl:accumulator-rule match="Member" select="map{}"/>
        <xsl:accumulator-rule match="Member/*" 
            select="map:put($value, node-name(), if (map:contains($value, node-name())) then map:get($value, node-name()) + 1 else 1)"/>
    </xsl:accumulator>

    <xsl:template match="Member">
        <xsl:copy>
            <xsl:fork>
                <xsl:for-each-group select="*" group-by="node-name()">
                    <xsl:apply-templates select="current-group()"/>
                </xsl:for-each-group>
            </xsl:fork>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="Member/*">
        <xsl:element name="{node-name()}_{accumulator-before('counters')(node-name())}">
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

Этот подход показывает только группировку,он не пытается создать особый случай Name элементов или каким-либо другим способом не выводить индекс, если существует только один такой элемент.

2 голосов
/ 12 ноября 2019

Во-первых, мое сочувствие. XML, в котором используются такие имена, как Payments_1 и Payments_2, действительно плохие новости, кто-то возненавидит вас за то, что вы генерируете его таким образом. Но если это тот тип XML, который вам сказали создать, я полагаю, это не ваша задача ставить его под сомнение.

Что касается требований, вы еще не дали понять, являются ли различные типыодноуровневых узлов всегда сгруппированы, как в вашем примере (все местоположения, затем все платежи и т. д.), или же их можно чередовать.

Один из способов уменьшить объем кода - это иметьодин аккумулятор держит карту. Карта будет использовать имена элементов в качестве ключа и текущее число братьев и сестер для этого элемента в качестве значения.

<accumulator name="counters" as="map(xs:QName, xs:integer)" initial-value="map{}">
  <xsl:accumulator-rule match="Member" select="map{}"/>
  <xsl:accumulator-rule match="Member/*" select="map:put($value, node-name(.), if (map:contains($value, node-name(.)) then map:get($value, node-name(.))+1 else 1"/>
</accumulator>

<xsl:template match="Members/*">
  <xsl:element name="{name()}_{accumulator-before('counters')(node-name(.))}">
    .... 

Другой способ сделать условную карту: put is

map:put($value, node-name(.), ($value(node-name(.)), 0)[1] + 1)
...