Транспонировать XML с использованием метода Мюнхена — множественные группировки

#xslt #transpose #muenchian-grouping

#xslt #транспонировать #мюнхенское-группирование

Вопрос:

Мне нужно транспонировать XML-файл, подобный этому:

 <?xml version="1.0" encoding="UTF-8"?>
<products>
    <product id="10" name="Widget 1" price="15.99" category_code="T" category="Toys" manufacturer_code="SC1" manufacturer="Some Company 1" />
    <product id="10" name="Widget 1" price="15.99" category_code="E" category="Electronics" manufacturer_code="SC1" manufacturer="Some Company 1" />
    <product id="10" name="Widget 1" price="15.99" category_code="T" category="Toys" manufacturer_code="SC2" manufacturer="Some Company 2" />
    <product id="10" name="Widget 1" price="15.99" category_code="E" category="Electronics" manufacturer_code="SC2" manufacturer="Some Company 2" />
    <product id="10" name="Widget 1" price="15.99" category_code="T" category="Toys" manufacturer_code="SC3" manufacturer="Some Company 3" />
    <product id="10" name="Widget 1" price="15.99" category_code="E" category="Electronics" manufacturer_code="SC3" manufacturer="Some Company 3" />
    <product id="11" name="Widget 2" price="21.99" category_code="V" category="Video Games" manufacturer_code="SC4" manufacturer="Some Company 4" />
    <product id="12" name="Widget 3" price="10.99" category_code="T" category="Toys" manufacturer_code="SC1" manufacturer="Some Company 2" />
</products>
  

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

 id,name,price,category_code_1,category_1,category_code_2,category_2,manufacturer_code_1,manufacturer_1,manufacturer_code_2,manufacturer_2,manufacturer_code_3,manufacturer_3
    10, Widget 1, 15.99, T, Toys, E, Electronics, SC1, Some Company 1, SC2, Some Company 2, SC3, Some Company 3 
    11, Widget 2, 21.99, V, Video Games,,, SC4, Some Company 4,,,,
    12, Widget 3, 10.99, T, Toys,,, SC1, Some Company 2,,,,
  

Как вы могли заметить, XML-данные можно рассматривать как результат объединения трех таблиц: product, product_category и product_manufacturer. Каждый продукт может принадлежать нескольким категориям и иметь нескольких производителей. Конечно, реальные данные, с которыми я имею дело, более сложны и находятся в совершенно другой области, но этот пример правильно описывает проблему.

