Пакетный режим против полностью потокового XSLT

Я написал XSLT для преобразования огромного входящего XML-файла в JSON с использованием потоковой передачи в пакетном режиме. Я новичок в XSLT и слышал, что есть лучший способ полной потоковой передачи кода XSLT, который более эффективен и быстрее, чем пакетный режим.

Может кто-нибудь, пожалуйста, помогите мне понять - 1. В чем разница между режимом серийной съемки и полностью потоковой передачей? 2. Как я могу преобразовать приведенный ниже код XSLT в полностью потоковое, чтобы улучшить производительность?

Ниже мой XSLT-код пакетного режима -

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:wd="urn:com.workday.report/INT1109_CR_REV_Customer_Invoices_to_Connect" exclude-result-prefixes="xs" version="3.0">
    <xsl:mode streamable="yes" on-no-match="shallow-skip"/>
    <xsl:output method="text" encoding="UTF-8" indent="no"/>
    <xsl:template match="wd:Report_Data">
        <xsl:iterate select="wd:Report_Entry/copy-of()">
            <!--Define Running Totals for Statistics -->
            <xsl:param name="TotalHeaderCount" select="0"/>
            <xsl:param name="TotalLinesCount" select="0"/>
            <!--Write Statistics -->
            <xsl:on-completion>
                <xsl:text>{"Stats": </xsl:text>
                <xsl:text>{"Total Header Count": </xsl:text>
                <xsl:value-of select="$TotalHeaderCount"/>
                <xsl:text>,</xsl:text>
                <xsl:text>"Total Lines Count": </xsl:text>
                <xsl:value-of select="$TotalLinesCount"/>
                <xsl:text>}}</xsl:text>
            </xsl:on-completion>
            <!--Write Header Details -->
            <xsl:text>{"id": "</xsl:text>
            <xsl:value-of select="wd:id"/>
            <xsl:text>",</xsl:text>
            <xsl:text>"revenue_stream": "</xsl:text>
            <xsl:value-of select="wd:revenue_stream"/>
            <xsl:text>",</xsl:text>
            <!--Write Line Details -->
            <xsl:text>"lines": [  </xsl:text>
            <!-- Count the number of lines for an invoice -->
            <xsl:variable name="Linescount" select="wd:total_lines"/>
            <xsl:iterate select="wd:lines">
                <xsl:text>      {</xsl:text>
                <xsl:text>"sequence": </xsl:text>
                <xsl:value-of select="wd:sequence"/>
                <xsl:text>,</xsl:text>
                <xsl:text>"sales_item_id": "</xsl:text>
                <xsl:value-of select="wd:sales_item_id"/>
                <xsl:text>",</xsl:text>
            </xsl:iterate>
            <xsl:text>}]}&#10;</xsl:text>
            <!--Store Running Totals -->
            <xsl:next-iteration>
                <xsl:with-param name="TotalHeaderCount" select="$TotalHeaderCount + 1"/>
                <xsl:with-param name="TotalLinesCount" select="$TotalLinesCount + $Linescount"/>                
            </xsl:next-iteration>
        </xsl:iterate>
    </xsl:template>
</xsl:stylesheet>

Вот образец XML -

<?xml version="1.0" encoding="UTF-8"?>
<wd:Report_Data xmlns:wd="urn:com.workday.report/INT1109_CR_REV_Customer_Invoices_to_Connect">
    <wd:Report_Entry>
        <wd:id>CUSTOMER_INVOICE-6-1</wd:id>
        <wd:revenue_stream>TESTA</wd:revenue_stream>        
        <wd:total_lines>1</wd:total_lines>
        <wd:lines>
            <wd:sequence>ab</wd:sequence>
            <wd:sales_item_id>Administrative Cost</wd:sales_item_id>            
        </wd:lines>
    </wd:Report_Entry>
    <wd:Report_Entry>
        <wd:id>CUSTOMER_INVOICE-6-10</wd:id>
        <wd:revenue_stream>TESTB</wd:revenue_stream>        
        <wd:total_lines>1</wd:total_lines>
        <wd:lines>
            <wd:sequence>ab</wd:sequence>
            <wd:sales_item_id>Data - Web Access</wd:sales_item_id>
        </wd:lines>
    </wd:Report_Entry>  
</wd:Report_Data>

