开发者

Sorting XPath results in the same order as multiple select parameters

开发者 https://www.devze.com 2023-01-30 07:11 出处:网络
I have an XML document as follows: <objects> <object uid=\"0\" /> <object uid=\"1\" />

I have an XML document as follows:

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

I can select multiple elements u开发者_JAVA百科sing the following query:

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

But this returns the elements in the same order they're declared in the XML document (uid=0, uid=1, uid=2) and I want the results in the same order as I perform the XPath query (uid=2, uid=0, uid=1).

I'm unsure if this is possible with XPath alone, and have looked into XSLT sorting, but I haven't found an example that explains how I could achieve this.

I'm working in Ruby with the Nokogiri library.


There is no way in XPath 1.0 to specify the order of the selected nodes.

XPath 2.0 allows a sequence of nodes with any specific order:

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

evaluates to a sequence in which all object items with @uid=2 precede all object items with @uid=1

If one doesn't have anXPath 2.0 engine available, it is still possible to use XSLT in order to output nodes in any desired order.

In this specific case the sequence of the following XSLT instructions:

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

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

produces the desired output:

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


I am assuming you are using XPath 1.0. The W3C spec says: The primary syntactic construct in XPath is the expression. An expression matches the production Expr. An expression is evaluated to yield an object, which has one of the following four basic types:

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

So I don't think you can re-order simply using XPath. (The rest of the spec defines document order and reverse document order, so if the latter does what you want you can get it using the appropriate axis (e.g. preceding).

In XSLT you can use <xsl:sort> using the name() of the attribute. The XSLT FAQ is very good and you should find an answer there.


An XSLT example:

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

Output:

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


I don't think there is a way to do it in xpath but if you wish to switch to XSLT you can use the xsl:sort tag:

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

more complete info here: http://www.w3schools.com/xsl/el_sort.asp


This is how I'd do it in Nokogiri:

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

Running that outputs:

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

An alternative to the search would be:

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

if you don't like using sort_by with the reverse.

XPath is useful for locating and retrieving the nodes but often the filtering we want to do gets too convoluted in the accessor so I let the language do it, whether it's Ruby, Perl or Python. Where I put the filtering logic is based on how big the XML data set is and whether there are a lot of different uid values I'll want to grab. Sometimes letting the XPath engine do the heavy lifting makes sense, other times its easier to let XPath grab all the object nodes and filter in the calling language.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号