An array in any other language is called a shelf in OmniMark and, although the two are similar in many ways there is one important difference. In most languages a programmer must know the size required for array before it is created. Once created an array remains the same size throughout the running of the program. If the number of cells in the array is too large for the actual number of values, then some cells are unused. Once all the cells are used there is no easy way to add more.
Although an OmniMark shelf can be declared with a size, it is common to simply declare shelfs of 'variable' size, with an optional initial size. As more values need storing, cells are easily added, and cells can be removed if no longer needed. It is possible to store values in existing cells, into newly created cells, or before or after existing cells. These features reveal that the underlying data structure used internally by OmniMark may well be a linked list. However visualising the structure as an array or 'shelf of values' remains most productive for understanding the structure.
A shelf can be declared, either globally or locally, to contain a certain type of values (stream, counter or switch) and with a variable size which is initially zero. The word 'shelf' is not used. An example could be:
global stream people variable initial-size 0
which declares a shelf called 'people'. This shelf contains no cells. To add a cell we can write
new people
The shelf now contains one cell and to store some data into it we can write
set people to "FT Chan, SPACE Learning Centre, Hong Kong"
It is usually more convenient to create a new shelf cell and assign a value to it in one step. If we write
set new people to "Joseph Fong, City University, Hong Kong"
Then the shelf has a new cell assigned the value above. The shelf now contains two cells and if we write
output people
or
output "%g(people)" ; people is a stream shelf
we get the last cell's value. It is a principle of OmniMark shelfs that the value obtained by using a just shelf name is the value of its last cell.
Even though no cell numbers were used in the above fragments, the shelf 'people' is still numerically indexed, like arrays in other languages. The first cell has an index of 1 (not zero, as in C, C++ or Java). In OmniMark access to a particular cell can be obtained using the word 'item' followed by the index of the cell required, as shown below:
output people item 1 ; FT Chan, SPACE Learning Centre, Hong Kong output people item 2 ; Joseph Fong, City University, Hong Kong
It is often more effective to refer to the cells in a shelf with words instead of numerical indices. OmniMark shelfs can be 'keyed' with unique words which allows them to be used as 'associative arrays' (each cell value associated with a key word). Assuming the people shelf had no cells, we could create two cells, each with a value and a key, as follows:
set new people key "chan" to "FT Chan, SPACE Learning Centre, Hong Kong" set new people key "fong" to "Joseph Fong, City University, Hong Kong"
in which case we could output any cell value by referring to its key:
output people key "fong"
or
output people key "chan"
To output the key value instead of the cell value, we can write
output key of people
which would produce "fong" since the shelf's default position is its last cell. We could output the key of any cell by referring to its item index.
output key of people item 1 ; 'chan'
It is possible to test a shelf to see if it has a certain key:
do when people has key "chan" ... else ... done
or to find out the number of cells in a shelf
output number of people
A shelf traversal can be done with a special 'repeat over' loop. This structure visits each cell of a shelf without specific reference to its item number or key. A counter variable to control iterations of the loop is not required. For example, to output all the values of the people shelf, each on a separate line, we could simply use
repeat over people output people join "%n" again
To output both keys and values, we could use
repeat over people output key of people output ":" output people join "%n" again
which produces.
chan:FT Chan, SPACE Learning Centre, Hong Kong fong:Joseph Fong, City University, Hong Kong
The example program shows how a keyed counter shelf can be used. The program submits a text file to a pattern matching rule which finds any vowels in the file and uses a shelf to store the frequency of each vowel. When a vowel is found, a test is performed on the shelf to see if this vowel has been located before. This is done by testing whether the shelf already has that vowel as a key. If it has, the cell in the corresponding cell is increased. When a previously undetected vowel is found, a new cell is created using the vowel as a key and initialised to 1.
[Code Sample: C05T01a.xom]
001 ; Count vowels 002 003 global counter vowels variable initial-size 0 004 005 process 006 submit file "../source/chap04.sgml" 007 repeat over vowels 008 output key of vowels join ":" join "%d(vowels)%n" 009 again 010 011 find ul["aeiou"] => char 012 local stream upChar 013 set upChar to "%ux(char)" 014 do when vowels has key upChar 015 increment vowels key upChar 016 else 017 set new vowels key upChar to 1 018 done 019 020 find any
When I run the above program, using the source file for chapter 4 of this book, I get the following output:
O:1824 E:3738 A:2251 I:1532 U:921
In the above program, the rule on line 11 will fire each time a vowel character (upper or lower case a,e,i,o or u) is found in the stream. This vowel is captured in the pattern variable 'char'. On lines 12 and 13 a local stream is used to hold the uppercase version of the vowel. The selection from lines 14 to 18 checks if the shelf 'vowels' already has a cell with this key and, if it does increments the cell value. If there is no cell with the current key, a new cell is created on line 17 with the initial value 1.
When the input stream is exhausted, control returns to line 7 where a 'repeat over' loop is used to traverse the shelf and output the keys and values of all its cells.
Streams, first discussed in Chapter 2, can be used in several ways, including simple strings, accumulative in-memory buffers and as pointers to disk files.
A stream can be assigned a simple string of characters like this:
process local stream simpleStream set simpleStream to "I don't want a pickle%n"
Such a stream can be have its value changed by similar assignments or can have more data appended like this:
process
local stream simpleStream
set simpleStream to "I don't want a pickle%n"
set simpleStream
to simpleStream join "I just want to ride my motorcycle."
If your main use for a stream is to always add more data to it, the above 'join' method can be tedious. A better solution in this case is to open the stream as an in-memory 'buffer' and then sequentially 'put' data into it. Each time new data is 'put', it is appended to the existing data. The following program shows how a stream buffer can be used in conjunction with a scanning routine to remove all the commas from a string so the result can be used later in calculations.
[Code Sample: C05T02a.xom]
001 ; Remove commas from a number
002 ; Uses a stream buffer
003
004 process
005 local stream inputData initial {"2,456,543,444,000"}
006 local stream noCommas
007
008 open noCommas as buffer
009 repeat scan inputData
010 match ","
011 ; consume each comma
012 match any => aDigit
013 put noCommas aDigit ;; append next digit
014 again
015 close noCommas
016
017 output "Input stream is %g(inputData)%n"
018 output "Buffer contains %g(noCommas)%n"
The results can be checked by looking at the output, which is
Input stream is 2,456,543,444,000 Buffer contains 2456543444000
The three steps required to use a stream buffer are
A disk file can be used to store stream data just as easily as an in-memory buffer and the syntax is almost identical. The steps involved are:
The following fragment of code declares a local stream called 'dataFile' and attaches it to a disk file called 'results.dat':
001 process 002 local stream dataFile 003 open dataFile as file "results.dat"
Once this is done we can write onto the file by using a 'put' action on the stream. The syntax is shown here as additional steps to the fragment above:
001 process 002 local stream dataFile 003 open dataFile as file "results.dat" 004 put dataFile "Text appearing on the first line of the file%n" 005 put dataFile "This appears on the second line%n" 006 put dataFile "Data for the third line%n"
When all required data is written, the stream (and thus the disk file) should be closed. This last fragment completes the steps:
001 process 002 local stream dataFile 003 open dataFile as file "results.dat" 004 put dataFile "Text appearing on the first line of the file%n" 005 put dataFile "This appears on the second line%n" 006 put dataFile "Data for the third line%n" 007 close dataFile
If the stream variable which points to a file or buffer is declared locally (as above) then it can only be used inside the current rule. To allow all rules to write to the same buffer or file, declare the stream globally, then open it early in the program - perhaps in a 'process-start' rule and close it late in the program - perhaps in a 'process-end' rule.
In most of the programs seen so far, the generic action 'output' has been used to write out data. The default destination for these output actions is the standard output device which is either the console screen (when using the command line version of OmniMark) or the log window (when using the OmniMark IDE). This is convenient while testing programs or when the amount output is small. Quite often we need to write the entire output of a program onto an external text file and this could be done using the technique demonstrated above, that is, by opening a stream as a file and using a 'put' action where we would previously use the generic 'output' action.
An alternative way to write output to a file is to redirect it. Users of the command line version of OmniMark can do this with the command line option '-of' discussed in Chapter 2, Topic 5 . Users of the IDE don't have access to this option. A technique for redirecting the 'output' action to a disk file is available from within a program, as shown in the following examples.
This program writes its output using the default destination:
[Code Sample: C05T02b.xom]
001 ; Write to standard output 002 003 process 004 output "Text appearing on the first line%n" 005 output "This appears on the second line%n" 006 output "Data for the third line%n"
When called from the command line with the command
omnimark -sb C05T02b.xom -of results.dat
The output is automatically redirected onto the text file 'results.dat'. To achieve the same results from within the program we can use the following technique:
[Code Sample: C05T02c.xom]
001 ; Redirect output to a file 002 003 process 004 local stream fileStream 005 open fileStream as file "results.dat" 006 using output as fileStream 007 do 008 output "Text appearing on the first line%n" 009 output "This appears on the second line%n" 010 output "Data for the third line%n" 011 done 012 close fileStream
Lines 4, 5 and 12 show the standard technique for declaring a stream, opening it and attaching it to a file, and closing it. The redirection of standard output to the file is done on line 6 with the 'using output as ...' action. This instruction controls all the actions in the 'do...done' block from line 7 to 11. It is important to realise that any redirection stays in effect for all actions controlled by the 'using output as ...' action. So, for example, an outline of a program which processes an SGML instance and redirects all the output from all the element rules is:
001 ; Redirect output from element rules 002 003 process 004 local stream dataFile 005 open dataFile as file "results.dat" 006 do sgml-parse document 007 scan file "someinstance.sgml" 008 using output as dataFile 009 output "%c" 010 done 011 close dataFile 012 013 ; element rules from here down... 014 ; use generic 'output' actions
The above is not a complete program, it just shows the general technique. A fully worked example is provided in the next topic where an SGML instance is processed to output a web page file in HTML.
To complete the discussion, it is easy to append output to an existing file using the action 'reopen' instead of 'open' and we can check if a file exists before using it with the selection
do when file "somefile.dat" exists ...
There are times when we may have redirected the output action to a file but still want some data written to standard output. This can be done by explicitly putting data to the built-in stream '#main-output' as shown in the following program. This program redirects all data written with the 'output' actions to a text file called 'mydata.dat'. However, during this activity a message is written directly to standard output. Note carefully in this situation that if there is no '-of' option supplied on the command line, then the message written to #main-output will appear on our console screen. If a '-of' option has been used then the message will be directed into the file named after that option.
[Code Sample: C05T02d.xom]
001 ; Writing to main output 002 003 global stream dataFile 004 005 process 006 open dataFile as file "mydata.dat" 007 using output as dataFile 008 do 009 output "Writing line 1 to the file%n" 010 output "Here is line 2 on the file%n" 011 put #main-output "A message for standard output." 012 output "This is the last line%n" 013 done 014 close dataFile
It is a principle of OmniMark programming that the stream '#main-output' always refers to your standard output device, even if the generic output action has been redirected. Standard output itself can be redirected with the '-of' option on the command line. If you want to ensure that data actually gets written to your console screen, irrespective of any redirection, put the data to the built-in stream '#console', for example
put #console "A message for the screen%n"
Similarly, access to your operating system's standard error stream is also available in OmniMark as a built-in stream. Standard error can be written to with the action
put #error "A message for standard error%n"
OmniMark referents make it possible to write data to an output stream before that data is known. This seems impossible, (it is impossible), but OmniMark takes care of the mechanics so that we can apparently do it. OmniMark allows us to output a referent (a place holder), onto the output stream. Then, when the program knows the value the place holder should take, it can be set to the known value.
Using a referent we could output a list of things from an input stream and have a count of how many of these occurred appearing before the list. To demonstrate, suppose we have the text below stored in a file called 'trivial.text'
one two three four five
We can easily write a program to output all of the lines in this file and then output a message indicating how many lines were processed:
[Code Sample: C05T03a.xom]
001 ; Trvial counting program
002
003 global counter numLines initial {0}
004
005 process
006 submit file "trivial.text"
007 output "--------------------------%n"
008 output "%d(numLines) lines were processed%n"
009
010 find line-start any-text+ => line "%n"
011 increment numLines
012 output "%d(numLines) %x(line)%n"
013
014 find any
This program's output is:
1 one 2 two 3 three 4 four 5 five -------------------------- 5 lines were processed
To have the message appear before the lines themselves, we write a similar program which outputs a referent before processing the lines, then sets the referent after processing the lines. Such a program is:
[Code Sample: C05T03b.xom]
001 ; Trvial referent program
002
003 global counter numLines initial {0}
004
005 process
006 output referent "XXX"
007 output "--------------------------%n"
008 submit file "trivial.text"
009 set referent "XXX" to "%d(numLines) lines were processed%n"
010
011 find line-start any-text+ => line "%n"
012 increment numLines
013 output "%d(numLines) %x(line)%n"
014
015 find any
for which the output is:
5 lines were processed -------------------------- 1 one 2 two 3 three 4 four 5 five
The technique used here shows the principle of referents. On line 6, a referent is output. This is where a place-holder is written to the output stream. Then, on line 8, the input stream is processed by the find rule which captures, outputs and counts the lines as they are processed. When all the lines are output and counted the place holder (the referent) is given a value (line 9).
A more useful example of referent use is when a table of contents is required at the top of some output document. The example which follows takes a simple SGML instance and converts it to HTML ready for viewing with a web browser. In the first version of the program, no referents are used and no table of contents is produced. In the second version a referent is used to place a table of contents at the top of the HTML file.
This example uses as input an SGML instance which contains some topics to be discussed at a meeting. Each topic has a name and one or more paragraphs of text following it. The structure conforms to the following DTD which could be stored in a file called 'meeting.dtd':
<!ELEMENT meeting - o (topic+)> <!ATTLIST meeting date NUMBERS #REQUIRED> <!ELEMENT topic - o (para+)> <!ATTLIST topic name CDATA #REQUIRED> <!ELEMENT para - o (#PCDATA)>
A sample instance is shown here. It contains just a few topics for a meeting on June 28, 2000. This could be stored in a file called 'meeting.sgml'.
<!DOCTYPE MEETING SYSTEM "meeting.dtd"> <MEETING date="28 6 2000"> <TOPIC name="Seminar Organisation"> <para>The next seminar will be held on the first Wednesday in July. David will contact Mr FT Chan who is visiting during that week to see if he can present a seminar. <TOPIC name="Assessment Committee"> <para>Al wants all staff to use the official template when submitting distributions for the last assessment period. <para>A copy of the spreadsheet can be obtained from the admin section of the school's web site. <TOPIC name="Orientation meeting"> <para>A meeting with new clients will be held in the conference centre towards the end of August. Staff who will have direct access to new clients are encouraged to attend the orientation so they can present a brief outline of the topics covered in their reports.
The following program parses the above instance and directs the output from all the element rules onto a file called 'meeting.html'
[Code Sample: C05T03c.xom]
001 ; Convert SGML to HTML 002 003 004 process 005 local stream htmlfile 006 open htmlfile as file "meeting.html" 007 do sgml-parse document 008 scan file "minutes.sgml" 009 using output as htmlfile 010 output "%c" 011 done 012 close htmlfile 013 014 element meeting 015 output "<HTML>%n<HEAD>%n<TITLE>Meeting Page</TITLE>%n" 016 output "</HEAD>%n<BODY>%n" 017 output "<H2>Meeting of %v(date)</H2>%n" 018 output "%c" 019 output "</BODY>%n</HTML>%n%n" 020 021 element topic 022 output "<H3>%v(name)</H3>%n" 023 output "%c" 024 025 element para 026 output "<P>%c</P>%n"
When the file 'meeting.html' is viewed with a web browser, it looks like this:
A modified version of the last program is shown below. Note how a referent is output before all the topics, then the topics are processed and their names collected into a stream and finally the referent is set to the value of the stream when all the topic names are known. A line-by-line description of the referent details is given after the code.
[Code Sample: C05T03d.xom]
001 ; Convert SGML to HTML
002 ; Uses a referent to put a list of topics
003 ; before topics are processed.
004
005 global stream topicList initial {""}
006
007 process
008 local stream htmlfile
009 open outfile with referents-allowed as file "minutes.html"
010 do sgml-parse document
011 scan file "minutes.sgml"
012 using output as htmlfile
013 output "%c"
014 done
015 close htmlfile
016
017 element meeting
018 output "<HTML>%n<HEAD>%n<TITLE>Meeting Page</TITLE>%n"
019 output "</HEAD>%n<BODY>%n"
020 output "<H2>Meeting of %v(date)</H2>%n"
021 output "<HR>%n"
022 output referent "TOC"
023 output "<HR>%n"
024 open topicList as buffer
025 put topicList "<OL>%n"
026 output "%c"
027 put topicList "</OL>%n"
028 close topicList
029 set referent "TOC" to topicList
030 output "</BODY>%n</HTML>%n%n"
031
032 element topic
033 output "<H3>%v(name)</H3>%n"
034 put topicList "<LI>%v(name)%n"
035 output "%c"
036
037 element para
038 output "<P>%c</P>%n"
In line 9 of this program the stream 'htmlfile' is attached to a disk file. Note that in this case the stream is explicitly opened using the modifier 'with referents-allowed'. This ensures that OmniMark makes arrangements to correctly store and replace the place-holder in the output stream before the final disk version of the file is closed.
On lines 21, 22 and 23 a referent called 'TOC' is output into the stream positioned after the main heading (line 20) and between to horizontal rule tags in the HTML. This is the position in the output stream which will ultimately hold the table of contents.
On lines 24 to 28, the global stream 'topicList' is opened as a buffer and the open tag for an ordered list is first placed into the buffer. Then the content of the meeting element (all the topics) is processed. When all topics have been processed the end tag for the ordered list is written into the buffer and the buffer is closed. Since the stream 'topicList' contains the table of contents it can be used to resolve the value of the referent - line 29.
Line 26 which processes all the topics causes the element rule for TOPIC to be fired each time a topic is processed. On line 34 the name of each topic is written into the 'topicList' buffer as an HTML list item.
When the output file 'meeting.html' is viewed with a browser the output is:
It is not a very difficult task to modify the program so that in the table of contents each topic is actually a hypertext link to the actual topic as it appears later in the document.
The Integrated Development Environment is a convenient tool for debugging OmniMark programs but production-level programs are usually best processed with the 'omnimark' executable on the command line. There are many different command line options available and a list of these can be seen by executing the command
omnimark -help
When many options are needed on the command line, typing them all sometimes becomes difficult and error prone. OmniMark provides a single command-line option ('-f') which allows all other options to be read from a file. Suppose we wish to execute OmniMark with these options:
We could do this by entering the command
omnimark -s myprogram.xom -of myoutput.dat -log error.log -d keyword schema
but typing this more than once would be extremely tedious. The alternative is to create a simple text file called 'omargs' (for example), which contains:
-s myprogram.xom -of myoutput.dat -log error.log -d keyword schema
and then calling OmniMark like this:
omnimark -f omargs
Functions are available in most languages and can be written is OmniMark using a 'define...function....as' structure. A typical function accepts some data via arguments (parameters) and returns data to its caller. The example below defines a function which accepts two counters and returns the smaller of them:
[Code Sample: C05T05a.xom]
001 ; A simple function
002
003 define
004 counter function Smaller( value counter num1,
005 value counter num2)
006 as
007 do when num1 < num2
008 return num1
009 else
010 return num2
011 done
012
013 process
014 local counter c1 initial {23}
015 local counter c2 initial {12}
016 local counter result
017
018 set result to Smaller( c1, c2 )
019 output "%d(result) is smaller%n"
The program below contains a function which returns the current date:
[Code Sample: C05T05b.xom]
001 ; A date function 002 003 define 004 stream function now 005 as 006 local stream ds 007 set ds to date "=n =D, =xY" 008 return ds 009 010 process 011 output "You ran this program on " join now join "%n"
The last example contains a function which accepts a stream shelf, a stream value and a key value and inserts the value into the shelf with the given key so that all values remain in alphabetical order. The program scans a file containing people's names. As each name is read it is inserted into the shelf. At the end of processing the ordered list of names is displayed.
[Code Sample: C05T05c.xom]
001 ; Keeping a sorted shelf 002 003 ; ----- Function starts here 004 define function insertToStream 005 ( MODIFIABLE stream data, 006 value stream new-key, 007 value stream new-data ) 008 as 009 local stream pos-key 010 011 ;; check for empty shelf 012 do when number of data = 0 013 set new data key new-key to new-data 014 return 015 done 016 017 018 ;; check for append new data 019 do when new-data > data lastmost 020 set new data key new-key to new-data 021 return 022 done 023 024 025 ;; general case, search for location to insert 026 repeat over data 027 set pos-key to key of data 028 exit when new-data < data 029 again 030 set new data key new-key before key pos-key to new-data 031 032 ; ----- Function ends here 033 034 global stream nameList variable initial-size 0 035 036 process 037 submit file "names.dat" 038 repeat over nameList 039 output "%g(nameList)%n" 040 again 041 042 find line-start any-text+ => aName 043 insertToStream( nameList, aName, aName ) 044 045 find any
If the input file 'names.dat' contains
Claudius Agripena Penelope Athene Clio Pandora Archimedes
Then the output will be
Agripena Archimedes Athene Claudius Clio Pandora Penelope
Since the 'insertToStream' function above requires both a value to insert into the ordered shelf and a key for that cell in the shelf, an error will be caused if the same key is used twice. Since my example uses each name as both a key and a value, the program would generate an error if two names were identical. To fix this problem, the program could be modified so that unique keys were generated within the program. Another approach is to modify the function so that keys are not associated with values at all.
A collection of functions can be stored in a file separate to the program which uses them. Files which contain collections of functions are usually called include files and their filenames have the extension 'xin'. If you have several functions stored in a separate file called 'myfunctions.xin' then these can be used by placing
include "myfunctions.xin"
early in your programs. The above assumes that the file 'myfunctions.xin' is in the current working directory. If your include files are elsewhere on your system, you need to specify their complete pathname.
It is quite convenient to store all your 'xin' files together in one place on your file system. You can use the '-i' command line option to tell OmniMark where these files are. For example, the command
omnimark -sb myprog.xom -i "C:\Data\includes\"
tells OmniMark to look in the directory 'C:\Data\includes' for any include files which are specified in the program.
When you install OmniMark on your system, several useful libraries of functions are installed with it.
Although the early versions of OmniMark provided only pattern matching and markup language facilities, the libraries now make it a very general purpose language. OmniMark now includes libraries for working with dates, file system utilities, database connectivity, CGI programming and so on. This topic covers the general principles involved with using these libraries and includes some sample programs which demonstrate just a few of them.
The OmniMark includable functions are installed in a subdirectory called 'xin' under the main directory where the language is installed. Also, many of these includable functions depend on platform specific dynamic linked libraries of external functions which are installed in a subdirectory called 'lib' under the main installation directory. When you want to use the libraries, the first thing to do is to identify the pathname to the 'xin' and 'lib' subdirectories. On my system, the full path to the 'xin' directory is
"C:\Program Files\OmniMark\xin\"
and the full path to the dynamic linked libraries is
"C:\Program Files\OmniMark\lib\"
Note that I have placed the above paths within quotation marks. This is necessary on my system since the directory 'Program Files' contains a blank space - you may not need to quote the paths on your system.
If you are using UNIX you will, of course, use a slightly different syntax. On my UNIX system the equivalent paths are, for the includable functions
/local/omnimark53/xin/
and for the dynamic linked libraries
/local/omnimark53/lib/
To use these libraries and functions, you need to tell OmniMark where they are on your system. The most convenient way to do this is to use the '-i' and the '-x' option in an OmniMark 'args' file. The sample args file below specifies these paths and also specifies the name of a program to run on my personal computer:
-i "C:\Program Files\OmniMark\xin\" -x "C:\Program Files\OmniMark\lib\=L.dll" -sb myprogram.xom
The suffix '=L.dll' is optional and reminds OmniMark that, on my system dynamic linked library files have an extension of 'dll'. On my UNIX system, the equivalent 'args' file would be
-i /local/omnimark53/xin/ -x /local/omnimark53/lib/=L.so -sb myprogram.xom
Here the suffix '=L.so' reminds OmniMark that dynamic linked library file on UNIX have the extension 'so'.
The 'omdate' library contains many functions for working with dates. The program below shows the most fundamental of these. It displays the current date and time on my system.
[Code Sample: C05T06a.xom]
001 ; Using the date library 002 003 include "omdate.xin" 004 005 process 006 output now-as-ymdhms
To execute this program, I create a file called 'omargs':
-i "C:\Program Files\OmniMark\xin\" -x "C:\Program Files\OmniMark\lib\=L.dll" -sb C05T06a.xom
and call OmniMark with
omnimark -f omargs
In the same way, I can calculate the day of the week for any date and display the name of that day. This program displays the name of the day of the week for the 28th of June, 2000.
[Code Sample: C05T06b.xom]
001 ; Using the date library
002
003 global stream weekday-name size 7 initial {
004 "Monday", "Tuesday", "Wednesday",
005 "Thursday", "Friday", "Saturday",
006 "Sunday"}
007
008 include "omdate.xin"
009
010 process
011 local counter dayNum
012 local stream dayName
013
014 set dayNum to ymd-weekday of "20000628"
015 output "It was day number %d(dayNum)%n"
016
017 set dayName to weekday-name item dayNum
018 output "Which was a %g(dayName)%n"
For any of the other useful date functions in the 'omdate' library browse the file 'omdate.xin' which is in the 'xin' subdirectory on your system.
For file system functions, use the library 'omfsys.xin'. It contains utilities which allow you to make directories, change file permissions, delete files, list the files in a directory, rename files and so on. The sample program below uses the 'FS_ListDirectory' library function to capture the names of all the files in the current working directory ('"."').
[Code Sample: C05T06c.xom]
001 ; Using the file system library 002 003 global stream fileNames variable initial-size 0 004 global stream errorMessage 005 006 include "omfsys.xin" 007 008 process 009 FS_ListDirectory "." into fileNames status errorMessage 010 repeat over fileNames 011 output fileNames join "%n" 012 again 013 output "The status of the operation was %n%g(errorMessage)%n"
For any of the other useful file system functions, browse the file 'omfsys.xin' which is in the 'xin' subdirectory on your system.
The counter data type which was introduced in Chapter 2, Topic 6 can only do calculations with whole numbers (integers). For applications requiring floating point calculations the 'omfloat.xin' library can be used. This library introduces the new data type 'float' for your programs to use. Once you include the library, you can then declare float variables and used them in the included functions. This program shows just the bare minimum of what is required. It creates two float variables, assigns values to them using literal strings, subtracts the values, and displays the result with four decimal places.
[Code Sample: C05T06d.xom]
001 ; Using 'float' variables 002 003 include "omfloat.xin" 004 005 global float num1 006 global float num2 007 global float difference 008 global stream result 009 010 process 011 set num1 to FP_v "5.675" 012 set num2 to FP_v "3.671" 013 set difference to FP_sub( num1, num2 ) 014 set result to FP_d( difference, 4 ) 015 output "The difference is %g(result)%n"
For any of the other useful floating point functions browse the file 'omfloat.xin' which is in the 'xin' subdirectory on your system.
As a summary on the use of OmniMark included libraries, follow these steps:
A file called 'stocks.dat' contains on each line a three letter stock exchange code, a colon and a company name. An example from the file is shown here:
CML:Coles Myer DVT: DavNet BHP:Broken Hill Pty Ltd ASX: Australian Stock Exchange JLA: Julia Mines RKN: Reckon Pty Ltd NAB:National Australia Bank
Write a program which reads this file and captures the data into a stream shelf. The value of each shelf cell should be the company name and the key for each cell should be the three letter code. Your program should display all the shelf values.
Modify the program from the previous task so that the company names are displayed in alphabetical order. Use the 'insertToStream' function.
Using the same input file, capture all the exchange codes into a single stream with a comma between each code and all the company names into another stream with a comma between each. The company names should have quotation marks around them. Display the two streams each on a separate line of output. The output should appear something like this:
CML,DVT,ASX,JLA,RKN,NAB,... "Coles Myer", "DavNet", "Australian Stock Exchange", ...
Hint: open each stream as a buffer and 'put' data onto the buffers as it is found. If possible, try to avoid having a trailing comma at the end of each stream.
The file 'quiz.dtd', shown here, defines the structure for SGML instances defining multiple choice questions.
<!-- preliminary DTD for Oweek quiz -->
<!ELEMENT quiz - o (question+)>
<!ELEMENT question - o (text, altlist, hint)>
<!ATTLIST question id ID #REQUIRED
author CDATA #REQUIRED
unit CDATA #REQUIRED
answer NUMBER #REQUIRED>
<!ELEMENT text - o (para+)>
<!ELEMENT altlist - o (alt+)>
<!ELEMENT (para,alt) - o (#PCDATA)>
<!ATTLIST alt id NUMBER #REQUIRED>
<!ELEMENT hint - o (#PCDATA)>
An SGML instance file 'quiz.sgml', shown below, contains some multiple choice questions conforming to the above DTD.
<!DOCTYPE quiz SYSTEM "quiz.dtd"> <QUIZ> <QUESTION id=q1 author="Errol Chopping" unit="School of Information Technology" answer=1> <TEXT> <para>What is the name of the mountain on which Bathurst hosts motor racing events? <ALTLIST> <ALT id=1>Mount Panorama <ALT id=2>Mount Fuji <ALT id=3>Clover Mountain <ALT id=4>Rev-head Mountain <HINT>Look west from the CSU campus to see the name written on the hillside. <QUESTION id=q8 author="Ruth Radliff" unit="School of Information Services" answer=3> <TEXT> <para>What is the URL of Charles Sturt University's main web site? <ALTLIST> <ALT id=1>cblake@csu.edu.au <ALT id=2>csu.com <ALT id=3>www.csu.edu.au <ALT id=4>www.csu.gov.au <HINT>CSU is an educational institution in Australia <QUESTION id=q3 author="Frank Jones" unit="Division of Cultural Risk" answer=2> <TEXT> <para>The generic name for the Bathurst campus of CSU is... <ALTLIST> <ALT id=1>Macquarie <ALT id=2>Mitchell <ALT id=3>Riverina <ALT id=4>Murray <HINT>The name is the same as the highway from Bathurst to Orange.
For this task, write a program which scans the SGML file and writes an HTML file containing the questions. Do not indicate the correct answer for each question, nor the hint for each question in the HTML file. Questions should appear numbered consecutively as they are output (do not use the 'id' attribute for numbering).
Using the same file 'quiz.sgml' as input, produce an HTML report which lists the questions. The report should appear in an output file called 'answers.html' and should be positioned in a table with each row of the table holding one question. In any row, the cells should contain the question id, its text and its answer respectively.
Modify the report table program from the previous task so that the number of questions appears before the actual table.
A program which reads stock exchange codes and company names into a shelf:
[Code Sample: C05S01.xom]
001 ; Displaying Stock Name
002
003 global stream stock variable initial-size 0
004
005 process
006 submit file "stocks.dat"
007 repeat over stock
008 output stock join "%n"
009 again
010
011 find line-start uc{3} => code
012 ":" white-space*
013 any-text+ => coname
014 set new stock key code to coname
015
016 find any
A modification of the program above which adds the company names to an ordered shelf.
[Code Sample: C05S02.xom]
001 ; Displaying Stock Names in order
002
003 define function insertToStream
004 ( MODIFIABLE stream data,
005 value stream new-key,
006 value stream new-data )
007 as
008 local stream pos-key
009
010 ;; check for empty shelf
011 do when number of data = 0
012 set new data key new-key to new-data
013 return
014 done
015
016
017 ;; check for append new data
018 do when new-data > data lastmost
019 set new data key new-key to new-data
020 return
021 done
022
023
024 ;; general case, search for location to insert
025 repeat over data
026 set pos-key to key of data
027 exit when new-data < data
028 again
029 set new data key new-key before key pos-key to new-data
030
031 ; ----- Function ends here
032
033 global stream stock variable initial-size 0
034
035 process
036 submit file "stocks.dat"
037 repeat over stock
038 output stock join "%n"
039 again
040
041 find line-start uc{3} => code
042 ":" white-space*
043 any-text+ => coname
044 insertToStream( stock, code, coname )
045
046 find any
The 'streamStarted' switch in this program only becomes true after the first values are put onto the buffers. When this switch is true, a comma is put onto each buffer before the company code and data.
[Code Sample: C05S03.xom]
001 ; Two buffered streams
002
003 global stream codeStream
004 global stream nameStream
005 global switch streamStarted initial {false}
006
007 process
008 open codeStream as buffer
009 open nameStream as buffer
010 submit file "stocks.dat"
011 close codeStream
012 close nameStream
013 output codeStream join "%n" join nameStream join "%n"
014
015 find line-start uc{3} => code
016 ":" white-space*
017 any-text+ => coname
018 do when streamStarted
019 put codeStream "," join code
020 put nameStream ",%"" join coname join "%""
021 else
022 put codeStream code
023 put nameStream "%"" join coname join "%""
024 activate streamStarted
025 done
026
027 find any
A program to render the SGML instance 'quiz.sgml' into HTML
[Code Sample: C05S04.xom]
001 ; Convert SGML multiple choice
002 ; to HTML.
003
004 process
005 local stream htmlfile
006 open htmlfile as file "quiz.html"
007 do sgml-parse document
008 scan file "quiz.sgml"
009 using output as htmlfile
010 output "%c"
011 done
012 close htmlfile
013
014 global counter questionNum initial {0}
015
016 element quiz
017 output "<HTML>%n<HEAD>%n"
018 output "<TITLE>Quiz Web Page</TITLE>%n"
019 output "</HEAD><BODY>%n"
020 output "<CENTER><H2>Quiz Web Page</H2></CENTER>%n"
021 output "<HR>%n"
022 output "%c"
023 output "<HR>%n"
024 output "</BODY>%n</HTML>%n%n"
025
026 element question
027 increment questionNum
028 output "<H3>Question %d(questionNum)</H3>%n"
029 output "%c"
030
031 element text
032 output "%c"
033
034 element para
035 output "<P>%c</P>%n"
036
037 element altlist
038 output "<OL type='a'>%n"
039 output "%c%n"
040 output "</OL>%n"
041
042 element alt
043 output "<LI>%c%n"
044
045 element #implied
046 suppress
A screenshot of the resulting file 'quiz.html' is shown below:
The following program produces the multiple choice questions, ids and answers in an HTML table.
[Code Sample: C05S05.xom]
001 ; An HTML report for a quiz 002 003 process 004 local stream htmlfile 005 open htmlfile as file "answers.html" 006 do sgml-parse document 007 scan file "quiz.sgml" 008 using output as htmlfile 009 output "%c" 010 done 011 close htmlfile 012 013 global stream correctAlt 014 015 element quiz 016 output "<HTML>%n<HEAD>%n" 017 output "<TITLE>Quiz Report</TITLE>%n" 018 output "</HEAD><BODY>%n" 019 output "<H2>Quiz Report</H2>%n" 020 output "<TABLE BORDER>%n" 021 output "<TR><TD><STRONG>Question id</STRONG>%n" 022 output "<TD><STRONG>Question Text</STRONG>%n" 023 output "<TD><STRONG>Answer</STRONG>%n" 024 output "%c" 025 output "</TABLE>%n" 026 output "</BODY>%n</HTML>%n%n" 027 028 element question 029 output "<TR><TD align=center>%v(id)%n" 030 set correctAlt to attribute answer 031 output "%c" 032 033 element text 034 output "<TD>%c" 035 036 element para 037 output "<P>%c</P>%n" 038 039 element altlist 040 output "%c" 041 042 element alt 043 do when "%v(id)" = "%g(correctAlt)" 044 output "<TD>%c%n" 045 else 046 suppress 047 done 048 049 element #implied 050 suppress
The report, when viewed with a web browser, appears thus:
By using a referent for the number of questions, we can have this appear before the questions are processed. A program which does this is:
[Code Sample: C05S06.xom]
001 ; An HTML report for a quiz
002 ; with number of questions.
003
004 process
005 local stream htmlfile
006 open htmlfile with referents-allowed as file "answers.html"
007 do sgml-parse document
008 scan file "quiz.sgml"
009 using output as htmlfile
010 output "%c"
011 done
012 close htmlfile
013
014 global counter QuestionCount initial {0}
015 global stream correctAlt
016
017 element quiz
018 output "<HTML>%n<HEAD>%n"
019 output "<TITLE>Quiz Report</TITLE>%n"
020 output "</HEAD><BODY>%n"
021 output "<H2>Quiz Report</H2>%n"
022 output "<HR>%n"
023 output "This table contains "
024 output referent "NC"
025 output " questions.%n"
026 output "<HR>%n"
027 output "<TABLE BORDER>%n"
028 output "<TR><TD><STRONG>Question id</STRONG>%n"
029 output "<TD><STRONG>Question Text</STRONG>%n"
030 output "<TD><STRONG>Answer</STRONG>%n"
031 output "%c"
032 output "</TABLE>%n"
033 output "</BODY>%n</HTML>%n%n"
034 set referent "NC" to "%d(QuestionCount)"
035
036 element question
037 increment QuestionCount
038 output "<TR><TD align=center>%v(id)%n"
039 set correctAlt to attribute answer
040 output "%c"
041
042 element text
043 output "<TD>%c"
044
045 element para
046 output "<P>%c</P>%n"
047
048 element altlist
049 output "%c"
050
051 element alt
052 do when "%v(id)" = "%g(correctAlt)"
053 output "<TD>%c%n"
054 else
055 suppress
056 done
057
058 element #implied
059 suppress
The resulting page appears as follows: