Løkker som rekursjon
Vi vet at vi kan lage løkker som går gjennom et sett med noder, f.eks slik:
<xsl:for-each select="//deltagere"> <div> <xsl:value-of select="navn"/> </div> </xsl:for-each>
Det er imidlertid ikke like åpenbart hvordan vi skal lage enkle løkker der vi skal la en indeks løpe over et område. Slik som vi kan formulere i Java:
for(int ix=0;ix<12;ix++){ System.out.println(ix) }
Vi merker oss at det samme kan skrives rekursivt slik:
void oneOut(int ix, int limit){ if (ix < limit){ System.out.println(ix); oneOut(ix+1,limit); } } ... oneOut(1,12);
Vi bruker som vanlig et sett med data, i dette tilfellet en XML-fil, og demonstrerer noen løsninger. Dataene er konstruerte og løselig basert på restaurantspalten i Dagbladet som gir terningkast til diverse bevertningssteder. Et utdrag av file er slik:
<?xml version="1.0" encoding="utf-8"?> <?xml-stylesheet code="text/xsl" href="trans1.xslt"?> <spisesteder> <sted> <navn>Bagatelle</navn> <terning>6</terning> </sted> <sted> <navn>Stamkafeen</navn> <terning>3</terning> </sted> <sted> <navn>Gruff</navn> <terning>2</terning> </sted> <sted> <navn>Lofoten fiskerestaurant</navn> <terning>5</terning> </sted> ... </spisesteder>
Vanlig sortering
La oss først lage en oversikt over spisestedene ordnet etter terningkast. Dette gjør vi på vanlig måte ved hjelp av to templates og indirekte utvalg og sortering. Ikke noe nytt her, bare en kjent løsning som referanse.
<?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>Restaurantliste</title> </head> <body> <h1>Restauranter</h1> <p>Ordnet etter terningkast</p> <xsl:apply-templates select="//sted"> <xsl:sort select="terning" order="descending"/> </xsl:apply-templates> </body> </html> </xsl:template> <xsl:template match="//sted"> <div><xsl:value-of select="navn"/> : <xsl:value-of select="terning"/></div> </xsl:template> </xsl:stylesheet>
Vi greier dette uten noen eksplisitt løkke siden linjene:
<xsl:apply-templates select="//sted"> <xsl:sort select="terning" order="descending"/> </xsl:apply-templates>
både løper gjennom alle steder og sorterer dem.
Gruppering
Nå ønsker vi å ordne spisestedene slik at vi får en egen overskrift for hver terningkategori. Dette kan gjøres på flere måter, men vi velger en ganske generell metode som baserer seg på en template som kjøres for hver kategori.
Vi introduserer en template til og lager:
<?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>Restaurantliste</title> </head> <body> <h1>Restauranter</h1> <p>Ordnet etter terningkast</p> <xsl:call-template name="onegroup"> <xsl:with-param name="denneverdi" select="6"/> <xsl:with-param name="stoppverdi" select="0"/> </xsl:call-template> </body> </html> </xsl:template> <xsl:template name="onegroup"> <xsl:param name="denneverdi"/> <xsl:param name="stoppverdi"/> <xsl:if test="$denneverdi > $stoppverdi"> <h2><xsl:value-of select="$denneverdi"/> : terninger</h2> <xsl:apply-templates select="//sted[child::terning=$denneverdi]"> <xsl:sort select="navn"/> </xsl:apply-templates> <xsl:call-template name="onegroup"> <xsl:with-param name="denneverdi" select="$denneverdi - 1"/> <xsl:with-param name="stoppverdi" select="$stoppverdi"/> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template match="//sted"> <div><xsl:value-of select="navn"/></div> </xsl:template> </xsl:stylesheet>
Templaten oneGroup implementerer den rekursive mekanismen. Vi ser videre at det er lagt inn en sorteringssetning som ordner spisestedene alfabestisk innen gruppa.
Vi ser at strukturen er svært lik den Java-koden vi introduserte innledningsvis.
Parvis gruppering
Det er en enkel utvidelse å lage løsninger av denne typen:
void oneOut(int ix, int step, int limit){ if (ix < limit){ System.out.println(ix); oneOut(ix+step,step,limit); } } ... oneOut(1);
En slik transformasjon som foretar gruppering av spisestedene slik at de som har terningkast 1 og 2 havner i en gruppe, de som har 3 og 4 og de som har 5 og 6, kan se slik ut:
Vi kan gjøre dette ved å bytte ut templaten "onegroup" med følgend einnhold:
<xsl:template name="onegroup"> <xsl:param name="denneverdi"/> <xsl:param name="stoppverdi"/> <xsl:param name="stepverdi"/> <xsl:if test="$denneverdi + $stepverdi < $stoppverdi +1"> <h2> <xsl:value-of select="$denneverdi"/>- <xsl:value-of select="$denneverdi + $stepverdi -1 "/> terninger </h2> <xsl:apply-templates select="//sted[(child::terning > $denneverdi - 1) and ( child::terning < $denneverdi + $stepverdi)]"> <xsl:sort select="navn"/> </xsl:apply-templates> <xsl:call-template name="onegroup"> <xsl:with-param name="denneverdi" select="$denneverdi + $stepverdi "/> <xsl:with-param name="stoppverdi" select="$stoppverdi"/> <xsl:with-param name="stepverdi" select="2"/> </xsl:call-template> </xsl:if> </xsl:template>