Can you define a custom collation using a function

2019-07-17 06:09发布

问题:

I'd like to define an ordering on strings used in a certain element. For example, <class>Senior</class> <class>Junior</class> <class>Sophomore</class> <class>Freshman<class> would describe a reasonable ordering on class.

Is there a way using <xsl:sort select='class'> that would sort by the ordering given above?

Thanks in advance.

回答1:

Have you looked into Saxon's custom collation extensions?

For example,

<xsl:variable name="rules" select="'&lt; Freshman &lt; Sophomore
                                    &lt; Junior   &lt; Senior'" />

This uses the RuleBasedCollator format from the Java class of that name.

To use it in your sort (piggybacking on Tim C's useful example input XML and stylesheet):

 <xsl:apply-templates select="object">
    <xsl:sort select="@class"
       collation="http://saxon.sf.net/collation?rules={encode-for-uri($rules)}"/>
 </xsl:apply-templates>

This gives the same results as Tim C's solution. (Tested using Saxon PE 9.3.0.5.)

It's not an xsl:function, but it gives you a little more flexibility than an array, and is arguably more succinct. AFAICT there is no way to create a custom collation using an XSLT user-defined function. Since you don't say why you want an xsl:function, it's hard to speculate on what alternatives will meet your needs.

For complete xsl:function-like flexibility, you could define your own collator in Java; see http://www.saxonica.com/documentation/extensibility/collation.xml on implementing the java.util.Comparator interface and specifying your comparator in the class attribute.



回答2:

What you could do in your XSLT is define a variable to represent your custom ordering, like so

<xsl:variable name="inline-array">
   <class sort="1">Senior</class>
   <class sort="2">Junior</class>
   <class sort="3">Sophomore</class>
   <class sort="4">Freshman</class>
</xsl:variable>

Then to access this 'array' you can define another variable to reference the XSLT document itself:

<xsl:variable name="array" 
   select="document('')/*/xsl:variable[@name='inline-array']/*" />

This now allows you to look up the sort attribute for a given class name when you are sorting (where current() represents the current node being sorted)

<xsl:sort select="$array[. = current()/@class]/@sort" />

As an example, here is the full XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="xml" indent="yes"/>

   <xsl:variable name="inline-array">
      <class sort="1">Senior</class>
      <class sort="2">Junior</class>
      <class sort="3">Sophomore</class>
      <class sort="4">Freshman</class>
   </xsl:variable>

   <xsl:variable name="array" 
      select="document('')/*/xsl:variable[@name='inline-array']/*"/>

   <xsl:template match="/objects">
      <xsl:copy>
         <xsl:apply-templates select="object">
            <xsl:sort select="$array[. = current()/@class]/@sort" />
         </xsl:apply-templates>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="@*|node()">
      <xsl:copy>
         <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
   </xsl:template>
</xsl:stylesheet>

When you apply this to the following sample XML...

<objects>
   <object id="2" name="Junior Jo" class="Junior" />
   <object id="1" name="Senior Sue" class="Senior" />
   <object id="4" name="Freshman Frank" class="Freshman" />
   <object id="3" name="Sophie Sophomore" class="Sophomore" />
</objects>

The following is returned

<objects>
   <object id="1" name="Senior Sue" class="Senior"></object>
   <object id="2" name="Junior Jo" class="Junior"></object>
   <object id="3" name="Sophie Sophomore" class="Sophomore"></object>
   <object id="4" name="Freshman Frank" class="Freshman"></object>
</objects>


标签: xslt xslt-2.0