AXE - Ajh's Xml Engine

A.J.Hurst

Version 3.4.1

15 Feb 2002

Table of Contents

1 Introduction to AXE
2 User Manual
     2.1Running AXE
     2.2Writing the Translation Files
     2.2.1Comments
     2.2.2Include Commands
     2.2.3Translation Commands
     2.3Rules for the Translation Commands
     2.4Interface to the Perl Program
3 Main Program
4 Read Translation File
5 Handlers
6 Support code
     6.1Opening the Translation File
     6.2Handle included translation file
     6.3Divert element text
     6.4Handle White Space
     6.5Define Subroutines
     6.6Handling Included Source Files
     6.7Recording the Translations
     6.8Get Context Subroutine
     6.9Get Source File Name Subroutine
7 Context Sensitive Translations
8 Section Numbering and Table of Contents
     8.1Collect Titles from AUX File
9 TODOs
10 BUGS
11 Makefile
12 Maintenance History
13 Indices
     13.1Files
     13.2Chunks
     13.3Identifiers

 

1. Introduction to AXE

This program converts XML documents to alternative formats. Currently translations for tex and html format are available, but others are possible.

Access the link here if you want to download the source code of this document. Or you can download the tangled program itself by accessing the link there (you will need to do this first if you want to process the previous file!)

2. User Manual

2.1. Running AXE

The AXE program converts XML source documents to TeX or HTML documents. To use the program, type

axe -t html file.xml 
which will convert source document file.xml to the target document file.html. Alternatively,
axe -t tex file.xml 
will convert file.xml to the target document file.tex, which may then be processed by a TeX processor.

The source document is required only to be well-formed in the XML sense. The translations are defined in an external file, called the translation file. The translation file is identified by composing the document type (the name of the root element of the source XML document) with the target format (current "tex" or "html"). This document is retrieved from a search path specified by the environment variable XMLLIB.

For example, if XMLLIB contains the string .:~/lib/xml, and axe is invoked with

axe -t html fred.xml
where fred contains
<article>
  <section><title>Fred's Article</title>
    <p>Blah, blah, blah, ...</p>
  </section>
</article>
then the translation file is known as article.html, and if it exists in the current directory (the "." entry in XMLLIB), that file will be read for the translation definitions, otherwise it will be looked up in the directory ~/lib/xml. If it doesn't exist there, it is an error.

2.2. Writing the Translation Files

Entries in the translation file fall into three main groups:

  1. Comments
  2. Include commands
  3. Translation commands
We consider each of these in the sections below. The model used for translation is that every element in the document is broken down into start,content,end; where each of start, content, and end has an appropriate translation, called the prefix, inner, and postfix components. The inner translation is defined by the nested content, and is recursively translated, but the prefix and postfix translations are specified directly.

For example, if the document to be translated consists of

<uri href="there">Go There</uri>
and the translation for the tag uri is defined as
<A HREF="@<href>">.^^.</A>
then the translated value is
<A HREF="there">Go There</A>
If the text Go There instead contained other elements, these would be translated according to their specific rules. This is indicated by the inner context flag ^^, which indicates where the element content is to be translated and inserted.

2.2.1. Comments

