xsl для каждой группы, группировка по выбранным дочерним элементам и атрибутам

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

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

Учитывая этот XML:

<?xml version="1.0" encoding="UTF-8"?>
<classes>
    <class>
        <name>param1/stateSettings</name>
        <list>
            <options>
                <default>0</default>
                <option key="0" value="Disabled"/>
                <option key="1" value="Enabled"/>
            </options>
        </list>
    </class>
    <class>
        <name>param2/stateSettings</name>
        <list>
            <options>
                <default>0</default>
                <option key="1" value="Enabled"/>
                <option key="0" value="Disabled"/>
            </options>
        </list>
    </class>
    <class>
        <name>param3/stateSettings</name>
        <list>
            <options>
                <default>1</default>
                <option key="1" value="Enabled"/>
                <option key="0" value="Disabled"/>
            </options>
        </list>
    </class>
    <class>
        <name>param4/stateSettings</name>
        <list>
            <options>
                <default>0</default>
                <option key="1" value="Disabled"/>
                <option key="0" value="Enabled"/>
            </options>
        </list>
    </class>
</classes>

Я бы (в конечном итоге) получил этот вывод:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <xs:simpleType name="stateSettingsType">
      <xs:annotation>
         <xs:documentation>
            <replaces>param1/stateSettings</replaces>
            <replaces>param2/stateSettings</replaces>
            <replaces>param3/stateSettings</replaces>
            <grouping-key value="0:1:Disabled:Enabled"/>
         </xs:documentation>
      </xs:annotation>
      <xs:restriction base="xs:string">
         <xs:enumeration value="1">
            <xs:annotation>
               <xs:appinfo>
                  <dataBinding enum="Enabled"/>
               </xs:appinfo>
            </xs:annotation>
         </xs:enumeration>
         <xs:enumeration value="0">
            <xs:annotation>
               <xs:appinfo>
                  <dataBinding enum="Disabled"/>
               </xs:appinfo>
            </xs:annotation>
         </xs:enumeration>
      </xs:restriction>
   </xs:simpleType>
   <xs:simpleType name="stateSettings2Type">
      <xs:annotation>
         <xs:documentation>
            <replaces>param4/stateSettings</replaces>
            <grouping-key value="1:0:Disabled:Enabled"/>
         </xs:documentation>
      </xs:annotation>
      <xs:restriction base="xs:string">
         <xs:enumeration value="1">
            <xs:annotation>
               <xs:appinfo>
                  <dataBinding enum="Disabled"/>
               </xs:appinfo>
            </xs:annotation>
         </xs:enumeration>
         <xs:enumeration value="0">
            <xs:annotation>
               <xs:appinfo>
                  <dataBinding enum="Enabled"/>
               </xs:appinfo>
            </xs:annotation>
         </xs:enumeration>
      </xs:restriction>
   </xs:simpleType>
</xs:schema>

(ключ группировки здесь только для справки, содержание не имеет значения)

Мой XSL до сих пор:

<?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" version="2.0">

    <xsl:output omit-xml-declaration="no" indent="yes"/>
    <xsl:output method="xml"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="options">
        <xsl:apply-templates select="option"/>
    </xsl:template>

    <xsl:template match="option">
        <xs:enumeration value="{@key}">
            <xs:annotation>
                <xs:appinfo>
                    <xsl:element name="dataBinding">
                        <xsl:attribute name="enum" select="@value"/>
                    </xsl:element>
                </xs:appinfo>
            </xs:annotation>
        </xs:enumeration>
    </xsl:template>

    <xsl:template match="name">
        <xsl:element name="replaces">
            <xsl:value-of select="../name"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="classes">
        <xsl:for-each-group select="class" group-by="substring-after(name, '/')">
            <xsl:variable name="typeName" select="current-grouping-key()"/>
            <xsl:for-each-group select="current-group()/list" group-by="string-join((options/option/@key, options/option/@value), ':')">
                <xsl:variable name="newTypeName">
                <xsl:choose>
                    <xsl:when test="position() gt 1">
                        <xsl:value-of select="string-join(($typeName,format-number(position(),'#'),'Type'),'')"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="string-join(($typeName,'Type'),'')"/>
                    </xsl:otherwise>
                </xsl:choose>
                </xsl:variable>
                <xs:simpleType name="{$newTypeName}">
                    <xs:annotation>
                        <xs:documentation>
                            <xsl:for-each select="current-group()">
                                <xsl:apply-templates select="../name"/>
                            </xsl:for-each>
                            <xsl:element name="grouping-key">
                                <xsl:attribute name="value" select="current-grouping-key()"/>
                            </xsl:element>
                        </xs:documentation>
                    </xs:annotation>
                    <xs:restriction base="xs:string">
                        <xsl:apply-templates select="options"/>
                    </xs:restriction>
                </xs:simpleType>
            </xsl:for-each-group>
        </xsl:for-each-group>
    </xsl:template>

    <xsl:template match="/">
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
            <xsl:apply-templates select="classes"/>
        </xs:schema>
    </xsl:template>