У меня очень ограниченные знания о XSLT, и с помощью SO и других ресурсов в Интернете я собрал таблицу стилей, которая частично предоставляет мне то, что мне нужно:

 <?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:key name="key_product_group" match="product" use="@id"/>
    <xsl:key name="key_category_group" match="product" use="concat(
             @id,
             @category_code,
             @category)"/>
    <xsl:key name="key_manufacturer_group" match="product" use="concat(
             @id,
             @manufacturer_code,
             @manufacturer)"/>
    <xsl:variable name="var_max_category_group"  >
        <xsl:for-each select="//product[generate-id(.) = generate-id(key('key_product_group',@id) )]">
            <xsl:sort select="count(key('key_product_group',@id )[generate-id() = generate-id(key('key_category_group',
                                   concat(@id,
                                  @category_code,
                                  @category)))])" order="descending"/>
            <xsl:if test="position() = 1">
                <xsl:value-of select="count(key('key_product_group',@id )[generate-id() = generate-id(key('key_category_group',
                                   concat(@id,
                                  @category_code,
                                  @category)))])"/>
            </xsl:if>
        </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="var_max_manufacturer_group">
        <xsl:for-each select="//product[generate-id(.) = generate-id(key('key_product_group',@id) )]">
            <xsl:sort select="count(key('key_product_group',@id )[generate-id() = generate-id(key('key_manufacturer_group',
                                   concat(@id,
                                  @category_code,
                                  @category)))])" order="descending"/>
            <xsl:if test="position() = 1">
                <xsl:value-of   select="count(key('key_product_group',@id )[generate-id() = generate-id(key('key_manufacturer_group',
                                   concat(@id,
                                  @manufacturer_code,
                                  @manufacturer)))])"/>
            </xsl:if>
        </xsl:for-each>
    </xsl:variable>
    <xsl:template match="/">
        <xsl:text>id,</xsl:text>
        <xsl:text>name,</xsl:text>
        <xsl:text>price,</xsl:text>
        <xsl:call-template name="loop_pcat">
            <xsl:with-param name="count" select="$var_max_category_group"/>
        </xsl:call-template>
        <xsl:call-template name="loop_pmf">
            <xsl:with-param name="count" select="$var_max_manufacturer_group"/>
        </xsl:call-template>
        <br></br>
        <xsl:variable name="var_group"
                          select="//product[generate-id(.) = generate-id(key('key_product_group',@id)[1])]"/>
        <xsl:for-each select="$var_group">
            <xsl:sort order="ascending" select="@id"/>
            <xsl:value-of select="@id"/>,
            <xsl:value-of select="@name"/>,
            <xsl:value-of select="@price"/>,
            <xsl:for-each select="key('key_product_group',@id )[generate-id() = generate-id(key('key_category_group',
                                   concat(@id,
                                  @category_code,
                                  @category)))]">
                <xsl:value-of select="@category_code"/>,
                <xsl:value-of select="@category"/>,
            </xsl:for-each>
            <xsl:for-each select="key('key_product_group',@id )[generate-id() = generate-id(key('key_manufacturer_group',
                                   concat(@id,
                                  @manufacturer_code,
                                  @manufacturer)))]">
                <xsl:value-of select="@manufacturer_code"/>,
                <xsl:value-of select="@manufacturer"/>,
            </xsl:for-each>
            <br></br>
        </xsl:for-each>
    </xsl:template>
    <xsl:template name="loop_pcat">
        <xsl:param name="count" select="1"/>
        <xsl:param name="limit" select="$count 1"/>
        <xsl:if test="$count > 0">
            <xsl:value-of select="concat('category_code_',$limit - $count)"/>
            <xsl:text>,</xsl:text>
            <xsl:value-of select="concat('category_',$limit - $count)"/>
            <xsl:text>,</xsl:text>
            <xsl:call-template name="loop_pcat">
                <xsl:with-param name="count" select="$count - 1"/>
                <xsl:with-param name="limit" select="$limit"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
    <xsl:template name="loop_pmf">
        <xsl:param name="count" select="1"/>
        <xsl:param name="limit" select="$count 1"/>
        <xsl:if test="$count > 0">
            <xsl:value-of select="concat('manufacturer_code_',$limit - $count)"/>
            <xsl:text>,</xsl:text>
            <xsl:value-of select="concat('manufacturer_',$limit - $count)"/>
            <xsl:text>,</xsl:text>
            <xsl:call-template name="loop_pmf">
                <xsl:with-param name="count" select="$count - 1"/>
                <xsl:with-param name="limit" select="$limit"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>
  

Приведенная выше таблица стилей выдает следующие результаты:

 id,name,price,category_code_1,category_1,category_code_2,category_2,manufacturer_code_1,manufacturer_1,manufacturer_code_2,manufacturer_2,manufacturer_code_3,manufacturer_3,
10, Widget 1, 15.99, T, Toys, E, Electronics, SC1, Some Company 1, SC2, Some Company 2, SC3, Some Company 3, 
11, Widget 2, 21.99, V, Video Games, SC4, Some Company 4, 
12, Widget 3, 10.99, T, Toys, SC1, Some Company 2, 
  

На выходе возникает по крайней мере одна серьезная проблема: все столбцы не существуют в каждой строке, например, в строках 2 и 3 отсутствуют category_code_2 и category_2, а также manufacture_code и manufacturer 2 и 3. Я уверен, что есть и другие проблемы с таблицей стилей, и я понятия не имею, как это будет работать с относительно большим XML-файлом, но сейчас я был бы очень признателен за вашу помощь в том, чтобы заставить таблицу стилей выдавать желаемый формат вывода.

