Ik heb vandaag mijn eerste zelfgegenereerde IMS-contentpackage in N@tschool weten te importeren en tot studieroute weten om te zetten.
Zelfgegenereerd omdat zowel bijna alle ruim 200 html-bestanden als het bijbehorende imsmanifest.xml zijn gecreeerd met behulp van een Access-database en een paar VBA-scriptjes.
Omdat de hierbij toegepaste techniek redelijk universeel toepasbaar is bij het maken van een grote reeks XML- of html-bestanden (of een groot samengesteld bestand als een manifest) in deze post wat meer uitleg over de gevolgde werkwijze.
De uitdaging
Bij datgene wat ik vandaag gemaakt heb (waarvan ik het eindresultaat helaas niet kan laten zien) ging het om een hele reeks (links naar) korte instructiefilmpjes. Alle metadata (titel, omschrijving, url) staan in een database. Gevraagd: voor elk filmpje een html-pagina te maken met daarin een overzichtelijke presentatie van de gegevens + een embedded mediaspeler waarop het filmpje kan worden afgespeeld. Het geheel te presenteren als studieroute in N@tschool, bij voorkeur met gebruikmaking van de standaard Natschool-navigatiestructuur.
Navigatiestructuur = IMS-contentpackage
Het probleem de uitdaging van de navigatiestructuur kan opgelost worden door aan de set html-bestanden een IMS-manifest toe te voegen. Dat is een XML-document waarin vastgelegd is wat voor bestanden er precies inzitten, wat de resources zijn (de als afzonderlijke eenheid te benaderen onderdelen) en welke hulpbestanden daarbij horen (afbeeldingen, stylesheets etc). Verder kun je in het manifest de onderlinge samenhang (zeg maar de sitemap) bepalen in het onderdeel 'organisation'.
Overzicht gebruikte werkwijze
Om de gewenste bestanden te kunnen genereren zijn de volgende componenten nodig
- Een tabel met daarin de onveranderlijke regels uit het uiteindelijk te genereren document (zeg maar de 'heading' en 'footing')
- Een tabel die als template fungeert voor ieder record wat moet worden opgenomen in het eindresultaat
- een tabel (of query) met gegevens die ingevoegd moeten worden
- een VBA (visual basic for applications) -script die het een met het ander combineert
Het script opent een tekstbestand voor schrijven, plaatst daar eerst de heading-regels in, vervolgens wordt voor elk record in de gegevenstabel een 'opgemaakt' stuk tekst gegenereerd waarna tenslotte de afsluitende 'footing'-regels worden toegevoegd.
Indien gewenst kan per (hoofd)record nog een aantal subrecords worden aangemaakt.
De tabelstructuren
Hieronder een overzicht van de structuur van de tabel met onveranderlijke gegevens.
De kolom 'gebruik' wordt in het VBA-script gebruikt om onderscheid te maken tussen het gedeelte wat aan het begin van het document moet komen en het gedeelte op het eind.
Deze tabel laat zich gemakkelijk aanmaken in Access door een voorbeeldbestand te hernoemen in .txt en het vervolgens te importeren in Access. Extra kolom toevoegen en Klaar is Kees.
vaste gegevens
| Nr |
gebruik |
Tekst |
| 10 |
start |
[?xml version="1.0" encoding="utf-8"?] |
| 20 |
start |
[manifest identifier="IMSMANIFEST" version="1.1" xmlns="http://www.imsglobal.org/xsd/imscp_v1p1" xmlns si="http://www.w3.org/2001/XMLSchema-instance" |
| 21 |
start |
xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 |
| 22 |
start |
http://www.imsglobal.org/xsd/imscp_v1p1.xsd"] |
| 30 |
start |
[metadata] |
| 40 |
start |
[schema]IMS Content[/schema] |
| 50 |
start |
[schemaversion]1.1[/schemaversion] |
| 60 |
start |
[/metadata] |
| 70 |
start |
[organizations default=""] |
| 80 |
start |
[organization identifier="" structure="hierarchical"] |
| 90 |
start |
[title]Overzicht[/title] |
| 100 |
start |
[item identifier="Main_Overzicht" isvisible="true" identifierref="Main_overzicht"] |
| 110 |
start |
[title]Overzicht[/title] |
| 120 |
eindsub |
[/item] |
| 180 |
resources |
[/item] |
| 370 |
resources |
[/organization] |
| 380 |
resources |
[/organizations] |
| 390 |
resources |
[resources] |
| 2760 |
eind |
[resource identifier="Main_Overzicht" type="webcontent" href="PMed_NasWS_index.htm"] |
| 2770 |
eind |
[file href="_index.htm" /] |
| 2780 |
eind |
[file href="voorbeeld.css" /] |
| 2790 |
eind |
[file href="player.js" /] |
| 2795 |
eind |
[/resource] |
| 2800 |
eind |
[/resources] |
| 2810 |
eind |
[/manifest] |
De tabel met template voor variabele gegevens
De tabel die de template vormt voor de variabele gegevens bestaat uit 7 kolommen.
De structuur is simpel: het veld 'begin' bevat het begin van de af te drukken regel, dan staat in veldnm1 de naam van het veld uit de gegevenstabel, er is ruimte voor een 'tussentekst', eventueel kan een tweede veldnaam worden opgegeven en tenslotte de sluittekst (of -tag).
template voor variabele gegevens
| nr |
gebruik |
begin |
veldnm1 |
tussen |
veldnm2 |
eind |
| 130 |
|
[item identifier=" |
ref |
" isvisible="true" identifierref=" |
ref |
"] |
| 140 |
|
[title] |
ondertitel |
|
|
[/title] |
Door twee veldnamen te gebruiken kun je de meest voorkomende regels html of xml code genereren. Ik heb deze methodiek inmiddels voor een hele reeks toepassingen gebruikt en heb eigenlijk nog nooit meer dan twee velden nodig gehad. Als je er meer nodig hebt dan kun je meestal ook wel meerdere regels gebruiken.
VBA-script: onderdeel MaakXMLBestand
In onderstaand voorbeeld wordt eerst de aanmaak van het IMS-manifest besproken. Hierbij gaat het om één bestand (imsmanifest.xml) met hoofd- en subsecties.
Sub MaakXMLBestand()
Dim strBasisPad As String
Dim bestandsnaam As String
Dim Handle As Byte
strBasisPad = "\pad\naar\het\bestand\"
bestandsnaam = "imsmanifest.xml"
Handle = FreeFile
Open strBasisPad & bestandsnaam For Output As #Handle
'beginregels afdrukken
DrukVasteTekstAf Handle, "start"
Print #Handle, ""
'organisation
MaakDeelXML Handle, SVH_main:="Qry_banden", SVH_XML_main:="Manifest_XML_main", _
SVH_sub:="qry_fragm", SVH_XML_sub:="Manifest_XML_sub"
Print #Handle, ""
'start van resources
DrukVasteTekstAf Handle, "resources"
'overzichtspagina's
MaakDeelXML Handle, SVH_main:="Qry_banden", SVH_XML_main:="resources_XML_main"
Print #Handle, ""
'afzonderlijke fragmentpagina's
MaakDeelXML Handle, SVH_main:="Qry_fragm", SVH_XML_main:="resources_XML_main"
'eindregels afdrukken
DrukVasteTekstAf Handle, "eind"
Close #Handle
End Sub
In de code worden een aantal keren de subroutines "DrukVasteTekstAf" en "MaakDeelXML" aangeroepen. Op die manier wordt achtereenvolgens de "organisations" en de "resources" sectie aangemaakt.
VBA-sript: DrukVasteTekstAf
Private Sub DrukVasteTekstAf(handle2 As Byte, strDeel As String)
'wordt opgeroepen vanuit MaakXMLbestand
Dim strSQL As String
Dim XmlRst As Recordset
strSQL = "SELECT Manifest_XML_vast.* From Manifest_XML_vast " _
& "WHERE (((Manifest_XML_vast.gebruik)=""" & strDeel & """)) " _
& "ORDER BY Manifest_XML_vast.nr"
'Debug.Print strSQL
Set XmlRst = CurrentDb.OpenRecordset(strSQL, dbOpenForwardOnly)
With XmlRst
Do Until .EOF
Print #handle2, .Fields("tekst")
.MoveNext
Loop
End With
End Sub
Rechttoe, rechtaan: er wordt een query gedefinieerd met als selectiecriterium het veld "gebruik". Vervolgens wordt het veld 'tekst' regel voor regel afgedrukt.
VBA-script: MaakDeelXML
Sub MaakDeelXML(handle1 As Byte, _
SVH_main As String, _
SVH_XML_main As String, _
Optional SVH_sub As String = "GEENSUB", _
Optional SVH_XML_sub As String)
Dim strSQL As String
Dim SubDataRst As Recordset
Dim DataRst As Recordset
Dim strresult As String
strSQL = "SELECT " & SVH_main & ".* FROM " & SVH_main _
& " ORDER BY " & SVH_main & ".volgnr"
Set DataRst = CurrentDb.OpenRecordset(strSQL, dbOpenForwardOnly)
With DataRst
Do Until .EOF
'Debug.Print strSQL
'main-gedeelte afdrukken
DrukRecordAf handle1, DataRst, SVH_XML_main
If SVH_sub <> "GEENSUB" Then
'sub-gedeelte afdrukken
strSQL = "SELECT " & SVH_sub & ".* FROM " & SVH_sub & " " _
& "WHERE (((" & SVH_sub & ".volgnr)=" & .Fields("volgnr") _
& "))"
Set SubDataRst = CurrentDb.OpenRecordset(strSQL, dbOpenForwardOnly)
If SubDataRst.RecordCount > 0 Then
Do Until SubDataRst.EOF
Print #handle1, ""
DrukRecordAf handle1, SubDataRst, SVH_XML_sub
SubDataRst.MoveNext
Loop
End If
DrukVasteTekstAf handle1, "eindsub"
End If
Print #handle1, ""
DataRst.MoveNext
Loop
End With
End Sub
In dit script worden 4 parameters meegegeven:
- tabelnaam van de template (hoofdrecord)
- tabel/querynaam van de gegevens (hoofdrecord)
- tabelnaam van de template van het subrecord
- tabel/querynaam van de gegevens van het subrecord.
Vervolgens wordt de gegevenstabel van het hoofdrecord stuk voor stuk doorgelopen waarbij eerst het hoofdrecord wordt afgedrukt (met drukRecordAf) en vervolgens in een loop het subrecord wordt doorlopen.
Na het subrecord wordt indien nodig nog een aantal vaste regels afgedrukt ter afsluiting van het subrecord.
Iets wat je handmatig in het VB-script zal moeten aanpassen is het selectiecriterium in het subrecord. Zowel de gegevenstabel van het hoofdrecord als die van het subrecord zullen een overeenkomstig veld moeten hebben waarop geselecteerd kan worden.
VBA-script: DrukRecordAf
Sub DrukRecordAf(handle2 As Byte, DataRst As Recordset, strBasis As String)
Dim XmlRst As Recordset
Dim strSQL As String
Dim strresult As String, result As Variant
Dim tlr As Long
strSQL = "SELECT " & strBasis & ".* FROM " & strBasis & " ORDER BY " & strBasis & ".nr"
Set XmlRst = CurrentDb.OpenRecordset(strSQL, dbOpenForwardOnly)
With XmlRst
Do Until .EOF
strresult = ""
For tlr = 2 To 6
If .Fields(tlr).Value <> "" Then
If .Fields(tlr).Name Like "veldnm*" Then
strresult = strresult & DataRst.Fields(.Fields(tlr).Value)
Else
strresult = strresult & .Fields(tlr).Value
End If
'Debug.Print strresult
End If
Next tlr
Print #handle2, strresult
.MoveNext
Loop
End With
End Sub
Hier wordt de regels met variabele gegevens gecreerd. De template wordt regel voor regel doorlopen waarbij stap voor stap de inhoud van veld 2 t/m 6 aan elkaar wordt geplakt. Als de naam van het veld begint met "veldnm" dan wordt niet de tekst van het veld maar het bijbehorende veld uit de gegevenstabel opgehaald.
Losse XML/HTML-bestanden
In bovenstaand voorbeeld wordt zoals gezegd één groot bestand gemaakt bestaande uit meerdere records die ieder weer een aantal onderrecords kunnen hebben.
Wanneer je een reeks losse html-bestanden wilt maken zul je meestal per hoofdrecord één bestand willen hebben met eventueel wat onderrecords.
De structuur van bovenstaand VBA-script verandert dan in die zin dat de query die alle records stuk voor stuk doorloopt zal verhuizen van MaakDeelXML naar de hoofdroutine MaakXMLbestand. Inplaats van een vaste bestandsnaam zal dan een veld uit de query worden opgegeven.
Voor deze tpepassing is geen gebruik gemaakt van subrecords en zijn voor het gemak alle templateregels in één bestand gezet. In dat geval kan volstaan worden met onderstaand VBA-script:
Sub MaakSetBestanden()
MaakBestand "mypag"
End Sub
Sub MaakBestand(typeBestand As String)
Dim DataRst As Recordset
Dim XmlRst As Recordset
Dim strPadNaam As String
Dim Handle As Byte
Dim strresult As String
Dim tlr As Long
Handle = FreeFile
strPadNaam = strBasisPad
Set DataRst = CurrentDb.OpenRecordset("qry_" & typeBestand, dbOpenForwardOnly)
Set XmlRst = CurrentDb.OpenRecordset(typeBestand & "Basis", dbOpenDynaset)
Do Until DataRst.EOF
Open strPadNaam & DataRst.Fields("Htmtitel") For Output As #Handle
With XmlRst
.MoveFirst
Do Until .EOF
strresult = ""
For tlr = 1 To 5
If .Fields(tlr).Value <> "" Then
If .Fields(tlr).Name Like "veldnm*" Then
strresult = strresult & DataRst.Fields(.Fields(tlr).Value)
Else
strresult = strresult & .Fields(tlr).Value
End If
End If
Next tlr
Print #Handle, strresult
.MoveNext
Loop
End With
Close #Handle
DataRst.MoveNext
Loop
End Sub
Dit script maakt gebruik van tabel en querynamen die aan een bepaalde structuur moeten voldoen. De template tabel heet 'typebestand
basis' en de gegevensquery '
qy_typebestand'. Op die manier kan de procedure MaakSetBestanden een hele reeks soorten pagina's achter elkaar laten maken.
Toepasbaarheid
Ik heb de database + bijbehorende scripts inmiddels al de nodige keren met succes toegepast. Zo gebruik ik het bijvoorbeeld voor het genereren van modulebeschrijvingen (html-pagina's) en het aanmaken van het XML-upload document voor de videotheekdienst van Surfnet.
Er zijn uiteraard andere wegen om hetzelfde doel te bereiken. Een webapplicatie met PHP en MySQL ligt voor de hand en je zou ook aan een XSLT-transformatie kunnen denken. De reden dat ik voor Access + VBA heb gekozen is dat ik goed thuis ben in Access en daar precies de queryresultaten kan bereiken die ik nodig heb. En datzelfde geldt voor VBA, in ieder geval voor wat betreft de koppeling met Access-tabellen.