XSLT для сортировки по значению нескольких атрибутов

у меня есть огромный файл конфигурации в формате XML. Система не заботится о порядке тегов, но мы, люди, делаем! (Главным образом для целей сопоставления версий.) я уже получил XSLT ниже которого работает хорошо, но я обнаружил, что этого недостаточно.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates>
      <xsl:sort select="(@name, name())[1]"/>
    </xsl:apply-templates>
  </xsl:copy>
</xsl:template>
</xsl:stylesheet>

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

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

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates>
      <xsl:sort select="@name"/>
      <xsl:sort select="@row"      data-type="number"/>
      <xsl:sort select="@col"      data-type="number"/>
      <xsl:sort select="@sequence" data-type="number"/>
      <xsl:sort select="@tabindex" data-type="number"/>
    </xsl:apply-templates>
  </xsl:copy>
</xsl:template>
</xsl:stylesheet>

мои данные выглядят аналогично этому, и проблема в том, что cell элементы не сортируются вообще (в пределах их grid группы), потому что у них нет . Этот вот почему я хотел бы расширить логику сортировки для использования name атрибут при наличии, иначе сортировка должна выполняться с использованием дополнительных атрибутов, таких как tabindex. Можно предположить, что в любой данной группе присутствуют одни и те же атрибуты.

<sections>
  <section name="SomeList">
    <caption>
      <![CDATA[Candidates]]>
    </caption>
    ...
    <parameters>
      <parameter name="pageSize">
        <![CDATA[50]]>
      </parameter>
    </parameters>
    ... 
    <grid>
      <cell row="0" col="7" tabindex="9" colspan="10">
        <field name="Entered" />
      </cell>
    </grid>
  </section>
</sections>

обновление:
С очень хорошей помощью Винсента я создал сортировку, которая работает достаточно хорошо для наших целей. здесь.

3 ответов


это ответ, который предполагает, что у вас нет смешанного содержимого в ваших данных. Он учитывает только два первых шага (@name и @col), вы можете адаптироваться к дальнейшим шагам. Возможно, его можно переписать с помощью рекурсивного именованного шаблона, который принимает список параметров сортировки в качестве входных данных. Не могли бы вы предоставить образец XML, если мой XSLT не работает для вас.

образец XSLT 2.0:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
        <xsl:template match="*">
            <xsl:copy>
                <xsl:copy-of select="@*"/>
                <xsl:for-each-group select="*" group-by="if (exists(@name)) then @name else ''">
                    <xsl:sort select="current-grouping-key()" data-type="text"/>
                    <xsl:for-each-group select="current-group()" group-by="if (exists(@row)) then @row else -1">
                        <xsl:sort select="current-grouping-key()" data-type="number"/>
                        <xsl:apply-templates select="current-group()"/>
                    </xsl:for-each-group>
                </xsl:for-each-group>
            </xsl:copy>
        </xsl:template>
</xsl:stylesheet>

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

Я беру в качестве входных данных следующий XML:

<?xml version="1.0" encoding="UTF-8"?>
<items>
    <item row="5" col="9"></item>
    <item name="d" row="20" col="12" tabindex="" sequence=""></item>
    <item row="1" col="5" ></item>
    <item name="d" row="5" col="6" ></item>
    <item name="a" row="7" col="8" ></item>
    <item name="s" row="1" col="5" ></item>
    <item name="c" row="5" col="9"></item>
    <item row="2" col="5" ></item>
    <item row="20" col="9"></item>
    <item row="0" col="9"></item>
    <item name="s" row="2" col="10" tabindex="" sequence=""></item>
    <item name="z" row="8" col="15" tabindex="" sequence=""></item>    
</items>

у меня следующие результаты :

<?xml version="1.0" encoding="UTF-8"?>
<items>
   <item row="0" col="9"/>
   <item row="1" col="5"/>
   <item row="2" col="5"/>
   <item row="5" col="9"/>
   <item row="20" col="9"/>
   <item name="a" row="7" col="8"/>
   <item name="c" row="5" col="9"/>
           <item name="d" row="5" col="6"/>
   <item name="d" row="20" col="12" tabindex="" sequence=""/>
   <item name="s" row="1" col="5"/>
   <item name="s" row="2" col="10" tabindex="" sequence=""/>
   <item name="z" row="8" col="15" tabindex="" sequence=""/>
</items>

рассмотрим этот XSLT для soecific элементов с заданными обязательными атрибутами:

   <?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output indent="yes"/>
    <xsl:template match="*">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates>
                <xsl:sort select="(@name, name())[1]"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="grid">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:for-each-group select="*" group-by="if (exists(@row)) then @row else -1">
                <xsl:sort select="current-grouping-key()" data-type="number"/>
                <xsl:for-each-group select="current-group()" group-by="if (exists(@col)) then @col else -1">
                    <xsl:sort select="current-grouping-key()" data-type="number"/>
                    <xsl:apply-templates select="current-group()"/>
                </xsl:for-each-group>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