Спасибо

MRSA

Комментарии:

1. Теперь, когда я думаю об этом немного больше, возможно, я смогу подсчитать количество каждой подгруппы для каждого продукта и после цикла for-each подгруппы вызвать рекурсивный цикл, чтобы добавить пустые столбцы для разницы максимального количества каждой подгруппы и индивидуального количества. Я опубликую решение позже сегодня.

2. Если вы также понимаете , почему таблица стилей делает то, что она делает, и что вы ей приказываете делать, то я бы не сказал, что ваши знания о XSLT настолько ограничены 😉

3. @mousio: Я думаю, что по большей части я понимаю таблицу стилей… но это всего лишь мой второй опыт работы с XSLT, первый был три года назад, когда мне пришлось преобразовать большой и сложный schmea в меньший и гораздо более простой, и я использовал для этого инструмент отображения XML, Altova IIRC, а затем немного изменил таблицу стилей. Я впервые пишу таблицу стилей с нуля. Спасибо за вашу помощь и комплимент 🙂

Ответ №1:

Я уже начал со своей собственной реализации, но затем вы опубликовали, и я смог повторно использовать часть вашего кода, чтобы немного ускорить процесс. Основные отличия заключаются в:

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

Вот моя результирующая таблица стилей и ее выходные данные:

 <?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" version="1.0" encoding="UTF-8"/>
    <xsl:key name="byProduct" match="product" use="@id"/> 
    <xsl:key name="byProductAndCategory" match="product" use="concat(@id,'#',@category_code)"/> 
    <xsl:key name="byProductAndManufacturer" match="product" use="concat(@id,'#',@manufacturer_code)"/>  
    <xsl:variable name="delimiter" select="','"/>
    <xsl:variable name="eol" select="'amp;#xa;'"/>
    <xsl:variable name="maxManufacturers">
        <xsl:for-each select="//product[count(.|key('byProduct',@id)[1])=1]">
            <xsl:sort select="count(key('byProductAndCategory',concat(@id,'#',@category_code)))"/>
            <xsl:if test="position()=last()">
                <xsl:value-of select="count(key('byProductAndCategory',concat(@id,'#',@category_code)))"/>
            </xsl:if>
        </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="maxCategories">
        <xsl:for-each select="//product[count(.|key('byProduct',@id)[1])=1]">
            <xsl:sort select="count(key('byProductAndManufacturer',concat(@id,'#',@manufacturer_code)))"/>
            <xsl:if test="position()=last()">
                <xsl:value-of select="count(key('byProductAndManufacturer',concat(@id,'#',@manufacturer_code)))"/>
            </xsl:if>
        </xsl:for-each>
    </xsl:variable>
    <xsl:template match="/">
        <!--create the file header-->
        <xsl:text>id</xsl:text>
        <xsl:value-of select="$delimiter"/>
        <xsl:text>name</xsl:text>
        <xsl:value-of select="$delimiter"/>
        <xsl:text>price</xsl:text>
        <!--recursive loop for each group to add appropriate number of columns, i.e. maximum number of repetitions-->
        <xsl:call-template name="loop_pcat_header">
            <xsl:with-param name="count" select="$maxCategories"/>
        </xsl:call-template>
        <!--recursive loop for each group to add appropriate number of columns, i.e. maximum number of repetitions-->
        <xsl:call-template name="loop_pmf_header">
            <xsl:with-param name="count" select="$maxManufacturers"/>
        </xsl:call-template>
        <xsl:value-of select="$eol"/>
        <xsl:apply-templates select="//product[count(.|key('byProduct',@id)[1])=1]"/>
    </xsl:template>
    <xsl:template match="product">
        <!-- id -->
        <xsl:value-of select="@id"/>
        <xsl:value-of select="$delimiter"/>
        <!-- name -->
        <xsl:value-of select="@name"/>
        <xsl:value-of select="$delimiter"/>
        <!-- price -->
        <xsl:value-of select="@price"/>
        <!-- category stuff -->
        <xsl:call-template name="loop_pcat_data">
            <xsl:with-param name="data" select="key('byProductAndManufacturer',concat(@id,'#',@manufacturer_code))"/>
            <xsl:with-param name="count" select="$maxCategories"/>
        </xsl:call-template>
        <!-- manufacturer stuff -->
        <xsl:call-template name="loop_pmf_data">
            <xsl:with-param name="data" select="key('byProductAndCategory',concat(@id,'#',@category_code))"/>
            <xsl:with-param name="count" select="$maxManufacturers"/>
        </xsl:call-template>
        <xsl:value-of select="$eol"/>
    </xsl:template>
    <!--recursive loop templates for file header and data-->
    <xsl:template name="loop_pcat_header">
        <xsl:param name="count" select="1"/>
        <xsl:param name="limit" select="$count 1"/>
        <xsl:if test="$count amp;> 0">
            <xsl:value-of select="$delimiter"/>
            <xsl:value-of select="concat('category_code_',$limit - $count)"/>
            <xsl:value-of select="$delimiter"/>
            <xsl:value-of select="concat('category_',$limit - $count)"/>
            <xsl:call-template name="loop_pcat_header">
                <xsl:with-param name="count" select="$count - 1"/>
                <xsl:with-param name="limit" select="$limit"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
    <xsl:template name="loop_pmf_header">
        <xsl:param name="count" select="1"/>
        <xsl:param name="limit" select="$count 1"/>
        <xsl:if test="$count amp;> 0">
            <xsl:value-of select="$delimiter"/>
            <xsl:value-of select="concat('manufacturer_code_',$limit - $count)"/>
            <xsl:value-of select="$delimiter"/>
            <xsl:value-of select="concat('manufacturer_',$limit - $count)"/>
            <xsl:call-template name="loop_pmf_header">
                <xsl:with-param name="count" select="$count - 1"/>
                <xsl:with-param name="limit" select="$limit"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
    <xsl:template name="loop_pcat_data">
        <xsl:param name="data"/>
        <xsl:param name="count" select="1"/>
        <xsl:param name="limit" select="$count 1"/>
        <xsl:if test="$count amp;> 0">
            <xsl:value-of select="$delimiter"/>
            <xsl:value-of select="$data[$limit - $count]/@category_code"/>
            <xsl:value-of select="$delimiter"/>
            <xsl:value-of select="$data[$limit - $count]/@category"/>
            <xsl:call-template name="loop_pcat_data">
                <xsl:with-param name="data" select="$data"/>
                <xsl:with-param name="count" select="$count - 1"/>
                <xsl:with-param name="limit" select="$limit"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
    <xsl:template name="loop_pmf_data">
        <xsl:param name="data"/>
        <xsl:param name="count" select="1"/>
        <xsl:param name="limit" select="$count 1"/>
        <xsl:if test="$count amp;> 0">
            <xsl:value-of select="$delimiter"/>
            <xsl:value-of select="$data[$limit - $count]/@manufacturer_code"/>
            <xsl:value-of select="$delimiter"/>
            <xsl:value-of select="$data[$limit - $count]/@manufacturer"/>
            <xsl:call-template name="loop_pmf_data">
                <xsl:with-param name="data" select="$data"/>
                <xsl:with-param name="count" select="$count - 1"/>
                <xsl:with-param name="limit" select="$limit"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>
  

