Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XPath 1.0 Order of returned attributes in a UNION

Tags:

xpath

<merge>
    <text>
        <div begin="A"   end="B" />
        <div begin="C"   end="D" />
        <div begin="E"   end="F" />
        <div begin="G"   end="H" />
    </text>
</merge>

I need a UNIONed set of attribute nodes, in the order A,B,C,D,E,F,G,H, and this will work:

/merge/text/div/@begin | /merge/text/div/@end

but only if each @begin comes before each @end, since the UNION operator is spec'd to return nodes in document order. (Yes?)

I need the nodeset to be in the same order, even if the attributes appear in a different order in the document, as here:

<merge>
    <text>
        <div end="B"   begin="A" />
        <div begin="C" end="D"   />
        <div end="F"   begin="E" />
        <div begin="G" end="H"   />
    </text>
</merge>

That is, I need elements to follow document order, but the attributes in each element to follow a determined order (either specified or alphabetical by attribute name).

like image 825
JPM Avatar asked Jan 06 '12 02:01

JPM


1 Answers

This simply isn't possible in pure XPath. First of all, attributes in XML are unordered. From the XML 1.0 Recommendation:

Note that the order of attribute specifications in a start-tag or empty-element tag is not significant.

An XPath engine might be reading and storing them in the order they appear in the document, but in terms of the spec, this is just a happy coincidence that cannot be relied upon.

Second, XPath has no sorting functionality. So, your best option is to sort the elements in your host language (e.g. XSLT or a general-purpose PL) after they've been selected.

Here's how to sort those attributes by value in XSLT:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:template match="/">
        <xsl:apply-templates
            select="/merge/text/div/@*[name()='begin' or name()='end']">
            <xsl:sort select="."/>
        </xsl:apply-templates>
    </xsl:template>
</xsl:stylesheet>

Note that I also merged your two expressions into one.

Edit: Use the following to output begin/end pairs in document order (as described in the comments):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:strip-space elements="*"/>
    <xsl:template match="div">
        <xsl:value-of select="concat(@begin, @end)"/>
    </xsl:template>
</xsl:stylesheet>
like image 88
Wayne Avatar answered Jan 03 '23 12:01

Wayne