2005-08-31

Gebruik van XML, XSLT en XHTML binnen Bibliotheek Wageningen UR

een voorstel voor verdergaande standaardisatie en uniformiteit.


Langzamerhand is binnen de afdeling een redelijke hoeveelheid ervaring opgedaan met het gebruik van XML en daarvan afgeleide technologie. Steeds weer blijkt echter dat hergebruik niet of alleen met veel extra inspanning mogelijk is. De voorstellen in dit document zijn gericht op betere herbruikbaarheid en structurering van de verschillende documenten.

XML


De huidige en toekomstige versies van WebQuery genereren in vergelijking tot de oude beter gestandaardiseerde XML. Bij de conversie naar XML en ander onderhoud waarbij de database nog in MINISIS blijft, is het handig daar vast rekening mee te houden door via de cgiparm file de nieuwe situatie zoveel mogelijk te benaderen. Dat houdt onder meer in:

  • gebruik voor het recordsetelement altijd de naam xxxset,
    en voor de recordelementen de naam xxx (dat laatste gebeurt automatisch bij wq_sfx=XML), waarin xxx staat voor de naam van de minisis database.

  • gebruik op het recordniveau uitsluitend de elementen die in de nieuwe situatie ook gegenereerd zullen worden, en geef ze inhoud conform de nieuwe situatie. Het gaat
    om de volgende elementen:
    • error
    • query
    • queryinfo (nog niet gebruiken, wordt in de nieuwe situatie altijd gegenereerd en bevat de query in gestructureerde vorm)
    • hits
    • xxx (de echte records, zie boven)
    • next
    • debug (niet gebruiken, wordt in de nieuwe situatie gegenereerd als om debugginginformatie gevraagd wordt)
  • gebruik zo min mogelijk verschillende (database)namen voor de zelfde gegevens. In de nieuwe situatie wordt altijd de naam van de tabel zelf gebruikt, ook bij joins.

