Vinkjeller
XML og XPATH
XMLfila er bygget opp slik:
<?xml version="1.0" encoding="utf-8"?>
<wines>
<wine>
<type>sparkling</type>
<name>Gosset Grande Réserve Brut</name>
<catalog>32299</catalog>
<country>Frankrike</country>
<volume>75</volume>
<price>259.00</price>
<dice>5</dice>
<description>Duften minner mest om epler</description>
</wine>
...
</wines>
Rekkefølgen av viner i basisfila er for alle praktiske formål tilfeldig.
Vi ønsker å formulere XPath-uttrykk for å lage forskjellige utdrag fra denne XML-fila, f.eks.
Antall viner i lista:
count(//wine)
Alle franske hvitviner:
//wine[country='Frankrike' and type='white']
Alle franske rødviner på kartong(?):
//wine[country='Frankrike' and type='red' and volume=300]
Alle italienske hvitviner med terningkast 6:
//wine[country='Italia' and type='white' and dice=6]
Beskrivelsen av alle franske hvitviner med terningkast 6:
//wine[country='Frankrike' and type='white' and dice=6]/description/text()
Antall franske rødviner på flaske
count(//wine[country='Frankrike' and type='red' and volume=75])
Gjennomsnittsprisen på alle franske rødviner på flaske
sum(//wine[country='Frankrike' and type='red' and volume=75]/price) div count(//wine[country='Frankrike' and code='red' and volume=75])
Alle chilenske rødviner som innholder fyldig i beskrivelsen
//wine[country='Chile' and type='red' and contains(description,'fyldig')]
... og hva mer du måtte finne på
Merk at vi har brukt //wine for å få takk i alle viner. Alternativt kunne vi skrive /winelist/wine. I dette tilfelle gjør det ingen forskjell siden alle viner (//wine) er nøyaktig samme nodesett som alle viner under winelist(/winelist/wine).
Vi forsøker å bruke XPath i en XSLT-transformasjon. Vi vil lage en HTML-fil som presenterer et utvalg av viner. Det første vi gjør er å lage en basis fil som kan se slik ut:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"
encoding="ISO-8859-1"
indent="yes"
omit-xml-declaration="yes"
doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"/>
<xsl:param name="mySelection" select="//wines/wine[country='Spania']"/>
<xsl:template match="/">
<html>
<head>
<title>Vinliste</title>
<link rel="STYLESHEET" href="vin.css"/>
</head>
<body>
<h1>Vinliste</h1>
<xsl:call-template name="ingress"/>
<!-- here is where we do the patch -->
<xsl:apply-templates select="$mySelection">
<xsl:sort select="type" order="ascending"/>
<xsl:sort select="dice" order="descending"/>
<xsl:sort select="price/volume" order="descending"/>
</xsl:apply-templates>
</body>
</html>
</xsl:template>
<xsl:template name="ingress">
<p class="ingress">
<xsl:value-of select="count(//wine)"/>
vinanmeldelser samlet fra dagspessen for demonstrasjonsformål.
Ikke for kommersiell bruk.<br/>
Børre S, mai.2006
</p>
<hr size="1"/>
</xsl:template>
<xsl:template match="//wine">
<div class="wine">
<div style="float:left">
<xsl:element name="img">
<xsl:attribute name="src">
<xsl:value-of select="type"/>
<xsl:value-of select="dice"/>.gif</xsl:attribute>
<xsl:attribute name="alt">
dice</xsl:attribute>
</xsl:element>
</div>
<div class="wine-name">
<xsl:value-of select="name"/>
</div>
<div class="wine-country">
<xsl:value-of select="country"/>
<span class="wine-price">
: kr <xsl:value-of select="price"/> / <xsl:value-of select="volume"/>cl
</span>
</div>
<div style="clear:left"/>
<div class="wine-desc">
<xsl:value-of select="description"/>
</div>
</div>
</xsl:template>
</xsl:stylesheet>
lxml og parameter
I modulen Parametere demonstreres hvordan det er mulig å parameterstyre XSLT-transformasjoner. Vi prøver dette også her.
Vi kan f.eks. bruke følgende script (vinselect2.py):
#! /usr/bin/python
import sys,cgi
from lxml import etree
import cgitb; cgitb.enable()
#-------------------------------------------------------------
# Return extracts from a winelist
# See: http://lxml.de/xpathxslt.html
# B. Stenseth 2011
#-------------------------------------------------------------
def produce(xp):
# fixed filenames
xmlfile='viner.xml'
xsltfile='tohtml_param.xslt'
htmlfile='result.html'
xmlTree=etree.parse(xmlfile)
xsltTree=etree.parse(xsltfile)
transform=etree.XSLT(xsltTree)
resultTree=transform(xmlTree,mySelection=xp)
print str(resultTree)
#-----------------------------------------
print "Content-type: text/html; charset=iso-8859-1\n"
form=cgi.FieldStorage()
# what was the question again ?
xpath=''
try:
xpath=form['xpath'].value
except:
xpath='//wine'
produce(xpath)
#produce("//wine[country='Spania']")
Database og AJAX
Dataene som er brukt er beskrevet i modulen Noen datasett . Det er bygget en løsning som benytter AJAX for å hente fram informasjon om et utvalg av viner fra en database til en del av en HTML-side.
Løsningen er prinsippielt slik:
Javascript
Javascriptkoden som kjører på klienten bestiller et vinutvalg, tar i mot ferdig formatert tekst og plasserer denne teksten som "innerHTML". Skriptet er slik:
// relies on jquery.js
function getIt(address,targetNodeId)
{
box = document.forms[0].vintype;
winetype = box.options[box.selectedIndex].value;
box = document.forms[0].land;
prodland = box.options[box.selectedIndex].value;
box = document.forms[0].volum;
volum = box.options[box.selectedIndex].value;
box = document.forms[0].terning;
terning = box.options[box.selectedIndex].value;
params='winetype='+winetype+'&prodland='+
prodland+'&volum='+volum+'&terning='+terning;
$.ajax({
url:address,
data:params,
success:function(data)
{
$('#'+targetNodeId).html(data);
},
error:function(data)
{
$('#'+targetNodeId).html("Could not access content");
}
});
}
Serverskriptet
Serverskriptet tolker forespørselen fra Javascriptkoden, slår opp i databasen, formaterer dataene i HTML-fragmenter og returnerer bestillingen. Skriptet er skrevet i Python og ser slik ut:
#! /usr/bin/python
import MySQLdb,cgi
""" Retrieve data from MySql-base vin
The return is selected wines wapped for HTML
The HTML(fragments) uses some classes for styles, see fragments below
"""
SQL_SELECTED_WINES="""SELECT * FROM wines WHERE
type='%s' AND
country='%s' AND
volume='%s' AND
dice='%s';
"""
HTML_WINE_DESCRIPTION="""
<div class="wine">
<div style="float:left"><img src="%s" alt="dice"></div>
<div class="wine-name">%s</div>
<div class="wine-country">%s
<span class="wine-price">: kr %s / %scl
</span>
</div>
<div style="clear:left">
</div><div class="wine-desc">%s</div>
</div>
"""
HTML_WINE_SELECTION="""<div>
%s
</div>
"""
HTML_NO_WINE="""
<p style="margin:50px;font-weight:bold">
%s
</p>
"""
#------------------------------------
# connect and execute a sql-request
#------------------------------------
def connectAndExecute(sql):
try:
myBase=MySQLdb.connect(host='frigg.hiof.no',
user='student',
passwd='student',
db='vin')
myTab=myBase.cursor()
myTab.execute(sql)
myBase.close()
return myTab.fetchall()
except MySQLdb.Error, e:
print "Error %d: %s" % (e.args[0], e.args[1])
return None
#------------------------------------
# make selected list
#------------------------------------
def makeSelection(vintype,land,volum,terning):
resultTxt=''
result=connectAndExecute(SQL_SELECTED_WINES%(vintype,land,volum,terning))
if result==None:
return HTML_NO_WINE%'Ingen viner funnet'
if len(result)==0:
return HTML_NO_WINE%'Ingen viner funnet'
for w in result:
navn=str(w[1])
vintype=str(w[3])
land=str(w[4])
terning=str(w[5])
terningimage=vintype+terning+'.gif'
volum=str(w[6])
pris=str(w[7])
beskrivelse=str(w[8])
resultTxt+=HTML_WINE_DESCRIPTION%\
(terningimage,navn,land,pris,volum,beskrivelse)+'\n'
resultTxt=HTML_WINE_SELECTION%resultTxt
return resultTxt
#-------------------------------------------------
# what is the job ?
#-------------------------------------------------
form=cgi.FieldStorage()
print 'Content-type: text/html; charset=iso-8859-1\n'
RESULT_TXT=''
winetype='red'
prodland='Frankrike'
volum='75'
terning='6'
if form.has_key('winetype'):
winetype=form['winetype'].value
if form.has_key('prodland'):
prodland=form['prodland'].value
if form.has_key('volum'):
volum=form['volum'].value
if form.has_key('terning'):
terning=form['terning'].value
RESULT_TXT=makeSelection(winetype,prodland,volum,terning)
print RESULT_TXT
Stilsett
Det er benyttet et enkelt stilsett (css):
body{
color: black;
font-family:"Verdana","Geneva","Arial","Times",sans-serif;
font-size:13px;
text-align:left;
margin:0px;
padding:20px;
}
h1{font-size:24px}
.ingress{font-size:12px}
.wine{margin-top:15px}
.wine-name{font-size:18px;padding-left:50px}
.wine-desc{}
.wine-country{padding-left:50px; font-weight:bold}
.wine-price{font-weight:normal}
.wine-author{font-weight:normal;font-size:10px;color:gray}