Сортировка XPath приводит к тому же порядку, что и несколько параметров выбора.

У меня есть XML-документ следующим образом:

<objects>
  <object uid="0" />
  <object uid="1" />
  <object uid="2" />
</objects>

Я могу выбрать несколько элементов, используя следующий запрос:

doc.xpath("//object[@uid=2 or @uid=0 or @uid=1]")

Но это возвращает элементы в том же порядке, в котором они объявлены в XML-документе (uid=0, uid=1, uid=2), и мне нужны результаты в том же порядке, в котором я выполняю запрос XPath (uid=2, uid=0, uid=1).

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

Я работаю на Ruby с библиотекой Nokogiri.


person Cameron Yule    schedule 09.12.2010    source источник
comment
Хороший вопрос, +1. Смотрите мой ответ для объяснения и двух разных решений: решение XPath 2.0 и XSLT 1.0.   -  person Dimitre Novatchev    schedule 09.12.2010


Ответы (5)


Пример XSLT:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:param name="pSequence" select="'2 1'"/>
    <xsl:template match="objects">
        <xsl:for-each select="object[contains(concat(' ',$pSequence,' '),
                                              concat(' ',@uid,' '))]">
            <xsl:sort select="substring-before(concat(' ',$pSequence,' '),
                                               concat(' ',@uid,' '))"/>
            <xsl:copy-of select="."/>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

Выход:

<object uid="2" /><object uid="1" />
person Community    schedule 09.12.2010

В XPath 1.0 нет способа указать порядок выбранных узлов.

XPath 2.0 допускает последовательность узлов. с любым конкретным заказом:

//object[@uid=2], //object[@uid=1]

оценивается как последовательность, в которой все object элементы с @uid=2 предшествуют всем object элементам с @uid=1

Если у вас нет механизма XPath 2.0, все же можно использовать XSLT для вывода узлов в любом желаемом порядке.

В этом конкретном случае последовательность следующих инструкций XSLT:

<xsl:copy-of select="//object[@uid=2]"/>

<xsl:copy-of select="//object[@uid=1]"/>

выдает желаемый результат:

<object uid="2" /><object uid="1" />
person Dimitre Novatchev    schedule 09.12.2010
comment
Спасибо, Дмитрий, я отметил это как полезное. Я решил выбрать ответ Алехандро как принятый, так как это именно то, что мне нужно, включая возможность передачи параметра, содержащего идентификатор, который я ищу. - person Cameron Yule; 13.12.2010
comment
@Cameron: В своем вопросе вы не упомянули, что хотите параметризовать. Конечно, если нужна параметризация, то лучше всего использовать сортировку. Вероятно, месяц назад я ответил на аналогичный вопрос с решением, которое использовало параметры и сортировку - это совсем не что-то новое. :) - person Dimitre Novatchev; 13.12.2010

Я предполагаю, что вы используете XPath 1.0. Спецификация W3C гласит: «Основной синтаксической конструкцией в XPath является выражение. Выражение соответствует производственному выражению. Выражение оценивается для получения объекта, который имеет один из следующих четырех основных типов:

* node-set (an unordered collection of nodes without duplicates)
* boolean (true or false)
* number (a floating-point number)
* string (a sequence of UCS characters)

Поэтому я не думаю, что вы можете изменить порядок, просто используя XPath. (Остальная часть спецификации определяет порядок документов и обратный порядок документов, поэтому, если последний делает то, что вы хотите, вы можете получить его, используя соответствующую ось (например, предыдущую).

В XSLT вы можете использовать <xsl:sort>, используя name() атрибута. Часто задаваемые вопросы о XSLT очень хороши, и вы должны найти там ответ.

person peter.murray.rust    schedule 09.12.2010

Я не думаю, что есть способ сделать это в xpath, но если вы хотите переключиться на XSLT, вы можете использовать тег xsl:sort:

<xsl:for-each select="//object[@uid=1 or @uid=2]">
  <xsl:sort: select="@uid" data-type="number" />
  {insert new logic here}
</xsl:for-each>

более полную информацию можно найти здесь: http://www.w3schools.com/xsl/el_sort.asp

person numone    schedule 09.12.2010

Вот как я бы сделал это в Нокогири:

require 'nokogiri'

xml = '<objects><object uid="0" /><object uid="1" /><object uid="2" /></objects>'

doc = Nokogiri::XML(xml)
objects_by_uid = doc.search('//object[@uid="2" or @uid="1"]').sort_by { |n| n['uid'].to_i }.reverse
puts objects_by_uid

Выполнение этого вывода:

<object uid="2"/>
<object uid="1"/>

Альтернативой поиску может быть:

objects_by_uid = doc.search('//object[@uid="2" or @uid="1"]').sort { |a,b| b['uid'].to_i <=> a['uid'].to_i }

если вам не нравится использовать sort_by с reverse.

XPath полезен для поиска и извлечения узлов, но часто фильтрация, которую мы хотим выполнить, становится слишком запутанной в методе доступа, поэтому я позволяю этому языку, будь то Ruby, Perl или Python. Где я размещаю логику фильтрации, зависит от того, насколько велик набор данных XML и есть ли много разных значений uid, которые я хочу получить. Иногда имеет смысл позволить движку XPath выполнять тяжелую работу, в других случаях проще позволить XPath захватить все object узлов и отфильтровать их на вызывающем языке.

person the Tin Man    schedule 09.12.2010
comment
Спасибо за ответ Грег. К сожалению, мне не нужны результаты, отсортированные по UID, мне нужно указать точный порядок, в котором они возвращаются. Мой пример использования 1,2 и 2,1 в вопросе мог сбивать с толку - с тех пор я обновил его для ясности. - person Cameron Yule; 13.12.2010
comment
@ Кэмерон Юл, да, это сбивало с толку. - person the Tin Man; 13.12.2010