XSLT


  • Genereer zo mogelijk XML of XHTML.
  • Valideer de gegenereerde uitvoer (als dat kan).
  • Structureer de XSLT's zoveel mogelijk. Dat betekent onder meer:

    • maak voor elke waarde van wq_sfx een aparte xslt. Weersta de verleiding om
      xslt's te combineren omdat er zoveel op de zelfde manier moet. Maak in dat geval
      eventueel een aparte xslt voor de gemeenschappellijke delen. Vaak is dat niet
      nodig; zet dan de gemeenschapplijke templates in de xslt met het kleinste aantal
      “eigen” templates of in de meestgebruikte, en importeer die in de andere xslt's.

    • maak templates zo klein mogelijk, zodat ze één element of attribuut behandelen.
      Gebruik voor de onderliggende elementen/attributen waar mogelijk “applytemplates”.
      Door die in de ogen van sommigen “extreme” uitsplitsing maak je niet
      alleen hergebruik eenvoudiger, maar je templates worden simpeler, omdat je veel
      minder conditionele statements nodig hebt (realiseer je dat een template alleen
      wordt gebruikt als het betreffende element voorkomt. De constructie waarbij een
      template wordt aangeroepen na een test op aanwezigheid van het betreffende
      element is principieel fout. Testen op afwezigheid (en dan iets met een ander
      element doen) kan wel zinvol zijn, maar is vaak niet nodig (bv. als altijd exact één
      van de twee aanwezig moet zijn).

    • kleine templates zijn beter leesbaar voor anderen dan grote. Fouten zijn daardoor
      eenvoudige op te sporen.

    • als je in een template de functionaliteit nodig hebt van een reeds bestaand
      template, maak daar dan zo mogelijk geen kopie van, maar gebruik het origineel
      als subtemplate:

      1. roep het aan via applyimports select=”.”

      2. geef het nieuwe template een andere mode mee, en roep het oude aan via
        applytemplates select=”.” (zonodig inclusief mode)

      3. doe het NIET met calltemplate als het ook met applytemplates kan.

    • maak gebruik van de importhiërarchie. Geïmporteerde templates, maar ook
      geïmporteerde globale parameters en variabelen hebben per definitie een lagere
      prioriteit dan die in de xslt waaarin ze geïmporteerd worden. Je kunt dus
      bijvoorbeeld in een algemeen bruikbare xslt (zie default.xslt) standaardwaarden
      zetten en die in een andere xslt overschrijven waar dat nodig is (zie bv.
      wwwtest.xslt en wwwtestupdate.xslt, die heel simpel zijn omdat ze voor vrijwel
      alles de functionaliteit van default.xslt gebruiken)

    • importeren van xslt's is meestal een betere manier van hergebruik dan kopieren
      en aanpassen.

    • maak voor de volgende pagina en andere links naar de zelfde tabel gebruik van
      de standaardparameter
      “service”, die vanaf WebQuery 5.28 altijd automatisch
      wordt meegegeven aan de xslt.

    • probeer zoveel mogelijk te generaliseren en via de cgiparm file te parametriseren.

    • gebruik zo min mogelijk parameters. Dat klinkt tegenstrijdig met het vorige punt,
      maar is het niet. Waar het om gaat is dat je nagaat of het nodig of zinvol kan zijn
      om iets variabel te maken, en hoe dat dan zou moeten. Soms is het beter een
      template voor een andere situatie te herschrijven in een andere xslt. Door goed
      structureren (imports) kan je ervoor zorgen dat de onderliggende templates weer
      wel uit de originele xslt gebruikt worden.

  • documenteer de templates door er boven te zetten waar ze voor bedoeld zijn. Niet
    door te zeggen wat ze doen (dat vertelt de code beter), maar door te zeggen wat
    daarvoor de reden is. Geef ook aan waar eventuele parameters voor gebruikt worden. Beperk de documentatie binnen de template tot het absolute minimum; het maakt het template per definitie minder leesbaar. Als het zou leiden tot betere leesbaarheid is het template te complex.

  • Denk na over de naamgeving, zowel van de templates als van de xsltfiles.
    Begin de naam van een xsltfile met de naam van de service waarvoor hij bedoeld is. Dat is in de nieuwe situatie altijd de enkelvoudvorm van een nederlandstalig zelfstandig
    naamwoord. Zet daarachter de elementengroep of de WebQuery suffix waarvoor het
    ding bedoeld (of beide: eerst de groep en dan de suffix, bv. titelbeschrijvingauteurkort.
    xslt). Combineer algemeen bruikbare templates (en algemeen bruikbaar
    betekent onder meer: onafhankelijk van de gebruikte service) tot zinvolle groepen en
    geef die een zinvolle naam, die in één taal aangeeft waarvoor ze bedoeld zijn. Dus
    niet “libraryauteur.xslt” maar “auteurkort.xslt” of “auteur.xslt”. Zo'n algemene naam
    kan NIET gebruikt worden voor templates die alleen voor auteurs uit de titelbeschrijvingservices gebruikt kunnen worden. In dat geval is “titelbeschrijvingauteur.
    xslt” een betere keuze. Voor de meeste templateverzamelingen zal iets dergelijks gelden, echt universeel bruikbare templates zijn helaas zeldzaam.

XHTML


XHTML wijkt op een aantal punten af van zijn voorganger, HTML 4.0. Omdat het xml is, wordt een aantal dingen strenger gecontroleerd. Daarom, en ter bevordering van de modulariteit, de volgende eisen en wensen:
  • geef in de XSLT aan dat de output XML is en geen HTML. De versie daarvan is dan ook 1.0 en niet 4.0.

  • schrijf elementen attribuutnamen altijd in kleine letters (voor xhtml is dat verplicht).

  • regel alle style attributen via een extern css style sheet waarnaar je in de html
    header verwijst. Gebruik ook voor de stylenamen uitsluitend kleine letters (sommige
    browsers maken wel, andere geen onderscheid tussen kleine letters en hoffdletters).

  • maak voor scripting gebruik van een externe scriptfile waarnaar je in de html header verwijst. Zet in event handlers (onclick etc.) geen complete scripts, maar alleen een aanroep van een functie.

    Een voorbeeld


    Voorbeelden hebben het nadeel dat ze vaak zonder nadenken gekopieerd en vervolgens aangepast worden totdat het resultaat werkt. Dat is hier uitdrukkelijk niet de bedoeling. Het onderstaande voorbeeld is op dit moment in gebruik voor de services
    wwwtest en titel, en daarmee voor allerlei verschillende testsituaties met verschillende
    databasetabellen. Het is voor een nieuwe service eenvoudig te gebruiken als
    generieke invoeren controleapplicatie, en als basis voor andere applicaties.
    Ongetwijfeld valt er nog het een en ander aan te verbeteren; het gaat echter om de
    algemene bruikbaarheid en de modulaire uitwerking van de functionaliteit.
    Het bestaat uit de volgende bestanden (zie bijlagen)

    • cgiparm files: wwwtest.WebQuery, titel.WebQuery

    • hulpbestand: recorddefinitions.
      xml

    • xslt files: default.xslt, defaultupdate.
      xslt, wwwtest.xslt


    Bijlage 1: wwwtest.WebQuery



    security on
    service wwwtest
    server db1.library.wur.nl
    xslt xslt/wwwtest.xslt
    xslt_html -
    #
    xslt_debug xslt/debug.xslt
    xslt_result xslt/wwwtest-result.xslt
    xslt_update xslt/wwwtest-update.xslt
    #
    xslt-parameter css='/css/WebQuery.css'
    xslt-parameter title='&&extra;'
    xslt-parameter script='no-script-just-testing'
    xslt-parameter browser='&&HTTP_USER_AGENT;'
    extra xslt: &&xslt;
    extra_test andere tekst bij suffix test

    Opmerkingen bij de bovenstaande regels:

    • WebQuery genereert zelf de xsltparameters “service” en “user”. Daarom mogen die
      niet in de cgiparmfile staan.

    • De “extra” regels worden in het voorbeeld niet volledig gebruikt; ze zijn een voorbeeld van een al langer bestaande maar relatief onbekende methode om de parameters slechts éénmaal te hoeven definiëren en toch bij de suffix “test” een
      andere titel te krijgen. In dat geval wordt bij het uitwerken van de xsltparameter
      “title” de regel “extra_test” gebruikt in plaats van de regel “extra”.


    Bijlage 2: titel.WebQuery



    server db1.library.wur.nl
    service titel
    security update
    xslt_pauline xslt/titelplus.xslt
    xslt xslt/debug.xslt
    hit-limit 500,10
    xslt_short xslt/cms_demo_short.xslt
    xslt_full xslt/cms_demo_full.xslt
    xslt_boolean xslt/boolean.xslt
    field ti=titel
    field au=auteur
    field dt=documenttype
    xslt-parameter css='/css/WebQuery.css'
    xslt-parameter title='&&extra;'
    xslt_wwwtest xslt/wwwtest.xslt
    xslt_update xslt/default-update.xslt
    extra titelbeschrijving
    extra_update titelbeschrijving update

Opmerkingen bij de inhoud van dit bestand (zie ook bijlage 1):


  • Gebruik de suffix “wwwtest” om te testen met de “default” xslt's.

  • De “extra” regels worden hier gebruikt om de titel bij update aan te passen.


Bijlage 3: recorddefinitions.xml


Dit bestand bevat de voor het update template benodigde informatie uit de xsd's,
aangevuld met opmaakgegevens. Per tabel is er een recorddefinition element.
Hieronder volgt een deel van het bestand (“...” staat voor een aantal weggelaten
regels):


<?xml version="1.0" encoding="ISO-8859-1"?>

<recorddefinition-set>
...
<recorddefinition name="titelbeschrijving">
<field name="documenttype" type="text" size="10"/>
<group name="artikel">
<group name="titel">
<group name="origineel" repeat="yes">
<field name="begintekst" type="text" size="50"/>
<field name="tekst" type="text" size="3000"/>
<field name="auteursvermelding" type="text" size="200"/>
</group>
<field name="engels" type="text"/>
</group>
<group name="auteur">
<group name="persoon" repeat="yes">
<field name="achternaam" type="text" size="50"/>
<field name="voorletters" type="text" size="30"/>
<field name="voorvoegsels" type="text" size="30"/>
<field name="functie" type="text" size="10"/>
<field name="titulatuur" type="text" size="20"/>
</group>
<group name="corporatie" repeat="yes">
<field name="naam" type="text" size="150"/>
<field name="plaats" type="text" size="36"/>
<field name="land" type="text" size="2"/>
<field name="code" type="text" size="6"/>
<field name="afdeling" type="text" size="100"/>
</group>
<group name="affiliatie">
<field name="naam" type="text" size="150"/>
<field name="plaats" type="text" size="36"/>
<field name="land" type="text" size="2"/>
<field name="code" type="text" size="6"/>
<field name="afdeling" type="text" size="100"/>
</group>
</group>
<group name="collatie">
<field name="paginering" type="text" size="40"/>
<field name="illustraties" type="text" size="60"/>
<field name="formaat" type="text" size="20"/>
<field name="begeleidend" type="text" size="40"/>
</group>
<field name="jaar" type="text" size="40"/>
<field name="deel" type="text" size="40"/>
<field name="nummer" type="text" size="40"/>
</group>
<group name="monografie">
...
</group>
...
</recorddefinition>
<recorddefinition name="wwwtest">
<field name="elem" type="textarea" rows="3" cols="100"/>
<field name="repeat" type="textarea" rows="5" cols="100" repeat="yes"/>
<group name="veld3">
<field name="sub1" type="text" size="50"/>
<field name="sub2" type="text" size="50"/>
<field name="sub3" type="text" size="50"/>
</group>
<group name="repsub" repeat="yes">
<field name="rsub1" type="text" size="50"/>
<field name="file01" type="file" size="50" boxtext="(check to remove this
occurrence)"/>
<field name="rsub3" type="text" size="50"/>
</group>
</recorddefinition>
</recorddefinition-set>



Bijlage 4: default.xslt



<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xml:space="default">