</xsl:stylesheet>

Мой текущий вывод XSL (XSD):

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <xs:simpleType name="stateSettingsType">
      <xs:annotation>
         <xs:documentation>
            <replaces>param1/stateSettings</replaces>
            <grouping-key value="0:1:Disabled:Enabled"/>
         </xs:documentation>
      </xs:annotation>
      <xs:restriction base="xs:string">
         <xs:enumeration value="0">
            <xs:annotation>
               <xs:appinfo>
                  <dataBinding enum="Disabled"/>
               </xs:appinfo>
            </xs:annotation>
         </xs:enumeration>
         <xs:enumeration value="1">
            <xs:annotation>
               <xs:appinfo>
                  <dataBinding enum="Enabled"/>
               </xs:appinfo>
            </xs:annotation>
         </xs:enumeration>
      </xs:restriction>
   </xs:simpleType>
   <xs:simpleType name="stateSettings2Type">
      <xs:annotation>
         <xs:documentation>
            <replaces>param2/stateSettings</replaces>
            <replaces>param3/stateSettings</replaces>
            <grouping-key value="1:0:Enabled:Disabled"/>
         </xs:documentation>
      </xs:annotation>
      <xs:restriction base="xs:string">
         <xs:enumeration value="1">
            <xs:annotation>
               <xs:appinfo>
                  <dataBinding enum="Enabled"/>
               </xs:appinfo>
            </xs:annotation>
         </xs:enumeration>
         <xs:enumeration value="0">
            <xs:annotation>
               <xs:appinfo>
                  <dataBinding enum="Disabled"/>
               </xs:appinfo>
            </xs:annotation>
         </xs:enumeration>
      </xs:restriction>
   </xs:simpleType>
   <xs:simpleType name="stateSettings3Type">
      <xs:annotation>
         <xs:documentation>
            <replaces>param4/stateSettings</replaces>
            <grouping-key value="1:0:Disabled:Enabled"/>
         </xs:documentation>
      </xs:annotation>
      <xs:restriction base="xs:string">
         <xs:enumeration value="1">
            <xs:annotation>
               <xs:appinfo>
                  <dataBinding enum="Disabled"/>
               </xs:appinfo>
            </xs:annotation>
         </xs:enumeration>
         <xs:enumeration value="0">
            <xs:annotation>
               <xs:appinfo>
                  <dataBinding enum="Enabled"/>
               </xs:appinfo>
            </xs:annotation>
         </xs:enumeration>
      </xs:restriction>
   </xs:simpleType>
</xs:schema>

Так в чем моя проблема? Ну, на самом деле есть две вещи, которые нужно исправить, чтобы получить «окончательный» результат, но мне достаточно решить только одну.

Проблема 1 связана с группировкой, выполненной в группе для каждой группы. Мне нужно сгруппировать на основе некоторых содержимого параметров. Моя текущая группировка на основе строк чувствительна к порядку элементов option. Элементы param1, param2 и param3 одинаковы и должны быть реорганизованы до одного простого типа в XSD. param4 имеет перевернутый ключ/значение и должен быть создан как новый simpleType (и да, старая система фактически использует этот тип "схемы"... Совсем не сбивает с толку :-D) Я могу не основывайте группировку на всех параметрах, так как там есть другие вещи, которые не имеют отношения к этой области и испортят группировку.

Проблема 2 больше косметическая. Было бы хорошо, если бы первый simpleType заменял наиболее распространенные типы, т.е. если у меня есть 58 одинаковых class и 2 несколько отличающихся, я бы хотел, чтобы наиболее распространенный simpleType имел имя без последовательности количество.