Запрошенный результат:

 id,name,price,category_code_1,category_1,category_code_2,category_2,manufacturer_code_1,manufacturer_1,manufacturer_code_2,manufacturer_2,manufacturer_code_3,manufacturer_3
10,Widget 1,15.99,T,Toys,E,Electronics,SC1,Some Company 1,SC2,Some Company 2,SC3,Some Company 3
11,Widget 2,21.99,V,Video Games,,,SC4,Some Company 4,,,,
12,Widget 3,10.99,T,Toys,,,SC1,Some Company 2,,,,
  

Ответ №2:

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

Следующая таблица стилей

 <?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" version="1.0" encoding="UTF-8" indent="yes"/>
    <!--Generate keys, first is the product group-->
    <xsl:key name="key_product_group" match="product" use="@id"/>
    <!--Here is keys for groupings that are needed, note concatenation of attributes-->
    <xsl:key name="key_category_group" match="product" use="concat(
             @id,
             @category_code,
             @category)"/>
    <!--another key-->
    <xsl:key name="key_manufacturer_group" match="product" use="concat(
             @id,
             @manufacturer_code,
             @manufacturer)"/>
    <!--we need the maximum number of distinct groups for each product so that can format the layout properly-->
    <xsl:variable name="var_max_category_group"  >
        <xsl:for-each select="//product[generate-id(.) = generate-id(key('key_product_group',@id) )]">
            <xsl:sort select="count(key('key_product_group',@id )[generate-id() = generate-id(key('key_category_group',
                                   concat(@id,
                                  @category_code,
                                  @category)))])" order="descending"/>
            <xsl:if test="position() = 1">
                <xsl:value-of select="count(key('key_product_group',@id )[generate-id() = generate-id(key('key_category_group',
                                   concat(@id,
                                  @category_code,
                                  @category)))])"/>
            </xsl:if>
        </xsl:for-each>
    </xsl:variable>
    <!--maximum for the second group-->
    <xsl:variable name="var_max_manufacturer_group">
        <xsl:for-each select="//product[generate-id(.) = generate-id(key('key_product_group',@id) )]">
            <xsl:sort select="count(key('key_product_group',@id )[generate-id() = generate-id(key('key_manufacturer_group',
                                   concat(@id,
                                  @category_code,
                                  @category)))])" order="descending"/>
            <xsl:if test="position() = 1">
                <xsl:value-of   select="count(key('key_product_group',@id )[generate-id() = generate-id(key('key_manufacturer_group',
                                   concat(@id,
                                  @manufacturer_code,
                                  @manufacturer)))])"/>
            </xsl:if>
        </xsl:for-each>
    </xsl:variable>
    <!--delmiter and line break characters defined here-->
    <xsl:variable name="var_delimiter" select="','"/>
    <xsl:variable name="var_line_break" select="'amp;#xa;'"/>

    <!--main procedure starts here-->
    <xsl:template match="/">
        <!--create the file header-->
        <xsl:text>id</xsl:text>
        <xsl:value-of select="$var_delimiter"/>
        <xsl:text>name</xsl:text>
        <xsl:value-of select="$var_delimiter"/>
        <xsl:text>price</xsl:text>
        <xsl:value-of select="$var_delimiter"/>
        <!--recursive loop for each group to add appropriate number of columns, i.e. maximum number of repetitions-->
        <xsl:call-template name="loop_pcat_header">
            <xsl:with-param name="count" select="$var_max_category_group"/>
        </xsl:call-template>
        <!--recursive loop for each group to add appropriate number of columns, i.e. maximum number of repetitions-->
        <xsl:call-template name="loop_pmf_header">
            <xsl:with-param name="count" select="$var_max_manufacturer_group"/>
        </xsl:call-template>
        <xsl:value-of select="$var_line_break"/>
        <!--select products and order ascending, the ordering is not really needed-->
        <xsl:variable name="var_group"
                          select="//product[generate-id(.) = generate-id(key('key_product_group',@id)[1])]"/>
        <xsl:for-each select="$var_group">
            <xsl:sort order="ascending" select="@id"/>
            <!--select non-repeatable attributes-->
            <xsl:value-of select="@id"/>
            <xsl:value-of select="$var_delimiter"/>
            <xsl:value-of select="@name"/>
            <xsl:value-of select="$var_delimiter"/>
            <xsl:value-of select="@price"/>
            <xsl:value-of select="$var_delimiter"/>
            <!--select repeatable groups for each product-->
            <xsl:for-each select="key('key_product_group',@id )[generate-id() = generate-id(key('key_category_group',
                                   concat(@id,
                                  @category_code,
                                  @category)))]">
                <xsl:value-of select="@category_code"/>
                <xsl:value-of select="$var_delimiter"/>
                <xsl:value-of select="@category"/>
                <xsl:value-of select="$var_delimiter"/>
                <!--count number of groups for each product and add blanks for the difference between the max and current instance-->
                <xsl:variable name="var_max_category_group_instance" select="count(key('key_product_group',@id )[generate-id() = generate-id(key('key_category_group',
                                   concat(@id,
                                  @category_code,
                                  @category)))])"/>
                <xsl:call-template name="loop_pcat_data">
                    <xsl:with-param name="count" select="$var_max_category_group - $var_max_category_group_instance"/>
                </xsl:call-template>
            </xsl:for-each>
            <!--select repeatable groups for each product-->
            <xsl:for-each select="key('key_product_group',@id )[generate-id() = generate-id(key('key_manufacturer_group',
                                   concat(@id,
                                  @manufacturer_code,
                                  @manufacturer)))]">
                <xsl:value-of select="@manufacturer_code"/>
                <xsl:value-of select="$var_delimiter"/>
                <xsl:value-of select="@manufacturer"/>
                <xsl:value-of select="$var_delimiter"/>
                <!--count number of groups for each product and add blanks for the difference between the max and current instance-->
                <xsl:variable name="var_max_manufacturer_group_instance" select="count(key('key_product_group',@id )[generate-id() = generate-id(key('key_manufacturer_group',
                                   concat(@id,
                                  @manufacturer_code,
                                  @manufacturer)))])"/>
                <xsl:call-template name="loop_pmf_data">
                    <xsl:with-param name="count" select="$var_max_manufacturer_group - $var_max_manufacturer_group_instance"/>
                </xsl:call-template>
            </xsl:for-each>
            <xsl:value-of select="$var_line_break"/>
        </xsl:for-each>
    </xsl:template>
    <!--recursive loop templates for file header and data-->
    <xsl:template name="loop_pcat_header">
        <xsl:param name="count" select="1"/>
        <xsl:param name="limit" select="$count 1"/>
        <xsl:if test="$count > 0">
            <xsl:value-of select="concat('category_code_',$limit - $count)"/>
            <xsl:value-of select="$var_delimiter"/>
            <xsl:value-of select="concat('category_',$limit - $count)"/>
            <xsl:value-of select="$var_delimiter"/>
            <xsl:call-template name="loop_pcat_header">
                <xsl:with-param name="count" select="$count - 1"/>
                <xsl:with-param name="limit" select="$limit"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
    <xsl:template name="loop_pcat_data">
        <xsl:param name="count" select="1"/>
        <xsl:param name="limit" select="$count 1"/>
        <xsl:if test="$count > 0">
            <xsl:value-of select="$var_delimiter"/>
            <xsl:value-of select="$var_delimiter"/>
            <xsl:call-template name="loop_pcat_data">
                <xsl:with-param name="count" select="$count - 1"/>
                <xsl:with-param name="limit" select="$limit"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
    <xsl:template name="loop_pmf_header">
        <xsl:param name="count" select="1"/>
        <xsl:param name="limit" select="$count 1"/>
        <xsl:if test="$count > 0">
            <xsl:value-of select="concat('manufacturer_code_',$limit - $count)"/>
            <xsl:value-of select="$var_delimiter"/>
            <xsl:value-of select="concat('manufacturer_',$limit - $count)"/>
            <xsl:value-of select="$var_delimiter"/>
            <xsl:call-template name="loop_pmf_header">
                <xsl:with-param name="count" select="$count - 1"/>
                <xsl:with-param name="limit" select="$limit"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
    <xsl:template name="loop_pmf_data">
        <xsl:param name="count" select="1"/>
        <xsl:param name="limit" select="$count 1"/>
        <xsl:if test="$count > 0">
            <xsl:value-of select="$var_delimiter"/>
            <xsl:value-of select="$var_delimiter"/>
            <xsl:call-template name="loop_pmf_data">
                <xsl:with-param name="count" select="$count - 1"/>
                <xsl:with-param name="limit" select="$limit"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>
  

