Search code examples
xmlsortingxsltmarkup

Alphabetize two elements in the same list with XSLT


I am very new to XSLT so please bear with me if my coding is sloppy or unnecessarily complicated. I am trying to create a list of songs that among other things needs to be alphabetized by title. For some of some of the songs I have listed titles in multiple languages. Is it possible to always sort alphabetically by Japanese, for example, but if there isn't one, then alphabetize by the english title. Here's an example:

<music-catalogue>
    <song>
        <title>
            <romaji>Agechikuten</romaji>
            <japanese>&#25562;&#20316;&#30000;</japanese>
        </title>
    </song>
    <song>
        <title>
            <romaji>Kamigami no Uta</romaji>
            <japanese>&#31070;&#12293;&#12398;&#35433;</japanese>
            <english>Song of the Gods</english>
        </title>
        <artist>
            <e-name>Himekami</e-name>
            <j-name>&#23019;&#31070;</j-name>
            <link>&himekami;</link>
        </artist>
    </song>
    <song>
        <title>
            <english>Freedom</english>
        </title>
        <artist>
            <e-name>12 Girls Band</e-name>
            <j-name>&#22899;&#23376;&#21313;&#20108;&#20048;&#22346;</j-name>
        </artist>
    </song>
    <song>
        <title>
            <romaji>Tinsagu nu Hana</romaji>
            <japanese>&#12486;&#12451;&#12531;&#12469;&#12464;&#12396;&#33457;</japanese>
        </title>
    </song>
</music-catalogue>

Currently, the songs are displayed by the content of the romaji element which would show:

  • Freedom
  • Agechikuten
  • Kamigami no Uta
  • Tinsagu nu Hana

Freedom is on top because there is not a romaji tag to be sorted. Is it possible to sort the songs by the content of the romaji element but if it doesn't exist, use the content of the english element to display this list:

  • Agechikuten
  • Freedom
  • Kamigami no Uta
  • Tinsagu nu Hana

Please let me know if I should include any more information or parts of my coding. Thank you!


Solution

  • This is similar to the solution proposed by @Tim C, but avoids the need to use string concatenation:

    <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="song/title">
           <xsl:sort select="romaji | self::*[not(romaji)]/english"/>
         </xsl:apply-templates>
     </xsl:template>
    
     <xsl:template match="title">
      <xsl:text>&#xA;</xsl:text>
      <xsl:value-of select=
       "romaji | self::*[not(romaji)]/english"/>
     </xsl:template>
    </xsl:stylesheet>
    

    When applied to the provided XML document (slightly modified to make it well-formed -- edited an undefined entity reference):

    <music-catalogue>
        <song>
            <title>
                <romaji>Agechikuten</romaji>
                <japanese>&#25562;&#20316;&#30000;</japanese>
            </title>
        </song>
        <song>
            <title>
                <romaji>Kamigami no Uta</romaji>
                <japanese>&#31070;&#12293;&#12398;&#35433;</japanese>
                <english>Song of the Gods</english>
            </title>
            <artist>
                <e-name>Himekami</e-name>
                <j-name>&#23019;&#31070;</j-name>
                <link>&amp;himekami;</link>
            </artist>
        </song>
        <song>
            <title>
                <english>Freedom</english>
            </title>
            <artist>
                <e-name>12 Girls Band</e-name>
                <j-name>&#22899;&#23376;&#21313;&#20108;&#20048;&#22346;</j-name>
            </artist>
        </song>
        <song>
            <title>
                <romaji>Tinsagu nu Hana</romaji>
                <japanese>&#12486;&#12451;&#12531;&#12469;&#12464;&#12396;&#33457;</japanese>
            </title>
        </song>
    </music-catalogue>
    

    the wanted, correct result is produced:

    Agechikuten
    Freedom
    Kamigami no Uta
    Tinsagu nu Hana
    

    Explanation:

    In this solution the wanted sort key is expressed precisely using set arithmetic:

     romaji | self::*[not(romaji)]/english
    

    where | is the XPath union operator.

    Do note, that this solution works correctly even if english comes before (in document order) romaji.