(Обратите внимание, что элементы default не имеют отношения к созданию типов в XSD, но я вернусь к ним на более позднем этапе в моем XSL при создании элементов xsd, которые могут иметь значения по умолчанию)

Я надеюсь, что кто-то достаточно любезен, чтобы сказать мне, что мне не хватает. Пожалуйста, дайте мне знать о любых других проблемах, которые могут возникнуть в моем коде :-)


person dahook    schedule 08.01.2016    source источник


Ответы (1)


Насколько я понимаю, вы сначала хотите отсортировать элементы option, поэтому ниже приведен ваш код плюс функция для сортировки и вставленный вызов функции для вычисления ключа группировки с параметрами сортировки:

<?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" version="2.0"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="mf">

    <xsl:output omit-xml-declaration="no" indent="yes"/>
    <xsl:output method="xml"/>
    <xsl:strip-space elements="*"/>

    <xsl:function name="mf:sort" as="element(option)*">
        <xsl:param name="input" as="element(option)*"/>
        <xsl:perform-sort select="$input">
            <xsl:sort select="xs:integer(@key)"/>
        </xsl:perform-sort>
    </xsl:function>

    <xsl:template match="options">
        <xsl:apply-templates select="option"/>
    </xsl:template>

    <xsl:template match="option">
        <xs:enumeration value="{@key}">
            <xs:annotation>
                <xs:appinfo>
                    <xsl:element name="dataBinding">
                        <xsl:attribute name="enum" select="@value"/>
                    </xsl:element>
                </xs:appinfo>
            </xs:annotation>
        </xs:enumeration>
    </xsl:template>

    <xsl:template match="name">
        <xsl:element name="replaces">
            <xsl:value-of select="../name"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="classes">
        <xsl:for-each-group select="class" group-by="substring-after(name, '/')">
            <xsl:variable name="typeName" select="current-grouping-key()"/>
            <xsl:for-each-group select="current-group()/list" group-by="string-join(for $opt in mf:sort(options/option) return ($opt/@key, $opt/@value), ':')">
                <xsl:variable name="newTypeName">
                <xsl:choose>
                    <xsl:when test="position() gt 1">
                        <xsl:value-of select="string-join(($typeName,format-number(position(),'#'),'Type'),'')"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="string-join(($typeName,'Type'),'')"/>
                    </xsl:otherwise>
                </xsl:choose>
                </xsl:variable>
                <xs:simpleType name="{$newTypeName}">
                    <xs:annotation>
                        <xs:documentation>
                            <xsl:for-each select="current-group()">
                                <xsl:apply-templates select="../name"/>
                            </xsl:for-each>
                            <xsl:element name="grouping-key">
                                <xsl:attribute name="value" select="current-grouping-key()"/>
                            </xsl:element>
                        </xs:documentation>
                    </xs:annotation>
                    <xs:restriction base="xs:string">
                        <xsl:apply-templates select="options"/>
                    </xs:restriction>
                </xs:simpleType>
            </xsl:for-each-group>
        </xsl:for-each-group>
    </xsl:template>

    <xsl:template match="/">
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
            <xsl:apply-templates select="classes"/>
        </xs:schema>
    </xsl:template>

</xsl:stylesheet>

В XSLT 3.0 с XPath 3.1 (который уже поддерживается Saxon 9.7 PE и EE) вы можете использовать функцию XPath sort и оператор ! для получения более компактного выражения:

<xsl:for-each-group select="current-group()/list" group-by="string-join(sort(options/option, function($opt) { $opt/xs:integer(@key) }) ! (@key, @value), ':')">
person Martin Honnen    schedule 08.01.2016
comment
Спасибо, это отлично работает! Я думал о некоторой сортировке, но я не мог узнать, как это сделать. Не все так интуитивно понятно, этот язык :-) - person dahook; 09.01.2016
comment
@dahook, XPath 3.1, уже поддерживаемый в XSLT 3.0 и Saxon 9.7 PE и EE, имеет функцию sort для сортировки последовательностей, поэтому я добавил строку, показывающую ее использование. Но людям нужно привыкнуть к функциям высшего порядка и встроенным функциям, если у них нет опыта функционального программирования. С другой стороны, процедурные и объектно-ориентированные языки программирования, такие как C# или Java, добавили аналогичные конструкции, в Javascript они были уже много лет, так что, возможно, он уже интуитивно понятен. - person Martin Honnen; 09.01.2016