Unlike XSLT processors, OmniMark uses a streaming model to process XML/SGML.

A streaming model has advantages but it also has disadvantages. The XML flows from top to bottom. You can’t randomly access the XML tree in memory.

So how can you process an XML node in the beginning of a document if it depends on a node that hasn’t streamed yet or maybe never will come?

Let’s say you have to add an attribute at the beginning of the document if and only if there is an annex add the end of the document.

Or you want to add simply a table of contents add the beginning of the document.

One solution could be to process the XML document twice, but that is not very efficient.

OmniMark solves this problem with referents.

What is a referent?

A referent is a placeholder you can place in the output stream, and fill at a later time.

Of course the output has to be buffered until the referent is filled. You can decide when this happens: at the end of the process, at the end of every document that is processed or in a certain scope within the document e.g. every table.

A simple example

OmniMark source code, simple referent example
  1. process
  2.   ;a comment line starts with a ";"
  3.   ;'%n' is the escape charater for newline
  4.   output referent 'c' || '%n'
  5.   output referent 'a' || '%n'
  6.   output referent 'b' || '%n'
  7.   output referent 'c'
  8.  
  9.   set referent 'c' to 'C-value'
  10.   set referent 'a' to 'A-value'
  11.   set referent 'b' to 'B-value'
  12.   set referent 'c' to 'the value is C'
the output result
  1. the value is C
  2. A-value
  3. B-value
  4. the value is C

A referent has a uniq name, e.g. referent 'the uniq name' and can be placed at different places in the output stream e.g. referent 'c'.

A referent with the same name has the same value.

In this example the referents are output and at a “later” time we set the value for the referents.

The referent 'c' first gets the value ‘C-value’ and later on it gets the final value 'the value is C'. So a referent can have a variable value.

Making a toc using referents

OmniMark source code, adding a toc before the doc
  1. global stream toToc
  2.  
  3. process
  4.   open toToc as referent 'myToc'
  5.   do xml-parse
  6.     scan '<doc><title>Title of doc</title>%n' ||
  7.           '<div><title>first division title</title>' ||
  8.           '<p>A paragraph in div1</p>' ||
  9.           '</div>%n' ||
  10.           '<div><title>second division title</title>' ||
  11.           '<p>A paragraph in div2</p>' ||
  12.           '</div>%n' ||
  13.          '</doc>'
  14.     output '%sc'
  15.   done
  16.   close toToc
  17.  
  18. element #implied
  19.   output '<%q>%c</%q>'
  20.  
  21. element 'doc'
  22.   output '<toc>%n'
  23.   output referent 'myToc'
  24.   output '</toc>%n'
  25.   output '<%q>%n'  
  26.   output '%c'
  27.   output '</%q>'
  28.  
  29. element title
  30.   put toToc '%t'
  31.   put #current-output & toToc '<%q>%c</%q>%sn'
the output result
  1. <toc>
  2.  <title>Title of doc</title>
  3.  <title>first division title</title>
  4.  <title>second division title</title>
  5. </toc>
  6. <doc>
  7. <title>Title of doc</title>
  8. <div><title>first division title</title>
  9. <p>A paragraph in div1</p></div>
  10. <div><title>second division title</title>
  11. <p>A paragraph in div2</p></div></doc>

We open the stream toToc and attach that stream to referent 'myToc' . So if we output data to stream ‘toToc’, it will flow into the referent ‘myToc’.

In line 31 we send the titles to two streams #current-output and toToc at the same time. #current-output is a predefined OmniMark stream, its name speaks for itself.

Determine the scope of the referents

Suppose we have an XHTML document with tables and we have to add <col> elements. We have to add as many <col> elements as there are <td> cells in a row. Because the <col> elements come before the <td> elements we can use referents to solve this problem.

OmniMark source code, scope of a referent
  1. global integer numberOfColumns initial {0}
  2.  
  3. process
  4.   do xml-parse
  5.     scan '<html>' ||
  6.        '<table>' ||
  7.    '<tr><td>r1k1</td><td>r1k2</td><td>r1k3</td></tr>' ||
  8.    '<tr><td>r2k1</td><td>r2k2</td><td>r2k3</td></tr>' ||
  9.        '</table>'||
  10.        '<table>' ||
  11.        '<tr><td>r1k1</td><td>r1k2</td></tr>' ||
  12.        '<tr><td>r2k1</td><td>r2k2</td></tr>' ||
  13.        '</table>'||        
  14.        '</html>'
  15.     output '%sc'
  16.   done
  17.  
  18. element #implied
  19.   output '<%q>%c</%q>'
  20.  
  21. element table
  22.   using nested-referents
  23.     do
  24.       output '<%q>%n'
  25.       output referent 'number of columns'
  26.       output '%n%c</%q>%sn'
  27.     done
  28.  
  29. element tr
  30.   set numberOfColumns to 0
  31.   output '<%q>%c</%q>%n'
  32.   set referent 'number of columns' to
  33.          '<col/>' ||* numberOfColumns
  34.  
  35. element td
  36.   increment numberOfColumns
  37.   output '<%q>%c</%q>'
first table 3 col, second 2 col
  1. <html><table>
  2. <col/><col/><col/>
  3. <tr><td>r1k1</td><td>r1k2</td><td>r1k3</td></tr>
  4. <tr><td>r2k1</td><td>r2k2</td><td>r2k3</td></tr>
  5. </table>
  6. <table>
  7. <col/><col/>
  8. <tr><td>r1k1</td><td>r1k2</td></tr>
  9. <tr><td>r2k1</td><td>r2k2</td></tr>
  10. </table></html>

The scope of the referent 'number of columns' is within the <table> so when the end of a table is reached the referent is solved. We only need one referent although we have more than one table.

The scope is set by using the construct using nested-referents. If this construct wasn’t used every table would have the same referent and so the same number of <col> elements, in this case 2 columns because the last table has 2 columns.

Rating 3.00 out of 5
[?]