при применении к этому XML

 <?xml version="1.0" encoding="UTF-8"?>
<products>
    <product id="10" name="Widget 1" price="15.99" category_code="T" category="Toys" manufacturer_code="SC1" manufacturer="Some Company 1" />
    <product id="10" name="Widget 1" price="15.99" category_code="E" category="Electronics" manufacturer_code="SC1" manufacturer="Some Company 1" />
    <product id="10" name="Widget 1" price="15.99" category_code="T" category="Toys" manufacturer_code="SC2" manufacturer="Some Company 2" />
    <product id="10" name="Widget 1" price="15.99" category_code="E" category="Electronics" manufacturer_code="SC2" manufacturer="Some Company 2" />
    <product id="10" name="Widget 1" price="15.99" category_code="T" category="Toys" manufacturer_code="SC3" manufacturer="Some Company 3" />
    <product id="10" name="Widget 1" price="15.99" category_code="E" category="Electronics" manufacturer_code="SC3" manufacturer="Some Company 3" />
    <product id="11" name="Widget 2" price="21.99" category_code="V" category="Video Games" manufacturer_code="SC4" manufacturer="Some Company 4" />
    <product id="12" name="Widget 3" price="10.99" category_code="T" category="Toys" manufacturer_code="SC1" manufacturer="Some Company 2" />
