Faculteit Toegepaste Wetenschappen Vakgroep Elektronica en Informatiesystemen Voorzitter: Prof. dr. ir. J. Van Campenhout
Ontwerp van preprint servers
Bart Verhaeghe Stefaan Tavernier
Promotor: Prof. Leo Storme Begeleiders: Lic. Jan De Beule, Lic. Benny Malengier en Prof Leo Storme
Scriptie ingediend tot het behalen van de academische graad van gediplomeerde in de aanvullende studies van informatica.
Academiejaar 2003 - 2004
Dankwoord Vooreerst wensen wij onze promotor Leo Storme te danken voor zijn steun en zijn hulp bij het nalezen van de thesis.
Tevens bedanken wij Jan De Beule, Benny Malengier en Geert Vernaeve voor hun praktische tips en technische uitleg.
i
"De auteur geeft de toelating deze scriptie voor consultatie beschikbaar te stellen en delen van de scriptie te kopiëren voor persoonlijk gebruik.
Elk ander gebruik valt onder de beperkingen van het auteursrecht, in het bijzonder met betrekking tot de verplichting de bron uitdrukkelijk te vermelden bij het aanhalen van resultaten uit deze scriptie."
Gent, 18 mei 2004.
De promotor
De auteurs
Leo Storme
Bart Verhaeghe
Stefaan Tavernier
ii
Inhoud
HOOFDSTUK 1 SITUERING ........................................................................................... 1 HOOFDSTUK 2 METHODE ............................................................................................. 5 HOOFDSTUK 3 VERWEZENLIJKINGEN .................................................................... 7 3.1 NIEUWE PREPRINTS IN DE PERSOONLIJKE BESTANDEN EEN NUMMER GEVEN ................. 7 3.2 FOUTMELDING .............................................................................................................. 7 3.3 HET PREPRINTSTATE-VELD............................................................................................ 8 3.4 VERSCHILLENDE STATUSSEN VOOR DE PREPRINTS ........................................................ 8 3.5 BACKUP-UP MAKEN ...................................................................................................... 8 3.6 PREPRINTS SORTEREN ................................................................................................... 8 3.7 HTML-TAGS ................................................................................................................... 9 3.8 WEBFORMULIER............................................................................................................ 9 3.9 CONFIGURATIEBESTAND PREPRINTCONFIG.PM ........................................................... 9 3.10 DE GEBRUIKERS INSTELLEN ...................................................................................... 10 3.11 SCREENSHOTS VAN DE ONTWIKKELDE WEBPAGINA................................................... 11 HOOFDSTUK 4 USE-CASE DIAGRAM ....................................................................... 17 HOOFDSTUK 5 DE IMPLEMENTATIE ...................................................................... 18 5.1 DE KLASSE PREPRINTPROCESSOR ............................................................................... 19 5.2 DE KLASSE PREPRINTDATABASE ................................................................................ 27 5.3 DE KLASSE USERPROCESSOR ...................................................................................... 29 5.4 DE KLASSE ERRORMAILSYSTEM ................................................................................ 29 5.5 DE KLASSE STRINGMANIP .......................................................................................... 30 5.6 HET HOOFDPROGRAMMA PREPRINTHTML .................................................................... 31 HOOFDSTUK 6 SEQUENTIEDIAGRAM..................................................................... 32 HOOFDSTUK 7 FOUTMELDINGEN ............................................................................ 36 HOOFDSTUK 8 BESLUIT............................................................................................... 38 8.1 MANUAL ........................................................................................................................ I 8.2 INFO FOR THE SYSTEM ADMINISTRATOR ........................................................................ II 8.3 PROGRAMMA CODE ....................................................................................................... II
iii
Hoofdstuk 1 Situering
Er werd ons gevraagd het preprint systeem van de onderzoeksgroep Incidentiemeetkunde aan te passen. Dat was een rudimentair programma dat een beperkt aantal velden inlas uit een bestand uit een persoonlijke directory op de server, en deze velden weergaf op een webpagina, met links naar de betrokken bestanden.
Wij moesten deze preprints een
identificatienummer geven, en bij duidelijke fouten in het bestand via e-mail een foutmelding geven aan de maker. Ook de onderzoeksgroep Nfam² wilde een preprint server in gebruik nemen, met de mogelijkheid voor een andere lay-out.
Het programma dat gebruikt wordt door de onderzoeksgroep Incidentiemeetkunde, haalt zijn informatie uit de preprintsIG-bestanden in de persoonlijke directories van de gebruikers op de server. In zo’n bestand kon bijvoorbeeld de volgende preprint staan:
author: Matthew Brown, Jan De Beule and Leo Storme title: Partial Spreads of T2(O) and T3(O) file: preprint1.ps, preprint1.pdf comment: any comment
Het gebruik van “:” bracht problemen mee omdat enkele gebruikers spaties zetten die het programma in de war brengen en het inlezen onmogelijk maken. verplichte volgorde voor de velden.
-1-
Er was ook een
Hieronder werd een screenshot genomen van de huidige webpagina.
Om meer informatie bij te houden, moesten we dit aanvullen met een ID-veld voor de identificatie; een nieuw statusveld duidt aan of de preprint al aangenomen is door een tijdschrift en mogelijk gepubliceerd is; keywords zouden kunnen gebruikt worden om specifieke preprints op te zoeken. We behouden het principe van serverside scripting, waarbij de code door de server omgezet in HTML-code en daarna doorgestuurd wordt over het internet. Hoe we dit precies doen volgt verderop.
-2-
We hebben ons gebaseerd op andere voorbeelden van pagina’s met preprints, zoals www.arxiv.org/list/math.FA/recent.
Authors and titles for recent submissions Fri, 14 May 2004 Thu, 13 May 2004 Wed, 12 May 2004 Tue, 11 May 2004 Fri, 7 May 2004
Fri, 14 May 2004 math.CV/0405242 [abs, ps, pdf, other] : Title: Analysis by discs Authors: E. Amar Comments: 11 pages Subj-class: Complex Variables; Functional Analysis MSC-class: 32A35; 32A36; 47B35
Thu, 13 May 2004 math.FA/0405217 [abs, ps, pdf, other] : Title: Continuous version of the Choquet Integral Reperesentation Theorem Authors: Piotr Puchala Comments: 8 pages Subj-class: Functional Analysis MSC-class: 54C60; 54C65; 46A55; 46B22
Deze website toont de preprints per dag; dat vonden wij een overbodig gegeven. De datum is niet zo belangrijk, het belangrijkste is dat er een identificatienummer gegeven wordt. Op de arxiv-website wordt dat ook wel gedaan, maar daar is het een eerder ingewikkeld nummer, dat niet gemakkelijk te onthouden is. Het gebruik van “subject-class”, een vorm van keywords, en MSC-class vonden we dan wel weer interessant.
-3-
De andere website die we hebben bekeken, www.math.ntnu.no/conservation/2004, heeft geen velden, behalve titel en auteur, maar er wordt wel een nummer gebruikt, namelijk het jaar gevolgd door een volgnummer.
Dat leek ons ideaal om te volgen, omdat het
eenvoudig en toch duidelijk is.
2004 Preprints [ Homepage | Help | 1996 | 1997 | 1998 | 1999 | 2000 | 2001 | 2002 | 2003 | 2004 | All Preprints ]
Preprints submitted in 2004 •
2004-023 G-Q. Chen and M. Torres: Divergence-measure fields, sets of finite perimeter, and conservation laws.
•
2004-022 S. Qamar and G. Warnecke: A high order kinetic flux-splitting method for the relativistic magnetohydrodynamics.
•
2004-021 S. Qamar and G. Warnecke: A high order kinetic flux-splitting method for the special relativistic hydrodynamics.
-4-
Hoofdstuk 2 Methode
De gebruikte programmeertaal is PERL. Er werd ons voorgesteld eventueel ook deze taal te gebruiken omdat het oude preprint systeem ook in PERL was geschreven en die software dus al geïnstalleerd was op de server, en gebruiksvriendelijk is qua updates. PERL is ook heel geschikt omdat het speciaal ontwikkeld is om snel bestanden te lezen en te verwerken, op een efficiënte manier, via reguliere expressies. De mogelijkheden voor hashes waren bijzonder handig voor het beoogde programma.
Het vroeg wel een
inspanning omdat we geen enkele ervaring hadden met PERL, en PERL-syntax nogal onduidelijk is voor de leek.
JAVA was een andere mogelijke optie, vooral omdat we ermee vertrouwd waren, maar de capaciteit die het inladen van zijn bibliotheken vraagt vonden we een te zware prijs om het te gebruiken.
PERL is ook sneller, en latere updates van JAVA kunnen problemen
veroorzaken.
We gebruiken de MySQL-databank om de informatie over de records gegroepeerd te bewaren. De databank vraagt gelukkig niet veel geheugen. We vonden ook gemakkelijk modules voor de connectie van PERL naar MySQL; MySQL bestaat verder in linux-versie, wat een vereiste was, en is vrij snel.
-5-
Nadat we de eerste richtlijnen gekregen hadden, hebben we een specificatiebestand opgesteld, dat uiteenzette waar het product aan moest voldoen. Dit hebben we voorgelegd, en zo konden we nog beter inschatten wat precies gevraagd werd. Met deze gegevens konden we een UML-schema, en een usecase-diagram (worden nog bijgevoegd) maken van onze applicatie en een indeling in klassen voorzien. Zo hadden we een blauwdruk voor het programmeren. Omdat ons programma een script is, dat niet voortdurend moet blijven lopen, moesten we geen rekening houden met computer resource management en andere processen.
-6-
Hoofdstuk 3 Verwezenlijkingen
3.1 Nieuwe preprints in de persoonlijke bestanden een nummer geven Wanneer er een year-veld in de preprint staat, gaat het programma na welke nummers al gebruikt zijn voor dat jaar, en neemt het volgende getal om als ID toe te kennen aan de preprint; dit
ID-nummer en de sleuteleigenschappen, d.i. titel en gebruiker, worden
opgeslagen in een MySQL-databank.
3.2 Foutmelding Bij fouten wordt een e-mail verstuurd naar de persoon die de fout ingevoerd heeft; per gebruiker worden de preprints ingelezen, en zo houdt het programma bij wie de fout gemaakt heeft; de identificatie van die gebruiker en zijn e-mailgegevens worden bijgehouden in het bestand users.ini.
Hier volgt een voorbeeld van users.ini:
USER staverni NAME Stefaan Tavernier EMAIL
[email protected]
PATH /u/staverni
-7-
3.3 Het preprintstate-veld Dit bepaalt hoe de preprint verwerkt wordt; enable is noodzakelijk om de preprint weer te geven; disable en error (het programma schrijft deze toestand wanneer een fout gevonden wordt) schakelen de preprint uit; update is een tussenstap, die omgezet wordt naar enable wanneer de update-informatie goed gevormd is, namelijk een geldige titel en ID. Een laatste optie voor het veld is find; dit is eerder een diagnostische functie die alle preprints van een gebruiker doorstuurt per e-mail en uitschrijft in preprintsIG.
PREPRINTSTATE DISABLE ERROR ID="2003-0001" TITLE="Dit is een nieuwe preprint"
ERROR ID="2000-0001" TITLE="moeilijkheden"
3.4 Verschillende statussen voor de preprints
“preprint” als ze nog niet opgestuurd zijn naar een tijdschrift
“submitted” als dit wel het geval is.
Later kan dat nog overgaan in “accepted”, dit is het stadium voor de publicatie, en
uiteindelijk in “published”. De informatie over het tijdschrift kan in het comment-veld
geplaatst worden.
3.5 Backup-up maken Voordat ons programma het bestand preprintsIG aanpast wordt er een back-up preprintsIG.old van dit bestand gemaakt; die kan dan hergebruikt worden wanneer preprintsIG zwaar aangepast wordt door foutmeldingen.
3.6 Preprints sorteren Op de uiteindelijke website kunnen de preprints gesorteerd worden volgens jaar, titel, auteur of keyword. Default worden slechts een beperkt aantal jaren weergegeven, maar het is mogelijk de vroegere jaren te bekijken via de link “older”.
-8-
3.7 HTML-tags De symbolen in de velden kunnen niet meer als HTML-tags worden geïnterpreteerd, zodat het uitzicht van de website niet kan beïnvloed worden. Case sensitivity (de computer maakt een verschil tussen hetzelfde woord met of zonder hoofdletter) is uitgeschakeld in PERL zodat we geen dubbele records krijgen die enkel van hoofdletter verschillen.
3.8 Webformulier Om het voor de gebruiker gemakkelijker te maken correcte preprints aan te maken, hebben we een webformulier gemaakt waarin de gebruikers naast de veldnamen de gegevens in kunnen vullen. Met een druk op de knop wordt dan de correcte code gegenereerd, die de gebruiker in het bestand preprintsIG kan plakken. De gegenereerde code wordt tussen <pre>-tags weergegeven, zodat er geen problemen bij het plakken in een ander bestand optreden.
We hadden eerst namelijk het probleem dat de tekst in een tabel werd
gekopieerd.
3.9 Configuratiebestand PreprintConfig.pm Dit bestand bevat de volgende variabelen:
De vindplaats van het users.ini-bestand:
$userconfigfilepath = "/var/www/cgi-bin/users.ini";
Variabelen die het uitzicht van de website bepalen.
$logo = "../Nfam2.gif"; $background = ""; $backgroundcolor = "#FDF5E6"; $linkcolor = "#0000EE"; $vlinkcolor = "#551A8B"; $alinkcolor = "#FF0000"; $textcolor = "#000000"; $pagetitle = "Research Group
for
Numerical Functional Analysis and Mathematical Modelling"; $htmltitle = "Research Group for Numerical Functional Analysis and Mathematical Modelling"; $titlecolor = "#CC6600"; $logoheight = 90; my $logowidth = 194; $links = "
If you need help: [
Help] [
Create Preprint]";
-9-
Het aantal recente jaren dat bij het tonen van de pagina wordt weergegeven:
$maxyears = 5;
De filenaam van de gebruikersbestanden:
$filename = "public_html/preprintsIG";
De server die de e-mails verstuurt:
$mailserver = "cage.ugent.be"; $mailfrom = "bart.verhaeghe\@swing.be";
De databankconnectie, enkel de databanknaam moet aangepast worden:
$database = "DBI:mysql:databasename:localhost"; $username = "root"; =het databankwachtwoord $passwd = "";
3.10 De gebruikers instellen In het bestand users.ini moeten de gegevens van de gebruikers worden ingevoerd.
NAME Stefaan Tavernier EMAIL
[email protected] PATH /u/staverni/public_html USER staverni
Hierbij is PATH de directory waar de bestanden staan waarnaar gelinkt wordt vanaf de website. Dit moet een subdirectory public_html zijn. USER is de loginnaam van de gebruiker op cage, en wordt bij iedere preprint ook opgeslagen in de databank. NAME wordt enkel in de e-mail gebruikt.
- 10 -
3.11 Screenshots van de ontwikkelde webpagina De screenshots die hieronder getoond worden, zijn gebaseerd op de preprints van twee personen. Deze gebruikers worden ingesteld in het “users.ini” bestand.
NAME Bart Verhaeghe EMAIL
[email protected] PATH /u/bverhaeg/public_html/preprints USER bverhaeg NAME Stefaan Tavernier EMAIL
[email protected] PATH /u/staverni/public_html/preprints USER staverni
De preprints van de gebruikers, die te vinden zijn in het bestand zoals is opgegeven in het “users.ini” bestand, zien er als volgt uit:
Bij de eerste gebruiker:
ID 2004-0002 SORTBYNAME verhaeghe AUTHORS Bart Verhaeghe, Stefaan Tavernier TITLE De titel van de preprint versie 3 COMMENT Een beetje commentaar FILES midi.jpg KEYWORDS thesisverdediging PREPRINTSTATE ENABLE ID 2004-0001 SORTBYNAME verhaeghe AUTHORS Bart Verhaeghe, Stefaan Tavernier TITLE De titel van de preprint versie 2 COMMENT Een beetje commentaar FILES midi.jpg KEYWORDS perl
Bij de tweede gebruiker:
PREPRINTSTATE ENABLE ID 2000-0001 SORTBYNAME tavernier AUTHORS Bart Verhaeghe, Stefaan Tavernier TITLE Preprint van Stefaan om te testen COMMENT Een beetje commentaar FILES frigo.jpg KEYWORDS preprints, testen
- 11 -
In onderstaande figuur worden alle preprints getoond gesorteerd volgens jaartal.
- 12 -
Je kan ook de preprints van een specifiek jaartal bekijken. In dit voorbeeld werd het jaar “2000” aangeklikt.
- 13 -
Ook kunnen preprints getoond worden volgens de beginletter van de auteur. Hier werd de letter “V” aangeklikt.
- 14 -
Er kan ook gezocht worden op een auteur of op keyword van een preprint. Hier werd gezocht op het keyword “Perl”.
- 15 -
Er is ook een webformulier voorzien om nieuwe preprints gemakkelijk aan te maken.
De code gegeneerd door het webformulier moet je in je preprint bestand kopiëren.
- 16 -
Hoofdstuk 4 USE-CASE DIAGRAM
De volgende figuur toont welke interactiemogelijkheden ieder type gebruiker met het preprintprogramma heeft.
Preprints verwijderen van de webpagina <<uses>>
ID-nummer toekennen aan een nieuwe preprint Persoon die preprints online wil plaatsen
<<uses>> <<uses>>
Preprints toevoegen aan de webpagina
Preprints inlezen, valideren en wegschrijven
<<uses>>
Preprints updaten
Preprints sorteren op jaar en bekijken op webpagina
<<extends>>
<<extends>>
Bezoeker van de webpagina
Preprints sorteren op auteur en bekijken op webpagina <<extends>>
Preprints zoeken op auteur en keyword, en bekijken ...
Gebruikers toevoegen en verwijderen
Administrator van de webpagina Instellingen van de webpagina wijzigen
- 17 -
Preprints sorteren op ID en bekijken op webpagina
Hoofdstuk 5 De Implementatie
- 18 -
5.1 De klasse PreprintProcessor Bespreking van zijn methodes: •
readUserPreprints(userref : $hashref) : @hashref
Deze methode zal de preprints uit een bestand lezen die behoren bij de opgegeven gebruiker. Alle voorgedefinieerde velden zullen worden ingelezen en bewaard in het geheugen. De methode geeft ons een array terug die de referenties bevat naar de ingelezen preprints.
Veronderstel als voorbeeld dat het preprintbestand van één gebruiker de volgende preprints bevat:
PREPRINTSTATE UPDATE ID 2002-0001 SORTBYNAME verhaeghe AUTHORS Ing. Verhaeghe Bart, Lic. Tavernier Stefaan TITLE Mijn eerste preprint: versie 2 FILES bestand1.pdf, bestand2.pdf KEYWORDS preprint STATUS PREPRINT
PREPRINTSTATE ENABLE ID 2002-0002 SORTBYNAME verhaeghe AUTHORS Ing. Verhaeghe Bart, Lic. Tavernier Stefaan TITLE Mijn tweede preprint COMMENT Een beetje commentaar FILES bestand1.pdf, bestand2.pdf KEYWORDS preprint STATUS SUBMITTED
De preprints worden als hashes (of associatieve rijen, een rij zonder vaste volgorde, werkend met sleutelwoorden) in het geheugen ingelezen. Nemen we als voorbeeld de titel, dan is “TITLE” de “key” en is de waarde de “value” in de hash.
if (/^TITLE (.*)/i) { $preprintrefs[$i]->{"TITLE"} = StringManip->trim($1); last SWITCH; }
Om meerdere waarden als “value” toe te voegen, worden deze waarden toegevoegd aan een array en wordt de referentie naar deze array toegevoegd als “value” aan de hash. Hieronder volgt een voorbeeld voor de auteurs van een preprint.
- 19 -
if (/^(AUTHORS|AUTHOR) (.*)/i) { $preprintrefs[$i]->{"AUTHORS"} = [StringManip->trimArray(split(/[\,|;]/,$2))]; last SWITCH; }
De preprints die volledig als hashes in het geheugen geladen zijn, zien er als volgt uit:
$VAR1 = { 'FILES' => [ 'bestand1.pdf', 'bestand2.pdf' ], 'TITLE' => 'Mijn eerste preprint: versie 2', 'SORTBYNAME' => 'verhaeghe', 'STATUS' => 'PREPRINT', 'KEYWORDS' => [ 'preprint' ], 'YEARID' => '2002-0001', 'PREPRINTSTATE' => 'UPDATE', 'AUTHORS' => [ 'Ing. Verhaeghe Bart', 'Lic. Tavernier Stefaan' ] };
$VAR2 = { 'FILES' => [ 'bestand1.pdf', 'bestand2.pdf' ], 'TITLE' => 'Mijn tweede preprint', 'SORTBYNAME' => 'verhaeghe', 'STATUS' => 'SUBMITTED', 'KEYWORDS' => [ 'preprint' ], 'YEARID' => '2002-0002', 'COMMENT' => 'Een beetje commentaar ', 'PREPRINTSTATE' => 'ENABLE', 'AUTHORS' => [ 'Ing. Verhaeghe Bart', 'Lic. Tavernier Stefaan' ] };
Hierboven zie je de volledige array die 2 referenties bevat naar hashes met de informatie uit de preprints.
•
processUserPreprints(userref : $hashref, preprintrefs : @hashref)
Deze methode zal telkens de methode “processUserPreprint(userref : $hashref, preprintrefs : $hashref)” oproepen, die de ingelezen preprints één voor één bekijkt en aan de hand van het veld “PREPRINTSTATE” de gewenste methode oproept. mogelijkheden:
“PREPRINTSTATE
ENABLE”
roept
Hier bestaan 4 de
methode
“preprintEnable(preprintref : $hashref, userref : $hashref)” op, “PREPRINTSTATE UPDATE” roept de methode “preprintUpdate(preprintref : $hashref, userref : $hashref)” op, “PREPRINTSTATE FIND” roept de methode “preprintFind(preprintref : $hashref, userref : $hashref)” op en bij “PREPRINTSTATE ERROR” of “PREPRINTSTATE DISABLE” wordt er niets opgeroepen.
- 20 -
•
preprintFind(preprintref : $hashref, userref : $hashref)
Deze methode zal alle ID’s en bijhorende titels voor de opgegeven gebruiker, uit de databank inlezen en uitschrijven in een string. Deze string wordt bewaard in het attribuut “errorstring” van deze klasse.
•
preprintEnable(preprintref : $hashref, userref : $hashref)
Deze methode zal wanneer er een ID-veld aanwezig is in de preprint, nagaan of het ID overeenstemt met de opgegeven titel. Wanneer er geen ID-veld aanwezig is, maar een YEAR-veld zal de methode een nieuw ID-nummer genereren en het YEAR-veld verwijderen. Wanneer er problemen zouden optreden bij het uitvoeren van deze methode, worden deze fouten in het attribuut “errorstring” van de klasse geschreven.
•
preprintUpdate(preprintref : $hashref, userref : $hashref)
Deze methode zal de titel in de databank updaten die overeenkomt met het opgegeven IDnummer. Wanneer er problemen zouden optreden bij het uitvoeren van deze methode, worden deze fouten in het attribuut “errorstring” van de klasse geschreven.
•
isValidUserPreprint(preprintref : $hashref)
Deze methode gaat na of alle verplichte velden voor de preprint aanwezig zijn. Het kijkt ook na of er een geldige waarde wordt opgegeven voor de velden YEAR, ID en STATE. Wanneer de preprint geldig is, zal de methode een string teruggeven met de waarde “OK”. In het andere geval zullen de gevonden fouten in de preprint in het attribuut “errorstring” geschreven worden.
Laten we bijvoorbeeld eens kijken hoe het YEAR-veld gecontroleerd wordt.
if (!($preprintref->{"YEAR"} =~ /^(\d{4})$/)) { $errorString .= "This is not a valid YEAR. Please change to e.g. \"YEAR 2004\".\n"; }
Dit betekent dat het YEAR-veld 4 cijfers moet bevatten.
- 21 -
•
addPreprint(preprintref : $hashref)
Deze methode voegt de opgegeven preprint als “value” toe aan een hash, waarbij de “key” het ID-nummer is. Hierdoor kunnen preprints zeer eenvoudig gesorteerd worden op IDnummer. Je hoeft dan enkel de sleutels (ID-nummers) van de preprints te sorteren. De referentie naar deze hash wordt bewaard onder het attribuut “preprints” van de klasse. Hieronder volgt een voorbeeld, met de structuur van de beschreven hash:
$VAR1 = { '2002-0001' => { 'FILES' => [ 'bestand1.pdf', 'bestand2.pdf' ], 'SORTBYNAME' => 'verhaeghe', 'ERRORSTRING' => '', 'STATUS' => 'PREPRINT', 'YEARID' => '2002-0001', 'TITLE' => 'Mijn eerste preprint: versie 2', 'KEYWORDS' => [ 'preprint' ], 'PREPRINTSTATE' => 'ENABLE', 'AUTHORS' => [ 'Ing. Verhaeghe Bart', 'Lic. Tavernier Stefaan' ], 'USER' => 'bverhaeg' },
'2002-0002' => { 'FILES' => [ 'bestand1.pdf', 'bestand2.pdf' ], 'SORTBYNAME' => 'verhaeghe', 'ERRORSTRING' => '', 'STATUS' => 'SUBMITTED', 'YEARID' => '2002-0002', 'COMMENT' => 'Een beetje commentaar ', 'TITLE' => 'Mijn tweede preprint', 'KEYWORDS' => [ 'preprint' ], 'PREPRINTSTATE' => 'ENABLE', 'AUTHORS' => [ 'Ing. Verhaeghe Bart', 'Lic. Tavernier Stefaan' ], 'USER' => 'bverhaeg' } };
Deze methode maakt ook een hash aan met de jaartallen als “key” en de ID’s van de preprints als “value”, en ook een hash met de waarde van “SORTBYNAME” als sleutel en de ID’s van de preprints als “value”.
Hiermee kan opnieuw gemakkelijk gesorteerd
worden op jaartal en op naam. De referenties naar deze twee hashes worden bewaard onder het attribuut “yearindex” en “authorindex” van de klasse.
De hashes zien er als volgt uit:
$VAR1 = { '2002' => [ '2002-0001', '2002-0002', };
$VAR1 = { 'verhaeghe' => [ '2002-0001', '2002-0002' ], };
- 22 -
•
writeUserPreprints(userref : $hashref, preprintref : @hashref)
Deze methode schrijft de opgegeven preprints terug weg naar het bestand van de opgegeven gebruiker. Hieronder zie je bijvoorbeeld hoe je voor elke preprint de titel kan wegschrijven.
if (open(DATAOUT, "> $filename")){ foreach my $preprintref (@preprintrefs){ if (exists $preprintref->{"TITLE"}) { print DATAOUT "TITLE ",$preprintref->{"TITLE"},"\r\n"; } } }
Na elke regel wordt de sequentie “\r\n” geschreven. Dit is een “return” in DOS-formaat. Zou nu alleen maar “\n” weggeschreven worden, dan zouden Windows-gebruikers hier problemen mee kunnen ondervinden. Maar daar Linux-gebruikers geen problemen hebben met het DOS-formaat, wordt dit dan ook gebruikt.
•
copyFile(source : $string, dest : $string)
Deze methode copieert een bestand. De methode wordt gebruikt om een backup-bestand aan te maken als er weggeschreven wordt naar het oorspronkelijke preprintsIG bestand.
•
printSortedByYear(year : $int)
Deze methode zoekt alle preprints die opgeslagen zijn onder het attribuut “preprints” van de klasse die onder het opgegeven jaar vallen. Nadien roept de methode de methode “printPreprints(preprintyearids : @string)” op met de gevonden ID’s. Het sorteren in PERL is vrij eenvoudig. Hier volgt een voorbeeld:
my $preprintyearidsref = $this->{"YEARINDEX"}{$year}; my @preprintyearidarray = @$preprintyearidsref; my @sorted = sort {$b cmp $a} @preprintyearidarray;
Opmerking: omdat arrays als referentie opslagen werden in de hash, moeten we een “dereference” toepassen (@$ = dereference naar een array).
- 23 -
•
printSortedByYears()
Deze methode print alle preprints volgens jaar af, gebruikmakend van de methode “printSortedByYear(year : $int)”.
•
printSortedByName(char : $char)
Deze methode sorteert alle sleutels van de hash die terug te vinden is onder het attribuut “yearindex”. Nadien roept het de methode “printPreprints(preprintyearids : @string)” op.
•
printSortedByNames()
Deze methode print alle preprints af gerangschikt op naam, gebruikmakend van de methode “printSortedByName(char : $char)”.
•
printSortedByKeyword(searchkey : $string)
Deze methode zoekt het opgegeven keyword in de velden “AUTHORS” en “KEYWORDS” van alle preprints die zich bevinden onder het attribuut “preprints” van de klasse.
Nadien
geeft
het
alle
gevonden
ID’s
door
aan
de
methode
“printPreprints(preprintyearids : @string)” om de gevonden preprints op de webpagina af te drukken.
•
printPreprints(preprintyearids : @string)
Deze methode print aan de hand van een opgegeven array van ID-nummers van preprints de gevraagde preprints af op de webpagina. Alle tekst die moet afgedrukt worden op de webpagina wordt gecodeerd in HTML-entities, zodat de ingegeven tekst het uitzicht van de webpagina niet kan beïnvloeden.
Hiervoor werd gebruik gemaakt van de module
“HTML::Entities”.
- 24 -
•
printYearIndex()
Deze methode print alle jaren waarvoor er een preprint bestaat af als een link op de webpagina.
•
printAllNameIndex()
Deze methode print het volledige alfabet af als een link naar de preprints met een SORTBYNAME-veld dat begint met de overeenkomstige letter.
•
printSearchByKeyword()
Deze methode plaatst het zoekveld op de webpagina.
•
setFileName(name : $string)
Met deze methode kan je de naam van het bestand waar de preprints zich in bevinden vastleggen.
•
setMaxYears(maxyears : $int)
Met deze methode kan je vastleggen hoeveel jaren er door de methode “printYearIndex()” op de webpagina zullen afgedrukt worden.
•
isUserPreprintChanged() : $string
Telkens wanneer er een preprint gewijzigd wordt, bijvoorbeeld doordat er een fout werd gevonden of wanneer een ID-nummer moet toegekend worden, wordt het attribuut “state” op “changed” gezet. Met de methode “isUserPreprintChanged() : $string” wordt nagegaan of de waarde van “state” gelijk is aan “changed”, zodat beslist kan worden om de preprints weg te schrijven naar het oorspronkelijke bestand. Na het oproepen van de methode wordt het attribuut “state” opnieuw leeggemaakt.
- 25 -
•
getErrorString() : $string
Telkens wanneer er in een preprint een fout gevonden wordt, wordt de fout in het attribuut “errorstring” van de klasse geschreven.
Wanneer we de methode “getErrorString() :
$string” oproepen, krijgen we alle fouten terug, zodat we deze kunnen emailen naar de gebruiker. Na het uitvoeren van deze methode, wordt het attribuut “errorstring” opnieuw leeggemaakt.
•
closeDatabase()
Wanneer alle preprints gecontroleerd zijn, kan je de database met deze methode afsluiten.
•
PreprintProcessor(database : $string, username : $string, passwd : $string)
De constructor van de klasse zorgt ervoor dat er een databank geopend wordt met de opgegeven parameters.
- 26 -
5.2 De klasse PreprintDatabase Bespreking van de methodes: •
check(yearid : $string, title : $string, userref : $hashref) : $string
Deze methode controleert of de opgegeven ID overeenkomt met de opgegeven titel van de preprint. Wanneer dit zo is, zal de methode een string met de waarde “OK” teruggeven. In het andere geval zal de methode ons niets teruggeven, maar de fouten schrijven in het attribuut “errorstring” van de klasse.
my $query = "SELECT title, user FROM preprints WHERE year = $year AND id = $id"; my $sth = $this->{"DATABASEHANDLER"}->prepare($query); $sth->execute(); my @row = $sth->fetchrow_array;
Hiermee wordt de titel en user opgevraagd van de gegeven ID uit de database. Er volgt een controle van de titel en user of deze dezelfde waarde hebben als de opgegeven argumenten.
•
existTableCreateTable()
Deze methode gaat na of de tabel “preprints” bestaat. Als deze niet bestaat, dan wordt er automatisch één aangemaakt.
my $query = "CREATE TABLE preprints(year YEAR(4) NOT NULL, id SMALLINT(6) UNSIGNED NOT NULL, title CHAR (255), user CHAR(8), PRIMARY KEY (year,id), UNIQUE(year,id))";
•
update(yearid : $string, title : $string, userref : $hashref) : $string
Deze methode zal de titel in de databank updaten, als het een nieuwe titel is. Wanneer eventuele fouten optreden, zoals een reeds bestaande titel, wordt dit in het attribuut “errorstring” van de klasse geschreven. Bij het succesvol uitvoeren, geeft de methode de waarde “OK” terug. my $query = "UPDATE preprints SET title = $qtitle WHERE year = $year AND id = $id"; my $sth = $this->{"DATABASEHANDLER"}->prepare($query); $sth->execute();
- 27 -
•
insert(year : $int, title : $string, userref : $hashref) : $string
Deze methode voert een nieuwe tupel toe aan de databank. Die tupel bestaat uit het IDnummer van de preprint, de titel en de gebruiker. Wanneer deze operatie succesvol werd uitgevoerd, zal de methode als waarde het nieuwe ID-nummer van de preprint teruggeven. Bij fouten zoals een reeds bestaande titel wordt er niets teruggegeven, en worden de foutmeldingen in het attribuut “errorstring” van de klasse geschreven. Het ID-nummer wordt bekomen door het hoogste ID-nummer voor een gegeven jaar in de databank te zoeken, en deze met 1 te vermeerderen.
my $query = "SELECT MAX(id) FROM preprints WHERE year = $year"; my $sth = $this->{"DATABASEHANDLER"}->prepare($query); $sth->execute(); my $query="INSERT INTO preprints(year,id,title,user)VALUES($year,$id,$qtitle,'".$userref>{"USER"}."')"; my $sth = $this->{"DATABASEHANDLER"}->prepare($query); $sth->execute();
•
getUserPreprints(userref : $hashref) : $string
Deze methode schrijft alle ID’s en titels uit van de preprints die voor de opgegeven gebruiker in de databank bestaan.
•
closeDatabase()
Hiermee wordt de databank afgesloten.
•
getErrorString() : $string
Al de foutmeldingen die bij één van de methodes zijn ontstaan, kunnen met deze methode worden opgevraagd. Na het uitvoeren van de methode worden de foutmeldingen gewist.
- 28 -
5.3 De klasse UserProcessor Bespreking van de methode: •
readUsers(userfile : $string) : @hashref
Deze methode leest de gebruikers in, uit het opgegeven bestand. De methode geeft ons na uitvoeren een array van referenties terug. Deze referenties verwijzen elk naar een hash waar een gebruiker is opgeslagen. Hieronder zie je een array met twee referenties die elk verwijzen naar een hash waar een gebruiker in opgeslagen zit.
$VAR1 = { 'NAME' => 'Bart Verhaeghe', 'USER' => 'bverhaeg', 'EMAIL' => '
[email protected]', 'PATH' => '/u/bverhaeg/public_html' };
$VAR2 = { 'NAME' => 'Stefaan Tavernier', 'USER' => 'staverni', 'EMAIL' => '
[email protected]', 'PATH' => '/u/staverni/public_html' };
5.4 De klasse ErrorMailSystem Bespreking van de methodes: •
ErrorMailSystem(mailserver : $string)
Dit is de constructor van de klasse “ErrorMailSystem”. De constructor wordt opgeroepen met een argument dat de mailserver vastlegt.
•
sendMail(from : $string, errorstring : $string, userref : $hashref)
Deze methode verzendt een email naar de opgegeven gebruiker met de verzamelde foutmeldingen. Om een email te versturen in PERL, wordt gebruik gemaakt van de module “Net::SMTP”.
- 29 -
5.5 De klasse StringManip Bespreking van de methodes: •
trim(@_string): $string
Deze methode neemt een lijst van strings als argument en verwijdert de spaties voor en na een string, en geeft het resultaat als string terug.
•
trimArray(@_string) : @string
Deze methode neemt een lijst van strings als argument en verwijdert de spaties voor en na elke string, en geeft een array van strings terug.
•
getYearFromYearID(yearid : $string) :$string
Deze methode geeft het jaartal terug uit een opgegeven ID-nummer. Bijvoorbeeld 2002 uit het ID-nummer 2002-0001.
•
getIDFromYearID(yearid : $string) : $string
Deze methode geeft het volgnummer terug uit een opgegeven ID-nummer. Bijvoorbeeld 0001 uit het ID-nummer 2002-0001.
•
generateYearID(year : $string, id : $string) : $string
Deze methode maakt een ID-nummer aan uit het jaartal en een volgnummer. Bijvoorbeeld het ID-nummer 2002-0001 uit het jaar 2002 en het volgnummer 0001.
•
parseYearID(yearid : $string) : $string
Deze methode maakt een nieuw ID-nummer aan uit het gegeven ID-nummer, zodat het IDnummer zeker geldig is.
- 30 -
5.6 Het hoofdprogramma preprinthtml Dit is het hoofdprogramma dat alle andere zelfgeschreven modules gebruikt bij zijn werking. Het maakt gebruik van de module “CGI” om de gewenste sortering af te leiden uit de HTML-querystring. De volgende code bouwt de hash “input” op met alle waarden die in de querystring voorkomen, zodat ze verder in het programma kunnen gebruikt worden.
$cgi = new CGI; for $key ( $cgi->param() ) { $input{$key} = $cgi->param($key); }
- 31 -
Hoofdstuk 6 Sequentiediagram
:preprinthtml
:Userprocessor
:PreprintProcessor
:PreprintDatabase
1: readUsers() 2: readUserPreprints() 3: processUserpreprints() 4: processUserPreprint()
5: [PSTATE = ENABLE & ID] check() 6: [PSTATE = ENABLE & YEAR] insert() 7: [PSTATE = UPDATE] update() 8: [PSTATE = FIND] getUserPreprints() 9: getErrorString()
10: processUserPreprint()
11: isUserPreprintChanged() 12: [CHANGED] writeUserPreprint() 13: getErrorString() 14: [ERROR] sendMail()
15: readUserPreprints()
16: closeDatabase() 17: printYearIndex() 18: printAllNameIndex() 19: printSearchByKeyword() 20: printPreprints()
- 32 -
:ErrorMailSystem
Bespreking van het sequentiediagram Het sequentiediagram toont schematisch de werking van het programma aan. Om in grote lijnen de werking van het programma uit te leggen, zal dan ook het sequentiediagram stap voor stap gebruikt worden. De nummers hier verwijzen naar het diagram.
1) De gebruiker die de webpagina wil bekijken, roept via een hyperlink het PERLprogramma op. Dit programma zal vervolgens de gebruikers uit een bestand inlezen.
2) Nu we alle gebruikers kennen, kunnen we voor elke gebruiker zijn preprints inlezen. De methode om de preprints in te lezen, leest enkel de preprints in van de opgegeven gebruiker.
3) Alle preprints die we in de vorige stap bekomen hebben, worden nu verwerkt.
4) De preprints worden één voor één bekeken. Deze methode is intern in de klasse opgesplitst in de volgende submethodes: “preprintEnable()”, “preprintUpdate()” en “preprintFind()”.
Deze
submethodes
worden
opgeroepen
al
naargelang
PREPRINTSTATE gevolgd wordt door ENABLE, UPDATE of FIND. De methodes “preprintEnable()”
en
“preprintUpdate()”
zullen
trouwens
ook
de
methode
“isValidUserPreprint()” aanroepen om de preprint te valideren.
5) Wanneer tijdens het uitvoeren van de methode “preprintEnable()” de preprint het veld ID blijkt te hebben, dan wordt de methode “check()” aangeroepen. Deze zal de geldigheid van het ID-nummer en de titel controleren.
6) Wanneer in het andere geval de preprint een veld YEAR heeft, dan wordt de methode “insert()” uitgevoerd. Hierdoor zal de preprint een nieuw ID-nummer toegekend krijgen.
7) Tijdens het uitvoeren van de methode “preprintUpdate()” zal de methode “update” aangeroepen worden. Deze zal de titel voor het opgegeven ID-nummer in de databank updaten.
- 33 -
8) Tijdens het uitvoeren van de methode “preprintFind()” zal de methode “getUserPreprints()” aangeroepen worden. Deze methode zal een “errorstring” opbouwen met alle ID’s en titels die in de databank aanwezig zijn voor de huidige gebruikersnaam.
9) Na het uitvoeren van één van de vorige methodes, willen we weten of het uitvoeren wel goed gelukt is. Via het uitvoeren van de methode “getErrorString()” komen we alle fouten te weten.
10) Van puntje 4 tot en met puntje 9 hebben we besproken hoe één preprint verwerkt werd. Alle andere preprints die ook in hetzelfde preprintbestand aanwezig zijn, zullen op dezelfde manier verwerkt worden.
11) Na alle preprints te hebben verwerkt voor één bepaalde gebruiker, willen we weten of er iets veranderd is door het systeem aan een preprint.
12) Als dit zo is, dan moeten de preprints opnieuw weggeschreven worden naar het oorspronkelijke bestand.
13) Ook willen we weten of er fouten zijn opgetreden tijdens het verwerken van alle preprints van één bepaalde gebruiker.
14) Wanneer er eventuele fouten zijn opgetreden, zullen deze fouten naar de gebruiker gemaild worden.
15) De stappen 2 tot en met 14 worden voor elke gebruiker herhaald. Hierdoor zijn alle preprints van alle gebruikers verwerkt en bewaard onder de klasse “PreprintProcessor”.
16) Na alle preprints verwerkt te hebben, wordt de databank afgesloten.
17) Nu alle preprints verwerkt zijn, moeten ze op de website verschijnen. Eerst en vooral worden alle jaren waarvoor er preprints beschikbaar zijn als een link uitgeprint. De websurfer kan hierdoor de preprints per jaar bekijken.
- 34 -
18) Ook worden alle letters van het alfabet afgeprint. Bij het aanklikken van één van de letters, verschijnen de preprints waarvoor het SORTBYNAME veld begint met de overeenkomstige letter.
19) Er kan ook gezocht worden op een keyword of op auteur. Hiervoor moet een zoekveld getekend worden op de webpagina.
20) Naargelang de keuze van de bezoeker van de website zal de methode “printSortedByYear()”,
“printSortedByYears()”,
“printSortedByName()”,
“printSortedByNames()” of “printSortedByKeyword()” opgeroepen worden om de preprints op de website uit te schrijven.
Wanneer we dus preprints op de website willen bekijken, zullen bij het genereren van de pagina de preprints van alle gebruikers verwerkt worden.
- 35 -
Hoofdstuk 7 Foutmeldingen
Het programma kan volgende foutmeldingen uitschrijven in het preprintsIG-bestand.
PREPRINTSTATE ERROR YEAR 1999 SORTBYNAME tav AUTHORS Tavernier Stefaan TITLE moeilijkheden ERROR You have already used the title: "moeilijkheden" with ID: "2000-0001".
Dit komt voor wanneer men een bestaande titel opnieuw probeert te gebruiken. Een gebruiker moet iedere keer een nieuwe titel gebruiken voor zijn artikels.
PREPRINTSTATE ERROR ID 2000-0002 SORTBYNAME albert AUTHORS George Albert TITLE moeilijkheden ERROR This is an unknown ID: "2000-0002". ERROR The ID "2000-0001" matches with the given title.
Deze error duidt erop dat men het ID-veld ongeoorloofd heeft aangepast; de titel is echter gekend voor 2000-0001, dus het nummer aanpassen is hier de oplossing.
PREPRINTSTATE ERROR ID 1999-0001 SORTBYNAME tav AUTHORS Tavernier Stefaan TITLE moeilijkheden ERROR "moeilijkheden" is already in use.
Hier werd geprobeerd een bestaande titel te updaten, maar er werd een reeds gebruikte titel gebruikt. Met een andere titel moet het wel lukken.
- 36 -
PREPRINTSTATE ERROR SORTBYNAME tav TITLE foeifoei ERROR No AUTHORS found. ERROR No YEAR or ID found. ERROR For a new preprint, YEAR is a necessity. ERROR For an existing preprint, ID is a necessity. ERROR Preprint is not correct and disabled.
Hierboven werden noodzakelijke velden vergeten. Title, sortbyname, authors en year (of ID na verwerking) zijn noodzakelijk. Opdat de preprint ingelezen zou worden, is ook preprintstate noodzakelijk.
PREPRINTSTATE ERROR ID 2005-0001 SORTBYNAME tavernier AUTHORS Tavernier S. TITLE Dit is een nieuwe preprint STATUS PUBLISHED ERROR This is an unknown ID: "2005-0001".
Hier is opnieuw een probleem met het ID-nummer. Het nummer is nog niet opgeslagen in de databank en dus onbekend. Het ID-veld moet waarschijnlijk vervangen worden door een year-veld.
PREPRINTSTATE ERROR YEAR 04 SORTBYNAME tav AUTHORS Tavernier Stefaan TITLE heden ERROR This is not a valid YEAR. Please change to e.g. "YEAR 2004". ERROR Preprint is not correct and disabled.
Het probleem hier is dat het jaartal niet volledig werd ingevuld. Het programma verwacht een jaartal met 4 cijfers.
- 37 -
Hoofdstuk 8 Besluit
Het geproduceerde programma voldoet aan de gestelde eisen, het geeft nieuwe preprints een identificatienummer, spoort fouten op en verwittigt de bevoegde persoon per e-mail zodat de fouten snel kunnen aangepast worden. Om fouten te vermijden hebben we een webformulier gemaakt, dat correcte code genereert via voorgedefinieerde velden. Een backup van het bestand wordt gemaakt om op terug te vallen. We zorgden ook voor een handleiding in het Engels en een installatiehulp voor de systeembeheerder.
We speelden ook met het idee de records in XML op te stellen, maar dat bleek niet zo praktisch: het zou nochtans gemakkelijk geweest zijn om na te gaan of alle benodigde velden aanwezig waren: via het XML-schema, dat bepaalt welke velden mogelijk zijn, kan men dit gemakkelijk nagaan, maar dit bleek het programma zwaar te vertragen. XML is ook ingewikkelder om op te stellen wanneer men in het bestand de code aanpast. Zonder XML is het echter gevaarlijk om een tag te vergeten, maar wij raden altijd aan om het webformulier te gebruiken om een preprint toe te voegen.
Wij zien verder nog ruimte voor mogelijke verbeteringen: -een webformulier dat nagaat welke gebruiker iets toevoegt, met beveiliging per gebruiker, en zo onmiddellijk in het juiste bestand schrijft, -onmiddellijk in het webformulier nagaan of het jaar geldig is en controleren of de bestanden bestaan, of de titel al bestaat, -bij een update kan het ID-nummer gebruikt worden om de overige info uit de databank te halen zodat niet nodeloos moet getypt worden, -gebruikersinformatie kan mogelijk automatisch uit het linuxbestand passwd, waar de systeemgebruikers in zitten, gehaald worden.
- 38 -
BIJLAGE 8.1 Manual To add preprints to the preprint section of the home page of the research group follow the steps below. 1. if not existing, create the directory 'public_html' in your home directory on cage 2. with the editor of your choice edit a file 'preprintsIG' in the directory 'public_html'. MAKE SURE THE SECURITY SETTINGS FOR THIS FILE MAKE IT WRITABLE. You do this by typing 'chmod 777 preprintsIG' in the command line. To keep a backup of the file before processing by the program, create a file 'preprintsIG.old' in the same directory and make it writable. 3.To make it easier for you we have made a web form which generates the code which you can than copy into 'preprintsIG'. Make sure to copy plain text and not the HTML table. The file preprintsIG must have the following format. For each preprint the following lines are required: preprintstate, year, sortbyname, author(s), title, and file. After preprintstate you have to put 'enable' before the record will be processed. The sortbyname-field is required to sort the preprints, so put the required name there. There can be optional lines like 'comment', 'msc-class', 'status' (of publication). A section in preprintsIG can look like: author Matthew Brown, Jan De Beule and Leo Storme year 2005 title Partial Spreads of T2(O) and T3(O) files preprint1.ps, preprint1.pdf comment any comment keywords so people can search on the keywords added msc-class status accepted (by a periodic; only one word is allowed, see the web form for the 4 possibilities) If you want to add another preprint, just add a similar section in the file. The year-fields will eventually change in an ID number, don't worry about that. If an error is detected you will be notified by e-mail. Take care about the format of the file preprintsIG. Just put a space between e.g. author and the information. Do not use enter in a field when you write over several lines. Also do not have open lines in a record; the second part will not be processed. Filenames on cage are case sensitive, so in the filename 'preprints' need to be in lower case and 'IG' in uppercase. 4. The system assumes that the preprints itself reside in the directory 'public_html'. The value of the field file: must be a comma separated list with existing files in that directory. If so, links to these files will be created.
-i-
It is possible that your account and public_html directory are not readable for the outside world. Therefore you should test if the preprints are displayed. If not, issue the following commands (only once): chmod 711 ~loginname chmod 711 ~loginname/public_html And, everytime you add a preprint with filename name.foo chmod 644 ~loginname/public_html/name.foo any question: mail Jan
8.2 Info for the system administrator Installatie -kopieer de .cgi en .pm bestanden in 1 directory -in het configuratiebestand PreprintConfig.pm kun je opgeven hoe de bestanden heten, welke server gebruikt wordt, ... -volgens
die
instellingen
moet
je
een
MySQL-databank
-pas de schrijfbeveiligingen op de bestanden aan
8.3 Programma code De volledige programmeercode is te vinden op de bijgevoegde CD.
- ii -
aanmaken