XSLT Muenchian Grouping по разным элементам на основе общего атрибута

Мне предоставляется XML, аналогичный следующему, который мне нужно обработать.

<root>
    <Header/>
    <Customer id="1" date="13/04/2014"/>
    <Account id="1" date="14/04/2014"/>
    <Account id="1" date="01/06/2015"/>
    <Address id="1" date="14/04/2014"/>
    <Customer id="2" date="12/08/2015"/>
    <Account id="2" date="13/08/2015"/>
    <Address id="2" date="13/08/2015"/>
    <Address id="2" date="03/09/2015"/>
    <Address id="2" date="27/01/2017"/>
    <Customer id="3" date="04/10/2015"/>
    <Customer id="3" date="01/02/2017"/>
    <Account id="3" date="05/10/2015"/>
    <Address id="3" date="08/10/2015"/>
    <Address id="3" date="03/09/2016"/>
</root>

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

Если старые могут быть удалены одновременно, я хотел бы вывести это примерно так.

<Customers>
    <Customer id="1">
        <Account/>
        <Address/>
    </Customer>
    <Customer id="2">
        <Account/>
        <Address/>
    </Customer>
    <Customer id="3">
        <Account/>
        <Address/>
    </Customer>
</Customers>

Если нет, то можно обработать файл в двух преобразованиях (одно для группировки по идентификатору клиента, и у каждого клиента есть несколько полей учетной записи / адреса, а затем в другом преобразовании удалить старые записи)

Исходный XML содержит около миллиона записей, поэтому производительность является проблемой. Преобразование занимает несколько минут, но больше 15 не работает.

В настоящее время у меня есть следующий XSLT

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

    <xsl:key name="nodes-by-id" match="//root/*" use="@id"/>

    <xsl:template match="root">
        <Customers>
            <xsl:for-each select="*[count(. | key('nodes-by-id', @id)[1]) = 1]">
                <xsl:variable name="current-grouping-key" select="@id"/>
                <xsl:variable name="current-group" select="key('nodes-by-id', $current-grouping-key)"/>
                <Customer>
                    <xsl:attribute name="id">
                        <xsl:value-of select="$current-grouping-key"/>
                    </xsl:attribute>
                    <CustomerElements>
                        <xsl:for-each select="$current-group/Customer">
                            <CustomerElement>
                                <xsl:attribute name="date">
                                    <xsl:value-of select="@date"/>
                                </xsl:attribute>
                            </CustomerElement>
                        </xsl:for-each>
                    </CustomerElements>
                    <xsl:apply-templates select="$current-group"/>
                </Customer>
            </xsl:for-each>
        </Customers>
    </xsl:template>
</xsl:stylesheet>

В настоящее время он просто пытается сгруппировать все элементы по их идентификатору, а затем вывести все элементы Customer. Получаю следующее:

<Customers>
    <Customer id="">
        <CustomerElements/>
    </Customer>
    <Customer id="1">
        <CustomerElements/>
    </Customer>
    <Customer id="2">
        <CustomerElements/>
    </Customer>
    <Customer id="3">
        <CustomerElements/>
    </Customer>
</Customers>

Я получаю клиента с пустым идентификатором, потому что я не игнорирую строку заголовка. Мой настоящий вопрос: почему переменная $ current-group не содержит никаких элементов?

Также есть советы о том, как игнорировать строку заголовка и отфильтровать записи со старыми датами.


person Riverchimp    schedule 05.06.2017    source источник
comment
Мой настоящий вопрос: почему переменная $ current-group не содержит никаких элементов? Да, - проверьте, что вы получаете с <xsl:copy-of select="$current-group"/>. Кроме того, чтобы исключить пустой идентификатор, вы можете начать с <xsl:for-each select="Customer[count(. | key('nodes-by-id', @id)[1]) = 1]">. - Тут сразу две проблемы - предлагаю разделить на несколько вопросов.   -  person michael.hor257k    schedule 06.06.2017
comment
Ах, так <xsl:copy-of select="$current-group"/> действительно выводит элементы, так что они там. Как мне получить доступ к отдельным элементам этой переменной? <xsl:copy-of select="$current-group[name() = 'Account']"/> лучший способ?   -  person Riverchimp    schedule 06.06.2017
comment
Вы могли бы сделать <xsl:copy-of select="$current-group/Account'"/>, но это не послужит вашей цели, если вы хотите сгруппировать и отсортировать их.   -  person michael.hor257k    schedule 06.06.2017
comment
Хммм, <xsl:copy-of select="$current-group/Account"/> ничего не возвращает, а <xsl:copy-of select="$current-group[name() = 'Account']"/> возвращает.   -  person Riverchimp    schedule 06.06.2017
comment
Ой, извините: я имел в виду <xsl:copy-of select="$current-group[self::Account]"/>.   -  person michael.hor257k    schedule 06.06.2017
comment
круто <xsl:copy-of select="$current-group[self::Account]"/> работает. Спасибо за вашу помощь.   -  person Riverchimp    schedule 06.06.2017


Ответы (1)


Я все разобрал. Это сегмент XSLT, который я использовал. Подробнее в комментариях XML.

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

    <xsl:key name="nodes-by-id" match="//root/*" use="@id"/>

    <xsl:template match="PR-030">
        <CustomerMeters>
        <!-- Using select="Customer[cou.... instead of select="*[cou... will couse it to ignore the header. However it requres
            the Customer element to be the first element for the icp in the xml. -->
            <xsl:for-each select="Customer[count(. | key('nodes-by-id', @id)[1]) = 1]">
                <xsl:variable name="current-grouping-key" select="@id"/>
                <xsl:variable name="current-group" select="key('nodes-by-id', $current-grouping-key)"/>

                <xsl:variable name="current-group-sorted">
                    <!-- If we sort all nodes by date order, then we can fetch the first Address/Customer/etc... from this group and we will have the latest-->
                    <xsl:for-each select="$current-group">
                        <!-- year -->
                        <xsl:sort select="substring(@date, 7, 4)" order="descending" data-type="number"/>
                        <!-- month -->
                        <xsl:sort select="substring(@date, 4, 2)" order="descending" data-type="number"/>
                        <!-- day -->
                        <xsl:sort select="substring(@date, 1, 2)" order="descending" data-type="number"/>
                        <xsl:copy-of select="current()" />
                    </xsl:for-each>
                </xsl:variable>
                <Customer>
                    <!-- In here I can get what I want from the current-group-sorted varaible-->
                    <!-- Because they are in date order I can just get the first occurance and it will be the most recent-->
                    <someField>
                        <xsl:value-of select="$current-group-sorted/*[self::Account][1]/@someAttribute"/>
                    </someField>
                </Customer>
            </xsl:for-each>
        </CustomerMeters>
    </xsl:template>
</xsl:stylesheet>
person Riverchimp    schedule 06.06.2017