The main work of our compiler is to properly fill the Screen and Item tables of our database. How we fill these tables depends upon the mode in which the compiler is running:
In minimal tagging mode, all information about items and some about screens is input from an outline file. Authors create the outline in a spreadsheet. They indicate indentation by skipping columns in the spreadsheet. For example, an item that they want indented by two has the first two columns blank. Following these blank columns, the spreadsheet contains an id, a title, and two other parameters each in it's own column. The author exports the outline out of the spreadsheet program in comma delimited format to make it available to the compiler.
In the routine below, the compiler:
Sub Read_Outline
'Get a line and return its indentation level
nLevel = nGetOlineLine(hInFIle, sCurrLine)
Do While Not EOF(hInFIle)
'parse a line of the outline
sId = sOlineParm(sCurrLine)
sTitle = sOlineParm(sCurrLine)
sParm1 = sOlineParm(sCurrLine)
sParm2 = sOlineParm(sCurrLine)
If sId = "" Or sTitle = "" Then
<Error trap>
Exit Sub
End If
'format the value for the SortOrder field
sCurrCount = Format$(nCount(nLevel))
sCurrCount = Right$("000" + sCurrCount, 3)
'fill in the values for an Item Record
item.itemid = sId
item.itemtitle = sTitle
item.parent = sParent(nLevel)
item.sortorder = sCurrCount
'Get level of next item to see if current one is a folder or not
nNxtLevel = nGetOlineLine(hInFIle, sNxtLine)
If nNxtLevel <= nLevel Then
'item is not a folder
item.itemtype = "4"
Else
'what type of folder?
If nLevel Mod 2 = 0 Then
item.itemtype = "1"
Else
item.itemtype = "3"
End If
End If
'flush item to database
If Not bFlushItem(item) Then
MsgBox "couldn't flush item"
End If
'fill in the values for a Screen record
screen.screenid = sId
screen.screentitle = sTitle
screen.Text = "Information not available at release time."
screen.ScreenType = "rtf"
screen.history = True
screen.orderid = sParm1
screen.fliter = sParm2
'flush screen to database
If Not bFlushScreen(screen) Then
MsgBox "couldn't flush screen"
End If
'update aray of parent ids
sParent(nLevel + 1) = sId
nCount(nLevel) = nCount(nLevel) + 1
'blank all parents lower than the current level
For J = nLevel + 1 To 5
nCount(J) = 1
Next J
'transfer next line to current line
sPrevTitle = sTitle
nLevel = nNxtLevel
sCurrLine = sNxtLine
Loop
End Sub
The id, title, filter, and OrderId fields are directly read from the file. The SortOrder, Parent, and ItemType fields have to be deduced from the position of the line in relation to the other lines in the outline. To track parent and SortOrder data the sParent and nCount arrays are used. The ItemType (which basically tells the front-end what icon to display in the table of contents outline control) is determined by the indentation of the line below the current one in the outline file.
This routine produces one Screen record (called the master Screen) for each Item in the outline. Later, as the compiler processes content files, it will match the filename of the content file with the ScreenIds created by the outline. The first screen in the content file is assumed to contain the text and title for the master screen. The compiler updates the title and text field of this master screen from the data in the content file. Any other screens in the content file are saved in the Screen table as Subscreens to the master screen. If the compiler does not find a master screen for a content file, it issues a warning.
In full tagging mode, the compiler generates all of the information it needs for the Item and Screen tables from tags embedded in the source text files. In the routine below, the compiler reads a paragraph from the current source file. If it is a paragraph with a tag in it, the tag data is processed. In most cases this processing is simply assigning the tag data to the variable for a field in the Screen or Item table.
If the paragraph read does not have a tag on it, the compiler puts it in a text buffer. If no screen or item tags have yet been found in the file, the compiler assumes that the text is part of the RTF Header. If a screen or item tag has been processed, the compiler assumes that the text is the content of the current screen. The text continues to be buffered until the next screen or item tag is found. At that time, the compiler flushes the data it has gathered for the current screen and item and flushes it to the database.
The following routine processes the paragraphs of content returned by sGetALine(hInFile).
sCurrLine = sGetALine(hInFile)
Do While Not EOF(hInFile)
'If is a dot tag line then process the tag
If bIsDotLine(sCurrLine, sDotTag, sDotData) Then
Select Case sDotTag
Case "..ITEM"
If bFirstRecord Then
If Not bProcessFrontMatter(sFrontBuffer) Then EndAPP
Else
ValidateData(Item, screen, VALIDATE_ITEM Or VALIDATE_SCREEN)
If Not bFlushItem(Item) Then EndAPP
If Not bFlushScreen(screen) Then EndAPP
End If
'<Clear out vars for next item>
bFirstRecord = False
Case "..SCREEN"
If bFirstRecord Then
If Not bProcessFrontMatter(sFrontBuffer) Then EndAPP
Else
ValidateData(Item, screen, VALIDATE_SCREEN)
If Not bFlushScreen(screen) Then EndAPP
End If
'<Clear out vars for next item>
bFirstRecord = False
Case "..ID"
screen.screenid = sDotData
Item.itemid = sDotData
Item.screenid = sDotData
Case "..TOCPOSITION"
Item.TocPosition = sMakeTocPos(sDotData)
Case "..PARENT"
Item.Parent = UCase$(sDotData)
Case "..ITEMTYPE"
Item.ItemType = LCase$(sDotData)
Case "..SCREENTYPE"
screen.ScreenType = LCase$(sDotData)
Case "..HOTSPOTS"
screen.HotId = sDotData
Case "..FILTERS"
screen.Filter = LCase$(sDotData)
Case "..PICTURE"
screen.MEdiaId = sDotData
Case "..TEXT"
sCurrBuffer = "rtf"
Case Else
<Error trap>
End Select
Else
'No dot command on the line so add it to a text buffer
'Process any hypertext in the text
nRet = bProcessCharStuff(sCurrLine)
Select Case sCurrBuffer
Case "rtf"
If Len(sRtfBuffer) + Len(sCurrLine) > 20000 Then
If Not bFlushToTmp(sRtfBuffer) Then EndAPP
sRtfBuffer = ""
End If
sRtfBuffer = sRtfBuffer + sCurrLine & gsCR
Case "front"
sFrontBuffer = sFrontBuffer + sCurrLine & gsCR
Case Else
<Error trap>
End Select
End If
'Read a new para of text
sCurrLine = sGetALine(hInFile)
Loop
The routine above uses the ScreenRecord and ItemRecord type variables we defined earlier to store and keep organized the data it collects for the Screen and Item records. As tags are processed, the components of these type variables are filled in. When it is time to flush the data to the database, the entire type variable is passed.
Text that is not tagged is initially buffered in a string variable (sRtfBuffer). As described earlier, to process chunks of text larger than Visual Basic's maximum size for string variables, we may need to further buffer text in a database memo field that can accommodate as much text as we need. Thus, if the length of the string variable plus the length of the current paragraph is greater then 20,000 (the working maximum we found for this operation), then the string variable is flushed to temporary storage and reset to empty.
The routine below illustrates the process of moving data from our internal variables and temporary storage location into the database.
Function bFlushScreen (screen As screenrecord) As Integer
'Update an existing record or add a new one
mtbScreen.Seek "=", screen.screenid
If mtbScreen.NoMatch Then
mtbScreen.AddNew
Else
mtbScreen.Edit
End If
'Save data
mtbScreen("ScreenID") = screen.screenid
mtbScreen("ScreenTitle") = screen.ScreenTitle
mtbScreen("Viewer") = screen.ScreenType
mtbScreen("MediaID") = screen.MEdiaId
mtbScreen("HotSpots") = screen.HotId
mtbScreen("Filter") = screen.Filter
mtbScreen("history") = screen.History
mtbScreen("parentid") = screen.parentid
mtbScreen("sortpath") = screen.sortpath
'xfer large data from the temp table to the screen table
If mtbTmp("text").FieldSize() > 10 Then
ChunkSize = 10000 ' Set size of chunk.
' Get field size.
TotalSize = mtbTmp("text").FieldSize()
NumChunks = TotalSize \ ChunkSize ' Set number of chunks.
' Set number of remaining bytes.
RemChunk = TotalSize Mod ChunkSize
' Set starting size of chunk.
CurSize = ChunkSize
For i = 0 To NumChunks
If i = NumChunks Then CurSize = RemChunk
curChunk = mtbTmp("text").GetChunk(i * ChunkSize, CurSize)
mtbScreen("Text").AppendChunk curChunk ' Write chunk to file.
Next i
'clear temp table for next time
InitTmpTable
End If
'Add what's in the text field to database
If screen.Text <> "" Then mtbScreen("Text").AppendChunk screen.Text
mtbScreen.Update
'<Initialize screen variable>
bFlushScreen = True
End Function
The routine checks to see if the record it is trying to create is already there. If it is, it simply updates it. Otherwise it creates a new record. Then the routine moves data from the type variable to the corresponding database fields. If there is content in the temporary storage table (mtbTmp("text")) it is assumed to belong in the Text field of the current Screen table record. The application moves it 10,000 bytes at a time from temporary to permanent storage. The compiler finally appends whatever is in the type variable for the Text field onto whatever data it has found in temporary storage.