The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Text::UberText::NewModule - Tutorial for writing UberText modules

INTRODUCTION

UberText provides a mechanism for modules to easily bind to the UberText command language by defining a namespace and a set of commands. Once your module is written, either the class name, or a blessed object is passed to the Dispatch object. Then when the document is parsed, the Dispatch object passes along the command data to your code.

This module provides a quick rudimentary overview of how to write modules to Text::UberText.

WRITING A MODULE

Defining the namespace and dispatch methods

The following example is designed for a technical writer that wants to pre-preprocess her documents with UberText. She needs consistant headers and footers for every document she writes, so she automates it by writing a module can handle the formatting for her.

 package ACME::Document;

 use strict;
 use vars qw/$Namespace $Dispatch/;

 $Namespace="acme.doc";
 $Dispatch={
        "media" => &media,
        "header" => &formatHeader,
        "footer" => &formatFooter,
        "warnings" => &formatWarning,
 };

A namespace and a basic dispatch table have been defined, now UberText needs to be informed about it.

Importing the code to UberText

In the main formatting program, the UberText->extend() method is called.

 $ub=Text::UberText->new();
 $ub->extend("Acme::Document");

The class name (or a blessed object) passed to extend() is sent to the Dispatch object. The Dispatch object then takes that parameter and calls the method uberText().

 package ACME::Document;

 sub uberText
 {
 my ($proto)=shift;
 my ($obj);
 if (ref($proto))
 {
     $obj=$proto;
 } else {
     $obj={};
     bless($obj,$proto);
     $obj->_init();
 }
 return ($obj,$Namespace,$Dispatch);
 }

The ACME::Document object returns an object for the Dispatch module to reference, the namespace assigned to the object, and the hash table identifying which methods to run.

The object returned from the uberText method doesn't need to be the same object that received the uberText call. It's possible to create a whole new object from a different namespace if you want to keep any UberText handlers in a seperate module namespace.

Since we have a blessed object, we can create a data structure to keep track of internal variables, store portions of the document we want to parse, or receive data from other modules.

Writing the command methods

Let's say we want to identify which media a particular document is best formatted for. We don't necessarily want to change any aspects of the document formatting, but we want to identify how the document is laid out.

Within the document we can create a command tag called "media"

 [acme.doc media:(plotter)]

When UberText encoounters the "media" command in the "acme.doc" namespace, it in turn calls ACME::Document->media();

The parameter passed to the media() method is the Text::UberText::Node::Command object that contained the tag. This object keeps track of the main document tree object, the command itself, and whatever options or values were passed in the command tag.

Here's a simple example of the media method:

 sub media
 {
 my ($self,$node)=@_;
 $self->{media}=$node->commandValue();
 return;
 }

This method never made any changes to the document, it simply was used to record information about the document itself.

The commandValue() method is used to return the optional value that was passed to the command, which in the above example was "plotter".

The header method was designed to create a standard header for all documents in order to remain consistant. In this particular case, the programmer wants a header that can be modified with a couple of lines detailing the title of the document or an executive summary.

 [acme.doc header -> ]
 Table Specifications For The ACME billing database
 [<- acme.doc ]

This is known as a block command. It surrounds a block of text that is associated in some way with the command tag. The text within the block is to be merged in the header of the document.

 sub header
 {
 my ($self,$node)=@_;
 my ($output);
 $output="ACME Corp\n";
 if ($self->{media} eq "plotter")
 {
         $output.=->_insertLogo();
 }
 if ($node->startBlock())
 {
    my ($tree,$index);
    $tree=$node->tree();
    $index=$node->index();
    $tree->run($index);
    $output.=$tree->output($index);
 }
 $output.="--------------------------------------------------\n\n";
 return $output;
 }

The method is very simple. It prints the company name, and if the media is applicable, it adds data to represent the drawing of the company logo (postscript commands, perhaps).

The startBlock method identifies whether or not the node in question contains any child elements. Since it did in this case, it took the UberText::Tree object that contained the structure information of the document and instructed it to process every child node of the header command. Then, it took the output of those nodes and added it to the header it already created. Finally, a dashed line was added to $output to identify the end of the header portion of the document.

If the "header" command tag didn't have an opening tag indicator (->) and a closing tag as well, then It would have ignored the process of checking for child nodes and running them.

The programmer has set up the footer tag to be a little different, there is no real variation in the footer, but there should be a couple of conditions that could alter the display of the footer.

 [acme.footer author:(John Public) condifential]

The footer needs to include the author's name in the document, and if the document contains proprietary information, it needs to place warnings in the document.

 sub footer
 {
 my ($self,$node)=@_;
 my ($output,$author);
 $output="\n--------------------------------------------------\n";
 $author=$node->getOptValue("author");
 if ($author)
 {
     $output.=$author."\n";
 } else {
     $output.="Unknown author\n";
 }
 if ($node->getOpt("classified"))
 {
    $output.="This document contains proprietary information, and ".
       "should not be publically distributed.";
 }
 return $output;
 }

There are two different method calls for the Node object. The getOpt value returns two values, a 1 or 0 depending on whether or not the option was even set in the command tag, and the value that was set to the option (if any).

The getOptValue method is a simpler way of checking the value passed to an option. If no value was set, undef is returned. Programmers should understand that an undef value doesn't necessarily mean the option was never specified in the command tag; it just means there was no value associated with it.

AUTHOR

Chris Josephes <cpj1@visi.com>

SEE ALSO

Refer to the Text::UberText::Node::Command module for all of the methods available to retrieve command data. Refer to the Text::UberText::Dispatch module for information on how additional modules are loaded. The Text::UberText::Tree module has information on the document tree structure.

COPYRIGHT

Copyright 2002, Chris Josephes. All rights reserved. This module is free software. It may be used, redistributed, and/or modified under the same terms as Perl itself.