KU Leuven Campus De Nayer Industrieel ingenieur Opleiding Elektronica-ICT 3e academisch bachelorjaar W E B T E C H N O L O G I E Academiejaar 2014-15 Lesgever: J. Vennekens Cursustekst ism. H. Crauwels
Inhoudsopgave 1 Overzicht: xhtml, css 1 1.1 Extensible hypertext markup language......................... 1 1.2 Cascading style sheets.................................. 2 1.3 PHP: include....................................... 3 2 De server 5 2.1 Apache server....................................... 5 2.2 Het HTTP-protocol................................... 5 3 Dynamische webpagina s met Python 10 3.1 Tekst-manipulaties met Python............................. 10 3.1.1 Nuttige string functies.............................. 10 3.1.2 Reguliere expressies............................... 10 3.2 Toepassing: verwerking van form gegevens....................... 13 3.2.1 Overzicht van CGI omgevingsvariabelen.................... 13 3.2.2 INPUT elementen................................ 14 3.2.3 SELECT element................................. 16 3.2.4 TEXTAREA element.............................. 17 3.3 Opmerkingen....................................... 17 3.3.1 Testen van een CGI script............................ 17 3.3.2 Veiligheid..................................... 18 4 PHP: Hypertext Preprocessor 19 4.1 Inleiding.......................................... 19 4.2 Basis syntax........................................ 19 4.3 Een eenvoudige form................................... 22 4.4 Een form bestaande uit meerdere pagina s....................... 25 4.5 Datums en tijden..................................... 26 4.6 Werken met bestanden.................................. 29 4.7 Cookies.......................................... 33 5 JavaScript 35 5.1 Ontstaan......................................... 35 5.2 De objecthiërarchie.................................... 35 5.3 Variabelen, operatoren en functies........................... 36 5.4 De controlestructuren.................................. 37 5.5 Zelf gemaakte objecten.................................. 38 5.6 Events........................................... 40 5.6.1 Types....................................... 40 5.6.2 Eventhandlers................................... 41 5.6.3 Events emuleren................................. 42 5.7 Het window object.................................... 43 5.8 Het document object................................... 47 5.8.1 Properties..................................... 47 5.8.2 Methods...................................... 47 5.9 Opmerkingen....................................... 54 6 ADOdb bibliotheek voor PHP 55 6.1 Intikken en uitvoeren van een query......................... 55 6.2 Database abstraction library.............................. 56 6.3 Voorbeeld met specifieke mysql connectie....................... 57 6.4 Met gebruik van de abstractielaag ADODB...................... 58 I
6.5 Verwerken van de resultaten set............................. 60 6.6 Resultset in een twee-dimensionale array........................ 62 6.7 Functies: recordcount en fieldcount........................... 63 6.8 Data manipulatie: insert en delete........................... 64 6.9 Selectie vam beperkt aantal rijen............................ 65 6.10 Voorbereiden van een SQL statement.......................... 66 6.11 Transacties........................................ 67 6.12 Cached queries...................................... 68 6.13 Genereren van html................................... 70 6.14 Omvorming naar specifieke formaten.......................... 71 7 AJAX: Asynchronous Javascript And XML 74 7.1 Werking.......................................... 74 7.2 Voorbeeld......................................... 76 7.3 Het XMLHttpRequest object.............................. 79 7.4 Voorbeeld: master-detail select boxen......................... 80 7.5 Andere mogelijkheden.................................. 82 8 Beveiliging voor HTTP 83 9 XML: extensible Markup Language 86 9.1 Definitie.......................................... 86 9.1.1 Inleiding...................................... 86 9.1.2 Eigenschappen.................................. 87 9.1.3 XML versus HTML............................... 88 9.2 XML syntax........................................ 88 9.2.1 Elementen..................................... 88 9.2.2 Namespaces.................................... 90 9.2.3 Parsing...................................... 90 9.3 Document Type Definition................................ 90 9.4 Verwerking........................................ 93 9.4.1 DOM....................................... 94 9.4.2 SAX........................................ 94 9.5 XML presentatie..................................... 95 9.5.1 CSS........................................ 96 9.5.2 XSLT en XSL:FO................................ 96 9.5.3 Client of server.................................. 96 9.6 Toepassingen van XML................................. 97 9.7 Referenties........................................ 98 10 XML Schema 100 10.1 Verantwoording...................................... 100 10.2 Schema definitie..................................... 101 10.3 Terminologie....................................... 102 10.4 Enkelvoudige types.................................... 102 10.5 Samengestelde types................................... 104 10.5.1 Lege inhoud.................................... 105 10.5.2 Enkelvoudige inhoud............................... 105 10.5.3 Samengestelde inhoud.............................. 105 10.5.4 Gemengde inhoud................................ 108 10.6 Attributen......................................... 108 10.7 Groepen.......................................... 109 10.8 Uitbreidbare schema s.................................. 110 II
11 XPath 111 11.1 XPath Introduction................................... 111 11.2 XPath Nodes....................................... 111 11.3 XPath Syntax....................................... 112 11.4 XPath Axes........................................ 114 11.5 XPath Operators..................................... 115 12 XQuery 116 12.1 Introduction........................................ 116 12.1.1 XQuery Example................................. 116 12.2 XQuery FLWOR Expressions.............................. 117 12.2.1 XQuery FLWOR + HTML........................... 118 12.2.2 XQuery Terms.................................. 119 12.3 XQuery Syntax...................................... 119 12.3.1 XQuery Adding Elements and Attributes................... 120 12.4 XQuery Selecting and Filtering............................. 121 13 XSL: EXtensible Stylesheet Language 123 13.1 Overview......................................... 123 13.2 Introduction to XSLT.................................. 123 13.2.1 Example study: How to transform XML into XHTML using XSLT..... 123 13.2.2 Template Element................................ 124 13.2.3 Value-of Element................................. 125 13.2.4 For-each Element................................. 126 13.2.5 Sort Element................................... 126 13.2.6 If Element..................................... 127 13.2.7 Choose Element................................. 127 13.2.8 Apply-templates Element............................ 128 13.2.9 XSLT Elements Reference............................ 129 13.2.10 XSLT Functions................................. 129 13.3 Voorbeeld......................................... 131 13.4 Introduction to XSL-FO................................. 133 13.4.1 XSL-FO Documents............................... 133 13.4.2 XSL-FO Areas.................................. 134 13.4.3 XSL-FO Output................................. 134 13.4.4 XSL-FO Flow................................... 136 13.4.5 XSL-FO Pages.................................. 136 13.4.6 XSL-FO Lists................................... 137 13.4.7 XSL-FO Tables.................................. 138 13.4.8 XSL-FO and XSLT................................ 139 13.4.9 XSL-FO Software................................. 140 14 Toepassingen van XML 141 14.1 XTM............................................ 141 14.2 SOAP........................................... 141 14.3 XFORMS......................................... 143 14.4 SMIL........................................... 144 14.5 RDF............................................ 146 14.6 CML............................................ 148 A HTML 4 Block & Level Elements en Inline Elements 149 B XML opgaven 150 III
1 Overzicht: xhtml, css 1.1 Extensible hypertext markup language XHTML is een beschrijvende set van codes voor het indelen en de structuur van een webdocument. Een webpagina moet als een structurele verzameling van gegevens gezien worden. De belangrijkste structuurelementen van een pagina zijn: kopteksten, alineateksten, lijsten, afbeeldingen, hyperlinks en logische secties. De taak van XHTML is om de gegevens op een pagina logisch te structureren: bijvoorbeeld welke tekst is een koptekst, welke tekst een alinea en waar een afbeelding moet komen. Er zijn verschillende subtypes van XHTML, maar in deze cursus zal met de strengste vorm gewerkt worden: XHTML 1.0 Strict: webdocumenten die geen verouderde elementen bevatten en vrij zijn van alle opmaakkenmerken. In het XHTML bestand moet door middel van DOCTYPE het type aangegeven worden: <?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/tr/xhtml/dtd/xhtml1-strict.dtd"> Omdat XHTML gebaseerd is op XML syntax, moet op de eerste lijn van een XHTML bestand een XML declaratie gegeven worden. Deze lijn bevat de XML versie en de character-encoding. In het voorbeeld wordt aangegeven dat het document conform de 1.0 specificatie van XML is en dat de ISO-8859-1 (Latin-1/West European) character set gebruikt wordt. De codes voor een koptekst bijvoorbeeld zijn <h1>... </h1>. Hoe deze koptekst in de browser weergegeven wordt, wordt bepaald door de basisinstellingen van de browser. Het is mogelijk om zelf, als ontwerper van de pagina, ook de weergave van de structuurelementen te bepalen. Maar dit gebeurt niet met XHTML. Hiervoor moet een aanvullende techniek gebruikt worden: CSS (Cascading Style Sheets) dient voor het opmaken van gegevens. Kenmerken van XHTML-documenten. Een XHTML-document is een kaal tekstbestand en bestaat uit pure ASCII-tekst. Het bevat geen codes voor bijvoorbeeld paginanummering, regelafstand of andere formatteringsaspecten. Daardoor is een webpagina platformonafhankelijk. Een XHTML-bestand bestaat uit tekst en tags: de tekst is de inhoud van de webpagina: datgene wat je via het web wil communiceren naar andere internetgebruikers; een tag is de code die u als ontwerper aanbrengt om de tekst te structureren (of vorm te geven); tags worden aangegeven door punthaken ( < en > ): <tagnaam>; de tagnaam wordt in kleine letters geschreven. Om een structuurelement aan te geven worden telkens twee tags gebruikt: een openingstag en een sluittag (gelijk aan de begintag, voorafgegaan door een slash, /). Bij veel tags kan een aantal extra elementen opgegeven worden waarmee de functionaliteit van een tag vergroot wordt: Zo n attribuut bestaat altijd uit een naam en een waarde, van elkaar gescheiden door een = teken. Een attribuut wordt altijd in de openingstag opgenomen. Een tag kan meerdere attributen hebben; deze attributen en de tagnaam worden van elkaar gescheiden door middel van een spatie. Tussen naam en waarde staan geen spaties; de naam wordt in kleine letters geschreven, de waarde tussen dubbele aanhalingstekens. Tussen begin- en eindtag staat normaal tekst die moet weergegeven worden. Er bestaan ook tags waarachter geen tekst volgt. De tag <img> dient om een afbeelding op een webpagina te plaatsen. Er is echter geen bijhorende tekst en de begintag zou dus direct moeten gevolgd worden door de 1
sluittag </img>. Men spreekt hier van een leeg element. Hiervoor bestaat een verkorte notatie: op het einde van de begintag wordt direct een slash geplaatst, gevolgd door de punthaak ( />). Er zijn twee soorten structuurelementen: block-level en inline (zie ook appendix A): basistags: html, head, body, h1, h2, h3, h4, h5, h6, p, pre, blockquote, hr, br; linking: a met href en name attributen; prentjes: img en imagemap; lijsten: ul, ol, li, dl, dd, dt; tabellen: table, caption, th, tr, td, attributen rowspan en colspan; formulieren: form met attributen method, action en enctype; tekstvelden: button; input met types text, checkbox, radio, file, hidden, submit, reset, andere formulierelementen: select met option; textarea; fieldset met legend. Validatie. Om na te gaan of een pagina aan alle regels voldoet, kan zo n pagina gevalideerd worden. Het W3C heeft een valideringsservice op validator.w3.org. De Validation Markup Service controleert HTML- en XHTML-documenten op correctheid. Fouten worden gemeld en toegelicht. Specifieke CSS validering kan gebeuren op jigsaw.w3.org/css-validator. Een lokale valideringsservice (zowel XHTML als CSS) is te vinden op http://validator.denayer.wenk.be 1.2 Cascading style sheets Een style sheet is een verzameling stijlregels, die bepalen hoe elementen in een document door de browser weergegeven moeten worden. De stijlregels kunnen bijvoorbeeld betrekking hebben op het lettertype, de lettergrootte, de kleur van de tekst, de achtergrondkleur en de uitlijning, maar ook op het inspringen en de plaats in het browservenster. Style sheets kunnen op verschillende manieren aan de elementen in een HTML-document gekoppeld worden: een inline stijl, een stijlblok en een extern stijlblad. Hiervoor kunnen onder meer de volgende elementen gebruikt worden: style, meta en link. Elke CSS-regel bestaat uit twee hoofdonderdelen: selector : bepaalt op welk element de regel van toepassing is: een HTML-element of een zelfbenoemd gebied van een pagina (bijvoorbeeld met <div> of <span>); declaratie : de instelling voor de opmaak van het element bestaat uit een eigenschap (keyword) en de waarde (value) van die eigenschap. Een selector kan uit één of meer declaraties bestaan; elke declaratie wordt afgesloten met een puntkomma (;). Eigenschap en waarde binnen een declaratie worden van elkaar gescheiden met een dubbelpunt (:). Een voorbeeld voor specificatie van het HTML-element body: body { background-color: silver; color: white; font-family: verdana, arial; } Enkele stijleigenschappen : 2
de box de margin de border de padding breedte en hoogte plaatsing van elementen regelhoogte indeling zwevende elementen positioneren lettertypen kleur en achtergrond tekst lijsten margin-top, margin-right, margin-bottom, margin-left en margin border-style, border-color, border-top-width, border-right-width, border-bottom-width, border-left-width, border-width, border-top, border-right, border-bottom, border-left en border padding-top, padding-right, padding-bottom, padding-left en padding width, height line-height en vertical-align display en white-space float en clear position, top, right, bottom, left, z-index, clip, overflow en visibility font-family, font-variant, font-style, font-weight, font-size en font color, background-color, background-image, background-repeat, background-attachment, background-position en background word-spacing, letter-spacing, text-decoration, text-transform, textalign en text-indent list-style-type, list-style-image, list-style-position en list-style Plaatsbepaling. Normaal wordt de inhoud van een HTML-document van boven naar beneden in de browser weergegeven. De volgorde waarin de elementen in de code staan, bepaalt de weergave op het beeldscherm. Bijvoorbeeld, twee tekstkolommen naast elkaar of aan de zijkanten geplaatste navigatiebalken is in pure XHTML onmogelijk. Een complexe weblay-out realiseren met tabellen is tegen de geest van het gescheiden houden van structuur (inhoud) en lay-out (opmaak), want een tabel is een structuurelement. Een deel van CSS is gericht op de plaatsing van inhoud (CSS-P) en kan bij voldoende doorzettingsvermogen aangewend worden om de gewenste layout te realiseren. Het visual formatting model bepaalt hoe elementen zich gedragen of met andere woorden de weergave: elk element maakt nul of meer blokken en elk type blok heeft zijn eigen weergave kenmerken. Koppen en alinea s worden normaal onder elkaar geplaatst terwijl hyperlinks en afbeeldingen naast elkaar gezet worden. Noteer dat het boxmodel (zie hoger) alleen de afmetingen van elementen behandelt. Het positioneringsschema bepaalt hoe de inhoud op het scherm wordt geplaatst: normaal, zwevend of absoluut. Daarnaast kan gebruik gemaakt worden van lagen (layer) waarbij tekst over een afbeelding kan gelegd worden zonder daarbij de hele afbeelding af te dekken. Een indexwaarde bepaalt in welke volgorde de inhoud op elkaar gelegd wordt. 1.3 PHP: include PHP is een server-side scripting taal (zie verder). Als alternatief voor server-side-include wordt in dit overzichtshoofdstuk toch al één PHP constructie vermeld. Je kan de inhoud van een bestand aan een PHP bestand toevoegen voordat deze op de server gestart wordt met de include of de require functie. Hiermee kan je headers, footers of andere elementen (bijv. menu s) gemakkelijk toevoegen aan verschillende pagina s. Het updaten van je site wordt een stuk gemakkelijker. Je kunt gedeelten die telkens worden weergegeven op je pagina s opslaan in een extern bestand en de inhoud van dit bestand laten inlezen in de betreffende pagina. Je kunt hier bijvoorbeeld denken aan een menu. Dit menu is hetzelfde voor iedere pagina. Als je iets wilt veranderen aan het menu, dan moet je alle HTML pagina s van je site wijzigen. Dat kost je een heleboel tijd natuurlijk, zeker als je site uit tientallen of zelfs honderden pagina s bestaat. 3
In plaats daarvan kan het menu opgeslagen worden in een extern bestand en geinclude worden in de pagina. Wil je iets veranderen aan het menu? Dan hoef je nu alleen dit ene externe bestand waar het menu in staat te wijzigen. De veranderingen zie je daarna op alle pagina s van je site. Je bespaart hierdoor dus een hoop tijd. Verder wordt ook de totale schijfruimte die je site nodig heeft kleiner, doordat je sommige codes opslaat in één bestand en je ze niet meer in iedere pagina hoeft op te nemen. De include() functie neemt alle inhoud van een bepaald bestand en neemt dit op in de pagina waarin de include functie staat. Voorbeeld: <div class="menu"> <!-- het includen van een menu --> <?php include("tweefunc.txt")?> </div> <div class="rechts"> <!-- de inhoud van de pagina --> </div> Hierbij worden de classes menu en rechts gebruikt om de webpagina in twee delen op te delen:.rechts.menu { margin-left: 180px; margin-bottom: 25px; background-color: #ccc; padding: 3px; color: red; font-size: 13pt; } { position: fixed; top: 80px; left:5px; width: 150px; border: solid 1px black; background-color: #9fc; padding: 3px; margin: 4px 2px 4px 2px; font-family: courier; } De inhoud van het bestand dat ingevoegd wordt: <ul> <li> <a href="incl.php">include <li> <a href="requ.php">require <li> <a href="inclf.php">include fout <li> <a href="requf.php">require fout <li> <a href="phpincl.php">uitleg </ul> </a> </li> </a> </li> </a> </li> </a> </li> </a> </li> In het voorbeeld is dit pure HTML, wel zonder html head en body tags. Om dit duidelijk te stellen wordt als suffix.txt gebruikt. Naast pure html kan natuurlijk ook javascript, php of andere constructies opgenomen worden. De require() functie is identiek aan de include() functie, alleen de foutafhandeling is anders. Wanneer het aangegeven bestand niet gevonden wordt, zal bij include een warning gegenereerd worden en zal de rest van de pagina getoond worden. Bij require wordt een error gegenereerd en zal de rest van de pagina niet getoond worden. Voorbeeld: <div class="menu"> <?php require("tweefunk.txt")?> </div> Het is aan te raden om altijd de require functie te gebruiken, omdat het meestal geen zin heeft om de rest van een pagina te tonen wanneer bepaalde bestanden niet gevonden worden. 4
2 De server 2.1 Apache server Het idee achter het HTTP-protocol waarmee clients en servers met elkaar praten, is ontwikkeld door mensen van het CERN (Europees Centrum voor Nuclair Onderzoek) in Geneve. Toen zij hun onderzoek over het delen van documenten tussen computers hadden afgerond, gaven ze het project over aan een Amerikaanse Universiteit (NSCA). Apache (<http://www.apache.org>) is de naam van een server die het HTTP protocol spreekt, en dus een zogenaamde webserver is. De naam Apache heeft een onduidelijke achtergrond: sommige mensen zeggen dat het een verbastering is van een *a pa*t*chy* (=gebrekkige) server, omdat het begin van de ontwikkeling veel patches (=verbeteringen) nodig waren om de server bruikbaar te maken. Andere mensen beweren dat het programma vernoemd is naar de Amerikaanse Apacheindianen, die zeer goed met onverwachte omstandigheden konden omgaan. Apache is veruit de meest gebruikte webserver voor het internet. Hij berust op versie 1.1 van het HTTP-protocol dat is vastgesteld door het World Wide Web Consortium <http://www.w3.org> (W3C). Een onderzoek van Netcraft <http://www.netcraft.com/survey/> uit juni 1999 wees uit dat meer dan 60 procent van alle webservers de Apache software gebruikt. The Netcraft Web Server Survey is a survey of Web Server software usage on Internet connected computers. We collect and collate as many hostnames providing an http service as we can find, and systematically poll each other with an HTTP request for the server name. In the January 2003 survey we received responses from 35,424,956 sites. Een webserver is de server kant in een client-server-model (letterlijke vertaling klant-dienstverlener ). Een webserver beantwoordt vragen van de webclient zoals Netscape of Lynx. De vragen die de webclient (of webbrowser) stelt zijn van de vorm: Heb jij die ene webpagina?. Het antwoord dat de webserver daarna geeft is van de vorm: Het artikel luidt:... of Ik kan de webpagina niet vinden.. 2.2 Het HTTP-protocol Uiteraard praten de server en client niet in het Nederlands; ze gebruiken in plaats daarvan het HTTP-protocol (HyperText Transfer Protocol). De huidige versie is HTTP 1.1 en is gedefinieerd in RFC 2616 <http://www.rfc-editor.org/rfc/rfc2616.txt>. Dit protocol is verdeeld in twee delen: de vraag van de client en het antwoord van de server. Het protocol is gebaseerd op tekst bestaande uit ASCII-tekens. 1. De vraag: dit is een regel tekst die verdeeld is in drie stukken: [vraagtype] [URL] [protocolversie] 1. [vraagtype]: mogelijke vraagtypen zijn: GET, POST, HEAD, PUT, DEL en TRACE. 2. [URL]: de URL (Uniform Request Locator) is de bestandsnaam en directory van de webpagina die je wilt opvragen. Voorbeeld: als je de webpagina http://www.linuxfocus.org/nederlands/index.html wilt opvragen, is de URL in de clientvraag /Nederlands/index.html. 3. [protocolversie]: de protocolversie kan HTTP/1.0 en HTTP/1.1 zijn. Deze vereiste regel mag gevolgd worden door andere regels om de clientvraag toe te lichten, zoals we zullen zien in een HTTP/1.1 vraag. 2. Het antwoord: het antwoord van de server is opgebouwd uit een header (=kopregels) en een inhoud. 5
Een voorbeeld. We kunnen het gedrag van een webclient nabootsen met een telnet programma. De webserver is te bereiken via een serveradres en een poortnummer. In het volgende voorbeeld is het adres van de webserver /www.linuxfocus.org/ en het poortnummer is 80, het standaard poortnummer voor webservers. >telnet www.linuxfocus.org 80 Trying 195.53.25.18... Connected to nova.linuxfocus.org. Escape character is ^]. GET / HTTP/1.0 <return> <return> HTTP/1.1 200 OK Date: Mon, 27 Sep 1999 21:23:20 GMT Server: Apache/1.3.3 (Unix) (Red Hat/Linux) Last-Modified: Sun, 26 Sep 1999 16:40:44 GMT ETag: "4b005-1616-37ee4c8c" Accept-Ranges: bytes Content-Length: 5654 Connection: close Content-Type: text/html <PAGE HTML> De meeste gebruikte HTTP-methode is GET. Bij deze methode wordt de absolute padnaam van de gevraagde pagina en de gebruikte versie van HTTP meegegeven. De eerste regel van het antwoord geeft de gebruikte protocolversie (HTTP/1.1), een antwoordnummer (200) en tenslotte de tekst OK. Het antwoordnummer geeft informatie over de aard van het antwoord. Een nummer groter dan 400, geeft aan dat er een fout is opgetreden. De meest voorkomende fout, is de 404-fout, dat aangeeft dat de opgevraagde webpagina niet aanwezig is. Daarna volgen een aantal regels die (met de eerste regel) de header vormen. Deze regels geven telkens informatie in de vorm Naam: info. Zo geven de volgende regels een datum, de versie van de server en de datum van de laatste verandering van de webpagina (hierdoor kan de client nagaan of zijn cache nog geldig is). De regel die begint met Content-Length geeft de lengte van de inhoud van het antwoord waarin de echt informatie staat en Content-Type vertelt de client van welk MIME-type de teruggegeven informatie is (tekst, HTML, plaatjes...). Het antwoordnummer of statuscode is een getal van drie cijfers, waarbij het eerste cijfer de algemene antwoordcategorie identificeert: 1xx : slechts een informatie-boodschap; 2xx : succesvol; 3xx : herdirectie van de klant naar een andere URL; 4xx : een fout langs de klant zijde; 5xx : een fout langs de server zijde. De meest voorkomende status codes in een HTTP antwoord zijn: 100 Continue 200 Ok de aanvraag is succesvol en een document wordt teruggestuurd; (bij een HEAD wordt alleen de antwoord hoofding teruggestuurd) 204 No Content de aanvraag is succesvol maar geeft geen data om terug te sturen naar de klant 300 Moved Permanently 304 Not Modified 400 Bad Request een niet juist gevormd HTTP aanvraag is ontvangen 404 Not Found de URL aanvraag is niet gebonden 501 Not Implemented de klant gebruikte een niet-gekende methode in de aanvraag 500 Internal Server Error alle andere serverfouten 6
Laten we eens kijken of we een fout kunnen veroorzaken: >telnet www.linuxfocus.org 80 Trying 195.53.25.18... Connected to nova.linuxfocus.org. Escape character is ^]. G0T / HTTP/1.0 <return> <return> HTTP/1.1 501 Method Not Implemented Date: Mon, 27 Sep 1999 21:22:03 GMT Server: Apache/1.3.3 (Unix) (Red Hat/Linux) Allow: GET, HEAD, OPTIONS, TRACE Connection: close Content-Type: text/html Zoals je ziet, geeft de header heel veel informatie. Het HTTP-protocol is erg eenvoudig zoals we ook in de volgende voorbeelden zullen zien: >telnet www.linuxfocus.org 80 Trying 195.53.25.18... Connected to nova.linuxfocus.org. Escape character is ^]. GET / <return> Resultaat: [de inhoud van de webpagina index.html van de website www.linuxfocus.org wordt dan getoond ].. In het bovenstaande voorbeeld heb je met telnet via poort 80 contact gemaakt met de webserver van www.linuxfocus.org (IP adres 195.53.25.1). De server wachtte vervolgens op een vraag en die gaf je door GET / te typen gevolgd door twee keer een carriage return. Er zijn twee carriage returns nodig omdat de lege regel na de GET-opdracht de server het teken geeft dat dit het eind van de vraag is. De server begint na dit signaal met het beantwoorden van de vraag en het sturen van het antwoord. Als het gesprek voorbij is, wordt de TCP/IP-verbinding door de server verbroken. Bij HTTP 1.0 zijn 16 headers mogelijk, maar geen enkele is verplicht. HTTP 1.1 definieert 46 headers, één daarvan (Host:) is verplicht bij een aanvraag. Omwille van NET-beleefdheid is het aangewezen om volgende headers op te nemen in een aanvraag: From : : e-mail adres van degene die de aanvraag doet; User-Agent : : identificatie van het programma dat de aanvraag doet, bijvoorbeeld User-agent: Mozilla/4.7 [en] (Win98; I) Host : : de internet-naam van de computer die de aanvraag doet. Omwille van de verplichte header wordt het iets ingewikkelder als we gebruik gaan maken van HTTP versie 1.1. Een gesprek dat er met HTTP/1.0 zo uit ziet: GET / HTTP/1.0 <return> <return> HTTP/1.1 200 OK Date: Tue, 24 Aug 1999 22:25:11 GMT Server: Apache/1.3.3 (Unix) (Red Hat/Linux) Last-Modified: Sun, 01 Aug 1999 11:50:52 GMT ETag: "4b005-1462-37a4349c" Accept-Ranges: bytes Content-Length: 5218 Connection: close Content-Type: text/html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">... 7
ziet er met HTTP/1.1 zo uit: GET / HTTP/1.1 <return> <return> HTTP/1.1 400 Bad Request Date: Tue, 24 Aug 1999 22:24:59 GMT Server: Apache/1.3.3 (Unix) (Red Hat/Linux) Connection: close Transfer-Encoding: chunked Content-Type: text/html <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <HTML><HEAD> <TITLE>400 Bad Request</TITLE> </HEADBODY> <H1>Bad Request</H1> Your browser sent a request that this server could not understand. <P> client sent HTTP/1.1 request without hostname (see RFC2068 section 9, and 14.23): </P> </BODY></HTML> Dezelfde vraag in versie 1.1 van het HTTP-protocol vereist dat er meer informatie met de vraag wordt meegezonden. De extra informatie die meegestuurd wordt, maakt de vraag specifieker en daarmee de inhoud van het gesprek nauwkeuriger en beter. Hieronder staat de correcte clientvraag in HTTP/1.1. De programmeurs van het Apache-project zijn zeer strict geweest in het naleven van de specificatie van dit protocol. Maar dit protocol maakt, hoewel stricter, ook meer mogelijk, zoals: authenticatie, virtuele websites (meerdere websites met hetzelfde IP-adres) en meer... Een voorbeeld: GET / HTTP/1.1 <return> Host:www.linuxfocus.org<return> <return> [...] Zoals bij de meeste client-server systemen zal de server het volgende doen als hij een vraag ontvangt: hij start een apart process om de vraag te gaan beantwoorden; het parent process blijft luisteren naar nieuwe vragen terwijl de vorige vraag nog niet beantwoord is. het aparte, child process beantwoordt ondertussen de vraag en het process zal gestopt worden als de vraag beantwoord is. Het algemene principe is dat een webserver maar één antwoord kan geven aan de client die de vraag stelde. De client stuurt een vraag en krijgt daarop het antwoord. De webserver is hiermee een interface tussen de webclient die het document opvraagt dat bij een URL hoort, en het besturingssysteem waarop Apache draait. Andere namen die voor URL gebruikt worden, zijn URI en URN die een iets andere betekenis hebben, maar op precies hetzelfde neerkomen: een unieke naam voor een webpagina. Sommige clientvragen kunnen niet direct door de server beantwoord worden. In dat geval kan de server een ander programma starten die de vraag probeert op te lossen. Dit is precies hoe CGI-scripten (Common Gateway Interface) werken. Naast GET bestaan nog ander methodes, o.a.: 8
HEAD : zoals een GET, maar de server moet alleen de antwoord-hoofdingen terugsturen en geen document met data; dit is nuttig om karakteristieken van een resource te controleren, zonder het volledige dokument te downloaden. POST : wordt gebruikt om data naar de server te sturen, om daar op een of andere manier verwerkt te worden (zoals bij een CGI script). Verschillen met een GET vraag: er wordt met de aanvraag een blok met data meegestuurd; er zijn normaal een aantal extra hoofdingen om deze data te beschrijven, zoals Content-Type: en Content-Length:; de aanvraag URI duidt geen document aan dat moet teruggestuurd worden; het is meestal een programma dat de opgestuurde data zal verwerken; Het HTTP antwoord is meestal de uitvoer van het programma, dus geen statisch bestand. POST wordt meestal gebruikt om HTML-formulier gegevens door te sturen naar CGI scripts. In dat geval is de Content-Type: hoofding gewoonlijk gelijk aan application/x-www-form-urlencoded en geeft de Content-Length: hoofding de lengte van de URL-geëncodeerde formulier gegevens. Het CGI script ontvangt de body van de boodschap via STDIN en decodeert dan de gegevens. Een voorbeeld van het opsturen van formulier gegevens via POST: POST /cgi-bin/hcr/drie.pl HTTP/1.0 Content-Type: application/x-www-form-urlencoded Content-Length: 22 appel=peer&banaan=kers Met een POST aanvraag kan je naast formulier gegevens ook nog andere data versturen. Alleen moet je er voor zorgen dat de zender en het ontvangende programma overeenkomen omtrent het formaat. Merk op dat bij GET de formulier gegevens bij in de URL opgenomen worden: GET /cgi-bin/hcr/drie.pl?appel=peer&banaan=kers De logbestanden op webserver antje: /var/log/httpd.access_log /var/log/httpd.error_log 9
3 Dynamische webpagina s met Python 3.1 Tekst-manipulaties met Python Het WWW is een tekstgebaseerde omgeving: een HTTP-request en HTTP-response bestaan allebei uit tekst. Om goed te functioneren in een dergelijke omgeving, moeten onze Python programma s dus tekst kunnen manipuleren. 3.1.1 Nuttige string functies Strings in Python zijn objecten, die een heel aantal nuttige methodes aanbieden. Het is belangrijk om te weten dat String objecten immutable zijn: dwz. dat een string nooit in place veranderd wordt! Al deze methodes geven dus een nieuwe string terug en laten de oude ongemoeid. string.split(separator) deelt een string op. Bijvoorbeeld 11:55:00. split ( : ) produceert de lijst [ 11, 55, 00 ]. Als er geen separator wordt meegegeven, wordt er op witruimte gesplitst. separator.join(lijst) doe de omgekeerde operatie. :. join ([ 11, 55, 00 ]) geeft opnieuw 11:55:00. string.rstrip(letters) verwijdert alle letters die voorkomen in de string letters van het einde van de string. Er bestaat ook een lsplit die hetzelfde doet met het begin van de string. Bijvoorbeeld 0001. lstrip ( 0 ) produceert 1. Zonder argument wordt alle witruimte verwijderd. string.replace(oud,nieuw) vervangt in string alle voorkomens van oud door nieuw. Een optioneel derde argument n kan worden meegegeven om het aantal vervangingen te beperken tot n. 3.1.2 Reguliere expressies Verschillende programmeertalen, waaronder ook Python, maken gebruik van Reguliere expressies om strings te analyseren en te bewerken. Reguliere expressies zijn een erg krachtige, maar helaas ook wat complexe manier om patronen in strings te beschrijven. In Python dient de module re om met reguliere expressies te werken. import r e Reguliere expressies worden in de eerste plaats gebruikt om patronen te zoeken in strings. r e. search ( patroon, s t r i n g ) Deze expressie geeft de waarde waar als in de string het patroon voorkomt dat door de reguliere expressie omschreven is. Het patroon kan gewoon een woord zijn en dan moet dat woord exact in de string voorkomen. Bijvoorbeeld, om alle lijnen met het woord appel uit een bestand te filteren: for l i j n in open ( bestand. txt ) : i f r e. search ( appel, l i j n ) : print l i j n Meestal moet echter naar iets gezocht worden waaraan verschillende woorden kunnen voldoen. Dat iets wordt gedefinieerd mbv. een reguliere expressie (RE). Een RE bevat tekst-tekens en operator-tekens. De tekst-tekens komen overeen met de corresponderende tekens in de strings waarmee vergeleken wordt. Tekst-tekens zijn bijvoorbeeld de letters van het alfabet en de cijfers. De operator-tekens specificeren herhalingen, keuzes, klassen,... Men noemt deze ook meta-tekens. 10
RE metatekens ^ het begin van de lijn $ het einde van de lijn (vlak voor de \n ). om het even welk teken, behalve \n keuze: één van de gespecificeerde patronen [] een teken uit de gespecificeerde verzameling tekens () om expressies te groeperen (voor keuzes en terugreferenties) \ het volgende metateken wordt als gewoon teken geïnterpreteerd de speciale betekenis wordt teniet gedaan Alleen die lijnen die slechts het woord appel, peer of kers bevatten: for l i j n in open ( bestand. txt ) : i f r e. search ( ˆ( appel peer k e r s ) $, l i j n ) : print l i j n Naast de klassieke specifieke tekens (\n, \r, \t, \f) zijn een aantal extra teken-patronen gedefinieerd: Teken patronen \w een alfanumeriek teken (miv. _ ) \W een niet-alfanumeriek teken \s een witte-ruimte teken \S een niet-witte-ruimte teken \d een cijfer \D een niet-cijfer De search functie kan niet alleen gebruikt worden om te zien of een patroon gevonden is in een tekst, maar ook om te zien welke tekst overeenkwam met dit patroon. Als het patroon gevonden werd, dan wordt een MatchObject object teruggegeven, waarvan de methodes start() en end() teruggeven waar in de string het patroon gevonden werd. s t r i n g = xxx33dd445fff665 patroon = r (\ d){3} # Een raw string, om de backslash niet te moeten escapen m = r e. search ( patroon, s t r i n g ) print s t r i n g [m. s t a r t ( ) :m. end ( ) ] # --> 445 Het is ook mogelijk om alle matches te zoeken met finditer (te gebruiken in een lusje) of findall (geeft een lijst terug). s t r i n g = xxx33dd445fff665 patroon = r (\ d){3} # Een raw string, om de backslash niet te moeten escapen a l l e m a a l = r e. f i n d i t e r ( patroon, s t r i n g ) for m in a l l e m a a l : print s t r i n g [m. s t a r t ( ) :m. end ( ) ] # --> 445 en 665 print l e n ( r e. f i n d a l l ( patroon, s t r i n g ) ) # --> 2 Elke match kan ook nog eens een aantal groepen bevatten. Om een groep je maken, gebruik je ronde haakjes in het patroon. Elk deelpatroon dat tussen haakjes voorkomt, wordt een groep. Deze groepen zijn dan genummerd in volgorde van voorkomen (te beginnen bij 1, want 0 is de hele match). Met de group methode van het MatchObject kan je een bepaalde groep opvragen. Uit de datum (in standaard datum formaat, d.i. Sat Jan 31 20:51:10 2015 ) de tijd bepalen: import time m = r e. search ( r (\ d\d ) : ( \ d\d ) : ( \ d\d ), time. ctime ( ) ) t i j d = { uur : m. group ( 1 ), min : m. group ( 2 ), s e c : m. group ( 3 ) } print t i j d # ---> { min : 20, sec : 55, uur : 20:55:25 } 11
Een RE bestaat dikwijls uit een herhaling van een bepaald patroon (bijv. een uur bestaat uit een opeenvolging van twee cijfers). Om deze herhaling niet expliciet te moeten definiëren, zijn er herhalingsoperatoren. RE herhalingsoperatoren * 0 of meerdere maal + 1 of meerdere maal? 0 of 1 maal {n} juist n maal {n,} tenminste n maal {n,m} tenminste n maal, maar niet meer dan m maal m = r e. search ( r (\w{ 3 } ). ( \ d { 2,4}), time. ctime ( ) ) dag = m. group ( 1 ) # juist drie letters j a a r = m. group ( 1 ) # twee tot vier cijfers Zoeken en vervangen. De functie re.sub(patroon, nieuwe, string) dient om in een string het patroon te vervangen door een nieuwe string. Deze vervanging gebeurt niet in place: de functie geeft de string terug die het resultaat is van de vervanging en laat de oorspronkelijke string onveranderd. De sub methode vervangt alle voorkomens van het gezochte patroon. Als optioneel vierde argument kan een getal n worden meegegeven om het aantal vervangingen te beperken tot n. t e k s t = De hond word gewassen en z i j n s t a a r t word b i j g e k n i p t. print r e. sub ( word, wordt, t e k s t ) Moest er in de tekst al een wordt met dt staan, dan zou deze vervangen worden door wordtt. Om dit te voorkomen, kunnen we dit proberen: t e k s t = De hond wordt gewassen en z i j n s t a a r t word b i j g e k n i p t. print r e. sub ( word [ ˆ t ], wordt, t e k s t ) Hiermee verdwijnt echter de spatie tussen word en bijgeknipt. We kunnen dit oplossen met een lookahead. Dit is iets van de vorm (?=...) en zal ervoor zorgen dat het patroon slechts match indien het door iets bepaalds gevolgd wordt, zonder echter de match zelf langer te maken. Er bestaat ook een negatieve lookahead (?!...) t e k s t = De hond word gewassen en z i j n s t a a r t wordt b i j g e k n i p t. print r e. sub ( word(?=[ˆ t ] ), wordt, t e k s t ) print r e. sub ( word (?! t ), wordt, t e k s t ) # Hetzelfde met negatieve lookahead In de vervangende tekst, kan je ook de notatie \i gebruiken om terug te verwijzen naar groep nummer i in het gematchte patroon. Hiermee krijgen we nog een derde alternatieve oplossing voor het voorgaande probleem: t e k s t = De hond word gewassen en z i j n s t a a r t wordt b i j g e k n i p t. print r e. sub ( word ( [ ˆ t ] ), r wordt \1, t e k s t ) We kunnen dit ook gebruiken om bv. een datum in Amerikaanse schrijfwijze om te zetten naar Europese schrijfwijze. datum = Nine e l e v e n gebeurde op 9/11/2001 print r e. sub ( r (\ d\d?)/(\ d\d?)/(\ d { 2,4}), r \2/\1/\3, datum ) In plaats van een string, kan het tweede argument van sub ook een functie zijn. Deze wordt dan opgeroepen voor elk voorkomen van het patroon die gevonden wordt, met het corresponderende MatchObject als argument. 12
def telwoord ( match ) : telwoorden = [ nul, een, twee, d r i e, v i e r, v i j f, z e s, zeven, acht, negen ] return telwoorden [ i n t ( match. group ( 0 ) ) ] t e k s t = Als i k 3 b i g g e t j e s heb en i k e e t er 1 op, dan heb i k er nog 2. print r e. sub ( r \d, telwoord, t e k s t ) 3.2 Toepassing: verwerking van form gegevens 3.2.1 Overzicht van CGI omgevingsvariabelen Een klassiek misverstand ivm CGI is dat men command-lijn opties en argumenten zou kunnen doorsturen naar het CGI programma. Dit is echter niet mogelijk omdat CGI de command lijn voor andere doeleinden gebruikt. Om aan het programma parameters door te geven, gebruikt CGI omgevingsvariabelen. De belangrijkste zijn: QUERY STRING: is gedefinieerd als alles wat volgt na de eerste? in de URL. Deze informatie kan toegevoegd worden ofwel door een ISINDEX document, of door een HTML form (met de GET actie). Het kan ook manueel ingesloten worden in de HTML anchor die de gateway refereert. Deze string is in het standaard URL formaat geëncodeerd: spaties zijn omgevormd in plussen en sommige speciale tekens zijn omgezet in een %HH hexadecimaal equivalent. Voordat de query string kan ontleed worden, moet deze dus eerst in het programma terug gedecodeerd worden. PATH INFO: CGI laat toe om extra informatie te embedden in de URL voor uw gateway die kan gebruikt worden om extra context-specifieke informatie over te brengen naar de scripts. Deze informatie is gewoonlijk beschikbaar als extra informatie na het pad van de gateway in de URL. Deze informatie wordt op geen enkele manier gecodeerd door de server. Een voorbeeld van gebruik van PATH_INFO is het overbrengen van locaties van bestanden naar het CGI programma. Bijvoorbeeld, een CGI programma op de server met naam /cgi-bin/joske dat bestanden uit de DocumentRoot van de server kan verwerken. Het moet mogelijk zijn om aan joske duidelijk te maken welk bestand moet verwerkt worden. Door extra pad informatie op het einde van de URL op te nemen zal joske de locatie van het document relatief tov DocumentRoot kennen via the PATH_INFO omgevingsvariabele. Ook gekend is het effectieve pad naar het document via de PATH_TRANSLATED omgevingsvariabele die door de server gegenereerd wordt. Bij een Python script zitten al deze variabelen in het woordenboek os.environ in de module os. 13
Naam betekenis SCRIPT_FILENAME The full pathname van de current CGI REQUEST_METHOD GET or POST QUERY_STRING de query string (voor de GET methode) CONTENT_LENGTH lengte van de input (voor de POST methode) HTTP_REFERER URL van de pagina die het script oproept HTTP_USER_AGENT browser type van de bezoeker REMOTE_ADDR IP adres van de bezoeker REMOTE_HOST hostnaam van de bezoeker REMOTE_PORT de poort waarop de bezoeker is geconnecteerd REMOTE_USER usernaam van de bezoeker DOCUMENT_ROOT root directory van de server HTTP_HOST hostnaam van de server SERVER_ADMIN email adres van de webmaster van de server SERVER_NAME fully qualified domein naam van de server SERVER_PORT het poort nummer waarop de server luistert SERVER_SOFTWARE gebruikte server software (bijv. Apache 1.3) PATH het systeempad van de server Niet alle omgevingsvariabelen worden gezet voor iedere CGI. De meeste CGI scripts hebben ook slechts een beperkt aantal omgevingsvariabelen nodig. Het maken van een lijst van alle omgevingsvariabelen: s t a r t = """Content-Type: text/html <! DOCTYPE HTML PUBLIC "-// IETF// DTD HTML// EN"> <html > <head > <title >Hoi </title > </head > <body > <table > """ eind = </table ></body></html> print s t a r t import os env = os. environ for naam in env : print <t r ><td> + naam + </td><td> + env [ naam ] + </td></t r > 3.2.2 INPUT elementen Perl script voor de verwerking van de gegevens uit de form met checkboxen en radio buttons. 1 #!/ usr/ bin/ python 3 class CGIParser : 5 def i n i t ( s e l f ) : s e l f. params = {} 7 for g e l i j k h e i d in CGIParser. g e t P a r a m e t e r L ijst ( ) : ( l i n k s, r e c h t s ) = CGIParser. p a r s e G e l i j k h e i d ( g e l i j k h e i d ) 9 s e l f. params [ l i n k s ] = r e c h t s 14
11 @staticmethod def p a r s e G e l i j k h e i d ( g e l i j k h e i d ) : 13 def hexchar ( h ) : return chr ( i n t ( h, 1 6 ) ) 15 def decodestring ( s t r i n g ) : import r e 17 s t r i n g = r e. sub ( \+,, s t r i n g ) s t r i n g = r e. sub ( %(\d {2}), lambda x : hexchar ( x. group ( 1 ) ), s t r i n g ) 19 return s t r i n g return map( decodestring, g e l i j k h e i d. s p l i t ( = ) ) 21 @staticmethod 23 def g e t P a r a m e t erlijst ( ) : import os 25 i f os. environ [ REQUEST METHOD ] == GET : return os. environ [ QUERY STRING ]. s p l i t ( & ) 27 else : import sys 29 l i j s t = [ ] for l i n e in sys. s t d i n : 31 l i j s t. append ( l i n e ) return l i j s t 33 def getparameter ( s e l f, p ) : 35 return s e l f. params [ p ] 37 def main ( ) : pagina1 = """Content-Type: text/html 39 <! DOCTYPE HTML PUBLIC "-// IETF// DTD HTML// EN"> 41 <html > <head > 43 <title >Hoi </title > </head > 45 <body > 47 <p>we kregen: """ 49 pagina2 = """!</p> 51 </body > </html >""" 53 p a r s e r = CGIParser ( ) print pagina1 + p a r s e r. getparameter ( veld ) + pagina2 55 main ( ) De omzettingsprocedure moet de data splitsen op de ampersands. Voor elk paar dat bekomen wordt, moet zowel de naam als de waarde URL gedecodeerd worden: plussen terug omzetten in spaties en hexadecimale getallen in de corresponderende tekens. Daarna kan met de naam en de waarde om het even wat gedaan worden. We verzamelen ze hier nu in een woordenboek. 15
3.2.3 SELECT element Wanneer meer dan één selectie gekozen (bij multiple) is, worden meerdere paren naar het script doorgestuurd: frequ=dagelijkse+rapportering&frequ=maandelijkse+rapportering In het script zou nu in de associatieve array in het element met sleutel frequ twee waarden moeten bewaard worden. #!/usr/bin/python class CGIParser : def i n i t ( s e l f ) : s e l f. params = {} for g e l i j k h e i d in CGIParser. g e t P a r a m e t e r L ijst ( ) : ( l i n k s, r e c h t s ) = CGIParser. p a r s e G e l i j k h e i d ( g e l i j k h e i d ) i f l i n k s in s e l f. params : s e l f. params [ l i n k s ]. append ( r e c h t s ) else : s e l f. params [ l i n k s ] = [ r e c h t s ] @staticmethod def p a r s e G e l i j k h e i d ( g e l i j k h e i d ) : def hexchar ( h ) : return chr ( i n t ( h, 1 6 ) ) def decodestring ( s t r i n g ) : import r e s t r i n g = r e. sub ( \+,, s t r i n g ) s t r i n g = r e. sub ( %(\d {2}), lambda x : hexchar ( x. group ( 1 ) ), s t r i n g ) return s t r i n g return map( decodestring, g e l i j k h e i d. s p l i t ( = ) ) @staticmethod def g e t P a r a m e t erlijst ( ) : import os i f os. environ [ REQUEST METHOD ] == GET : return os. environ [ QUERY STRING ]. s p l i t ( & ) else : import sys l i j s t = [ ] for l i n e in sys. s t d i n : l i j s t. append ( l i n e ) return l i j s t def getparameter ( s e l f, p ) : return s e l f. params [ p ] [ 0 ] def getparametervalues ( s e l f, p ) : return s e l f. params [ p ] def main ( ) : pagina1 = """Content-Type: text/html 16
<! DOCTYPE HTML PUBLIC "-// IETF// DTD HTML// EN"> <html > <head > <title >Hoi </title > </head > <body > <p>we kregen: """ pagina2 = """!</p> </body > </html >""" p a r s e r = CGIParser ( ) print pagina1 + p a r s e r. getparameter ( veld ) + pagina2 main ( ) 3.2.4 TEXTAREA element De POST methode gebruikt standard input om de data over te brengen en hier is geen beperking op de lengte van de input data. Daarom is deze methode meestal te verkiezen, alhoewel het enkele lijnen meer code vraagt. Door deze lijnen wat uit te breiden kan er ook een beperking geplaatst worden op de hoeveelheid data die door het script binnen genomen wordt. 1 def g e t P a r a m e t e r Lijst ( ) : import os 3 i f os. environ [ REQUEST METHOD ] == GET : return os. environ [ QUERY STRING ]. s p l i t ( & ) 5 else : import sys 7 import os n = i n t ( os. environ [ CONTENT LENGTH ] ) 9 i f ( n > 3 2 7 6 8 ) : raise ValueError ( Input te g r o o t ) 11 return sys. s t d i n. read ( n ). s p l i t ( ) In het totaal wordt niet meer dan 32768 bytes binnengenomen en de waarde van elk paar kan hoogstens 8192 bytes lang zijn. 3.3 Opmerkingen CGI scripts moeten in een specifieke directory geïnstalleerd worden zodat de server ze kan vinden en uitvoeren. De plaats van de directory is door de webmaster bepaald, bijvoorbeeld /usr/local/httpd/cgi-bin 3.3.1 Testen van een CGI script Om een CGI script te testen, moet een HTML document met form geïnstalleerd worden in de public_html directory van een gebruiker en het script zelf in de cgi-bin directory van de DocumentRoot directory van de webserver. Dan moet de browser opgestart worden en kan de form ingevuld worden zodat het script input krijgt. Om alleen het script zelf te testen zonder een webserver en een browser, kan de volgende methode gebruikt worden. Een aantal omgevingsvariabelen worden gezet, bijv. in de C-shell: 17
of setenv REQUEST_METHOD GET setenv QUERY_STRING "naam=joske+flup&adres=ginder+6" setenv REQUEST_METHOD POST setenv CONTENT_LENGTH 30 en het script kan gestart worden: script.py (bij GET) of script.py < inn (bij POST) waarbij inn een bestand is met de input voor het script (bijv. naam=joske+flup&adres=ginder+6). 3.3.2 Veiligheid Omdat een CGI programma uitvoerbaar is, is het eigenlijk hetzelfde als om het even wie een programma laten uitvoeren op uw systeem, wat helemaal niet het veiligste is om te laten doen. Een mogelijk probleem is het gebruik van os.system, die een programma uitvoert alsof het ingetikt is op de commandlijn. Bijvoorbeeld een Python script met de volgende lijn: os. system ( / usr / bin / c a l + maand + 1999 ) ; Op zich geen probleem: het effect is de kalender van de gespecificeerde maand in 1999 te tonen. Veronderstel echter dat de waarde voor de variabele maand afkomstig van de input van een gebruiker via een form. Een brave gebruiker zal natuurlijk op de juiste manier een maand en jaar ingeven. Maar een hacker heeft hier de mogelijkheid om de inhoud van het paswoord bestand van het server systeem over te halen naar zijn eigen systeem. De input die hij moet geven is ; cat /etc/passwd; echo" De informatie die nu naar de client gestuurd wordt, is de output van drie commands: system("/usr/bin/cal ; cat /etc/passwd; echo 1999"); D.i. de kalender van de huidige maand, de inhoud van het paswoord bestand en het getal 1999. Een andere, niet gewenste input is ; rm -rf *; echo". Zelfs als de server met vrij beperkte privileges draait, kunnen toch heel wat bestanden hierdoor verwijderd worden. Een echte ramp is het natuurlijk wanneer de webserver met root privileges draait. De eenvoudige GET methode houdt ook gevaren in. De data wordt doorgestuurd als laatste deel van de URL. Daardoor zal de volledige data opgenomen worden in de logfile van de webserver. Meestal kan deze logfile door iedereen gelezen worden. De GET methode is dus niet aangewezen wanneer een paswoord, kredietkaartgegevens of andere gevoelige informatie moet opgevraagd worden. In deze gevallen moet met de POST methode gewerkt worden. Beveiliging. Er moeten dus enkele veiligheidsvoorzieningen geïmplementeerd worden bij het gebruik van CGI programma s. Een voorbeeld hiervan heeft te maken met de directory waarin de CGI programma s verzameld zijn. Dit is een speciale directory die onder controle van de webmaster staat. Dus niet iedereen kan zomaar CGI programma s installeren. Hiervoor is toelating van de webmaster nodig. Bij veel verschillende webservers is dit de directory cgi-bin. Een CGI programma kan in om het even welke taal geschreven worden die op het systeem uitvoerbaar is. Voorbeelden zijn Python, C/C++, Fortran, PERL, TCL, een Unix shell, Visual Basic. Indien voor C of Fortran gekozen wordt, waarbij het programma moet gecompileerd worden, wordt meestal de broncode in de directory /cgi-src geplaatst. Veel CGI-ontwerpers verkiezen echter om CGI scripts te schrijven waarbij het script zelf in de /cgi-bin directory geplaatst moet worden. CGI scripts zijn gemakkelijker te debuggen, aan te passen en te onderhouden dan gecompileerde programma s. 18
4 PHP: Hypertext Preprocessor 4.1 Inleiding PHP is een server-side scripting taal voor het creëren van dynamische Web pagina s. Je creëert een pagina met PHP en HTML. Wanneer een bezoeker de pagina opent, verwerkt de server de PHP commands en zendt dan de resultaten naar de browser van de bezoeker. PHP is Open Source en cross-platform (bijv. zowel Windows als Unix). PHP kan als een Apache module geconfigureerd worden en als een binary zodat het als een CGI kan gebruikt worden. Wanneer PHP als een Apache module geconfigureerd is, is PHP bijzonder lightweight en snel. Zonder enige proces creatie overhead, kan PHP snel resultaatpagina s terugzenden, maar heeft daarbij geen behoefte aan de tuning van mod_perl om het benodigde geheugen in de server klein te houden. Naast het manipuleren van de inhoud van de pagina s, kan PHP ook HTTP headers zenden. Zetten van cookies, authenticatie doen en herdirigeren van gebruikers is mogelijk. PHP biedt een uitstekende connectiviteit met verschillende databanken. Er is integratie van tal van externe bibliotheken: ongeveer alles vanaf het genereren van PDF documenten tot het parsen van XML is mogelijk. PHP zit gewoon in uw Web pagigna s: Een PHP blok start met <?php en eindigt met?>. (PHP kan geconfigureerd worden om <script language="php"> </script> te gebruiken of de ASP-stijl <% %> tags.) De PHP engine verwerkt alles wat tussen deze tags staat. De syntax van PHP is gelijkaardig aan die van C en Perl. Variabelen hoeven niet voor het gebruik gedeclareerd te worden en het is gemakkelijk om arrays en associatieve arrays (hashes) te creëren. PHP heeft zelfs wat rudimentaire objectgerichte elementen, wat het mogelijk maakt om je PHP code in te kapselen. 4.2 Basis syntax <?php echo Dag, wereld! ;?> produceert Dag, wereld! Variabelen worden aangegeven door een $ als eerste teken. Een alternatief van bovenstaande met een variabele: <?php $message = Dag, wereld! ; echo $message ;?> Voor string concatenatie is er de. (punt) operator; daarnaast zijn er de klassieke rekenkundige operatoren: <?php $ g r e e t i n g = Dag ; $num = 3 + 2 ; $num++; echo $ g r e e t i n g $num mensen! ;?> produceert Dag 6 mensen! PHP heeft een zeer uitgebreide verzameling operatoren en hun functie is net zoals je ze verwacht, zeker wanneer is je een C of C++ achtergrond hebt. Dus een goeie vuistregel in PHP is: When in doubt, try it; it will probably work. Bij een string tussen dubbel quotes (") worden de variabelen vervangen door hun waarden, maar bij een string tussen enkel quotes ( ) gebeurt dit niet, net zoals in Perl. 19
<?php $name = Suzanne ; $ g r e e t i n g 1 = Dag, $name! ; $ g r e e t i n g 2 = Dag, $name! ; echo $ g r e e t i n g 1 <br /> \n ; echo $ g r e e t i n g 2 <br /> \n ;?> produceert Dag, Suzanne! Dag, $name! Het \n teken in een string wordt omgezet naar een nieuwe lijn, net zoals in Perl en C. De string moet wel tussen dubbel quotes (") staan. Variabelen. Hierboven zijn reeds gewone variabelen gebruikt. Daarnaast is het in PHP mogelijk om de omgevingsvariabelen aan te spreken. Hieronder zitten onder andere de omgevingsvariabelen die door de server ingevuld worden bij een CGI programma (ook wanneer PHP als een module geconfigureerd is). Bijvoorbeeld, indien de pagina "http://www.pubdenayer.wenk.be/boerderij/vee/koe.html" deze code bevat <?php echo v a r i a b e l e $ SERVER[ REQUEST URI ] :. $ SERVER [ REQUEST URI ] ;?> wordt er variabele $_SERVER["REQUEST_URI"] : [/boerderij/vee/koe.html] afgedrukt. Arrays. Array indices (reguliere of associatieve) worden aangeduid met vierkante haken ([ ]): $ f r u i t [ 0 ] = banaan ; $ f r u i t [ 1 ] = appel ; $ f a v o r i e t e n [ b e e s t ] = s c h i l d p a d ; $ f a v o r i e t e n [ monster ] = c o o k i e ; Wanneer je iets aan een array toewijst zonder de index te specificeren, wordt deze waarde achteraan aan de array toegevoegd. De array wordt dus met één element uitgebreid. De $fruit array kan dus ook als volgt ingevuld worden: $ f r u i t [ ] = banaan ; $ f r u i t [ ] = appel ; Multidimensionale arrays: $mens [ David ] [ hemd ] = blauw ; $mens [ David ] [ wagen ] = o pel ; $mens [ Adam ] [ hemd ] = wit ; $mens [ Adam ] [ wagen ] = toyota ; Een shortcut om arrays te creëren met de array() functie: $ f r u i t = array ( banaan, appel ) ; $ f a v o r i e t e n = array ( b e e s t => s c h i l d p a d, monster => c o o k i e ) ; $mens = a r r a y ( David => a r r a y ( hemd => blauw, wagen => o pel ), Adam => a r r a y ( hemd => wit, wagen => toyota ) ) ; De built-in functie count() geeft aan hoeveel elementen er in een array zitten: $ f r u i t = array ( banaan, appel ) ; print $ f r u i t. :. count ( $ f r u i t ) ; drukt af Array : 2. 20
Controle structuren. Lus structuren zoals for en while: for ( $ i = 4 ; $ i < 8 ; $ i++) { print Ik e e t $ i a ppels ; print per dag.< br />\n ; } drukt af Ik eet 4 appels per dag. Ik eet 5 appels per dag. Ik eet 6 appels per dag. Ik eet 7 appels per dag. Alternatief: $ i = 4 ; while ( $ i < 8) { print Ik e e t $ i a ppels per dag.< br />\n ; $ i++; } Je kan ook de controle structuren if en elseif gebruiken: $ u s e r t e l = 1 2 3 ; i f ( $ u s e r t e l > 200) { print De s i t e i s erg druk op d i t moment! ; } e l s e i f ( $ u s e r t e l > 100) { print De s i t e i s min o f meer a c t i e f! ; } else { print De s i t e i s n i e t in t r e k s l e c h t s $ u s e r t e l g e b r u i k e r s. ; } Een gelijkaardige vuistregel als bij operatoren geldt hier ook: switch, do...while, en zelfs de?: constructie bestaan ook. Bereik van variabelen worden gebruikt. : geeft aan in hoeverre de waarde van een variabele door een script kan Het bereik van variabelen is beperkt tot de pagina waarin ze gemaakt zijn. Een variabele die binnen een functie gedeclareerd wordt, kan alleen binnen die functie gebruikt worden. Een variabele die buiten een functie gedeclareerd wordt, is voor alle overige code van een pagina bereikbaar, maar niet binnen functies. Binnen een functie hebben variabelen een lokaal bereik, of hebben een verschillende waarde van een variabele met dezelfde naam buiten een functie. Om de waarde van een variabele die buiten een functie gedeclareerd is, toch binnen een functie te kunnen gebruiken, moet deze binnen de functie als global gedeclareerd worden. <?php $appel = a p p e l t j e ; function proberen ( ) { g l o b a l $appel ; $peer = p e e r t j e ; $samen = $appel. s en. $peer. s ; echo $samen ; } proberen ( ) ;?> Waardeveranderingen van een global variabele binnen een functie hebben ook effect buiten de functie. 21
Overzicht configuratie. De functie phpinfo() genereert een lijst met daarin de beschrijving van PHP-configuratie van de server: de waarde van een configuratievariabele, of een bepaalde module wel of niet geïnstaleerd is en de namen van de superglobals. Superglobals of autoglobals zjn speciale arrays, die automatisch door PHP als globaal worden aangemerkt. Ze kunnen dus overal gebruikt worden: in het hoofdscript, in functies en elders op een pagina in een los PHP statement. $_SERVER $_GET $_POST $_COOKIE $_FILES $_ENV $_REQUEST $_SESSION de webserver vult zelf deze array in met variabelen zoals de naam van het huidige script, de naam van de server, het gebruikte protocol, welke querystring is doorgegeven,... een associatieve array met variabelen die via de HTTP-methode GET aan het script is doorgegeven. een associatieve array met variabelen die via de HTTP-methode POST aan het script is doorgegeven. deze array bevat variabelen die via cookies aan het script zijn doorgegeven. een array met informatie over bestanden die naar het huidige script zijn geuploaded via de HTTP-methode POST. een array met informatie over de omgeving waarin PHP draait. een array waarin alle inhoud uit de arrays afkomstig van gebruikersinvoer is opgeslagen: $_GET, $_POST, $_COOKIE, $_FILES. een array waarin alle sessievariabelen zijn opgeslagen, bijvoorbeeld variabelen die op alle pagina s van een webapplicatie bereikbaar moeten zijn, zoals een loginnaam. 4.3 Een eenvoudige form Eén van de meest handige elementen van PHP is de mogelijkheid om automatisch de waarden van variabelen van een form in variabelen van PHP te laden, zodat het verwerken van een form eenvoudig wordt. In PHP 4.0 werden voor alle input velden aparte variabelen gecreëerd. Bij het submitten van een form met een input veld, gelijk aan <input type="text" name="naam" value="wim Peters" /> heeft de variabele $naam een waarde gelijk aan Wim Peters, tijdens het verwerken van de pagina met PHP. Dus, je kan deze variabele afdrukken of zijn waarde testen: echo Hi $naam! ; i f ($naam == Wim P eters ) { echo P lease check your email. ; } Het verwerken van eenvoudige forms met PHP kan als volgt gebeuren. De pagina wordt opgedeeld in twee functies: één voor het tonen van de form en een tweede voor het verwerken van de form. Omdat alles in één PHP pagina verzameld wordt, zal de logica van de pagina controleren wat aan de gebruiker moet getoond worden. In het voorbeeld wordt aan de gebruiker gevraagd om een kleine vragenlijst met naam, email adres en nog enkele vragen te beantwoorden. Na het versturen van de ingevulde form, zal een pagina met een appreciatie getoond worden. Tonen van de form. De eerste functie toont de form: <?php function display_form() {?> <form action="<?php echo $_SERVER["PHP_SELF"];?>" method="get"> 22
Naam: <input type="text" name="naam" /><br /> Favoriet fruit: <br /> <input type="radio" name="fruit" value="peer" /> Sappige vlaamse peren <br /> <input type="radio" name="fruit" value="kriek" /> Nederlandse krieken <br /> <input type="radio" name="fruit" value="citroen" /> Spaanse citroenen <br /> <br /><br /> Gezellige momenten om fruit te eten: <br /> <input type="checkbox" name="tijd[]" value="m" />Morgen <br /> <input type="checkbox" name="tijd[]" value="n" />Middag <br /> <input type="checkbox" name="tijd[]" value="t" />Tussendoor <br /> <input type="checkbox" name="tijd[]" value="a" />Avond <br /> <br /><br /> <input type="hidden" name="stage" value="resultaat" /> <input type="submit" value="dankuwel!" /> </form> <?php }?> Het grootste gedeelte van de tekst hierboven is gewoon HTML om de form te creëren. Daarnaast zijn er een aantal extra elementen die nu uitgelegd worden. Het element PHP_SELF van de superglobal $_SERVER is een handige referentie zijn waarde is de URL van de huidige pagina. Het action attribuut van de form wordt gezet op deze waarde, omdat we wensen dat deze pagina de form zal verwerken. Door gebruik te maken van $_SERVER["PHP_SELF"] in plaats het echte pad naar de pagina, kunnen we ons script hernoemen of verplaatsen zonder ons druk te moeten maken over het expliciet zetten van de pagina locatie elke keer dat we dit script wensen te hergebruiken. Merk op dat we in en uit PHP mode kunnen gaan op gelijk welke plaats, zelfs binnen een functie. PHP is in staat om alle tussenliggende HTML te negeren en gewoon te wachten op het volgende tag dat het begin van een PHP blok aangeeft. Dit is veel eenvoudiger dan in PHP mode te blijven en alle HTML af te drukken met behulp van echo. Er is een onderscheid tussen het type variabele voor de radio buttons en voor de check boxes. Bij tijd[] vind je vierkante haken achter zijn naam en bij fruit niet. Bij radio buttons mag je slechts één juist antwoord kiezen, dus de waarde van fruit is gewoon een string. Bij check boxes daarentegen, kan je meerdere mogelijkheden kiezen. PHP heeft dan een array nodig om alle antwoorden te stockeren. Aangeven dat tijd[] een array is in plaats van een scalar, wordt gedaan door middel van de vierkante haken achter de naam toe te voegen. Tot slot, er is een hidden form-element, stage. We gebruiken $stage om aan te geven of we de form wensen te tonen of dat we de resultaten wensen te verwerken. In plaats van het action attribuut kan ook het target attribuut gebruikt worden. In dat geval wordt bij de submit een nieuw venster gemaakt bovenop het bestaande venster. Verwerken van de form. De functie process_form() verwerkt de ingevulde form-velden: 1 <?php function p r o c e s s f o r m ( ) 3 { $naam = $ GET [ naam ] ; 5 i f ($ GET [ f r u i t ] == peer ) { $ f r u i t t e k s t = Vlaamse peren : sappig! ; } 7 e l s e i f ($ GET [ f r u i t ] == k r i e k ) { $ f r u i t t e k s t = Nederlandse k r i e k e n z i j n te zuur ; } 9 else { $ f r u i t t e k s t = Spaanse c i t r o e n t j e s : z e e r s m a k e l i j k ; } 23
11 $ f a v o r i e t t a l = count($ GET [ t i j d ] ) ; i f ( $ f a v o r i e t t a l <= 1) { 13 $ t i j d t e k s t = Je zou wat meer f r u i t moeten eten. ; } e l s e i f ( $ f a v o r i e t t a l > 1 && $ f a v o r i e t t a l < 4) { 15 $ t i j d t e k s t = Dit i s z e e r gezond. ; } else { 17 $ t i j d t e k s t = Je e e t te v e e l f r u i t. ; } 19 echo Hallo $naam. <br /> ; echo $ f r u i t t e k s t $ t i j d t e k s t ; 21 }?> De namen van de form velden zijn elementen van de superglobal $_GET en deze kunnen binnen de process_form() functie aangesproken worden. Er wordt getest welke fruit optie gekozen is en er wordt een passend antwoord gecreëerd. Dan gebruiken we count() om te berekenen hoeveel gezellige momenten gekozen zijn. Tussen haakjes, de betekenis van && is de logisch "en". Dus, $favoriet_tal moet zowel groter zijn dan 1 als kleiner dan 4 om de test waar te laten zijn. Op het einde van de functie wordt de naam van de persoon afgedrukt en onze appreciatie in verband met zijn houding ten aanzien van fruit. Het is duidelijk dat hier een heleboel andere, interessantere acties zouden kunnen uitgevoerd worden, zoals deze informatie in een databank stoppen, of de resultaten in een mooiere layout tonen. Het geheel samenstellen. Er moet nu nog een beetje code toegevoegd worden om de twee functies op het juiste moment te laten uitvoeren. Dus, voeg na de functiedefinities display_form() en process_form() de volgende code toe: <?php?> i f ( empty($ GET [ s t a g e ] ) ) { d i s p l a y f o r m ( ) ; } else { p r o c e s s f o r m ( ) ; } Eerst wordt er getest of de variabele $_GET["stage"] leeg is. In PHP wordt een variable als leeg beschouwd als de variabele nog geen waarde toegekend gekregen heeft of wanneer de waarde niets is (een lege string "" of nul, 0). Wanneer iemand de pagina opvraagt, dan is $_GET["stage"] leeg. Dus, de form wordt getoond. Op het moment van submitten, krijgt de variabele $stage de waarde resultaat. Op de server wordt als action dezelfde pagina behandeld, maar nu met een ingevulde $_GET["stage"] variabele: de form wordt verwerkt en het resultaat van de echo s wordt naar de client gestuurd. <?php 2 function p r o c e s s f o r m ( ) { 4 $voornaam = $ GET [ voornaam ] ; $ simpel = $ GET [ f r e e n k ] ; 6 $meervoudig = $ GET [ fremul ] ; $mededeling = $ GET [ melding ] ; 8 $n = count( $meervoudig ) ; 10 echo Hallo $voornaam. <br /> ; echo <p>mededeling : $ mededeling </p> ; 24
12 echo <p>je koos voor : $simpel </p> ; echo <p>je hebt ook $n mogelijkheden aangeduid : ; 14 echo <ul> ; for ( $ i =0; $ i < $n ; $ i++) 16 echo <l i > $meervoudig [ $ i ] </ l i > ; echo </ul ></p> ; 18 }?> 4.4 Een form bestaande uit meerdere pagina s. In HTTP is het niet gemakkelijk om data te transfereren van één pagina naar een andere, omdat er geen ingebouwd mechanisme is voor een reeks van pagina s. In PHP kunnen we toch voor het doorgeven van data zorgen en op die manier een form maken die over verschillende pagina s verspreid is. Het kernidee is dat alle input van een vorige pagina bewaard wordt als hidden variabelen in de form van de volgende pagina. Om dit te realiseren, krijgt de $state variabele een bijkomende functie. Als voorbeeld wordt de form uit het vorige voorbeeld in twee gedeeld. In het eerste deel wordt de naam gevraagd (functie display_naam()) en in het tweede deel de informatie omtrent fruit verbruik (functie display_fruit()). Daarnaast blijft er een derde functie om de form te verwerken. 1 <?php function display naam ( ) 3 {?> 5 <form a c t i o n= <?php echo $ SERVER [ PHP SELF ] ;?> method= post > Naam: <input type= t e x t name= naam /><br /> 7 <input type= hidden name= s t a g e value= f r u i t /> <input type= submit value= Verder naar keuzes! /> 9 </form> <?php 11 }?> De waarde van de volgende stage is aangepast naar fruit om nauwkeuriger te beschrijven wat er gaat komen. <?php function display_fruit() { $naam = $_POST["naam"]; echo $naam. ", een korte vragenlijst:<p>";?> <form action="<?php echo $_SERVER["PHP_SELF"];?>" method="post"> Favoriet fruit: <br /> <input type="radio" name="fruit" value="peer" /> Sappige vlaamse peren <br /> <input type="radio" name="fruit" value="kriek" /> Nederlandse krieken <br /> <input type="radio" name="fruit" value="citroen" /> Spaanse citroenen <br /> <br /><br /> Gezellige momenten om fruit te eten: <br /> <input type="checkbox" name="tijd[]" value="m" />Morgen <br /> <input type="checkbox" name="tijd[]" value="n" />Middag <br /> <input type="checkbox" name="tijd[]" value="t" />Tussendoor <br /> 25
<input type="checkbox" name="tijd[]" value="a" />Avond <br /> <br /><br /> <input type="hidden" name="naam" value="<?php echo htmlspecialchars($naam);?>" /> <input type="hidden" name="stage" value="resultaat" /> <input type="submit" value="dankuwel!" /> </form> < /p> <?php }?> De code van deze functie is bijna gelijk aan de code uit het enkelvoudige form voorbeeld. Om de informatie uit de eerste form, via deze form, door te geven zodat de verwerkende functie de informatie ter beschikking heeft, worden hidden variabelen gebruikt. In dit voorbeeld wordt de waarde van de variabele $naam afgedrukt in het hidden veld met NAME attribuut gelijk aan naam. Deze waarde kan echter niet zo maar afgedrukt worden, maar via de PHP functie htmlspecialchars(). In HTML zijn er vier tekens die niet mogen gebruikt worden tenzij als markup: <, >, ", and &. Om de browser niet in verwarring te brengen, wordt $naam door htmlspecialchars() gejaagd, zodat iemand met een naam oorspronkelijk gelijk aan "CD&V", deze naam "CD&V" blijft behouden. Wanneer deze tweede form gesubmit wordt, gaat geen informatie verloren. Daardoor moeten we bijna niets wijzigen aan de functie process_form(). Omdat we hier echter met de method POST gewerkt hebben, moet in het script dat de formgegevens verwerkt, de superglobal $_POST gebruikt worden. Tot slot moet een beetje toegevoegd worden aan de pagina-display logica. <?php i f ( empty($ POST [ s t a g e ] ) ) { display naam ( ) ; } e l s e i f ($ POST [ s t a g e ] == f r u i t ) { d i s p l a y f r u i t ( ) ; } else { p r o c e s s f o r m ( ) ; }?> Het enige wat hier toegevoegd is, is het elseif tussen de twee statements uit het originele voorbeeld. Indien nog meer form pagina s toegevoegd moeten worden, moeten alleen de display functies geschreven worden en in de if-controle moet bij de juiste elseif de oproep van elke display functie gebeuren. 4.5 Datums en tijden PHP heeft een heleboel functies om datums en tijden te tonen en te manipuleren. Met behulp van date() kan een datum of een tijd getoond worden in een bepaald formaat. Er zijn twee argumenten: het formaat dat beschrijft hoe de datum moet afgebeeld worden en de timestamp die aangeeft welke datum moet afgedrukt worden. De timestamp is gelijk aan het aantal seconden sinds 1 januari 1970. De timestamp voor juist nu, op dit moment wordt berekend met de functie time(). Voor timestamps voor andere momenten kan gebruik gemaakt worden van de functie mktime(). Deze functie heeft als argumenten (in deze volgorde): uur, minuut, seconde, maand, dag en jaar van het moment waarvoor je het aantal seconden sinds 1 januari 1970 wenst te berekenen. <?php $birthday stamp = mktime(19, 4 5, 0, 3, 1 0, 1 9 7 5 ) ; $ birthday formatted = date ( F d, Y g : i a, $birthday stamp ) ; echo David i s geboren op $ birthday formatted.?> 26
produceert David is geboren op March 10, 1975-7:45 p.m. Natuurlijk is de functie date() niet zo erg bruikbaar wanneer je een specifieke datum wil afdrukken omdat je het specieke formaat toch al op voorhand weet. Deze functies zijn wel handig om een deel van een form te genereren waarin de gebruiker een datum moet uitkiezen: <s e l e c t name= when > <?php $d = time ( ) ; for ( $ i = 0 ; $ i < 1 0 ; $ i++) { echo <o ption value=. $d. >. date ( F d, $d ). </option> ; $d += 8 6 4 0 0 ; }?> </ s e l e c t > Dit PHP script creëert een select box met 10 keuzes vandaag en elk van de volgende negen dagen. Voor de lus wordt de huidige datum in de variabele $d gestopt. Elke <option> wordt afgedrukt met een waarde gelijk aan de timestamp en een getoonde tekst gelijk aan de corresponderende maand en dag ( July 27, July 28, enz.) Na het afbeelden van de <option> wordt $d verhoogd met 86,400 (het aantal seconden in een dag 24 uren * 60 minuten * 60 seconden). Een aantal mogelijkheden voor de formatteer opties in het eerste argument: formatting code betekenins d dag van de maand (01..31) 16 D dag van de week (Mon, Tue,...) Wed l dag, tekstueel (Monday,...) Wednesday m maand (01..12) 04 M maand (Jan, Feb,...) Apr F maand, tekstueel (January,...) April y jaar, 2 cijfers 03 Y jaar, 4 cijfers 2003 z dag van het jaar (1..366) 105 a am of pm pm A AM of PM PM g uur, zonder leidende nul (1..12) 8 G uur, zonder leidende nul (0..23) 20 h uur (01..12) 08 H uur (00..23) 20 i minuten (00..59) 32 s seconden (00..59) 16 U aantal seconden sinds 1/1/1970 1050528736 Door het combineren van mktime() en date(), kan je informatie uitzoeken omtrent een specifieke datum die door een gebruiker opgegeven is. Veronderstel dat we wensen te weten komen op welke datum de eerste zondag (of een andere dag van de week) valt na een bepaalde datum. De functie die de specifieke form zal genereren en afbeelden: <?php function d i s p l a y f o r m ( ) { $dagen = array ( Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday ) ; $months = array ( 1 => January, February, March, April, May, June, July, August, September, 27
October, November, December ) ;?> <form a c t i o n= <?php echo $ SERVER [ PHP SELF ] ;?> method= g et > Zoek de e e r s t e <s e l e c t name= dotw > <?php for ( $ i = 0 ; $ i < 7 ; $ i++) { echo <option> $dagen [ $ i ] </option> ; } echo </ s e l e c t > na <s e l e c t name= maand > ; for ( $ i = 1 ; $ i <= 1 2 ; $ i++) { echo <o ption value=\ $ i \ > $months [ $ i ] </option> ; } echo </ s e l e c t > <s e l e c t name= dag > ; for ( $ i = 1 ; $ i <= 3 1 ; $ i++) { echo <option> $ i </option> ; } echo </ s e l e c t > <s e l e c t name= j a a r > ; $ s t a r t j a a r = date ( Y ) 1 0 ; $ e i n d j a a r = $ s t a r t j a a r + 2 0 ; for ( $ i = $ s t a r t j a a r ; $ i <= $ e i n d j a a r ; $ i++) { echo <option> $ i </option> ; } echo </ s e l e c t > ; echo <input type= hidden name= s t a g e value= p r o c e s s /> ; echo <input type= submit value= Doe het! /> </form> ; }?> De $maanden array is een beetje anders geinitialiseerd omdat we wensen dat de index van Januari gelijk is aan 1. Het is ook efficienter om een groot deel van de form programmatorisch te genereren in plaats van alle formelementwaarden expliciet op te sommen. Het enige gedeelte van display_form() dat niet in PHP mode is, is het stuk in het begin van de form. Door $start_jaar en $eind_jaar te zetten met behulp van date( Y ) wordt een jaar-bereik gemaakt 10 jaar voor en na het huidige jaar. De functie om de form te verwerken en de test (pagina-display logica) om het juiste gedeelte uit te voeren: <?php function p r o c e s s f o r m ( ) { $dotw = $ REQUEST[ dotw ] ; $month = $ REQUEST[ maand ] ; $day = $ REQUEST[ dag ] ; $year = $ REQUEST[ j a a r ] ; $timestamp = mktime(0, 0, 0, $month, $day, $year ) ; $next dotw = ; $ next timesta mp = $timestamp ; while ( $ next do tw!= $dotw ) { $next timestamp += 8 6 4 0 0 ; $next dotw = date ( l, $next timestamp ) ; // dag, tekstueel } $ f o r m a t t e d f i r s t = date ( F d, Y, $timestamp ) ; $ formatted next = date ( F d, Y, $next timestamp ) ; 28
echo De e e r s t e $dotw na $ f o r m a t t e d f i r s t i s $ formatted next. ; }?> <?php i f (empty($ REQUEST[ s t a g e ] ) ) { d i s p l a y f o r m ( ) ; } else { p r o c e s s f o r m ( ) ; }?> Deze functie vormt de ingegeven datum om naar een Unix timestamp. Eventueel zou hier code kunnen toegevoegd worden om na te gaan of alle ingegeven waarden wel binnen een aanvaardbaar bereik liggen. De lus wordt uitgevoerd zolang de dag van de week voor de volgende dag, die we aan het zoeken zijn, verschillend is van de dag van de week die de gebruiker gegeven heeft. Bij verschil wordt de volgende dag geincrementeerd (door er 86400 seconden = 24 uur * 60 minuten * 60 seconden bij op te tellen) en de waarde van de weekdag wordt herberekend. Wanneer de twee waarden gelijk zijn, drukt process_form() het volgende af: De eerste Sunday na June 25, 1999 is June 27, 1999. Soms is de datum-behandelingscode wat gecompliceerder om rekening te kunnen houden met daylight savings time en verschillende tijdszones. De functies date() and mktime() werken op tijden in de lokale tijdszone van uw machine. Indien je wenst te werken met tijden Greenwich Mean Time (GMT), kan je gmdate() en gmmktime() gebruiken. 4.6 Werken met bestanden Een bestand openen. Met de functie fopen kunnen we een bestand openen. Het resultaat is een file-pointer; deze filepointer wordt gebruikt om gegevens uit bestanden te halen en om gegevens naar bestanden weg te schrijven. De functie heeft 2 argumenten, de bestandsnaam en de manier hoe het geopend moet worden. De eerste spreekt voor zich, we geven gewoon een bestandsnaam als "bestanden/bestand.dat". Wanneer we een bestand in een bovenliggende map willen openen dan moeten we 2 punten gebruiken. Bijvoorbeeld wanneer we in de dir "htdocs/forum/" zitten en we willen het bestand "config.php" in "htdocs/" openen, dan gebruiken we als bestandsnaam "../config.php". Het tweede argument bevat 1 of 2 tekens die aangeven hoe het bestand geopend moet worden. Hierbij kunnen we een bestand zodanig openen dat we het kunnen lezen en/of schrijven. Ook kunnen we een bestand aanmaken. Als we iets willen schrijven moeten we ook weten of we het aan het begin van het bestand willen schrijven, aan het eind van het bestand of juist over alle huidige inhoud van het bestand heen. r r+ w w+ a a+ openen voor alleen lezen. Beginnen met lezen bij het begin van het bestand. voor lezen en schrijven. Beginnen met lezen/schrijven bij het begin van het bestand. openen om alleen te schrijven. Schrijft alle bestaande gegevens in het bestand over. openen voor lezen en schrijven. Schrijft alle bestaande gegevens in het bestand over. openen voor alleen schrijven. Beginnen met schrijven aan het einde van het bestand. openen voor lezen en schrijven. Beginnen met lezen/schrijven aan het einde van het bestand. Bij de modes w, w+, a en a+ wordt het bestand aangemaakt indien deze niet bestaat. Voor het schrijven naar bestanden en/of het aanmaken van bestanden is er toestemming van de server nodig om te kunnen schrijven. Dit betekent een chmod van 766 of 777. Wij raden aan 766 te gebruiken. Indien deze niet werkt raden wij aan 777 te gebruiken. Dit heeft te maken met de permissies die de gebruikers hebben. Een webserver met minder permissies is wellicht veiliger. 29
Voor het lezen hebben we slechts 444 nodig. Wij raden echter aan voor alle bestanden waar alleen van gelezen wordt 744 te gebruiken. Indien er bestanden op de server uitgevoerd moeten worden (PHP scripts worden niet uitgevoerd), dan moet u de chmod instellen op 755 of met meer permissies. Einde van het bestand. Bij het lezen van bestanden is het belangrijk om te weten wanneer men aan het einde van het bestand is. Waarom zal duidelijk worden in het voorbeeld bij Gegevens lezen uit een bestand. Om dit te doen gebruiken we de functie feof(), wat staat voor File End Of File. Deze functie heeft 1 argument nodig. Dit is namenlijk de file pointer. Meestal gebruiken we als file pointer de variabele $file of $fp. Deze functie geeft TRUE terug indien we aan het einde van het bestand zijn en anders geeft het simpelweg FALSE terug. Gegevens lezen uit een bestand. Nu we inmiddels weten hoe we een bestand kunnen openen gaan we er ten eerste gegevens uit lezen. We doen dit met de functie fgets() om een hele tekstregel in één keer uit het bestand te halen. We kunnen ook fgetc() gebruiken om slechts 1 teken uit het bestand te halen. Het volgende voorbeeld haalt elke tekstregel stuk voor stuk uit het bestand en deze geeft het gelijk weer als output d.m.v. echo. <?php $bestand = f i l i n. dat ;?> $ f i l e = fopen ( $bestand, r ) ; i f ( $ f i l e!= f a l s e ) { while (! feof ( $ f i l e ) ) { $ t e k s t r e g e l = fgets ( $ f i l e, 8 0 ) ; echo $ t e k s t r e g e l ; } } else { echo Fout b i j het openen van het bestand! ; } fclose ( $ f i l e ) ; Als eerste maakt dit script een variabele genaamd bestand aan. Dan openen we $bestand (nl. filin.dat ) met als mode r. Hiermee geven we dus aan dat wel alleen willen lezen en willen beginnen met lezen aan het begin van het bestand. Vervolgens controleren we nog even of het bestand wel goed geopend is en zo ja dan gaan we van het bestand lezen. Om de gegevens uit het bestand te lezen gebruiken we een while loop. Zolang we niet aan het einde van het bestand zijn lezen we telkens één tekstregel in. Deze slaan we tijdelijk op in $tekstregel. Gelijk daarna geven we $tekstregel als output dmv de opdracht echo. Wanneer het bestand niet goed geopend is dan geven we als output de tekst Fout bij het openen van het bestand!. Het volgende voorbeeld doet ongeveer hetzelfde: alleen wordt het bestand per teken ingelezen en niet per complete tekstregel. <?php $bestand = f i l i n c. dat ; $ f i l e = fopen ( $bestand, r ) ; i f ( $ f i l e!= f a l s e ) { while (! feof ( $ f i l e ) ) { $char = fgetc ( $ f i l e ) ; 30
?> i f ( $char!= f a l s e ) { echo $char ; } } } else { echo Fout b i j het openen van het bestand! ; } fclose ( $ f i l e ) ; Door de functie file() te gebruiken, moet het bestand niet expliciet geopend en gesloten worden. Deze functie leest het volledige bestand in een array in, elke arrayelement bevat een lijn van het tekstbestand. <?php $ l i j n e n = f i l e ( f i l a n. dat ) ; $n = count( $ l i j n e n ) ; for ( $t = 0 ; $t < $n ; $t++ ) { echo $ l i j n e n [ $t ] ; }?> Schrijven naar bestanden. Het schrijven naar een bestand werkt bijna hetzelfde als het lezen van een bestand. Schrijven doen we met de functie fputs(). De functie fputs() heeft 3 argumenten. De eerste is de file pointer (meestal $file of $fp). Het tweede argument is de tekstregel die we willen toevoegen aan het bestand. Het derde argument is de lengte van de tekstregel. Hiervoor gebruiken we vrijwel altijd strlen($tekstregel) waarbij $tekstregel natuurlijk de tekstregel is. De functie strlen() geeft de lengte van een tekstregel terug. Het volgende voorbeeld kopieërt het bestand orgineel.dat naar het bestand nieuw.dat. Natuurlijk is deze manier van kopiëren wat omslachtig. <?php $ o r i g i n e e l = f i l i n. dat ; $nieuw = /tmp/ nieuw. dat ;?> $fpoud = fopen ( $ o r i g i n e e l, r ) ; $fpnew = fopen ( $nieuw, w ) ; i f ( ( $fpoud!= f a l s e ) && ( $fpnew!= f a l s e ) ) { while (! feof ( $fpoud ) ) { $ t e k s t r e g e l = fgets ( $fpoud, 8 0 ) ; fputs ( $fpnew, $ t e k s t r e g e l, s t r l e n ( $ t e k s t r e g e l ) ) ; } } else { echo Fout b i j het openen van de bestanden! ; } fclose ( $fpoud ) ; fclose ( $fpnew ) ; In plaats van de fputs functie kan ook de fwrite functie gebruikt worden. Bij deze functie moet het derde argument niet gespecificeerd worden. 31
Bestand sluiten. Zoals je bij de voorbeelden misschien al opgevallen is, worden de bestanden weer gesloten met de functie fclose(). Voor een op alle servers goed werkend script dienen bestanden altijd netjes gesloten te worden indien er (in dat script) niets meer van gelezen wordt of niets meer naar geschreven wordt. Een uitgebreid voorbeeld. Nu we bestanden kunnen openen en lezen kunnen we al een redelijk dynamische pagina in elkaar zetten. Het voorbeeld hieronder is een eenvoudig gastenboek. Iedereen kan via het formulier een bericht toevoegen. Alle berichten worden gewoon onder elkaar neergezet en er is geen limiet aan het aantal berichten dat in het gastenboek komen. <?php 2 // Het bestand waar alle berichten in opgeslagen worden $bestand = /tmp/ gastenboek. dat ; 4 i f ( i s s e t ($ REQUEST[ s t a g e ] ) ) 6 { $ t e k s t = <b>. $ REQUEST[ t i t e l ]. </b> : ; 8 $ t e k s t.= $ REQUEST[ b e r i c h t ]. <br />\n ; $ f i l e = fopen ( $bestand, a ) ; 10 i f ( $ f i l e!= f a l s e ) { 12 fputs ( $ f i l e, $ tekst, s t r l e n ( $ t e k s t ) ) ; echo <br />Bericht i s toegevoegd!< br /> ; 14 } else 16 { echo Fout b i j het openen van bestand! ; 18 } fclose ( $ f i l e ) ; 20 } else 22 { i f ( i s f i l e ( $bestand ) ) 24 { $fp = fopen ( $bestand, r ) ; 26 echo Inhoud : <br /> ; while (! feof ( $fp ) ) 28 { echo fgets ( $fp, 1 0 2 4 ) ; 30 } fclose ( $fp ) ; 32 } echo <br /><hr />Bericht toevoegen :<br /><br /> ; 34 echo <form a c t i o n=. $ SERVER [ PHP SELF ]. method= post > ; echo T i t e l :< br /> ; 36 echo <input type= t e x t name= t i t e l value= s i z e = 30 /> ; echo <br /><br /> Bericht :< br /> ; 38 echo <t e x t a r e a name= b e r i c h t c o l s = 30 rows= 8 > ; echo </textarea ><br /> ; 40 echo <input type= hidden name= s t a g e value= ingevuld /> ; echo <i n p u t type= submit name= submit 42 value= Toevoegen s i z e = 30 /></form> ; } 44?> 32
4.7 Cookies Zetten en lezen van cookies is gemakkelijk in PHP. Om een cookie te creëren of aan te passen kan de PHP functie setcookie() gebruikt worden. Deze functie kan tot zes argumenten hebben, afhankelijk van hoeveel controle je over de cookie wenst te hebben en wie de waarde van de cookie kan lezen. De eenvoudigste manier om een cookie te zetten: <?php setcookie ( naam, jan ) ;?> Bij elke volgende pagina op uw site die door deze browser bezocht wordt (zonder dat de gebruiker de site verlaat), heb je de waarde jan gestockeerd in de variabele $naam ter beschikking om vanuit PHP gemakkelijk aan te spreken. Dit type cookie wordt session cookie genoemd, omdat die actief blijft gedurende de lengte van een sessie van een gebruiker. Indien je wenst dat de cookie blijft bestaan nadat de persoon zijn of haar browser afgesloten heeft, moet aan setcookie() een derde argument gegeven worden, de Unix timestamp van het moment wanneer de cookie mag vervallen (het aantal seconden sinds 1 januari 1970). Bijvoorbeeld, als de cookie mag vervallen op 1 januari 2005: <?php $ v e r v a l = mktime ( 0, 0, 0, 1, 1, 2 0 0 5 ) ; setcookie ( naam, jan, $ v e r v a l ) ;?> Indien de cookie moet gewijzigd worden om een nieuwe waarde te stockeren, kan de waarde eenvoudig overschreven worden. Zelfs indien je bovenstaande cookie reeds op een vorige pagina gestuurd hebt, kan je de waarde van de naam cookie wijzigen in marie: <?php $ v e r v a l = mktime ( 0, 0, 0, 1, 1, 2 0 0 5 ) ; setcookie ( naam, marie, $ v e r v a l ) ;?> Merk op dat deze code niet de waarde van de PHP variabele $naam wijzigt. Deze variabele krijgt zijn waarde wanneer de pagina geladen wordt. Indien je er zeker wil van zijn dat de naam cookie en de $naam variabele steeds dezelfde waarde hebben, kan je dit realiseren met de volgende code: <?php $naam = marie ; $ v e r v a l = mktime( 0, 0, 0, 1, 1, 2 0 0 5 ) ; setcookie ( naam, $naam, $ v e r v a l ) ;?> De vierde en de vijfde parameter van setcookie() geven u de controle over het pad en het domein van wie je cookie kan lezen. Normaal kunnen alleen pagina s gelijk aan of lager in de hiërarchie op dezelfde server die de cookie gestuurd heeft, de waarde lezen omwille van veiligheidsargumenten. Wanneer je echter een account hebt dat soms "www.domain.com" is, maar ook "other.domain.com" is mogelijk, en met uw account is het mogelijk om pagina s te leveren vanuit ~/myhome, dan kan je best setcookie() als volgt aanpassen: <?php setcookie ( naam, marie, $ verval, /myhome,. domain. com ) ;?> De laatste parameter van setcookie(), tot nu nog niet gebruikt, geeft aan dat de cookie alleen mag verstuurd worden via een veilige HTTP verbinding (zoals SSL). Om dit te realiseren, moet de zesde waarde gelijk gezet worden aan 1. Het verwijderen van een cookie is eenvoudig door in setcookie() alleen de naam van de cookie door te geven. PHP zal dan er voor zorgen dat de cookie verwijderd wordt. <?php setcookie ( naam ) ;?> Omdat cookies gezet worden in de HTTP header, moet setcookie() opgeroepen worden voordat enige vorm van output gegenereerd wordt. Indien je later nog setcookie() oproept, geeft PHP een waarschuwing en worden de cookies niet verstuurd. 33
Goed: <?php setcookie ( naam, marie ) ; echo Dag i e d e r e e n! ;?> Verkeerd: <?php echo Dag i e d e r e e n! ; setcookie ( naam, marie ) ;?> Een lijst van cookies die actueel in een mozilla browser ingeladen zijn, kan teruggevonden worden onder Edit/Preferences/Privacy & Security. Onder het item Cookies kan op Managed Stored Cookies geklikt worden. 34
5 JavaScript 5.1 Ontstaan JavaScript is ontwikkeld door Netscape. Voordat Sun met de programmeertaal Java kwam, heette JavaScript nog LiveScript, en was bedoeld om meer interactiviteit op Web-pagina s mogelijk te maken dan mogelijk was met CGI-scripts. Toen Java een groot succes werd, paste Netscape de naam aan en voegde wat extra functionaliteit toe. Alhoewel Java en JavaScript verschillende overeenkomsten hebben, zijn er toch ook veel verschillen. Het meest kenmerkende verschil is dat JavaScript veel beter met HTML kan samenwerken dan Java. JavaScript is een voorbeeld van client-side scripting. Men kan scripts schrijven die reageren op keuzes die een gebruiker maakt in een formulier, of op veranderingen in de browser. 5.2 De objecthiërarchie JavaScript is object-gebaseerd en maakt gebruikt van het DOM (Document Object Model). Een object is een speciaal gegevenstype, waarin gegevens en functies gecombineerd worden. Er worden niet alleen een hele reeks waarden in opgeslagen; elk object heeft bovendien functies in zich, waarmee het rechstreeks de opgeslagen waarden kan bewerken. De properties (of eigenschappen) zijn de onderdelen van een object waarin de losse gegevens bewaard worden. De methoden zijn functies die aan een bepaald object gekoppeld waarmee operaties op de properties van het object kunnen uitgevoerd worden. In het hiërarchisch systeem van parent- en child-objecten is een child-object een object dat als property van het parent-object dient. window location history document navigator images[] forms[] anchors[] links[] elements[] Object Omschrijving window Dit object staat bovenaan in de hiërarchie, soms ook self of top genoemd. Het heeft methods en properties om met het browservenster te werken. location bevat methods en properties om met de huidige URL te werken. history om de history-list van de browser aan te spreken en er gebruik van te maken. document het meest gebruikte object in JavaScript: om te werken met de HTMLelementen, formulieren, links en beelden. navigator losstaand object met informatie over de gebruikte browser. Voorbeeld 1: in dit script worden een aantal properties van de verschillende hierboven vermelde objecten geïllustreerd door deze uit te schrijven met behulp van de write() methode van het document object. Er is ook een onclick-eventhandler opgenomen. <html> 2 <head> < t i t l e > een J a vascript s c r i p t </ t i t l e > 4 </head> <body> 6 h i e r o n d e r komt het <hr /> 8 <s c r i p t type= t e x t / j a v a s c r i p t > 35
document. w r i t e ( <h1>dikke g r o t e t i t e l :, window. name, </h1> ) ; 10 document. w r i t e ( l o c a t i e van deze pagina : +document.url) ; document. w r i t e ( <hr /> l a a t s t e a a npassing op : +document. l a s t M o d i f i e d ) ; 12 document. w r i t e ( <hr />h i e r g esukkeld via : +document. r e f e r r e r ) ; document. w r i t e ( <hr /> v o l l e d i g e URL: +l o c a t i o n. href ) ; 14 document. w r i t e ( <hr />a a ntal bezochte paginas : +h i s t o r y. length ) ; document. w r i t e ( <hr />g e b r u i k t e navigator : +navigator. appcodename ) ; 16 </ s c r i p t > <hr /> 18 en nu een eventhandler <form> 20 <input type= button value= hoi onclick= a l e r t ( K e i t o f? ) /> </form> 22 <hr /> dat was het 24 </body> </html> 5.3 Variabelen, operatoren en functies Een variabele kan op twee manieren gedeclareerd worden: impliciet : totaal = 37; expliciet : var totaal = 37; Bij de impliciete declaratie moet de variabele direct een waarde krijgen. Bij de expliciete declaratie hoeft deze initialisatie niet, alleen de naam vooraf gegaan door het keyword var is voldoende. Elke variabele heeft een bereik: een code-gedeelte waarin de variabele geldig of zichtbaar is: lokaal: de variabele is uitsluitend zichtbaar in de functie waarbinnen hij gedeclareerd is: functievariabelen die met het keyword var worden gedeclareerd en functieparameters; globaal: variabelen gedeclareerd buiten een functie, bijvoorbeeld in de header van een webpagina en impliciet gedeclareerde functievariabelen: alle JavaScript-statements in de webpagina hebben toegang tot de variabele. Op variabelen kunnen bewerkingen gebeuren met behulp van operatoren. klassieke operatoren: 1. string: + 2. rekenkundig: ++, --, *, /, %, +, - 3. vergelijking: <=, >=, >, <, ==,!= 4. bit: <<, >>, &, ^, 5. logisch: &&,,! 6. toewijzing: =, *=, /=, %=, +=, -= 7. voorwaardelijke:?: JavaScript kent de Een functie is een stuk code dat een bepaalde taak uitvoert. Door de code als een functie op te nemen in het script, kan de taak op verschillende plaatsen uitgevoerd worden zonder telkens de volledige code te herhalen maar door de functie op te roepen. Een functie wordt gedefinieerd met het keyword function, gevolgd door de naam. Hierna komen tussen haakjes de eventuele parameters. De code die moet uitgevoerd worden staat tussen akkolades ({...}). Een functie accepteert argumenten die bij de oproep meegegeven worden, in de parametervariabelen. 36
Een functie kan met het keyword return een waarde teruggeven aan het oproepende statement. Voorbeeld 2: illustratie van variabelen en een functie. 1 <html> <head> 3 < t i t l e > v a r i a b e l e n en f u n c t i e s </ t i t l e > <s c r i p t type= t e x t / j a v a s c r i p t > 5 function t e l o p ( a, b ) { 7 var r e s u l t a a t = a + b ; /* lokaal */ f a c t o r = 2 ; /* impliciet, dus globaal */ 9 return r e s u l t a a t ; } 11 </ s c r i p t > </head> 13 <body> <s c r i p t type= t e x t / j a v a s c r i p t > 15 var x, y, fsom, s t r ; /* globaal */ document. w r i t e ( <h2>een f u n c t i e </h2> ) ; 17 var som = t e l o p ( 1 0, 2 0 ) ; document. w r i t e ( <p>twee g e t a l l e n : + som + </p> ) ; 19 var woord = t e l o p ( j e f, ke ) ; document. w r i t e ( <p>twee woorden : + woord + </p> ) ; 21 s t r = prompt ( een e e r s t e g e t a l, 0. 0 ) ; x = p a r s e F l o a t ( s t r ) ; 23 s t r = prompt ( een tweede g e t a l, 0. 0 ) ; y = p a r s e F l o a t ( s t r ) ; 25 fsom = f a c t o r t e l o p ( x, y ) ; document. w r i t e ( <p>twee g e t a l l e n : + x + + + y + = + fsom + </p>); 27 </ s c r i p t > <hr /> 29 </body> </html> De + (plus) operator kan zowel op getallen als op strings toegepast worden. Bij het optellen van twee strings wordt de string rechts van de operator aan het einde van de string links van de operator geplakt en er ontstaat één lange string. Het resultaat van de prompt functie is een string. In het voorbeeld wordt deze string omgezet naar een floating-point getal met behulp van de functie parsefloat. Er bestaat ook een functie parseint die een string naar een integer converteert. 5.4 De controlestructuren Met behulp van deze structuren heeft men controle over de wijze waarop het programma wordt uitgevoerd. De klassieke structuren zijn: if ( conditie ) statement else statement while ( eind conditie ) statement for ( teller ; eind conditie ; tellerbewerking ) statement break en continue. Daarnaast bestaan er twee andere controlestructuren die speciaal zijn ontworpen voor de behandeling van objecten: for ( waarde in object ) statement 37
with ( object ) statement In al deze controlestructuren staat statement voor één statement of voor een compound statement, d.i. een groep statements tussen akkolades ({...}). 5.5 Zelf gemaakte objecten Het is mogelijk om zelf eigen objecten te maken. Hiervoor moet een functie geschreven worden die als naam de naam van het nieuwe object krijgt en als taak heeft de verschillende properties van het object te benoemen en te initialiseren. Zo n functie heet een objectdefinitie of een constructor. In deze functie wordt het keyword this gebruikt: dit verwijst naar het huidige object dat gecreëerd wordt. De effectieve creatie van een instantie van een object gebeurt met het keyword new gevolgd door de constructor met als argumenten de waarden die de verschillende properties van de instantie moeten krijgen. Voorbeeld 3: definitie van een constructor, creatie van enkele objecten en weergave ervan in een tabel. <html> 2 <head> < t i t l e > e i g e n o b j e c t e n in een tabel </ t i t l e > 4 <s c r i p t type= t e x t / j a v a s c r i p t > function Kampioen (naam, p r i j s, p l a a t s ) /* constructor */ 6 { t h i s. naam = naam ; 8 t h i s. p r i j s = p r i j s ; t h i s. p l a a t s = p l a a t s ; 10 } var a = new Kampioen ( j o s, 1700, mechelen ) ; /* creatie */ 12 var b = new Kampioen ( marieke, 3400, d u f f e l ) ; var c = new Kampioen ( wiezeke, 1900, kontich ) ; 14 var d = new Kampioen ( f r a n c i n e, 1000, l i n t ) ; var som = 0 ; /* globaal */ 16 function t a b e l r i j ( een, twee, d r i e ) { 18 document. w r i t e ( <tr ><td>, een, </td><td>, twee, </td><td>, 20 drie, </td></tr> ) ; som += twee ; 22 } </ s c r i p t > 24 </head> <body> 26 <hr /> <p a l i g n = c e n t e r > 28 <t a b l e c e l l p a d d i n g = 4 c e l l s p a c i n g = 13 border= 1 > <s c r i p t type= t e x t / j a v a s c r i p t > 30 t a b e l r i j ( a. naam, a. p r i j s, a. p l a a t s ) ; t a b e l r i j ( b. naam, b. p r i j s, b. p l a a t s ) ; 32 t a b e l r i j ( c. naam, c. p r i j s, c. p l a a t s ) ; t a b e l r i j ( d. naam, d. p r i j s, d. p l a a t s ) ; 34 t a b e l r i j (, som, ) ; </s c r i p t > 36 </table > </p> 38
38 <hr /> </body> 40 </html> Het is in JavaScript mogelijk om nog nieuwe properties toe te voegen nadat een object gecreëerd is. Zo n toevoeging heeft enkel effect op de instantie van het object dat vermeld wordt in het toekenningsstatement. Andere instanties van het object krijgen niet automatisch deze bijkomende property. Indien alle instanties de bijkomende property moeten hebben, moet de constructor aangepast worden. Een array is een genummerde verzameling variabelen of een geordende verzameling gerelateerde gegevens. In JavaScript zijn alleen eendimensionale arrays beschikbaar. Het eerste element in een array heeft volgnummer 0. Een nieuwe array maken gebeurt met het keyword new gevolgd door de naam van het object, d.i. Array: kolom = new Array ( ) ; r i j = new Array ( 2 0 ) ; t a b e l = new Array ( l i n t, d u f f e l mechelen, kontich, walem ) ; t a b e l. r e v e r s e ( ) ; t a b e l. sort ( ) ; In het tweede geval wordt de grootte van de array aangegeven bij de creatie. Dit heeft echter geen specifieke voordelen, omdat de grootte steeds kan aangepast worden. De property length bevat het aantal elementen in de array. Daarnaast zijn er ook enkele methodes, bijvoorbeeld reverse() om de volgorde van de elementen in een array om te draaien en sort() om de elementen oplopend op ASCII-volgorde te sorteren. Voorbeeld 4: illustratie van de controlestructuren voor de behandeling van objecten. 1 <html> <head> 3 < t i t l e > een a r r a y van e i g e n o b j e c t e n </ t i t l e > <s c r i p t type= t e x t / j a v a s c r i p t > 5 function Kampioen (naam, p r i j s, p l a a t s ) { 7 t h i s. naam = naam ; t h i s. p r i j s = p r i j s ; 9 t h i s. p l a a t s = p l a a t s ; } 11 var a = new Kampioen ( j o s, 1700, mechelen ) ; var b = new Kampioen ( marieke, 3400, d u f f e l ) ; 13 var c = new Kampioen ( wiezeke, 1900, kontich ) ; var d = new Kampioen ( f r a n c i n e, 1000, l i n t ) ; 15 var e = new Kampioen ( ) ; with ( e ) 17 { naam = marcel ; 19 p r i j s = 0 ; p l a a t s = walem ; 21 } function t a b e l r i j ( x ) 23 { document. w r i t e ( <tr > ) ; 25 for ( element in x ) document. w r i t e ( <td>, x [ element ], </td> ) ; 27 document. w r i t e ( </tr> ) ; } 29 </ s c r i p t > 39
</head> 31 <body> <hr /> 33 <p a l i g n = c e n t e r > <s c r i p t type= t e x t / j a v a s c r i p t > 35 var l i j s t = new Array ( a, b, c, d, e ) ; b. gsm = 0475.123456 ; 37 document. w r i t e ( <t a b l e c e l l p a d d i n g = 8 c e l l s p a c i n g = 8 border= 1 > ) ; for ( var i = 0 ; i< l i j s t. length ; i ++) 39 { t a b e l r i j ( l i j s t [ i ] ) ; 41 } document. w r i t e ( </table > ) ; 43 l i j s t. r e v e r s e ( ) ; </ s c r i p t > 45 </p> <hr /> 47 </body> </html> 5.6 Events 5.6.1 Types Wanneer de gebruiker een koppeling aanklikt, tekst selecteert of invoert of zelfs maar de muis over een afbeelding op een pagina beweegt, wordt een event (gebeurtenis) gegenereerd. Op zo n event kan gereageerd worden door een scriptje te schrijven dat het event opvangt. Een overzicht van alle mogelijke events: Event Abort Blur Click Change Error Focus Load Mouseout Mouseover Reset Select Submit Unload Beschrijving treedt op wanneer de gebruiker het laden van een afbeelding afbreekt. treedt op wanneer de focus van een tekstveld of venster wordt opgeheven door bijvoorbeeld op de Tab toets te drukken of in een ander tekstveld te klikken. treedt op wanneer de gebruiker op een koppeling, knop of ander formulierelement klikt. treedt op wanneer de huidige waarde van een formulierveld veranderd wordt door de gebruiker (bij het indrukken van Enter). treedt op wanneer er een fout ontstaat tijdens het laden van een document of een afbeelding. treedt op wanneer een formulierelement of venster de focus krijgt doordat de gebruiker erin klikt, of de cursor er met de Tab-toets naartoe beweegt. treedt op wanneer een pagina in de browser wordt geladen. treedt op wanneer de muiscursor buiten het gebied van een koppeling beweegt. treedt op wanneer de muiscursor over een koppeling beweegt. treedt op wanneer de gebruiker een formulier leegmaakt door op de knop Reset van het formulier te klikken. treedt op wanneer de gebruiker tekst in een tekstveld van een formulier selecteert. treedt op wanneer de gebruiker op de knop Submit van het formulier te klikt. treedt op wanneer een pagina verlaten wordt (wanneer een andere pagina geladen wordt, of bij het beëindigen van het programma). Voorbeeld : imagemap met mouse events. 40
<html> 2 <head> <t i t l e >Events op een imagemap</ t i t l e > 4 <meta http equiv= Content Type content= t e x t /html ; c h a r s e t=iso 8859 1 > <s c r i p t type= t e x t / j a v a s c r i p t > 6 function showtext ( p l a c e ) { 8 document. tekstvak. d i s p l a y. value = p l a c e ; } 10 </s c r i p t > <map name= mapdis. map i d= mapdis. map > 12 <area shape= polygon onmouseover= showtext ( Cash Register ) onmouseout= showtext ( ) a l t = Cash 14 coords = 9 4,10,95,25,96,26,98,75,101,77,102,92,74,97,4,94, 4, 7 9, 2 2, 7 6, 2 9, 3 7, 4 4, 2 5, 4 4, 1 1, 9 4, 9 href= pech. html > 16 <area shape= polygon onmouseover= showtext ( Cellular P hone ) onmouseout= showtext ( ) a l t = C e l l Phone 18 coords = 2 0 6,2,209,19,209,63,203,97,173,97,179,71,178, 1 3,199,13,203,0 href= pech. html > 20 <area shape= polygon onmouseover= showtext ( Toy Box ) onmouseout= showtext ( ) a l t = box 22 coords = 3 5 6,25,359,75,289,77,290,23,358,24 href= pech. html > <area shape= d e f a u l t nohref> 24 </map> </head> 26 <body> <h1>image map met d e t e c t i e van muisbewegingen </h1> 28 <div a l i g n= c e n t e r > <img s r c= mapdis. g i f a l t = Image Map Display border= 0 30 width = 367 height = 106 usemap= #mapdis. map /> <br /> 32 <form name= tekstvak > <input type= t e x t name= d i s p l a y s i z e = 20 /> 34 </form> </div> 36 </body> 38 </html> 5.6.2 Eventhandlers Een eventhandler is een stuk code dat het event dat is opgetreden, herkent en er iets mee doet. Zo n eventhandler wordt direct als attribuut in de tagdefinitie opgenomen. en niet tussen <script>...</script> gezet. De eventhandler bestaat uit de naam van het event, voorafgegaan door on, bijvoorbeeld, onclick. <HTML_tag attributen onevent="javascript-code"> Het is aan te raden om het JavaScript-code gedeelte beperkt te houden, bijvoorbeeld het oproepen van een functie die in de header van de web-pagina gedefinieerd is. Niet alle objecten herkennen alle events. Een overzicht van de beschikbare eventhandlers voor de verschillende objecten: 41
Object selectielijst text-invoervak textarea-invoervak element button checkbox radiobutton standaardknop reset standaardknop submit hypertekst-koppeling regio van een imagemap document window framesets formulier afbeelding Beschikbare eventhandler onblur, onchange, onfocus onblur, onchange, onfocus, onselect onblur, onchange, onfocus, onselect onclick onclick onclick onclick onclick onclick, onmouseover, onmouseout onmouseover, onmouseout onload, onunload, onerror onload, onunload, onblur, onfocus, onerror onblur, onfocus onsubmit, onreset onload, onerror, onabort, onmouseover, onmouseout 5.6.3 Events emuleren Soms is het nuttig om een bepaald event zelf expliciet te genereren, zonder een tussenkomst van de gebruiker. Een voorbeeld is om vanuit een script de focus op een bepaald veld in te stellen, zodat de gebruiker daar gegevens in kan voeren. Een tweede toepassing is een ingevuld formulier eerst controleren voordat het naar de server gestuurd wordt, door de muisklik op de knop Submit op te vangen met een eventhandler. De gegevensinvoer kan dan gecontroleerd worden en indien er geen fouten aanwezig zijn kan het ingevulde formulier naar de server doorgestuurd worden door vanuit het script het event submit te emuleren. Indien er toch fouten ontdekt worden, kan de gebruiker daarop gewezen worden zodat er verbeteringen kunnen aangebracht worden. Deze werkwijze voorkomt dat het formulier een paar keer nutteloos over het Internet naar de server heen en weer reist. Er kunnen zes events geëmuleerd worden: blur(), click(), focus(), reset(), select(), submit(). De expliciete generatie gebeurt door deze functieoproep op te nemen in het script. Het is natuurlijk mogelijk dat hierdoor een eventuele eventhandler geactiveerd wordt. Hierdoor kan het script in een oneindige lus verzeild geraken. Bij het submitten van een formulier kan door middel van een JavaSript routine gecontroleerd worden of alle belangrijke velden wel ingevuld zijn. Indien dit niet zo is, wordt de form-informatie niet doorgestuurd naar de server maar wordt teruggekeerd naar het formulier. De testroutine geeft de waarde true of false terug afhankelijk van het feit of de gegevens mogen doorgestuurd worden of niet: function testnaam ( ) 2 { i f ( document. forms [ 0 ]. elements [ 0 ]. value == ) 4 { a l e r t ( Het veld NAAM moet ingevuld worden! ) ; 6 document. forms [ 0 ]. elements [ 0 ]. f o c u s ( ) ; return f a l s e ; 8 } else 10 { return true ; 12 } } De eventhandler bij de form-tag: <form name="invul" method="get" action="..." onsubmit="return testnaam();" 42
5.7 Het window object Het object window bevindt zich bovenaan in de objecthiërarchie. Elk open venster van de browser kent een eigen window object. Methode alert( tekst ); confirm( tekst ); prompt( tekst, waarde ); open( url, naam, opties ); close(); focus(); blur(); settimeout("stat", tijd); cleartimeout(id); Omschrijving toont een dialoogvenster met een waarschuwing voor de gebruiker; de gebruiker kan alleen op OK klikken. toont een dialoogvenster waarin de gebruiker om een bevestiging wordt gevraagd; geeft de waarde true terug bij klikken op OK en de waarde false bij klikken op Cancel. zet een boodschap op het scherm en vraagt de gebruiker iets in te voeren. Als standaardwaarde wordt waarde getoond. Het resultaat is een string met de door de gebruiker ingevoerde tekst. Deze string moet eventueel expliciet omgezet worden naar een getal. het creëren van een nieuw venster; url is de URL van de pagina die het nieuwe venster getoond wordt; naam wordt toegewezen aan de name property van het nieuwe venster en opties specificeren de vorm van het nieuwe venster. De returnwaarde is een object van de soort window. het sluiten van een window. geeft een bepaald venster de keyboard-focus: het venster wordt op de meeste platformen naar de voorgrond gebracht. verwijdert de keyboard-focus: het venster wordt op de meeste platformen naar de achtergrond geplaatst. het starten van een timeout; de parameter stat moet tussen dubbele aanhalingstekens staan en geeft de statements die moeten uitgevoerd worden wanneer de wachttijd voorbij is. Deze wachttijd wordt in de tweede parameter, tijd, in milliseconden gespecificeerd. Het resultaat is een timer identificatie, ID. het stoppen van een lopende timer, aangegeven door ID. Meestal is er maar één window object. In dat geval mag de naam worden weggelaten als een methode van het object opgeroepen wordt. alert( Keitof? ); kan dus ook geschreven worden als window.alert( Keitof? );. De property name bevat de naam van het venster. Bij het starten van de browser is deze property leeg. Bij het maken van een nieuw bijkomend venster met de methode window.open(), krijgt dit venster wel een naam. Met de property status kan de tekst in de statusbalk naar believen gezet worden. De statusbalk is de grijze balk onder in het venster van de browser. Deze wordt meestal gebruikt om de URL weer te geven wanneer de muiscursor over een link beweegt. Met het derde argument van window.open(), de opties, kan het uiterlijk van het nieuw te maken venster bepaald worden: Optie height=aantal pixels width=aantal pixels resizable=yes no location=yes no menubar=yes no scrollbars=yes no toolbar=yes no status=yes no Werking de hoogte van het nieuwe venster. de breedte van het nieuwe venster. afmetingen zijn wel of niet te wijzigen door de gebruiker. toont wel of niet de URL in het nieuwe venster. toont wel of geen menubalk. toont wel of geen schuifbalken. toont wel of geen werkbalk (met Back, Forward, Stop). toont wel of geen statusbalk. 43
Alleen de opties die expliciet opgegeven worden, worden veranderd in het nieuw te openen venster; de anderen behouden hun standaardwaarde. Deze opties moeten in het derde argument allemaal achter elkaar doorgeschreven worden, gescheiden door komma s. Er mogen geen spaties in deze lijst (tussen enkele aanhalingstekens) voorkomen. Voorbeeld 5: illustratie van enkele properties en methoden van het window object. 1 <html> <head> 3 < t i t l e > het window o b j e c t </ t i t l e > <s c r i p t type= t e x t / j a v a s c r i p t > 5 function s t a t r e g e l ( txt ) { 7 window. s t a t u s = txt ; } 9 function Venster ( ) { 11 k i j k e n = open ( j s 1. html, k i j k v e n s t e r, width =400, height =200, s t a t u s=yes, t o o l b a r=no, l o c a t i o n=no ) ; 13 } var i = 0 ; 15 function timer ( ) { 17 boodschap = r e e d s v o o r b i j + i ; s t a t r e g e l ( boodschap ) ; 19 i ++; timerid = settimeout ( timer ( ), 1 0 0 0 ) ; 21 } function s t o p t i m e r ( ) 23 { cleartimeout ( timerid ) ; 25 s t a t r e g e l ( ) ; } 27 </ s c r i p t > </head> 29 <body> <h1> De s t a t u s b a l k </h1> 31 <p a l i g n = c e n t e r > <a href= j s 4. html 33 onmouseover= s t a t r e g e l ( terug naar voorbeeld 4 ) ; return true ; onmouseout= s t a t r e g e l ( ); > 35 Even terug naar voorbeeld 4 </a> <br /> 37 <a href= j s 1. html onmouseover= s t a t r e g e l ( helemaal terug naar voorbeeld 1 ) ; return true ; 39 onmouseout= s t a t r e g e l ( ); > Voorbeeld 1 </a> 41 </p> <hr /> 43 <p a l i g n = l e f t > <form> 45 <ul> <l i > 47 <input type= button value= Doen onclick= Venster ( ) ; /> : openen van een nieuw v e n s t e r </ l i > 44
49 <l i > <input type= button value= Dicht onclick= k i j k e n. c l o s e ( ) ; /> 51 : terug s l u i t e n van d i t nieuw v e n s t e r </ l i > <l i > 53 <input type= button value= blur onclick= k i j k e n. blur ( ) ; /> : f o c u s afnemen van nieuw v e n s t e r </ l i > 55 <l i > <input type= button value= f o c u s onclick= k i j k e n. f o c u s ( ) ; /> 57 : f o c u s teruggeven aan nieuw v e n s t e r </ l i > </ul> 59 </form> </p> <hr /> 61 <p> en nu een t e l l e r t j e in de s t a t u s b a l k </p> <p> 63 <form> <ul> 65 <l i > het s t a r t e n van de t e l l e r : 67 <input type= button value= s t a r t onclick= timer ( ) ; /> </ l i > <l i > 69 het stoppen van de t e l l e r : <input type= button value= stop onclick= s t o p t i m e r ( ) ; /> </ l i > 71 </ul> </form> </p> 73 </body> </html> Het object Date() wordt gebruikt om met datums en tijden te werken. Datums en tijden worden gerekend in het aantal milliseconden dat verstreken is sinds 1 januari 1970. Elk jaar wordt door een geheel getal voorgesteld: 1997 door 97, 1998 door 98 en 2000 dus door 100, 2001 door 101, zodat er (simplistisch geredeneerd) geen problemen zijn met het jaar 2000. Welk tijdstip voorgesteld wordt door een instantie van het object Date(), wordt bepaald door het argument van de constructor. Wanneer geen argumenten meegegeven worden, zal JavaScript de huidige datum en tijd toekennen aan het object. nu = new Date ( ) ; 2 toen = new Date ( jaar, maand, dag ) ; Er zijn verschillende methodes om uit een Date() object datum- en tijdinformatie te extraheren of in te stellen. Enkele daarvan worden in volgend voorbeeld gebruikt. Voorbeeld 6: creatie van een klok. <html> 2 <head> < t i t l e > een klok </ t i t l e > 4 <s c r i p t type= t e x t / j a v a s c r i p t > var t i j d I D = n u l l ; 6 var t i j d l o o p t = f a l s e ; function stopklok ( ) 8 { i f ( t i j d l o o p t ) 10 cleartimeout ( t i j d I D ) ; t i j d l o o p t = f a l s e ; 12 } function toonklok ( ) 14 { 45
var nu = new Date ( ) ; 16 var uur = nu. gethours ( ) ; var min = nu. getminutes ( ) ; 18 var s e c = nu. getseconds ( ) ; var huidig = uur ; 20 huidig += ( min < 10? :0 : : ) + min ; huidig += ( s e c < 10? :0 : : ) + s e c ; 22 document. form. klok. value = huidig ; document. forms [ 0 ]. elements [ 1 ]. value = huidig ; 24 t i j d I D = settimeout ( toonklok ( ), 1 0 0 0 ) ; t i j d l o o p t = true ; 26 } function s t a r t k l o k ( ) 28 { stopklok ( ) ; 30 toonklok ( ) ; } 32 </ s c r i p t > </head> 34 <body onload= s t a r t k l o k () > <hr /> 36 <p a l i g n = c e n t e r > <s c r i p t type= t e x t / j a v a s c r i p t > 38 var begin = new Date ( ) ; var dag = begin. getdate ( ) ; 40 var mnd = begin. getmonth ( ) + 1 ; var j a a r = begin. getyear ( ) + 1 9 0 0 ; 42 document. w r i t e ( <p>beginnen b i j : + begin + </p> ) ; document. w r i t e ( <p> + dag +. + mnd +. + j a a r + </p> ) ; 44 </ s c r i p t > </p> 46 <hr /> <p> 48 <form name = form > <input type= t e x t name= klok s i z e = 20 /> 50 <input type= t e x t name= backup s i z e = 20 /> </form> 52 </p> <hr /> </body> 54 </html> De klokwaarde wordt weergegeven in een <input type="text"> element van een formulier. Dit is één van de enige elementen van een document dat nog kan gewijzigd worden nadat het document op het scherm geplaatst is. Om de value property van zo n element te kunnen wijzigen, moet een naam gebruikt worden die gebaseerd is op de objecthiërarchie, bijvoorbeeld, document.forms[0].elements[1].value. Door zowel de form als het input element een name attribuut te geven, kunnen in plaats van de arrayelementen ook deze namen gebruikt worden, bijvoorbeeld, document.form.klok.value. Het <input type="text"> element wordt gebruikt om een waarde weer te geven. Om de gebruiker te beletten dat hij zelf in dat veld iets zou intikken, kan de eventhandler onfocus="blur()" bij de input-tag opgenomen worden. Om het helemaal correct te maken zou in de body-tag de eventhandler onunload de functie stopklok() moeten oproepen om de klok netjes af te sluiten. 46
5.8 Het document object Het object document is belangrijk binnen JavaScript, omdat veel interactie tussen maker en gebruiker via Web-pagina s plaatsvindt. Datgene wat de gebruiker uiteindelijk te zien krijgt, wordt voor een groot deel bepaald door het document object. Document is een kindobject van window. Elk venster of frame heeft zijn eigen document als inhoud. Als een document eenmaal op het scherm staat, kan het op een paar elementen na, niet meer gewijzigd worden. Deze elementen zijn: <input type="text">, <textarea>, <img> en <select>. 5.8.1 Properties Enkele read-only properties die met kleuren te maken hebben: HTML JavaScript Werking <body text="#rrggbb"> document.fgcolor kleur van de tekst <body bgcolor="#rrggbb"> document.bgcolor achtergrondkleur <body link="#rrggbb"> document.linkcolor een nog niet bezochte link <body vlink="#rrggbb"> document.vlinkcolor een reeds bezochte link <body alink="#rrggbb"> document.alinkcolor een actieve link (die op dat moment wordt aangeklikt) De kleurwaarden worden aangegeven met een hexadecimaal getal met als waarde #rrggbb voor respectievelijk rood, groen en blauw. Daarnaast kan ook één van de 256 voorgedefinieerde namen gebruikt worden, zoals black, white, red, green, blue, yellow, lightseagreen, mediumpurple of cornflowerblue. Daarnaast zijn er een aantal read-only properties met informatie over het HTML document zelf: Eigenschap document.title document.url document.lastmodified document.referrer Omschrijving de titel van het HTML-document (<title>...</title>) bevat de URL van het huidige document datum waarop het document voor het laatst gewijzigd is vanuit welke HTML-pagina de huidige pagina is aangeroepen Document is het parentobject voor volgende onderliggende objecten. Elk van deze objecten is een array, een verzameling van bij elkaar horende gegevensstructuren. Elk element van een array heeft een eigen volgnummer, te beginnen vanaf nul. Array links[] anchors[] images[] forms[] Beschrijving bevat informatie over alle koppelingen die op de pagina aanwezig zijn bevat alle ankers, plaatsen op de huidige pagina waar naartoe kan gesprongen worden, van het huidige document alle afbeeldingsobjecten van het huidige document alle formulieren van het huidige document Het aantal elementen dat in zo n array aanwezig is, kan bepaald worden met de length property, bijvoorbeeld document.images.length. 5.8.2 Methods Het maken van een instant HTML-pagina gebeurt met behulp van de verschillende methoden van het document object: 47
Methode Werking open(); treft voorbereidingen om het versturen van gegevens via document.write() mogelijk te maken. write(s1, s2,...); zendt tekst naar het document die in het venster van de browser moet worden weergegeven. Strings (s1, s2) kunnen door komma s van elkaar gescheiden worden, of met een plus-teken worden samengevoegd. close(); de tegenhanger van open(). Na een document.close() verschijnt de melding document:done in de statusbalk van de browser. Daarna kan aan het uiterlijk van het document niets meer veranderen. Voorbeeld 7: illustratie van dynamische beeldjes. Na elke drie seconden wordt een volgend beeldje getoond, tenzij de gebruiker ongeduldig wordt en op de knop volgende klikt. <html> 2 <head> < t i t l e > dynamische b e e l d j e s </ t i t l e > 4 <s c r i p t type= t e x t / j a v a s c r i p t > var t e k s t = new Array ( 9 ) ; 6 t e k s t [ 0 ] = t a a r t nul ; t e k s t [ 1 ] = t a a r t een a c h t s t e ; 8 t e k s t [ 2 ] = t a a r t een v i e r d e ; t e k s t [ 3 ] = t a a r t d r i e a c h t s t e ; 10 t e k s t [ 4 ] = t a a r t h a l f ; t e k s t [ 5 ] = t a a r t v i j f a c h t s t e ; 12 t e k s t [ 6 ] = t a a r t d r i e v i e r d e ; t e k s t [ 7 ] = t a a r t zeven a c h t s t e ; 14 t e k s t [ 8 ] = t a a r t v o l l e d i g ; var amai = \n amai zeg ; 16 var t e l l e r = 0 ; var nietwachten = f a l s e ; 18 function t o o n f o t o ( ) { 20 i f ( t e l l e r++ >= 8 ) t e l l e r = 0 ; 22 var b e e l d j e = / i c o n s / + p i e + t e l l e r +. g i f ; document. beeld2. s r c=b e e l d j e ; 24 i f ( nietwachten ) document. eronder. u i t l e g. value=t e k s t [ t e l l e r ] + amai ; 26 else document. eronder. u i t l e g. value=t e k s t [ t e l l e r ] ; 28 nietwachten = f a l s e ; } 30 function timer ( ) { 32 t o o n f o t o ( ) ; timerid = settimeout ( timer ( ), 3 0 0 0 ) ; 34 } function o ngeduldig ( ) 36 { cleartimeout ( timerid ) ; 38 nietwachten = true ; timer ( ) ; 40 } </ s c r i p t > 48
42 </head> <body onload=timer ()> 44 <hr /> <p a l i g n = c e n t e r > 46 Beweeg de c u r s o r over het b e e l d j e : <a href= # 48 onmouseover= document. beeld1. s r c= / g i f / r a r r o w. g i f onmouseout= document. beeld1. s r c= / g i f / l a r r o w. g i f > 50 <img s r c = / g i f / l a r r o w. g i f name = beeld1 /> </a> 52 </p> <hr /> 54 <s c r i p t type= t e x t / j a v a s c r i p t > var abc = <img s r c = / i c o n s / p i e ; 56 var def =. g i f /> ; for ( var i =0; i <=8; i ++) 58 { var samen = abc + i + def ; 60 document. w r i t e ( samen ) ; } 62 </ s c r i p t > <hr /> 64 <h2> Wisselende b e e l d j e s </h2> <img s r c = / i c o n s / pie1. g i f name = beeld2 /> 66 <form name = eronder > <t e x t a r e a name= u i t l e g rows= 3 c o l s = 50 > 68 met wat t e k s t in </textarea > <p> 70 <input type= button value= volgende onclick= o ngeduldig ( ) ; /> 72 <hr /> </body> 74 </html> </p> </form> Door aan het img-tag een name attribuut te geven kan deze naam gebruikt worden om het beeld te wijzigen (in plaats van via het juiste element uit het images object). Dezelfde techniek is ook toegepast om de inhoud van de <textarea> te wijzigen. Voorbeeld 8: illustratie van gebruik van formvelden: er kunnen twee getallen ingetikt worden en hierop wordt een bewerking uitgevoerd. <html> 2 <head> < t i t l e > f o r m u l i e r : rekenmachine </ t i t l e > 4 <s c r i p t type= t e x t / j a v a s c r i p t > function plus ( f ) 6 { f. r e s u l t a a t. value = p a r s e F l o a t ( f. g e t a l 1. value ) 8 + p a r s e F l o a t ( f. g e t a l 2. value ) ; } 10 function min ( f ) { 12 f. r e s u l t a a t. value = p a r s e F l o a t ( f. g e t a l 1. value ) p a r s e F l o a t ( f. g e t a l 2. value ) ; 14 } function maal ( f ) 49
16 { f. r e s u l t a a t. value = p a r s e F l o a t ( f. g e t a l 1. value ) 18 p a r s e F l o a t ( f. g e t a l 2. value ) ; } 20 function d e e l ( f ) { 22 f. r e s u l t a a t. value = p a r s e F l o a t ( f. g e t a l 1. value ) / p a r s e F l o a t ( f. g e t a l 2. value ) ; 24 } </ s c r i p t > 26 </head> <body> 28 <hr /> <p a l i g n = c e n t e r > 30 <form name= reken > <table > 32 <tr> <td> Geta l 1: </ td> 34 <td> <input type= t e x t name= g e t a l 1 s i z e = 25 /> </td> <td> </td> 36 <td> <input type= button value= + onclick= plus ( t h i s. form ) /></td> </tr> 38 <tr> <td> Geta l 2: </ td> 40 <td> <input type= t e x t name= g e t a l 2 s i z e = 25 /> </td> <td> </td> 42 <td> <input type= button value= onclick= min ( t h i s. form ) /></td> </tr> 44 <tr> <td> </td> 46 <td> </td> <td> </td> 48 <td> <input type= button value= onclick= maal ( t h i s. form ) /></td> </tr> 50 <tr> <td> Resultaat :</td> 52 <td> <input type= t e x t name= r e s u l t a a t s i z e = 25 onfocus= blur ( ) /></td> <td> </td> 54 <td> <input type= button value= / onclick= d e e l ( t h i s. form ) /></td> <td> <input type= r e s e t name= wisknop value= wissen s i z e = 15 /></td> 56 </tr> </table > 58 </form> </p> 60 <hr /> </body> 62 </html> Er worden drie invoertekstvelden gebruikt. Deze moeten met de volledige naam aangesproken worden, bijvoorbeeld document.reken.getal1. Dit kan korter door bij de functie oproep het form object als argument mee te geven, namelijk this.form. Het is ook mogelijk om één enkel veld mee te geven bij een functie oproep. Hiervoor moet als argument this gespecificeerd worden. Het invoertekstveld met naam resultaat wordt gebruikt om het resultaat van de bewerking weer te geven. Omdat de gebruiker hierin niets mag kunnen tikken, is een eventhandler onfocus 50
opgenomen (de actie blur() zorgt ervoor dat de focus wordt opgeheven).. Voorbeeld 9: illustratie van gebruik van selectielijsten in een form. Er wordt ook een rollend bericht in een text element weergegeven. <html> 2 <head> < t i t l e > f o r m u l i e r : kleuren </ t i t l e > 4 <s c r i p t type= t e x t / j a v a s c r i p t > var b l e n g t e =150; 6 var b u f f e r = ; for ( i =1; i <b l e n g t e ; i++) 8 b u f f e r += ; var t e k s t = Hoi, wat s p e l e n met kleuren, ; 10 t e k s t += h e e f t U n i e t s anders te doen? ; t e k s t += in i e d e r g eval v e e l p l e z i e r! ; 12 var mess = b u f f e r+t e k s t ; var mess2 = ; 14 var mess1 = mess ; var t e l l e r = 0 ; 16 function r o l l e n d ( ) { 18 mess2 = mess1. s u b s t r i n g ( 1, mess1. length ) ; mess2 += ; 20 window. s t a t u s = mess2 ; settimeout ( r o l l e n d ( ) ;, 1 0 0 ) ; 22 t e l l e r ++; mess1 = mess2 ; 24 i f ( t e l l e r == mess. length ) { 26 mess1 = mess ; t e l l e r = 0 ; 28 } } 30 function k i j k ( f ) { 32 var hstr, bstr, t s t r, e s t r ; var aa = f. a chter. o p t i o n s [ f. a chter. s e l e c t e d I n d e x ]. value ; 34 var vv = document. k l e u r. voor. o p t i o n s [ document. k l e u r. voor. s e l e c t e d I n d e x ]. value ; 36 b s t r = <body onload= r o l l e n d ( ) b g c o l o r= + aa ; b s t r += t e x t= + vv + > ; 38 h s t r = <html><head><t i t l e >Kleuren </ t i t l e > ; h s t r += <s c r i p t s r c= r o l l e n d. j s type= t e x t / j a v a s c r i p t ></ ; 40 h s t r += s c r i p t ></head> ; t s t r = <body><f o n t c o l o r = + vv + > ; 42 t s t r += Gewone t e k s t h e e f t deze k l e u r ; t s t r += </font><br /> ; 44 e s t r = <form><input type= button value= S l u i t ; e s t r += onclick= a f s l u i t e n ( ) /> <p><input type= t e x t s i z e = 55 /> ; 46 e s t r += </p></form></body></html> ; 48 raam = open (, Eigenschappen, width =400, height =300, s t a t u s=yes, t o o l b a r=no, s c r o l l b a r s=no ) ; 50 raam. document. open ( ) ; 51
raam. document. w r i t e ( h s t r ) ; 52 raam. document. w r i t e ( b s t r ) ; raam. document. w r i t e ( t s t r ) ; 54 raam. document. w r i t e ( <p>tekstkleur :, raam. document. fgcolor, </p> ) ; 56 raam. document. w r i t e ( <p>achtergrondkleur :, raam. document. bgcolor, </p> ) ; 58 raam. document. w r i t e ( e s t r ) ; raam. document. c l o s e ( ) ; 60 } </ s c r i p t > 62 </head> <body onload= r o l l e n d () > 64 <hr /> <h2> Kies twee kleuren </h2> 66 <p a l i g n = c e n t e r > <form name= k l e u r > 68 <table > <tr> 70 <td> Achter g r o nd : </td> <td> <s e l e c t name= a chter > 72 <o ption s e l e c t e d= true value= Black >Zwart </option> <o ption value= White >Wit </option> 74 <o ption value= Maroon >Maroon </option> <o ption value= Yellow >Geel </option> 76 </ s e l e c t > </td> </tr> 78 <tr> <td> Voorgrond : </td> 80 <td> <s e l e c t name= voor > <o ption s e l e c t e d= true value= White >Wit </option> 82 <o ption value= Black >Zwart </option> <o ption value= Maroon >Maroon </option> 84 <o ption value= Green >Groen </option> </ s e l e c t > </td> 86 <td> <input type= button value= Kijken onclick= k i j k ( t h i s. form ) /> </td> 88 <td> <input type= r e s e t name= wisknop 90 </tr> </table > 92 </form> </p> 94 </body> </html> value= wissen s i z e = 15 /> </td> Ook in dit voorbeeld is als argument this.form meegegeven, zodat een verkorte naam kan gebruikt worden. Ter vergelijking is ook de volledige naam gebruikt. De property selectedindex van een select object geeft de indexwaarde van de huidige geselecteerde waarde. Met behulp van de options[i].value property kan de geselecteerde waarde achterhaald worden. In het nieuw gecreëerd venster is een tekstelement aanwezig met daarin een rollend bericht. Om dit te realiseren is een verwijzing naar een JavaScript bestand in de header van de gegenereerde pagina voor het subvenster opgenomen. 52
Een verwijzing naar een JavaScript bestand op een server is mogelijk door het src attribuut van de <script>-tag. In dit geval maakt de broncode geen deel uit van de webpagina zelf. De waarde van src kan zowel een absolute als een relatieve verwijzing zijn: <script src="rollend.js" type="text/javascript"></script> <script src="http://www.server.be/bestand.js"></script> Wanneer zo n lijn door JavaScript zelf in een webpagina moet geschreven worden, kan er verwarring optreden tijdens de interpretatie. Er is een <script>-tag gestart en in een write komt als argument terug een <script>-tag en een </script>-tag voor. Maar deze </script> mag het script zelf niet beëindigen. Daar moet deze tag in twee delen weggeschreven worden. De inhoud van dit JavaScript bestand is: 1 var b l e n g t e =60; var b u f f e r = ; 3 for ( i =1; i <b l e n g t e ; i++) b u f f e r += ; 5 var t e k s t = Hoi, en toch b l i j v e n verder s p e l e n met kleuren, ; t e k s t += h e e f t U n i e t s anders te doen? ; 7 t e k s t += in i e d e r g eval v e e l p l e z i e r! ; var mess = b u f f e r+t e k s t ; 9 var mess2 = ; var mess1 = mess ; 11 var t e l l e r = 0 ; var subid ; 13 function r o l l e n d ( ) { 15 mess2 = mess1. s u b s t r i n g ( 1, mess1. length ) ; mess2 += ; 17 // window. status = mess2; document. forms [ 0 ]. elements [ 1 ]. value = mess2 ; 19 subid = settimeout ( r o l l e n d ( ) ;, 1 0 0 ) ; t e l l e r ++; 21 mess1 = mess2 ; i f ( t e l l e r == mess. length ) 23 { mess1 = mess ; 25 t e l l e r = 0 ; } 27 } function a f s l u i t e n ( ) 29 { window. opener. document. k l e u r. i n f o. value = Math. PI ; 31 cleartimeout ( subid ) ; settimeout ( s e l f. c l o s e ( ) ;, 1 0 0 0 ) ; 33 } In dit voorbeeld wordt het attribuut opener van het window object gebruikt. Dit attribuut bevat een referentie naar het window object dat het script bevat dat de open van dit window opgeroepen heeft. Hiermee kan dus een formulier element van het parent window ingevuld worden. Elke stringvariabele is een instantie van het string object. De eigenschap string.length geeft de lengte weer. Daarnaast zijn er een heleboel methoden om bewerkingen op strings uit te voeren: 53
Methode charat(index); indexof(teken,index); lastindexof(tek,ind); substring(ind1,ind2); tolowercase() touppercase() Werking geeft het teken op positie index binnen de string terug. resultaat is de positie van teken in de string te beginnen vanaf positie index. zoals indexof maar te beginnen vanaf het einde. resultaat is een substring uit de oorspronkelijke string, beginnen bij ind1 en lopend tot ind2. zet de hele string in kleine letters. zet de hele string in hoofdletters. Testen of een radio-button in een form ingedrukt is, kan door de property checked van het formelement te vergelijken met de waarde true: i f ( document. forms [ 0 ]. elements [ 2 ]. checked == true ) { } 5.9 Opmerkingen 1. JavaScript is een geïnterpreteerde scripttaal, hetgeen inhoudt dat de browser een HTML pagina met scriptcode regel per regel van boven naar beneden inleest, vertaalt en direct uitvoert. Alle functies en variabelen moeten eerst gedefinieerd zijn (eventueel impliciet) voordat ze gebruikt kunnen worden. Functies en globale variabelen zijn daarom meestal terug te vinden in de header van de HTML pagina. 2. JavaScript is niet kritisch in het gebruik van enkele of dubbele quotes voor strings, zolang maar hetzelfde teken gebruikt wordt om een string te openen en af te sluiten. Aangezien HTML eist dat de waarden van attributen tussen dubbele quotes staan, is het aan te raden in de JavaScript-code de strings binnen enkele quotes te plaatsen. 3. Wanneer bij het inladen van een HTML-pagina die JavaScript-code bevat, een syntax-fout gevonden wordt, wordt dit gemeld in de statusbalk van het venster. Door achter Location (waar normaal de URL ingetikt wordt) het woord javascript: te tikken, wordt een extra venster gecreëerd, waarin meer details over de fout kunnen gevonden worden. 4. Commentaar in een script kan op twee manieren: // : commentaar tot op het einde van de regel; /*... */: commentaar over meerdere regels. 5. Oudere browsers kennen de script-tag niet. De tag zelf wordt genegeerd, maar wat volgt (de JavaScript-code) zal op de gebruikelijke HTML-wijze geïnterpreteerd worden. Om deze rommel niet op het scherm te zien verschijnen, is het nuttig om de JavaScript code tussen HTML-commentaartags te plaatsen (links). Om XHTML-validators de Javascript te laten overslaan, kan de javascript code tussen speciale tekens geplaatst worden (rechts). <script type="text/javascript"> <!-- de JavaScript-code // --> </script> <script type="text/javascript"> //<![CDATA[ de JavaScript-code //]]> </script> 54
6 ADOdb bibliotheek voor PHP Databank toegangsfuncties zijn in PHP niet gestandaardiseerd: voor elke databank bestaat er een aparte application program interface. Om de verschillen tussen de verschillende databank API s te verbergen is er een nood aan een databank klassen-bibliotheek zodat gemakkelijk van databank kan gewisseld worden. ADOdb is zo n bibliotheek en geeft ondersteuning voor MySQL, Oracle, Sybase, PostgreSQL, DB2, ODBC, Microsoft SQL Server, Access, Foxpro,... Eigenschappen: gemakkelijk voor Windows programmeurs; naast select ook update en insert mogelijk; metatype systeem zodat vanuit verschillende databanken CHAR, TEXT en STRING als gelijkaardig kunnen beschouwd worden; gemakkelijk overdraagbaar omdat alle databank afhankelijke code in stub functies zit; tabel en index creatie met behulp van de datadict klassen; database performance monitoring en SQL tuning. 6.1 Intikken en uitvoeren van een query 1 <?php $query = s e l e c t from boetes ; 3 $hostn = $ ENV [ HOSTNAME ] ; i f ( $hostn == a n t j e ) 5 { i n c l u d e / apache /adodb/ tohtml. i n c. php ; 7 i n c l u d e / apache /adodb/adodb. i n c. php ; } 9 else { 11 i n c l u d e ( / usr / l o c a l / httpd / htdocs /adodb/ tohtml. i n c. php ) ; i n c l u d e ( / usr / l o c a l / httpd / htdocs /adodb/adodb. i n c. php ) ; 13 } 15 i f ( $hostn == a n t j e ) { 17 $ dbdriver = o c i 8 ; # eg oci or postgres $ user = f l i p ; 19 $password = f l i p ; $ c c = (DESCRIPTION=(ADDRESS = (PROTOCOL = TCP) 21 (HOST = a n t j e. denayer. wenk. be ) (PORT = 1 5 2 1 ) ) (CONNECT DATA=(SID=r e i s ) ) ) ; } 23 else { 25 $ dbdriver = p o s t g r e s ; # eg oci or postgres $ s e r v e r = l o c a l h o s t ; 27 $database = t e n n i s ; $ user = hcr ; 29 $password = ; } 31 $db = ADONewConnection ( $ dbdriver ) ; 55
33 $db >debug = f a l s e ; $db >d i s a b l e B l o b s = true ; 35 i f ( $hostn == a n t j e ) { 37 $db >Connect ( $cc, $ user, $password ) ; } 39 else { 41 $db >Connect ( $ server, $user, $password, $database ) ; } 43 $ r s = $db >Execute ( $query ) ; i f (! $ r s ) 45 print $db >ErrorMsg ( ) ; else 47 { $n = $rs >RowCount ( ) ; 49 $m = $rs >FieldCount ( ) ; echo Het r e s u l t a a t bevat $n r i j e n en $m kolommen ; 51 echo <br /><br /> ; rs2html ( $rs, border=1 ) ; 53 } $db >Close ( ) ; 55?> 6.2 Database abstraction library Make your PHP scripts portable across databases with the powerful ADODB database abstraction library. First up, let s get the lingo straight: what the heck is a database abstraction library anyway? If you ve worked with different databases, you ve probably seen that each database operates in a slightly different manner from the others. The data types aren t always uniform, and many of them come with proprietary extensions (transactions, stored procedures et al) that aren t supported elsewhere. Additionally, the API to interact with these databases is not uniform; PHP itself comes with a different API for each supported database type, For all these reasons, switching from one database to another is typically a complex process, one which usually involves porting data from one system to another (with the assorted datatyping complications), rewriting your code to use the new database API, and testing it to make sure it all works. And that s where a database abstraction layer can help. Typically, a database abstraction layer functions as a wrapper around your code, exposing a set of generic methods to interact with a database server. These generic methods are internally mapped to the native API for each corresponding database, with the abstraction layer taking care of ensuring that the correct method is called for your selected database type. Additionally, most abstraction layers also incorporate a generic superset of datatypes, which get internally converted into datatypes native to the selected RDBMS. In order to better understand the difference, consider the following diagram: database mysql connect() ora logon() pg connect() PHP PHP PHP 56
database mysql connect() ora logon() pg connect() Abstraction layer connect() PHP PHP PHP As you can see, without an abstraction layer in place, you need to use a different API call for each of the three database types. With an abstraction layer in place, however, you can transparently use a single, generic call, and have the abstraction layer convert it into the native API call. A number of different abstraction layers are available for PHP, most notably the PEAR DBI, Metabase and PHPLib. The one I m going to use in this article is named ADODB (Active Data Objects DataBase), and it s one of the most full-featured and efficient PHP abstraction libraries available today. Developed by John Lim, the library currently supports a wide variety of database systems, including MySQL, PostgreSQL, Oracle, Interbase, Microsoft SQL Server, Access, ODBC and others, and has been used in a number of well-known open-source PHP projects, including phplens, PostNuke and Webodex. You can download a copy of ADODB from <a href="http://php.weblogs.com/adodb">http://php.weblogs.com/adodb</a> - get your copy now, set it up, and flip the page for an example of how it can be used. 6.3 Voorbeeld met specifieke mysql connectie Before we get into the code, you might want to take a quick look at the database table I ll be using throughout this article. Here it is: mysql> SELECT * FROM library; +----+-------------------+----------------+ id title author +----+-------------------+----------------+ 14 Mystic River Dennis Lehane 15 For Kicks Dick Francis 16 XML and PHP Vikram Vaswani 17 Where Eagles Dare Jack Higgins +----+-------------------+----------------+ As you might have guessed, the library table contains a list of all the books currently taking up shelf space in my living room. Each record within the table is identified by a unique number (the geek term for this is foreign key, but you can forget that one immediately). Now, let s suppose I want to display a list of my favourite books on my personal Web site. Everything I need is stored in the table above; all yours truly has to do is write a script to pull it out and massage it into a readable format. Since PHP comes with out-of-the-box support for MySQL, accomplishing this is almost as simple as it sounds. 57
1 <?php // uncomment this to see plaintext output in your browser 3 // header(" Content- Type: text/ plain"); // open connection to database 5 $ connection = mysql connect ( l o c a l h o s t, john, doe ) or die ( Unable to connect! ) ; 7 // select database m y s q l s e l e c t d b ( db278 ) or die ( Unable to s e l e c t database! ) ; 9 // execute query $query = SELECT FROM l i b r a r y ; 11 $ r e s u l t = mysql query ( $query ) or die ( Error in query : $query.. m y s q l e r r o r ( ) ) ; 13 // iterate through rows and print column data // in the form TITLE - AUTHOR 15 while ( $row = mysql fetch r o w ( $ r e s u l t ) ) { 17 echo $row [ 1 ] $row [ 2 ] \ n ; } 19 // get and print number of rows in resultset echo \n [. mysql num rows ( $ r e s u l t ). rows r eturned ] \ n ; 21 // close database connection m y s q l c l o s e ( $ connection ) ; 23?> Here s what the output looks like: Mystic River - Dennis Lehane For Kicks - Dick Francis XML and PHP - Vikram Vaswani Where Eagles Dare - Jack Higgins [4 rows returned] The process here is fairly straightforward: connect to the database, execute a query, retrieve the result and iterate through it. The example above uses the mysql_fetch_row() function to retrieve each row as an integer-indexed array, with the array indices corresponding to the column numbers in the resultset; however, it s just as easy to retrieve each row as an associative array (whose keys correspond to the column names) with mysql_fetch_assoc(), or an object (whose properties correspond to the column names) with mysql_fetch_object(). The problem with this script? Since I ve used MySQL-specific functions to interact with the database, it s going to crash and burn the second I switch my data over to PostgreSQL or Oracle. Which is where the database abstraction layer comes in. 6.4 Met gebruik van de abstractielaag ADODB In order to demonstrate how the abstraction layer works, I ll use it to rewrite the previous example - take a look: 1 <?php // include the ADODB library 3 i n c l u d e ( adodb. i n c. php ) ; // create an object instance 5 // configure it for a MySQL connection $db = NewADOConnection ( mysql ) ; 7 // open connection to database $db >Connect ( l o c a l h o s t, john, doe, db278 ) 58
9 or die ( Unable to connect! ) ; // execute query 11 $query = SELECT FROM l i b r a r y ; $ r e s u l t = $db >Execute ( $query ) 13 or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; // iterate through resultset 15 // print column data in format TITLE - AUTHOR while (! $ r e s u l t >EOF) 17 { echo $ r e s u l t >f i e l d s [ 1 ].. $ r e s u l t >f i e l d s [ 2 ]. \n ; 19 $ r e s u l t >MoveNext ( ) ; } 21 // get and print number of rows in resultset echo \n [. $ r e s u l t >RecordCount ( ). rows r eturned ] \ n ; 23 // close database connection $db >Close ( ) ; 25?> This output of this snippet is equivalent to that of the previous one; however, since it uses the ADODB abstraction library, rather than PHP s native API, to interact with the database server, it holds out the promise of continuing to work no matter which database I use. I ll show you how in a minute - but first, a quick explanation of the functions used above: 1. The first step is, obviously, to include the abstraction layer in your script. // include the ADODB library i n c l u d e ( adodb. i n c. php ) ; Note that the ADODB library doesn t consist of just this file - in fact, there are over thirty different files included with the library, many of them drivers for different databases. You don t need to worry about including each and every one; simply include the main class file, as above, and it will invoke the appropriate drivers or additional classes as required. 2. Next, create an instance of the ADODB class. // create an object instance // configure library for a MySQL connection $db = NewADOConnection ( mysql ) ; The parameter passed to the object constructor tells ADODB which type of database you re trying to connect to. In this case, I ve used the argument mysql, since I m going to be connecting to a MySQL database server; you could just as easily use pgsql or oci8 or... 3. Next, it s time to open up a connection to the database. This is accomplished via the Connect() method, which must be passed a set of connection parameters. // open connection to database $db >Connect ( l o c a l h o s t, john, doe, db278 ) or die ( Unable to connect! ) ; Reading this may make your head hurt, but there is method to the madness - roughly translated, the line of code above attempts to open up a connection to the MySQL database named db278, on the host named localhost, with the username john and password doe. 4. Once the Connect() method does its job, the object s Execute() method can be used to execute SQL queries on that database. 59
// execute query $query = SELECT FROM l i b r a r y ; $ r e s u l t = $db >Execute ( $query ) or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; Successful query execution returns a new object containing the results of the query. Note the special ErrorMsg() method, which can be used to obtain the last error message generated by the system. 5. The result object returned in the previous step exposes methods and properties that can be used to extract specific fields or elements from the returned resultset. // iterate through resultset // print column data in format TITLE - AUTHOR while (! $ r e s u l t >EOF) { echo $ r e s u l t >f i e l d s [ 1 ].. $ r e s u l t >f i e l d s [ 2 ]. \n ; $ r e s u l t >MoveNext ( ) ; } In this case, the object s MoveNext() method is used, in combination with a while loop, to iterate through the returned resultset and display individual fields (these individual fields are accessed as array elements of the object s fields property). This data is then printed to the output device. Once all the rows in the resultset have been processed, the object s RecordCount() method is used to print the number of rows in the resultset. // get and print number of rows in resultset echo \n [. $ r e s u l t >RecordCount ( ). rows r eturned ] \ n ; 6. Finally, with all the heavy lifting done, the Close() method is used to gracefully close the database connection and disengage from the database. // close database connection $db >Close ( ) ; In the event that someone (maybe even me) decides to switch to a different database, changes required in the script above would be to the line instantiating the connection object - a new argument would need to be passed to the object constructor, with a new database type and to the line with the Connect call. Everything else would stay exactly the same, and my code would continue to work exactly as before. This is the beauty of an abstraction layer - it exposes a generic API to developers, allowing them to write one piece of code that can be used in different situations, with all the ugly bits hidden away and handled internally. Which translates into simpler, cleaner code, better script maintainability, shorter development cycles and an overall Good Feeling. Simple, huh? 6.5 Verwerken van de resultaten set ADODB also offers a number of alternative methods to process a resultset. For example, you can retrieve the resultset as a string-indexed associative array, where the keys are field names and the values are the corresponding field values. Consider the following example, which demonstrates: <?php 2 // include the ADODB library i n c l u d e ( adodb. i n c. php ) ; 4 // create an object instance 60
// configure library for a MySQL connection 6 $db = NewADOConnection ( mysql ) ; // open connection to database 8 $db >Connect ( l o c a l h o s t, john, doe, db278 ) or die ( Unable to connect! ) ; 10 // get resultset as associative array $ADODB FETCH MODE = ADODB FETCH ASSOC; 12 // execute query $query = SELECT FROM l i b r a r y ; 14 $ r e s u l t = $db >Execute ( $query ) or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; 16 // iterate through resultset // print column data in format TITLE - AUTHOR 18 while (! $ r e s u l t >EOF) { 20 echo $ r e s u l t >f i e l d s [ t i t l e ]. ; echo $ r e s u l t >f i e l d s [ author ]. \n ; 22 $ r e s u l t >MoveNext ( ) ; } 24 // get and print number of rows in resultset echo \n [. $ r e s u l t >RecordCount ( ). rows r eturned ] \ n ; 26 // close database connection $db >Close ( ) ; 28?> In this case, the value of the special $ADODB_FETCH_MODE variable tells ADODB how the resultset should be structured. You can also fetch each row as an object, whose properties correspond to the field names, via ADODB s FetchNextObject() method. <?php 2 // include the ADODB library i n c l u d e ( adodb. i n c. php ) ; 4 // create an object instance // configure library for a MySQL connection 6 $db = NewADOConnection ( mysql ) ; // open connection to database 8 $db >Connect ( l o c a l h o s t, john, doe, db278 ) or die ( Unable to connect! ) ; 10 // execute query $query = SELECT FROM l i b r a r y ; 12 $ r e s u l t = $db >Execute ( $query ) or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; 14 // iterate through resultset // print column data in format TITLE - AUTHOR 16 while ( $row = $ r e s u l t >FetchNextObject ( ) ) { 18 echo $row >TITLE.. $row >AUTHOR. \n ; } 20 // get and print number of rows in resultset echo \n [. $ r e s u l t >RecordCount ( ). rows r eturned ] \ n ; 22 // close database connection $db >Close ( ) ; 24?> 61
Note that this FetchNextObject() method automatically moves to the next row in the resultset, and does not require an explicit call to MoveNext(). Once the end of the resultset is reached, the method returns false. 6.6 Resultset in een twee-dimensionale array You can replace the Execute() method with the GetAll() method, which returns the complete resultset as a two-dimensional array of field-value pairs. This array can then be processed with a simple foreach or for loop. Consider the following example, which demonstrates: <?php 2 // include the ADODB library i n c l u d e ( adodb. i n c. php ) ; 4 // create an object instance // configure library for a MySQL connection 6 $db = NewADOConnection ( mysql ) ; // open connection to database 8 $db >Connect ( l o c a l h o s t, john, doe, db278 ) or die ( Unable to connect! ) ; 10 // execute query $query = SELECT FROM l i b r a r y ; 12 $ r e s u l t = $db >GetAll ( $query ) or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; 14 // clean up $db >Close ( ) ; 16 // uncomment the following line to see the returned array. // print_r($result); 18 // iterate through resultset // print column data in format TITLE - AUTHOR 20 f o r e a c h ( $ r e s u l t as $row ) { 22 echo $row [ 1 ].. $row [ 2 ]. \n ; } 24 // get and print number of rows in resultset echo \n [. s i z e o f ( $ r e s u l t ). rows r eturned ] \ n ; 26?> In this case, the GetAll() method creates a two-dimensional array of result data, which looks something like this. Array ( [0] => Array ( [0] => 14 [id] => 14 [1] => Mystic River [title] => Mystic River [2] => Dennis Lehane [author] => Dennis Lehane ) [1] => Array ( [0] => 15 62
) [id] => 15 [1] => For Kicks [title] => For Kicks [2] => Dick Francis [author] => Dick Francis ) [2] => Array ( [0] => 16 [id] => 16 [1] => XML and PHP [title] => XML and PHP [2] => Vikram Vaswani [author] => Vikram Vaswani )... and so on... This array can then be iterated over by a foreach loop, and the required values accessed as regular array elements. The size of the array is equivalent to the total number of rows in the resultset. This method provides a useful alternative to the Execute() method, especially in situations when you would prefer to have the entire recordset available at once, rather than one row at a time. 6.7 Functies: recordcount en fieldcount ADODB comes with a number of utility functions that provide you with useful information on the query you just executed. The most useful of these are the RecordCount() and FieldCount() methods, which return the number of rows and columns in the recordset respectively. Here s a simple example, which demonstrates: <?php 2 // include the ADODB library i n c l u d e ( adodb. i n c. php ) ; 4 // create an object instance // configure library for a MySQL connection 6 $db = NewADOConnection ( mysql ) ; // open connection to database 8 $db >Connect ( l o c a l h o s t, john, doe, db278 ) or die ( Unable to connect! ) ; 10 // execute query $query = SELECT FROM l i b r a r y ; 12 $ r e s u l t = $db >Execute ( $query ) or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; 14 // get and print number of rows in resultset echo $ r e s u l t >RecordCount ( ). rows r eturned \n ; 16 // get and print number of fields in resultset echo $ r e s u l t >FieldCount ( ). f i e l d s r eturned \n ; 18 // clean up $db >Close ( ) ; 20?> You can obtain further information on each field with the FetchField() method, which returns an object containing detailed information on the field properties, including its name and type. Consider the following variant of the example above, which might make this a little clearer: 63
<?php 2 // include the ADODB library i n c l u d e ( adodb. i n c. php ) ; 4 // create an object instance // configure library for a MySQL connection 6 $db = NewADOConnection ( mysql ) ; // open connection to database 8 $db >Connect ( l o c a l h o s t, john, doe, db278 ) or die ( Unable to connect! ) ; 10 // execute query $query = SELECT FROM l i b r a r y ; 12 $ r e s u l t = $db >Execute ( $query ) or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; 14 // get field information for ( $x=0; $x<$ r e s u l t >FieldCount ( ) ; $x++) 16 { p r i n t r ( $ r e s u l t >FetchField ( $x ) ) ; 18 } // clean up 20 $db >Close ( ) ;?> Here s what the output might look like for the id field: stdclass Object ( [name] => id [table] => library [def] => [max_length] => 3 [not_null] => 1 [primary_key] => 1 [multiple_key] => 0 [unique_key] => 0 [numeric] => 1 [blob] => 0 [type] => int [unsigned] => 1 [zerofill] => 0 [binary] => ) 6.8 Data manipulatie: insert en delete In case you re performing an INSERT query on a table containing an auto-increment primary key, you can obtain the last auto-increment ID generated via ADODB s Insert_ID() method. 1 <?php // include the ADODB library 3 i n c l u d e ( adodb. i n c. php ) ; // create an object instance 5 // configure library for a MySQL connection $db = NewADOConnection ( mysql ) ; 7 // open connection to database $db >Connect ( l o c a l h o s t, john, doe, db278 ) 64
9 or die ( Unable to connect! ) ; // execute query 11 $ t i t l e = $db >q s t r ( I t s Not Me, I t s You! ) ; $author = $db >q s t r ( J. Luser ) ; 13 $query = INSERT INTO l i b r a r y ( t i t l e, author ) VALUES ( $ t i t l e, $author ) ; $ r e s u l t = $db >Execute ( $query ) 15 or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; // print auto - generated ID 17 i f ( $ r e s u l t ) { 19 echo Last i n s e r t e d ID i s. $db >I n s e r t I D ( ) ; } 21 // clean up $db >Close ( ) ; 23?> Note also the qstr() method, which comes in handy when you need to escape special characters in your query string. If you re executing a query that affects the table, either by adding, deleting or modifying rows, the Affected_Rows() method can come in handy to tell you the number of rows affected. 1 <?php // include the ADODB library 3 i n c l u d e ( adodb. i n c. php ) ; // create an object instance 5 // configure library for a MySQL connection $db = NewADOConnection ( mysql ) ; 7 // open connection to database $db >Connect ( l o c a l h o s t, john, doe, db278 ) 9 or die ( Unable to connect! ) ; // execute query 11 $query = DELETE FROM l i b r a r y WHERE author = J. Luser ; $ r e s u l t = $db >Execute ( $query ) 13 or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; // return number of affected rows 15 i f ( $ r e s u l t ) { 17 echo $db >Affected Rows ( ). rows d e l e t e d ; } 19 // clean up $db >Close ( ) ; 21?> 6.9 Selectie vam beperkt aantal rijen The SelectLimit() method can be used to restrict the number of rows retrieved. 1 <?php // include the ADODB library 3 i n c l u d e ( adodb. i n c. php ) ; // create an object instance 5 // configure library for a MySQL connection $db = NewADOConnection ( mysql ) ; 7 // open connection to database $db >Connect ( l o c a l h o s t, john, doe, db278 ) 65
9 or die ( Unable to connect! ) ; // execute query 11 // get 5 rows, starting from row 3 $query = SELECT FROM l i b r a r y ; 13 $ r e s u l t = $db >S e l e c t L i m i t ( $query, 5, 3) or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; 15 // iterate through resultset while (! $ r e s u l t >EOF) 17 { echo $ r e s u l t >f i e l d s [ 1 ].. $ r e s u l t >f i e l d s [ 2 ]. \n ; 19 $ r e s u l t >MoveNext ( ) ; } 21 // clean up $db >Close ( ) ; 23?> In this case, the SelectLimit() method can be used to obtain a subset of the complete resultset retrieved from the database. The first argument to the method is the query to execute, the second is the number of rows required, and the third is the row offset from which to begin. Finally, you can obtain a list of databases on the server via the MetaDatabases() method, and a list of tables within the current database via the MetaTables() method. 1 <?php // include the ADODB library 3 i n c l u d e ( adodb. i n c. php ) ; // create an object instance 5 // configure library for a MySQL connection $db = NewADOConnection ( mysql ) ; 7 // open connection to database $db >Connect ( l o c a l h o s t, john, doe, db278 ) 9 or die ( Unable to connect! ) ; // get database list 11 echo Databases : \ n ; f o r e a c h ( $db >MetaDatabases ( ) as $d ) 13 { echo $d\n ; 15 } // get table list 17 echo \ ntables in c u r r e n t database : \ n ; f o r e a c h ( $db >MetaTables ( ) as $ t a b l e ) 19 { echo $ t a b l e \n ; 21 } // clean up 23 $db >Close ( ) ;?> 6.10 Voorbereiden van een SQL statement In the event that you need to execute a particular query multiple times with different values - for example, a series of INSERT statements - the ADODB class comes with two methods that can save you a huge amount of time and also reduce overhead. Consider the following example, which demonstrates: <?php 66
2 // include the ADODB library i n c l u d e ( adodb. i n c. php ) ; 4 // create an object instance // configure library for a MySQL connection 6 $db = NewADOConnection ( mysql ) ; // open connection to database 8 $db >Connect ( l o c a l h o s t, john, doe, db278 ) or die ( Unable to connect! ) ; 10 // prepare query $query = $db >Prepare ( INSERT INTO l i b r a r y ( t i t l e, author ) VALUES (?,?) ) ; 12 // read title - author list in from CSV file $data = f i l e ( l i s t. txt ) ; 14 // iterate through each line in file f o r e a c h ( $data as $ l ) 16 { // split on comma 18 $ a r r = explode (,, $ l ) ; // insert values into prepared query 20 $ r e s u l t = $db >Execute ( $query, array ( $ a r r [ 0 ], $ a r r [ 1 ] ) ) or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; 22 } // clean up 24 $db >Close ;?> The Prepare() function, which takes an SQL query as parameter, readies a query for execution, but does not execute it (kinda like the priest that walks down the last mile with you to the electric chair). Instead, prepare() returns a handle to the prepared query, which is stored and then passed to the Execute() method, which actually executes the query. Note the two placeholders used in the query string passed to Prepare() - these placeholders are replaced by actual values each time Execute() runs on the prepared statement. The second argument to Execute() is a PHP array containing the values to be substituted in the query string. It should be noted that using Prepare() can provide performance benefits when you have a single query to be executed a large number of times with different values. However, this benefit is only available to you if your database system supports prepared queries (MySQL does not at this time, although Interbase and Oracle do); in all other cases, only simulated functionality is available and Prepare() becomes equivalent to a simple Execute(), with no inherent performance gain. 6.11 Transacties Find out how ADODB can be used to optimize multiple-run queries, commit and roll back transactions, and improve performance by caching query results. If your database system supports transactions (MySQL doesn t, but quite a few others do), you ll be pleased to hear that ADODB allows you to transparently use this feature in your scripts. The following example demonstrates: 1 <?php // include the ADODB library 3 i n c l u d e ( adodb. i n c. php ) ; // create an object instance 5 // configure library for a MySQL connection $db = NewADOConnection ( mysql ) ; 7 // open connection to database $db >Connect ( l o c a l h o s t, john, doe, db278 ) 9 or die ( Unable to connect! ) ; 67
// turn off auto - commit 11 // begin transaction block $db >BeginTrans ( ) ; 13 // first query $query = INSERT INTO l i b r a r y ( t i t l e, author ) ; 15 $query.= VALUES ( T i t l e A, Author B ) ; $ r e s u l t = $db >Execute ( $query ) 17 or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; // use ID from first query in second query 19 i f ( $ r e s u l t ) { 21 $ id = $db >I n s e r t I D ( ) ; $query = INSERT INTO p u r c h a s e i n f o ( id, p r i c e ) ; 23 $query = VALUES ( $id, USD 3 9. 9 9 ) ; $ r e s u l t = $db >Execute ( $query ) 25 or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; } 27 // if no failures i f ( $ r e s u l t ) 29 { // commit 31 $db >CommitTrans ( ) ; } 33 // else rollback else 35 { $db >RollbackTrans ( ) ; 37 } // clean up 39 $db >Close ;?> The first step here is to turn off auto-committal of data to the database, via the BeginTrans() method; this method also marks the beginning of a transaction block, one which can be ended by either CommitTrans() or RollbackTrans(). Once auto-commit has been turned off, you can go ahead and execute as many queries as you like, secure in the knowledge that no changes have (yet) been made to the database. Every call to Execute() within the transaction block returns either a true or false value, depending on whether or not the query was successful. These values can be tracked, and used to determine whether or not the entire transaction should be committed. Once you re sure that all is well, you can save your data to the database via a call to the CommitTrans() method. In the event that you realize you made a mistake, you can rewind gracefully with the RollbackTrans() function. 6.12 Cached queries One of the coolest things about ADODB has to be its support for cached queries. Why? Because caching your queries can result in a fairly significant performance improvement, especially if you re executing the same tired old SELECT every time. In order to illustrate the difference, let s take a look at how this normally works: <?php 2 // include the ADODB library i n c l u d e ( adodb. i n c. php ) ; 4 // create an object instance // configure it for a MySQL connection 68
6 $db = NewADOConnection ( mysql ) ; // open connection to database 8 $db >Connect ( l o c a l h o s t, john, doe, db278 ) or die ( Unable to connect! ) ; 10 // execute query $query = SELECT FROM l i b r a r y ; 12 $ r e s u l t = $db >Execute ( $query ) or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; 14 // iterate through resultset // print column data in format TITLE - AUTHOR 16 while (! $ r e s u l t >EOF) { 18 echo $ r e s u l t >f i e l d s [ 1 ].. $ r e s u l t >f i e l d s [ 2 ]. \n ; $ r e s u l t >MoveNext ( ) ; 20 } // get and print number of rows in resultset 22 echo \n [. $ r e s u l t >RecordCount ( ). rows r eturned ] \ n ; // close database connection 24 $db >Close ( ) ;?> This should be familiar to you by now - it s a very basic SQL SELECT operation with ADODB. If this was your personal Web site, and you were getting 5000 hits a minute, you d be running the query above 30,000 times an hour. As you might imagine, this will have your database server scurrying around like a hamster on cocaine - not to mention affecting the performance of your Web site. ADODB offers a better option - caching the results of the first SELECT query, and using this cached resultset in each subsequent run of the query. This reduces the load on the database server, and can also provide you with an incremental performance benefit. Here s what the revised script looks like: 1 <?php // include the ADODB library 3 i n c l u d e ( adodb. i n c. php ) ; // set cache location 5 $ADODB CACHE DIR =. ; // create an object instance 7 // configure it for a MySQL connection $db = NewADOConnection ( mysql ) ; 9 // open connection to database $db >Connect ( l o c a l h o s t, john, doe, db278 ) 11 or die ( Unable to connect! ) ; // execute query 13 $query = SELECT FROM l i b r a r y ; $ r e s u l t = $db >CacheExecute (300, $query ) 15 or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; // iterate through resultset 17 // print column data in format TITLE - AUTHOR while (! $ r e s u l t >EOF) 19 { echo $ r e s u l t >f i e l d s [ 1 ].. $ r e s u l t >f i e l d s [ 2 ]. \n ; 21 $ r e s u l t >MoveNext ( ) ; } 23 // get and print number of rows in resultset echo \n [. $ r e s u l t >RecordCount ( ). rows r eturned ] \ n ; 69
25 // close database connection $db >Close ( ) ; 27?> The first argument to CacheExecute() is the number of seconds to cache the query results; the second is, obviously, the query string itself. The remainder of the script remains unchanged - a cached resultset is processed in exactly the same manner as a non-cached one. You can also use the CacheFlush() method to flush all queries from the cache. 6.13 Genereren van html ADODB also comes with a couple of methods designed specifically for common Web development tasks. One of the most useful is the GetMenu() method, which retrieves and iterates over a resultset, and uses it to automatically build a form drop-down list containing the database records. This comes in very handy for dynamically-generated forms, when the items in the various form listboxes have to be dynamically built from a database. Here s an example of how it works: 1 <html> <head></head> 3 <body> <?php 5 // include the ADODB library i n c l u d e ( adodb. i n c. php ) ; 7 // create an object instance // configure it for a MySQL connection 9 $db = NewADOConnection ( mysql ) ; // open connection to database 11 $db >Connect ( l o c a l h o s t, john, doe, db278 ) or die ( Unable to connect! ) ; 13 // execute query $query = SELECT t i t l e, id FROM l i b r a r y ; 15 $ r e s u l t = $db >Execute ( $query ) or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; 17 // print HTML menu print $ r e s u l t >GetMenu ( l i b r a r y,, f a l s e ) ; 19 // close database connection $db >Close ( ) ; 21?> </body> 23 </html> The GetMenu() method takes a number of arguments, which can be used to control the behaviour of the generated list box. The first argument is the name for the list ( library, in this case); the second is the default value for the list; the third lets you specify whether the first item in the list should be empty; and the fourth lets you control whether or not the list allows multiple selection. Here s the HTML code generated by the script above: <select name="library" > <option value="15">mystic River</option> <option value="16">where Eagles Dare</option> <option value="17">xml and PHP</option> </select> As you can see, the contents of the list box are built from the resultset returned by the query; the first column of the resultset becomes the label for each list item, while the second is the corresponding value. 70
The GetMenu() method can simplify the task of developing a Web form substantially, significantly reducing the amount of code you have to write - consider using it the next time you need to build a list box from the records in a database. 6.14 Omvorming naar specifieke formaten ADODB also allows you to export a resultset into a variety of different formats - comma-separated text, tab-separated text, or even an HTML table. These functions are not part of the ADODB class per se; rather, they are packaged as ancillary functions in a separate file, which needs to include()-d in your scripts. The following example demonstrates: 1 <?php // include the ADODB library 3 i n c l u d e ( adodb. i n c. php ) ; // include conversion functions 5 i n c l u d e ( toexport. i n c. php ) ; // create an object instance 7 // configure it for a MySQL connection $db = NewADOConnection ( mysql ) ; 9 // open connection to database $db >Connect ( l o c a l h o s t, john, doe, db278 ) 11 or die ( Unable to connect! ) ; // execute query 13 $query = SELECT t i t l e, id FROM l i b r a r y ; $ r e s u l t = $db >Execute ( $query ) 15 or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; // return a CSV string 17 echo r s 2 c s v ( $ r e s u l t ) ; // close database connection 19 $db >Close ( ) ;?> Here s the output: title,id Mystic River,15 Where Eagles Dare,16 XML and PHP,17 You can suppress the first line, the column list, by adding an extra argument to the call to rs2csv(), like this: <?php 2 // snip // return a CSV string 4 echo r s 2 c s v ( $ r e s u l t, f a l s e ) ;?> And here s the revised output: Mystic River,15 Where Eagles Dare,16 XML and PHP,17 You can format the data as a tab-separated string with the rs2tab() function, 71
1 <?php // include the ADODB library 3 i n c l u d e ( adodb. i n c. php ) ; // include conversion functions 5 i n c l u d e ( toexport. i n c. php ) ; // create an object instance 7 // configure it for a MySQL connection $db = NewADOConnection ( mysql ) ; 9 // open connection to database $db >Connect ( l o c a l h o s t, john, doe, db278 ) 11 or die ( Unable to connect! ) ; // execute query 13 $query = SELECT t i t l e, id FROM l i b r a r y ; $ r e s u l t = $db >Execute ( $query ) 15 or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; // return a tab- separated string 17 echo r s2tab ( $ r e s u l t ) ; // close database connection 19 $db >Close ( ) ;?> which returns the following output: title id Mystic River 15 Where Eagles Dare 16 XML and PHP 17 or as an HTML table with the rs2html() function, <html> 2 <head></head> <body> 4 <?php // include the ADODB library 6 i n c l u d e ( adodb. i n c. php ) ; // include conversion functions 8 i n c l u d e ( tohtml. i n c. php ) ; // create an object instance 10 // configure it for a MySQL connection $db = NewADOConnection ( mysql ) ; 12 // open connection to database $db >Connect ( l o c a l h o s t, john, doe, db278 ) 14 or die ( Unable to connect! ) ; // execute query 16 $query = SELECT t i t l e, id FROM l i b r a r y ; $ r e s u l t = $db >Execute ( $query ) 18 or die ( Error in query : $query.. $db >ErrorMsg ( ) ) ; // return a table 20 echo rs2html ( $ r e s u l t ) ; // close database connection 22 $db >Close ( ) ;?> 24 </body> </html> 72
which looks like this: title id Mystic River 15 Where Eagles Dare 16 XML and PHP 17 A number of other interesting conversion functions are also shipped with ADODB - take a look at the documentation for more information. Bijv. $db->dbtimestamp( mktime(0,0,0,18,3,2008) ), the result of this function can be used as a value for a date attribute in an INSERT statement. 73
7 AJAX: Asynchronous Javascript And XML 7.1 Werking Ajax bestaat reeds enkele jaren maar is pas goed doorgedrongen sinds 2005, toen J.J.Garett de naam Ajax introduceerde. De idee van Ajax is relatief eenvoudig, maar het geeft een nieuw zicht op het ontwikkelen van Web interacties. In een traditionele web interactie zendt de de client boodscahppen naar de server, door een link in een document in de browser aan te klikken of door een formulier naar de server te submitten. Dan wacht de server totdat de server een antwoord geeft door middel van een nieuw document. Het volledige scherm in de browser wordt vervangen door een nieuw document. Complexe documenten kunnen wel wat tijd vragen om van de server naar de client getransporteerd te worden en zelfs nog meer tijd om door de browser getoond te worden. In relatief intensieve web-activiteiten die gedurende een zekere tijd actief blijven kan de delay voor het ontvangen en tonen van een antwoord document storend zijn voor de gebruiker. user activity user activity user activity CLIENT data data data data t SERVER system processing system processing Een Ajax webtoepassing verschilt op twee punten van een klassieke webtoepassing: 1. communicatie van de browser met de server is asynchroon: de browser hoeft niet op het antwoord van de server te wachten; de gebruiker dan gewoon verder doen met wat hij bezig was te doen terwijl de server het gevraagde document doorstuurt de door de brwoser getoond wordt; 2. het door de server doorgestuurde document is meestal slechts een klein deel van het getoonde document: het vraagt dus minder tijd om het door te sturen en door de browser te laten zien. Hierdoor krijgt men een veel snellere interactie tussen browser en server. browser UI user activity input AJAX engine client-side processing display input display input display input display data data data data data data data data t SERVER server-side server-side server-side server-side processing processing processing processing De x in Ajax (XML) geeft aan dat in de meeste gevallen de data die door de server teruggestuurd wordt de vorm heeft van een XML document met daarin de nieuwe data die in de browser moet getoond worden. Maar de tegestuurde data kan ook gewone tekst zijn, bijvoorbeeld gegevens of Javascript code. Eén van de doelstellingen van Ajax is het creëren van webtoepassingen die meer gelijken op desktop (client residente) toepassingen op het vlak van snelheid van interactie en dus een prettiger ervaring voor de eindgebruiker. Het opzet van Ajax is het bouwen van een snelle, dynamisch website met eventueel besparen op resources: 74
ipv. een server en het netwerk: gebruikmaken van de kracht van alle clients vroeger: bewerken van webpagina s: server-side met bijv. volledige pagina naar de client gestuurd; PHP scripts en dan werd de Ajax laat verwerking op de client computer toe (in JavaScript) met data ontvangen van de server Ajax kan selectief een deel van een pagina die getoond wordt in de browser, aanpassen, zonder dat het gehele document met alle beelden, menu s,... moet herladen worden bijvoorbeeld velden van formulieren, keuzes van de gebruiker, kunnen verwerkt worden en de resultaten onmiddellijk getoond worden in dezelfde pagina. Ajax heeft als voordeel ten opzichte van zijn competitors (ASP.NET en JSP) dat het verschillende technologiën omvat die reeds in de meeste browsers en servers aanwezig zijn: standaard gebaseerde presentatie met XHTML en CSS; dynamische display en interactie mbv. het Document Object Model; data uitwisseling en manipulatie met XML en XSLT; asynchroon data opvragen met het XMLHttpRequest; JavaScript om alles aan elkaar te koppelen. Met Ajax werken vereist ook niet het aanleren van een nieuwe tool of een nieuwe scripting taal; wel een nieuwe manier van denken over webinteracties. In een klassieke webtoepassing triggert een gebruikersactie in de interface een HTTP request naar een web server. De server doet wat verwerking, zoals data opvragen, berekeningen,..., en stuurt dan een HTML pagina terug naar de client. Deze benadering is technisch in orde maar ze zorgt niet voor een prettige ervaring bij de eindgebruiker. Terwijl de server zijn deel aan het uitvoeren is, is de gebruiker aan het wachten. En bij elke stap in een taak, de gebruiker wacht nog wat meer. browser client browser client user interface HTTP request http(s) transport HTML+CSS data webserver databank backend verwerking server-side systemen webtoepassing klassiek model javascript call user interface AJAX engine HTML+CSS data HTTP request http(s) transport XML data web en XML server databank backend verwerking server-side systemen webtoepassing AJAX model Wanneer het web niet vanuit de context van hypertext maar vanuit het aanbieden van toepassingen uit het niets zou herontwikkeld worden, zouden we de gebruiker niet zoveel laten wachten. Eens een interface geladen is, zou de gebruikers niet hoeven te wachten telkens de toepassing iets nodig heeft van de server. De gebruiker hoeft zelfs niet te weten dat de toepassing naar de server gaat. Een Ajax toepassing elimineert de start-stop-start-stop aard van een interactie over het Web door een intermediaire lag, een Ajax engine, te introduceren tussen de gebruiker en de server. In plaats van een webpagina te laden bij het begin van een sessie, laadt de browser een Ajax 75
engine (geschreven in JavaScript en gewoonlijk verborgen in een apart frame). Deze engine is verantwoordelijk voor het tonen van de interface die de gebruiker ziet en voor de communicatie met de server op vraag van de gebruiker. De Ajax engine laat toe dat de gebruikers interactie met de toepassing asynchroon gebeurt, onafhankelijk van de communicatie met de server. De gebruiker staart dus nooit naar een blank browser window en een zandloper icoon, wachtend op de server die iets moet doen. Elke gebruikersactie die normaal een HTTP request zou genereren, wordt een Javascript oproep naar de Ajax engine instead. Elk antwoord op een gebruikersactie dat geen trip naar de server vereist (eenvoudige data validatie, editeren van data in memory, soms zelfs navigatie), verwerkt de engine zelfstandig. Wanneer de engine iets van de server nodig heeft om te kunnen antwoorden (submitten van formuliergegevens, laden van bijkomende interface code, opvragen van nieuwe data), dan worden deze requests asynchroon gedaan, meestal door gebruik te maken van XML, zonder de gebruikersinteractie met de toepassing te storen. 7.2 Voorbeeld Creatie van een eenvoudig formulier met twee 2 invoervelden: in het eerste veld kan door de gebruiker willekeurige tekst ingetikt worden; deze tekst zal naar een PHP script doorgestuurd worden die deze tekst zal omzetten naar hoofdletters en dan zal terugsturen naar de browser; in de browser zal deze tekst in het tweede invoerveld getoond worden. De verschillende acties zijn: luisteren op een key-press event bij het invoerveld; bij een key-press een boodschap sturen naar het PHP script op de server; verwerken van de invoer met PHP en terugsturen van het resultaat; opvangen van de teruggezonden data en tonen. Het HTML-gedeelte: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/tr/xhtml1/dtd/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <title>ajax - PHP voorbeeld</title> </head> <body> <form name="testform"> Input tekst: <input type="text" onkeyup="dowork();" name="inputtext" id="inputtext" /> Output tekst: <input type="text" name="outputtext" id="outputtext" /> </form> </body> </html> Telkens een toets ingedrukt wordt, wordt de functie dowork() functie opgeroepen. In deze functie wordt ondermeer de communicatie tussen de browser en de server opgezet. Voor deze communicatie tussen client en server moet de client code een XMLHttpRequest object creëren. Dit object zal verantwoordelijke zijn voor de Ajax-PHP communicatie. Omdat de code voor de creatie browser-afhankelijk is, moeten er enkele verschillende gevallen onderscheiden worden om ook oudere browsers te ondersteunen: <s c r i p t language= j a v a s c r i p t type= t e x t / j a v a s c r i p t > 2 <! // Get the HTTP Object 76
4 function gethttpobject ( ) { 6 i f ( window. ActiveXObject ) return new ActiveXObject ( M i c r o s o f t.xmlhttp ) ; 8 else i f ( window. XMLHttpRequest ) return new XMLHttpRequest ( ) ; 10 else { 12 a l e r t ( Your browser does not support AJAX. ) ; return n u l l ; 14 } } 16 // Change the value of the outputtext field function seto utput ( ) 18 { i f ( httpobject. r eadystate == 4) 20 { document. getelementbyid ( outputtext ). value = 22 httpobject. responsetext ; } 24 } // Implement business logic 26 function dowork ( ) { 28 httpobject = gethttpobject ( ) ; i f ( httpobject!= n u l l ) 30 { httpobject. open ( GET, uppercase. php? inputtext= 32 +document. getelementbyid ( inputtext ). value, true ) ; httpobject. send ( n u l l ) ; 34 httpobject. o nreadystatechange = setoutput ; } 36 } var httpobject = n u l l ; 38 //--> </ s c r i p t > In de dowork functie wordt eerst een XMLHttpRequest object gecreëerd. Daarmee kan de waarde van het inputtext veld naar het serverscript gestuurd worden. Hiervoor wordt een URL met parameter samengesteld zodat in het PHP script de $_GET super-global array kan gebruikt worden om de data op te vragen. Met de send() functie van het XMLHttpRequest object wordt de aanvraag naar de server gestuurd. Om het antwoord van de server op te vangen wordt een andere property van het XMLHttpRequest object gebruikt. Aan deze parameter wordt een functie toegewezen en deze functie zal opgeroepen worden wanneer de status van het object wijzigt. De setoutput() functie zal de waarde van het tweede veld van het formulier wijzigen. Deze waarde kan pas gewijzigd worden wanneer de toestand van het XMLHttpRequest object complete is. In deze functie moet dus eerst de actuale toestand van het XMLHttpRequest object gecontroleerd worden door middel van de readystate property. Langs de server kant moet in de PHP code de $_GET super-global array gecontroleerd worden. De inhoud moet omgevormd worden maar hoofdletters en met behulp van echo terug naar de client gestuurd worden. <?php i f ( i s s e t ($ GET [ inputtext ] ) ) echo s t r t o u p p e r ($ GET [ inputtext ] ) ;?> 77
Intermezzo: toegang tot document elementen. De elementen in een XHTM document hebben corresponderende objecten die zichtbaar zijn in Javascript. Zowel voor eventhandling als voor dynamisch aanpassen van documenten moeten deze objecten vanuit Javascript kunnen geaddresseerd worden. In de orginele (DOM 0) manier worden deze objecten aangesproken door middel van de forms en elements arrays van het document object (property van het window object. <form name="flip" action=""> <input type="button" name="doen" id="doenid" /> <input type="button" name="stop" id="stopid" /> <input type="text" name="plaats" id="plaatsid" /> </form> Het aanspreken van het tekst invoerveld: var veld = document. forms [ 0 ]. elements [ 2 ] ; Dit is geen flexibele manier van adresseren want bij het toevoegen van bijkomende elementen in het formulier moeten de indices in de arrays herbekeken worden. Een andere manier maakt gebruik van de namen van de elementen: var veld = document. f l i p. p l a a t s ; Een probleem hiermee is dat in de XHTML 1.1 standaard een <form> tag geen name attribuut mag hebben. Dus het valideren geeft een probleem alhoewel de browsers geen last hebben van een name attribuut bij form tags. De elementen in het formulier mogen natuurlijk wel een name attribuut hebben omdat deze nodig zijn voor de verwerkingsscripts langs de serverkant. In Dom 1 is de Javscript methode getelementbyid gedefinieerd. Aangezien de identifier van een element (attribuut id) uniek is in een document, kan op basis van de id elk element geadressseerd worden onafhankelijk van de nesting van het element in andere elementen van het document. var veld = document. getelementbyid ( plaatsid ) ; Het argument van getelementbyid kan een constante string zijn, maar ook een variabele waarvan de waarde een string is. Omdat het name attribuut nodig is voor de serverkant en het id attribuut nuttig is langs de client kant, hebben formulier elementen dikwijls beide attributen, gewoonlijk met dezelfde waarde. Knoppen in een groep van checkboxen hebben dikwijls dezelfde naam. De knoppen in een radio knoppengroep hebben steeds dezelfde naam. Elke knop zou een apart id attribuut kunnen krijgen, maar dit levert geen gemakkelijke manier op om een groep van checkboxen of radioknoppen te doorzoeken. Een alternatief wordt geboden door de impliciete array die met elke checkbox- of radioknoppen-groep geassocieerd is. <form id="kargroep"> <input type="checkbox" name="voertuig" value="auto" /> Auto <input type="checkbox" name="voertuig" value="fiets" /> Fiets <input type="checkbox" name="voertuig" value="boot" /> Boot </form> De impliciete array voertuig heeft drie elementen, referenties naar de drie objecten geassocieerd met de drie checkbox elementen in de groep. var aantalcheck = 0 ; var grp = document. getelementbyid ( kargroep ) ; for ( i =0; i <grp. v o e r t u i g. length ; i ++) { i f ( grp. v o e r t u i g [ i ]. checked ) aantalcheck++; } 78
7.3 Het XMLHttpRequest object Dit object wordt door Javascript gebruikt om data uit te wisselen met de server, in gewone tekstt, XML of JSON formaat. Het XML formaat wordt automatisch geparsed door het object bij laden en is beschikbaar door middel van methodes van DOM. JSON bestanden worden geparsed dmv. de eval() Javascript functie. Voor elke aanvraag naar de server wordt een nieuwe instantie gecreëerd door een oproep van de constructor. De open methode start de connectie, in read of write mode, om data van de server te ontvangen of te versturen. Deze data wordt door de server verwerkt met een server-side taal zoals PHP, Java,... De connectie heeft verschilende opeenvolgende toestanden die aangegeven worden in het readystate attribuut van het object. Wanneer de finale toestand bereikt wordt, kan de data in een ander attribuut gevonden worden. Dit kan gewone tekst of een XML document zijn. Het JSON formaat wordt geladen als gewone tekst en geparsed door Javascript. De klasse heeft een aantal attributen voor het bewaren van de toestand en de data: attribuut type readystate unsigned short deze code wijzigt opeenvolgend van waarde (van 0 tot 4) totdat de server klaar is 0 Not initialized 3 Received 1 Open 4 Loaded 2 Sent status unsigned short 200 ok 404 de pagina is niet gevonden statustext DOMString bevat de label van de toestand, corresponderend met de toestandscode responsetext DOMString bevat geladen data als een string van tekens (is pas volledig opgevuld wanneer de toestand 4 is responsexml DOMDocument bevat een XML geladen bestand file; mbv. DOM methoden kan data geextraheerd worden; dit veld is opgevuld wanneer de code gelijk is aan 4, bevat in alle andere gevallen null onreadystatechange EventListener wordt opgeroepen wanneer readystate een waarde toegewezen wordt. Naast de constructor heeft de klasse twee belangrijke methodes, open om een sessie te creëren en een serverfile toe te wijzen, en send om data naar de server te sturen. abort() getallresponseheaders() getresponseheader( DOMString) open(mode, url, boolean [, login, password]) send( string ) setrequestheader( DOMString, DomString) resets het object en stopt alle activiteit gecreëerd door het object geeft alle headers terug in een string, gescheiden door CR en LF codes geeft de header terug van de ontvangen data, na de laatste request; meerdere headers zouden moeten gescheiden zijn door een komma en een spatie mode requesttype, GET, POST, HEAD of andere http methoden url de locatie van het bestand, dmv. een pad boolean true (asynchroon) / false (synchroon) optioneel een login en een paswoord als bjkomende argumenten null of leeg bij een GET command, in de andere gevallen een string; genereert een DOMException (INVALID_STATE_ERR) indien de readystate code niet gelijk is aan 1; argumenten zijn de naam van de header en de waarde; meerdere waarden kunnen opeenvolgend gezonden worden; genereert een DOMException (INVALID_STATE_ERR) indien de readystate code niet gelijk is aan 1 79
7.4 Voorbeeld: master-detail select boxen Creatie van een formulier waarin de bezoeker eerst een hoofdcategorie selecteert en dan een bijhorende subcategorie. Zo n formulier is niet zo eenvoudig te maken omdat bij het ontwerp nog niet geweten is welke waarde uit de hoofdlijst geselecteerd zal worden om op basis daarvan een gepaste detail selectielijst te tonen. hoofdcategorie Audi BMW Mercedes Lexus bijhorende subcategories A3, A4, A6, A8 voor Audi 320, 520, 630, 745 voor BMW A180, C200, E320, S500 voor Mercedes IS200, GS300, LS600 voor Lexus Met Ajax wordt een boodschap naar de server gestuurd telkens de bezoeker de waarde in de master select box wijzigt. De server construeert een lijst met relevante detail items en zendt deze terug naar de browser. Deze lijst wordt door de browser aan de detail select lijst toegewezem. Het HTML formulier: <form name="testform" action=""> Select car brand:<br/> <select name="brand" id="brand" onchange="dowork();"> <option value="audi">audi</option> <option value="bmw">bmw</option> <option value="mercedes">mercedes</option> <option value="lexus">lexus</option> </select> <br/><br/>select car type:<br/> <select name="type" id="type"> <option value="1">a3</option> <option value="2">a4</option> <option value="3">a6</option> <option value="4">a8</option> </select> </form> In dit formulier worden in de detaillisjt de mogelijkheden voor Audi getoond. Maar deze detaillijst wordt aangepast telkens er in de hoofdlijst een andere selectie gebeurt door middel van de eventhandler dowork(). De Javascript code in de Ajax engine: <s c r i p t language= j a v a s c r i p t type= t e x t / j a v a s c r i p t > <! function gethttpobject ( ) { // construeer het HTTP Object: zie vorig voorbeeld } // wijzig de detail select lijst function seto utput ( ) { i f ( httpobject. r eadystate == 4) { var combo = document. getelementbyid ( type ) ; combo. o p t i o n s. length = 0 ; var r esponse = httpobject. responsetext ; 80
var items = r esponse. s p l i t ( ; ) ; var count = items. length ; for ( var i =0; i <count ; i ++) { var o p t i o n s = items [ i ]. s p l i t ( ) ; combo. o p t i o n s [ i ] = new Option ( o p t i o n s [ 0 ], o p t i o n s [ 1 ] ) ; } } } // de event handler : business logica function dowork ( ) { httpobject = gethttpobject ( ) ; i f ( httpobject!= n u l l ) { httpobject. open ( GET, subcat. php? brand= +document. getelementbyid ( brand ). value, true ) ; httpobject. o nreadystatechange = setoutput ; httpobject. send ( n u l l ) ; } } var httpobject = n u l l ; //--> </s c r i p t > De server moet op een of ander manier een lijst van mogelijkheden doorsturen. In dit geval wordt dit gedaan door middel van een speciaal geformatteerde string: de verschillende items zijn van elkaar gescheiden door een puntkomma en elk item bevat een waarde en een label van elkaar gescheiden door een minteken. Bijvoorbeeld: A3-1;A4-2;A6-3;A8-4. Met behulp van de Javascript split () functie in de setoutput functie wordt deze string gemakkelijk uit elkaar gerafeld. Daarnaast wordt de actuele lijst leeggemaakt en worden de nieuwe items toegevoegd aan de lijst. Het PHP script langs de serverkant: <?php i f ( i s s e t ($ GET [ brand ] ) ) { switch ($ GET [ brand ] ) { case Audi : echo A3 1; A4 2 ; A6 3 ; A8 4 ; break ; case BMW : echo 320 1;520 2;630 3;745 4 ; break ; case Mercedes : echo A180 1 ; C200 2 ; E320 3 ; S500 4 ; break ; case Lexus : echo IS200 1 ; GS300 2 ; LS600 3 ; break ; } }?> 81
7.5 Andere mogelijkheden In de volgende script-fragmenten is xhr een object van de klasse XMLHttpRequest. De tekst die van de server teruggestuurd wordt, kan ook in de body van een pagina geplaatst worden: <div id= zone >... t e k s t d i e vervangen wordt door t e k s t ontvangen van s e r v e r... </div> In de callback functie die met xhr.onreadystatechange gezet wordt: document. getelementbyid ( zone ). innerhtml = Ontvangen : + xhr. responsetext ; In plaats van de GET methode kan ook de POST methode gebruikt worden. In het voorbeeld moet een tekst naar de server gestuurd worden welke deze bewaart in een bestand. De oproep naar de open methode heeft nu als eerste argument POST; de url specificeert de naam van het script dat de toegestuurde data moet ontvangen en verwerken. De send methode heeft als argument een string van parameters. xhr. open ( POST, ajax post text. php, true ) ; xhr. setrequestheader ( Content Type, a p p l i c a t i o n /x www form urlencoded ) ; var data = f i l e = + u r l + &content= + content ; xhr. send ( data ) ; De parameter van de send methode is overeenkomstig het formaat van de HTML POST methode; wanneer meerdere waarden gestuurd worden, worden deze van elkaar gescheiden door ampersand symbolen. De data kan ook uit een XML bestand geextraheerd worden in plaats van als gewone tekst uit de property responsetext: // wijs XML bestand toe aan een variabele var doc = xhr. responsexml ; // lees eerste element van het XML document var element = doc. getelementsbytagname( r o o t ). item ( 0 ) ; // wijs inhoud toe aan een formulier element document. ajax. dyn. value= element. f i r s t C h i l d. data ; 82
8 Beveiliging voor HTTP Het HTTP protocal verzendt informatie als gewone tekst. Gevoelige informatie die via HTTP verstuurd wordt is dan ook niet veilig. Om deze reden is het aangewezen om dergelijke informatie te versleutelen, zodat zij niet onderschept kan worden. De meeste gewone encryptiemethodes die hiervoor gebruikt zouden kunnen worden, vereisen dat er al een secuur communicatiekanaal bestaat, waarlangs de te gebruiken versleuteling kan worden afgesproken (bv. de geheime code van een bankkaart die middels een brief met afkrabvakje verstuurd wordt). Voor internettoepassingen is dit vanzelfsprekend niet erg handig. Daarom wordt hier typisch gebruik gemaakt van publieke sleutel encryptie. Bij een dergelijke vorm van encryptie publiceer je een publieke sleutel, die iedereen mag gebruiken om jou versleutelde boodschappen te sturen. Je hebt je publieke sleutel zodanig geconstrueerd dat enkel jij weet hoe boodschappen die hiermee versleuteld zijn weer leesbaar gemaakt kunnen worden. De kennis die je toelaat om de boodschappen te decrypteren wordt de private sleutel genoemd en die moet je vanzelfspreken geheim houden. De basis van dergelijke encryptiemethodes is het idee van een éénwegsfunctie: een functie die zelf eenvoudig te berekenen valt, maar waarvan de inverse juist heel moeilijk te berekenen is. Een typisch en veel gebruikt voorbeeld is het vermenigvuldigen van twee priemgetallen. Encryptiealgoritmes zoals bv. RSA vertrouwen dus op het feit dat het factorizeren van een getal in zijn priemfactoren zeer moeilijk is (voor grote getallen, natuurlijk) om de vertrouwelijke gegevens geheim te houden. Ze gebruiken hiervoor niet zomaar een éénwegsfunctie, maar een éénwegsfunctie met valluik, dwz. een functie waarvoor het heel moeilijk is om zijn inverse te berekenen, tenzij je wat extra informatie over de functie hebt. Voor RSA bestaat deze extra informatie ( het valluik ) uit de priemfactoren van een groot getal. De details van RSA zijn als volgt. Je kiest twee grote priemgetallen p en q en vermenigvuldigt deze: n = pq. Je berekent ook het getal (p 1)(q 1), dat genoteerd wordt als φ(n). Een stelling van de beroemde wiskundige Euler zegt dan dat, voor elke x die geen delers gemeeschappelijk heeft met n (dwz. ggd(e, n) = 1), x φ(n) modn = 1. Voor kleine getallen is dit eenvoudig na te rekenen: Neem p = 3 en q = 11; Dan is n = 33 en φ(n) = 2 10 = 20; Voor x = 2 is het inderdaad zo dat ggd(2, 33) = 1, dus zegt de stelling dat: 2 20 mod 33 = 1048576 mod 33 = 1 Het valt eenvoudig na te rekenen dat 1048575 = 31775 33, dus we zien dat de stelling hier inderdaad klopt. Om de publieke sleutel te maken, zoek je nu een e die geen delers gemeeschappelijk heeft met φ(n), maw. in dit geval mag e niet deelbaar zijn door 2 of 5. We nemen e = 3. De publieke sleutel is nu het paar (e, n) = (3, 15). De private sleutel bereken je als volgt: Zoek een d zodat d e mod φ(n) = 1 Deze d is dan de private sleutel. In dit geval is d = 7 aangezien de = 7 3 = 21 en 21 mod 20 = 1. Er bestaan algoritmes die zo n d snel kunnen vinden, dus dat is geen probleem. Het is natuurlijk belangrijk dat enkel jij deze d kan berekenen, omdat enkel jij φ(n) = (p 1)(q 1) kent, aangezien enkel jij weet wat de priemfactoren p en q van n zijn. Het encrypteren van een boodschap met de publieke sleutel gebeurt nu als volgt. We splitsen de boodschap (een hele reeks bits) op in een aantal bitstrings van gelijke lengte, zodat elke bitstring een waarde m < n heeft. De encodering ENC(m) is nu: ENC(m) = m e mod n. 83
Deze ENC(m) = c (de zogenaamde cypher text) wordt dan verstuurd naar de eigenaar van de private sleutel, die de oorspronkelijke plain text m weer kan terugvinden met volgende operatie: DEC(c) = c d mod n. Een interessant eigenschap van dit algoritme is dus dat encodering en decodering in wezen dezelfde operatie zijn, namelijk een machtsverheffing modulo n. Waarom is DEC(c) nu eigenlijk opnieuw gelijk aan de oorspronkelijke m? We kunnen dit als volgt inzien: DEC(c) =DEC(ENC(m)) = (m e mod n) d mod n =m de mod n = m de 1+1 mod n = m de 1 m mod n Omwille van onze keuze van d weten we dat er een k N bestaat, zodat de 1 = kφ(n). Dus: m de 1 m mod n = m kφ(n) m mod n = (m φ(n) ) k m mod n = ((m φ(n) mod n) k m mod n. De stelling van Euler vertelt ons nu dat m φ(n) mod n = 1, zodat ((m φ(n) mod n) k m mod n = 1 k m mod n = m mod n, en aangezien m < n is m mod n = m. Op deze manier kunnen we dus informatie versturen waarvan we zeker zijn dat ze enkel maar gelezen zal kunnen worden door de eigenaar van een bepaalde publieke sleutel. We kunnen hetzelfde algoritme echter ook gebruiken om ons ervan te vergewissen dat een bepaalde boodschap wel degelijk afkomstig is van de eigenaar van een bepaalde publieke sleutel. Hiervoor moet de eigenaar van deze sleutel zijn decryptie functie DEC gebruiken om de boodschap te encrypteren. Maw. als m de boodschap is, dan verstuurt hij c = m d mod n. Deze boodschap is nu allesbehalve geheim, aangezien iedereen die de bijhorende publieke sleutel kent ze kan ontcijferen door ENC(c) = c e mod n = m te berekenen. Maar het is wel zo dat deze boodschap zeker van de juiste afzender afkomstig is. De enige die een boodschap zodanig kan encoderen dat de publieke sleutel e kan gebruikt worden om ze te ontcijferen, is immers diegene die de private sleutel d kent. Natuurlijk zal deze methode enkel maar werken als de publieke sleutel die we gebruiken om de boodschap te ontcijferen inderdaad de juiste sleutel is, dwz. de publieke sleutel die overeenkomt met de private sleutel die enkel maar bekend is aan de bedoelde ontvanger. Een mogelijke kwetsbaarheid is dus dat een belager zou kunnen trachten om de verzender de boodschap te laten decoderen met zí jn publieke sleutel, in plaats van met de publieke sleutel van de bedoelde ontvanger. We hebben nu dus het probleem nog niet opgelost, maar enkel gereduceerd tot het probleem van het te pakken krijgen van de juiste publieke sleutel, dwz. die van de bedoelde ontvanger. Dit laatste probleem wordt opgelost door middel van certificaten. Een certificaat wordt uitgereikt door een betrouwbare instantie en geldt als een bewijs van echtheid van een publieke sleutel: het is, met andere woorden, een garantie dat een bepaalde publieke sleutel inderdaad de sleutel is van een bepaalde persoon/firma. Hoe zit dit nu op technisch vlak precies in elkaar? Een certificaat bestaat uit een publieke sleutel en een verwijzing naar een ondertekenaar van het certificaat. Een cliënt kan aan de ondertekenaar vragen om na te kijken of de publieke sleutel in het certificaat wel degelijk de sleutel is van de server waarmee hij contact probeert te leggen. De ondertekenaar zal zijn antwoord encrypteren met zijn eigen private sleutel en een certificaat meesturen dat zijn eigen publieke sleutel bevat. De cliënt kan dan verifiëren dat het antwoord inderdaad afkomstig is van de ondertekenaar, als hij tenminste gelooft dat het certificaat dat hij ontvangt inderdaad juist is. Dit kan hij opnieuw navragen bij de instantie die dit tweede certificaat ondertekend heeft. Op deze manier creëren we dus een keten van vertrouwen ( chain of trust ), waarbij elk certificaat geverifieerd kan worden bij een hogere instantie. Een dergelijke keten is natuurlijk waardeloos zolang ze geen betrouwbaar beginpunt heeft. Daarom beschikken hedendaagse webbrowsers over een aantal ingebouwde broncertificaten 84
( root certificates ) die kunnen dienen als startpunt voor dergelijke ketens. Deze broncertificaten zijn dus gewoon publieke sleutels van bepaalde bedrijven die (tegen betaling) de echtheid van andere publieke sleutels controleren en hiervoor borg staan. Publieke sleutel encryptie maakt in het algemeen gebruik van berekeningen die ingewikkelder zijn dan voor gewone encryptie. In de praktijk zal men de publieke sleutel encryptie dan ook enkel maar gebruiken om, tijdens de initiële handshake tussen een cliënt en een server, een gedeeld geheim af te spreken, dat van dan af gebruikt zal worden om de rest van de communicatie op een snellere manier te versleutelen. We hebben reeds alle componenten gezien die nodig zijn om een dergelijke gedeeld geheim tot stand te brengen: De cliënt vraagt het certificaat van de server waarmee hij wenst te praten. De cliënt verifiëert dit certificaat en kan er dan zeker van zijn dat hij over de correcte publieke sleutel van de juiste server beschikt. De cliënt genereert een groot willekeurig getal g en encrypteert dit met de publieke sleutel van de server. De server ontvangt deze geëncrypteerde boodschap en ontcijfert ze met zijn private sleutel. Het eindresultaat is dat zowel cliënt als server (maar niemand anders) het getal g kennen. Dit wordt dan gebruikt als geheime sleutel om de rest van de communicatie te beveiligen. De hierboven beschreven procedure wordt gevolgd in het SSL ( Secure Socket Layer ) protocol, dat zich in de transportlaag van het OSI model bevindt. Door HTTP berichten (applicitielaag) via SSL te versturen, in plaats van rechtstreeks over TCP/IP, komen zij dus ongelezen bij de juiste bestemming terecht. Deze methode van HTTP over SSL wordt ook wel https genoemd en door URLs van de vorm https://webadres/pagina te gebruiken wordt aan webbrowsers opgedragen om dit protocol (of eigenlijk: deze combinatie van het HTTP en SSL protocol) te gebruiken. De meeste webbrowsers geven ook duidelijk aan of dit gelukt is, bijvoorbeeld door een andere kleur te geven aan de adresbalk of ergens een pictogrammetje van een slot af te beelden. 85
9 XML: extensible Markup Language 9.1 Definitie 9.1.1 Inleiding Bij XML gaat alles om markup: een manier om in data informatie te stoppen die beschrijft wat die data is. In data worden tags gezet die meer informatie geven over de data, die dus semantiek toevoegen aan de data. Door aan je data tags toe te voegen, creëer je data-containers. Elke tag geeft het begin of einde van een nieuwe data-container aan. XML is een meta-taal om markup talen te definiëren: In XML kan je je eigen tags definiëren, zodat je zelf kan kiezen welke semantiek je toekent aan je data. Deze tags kunnen in principe eender wat zijn in eender welke taal. Door een hele set van tags te definiëren en de mogelijke relaties tussen de tags kan je een nieuwe markup taal maken. Tags worden omgeven door scherpe haakjes: <tag>. XML-tags beschrijven de inhoud en structuur van een document, niet de opmaak. Een XML-document is gestructureerd als een boom, met de wortel ( root ) bovenaan en de takken naar beneden. Afgeleid van SGML (Standard Generalized Markup Language), een ISO standaard (ISO 8879) sinds 1986. XML is SGML voor het web : een vereenvoudigde versie van SGML om data-uitwisseling via het web mogelijk te maken. XML is een recommendation (een standaard) uitgevaardigd op 10/2/1998 door het W3C (World Wide Web Consortium), een onafhankelijke organisatie die zich web-gerelateerde technologieën en standaarden ontwikkelt. Voorbeeld: <?xml version="1.0"?> <!-- voorbeeld: eenvoudige songtekst --> <song> <titel>ticket to ride</titel> <auteur>lennon & McCartney</auteur> <album>het rode</album> <strofe> I think I m gonna be sad I think it s today, yeh The girl that s driving me mad is going away </strofe> <refrein> She s got a ticket to ride She s got a ticket to ride She s got a ticket to ride But she don t care </refrein> </song> Er zijn tegenwoordig veel mogelijkheden om data op te slaan en te gebruiken. Men kan bijvoorbeeld gebruik maken van een databank, maar hiervoor heeft men speciale databanksoftware nodig. Data kan ook lokaal of in een netwerk bewaard worden in normale bestanden, maar dan moet de applicatie de structuur van het bestand kennen. De applicatie zal extra functies moeten bevatten om de data correct te kunnen lezen, bewerken en op te slaan. Als men de dataopslag niet afhankelijk wil maken van één bepaalde applicatie, kan men ze op een gestructureerde manier opslaan. Om dit te realiseren wordt er dikwijls gekozen voor XML-bestanden. 86
9.1.2 Eigenschappen XML is pure tekst (niet binair) en daarom platform- en applicatie-onafhankelijk. Een XMLdocument kan je in principe met eender welk programma op eender welk platform bekijken. Vergelijk dit met het alfabet: twee mensen die een verschillende taal spreken, maar wel hetzelfde alfabet gebruiken, zullen elkaars woorden altijd kunnen herkennen. Als twee mensen elkaars woorden herkennen, wil dit nog niet zeggen dat ze elkaar begrijpen. Iemand spreekt misschien een taal die de ander niet begrijpt. Dit gaat ook op voor XML. Je weet dat je in XML je eigen tags kan definiëren, maar als iedereen zijn eigen tags gaat aanmaken, begrijpt niemand elkaar nog. Enkel als men afspraken maakt over de gebruikte tags, worden XML-gegevens uitwisselbaar. Daarom wordt er gestreefd naar standaarden. Voorbeelden van XML-standaarden zijn XHTML (HTML volgens de XML syntax), MathML (weergeven van wiskundige formules), SMIL (Synchroniseren van Multimedia-informatie), SVG (Weergeven van grafische informatie) en XSLT (zie verder). XML-teksten zijn leesbaar voor mensen en toch handelbaar voor computers. XML is een compromis tussen twee uitersten: voor de machine supersnel toegankelijke maar voor de mens bijna onleesbare codes versus pure tekstgegevens die voor de mens vlot leesbaar zijn maar heel moeilijk te verwerken door computers. Persoonsgegevens weergegeven numeriek (interne computervoorstelling), XML en vlakke tekst: 12345 83019475692745839 <?xml version="1.0"?> <verjaren> <persoon sis="12345"> <naam> Willy Asselman </naam> <gebdat> <dag>29</dag> <maand>2</maand> <jaar>1948</jaar> </gebdat> </persoon> </verjaren> Willy Asselman werd geboren op 29 februari 1948. Zijn SIS nummer is 12345. Bij XML is inhoud gescheiden van opmaak. Dit maakt het heel flexibel: dezelfde inhoud kan makkelijk op verschillende manieren worden getoond. Een ander voordeel is het singlesource principe: één bronbestand van waaruit verschillende presentaties kunnen worden afgeleid. Bij wijzigingen moet enkel de bron worden aangepast en de verschillende presentatievormen veranderen automatisch mee. Zo kan je bijvoorbeeld een andere vorm kiezen voor verschillende soorten gebruikers of toepassingsgebieden (bijvoorbeeld PDA of WAP). Met behulp van XML kan je: gegevens uitwisselen; data stockeren (volgens het single-source principe); efficiënt zoeken in grote hoeveelheden gegevens: als je je tags goed kiest, kan je ze gebruiken om sneller en correcter iets te zoeken in je data; navigeren door gegevens. XML laat toe om veel dingen aan de client-kant te doen, zodat je website sneller wordt. Een hele brok informatie kan in XML-vorm naar de browser worden gestuurd, waarna de browser iets doet met die informatie. XML op zichzelf doet niets. Het is geen programmeertaal of tool, maar enkel een syntax die toelaat om informatie op een gestructureerde en welbepaalde manier op te slaan en uit te wisselen. 87
9.1.3 XML versus HTML HTML=opmaak, XML=inhoud HTML is gericht op opmaak van documenten: het bepaalt voornamelijk hoe een document er moet uitzien, maar nauwelijks wat de inhoud ervan is. XML is wel gericht op de inhoud en trekt zich niets aan van de manier waarop die inhoud getoond wordt. HTML=vast, XML=uitbreidbaar Tags in HTML zijn voorgedefinieerd en kan je dus niet zelf veranderen of uitbreiden. XMLtags daarentegen zijn volledig vrij: je bepaalt ze namelijk zelf. Je kan dus makkelijk nieuwe tags bijmaken als dat nodig is. HTML en XML zijn complementair XML heeft zeker niet de pretentie om HTML in de toekomst te willen vervangen. Het is daarentegen eerder een aanvulling: waar XML de informatiebron is, kan HTML een manier zijn om die informatie te tonen. Beide markup talen vullen elkaar op die manier aan. XHTML Voldoet aan de syntax regels van XML en is conform aan het vocabulaire van HTML. XHTML kan daarom zowel door een XML parser als een HTML browser verwerkt worden. Doordat XHTML voldoet aan de XML syntax, heeft het de volgende voordelen: het is beter uitwisselbaar dan traditionele HTML; het is strikter in het toepassen van de XML syntax en daarom properder (de code is welgevormd); HTML wordt dus beter dankzij XML. HTML moet aan de volgende regels voldoen om juiste XHTML te kunnen zijn: documenten moeten welgevormd zijn. bv. waarden van een attribuut altijd tussen aanhalingstekens; elementen moeten afgesloten worden: <br> kan niet, <br/> wel; elementen moeten correct genest zijn elementen en attribuutnamen moeten in kleine letters: geen <INPUT TYPE="button" VALUE="Klik hier" />, wel <input type="button" value="klik hier" />; attributen en hun waarden moeten expliciet gedefinieerd worden: geen <td nowrap> wel <td nowrap="nowrap">. 9.2 XML syntax 9.2.1 Elementen De basis van een XML-document wordt gevormd door de data-containers die daarstraks al vernoemd werden. Deze containers worden in XML-jargon elementen genoemd. Elementen zijn de bouwstenen van een XML-document. Elementen zijn omgeven door tags: ieder XML-document heeft een rootelement waarvan elke andere tag een kind is. In het voorbeeld is dit de tag <verjaren>, die pas op de laatste regel wordt gesloten. Elke geopende tag moet ook gesloten worden. Het sluiten gebeurt met dezelfde tag, maar voorafgegaan met een slash (/). De begin- en eindtag omsluiten de data en vormen hiermee een XML-element. Een speciaal geval zijn empty elements: elementen die geen inhoud hebben. Er bestaat een afgekorte notatie voor empty elements waarbij de starttag lichtjes gewijzigd wordt en geen eindtag nodig is. Voluit: <leeg></leeg> Afgekort: <leeg/> 88
XML-tags zijn hoofdlettergevoelig. FOUT: <Titel>Ticket to ride</title> Bij het nesten van elementen is het belangrijk dat dit syntactisch correct gebeurt: een subelement moet afgesloten worden voordat het bovenliggende element wordt afgesloten. Data-containers kunnen ook eigenschappen of attributen hebben. Een attribuut geeft extra informatie over een bepaald element. Het attribuut wordt beschreven in de starttag van een element en heeft een naam en een waarde. De waarde van een attribuut moet altijd tussen aanhalingstekens. Zowel enkele quotes ( ) als dubbele quotes (") zijn toegelaten, maar ze mogen niet gemengd worden binnen één attribuut. Deze attributen zijn meestal korte gegevens zoals een volgnummer, tijds- en/of datumaanduiding. Door het nesten van elementen ontstaat er een boomstructuur met als wortel de root tag. Elk XML-document moet precies één root element hebben. <?xml version="1.0"?> <verjaren> verjaren <persoon sis="12345"> <naam> Willy Asselman </naam> persoon sis <gebdat> <dag>29</dag> <maand>2</maand> naam gebdat <jaar>1948</jaar> </gebdat> </persoon> </verjaren> dag maand jaar De nodes in een XML boom: Tekst nodes : de effectieve inhoud (leaf nodes) Element nodes : definiëren de hiërarchische logische groepering van de inhoud; elke node heeft een naam die gebruikt wordt in de start- en eind-tags: <joske>... </joske> Attribuut nodes : niet-geordendend, elke node is geassocieerd met een element node; elke node heeft een naam en een waarde Commentaar nodes : dit is tekst die door de parser (cf. parsing) volledig genegeerd wordt. De tekst maakt dus geen deel uit van de XML, maar biedt aan de auteur de gelegenheid om informatie voor zichzelf of de bestemmeling toe te voegen. Dit kan vooral handig zijn om moeilijke constructies in een DTD (cf. parsing) uit te leggen. Voorbeeld : <!-- zeer moeilijke constructie -->. Proces instructie : tekst die door de parser genegeerd wordt, maar gebruikt wordt door de applicatie die de XML verwerkt (cf. verwerking); elke node heeft een target en een waarde. Proces instructies kunnen extra informatie doorgeven waarmee de applicatie iets kan doen. Voorbeeld : <?xml:stylesheet type="text/xsl" href="vb_10_1.xsl"?>; hierin is xml:stylesheet de target en type="text/xsl" href="vb_10_1.xsl" de waarde. Root node : elke XML tree heeft één root node die de volledige boom voorstelt. CDATA sectie: een syntactische aanduiding van een stuk tekst waarvan je niet wil dat het door de applicatie als XML wordt aanzien. Dit stuk tekst wordt bijgevolg niet geparset en de formattering ervan blijft bewaard (de witruimtes, carriage returns en tabs blijven dus zoals ze ingetypt waren). Voorbeeld van een CDATA sectie: <par> Een XML voorbeeldje: <![CDATA[ <song> 89
]]> </par> <titel>ticket to ride</titel> <auteur>lennon & McCartney</auteur> </song> 9.2.2 Namespaces Door gebruik te maken van namespaces vermijdt men name clashes, situaties waarin dezelfde tagnaam gebruikt wordt in verschillende contexten. Een namespace kan bijvoorbeeld identificeren of een adres een klassiek postadres is, of een e-mail adres, of een IP adres. Tag-namen binnen een namespace moeten uniek zijn. In een XML document worden namespaces gedeclareerd met behulp van het xmlns attribuut. <bib xmlns:mybib="http://www.myserver.net/" >... </bib> Naar deze namespace kan gerefereerd worden door gebruik te maken van het prefix mybib. Daardoor refereeert mybib:author naar de AUTHOR in de namespace. De URI http://www.myserver.net/ identificeert de namespace. Deze URI is alleen maar een identifier en hoeft dus niet naar iets te verwijzen. Wanneer de prefix definitie :mybib weggelaten wordt, wordt de namespace geidentificeerd door http://www.myserver.net/ de default namespace voor het document. Namespaces kunnen in elk element gedefinieerd worden. Hun bereik is het element waarin ze gedefinieerd zijn, en alle descendants. Om verwarring te vermijden kunnen best alle namespaces in het root element gedefinieerd worden met unieke prefixes. 9.2.3 Parsing Vooraleer ermee te kunnen werken, moet een XML-document geparset worden. Een XML parser vertrekt van het tekstuele XML-document en construeert de bijhorende tree voorstelling. Bij het parsen worden twee eigenschappen van het document gecheckt (het tweede is optioneel): welgevormdheid (wellformedness): dit begrip geeft aan of een XML-document syntactisch correct is. Fouten kunnen bijvoorbeeld zijn: een starttag zonder eindtag of geen aanhalingstekens rond de waarde van een attribuut. Het feit dat twee bestanden welgevormd zijn, wil nog niet zeggen dat ze perfect uitwisselbaar zijn. Misschien gebruiken beide bestanden wel verschillende tags om hetzelfde te zeggen? validiteit (validity): dit wil zeggen dat de structuur van de elementen en attributen aan bepaalde voorwaarden moeten voldoen. De volgorde van de elementen en hun samenstelling in sub-elementen moet overeenstemmen met een vooraf bepaalde definitie (een informatiemodel). Deze definitie kan in een DTD of schema staan. 9.3 Document Type Definition Validiteit checken kan via een DTD (Document Type Definition): geeft aan welke elementen en attributen in welke volgorde mogen voorkomen in het XML-document. Een DTD kan intern (in het XML-bestand zelf) of extern (in een apart bestand) gedefinieerd zijn. In beide gevallen gaat de parser checken of de inhoud van het XML-document overeenstemt met wat in de DTD is aangegeven. Een DTD onderscheidt: Entiteiten: een bepaald stuk tekst een naam geven om daarna enkel de naam te gebruiken in plaats van de hele tekst (vergelijk met macro s in een programmeertaal of tekstverwerker). Elementen: welke elementen zijn toegelaten en uit welke sub-elementen bestaan ze? Maw.: hoe moet de boom eruit zien? Syntax: <!ELEMENT naam (inhoud)>. De inhoud geeft een opsomming van de sub-elementen of het type van de data. Voor een leeg element, gebruik EMPTY, zonder haakjes. 90
, elementen moeten beide voorkomen één van de elementen moet voorkomen Syntax:? het element is optioneel (komt 0 of 1 keer voor) * het element komt 0 of meer keer voor + het element komt 1 of meer keer voor Attributen: welke attributen mag een bepaald element hebben? <!ATTLIST elementnaam Syntax: attnaam type standaardwaarde attnaam type standaardwaarde > Types: CDATA (a e i.) ID IDREF Entity character data waarde van een vooraf bepaalde keuzelijst een unieke identificator verwijzing naar een ID van een ander element een entiteit #REQUIRED attribuut moet bij element vermeld worden #IMPLIED attribuut is niet verplicht Standaardwaarden: #FIXED waarde van het attribuut ligt vast Enumerated waarde die als standaardwaarde wordt gebruikt #PCDATA: Parsed Character Data: data die door de parser wordt gecheckt op voorkomens van speciale karakters en elementen. Voorbeelden: <!ELEMENT verjaren (persoon*)> <!ELEMENT persoon (naam,gebdat?)> <!ATTLIST persoon sis ID #REQUIRED> <!ELEMENT naam (#PCDATA)> <!ELEMENT gebdat (dag,maand,jaar?)> <!ELEMENT dag (#PCDATA)> <!ELEMENT maand (#PCDATA)> <!ELEMENT jaar (#PCDATA)> <!ELEMENT verjaren (vriend*)> <!ELEMENT vriend (sis,naam,geboortedatum?)> <!ELEMENT sis (#PCDATA)> <!ELEMENT naam (#PCDATA)> <!ELEMENT geboortedatum (#PCDATA)> <?xml version="1.0"?> <verjaren> <persoon sis="12345"> <naam> Willy Asselman </naam> <gebdat> <dag>29</dag> <maand>2</maand> <jaar>1948</jaar> </gebdat> </persoon> </verjaren> <?xml version="1.0"?> <verjaren> <vriend> <sis>12345</sis> <naam> Willy Asselman </naam> <geboortedatum> 29/2/1948 </geboortedatum> </vriend> </verjaren> Merk op dat namespaces en DTDs niet goed samenwerken. In een DTD is de element definitie <!ELEMENT mybib:bib...> gewoon de definitie van het element mybib:bib. Het betekent niet dat het element BIB gedefinieerd is binnen de namespace waarop het prefix mybib gemapt is. Het geeft zelfs niet aan dat de dubbelpunt een prefix van een lokale naam scheidt. Afhankelijk van de toepassing moet dus het document valid zijn ten opzichte van een DTD en conform met de XML namespaces aanbeveling. 91
Een DTD kan op twee manier aangeroepen worden vanuit een XML bestand: Ingebed in het XML document zelf: in het XML document schrijf je de volledige DTD binnen de rechte haakjes van <!DOCTYPE rootelement [...]>. De naam van het root element van je XML moet dan wel overeenkomen met de naam die je na DOCTYPE hebt geschreven. Via een verwijzing naar een extern bestand: in het XML document verwijs je naar het bestand waarin de DTD staat. Dat bestand is géén XML bestand en bevat dus ook niet de regel <?xml version="1.0"?>. De verwijzing gebeurt als volgt: <!DOCTYPE rootelement PUBLIC "URI" "dtdbestandsnaam.dtd">. Meestal is het beter om een DTD in een apart bestand te stoppen, omdat je dan dezelfde DTD kan aanroepen vanuit verschillende XML bestanden. Als je een XML bestand, met daarin een verwijzing naar een DTD, opent in Internet Explorer, zal IE enkel de syntax checken van beide bestanden. Er gebeurt dus enkel een check op de welgevormdheid en niét op de validiteit. Als je de validiteit van de XML wil nagaan, heb je een parser nodig. Voorbeeld XML document en bijhorende DTD bestand: Het XML-bestand: <?xml version="1.0"?> <?xml-stylesheet type="text/css" href="bkd.css"?> <!DOCTYPE boekenkast SYSTEM "bkd.dtd"> <boekenkast> <boven> &hoofding; </boven> <boek> <isbn>90 395 1586 7</isbn> <titel>basiscursus XML</titel> <auteur>ingrid Tan en Harry Heijkoop</auteur> <prijs kleur="groen">16</prijs> </boek> <boek> <isbn>0 13 046346 9</isbn> <titel>core PHP programming</titel> <auteur>leon Atkinson</auteur> <prijs kleur="geel">24</prijs> </boek> <boek> <isbn>0 201 56889 6</isbn> <titel>a guide to LaTeX</titel> <auteur>helmut Kopka en Patrick Daly</auteur> <prijs kleur="rood">33</prijs> </boek> <boek> <isbn>0 596 00420 6</isbn> <titel>learning XML</titel> <auteur>erik T. Ray</auteur> <prijs kleur="groen">18</prijs> </boek> </boekenkast> De SYSTEM parameter in het DOCTYPE statement geeft aan waar het DTD bestand op ht systeem kan teruggevonden worden. Bijhorend DTD-bestand: 92
<!-- DTD voor boekenkast --> <!ELEMENT boekenkast (boven,boek+)> <!ENTITY hoofding "Mijn boekenkast"> <!ELEMENT boven (#PCDATA)> <!ELEMENT boek (isbn, titel, auteur, prijs)> <!ELEMENT isbn (#PCDATA)> <!ELEMENT titel (#PCDATA)> <!ELEMENT auteur (#PCDATA)> <!ELEMENT prijs (#PCDATA)> <!ATTLIST prijs kleur CDATA #IMPLIED> Andere bekende standaarden voor de modellering van XML zijn XML Schema, Schematron en RelaxNG. Zoals een XML-bestand zelf, kan ook een informatiemodel (DTD, Schema,...) als een boom worden weergegeven. 9.4 Verwerking Een XML-bestand moet je bekijken als een bronbestand waarmee je dingen kan doen, bijvoorbeeld gegevens bij elkaar voegen, sorteren en op een bepaalde manier presenteren. Vooraleer je iets kan doen met XML, moet je het uiteraard eerst parsen (zie vorige sectie). Parsen is nochtans maar de eerste stap. Daarna moet je je XML-gegevens op één of andere manier verwerken (of processen). Je kan ze bijvoorbeeld omzetten naar een ander formaat (zoals RTF of PDF), of ze presenteren op één of andere manier. <?xml version="1.0"?> <verjaren> <persoon sis="12345"> <naam> Willy Asselman </naam> <gebdat> <dag>29</dag> <maand>2</maand> <jaar>1948</jaar> </gebdat> </persoon> <persoon sis="24689"> <naam> Guido Folens </naam> <gebdat> <dag>9</dag> <maand>5</maand> <jaar>1968</jaar> </gebdat> </persoon> </verjaren> Verwerking van een XML-document kan op twee manieren: P ar s i n g V er w er k i n g Verjaardagen: Willy Asselman: 29.02 Guido Folens : 9.05 Boom-gebaseerd: het gehele document wordt als een boomstructuur in het geheugen geladen. Alle informatie in het document is dan toegankelijk via de knopen van de boom. Je kan bijvoorbeeld alle nodes (knopen) van de boom doorlopen en afdrukken in een tabel. Voorbeeld: DOM. 93
Event-gebaseerd: het document wordt ingelezen als een stroom van data en (quasi-) onmiddellijk verwerkt. De parser reageert daarbij op bepaalde events in de data (het voorkomen van bepaalde tags). Voorbeeld: SAX. Merk op. Men spreekt vaak van DOM- of SAX-parsers, maar deze term dekt eigenlijk niet volledig de lading. Vooraleer men XML kan verwerken, moet men het eerst parsen op de boom- of eventgebaseerde manier. Maar een zogenaamde DOM-parser doet veel meer dan dat: hij doet ook de verwerking (waar het uiteindelijk om draait). 9.4.1 DOM Document Object Model: programmeertaal- en platformonafhankelijke definitie van standaard functionaliteiten voor het navigeren door en manipuleren van XML-documenten op een boomgebaseerde manier. Een DOM parser leest het volledige XML-document in en bouwt er een boomstructuur van in het geheugen. Alle items van het document (elementen, inhoud van elementen, attributen, process instructies,...) worden omgezet naar een node van de boom. Daarna kan je elke node apart gaan bewerken, navigeren door de boom, enz. Voordelen: Recht-voor-de-raap: DOM geeft mooi het systeem van parsen aan: eerst checken op welgevormdheid (en eventueel validiteit) bij het inlezen van het document en opbouwen van de boom, dan verwerking via de afzonderlijke nodes. Random access tot alle elementen, attributen, enz. Nadelen: Moeilijk voor zeer grote documenten. Kan traag zijn. Eén fout tegen welgevormdheid of validiteit zorgt ervoor dat je niets kan doen met je document, ook niet als de informatie die je nodig hebt in het begin staat en de fout helemaal op het einde. <?xml version= 1.0?> <verjaren> <persoon sis= 12345 > <naam> Willy Asselman </naam> <gebdat> <dag>29</dag> <maand>2</maand> <jaar>1948</jaar> </gebdat> </persoon> <persoon>... </persoon> </verjaren> P ar s i n g naam verjaren persoon gebdat sis dag maand jaar Verwerking Verjaardagen: Willy Asselman: 29.02 Guido Folens : 9.05 9.4.2 SAX Simple API for XML (API: Application Programming Interface): beschrijving van interface om XML op een event-gebaseerde manier te verwerken. Een SAX-parser parset en verwerkt de gegevens onmiddellijk als ze binnenkomen. Tags of proces instructies worden beschouwd als events die optreden wanneer ze in de datastroom voorkomen. De parser reageert vervolgens op die events op een vooraf bepaalde manier. 94
Voordelen: <?xml version= 1.0?> <verjaren> <persoon sis= 1 > <naam> Willy Asselman </naam> <gebdat> <dag>29</dag> <maand>2</maand> <jaar>1948</jaar> </gebdat> </persoon> <persoon>... </persoon> </verjaren> <verjaren><persoon sis= 1 ><naam>willy Verjaardagen: 1. Willy Asselman: 29.02 2. Guido Folens : 9.05 Immens grote XML-bestanden kunnen efficiënt verwerkt worden omdat de parser niet eerst alle gegevens moet inlezen alvorens er iets mee te doen. Als je maar een klein deel van de gegevens nodig hebt uit het XML-bestand, is het nodeloos omslachtig eerst alles in een boomstructuur te gieten en dan slechts een paar blaadjes van de boom te gebruiken. Parsen en verwerken gebeurt tegelijk, waardoor het mogelijk is om een deel van de informatie te verwerken zelfs als er op het einde een fout optreedt. Snel. Nadelen: Geen random access tot het document. Geen informatie over de toekomst: een SAX parser verwerkt iets naargelang het binnenkomt en weet niet wat er in de toekomst gaat komen. Met DOM is, eens de parsing gebeurd is, de volledige boom toegankelijk. 9.5 XML presentatie Pure XML tonen in de webbrowser: de recentste versies van Internet Explorer, Netscape, Opera en Amaya ondersteunen XML, weliswaar elk in verschillende mate. Een webbrowser weet perfect hoe hij een HTML document moet tonen: de opmaak wordt aangegeven door de HTML tags. XML daarentegen maakt abstractie van de opmaak van een document. Als je een XML-document wil tonen in een webbrowser, heb je echter wel opmaak nodig. Dit kan op verschillende manieren: Style Sheets: dit betekent dat je XML document wordt behouden zoals het is, maar er wordt een document aan gekoppeld waarin de opmaak wordt aangegeven. Aan het XML-document koppel je dus een ander document waarin staat hoe je de verschillende elementen en attributen wil tonen. Het belangrijkste voorbeeld hiervan is CSS (Cascading Style Sheet), waarbij XML-inhoud aan opmaak wordt gekoppeld. Een CSS kan ofwel in het XML-document zelf staan, ofwel in een apart bestand. Het maakt de vertaling tussen XML-items en hun opmaak. Transformatie: de XML wordt op één of andere manier getransformeerd naar een formaat dat de webbrowser begrijpt en waarin opmaak wel gedefinieerd is (normaal zal dit HTML zijn). Je zal dus een programma moeten schrijven dat je XML gegevens op een welbepaalde manier omzet naar HTML. Diverse programmeer- en scripting-talen, zoals Javascript, VB- Script, Perl, Java, Omnimark of C kunnen hiervoor gebruikt worden. 95
Via XML browsers: een andere manier om XML te tonen is om zelf een programma te maken dat XML kan verwerken. Een uitgever van boeken kan bijvoorbeeld een zogenaamde e-book reader ontwikkelen waarin je een boek kan lezen rechtstreeks vanuit XML-formaat. Een voorbeeld hiervan is de DocArch XML Viewer: een applicatie die visueel gehandicapten toelaat om elektronische boeken in XML formaat te lezen op het scherm (http://www.kuleuven.be/esyb/) 9.5.1 CSS Een CSS-bestand om de opmaak van de gegevens uit het XML-bestand (met root boekenkast) te realiseren: boven titel isbn auteur prijs { font-family: "Times New Roman"; font-size: 22pt; color: yellow; text-align: center; display: block; } { font-family: Courier; font-size: 18pt; color: red; margin-left: 20pt; display: block; } { font-family: Courier; font-size: 12pt; color: green; margin-top: 40pt; margin-left: 40pt; display: block; } { font-family: monospace; font-size: 14pt; color: blue; margin-left: 30pt; display: block; } { font-family: monospace; font-size: 30pt; color: black; background-color: #ccc; position: relative; top: -40px; left: 350px; margin-left: 30pt; margin-bottom: 1em; display: inline; } 9.5.2 XSLT en XSL:FO XSL (extensible Stylesheet Language is een taal waarmee je XML-documenten kan opmaken. XSL echter is meer dan een pure stylesheet taal, zoals CSS. Het kan XML-documenten niet alleen opmaken zoals style sheets dat typisch doen, maar ook formatteren. Met XSL kan je de gegevens weergeven naargelang hun inhoud, bijvoorbeeld alle negatieve getallen in het rood. Dit gaat verder dan enkel het tonen van bepaalde elementen op een bepaalde manier. XSL bestaat uit twee standaarden: XSL:FO: XSL Formatting Objects: hiermee kan je XML documenten opmaken. XSL:FO is veel geavanceerder in opmaak-mogelijkheden dan CSS. XSLT: XSL Transformations: hiermee kan je XML transformeren naar andere formaten. Je kan XML gegevens omzetten naar HTML, ze filteren en sorteren. In XSLT kan je typische programmeertaal constructies gebruiken zoals if-then-else, zodat je een formattering kan toepassen op basis van de inhoud van de gegevens. XSLT werkt met templates: stukken HTML met daartussen XSLT instructies. Via deze instructies kan je informatie uit een XML document halen en in de HTML template plakken. De opmaak definieer je dus in je XSL bestand, terwijl de inhoud uit het XML bestand komt. 9.5.3 Client of server Beide manieren van verwerking (stylesheets en transformaties) kunnen zowel aan client- als serverkant worden toegepast. Omdat de verwerking van gegevens (sorteren, filteren, opmaken) voornamelijk aan de client-kant gebeurt, kan de server ontlast worden van deze taken. De server stuurt brokken XML-informatie naar de client, die hiermee vervolgens dingen doet die de gebruiker wil. Als de gebruiker de informatie op een andere manier wil zien (bijvoorbeeld anders sorteren), is het niet nodig om terug naar de server te gaan. Voorbeelden van XSL aan Client- en Server-kant: Client kant: de server stuurt een XML- en XSL-bestand naar de client, die beide omzet naar HTML. 96
SERVER CLIENT XML HTML XSL Server kant: een script op de server verwerkt een XML-en een XSL-bestand tot één HTMLbestand en stuurt dit door naar de client. SERVER CLIENT XML XSL HTML HTML 9.6 Toepassingen van XML XML kent vele toepassingsgebieden, aangezien het een zeer breed inzetbare technologie is. Binnen de XML-wereld zijn er traditioneel twee onderverdelingen: de data-wereld en de documentwereld. Bij de eerste invalshoek wordt XML gebruikt om gegevens op te slaan of uit te wisselen die vroeger uitsluitend in databanken terug te vinden waren, zoals financiële gegevens, klantenrecords en stockinformatie. Dit zijn typisch gegevens die zeer sterk kunnen gestructureerd worden en die in kleine stukjes kunnen worden opgekapt. De document-wereld gebruikt XML om tekst-georiënteerde informatie op te slaan of uit te wisselen, zoals boeken of krantenartikels. Deze informatie is ook goed te structureren, maar zal doorgaans in veel grotere stukken verdeeld worden (hoofdstukken, paragrafen, alinea s,...). Het volgende schema geeft een aantal belangrijke toepassingsgebieden van XML aan: XML maken XML modellering DTD - Schema parsing XML editors transformatie Content management Versiebeheer Knowledge management Data stockeren Zoeken Navigeren Filteren Adressering: XPATH Linking: XLINK Meta-data / Ontologiën: RDF Topix Maps: XTM Semantic Web Uitwisselen e-commerce web-services security applicatie-integratie SOAP Data stockeren XML en databanken XML Query Publiceren transformatie: XSLT Web: XHTML, XFORMS WAP: WML Print: XSL:FO Multimedia: SMIL, SVG, MathML 97
Toepassingen: Data stockeren: Vooral in de document-wereld is XML een interessante methode om gegevens op te slaan. Via het single source principe worden gegevens dan vanuit één bron gebruikt voor allerlei toepassingen, zoals publiceren via het Internet, gebruik in applicaties, enz. In de data-wereld is het meestal interessanter om de gegevens te blijven opslaan in databanken, omdat XML veel minder efficiënt is in het opslaan en toegankelijk maken van dergelijke gegevens. Voorbeeld: uitgevers van kranten, die XML gebruiken als bron voor de gedrukte krant, voor hun website en voor andere toepassingen. Aangezien XML platform- en applicatie-onafhankelijk is, is het een geschikte technologie om informatie op lange termijn op te slaan. Dergelijke toepassingen zijn vooral in de e-government sector interessant, omdat de overheid de verplichting heeft om bepaalde documenten gedurende tientallen jaren te bewaren. XML databanken: steeds meer traditionele databanksystemen ondersteunen XML, bijvoorbeeld om informatie te exporteren vanuit data-tabellen naar een XML-bestand. Daarnaast zijn er de laatste jaren zgn. native XML databanken ontwikkeld; in deze systemen wordt XML opgeslagen in zijn typische boomstructuur. Gegevens uitwisselen: zowel voor documenten als voor data. Tussen applicaties: als twee applicaties met elkaar moeten communiceren, bv. via het Internet, is XML hiervoor een geschikte methode. Tussen databanken met voornaamste toepassingsgebied: e-commerce, e-business Publiceren: Tekst: bv. omzetten van XML naar (X)HTML of PDF via XSLT en XSL:FO. Multimedia: bv. 2-D vectorgrafieken op het web met SVG (Scalable Vector Graphics), synchronisatie van verschillende media met SMIL (Synchronised Multimedia Integration Language) en modelleren van wiskundige formules met MathML. Zoeken, navigeren en filteren: zoeken efficiënter maken met behulp van metadata ( Semantic Web ): bv. RDF (Resource Description Framework) geavanceerde linking-mechanismes om makkelijker te kunnen navigeren: bv. Topic Maps 9.7 Referenties Boek: Beginning XML, 3rd Edition, Wrox Press Ltd, ISBN 0-7645-7077-3, September 2004, http://www.wrox.com, uitstekend boek dat alle aspecten uit deze tutorial behandelt; is zowel een inleidend boek als een stevige reference guide. (direct: http://www.wrox.com/wileycda/wroxtitle/productcd-0764570773.html) Tutorials: op http://www.w3schools.com vind je een berg gratis tutorials over Interneten XML-gerelateerde technologieën en standaarden. XML algemeen: http://www.xml.com/pub/a/axml/axmlintro.html : de xml specificatie, doorspekt met commentaren; http://www.oasis-open.org/cover/: complete en up to date referentie-site, nieuwtjes, tools,... ivm SGML/XML en aanverwanten; http://www.w3c.org/xml/: de officiële referentie van de XML specificatie, veel links naar software, tutorials, publicaties,...; http://xml.apache.org/: initiatief voor het bevorderen van XML gebruik, door aanbieden van kwaliteits software, gratis en open source; http://msdn.microsoft.com/xml/default.asp: overzicht van de MS initiatieven rond XML, downloads, boeken, white papers,...; http://java.sun.com/xml/: vergelijkbaar initiatief voor java ontwikkelaars bij sun, interessante online tutorials. XML Schema: http://www.w3.org/xml/schema. 98
DOM: http://www.w3c.org/dom/: officiële referentie van de DOM specificatie, met links naar implementaties. SAX: http://www.megginson.com/sax/index.html: alles over SAX bij de ontwerper van de API. XSL/XSLT: http://www.w3.org/style/xsl/: officiële referentie van de XSL en XSLT specificatie, met links naar implementaties en tutorials ; http://www.xslt.com/: vergelijkbaar met xml.com maar dan specifieker voor xslt; http://www.zvon.org/o_html/group_xsl.html: uitstekende XSL referenties en tutorials. Kritische bemerkingen over XML: http://xmlsucks.org/. 99
10 XML Schema 10.1 Verantwoording Een populaire tegenhanger van DTD s is XML Schema. Schema s hebben hetzelfde doel, maar kennen meer datatypes en zijn daarom beter geschikt voor bijvoorbeeld e-commerce applicaties. Schema s volgen, in tegenstelling tot DTD s, de XML syntax en kunnen daarom verwerkt worden met een gewone XML-parser. XML Schema is een XML-gebaseerd alternatief voor DTDs. Een XML Schema beschrijft de structuur van een XML document. De XML Schema taal wordt ook XML Schema Definition (XSD) genoemd. Het doel van een XML Schema is het definiëren van de toegelaten bouwstenen van een XML document, net zoals een DTD. XML Schema is een W3C Standard en is een W3C Recommendation sinds 2 mei 2001. Een XML Schema is een document dat een aantal dingen definiëert: welke elementen in een document mogen voorkomen welke attributen in een document mogen voorkomen welke elementen subelementen (children) zijn hoe vaak elementen mogen of moeten voorkomen de hoeveelheid subelementen en de volgorde of een element leeg is of tekst bevat de mogelijke waarden van elementen en attributen standaardwaarden voor elementen en attributen Er is een zekere analogie met klassen en objecten in een objectgerichte programmeertaal: een schema is gelijkaardig aan een klasse definitie en een XML dokument dat voldoet aan de structuur gedefinieerd in een schema, is gelijkaardig aan een object van de schema-klasse. Zo n XML dokument kan beschouwd worden als een instantie van het schema. Een schema specificeert ten eerste de structuur van een XML document: welke elementen en attributen kunnen voorkomen, waar en hoe dikwijls. Ten tweede wordt het data type van elk element en attribuut aangegeven (en dit veel gedetailleerder dan in een DTD). XML Schemas zijn de opvolgers van DTDs en zullen in de meeste webtoepassingen de vervanger zijn voor DTDs. XML Schemas zijn rijker en krachtiger dan DTDs XML Schemas worden geschreven in XML: geen nieuwe taal aanleren; gebruikmaken van een XML editor om Schema bestanden te editeren; gebruikmaken van een XML parser om Schema bestanden te parsen; met behulp van XML DOM kan het Schema bewerkt worden; Schema transformatie met XSLT. XML Schemas zijn uitbreidbaar met toekomstige toevoegingen omdat ze in XML geschreven zijn: hergebruik van een Schema in een ander Schema; creatie van eigen datatypes afgeleid van de standard types; in eenzelfde document refereren naar verschillende schemas XML Schemas ondersteunen data types: gemakkelijker om de toegelaten dokument inhoud te beschrijven gemakkelijker om de correctheid van de data te valideren gemakkelijker om te werken met data uit een databanl gemakkelijker om beperkingen op data (facets) te definiëren 100
gemakkelijker om datapatronen (formats) te definiëren gemakkelijker om data om te vormen tussen verschillende data types. XML Schemas ondersteunen naamruimtes. XML Schemas beveiligen data communicatie. Wanneer data van en zender naar een ontvanger gestuurd wordt, is het essentieel dat beide partijen dezelfde verwachtingen hebben omtrent de inhoud. Met XML Schemas, kan de zender de data beschrijven zoals de ontvanger ze moet begrijpen. Een datum als 03-11-2004 zal in sommige landen begrepen worden als 3 november 2004 en in andere lanen als 11 maart 2004. Een XML element met het data type date: <da te type= da te >2004 03 11</date> verzekert een gelijkaardig begrijpen van de inhoud langs beide kanten omdat het XML data type date het format YYYY-MM-DD vereist. Zelfs als dokumenten well-formed zijn, kunnen ze nog fouten bevatten en deze fouten kunnen ernstige gevolgen hebben. Bijvoorbeeld een bestelling van 5 gros laserprinters in plaats van 5 laserprinters. Met behulp van XML Schemas kunnen de meeste van deze fouten gevonden worden door de validerende software. 10.2 Schema definitie Een schema wordt geschreven met behulp van een aantal namen (schema, element, sequence, string) uit een namespace dat eigenlijk een schema van schema s is. De naam van deze namespace is http://www.w3.org/2001/xmlschema. Het root element is schema. Hierin wordt de namespace voor het schema van schema s gespecificeerd, met gewoonlijk ook een prefix. Het schema zelf definieert een namespace, de naam ervan wordt aangegeven in targetnamespace attribuut. Elk top-level element dat in het schema voorkomt wordt in deze target namespace opgenomen. Elementen die genest zitten in top-level elementen kunnen ook in de namespace opgenomen worden door het attribuut elementformdefault gelijk te stellen aan qualified. De default namespace voor de namen zonder prefix in het schema wordt aangegeven met een andere xmlns specificatie maar dan zonder prefix. Voorbeeld: <xsd : schema xmlns : xsd = http : / /www. w3. org /2001/XMLSchema targetnamespace = http : / /www. denayer. be/ planschema xmlns = http : / /www. denayer. be/ planschema elementformdefault = q u a l i f i e d > Het is ook mogelijk de XMLSchema namen de default te maken, zodat daarbij geen prefix nodig is. In dat geval moeten wel de namen in de target namespace geprefixed worden: <schema xmlns = http : / /www. w3. org /2001/XMLSchema targetnamespace = http : / /www. denayer. be/ planschema xmlns : plan = http : / /www. denayer. be/ planschema elementformdefault = q u a l i f i e d > De naam schema moet nu geen prefix hebben omdat zijn namespace de default is. Bij het maken van een instantie van een schema moeten de gebruikte namespaces aangegeven worden als attributen in de tag van het root element: <plannen xmlns = http : / /www. denayer. be/planschema xmlns : x s i = http : / /www. w3. org /2001/XMLSchema instance x s i : schemalocation = http : / /www. denayer. be/planschema plan. xsd > De eerste namespace is de default. In de tweede namespace worden een aantal items gedefinieerd die in een instanctie van een schema gebruikt worden, bijvoorbeeld het attribuut schemalocation 101
dat aangeeft waar het schema van deze instantie kan gevonden worden. Het schemalocation attribuut heeft twee waarden: de eerste waarde geeft de naamruimte die gebruikt wordt, de tweede is de locatie van het XML schema dat gebruikt wordt voor deze naamruimte. 10.3 Terminologie Eerst wat terminologie. Een XML-document is opgebouwd uit elementen. Elementen kunnen drie soorten van dingen bevatten: Andere elementen; Tekst; Attributen. Dit is best duidelijk te maken met een voorbeeld: < l i e d j e genre= s c h l a g e r > <t i t e l >Je hebt me 1000 maal belogen </ t i t e l > <a r t i e s t > <artiestennaam>laura Lynn</artiestennaam> <echte naam>s a b r i n a Tack</echte naam> </ a r t i e s t > </ l i e d j e > Hier is liedje een element dat op zijn beurt weer twee andere elementen (titel en artiest bevat); daarnaast heeft liedje ook een attribuut genre. Het element titel bevat de tekst Je hebt me 1000 maal belogen, terwijl het element artiest zelf weer twee andere elementen bevat. Alles wat er tussen het begin- en eindtag van een element staat, wordt zijn inhoud genoemd. Elk element in een XML document heeft een type. We maken onderscheid tussen twee verschillende soorten van types: een element met een enkelvoudig type ( simple type ) kan enkel maar tekst bevatten en mag zelf geen attributen hebben; een element met een samengesteld type ( complex type ) bevat op zijn beurt weer andere elementen en/of heeft attributen. In het voorbeeld hierboven hebben titel, artiestennaam en echte-naam een enkelvoudig type, terwijl liedje en artiest samengestelde types hebben. 10.4 Enkelvoudige types Een element met een enkelvoudig type heeft geen attributen en bevat enkel tekst. In een XML schema kunnen echter verschillende soorten van tekst onderscheiden worden, afhankelijk van wat de tekst juist voorstelt. Dit past binnen het opzet van XML: we willen dat een XML document zo precies mogelijk de betekenis van onze gegevens weergeeft, zodat we ze kunnen uitwisselen met andere partijen én er zeker van zijn dat die de gegevens op dezelfde manier zullen interpreteren. XML schema biedt een heel aantal ingebouwde enkelvoudige data-types aan. De belangrijkste zijn: xsd:string xsd:decimal (een kommagetal) xsd:integer xsd:boolean xsd:date xsd:time Om aan te geven dat een element een bepaald type heeft, gebruikt men de uitdrukking: 102
<xsd : element name=element type=type /> Het volgende stukje XML <naam>sabrina Tack</naam> <schoenmaat >38</schoenmaat> <geboortedatum >1976 06 17</geboortedatum> volgt bijvoorbeeld deze declaraties: <xsd : element name= naam type= xsd : s t r i n g /> <xsd : element name= schoenmaat type= xsd : i n t e g e r /> <xsd : element name= geboortedatum type= xsd : da te /> Naast deze ingebouwde enkelvoudige types kunnen we ook zelf nieuwe enkelvoudige types aanmaken, door middel van een simpletype declaratie. Dit gebeurt door het opleggen van beperkingen aan bepaalde facetten van de ingebouwde types. Bijvoorbeeld: <xsd : simpletype name= kortwoord > <xsd : r e s t r i c t i o n base= xsd : s t r i n g > <xsd : maxlength value= 5 /> </xsd : r e s t r i c t i o n > </xsd : simpletype> Hier definiëren we een nieuw enkelvoudig type kortwoord door aan het ingebouwde basistype xsd:string de beperking op te leggen dat zijn facet maxlength de waarde 5 moet hebben. Een kortwoord zal met andere woorden een string van maximum 5 lettertekens zijn. Eenmaal we een dergelijke type gedefiniëerd hebben, kunnen we het op dezelfde manier gebruiken als de ingebouwde types. Bijvoorbeeld: <xsd : element name= a f k o r t i n g type= kortwoord /> Als we van plan zijn om een enkelvoudig type slechts één keer te gebruiken, is het een beetje omslachtig om er een naam voor te moeten verzinnen. In dat geval kunnen we ervoor opteren om een naamloos type op te nemen in de element-declaratie, zoals: <xsd : element name= a f k o r t i n g > <xsd : simpletype> <xsd : r e s t r i c t i o n base= xsd : s t r i n g > <xsd : maxlength value= 5 /> </xsd : r e s t r i c t i o n > </xsd : simpletype> </xsd : element > Dit drukt precies hetzelfde uit als de typering hierboven, maar we hoeven nu de naam kortwoord niet te verzinnen. Bij het definiëren van nieuwe enkelvoudige types kunnen we onder andere volgende facetten gebruiken: Voor strings: <xsd:length value=n> De string heeft lengte n <xsd:minlength value=n> De string heeft lengte groter dan of gelijk aan n <xsd:maxlength value=n> De string heeft lengte kleiner dan of gelijk aan n <xsd:pattern value=re> De string voldoet aan de reguliere expressie re Voor getallen: <xsd:mininclusive value=n> Het getal is groter dan of gelijk aan n <xsd:minexclusive value=n> Het getal is strikt groter dan n 103
<xsd:maxinclusive value=n> Het getal is kleiner dan of gelijk aan n <xsd:maxexclusive value=n> Het getal is strikt kleiner dan n <xsd:totaldigits value=n> Het getal (uitgeschreven als decimaal komma-getal) bestaat uit maximaal n cijfers <xsd:fractiondigits value=n> Het getal heeft maximaal n cijfers na de komma Daarnaast is er ook nog het facet enumeration om types te definiëren als opsomming van een aantal mogelijke waarden, zoals: <xs : simpletype name= haarkleur > <xs : r e s t r i c t i o n base= xs : s t r i n g > <xs : enumeration value= blond /> <xs : enumeration value= bruin /> <xs : enumeration value= zwart /> <xs : enumeration value= rood /> </xs : r e s t r i c t i o n > </xs : simpletype> Een element met een enkelvoudig type kan een default waarde hebben, die het zal aannemen indien er geen andere waarde wordt opgegeven: <xsd : element name= haarkleur type= xsd : s t r i n g d e f a u l t = blond /> Dit betekent dat als in een document het element <haarkleur /> verschijnt, dit exact hetzelfde zal betekenen als wanneer er stond: <haarkleur >blond</haarkleur > (Maar het betekent dus niet dat er automatisch een <haarkleur>-element zal worden toegevoegd als er geen staat.) Een element met enkelvoudig type kan ook een vaste waarde hebben. In dit geval is deze waarde de enige die toegelaten is. Als we bijvoorbeeld schrijven: <xsd : element name= s t a t u s type= xsd : s t r i n g f i x e d = aanwezig /> Dan mag enkel <status >aanwezig </status > in het XML document verschijnen. Het is niet toegelaten om voor een element zowel een default waarde als een vaste waarde op te geven. 10.5 Samengestelde types Een element dat attributen heeft of andere elementen bevat, moet een samengesteld type hebben. Samengestelde types kunnen verschillende soorten van eisen opleggen aan de inhoud van een element: Een samengesteld type kan een lege inhoud opleggen: dit wilt zeggen dat het element geen inhoud heeft (maar natuurlijk wel nog attributen kan hebben); Een samengesteld type kan een enkelvoudige inhoud opleggen: dit wilt zeggen dat de inhoud van het element van een enkelvoudig type moet zijn; Een samengesteld type kan een samengestelde inhoud opleggen: dit wilt zeggen dat de inhoud van het element moet bestaan uit andere elementen; Een samengestelde type kan een gemengde inhoud opleggen: dit wilt zeggen dat het element zowel inhoud van een enkelvoudig type als andere elementen bevat. We bekijken nu elk van deze mogelijkheden in meer detail. 104
10.5.1 Lege inhoud We kunnen als volgt een element van een samengesteld type declareren dat wel een attribuut heeft maar geen inhoud: <xs : element name= scho en > <xs : complextype> <xs : a t t r i b u t e name= maat type= i n t e g e r > </xs : complextype> </xs : element > Dit beschrijft dan XML zoals: <scho en maat= 42 /> 10.5.2 Enkelvoudige inhoud Als een element attributen heeft, dan moet het sowieso een samengesteld type hebben, zelfs als zijn inhoud gewoon uit een enkelvoudig type bestaat. We kunnen het type van een dergelijk element als volgt declareren: <xsd : element name= l e n g t e > <xsd : complextype> <xsd : simpleco ntent > <xsd : e x t e n s i o n base= xsd : decimal > <xsd : a t t r i b u t e name= eenheid type= xsd : s t r i n g /> </xsd : extension> </xsd : simplecontent> </xsd : complextype> </xsd : element > Hier breiden we het enkelvoudige type decimal uit met een attribuut eenheid. <l e n g t e eenheid= cm > 184 </lengte > Moesten we dit attribuut er niet bij hebben gewild, dan hadden we hetzelfde natuurlijk korter kunnen schrijven als: <xsd : element name= l e n g t e type= xsd : decimal > 10.5.3 Samengestelde inhoud Als een element een samengesteld type heeft met een samengestelde inhoud, dan betekent dit dat de inhoud van dit element bestaat uit andere elementen. Er zijn drie verschillende manieren om te specifiëren welke elementen dit mogen zijn: xsd:choose: slechte één van de opgesomde elementen moet (en mag) voorkomen; xsd:all: al de opgesomde elementen moeten voorkomen; xsd:sequence: de opgesomde elementen moeten allemaal voorkomen én in een vaste volgorde staam. Bijvoorbeeld: <xsd : complextype name= instrumentsoorten > <xsd : choose> <xsd : element name= houtblazer type= s t r i n g /> <xsd : element name= k o p e r b l a z e r type= s t r i n g /> <xsd : element name= slagwerk type= s t r i n g /> </xsd : choose> </xsd : complextype> 105
Dit definiëert een samengesteld type instrumenttype op een zodanige manier dat elk element van dit type ofwel een element <houtblazer> ofwel een element <koperblazer> ofwel een element <slagwerk> moet bevatten. Als we bijvoorbeeld een element instrument van type instrumenttype definiëren <xsd : element name= instrument type= instrumentsoorten /> dan kunnen we dit schrijven: <instr ument > <houtblazer>hobo</houtblazer> </instrument > maar bijvoorbeeld dit mag niet: <instr ument > <houtblazer>hobo</houtblazer> <koperblazer >tromper </koperblazer > </instrument > Dit schema, daarentegen: <xsd : complextype name= sla g wer ktype > <xsd : all> <xsd : element name= naam type= s t r i n g /> <xsd : element name= s o o r t type= s t r i n g /> <xsd : element name= m a t e r i a a l type= s t r i n g /> </xsd : all> </xsd : complextype> <xsd : element name= slagwerk type= slagwerktype /> laat toe om dit te schrijven: <slagwerk> <soort>melodisch </soort> <materiaal>hout</soort> <naam>xylofoon </naam> </slagwerk> maar dit mag niet, omdat hier het materiaal ontbreekt: <slagwerk> <soort>melodisch </soort> <naam>vibrafoon </naam> </slagwerk> Moest er tot slot dit staan: <xsd : complextype naam= sla g wer ktype > <xsd : sequence> <xsd : element name= naam type= s t r i n g /> <xsd : element name= s o o r t type= s t r i n g /> <xsd : element name= m a t e r i a a l type= s t r i n g /> </xsd : sequence> </xsd : complextype> dan moet eerst de naam, dan de soort en tot slot het materiaal vermeld worden. Enkel dit mag dus: <slaginstrument > <naam>xylofoon </naam> 106
<soort>melodisch </soort> <materiaal>hout</soort> </slaginstrument > Net als bij enkelvoudige types is het trouwens weer zo dat we een samengesteld type niet noodzakelijk een naam moeten geven. Zoals we niet van plan zijn om het type ergens anders nog te hergebruiken, kunnen we met andere woorden net zo goed dit schrijven: <xsd : element naam= slagwerk > <xsd : complextype> definitie van het type </xsd : complextype> </xsd : element > als dit: <xsd : element naam= slagwerk type = slagwerktype /> <xsd : complextype = sla g wer ktype > definitie van het type </xsd : complextype> Bij de elementen in een sequence mag er ook worden gespecifieerd dat een element een minimum- /maximum aantal keren mag voorkomen. Bijvoorbeeld: <xsd : element name= maat > <xsd : complextype> <xsd : sequence> <xsd : element name= noot type= s t r i n g minoccurs= 0 maxoccurs= 16 /> </xsd : sequence> </xsd : complextype> </xsd : element > Dit zegt dat een maat bestaat uit 0,1,2,..., 15 of 16 noten. Dit mag dus allemaal: <maat> </maat> <maat> <noot>s o l </noot> </maat> <maat> <noot>do</noot> <noot>r e </noot> <noot>mi</noot> </maat> Ook maxoccurs = unbounded is mogelijk en dan mag het element een onbegrensd aantal keer voorkomen. Indien niet gespecifiëerd is de default van zowel maxoccurs als minoccurs gelijk aan 1, en moet het element dus exact één maal voorkomen. Een complex type mag natuurlijk op zijn beurt weer ander complexe types bevatten, zoals hier: <xsd : element name= instrument > <xsd : complextype> <xsd : choose> <xsd : element name= houtblazer type= s t r i n g /> <xsd : element name= k o p e r b l a z e r type= s t r i n g /> <xsd : element name= slagwerk > <xsd : complextype> <xsd : a l l > <xsd : element name= naam type= s t r i n g /> <xsd : element name= s o o r t type= s t r i n g /> <xsd : element name= m a t e r i a a l type= s t r i n g /> </xsd : a l l > </xsd : complextype> 107
</xsd : element > </xsd : element > en dan kan bijvoorbeeld dit geschreven worden: <instr ument > <slagwerk> <naam>xylofoon </naam> <soort>melodisch </soort> <materiaal>hout</soort> </slagwerk> </instrument > De declaratie van een samengesteld element kan ook herbruikt worden. Hiervoor moet het element wel globaal declareerd zijn (dwz. rechtstreeks als kind van xsd-schema en dus niet binnen een andere samengesteld element). Er kan dan naar verwezen worden met een ref attribuut, zoals hier: <xsd : schema xmlns : xs= http : / /www. w3. org /2001/XMLSchema > <xsd : element name= instrument > zoals hierboven </xsd : element > <xsd : element name= o r k e s t > <xsd : complextype> <xsd : sequence> <xsd : element ref= instrument maxoccurs= unbounded /> </xsd : sequence> </xsd : complextype> </xsd : element > Ook met enkelvoudige elementen kan men dit trouwens doen. 10.5.4 Gemengde inhoud Door in een complextype declaratie het attribuut mixed= true op te nemen, laat men toe dat er voor/tussen/na de elementen ook gewone tekst voorkomt. Bijvoorbeeld: <xsd : element name= a a nspreking > <xsd : complextype mixed= true > <xsd : sequence> <xsd : element name= naam /> </xsd : sequence> </xsd : complextype> </xsd : element > beschrijft de volgende XML: <aanspreking> B e s t e mevrouw <naam>tack</name>, </aanspreking> 10.6 Attributen Attributen zijn erg gelijkaardig aan elementen met enkelvoudige types. Ze moeten ook een enkelvoudig data-type hebben en kunnen ook default of vaste waardes krijgen. Bijvoorbeeld: <xsd : a t t r i b u t e name= genre type= s t r i n g d e f a u l t = s c h l a g e r /> laat toe om 108
< l i e d j e genre= hardrock > te schrijven. Als het attribuut genre niet expliciet gezet wordt, wordt het automatisch toegevoegd met zijn default waarde. Gegeven de bovenstaande declaratie is er dus geen verschil tussen <l i e d j e > en < l i e d j e genre= s c h l a g e r > (Merk op dat dit dus wel een beetje verschilt van hoe default waardes voor enkelvoudige elementen werken.) Attributen zijn normaalgezien niet verplicht. Om aan te geven dat dit wel zo is, kan use="required" gebruikt worden, zoals in: <xsd : a t t r i b u t e name= genre type= xsd : s t r i n g use= r e q u i r e d /> Zoals al gezegd, de default waarde is use="optional". (Ook dit is trouwens weer een verschilpuntje met enkelvoudige types, waar de default-waarde van minoccurs gelijk is aan 1 ipv. 0.) Ook hier is het weer mogelijk om globaal gedeclareerde attributen op verschillende plaatsen te gebruiken door middel van het ref-attribuut: <xsd : schema mlns : xs= http : / /www. w3. org /2001/XMLSchema > <xsd : a t t r i b u t e name= genre type= xsd : s t r i n g /> <xsd : element name= l i e d j e > <xsd : complextype> <xsd : a t t r i b u t e ref= genre /> </xsd : complextype> </xsd : element> </xsd : schema> 10.7 Groepen Om groepen van elementen of attributen gemakkelijk te hergebruiken, kan je hen een naam geven, waarnaar je dan op een andere plaats kan verwijzen met een ref-attribuut. Voor groepen van elementen doe je dit zo: <xsd : group name= a l i a s g r o e p > <xsd : sequence > <xsd : element name= a r tiestennaam type= xsd : s t r i n g /> <xsd : element name= echte naam minoccurs= 0 type= xsd : s t r i n g /> </xsd : sequence> </xsd : group> <xsd : element name= a r t i e s t > <xsd : sequence > <xsd : group r e f = a l i a s g r o e p /> <xsd : element name= manager type= xsd : s t r i n g /> </xsd : element> </xsd : element > Het is verplicht dat er binnen de group-tag een all, choice of sequence volgt. Groepen van attributen maak en gebruik je zo: <xsd : a ttributegroup name= naamattributen > <xsd : a t t r i b u t e name= voornaam type= xsd : s t r i n g /> <xsd : a t t r i b u t e name= achternaam type= xsd : s t r i n g /> </xsd : attributegroup> <xsd : element name= persoon > 109
<xsd : complextype> <xsd : a ttributegroup r e f= naamattributen /> </xsd : complextype> </xsd : element > 10.8 Uitbreidbare schema s Als je wilt toelaten dat er in een XML document op bepaalde plaatsen elementen voorkomen waarvan je zelf de naam niet wil vastleggen, kan je hiervoor het any-tag gebruiken. Bijvoorbeeld: <xsd : element name= l i e d j e > <xsd : complextype> <xsd : sequence> <xsd : element name= t i t e l type= xsd : s t r i n g /> <xsd : any minoccurs= 0 /> </xsd : sequence> </xsd : complextype> </xsd : element > Nu mag er dan tenminste volgens dit XML schema bijvoorbeeld geschreven worden: <l i e d j e > <t i t e l > Je hebt me 1000 maal bedrogen </ t i t e l > <sabrina > tack </sabrina > </ l i e d j e > Om het document te kunnen valideren, moet er dan natuurlijk wel nog ergens (in een ander schema document) gespecifi eerd worden hoe een sabrina-tag eruit ziet. Op deze manier kan dus een uitbreidbaar schema gebouwd worden, dat met andere schema s gecombineerd kan worden. Voor attributen bestaat er een gelijkaardig anyattribute-tag. 110
11 XPath 11.1 XPath Introduction XQuery XPointer XPath XSLT XLink XPath is a syntax for defining parts of an XML document XPath is a language for finding information in an XML document. XPath is used to navigate through elements and attributes in an XML document. XPath is a major element in the W3C s XSLT standard - and XQuery and XPointer are both built on XPath expressions. So an understanding of XPath is fundamental to a lot of advanced XML usage. XPath uses path expressions to navigate in XML documents XPath uses path expressions to select nodes or node-sets in an XML document. These path expressions look very much like the expressions you see when you work with a traditional computer file system. XPath contains a library of standard functions XPath includes over 100 built-in functions. There are functions for string values, numeric values, date and time comparison, node and QName manipulation, sequence manipulation, Boolean values, and more. XPath is a major element in the W3C s XSLT standard XPath is a W3C Standard (16 November 1999) XQuery and XPointer are both built on XPath expressions. 11.2 XPath Nodes In XPath, there are seven kinds of nodes: element, attribute, text, namespace, processinginstruction, comment, and document (root) nodes. XML documents are treated as trees of nodes. The root of the tree is called the document node (or root node). Look at the following XML document: <?xml version="1.0" encoding="iso-8859-1"?> <bookstore> <book> <title lang="en">harry Potter</title> <author>j K. Rowling</author> <year>2005</year> <price>29.99</price> </book> </bookstore> Example of nodes in the XML document above: <bookstore> : document node <author>j K. Rowling</author> : element node lang="en" : attribute node Atomic values are nodes with no children or parent. Examples: J K. Rowling, "en". Items are atomic values or nodes. Relationship of Nodes : with examples based on 111
<bookstore> <book> <title>harry Potter</title> <author>j K. Rowling</author> <year>2005</year> <price>29.99</price> </book> </bookstore> Parent: each element and attribute has one parent. The book element is the parent of the title, author, year, and price Children: element nodes may have zero, one or more children. The title, author, year, and price elements are all children of the book element. Siblings : nodes that have the same parent. The title, author, year, and price elements are all siblings. Ancestors : a node s parent, parent s parent, etc. The ancestors of the title element are the book element and the bookstore element. Descendants : a node s children, children s children, etc. Descendants of the bookstore element are the book, title, author, year, and price elements. 11.3 XPath Syntax XPath uses path expressions to select nodes or node-sets in an XML document. selected by following a path or steps. We will use the following XML document in the examples below. The node is <?xml version="1.0" encoding="iso-8859-1"?> <bookstore> <book> <title lang="eng">harry Potter</title> <price>29.99</price> </book> <book> <title lang="eng">learning XML</title> <price>39.95</price> </book> </bookstore> The most useful path expressions are listed below: Examples Expression Description nodename Selects all child nodes of the node / Selects from the root node // Selects nodes in the document from the current node that match the selection no matter where they are. Selects the current node.. Selects the parent of the current node @ Selects attributes 112
Path Expression bookstore /bookstore bookstore/book //book bookstore//book //@lang Result Selects all the child nodes of the bookstore element Selects the root element bookstore Note: If the path starts with a slash ( / ) it always represents an absolute path to an element! Selects all book elements that are children of bookstore Selects all book elements no matter where they are in the document Selects all book elements that are descendant of the bookstore element, no matter where they are under the bookstore element Selects all attributes that are named lang Predicates are used to find a specific node or a node that contains a specific value. Predicates are always embedded in square brackets. Examples Path Expression /bookstore/book[0] /bookstore/book[last()] /bookstore/book[last()-1] /bookstore/book[position()<3] //title[@lang] //title[@lang= eng ] /bookstore/book[price>35.00] /bookstore/book[price>35.00]/title Result Selects the first book element that is the child of the bookstore element. Selects the last book element that is the child of the bookstore element Selects the last but one book element that is the child of the bookstore element Selects the first two book elements that are children of the bookstore element Selects all the title elements that have an attribute named lang Selects all the title elements that have an attribute named lang with a value of eng Selects all the book elements of the bookstore element that have a price element with a value greater than 35.00 Selects all the title elements of the book elements of the bookstore element that have a price element with a value greater than 35.00 Note: IE5 and later has implemented that [0] should be the first node, but according to the W3C standard it should have been [1]! The construction /bookstore/book/title[contains(text(), joske ] selects all the title elements of the book elements of the bookstore element that have the text joske within the title. XPath wildcards can be used to select unknown XML elements. Wildcard Description * Matches any element node @* Matches any attribute node node() Matches any node of any kind Examples: Path Expression Result /bookstore/* Selects all the child nodes of the bookstore element //* Selects all elements in the document //title[@*] Selects all title elements which have any attribute By using the operator in an XPath expression you can select several paths. Examples 113
Path Expression //book/title //book/price //title //price /bookstore/book/title //price Result Selects all the title AND price elements of all book elements Selects all the title AND price elements in the document Selects all the title elements of the book element of the bookstore element AND all the price elements in the document 11.4 XPath Axes An axis defines a node-set relative to the current node. AxisName Result ancestor Selects all ancestors (parent, grandparent, etc.) of the current node ancestor-or-self Selects all ancestors (parent, grandparent, etc.) of the current node and the current node itself attribute Selects all attributes of the current node child Selects all children of the current node descendant Selects all descendants (children, grandchildren, etc.) of the current node descendant-or-self Selects all descendants (children, grandchildren, etc.) of the current node and the current node itself following Selects everything in the document after the closing tag of the current node following-sibling Selects all siblings after the current node namespace Selects all namespace nodes of the current node parent Selects the parent of the current node preceding Selects everything in the document that is before the start tag of the current node preceding-sibling Selects all siblings before the current node self Selects the current node Location Path Expression A location path can be absolute or relative. An absolute location path starts with a slash ( / ) and a relative location path does not. In both cases the location path consists of one or more steps, each separated by a slash: An absolute location path: /step/step/... A relative location path: step/step/... Each step is evaluated against the nodes in the current node-set. A step consists of: an axis (defines the tree-relationship between the selected nodes and the current node) a node-test (identifies a node within an axis) zero or more predicates (to further refine the selected node-set) The syntax for a location step is: Examples axisname::nodetest[predicate] 114
Example child::book attribute::lang child::* attribute::* child::text() child::node() descendant::book ancestor::book ancestor-or-self::book child::*/child::price Result Selects all book nodes that are children of the current node Selects the lang attribute of the current node Selects all children of the current node Selects all attributes of the current node Selects all text child nodes of the current node Selects all child nodes of the current node Selects all book descendants of the current node Selects all book ancestors of the current node Selects all book ancestors of the current node - and the current as well if it is a book node Selects all price grandchildren of the current node 11.5 XPath Operators An XPath expression returns either a node-set, a string, a Boolean, or a number. Below is a list of the operators that can be used in XPath expressions: Operator Description Example Return value Computes two node-sets //book //cd Returns a node-set with all book and cd elements + Addition 6 + 4 10 - Subtraction 6 4 2 * Multiplication 6 4 24 div Division 8 div 4 2 = Equal price = 9.80 true if price is 9.80 false if price is 9.90!= Not equal price! = 9.80 true if price is 9.90 false if price is 9.80 < Less than price < 9.80 true if price is 9.00 false if price is 9.80 <= Less than or equal to price <= 9.80 true if price is 9.00 false if price is 9.90 > Greater than price > 9.80 true if price is 9.90 false if price is 9.80 >= Greater than or equal to price >= 9.80 true if price is 9.90 false if price is 9.70 or or price = 9.80 or price = 9.70 true if price is 9.80 false if price is 9.50 and and price > 9.00 and price < 9.90 true if price is 9.80 false if price is 8.50 mod Division remainder 5 mod 2 1 115
12 XQuery 12.1 Introduction The best way to explain XQuery is to say that XQuery is to XML what SQL is to database tables. XQuery is designed to query XML data - not just XML files, but anything that can appear as XML, including databases. XQuery is a language for finding and extracting elements and attributes from XML documents. Here is an example of a question that XQuery could solve: Select all CD records with a price less than $10 from the CD collection stored in the XML document called cd catalog.xml. XQuery 1.0 and XPath 2.0 share the same data model and support the same functions and operators. If you have already studied XPath you will have no problems with understanding XQuery. XQuery can be used to: Extract information to use in a Web Service Generate summary reports Transform XML data to XHTML Search Web documents for relevant information XQuery is a W3C Recommendation (January 23, 2007). XQuery is compatible with several W3C standards, such as XML, Namespaces, XSLT, XPath, and XML Schema. 12.1.1 XQuery Example Let s try to learn some basic XQuery syntax by looking at an example. We will use the following XML document books.xml in the examples below. <?xml version="1.0" encoding="iso-8859-1"?> <bookstore> <book category="cooking"> <title lang="en">everyday Italian</title> <author>giada De Laurentiis</author> <year>2005</year> <price>30.00</price> </book> <book category="children"> <title lang="en">harry Potter</title> <author>j K. Rowling</author> <year>2005</year> <price>29.99</price> </book> <book category="web"> <title lang="en">xquery Kick Start</title> <author>james McGovern</author> <author>per Bothner</author> <author>kurt Cagle</author> <author>james Linn</author> <author>vaidyanathan Nagarajan</author> <year>2003</year> <price>49.99</price> </book> <book category="web"> <title lang="en">learning XML</title> <author>erik T. Ray</author> 116
<year>2003</year> <price>39.95</price> </book> </bookstore> How to Select Nodes From books.xml? XQuery uses functions to extract data from XML documents. The doc() function is used to open the books.xml file: doc ( books. xml ) XQuery uses path expressions to navigate through elements in an XML document. The following path expression is used to select all the title elements in the books.xml file: doc ( books. xml )/ bookstore /book/ t i t l e (/bookstore selects the bookstore element, /book selects all the book elements under the bookstore element, and /title selects all the title elements under each book element) The XQuery above will extract the following: <title lang="en">everyday Italian</title> <title lang="en">harry Potter</title> <title lang="en">xquery Kick Start</title> <title lang="en">learning XML</title> XQuery uses predicates to limit the extracted data from XML documents. The following predicate is used to select all the book elements under the bookstore element that have a price element with a value that is less than 30: doc ( books. xml )/ bookstore /book [ p r i c e <30] The XQuery above will extract the following: <book category="children"> <title lang="en">harry Potter</title> <author>j K. Rowling</author> <year>2005</year> <price>29.99</price> </book> 12.2 XQuery FLWOR Expressions Look at the following path expression: doc ( books. xml )/ bookstore /book [ p r i c e >30]/ t i t l e The expression above will select all the title elements under the book elements that are under the bookstore element that have a price element with a value that is higher than 30. The following FLWOR expression will select exactly the same as the path expression above: for The result will be: $x in doc ( books. xml )/ bookstore/book where $x/ p r i c e >30 return $x/ t i t l e <title lang="en">xquery Kick Start</title> <title lang="en">learning XML</title> With FLWOR you can sort the result: 117
for $x in doc ( books. xml )/ bookstore/book where $x/ p r i c e >30 order by $x/ t i t l e return $x/ t i t l e FLWOR is an acronym for For, Let, Where, Order by, Return. The for clause selects all book elements under the bookstore element into a variable called $x. The where clause selects only book elements with a price element with a value greater than 30. The order by clause defines the sort-order. Will be sort by the title element. The return clause specifies what should be returned. Here it returns the title elements. The result of the XQuery expression above will be: <title lang="en">learning XML</title> <title lang="en">xquery Kick Start</title> 12.2.1 XQuery FLWOR + HTML Look at the following XQuery FLWOR expression: for $x in doc ( books. xml )/ bookstore/book/ t i t l e order by $x return $x The expression above will select all the title elements under the book elements that are under the bookstore element, and return the title elements in alphabetical order. Now we want to list all the book-titles in our bookstore in an HTML list. We add <ul> and <li> tags to the FLWOR expression: <ul> { for } </ul> The result of the above will be: $x in doc ( books. xml )/ bookstore/book/ t i t l e order by $x return <l i >{$x}</ l i > <ul> <li><title lang="en">everyday Italian</title></li> <li><title lang="en">harry Potter</title></li> <li><title lang="en">learning XML</title></li> <li><title lang="en">xquery Kick Start</title></li> </ul> Now we want to eliminate the title element, and show only the data inside the title element: <ul> { for } </ul> $x in doc ( books. xml )/ bookstore/book/ t i t l e order by $x return <l i >{data ( $x)}</ l i > The result will be (an HTML list): 118
<ul> <li>everyday Italian</li> <li>harry Potter</li> <li>learning XML</li> <li>xquery Kick Start</li> </ul> 12.2.2 XQuery Terms In XQuery, there are seven kinds of nodes: element, attribute, text, namespace, processinginstruction, comment, and document (root) nodes. XML documents are treated as trees of nodes. The root of the tree is called the document node (or root node). Definition and examples of these kinds of nodes: see XPath. 12.3 XQuery Syntax XQuery is case-sensitive and XQuery elements, attributes, and variables must be valid XML names. Some basic syntax rules: XQuery is case-sensitive XQuery elements, attributes, and variables must be valid XML names An XQuery string value can be in single or double quotes An XQuery variable is defined with a $ followed by a name, e.g. $bookstore XQuery comments are delimited by (: and :), e.g. (: XQuery Comment :). XQuery Conditional Expressions. Look at the following example: If-Then-Else expressions are allowed in XQuery. for $x in doc ( books. xml )/ bookstore/book return i f ( $x/ @category= CHILDREN ) then <c h i l d >{data ( $x/ t i t l e )}</ c h i l d > else <adult >{data ( $x/ t i t l e )}</ adult> Notes on the "if-then-else" syntax: parentheses around the if expression are required. else is required, but it can be just else (). The result of the example above will be: <adult>everyday Italian</adult> <child>harry Potter</child> <adult>learning XML</adult> <adult>xquery Kick Start</adult> XQuery Comparisons. 1. General comparisons: =,!=, <, <=, >, >= 2. Value comparisons: eq, ne, lt, le, gt, ge In XQuery there are two ways of comparing values. The difference between the two comparison methods are shown below. XQuery expressions: $ bookstore// book/@q > 10 The expression above returns true if any q attributes have values greater than 10. $ bookstore// book/@q gt 10 Look at the following The expression above returns true if there is only one q attribute returned by the expression, and its value is greater than 10. If more than one q is returned, an error occurs. 119
12.3.1 XQuery Adding Elements and Attributes As we have seen in a previous chapter, we may include elements and attributes from the input document ( books.xml ) in the result: for $x in doc ( books. xml )/ bookstore /book/ t i t l e order by $x return $x The XQuery expression above will include both the title element and the lang attribute in the result, like this: <title lang="en">everyday Italian</title> <title lang="en">harry Potter</title> <title lang="en">learning XML</title> <title lang="en">xquery Kick Start</title> The XQuery expression above returns the title elements the exact same way as they are described in the input document. We now want to add our own elements and attributes (some HTML elements) to the result! We will put the result in an HTML list - together with some text: <html> <body> <h1>bookstore </h1> <ul> { for } </ul> </body> </html> $x in doc ( books. xml )/ bookstore /book order by $x/ t i t l e return <l i >{data ( $x/ t i t l e ) }. Category : { data ( $x/ @category)}</ l i > The XQuery expression above will generate the following result: <html> <body> <h1>bookstore</h1> <ul> <li>everyday Italian. Category: COOKING</li> <li>harry Potter. Category: CHILDREN</li> <li>learning XML. Category: WEB</li> <li>xquery Kick Start. Category: WEB</li> </ul> </body> </html> Next, we want to use the category attribute as a class attribute in the HTML list: <html> <body> <h1>bookstore </h1> <ul> { for $x in doc ( books. xml )/ bookstore/book order by $x/ t i t l e 120
} </ul> </body> </html> return < l i c l a s s = {data ( $x/ @category)} >{data ( $x/ t i t l e )}</ l i > The XQuery expression above will generate the following result: <html> <body> <h1>bookstore</h1> <ul> <li class="cooking">everyday Italian</li> <li class="children">harry Potter</li> <li class="web">learning XML</li> <li class="web">xquery Kick Start</li> </ul> </body> </html> 12.4 XQuery Selecting and Filtering As we have seen in the previous chapters, we are selecting and filtering elements with either a Path expression or with a FLWOR expression. Look at the following FLWOR expression: for $x in doc ( books. xml )/ bookstore/book where $x/ p r i c e >30 order by $x/ t i t l e return $x/ t i t l e The for clause (optional) binds a variable to each item returned by the in expression. The for clause results in iteration. There can be multiple for clauses in the same FLWOR expression. To loop a specific number of times in a for clause, you may use the to keyword: Result: for $x in (1 to 5) return <test >{$x}</ test> The at keyword can be used to count the iteration: Result: <test>1</test> <test>2</test> <test>3</test> <test>4</test> <test>5</test> for $x at $ i in doc ( books. xml )/ bookstore/book/ t i t l e return <book>{$ i }. { data ( $x)}</book> <book>1. Everyday Italian</book> <book>2. Harry Potter</book> <book>3. XQuery Kick Start</book> <book>4. Learning XML</book> It is also allowed with more than one in expression in the for clause. Use comma to separate each in expression: 121
Result: for $x in ( 1 0, 2 0 ), $y in (100,200) <test>x=10 and y=100</test> return <test>x={$x} and y={$y}</test> <test>x=10 and y=200</test> <test>x=20 and y=100</test> <test>x=20 and y=200</test> The let clause (optional) allows variable assignments and it avoids repeating the same expression many times. The let clause does not result in iteration. l e t $x := (1 to 5) return <test >{$x}</ test> Result: <test>1 2 3 4 5</test> The where clause (optional) is used to specify one or more criteria for the result: where $x/ p r i c e >30 and $x/ p r i c e <100 The order by clause (optional) is used to specify the sort order of the result. Here we want to order the result by category and title: Result: for $x in doc ( books. xml )/ bookstore /book order by $x/ @category, $x/ t i t l e return $x/ t i t l e <title lang="en">harry Potter</title> <title lang="en">everyday Italian</title> <title lang="en">learning XML</title> <title lang="en">xquery Kick Start</title> The return clause specifies what is to be returned. Result: for $x in doc ( books. xml )/ bookstore /book return $x/ t i t l e <title lang="en">everyday Italian</title> <title lang="en">harry Potter</title> <title lang="en">xquery Kick Start</title> <title lang="en">learning XML</title> 122
13 XSL: EXtensible Stylesheet Language 13.1 Overview It started with XSL and ended up with XSLT, XPath, and XSL-FO. The World Wide Web Consortium (W3C) started to develop XSL because there was a need for an XML-based Stylesheet Language. HTML uses predefined tags and the meaning of the tags are well understood. The <table> element in HTML defines a table - and a browser knows how to display it. Adding styles to HTML elements is simple. Telling a browser to display an element in a special font or color, is easy with CSS. XML does not use predefined tags (we can use any tag-names we like), and the meaning of these tags are not well understood. A <table> element could mean an HTML table, a piece of furniture, or something else - and a browser does not know how to display it. XSL describes how the XML document should be displayed! But XSL is more than a Style Sheet Language. XSL consists of three parts: XSLT : a language for transforming XML documents XPath : a language for navigating in XML documents XSL-FO : a language for formatting XML documents 13.2 Introduction to XSLT XSLT is a language for transforming XML documents into XHTML documents or to other XML documents. XSLT (XSL Transformations, a W3C Recommendation) is the most important part of XSL and it uses XPath to navigate in XML documents. XSLT is used to transform an XML document into another XML document, or another type of document that is recognized by a browser, like HTML and XHTML. Normally XSLT does this by transforming each XML element into an (X)HTML element. With XSLT you can add/remove elements and attributes to or from the output file. You can also rearrange and sort elements, perform tests and make decisions about which elements to hide and display, and a lot more. A common way to describe the transformation process is to say that XSLT transforms an XML source-tree into an XML result-tree. XSLT uses XPath to find information in an XML document. XPath is used to navigate through elements and attributes in XML documents. In the transformation process, XSLT uses XPath to define parts of the source document that should match one or more predefined templates. When a match is found, XSLT will transform the matching part of the source document into the result document. XSLT became a W3C Recommendation November 16, 1999. 13.2.1 Example study: How to transform XML into XHTML using XSLT The root element that declares the document to be an XSL style sheet is <xsl:stylesheet> or <xsl:transform> (completely synonymous). The correct way to declare an XSL style sheet according to the W3C XSLT Recommendation is: or: <xsl : stylesheet v e r s i o n = 1.0 xmlns : xsl= http : / /www. w3. org /1999/XSL/ Transform > <xsl : transform v e r s i o n = 1.0 xmlns : xsl= http : / /www. w3. org /1999/XSL/ Transform > To get access to the XSLT elements, attributes and features we must declare the XSLT namespace at the top of the document. The xmlns:xsl="http://www.w3.org/1999/xsl/transform" points to the official W3C XSLT namespace. If you use this namespace, you must also include the attribute version="1.0". 123
We want to transform the following XML document ("cdcatalog.xml") into XHTML: <?xml version="1.0" encoding="iso-8859-1"?> <catalog> <cd> <title>empire Burlesque</title> <artist>bob Dylan</artist> <country>usa</country> <company>columbia</company> <price>10.90</price> <year>1985</year> </cd>... </catalog> Viewing XML Files in Firefox and Internet Explorer: Open the XML file (typically by clicking on a link). The XML document will be displayed with color-coded root and child elements. A plus (+) or minus sign ( ) to the left of the elements can be clicked to expand or collapse the element structure. To view the raw XML source (without the + and signs), select "View Page Source" or "View Source" from the browser menu. Then you create an XSL Style Sheet ("cdcatalog.xsl") with a transformation template: <?xml v e r s i o n = 1.0 encoding= ISO 8859 1?> 2 <xsl : stylesheet v e r s i o n = 1.0 xmlns : xsl= http : / /www. w3. org /1999/XSL/ Transform > 4 <xsl : template match= / > <html> 6 <body> <h2>my CD C o l l e c t i o n </h2> 8 <t a b l e border= 1 > <t r b g c o l o r= #9acd32 > 10 <th a l i g n = l e f t >T i t l e </th> <th a l i g n = l e f t >Artist </th> 12 </tr> <xsl : for each s elect= c a t a l o g /cd > 14 <tr > <td><xsl : value of s elect= t i t l e /></td> 16 <td><xsl : value of s elect= a r t i s t /></td> </tr > 18 </xsl : for each> </table > 20 </body> </html> 22 </xsl : template> </xsl : stylesheet> The XSL Style Sheet has to be linked to the XML Document. Add the XSL style sheet reference to your XML document ("cdcatalog.xml") by adding one line before the root tag <catalog> <?xml stylesheet type= t e x t / xsl href= c d c a t a l o g. xsl?> If you have an XSLT compliant browser it will nicely transform your XML into XHTML. 13.2.2 Template Element An XSL style sheet consists of one or more set of rules that are called templates. Each template contains rules to apply when a specified node is matched. 124
The <xsl:template> element is used to build templates. The match attribute is used to associate a template with an XML element. The match attribute can also be used to define a template for the entire XML document. The value of the match attribute is an XPath expression (i.e. match="/" defines the whole document). Let s look at a simplified version of the XSL file from the previous chapter: <?xml v e r s i o n = 1.0 encoding= ISO 8859 1?> <xsl : stylesheet v e r s i o n = 1.0 xmlns : xsl= http : / /www. w3. org /1999/XSL/ Transform > <xsl : template match= / > <html> <body> <h2>my CD C o l l e c t i o n </h2> <t a b l e border= 1 > <t r b g c o l o r= #9acd32 > <th>t i t l e </th> <th>artist </th> </tr > <tr > <td >.</td> <td >.</td> </tr > </table > </body> </html> </xsl : template> </xsl : stylesheet> Since an XSL style sheet is an XML document itself, it always begins with the XML declaration: <?xml version="1.0" encoding="iso-8859-1"?>. The next element, <xsl:stylesheet>, defines that this document is an XSLT style sheet document (along with the version number and XSLT namespace attributes). The <xsl:template> element defines a template. The match="/" attribute associates the template with the root of the XML source document. The content inside the <xsl:template> element defines some HTML to write to the output. The last two lines define the end of the template and the end of the style sheet. The result of the transformation above will look like this: My CD Collection Title Artist.. The result from this example was a little disappointing, because no data was copied from the XML document to the output. 13.2.3 Value-of Element The <xsl:value-of> element can be used to extract the value of an XML element and add it to the output stream of the transformation: <tr > <td><xsl : value of <td><xsl : value of </tr> s elect= c a t a l o g /cd/ t i t l e /></td> s elect= c a t a l o g /cd/ a r t i s t /></td> Note: The value of the select attribute is an XPath expression. An XPath expression works like navigating a file system; where a forward slash (/) selects subdirectories. 125
The result of the transformation above will look like this: My CD Collection Title Artist Empire Burlesque Bob Dylan The result from this example was also a little disappointing, because only one line of data was copied from the XML document to the output. 13.2.4 For-each Element The <xsl:for-each> element allows you to do looping in XSLT. The XSL <xsl:for-each> element can be used to select every XML element of a specified node-set: <xsl : for each s elect= c a t a l o g /cd > <tr> <td><xsl : value of s elect= t i t l e /></td> <td><xsl : value of s elect= a r t i s t /></td> </tr > </xsl : for each> The result of the transformation above will look like this: My CD Collection Title Artist Empire Burlesque Bob Dylan Hide your heart Bonnie Tyler The very best of Cat Stevens Unchain my heart Joe Cocker...... Filtering the Output. We can also filter the output from the XML file by adding a criterion to the select attribute in the <xsl:for-each> element. <xsl : for each s elect= c a t a l o g /cd [ a r t i s t= Bob Dylan ] > Legal filter operators are: = (equal),! = (not equal), < less than, > greater than. The result of the transformation above will look like this: 13.2.5 Sort Element My CD Collection Title Artist Empire Burlesque Bob Dylan The <xsl:sort> element is used to sort the output. To sort the output, simply add an <xsl:sort> element inside the <xsl:for-each> element in the XSL file: <xsl : for each s elect= c a t a l o g /cd > <xsl : sort s elect= a r t i s t /> <tr > <td><xsl : value of s elect= t i t l e /></td> <td><xsl : value of </tr > </xsl : for each> s elect= a r t i s t /></td> Note: The select attribute indicates what XML element to sort on. The result of the transformation above will look like this: 126
My CD Collection Title Artist Romanza Andrea Bocelli One night only Bee Gees Empire Burlesque Bob Dylan Hide your heart Bonnie Tyler The very best of Cat Stevens...... 13.2.6 If Element The <xsl:if> element is used to put a conditional test against the content of the XML file. To put a conditional if test against the content of the XML file, add an <xsl:if> element to the XSL document. <xsl : i f test= e x p r e s s i o n >...... some output i f the e x p r e s s i o n i s true...... </xsl : i f > To add a conditional test, add the <xsl:if> element inside the <xsl:for-each> element in the XSL file: <xsl : for each s elect= c a t a l o g /cd > <xsl : i f test= p r i c e > ; 10 > <tr > <td><xsl : value of s elect= t i t l e /></td> </tr > </xsl : i f > </xsl : for each> <td><xsl : value of s elect= a r t i s t /></td> Note: The value of the required test attribute contains the expression to be evaluated. The code above will only output the title and artist elements of the CDs that has a price that is higher than 10. The result of the transformation above will look like this: 13.2.7 Choose Element My CD Collection Title Artist Empire Burlesque Bob Dylan Still got the blues Gary Moore One night only Bee Gees Romanza Andrea Bocelli Black Angel Savage Rose 1999 Grammy Nominees Many The <xsl:choose> element is used in conjunction with <xsl:when> and <xsl:otherwise> to express multiple conditional tests. <xsl : choose> <xsl :when test= e x p r e s s i o n >... some output... </xsl :when> <xsl : otherwise> 127
... some output.... </xsl : otherwise> </xsl : choose> To insert a multiple conditional test against the XML file, add the <xsl:choose>, <xsl:when>, and <xsl:otherwise> elements to the XSL file: <xsl : for each s elect= c a t a l o g /cd > 2 <tr > <td><xsl : value of s elect= t i t l e /></td> 4 <xsl : choose> <xsl :when test= p r i c e > ; 10 > 6 <td b g c o l o r= #f f 0 0 f f > <xsl : value of s elect= a r t i s t /></td> 8 </xsl :when> <xsl : otherwise> 10 <td><xsl : value of s elect= a r t i s t /></td> </xsl : otherwise> 12 </xsl : choose> </tr> 14 </xsl : for each> The code above will add a pink background-color to the Artist column WHEN the price of the CD is higher than 10. Here is another example that contains two <xsl:when> elements: <xsl : for each s elect= c a t a l o g /cd > 2 <tr > <td><xsl : value of s elect= t i t l e /></td> 4 <xsl : choose> <xsl :when test= p r i c e > ; 10 > 6 <td b g c o l o r= #f f 0 0 f f > <xsl : value of s elect= a r t i s t /></td> 8 </xsl :when> <xsl :when test= p r i c e > ; 9 > 10 <td b g c o l o r= #c c c c c c > <xsl : value of s elect= a r t i s t /></td> 12 </xsl :when> <xsl : otherwise> 14 <td><xsl : value of s elect= a r t i s t /></td> </xsl : otherwise> 16 </xsl : choose> </tr> 18 </xsl : for each> The code above will add a pink background color to the Artist column WHEN the price of the CD is higher than 10, and a grey background-color WHEN the price of the CD is higher than 9 and lower or equal to 10. 13.2.8 Apply-templates Element The <xsl:apply-templates> element applies a template to the current element or to the current element s child nodes. If we add a select attribute to the <xsl:apply-templates> element it will process only the child element that matches the value of the attribute. We can use the select attribute to specify the order in which the child nodes are processed. Look at the following XSL style sheet: 128
<?xml v e r s i o n = 1.0 encoding= ISO 8859 1?> 2 <xsl : stylesheet v e r s i o n = 1.0 xmlns : xsl= http : / /www. w3. org /1999/XSL/ Transform > 4 <xsl : template match= / > <html> 6 <body> <h2>my CD C o l l e c t i o n </h2> 8 <x s l : apply templates/> </body> 10 </html> </xsl : template> 12 <x s l : template match= cd > <p> 14 <xsl : apply templates s elect= t i t l e /> <xsl : apply templates s elect= a r t i s t /> 16 </p> </xsl : template> 18 <xsl : template match= t i t l e > T i t l e : <span s t y l e = c o l o r :# f f 0 0 0 0 > 20 <xsl : value of s elect=. /></span> <br /> 22 </xsl : template> <xsl : template match= a r t i s t > 24 A r t i s t : <span s t y l e = c o l o r :#00 f f 0 0 > <xsl : value of s elect=. /></span> 26 <br /> </xsl : template> 28 </xsl : stylesheet> The result of the transformation will look like this: My CD Collection Title: Empire Burlesque Artist: Bob Dylan Title: Hide your heart Artist: Bonnie Tyler... 13.2.9 XSLT Elements Reference The XSLT elements from the W3C Recommendation (XSLT Version 1.0). (N: indicates the earliest version of Netscape that supports the tag: IE: indicates the earliest version of Internet Explorer that supports the tag.) 13.2.10 XSLT Functions XQuery 1.0, XPath 2.0, and XSLT 2.0 share the same functions library. XSLT includes over 100 built-in functions. There are functions for string values, numeric values, date and time comparison, node and QName manipulation, sequence manipulation, Boolean values, and more. The URI of the XSLT function namespace is: http://www.w3.org/2005/02/xpath-functions. The default prefix for the function namespace is fn:. 129
Note: Elements supported in IE 5 may have NON-standard behavior, because IE 5 was released before XSLT became an official W3C Recommendation. Element Description IE N apply-imports Applies a template rule from an imported style sheet 6 apply-templates Applies a template rule to the current element or to the current 5 6 element s child nodes attribute Adds an attribute 5 6 attribute-set Defines a named set of attributes 6 6 call-template Calls a named template 6 6 choose Used in conjunction with <when> and <otherwise> to express 5 6 multiple conditional tests comment Creates a comment node in the result tree 5 6 copy Creates a copy of the current node (without child nodes and 5 6 attributes) copy-of Creates a copy of the current node (with child nodes and attributes) 6 6 decimal-format Defines the characters and symbols to be used when converting 6 numbers into strings, with the format-number() function element Creates an element node in the output document 5 6 fallback Specifies an alternate code to run if the processor does not support 6 an XSLT element for-each Loops through each node in a specified node set 5 6 if Contains a template that will be applied only if a specified condition 5 6 is true import Imports the contents of one style sheet into another. Note: An 6 6 imported style sheet has lower precedence than the importing style sheet include Includes the contents of one style sheet into another. Note: An 6 6 included style sheet has the same precedence as the including style sheet key Declares a named key that can be used in the style sheet with 6 6 the key() function message Writes a message to the output (used to report errors) 6 6 namespace-alias Replaces a namespace in the style sheet to a different namespace 6 in the output number Determines the integer position of the current node and formats 6 6 a number otherwise Specifies a default action for the <choose> element 5 6 output Defines the format of the output document 6 6 param Declares a local or global parameter 6 6 preserve-space Defines the elements for which white space should be preserved 6 6 processing-instruction Writes a processing instruction to the output 5 6 sort Sorts the output 6 6 strip-space Defines the elements for which white space should be removed 6 6 stylesheet Defines the root element of a style sheet 5 6 template Rules to apply when a specified node is matched 5 6 text Writes literal text to the output 5 6 transform Defines the root element of a style sheet 6 6 value-of Extracts the value of a selected node 5 6 variable Declares a local or global variable 6 6 when Specifies an action for the <choose> element 5 6 with-param Defines the value of a parameter to be passed into a template 6 6 130
Tip: Functions are often called with the fn: prefix, such as fn:string(). However, since fn: is the default prefix of the namespace, the function names do not need to be prefixed when called. In addition, there are the following built-in XSLT functions: Name current() document() element-available() format-number() function-available() generate-id() key() system-property() unparsed-entity-uri() Description Returns the current node Used to access the nodes in an external XML document Tests whether the element specified is supported by the XSLT processor Converts a number into a string Tests whether the function specified is supported by the XSLT processor Returns a string value that uniquely identifies a specified node Returns a node-set using the index specified by an <xsl:key> element Returns the value of the system properties Returns the URI of an unparsed entity 13.3 Voorbeeld Vervang CSS regel in het XML bestand met root boekenkast door: <?xml-stylesheet type="text/xsl" href="bkx.xsl"?> Een XSL-stylesheet bestaat uit template elementen. Zo n element komt overeen met specifieke XML document nodes op basis van de XPath expressie (locatiepad) in het match attribuut. Een locatiepad selecteert een verzameling nodes relatief tov. de context node. Een pad bestaat uit een opeenvolging van nodes, elementen en attributen (aangegeven door @), van het XML document gescheiden door / s. Het pad / duidt de root node van het XML document aan. <?xml v e r s i o n = 1.0?> 2 <xsl : stylesheet xmlns= http : / /www. w3. org /1999/ xhtml xmlns : xsl= http : / /www. w3. org /1999/XSL/ Transform v e r s i o n= 1.0 > 4 <xsl : template match= / > <html> <head> </head> 6 <body> <h1> Bo ekenka st </h1> 8 <t a b l e border = 1 c e l l p a d d i n g = 3 c e l l s p a c i n g = 1 > <tr> 10 <th> T i t e l </th> <th> Auteur </th> </tr> 12 <xsl : apply templates s elect= boekenkast/ boek /> </table > 14 </body> </html> 16 </xsl : template> <xsl : template match= boekenkast/ boek > 18 <tr > <td> <xsl : value of s elect= t i t e l /> </td> 20 <td> <xsl : value of s elect= auteur /> </td> </tr> 22 </xsl : template> / xsl : stylesheet> Binnen de template wordt op lijn 12 een tweede template geactiveerd. Deze komt overeen met de kind-nodes van het locatiepad boekenkast/boek. 131
value-of : met als attribuut select gevolgd door een XPath expressie: het resultaat is de tekst inhoud van de node van het XML-document; iteratie : in plaats van een template te activeren op lijn 12 kan ook een lus uitgevoerd worden om alle kind-nodes te doorlopen: <xsl : for each s elect= boekenkast/ boek > <tr> <td> <xsl : value of s elect= t i t e l /> </td> <td> <xsl : value of s elect= auteur /> </td> </tr> </xsl : for each> filteren : door in het locatiepad een predicaat expressie op te nemen, bijv. het kleur attribuut van het prijs element moet gelijk zijn aan geel <xsl : for each s elect= boekenkast/ boek [ p r i j s [ @kleur= g e e l ]] > <tr> <td> <xsl : value of s elect= t i t e l /> </td> <td> <xsl : value of s elect= auteur /> </td> </tr > </xsl : for each> if : met als attribuut test met als waarde een conditie waaraan moet voldaan zijn (< is < en > is >); meerdere condities kunnen gecombineerd worden met and en or <xsl : for each s elect= boekenkast/ boek > <xsl : i f test= p r i j s > ; 20 > <tr > <td> <xsl : value of s elect= t i t e l /> </td> </tr > </xsl : i f > </xsl : for each> <td> <xsl : value of choose : wanneer er meerdere mogelijkheden zijn s elect= auteur /> </td> <xsl : for each s elect= boekenkast/ boek > <xsl : choose> <xsl :when test= p r i j s &l t ; 20 > <tr> <td> <xsl : value of s elect= t i t e l /> </td> <td> <xsl : text > </xsl : text > </td> <td> <xsl : text > </xsl : text > </td> </tr > </xsl :when> <xsl :when test= p r i j s > ; 20 and p r i j s &l t ; 30 > <tr> <td> <xsl : text > </xsl : text > </td> <td> <xsl : value of s elect= auteur /> </td> <td> <xsl : text > </xsl : text > </td> </tr > </xsl :when> <xsl : otherwise> <tr> <td> <xsl : text > </xsl : text > </td> <td> <xsl : text > </xsl : text > </td> <td> <xsl : value of s elect= p r i j s / @kleur /> </td> </tr > </xsl : otherwise> </xsl : choose> </xsl : for each> sortering : over verschillende elementen en/of attributen door lijn 12 te vervangen door 132
<xsl : apply templates s elect= boekenkast/boek > <xsl : sort order= a scending s elect= p r i j s / @kleur /> <xsl : sort data type= number order= descending s elect= p r i j s /> </x s l : apply templates> en de template tussen lijn 17 en 22 te definiëren als: <xsl : template match= boekenkast/ boek > <tr> <td> <xsl : value of s elect= t i t e l /> </td> <td> <xsl : value of s elect= p r i j s / @kleur /> </td> <td> <xsl : value of s elect= p r i j s /> </td></tr > </xsl : template> Merk op dat in een XML document de tekst tussen tags (de eigenlijke informatie) niet noodzakelijk vrij is van spaties. Deze spaties kunnen door de XSLT machine verwijderd worden dmv. een toplevel element: <xsl:strip-space elements="prijs" />. De constructie <xsl:preserve-space elements="titel isbn" /> behoudt alle spaties. 13.4 Introduction to XSL-FO XSL-FO is about formatting XML data for output. XSL-FO is an XML-based markup language describing the formatting of XML data for output to screen, paper or other media. XSL-FO stands for Extensible Stylesheet Language Formatting Objects. XSL-FO and XSL is the same thing, but this requires some explanation. Styling is both about transforming and formatting information. When the World Wide Web Consortium (W3C) made their first XSL Working Draft, it contained the language syntax for both transforming and formatting XML documents. Later, the XSL Working Group at W3C split the original draft into separate recommendations: XSLT, a language for transforming XML documents XSL or XSL-FO, a language for formatting XML documents XPath, a language for navigating through elements and attributes in XML documents. XSL-FO became a W3C Recommendation 15. October 2001. Formally named XSL. 13.4.1 XSL-FO Documents XSL-FO documents are XML files with output information. They contain information about the output layout and output contents. XSL-FO documents are stored in files with a.fo or a.fob file extension. It is also quite common to see XSL-FO documents stored with an.xml extension, because this makes them more accessible to XML editors. XSL-FO documents have a structure like this: <?xml v e r s i o n = 1.0 encoding= ISO 8859 1?> <f o : r o o t xmlns : f o= http : / /www. w3. org /1999/XSL/Format > <f o : layout master set > <f o : simple page master master name= A4 > <!-- Page template goes here -- > </ f o : simple page master> </ f o : layout master set > <f o : page sequence master reference= A4 > <!-- Page content goes here -- > </f o : page sequence> </f o : root> XSL-FO documents are XML documents, and must always start with an XML declaration. The <fo:root> element is the root element of XSL-FO documents. The root element also declares the namespace for XSL-FO. 133
The <fo:layout-master-set> element contains one or more page templates. Each <fo:simple-page-master> element contains a single page template. Each template must have a unique name (master-name). One or more <fo:page-sequence> elements describe the page contents. The master-reference attribute refers to the simple-page-master template with the same name. Note: The master-reference A4 does not actually describe a predefined page format. It is just a name. You can use any name like MyPage, MyTemplate, etc. 13.4.2 XSL-FO Areas The XSL formatting model defines a number of rectangular areas (boxes) to display output. All output (text, pictures, etc.) will be formatted into these boxes and then displayed or printed to a target media. We will take a closer look at the following areas: pages, regions, block areas, line areas, inline areas. XSL-FO output is formatted into pages. Printed output will normally go into many separate pages. Browser output will often go into one long page. XSL-FO Pages contain regions. Each XSL-FO Page contains a number of regions: region-body (the body of the page), region-before (the header of the page), region-after (the footer of the page), region-start (the left sidebar), region-end (the right sidebar). XSL-FO Regions contain block areas. XSL-FO Block areas define small block elements (the ones that normally starts with a new line) like paragraphs, tables and lists. XSL-FO Block areas can contain other Block areas, but most often they contain line areas. XSL- FO Line areas define text lines inside Block areas. XSL-FO Line areas contain inline areas. XSL-FO Inline areas define text inside lines (bullets, single character, graphics, and more). 13.4.3 XSL-FO Output XSL-FO defines output inside <fo:flow> elements. Blocks of content Flows into Pages and then to the output media. XSL-FO output is normally nested inside <fo:block> elements, nested inside <fo:flow> elements, nested inside <fo:page-sequence> elements: <f o : page sequence> <f o : f l o w flow name= xsl region body > <f o : block> <!-- Output goes here -- > </f o : block> </f o : flow> </f o : page sequence> XSL-FO Example <?xml v e r s i o n = 1.0 encoding= ISO 8859 1?> <f o : r o o t xmlns : f o= http : / /www. w3. org /1999/XSL/Format > <f o : layout master set > <f o : simple page master master name= A4 > </ f o : simple page master> </ f o : layout master set > <f o : page sequence master reference= A4 > <f o : f l o w flow name= xsl region body > <f o : block>hello W3Schools</f o : block> </f o : flow> </f o : page sequence> </f o : root> The output from this code would be something like this: Hello W3Schools 134
Block Area Attributes. <f o : block border width= 1mm > This block o f output w i l l have a one m i l l i m e t e r border around i t. </f o : block> Since block areas are rectangular boxes, they share many common area properties: space before and space after, margin, border, padding. Blocks are sequences of output in rectangular boxes: space before margin border padding content space after The space before and space after is the empty space separating the block from the other blocks. The margin is the empty area on the outside of the block. The border is the rectangle drawn around the external edge of the area. It can have different widths on all four sides. It can also be filled with different colors and background images. The padding is the area between the border and the content area. The content area contains the actual content like text, pictures, graphics, or whatever. Block Margin, padding and background: margin padding margin-top padding-before background-color margin-bottom padding-after background-image margin-left padding-start background-repeat margin-right padding-end background-attachment (scroll or fixed) Block Border attributes: border-style border-color border-width border-top-style border-top-color border-top-width border-bottom-style border-bottom-color border-bottom-width border-left-style border-left-color border-left-width border-right-style border-right-color border-right-width Instead of top, bottom, left, right one can use before, after, start, end respectively. Blocks are sequences of output that can be styled individually: <f o : block f o n t s i z e = 12pt font family = s a n s s e r i f > This block o f output w i l l be w r i t t e n in a 12 pt s a n s s e r i f f o n t. </f o : block> Font and text attributes: Example: font-family text-align wrap-option (defines word wrap) font-weight text-align-last break-before (defines page breaks) font-style text-indent break-after (defines page breaks) font-size start-indent reference-orientation font-variant end-indent (defines text rotation in 90ïncrements) <f o : block f o n t s i z e = 14pt font family= verdana c o l o r= red space before= 5mm space after= 5mm > W3Schools </f o : block> <f o : block text indent= 5mm font family= verdana f o n t s i z e = 12pt space before= 5mm space after= 5mm > At W3Schools you w i l l f i n d a l l the Web building t u t o r i a l s you 135
need, from b a s i c HTML and XHTML to advanced XML, XSL, Multimedia and WAP. </f o : block> Result: W3Schools At W3Schools you will find all the Web-building tutorials you need, from basic HTML and XHTML to advanced XML, XSL, Multimedia and WAP. When you look at the example above, you can see that it will take a lot of code to produce a document with many headers and paragraphs. Normally XSL-FO documents do not combine formatting information and content like we have done here. With a little help from XSLT we can put the formatting information into templates and write a cleaner content. 13.4.4 XSL-FO Flow XSL-FO pages are filled with content from <fo:flow> elements. XSL-FO uses <fo:page-sequence> elements to define output pages. Each output page refers to a page master which defines the layout. Each output page has a <fo:flow> element defining the output. Each output page is printed (or displayed) in sequence. The <fo:flow> element contains all the elements to be printed to the page. When the page is full, the same page master will be used over (and over) again until all the text is printed. The <fo:flow> element has a flow-name attribute. The value of the flow-name attribute defines where the content of the <fo:flow> element will go. The legal values are: xsl-region-body (into the region-body), xsl-region-before (into the regionbefore), xsl-region-after (into the region-after), xsl-region-start (into the region-start), xsl-regionend (into the region-end). 13.4.5 XSL-FO Pages XSL-FO uses page templates called Page Masters to define the layout of pages. Each template must have a unique name: <f o : simple page master master name= i n t r o > <f o : region body margin= 5in /> </ f o : simple page master> <f o : simple page master master name= l e f t > <f o : region body margin left = 2in margin right= 3in /> </ f o : simple page master> <f o : simple page master master name= r i g h t > <f o : region body margin left = 3in margin right= 2in /> </ f o : simple page master> In the example above, three <fo:simple-page-master> elements define three different templates. Each template (page-master) has a different name. The first template is called intro. It could be used as a template for introduction pages. The second and third templates are called left and right. They could be used as templates for even and odd page numbers. XSL-FO uses the following attributes to define the size of a page: page-width defines the width of a page; page-height defines the height of a page. 136
XSL-FO uses the following attributes to define the margins of a page: margin-top, margin-bottom, margin-left, margin-right, margin (defines all four margins). XSL-FO uses the following elements to define the regions of a page: region-body defines the body region, region-before defines the top region (header), region-after defines the bottom region (footer), region-start defines the left region (left sidebar), region-end defines the right region (right sidebar). Note that the region-before, region-after, region-start, and region-end is a part of the body region. To avoid text in the body region to overwrite text in these regions, the body region must have margins at least the size of these regions. Margin Top Region Before Margin Left Region Start Region Body Region After Margin Bottom Region End Margin Right XSL-FO Example. This is an extract from an XSL-FO document: <f o : simple page master master name= A4 page width = 297mm pa g e heig ht= 210mm margin top= 1cm margin bottom= 1cm margin left = 1cm margin right= 1cm > <f o : region body margin= 3cm /> <f o : r egion before extent= 2cm /> <f o : r e g i o n a f t e r extent= 2cm /> <f o : r e g i o n s t a r t extent= 2cm /> <f o : region end extent= 2cm /> </ f o : simple page master> The code above defines a Simple Page Master Template with the name A4. The width of the page is 297 millimeters and the height is 210 millimeters. The top, bottom, left, and right margins of the page are all 1 centimeter. The body has a 3 centimeter margin (on all sides). The before, after, start, and end regions (of the body) are all 2 centimeters. The width of the body in the example above can be calculated by subtracting the left and right margins and the region-body margins from the width of the page itself: 297mm (2 1cm) (2 3cm) = 297mm 20mm 60mm = 217mm. Note that the regions (region-start and region-end) are not a part of the calculation. As described earlier, these regions are parts of the body. 13.4.6 XSL-FO Lists XSL-FO uses List Blocks to define lists. There are four XSL-FO objects used to create lists: 1. fo:list-block (contains the whole list) 2. fo:list-item (contains each item in the list) 3. fo:list-item-label (contains the label for the list-item - typically an <fo:block> containing a number, character, etc.) 4. fo:list-item-body (contains the content/body of the list-item i - typically one or more <fo:block> objects) 137
An XSL-FO list example: <f o : l i s t b l o c k > 2 <f o : list item > <f o : list item label > 4 <f o : block > </f o : block> </f o : list item label > 6 <f o : list item body > <f o : block>volvo</f o : block> 8 </ f o : list item body > </f o : list item > 10 <f o : list item > <f o : list item label > 12 <f o : block > </f o : block> </f o : list item label > 14 <f o : list item body > <f o : block>saab</f o : block> 16 </ f o : list item body > </f o : list item > 18 </f o : l i s t b l o c k > The output from this code would be: Volvo Saab 13.4.7 XSL-FO Tables XSL-FO uses the <fo:table-and-caption> element to define tables. The XSL-FO table model is not very different from the HTML table model. There are nine XSL-FO objects used to create tables: fo:table-and-caption, fo:table, fo:table-caption, fo:table-column, fo:table-header, fo:table-footer, fo:table-body, fo:table-row, fo:table-cell. XSL-FO uses the <fo:table-and-caption> element to define a table. It contains a <fo:table> and an optional <fo:caption> element. The <fo:table> element contains optional <fo:table-column> elements, an optional <fo:table-header> element, a <fo:table-body> element, and an optional <fo:table-footer> element. Each of these elements has one or more <fo:table-row> elements, with one or more <fo:table-cell> elements: <f o : table and caption> 2 <f o : table > <f o : table column column width= 25mm /> 4 <f o : table column column width= 25mm /> 6 <f o : table header> <f o : table row> 8 <f o : t a b l e c e l l > <f o : block font weight= bold >Car</f o : block> 10 </f o : t a b l e c e l l > <f o : t a b l e c e l l > 12 <f o : block font weight= bold >Price </f o : block> </f o : t a b l e c e l l > 14 </f o : table row> </f o : table header> 16 <f o : table body> 18 <f o : table row> <f o : t a b l e c e l l > 20 <f o : block>volvo</f o : block> </f o : t a b l e c e l l > 22 <f o : t a b l e c e l l > 138
<f o : block>$50000</f o : block> 24 </f o : t a b l e c e l l > </f o : table row> 26 <f o : table row> <f o : t a b l e c e l l > 28 <f o : block>saab</f o : block> </f o : t a b l e c e l l > 30 <f o : t a b l e c e l l > <f o : block>$48000</f o : block> 32 </f o : t a b l e c e l l > </f o : table row> 34 </f o : table body> 36 </f o : table > </ f o : table and caption> The output from this code would something like this: Car Price Volvo $50000 SAAB $48000 13.4.8 XSL-FO and XSLT The example above from the chapter about XSL-FO Blocks can be rewritten with a little help from XSLT. Remove the XSL-FO information from the document (to an XML document): <header > W3Schools </header > <paragraph> At W3Schools you w i l l f i n d a l l the Web building t u t o r i a l s you need, from b a s i c HTML and XHTML to advanced XML, XSL, Multimedia and WAP. </paragraph> Add an XSLT transformation: 1 <x s l : template match= hea der > <f o : block f o n t s i z e = 14pt font family= verdana c o l o r= red 3 space before= 5mm space after= 5mm > <x s l : apply templates/> 5 </f o : block> </xsl : template> 7 <xsl : template match= paragraph > <f o : block text indent= 5mm font family= verdana f o n t s i z e = 12pt 9 space before= 5mm space after= 5mm > <x s l : apply templates/> 11 </f o : block> </xsl : template> 139
And the result will be the same: W3Schools At W3Schools you will find all the Web-building tutorials you need, from basic HTML and XHTML to advanced XML, XSL, Multimedia and WAP. 13.4.9 XSL-FO Software XSL-FO needs formatting software to produce output. An XSL-FO processor is a software program for formatting XSL documents for output. Most XSL-FO processors can output PDF documents and quality print, as well as HTML and other formats. export FOP_HOME = /home/hcr/tls/fop-0.94 xmlxsl boek.xml boek.xsl > klop.fo fop klop.fo klop.pdf fop -xml boek.xml -xsl boek.xsl -pdf klop.pdf 140
14 Toepassingen van XML 14.1 XTM XTM (XML Topic Maps) kan gedefinieerd worden als een gereedschap waarmee je alle sleutelconcepten binnen de informatie van een organisatie kan verzamelen en dit allemaal onderling kan doorverbinden. Door gebruik te maken van topic maps maak je een index aan over alle informatie die zich bevindt buiten deze informatie. Wat men hiermee bedoelt, is dat de topic maps, geschreven in XML, zich niet in de database of andere informatieve documenten bevinden maar op een hoger niveau, buiten de gegevens. Een topic map gebruikt de sleutelconcepten, beschreven in de databases en andere documenten, en verbindt hen met elkaar, onafhankelijk van wat er gezegd wordt over deze concepten in de te indexeren informatie. Wanneer een document bijvoorbeeld zegt: De onderhoudsprocedure voor deel X bestaat uit de volgende stappen..., zal de topic map zeggen: Deel X is van type Q, is ingesloten in delen Y en Z en de onderhoudsprocedure bevindt zich in document W. Dit betekent een stap terug nemen van de details en eerder op het bos focussen dan op de bomen afzonderling. Anders gezegd betekent dit het beheren van de betekenis van de informatie in plaats van de informatie zelf. Het meeste worden topic maps gebruikt om websites te bouwen die volledig aangedreven worden door deze map, om zo goed mogelijk hun informatie-zoekende doeleinden te vervullen. De map voorziet in een site structuur en de pagina-inhoud wordt gedeeltelijk van de map zelf genomen en deels van de zogenaamde occurences (gelinkte informatie). Deze oplossing is perfect voor sites zoals portals, catalogs, site indexes, enzoverder. XTM staat voor XML topic maps. XTM is een open standaard ontwikkeld voor de archivering van digitale informatie. Het is een applicatie- en apparaat-onafhankelijke laag of kaart waarin informatiebronnen met elkaar in relatie worden gebracht. Kennis wordt weergegeven door linken tussen onderwerpen weer te geven en de aard van hun onderlinge associatie. De band met XML komt voort uit de XML-namespace. Dit omschrijft een topic map volledig in XML-grammatica. Het is deze dat volledig gestandariseerd is. XTM = XML topic maps. Het nut van topic maps ligt hem in kennismanagement en doelgericht zoeken in digitale documenten. XTM is gebaseerd op 3 pijlers: 1. de opbouw: de bouwsteen van een TM (topic map) is het topic zelf. Met het topic bedoelen we het onderwerp van de digitale informatie. 2. het doel: de lijm die de verschillende bouwstenen met elkaar verbindt. Het geeft de associaties weer die tussen de verschillende topics bestaan. 3. de context: geeft weer binnen welk perspectief, in welke taal, in welke versie het topic als waar mag aanzien worden. Voordelen van topic maps: meerdere topic maps kunnen op eenzelfde informatiebron van toepassing zijn; wijziging van de topic map heeft geen invloed op de informatiebronnen zelf; niet afhankelijk van de interne structuur van de informatie bronen. 14.2 SOAP SOAP (Simple Object Access Protocol) is a protocol for exchanging XML-based messages over a computer network, normally using HTTP. It is important for application development to allow Internet communication between applications/programs running on different operating systems. There are several different types of messaging patterns in SOAP, but by far the most common is the Remote Procedure Call (RPC) pattern, where the client sends a request message to the server, which immediately sends a response message to the client. RPC represents a compatibility and security problem. Firewalls and proxy servers will normally block this kind of traffic. A better way to communicate between applications is over HTTP, because HTTP is supported by all Internet browsers and servers. SOAP was created to accomplish this. Originally designed by Dave Winer, 141
Don Box, Bob Atkinson, and Mohsen Al-Ghosein in 1998 with backing from Microsoft as an object access protocol, the SOAP specification is currently maintained by the XML Protocol Working Group of the World Wide Web Consortium. Here is an EXAMPLE of how a client might format a SOAP message REQUESTing product information from a fictional warehouse web service. The client needs to know which product corresponds with the ID 827635: <soap:envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:body> <getproductdetails xmlns="http://warehouse.example.com/ws"> <productid>827635</productid> </getproductdetails> </soap:body> </soap:envelope> Here is how the warehouse web service might format its REPLY message with the requested product information: <soap:envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:body> <getproductdetailsresponse xmlns="http://warehouse.example.com/ws"> <getproductdetailsresult> <productname>toptimate 3-Piece Set</productName> <productid>827635</productid> <description>3-piece luggage set. Black Polyester.</description> <price>96.50</price> <instock>true</instock> </getproductdetailsresult> </getproductdetailsresponse> </soap:body> </soap:envelope> SOAP short summary: SOAP stands for Simple Object Access Protocol SOAP is a communication protocol SOAP is for communication between applications SOAP is a format for sending messages SOAP is designed to communicate via Internet SOAP is platform independent SOAP is language independent SOAP is based on XML SOAP is simple and extensible SOAP allows you to get around firewalls SOAP will be developed as a W3C standard SOAP staat voor Simple Object Access Protocol. Het is een verzameling afspraken in een XMLschema gegoten en dus platform onafhankelijk. SOAP is onstaan in 1998, gelijktijdig met de XML standaard en streeft er eveneens naar een W3C standaard te worden. SOAP beschrijft de codering en uitwisseling van gestructureerde, getypte data. SOAP wordt gebruikt voor het uitwisselen van alle soorten gestructureerde data en dient als de standaard boodschap service. Een SOAP boodschap vereist minimum twee componenten: een envelope welke de data bevat en een body, de data zelf. Een derde en optionele component is een header, welke extra beschrijvende informatie over de boodschap of de data bevat of gewoonweg verwerkingsinstructies. 142
SOAP is een lichtgewicht communicatie protocol en kan gebruikt worden met alle protocols, maar werkt momenteel over http, aangezien http nu en in de nabije toekomst een goede standaard is voor informatie-uitwisseling. SOAP speelt ook een grote rol in de nieuwe.net toepassingen van MS voor internet applicaties. Samengevat is SOAP een XML based protocol om applicaties te laten communiceren over http. 14.3 XFORMS A new technology, XForms, is under development within the W3C and aims to combine XML and forms. The design goals of XForms meet the shortcomings of HTML forms point for point: Excellent XML integration (including XML Schema) It combines existing XML technologies. Rather than reinventing the wheel, XForms uses a number of existing XML technologies, such as XPath for addressing and calculating values, and XML Schema for defining data types. This has a dual benefit: ease of learning for people who already know these technologies, and the ability for implementors to use off-the-shelf components to build their systems. Provide commonly-requested features in a declarative way, including calculation and validation Strong separation of purpose from presentation Universal accessibility; XForms has been designed so that it will work equally well with accessible technologies (for instance for blind users) as with traditional visual browsers. It is device independent. The same form can be delivered without change to a traditional browser, a PDA, a mobile phone, a voice browser, and even some more exotic emerging clients such as an Instant Messenger. This greatly eases providing forms to a wide audience, since forms only need to be authored once. It is easier to author complicated forms. Forms are for collecting data, so it s not surprising that the most important concept in XForms is instance data, an internal representation of the data mapped to the familiar form controls. Instance data is based on XML and defined in terms of XPath s internal tree representation and processing of XML. One form control is not found in XForms an equivalent of the HTML <input type="hidden">, which authors frequently use to maintain state information on the client. XForms processing maintains instance data in memory, which makes it possible to store structured information rather than primitive name-value pairs. In essence, any instance data not mapped to a form control is hidden data. The XForms specification also provides sophisticated ways to combine form controls, including repeating lists such as found on a purchase order, and portions that show or hide based on various interactions. Since XForms technology reads and writes XML instance data, it enables existing investments to be leveraged as building blocks in a larger system more smoothly than with ordinary XHTML forms. Additionally, Web Services will increase the amount of information interchange over the Web, which will in turn increase the need for information to be entered through XForms. The advantages of XForms: Because XForms uses declarative markup to declare properties of values, and to build relationships between values, it is much easier for the author to create complicated, adaptive, forms, without having to resort to scripting. What can I do with XForms that I can t do with old HTML Forms? XForms can do everything that HTML Forms can do, and then some. In particular XForms lets you: Check data values while the user is typing them in. Indicate that certain fields are required, and that the form cannot be submitted without them. Submit forms data as XML. Integrate with Web services, for instance by using SOAP and XML RPC. Submit the same form to different servers (for instance a search string to different search engines). Save and restore values to and from a file. 143
Use the result of a submit as input to a further form. Get the initial data for a form from an external document. Calculate submitted values from other values. Constrain values in certain ways, such as requiring them to be in a certain range. Build shopping basket and wizard style forms without needing to resort to scripting. HTML Forms, introduced in 1993, were the basis of the e-commerce revolution. XForms is the new technology that replaces HTML Forms. The advantages of XForms include: It improves the user experience: XForms has been designed to allow much to be checked by the browser, such as types of fields being filled in, or that one date is later than another. This reduces the need for round trips to the server or for extensive script-based solutions, and improves the user experience by giving immediate feedback to what is being filled in. With XForms, the rules for describing and validating data are expressed in XML. It combines existing XML technologies: XForms uses a number of existing XML technologies, such as XPath for addressing and calculating values, and XML Schemas for defining data types. XForms separates the data logic of a form from its presentation. XForms Uses XML To Store And Transport Data. data submitted from the form, are transported over the internet using XML. All future browsers should support XForms. 14.4 SMIL The Synchronized Multimedia Integration Language (SMIL, pronounced smile ) enables simple authoring of interactive audiovisual presentations. SMIL is typically used for rich media /multimedia presentations which integrate streaming audio and video with images, text or any other media type. SMIL is an XML based, easy-to-learn HTML-like language, and many SMIL presentations are written using a simple text-editor. SMIL was created specifically to solve the problems of coordinating the display of a variety of media (multimedia) on Web sites. It allows authors to describe the temporal behavior of a multimedia presentation, associate hyperlinks with media objects and describe the layout of the presentation on a screen. By using a single time line for all of the media on a page their display can be properly time coordinated and synchronized. SMIL-Dateien künnen mit Java-Applets und -Servlets oder CGI-Skripten verknüpft werden und so beispielsweise auf eine Datenbank zugreifen. Als Dateierweiterung wird die Extension.smi oder.smil verwendet. SMIL wordt ook gebruikt in o.a. mobiele telefoons, meer bepaald voor MMS berichten op te stellen. What can SMIL do? SMIL can be used to create Internet or Intranet presentations SMIL can be used to create slide-show presentations SMIL has been described as the Internet answer to PowerPoint SMIL presentations can display multiple file types (text, video, audio...) SMIL presentations can display multiple files at the same time SMIL presentations can display files from multiple web servers SMIL presentations can contain links to other SMIL presentations SMIL presentations can contain control buttons (stop, start, next,...) SMIL has functions for defining sequences and duration of elements SMIL has functions for defining position and visibility of elements For describing the syntax of SMIL documents, this specification uses two notations: An augmented Backus-Naur form (BNF) similar to the one defined for HTTP 1.1 An XML Document Type Definition (DTD). Rather than being formulated as a standalone multimedia vocabulary (like SMIL 1.0), SMIL 2.0 syntax and semantics may be reused in other XML-based languages, as when SMIL components are used for integrating timing into exstensible HyperText Markup Language (XHTML) or Scalable Vector Graphics (SVG). 144
SMIL 2.0 is currently comprised of the following modules: Animation, Content Control, Layout, Linking (which supports XML Pointer Language, but does not require that browsers be able to process XPointers in SMIL 2.0 Uniform Resource Identifier attributes), Media Objects, Metainformation, Structure, Timing and Synchronization, Time Manipulation, and Transition Effects. SMIL 2.0 features are accessible via two language profiles. The SMIL 2.0 Language Profile contains support for all of the major SMIL 2.0 features including: animation, content control, layout, linking, media object, metainformation, structure, timing and transition effects. This profile is intended for Web clients that support direct playback from SMIL 2.0 markup. The SMIL 2.0 Basic Language Profile is a baseline subset of the SMIL 2.0 Language Profile that is intended for resource-constrained devices such as mobile phones and portable disc players. A SMIL document is similar in structure to an HTML document in that they are typically divided between a <head> section and a <body> section. The <head> section contains layout and metadata information. The <body> section contains the timing information, and is generally comprised of combinations of two main tags: parallel (<par>) and sequential (<seq>). Het uiterlijk van een SMIL-pagina wordt vormgegeven met style sheets, om precies te zijn met CSS Level 2. The language can also associate different media objects with different bandwidths. In december 2005 heeft het World Wide Web Consortium (W3C) een uitbreiding van SMIL voorgesteld: SMIL 2.1. SMIL 2.1 is een extensie op SMIL 2.0 (7 augustus 2001), waar aandacht is besteed aan enkele presentatie-aspecten, maar ook aan enkele ingrepen die toelaten de omvang van SMIL-documenten verder te beperken, waardoor minder bandbreedte wordt ingenomen bij transmissie. SMIL is een afkorting van Synchronized Multimedia Integration Language. De taal, die voor een flink deel door mensen van het Centrum voor Wiskunde en Informatica (CWI) is ontwikkeld, is bedoeld als stuctuur voor het weergeven van multimedia elementen op het net. Smil is in de eerste plaats ontwikkeld om de coordinatieproblemen op te lossen. Door middel van een tijdschaal (timeline) en opdeling van de pagina in statische en dynamische stukken, kan de oplevering van multimedia elementen worden gepland. Daarvoor zijn dan geen ingewikkelde scripts meer nodig. Wie vertrouwd is met de structuur van HTML komt veel oude bekenden tegen. Dat is logisch, want het is een taal die nauw samenwerkt met HTML. De basisstructuur van een SMIL-bestand is als volgt: <!DOCTYPE smil PUBLIC "-//W3C//DTD SMIL 1.0//EN" "http://www.w3.org/tr/rec-smil/smil10.dtd"> <smil> <head> </head> <body>... </body> </smil> SMIL werkt ook nauw samen met XML. Het uiterlijk van een SMIL-pagina wordt vormgegeven met style sheets, om precies te zijn met CSS Level 2. In SMIL wordt onderscheidt gemaakt in diverse soorten multimedia, namelijk: animation, audio, img, text, textstream, video. Door nu in je SMIL-bestand de soort multimedia, de plaats en de timing vast te leggen, kun je complexe pagina s relatief eenvoudig opmaken. Een multimedia element wordt omsloten door de <PAR> code. Zo laat je een geluid horen: 145
<PAR> <audio id="a" begin="6s" src="audio" /> </PAR> Iets ingewikkelder: eerst wordt het geluid geactiveerd en tijdens het afspelen wordt een afbeelding zichtbaar. Hiervoor gebruik je de <seq> (sequentie)-code: <seq> <par> <audio id="a" begin="6s"... /> <img begin="id(a)(4s)"... /> </par> </seq> En klaar is kees, door het lezen van deze prachtig geschreven initiatie tekst, kan u zich nu een SMIL pro noemen! 14.5 RDF The Resource Description Framework (RDF) is a language for representing information about resources in the World Wide Web. It is particularly intended for representing metadata about Web resources, such as the title, author, and modification date of a Web page, copyright and licensing information about a Web document, or the availability schedule for some shared resource. However, by generalizing the concept of a Web resource, RDF can also be used to represent information about things that can be identified on the Web, even when they cannot be directly retrieved on the Web. Examples include information about items available from on-line shopping facilities (e.g., information about specifications, prices, and availability), or the description of a Web user s preferences for information delivery. RDF is intended for situations in which this information needs to be processed by applications, rather than being only displayed to people. RDF provides a common framework for expressing this information so it can be exchanged between applications without loss of meaning. Since it is a common framework, application designers can leverage the availability of common RDF parsers and processing tools. The ability to exchange information between different applications means that the information may be made available to applications other than those for which it was originally created. RDF is based on the idea of identifying things using Web identifiers (called Uniform Resource Identifiers, or URIs), and describing resources in terms of simple properties and property values. This enables RDF to represent simple statements about resources as a graph of nodes and arcs representing the resources, and their properties and values. To make this discussion somewhat more concrete as soon as possible, the group of statements there is a Person identified by http://www.w3.org/people/em/contact#me, whose name is Eric Miller, whose email address is em@w3.org, and whose title is Dr. could be represented as the RDF graph (see Figure): 146
http://www.w3.org/2000/10/swap/pim/contact#person http://www.w3.org/1999/02/22-rdf-syntax-ns#type http://www.w3.org/people/em/contact#me http://www.w3.org/2000/10/swap/pim/contact#fullname Eric Miller http://www.w3.org/2000/10/swap/pim/contact#mailbox mailto:em@w3.org http://www.w3.org/2000/10/swap/pim/contact#personaltitle Dr. RDF is designed to provide an infrastructure supporting Meta Data across many WWW-based activities. RDF is the result of a number of Meta Data communities bringing together their needs to provide a robust and flexible architecture for supporting Meta Data on the Internet and the WWW. Example applications include site maps, content ratings, stream channel definitions, search engine data collection, digital library collections, and distributed authoring. RDF allows different application communities to define the Meta Data property set that best serves the needs of each community. RDF provides a uniform and interoperable means to exchange the Meta Data between programs and across the WWW. Furthermore, RDF provides a means for publishing both a human-readable and a machine-understandable definition of the property set itself. RDF uses XML as the transfer syntax in order to leverage other tools and code bases being built around XML. EXAMPLE: <?xml version="1.0"?> <rdf:rdf xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:contact="http://www.w3.org/2000/10/swap/pim/contact#"> <contact:person rdf:about="http://www.w3.org/people/em/contact#me"> <contact:fullname>eric Miller</contact:fullName> <contact:mailbox rdf:resource="mailto:em@w3.org"/> <contact:personaltitle>dr.</contact:personaltitle> </contact:person> </rdf:rdf> Het Resource Description Framework (RDF) is een gemeenschappelijke taal, gebaseerd op XML, waarin computers gegevens kunnen weergeven en uitwisselen, vergelijkbaar met HTML waarin computers hypertekst kunnen weergeven en uitwisselen. In feite is het alleen maar XML met enkele tips over welke bits gegevens bevatten en hoe je de betekenis van die gegevens kunt vinden. RDF wordt momenteel veelvuldig toegepast bij een bepaald soort webpublicaties: weblogs. Meer specifiek gebruiken ze een RDF-toepassing, RSS genaamd. Resource Description Framework, as its name implies, is a framework for describing and interchanging metadata. RDF provides a model for metadata, and a syntax so that independent parties can exchange it and use it. What it doesn t provide though is any Properties of its own. RDF 147
doesn t define Author or Title or Director or Business-Category. That would be a job for GOD, if there were one. Since there isn t, it s a job for everyone. RDF is carefully designed to have the following characteristics: Independence, Interchange, Scalability, Statements Can Be Resources, Values Can Be Resources, Properties are Resources 14.6 CML Chemical and biosciences data needs to be represented in a structured and unambiguous way, for computers to stored and read back again later and to attach meaning to that data. In the past many different structures were invented to do this, including comma separated variable files, binary files, and relational database schemas. One of the issues was that the formats were proprietary and this led to incompatibility between software systems. Extensible Markup Language (XML) was created to overcome this problem. The authority for XML is the World Wide Web Consortium (W3C), which has various working groups and makes specifications available at their web site. Chemical Markup Language brings the power of XML to the management of chemical information. In simple terms it is HTML with Molecules, but there is a great deal more. CML, and associated tools, allows for the conversion of current files without semantic loss, structured documents including chemical publications, and precise location of information within files. CML has been designed carefully so that it is as easy as possible for the average chemist to understand it. Like a lot of chemistry it s not trivial, but it s no harder than Cahn-Ingold-Prelog chirality rules, and easier than Huckel theory. It helps if you know something about HTML (the hypertext language of the WWW) and have tried to transfer data files between sites or programs. It s not magic, and doesn t do anything that isn t really just common sense, but it hides a lot of the hassle that we have at present. EXAMPLE <cml> <molecule id="m1"> <atomarray> <atom elementtype="n"/> <atom elementtype="o"/> </atomarray> </molecule> </cml> 148
A HTML 4 Block & Level Elements en Inline Elements Most HTML 4 elements permitted within the BODY are classified as either block&level elements or inline elements. Block&level elements typically contain inline elements and other block-level elements. When rendered visually, block&level elements usually begin on a new line. The following are defined as block&level elements in HTML 4: ADDRESS Address BLOCKQUOTE Block quotation CENTER Centered block DIR Directory list DIV Generic block-level container DL Definition list FIELDSET Form control group FORM Interactive form H1 Level-one heading H2 Level-two heading H3 Level-three heading H4 Level-four heading H5 Level-five heading H6 Level-six heading HR Horizontal rule ISINDEX Input prompt MENU Menu list NOFRAMES Frames alternate content NOSCRIPT Alternate script content OL Ordered list P Paragraph PRE Preformatted text TABLE Table UL Unordered list The following elements may also be considered block&level elements since they may contain block&level elements: DD Definition description DT Definition term FRAMESET Frameset LI List item TBODY Table body TD Table data cell TFOOT Table foot TH Table header cell THEAD Table head TR Table row The following elements may be used as either block&level elements or inline elements. If used as inline elements (e.g., within another inline element or a P), these elements should not contain any block-level elements. APPLET Java applet BUTTON Button DEL Deleted text IFRAME Inline frame INS Inserted text MAP Image map OBJECT Object SCRIPT Client-side script The following are defined as inline elements in HTML 4: A Anchor ABBR Abbreviation ACRONYM Acronym B Bold text BASEFONT Base font change BDO BiDi override BIG Large text BR Line break CITE Citation CODE Computer code DFN Defined term EM Emphasis FONT Font change I Italic text IMG Inline image INPUT Form input KBD Text to be input LABEL Form field label Q Short quotation S Strike-through text SAMP Sample output SELECT Option selector SMALL Small text SPAN Generic inline container STRIKE Strike-through text STRONG Strong emphasis SUB Subscript SUP Superscript TEXTAREA Multi-line text input TT Teletype text U Underlined text VAR Variable 149
B XML opgaven Opgave: XML, DTD en CSS 1. Maak een XML-bestand voor de reisgegegens. Elementen die moeten opgenomen worden per boeking zijn: de reisagent, land en regio van bestemming, klantnaam, aantal personen (jong en oud), datum van vertrek en datum van terugkomst. Voorzie bij boeking ook een verplicht attribuut van type ID. Vul in het XML-bestand een aantal gegevens in. 2. Maak een bijhorend Document Type Definition (DTD-)bestand. Voorzie hierin ook een ENTITY voor bijvoorbeeld een hoofding aan het document te geven. 3. Valideer je XML-bestand op http://www.stg.brown.edu/service/xmlvalid. Hiervoor moet je DTD wil intern zijn; vervang hiervoor de referentie naar het DTD-bestand in je XML-bestand door: <!DOCTYPE boekenkast [... ]> 4. Maak een bijhorend XML Schema bestand. Valideer je XML-bestand op basis van dit XML Schema bestand op http://tools.decisionsoft.com/schemavalidate/ 5. Maak een CSS bestand zodat de gegevens uit het XML bestand overzichtelijk getoond worden. Gebruik verschillende fontgroottes, kleuren (voor- en achtergrond) en plaats een aantal data-elementen naast elkaar. Opgave XPath en XQuery Vul de volgende queries in: (: alle bestemming-nodes van de reisen met als bestemming Frankrijk :) (: alle regio-nodes van de reizen met als bestemming Frankrijk :) (: alle reisgegevens van category berg :) (: alle descendants van reizen van category zee :) (: alle vertrek-nodes van reizen van category zon :) (: alle klantnamen (NIET de volledige klant-node) van reizen van category berg :) (: alle reisgegevens van de volgende zuster-nodes van reizen van category zee, maar enkel die van category zon:) (: klantnamen van volgende zuster-nodes van reizen van category zee, maar enkel die van category zon :) (: laatste reis van category berg :) (: alle klantnamen (NIET de klant-nodes) die de letters ers bevatten :) (: alle reisgegevens met vertrek in juli en augustus :) (: agent-nodes van reizen die in juli vertrekken :) (: het aantal kinderen van reizen die in augustus vertrekken of terugkomen :) (: HTML tabel met per rij klant,vertrekdatum en agent gesorteerd op vertrekdatum :) (: HTML tabel met per rij een volgnr,klant,regio en agent van de reizen met bestemming Frankrijk gesorteerd op regio :) Opgave XSL : gegeven een XML document. 1. Construeer een XSL stylesheet om de inhoud in HTML tabel vorm te tonen. 2. Voeg een filter toe om alleen de reizen met bestemming Frankrijk te tonen. 3. Voor vertrek in de maand mei, toon alleen het aantal jonge reizigers; voor vertrek in de maand juni, toon alleen het aantal oudere reizigers; voor vertrek in de maand juli, toon beide aantallen; in de andere gevallen, toon geen aantallen. 4. Sorteer op vertrekdatum en klantnaam. 150
Saxon java -jar saxon8.jar -t samples/data/books.xml samples/styles/books.xsl > boek.html java -cp saxon8.jar net.sf.saxon.query -t -s samples/data/books.xml samples/query/books-to-html.xq >temp.html Fop (instellen van FOP_HOME omgevingsvariabele) fop bestand.fo bestand.pdf fop -xml dok.xml -xsl dok.xsl -df dok.pdf Configuratie in /apache/conf/magic: #XML extensible Markup Language O string \<?xml text/xml 151
FACULTEIT INDUSTRIELE INGENIEURSWETENSCHAPPEN! CAMPUS DE NAYER! Het Semantisch Web Joost Vennekens Academiejaar 2014-2015
Inhoudsopgave 1 Het semantisch web 5 1.1 Het Resource Description Framework (RDF)..................... 6 1.1.1 Resources en URIs................................ 7 1.1.2 Drietallen..................................... 7 1.1.3 Letterlijke waardes (literals).......................... 9 1.1.4 Anonieme knopen................................ 9 1.1.5 Formaten voor RDF............................... 10 1.1.6 RDF/XML..................................... 11 1.2 Linked Data........................................ 14 1.3 Apache Jena....................................... 15 1.4 Labo............................................ 18 1.4.1 Info over jezelf.................................. 18 1.4.2 Info over je buurman.............................. 18 2 SPARQL 19 2.1 De basis.......................................... 19 2.2 Paden van eigenschappen............................... 22 2.3 In Jena.......................................... 23 2.4 Labo............................................ 25 2.4.1 DBPedia...................................... 25 2.4.2 Jena........................................ 25 3 RDF Schema 27 3.1 Structuur van RDFS................................... 27 3.2 Gebruik van RDFS.................................... 29 3.2.1 Communicatie.................................. 29 3.2.2 Inferentie..................................... 29 3.2.3 Validatie...................................... 29 3.2.4 Integratie..................................... 29 3.3 In Jena.......................................... 30 4 OWL 31 3
Het semantisch web 1 Het Semantisch Web is bedoeld om de opvolger te worden van het huidige World Wide Web. De voornaamste betrachting is om ervoor te zorgen dat er veel meer mogelijk wordt door een internet te bouwen dat niet langer uitsluitend voor mensenogen bedoeld is, maar waarop de informatie ook gemakkelijk door automatische tools verwerkt kan worden. Laten we deze noodzaak eens illustreren met een voorbeeldje. In de komische tv-serie The Big Bang Theory speelt het personage Sheldon een hoofdrol. In de serie is dit personage afkomstig uit Texas. Veronderstel nu dat we ons afvragen of de acteur die dit personage vertolkt misschien in het echt zelf ook uit Texas komt. Moesten we bijvoorbeeld beschikken over een relationele databank met daarin gegevens over acteurs (in de actor tabel) en de rollen die zij spelen in tv-series (in de cast tabel), dan zouden we onze nieuwsgierigheid kunnen bevredigen met een eenvoudige SQL-query: select birthplace from actor where actor_id = (select actor_id from cast where tvshow = "The Big Bang Theory" and role = "Sheldon Cooper"); Op het (huidige) WWW is het echter moeilijker om deze informatie terug te vinden. Dit komt doordat webpagina s vandaag de dag enkel maar bestaan uit tekst in een natuurlijke taal zoals Engels of Nederlands. Dit zorgt ervoor dat het overgrote deel van de informatie die in zo n webpagina vervat zit, niet begrepen kan worden door computerprogramma s. Het resultaat is dat het opvragen van informatie wel moet verlopen via de uiterst domme methode van de keyword search, waarbij enkel maar gekeken wordt of een bepaalde zoekterm zich toevallig letterlijk in een webpagina bevindt. Voor ons voorbeeld, zouden we bijvoorbeeld de zoekterm Big Bang Theory kunnen gebruiken. Dit zal ons een hoop pagina s opleveren, waarvan sommige over een natuurkundige theorie zullen gaan en andere over de tv-serie die we in gedachten hebben. We moeten dan handmatig één van de juiste pagina s kiezen, waarop we dan bijvoorbeeld volgende informatie aantreffen: Cast Johnny Galecki Jim Parsons Kaley Cuoco... Leonard Hofstadter Sheldon Cooper Penny Een computerprogramma ziet hier gewoon een <table> met daarin wat <tr>-en en <td>-en, maar wij kunnen uit deze tabel de informatie halen die we nodig hebben, namelijk dat de rol van Sheldon in The Big Bang Theory vertolkt wordt door ene Jim Parsons. Dit geeft ons dan een nieuwe zoekterm, die ons dan uiteindelijk in staat zal stellen om de informatie terug te vinden die we zoeken. 5
De bedoeling van het Semantisch Web is nu om een nieuwe versie van het WWW te bouwen, waarin dit soort van opzoekwerk geautomatiseerd kan worden. Het mag uit dit voorbeeld duidelijk zijn dat eenvoudige keyword search hiervoor niet langer zal volstaan. In plaats daarvan zal de vraag van de gebruiker gesteld moeten worden in een vorm die meer lijkt op het SQL-statement van hierboven. Daarnaast zal er echter ook een inspanning nodig zijn van de content providers die webpagina s op het Semantisch Web zetten: zij zullen dit niet langer (enkel maar) in een formaat doen dat bedoeld is om door mensen gelezen te worden, maar ze zullen dit (ook) in een gestructureerd, computerleesbaar formaat moeten doen. Hierdoor zal dus bijvoorbeeld de informatie dat Jim Parsons de rol van Sheldon Cooper speelt ook toegankelijk worden voor een computeralgoritme, en zal het Semantisch Web dus een functionaliteit kunnen vervullen die gelijkaardig is aan die van een databank. Een belangrijk verschilpunt met een gewone databank is en blijft natuurlijk wel de gedistribueerde aard van het web: in het Semantisch Web zal informatie van op verschillende webpagina s, geschreven door verschillende personen, gecombineerd moeten worden. We kunnen vanzelfsprekend niet verwachten dat alle personen die, bijvoorbeeld, informatie over tv-series online willen zetten, op voorhand even met elkaar gaan overleggen om een gezamenlijke databank-structuur af te spreken. Er is dus, met andere woorden, nood aan een manier op gegevens op te slaan, die: flexibel is, zodat elke gebruiker zijn eigen voorstellingswijze kan kiezen; algemeen is, zodat gegevens over eender welk onderwerp kunnen worden opgeslagen. toelaat dat gegevens gemakkelijk uitgewisseld kunnen worden; toelaat dat gegevens uit verschillende bronnen eenvoudig met elkaar gecombineerd kunnen worden. Hiervoor wordt RDF gebruikt. 1.1 Het Resource Description Framework (RDF) Het RDF formaat tracht als volgt aan de hierboven aangehaalde vereisten tegemoet te komen: Het is een tekstgebaseerd formaat: een RDF bestand bevat enkel maar ASCII tekst. Dit betekent dat gegevens kunnen uitgewisseld en bekeken worden zonder dat men zich zorgen hoeft te maken over binaire encoderingen. Een RDF bestand bestaat uit een verzameling feiten. Dit betekent dat verschillende RDF bestanden zonder problemen samengevoegd kunnen worden, door ze gewoon achter elkaar te plakken. Zo n samengevoegd bestand bevat dan gewoon al de feiten die in beide afzonderlijke bestanden voorkwamen. RDF maakt gebruik van naamruimtes, zodat gegevens uit verschillende bronnen gecombineerd kunnen worden, zonder het risico op name clashes, waarbij dezelfde naam door verschillende bronnen in een andere betekenis gebruikt zou worden. Zoals al aangegeven door de naam van dit formaat (Resource Description Framework), dient RDF voor het beschrijven van resources. De volgende sectie gaat dieper in op wat deze resources juist zijn en hoe we ze kunnen identificeren.
1.1.1 Resources en URIs Net zoals een databank, bevat een RDF bestand informatie over entiteiten. En, net zoals in een databank, wordt elke entiteit geïdentificeerd door een primaire sleutel. In RDF terminologie wordt een entiteit een resource genoemd en de primaire sleutel van zo n resource heet een URI (Uniform Resource Identifier). Een speciaal geval van zo n URI is de ons allemaal welbekende URL (Uniform Resource Locator). Een URL is niets anders dan de URI van een resource die toevallig een webpagina is. Aangezien een webpagina gedownload kan worden via het internet, volstaat het om diens primaire sleutel aan een webbrowser te geven, om de pagina te zien te krijgen. Andere resources, zoals bijvoorbeeld de actrice Kaley Cuoco, kunnen natuurlijk niet zomaar gedownload worden via het internet, maar we kunnen er wel informatie over opslaan. De URI die verwijst naar deze actrice is dus geen URL, aangezien deze URI een resource identificeert die zich buiten het internet bevindt. Net als primaire sleutels in een databank, moeten URIs natuurlijk uniek zijn. Met andere woorden, als we een URI gebruiken om te verwijzen naar de actrice Kaley Cuoco, dan moeten we er zeker van kunnen zijn dat er op het hele internet nergens iemand anders dezelfde URI gebruikt om naar een andere resource te verwijzen. Om dit te verzekeren, worden URIs geconstrueerd volgens hetzelfde stramien als URLs. Er is immers al een globale structuur opgezet, die verzekert dat de controle over elke bepaalde URL steeds bij slechts één instantie ligt. Bijvoorbeeld, de auteur van deze tekst heeft, als enige op de wereld, controle over deze URL: http://www.cs.kuleuven.be/ joost. Als ik zelf een URI wil verzinnen, kan ik deze dus best laten beginnen met deze URL, en dan ben ik zeker dat niemand anders ooit toevallig dezelfde URI zal gebruiken. Dit wordt dan bijvoorbeeld zoiets: http://www.cs.kuleuven.be/~joost/resource#kaley_cuoco Deze URI kan ik nu gebruiken om naar hartelust informatie over Kaley Cuoco op te slaan. Deze informatie zal echter alleen maar bruikbaar zijn voor mensen die deze URI ook kennen. Daarom doe ik er misschien beter aan om niet mijn eigen URI te verzinnen, maar om een bestaande URI te herbruiken. Als we even rondzoeken op het web, merken we immers dat er al een URI bestaat die naar precies deze actrice verwijst, namelijk deze: http://dbpedia.org/resource/kaley_cuoco We zien hier meteen ook dat er dus toch een verschil is tussen primaire sleutels in een databank en URIs: hoewel elke URI steeds een naam is voor een unieke resource, kan dezelfde resource wel verschillende URIs hebben. Dit is ook logisch, aangezien het omgekeerde onmogelijk af te dwingen zou zijn doorheen het ganse internet. Software die gebruik maakt van RDF zal dus in staat moeten zijn om in bepaalde gevallen te concluderen dat twee verschillende URIs toch naar dezelfde resource verwijzen, net zoals wij bijvoorbeeld in een dagdagelijks gesprek plots tot de conclusie kunnen komen dat de actrice Kaley Cuoco en Penny uit de Big Bang Theory ook gewoon maar twee verschillende namen voor dezelfde vrouw zijn. 1.1.2 Drietallen De atomaire feiten in een RDF bestand zijn zogenaamde drietallen of RDF triples. Zo n drietal bestaat uit: 1. een entiteit waarover het feit gaat; deze wordt het subject of onderwerp van het feit genoemd. 2. een relatie die deze entiteit heeft met een andere entiteit; dit wordt de relatie of het predikaat van het feit genoemd.
3. de andere entiteit in kwestie; dit wordt het object of (lijdend) voorwerp genoemd. Merk op dat er in RDF dus enkel maar sprake is van binaire relaties. In een RDF bestand, gaan we alledrie deze componenten dus ook het predikaat! voorstellen door middel van een URI. Dit is geen toeval: het is immers zo dat RDF geen strikt onderscheid maakt tussen entiteiten en relaties. Predikaten worden dus ook gewoon gezien als een soort van resources en, meer nog, dezelfde URI kan in verschillende drietallen nu eens de rol van subject/object spelen en dan weer de rol van predikaat. Hier is een voorbeeld van een RDF drietal: http://dbpedia.org/resource/kaley_cuoco http://dbpedia.org/property/portrayer http://dbpedia.org/resource/penny_(the_big_bang_theory) Dit drietal vertelt ons dat Kaley Cuoco (onderwerp) de rol speelt van (predikaat) Penny uit The Big Bang Theory (voorwerp). Het valt hierbij op dat het gebruik van URIs zorgt voor een notatie die nogal veel schrijfwerk vereist. Om hier een mouw aan te passen, gebruiken we, net zoals in XML, een notatie waarbij naamruimtes kunnen afgekort worden door een prefix. Prefix dbpedia dbprop jve Naamruimte http://dbpedia.org/resource http://dbpedia.org/property http://www.cs.kuleuven.be/ joost/resource Met deze prefixen, kunnen we ons drietal nu wat korter noteren: dbpedia:kaley_cuoco dbprop:portrayer dbpedia:penny_(the_big_bang_theory) RDF bestanden worden vaak visueel voorgesteld door een gelabelde grafe. Het bovenstaande drietal zou er dan bijvoorbeeld zo uitzien: dbpedia:kaley_cuoco dbprop:portrayer dbpedia:penny_(the_big_bang_theory) Het bovenstaande drietal is te vinden in de RDF databanken (triple store) van DBPedia. Het kan echter probleemloos gecombineerd worden met feiten uit een andere RDF databank, zoals bijvoorbeeld: jve:joost_vennekens jve:fan dbpedia:kaley_cuoco Deze nieuwe informatie kan nu gewoon bijgetekend worden bij de grafe die we al hadden. Op deze manier kan informatie uit verschillende bronnen en over URIs uit verschillende naamruimten probleemloos gecombineerd worden. dbpedia:kaley_cuoco dbprop:portrayer dbpedia:penny_(the_big_bang_theory) jve:fan jve:joost_vennekens
1.1.3 Letterlijke waardes (literals) We hebben tot dusver al gezien hoe we RDF drietallen kunnen gebruiken om relaties tussen entiteiten te beschrijven. Daarnaast is het natuurlijk ook mogelijk om informatie bij te houden over de attributen van een entiteit. Ook dit kan met een drietal. Het verschil is dat het voorwerp van zo n drietal nu niet langer een naam voor een resource zal zijn, maar gewoon een letterlijke waarde, die enkel maar zichzelf voorstelt. In dit geval, noemen we het predikaat van het feit ook wel een eigenschap (property) en het voorwerp de waarde van deze eigenschap. We herkennen zo n letterlijke waarde aan het feit dat ze tussen aanhalingstekens staat. Zo bevat DBPedia bijvoorbeeld volgende informatie over een bijnaam van Penny uit The Big Bang Theory: dbpedia:penny_(the_big_bang_theory) dbpprop:nickname "Bestie" Merk op zo n letterlijke waarde natuurlijk niet in een naamruimte zit, aangezien ze geen naam is maar een waarde. In de grafische voorstelling van een RDF databank, worden letterlijke waardes in een rechthoek getekend. dbpedia:kaley_cuoco dbprop:portrayer dbpedia:penny_(the_big_bang_theory) jve:fan dbpprop:nickname jve:joost_vennekens Bestie Normaalgezien zijn letterlijke waardes gewoon strings. Het is echter ook mogelijk om een letterlijke waarde te annoteren met een bepaald datatype. Dit gebeurt door het datatype in kwestie er achter te schrijven, gescheiden door het verbindingsteken ^^. Bijvoorbeeld: dbpedia:kaley_cuoco dbprop:dataofbirth "1985"^^<http://www.w3.org/2001/XMLSchema#integer> Hier wordt gedeclareerd dat 1985 een letterlijke waarde is van het XSD datatype integer. Dit betekent bijvoorbeeld dat deze waarde hetzelfde is als deze letterlijke waarde: "0001985"^^<http://www.w3.org/2001/XMLSchema#integer> Zonder het bijhorende datatype, zou dit niet het geval zijn. Naast annotaties die het datatype van een letterlijke waarde specifiëren, zijn er ook annotaties mogelijk die aangeven in welke taal een string geschreven is. Hiervoor wordt dan het scheidingsteken @ gebruikt: jve:joost_vennekens dbpedia:teacher "industriele ingenieurswetenschappen"@nl 1.1.4 Anonieme knopen Zoals we al gezien hebben, kunnen in RDF enkel maar binaire relaties voorgesteld worden. Dit is gelukkig geen echte beperking, aangezien we ook relaties met meer argumenten kunnen encoderen door het invoeren van bijkomende entiteiten. We hebben hier al een voorbeeld van gezien, namelijk met de relatie dat een acteur een rol speelt in een serie. In een relationele databank zouden we dit voorstellen door een drievoudige relatie:
Acteur Serie Rol Jim Parsons The Big Bang Theory Sheldon Kaley Cuoco The Big Bang Theory Penny Kaley Cuoco 8 Simple Rules Karin In RDF gaan we dit oplossen door twee entiteiten samen te nemen tot een nieuwe entiteit, zoals we bijvoorbeeld The Big Bang Theory en Penny hebben samengenomen tot Penny_(The_Big_Bang_Theory). dbpedia:kaley_cuoco dbprop:portrayer dbprop:name dbpedia:penny_(the_big_bang_theory) dbpedia-owl:series Penny dbpedia:the_big_bang_theory In dit geval heeft deze nieuwe entiteit ook een eigen naam gekregen in de dbpedia naamruimte. Vaak is het echter niet nodig om zo n nieuwe entiteit ook een globale naam te geven. In dat geval kunnen we een anonieme knoop (of blank node) gebruiken. Zo n anonieme knoop zit in een namaak-naamruimte met als naam een enkele underscore _. Zo zegt volgende database dat Kaley Cuoco iemand speelt die als naam Penny heeft, zonder dat die iemand een eigen URI krijgt. dbpedia:kaley_cuoco dbprop:portrayer _:anon _:anon dbpprop:name "Penny" Zoals we in dit voorbeeld zien, kan de anonieme knoop _:anon wel herbruikt worden binnen hetzelfde RDF bestand, maar niet daarbuiten. Het is, met andere woorden, een lokale naam en geen globale. 1.1.5 Formaten voor RDF Als we een verzameling RDF drietallen willen neerschrijven voor gebruik door een computerprogramma, dan zullen we dit een bepaald formaat moeten doen. Er zijn hiervoor een aantal opties beschikbaar. Het N3 formaat. Dit is het meest mens-vriendelijke formaat. Een RDF drietal ziet er hier gewoon uit zoals in onze voorbeelden tot dusver, met dan dat verschil dat elk drietal beëindigd moet worden met een punt. Daarnaast is er ook de notatie @prefix om de prefix van een naamruimte te definiëren. De bestandsextensie van het N3 formaat is *.n3. @prefix dbpedia: <http://dbpedia.org/resource/>. @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>. @prefix foaf: <http://xmlns.com/foaf/0.1/>. dbpedia:howard_wolowitz rdf:type foaf:person. dbpedia:howard_wolowitz dbpprop:religion dbpedia:judaism. dbpedia:howard_wolowitz dbpprop:occupation dbpedia:aerospace_engineer. dbpedia:howard_wolowitz dbpprop:nickname "Froot Loops"@en. dbpedia:howard_wolowitz dbpprop:nickname "Wolowizard"@en. Om schrijfwerk te besparen, zijn er nog twee afkortingen mogelijk: de puntkomma mag gebruikt worden om twee opeenvolgende drietallen met hetzelfde onderwerp te scheiden, en de komma mag gebruikt worden om twee opeenvolgende drietallen met hetzelfde onderwerp én dezelfde relatie te scheiden. Zo kan de bovenstaande verzameling drietallen herschreven worden tot:
dbpedia:howard_wolowitz rdf:type foaf:person; dbpprop:religion dbpedia:judaism; dbpprop:occupation dbpedia:aerospace_engineer; dbpprop:nickname "Froot Loops"@en, "Wolowizard"@en. 1.1.6 RDF/XML In de praktijk zijn de meeste RDF bestanden in de eerste plaats bedoeld om door computerprogramma s gelezen en verwerkt te worden. Hiervoor wordt dan meestal niet het bovenstaande N3 formaat gebruikt, maar wel een XML-gebaseerd formaat. In deze voorstelling, bestaat een RDF bestand uit een <RDF> wortelelement. Een drietal wordt dan voorgesteld door een <Description> element, dat een kind is van dit wortelelement. Het onderwerp van het drietal wordt gegeven als de waarde van het attribuut about van dit <Description> element. Het <Description> element zelf heeft precies één kind, namelijk een XML element dat de relatie van het drietal voorstelt. Het voorwerp, tot slot, wordt gegeven in het attribuut resource van dit element. Samengevat, ziet zo n bestand er dus als volgt uit: <RDF> <Description about="onderwerp"> <relatie resource="voorwerp" /> </Description>... </RDF> In de praktijk is het natuurlijk nog net iets ingewikkelder dan dit, omdat zowel de RDF-tags (RDF, Description,... ) zelf, als de URIs waarover de opgeslagen informatie gaat, in een bepaalde XML naamruimte moeten zitten. Een volledige voorbeeld zal er dus als volgt uitzien: <?xml version="1.0" encoding="utf-8"?> <rdf:rdf xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:dbpprop="http://dbpedia.org/property/"> <rdf:description rdf:about="http://dbpedia.org/resource/howard_wolowitz"> <rdf:type rdf:resource="http://xmlns.com/foaf/0.1/person" /> </rdf:description> <rdf:description rdf:about="http://dbpedia.org/resource/howard_wolowitz"> <dbpprop:religion rdf:resource="http://dbpedia.org/resource/judaism" /> </rdf:description> <rdf:description rdf:about="http://dbpedia.org/resource/howard_wolowitz"> <dbpprop:occupation rdf:resource="http://dbpedia.org/resource/aerospace_engineer" /> </rdf:description>... </rdf:rdf> De XML voorstelling van een letterlijke waarde ziet er een beetje anders uit. In plaats van een attribuut resource="uri" toe te voegen aan het XML-element dat de relatie voorstelt, komt een letterlijke waarde gewoon als inhoud van dit XML-element:
<Description about="onderwerp"> <relatie>letterlijke waarde</relatie> </Description> Als de letterlijke waarde een type (^^type) of een taal (@taal) heeft, kan dit worden opgenomen door een attribuut rdf:datatype of xml:lang bij het <relatie>-element. We kunnen de XML versie van ons voorbeeld dus vervolledigen met volgende twee drietallen: <rdf:description rdf:about="http://dbpedia.org/resource/howard_wolowitz"> <dbpprop:nickname xml:lang="en">froot Loops</dbpprop:nickname> </rdf:description> <rdf:description rdf:about="http://dbpedia.org/resource/howard_wolowitz"> <dbpprop:nickname xml:lang="en">wolowizard</dbpprop:nickname> </rdf:description> Het is opnieuw mogelijk om verschillende drietallen met hetzelfde onderwerp samen te nemen. <?xml version="1.0" encoding="utf-8"?> <rdf:rdf xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:dbpprop="http://dbpedia.org/property/"> <rdf:description rdf:about="http://dbpedia.org/resource/howard_wolowitz"> <rdf:type rdf:resource="http://xmlns.com/foaf/0.1/person" /> <dbpprop:religion rdf:resource="http://dbpedia.org/resource/judaism" /> <dbpprop:occupation rdf:resource="http://dbpedia.org/resource/aerospace_engineer" /> <dbpprop:nickname xml:lang="en">froot Loops</dbpprop:nickname> <dbpprop:nickname xml:lang="en">wolowizard</dbpprop:nickname> </rdf:description> </rdf:rdf> Laten we nu nog wat extra informatie toevoegen, namelijk het feit dat de acteur Simon Helberg dit personage speelt. Dit kan natuurlijk door gewoon een tweede <Description> element toe te voegen. <rdf:rdf...> <rdf:description rdf:about="http://dbpedia.org/resource/simon_helberg"> <dbprop:portrayer rdf:resource="http://dbpedia.org/resource/howard_wolowitz" /> </rdf:description> <rdf:description rdf:about="http://dbpedia.org/resource/howard_wolowitz"> <dbpprop:occupation rdf:resource="http://dbpedia.org/resource/aerospace_engineer" />... </rdf:description> </rdf:rdf> Hier kunnen we dus zien dat Simon Helberg een personage speelt dat Lucht- en Ruimtevaart Ingenieur is, doordat de URI van Howard Wolowitz deze twee feiten met elkaar verbindt. Er is hiervoor ook een kortere notatie toegestaan, waarbij we de informatie over Howard Wolowitz rechtstreeks in de beschrijving van Simon Helberg plaatsen:
<rdf:rdf...> <rdf:description rdf:about="http://dbpedia.org/resource/simon_helberg"> <dbpprop:portrayer> <rdf:description rdf:about="http://dbpedia.org/resource/howard_wolowitz"> <dbpprop:occupation rdf:resource="http://dbpedia.org/resource/aerospace_engineer" />... </rdf:description> </dbpprop:portrayer> </rdf:description> </rdf:rdf> Deze notatie wordt ook wel striped of gestreepte notatie genoemd, omdat er hier steeds een <Description> element wordt afgewisseld met een relatie. Deze notatie geeft aanleiding tot verschillende manieren om dezelfde grafe-structuur te serializeren. Veronderstel bijvoorbeeld dat het volgende seizoen van The Big Bang Theory een verhaallijn zou bevatten waarin Penny een doctoraat gaat maken met de auteur van deze tekst als promotor. dbpedia:kaley_cuoco jve:fan jve:joost_vennekens dbprop:portrayer jve:promotor dbpedia:penny_(the_big_bang_theory) We kunnen deze grafe nu op verschillende manieren serializeren in de streepjesnotatie, afhankelijk van welk startpunt we nemen. Twee voorbeelden (in afgekorte notatie): <Description about="joost"> <fan> <Description about="kaley"> <portrayer> <Description about="penny"> <promotor resource="joost" /> </Description> </portrayer> </Description> </fan> </Description> <Description about="kaley"> <portrayer> <Description about="penny"> <promotor> <Description about="joost"> <fan resource="kaley" /> Daarnaast blijft het natuurlijk ook nog </Description> </portrayer> </Description> </fan> </Description> steeds mogelijk om gewoon de drie drietallen naast elkaar te beschrijven. Laat ons deze twee
<Description about="kaley"> <portrayer resource="penny" /> </Description> <Description about="penny"> opties ook eens naast elkaar zetten: <promotor resource="joost"> </Description> <Description about="joost"> <fan resource="kaley" /> </Description> <Description about="kaley"> <portrayer> <Description about="penny"> <promotor> <Description about="joost"> <fan resource="kaley" /> </Description> </portrayer> </Description> </fan> </Description> Een voordeel van de gestreepte notatie is dat we hierin gemakkelijk anonieme knopen kunnen gebruiken, door gewoon het attribuut rdf:about van een genest <description> element weg te laten. Vanzelfsprekend heeft deze manier om anonieme knopen te noteren wel haar beperkingen, aangezien we een anonieme knoop nu dus helemaal geen naam meer geven, zelfs geen lokale, en we er dus niet naar kunnen terugverwijzen. Dit zal een probleem zijn als dezelfde anonieme knoop bijvoorbeeld het voorwerp moet zijn van verschillende drietallen, of wanneer we zo n anonieme knoop zouden willen kiezen als startpunt voor het serialiseren van een lus in de grafe. Daarom bestaat er ook een manier om een anonieme knoop toch een lokale naam te geven. Dit gebeurt door het attribuut nodeid te gebruiken in plaats van het attribuut about of het attribuut resource. <rdf:description rdf:nodeid="blank1"> <dbpprop:portrayer> <rdf:description rdf:about="http://dbpedia.org/resource/penny_(the_big_bang_theory)"> <jve:promotor> <rdf:description rdf:about="http://cs.kuleuven.be/~joost/joost_vennekens"> <jve:fan nodeid="blank1" /> </rdf:description> </dbpprop:portrayer> </rdf:description> </jve:fan> </rdf:description> 1.2 Linked Data Tegenwoordig spreekt men vaak over Linked Data. Met dit modewoord wordt eigenlijk iets heel eenvoudigs bedoeld. We weten al dat in een normaal RDF bestand alle resources geïdentificeerd worden door een URI, die eruit ziet als een URL. In tegenstelling tot een URL, is een URI gewoon een naam en hoeft er op de overeenkomstige locatie niets te vinden te zijn. Linked Data houdt nu eenvoudigweg in dat we toch gaan zorgen dat alle URIs die we
introduceren overeenkomen met een URL waarop er informatie te vinden is over de resource die door de URI geïdentificeerd wordt. Als je bijvoorbeeld de volgende URI intikt in een webbrowser: http://dbpedia.org/resource/howard_wolowitz dan zal je effectief een webpagina zien verschijnen met daarop informatie over het personage in kwestie. Onderaan de pagina zal je ook een link terugvinden waarmee deze informatie o.a. in RDF/XML of N3 formaat gedownload kan worden. Deze bestanden bevinden zich op DBPedia altijd op een vaste locatie, die je kan afleiden uit de URI door resource te veranderen in data en een bestandsextensie naar keuze (.rdf of.n3) toe te voegen: http://dbpedia.org/data/howard_wolowitz.rdf Als je zo n bestand download, zal je daarin weer andere URIs terugvinden, en ook met deze URIs komen dan weer URLs overeen waarop je over die concepten dan weer informatie kan vinden. Op deze manier wordt het hele web dus een gedistribueerde databank, waarvan je deeltjes kan downloaden naar gelang je ze nodig hebt. 1.3 Apache Jena Een standaard bibliotheek voor het werken met RDF bestanden in Java is het Jena pakket van Apache. Dit valt te downloaden van http://jena.apache.org/. Compileren en uitvoeren van programma s die gebruik maken van dit pakket vereist simpelweg dat een aantal *.jar archieven worden toegevoegd aan het classpath waarin de java omgeving zijn pakketen gaat zoeken. Van op de commandline kan dit bijvoorbeeld zo: javac -cp ".:apache-jena-2.10.0/lib/*" Test.java java -cp ".:apache-jena-2.10.0/lib/*" Test Indien je gebruikmaakt van een IDE, moeten deze bibliotheken in de configuratie hiervan ook ergens worden toegevoegd. Deze bibliotheek bevat een klasse Model die RDF databank voorstelt. Een dergelijk model moet worden aangemaakt dmv. een ModelFactory en kan dan gevuld worden met de gegevens uit één of meerdere bestanden. Het volgend voorbeeld leest een RDF bestand in, en print dan het resulterende model naar standaard uitvoer. import com.hp. hpl. jena. rdf. model. * ; import java. u t i l. * ; import java. io. * ; public class Test { prviate static final String bestandsnaam = " test. rdf " ; public static void main ( String args [ ] ) { Model model = ModelFactory. createdefaultmodel ( ) ; model. read ( bestandsnaam ) ; // write i t t o standard out model. write ( System. out ) ;
} } Een Model bevat een aantal Resources, die kunnen worden opgevraagd op basis van hun URI: Resource howard = model. getresource ( " http :// dbpedia. org/resource/howard_wolowitz " ) ; Naast Resources heeft een Model ook Properties (= predikaten), die eveneens op basis van URI worden opgevraagd: Property nickname = model. getproperty ( " http :// dbpedia. org/property/nickname" ) ; De verschillende eigenschappen van een Resource kunnen nu worden opgevraagd met zijn methode Statement getproperty ( Property p ) In tegenstelling tot wat je op basis van de naam van deze methode zou verwachten, geeft ze dus géén Property terug, maar wel een Statement waarmee de opgegeven eigenschap van de resource gedefinieerd wordt. Met andere woorden, het resultaat van r.getproperty(p) is een RDF drietal, waarvan r het onderwerp is en p de relatie. Resource howard = model. getresource ( " http :// dbpedia. org/resource/howard_wolowitz " ) ; Property nickname = model. getproperty ( " http :// dbpedia. org/property/nickname" ) ; Statement d r i e t a l = howard. getproperty ( nickname ) ; De drie onderdelen van een Statement kunnen worden opgevraagd met volgende methodes: Resource getsubject ( ) Property getpredicate ( ) RDFNode getobject ( ) De klasse RDFNode heeft natuurlijk twee subklassen, namelijk Resource en Literal. Een Literal stelt een letterlijke waarde voor, en heeft verschillende methodes om deze waarde te (proberen) op te vragen als een of ander Java datatype (getstring(), getint(), getfloat()). Statement d r i e t a l = howard. getproperty ( nickname ) ; RDFNode voorwerp = d r i e t a l. getobject ( ) ; String bijnaam = ( ( L i t e r a l ) voorwerp ). getstring ( ) ) ; De klasse Statement biedt ook een aantal convenience methods aan die ons toelaten deze expliciete typecast te vermijden. L i t e r a l l = d r i e t a l. g e t L i t e r a l ( ) ; String n = d r i e t a l. getstring ( ) ; Deze methodes proberen dus telkens het voorwerp van het RDF drietal op te vragen in een bepaald formaat. Het volgend voorbeeld laat zien hoe we kunnen omgaan met een voorwerp dat een Resource is in plaats van een letterlijke waarde. Resource simon = model. getresource ( " http :// dbpedia. org/resource/simon_helberg " ) ; Property portrayer = model. getproperty ( " http :// dbpedia. org/property/ portrayer " ) ; Resource howard = simon. getproperty ( portrayer ). getresource ( ) ; Property nickname = model. getproperty ( " http :// dbpedia. org/property/nickname" ) ; String bijnaam = howard. getproperty ( nickname ). getstring ( ) ; Tot dusver hebben we telkens maar één bijnaam opgevraagd, ook al zitten er in ons RDF bestand eigenlijk twee. Het effect is dat we willekeurig één van deze twee te zien krijgen. Als we alle bijnamen willen overlopen, moeten we in plaats van de methode getproperty
gebruik maken van listproperties. Het resultaat hiervan is een iterator die ons toelaat om te itereren over alle Statements met het juiste onderwerp en predikaat. Resource howard = model. getresource ( " http :// dbpedia. org/resource/howard_wolowitz " ) ; Property nickname = model. getproperty ( " http :// dbpedia. org/property/nickname" ) ; Iterator <Statement> i t = howard. l i s t P r o p e r t i e s ( nickname ) ; while ( i t. hasnext ( ) ) { Statement d r i e t a l = ( Statement ) i t. next ( ) ; String bijnaam = d r i e t a l. getstring ( ) ; System. out. println ( "Een bijnaam : " + bijnaam ) ; } Tot dusver hebben we het Model steeds gevuld met data uit één lokaal bestand. Het is echter ook mogelijk om een meerdere bestanden te laden in hetzelfde model, en deze hoeven zich ook niet lokaal te bevinden. In onderstaand voorbeeld combineren we gegevens uit twee verschillende RDF bestanden op het internet, om de Nederlandse naam voor het beroep van een persoon (bv. Leonard_Hofstadter) te ontdekken. We maken in dit voorbeeld ook gebruik van de methode getlanguage() uit de klasse Literal, waarmee de taal van een letterlijke waarde kan worden opgevraagd (zoals gegeven door het lang-attribuut in XML, of door de "string"@taal notatie in N3). public class Test { private static String changeuri ( String o r i g ) { return orig. replace ( "/resource/", "/data/" ) + ". rdf " ; } } public static void main ( String args [ ] ) { Model model = ModelFactory. createdefaultmodel ( ) ; String URI = " http ://dbpedia. org/resource/" + args [ 0 ] ; model. read ( changeuri ( URI ) ) ; Resource persoon = model. getresource ( URI ) ; Property occupation = model. getproperty ( " http :// dbpedia. org/property/occupation " ) ; Resource beroep = persoon. getproperty ( occupation ). getresource ( ) ; model. read ( changeuri ( beroep. tostring ( ) ) ) ; Property label = model. getproperty ( " http ://www.w3. org/2000/01/rdf schema#label " ) ; Iterator <Statement> i t = beroep. l i s t P r o p e r t i e s ( label ) ; Statement s ; String lang ; while ( i t. hasnext ( ) ) { s = i t. next ( ) ; lang = s. g e t L i t e r a l ( ). getlanguage ( ) ; i f ( lang. equals ( " nl " ) ) System. out. println ( s. getstring ( ) ) ; }
1.4 Labo 1.4.1 Info over jezelf Maak een RDF/XML bestand met daarin informatie over jezelf. Geef jezelf daarvoor een URI van volgende vorm: http://chaplin.denayer.wenk.be/ exxx We gebruiken een aantal relaties die allemaal in de naamruimte http://www.w3.org/2006/vcard/ns# zitten: Relatie VCard fn adr locality streetaddress email koppelt een persoon aan een naamkaartje geeft de naam van de persoon over wie het naamkaartje gaat geeft het adres dat bij een naamkaartje hoort geeft de gemeente die bij een adres hoort geeft de straatnaam en huisnummer die bij een adres hoort geeft het emailadres dat bij een naamkaartje hoort Om gemeentes voor te stellen, maken we geen gebruik van letterlijke waardes, maar wel van een dbpedia URI, zoals bijvoorbeeld: http://dbpedia.org/resource/sint-katelijne-waver (Als er toevallig over jouw gemeente geen informatie op DBPedia staat, zoek dan een naburige gemeente waarvoor dit wel het geval is.) Je kan je resulterende RDF bestand laten controleren (en trouwens ook visualiseren) door: http://www.w3.org/rdf/validator/ Als alles klopt, zet je bestand dan online op volgende locatie: http://chaplin.denayer.wenk.be/ exxx/data.rdf 1.4.2 Info over je buurman Voeg aan het bestand een bijkomend drietal toe, dat aangeeft wie in dit labo jouw buurman is. Gebruik hiervoor de relatie: http://chaplin.denayer.wenk.be/relatie/buurman Schrijf nu een Apache Jerna programma dat uit jouw eigen RDF bestand en dat van je collega s volgende informatie ophaalt: De URI van je eigen gemeente. De URI van je buurman. De naam van je buurman. Schrijf tot slot een programma dat ook informatie van DBPedia kan combineren met jouw eigen RDF bestanden om volgende informatie op te vragen: De burgemeester van jouw eigen gemeente. De burgemeester van de gemeente van jouw buurman.
SPARQL 2 Zoals in het vorig hoofdstuk gezien, kunnen we gegevens opvragen uit een (combinatie van) RDF-bestand(en), door de bijhorende grafe te doorlopen met een Java programma. Dit is natuurlijk een repetitieve taak. Om niet voor elke vraag die we hebben een nieuwe Java programma te moeten schrijven, kunnen we gebruik maken van SPARQL. Deze afkorting is een recursief acroniem voor SPARQL Protocol and RDF Query Language. Het is essentieel een taal die toelaat om patronen te beschrijven, die dan kunnen opgezocht worden in RDF grafes. 2.1 De basis Eén van de eerste dingen die we ons in het hoofdstuk rond RDF hebben afgevraagd, was welke acteur de rol van Sheldon Cooper speelt. Hier waren we dus op zoek naar volgend patroon in een RDF grafe: dbprop:portrayer dbpedia:sheldon_cooper? Als we in een concrete RDF-grafe dit patroon kunnen terugvinden, dan vinden we het antwoord dat we zoeken op de plaats van het vraagteken. In SPARQL beschrijven we deze zoekopdracht als volgt: select?x where { dbpedia:sheldon_cooper dbpprop:portrayer?x. } In het where-gedeelte beschrijven we het patroon dat we willen zoeken. Hierbij is?x een plaatshouder, die zal overeenkomen met eender welke knoop. In het select-stuk specifiëren we dan welke van plaatshouders in het patroon het antwoord bevat waarnaar we op zoek zijn. Normaalgezien begint een SPARQL query ook met het declareren van een aantal naamruimtes: prefix dbpedia: <http://dbpedia.org/resource/> prefix dbpprop: <http://dbpedia.org/property/> select?x where { dbpedia:sheldon_cooper dbpprop:portrayer?x. } Een SPARQL query ten opzichte van de DBPedia RDF-databank kan je bijvoorbeeld laten uitvoeren op deze locatie: 19
http://dbpedia.org/sparql Deze tool kent bovendien ook een heel aantal standaard naamruimtes met hun standaard prefixen, zodat we ook deze declaraties kunnen weglaten. Het resultaat van bovenstaand query is dan de volgende URI: http://dbpedia.org/resource/jim_parsons Er kunnen natuurlijk meerdere knopen overeenkomen met hetzelfde patroon. Zo werd het personage van Sheldon bedacht door twee mensen, en zal het resultaat van deze query select?x where { dbpedia:sheldon_cooper dbpedia-owl:creator?x. } bestaan uit twee URIs: http://dbpedia.org/resource/chuck_lorre http://dbpedia.org/resource/bill_prady Naast URIs, kunnen natuurlijk ook letterlijke waardes worden teruggegeven. Het vinden van het beroep van Sheldon kan bijvoorbeeld zo: select?x where { dbpedia:sheldon_cooper dbpprop:occupation?beroep.?beroep rdfs:label?x. } Een greep uit het resultaat: "Theoretical physics"@en "Theoretische natuurkunde"@nl "Teoretisk fysikk"@no... Om enkel maar de Nederlandse versie over te houden, moeten we het resultaat dwz. de verzameling van alle knopen die matchen met ons patroon gaan uitdunnen met een filter(voowaarde) instructie. Om het taal-label van een knoop op te vragen, gebruiken we de functie lang. select?x where { dbpedia:sheldon_cooper dbpprop:occupation?beroep.?beroep rdfs:label?x. filter(lang(?x) = "nl"). } Een mogelijk probleem met deze query is nog dat hij niet zal werken als men toevallig het taal-label als NL geschreven heeft ipv. als nl. Er bestaat een functie langmatches, die nagaat of twee taal-labels dezelfde taal voorstellen, onafhankelijk van dit soort details. Een laatse schoonheidsfoutje is dat we nu in ons resultaat de volledige literal, inclusief taal-label, terugkrijgen: "Theoretische natuurkunde"@nl Om enkel de string zelf over te houden, zonder taal-label, kunnen we de functie str gebruiken.
select str(?x) where { dbpedia:sheldon_cooper dbpprop:occupation?beroep.?beroep rdfs:label?x. filter(langmatches(lang(?x),"nl")). } Bovenstaande query bevat twee plaatshouders, waarvan er uiteindelijk maar eentje wordt opgevraagd in het select gedeelte. Het is natuurlijk ook mogelijk om er meerdere op te vragen. select * where {?personage dcterms:subject category:the_big_bang_theory_characters.?personage dbpprop:nickname?nick. } Het resultaat van deze query is een overzicht van alle personages uit The Big Bang Theory die een bijnaam hebben, met deze bijnaam erbij. Om enkel maar de string waarde van de bijnaam literals over te houden, kan natuurlijk weer de functie str gebruikt worden. select?personage str(?nick) where {?personage dcterms:subject category:the_big_bang_theory_characters.?personage dbpprop:nickname?nick. } personage http://dbpedia.org/resource/sheldon_cooper http://dbpedia.org/resource/sheldon_cooper http://dbpedia.org/resource/sheldon_cooper http://dbpedia.org/resource/penny_(the_big_bang_theory) http://dbpedia.org/resource/howard_wolowitz http://dbpedia.org/resource/rajesh_koothrappali callret-1 Moon Pie Shelly, Shelly Bean Smelly Shelly, Dr. Dumbass "Bestie" Fruit Loops Raj Omdat voor de tweede kolom de functie str wordt opgeroepen, krijgt deze kolom een titel die niet zo informatief is. Net zoals bij SQL kunnen we de as-instructie gebruiken om dit desgewenst aan te passen. Hierbij is wel een extra paar haakjes noodzakelijk. select?personage (str(?nick) as?bijnaam) where {... } Als we hetzelfde where-patroon gebruiken, maar de bijnaam niet opvragen, krijgen we een overzicht van alle personages die een bijnaam hebben. Net zoals we dit kennen van SQL, zal een personage met meer dan één bijnaam meermaals voorkomen in deze lijst. En net zoals in SQL, kunnen we desgewenst een select distinct doen om dit te vermijden. Aangezien het patroon waarnaar we op zoek gaan in bovenstaande query vereist dat het personage in kwestie een bijnaam heeft, vindt deze query dus geen personages terug die geen bijnaam hebben. Soms is het echter wenselijk om net zoals met een outer join in een relationele databank dit toch te doen. Dit kan door een deel van het te zoeken patroon optioneel te maken: als het er is, zal het worden opgevraagd, maar als het er niet is, zal de rest van het patroon toch nog kunnen matchen. select?personage (str(?nick) as?bijnaam) where {?personage dcterms:subject category:the_big_bang_theory_characters. optional {?personage dbpprop:nickname?nick }. }
personage http://dbpedia.org/resource/sheldon_cooper http://dbpedia.org/resource/sheldon_cooper http://dbpedia.org/resource/sheldon_cooper http://dbpedia.org/resource/leonard_hofstadter http://dbpedia.org/resource/penny_(the_big_bang_theory) http://dbpedia.org/resource/howard_wolowitz http://dbpedia.org/resource/rajesh_koothrappali bijnaam Moon Pie Shelly, Shelly Bean Smelly Shelly, Dr. Dumbass "Bestie" Fruit Loops Raj Deze optional instructie is natuurlijk vooral belangrijk omwille van de flexibiliteit van het RDF formaat, waardoor het perfect denkbaar is dat resources van dezelfde soort, in tegenstelling tot bijvoorbeeld entiteiten van dezelfde entiteitverzameling in een relationele databank, een verschillende verzameling attributen hebben. De flexibiliteit van het RDF formaat komt verder nog tot uiting in het feit dat plaatshouders niet alleen maar gebruikt kunnen worden om, zoals in een relationele databank, resources op te vragen, maar ook om relaties op te vragen. Moesten we bijvoorbeeld vergeten zijn wat Sheldon Cooper en Jim Parsons ook al weer met elkaar te maken hebben, dan kunnen we dit als volgt opvragen: select?relatie where { dbpedia:sheldon_cooper?relatie dbpedia:jim_parsons. } En kunnen we nu intussen eigenlijk al opvragen of de acteur die Sheldon speelt zelf uit Texas komt? Jazeker: select?biggerplace where { dbpedia:sheldon_cooper dbpprop:portrayer?acteur.?acteur dbpedia-owl:birthplace?plaats.?plaats dbpedia-owl:ispartof?biggerplace. } Het resultaat van deze query is een aantal geografische regio s die de stad Houston bevatten: http://dbpedia.org/resource/fort_bend_county,_texas http://dbpedia.org/resource/texas http://dbpedia.org/resource/montgomery_county,_texas http://dbpedia.org/resource/harris_county,_texas 2.2 Paden van eigenschappen In de queries die we tot dusver gebruikt hebben, zochten we steeds naar patronen die op basis van knopen die verbonden zijn door middel van één enkele relatie. Het is echter ook mogelijk om via ingewikkeldere paden van één knoop naar een andere te geraken. De algemene notatie is als volgt: startknoop pad eindknoop. Het eenvoudigste geval is dat het pad slechts uit één relatie bestaat, en dan zijn we natuurlijk gewoon terug in hetzelfde geval als voorheen. Meer complexe paden kunnen beschreven worden met een reguliere-expressie-achtige notatie:
Pad notatie ^pad (pad) pad1 / pad2 pad1 pad2 pad* pad+ pad? pad{n,m} pad{n} pad{n,} pad{,n} Betekenis het omgekeerde (= van achter naar voor) van pad haakjes dienen voor groeperen het pad bestaande uit pad1, gevolgd door pad2 ofwel pad1, ofwel pad2 een pad dat bestaat uit nul of meer keer pad na elkaar hetzelfde, maar dan één of meer keer hetzelfde, maar dan nul of één keer hetzelfde, maar dan tussen n en m keer hetzelfde, maar dan exact n keer hetzelfde, maar dan minimum n keer hetzelfde, maar dan maximum n keer Op deze manier kunnen we bijvoorbeeld opvragen wie er in dezelfde tv-serie speelt als Jim Parsons: select?acteur where { dbpedia:jim_parsons (^dbpedia-owl:starring / dbpedia-owl:starring)?acteur. } Dit konden we natuurlijk voorheen ook al, maar toen nog met meer schrijfwerk: select?acteur where {?serie dbpedia-owl:starring dbpedia:jim_parsons.?serie dbpedia-owl:starring?acteur. } DBPedia bevat ook gedeeltelijke informatie over welke Amerikaanse staten aan elkaar grenzen. select?van?naar where {?van dbpprop:borderingstates?naar. } Met onderstaande query kunnen we dan berekenen welke staten we vanuit Texas kunnen bereiken met hoogstens 1 tussenliggende staat. select?state where { dbpedia:texas (^dbpprop:borderingstates dbpprop:borderingstates){,2}?state } Als we een arbitrair aantal tussenstappen willen toelaten, wordt dit: select?state where { dbpedia:texas (^dbpprop:borderingstates dbpprop:borderingstates)+?state } 2.3 In Jena Naast de DBPedia website, bevat ook het Jena pakket een SPARQL engine. Deze kan gebruikt worden als alleenstaande command-line applicatie, maar ook via een Java API. De benodigde klassen zitten in het paket import com.hp.hpl.jena.query.*;
Het stellen van een query aan een Model verloopt in drie stappen. De statische methode QueryFactory.create(String query) wordt gebruikt om een Query object aan te maken, gegeven een SPARQL String. Deze Query moet dan gekoppeld worden aan het Model waaraan de vraag in kwestie gesteld moet worden. Dit gebeurt met de statische methode QueryExecutionFactory.create(Query q, Model). Het resultaat is een QueryExecution object. Tot slot kan de query dan worden uitgevoerd met de methode execselect() van zo n QueryExecution object. Het resultaat is een Iterator die itereert over QuerySolution objecten.
2.4 Labo 2.4.1 DBPedia Zoals we al gezien hebben, wordt de relatie dbpprop:series wordt gebruikt om afleveringen (onderwerp) te verbinden met hun tv-serie (voorwerp). 1. Welke afleveringen bestaan er allemaal van de tv-serie dbpedia:game_of_thrones? 2. Hoeveel verschillende regisseurs hebben er een aflevering van deze tv-serie geregisseerd? (De regisseur van een aflevering wordt aangegeven met de relatie dbpedia-owl:director.) 3. Van welke tv-series heeft de regisseur van de aflevering dbpedia:you_win_or_you_die al allemaal afleveringen geregisseerd? 4. Is er een relatie tussen de resource dbpedia:you_win_or_you_die en de resource dbpedia:the_pointy_end? 5. Welke waren de drie volgende afleveringen na dbpedia:you_win_or_you_die? 2.4.2 Jena Herschrijf je programma uit de vorige opgave zodat de URI van de gemeente van je buurman nu wordt opgevraagd dmv. een SPARQL query.
RDF Schema 3 Zoals we in het vorige hoofdstuk gezien hebben, kan iedereen die daar zin in heeft zijn eigen RDF naamruimte verzinnen en deze vullen met zijn eigen URIs. Informatie kan gedeeld worden tussen verschillende bronnen, maar enkel maar op basis van gemeenschappelijke URIs. Het is met andere woorden enkel maar indien verschillende bronnen informatie over dezelfde resource bevatten, die ze bovendien allemaal identificeren met dezelfde URI, dat ze iets aan elkaars informatie kunnen hebben. Deze situatie is natuurlijk niet ideaal. Er is nood aan een gemeenschappelijke kennisstructuur, die toelaat dat één bron toch iets van de informatie uit een andere bron kan begrijpen, ook als ze elkaars URIs niet kennen. Dit wordt verwezenlijkt door RDF Schema (RDFS), waarmee een bron de URIs die ze gebruikt kan beschrijven en kaderen ten opzichte van andere concepten. Dit zal bijvoorbeeld toelaten dat ook iemand die de specifieke URI http://dbpedia.org/resource/the_big_bang_theory niet kent, toch kan uitvissen dat het hier over een TV-serie gaat. De principes achter RDFS zijn eenvoudig: De flexibiliteit van RDF moet bewaard blijven, dwz. dat iedereen nog steeds vrij moet zijn om eender welke informatie in een RDF bestand te gieten en te combineren met bestaande RDF databanken, zonder dat hier allerlei beperkingen aan worden opgelegd. RDFS is zelf ook gewoon RDF. Net zoals bv. het XSD-formaat een XML-formaat is om XML-formaten in te beschrijven, is RDFS een RDF-formaat om RDF-formaten in te beschrijven. 3.1 Structuur van RDFS RDFS heeft een objectgeöriënteerd wereldbeeld: er zijn objecten, die behoren tot klassen. Wegens de vereiste flexibiliteit, is er geen limiet op het aantal klassen waartoe een object kan behoren. De klassen kunnen geördend worden in een overervingshiërarchie. Deze informatie wordt allemaal beschreven met URIs uit volgende naamruimte: http://www.w3.org/2000/01/rdf-schema# 27
URI rdf:type rdfs:label rdfs:resource rdfs:property rdf:class rdfs:domain rdfs:range rdfs:subclassof rdfs:subpropertyof Betekenis geeft aan tot welke klasse een object behoort een string die een object beschrijft de basisklasse, waartoe alle objecten behoren de klasse van alle resources die gebruikt kunnen worden als relaties de klasse waartoe alle klassen behoren verbindt een property met zijn domein, dwz., de klasse waartoe zijn onderwerpen behoren verbindt een property met zijn bereik, dwz., de klasse waartoe zijn voorwerpen behoren koppelt een klasse aan een superklasse koppelt een eigenschap aan een grotere eigenschap Als we zelf een eigen klasse van objecten willen aanmaken, volstaat het dus om de klasse zelf te declareren als iets van rdf:type rdfs:class en dan elk van de objecten deze nieuwe klasse als rdf:type te geven. Objecten van onze nieuwe klasse kunnen dan natuurlijk ook eigenschappen hebben, die we kunnen declareren als rdfs:property met een bepaalde rdf:domain en eventueel een bepaalde rdfs:range. Als we verschillende klassen of eigenschappen hebben, dan kunnen we bovendien het verband hiertussen aangeven met een rdfs:subclassof (voor klassen) of een rdfs:subpropertyof (voor eigenschappen). Hieronder declareren we twee nieuwe klassen, waarbij de ene de andere omvat. jve:denayerstudent rdf:type rdfs:class. jve:mijnstudent rdf:type rdfs:class. jve:mijnstudent rdfs:subclassof jve:denayerstudent. In RDFS bestanden zullen dergelijke declaraties natuurlijk vaak voorkomen. In XML/RDF formaat vereist zo n declaratie net zoals de meeste dingen aardig wat schrijfwerk: <rdf:description about="http://www.cs.kuleuven.be/~joost/mijnstudent"> <rdfs:type resource="http://www.w3.org/2000/01/rdf-schema#class" /> </rdf:description> Dit mag echter ook afgekort worden als volgt: <rdfs:class about="http://www.cs.kuleuven.be/~joost/mijnstudent" /> Het voorwerp van de type-relatie wordt hier dus als tag gebruikt, en het onderwerp komt in zijn about-attribuut. Nu voegen we een relatie toe, die als domain één van deze klassen heeft. jve:thesisonderwerp rdf:type rdfs:property; rdfs:domain jve:mijnstudent. We kunnen nu deze nieuwe klassen en eigenschappen gebruiken nu gebruiken om informatie over nieuwe objecten bij te houden, bijvoorbeeld: jve:dendinges jve:thesisonderwerp jve:semantischweb. Natuurlijk, dat hadden we even goed kunnen doen, zonder deze relatie op voorhand te beschrijven in RDFS. Wat schieten we er nu mee op dat we dit wel gedaan hebben?
3.2 Gebruik van RDFS 3.2.1 Communicatie De eerste reden om een RDFS bestand op te stellen, is om ergens een verslag te hebben van de beslissingen die genomen zijn over hoe data zal worden voorgesteld, onafhankelijk van de voorgestelde data zelf. In dit opzicht manier speelt een RDFS bestand dezelfde rol als een ER-diagramma in een relationele databank. Dit RDFS bestand kan dan gebruikt worden om te communiceren met mensen die de gegevens zouden willen gebruiken of aanleveren in een compatibel formaat. 3.2.2 Inferentie Naast communicatie tussen mensen, kan RDFS natuurlijk ook gebruikt worden door computerprogramma s. De belangrijkste taak hier is inferentie. Hiermee bedoelen we dat, op een basis van RDFS-bestand, méér drietallen kunnen worden afgeleid dan er effectief in ons RDF bestand staan. Bijvoorbeeld, het RDFS schema zegt dat het domein van de relatie jve:thesisonderwerp de klasse jve:mijnstudent is, en bovendien dat deze klasse een subklasse is van jve:denayerstudent. Als volgend feit effectief in ons RDF-bestand staat: jve:dendinges jve:thesisonderwerp jve:semantischweb. kunnen we daar dankzij het RDFS schema ook volgende twee feiten uit afleiden: jve:dendinges rdf:type jve:mijnstudent. jve:dendinges rdf:type jve:denayerstudent. Deze feiten worden virtueel genoemd, omdat we weten dat ze waar moeten zijn, hoewel ze nergens effectief staan opgeschreven. Als we een RDFS schema hebben voor een RDF bestand, dan gaan we onze queries niet meer stellen aan dit bestand zelf, maar aan de zogenaamde afgeleide grafe (inferred graph) hiervan, die bestaat uit alle drietallen die effectief in het bestand staan plus alle virtuele drietallen die dankzij het RDFS schema kunnen worden afgeleid. Op deze manier kunnen we dankzij RDFS een complexe en grote gegevensstructuur vaak op een meer compacte manier beschrijven. Een implementatie kan dan natuurlijk kiezen of ze deze afgeleide grafe in zijn geheel gaat opstellen, of enkel maar gedeeltes ervan constueren op het moment dat een gebruiker er een query aan stelt. 3.2.3 Validatie Een tweede taak waarvoor RDFS gebruikt kan worden, is validatie. Hiermee wordt bedoeld dat we gaan nagaan of de afgeleide grafe van een RDF bestand wel op een correcte manier gemaakt kan worden. Het is bijvoorbeeld mogelijk dat een RDFS schema zegt dat een bepaalde letterlijke waarde het type xsd:integer moet hebben, terwijl er in het RDF bestand een kommagetal staat. In zo n geval bevat het RDF bestand dus een fout ten opzichte van het schema. 3.2.4 Integratie Het gebruik van RDFS kan ook helpen om informatie uit verschillende bronnen te integreren. Zoals we al gezien hebben, kan het integreren van informatie uit verschillende bronnen heel eenvoudig zijn, als die tenminste allemaal gebruik gemaakt hebben van dezelfde URIs. In zo n geval, volstaat het immers om gewoon de feiten uit verschillende bronnen samen te voegen tot één grote grafe.
Zelfs als de verschillende bronnen andere URIs gebruikt hebben, dan kan de integratie nog altijd vlot verlopen. Er bestaat namelijk een manier om aan te geven dat verschillende URIs naar hetzelfde object verwijzen, namelijk door middel van de relatie http://www.w3.org/2002/07/owl#sameas. Dit is een ingebouwde relatie, waarmee er tijdens het construeren van de afgeleide grafe iets speciaals zal gebeuren: als er van twee resources gezegd wordt dat ze sameas zijn, worden beide voorgesteld door één en dezelfde knoop in de grafe. De meest complexe situatie krijgen we natuurlijk als er geen mooi één-op-één verband is tussen de concepten die in de twee verschillende bronnen gebruikt worden. In een dergelijk geval kunnen we toch nog steeds gebruik maken van relaties zoals rdfs:subclassof, om concepten uit de verschillende bronnen aan elkaar te relateren zonder dat ze exact hetzelfde hoeven te zijn. Op deze manier kunnen er mogelijk toch zinvolle conclusies getrokken worden uit de gecombineerde kennis van de twee bronnen. 3.3 In Jena Om een afgeleide grafe te maken, vertrekken we in Jena vanuit twee Model objecten, eentje waarin het RDFS schema is ingelezen, en eentje waarin het RDF databestand zit. Deze twee worden dan gecombineerd tot één Model object (eigenlijk van de subklasse InfModel van Model), dat de afgeleide grafe voorstelt. Dit gebeurt met volgende statische methode uit de klasse ModelFactory: public static InfModel createrdfsmodel ( Model RDFS, Model Data ) Voor de rest kan deze afgeleide grafe natuurlijk op dezelfde manier ondervraagd worden als een gewone grafe. Een bijkomende functionaliteit die door de klasse InfModel wordt aangeboden is validatie: de methode validate() zal controleren of het RDF bestand wel aan het opgegeven schema voldoet. Het resultaat is een ValidityReport object. De methode isvalid() van dit object geeft een boolean terug, en de methode getreports() produceert een iterator over al de fouten die gevonden zijn.
OWL 4 De Web Ontology Language OWL is een uitbreiding van RDFS. Hierin worden opnieuw een aantal klassen en de relaties tussen deze klassen (= een ontologie) beschreven. Het verschil met RDFS is dat er in OWL veel meer uitgedrukt kan worden, zodat een breder gamma aan relaties beschreven kan worden. We illustreren dit met een voorbeeld. Net zoals RDFS, beschrijft OWL een aantal klassen. In OWL wordt het concept van een klasse echter iets strenger geïnterpreteerd dan in RDFS, zodat deze klassen niet van het type rdfs:class zijn, maar van een subtype owl:class daarvan. <rdf:rdf xmlns="http://www.cs.kuleuven.be/~joost/voorbeeld.owl#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:owl="http://www.w3.org/2002/07/owl#" xmlns:xsd="http://www.w3.org/2001/xmlschema#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <owl:class rdf:about="http://www.cs.kuleuven.be/~joost/voorbeeld.owl#docent"/> <owl:class rdf:about="http://www.cs.kuleuven.be/~joost/voorbeeld.owl#student"/> </rdf:rdf> In OWL wordt een onderscheid gemaakt tussen data properties, dwz. relaties tussen een resource en een letterlijke waarde, en object properties, relaties tussen twee resources. We declareren als volgt een object property van de klasse Student naar de klasse Docent. (Deze declaratie moeten natuurlijk bij in het wortelelement <rdf:rdf> komen.) <owl:objectproperty rdf:about="http://www.cs.kuleuven.be/~joost/voorbeeld.owl#promotor"> <rdfs:range rdf:resource="http://www.cs.kuleuven.be/~joost/voorbeeld.owl#docent"/> <rdfs:domain rdf:resource="http://www.cs.kuleuven.be/~joost/voorbeeld.owl#student"/> </owl:objectproperty> Zoals voorheen reeds opgemerkt, is dit een verkorte notatie voor: <rdf:description 31
rdf:about="http://www.cs.kuleuven.be/~joost/voorbeeld.owl#promotor"> <rdfs:type rdf:resource="http://www.w3.org/2002/07/owl#class"/> <rdfs:range rdf:resource="http://www.cs.kuleuven.be/~joost/voorbeeld.owl#docent"/> <rdfs:domain rdf:resource="http://www.cs.kuleuven.be/~joost/voorbeeld.owl#student"/> </rdf:description> Nu voegen we hieraan nog twee klassen toe. <owl:class rdf:about="http://www.cs.kuleuven.be/~joost/voorbeeld.owl#thesisstudent" /> <owl:class rdf:about="http://www.cs.kuleuven.be/~joost/voorbeeld.owl#mijnthesisstudent" /> Desgewenst kunnen we nu met de relatie rdfs:subclassof aangeven dat de verzameling van mijn thesisstudenten een deel is van de verzameling thesisstudenten, die op haar beurt weer een deel is van de verzameling van alle studenten. Met OWL kunnen we echter meer doen dan alleen maar dat: we kunnen ook specifiëren precies welke studenten er in de verzameling thesisstudenten zitten. Dit zijn namelijk precies die studenten die een promotor hebben. In OWL kunnen we nieuwe klassen construeren op basis van andere klassen. Een voorbeeld van zo n definitie is de volgende uitdrukking: promotor some Docent Deze uitdrukking beschrijft de verzameling van alle objecten die via de relatie promotor verbonden zijn met een aantal (> 0) objecten van de klasse Docent. Hiervoor bestaat natuurlijk ook een XML syntax: <owl:restriction> <owl:onproperty rdf:resource = "http://www.cs.kuleuven.be/~joost/voorbeeld.owl#promotor"/> <owl:somevaluesfrom rdf:resource = "http://www.cs.kuleuven.be/~joost/voorbeeld.owl#docent"/> </owl:restriction> We kunnen nu de relatie owl:equivalentclass gebruiken om aan te geven dat de aldus geconstrueerde verzameling identiek is aan de verzameling Thesisstudent. <owl:class rdf:about="...#thesisstudent"> <owl:equivalentclass> <owl:restriction> <owl:onproperty rdf:resource="...#promotor"/> <owl:somevaluesfrom rdf:resource = "...#Docent"/> </owl:restriction> </owl:equivalentclass> </owl:class> Om de klasse MijnThesisstudent te definiëren, kunnen we gebruik maken van de mogelijkheid om een klasse te construeren door opsomming van haar objecten. promotor some { jve } Ook hiervoor bestaat weer een RDF/XML syntax:
<rdf:description> <rdf:type rdf:resource="http://www.w3.org/2002/07/owl#class"> <owl:oneof rdf:parsetype="collection"> <rdf:description rdf:about="http://www.cs.kuleuven.be/~joost/voorbeeld.owl#joostvennekens"/> </owl:oneof> <rdf:description> Als we deze informatie ook toevoegen, dan zal een OWL redeneeralgoritme in staat zijn om af te leiden dat volgende relatie geldt tussen de verschillende klassen, ook al hebben we dit niet expliciet gezegd: Student Thesisstudent MijnThesisstudent Als we nu bovendien volgende informatie zouden toevoegen over de promotor van een student: <Description about="http://www.cs.kuleuven.be/~joost/voorbeeld.owl#dendinges"> <jve:promotor resource="http://www.cs.kuleuven.be/~joost/jve" /> </Description> dan zal er ook automatisch worden afgeleid dat deze student tot alledrie van bovenstaande klassen behoort.