<!--default xslt voor gebruik met WebQuery-->
<xsl:output method="xml" version="1.0" encoding="ISO-8859-1" indent="yes" doctype-public="-//W3C//DTD XHTML 1.0
Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" omit-xml-declaration="no"/>
<!--Dit stylesheet is bedoeld als basis voor alle andere stylesheets. Het bevat defaults voor alle door WebQuery en wqoracle gegenereerde elementen, en voor de "standaard" door wqstub/mindbsrv gegenereerde elementen. Om de match-regels overzichtelijk te houden en overrides in andere templates te vereenvoudigen zijn verschillende modes gebruikt voor recordset, record en velden:
recordset: mode="recordset"
record: mode="record"
velden: default mode
De vormgeving van de html-elementen is geregeld via class attributen, zodat ze via een css kunnen worden aangepast.
Ten behoeve van het testen hebben alle templates een naam, die in de output als xml-commentaar wordt weergegeven. De templates worden nooit via deze naam (met call-template) aangeroepen, maar altijd via de match parameter (met apply-templates of apply-imports). De enige uitzondering op deze regel is het script-ref template, omdat daarin geen xml element maar een variabele verwerkt wordt.
Gebruikte xslt-parameters:
service = de gebruikte WebQuery service. Wordt standaard door WebQuery (5.28+) meegegeven
title = de te gebruiken titel. Kan in de cgiparm file worden meegegeven. Indien niet
meegegeven, wordt 'WebQuery' als titel weergegeven.
css = de te gebruiken cascasding style sheet. Kan in de cgiparm file worden meegegeven.
Indien niet meegegeven, wordt '/css/WebQuery.css' gebruikt.
script = de te gebruiken (java)script-file. Kan in de cgiparm file worden meegegeven.
Indien niet meegegeven, wordt geen scriptfile gebruikt.
-->

