Используйте XSLT для группировки повторяющихся XML-данных.

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

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

Желательно XSLT 1.0

Это возможно?

Мне нужно исходить из этого:

<table name = "dogs">
    <fields>
        <field name = "name" value = "dog1"></field>
        <field name = "age" value = "2"></field>
        <field name = "haircolor" value = "brown"></field>
        <field name = "name" value = "dog2"></field>
        <field name = "age" value = "10"></field>
        <field name = "haircolor" value = "white"></field>
        <field name = "name" value = "dog3"></field>
        <field name = "age" value = "7"></field>
        <field name = "haircolor" value = "black"></field>
        <field name = "name" value = "dog4"></field>
        <field name = "age" value = "4"></field>
        <field name = "haircolor" value = "brown"></field>
    </fields>
</table>

К этому:

<dogs count = "4">
    <dog>
        <name>dog1</name>
        <age>2</age>
        <haircolor>brown</haircolor>
    </dog>
    <dog>
        <name>dog2</name>
        <age>10</age>
        <haircolor>white</haircolor>
    </dog>
    <dog>
        <name>dog3</name>
        <age>7</age>
        <haircolor>black</haircolor>
    </dog>
    <dog>
        <name>dog4</name>
        <age>4</age>
        <haircolor>brown</haircolor>
    </dog>
</dogs>

person Ronald    schedule 28.05.2014    source источник
comment
Вы должны сказать, собираетесь ли вы использовать XSLT 1.0 или 2.0, потому что в версии 2.0 лучше поддерживается группировка.   -  person Lumi    schedule 28.05.2014
comment
Предпочтительно XSLT 1.0, но если у вас есть решение в версии 2.0, я открыт для предложений.   -  person Ronald    schedule 28.05.2014
comment
Я могу не знать, сколько описательных элементов о собаках будет. Ну, вам нужно знать кое-что, иначе это невозможно. Например, можно ли предположить, что группа всегда будет начинаться с имени?   -  person michael.hor257k    schedule 28.05.2014
comment
Привет Михаил, ты прав. Таких таблиц будет несколько и я буду знать первое поле. Это не всегда будет «имя», но я могу указать его, возможно, через переменную. Ваше решение делает то, что просили, отлично.   -  person Ronald    schedule 28.05.2014
comment
Это не всегда будет "имя", но я могу указать его, возможно, через переменную. Боюсь, это не сработает, потому что вы не можете использовать переменную в шаблоне соответствия. Однако я изменил свой ответ, чтобы использовать имя поля first в качестве «подсказки» для создания новой группы.   -  person michael.hor257k    schedule 28.05.2014


Ответы (2)


Предположим, что каждая группа полей начинается с имени: .

XSLT 1.0

<?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="fields-by-lead" match="field[@name!='name']" use="generate-id(preceding-sibling::field[@name='name'][1])" />

<xsl:template match="/table">
    <xsl:variable name="names" select="fields/field[@name='name']" />
    <dogs count="{count($names)}">
        <xsl:for-each select="$names">
            <dog>
                <name><xsl:value-of select="@value"/></name>
                <xsl:for-each select="key('fields-by-lead', generate-id())">
                    <xsl:element name="{@name}">
                        <xsl:value-of select="@value"/>
                    </xsl:element>
                </xsl:for-each>
            </dog>
        </xsl:for-each>
    </dogs>
</xsl:template>

</xsl:stylesheet>

Редактировать:

Следующая модификация запускает новую группу для каждого поля, имя которого совпадает с именем самого первого поля.

XSLT 1.0

<?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="fields-by-lead" match="field" use="generate-id(preceding-sibling::field[@name=/table/fields/field[1]/@name][1])" />

<xsl:template match="/table">
    <xsl:variable name="lead-label" select="/table/fields/field[1]/@name" />
    <xsl:variable name="leads" select="fields/field[@name=$lead-label]" />
    <dogs count="{count($leads)}">
        <xsl:for-each select="$leads">
            <dog>
                <xsl:for-each select=". | key('fields-by-lead', generate-id())[@name!=$lead-label]">
                    <xsl:element name="{@name}">
                        <xsl:value-of select="@value"/>
                    </xsl:element>
                </xsl:for-each>
            </dog>
        </xsl:for-each>
    </dogs>
</xsl:template>

</xsl:stylesheet>
person michael.hor257k    schedule 28.05.2014

