Parametre
Vi kan lage konstruksjoner av følgende type:
... <xsl:apply-templates select="IOC/OlympicGame" > <xsl:with-param name="sted" select="'Barcelona'"/> </xsl:apply-templates> ... <xsl:template match="OlympicGame"> <xsl:param name="sted"/> <xsl:if test="$sted=@place"> ... do something ... </xsl:if> </xsl:template> ...
Slike konstruksjoner er parallelle til måten vi bruker parametre på ved prosedyre-kall i andre programmeringsspråk. Parameterstyring av templates er interessant i seg selv, men det løser ikke det problemet vi stadig møter når vi skal planlegge XSLT-transformasjoner. Hvordan skal vi kunne parameterstyre hele transformasjonen ?
Hvis vi f.eks. ønsker å ekstrahere resultatene fra 200m i Barcelona fra vår lange XML-fil med flere olymiader og flere øvelser, har vi et problem. Det virker litt tungvindt å skrive en transformasjon for alle mulige kombinasjoner av sted og øvelse. Vi kan nærme oss problemet ved å se på en konstruksjon der vi innfører to "globale parametere":
<xsl:param name="sted" select="'Barcelona'"/> <xsl:param name="distanse" select="'200m'"/>
Når vi kaller dem globale så innebærer det at de er definert utenfor alle templates i stilsettet og de er tilgjengelig i hele fila, alle templates. Transformasjonsfila er i sin helhet slik:
<?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" version="1.0" encoding="UTF-8" indent="yes"/> <xsl:param name="sted" select="'Barcelona'"/> <xsl:param name="distanse" select="'200m'"/> <xsl:template match="/"> <html> <head> <title>Olympics</title> <meta HTTP-EQUIV="Content-Type" content="text/html; charset=iso-8859-1"/> <link rel="STYLESHEET" href="olymp.css"/> </head> <body> <h1>Resultater sprint</h1> <xsl:apply-templates select="IOC/OlympicGame[@place=$sted]"/> </body> </html> </xsl:template> <xsl:template match="OlympicGame"> <h2> <xsl:value-of select="@place"/> - <xsl:value-of select="@year"/> </h2> <xsl:apply-templates select="event[@dist=$distanse]"/> </xsl:template> <xsl:template match="event"> <h3><xsl:value-of select="@track"/></h3> <xsl:apply-templates select="athlet"> <xsl:sort select="result" order="ascending"/> </xsl:apply-templates> </xsl:template> <xsl:template match="athlet"> <p> <xsl:value-of select="result"/> : <xsl:value-of select="name"/> </p> </xsl:template> </xsl:stylesheet>
Det virker rimelig å ønske seg et transformasjonsverktøy som kan ta en liste av parametre som input, tolke disse som globale parametre og så foreta transformasjonen. Og heldigvis, slike finnes. Vi skal se på to, ett Javaverktøy: Saxon og et Pythonverktøy: lxml. I modulen XSLT på klienten kan du dessuten se hvordan vi kan gjøre dette på kliente.
Saxon
Saxon [1] distribueres som en jar-fil. Saxon kan ta en parameterliste som input. Et kall på Saxon som løser vårt problem er slik:
java -jar C:\saxon8\saxon8.jar -o outputfil xmlfil xsltfil sted=Barcelona distanse=100m
Saxon er et Java-prosjekt med åpen kildekode og gir oss alt det verktøyet vi trenger for å skrive Javaprogrammer som transformerer som vi måtte ønske.
De siste versjoenen av Saxon (8 +) distribureres også som .Net -biliotek.
lxml
lxml [2] synes å være det biblioteket som gir best og enklest funksjonalitet når det gjelder XSLT og XPath i Python.
Eksempel Olympiade
Vi tar utgangspunkt i rådata fra Olympiade eksempelet: page2/all_results.xml og bruker den samme transformasjonen som er sitert ovenfor, med to globale parametre.
Vi bruker et Pythonprogram som cgi-script for å utføre transformasjonen basert på de ønskene (sted og øvelse) vi sender inn fra vår HTML-side.
#! /usr/bin/python import sys,cgi from lxml import etree #------------------------------------------------------------- # Testing lxml, parametric xslt # Work on fixed files: all_results.xml and olymp_x.xslt # Parameters are: # sted (Barcelona, Atlanta, ) # distanse (100m or 200m or 400m) # B. Stenseth 2011 #------------------------------------------------------------- def loadFile(fname): try: file=open(fname,'r') res=file.read() file.close() return res except: errorReturn('error reading: ' + fname) def storeFile(fname,content): try: outf=open(fname,'w') outf.write(content) outf.close() except: errorReturn('error writing: ' + fname) def errorReturn(msg): print """<html> <head><title>errormsg</title></head> <body><p>%s</p></body> </html>"""%msg exit() def produce(place,distance): # fixed filenames xmlfile='all_results.xml' xsltfile='olymp_x.xslt' htmlfile='result.html' xmlTree=etree.parse(xmlfile) xsltTree=etree.parse(xsltfile) transform=etree.XSLT(xsltTree) d=etree.XSLT.strparam(distance) p=etree.XSLT.strparam(place) resultTree=transform(xmlTree,sted=p,distanse=d) print str(resultTree) #----------------------------------------- print 'Content-type: text/html\n' form=cgi.FieldStorage() # what was thew question again ? place='' distance='' try: place=form['1'].value except: place='Barcelona' try: distance=form['2'].value except: distance='400m' produce(place,distance)
Eksempel Fotball
Vi tar utgangspunkt i en XML-fil som inneholder resultatene fra Engelsk Premier League sesongen 2001/2002. Fila er bygd opp slik:
<?xml version="1.0" encoding="utf-8"?> <liga> <land>England</land> <serie>Premier League</serie> <sesong>2001/2002</sesong> <kamp> <dato>18.08.2001</dato> <hlag>Charlton</hlag> <hmal>1</hmal> <blag>Everton</blag> <bmal>2</bmal> </kamp> <kamp> <dato>18.08.2001</dato> <hlag>Derby</hlag> <hmal>2</hmal> <blag>Blackburn</blag> <bmal>1</bmal> </kamp> ... </liga>
Fila i sin helhet
Vi skriver en transformasjon som ekstraherer resultater avhengig av angitt hjemmelag og bortelag:
<?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" version="1.0" encoding="UTF-8" indent="yes"/> <xsl:param name="hjemmelag" select="'Chelsea'"/> <xsl:param name="bortelag" select="'Arsenal'"/> <xsl:template match="/"> <html> <head> <title>Fotball</title> </head> <body> <h1><xsl:value-of select="liga/land"/></h1> <h2><xsl:value-of select="liga/serie"/> - <xsl:value-of select="liga/sesong"/></h2> <xsl:for-each select="liga/kamp"> <xsl:if test="($hjemmelag='*' and $bortelag='*' ) or (hlag=$hjemmelag and $bortelag='*') or ($hjemmelag='*' and blag=$bortelag) or (hlag=$hjemmelag and blag=$bortelag)"> <xsl:apply-templates select="self::node()"/> </xsl:if> </xsl:for-each> </body> </html> </xsl:template> <xsl:template match="kamp"> <div> <xsl:value-of select="dato"/>: <xsl:value-of select="hlag"/>-<xsl:value-of select="blag"/> : <xsl:value-of select="hmal"/>-<xsl:value-of select="bmal"/> </div> </xsl:template> </xsl:stylesheet>
Begge filene legges på cgi-bin området på tjeneren og vi bruker et Python-script, for å utføre transformasjonen basert på de ønskene vi sender inn.
#! /usr/bin/python import sys,cgi from lxml import etree #------------------------------------------------------------- # Testing lxml, parametric xslt # Work on fixed files: eng0_01_02.xml and trans1.xslt # Parameters are: # hjemmelag (*: wildcard) # bortelag (*: wildcard) # B. Stenseth 2011 #------------------------------------------------------------- def loadFile(fname): try: file=open(fname,'r') res=file.read() file.close() return res except: sys.exit('gir opp...') def storeFile(fname,content): try: outf=open(fname,'w') outf.write(content) outf.close() except: sys.exit('gir opp...') def produce(hjemme,borte): # fixed filenames xmlfile='eng0_01_02.xml' xsltfile='trans1.xslt' htmlfile='result.html' xmlTree=etree.parse(xmlfile) xsltTree=etree.parse(xsltfile) transform=etree.XSLT(xsltTree) h=etree.XSLT.strparam(hjemme) b=etree.XSLT.strparam(borte) resultTree=transform(xmlTree,hjemmelag=h,bortelag=b) print str(resultTree) # write it to file # storeFile(htmlfile,result) #----------------------------------------- print "Content-type: text/html\n" form=cgi.FieldStorage() # what was the question again ? hjemme='' borte='' try: hjemme=form['hjemmelag'].value except: hjemme='*' try: borte=form['bortelag'].value except: borte='*' produce(hjemme,borte)