</products>
  

генерирует следующий вывод:

 id,name,price,category_code_1,category_1,category_code_2,category_2,manufacturer_code_1,manufacturer_1,manufacturer_code_2,manufacturer_2,manufacturer_code_3,manufacturer_3,
10,Widget 1,15.99,T,Toys,E,Electronics,SC1,Some Company 1,SC2,Some Company 2,SC3,Some Company 3,
11,Widget 2,21.99,V,Video Games,,,SC4,Some Company 4,,,,,
12,Widget 3,10.99,T,Toys,,,SC1,Some Company 2,,,,,
  

Комментарии:

1. Рад видеть некоторый прогресс, отличная работа! Однако это все еще не соответствует вашему желаемому результату (см. Ваш вопрос): мне кажется, в выводе слишком много запятых?

2. @mousio: Спасибо, что заметили ошибку, я спешил опубликовать решение и не заметил, что при очистке кода, которую я провел перед публикацией, в нем была ошибка. Итак, я исправил это и избавился от лишних запятых.

3. @mousio: Ваш ответ ниже идеален, хотя у меня есть пара вопросов, которые я задаю здесь, поскольку я не могу добавить комментарии к вашему ответу: 1- Как мне пометить его как ответ? 2. Действительно ли нам нужен разделитель для конкатенации? Я целенаправленно добавил категорию и производителя как часть ключа для групп, поскольку в реальном сценарии для групп нет единого идентификатора, и мне приходится группировать по нескольким полям.

4. Чтобы отметить ответ как принятый , нажмите на галочку рядом с ответом, чтобы переключить его с пустого на зеленый :] Что касается объединения значений ключа: у меня просто есть привычка добавлять разделитель, чтобы избежать ситуаций, когда, например, 123 может быть concat('12','3') или concat('1','23') . Вы можете использовать все, что подходит вам и соответствует вашим потребностям; Я использовал свои ключи, исходя из предположения, что каждому коду pcat или pmf будет соответствовать одно имя pcat или pmf.

5. @mousio: Спасибо за ваш ответ, ваши рассуждения об использовании delimiter имеют полный смысл. Чтобы отметить ответ как принятый, я не вижу знака hallow, вероятно, что-то не так с моим ответом, попробую другой.