<xsl:param name="service" select="'/WebQuery'"/>
<xsl:param name="title" select="'WebQuery'"/>
<xsl:param name="css" select="'/css/WebQuery.css'"/>
<xsl:param name="script"/>
<!--
template voor het root element
Dit template bevat de basis html (header en body tags). De header kan via xslt-parameters in de
cgiparm file worden aangepast. Voor niet meegegeven parameters worden de volgende defaults gebruikt:
xslt-parameter title='WebQuery'
xslt-parameter css='/css/WebQuery.css'
Voor script is de default dat er geen script wordt gebruikt.
-->
<xsl:template match="/" name="default-root">
<xsl:comment>default-root</xsl:comment>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<link rel="stylesheet" type="text/css" href="{$css}"/>
<title>
<xsl:value-of select="$title"/>
</title>
<xsl:call-template name="script-ref">
<xsl:with-param name="script" select="$script"/>
</xsl:call-template>
</head>
<body class="page">
<xsl:apply-templates mode="recordset"/>
</body>
</html>
</xsl:template>
<!--
template voor de recordset
Dit template maakt een tabel waarin de records worden getoond, en de eerste regel van die tabel,
met de naam van de recordset.
-->
<xsl:template match="*" mode="recordset" name="default-recordset">
<xsl:comment>default-recordset; node='<xsl:value-of select="name()"/>'</xsl:comment>
<table class="status">
<tr class="page">
<td class="label">
<xsl:value-of select="name()"/>
</td>
</tr>
<xsl:apply-templates mode="record"/>
</table>
</xsl:template>
<!--
record level templates
Dit zijn templates voor alle elementen op het record niveau: error, hits, next, debug, query,
queryinfo en (als default) het record zelf.
error toont de onderliggende structuur (attributen en elementen) in tabelvorm.
hits toont een tabelregel met het aantal gevonden records. Omdat dit nooit nul kan zijn
(in dat geval wordt een error element gegenereerd) wordt alleen gecontroleerd of er
exact 1 record is, en dan de meervouds-s weggelaten.
next onderscheidt de twee mogelijke vormen: de oude (minisis) vorm iwordt herkend aan het
bestaan van het isn attribuut.
debug wordt genegeeerd, evenals
query en
queryinfo
* (de echte records) worden weergegeven als nieuwe tabel in een regel van de bovenliggende.
-->
<xsl:template match="error" mode="record" name="default-error">
<xsl:comment>default-error></xsl:comment>
<tr class="error">
<td>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates/>
</td>
</tr>
</xsl:template>
<!--
-->
<xsl:template match="hits" mode="record" name="default-hits">
<xsl:comment>default-hits</xsl:comment>
<tr class="hits">
<td class="label">
</td>
<td>
<xsl:value-of select="."/>
<xsl:text> record</xsl:text>
<xsl:if test=". != '1'">
<xsl:text>s</xsl:text>
</xsl:if>
<xsl:text> found</xsl:text>
</td>
</tr>
</xsl:template>
<!--
-->
<xsl:template match="next" mode="record" name="default-next">
<xsl:comment>default-next</xsl:comment>
<tr class="next">
<td class="label">
</td>
<td>
<xsl:choose>
<xsl:when test="@isn">
<a href="{@service}/{@isn}?{.}">more...</a>
</xsl:when>
<xsl:otherwise>
<a href="{$service}?wq_qry={../query}&wq_ofs={./@wq_ofs}&wq_max={./@wq_max}">more...</a>
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:template>
<!--
-->
<xsl:template match="debugqueryqueryinfo" mode="record" name="default-special">
<xsl:comment>default-special></xsl:comment>
<!-- just ignore these elements -->
</xsl:template>
<!--
-->
<xsl:template match="*" mode="record" name="default-record">
<xsl:comment>default-record; node='<xsl:value-of select="name()"/>'</xsl:comment>
<tr>
<xsl:attribute name="class"><xsl:choose><xsl:when test="position() mod 2 =
1">rowodd</xsl:when><xsl:otherwise>roweven</xsl:otherwise></xsl:choose></xsl:attribute>
<td class="label">
<xsl:value-of select="name(.)"/>
</td>
<td>
<table class="status">
<xsl:apply-templates select="@*"/>
<xsl:apply-templates/>
</table>
</td>
</tr>
</xsl:template>
<!--
-->
<!--
field level templates
* is identiek aan het template voor een record, afgezien van de mode.
@* toont het attribuut in een tabelregel
-->
<xsl:template match="*" name="default-all-elements">
<xsl:comment>default-all-elements; node='<xsl:value-of select="name()"/>'</xsl:comment>
<tr>
<xsl:attribute name="class"><xsl:choose><xsl:when test="position() mod 2 =
1">rowodd</xsl:when><xsl:otherwise>roweven</xsl:otherwise></xsl:choose></xsl:attribute>
<td class="label">
<xsl:value-of select="name(.)"/>
</td>
<td>
<table class="status">
<xsl:apply-templates select="@*"/>
<xsl:apply-templates/>
</table>
</td>
</tr>
</xsl:template>
<!--
-->
<xsl:template match="@*" name="default-all-attributes">
<xsl:comment>default-all-attributes; node='<xsl:value-of select="name()"/>'</xsl:comment>
<tr class="attribute">
<td class="label">
<xsl:value-of select="name(.)"/>
</td>
<td class="data">
<!-- xsl:apply-templates/ here generates a loop in libxslt; WHY ?? -->
<xsl:value-of select="."/>
</td>
</tr>
</xsl:template>
<!--
template voor het refereren naar een script
Deze code had ook rechtstreeks in het root template kunnen staan. Er is hier toch gekozen voor
een eigen template om in een aanroepend template eenvoudig iets anders te kunnen doen, bijvoorbeeld
als er van een andere scripttaal gebruik gemaakt wordt.
-->
<xsl:template name="script-ref">
<xsl:param name="script"/>
<xsl:comment>default-script-ref; script='<xsl:value-of select="$script"/>'</xsl:comment>
<!--
-->
<xsl:if test="$script">
<!--
volgens de html4.0 standaard moet de browser een eventuele inhoud van de script tag negeren
als het src attribute aanwezig is. Het is dus zinloos om daar iets in te zetten (bv.
"script niet gevonden"), omdat de browser dat NOOIT zal mogen laten zien.
-->
<script type="text/javascript" src="{$script}"/>
</xsl:if>
</xsl:template>
<!--
-->
</xsl:stylesheet>
Bijlage 5: defaultupdate.
xslt
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xml:space="default">
<!--
default xslt voor updates met WebQuery
-->
<xsl:import href="default.xslt"/>
<!--
-->
<xsl:output method="html" version="4.0" encoding="ISO-8859-1" indent="yes" doctype-public="-//W3C//DTD XHTML 1.0
Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" omit-xml-declaration="no"/>
<!--
Gebruikte parameters:
service = de gebruikte WebQuery service. Wordt standaard door WebQuery (5.28+) meegegeven
title = de te gebruiken titel. Kan in de cgiparm file worden meegegeven. Indien niet
meegegeven, wordt 'WebQuery' als titel weergegeven.
css = de te gebruiken cascasding style sheet. Kan in de cgiparm file worden meegegeven.
Indien niet meegegeven, wordt '/css/WebQuery.css' gebruikt.
script = de te gebruiken (java)script-file. Kan in de cgiparm file worden meegegeven.
Indien niet meegegeven, wordt geen scriptfile gebruikt.
Dit stylesheet verwacht een record definitie in het document 'record-definitions.xml' in deze directory
-->
<xsl:variable name="record-definition" select="document('record-definitions.xml')/recorddefinition-set"/>
<!--
record level templates
De op recordniveau mogelijke standaardelementen worden verwerkt door de default templates.
Omdat er in deze file ook een * template bestaat moet hier expliciet een apply-imports worden
uitgevoerd (of geprutst worden met prioriteiten, maar dat vond ik minder duidelijk).
-->
<xsl:template match="debugerrorhitsnextqueryqueryinfo" mode="record" name="special-record-update">
<xsl:apply-imports select="." mode="record"/>
<!-- use defaults -->
</xsl:template>
<!--
Het basisidee achter de overige templates in dit stylesheet is dat afwisselend de record-definitie
en het werkelijke record bekeken worden. Daartoe wordt het huidige record in een variabele gezet,
die als parameter aan het processing template wordt meeegegeven. Om alles goed te kunnen onderscheiden
worden de modes update en update-def gebruikt.
Het record-template geeft de besturing door aan het processing template. Dat bekijkt voor elk
gedefinieerd veld of het voorkomt. Zo ja, dan krijgt dat de besturing, en anders wordt een leeg
formulierveld gemaakt. Voor herhaalbare processing template altijd een leeg formulierveld aan.
Op groepsniveau werkt het om een soortgelijke manier.
-->
<xsl:template match="*" mode="record" name="default-record-update">
<xsl:comment>default-record-update; node='<xsl:value-of select="name()"/>'</xsl:comment>
<xsl:variable name="database-record" select="current()"/>
<tr>
<xsl:attribute name="class"><xsl:choose><xsl:when test="position() mod 2 =
1">rowodd</xsl:when><xsl:otherwise>roweven</xsl:otherwise></xsl:choose></xsl:attribute>
<td>
<form action="{$service}/update/{@isn}" method="post" accept-charset="UTF-8" enctype="multipart/form-data">
<table class="status">
<tr class="attribute">
<td class="label">
<xsl:value-of select="name($database-record)"/>
</td>
<td class="label">
<xsl:apply-templates select="@*" mode="update"/>
</td>
</tr>
<xsl:for-each select="$record-definition/recorddefinition[@name=name($database-record)]">
<xsl:apply-templates mode="update-def">
<xsl:with-param name="database-record" select="$database-record"/>
</xsl:apply-templates>
</xsl:for-each>
<tr>
</tr>
<tr>
<td class="label">
</td>
<td>
<input name="wq_crc" type="hidden" value="{@crc}"/>
<input name="wq_sfx" type="hidden" value="XML"/>
<input name="wq_dbg" type="hidden" value=":all"/>
<input type="submit" value="wijzig record"/>
<input type="reset" value="origineel scherm"/>
</td>
</tr>
</table>
</form>
</td>
</tr>
</xsl:template>
<!--
attribute templates
-->
<xsl:template match="@*" mode="update" name="update-all-attributes">
<xsl:comment>update-all-attributes; node='<xsl:value-of select="name()"/>'</xsl:comment>
<xsl:value-of select="name()"/>
<xsl:text>=</xsl:text>
<xsl:apply-templates/>
<xsl:text> </xsl:text>
</xsl:template>
<!--
field level template
-->
<xsl:template match="*" mode="update" name="default-field-update">
<xsl:comment>default-field-update; node='<xsl:value-of select="name()"/>'</xsl:comment>
<xsl:param name="definition" select="/.."/>
<xsl:variable name="database-record" select="current()"/>
<xsl:variable name="name">
<xsl:value-of select="name()"/>
<xsl:text>_</xsl:text>
<xsl:value-of select="position()"/>
</xsl:variable>
<tr valign="top">
<xsl:attribute name="class"><xsl:choose><xsl:when test="position() mod 2 =
1">rowodd</xsl:when><xsl:otherwise>roweven</xsl:otherwise></xsl:choose></xsl:attribute>
<td class="label">
<xsl:value-of select="$name"/>
<xsl:apply-templates select="@occ" mode="update"/>
</td>
<td>
<xsl:choose>
<xsl:when test="name($definition) = 'group'">
<input type="hidden" name="{$name}"/>
<table class="status" width="100%">
<xsl:apply-templates select="$definition/*" mode="update-def">
<xsl:with-param name="database-record" select="$database-record"/>
</xsl:apply-templates>
</table>
</xsl:when>
<xsl:when test="$definition/@readonly = 'yes'">
<xsl:apply-templates mode="update"/>
</xsl:when>
<xsl:when test="$definition/@type = 'file'">
<xsl:apply-templates mode="update"/>
<input name="{$name}" type="checkbox" value="&&delete;"/>
<xsl:value-of select="$definition/@boxtext"/>
</xsl:when>
<xsl:when test="$definition/@type = 'textarea'">
<textarea name="{$name}" rows="{$definition/@rows}" cols="{$definition/@cols}" wrap="virtual">
<xsl:apply-templates mode="update"/>
</textarea>
</xsl:when>
<xsl:otherwise>
<input name="{$name}" type="{$definition/@type}">
<xsl:attribute name="size">
<xsl:choose>
<xsl:when test="$definition/@size > 60">
<xsl:text>55</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$definition/@size"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:attribute name="value"><xsl:apply-templates mode="update"/></xsl:attribute>
</input>
<xsl:if test="$definition/@size > 60">
<xsl:text> ... [</xsl:text>
<xsl:value-of select="$definition/@size"/>
<xsl:text>]</xsl:text>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:template>
<!--
processing template
-->
<xsl:template match="fieldgroup" mode="update-def" name="update-field-or-group">
<xsl:param name="database-record" select="/.."/>
<xsl:variable name="definition" select="current()"/>
<xsl:variable name="occurrences" select="count($database-record/*[name()=$definition/@name])"/>
<xsl:comment>update-field-or-group; <xsl:value-of select="name()"/>='<xsl:value-of select="@name"/>', database node='<xsl:valueof
select="name($database-record)"/>'</xsl:comment>
<tr>
<td>
<xsl:apply-templates select="$database-record/*[name()=$definition/@name]" mode="update">
<xsl:with-param name="definition" select="$definition"/>
</xsl:apply-templates>
</td>
</tr>
<xsl:if test="@repeat='yes' or $occurrences=0">
<xsl:variable name="name">
<xsl:value-of select="@name"/>
<xsl:text>_</xsl:text>
<xsl:value-of select="$occurrences + 1"/>
</xsl:variable>
<tr valign="top">
<td class="label">
<xsl:value-of select="$name"/>
<xsl:text> [nieuw]</xsl:text>
</td>
<td>
<xsl:choose>
<xsl:when test="name() = 'group'">
<input type="hidden" name="{$name}"/>
<table class="status" width="100%">
<xsl:apply-templates select="$definition/*" mode="update-def">
<xsl:with-param name="database-record" select="/.."/>
</xsl:apply-templates>
</table>
</xsl:when>
<xsl:when test="@readonly = 'yes'">
<xsl:apply-templates mode="update-def"/>
</xsl:when>
<xsl:when test="@type = 'textarea'">
<textarea name="{$name}" rows="{@rows}" cols="{@cols}" wrap="virtual">
<xsl:apply-templates mode="update-def"/>
</textarea>
</xsl:when>
<xsl:otherwise>
<input name="{$name}" type="{@type}">
<xsl:attribute name="size">
<xsl:choose>
<xsl:when test="@size > 60">
<xsl:text>55</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@size"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:attribute name="value"><xsl:apply-templates mode="update"/></xsl:attribute>
</input>
<xsl:if test="@size > 60">
<xsl:text> ... [</xsl:text>
<xsl:value-of select="@size"/>
<xsl:text>]</xsl:text>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:if>
</xsl:template>
<!--
-->
</xsl:stylesheet>
<\pre>

