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>揚作田</japanese>
</title>
</song>
<song>
<title>
<romaji>Kamigami no Uta</romaji>
<japanese>神々の詩</japanese>
<english>Song of the Gods</english>
</title>
<artist>
<e-name>Himekami</e-name>
<j-name>姫神</j-name>
<link>&himekami;</link>
</artist>
</song>
<song>
<title>
<english>Freedom</english>
</title>
<artist>
<e-name>12 Girls Band</e-name>
<j-name>女子十二乐坊</j-name>
</artist>
</song>
<song>
<title>
<romaji>Tinsagu nu Hana</romaji>
<japanese>ティンサグぬ花</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
- F开发者_如何学JAVAreedom
- Kamigami no Uta
- Tinsagu nu Hana
Please let me know if I should include any more information or parts of my coding. Thank you!
I think this can be achieved by the xsl:sort element, which can be used with both xsl:for-each and xsl:apply-templates
<xsl:sort select="concat(title/romaji, title[not(romaji)]/english)" />
So, this means sort of the romaji title, but if if this no present, then the english title will be used.
Here is a simple XSLT to demonstrate
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/music-catalogue">
<ol>
<xsl:apply-templates select="song">
<xsl:sort select="concat(title/romaji, title[not(romaji)]/english)" />
</xsl:apply-templates>
</ol>
</xsl:template>
<xsl:template match="song">
<li>
<xsl:value-of select="concat(title/romaji, title[not(romaji)]/english)" />
</li>
</xsl:template>
</xsl:stylesheet>
When applied on your sample XML, the following results are output
<ol>
<li>Agechikuten</li>
<li>Freedom</li>
<li>Kamigami no Uta</li>
<li>Tinsagu nu Hana</li>
</ol>
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>
</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>揚作田</japanese>
</title>
</song>
<song>
<title>
<romaji>Kamigami no Uta</romaji>
<japanese>神々の詩</japanese>
<english>Song of the Gods</english>
</title>
<artist>
<e-name>Himekami</e-name>
<j-name>姫神</j-name>
<link>&himekami;</link>
</artist>
</song>
<song>
<title>
<english>Freedom</english>
</title>
<artist>
<e-name>12 Girls Band</e-name>
<j-name>女子十二乐坊</j-name>
</artist>
</song>
<song>
<title>
<romaji>Tinsagu nu Hana</romaji>
<japanese>ティンサグぬ花</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
.
The common way to achieve this kind of sort is by the union of the wanted nodes:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
<xsl:template match="music-catalogue">
<xsl:copy>
<xsl:apply-templates select="song">
<xsl:sort select="title/romaji | title[not(romaji)]/english"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
produces:
<music-catalogue>
<song>
<title>
<romaji>Agechikuten</romaji>
<japanese>揚作田</japanese>
</title>
</song>
<song>
<title>
<english>Freedom</english>
</title>
<artist>
<e-name>12 Girls Band</e-name>
<j-name>女子十二乐坊</j-name>
</artist>
</song>
<song>
<title>
<romaji>Kamigami no Uta</romaji>
<japanese>神々の詩</japanese>
<english>Song of the Gods</english>
</title>
<artist>
<e-name>Himekami</e-name>
<j-name>姫神</j-name>
<link/>
</artist>
</song>
<song>
<title>
<romaji>Tinsagu nu Hana</romaji>
<japanese>ティンサグぬ花</japanese>
</title>
</song>
</music-catalogue>
精彩评论