Comments are indicated by a leading non-blank character of `#'. Subsequent text up to the end of line is ignored.

2.2.2. Include Commands

Include commands have the form:

include filename
where filename is a relative or absolute filename. If relative, it is searched for in the current directory, i.e., the directory in which axe was invoked. (Not any directory visited by other includes or the directory containing the including file. Note that it is intended to change this rule.)

2.2.3. Translation Commands

These define the translations to be applied upon recognizing the various start and end tags for each element in the document. The translation is enclosed in matching start and end XML tags, and is free format in that blanks and new lines may be used to improve readability. The translation itself consists of various replacement fragments or texts, which can be string literals or code fragments. The latter are Perl code fragments executed in the AXE environment. Each replacement fragment is separated from adjacent ones by the Perl concatenation operator "."

The translation is usually in three parts, corresponding to the prefix translation, applied before the element content is translated, then the element content itself, then the postfix translation, applied after the element content is translated. The element content is indicated either by a bare variable name, where the variable name is the same as the element tag, or by the characters ^^. Either the prefix or postfix translations can be empty.

Where the element content indicator does not appear, the translation is treated as a prefix translation only. Note that this allows means that there is no translation of the element content, and if there is any, it will not appear in the translated document at this point. This is usually used in the case of empty elements, but it can also be used to store content for later use, as any content that does appear will be saved under the eponymous variable name (that is, the variable with the same name as the element tag).

String replacement texts may be singly or doubly quoted, as in Perl. Singly quoted strings are not evaluated, meaning that everything within them is treated literally. Doubly quoted strings are evaluated (in the AXE environment), which means that variables are replaced by their values, and escaped characters are replaced by their escaped values (\n becomes a new line, \t becomes a tab, and so on). A string may be unquoted, in which case it must not contain any full stops/periods, and it will not be evaluated for escaped characters or variables.

Code fragments are enclosed in outer matching curly braces {}, and are evaluated in the AXE environment. Calls to interface routines must therefore be prefaced with a main:: prefix. Note that the value of the last expression in the code fragment is the value of that fragment, and is appended to the replacement text being assembled. Note that if the code fragment consists of just a single variable reference, the enclosing braces may be omitted.

Within the replacement text (all forms), the sequence @ is replaced by the attribute value for attr. If this attribute is not present in the original XML tag, an error is flagged. The sequence @?<attr> is replaced by an expression that evaluates to true if the attribute is present, false otherwise.

Examples of string translations:

<uri> '<A>' .$uri. "</A>" </uri> Replace the XML uri element with the equivalent HTML element <A>inner text</A>
<uri> <A HREF="@<href>" .^^. </A> </uri> Replace the XML uri element <uri href="blah"> inner text </uri> with the equivalent HTML element <A HREF="blah"> inner text </A>
<uri href> <A HREF="@<href"> .^^. </A> </uri> Replace the XML uri element <uri href="blah"> inner text </uri> with the equivalent HTML element <A HREF="blah"> inner text </A>

Note that in both of the last two cases an href attribute is required. It is an error if the attribute is not present, in the former case detected by the translator, in the latter by the parser.

Examples of code translations:

<today>$today</today> Replace the XML today element (presumably empty: nothing is done with the content) with the value of the variable $today (which has presumably been set somewhere else).
<section>
{main::enter_section(0)}
.^^.
{main::exit_section(0)}
</section>
Call the enter_section routine before translating the inner content, and the exit_section after.

2.3. Rules for the Translation Commands

A translation command has the general form (in BNF)

ElementTranslation = start prefix ['.' inner ['.' postfix]] end |
start inner ['.' postfix] end  
.
start = '<' elementname '>' .
prefix = translation ('.' translation)* .
inner = '^^' | '$' elementname .
postfix = translation ('.' translation)* .
translation = single | double | code | string | variable | 
      conditional translation .
conditional = '?' '<' attributename '>' .
single = "'" non-single-quote-char* "'" .
double = '"' non-double-or-escaped-char '"' .
code = '{' valid-Perl-code-fragment '}' .
string = non-period-char* .
variable = scalar-Perl-variable .
end = '</' elementname '>' .
where elementname is a valid XML element name, attributename is a valid XML attribute name, non-single-quote-char is any character but ', non-double-or-escaped-char is any character but ", or the pair of characters \ followed by any character, non-period-char is any character but ., and scalar-Perl-variable and valid-Perl-code-fragment are the appropriate Perl (syntactically correct) elements.

2.4. Interface to the Perl Program

output(string)
Append "string" to the output.
on_output()
Turn on output. Output is processed normally
off_output()
Turn off output. No output is made to the target file or the saved stream.
enter_section(int)
Start numbering and title collecting for a new section. The top level section is 0, and it is numbered with a single digit. Subsequent section level nesting (values of 1 and up) is numbered with decimal points indicating the subsection number.
exit_section(int)
Exit the section started with enter_section(int). If the section nesting is not strictly followed, a warning message is generated.
current_context()
Return a string representing the current element tag.
sourcename()
Return a string representing the source file name.

3. Main Program

"axe" 1 =
<define where is the perl interpreter 2>
<define package use 3>
<do initializations 35>
<process options 4>
<handle auxilary files 5>
<get document type 9>
<read and process translation file 6>
<parse and process the XML file 7>
<do finalizations 70>
<define all handlers and subroutines 8>
<>

This defines all the components of the main program.

<define where is the perl interpreter 2> =
#!/usr/bin/perl -w<>
Macro referenced in scrap <1>.

Define the interpreter to use. The -w flag is there mainly out of habit. (It warns if you do various (slightly) naughty things.)

<define package use 3> =
use XML::Parser;
use Getopt::Long;
use FileHandle;
<>
Macro referenced in scrap <1>.

load the XML Parser module, and the Get options module. I looked for the ``short'' options, but apparently it's out of date. FileHandle is now used to open the translations file, to allow nested include files (version 2.7ff).

<process options 4> =
$target = ""; $document = "";
GetOptions("target=s" => \$target,
           "document=s" => \$document,
           "verbose=s" => \$debug);
$lineno = 1;
<>
Macro referenced in scrap <1>.

Read and process the options. At the moment, there's three:

-t format
Output document in format format.
-d document
Process according to document type document. Note: this is not the document type definition! It is used to define the base name of the translation file, until I get a better model for doing it.
-v
issue debugging output. This really shouldn't be a string option, but I couldn't find the documentation about how to set a boolean option.

<handle auxilary files 5> =
$sourcefile = shift;
die "Can't find file \"$sourcefile\"" unless -f $sourcefile;
if ($sourcefile !~ m#^/.*$#) {
  my($cwd)=`pwd`; chop($cwd);
  $sourcefile=$cwd."/".$sourcefile;
}
$opfile = $sourcefile;
$mode = $target;
$opfile =~ s/\.\w+$/.$mode/;
open(OP,">$opfile");
<read AUX file at start 68>
<>
Macro referenced in scrap <1>.

Now grab the (assumed) only parameter, which is the input source file name, and reconstruct the output file name, with an extension given by the target language. For example, if you call this program with the shell line axe -t tex loadpw.xml, then output will be written to a file loadpw.texOpen the output file for writing.

An auxiliary file AUX is also opened with extension .aux

<read and process translation file 6> =
$transfile = "$document.$target";
<open the translation file 33>
%transtable = ();
$|=1;
do {
  while (get_transline()) {
    <process a translation line 10>
  }
  close_transfile();
} until ($xflev < 0);
$|=0;
print "\n"; # close last translation file trace line
<>
Macro referenced in scrap <1>.

Now open and read the file containing the style transformations. This has a main name of the root element (or otherwise specified explicitly with the -d switch), and an extension corresponding to the target language (determined by the -t switch).

The variable $xflev is used to count the number of nested open translation files, and indexes into the stack of open translation files @xfhandle

%transtable is the hash used to store all the translations.

<parse and process the XML file 7> =
my $parser = new XML::Parser(ErrorContext => 4,
                             NoExpand => 1);
$parser->setHandlers(Start => \&start_handler,
                     End   => \&end_handler,
                     Char  => \&char_handler,
                     Proc  => \&proc_handler,
                     Entity  => \&entity_handler,
                     Default => \&default_handler);
$sourcehandle = new FileHandle;
$sourcehandle->open("< ".$sourcefile);
$linenumber=1;
$parser->parse($sourcehandle);
<>
Macro referenced in scrap <1>.

This defines all the interface with the XML Parser. We set an error context of 4 lines. The NoExpand option doesn't appear to work. We also set the various handler linkages, and then do the parse of the input document.

<define all handlers and subroutines 8> =
<define start handler 14>
<define attribute substitutions 19>
<define end handler 15>
<define char handler 28>
<define proc handler 30>
<define default handler 31>
<define entity handler 32>
<define output routine 52>
<define subroutines 41>
<define translation file routines 36>
<>
Macro referenced in scrap <1>.

4. Read Translation File

The translation file is the real secret to this program. It works on the principle that every tag has some form of processing attached to it that defines the style of the output document. By separating the translation specification from this program, we have a more generic program that can be used in a range of contexts. Hence each translation file defines the particular translations needed to map the document into the various formats.

Currently we have TeX and HTML formats defined, but there's no reason why others could not be defined. Simply build the file doctype.format, where doctype is the root element that you wish to translate, and format is the format you wish to translate it to. For example, litprog.html translates literate program documents (as generated by XLP) in HTML documents.

Here are some example translation lines:

FileLineMeaning
litprog.html<itemize><UL> translate the XML tag itemize to the HTML tag UL
litprog.html<itemize/item><LI> translate the XML tag item to the HTML tag LI when it is a child of the itemize element.
litprog.tex <itemize>\begin{itemize} translate the XML tag itemize to the TeX command \begin{itemize}
litprog.tex <itemize/item>"\item " translate the XML tag item to the TeX command \item (followed by a space) when it is a child of the itemize element.
litprog.html >t;">" translate the entity >t; to a greater than character

<get document type 9> =
if ($document =~ /^$/) {
  open (INP, $sourcefile) or die "Cannot open file $sourcefile for reading\n";
GETDOCTYPE: while (<INP>) {
    if (/<\?axe "([^"]*)"\?>/) {
      $document=$1; 
      print "got PI with type $document\n";
      last GETDOCTYPE;
    }
    if (/<([^!?][^> ]*).*>/) {$document=$1; last GETDOCTYPE;}
  }
}
close INP;
<>
Macro referenced in scrap <1>.

If we don't have an explicit specification of the document type, read the input file, searching for either a processing instruction for this program (axe), or the root element start tag. In the former case, use the attribute, in the latter case, use the root element name as the base name for the translation file.

The document type defines what translation file to read, and is defined by the root element tag. Read the source file to find out the root element type and use that. We collect the tag as far as the first blank or closing angle bracket.

<process a translation line 10> =
<skip blank and comment lines 13>
my($element,$translation);
my($line) = $_;
if ($line=~s#^ *<([^/].*?)>##) {
  <read an element translation 11>
} else {
  <read an entity translation 12>
}
<process and store translation 56>
<>
Macro referenced in scrap <6>.

On entry to this chunk, $_ contains the current translation line.

Each input line of the transform file consists of two fields. The first field is mandatory, and is either the xml start or end tag, marked up in angle brackets, or an entity designation. The second field is the target translation, and extends to the end of line.

Text can extend across a new line boundary by escaping the new line with a \ character, that is, if the last character on a line is the \ character, another line is read and appended to the partially constructed line. Leading blanks on these lines are stripped, while multiple trailing blanks are reduced to a single blank. This allows indentation to be stripped to save space, but an essential separating blank can be inserted by using at least one trailing blank.

The translation can be a literal, or a piece of code that is evaluated instead. In the latter case, the code is enclosed in braces: `{}'.