2005-06-20

Een bibliotheek van xslt templates

Ik stel voor dat we, voordat iedereen allerlei template libraries gaat zitten maken, eerst wat algemene afspraken over dit soort dingen maken. Vorige week heb ik voor "het" korte format iets dergelijks gedaan tbv. de uitleen.

M.i. belangrijke aspecten hiervan (ivm. herbruikbaarheid) zijn:

- inhoud: geen enkele html-tag, wel tekst-indeling volgens standaards (ISBD), behalve evt. op het niveau van de titelbeschrijving zelf. In geen geval style elementen of van de omgeving (bv database of toepassing) afhankelijke zaken. Op titelbeschrijving-niveau kan evt. een class attribute zinvol zijn.

- naamgeving (mijn voorkeur: duidelijke namen voor de xslt's, waarbij niet de applicatie maar de inhoud duidelijk wordt. (bv. titelbeschrijving-kort voor de enige echte korte titelbeschrijving (en over de inhoud daarvan zal de bibliotheek - dus niet alleen onze afdeling - het eens moeten zijn, dus eerst bespreken in projectgroepvergadering van 1501), geen underscores maar koppeltekens in de naam)

- template-gebruik: nooit call-template gebruiken maar altijd apply-templates,
en altijd consequent gebruiken daarvan, dus een template voor elk element op elk niveau, inclusief dezelfde mode als de caller. Conditionele vaste teksten op het laagst mogelijke niveau; pre-literals
vóór en post-literals ná de inhoud, ook voor herhalingen (dus bv. komma-spatie vóór de volgende occurrencee en niet ná de vorige, en een punt ná de laatste). Dit klinkt misschien overdreven, maar het is essentieel voor doelmatig hergebruik. In een toepassing kunnnen dan waar nodig templates gemaakt worden die details anders doen. In de huidige situatie (zoals in de productie-omgeving gegroeid) kan dat niet, omdat geen rekening gehouden is met hergebruik van delen.

- literals: altijd als text element opnemen, niet "los" neerzetten. Tabs en returns (alleen als ze echt in elke denkbare situatie noodzakelijk zijn) opnemen als numerieke enttites ( resp
), niet met de tab- resp. enter-toets.

- alleen templates in zo'n bibliotheek opnemen die universeel bruikbaar zijn, en dus vooraf nadenken over mogelijke herbruikbaarheid (ook in toepassingen die nog niet bedacht zijn ;-).

- goed documenteren, bij voorkeur boven elke template een korte uitleg in een javadoc-compatibel commentaar.

zie titelbeschrijving-short.xslt in de productie-omgeving voor een (nog niet compleet) voorbeeld. Dit is een afgeleide van het belangrijkste deel van het beruchte "restregel" template (dat ooit bedoeld was als "de" korte titelbeschrijving), en bevat nog een aantal daarvan afkomstige fouten. En, inderdaad, de naam ervan moet eigenlijk titelbeschrijving-kort worden en de documentatie moet er nog bij.

Age Jan

2005-03-17

The first application using Oracle as underlying dbms

We are about ot publish our first bibliographic database on the web using Oracle. At this moment the ISRIC world soil information database, is still relying on the underlying Minisis database. The database used to be prepared offline and published using WebQuery. ISRIC wants to add and modify records via the web interface. Since this is a relatively simple application we decided it would be our first Oracle based application. Modifying records works fine. Now we are implementing xsd validation and ran into problems using the libxml libraries. We needed to implement the newest version and these are stricter. So we ran into problems with silly xslt mistakes which did not create any problems before. We have been debugging and everything is working again.

2005-03-16

What is WebQuery ?

WebQuery is the name of an interface that easily enables searching a XML database via a SRU (Search and retrieve by URL) like interface. It also enables you to update the records in the database via a form. It is the basis of our new Library Content Management System and it is developed by the application development and management department of Wageningen University and Research Library.

We started with WebQuery in 1994. It's first version was ready in february 1995. It started off as an interface to search Minisis databases, a relational database management system for documentary databases, running on HP3000's. It still works with this DBMS and also with a newer version 9 of Minisis running on windows servers. At the moment we are developing a version that will also work with Oracle databases. We do not use a lot of Oracle specific tools. We want it to work with any database that can handle XML well. We are now developing for a Linux server environment.

2005-03-03

WebQuery 5.24

Ik heb zojuist WebQuery 5.24 geactiveerd. Hierin zijn de volgende problemen opgelost. Er zijn geen nieuwe features toegevoegd.

de buitenste haakjes verdwijnen

wq_rel is nu onafhankelijk van de onderliggende database (altijd AND, OR of AND NOT, in hoofdletters). Hoe het in terechtkomt is nog afhankelijk van de database (oracle: NOT; minisis: AND NOT)

WebQuery aborteert niet meer ("premature end of script headers" melding) bij &ER; en andere macro's die met een E beginnen en niet tot een tag leiden

wq_fmt binnen &KY: wordt nu voorafgegaan door een ampersand in plaats van een vraagteken.

wq_mod is nu echt optioneel; in plaats daarvan mag ook het juiste keyword (new of update) voor het isn gezet worden (dus bv. <form action="/WebQuery/wwwtest/update/7" method="post">). In principe was dat al zo, het werkte alleen niet goed (er werd altijd gecontroleerd of wq_mod er was) en is daarom nog nooit gebruikt.

In de command-line versie (WebQueryGet resp WebQueryPost) is het probleem opgelost waardoor WebQuery aborteerde bij conversie-cgiparm files (met *hp3000 in de server-regel)

Age Jan

2004-11-30

WebQuery 5.21

Op verzoek van Pauline nog wat extra informatie.

Query syntax:

nieuw: de impliciete OR werkt nu op basis van de veldnaam. Dat betekent dat het niet meer nodig is om daarvoor wq_val te gebruiken. Hiermee is de laatste noodzaak voor het gebruik van wq_fld/wq_val verdwenen. wq_fld en wq_val mogen (liever niet, natuurlijk) nog wel worden gebruikt, met dezelfde effecten als tot nu toe.
de bestaande eisen worden consequenter gecontroleerd
Dat betekent dat veldnamen ook echt moeten bestaan (wordt in mindbsrv nog niet gecontroleerd, aliassen worden in WebQuery nu wel gecontroleerd), Bij wq_fld en wq_lim komt dus nu een foutmelding als de bijbehorende aliasdefinitie (field xxx=yyy in de cgiparm file) er niet is.
Het betekent ook dat direct na een wq_rel een veldnaam moet komen (dus geen wq_rel, wq_max, etc.), en dat wq_val alleen mag volgen op wq_fld, wq_lim of een veldnaam, dus niet op wq_max, wq_rel etc.

Nog even iets over veldnamen en aliassen: WebQuery splitst de veldnaam in een naam en occurrence-informatie op basis van de underscore. Voor updates was dat al lang het geval, maar omdat in het post-minisis tijdperk de query op de zelfde manier naar de backend server gestuurd wordt als de update-gegevens gebeurt dat nu ook voor queries. Zet dus in veldnamen en aliassen nooit een underscore, dat kan later tot fouten leiden. Spaties mogen wel in aliassen, niet in veldnamen.

"field type publikatie=publikatietype" mag wel. WebQuery vervangt dan de alias "type publikatie" door de veldnaam "publikatietype" (bij minisis is dit onzin, omdat de veldnaam (de minisis mnemonic) maximaal 6 tekens lang is, bij oracle kan dit wel)
"field publikatietype=type publikatie" mag niet (spatie in de veldnaam)

Op dit moment werken aliassen alleen bij wq_fld en wq_lim.

Age Jan

2004-11-25

voorbeeld van het gebruik van zelfbedachte parameters in cgiparm file

Hopelijk ter verduidelijking van een van de minder bekende mogelijkheden van WebQuery het volgende naar aanleiding van een vraag die ik vanmiddag kreeg.

Soms is het handig om een zelfbedachte parameter te introduceren in de cgiparm file. Een voorbeeld daarvan is de situatie waarin je een aantal parameters meegeeft aan een xslt. In dat geval moet je er rekening mee houden dat alle xslt-parameters met de zelfde suffix (of zonder) samen één parameter zijn voor WebQuery. Als je een aantal xslt-parameters hebt die altijd de zelfde waarde hebben, en één die bij een aantal suffixen andere waarden heeft, moet je dus alle

xslt-parameters voor elke suffix opnemen, dus als de basisset xslt-parameters er uit ziet als:

xslt-parameter aap='aap'
xslt-parameter xslt='&&xslt;'
xslt-parameter service='&&SERVICE;'
xslt-parameter andere-service='&&SCRIPT_NAME;/andereservice'

en er in de praktijk tien verschillende mogelijkheden zijn voor de waarde van andere-service, moet je in totaal 40 xslt-parameter regels opnemen, vier voor elke mogelijke waarde van andere-service.

Niet dus... er is een andere mogelijkheid, die er maar 14 nodig heeft. Daarvoor wijzig je de laatste regel in

xslt-parameter andere-service='&&SCRIPT_NAME;/&&andereservice;'

waarmee je aangeeft dat je niet de tekst "andereservice" maar de inhoud van de cgiparm parameter "andereservice" wilt gebruiken. Vervolgens definieer je die voor elke suffix, dus bijvoorbeeld

andereservice standaard-andereservice
andereservice_een alternatieve-andereservice
andereservice_twee nog-een-andere
andereservice_drie &&SERVICE;
hetkannnogcomplexer derde-waarde

etc. Als wq_sfx=een wordt meegegeven, krijgt xs xslt-parameter "andere-service" dus de waarde 'alternatieve-andereservice' (inclusief de enkele quotes).

Zoals je ziet, kan je ook de waarde van de meeste andere cgiparm parameters, zoals de naam van de gebruikte xslt ook meegeven als xslt-parameter.

Age Jan

2004-11-15

Overbodige variabelen in xslt's

Bij het beantwoorden van een vraag van Marc viel mijn oog (in dit geval in sfx-button.xslt) op een sutiatie die ik de laatste tijd vaker tegenkwam, waarin eerst een variabel wordt gevuld, die dan vervolgens wordt afgedrukt, zoals in het volgende fragment:
<xsl:when test="artikel/auteur/corporatie/naam and artikel/auteur/corporatie/naam!=''"><xsl:variable name="must-urlencode-it">
<xsl:call-template name="urlencode">
<xsl:with-param select="artikel/auteur/corporatie/naam" name="string">
</xsl:call-template>
</xsl:variable>
<xsl:text>&aulast=</xsl:text>
<xsl:value-of select="$must-urlencode-it">
</xsl:when<>/xsl:value-of></xsl:with-param>
Dit fragment doet precies het zelfde als het volgende:
<xsl:when test="artikel/auteur/corporatie/naam and artikel/auteur/corporatie/naam!=''">
<xsl:text>&aulast=</xsl:text>
<xsl:call-template name="urlencode">
<\xsl:with-param select="artikel/auteur/corporatie/naam" name="string">
</xsl:call-template>
</xsl:when></xsl:with-param>
en omdat lege velden niet in de database mogen voorkomen kan het nog eenvoudiger:
<xsl:when test="artikel/auteur/corporatie/naam">
<xsl:text>&aulast=</xsl:text>
<\xsl:call-template name="urlencode">
<xsl:with-param select="artikel/auteur/corporatie/naam" name="string">
</xsl:call-template>
</xsl:when></xsl:with-param>
Vooral omdat het in dit geval gaat om de inhoud van een attribuut, waardoor xmlspy tientallen van dit soort constructies op één regel zet, zou ik iedereen willen vragen niet alleen te zoeken naar een constructie die werkt, maar ook even na te denken over de vraag of het niet eenvoudiger kan. Hier scheelt het m.i. behoorlijk in de leesbaarheid, en dus in de onderhoudbaarheid.
Age Jan

2004-09-28

WebQuery batch interface

Op verzoek van Peter heb ik, op basis van de scripts die ik op de HP3000 gebruikte voor het testen van WebQuery, een script gemaakt waarmee je WebQuery redelijk eenvoudig in een shellscript kunt aanroepen. Het staat in /usr/local/bin op de lx1 en is dus zonder enige aanpassing voor iedereen bruikbaar.
De aanroep is: WebQueryGet <webqueryservice><query>Of : WebQueryPost <webqueryservice><query>
(inderdaad, twee namen voor één script, de eerste voor een GET, de tweede voor een POST)
In beide gevallen zijn de zelfde twee parameters nodig:
Met <webqueryservice>bedoel ik de naam die in de websituatie gebruikt wordt na /WebQuery/ . De query moet op dezelfde manier gegeven worden als in een normale GET.
Cookies worden vastgehouden in een tijdelijk bestand waarvan de naam is afgeleid van de het procesnummer van de shell van waaruit je het script aanroept. Dat betekent dat je die kunt terugvinden via die shell. Vanuit die shell is die file bereikbaar is met de naam "/tmp/WQ$$.cookie". Het script gebruikt uit die file het WebQueryDatabase cookie, zodat je ook secured databases kunt benaderen via een username. Geef daarvoor de volgende twee opdrachten binnen dezelfde shell:
1) WebQueryPost "" "@=login&A=<denaamvandegebruiker>&B=<zijnofhaarpassword>"
Waarbij de < > en wat er tussen staat vervangen worden door resp. gebruikersnaam en password.
2) de WebQueryPost of WebQueryGet waarmee je de database raadpleegt of wijzigt.

