Sortering, stringer og beregninger
Vi bruker et lite datasett som eksempel i denne modulen. Anta følgende resultatfil fra en konkurranse der deltagerne hver har fått notert to tider (timer,minutter,sekunder). Vi skal etterhvert interessere oss for samlet tid, rekkefølge osv.:
<?xml version="1.0" encoding="utf-8"?> <resultater> <deltager> <navn>Syversen</navn> <t1>1:31:12</t1> <t2>1:22:09</t2> </deltager> <deltager> <navn>Andersen</navn> <t1>1:30:12</t1> <t2>1:12:09</t2> </deltager> </resultater>
Sortering
La oss først se på de grunnleggende sorteringsmulighetene vi har i XSLT. Hvis vi ønsker å lage en HTML-fil som skriver ut deltagerlista alfabetisk, basert på navn, kan vi lage følgende transformasjon:
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" doctype-system="about:legacy-compat" encoding="UTF-8"/> <xsl:template match="/"> <html> <head><title>resultater</title></head> <body><h1>Resultater</h1> <xsl:apply-templates select="/resultater/deltager"> <xsl:sort select="navn"/> </xsl:apply-templates> </body></html> </xsl:template> <xsl:template match="deltager"> <h3><xsl:value-of select="navn"/></h3> <div> <xsl:value-of select="t1"/> og <xsl:value-of select="t1"/> </div> </xsl:template> </xsl:stylesheet>
Hva dersom vi introduserer en blank foran innholdet i navne-elementet til den ene deltageren, Syversen ? Hvis vi kjører den samme transformasjonen vil vi få Syversen foran Andersen. Det blanke tegnet inngår altså i sammenligningen av navne-elemener og forstyrrer ordningen. Vi kan korrigere for dette ved å endre sorteringselementet i transformasjonen slik at omgivende whitespace ignoreres:
<xsl:sort select="normalize-space(navn)"/>
Beregning
La oss se på beregning av samlede resultater. Vi ønsker å finne samlet tid for de to øvelsene for alle deltagerne. Vi har valgt et format for tidsangivelse som er ganske ryddig:
<t1>1:30:12</t1>
og oppgaven lar seg løse med å trekke ut delstringer, tolke dem numerisk og utføre nødvendige beregninger. XSLT gis oss verktøy for dette. En transformasjon som presenterer resultatene med angivelse av samlet tid i antall sekunder kan se slik ut:
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" doctype-system="about:legacy-compat" encoding="UTF-8"/> <xsl:template match="/"> <html> <head> <title>resultater</title> </head> <body><h1>Resultater</h1> <xsl:apply-templates select="/resultater/deltager"/> </body> </html> </xsl:template> <xsl:template match="deltager"> <h3><xsl:value-of select="navn"/></h3> <xsl:variable name="sekundsum" select="3600*(number(substring(t1,1,1))+number(substring(t2,1,1))) +60*(number(substring(t1,3,2))+number(substring(t2,3,2))) +(number(substring(t1,6,2))+number(substring(t2,6,2)))"/> <div><xsl:value-of select="$sekundsum"/></div> </xsl:template> </xsl:stylesheet>
Merk at tegnindekseringen begynner på 1, ikke 0 som vi er vant med i andre språk.
Koden som er foreslått er uten sikkerhetsnett. Hva hvis vi har feil i XML-formatet, eller at en deltager har brukt 10 timer? Det er ganske mye pes å skrive rutiner for datakontroll i XSLT. På den annen side har XSLT en nødutgang som ikke skaper katastrofe i form av programkrasj. Hvis vi introduserer feil format i en av tidene til Andersen, vil vi normalt få følgende resultat: trans3feil.html Vi får altså resultatet NaN (Not a Number). Hvis vi gjør en feil i transformasjonsfila og forsøker å regne på ikke-numeriske tegn eller tegn som vi prøver å hente fra en string med gal indeks får vi samme respons.
Dersom vi bare forsøker å framstille en substring som ikke finnes, f.eks. ved å skrive:
<div><xsl:value-of select="substring(t1,100,20)"/></div>
skjer det ingenting. Ingen feil og ingen string.
Vi ønsker å forbedre vår resultatframstilling noe og vil ha ut det samlede resultatet i samme form som delresultatene i XML-fila, dvs. t:mm.ss. Følgende transformasjon tar seg av dette:
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" doctype-system="about:legacy-compat" encoding="UTF-8"/> <xsl:template match="/"> <html> <head><title>resultater</title></head> <body><h1>Resultater</h1> <xsl:apply-templates select="/resultater/deltager"/> </body></html> </xsl:template> <xsl:template match="deltager"> <h3><xsl:value-of select="navn"/></h3> <xsl:variable name="sekundsum" select=" 3600*(substring(t1,1,1)+substring(t2,1,1)) +60*(substring(t1,3,2)+substring(t2,3,2)) +(substring(t1,6,2)+substring(t2,6,2))"/> <xsl:variable name="timer" select ="floor($sekundsum div 3600)"/> <xsl:variable name="minutter" select="floor(($sekundsum - $timer * 3600) div 60)"/> <xsl:variable name="sekunder" select="$sekundsum -($timer * 3600) -($minutter*60)"/> <div> <xsl:value-of select="concat($timer,':',$minutter,':',$sekunder)"/> </div> </xsl:template> </xsl:stylesheet>
Vi har brukt funksjonen floor() som runder ned til nærmeste hele tall og funksjonen concat() som slår sammen stringer.
Merk at vi har forenklet uttrykket ved at vi har fjernet kallene på number()- funksjonen. Dette kan vi gjøre siden XSL-er i stand til å tolke talltegn som numeriske verdier i et numerisk uttrykk.
Beregning og sortering
Vi ønsker nå å få sortert resultatlista etter den samlede tiden. Vi må altså lage et sorteringskriterium som er basert på en beregnet verdi. Dette fungerer:
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" doctype-system="about:legacy-compat" encoding="UTF-8"/> <xsl:template match="/"> <html> <head><title>resultater</title></head> <body><h1>Resultater</h1> <xsl:apply-templates select="/resultater/deltager"> <xsl:sort select= "3600*(substring(t1,1,1)+substring(t2,1,1)) +60*(substring(t1,3,2)+substring(t2,3,2)) +(substring(t1,6,2)+substring(t2,6,2))" order="ascending" data-type="number"/> </xsl:apply-templates> </body></html> </xsl:template> <xsl:template match="deltager"> <xsl:variable name="sumsek" select= "3600*(substring(t1,1,1)+substring(t2,1,1)) +60*(substring(t1,3,2)+substring(t2,3,2)) +(substring(t1,6,2)+substring(t2,6,2))"/> <h3><xsl:value-of select="navn"/></h3> <xsl:variable name="timer" select="floor($sumsek div 3600)"/> <xsl:variable name="minutter" select="floor(($sumsek - $timer * 3600) div 60)"/> <xsl:variable name="sekunder" select="$sumsek -($timer * 3600) -($minutter*60)"/> <div> <xsl:value-of select="concat($timer,':',$minutter,':',$sekunder)"/> </div> </xsl:template> </xsl:stylesheet>
Det virker ganske klønete å regne ut sekundsummen to ganger, først som sortingskriterium og deretter i forbindelse med presentasjonen. Se om du kan finne på noe bedre og fortell meg om det.