The code above does the basic assembly of translation lines. We check to see if the current line as read ends with a back slash \. While it does, we chop it off, replace multiple trailing blanks with a single one, read the next line and excise any leading blanks from that, and append the newly read material to the existing line.

If there is a trailing back slash on the newly read line, the process is repeated.

Finally, we have a fully assembled translation line. Process and save this translation.

<read an element translation 11> =
$element=$1;
while (! ($line=~s#</$element>$##)) {
  chop($line);
#print "looking at element =>$line<=\n";
  do {
    get_transline();
  } while (/^ *#/);
  s/^ *//;
  $line .= $_;
  if ($xfhandle[$xflev]->eof) {
    print STDERR "EOF while scanning element translation!!\n";
    print STDERR "currently read:<$element>$line<=\n";
    last;
  }
}
chop($line);
#print "found element =>$line<=\n";
$line="<$element>==$line";
<>
Macro referenced in scrap <10>.

We have recognized an element translation. Read lines until we find the matching end tag, barfing if we hit end of file in the process. The last thing we do is to construct the form <tag>==translation as an artifact to assist in subsequent processing of the translation (see chunk <process and store translation 56>).

<read an entity translation 12> =
while ($line=~s/\\$//) {
  chop($line);
  get_transline();
  s/^ *//;
  $line .= $_;
  if ($xfhandle[$xflev]->eof) {
    print STDERR "EOF while scanning entity translation!!\n";
    last;
  }
}
<>
Macro referenced in scrap <10>.

<skip blank and comment lines 13> =
next if /^\w*$/;
next if /^\w*\#.*$/;
<>
Macro referenced in scrap <10>.

Ignore blank or comment lines, and cycle for next line of input.

5. Handlers

<define start handler 14> =
sub start_handler {
  my($p,$el) = @_; shift; shift;
  my($rattr) = {@_};
  $parsenodes[$curnode++]=$p;
  #my($attrstring) = join(" ",sort keys %$rattr);
  $start="";
  if ($debug) {print "<$start$el>";}
  #if ($attrstring) {print "<$el $attrstring>";}
  #else {print "<$el>";}
  #if (defined($transtable{$el.' '.$attrstring})) {
    #$translation = $transtable{$el.' '.$attrstring};
  #} else {
    $translation = $transtable{$el};
  #}
  if (defined($translation)) {
    <check and resolve for multiple translations 16>
  } else {
    print "<$el> tag at line $lineno ignored: no translation! ";
    print "(possible missing attributes?)\n"; 
    return;
  }
  <perform the translation 18>
  start($el);
}
<>
Macro referenced in scrap <8>.

Each time the start handler is called, we use the element tag as a key to access the translation table. If no entry is found, a warning message is printed and the tag ignored. Otherwise, the tag is translated into the output document according to the specification in the translation file, now stored in the translation table.

<define end handler 15> =
sub end_handler {
  my ($p,$el) = @_;
  $start="/";
  if ($debug) {print "<$start$el>";}
  $translation = $transtable{$start.$el};
  if (defined($translation)) {
    <check and resolve for multiple translations 16>
  } else {
    print "<$start$el> tag ignored: no translation given\n"; return;
  }
  if ($translation=~s/^\$$el\.//) {
    end($el,1);
  } else {
    end($el,0);
  }
  <perform the translation 18>
  $curnode--;
}
<>
Macro referenced in scrap <8>.

The handling of end tags is very similar to the handling of start tags (see <define start handler 14>), except that we define the translation table key as the concatenation of the end character / with the element tag.

<check and resolve for multiple translations 16> =
if (ref($translation)) {
  my(%definedtranslations) = %$translation;
  <get full context list 17>
  my($checkimmediate) = 1;
  my($imm) = $fullcontext[-1];
  while (@fullcontext) {
    my($immediatecontext) = $fullcontext[-1];
    if (!defined($immediatecontext)) {$immediatecontext=""};
    if (defined($translation->{$immediatecontext."/"})) {
      $translation = $translation->{$immediatecontext."/"};
      last
    } else {
      if (defined($translation->{"~"})) {
        $translation = $translation->{"~"};
        last
      } else {
        $checkimmediate = 0
      }
    }
    pop(@fullcontext)
  }
  if (@fullcontext) {
    if (!$checkimmediate) {
      print STDERR "no immediate translations for $imm/$el ";
      print STDERR "in ",join(',',sort keys(%definedtranslations)),"\n"; 
    }
  } else {
    print STDERR "no translation for $el ";
    print STDERR "in ",join(',',sort keys(%definedtranslations)),"\n";    
  }
}
<>
Macro referenced in scraps <14>, <15>.

When multiple translations are given, it means that there are context sensitive translations. These are indicated by the translation value being a reference to a table of contexts, rather than the string value of the translation itself. Multiple translations are resolved against the context tree, working our way up the current path. If no match is found, we check the default context (indicated by a key of just ``~'').

Ancestor contexts are not properly handled. If an immediate context is not found, the algorithm simply searches all contexts, without taking note of the translation specification. This is currently a kludge. The algorithm used is the following two stage process.

  1. get hold of list of contexts in outermost first order. The Expat context value supplies this.
  2. Pop elements from the tail of this list, testing against the keys(%$translation) value. The first match is the correct one to use.

<get full context list 17> =
my(@fullcontext) = $p->context;
if (!@fullcontext) {@fullcontext = ("~")};
<>
Macro referenced in scrap <16>.

We get the current context from the Expat variable context. One special case needs attention: if we haven't seen the root element yet, the context is void, so we set the context to the special global context flagged as ~.

<perform the translation 18> =
my($val); 
if ($translation) {
  my($code,$conditional); 
  while ($translation) {
    $conditional=1;
    <check, translate and store a conditional result 20>
    #print "looking at |$translation|\n";
    $translation =~ s/^ *//;
    <check and translate a doubly quoted string 21>
    <check and translate a singly quoted fragment 22>
    <check and translate a code fragment 23>
    <check and translate a bare variable 26>
    <check and translate a bare attribute 25>
    <check and translate a bare string 24>
    <check for suspect translation 27>
    else {
      print "Unknown translation =>$translation<=\n";
      $val = "<empty>"; # </empty>
    }
    if ($debug) {print "evaluating $start$el =>$val\n";}
  }
} else {
    $val = "<empty>"; # </empty>
    if ($debug) {print "evaluating $start$el =>$val\n";}
}
<>
Macro referenced in scraps <14>, <15>.

This is the heart of the translation algorithm. While there is still some translation to be made, extract the next fragment up to and including the next . or end-of-line. There are several choices: doubly quoted strings (which are evaluated); singly quoted strings (which are not); code fragments (enclosed in curly braces, and are evaluated); bare variables ($varname (evaluated); and bare strings (unquoted, but must not contain . characters). Anything else is flagged as suspect, and an evaluation attempt made. The fall-through else should never get executed. The outermost else is there to ensure that the final debug statement doesn't get an undefined value.

<define attribute substitutions 19> =
sub subs_attr {
  my($rattr,$translation,,$element,$iscode)=@_;
  my($query,$key,$lookup,$isdef);
  while ($translation=~/@(\??)<(.*?)>/) {
    $query=$1; $key=$2; $lookup=$$rattr{$key}; $isdef=defined($lookup);
    if (!$isdef) {$isdef='""'; $lookup="";}
    if ($query) {
      $translation=~s/@\?<$key>/$isdef/g;
    } elsif ($isdef) {
      if ($iscode) {$translation=~s/@<$key>/"$lookup"/g;}
      else {$translation=~s/@<$key>/$lookup/g;}
    } else {
      if ($iscode) {
        $translation=~s/@<$key>/main::missing_attr("$key","$element")/g;
      } else {
        $lookup=missing_attr($key,$element);
        $translation=~s/@<$key>/$lookup/g;
      }
    }
  }
#print "subs_attr returns |$translation|\n";
  return $translation;
}
<>
Macro referenced in scrap <8>.

This routine is called to replace any strings of the form @<attr> or @?<attr> in the translation. These are replaced with the actual value of the attribute attr in the first case (an error message is issued if it is not defined in the input source, or the boolean value 1 or "" in the second case, depending upon whether the attribute is defined in the input source or not respectively.

<check, translate and store a conditional result 20> =
if ($translation =~ s/^\?([^.]+)://) {
  $val=$1;
  if ($val=~/<(.*)>/) {
    if (defined($$rattr{$1})) {$conditional=1;} else {$conditional=0;}
  } else {
    print STDERR "ill-formed conditional translation $val\n";
  }
}
<>
Macro referenced in scrap <18>.

<check and translate a doubly quoted string 21> =
if ($translation =~ s/^ *((".*?[^\\]")|("")) *(\.|$)//) {
  if ($conditional) {
    $code=subs_attr($rattr,$1,$el,0);
    {package AXE; $val=eval($code); if ($@) {print STDERR $@;}}
    output($val);
  }
}
<>
Macro referenced in scrap <18>.

A doubly quoted string is evaluated to interpret nested variables, escaped characters and the like. Note that the inclusion of the non-backslash character means that the string must contain at least one character, hence the alternative pattern to match the null string.

<check and translate a singly quoted fragment 22> =
elsif ($translation =~ s/^ *'(.*?)' *(\.|$)//) {
  if ($conditional) {
    $val=subs_attr($rattr,$1,$el,0); output($val);
  }
}
<>
Macro referenced in scrap <18>.

<check and translate a code fragment 23> =
elsif ($translation =~ /^{/) {
  if ($conditional) {
    my($count)=1; my($code)=substr($translation,0,1);
    my($indouble,$insingle,$instring,$lastescape)=(0,0,0,0);
    $translation=substr($translation,1);
    while ($count>0) {
      my($char)=substr($translation,0,1);
      $instring = $insingle | $indouble;
      if ($char eq '"') {
        if (!$lastescape) {$indouble=!$indouble;}
      } 
      elsif ($char eq "'") {$insingle=!$insingle;}
      elsif ($char eq "}") {$count-- unless ($instring);}
      elsif ($char eq "{") {$count++ unless ($instring);}
      $lastescape = $char eq '\\';
      $code.=$char; $translation=substr($translation,1);
    }
    $translation=~s/^\.//;
    $code=subs_attr($rattr,$code,$el,1);
    {package AXE; $val=eval($code); if ($@) {print $@;}}
    if (defined($val)) {output($val);} 
    else {
      print "Cannot evaluate code=$code for element $start$el\n";
      $val="";
    }
  }
}
<>
Macro referenced in scrap <18>.

<check and translate a bare string 24> =
elsif ($translation =~ s/^([^.]*?)(\.|$)//) {
  if ($conditional) {
    $code=subs_attr($rattr,$1,$el,0); $val=$code; output($code);
  }
} 
<>
Macro referenced in scrap <18>.

<check and translate a bare attribute 25> =
elsif ($translation =~ s/^(@<.*?>)(\.|$)//) {
  if ($conditional) {
    $code=subs_attr($rattr,$1,$el,0); $val=$code; output($code);
  }
}
<>
Macro referenced in scrap <18>.

<check and translate a bare variable 26> =
elsif ($translation =~ s/^(\$.*?)(\.|$)//) {
  if ($conditional) {
    {package AXE; $val=eval($1); if ($@) {print $@;}}
    if (defined($val)) {output($val);} 
    else {print "$1 fails!\n"; $val="";}
  }
}
<>
Macro referenced in scrap <18>.

Note that we do not need to substitute for attribute markers in this case.

<check for suspect translation 27> =
elsif ($translation =~ s/^(.*)(\.|$)//) {
  if ($conditional) {
    $code=subs_attr($rattr,$1,$el,0); 
    print "iffy translation =>$translation,$code<=\n";
    {package AXE; $val=eval($code); if ($@) {print $@;}}
    if (defined($val)) {output($val);} 
    else {print "$translation fails!\n"; $val="";}
  }
}
<>
Macro referenced in scrap <18>.

<define char handler 28> =
sub char_handler {
    my ($p, $data) = @_;
    if ($data =~ /\n/ ) {
      $lineno++;
      $data =~ s/\n/ / if $ignorenl;
    }
<>
Macro defined by scraps <28>, <29>.
Macro referenced in scrap <8>.

We rely on newlines coming in one at at time for this to work.

<define char handler 29> =
    if ($collapseblnk && $data =~ /^\s/) {
      my($prev) = substr($CONTENT,-1,1);
      if ($prev =~ /\s/ ) {
        $data =~ s/^\s*//;
      }
    }
    output($data);
}
<>
Macro defined by scraps <28>, <29>.
Macro referenced in scrap <8>.

<define proc handler 30> =
sub proc_handler {
  my($p,$target,$data) = @_;
  #print "proc handler finds \$target=$target, \$data=$data!\n";
  if ($target eq "axe") {
  }
}
<>
Macro referenced in scrap <8>.

My original intention was to have this handle a processing instruction for AXE. However, the timing of reading the translation file is not quite right, since we have to do that before reading any translations. My current thinking is to use a "just-in-time" reading, but since this involves a little rearrangement of code, I haven't plucked up the courage to do it just yet!

<define default handler 31> =
sub default_handler {
    my ($p, $string) = @_;
#    print "*** default handler called with string ==>$string<==\n";
    my ($trans) = $entity{$string};
    if (defined($trans)) {
      output($entity{$string});
    } elsif ($string =~ /&.*;/) {
      print "Undefined entity $string\n";
    } else {
      # do nothing
    }
}
<>
Macro referenced in scrap <8>.

<define entity handler 32> =
sub entity_handler {
  my($p, $name, $val) = @_;
#  print "Entity Handler called with name=$name and value=$val\n";
}
<>
Macro referenced in scrap <8>.

6. Support code

6.1. Opening the Translation File

We want to allow translation files to be stored in a range of places. The environment variable XMLLIB is used to define the directories to be searched for the target files. If this is not defined, use ``.'' as default. I use an XMLLIB declaration of ``.:/home/ajh/lib/xml''.

<open the translation file 33> =
$lib = $ENV{"XMLLIB"};
if (!defined($lib)) {$lib = ".";}
open_transfile($transfile) or 
  die "cannot locate $transfile anywhere in $lib\n";
<>
Macro referenced in scrap <6>.

6.2. Handle included translation file

Translation files may include other translation files. Definitions in the included file are processed as they are read, so they will override existing definitions, and in turn their definitions may be overridden by subsequent definitions, either in-line or in other included files.

<handle included translation file 34> =
open_transfile($1);
<>
Macro referenced in scrap <56>.

<do initializations 35> =
$xflev=-1;
<>
Macro defined by scraps <35>, <39>, <42>, <51>, <63>.
Macro referenced in scrap <1>.

<define translation file routines 36> =
sub open_transfile {
  my($tfname) = @_;
  #print "opening translation file $tfname\n";
  $xflev++;
  $xfhandle[$xflev] = new FileHandle;
  if ($tfname=~m#^/(.*)/([^/]+)#) {
    #print "got absolute file $tfname\n";
    if ($xfhandle[$xflev]->open("< $tfname")) {
      print " [$tfname";
      return 1;
    } else {
      return 0;
    }
  } 
  $path="";
  my($next) = $lib;
  while ($next) {
    #print "looking in $next for $tfname\n";
    $next =~ s/([^:]*)(:|$)//;
    if (defined($1)) {
      $path = $1;
      if ($xfhandle[$xflev]->open("< $path/$tfname")) {
        #$xfname[$xflev]="$path/$tfname";
        print " [$path/$tfname";
        return 1;
      }
    }
  }
  print STDERR " *** $tfname not in $lib]";
  $xflev--;
  return 0;
}
<>
Macro defined by scraps <36>, <37>, <38>.
Macro referenced in scrap <8>.

<define translation file routines 37> =
sub get_transline {
  if ($xfhandle[$xflev]->eof()) {return 0}
  $_ = $xfhandle[$xflev]->getline();
  return 1;
}
<>
Macro defined by scraps <36>, <37>, <38>.
Macro referenced in scrap <8>.

<define translation file routines 38> =
sub close_transfile {
  $xfhandle[$xflev]->close();
  $xflev--;
  print "]";
}
<>
Macro defined by scraps <36>, <37>, <38>.
Macro referenced in scrap <8>.

Set the initial value of the include directory to be the current working directory.

6.3. Divert element text

On occasions, we do not want to output the element text directly, but rather buffer it up for later handling. This code does that. We use a flag variable $divert to turn on this diversion, and a string variable $content to save the element content.

We can also turn output on and off. This is done with the global flag output_active, which is turned on and off with the two routines on_output and off_output.

<do initializations 39> =
$divert = 0; $content = ""; $output_active = 1;
$xfhandle[0] = new FileHandle;
<>
Macro defined by scraps <35>, <39>, <42>, <51>, <63>.
Macro referenced in scrap <1>.

<define output active routine 40> =
sub on_output {
  $output_active = 1;
}

sub off_output {
  $output_active = 0;
}
<>
Macro referenced in scrap <41>.

6.4. Handle White Space

There are various things we might want to do with white space. One is to ignore newline characters, and translate them to blanks. Another is to collapse multiple sequences of blanks and/or newlines to a single blank. Here are routines to turn these features on and off

<define subroutines 41> =
<define output active routine 40>
<define ignore newlines routine 43>
<define collapse blanks 44>
<>
Macro defined by scraps <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>.
Macro referenced in scrap <8>.

<do initializations 42> =
$ignorenl = 0; $collapseblnk = 0;
<>
Macro defined by scraps <35>, <39>, <42>, <51>, <63>.
Macro referenced in scrap <1>.

<define ignore newlines routine 43> =
sub ignore_newlines {
  $ignorenl = $_[0];
}
<>
Macro referenced in scrap <41>.

There are occasions when we want to ignore newlines in the xml code, and not pass them through. Calling this routine with a TRUE parameter will ignore further newlines until the routine is called again with a FALSE parameter.

<define collapse blanks 44> =
sub collapse_blanks {
  $collapseblnk = @_;
}
<>
Macro referenced in scrap <41>.

6.5. Define Subroutines

<define subroutines 45> =
<define attribute handling routines 48>
sub start_collect {
  $divert=1; $content="";
}
<>
Macro defined by scraps <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>.
Macro referenced in scrap <8>.

<define subroutines 46> =
sub end_collect {
  my($savename) = @_;
  if ($savename) {$$savename = $content;}
  $divert=0; $content="";
}
<>
Macro defined by scraps <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>.
Macro referenced in scrap <8>.

<define subroutines 47> =
sub get_attr {
  my($attrname,$rattr) = @_;
  my($attrv) = $$rattr{$attrname};
  if (defined($attrv)) {$$attrname = $attrv;}
  else {$$attrname="";}
}
<>
Macro defined by scraps <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>.
Macro referenced in scrap <8>.

<define attribute handling routines 48> =
sub missing_attr {
  my($attrname,$element) = @_;
  print STDERR "attempt to reference missing attribute \"$attrname\" ";
  print STDERR "in element <$element>\n"; 
  return "(*** missing attr $attrname ***)";
}
<>
Macro referenced in scrap <45>.

missing_attr is called when there is an attempt to use an attribute that has not been defined in the source text.

6.6. Handling Included Source Files

Besides the included translation files described above, we also may want to include source files. To do this we define a support routines nested_source. This takes a parameter defining the name of the file to be included, and parses this file, using the same parsing parameters as for the main source document.

<define subroutines 49> =
sub nested_source {
  my($filename) = @_;
  my($savelinenumber) = $linenumber;
  my $parser = new XML::Parser(ErrorContext => 4,
                               NoExpand => 1);
  $parser->setHandlers(Start => \&start_handler,
                       End   => \&end_handler,
                       Char  => \&char_handler,
                       Entity  => \&entity_handler,
                       Default => \&default_handler);
  $sourcehandle = new FileHandle;
  $sourcehandle->open("< ".$filename) or return 1;
  $linenumber=1;
  {eval($parser->parse($sourcehandle)); if ($@) {return 2;}}
  $linenumber=$savelinenumber;
  return "";
}
<>
Macro defined by scraps <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>.
Macro referenced in scrap <8>.

<define subroutines 50> =
sub nested_target {
  my($filename,$translate) = @_;
  my($savelinenumber) = $linenumber;
  $sourcehandle = new FileHandle;
  if($sourcehandle->open("< ".$filename)) {
    $linenumber=1;
    while ($_=$sourcehandle->getline) {
      if ($translate) {s/</</g;}
      output($_);
    }
    $linenumber=$savelinenumber;
  } else {
    print STDERR "cannot open nested target file $filename\n";
  }
}
<>
Macro defined by scraps <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>.
Macro referenced in scrap <8>.

6.7. Recording the Translations

In version 3, we keep track of all components of the translation. The array @element_start contains the index of the start point of every currently open element. When an element is closed, this substring is copied into a variable of the same name in the AXE namespace. This variable is available for use elsewhere, unless overwritten by an outer or subsequent element with the same name.

<do initializations 51> =
$CONTENT=""; $element_start[0]=0; $element_depth=0;
<>
Macro defined by scraps <35>, <39>, <42>, <51>, <63>.
Macro referenced in scrap <1>.

The variable $CONTENT accumulates all text fed to the output routine.

@element_start records the starting indices of all currently open elements.

$element_depth records the number of currently open elements.

<define output routine 52> =
sub output {
  if (defined(@_)) {
    my($data) = @_; 
    if ($output_active) {$CONTENT .= $data}; 
    return "";
  }
}
<>
Macro referenced in scrap <8>.

The actual output is done by the output routine, which buffers all output into the $CONTENT variable.

<define subroutines 53> =
sub start {
  my($name) = @_;
  $name=~s/:/_/g;
  $element_start[$element_depth++] = length($CONTENT);
}

sub end {
  my($name,$doout) = @_;
  $name=~s/:/_/g;
  $element_depth--;
  my($trimpnt)=$element_start[$element_depth];
  package AXE;
  $$name = substr($::CONTENT,$trimpnt);
  if (!$doout) {$::CONTENT=substr($::CONTENT,0,$trimpnt);}
}
<>
Macro defined by scraps <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>.
Macro referenced in scrap <8>.

6.8. Get Context Subroutine

<define subroutines 54> =
sub current_context () {
  return $parsenodes[$curnode-1]->current_element
}
<>
Macro defined by scraps <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>.
Macro referenced in scrap <8>.

6.9. Get Source File Name Subroutine

<define subroutines 55> =
sub sourcename () {
  return $sourcefile;
}
<>
Macro defined by scraps <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>.
Macro referenced in scrap <8>.

Return the source file name as a string. Note that this name is always converted to an absolute pathname.

7. Context Sensitive Translations

We want to be able to change the translations of various elements depending upon the context. For example, the <item> tag should translate to the <LI> tag in the itemize element, but a <DD> tag in the description element.

XSLT uses a context sensitive notation for its translations, so I'll follow that style for now, at least until I can a good reason for doing something different. The notation context/tag will mean recognize tag only when it has the immediate parent element context, and the notation context//tag will mean recognize tag only when it has some ancestor context.

For example, the item context might be specified with something like:

<description/item><DD>
</description/item></DD>
<itemize/item><LI>
</itemize/item></LI>

To do this, we need to be able to examine the context of the current element. The Expat method call context will do this for us.

We also need a data structure to handle the multiplicity of translations that may be invoked. We will assume that each relevant translation will be called in some (yet to be determined) order. If there is more than one translation for a given tag, we need to build a list of these translations. This is relevant for chunk <process a translation line 10>.

<process and store translation 56> =
if ($line =~ /^include (.*)$/) {
  <handle included translation file 34>
} elsif ($line =~ /<([^>]*)>==(.*)$/) {
  $istag = 1; $element = $1; $translation = $2;
  <process element and translation 57>
} elsif ($line =~ /&([^;]*);(.*)$/) {
  $istag = 0; $element = $1; $translation = $2;
  <process entity and translation 62>
} else {
  print "Could not extract specifications from $line\n";
  $element = ""; $translation = "";
}
<>
Macro referenced in scrap <10>.

Look at the line. We expect it to have the form <tag>==translation if it is a tag translation, or &name;translation if it is an entity translation. Match this and check that both tag and translation get specified correctly. (The double equals is inserted in chunk <read an element translation 11> to simplify the pattern matching here.)

<process element and translation 57> =
#print "$element==$translation\n";
$element =~ m#^(.+/)?(.*)$#;
if (defined($1)) {$context=$1;} else {$context="";}
if (defined($2)) {$element=$2;} else {$element="";}
xpcarp("No element tag present in $element") unless defined($2);
<>
Macro defined by scraps <57>, <58>, <60>.
Macro referenced in scrap <56>.

Then strip off a) any material up to and including a slash if present (this is the context of the element, field $1), and b) the element tag (which must be present, field $2). Carp if the latter (field 3) is not found.

<process element and translation 58> =
if ($translation=~/^ *((.*?\.)?)(<inner pattern 59>)((\..*)?) *$/) {
  $prefix=$1; $innerflag=$3; $postfix=$7; $start="";
  $prefix=~s/\.$//; 
  $postfix=~s/^\.//; 
  if ($innerflag !~ /^\@\@$/) {
    $postfix="\$$element.".$postfix;
  }
  if ($innerflag =~ /^\$/) {
    print STDERR "Warning: use of \$$element deprecated from version 3.1.4\n";
  }
<>
Macro defined by scraps <57>, <58>, <60>.
Macro referenced in scrap <56>.

See if the translation has the standard form prefix.$tag.postfix, or the alternate form prefix.^^.postfix. If so, split it into these parts, then reinsert the $tag. at the start of the postfix tranbslation, to indicate that element content is to be output at that point.

If the inner pattern is @@, then no inner context is emitted, and we do not add the $tag to the start of the postfix translation.

<inner pattern 59> =
(\$$element)|(\^\^)|(\@\@)<>
Macro referenced in scrap <58>.

The inner context is defined by one of three forms:

  1. The element name or tag used as a Perl variable, $tag, with the same interpretation as the next form. (This form is deprecated, but retained for compatibility.)
  2. The two characters ^^, indicating that inner context should be output at that point.
  3. The two characters @@, indicating that inner context should not be output.

<process element and translation 60> =
  foreach $translation ($prefix,$postfix) {
    <define pre/postfix translation 61>
    $start="/";
  }
} else {
  $start="";
  <define pre/postfix translation 61>
  $translation=""; $start="/";
  <define pre/postfix translation 61>
}
<>
Macro defined by scraps <57>, <58>, <60>.
Macro referenced in scrap <56>.

Split the translation into prefix,content,postfix. If this match is not possible, assume that all the translation is a prefix one.

<define pre/postfix translation 61> =
$tval = $transtable{$start.$element};
if (!defined($tval)) {
  if ($context) {
    $rtran = {$context,$translation}; 
    $transtable{$start.$element} = $rtran;
  } else {
    $transtable{$start.$element} = $translation;
  }
} else {
  if (ref($tval)) {$rtran = $tval;} 
  else {$rtran = {"~",$tval};}
  if ($context) {$rtran->{$context} = $translation;} 
  else {$rtran->{"~"} = $translation;}
  $transtable{$start.$element} = $rtran;
}
<>
Macro referenced in scrap <60>.

Store the translation in a hash table indexed by the element tag. The $context may be empty, tag/, or tag//. See chunk <perform the translation 18> for how this is used in the parse phase.

<process entity and translation 62> =
$translation =~ s/^"(.*)"$/$1/;
$entity{"&$element;"}=$translation;
<>
Macro referenced in scrap <56>.

Handling entity definitions is pretty straightforward. We just store the translation in an associate array entity, indexed by the entity name.

8. Section Numbering and Table of Contents

A common task in documents is defining numbered sections and constructing a table of contents. Of particular relevance in the html version is hyperlinking from the table of contents to the sections. The code in this section relates to handling these issues.

<do initializations 63> =
$sectionnos[0] = 0; $sectionlevel = -1; $sectiontitles[0]="";
$sectionerrorflag=0;
<>
Macro defined by scraps <35>, <39>, <42>, <51>, <63>.
Macro referenced in scrap <1>.

First we define and initialize some data structures. The array $sectionnos stores the section number for each level of nesting, while $sectionlevel defines the current depth of sectioning. The array @sectiontitles stores the section titles, initially read in from the AUX file, but updated as each new section title is processed.

<define subroutines 64> =
sub enter_section {
  my($level) = @_;
  ++$sectionnos[++$sectionlevel];
  $sectionnos[$sectionlevel+1] = 0;
  if ($sectionlevel != $level) {
    print STDERR "improper nesting of section level $level at line $lineno\n";
  }
  if (defined($$rattr{'title'})) {
    print STDERR "old format title attribute \"",$$rattr{'title'},"\"\n";
  }
  return "";
}
<>
Macro defined by scraps <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>.
Macro referenced in scrap <8>.

enter_section is called whenever we enter a new section, subsection, etc.. It checks that we have the proper sequence of nesting, and starts a new numbering counter.

<define subroutines 65> =
sub section_title {
  package main;
  my($title) = @_;
  $title =~ s/\n/ /g;
  my($sectitle);
  $sectitleno++;
  my($oldsectitle) = $sectiontitles[$sectitleno];
  if (!defined($oldsectitle)) {$oldsectitle="";}
  $sectitle = section_string().' '.$title;
  if (($oldsectitle ne $sectitle) & !$sectionerrorflag) {
    print STDERR "Section numbers and/or titles changed: rerun\n";
    $sectionerrorflag=1;
  }
  $sectiontitles[$sectitleno] = $sectitle;
  return "";
}
<>
Macro defined by scraps <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>.
Macro referenced in scrap <8>.

enter_section is called whenever we detect that a new section, subsection, subsubsection, etc., is entered. The parameter passed is the expected nesting level. The title string is now passed by a separate call to section_title. The titles are assembled into an array of title strings (including the section numbers) for later use in the table of contents.

<define subroutines 66> =
sub exit_section {
  --$sectionlevel; return "";
}
<>
Macro defined by scraps <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>.
Macro referenced in scrap <8>.

As we leave each section, all that needs to be done is to updated the section nesting level.

<define subroutines 67> =
sub section_string {
  return join('.',@sectionnos[0..$sectionlevel]);
}
<>
Macro defined by scraps <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>.
Macro referenced in scrap <8>.

8.1. Collect Titles from AUX File

Section titles require a two pass algorithm over the source file, since the XML parser only does a single pass. (If we used a tree-driven parser, things might be different here.) The titles are stored in an auxiliary file which is read in at the start of operations, so that it can be output on when the appropriate table of contents command is encountered, which may be before all titles are processed.

As we go, the titles from the aux file are compared with the newly read titles, and an error message issued if they differ.

<read AUX file at start 68> =
$auxfile = $sourcefile;
$auxfile =~ s/\.\w+$/.aux/;
if (open(AUX,"<$auxfile")) {
  for ($i=1; <AUX>; $i++) {
    chop;
    $sectiontitles[$i] = $_;
  }
  close(AUX);
  $sectitleno = 0;
} else {
  print STDERR "No $auxfile to open\n";
}
<>
Macro referenced in scrap <5>.

Read the AUX file (named as source file with extension changed to .aux) before starting any processing. This allows us to have an array of section titles for use in generating any table of contents.

<define subroutines 69> =
sub gen_toc_line {
  my($i,$template) = @_;
  my($title) = $sectiontitles[$i];
  $title =~ /^([0-9.]+) (.*)$/;
  my($tno,$tstr) = ($1,$2);
  $template =~ s/<number>/$tno/g;
  $template =~ s/<string>/$tstr/g;
  output($template);
}
<>
Macro defined by scraps <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>.
Macro referenced in scrap <8>.

<do finalizations 70> =
print OP $CONTENT;
if (@sectiontitles > 1) {
  open(AUX,">$auxfile");
  for ($i=1; $i<@sectiontitles; $i++) {
    my($title) = $sectiontitles[$i];
    print AUX $title,"\n";
  }
  close(AUX);
} else {
  if (-f $auxfile) {unlink $auxfile;}
}
<>
Macro referenced in scrap <1>.

Write out the up-to-date list of section titles.

9. TODOs

(In order of priority.)

  1. Recognize XML and DOCTYPE markup at start.
  2. allow a specific start and end translations, triggered before reading the first line of input, and after processing the last line of input (this can be done by the root element, so it is low priority).
  3. when a translation fails, print context of elements
  4. Allow non-immediate context processing of translations

10. BUGS

  1. formal parameters in macro calls get garbled in TeX mode
  2. included files with relative path names should be relative to the including file, not the current directory.
  3. included file handling is a mess, and the closing brackets don't come out in the right places.
  4. Context processing is a hack. The distinction between immediate and non-immediate is not made. Currently all contexts are immediate.

11. Makefile

"Makefile" 71 =
SYNCH = $(HOME)/bin/synch
AXE= /home/ajh/bin/axe
default=axe
TRANSFERFILES=axe.w
WEBPAGE=$(HOME)/public_html/research/literate
CSSEWEBPAGE=$(HOME)/shelob/research/literate
all: axe axe.html
include $(HOME)/etc/MakeXMLLiterate
install: axe install-html
        cp axe $(HOME)/bin/axe
Makefile: axe.tangle
axe: axe.tangle
axe.exe:    axe
        chmod 755 axe
axe.html: axe.xml
        $(AXE) -t html axe.xml
install-html: axe.html
        cp -p axe.html $(WEBPAGE)/axe.html
        if [ -d $(CSSEWEBPAGE) ] ; then \
          cp -p axe.html $(CSSEWEBPAGE)/axe.html ; \
        fi
<>

12. Maintenance History

29 Nov 1999 John Hurst1.0 initial markup (as "xml")
30 Nov 1999 John Hurst1.0.1 move DTD to external
01 Dec 1999 John Hurst1.0.2 add tex capability
01 Dec 1999 John Hurst1.0.3 add GetOpt
01 Dec 1999 John Hurst1.0.4 add element attributes
09 Dec 1999 John Hurst2.0 read conversions from .lang file
09 Dec 1999 John Hurst2.0.1 allow double quotes around translations
10 Dec 1999 John Hurst2.0.2 allow folded lines on translation file input
13 Dec 1999 John Hurst2.1 read document type from input document
14 Dec 1999 John Hurst2.1.1 ... and improvements on same
14 Dec 1999 John Hurst2.2 add library path in env var XMLLIB
15 Dec 1999 John Hurst2.2.1 minor changes and tidy ups
16 Dec 1999 John Hurst2.2.2 entity handling
21 Dec 1999 John Hurst2.2.3 divert element contents
24 Dec 1999 John Hurst2.2.4 ... and in start/end handlers, too
04 Jan 2000 John Hurst2.2.5 add ignore CR routine
04 Jan 2000 John Hurst2.2.6 skip blank lines in translation file, warn if no translation encountered
05 Jan 2000 John Hurst2.2.7 add debug option
09 Jan 2000 John Hurst2.3.0 convert to XML lit prog
13 Jan 2000 John Hurst2.4.0 add context sensitive translation
14 Jan 2000 John Hurst2.4.1 add entity handling
15 Jan 2000 John Hurst2.4.2 define entity translations in translation file
25 Jan 2000 John Hurst2.5.0 add internal section numbers, titles, and table of contents
25 Jan 2000 John Hurst2.5.1 revise extraction of document type
06 Mar 2000 John Hurst2.6.0 revise section interface procedures
11 Mar 2000 John Hurst2.7.0 change XFR file to $xfhandle FileHandle
12 Mar 2000 John Hurst2.7.1 add "include file" to translations file
19 Mar 2000 John Hurst2.7.2 rename as AXE
19 Mar 2000 John Hurst2.8.0 read source from file handle to allow source inclusions
20 Mar 2000 John Hurst2.8.1 keep blanks on end of escaped line
24 Mar 2000 John Hurst2.9.0 (debug) keep blanks on end of escaped line, change translation file trace, add identifier hot links and cross referencing
25 Mar 2000 John Hurst2.9.1 additions to User Manual
26 Mar 2000 John Hurst2.9.2 modify translation data structures and lookup; add simple attribute matching to tags
02 Apr 2000 John Hurst2.9.3 updated debugging trace
11 Apr 2000 John Hurst3.0.0 revise translation file format
12 Apr 2000 John Hurst3.0.1 new translation file format bug fixes
13 Apr 2000 John Hurst3.0.2 move translation script variables into separate name space
17 Apr 2000 John Hurst3.0.3 update User Manual
18 Apr 2000 John Hurst3.1.0 revise trans file format (again)
19 Apr 2000 John Hurst3.1.1 add attribute translation and check
20 Apr 2000 John Hurst3.1.2 documentation updates, and add conditional translations
22 Apr 2000 John Hurst3.1.3 add subroutine call when attributes undefined
26 Apr 2000 John Hurst3.1.4 fixed bug with variable setting, added warning about inner content using variable names
28 Apr 2000 John Hurst3.2.0 modified include file handling (needs more work still).
04 May 2000 John Hurst3.2.1 improved translation file handling.
12 May 2000 John Hurst3.2.2 fix bug in output of evaluation trace.
16 May 2000 John Hurst3.2.3 add current element and interface
27 May 2000 John Hurst3.2.4 add source file name interface
15 Jun 2000 John Hurst3.2.5 add ignore blanks interface
17 Jun 2000 John Hurst3.3.0 aux file not written if empty
18 Sep 2000 John Hurst3.3.1 add PI to define translation file
20 Nov 2000 John Hurst3.4.0 hacked ancestor translation support -- needs more work
21 Nov 2000 John Hurst3.4.1 fixed bug -- still needs more work

 

13. Indices

13.1. Files

"axe"  <1>
"Makefile"  <71>

13.2. Chunks

<check and resolve for multiple translations  <16>> Referenced in scraps <14>, <15>.
<check and translate a bare attribute  <25>> Referenced in scrap <18>.
<check and translate a bare string  <24>> Referenced in scrap <18>.
<check and translate a bare variable  <26>> Referenced in scrap <18>.
<check and translate a code fragment  <23>> Referenced in scrap <18>.
<check and translate a doubly quoted string  <21>> Referenced in scrap <18>.
<check and translate a singly quoted fragment  <22>> Referenced in scrap <18>.
<check for suspect translation  <27>> Referenced in scrap <18>.
<check, translate and store a conditional result  <20>> Referenced in scrap <18>.
<define all handlers and subroutines  <8>> Referenced in scrap <1>.
<define attribute handling routines  <48>> Referenced in scrap <45>.
<define attribute substitutions  <19>> Referenced in scrap <8>.
<define char handler  <28>, <29>> Referenced in scrap <8>.
<define collapse blanks  <44>> Referenced in scrap <41>.
<define default handler  <31>> Referenced in scrap <8>.
<define end handler  <15>> Referenced in scrap <8>.
<define entity handler  <32>> Referenced in scrap <8>.
<define ignore newlines routine  <43>> Referenced in scrap <41>.
<define output active routine  <40>> Referenced in scrap <41>.
<define output routine  <52>> Referenced in scrap <8>.
<define package use  <3>> Referenced in scrap <1>.
<define pre/postfix translation  <61>> Referenced in scrap <60>.
<define proc handler  <30>> Referenced in scrap <8>.
<define start handler  <14>> Referenced in scrap <8>.
<define subroutines  <41>, <45>, <46>, <47>, <49>, <50>, <53>, <54>, <55>, <64>, <65>, <66>, <67>, <69>> Referenced in scrap <8>.
<define translation file routines  <36>, <37>, <38>> Referenced in scrap <8>.
<define where is the perl interpreter  <2>> Referenced in scrap <1>.
<do finalizations  <70>> Referenced in scrap <1>.
<do initializations  <35>, <39>, <42>, <51>, <63>> Referenced in scrap <1>.
<get document type  <9>> Referenced in scrap <1>.
<get full context list  <17>> Referenced in scrap <16>.
<handle auxilary files  <5>> Referenced in scrap <1>.
<handle included translation file  <34>> Referenced in scrap <56>.
<inner pattern  <59>> Referenced in scrap <58>.
<open the translation file  <33>> Referenced in scrap <6>.
<parse and process the XML file  <7>> Referenced in scrap <1>.
<perform the translation  <18>> Referenced in scraps <14>, <15>.
<process a translation line  <10>> Referenced in scrap <6>.
<process and store translation  <56>> Referenced in scrap <10>.
<process element and translation  <57>, <58>, <60>> Referenced in scrap <56>.
<process entity and translation  <62>> Referenced in scrap <56>.
<process options  <4>> Referenced in scrap <1>.
<read AUX file at start  <68>> Referenced in scrap <5>.
<read an element translation  <11>> Referenced in scrap <10>.
<read an entity translation  <12>> Referenced in scrap <10>.
<read and process translation file  <6>> Referenced in scrap <1>.
<skip blank and comment lines  <13>> Referenced in scrap <10>.

13.3. Identifiers

AUX   <68>, <70>, <68>, <5>.
auxfile   <68>, <70>, <68>.
collapseblnk   <42>, <44>, <42>, <29>.
CONTENT   <51>, <70>, <53>, <52>, <51>, <29>.
element_depth   <51>, <53>, <51>.
element_start   <51>, <53>, <51>.
end   <53>, <15>, <8>.
end_handler   <15>, <49>, <15>, <7>.
enter_section   <64>, <65>.
exit_section   <66>.
ignorenl   <42>, <43>, <42>, <28>.
ignore_newlines   <43>.
missing_attr   <48>, <19>.
nested_source   <49>.
nested_target   <50>.
off_output   <40>.
on_output   <40>.
parser   <7>, <49>, <7>.
section_string   <67>, <65>.
section_title   <65>.
sourcefile   <5>, <68>, <55>, <9>, <7>, <5>.
sourcehandle   <7>, <50>, <49>, <7>.
start   <53>, <61>, <60>, <58>, <53>, <23>, <18>, <15>, <14>, <8>, <5>.
start_handler   <14>, <49>, <14>, <7>.
subs_attr   <19>, <27>, <25>, <24>, <23>, <22>, <21>, <19>.
transtable   <6>, <61>, <15>, <14>, <6>.
xfhandle   <6>, <39>, <38>, <37>, <36>, <12>, <11>.
xflev   <6>, <38>, <37>, <36>, <35>, <12>, <11>, <6>.