De uitvoer van het script verschijnt in stdout, eventuele foutmeldingen in stderr. Het is dus verstandig om die met redirects en/of een pipe verder te verwerken.
Een voorbeeld dat het resultaat neerzet in de file resultaat en fouten meldt in de file foutmeldingen is:
WebQueryGet "clcwww" "ti=water&ti=oil" >resultaat 2>foutmeldingen

Age Jan

2004-08-23

eenvoudiger minisis conversie xslt

Op verzoek van Peter heb ik een mogelijkheid in WebQuery ingebouwd om de minisis-conversie te vereenvoudigen. Geen gedoe meer met aanroepen van de minisis-conversie template in je eigen templates. Als in een cgiparm-file een sterretje vóór de servernaam hp3000.library.wur.nl gezet wordt, doet WebQuery een automagische conversie, dat wil zeggen dat de conversie uitgevoerd wordt zonder dat je er verder iets voor hoeft te doen resp mag doen. Je xslt krijgt dan ALTIJD een geconverteerd bestand aangeboden. Zelfs met wq_sfx=XML wordt de conversie uitgevoerd.
Als voorbeeld heb ik de ejournals.xslt uit de testomgeving gekopieerd naar xxxejournals.xslt, en de wwwsrc.WebQuery cgiparm file naar wwwsrcc.WebQuery. Die kopieen werken op de nieuwe manier, als je tenminste ook nog de testversie (5.19) van WebQuery (dus bijvoorbeeld
http://test.library.wur.nl/test/WebQuery/wwwsrcc?ti=science&wq_max=10&wq_fmt=xml ) gebruikt.

Age Jan

2004-08-17

WebQuery Request cookie problemen

Vanmorgen bleek bij het uietzoeken van een support call dat het WebQueryRequest cookie, dat gezet wordt als iemand een database probeert te benaderen zonder op de juiste manier te zijn ingelogd, soms ongewenste bij-effecten heeft.
Het werkt als volgt: als een niet-toegankelijke pagina wordt benaderd wordt het WebQueryRequest cookie meegegeven, waarin staat wat er niet lukte. Meestal wordt dan een login pagina getoond. Na het inloggen wordt dan de mogelijkheid gegeven om de oorspronkelijke pagina opnieuw op te roepen, of wordt dat automatisch gedaan.
Het probleem is, dat als, zoals op de desktop, geen login pagina getoond wordt (maar de "my library" login knop), het WebQueryRequest cookie nog een tijd blijft bestaan. Wordt binnen die tijd een (niet aan de vorige pagina gerelateerde) login pagina getoond, bijvoorbeeld die van wurpubrd, dan zal, omdat het cookie voorrang heeft boven de eventueel in die pagina aanwezige C- en D velden, gecontroleerd worden of de voorgaande pagina (in het vorbeeldgeval een profielu pagina) nu wel toegankelijk is. Als dit niet het geval is verschijnt een foutmelding, die aangeeft dat een voor de gebruiker nu niet relevante pagina niet toegankelijk is.
Omdat WebQuery niet kan weten of het WebQueryRequest cookie door zo'n constructie zinloos geworden is, is op dit moment de enige oplossing om eerst expliciet uit te loggen en dan opnieuw in te loggen (uitloggen verwijdert het cookie). Een betere oplossing is m.i. om in de desktop op een andere manier te controleren of iemand is ingelogd. Daarvoor zou het in 5.17 geintroduceerde WebQueryUser cookie gebruikt kunnen worden, of een ander soort who-am-i uitvoer.
Wat is jullie mening hierover?
, Age Jan

WebQuery 5.18

Zojuist heb ik WebQuery 5.18 geactiveerd. Deze versie bevat twee langverwachte nieuwe query-parameters: wq_inf en wq_par.
De waarde die aan wq_inf wordt meegegeven wordt door WebQuery wel als query-element gezien, maar er wordt NIETS mee gedaan. Hij wordt dus ook niet doorgegeven aan de database-server. Maar omdat je in de cgiparm file nu ook alle query-elementen kunt benaderen kan je hem bijvoorbeeld wel doorgeven aan een xslt als xslt-parameterwaarde. Het voorbeeld:
xslt-parameter css='rsc/stijl/&&wq_inf1;_01.css'
Zorgt ervoor dat je via een dropdown box de kleur van de toegepaste standaardstijl kunt wijzigen. Dat geeft gelijk aan dat je eigenlijk verplicht bent om in de xslt te controleren of een op die manier doorgegeven variabele een geldige waarde heeft, want de eindgebruiker kan er in principe ALLES in zetten. Door volgnummers te gebruiken kan je meer dan één wq_inf waarde gebruiken. Het oneigenlijk gebruik van de gewone query-elementen om iets aan een xslt door te geven kan dus weggewerkt worden.
De waarde die aan wq_par wordt meegegeven moet er één zijn uit het volgende rijtje: "open", "(", "close", ")". Hiermee kan je prioriteiten aangeven binnen de query. Deze functionaliteit vervangt die van wq_lim. Voorlopig blijft wq_lim als invoer nog wel ondersteund; intern wordt het omgezet naar wq_par's.
Als gevolg van deze twee wijzigingen kan ik geen enkele reden meer bedenken om wq_qry nog te gebruiken, afgezien van de drie redenen waarom die mogelijkheid bestaat, namelijk het HANDMATIG geven van een complete query DOOR DE EINDGEBRUIKER, het weergeven van de query op het scherm en (voorlopig nog) het via een hidden field doorgeven van de "previous query" aan de volgende WebQuery call. Omdat de inhoud van het wq_qry element afhankelijk is van de achterliggende database-server zal elk ander gebruik van wq_qry op enig moment zonder nadere aankondiging tot problemen leiden.
Het probleem dat de impliciete OR (een veld gevolgd door eeen of meer "losse" wq_val elementen) niet van haakjes voorzien werd is eveneens opgelost.
De controle van de meegegeven query-elementen is iets aangepast, waardoor verkeerd geplaatste variabelen een betere foutmelding geven.
De debug-info wordt nu in xml gegeven (uiteraard alleen in debug-mode). Debug-mode is nu ook onafhankelijk van je ip-adres in te stellen met "wq_dbg=on" (zet dat svp NOOIT in een productie-pagina, want dan krijgt iedereen het te zien)
Met vriendelijke groet,
Age Jan Kuperus

2004-02-17

browsers en character sets

De bekende "browsers" gaan verwarrend en inconsequent om met charactersets. Als gevolg daarvan krijgt WebQuery problemen bij het invoeren van gegevens via formulieren. De theoretische oplossing is toevoeging van het attribuut accept-charset="iso-8859-1" aan alle formulierdefinities. Bij Mozilla en wellicht ook bij alle andere echte webbrowsers lost dit in één klap het probleem op. Bij de Internet Exploder van Microschoft natuurlijk niet, want die trekt zich van standaards nooit wat aan. Als het formulier op een html pagina staat (daarbij geeft Apache altijd aan dat het iso-8859-1 is), wordt ook iets dergelijks teruggegeven (zoals bekend met een paar niet-standaard tekens in de reeks 0x80-0x9f). Als het formulier op een pagina staat die door WebQuery of php in utf-8 gemaakt wordt, wordt echter utf-8 teruggestuurd, ongeacht of er accept-charset="iso-8859-1" in de fom tag staat of niet.
We hebben een aantal mogelijkheden om dit probleem aan te pakken:
  1. IE uitbannen. Dit heeft mijn voorkeur maar is helaas niet realistisch in onze omgeving.
  2. Idem, maar alleen voor het invoeren en wijzigen. Onhandig voor de gebruiker.
  3. Altijd iso-8859-1 genereren in WebQuery en php scripts. Voor mij is dit de tweede keus, maar ik weet niet hoeveel extra werk dat oplevert.
  4. Een hidden field met een vaste naam en inhoud opnemen in elk formulier, waarmee we kunnen testen wat er teruggegeven wordt, en op basis waarvan we dan een extra vertaalslag inbouwen van utf-8 naar iso-8859-

1. Technisch haalbaar, maar niet-standaard, niet 100% zeker, en levert extra werk op. Wat mij betreft hooguit als noodoplossing.
Verwachten jullie problemen bij oplossing 3, of hebben jullie zelf een ander voorkeur? , Age Jan

2004-02-11

Het gebruik van exslt

Naar aanleiding van een vraag van Peter gistermiddag heb ik gisteravond een voorbeeld uitgewerkt van het gebruik van een van de belangrijkste xslt extensies. De essentie van de vraag was of het mogelijk is om een transformatie in twee (of meer) stappen uit te voeren binnen 1 xslt. Het antwoord daarop is in principe ja, maar er is een functie voor nodig die pas in xslt 2.0 standaard wordt. Veel xslt processoren hebben hem echter wel, of ze hebben er een workaround voor.Omdat de vraag voortkwam uit een discussie met Irene of er met een xslt een index op de rubriekendatabase gemaakt kon worden op basis van twee velden is het voorbeeld een xslt die een complete rubriekenlijst geeft, gevolgd door een engelstalige en een nederlandstalige index. De benodigde functionaliteit voor het aanroepen van de xslt extensies zit nog niet in WebQuery, en dat was meteen een aanleiding om te laten zien hoe je zoiets vanaf de linux command line kunt regelen. Voor degenen die meteen resultaten willen zien:xsltproc /DATA/www/WebQuery/xslt/rubriekenlijst-met-index.xslt "http://library.wur.nl/WebQuery/rubrik?wq_qry=isn%201%2f100&wq_fmt=xml&wq_sfx=XML"De xslt is voorzien van (hopelijk voldoende) commentaar. Toch wil ik er nog een aantal dingen aan toevoegen.Allereerst een motivatie voor het gebruik van de command line zoals hierboven. Op die manier kan je een dergelijke lijst ook heel eenvoudig automatisch elke dag/week/maand laten genereren als html file, door de output te redirecten en het geheel via een van de standaard schedulers (mijn voorkeur gaat uit naar cron, daar kom ik maandag op terug) op vaste momenten te laten uitvoeren. Vergelijkbaar met een job op de hp3000. Voor de hele rubrieken database duurt de query nu ongeveer 35 seconden, misschien net te lang om hem elke keer dat iemand hem nodig heeft uit te laten voeren. (het gaat hier uiteindelijk om een vervanging van een papieren versie; het zou me niet verbazen als hij meteen geprint wordt en dan pas een half jaar later weer eens opnieuw gevraagd wordt.Waarom niet cachen, zoals nu bij diverse toepassingen gebeurt? In de eerste plaats omdat ik vind dat dat een onjuist mechanisme is. Zoiets zou, als het al nodig is, binnen WebQuery, of nog beter, binnen Apache geregeld moeten worden, en niet binnen de toepassing. Daarnaast wordt steeds het zelfde wiel enigszins gewijzigd opnieuw uitgevonden, bewijst de huidige praktijk dat het (nog) niet probleemloos werkt, en de plaatsing van cache files op min of meer willekeurige plaatsen binnnen de website leidt tot security problemen.Dan nog een paar kanttekeningen bij de xslt. Ik heb geprobeerd daarin duidelijk te maken hoe je zo'n probleem (een basislijst met index(en)) kunt aanpakken, niet hoe je er een mooie lijst van maakt (de layout is zo simpel mogelijk).De basislijst maakt gebruik van het feit dat er in de database-uitvoer alleen tekst voorkomt binnen gewone velden en subvelden. Daardoor kan de volledige uitvoer ervan geregeld worden met het text() template. Dat drukt de naam van zijn parent node af, en zijn eigen inhoud. Zou er wel tekst (denk aan alleen maar een linefeed) op andere plaatsen kunnen voorkomen, dan wordt de uitvoer onleesbaar. "linefeed off" in de cgiparm file is dus een must voor deze oplossing.Nadat de basislijst gemaakt is komt de belangrijkste "truc". De variabele "index" wordt opgebouwd als result tree fragment. De templates die aangeroepen worden met mode="index" zorgen dus niet voor uitvoer in het eindresultaat, maar alleen voor het "vullen" van deze variabele.Ik heb gekozen voor een enkel template dat voor elke rubrieksnaam een keer wordt aangeroepen, met de taal en de code als parameters. Dat is m.i. de duidelijkste en meest declaratieve manier. Inplaats van de namen van alle velden had het match attribute ook "*" kunnen zijn, maar dit leek me duidelijker. Bij de aanroep is de taal als constante (dus als " ' e n ' " resp. " ' n l ' ") opgegeven, omdat die niet in de database voorkomt, maar afgeleid is uit de veldnaam.In het root template zou je na het aanmaken van de index deze kunnen controleren door de regeltoe te voegen. Daarmee zet je de volledige index als nodeset in de uitvoer. Dat komt dus (bijna) op het zelfde neer als wanneer de apply-templates die nu binnen de variabele-definitie staat daar buiten had gestaan.Helaas is dat ongeveer het enige dat je in xslt 1.0 met een result tree fragment, want dat is deze variable nu, kunt doen. Er is echter een extensie-functie die hem kan omzetten naar een node-set. Die gebruik ik in de rest van het root template tweemaal, een keer voor elke index. Omdat de attributen van het rubriek-element in de index-variabele alle gegevens bevat is de template die de uiteindelijk uitvoer ervan verzorgt nu heel eenvoudig.Ik hoop dat het bovenstaande weer wat vragen wegneemt. Het zal ongetwijfeld nieuwe oproepen, die hoor ik dan maandag wel. We zullen het dan, wat mij betreft, hebben over de manier waarop je regelmatig terugkerende zaken in unix kan regelen.Groeten,Age Jan