XSLT для сортировки по значению нескольких атрибутов - PullRequest
7 голосов
/ 25 ноября 2011

У меня огромный конфигурационный файл в формате 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>

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

У меня практически нет понимания 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. Вот почему я хотел бы расширить логику сортировки для использования атрибута 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 ]

4 голосов
/ 25 ноября 2011

Это ответ, который предполагает, что у вас нет смешанного контента в ваших данных.Здесь учитываются только два первых шага (@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>
1 голос
/ 25 ноября 2011

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

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

<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 и требует жесткого кодирования порядка и сортирует тип данных.

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

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

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

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

1 голос
/ 25 ноября 2011

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

   <?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>

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...