Een dBaseIII+-bestand lezen met JavaScript
Lang geleden, in de jaren '80 en '90 van de vorige eeuw en MS-DOS hèt operating system was voor PC's, bestond er
een programma voor relationele databases met de naam dBaseIII+. De vergaande mogelijkheden om daar programma's voor te schrijven
maakte het bijbehorende bestandsformaat (.dbf) razend populair, maar er was iets toch niet helemaal in orde:
Citaat: "De belangrijkste beperking was dat database-integriteit niet standaard werd afgedwongen, maar in ieder afzonderlijk
programma moest worden ingebouwd. Mede daardoor verloor het in de jaren negentig veel marktaandeel aan producten die hiertoe
wel in staat waren".. (bron: Wikipedia)
De maker van dBaseIII+,
Alle versies van dBase zijn nog te downloaden van winworldpc.com.
Het is interessant om te proberen zo'n database te benaderen met JavaScript. Op deze pagina wordt code besproken waarmee dat kan. Het programma leest databases van dBaseIII+ en dBaseIV, die echter geen memo-velden kunnen hebben.
De downloadfile bevat naast de code van het voorbeeld een dBaseIII+-bestand, zodat je een beetje met het voorbeeld kunt spelen.
Het voorbeeld is bedoeld als "proof of concept", om te bewijzen dat het mogelijk is dBaseIII+-bestanden in te
lezen met HTML en JavaScript, zodat de inhoud kan worden gebruikt in een web-applicatie. Tests of het wel gaat om een geldig
.dbf-bestand gaat zijn ingebouwd, maar mogelijk te beperkt.
De code van het voorbeeld kun je downloaden om zelf te gebruiken of om aan door te ontwikkelen, echter zonder de "Sluiten"-knop.
Zie daarvoor punt 3 in het item Window openen/sluiten vanuit een ander window.
- Er worden drie items besproken:
- De opbouw van een dBaseIII+-bestand.
- De werking van het script.
- Toepassen in je eigen site.
- Inspiratie voor dit item is ontstaan toen ik de code terugvond van een Turbo Pascal-Unit die dBaseIII+-bestanden kan lezen. Die code heb ik in de jaren 90 van de vorige eeuw zelf geschreven als onderdeel van een hobby-project. De JavaScript-code is een wat eenvoudiger versie van de Pascal-code.
- De HTML en het JavaScript vormen de kern van het voorbeeld. CSS wordt alleen gebruikt voor de opmaak.
- De opbouw van een dBaseIII-bestand.
- Een DBaseIII+-bestand is een reeks bytes, geheel (dBaseIII+ en eerder) of bijna geheel (DBaseIV en later) gecodeerd in de ASCII-tekenset.
- De inhoud bestaat uit een header, gevolgd door de records met data. Deze twee delen worden gescheiden door een byte met
ASCII-karakter 13 (= Control+M). Het einde van het bestand is een byte met ASCII-karakter 26 (= Control+Z).
- De header bevat de beschrijving van de database in termen van aantal records, naam en lengte van de datavelden, enzovoort.
- De records zijn onderverdeeld in velden, elk met een vaste, bij de aanmaak van de database gedefinieerde, lengte.
- Volg DEZE LINK voor een beschrijving van de database.
- De werking van het script
- Zodra een bestand is gekozen wordt dat ingelezen. Daarvoor wordt de
File API ingezet. Deze API wordt
beschreven in het item Een tekstfile lezen met JavaScript. Deze applicatie
gebruikt readAsArrayBuffer() omdat het, hoewel byte voor byte ASCII-gecodeerd, geen 'echte' tekstfile is. De arrayBuffer
wordt omgezet naar een gewone array voor verdere bewerking.
- Vervolgens worden de header-data uit de array gehaald en in de bovenste van de twee tekst(-uitvoer)boxen gezet.
Uit de gegevens van de datavelden wordt de lengte van een record berekend en vergeleken met het getal dat elders in de header staat. De uitvoering van het programma wordt gestaakt als er verschil is.
Ook wordt gekeken of het aantal datavelden overeenkomt met de header-data. Het programma stopt als er verschil is. - Tenslotte worden de records veld voor veld uit de database gehaald en in de onderste tekst(-uitvoer)box gezet. De
afzonderlijke velden worden gescheiden door een verticale streep, met aan weerszijden een spatie. Dit tn behoeve van de
leesbaarheid.
Als eerste wordt de wismarkering getoond (spatie of asterisk). Verwijderde records worden dus gewoon getoond.
Tenslotte wordt gekeken of het aantal getoonde records overeenkomt met de data in de header. Er volgt een melding als er verschil is en het programma stopt gewoon.
- HTML
- De HTML voor het openen van een bestand bestaat uit één input-tag met type="file", een id en het attribuut accept, waarin de extensie van soort in te lezen bestanden staat. Voor deze applicatie is dat ".dbf".
- Voor het tonen van de inhoud van de file is extra HTML, CSS en JavaScript nodig, Dit omdat de binaire informatie eerst moet worden omgezet in leesbare informatie.
- <input type="file"> maakt een knop met de tekst "Bestand kiezen", met daarbij de tekst "Geen bestand gekozen". Als er een bestand is gekozen wordt "Geen bestand gekozen" vervangen door de naam van het bestand. Die teksten worden door de browser gegenereerd in combinatie met de taalinstelling van het Operating System. Ze kunnen niet worden aangepast. Je kunt ze wel onzichtbaar maken door met CSS color:transparent in te stellen.
- Met Windows en Linux wordt de map waar je het laatst een bestand hebt geopend door de browser onthouden.
- Voor het tonen van de leesbaar gemaakte informatie zijn in het voorbeeld <textarea>'s ingezet, elk met een
eigen
id voor de communicatie met JavaScript. Er is gekozen voor <textarea> omdat het eenvoudig is om scrollbars te maken als de tekst er niet in past (op aanraakschermen komen er geen scrollbars, daar kun je swipen). - Om te zorgen dat de record-data steeds op één regel komt, heeft de onderste <textarea> het attribuut wrap="off".
- De knop "Overnieuw" is een input-tag met type="button". Deze laadt de pagina opnieuw.
Dat is de eenvoudigste manier om het FileReader-object te resetten.
- CSS
- De CSS, die in het voorbeeld in de <head> staat, maakt alleen de twee <textarea>'s op. Om de tekst eenvoudig in kolommen te krijgen, is het default monospace-font van de browser gebruikt.
- JavaScript
- Het JavaScript bestaat uit twee delen:
- Het lezen van het bestand en de omzetting naar een gewone array met bytes.
- Het verwerken van de bytes.
- Het lezen van het bestand en de omzetting naar een gewone array met bytes
- Dit deel van het JavaScript staat in de <body>. Het moet na de tag input type="file" komen, omdat de id van de input-tag moet bestaan.
- Er is alleen een EventListener die reageert als het file-navigatie dialog is veranderd en die het volgende doet:
- Er wordt een constante gedefinieerd (in het voorbeeld: file), waar de naam van het gekozen bestand in wordt gezet. Die naam wordt gehaald uit event.target.files[0]. Dit verwijst naar de naam van het gekozen bestand, die wordt opgeslagen op positie [0] van de array files van het object FilesList binnen de File API.
- Als de file niet bestaat, gebeurt er niets. Deze conditie treedt op als de file wordt verwijderd nádat de applicatie is gestart en vóórdat de keuze wordt gemaakt. In de praktijk zal dat nauwelijks voorkomen.
- Als de file wel bestaat, wordt een nieuw FileReader-object gemaakt.
- Vervolgens wordt een event-handler gedefinieerd die actief wordt als het bestand helemaal is gelezen (en load wordt afgevuurd).
- Tenslotte wordt het bestand ingelezen met readAsArrayBuffer(file).
- De event-handler zet de gelezen data in een constante, in het voorbeeld is dat content.
- Vervolgens wordt content in een array van unsigned 8-bits integers gezet. Dat gebeurt met bytes = new Uint8Array(content). een 8-bits unsigned integer is een getal dat minimaal 0 en maximaal 255 is.
- Vervolgens wordt de inhoud van bytes naar de "gewone" array DBFbytesArray gekopieerd en wordt de function runProgram() gestart voor de verdere verwerking.
- Tenslotte wordt, om conflicten met FileReader te voorkomen, de knop "Bestand kiezen" uitgeschakeld. (De pagina wordt opnieuw geladen door op "Overnieuw" te klikken.)
- Het verwerken van de bytes
- Dit deel van het JavaScript staat in de file dbaseiii.js. De meest belangrijke function hier is runProgram(),
die de data in de de array DBFbytesArray verwerkt. Daarnaast zijn er nog een paar hulp-functions.
Zie ook de beschrijving van de dBaseIII+ database.
- Als eerste wordt DBFbytesArray gedeclareerd, als dynamische array.
- De function runProgram() begint met de declaratie van een reeks variabelen, waarvan de scope dus is beperkt tot deze function.
- De variabelen xx en yy wordt geïnitialiseerd als verwijzing naar de bovenste resp. onderste <textarea>.
- Als eerste wordt het eerste byte bekeken. Als dat niet geldig is stopt de uitvoering van het programma.
- Daarna worden achtereenvolgens de datum van de laatste wijziging, het aantal records in de database, het aantal bytes in de header en het aantal bytes in een record gerapporteerd.
- Nu zijn de veldbeschrijvingen aan de beurt. Het eerste byte wordt bekeken: als dat ASCII-teken 13 is, is dat het einde
van de header en wordt begonnen met de rapportage. Anders worden de veldbeschrijvingen verwerkt. Daarvoor wordt een viertal
variabelen geïnitialiseerd:
º nVelden = 0 Dit is een schaduwtelling, om te vergelijken met de header-informatie.
º vIndex = 32 Dit is de byte waar de dataveld-informatie begint.
º mBytesPerRecord = 0 Dit is een schaduwtelling, om te vergelijken met de header-informatie.
º iVeld = 0 Dit is een index in de array DBFbytesPerVeld, die wordt gebruikt bij het daadwerkelijk rapporteren van de velden. - Als het eerste byte iets anders is dan ASCCI-teken 13, wordt dat in een string gezet, die later verder wordt bewerkt. Die string bevat de veldnaam, het veldtype, de veldlengte en het aantal decimalen, gescheiden door komma's. De string wordt opgeslagen in DBFveldenArray. Tenslotte wondt vIndex opgehoogd met 32, dat is het aantal bytes per record.
- Zodra ASCII-teken 13 is gevonden, wordt een tweetal checks uitgevoerd. Het aantal velden wordt berekend en vergeleken met de header-data. Ook het berekende aantal bytes per record word vergeleken met de header-data. Als een van beide tests faalt, stopt het programma.
- Tenslotte wordt de lijst van datavelden in DBFveldenArray op het scherm gezet.
- Nu zijn de records aan de beurt. ls eerste wordt vIndex met 1 opgehoogd, om DBFbytesArray goed te positioneren. Ook wordt de schaduwteller mRecords geïnitialiseerd op 0.
- Vervolgens wordt voor het aantal records nRecords, genoemd in de header, het eerste byte bekeken. Als dat niet ASCII-teken 26 is, is het een record. Deze byte wordt opgeslagen in een string, gevolgd door " | ".
- Daarna wordt voor alle datavelden byte voor byte de informatie aan de string toegevoegd, aan het eind van elk veld gevolgd door " | ". Zodra het hele record is behandeld wordt de string op het scherm gezet en worden mRecords en vIndex verhoogd.
- Zodra ASCII-teken 26 wordt gevonden in het eerste byte, is de hele database doorgelezen. Er is nog één test,
nl. of mRecords gelijk is aan het aantal records dat genoemd is in de database. Als dat niet zo is volgt er nog een
melding. In elk geval stop het pogramma.
- N.B.: Bij het tellen van het aantal records wordt de wismarkering genegeerd. Het aantal records is dus inclusief
de verwijderde records. dBaseIII+ doet dat ook zo.
- De function voorloopNullen() plaatst een nul voor een enkel cijfer. Dit wordt alleen gebruikt bij het rapporteren van de "datum laatste wijziging".
- De function bytes2Int16(index) zet de byte, aangewezen door index, en de volgende byte om in een 16-bits integer getal.
- De function bytes2Int32(index) zet de byte, aangewezen door index, en de drie volgende bytes om in een 32-bits integer getal.
- De function TrailSpace(stg,maxlen) vult de string stg aan het einde aan met spaties, zodat stg.length gelijk wordt aan maxlen.
- De function leadSpace(stg,maxlen) vult de string stg aan het begin aan met spaties, zodat stg.length
gelijk wordt aan maxlen.
- Toepassen in je eigen site
- Download de .zip-file en pak hem uit. Verander eventueel de filenaam html800a.htm in index.html. Je hebt nu een werkend voorbeeld.
- Kopieer de code van de <body> van html800a.htm naar je eigen project. Minimaal benodigd zijn:
- De regel
<script src="dbaseiii.js"></script>
in de <head>. - De regel
<p><input type="file" id="fileInput"
in de <body>.
accept=".dbf"></p> - De tag
<script> … </script>
-tag in de <body>. - De regel
<p><input type="button"
in de <body>.
onclick="window.location.reload()" value="Overnieuw"></p>
- De regel
- Als je de bovenste <textarea> weglaat, moet je alle referenties aan de variabele
xx
in de file dbaseIII.js verwijderen.
Als je de onderste <textarea> weglaat, moet je alle referenties aan de variabeleyy
in de file dbaseIII.js verwijderen. - Pas de kleur van de achtergrond aan in de <body>-tag. Pas afmetingen van de <textarea> aan, enz.
- Tenslotte...
- De hier besproken applicatie leest alleen de .dbf-bestand en toont de inhoud op het scherm. Verder wordt daar niets mee gedaan. Als je de data wilt bewerken, of er mee wilt gaan rekenen, zul je mogelijk veel extra code nodig hebben. Die zet je het beste in aparte functions, dus niet binnen runProgram(). Het kan daarbij nodig zijn om de declaratie van variabelen te verplaatsen naar buiten runProgram() om de scope te vergroten.
Gebruik:
- De volledige code staat in de downloadfile.
Downloaden:
Druk op de knop:
File: voorb800.zip, 3396 bytes.