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:

     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.

The role of streams

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:

     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:

     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.

Equivalence of output operations

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
     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"

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.

Default output and the #main-output stream

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:

     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.

Changing the destination of the current output scope

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:

     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
        output "one"
        output-to bar-file
        output "two"

The code above changes the stream in the output scope created by the using output as statement from foo-file to bar-file.

Output to multiple destinations

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
     open my-buffer as buffer
     using output as file "myfile.txt" & my-buffer
        submit "Mary had a little lamb"

Referencing the streams in the current output scope

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
     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.