мой пример должен охватывать сортировку разделов и параметров с первым совпадением шаблонов *. А также сортировка сетки по строкам и col. Вы можете расширить для любых элементов orther, которые имеют различные атрибуты сортировки, дублируя шаблон.

Если у вас есть несколько элементов для одних и тех же атрибутов, используйте match="elt1|elt2|elt3".


вот общее, простое и не длинное (60 хорошо отформатированных строк) решение.

сортировка выполняется на все wanted атрибуты, и это не требует ручного дублирования шаблонов:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:my="my:my" exclude-result-prefixes="my">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:param name="pSortTypes" as="element()*">
      <attr name="name" type="alpha" maxLength="15"/>
      <attr name="row" type="numeric" maxLength="6"/>
      <attr name="col" type="numeric" maxLength="4"/>
      <attr name="tabindex" type="numeric" maxLength="2"/>
      <attr name="sequence" type="numeric" maxLength="3"/>
    </xsl:param>

 <xsl:template match="*">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates select="*">
     <xsl:sort select="my:OrderedAttributeTuple(.)"/>
    </xsl:apply-templates>
  </xsl:copy>
 </xsl:template>

 <xsl:function name="my:OrderedAttributeTuple" as="xs:string">
  <xsl:param name="pElem" as="element()"/>

  <xsl:variable name="vResult" as="xs:string*">
      <xsl:apply-templates select="$pSortTypes">
       <xsl:with-param name="pElem" select="$pElem"/>
      </xsl:apply-templates>
  </xsl:variable>

  <xsl:sequence select="string-join($vResult, '')"/>
 </xsl:function>

 <xsl:template match="attr">
  <xsl:param name="pElem" as="element()"/>

  <xsl:variable name="vVal" select=
       "string($pElem/@*[name() eq current()/@name])"/>

  <xsl:variable name="vPad" as="xs:string*" select=
   "for $cnt in xs:integer(@maxLength) - string-length($vVal),
        $i in 1 to $cnt
     return '.'
   "/>

   <xsl:variable name="vPadding" select="string-join($vPad, '')"/>

   <xsl:variable name="vTuple">
       <xsl:sequence select=
        "if(@type eq 'alpha')
           then concat($vVal, $vPadding)
           else concat($vPadding, $vVal)
        "/>
    </xsl:variable>

   <xsl:sequence select="string($vTuple)"/>
 </xsl:template>
</xsl:stylesheet>

когда это преобразование применяется к этому XML-документу:

<items>
    <item row="5" col="9"/>
    <item name="d" row="20" col="12" tabindex="" sequence=""/>
    <item row="1" col="5" />
    <item name="d" row="5" col="6" />
    <item name="a" row="7" col="8" />
    <item name="s" row="1" col="5" tabindex="3" sequence="4"/>
    <item name="s" row="3" col="3" tabindex="3" sequence="4"/>
    <item name="c" row="5" col="9"/>
    <item row="2" col="5" />
    <item row="20" col="9"/>
    <item row="0" col="9"/>
    <item name="s" row="3" col="3" tabindex="1" sequence="2"/>
    <item name="s" row="2" col="10" tabindex="1" sequence="2"/>
    <item name="z" row="8" col="15" tabindex="" sequence=""/>
</items>

желаемый, правильно отсортированный результат произведено:

<items>
   <item row="0" col="9"/>
   <item row="1" col="5"/>
   <item row="2" col="5"/>
   <item row="5" col="9"/>
   <item row="20" col="9"/>
   <item name="a" row="7" col="8"/>
   <item name="c" row="5" col="9"/>
   <item name="d" row="5" col="6"/>
   <item name="d" row="20" col="12" tabindex="" sequence=""/>
   <item name="s" row="1" col="5" tabindex="3" sequence="4"/>
   <item name="s" row="2" col="10" tabindex="1" sequence="2"/>
   <item name="s" row="3" col="3" tabindex="1" sequence="2"/>
   <item name="s" row="3" col="3" tabindex="3" sequence="4"/>
   <item name="z" row="8" col="15" tabindex="" sequence=""/>
</items>

обратите внимание:

  1. сортировка выполняется на все атрибуты, указанные во внешнем параметре ($pSortTypes). Сравните это с принятым в настоящее время ответом, который сортируется только на @name и @row и требует жесткого кодирования типа данных order и sort.

  2. точный разыскиваемый порядок сортировки атрибутов может быть указано. Это их порядок, как в $pSortTypes.

  3. указывается тип данных сортировки для каждого атрибута на на $pSortTypes (В настоящее время просто "alpha" и "numeric")

  4. максимальная длина строкового представления значения атрибута указан как на $pSortTypes. Это используется для правильного заполнения/выравнивания, а также увеличивает сортировки эффективность.

  5. это демонстрирует, как решить даже самые сложные проблемы сортировки, имея пользовательский xsl:function (в данном случае my:OrderedAttributeTuple()), который генерирует один ключ сортировки.