person XYZ    schedule 07.05.2019    source источник
comment
есть лучший способ полной потоковой передачи кода XSLT, который более эффективен и быстрее, чем пакетный режим: где это сказано, какая-либо связанная статья, которая утверждает это? Для какой именно реализации? В целом использование copy-of() упрощает задачу выбора дочернего контента, как люди привыкли к XSLT 1 и 2, но может потребовать больше памяти. Но использование большего объема памяти не обязательно означает, что он будет медленнее. Так ваш нынешний подход плохо работает? По потреблению памяти? Или по скорости? Что касается того, чтобы избежать copy-of(), изучите аккумуляторы и, если целью является JSON, карты.   -  person Martin Honnen    schedule 08.05.2019
comment
Также будет полезно, если вы предоставите короткий входной образец и соответствующий JSON, который хотите создать. Создает ли опубликованный XSLT один объект JSON верхнего уровня или последовательность объектов?   -  person Martin Honnen    schedule 08.05.2019
comment
Мартин, я обновил приведенный выше XSLT и добавил образец XML. В конце концов мне нужно будет преобразовать XML с 60 полями, около 50 000 заголовков и 10 000 000 строк. Я протестировал 80 заголовков и 50 000 строк, и это заняло около 4 минут. Я не уверен, смогу ли я экстраполировать свое окончательное время выполнения, глядя на это. Поэтому захотелось рассмотреть и другие варианты, когда я прочитал о полностью потоковой передаче XSLT в моем продуктовом сообществе с небольшими подробностями, хотя и не смог найти достаточно онлайн.   -  person XYZ    schedule 08.05.2019
comment
Был бы очень признателен, если бы вы могли поделиться своим опытом о полной потоковой передаче и пакетном режиме, и когда мы должны использовать один вместо другого.   -  person XYZ    schedule 08.05.2019


Ответы (1)


Если порядок свойств в JSON не имеет значения, вы можете напрямую создавать карты и массивы XSLT / XPath 3 с xsl:map/xsl:map-entry (или конструктором XPath 3.1 map) и специфическим для Saxon элементом расширения saxon:array (к сожалению, в языковом стандарте XSLT 3 отсутствует инструкция по созданию массива). Более того, большинство ваших итерационных параметров, кажется, легко реализовать в виде аккумуляторов:

<?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:saxon="http://saxon.sf.net/"
    extension-element-prefixes="saxon"
    xpath-default-namespace="urn:com.workday.report/INT1109_CR_REV_Customer_Invoices_to_Connect"
    exclude-result-prefixes="#all"
    version="3.0">

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

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

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

    <xsl:accumulator name="lines-count" as="xs:integer" initial-value="0" streamable="yes">
        <xsl:accumulator-rule match="Report_Entry/total_lines/text()" select="$value + xs:integer(.)"/>
    </xsl:accumulator>

    <xsl:template match="Report_Data">
        <xsl:apply-templates/>
        <xsl:sequence
            select="map {
                     'Stats': map { 
                          'Total Header Count' : accumulator-after('header-count'),
                          'Total Lines Count' : accumulator-after('lines-count')
                        }
                    }"/>
    </xsl:template>

    <xsl:template match="Report_Entry">
        <xsl:map>
            <xsl:apply-templates/>
        </xsl:map>
    </xsl:template>

    <xsl:template match="Report_Entry/id | Report_Entry/revenue_stream | lines/sequence | lines/sales_item_id">
        <xsl:map-entry key="local-name()" select="string()"/>
    </xsl:template>

    <xsl:template match="Report_Entry/lines">
        <xsl:map-entry key="local-name()">
            <saxon:array>
                <xsl:apply-templates/>
            </saxon:array>
        </xsl:map-entry>
    </xsl:template>

</xsl:stylesheet>

В этом примере используется адаптивный метод вывода, поскольку ваш текущий образец не создает ни одного объекта JSON, и я просто попытался создать тот же вывод, что и ваш текущий код; для метода вывода JSON потребуется одна карта или массив в качестве результата основной последовательности.

Код работает с потоковой передачей и Saxon EE 9.9.1.1 в oXygen, к сожалению, 9.8 не считает код потоковым.

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

До сих пор я бы сказал, что более важно найти способ обойти анализ потоковой передачи и обеспечить, чтобы потоковый код возвращал правильный и тот же результат, что и непотоковый код; например, при экспериментировании с подходом к генерации JSON с потоковой передачей с использованием двух шагов преобразования, где некоторые образцы данных, подобные вашим, являются входными, представление XML для JSON является результатом первого преобразования, а JSON должен быть результатом использования xml-to-json в результате первого шага я столкнулся с саксонской ошибкой https://saxonica.plan.io/issues/4215.

Что касается потоковой передачи, кажется, что недостаточно тестового покрытия или зрелости реализации, чтобы иметь возможность надежно комбинировать функции сложным и масштабируемым образом, частично из-за сложной спецификации, частично из-за ограниченного использования этого материала XSLT-сообществом.

Поэтому, если вы найдете рабочий способ решения конкретной проблемы для использования потоковой передачи, чтобы снизить потребление памяти или управлять им по сравнению с обычной обработкой на основе дерева XSLT 2/3, вы, конечно, можете поэкспериментировать с изменениями для повышения производительности, но это легко сломать. .

Одно общее наблюдение заключается в том, что потоковая передача позволяет вам получить доступ ко всем атрибутам текущего обрабатываемого / согласованного элемента, но не к его дочерним элементам, поэтому может очень помочь вставка шагов обработки, которые преобразуют элементы в атрибуты, если у вас есть простая структура дочерних элементов. Таким образом, вы сможете избежать copy-of(). Но, конечно, вам нужен способ объединения двух таблиц стилей, который Saxon позволяет с его API, но для этого требуется написание кода Java или .NET.

person Martin Honnen    schedule 08.05.2019