Output in OmniMark is part of the streaming architecture of the language. Data is processed as it streams and all streaming data has a source and a destination. Within an OmniMark program, the source of streaming data is always the current input scope, and the destination is always the current output scope. To direct output to a particular destination, such as a file, you must make that destination part of the current output scope.
Once you do this, all output in your program will go to the destination attached to your stream. This program
shows how this works:
process using output as file "out.txt" submit file "in.txt" find "$" digit+ => dollars "." digit{2} => cents output dollars || "," || cents || "$"
The output scope created by using output as file "out.txt"
exists for all code
governed by the using
statement. In this case, that code is the submit
action on the next line.
submit
initiates scanning by
find
rules. Therefore, any data that streams to output during the submit
flows to that output
scope. Any find
rules that fire as a result of the submit
are inside the output scope of the submit
, so any output they generate goes to the output scope.
The find
rule itself does not specify where its output goes. It simply goes to the current output scope.
This separation between generating output and deciding where output goes is fundamental to OmniMark. It allows you to write code to create output and call that code from many different places. Each place you call that code from can first establish an appropriate current output scope. The code that generates output is highly reusable because it operates completely independently of where the output is going.
OmniMark directs output to an output destination by means of streams. In the examples shown so far, OmniMark
has created and used anonymous streams. In the expression using output as file "out.txt"
, an anonymous
stream is created and attached to the file "out.txt". That anonymous stream is made the current output
destination by the using output as
prefix. Data written to the current output destination flows through
that anonymous stream to the file "out.txt".
You can also create a named stream and make it the current output destination:
process local stream out-file open out-file as file "out.txt" using output as out-file submit file "in.txt"
There are several reasons to create named streams.
If you want to reuse an output destination at different points in a program, you can attach that destination to a named stream and then make that stream part of the current input scope whenever required.
Named streams play the role of string variables in OmniMark. You can stream data to a string variable by
opening a named stream as a buffer:
process local stream temp-result open temp-result as buffer using output as temp-result submit file "in.txt" close temp-result
Notice that while an anonymous stream created by using output as
is closed and discarded as soon
as the output scope ends, the named steam temp-result
in the example above belongs to the scope of
the process rule. It remains active and open after the output scope ends, and it must be closed explicitly
before its contents are read.
The consequence of this is that there is exactly one output mechanism in OmniMark that applies to all output operations. This means that all OmniMark keywords that create output use the same mechanism and thus can all work on the full range of output destinations from buffers, to files, to network data streams.
OmniMark has three operations that create output:
You can use any one of them to write to any output destination. For example, you can use set
to place
a value in a file:
set file "mary.txt" to "Mary had a little lamb"
Conversely, you can use output
to write a value to a shelf item:
open Mary as buffer using output as Mary do output "Mary had a little lamb" output "Its fleece was white as snow" output "And everywhere that Mary went" output "The lamb was sure to go" done
This is much more efficient than writing:
set Mary to "Mary had a little lamb" set Mary to Mary || "Its fleece was white as snow" set Mary to Mary || "And everywhere that Mary went" set Mary to Mary || "The lamb was sure to go"
This is a powerful feature of OmniMark. It enables you to choose the type of data assignment mechanism
appropriate to the scale of operation you want to perform. You can use set
for any kind of small-scale
assignment, whether to a file or a shelf item, without any of the bother of opening files or buffers. For
large-scale operations, you can use output
and perform multiple updates without the need to specify the
destination, or even worry about the kind of destination involved. Choosing the method appropriate to the scale
of operation you are performing will greatly simplify your code.
An OmniMark program starts with a default output scope with a default stream attached to a default
destination. The name of the default stream is #main-output
and its default attachment is standard
output (stdout). stdout is the screen, unless it is redirected by a calling process, such as a web server (which
binds stdout to itself when launching a CGI script).
This means that if you create an OmniMark program that does not direct its output in any way, the output will
appear on the screen:
process output "Hello, World%n"
You can change the attachment of #main-output
on the command line using the -of command line option,
which is described in the OmniMark Engine documentation. This attaches #main-output
to the file named in
the -of
parameter. This allows you to write programs in OmniMark without specifying their
output, and specify the output file on the command line when you run the program.
If #main-output
is redirected in this way, you still have access to stdout through the built-in stream
#process-output
, which is permanently attached to stdout.
The statement using output as
both establishes a new output scope and places streams in that output
scope. However, you can change the streams in the current output scope without creating a new output scope. To
do this, you use the output-to
command:
process local stream foo-file local stream bar-file open foo-file as file "foo.txt" open bar-file as file "bar.txt" using output as foo-file do output "one" output-to bar-file output "two" done
The code above changes the stream in the output scope created by the using output as
statement from
foo-file to bar-file.
It is possible to send output to multiple destinations simultaneously by placing more than one stream in the
current output scope:
global stream my-buffer process open my-buffer as buffer using output as file "myfile.txt" & my-buffer submit "Mary had a little lamb"
You can refer to the streams in the current output scope with the keyword #current-output
. For
instance, this code uses #current-output to add a new stream to an existing output scope:
global stream my-buffer process using output as file "myfile.txt" submit "Mary had a little lamb" find "had" open my-buffer as buffer using output as my-buffer & #current-output output "I've been had!"
This code will place I've been had!
in both the file myfile.txt
and the shelf item
my-buffer
.