Вот способ XSLT 1.0 сделать это:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

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

<xsl:template match="table">
  <xsl:element name="{@name}">
    <xsl:variable name="fields" select="fields/field[@name = current()/fields/field[1]/@name]"/>
    <xsl:attribute name="count"><xsl:value-of select="count($fields)"/></xsl:attribute>
    <xsl:apply-templates select="$fields"/>
  </xsl:element>
</xsl:template>

<xsl:template match="field">
  <xsl:variable name="this" select="."/>
  <xsl:element name="{../../@name}">
    <name><xsl:value-of select="@value"/></name>
    <xsl:apply-templates select="following-sibling::field[1][not(@name = $this/@name)]" mode="trans">
      <xsl:with-param name="head-name" select="$this/@name"/>
    </xsl:apply-templates>
  </xsl:element>

</xsl:template>

<xsl:template match="field" mode="trans">
  <xsl:param name="head-name"/>
  <xsl:element name="{@name}"><xsl:value-of select="@value"/></xsl:element>
  <xsl:apply-templates select="following-sibling::field[1][not(@name = $head-name)]" mode="trans">
    <xsl:with-param name="head-name" select="$head-name"/>
  </xsl:apply-templates>
</xsl:template>

</xsl:stylesheet>

Если вы хотите использовать параметр, код может использовать

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:param name="head-name" select="'name'"/>

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

<xsl:template match="table">
  <xsl:element name="{@name}">
    <xsl:variable name="fields" select="fields/field[@name = $head-name]"/>
    <xsl:attribute name="count"><xsl:value-of select="count($fields)"/></xsl:attribute>
    <xsl:apply-templates select="$fields"/>
  </xsl:element>
</xsl:template>

<xsl:template match="field">
  <xsl:variable name="this" select="."/>
  <xsl:element name="{../../@name}">
    <name><xsl:value-of select="@value"/></name>
    <xsl:apply-templates select="following-sibling::field[1][not(@name = $head-name)]" mode="trans"/>
  </xsl:element>
</xsl:template>

<xsl:template match="field" mode="trans">
  <xsl:element name="{@name}"><xsl:value-of select="@value"/></xsl:element>
  <xsl:apply-templates select="following-sibling::field[1][not(@name = $head-name)]" mode="trans">
    <xsl:with-param name="head-name" select="$head-name"/>
  </xsl:apply-templates>
</xsl:template>

</xsl:stylesheet>
person Martin Honnen    schedule 28.05.2014
comment
Может быть, ваше решение сломается, если @name не уникален? Я думаю, что решение @ michael.hor257k справляется с этим правильно. - person Marcus Rickert; 28.05.2014
comment
Майкл предположил, что field, начинающий группу, имеет атрибут name со значением name. Я предположил, что field, начинающий группу, имеет атрибут name с тем же значением, что и самый первый field. Конечно, с обоими решениями могут быть и есть другие элементы field с разными значениями атрибута name, поэтому я не уверен, где, по вашему мнению, предложение прерывается, если @name не уникален. Братская рекурсия собирает те field, которые имеют атрибут name, отличный от того, с которого начинается группа. Таким образом, может быть field элементов с одинаковыми именами. - person Martin Honnen; 28.05.2014
comment
Ты прав. Имя собаки не в @name, а в @value. Извините за путаницу. - person Marcus Rickert; 28.05.2014
comment
Маркус, ваше решение работает, мне удалось использовать его для реального теста, и оно заработало на официальном XML. Как и в случае с решением Майкла, первым элементом в списке должно быть (или будет изменено) «имя». Могу ли я каким-то образом предоставить это как переменную, чтобы я мог использовать ее для всех таблиц в XML (их будет больше)? - person Ronald; 28.05.2014
comment
@ Рональд, поскольку в коде просто <xsl:template match="table">, он должен работать с любым количеством таблиц. Обычно вы просто добавляете шаблон, чтобы убедиться, что корневой элемент скопирован, например. <xsl:template match="/*"><xsl:copy><xsl:apply-templates/></xsl:copy></xsl:template>. Вместо того, чтобы искать name первого field, вы, конечно, также можете указать имя в параметре таблицы стилей <xsl:param name="head-name" select="'name'"/>, а затем использовать, например. <xsl:variable name="fields" select="fields/field[@name = $head-name]"/> и удалите параметр шаблона, который я использовал. - person Martin Honnen; 28.05.2014