]> granicus.if.org Git - docbook-dsssl/commitdiff
Copy of the saxon61 sources; not converted to the 6.3 API
authorNorman Walsh <ndw@nwalsh.com>
Sun, 13 May 2001 21:46:06 +0000 (21:46 +0000)
committerNorman Walsh <ndw@nwalsh.com>
Sun, 13 May 2001 21:46:06 +0000 (21:46 +0000)
18 files changed:
xsl/extensions/saxon63/.cvsignore [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/CVS.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/Callout.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/CalloutEmitter.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/ColumnScanEmitter.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/ColumnUpdateEmitter.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/CopyEmitter.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/FormatCallout.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/FormatGraphicCallout.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/FormatTextCallout.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/FormatUnicodeCallout.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/LineCountEmitter.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/NumberLinesEmitter.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/Table.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/Text.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/TextFactory.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/Verbatim.java [new file with mode: 0644]
xsl/extensions/saxon63/com/nwalsh/saxon/package.html [new file with mode: 0644]

diff --git a/xsl/extensions/saxon63/.cvsignore b/xsl/extensions/saxon63/.cvsignore
new file mode 100644 (file)
index 0000000..4d3c216
--- /dev/null
@@ -0,0 +1 @@
+.classes
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/CVS.java b/xsl/extensions/saxon63/com/nwalsh/saxon/CVS.java
new file mode 100644 (file)
index 0000000..529546b
--- /dev/null
@@ -0,0 +1,90 @@
+package com.nwalsh.saxon;
+
+import java.io.*;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.text.DateFormat;
+import java.text.ParseException;
+
+/**
+ * <p>Saxon extension to convert CVS date strings into local time</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000 Norman Walsh.</p>
+ *
+ * <p>This class provides a
+ * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon</a>
+ * extension to turn the CVS date strings, which are UTC:</p>
+ *
+ * <pre>&#36;Date: 2000/11/09 02:34:20 &#36;</pre>
+ *
+ * <p>into legibly formatted local time:</p>
+ *
+ * <pre>Wed Nov 08 18:34:20 PST 2000</pre>
+ *
+ * <p>(I happened to be in California when I wrote this documentation.)</p>
+
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @version $Id$
+ *
+ */
+public class CVS {
+  /**
+   * <p>Constructor for CVS</p>
+   *
+   * <p>All of the methods are static, so the constructor does nothing.</p>
+   */
+  public CVS() {
+  }
+
+  /**
+   * <p>Convert a CVS date string into local time.</p>
+   *
+   * @param cvsDate The CVS date string.
+   *
+   * @return The date, converted to local time and reformatted.
+   */
+  public static String localTime (String cvsDate) {
+    // A cvsDate has the following form "$Date$"
+    if (!cvsDate.startsWith("$Date: ")) {
+      return cvsDate;
+    }
+
+    String yrS = cvsDate.substring(7,11);
+    String moS = cvsDate.substring(12,14);
+    String daS = cvsDate.substring(15,17);
+    String hrS = cvsDate.substring(18,20);
+    String miS = cvsDate.substring(21,23);
+    String seS = cvsDate.substring(24,26);
+
+    TimeZone tz = TimeZone.getTimeZone("GMT+0");
+    GregorianCalendar gmtCal = new GregorianCalendar(tz);
+
+    try {
+      gmtCal.set(Integer.parseInt(yrS),
+                Integer.parseInt(moS)-1,
+                Integer.parseInt(daS),
+                Integer.parseInt(hrS),
+                Integer.parseInt(miS),
+                Integer.parseInt(seS));
+    } catch (NumberFormatException e) {
+      // nop
+    }
+
+    Date d = gmtCal.getTime();
+
+    return d.toString();
+  }
+}
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/Callout.java b/xsl/extensions/saxon63/com/nwalsh/saxon/Callout.java
new file mode 100644 (file)
index 0000000..f0be0cd
--- /dev/null
@@ -0,0 +1,91 @@
+package com.nwalsh.saxon;
+
+import com.icl.saxon.om.*;
+import com.icl.saxon.tree.*;
+
+/**
+ * <p>A class for maintaining information about callouts.</p>
+ *
+ * <p>To make processing callouts easier, they are parsed out of the
+ * input structure and stored in a sorted array. (The array is sorted
+ * according to the order in which the callouts occur.)</p>
+ *
+ * <p>This class is just the little record
+ * that we store in the array for each callout.</p>
+ */
+public class Callout implements Comparable {
+  /** The callout number. */
+  private int callout = 0;
+  /** The area ElementInfo item that generated this callout. */
+  private ElementInfo area = null;
+  /** The line on which this callout occurs. */
+  private int line = 0;
+  /** The column in which this callout appears. */
+  private int col = 0;
+
+  /** The constructor; initialize the private data structures. */
+  public Callout(int callout, ElementInfo area, int line, int col) {
+    this.callout = callout;
+    this.area = area;
+    this.line = line;
+    this.col = col;
+  }
+
+  /**
+   * <p>The compareTo method compares this Callout with another.</p>
+   *
+   * <p>Given two Callouts, A and B, A < B if:</p>
+   *
+   * <ol>
+   * <li>A.line < B.line, or</li>
+   * <li>A.line = B.line && A.col < B.col, or</li>
+   * <li>A.line = B.line && A.col = B.col && A.callout < B.callout</li>
+   * <li>Otherwise, they're equal.</li>
+   * </ol>
+   */
+  public int compareTo (Object o) {
+    Callout c = (Callout) o;
+
+    if (line == c.getLine()) {
+       if (col > c.getColumn()) {
+         return 1;
+       } else if (col < c.getColumn()) {
+         return -1;
+       } else {
+         if (callout < c.getCallout()) {
+           return -1;
+         } else if (callout > c.getCallout()) {
+           return 1;
+         } else {
+           return 0;
+         }
+       }
+    } else {
+       if (line > c.getLine()) {
+         return 1;
+       } else {
+         return -1;
+       }
+    }
+  }
+
+  /** Access the Callout's area. */
+  public ElementInfo getArea() {
+    return area;
+  }
+
+  /** Access the Callout's line. */
+  public int getLine() {
+    return line;
+  }
+
+  /** Access the Callout's column. */
+  public int getColumn() {
+    return col;
+  }
+
+  /** Access the Callout's callout number. */
+  public int getCallout() {
+    return callout;
+  }
+}
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/CalloutEmitter.java b/xsl/extensions/saxon63/com/nwalsh/saxon/CalloutEmitter.java
new file mode 100644 (file)
index 0000000..693c6e8
--- /dev/null
@@ -0,0 +1,527 @@
+package com.nwalsh.saxon;
+
+import java.util.Stack;
+import java.util.StringTokenizer;
+import org.xml.sax.*;
+import org.w3c.dom.*;
+import javax.xml.transform.TransformerException;
+import com.icl.saxon.Builder;
+import com.icl.saxon.Context;
+import com.icl.saxon.expr.*;
+import com.icl.saxon.functions.Extensions;
+import com.icl.saxon.om.*;
+import com.icl.saxon.output.*;
+import com.icl.saxon.pattern.*;
+import com.icl.saxon.tinytree.TinyBuilder;
+import com.icl.saxon.tree.*;
+
+/**
+ * <p>Saxon extension to decorate a result tree fragment with callouts.</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000 Norman Walsh.</p>
+ *
+ * <p>This class provides the guts of a
+ * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon 6.*</a>
+ * implementation of callouts for verbatim environments. (It is used
+ * by the Verbatim class.)</p>
+ *
+ * <p>The general design is this: the stylesheets construct a result tree
+ * fragment for some verbatim environment. The Verbatim class initializes
+ * a CalloutEmitter with information about the callouts that should be applied
+ * to the verbatim environment in question. Then the result tree fragment
+ * is "replayed" through the CalloutEmitter; the CalloutEmitter builds a
+ * new result tree fragment from this event stream, decorated with callouts,
+ * and that is returned.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @see Verbatim
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @version $Id$
+ *
+ */
+public class CalloutEmitter extends CopyEmitter {
+  /** A stack for the preserving information about open elements. */
+  protected Stack elementStack = null;
+
+  /** A stack for holding information about temporarily closed elements. */
+  protected Stack tempStack = null;
+
+  /** Is the next element absolutely the first element in the fragment? */
+  protected boolean firstElement = false;
+
+  /** The FO namespace name. */
+  protected static String foURI = "http://www.w3.org/1999/XSL/Format";
+
+  /** The default column for callouts that specify only a line. */
+  protected int defaultColumn = 60;
+
+  /** Is the stylesheet currently running an FO stylesheet? */
+  protected boolean foStylesheet = false;
+
+  /** The current line number. */
+  private static int lineNumber = 0;
+
+  /** The current column number. */
+  private static int colNumber = 0;
+
+  /** The (sorted) array of callouts obtained from the areaspec. */
+  private static Callout callout[] = null;
+
+  /** The number of callouts in the callout array. */
+  private static int calloutCount = 0;
+
+  /** A pointer used to keep track of our position in the callout array. */
+  private static int calloutPos = 0;
+
+  /** The FormatCallout object to use for formatting callouts. */
+  private static FormatCallout fCallout = null;
+
+  /** <p>Constructor for the CalloutEmitter.</p>
+   *
+   * @param namePool The name pool to use for constructing elements and attributes.
+   * @param graphicsPath The path to callout number graphics.
+   * @param graphicsExt The extension for callout number graphics.
+   * @param graphicsMax The largest callout number that can be represented as a graphic.
+   * @param defaultColumn The default column for callouts.
+   * @param foStylesheet Is this an FO stylesheet?
+   */
+  public CalloutEmitter(NamePool namePool,
+                       int defaultColumn,
+                       boolean foStylesheet,
+                       FormatCallout fCallout) {
+    super(namePool);
+    elementStack = new Stack();
+    firstElement = true;
+
+    this.defaultColumn = defaultColumn;
+    this.foStylesheet = foStylesheet;
+    this.fCallout = fCallout;
+  }
+
+  /**
+   * <p>Examine the areaspec and determine the number and position of 
+   * callouts.</p>
+   *
+   * <p>The <code><a href="http://docbook.org/tdg/html/areaspec.html">areaspecNodeSet</a></code>
+   * is examined and a sorted list of the callouts is constructed.</p>
+   *
+   * <p>This data structure is used to augment the result tree fragment
+   * with callout bullets.</p>
+   *
+   * @param areaspecNodeSet The source document &lt;areaspec&gt; element.
+   *
+   */
+  public void setupCallouts (NodeSetIntent areaspecNodeSet) {
+    callout = new Callout[10];
+    calloutCount = 0;
+    calloutPos = 0;
+    lineNumber = 1;
+    colNumber = 1;
+
+    // First we walk through the areaspec to calculate the position
+    // of the callouts
+    //  <areaspec>
+    //  <areaset id="ex.plco.const" coords="">
+    //    <area id="ex.plco.c1" coords="4"/>
+    //    <area id="ex.plco.c2" coords="8"/>
+    //  </areaset>
+    //  <area id="ex.plco.ret" coords="12"/>
+    //  <area id="ex.plco.dest" coords="12"/>
+    //  </areaspec>
+    try {
+      int pos = 0;
+      int coNum = 0;
+      boolean inAreaSet = false;
+      NodeInfo areaspec = areaspecNodeSet.getFirst();
+      NodeInfo children[] = areaspec.getAllChildNodes();
+
+      for (int count = 0; count < children.length; count++) {
+       NodeInfo node = children[count];
+       if (node.getNodeType() == NodeInfo.ELEMENT) {
+         if (node.getNodeName().equalsIgnoreCase("areaset")) {
+           coNum++;
+           NodeInfo areas[] = node.getAllChildNodes();
+           for (int acount = 0; acount < areas.length; acount++) {
+             NodeInfo area = areas[acount];
+             if (area.getNodeType() == NodeInfo.ELEMENT) {
+               if (area.getNodeName().equalsIgnoreCase("area")) {
+                 addCallout(coNum, area, defaultColumn);
+               } else {
+                 System.out.println("Unexpected element in areaset: "
+                                    + area.getNodeName());
+               }
+             }
+           }
+         } else if (node.getNodeName().equalsIgnoreCase("area")) {
+           coNum++;
+           addCallout(coNum, node, defaultColumn);
+         } else {
+           System.out.println("Unexpected element in areaspec: "
+                              + node.getNodeName());
+         }
+       }
+      }
+
+      // Now sort them
+      java.util.Arrays.sort(callout, 0, calloutCount);
+    } catch (TransformerException e) {
+      //nop;
+    }
+  }
+
+  /** Process characters. */
+  public void characters(char[] chars, int start, int len)
+    throws TransformerException {
+
+    // If we hit characters, then there's no first element...
+    firstElement = false;
+
+    if (lineNumber == 0) {
+      // if there are any text nodes, there's at least one line
+      lineNumber++;
+      colNumber = 1;
+    }
+
+    // Walk through the text node looking for callout positions
+    char[] newChars = new char[len];
+    int pos = 0;
+    for (int count = start; count < start+len; count++) {
+      if (calloutPos < calloutCount
+         && callout[calloutPos].getLine() == lineNumber
+         && callout[calloutPos].getColumn() == colNumber) {
+       if (pos > 0) {
+         rtfEmitter.characters(newChars, 0, pos);
+         pos = 0;
+       }
+
+       closeOpenElements(rtfEmitter);
+
+       while (calloutPos < calloutCount
+              && callout[calloutPos].getLine() == lineNumber
+              && callout[calloutPos].getColumn() == colNumber) {
+         fCallout.formatCallout(rtfEmitter, callout[calloutPos]);
+         calloutPos++;
+       }
+
+       openClosedElements(rtfEmitter);
+      }
+
+      if (chars[count] == '\n') {
+       // What if we need to pad this line?
+       if (calloutPos < calloutCount
+           && callout[calloutPos].getLine() == lineNumber
+           && callout[calloutPos].getColumn() > colNumber) {
+
+         if (pos > 0) {
+           rtfEmitter.characters(newChars, 0, pos);
+           pos = 0;
+         }
+
+         closeOpenElements(rtfEmitter);
+
+         while (calloutPos < calloutCount
+                && callout[calloutPos].getLine() == lineNumber
+                && callout[calloutPos].getColumn() > colNumber) {
+           formatPad(callout[calloutPos].getColumn() - colNumber);
+           colNumber = callout[calloutPos].getColumn();
+           while (calloutPos < calloutCount
+                  && callout[calloutPos].getLine() == lineNumber
+                  && callout[calloutPos].getColumn() == colNumber) {
+             fCallout.formatCallout(rtfEmitter, callout[calloutPos]);
+             calloutPos++;
+           }
+         }
+
+         openClosedElements(rtfEmitter);
+       }
+
+       lineNumber++;
+       colNumber = 1;
+      } else {
+       colNumber++;
+      }
+      newChars[pos++] = chars[count];
+    }
+
+    if (pos > 0) {
+      rtfEmitter.characters(newChars, 0, pos);
+    }
+  }
+
+  /**
+   * <p>Add blanks to the result tree fragment.</p>
+   *
+   * <p>This method adds <tt>numBlanks</tt> to the result tree fragment.
+   * It's used to pad lines when callouts occur after the last existing
+   * characater in a line.</p>
+   *
+   * @param numBlanks The number of blanks to add.
+   */
+  protected void formatPad(int numBlanks) {
+    char chars[] = new char[numBlanks];
+    for (int count = 0; count < numBlanks; count++) {
+      chars[count] = ' ';
+    }
+
+    try {
+      rtfEmitter.characters(chars, 0, numBlanks);
+    } catch (TransformerException e) {
+      System.out.println("Transformer Exception in formatPad");
+    }
+  }
+
+  /**
+   * <p>Add a callout to the global callout array</p>
+   *
+   * <p>This method examines a callout <tt>area</tt> and adds it to
+   * the global callout array if it can be interpreted.</p>
+   *
+   * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are
+   * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed.
+   * If only a line is specified, the callout decoration appears in
+   * the <tt>defaultColumn</tt>.</p>
+   *
+   * @param coNum The callout number.
+   * @param node The <tt>area</tt>.
+   * @param defaultColumn The default column for callouts.
+   */
+  protected void addCallout (int coNum,
+                            NodeInfo node,
+                            int defaultColumn) {
+
+    ElementInfo area = (ElementInfo) node;
+    ExtendedAttributes attr = area.getAttributeList();
+    String units  = attr.getValue("units");
+    String coords = attr.getValue("coords");
+
+    if (units != null
+       && !units.equalsIgnoreCase("linecolumn")
+       && !units.equalsIgnoreCase("linerange")) {
+      System.out.println("Only linecolumn and linerange units are supported");
+      return;
+    }
+
+    if (coords == null) {
+      System.out.println("Coords must be specified");
+      return;
+    }
+
+    // Now let's see if we can interpret the coordinates...
+    StringTokenizer st = new StringTokenizer(coords);
+    int tokenCount = 0;
+    int c1 = 0;
+    int c2 = 0;
+    while (st.hasMoreTokens()) {
+      tokenCount++;
+      if (tokenCount > 2) {
+       System.out.println("Unparseable coordinates");
+       return;
+      }
+      try {
+       String token = st.nextToken();
+       int coord = Integer.parseInt(token);
+       c2 = coord;
+       if (tokenCount == 1) {
+         c1 = coord;
+       }
+      } catch (NumberFormatException e) {
+       System.out.println("Unparseable coordinate");
+       return;
+      }
+    }
+
+    // Make sure we aren't going to blow past the end of our array
+    if (calloutCount == callout.length) {
+      Callout bigger[] = new Callout[calloutCount+10];
+      for (int count = 0; count < callout.length; count++) {
+       bigger[count] = callout[count];
+      }
+      callout = bigger;
+    }
+
+    // Ok, add the callout
+    if (tokenCount == 2) {
+      if (units != null && units.equalsIgnoreCase("linerange")) {
+       for (int count = c1; count <= c2; count++) {
+         callout[calloutCount++] = new Callout(coNum, area,
+                                               count, defaultColumn);
+       }
+      } else {
+       // assume linecolumn
+       callout[calloutCount++] = new Callout(coNum, area, c1, c2);
+      }
+    } else {
+      // if there's only one number, assume it's the line
+      callout[calloutCount++] = new Callout(coNum, area, c1, defaultColumn);
+    }
+  }
+
+  /** Process end element events. */
+  public void endElement(int nameCode)
+    throws TransformerException {
+
+    if (!elementStack.empty()) {
+      // if we didn't push the very first element (an fo:block or
+      // pre or div surrounding the whole block), then the stack will
+      // be empty when we get to the end of the first element...
+      elementStack.pop();
+    }
+    rtfEmitter.endElement(nameCode);
+  }
+
+  /** Process start element events. */
+  public void startElement(int nameCode,
+                          org.xml.sax.Attributes attributes,
+                          int[] namespaces,
+                          int nscount)
+    throws TransformerException {
+
+    if (!skipThisElement(nameCode)) {
+      StartElementInfo sei = new StartElementInfo(nameCode, attributes,
+                                                 namespaces, nscount);
+      elementStack.push(sei);
+    }
+
+    firstElement = false;
+
+    rtfEmitter.startElement(nameCode, attributes, namespaces, nscount);
+  }
+
+  /**
+   * <p>Protect the outer-most block wrapper.</p>
+   *
+   * <p>Open elements in the result tree fragment are closed and reopened
+   * around callouts (so that callouts don't appear inside links or other
+   * environments). But if the result tree fragment is a single block
+   * (a div or pre in HTML, an fo:block in FO), that outer-most block is
+   * treated specially.</p>
+   *
+   * <p>This method returns true if the element in question is that
+   * outermost block.</p>
+   *
+   * @param nameCode The name code for the element
+   *
+   * @return True if the element is the outer-most block, false otherwise.
+   */
+  protected boolean skipThisElement(int nameCode) {
+    if (firstElement) {
+      int thisFingerprint    = namePool.getFingerprint(nameCode);
+      int foBlockFingerprint = namePool.getFingerprint(foURI, "block");
+      int htmlPreFingerprint = namePool.getFingerprint("", "pre");
+      int htmlDivFingerprint = namePool.getFingerprint("", "div");
+
+      if ((foStylesheet && thisFingerprint == foBlockFingerprint)
+         || (!foStylesheet && (thisFingerprint == htmlPreFingerprint
+                               || thisFingerprint == htmlDivFingerprint))) {
+       // Don't push the outer-most wrapping div, pre, or fo:block
+       return true;
+      }
+    }
+
+    return false;
+  }
+
+  private void closeOpenElements(Emitter rtfEmitter)
+    throws TransformerException {
+    // Close all the open elements...
+    tempStack = new Stack();
+    while (!elementStack.empty()) {
+      StartElementInfo elem = (StartElementInfo) elementStack.pop();
+      rtfEmitter.endElement(elem.getNameCode());
+      tempStack.push(elem);
+    }
+  }
+
+  private void openClosedElements(Emitter rtfEmitter)
+    throws TransformerException {
+    // Now "reopen" the elements that we closed...
+    while (!tempStack.empty()) {
+      StartElementInfo elem = (StartElementInfo) tempStack.pop();
+      AttributeCollection attr = (AttributeCollection)elem.getAttributes();
+      AttributeCollection newAttr = new AttributeCollection(namePool);
+
+      for (int acount = 0; acount < attr.getLength(); acount++) {
+       String localName = attr.getLocalName(acount);
+       int nameCode = attr.getNameCode(acount);
+       String type = attr.getType(acount);
+       String value = attr.getValue(acount);
+       String uri = attr.getURI(acount);
+       String prefix = "";
+
+       if (localName.indexOf(':') > 0) {
+         prefix = localName.substring(0, localName.indexOf(':'));
+         localName = localName.substring(localName.indexOf(':')+1);
+       }
+
+       if (uri.equals("")
+           && ((foStylesheet
+                && localName.equals("id"))
+               || (!foStylesheet
+                   && (localName.equals("id")
+                       || localName.equals("name"))))) {
+         // skip this attribute
+       } else {
+         newAttr.addAttribute(prefix, uri, localName, type, value);
+       }
+      }
+
+      rtfEmitter.startElement(elem.getNameCode(),
+                             newAttr,
+                             elem.getNamespaces(),
+                             elem.getNSCount());
+
+      elementStack.push(elem);
+    }
+  }
+
+  /**
+   * <p>A private class for maintaining the information required to call
+   * the startElement method.</p>
+   *
+   * <p>In order to close and reopen elements, information about those
+   * elements has to be maintained. This class is just the little record
+   * that we push on the stack to keep track of that info.</p>
+   */
+  private class StartElementInfo {
+    private int _nameCode;
+    org.xml.sax.Attributes _attributes;
+    int[] _namespaces;
+    int _nscount;
+
+    public StartElementInfo(int nameCode,
+                           org.xml.sax.Attributes attributes,
+                           int[] namespaces,
+                           int nscount) {
+      _nameCode = nameCode;
+      _attributes = attributes;
+      _namespaces = namespaces;
+      _nscount = nscount;
+    }
+
+    public int getNameCode() {
+      return _nameCode;
+    }
+
+    public org.xml.sax.Attributes getAttributes() {
+      return _attributes;
+    }
+
+    public int[] getNamespaces() {
+      return _namespaces;
+    }
+
+    public int getNSCount() {
+      return _nscount;
+    }
+  }
+}
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/ColumnScanEmitter.java b/xsl/extensions/saxon63/com/nwalsh/saxon/ColumnScanEmitter.java
new file mode 100644 (file)
index 0000000..0b7c3c9
--- /dev/null
@@ -0,0 +1,167 @@
+package com.nwalsh.saxon;
+
+import org.xml.sax.*;
+import javax.xml.transform.TransformerException;
+import com.icl.saxon.output.*;
+import com.icl.saxon.om.*;
+import com.icl.saxon.tinytree.TinyBuilder;
+import com.icl.saxon.expr.FragmentValue;
+import com.icl.saxon.Builder;
+
+/**
+ * <p>Saxon extension to scan the column widthsin a result tree fragment.</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000 Norman Walsh.</p>
+ *
+ * <p>This class provides a
+ * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon 6.*</a>
+ * implementation to scan the column widths in a result tree
+ * fragment.</p>
+ *
+ * <p>The general design is this: the stylesheets construct a result tree
+ * fragment for some colgroup environment. That result tree fragment
+ * is "replayed" through the ColumnScanEmitter; the ColumnScanEmitter watches
+ * the cols go by and extracts the column widths that it sees. These
+ * widths are then made available.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @version $Id$
+ *
+ */
+public class ColumnScanEmitter extends com.icl.saxon.output.Emitter {
+  /** The number of columns seen. */
+  protected int numColumns = 0;
+  protected String width[] = new String[5];
+  protected NamePool namePool = null;
+
+  /** The FO namespace name. */
+  protected static String foURI = "http://www.w3.org/1999/XSL/Format";
+
+  /** Construct a new ColumnScanEmitter. */
+  public ColumnScanEmitter(NamePool namePool) {
+    numColumns = 0;
+    this.namePool = namePool;
+  }
+
+  /** Return the number of columns. */
+  public int columnCount() {
+    return numColumns;
+  }
+
+  /** Return the number of columns. */
+  public String[] columnWidths() {
+    return width;
+  }
+
+  /** Discarded. */
+  public void characters(char[] chars, int start, int len)
+    throws TransformerException {
+    // nop
+  }
+
+  /** Discarded. */
+  public void comment(char[] chars, int start, int length)
+    throws TransformerException {
+    // nop
+  }
+
+  /** Discarded. */
+  public void endDocument()
+    throws TransformerException {
+    // nop
+  }
+
+  /** Discarded. */
+  public void endElement(int nameCode)
+    throws TransformerException {
+    // nop
+  }
+
+  /** Discarded. */
+  public void processingInstruction(java.lang.String name,
+                                   java.lang.String data)
+    throws TransformerException {
+    // nop
+  }
+
+  /** Discarded. */
+  public void setDocumentLocator(org.xml.sax.Locator locator) {
+    // nop
+  }
+
+  /** Discarded. */
+  public void setEscaping(boolean escaping)
+    throws TransformerException {
+    // nop
+  }
+
+  /** Discarded. */
+  public void setNamePool(NamePool namePool) {
+    // nop
+  }
+
+  /** Discarded. */
+  public void setUnparsedEntity(java.lang.String name, java.lang.String uri)
+    throws TransformerException {
+    // nop
+  }
+
+  /** Discarded. */
+  public void setWriter(java.io.Writer writer) {
+    // nop
+  }
+
+  /** Discarded. */
+  public void startDocument()
+    throws TransformerException {
+    // nop
+  }
+
+  /** Examine for column info. */
+  public void startElement(int nameCode,
+                   org.xml.sax.Attributes attributes,
+                   int[] namespaces, int nscount)
+    throws TransformerException {
+
+    int thisFingerprint = namePool.getFingerprint(nameCode);
+    int colFingerprint = namePool.getFingerprint("", "col");
+    int foColFingerprint = namePool.getFingerprint(foURI, "table-column");
+
+    if (thisFingerprint == colFingerprint
+       || thisFingerprint == foColFingerprint) {
+      if (numColumns >= width.length) {
+       String newWidth[] = new String[width.length+10];
+       for (int count = 0; count < width.length; count++) {
+         newWidth[count] = width[count];
+       }
+       width = newWidth;
+      }
+
+      if (thisFingerprint == colFingerprint) {
+       if (attributes.getValue("width") == null) {
+         width[numColumns++] = "1*";
+       } else {
+         width[numColumns++] = attributes.getValue("width");
+       }
+      } else {
+       if (attributes.getValue("column-width") == null) {
+         width[numColumns++] = "1*";
+       } else {
+         width[numColumns++] = attributes.getValue("column-width");
+       }
+      }
+    }
+  }
+}
+
+
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/ColumnUpdateEmitter.java b/xsl/extensions/saxon63/com/nwalsh/saxon/ColumnUpdateEmitter.java
new file mode 100644 (file)
index 0000000..99b43c8
--- /dev/null
@@ -0,0 +1,96 @@
+package com.nwalsh.saxon;
+
+import org.xml.sax.*;
+import com.icl.saxon.output.*;
+import com.icl.saxon.om.*;
+import javax.xml.transform.TransformerException;
+import com.icl.saxon.tinytree.TinyBuilder;
+import com.icl.saxon.expr.FragmentValue;
+import com.icl.saxon.Builder;
+import com.icl.saxon.tree.AttributeCollection;
+
+/**
+ * <p>Saxon extension to scan the column widthsin a result tree fragment.</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000 Norman Walsh.</p>
+ *
+ * <p>This class provides a
+ * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon 6.*</a>
+ * implementation to scan the column widths in a result tree
+ * fragment.</p>
+ *
+ * <p>The general design is this: the stylesheets construct a result tree
+ * fragment for some colgroup environment. That result tree fragment
+ * is "replayed" through the ColumnUpdateEmitter; the ColumnUpdateEmitter watches
+ * the cols go by and extracts the column widths that it sees. These
+ * widths are then made available.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @version $Id$
+ *
+ */
+public class ColumnUpdateEmitter extends CopyEmitter {
+  /** The number of columns seen. */
+  protected int numColumns = 0;
+  protected String width[] = null;
+  protected NamePool namePool = null;
+
+  /** The FO namespace name. */
+  protected static String foURI = "http://www.w3.org/1999/XSL/Format";
+
+  /** Construct a new ColumnUpdateEmitter. */
+  public ColumnUpdateEmitter(NamePool namePool,
+                            String width[]) {
+    super(namePool);
+    numColumns = 0;
+    this.width = width;
+    this.namePool = namePool;
+  }
+
+  /** Examine for column info. */
+  public void startElement(int nameCode,
+                   org.xml.sax.Attributes attributes,
+                   int[] namespaces, int nscount)
+    throws TransformerException {
+
+    int thisFingerprint = namePool.getFingerprint(nameCode);
+    int colFingerprint = namePool.getFingerprint("", "col");
+    int foColFingerprint = namePool.getFingerprint(foURI, "table-column");
+
+    if (thisFingerprint == colFingerprint) {
+      AttributeCollection attr = new AttributeCollection(namePool, attributes);
+      int widthFingerprint = namePool.getFingerprint("", "width");
+
+      if (attr.getValueByFingerprint(widthFingerprint) == null) {
+       attr.addAttribute(widthFingerprint, "CDATA", width[numColumns++]);
+      } else {
+       attr.setAttribute(widthFingerprint, "CDATA", width[numColumns++]);
+      }
+      attributes = attr;
+    } else if (thisFingerprint == foColFingerprint) {
+      AttributeCollection attr = new AttributeCollection(namePool, attributes);
+      int widthFingerprint = namePool.getFingerprint("", "column-width");
+
+      if (attr.getValueByFingerprint(widthFingerprint) == null) {
+       attr.addAttribute(widthFingerprint, "CDATA", width[numColumns++]);
+      } else {
+       attr.setAttribute(widthFingerprint, "CDATA", width[numColumns++]);
+      }
+      attributes = attr;
+    }
+
+    rtfEmitter.startElement(nameCode, attributes, namespaces, nscount);
+  }
+}
+
+
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/CopyEmitter.java b/xsl/extensions/saxon63/com/nwalsh/saxon/CopyEmitter.java
new file mode 100644 (file)
index 0000000..ee15c9b
--- /dev/null
@@ -0,0 +1,150 @@
+package com.nwalsh.saxon;
+
+import java.util.Stack;
+import java.util.StringTokenizer;
+import org.xml.sax.*;
+import org.w3c.dom.*;
+import javax.xml.transform.TransformerException;
+import com.icl.saxon.Builder;
+import com.icl.saxon.Context;
+import com.icl.saxon.expr.*;
+import com.icl.saxon.functions.Extensions;
+import com.icl.saxon.om.*;
+import com.icl.saxon.output.*;
+import com.icl.saxon.pattern.*;
+import com.icl.saxon.tinytree.TinyBuilder;
+import com.icl.saxon.tree.*;
+
+/**
+ * <p>A Saxon 6.0 Emitter that clones its input.</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000 Norman Walsh.</p>
+ *
+ * <p>This class provides a
+ * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon 6.*</a>
+ * implementation of an emitter that manufactures a cloned result
+ * tree fragment.</p>
+ *
+ * <p>The purpose of this emitter is to provide something for
+ * CalloutEmitter and NumberLinesEmitter to extend.
+ * This emitter simply copies all input to a new result tree fragment.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @see CalloutEmitter
+ * @see NumberLinesEmitter
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @version $Id$
+ *
+ */
+public class CopyEmitter extends com.icl.saxon.output.Emitter {
+  /** The result tree fragment containing the copied fragment. */
+  protected FragmentValue rtf = null;
+  protected Emitter rtfEmitter = null;
+
+  /** <p>The namePool.</p>
+   *
+   * <p>Copied from the caller, it should be the runtime name pool.</p>
+   */
+  protected NamePool namePool = null;
+
+  /** <p>Constructor for the CopyEmitter.</p>
+   *
+   * @param namePool The name pool to use for constructing elements and attributes.
+   */
+  public CopyEmitter(NamePool namePool) {
+    rtf = new FragmentValue();
+    rtfEmitter = rtf.getEmitter();
+    this.namePool = namePool;
+  }
+
+  /**
+   * <p>Return the result tree fragment constructed by replaying events
+   * through this emitter.</p>
+   */
+  public FragmentValue getResultTreeFragment() {
+    return rtf;
+  }
+
+  /** Copy characters. */
+  public void characters(char[] chars, int start, int len)
+    throws TransformerException {
+    rtfEmitter.characters(chars, start, len);
+  }
+
+  /** Copy comments. */
+  public void comment(char[] chars, int start, int length)
+    throws TransformerException {
+    rtfEmitter.comment(chars, start, length);
+  }
+
+  /** Copy end document events. */
+  public void endDocument()
+    throws TransformerException {
+    rtfEmitter.endDocument();
+  }
+
+  /** Copy end element events. */
+  public void endElement(int nameCode)
+    throws TransformerException {
+    rtfEmitter.endElement(nameCode);
+  }
+
+  /** Copy processing instructions. */
+  public void processingInstruction(java.lang.String name,
+                                   java.lang.String data)
+    throws TransformerException {
+    rtfEmitter.processingInstruction(name, data);
+  }
+
+  /** Copy set document locator events. */
+  public void setDocumentLocator(org.xml.sax.Locator locator) {
+    rtfEmitter.setDocumentLocator(locator);
+  }
+
+  /** Copy set escaping events. */
+  public void setEscaping(boolean escaping)
+    throws TransformerException {
+    rtfEmitter.setEscaping(escaping);
+  }
+
+  /** Copy set name pool events. */
+  public void setNamePool(NamePool namePool) {
+    rtfEmitter.setNamePool(namePool);
+  }
+
+  /** Copy set unparsed entity events. */
+  public void setUnparsedEntity(java.lang.String name, java.lang.String uri)
+    throws TransformerException {
+    rtfEmitter.setUnparsedEntity(name, uri);
+  }
+
+  /** Copy set writer events. */
+  public void setWriter(java.io.Writer writer) {
+    rtfEmitter.setWriter(writer);
+  }
+
+  /** Copy start document events. */
+  public void startDocument()
+    throws TransformerException {
+    rtfEmitter.startDocument();
+  }
+
+  /** Copy start element events. */
+  public void startElement(int nameCode,
+                          org.xml.sax.Attributes attributes,
+                          int[] namespaces,
+                          int nscount)
+    throws TransformerException {
+    rtfEmitter.startElement(nameCode, attributes, namespaces, nscount);
+  }
+}
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/FormatCallout.java b/xsl/extensions/saxon63/com/nwalsh/saxon/FormatCallout.java
new file mode 100644 (file)
index 0000000..c8be006
--- /dev/null
@@ -0,0 +1,112 @@
+package com.nwalsh.saxon;
+
+import org.xml.sax.SAXException;
+import org.w3c.dom.*;
+
+import javax.xml.transform.TransformerException;
+
+import com.icl.saxon.om.ElementInfo;
+import com.icl.saxon.om.NamePool;
+import com.icl.saxon.output.Emitter;
+import com.icl.saxon.tree.AttributeCollection;
+
+import com.nwalsh.saxon.Callout;
+
+/**
+ * <p>Utility class for the Verbatim extension (ignore this).</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000, 2001 Norman Walsh.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @see Verbatim
+ *
+ * @version $Id$
+ **/
+
+public abstract class FormatCallout {
+  protected static final String foURI = "http://www.w3.org/1999/XSL/Format";
+  protected static final String xhURI = "http://www.w3.org/1999/xhtml";
+  protected boolean foStylesheet = false;
+  protected NamePool namePool = null;
+
+  public FormatCallout(NamePool nPool, boolean fo) {
+    namePool = nPool;
+    foStylesheet = fo;
+  }
+
+  public String areaLabel(ElementInfo area) {
+    String label = null;
+
+    if (area.getAttributeList().getValue("label") != null) {
+      // If this area has a label, use it
+      label = area.getAttributeList().getValue("label");
+    } else {
+      // Otherwise, if its parent is an areaset and it has a label, use that
+      ElementInfo parent = (ElementInfo) area.getParentNode();
+      if (parent != null
+         && parent.getLocalName().equalsIgnoreCase("areaset")
+         && parent.getAttributeList().getValue("label") != null) {
+       label = parent.getAttributeList().getValue("label");
+      }
+    }
+
+    return label;
+  }
+
+  public void startSpan(Emitter rtf)
+    throws TransformerException {
+    // no point in doing this for FO, right?
+    if (!foStylesheet && namePool != null) {
+      int spanName = namePool.allocate("", "", "span");
+      AttributeCollection spanAttr = new AttributeCollection(namePool);
+      int namespaces[] = new int[1];
+      spanAttr.addAttribute("", "", "class", "CDATA", "co");
+      rtf.startElement(spanName, spanAttr, namespaces, 0);
+    }
+  }
+
+  public void endSpan(Emitter rtf)
+    throws TransformerException {
+    // no point in doing this for FO, right?
+    if (!foStylesheet && namePool != null) {
+      int spanName = namePool.allocate("", "", "span");
+      rtf.endElement(spanName);
+    }
+  }
+
+  public void formatTextCallout(Emitter rtfEmitter,
+                               Callout callout) {
+    ElementInfo area = callout.getArea();
+    int num = callout.getCallout();
+    String userLabel = areaLabel(area);
+    String label = "(" + num + ")";
+
+    if (userLabel != null) {
+      label = userLabel;
+    }
+
+    char chars[] = label.toCharArray();
+
+    try {
+      startSpan(rtfEmitter);
+      rtfEmitter.characters(chars, 0, label.length());
+      endSpan(rtfEmitter);
+    } catch (TransformerException e) {
+      System.out.println("Transformer Exception in formatTextCallout");
+    }
+  }
+
+  public abstract void formatCallout(Emitter rtfEmitter,
+                                    Callout callout);
+}
+
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/FormatGraphicCallout.java b/xsl/extensions/saxon63/com/nwalsh/saxon/FormatGraphicCallout.java
new file mode 100644 (file)
index 0000000..a615902
--- /dev/null
@@ -0,0 +1,89 @@
+package com.nwalsh.saxon;
+
+import org.xml.sax.SAXException;
+import org.w3c.dom.*;
+
+import javax.xml.transform.TransformerException;
+
+import com.icl.saxon.om.ElementInfo;
+import com.icl.saxon.om.NamePool;
+import com.icl.saxon.output.Emitter;
+import com.icl.saxon.tree.AttributeCollection;
+
+import com.nwalsh.saxon.Callout;
+
+/**
+ * <p>Utility class for the Verbatim extension (ignore this).</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000, 2001 Norman Walsh.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @see Verbatim
+ *
+ * @version $Id$
+ **/
+
+public class FormatGraphicCallout extends FormatCallout {
+  String graphicsPath = "";
+  String graphicsExt = "";
+  int graphicsMax = 0;
+
+  public FormatGraphicCallout(NamePool nPool, String path, String ext, int max, boolean fo) {
+    super(nPool, fo);
+    graphicsPath = path;
+    graphicsExt = ext;
+    graphicsMax = max;
+  }
+
+  public void formatCallout(Emitter rtfEmitter,
+                           Callout callout) {
+    ElementInfo area = callout.getArea();
+    int num = callout.getCallout();
+    String userLabel = areaLabel(area);
+    String label = "(" + num + ")";
+
+    if (userLabel != null) {
+      label = userLabel;
+    }
+
+    try {
+      if (userLabel == null && num <= graphicsMax) {
+       int imgName = 0;
+       AttributeCollection imgAttr = null;
+       int namespaces[] = new int[1];
+
+       if (foStylesheet) {
+         imgName = namePool.allocate("fo", foURI, "external-graphic");
+         imgAttr = new AttributeCollection(namePool);
+         imgAttr.addAttribute("", "", "src", "CDATA",
+                              graphicsPath + num + graphicsExt);
+       } else {
+         imgName = namePool.allocate("", "", "img");
+         imgAttr = new AttributeCollection(namePool);
+         imgAttr.addAttribute("", "", "src", "CDATA",
+                              graphicsPath + num + graphicsExt);
+         imgAttr.addAttribute("", "", "alt", "CDATA", label);
+       }
+
+       startSpan(rtfEmitter);
+       rtfEmitter.startElement(imgName, imgAttr, namespaces, 0);
+       rtfEmitter.endElement(imgName);
+       endSpan(rtfEmitter);
+      } else {
+       formatTextCallout(rtfEmitter, callout);
+      }
+    } catch (TransformerException e) {
+      System.out.println("Transformer Exception in graphic formatCallout");
+    }
+  }
+}
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/FormatTextCallout.java b/xsl/extensions/saxon63/com/nwalsh/saxon/FormatTextCallout.java
new file mode 100644 (file)
index 0000000..0c0c0f7
--- /dev/null
@@ -0,0 +1,44 @@
+package com.nwalsh.saxon;
+
+import org.xml.sax.SAXException;
+import org.w3c.dom.*;
+
+import javax.xml.transform.TransformerException;
+
+import com.icl.saxon.om.ElementInfo;
+import com.icl.saxon.om.NamePool;
+import com.icl.saxon.output.Emitter;
+
+import com.nwalsh.saxon.Callout;
+
+/**
+ * <p>Utility class for the Verbatim extension (ignore this).</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000, 2001 Norman Walsh.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @see Verbatim
+ *
+ * @version $Id$
+ **/
+
+public class FormatTextCallout extends FormatCallout {
+  public FormatTextCallout(NamePool nPool, boolean fo) {
+    super(nPool, fo);
+  }
+
+  public void formatCallout(Emitter rtfEmitter,
+                           Callout callout) {
+    formatTextCallout(rtfEmitter, callout);
+  }
+}
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/FormatUnicodeCallout.java b/xsl/extensions/saxon63/com/nwalsh/saxon/FormatUnicodeCallout.java
new file mode 100644 (file)
index 0000000..e4fdc0e
--- /dev/null
@@ -0,0 +1,68 @@
+package com.nwalsh.saxon;
+
+import org.xml.sax.SAXException;
+import org.w3c.dom.*;
+
+import javax.xml.transform.TransformerException;
+
+import com.icl.saxon.om.ElementInfo;
+import com.icl.saxon.om.NamePool;
+import com.icl.saxon.output.Emitter;
+import com.icl.saxon.tree.AttributeCollection;
+
+import com.nwalsh.saxon.Callout;
+
+/**
+ * <p>Utility class for the Verbatim extension (ignore this).</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000, 2001 Norman Walsh.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @see Verbatim
+ *
+ * @version $Id$
+ **/
+
+public class FormatUnicodeCallout extends FormatCallout {
+  int unicodeMax = 0;
+  int unicodeStart = 0;
+
+  public FormatUnicodeCallout(NamePool nPool, int start, int max, boolean fo) {
+    super(nPool, fo);
+    unicodeMax = max;
+    unicodeStart = start;
+  }
+
+  public void formatCallout(Emitter rtfEmitter,
+                           Callout callout) {
+
+    ElementInfo area = callout.getArea();
+    int num = callout.getCallout();
+    String label = areaLabel(area);
+
+    try {
+      if (label == null && num <= unicodeMax) {
+       char chars[] = new char[1];
+       chars[0] = (char) (unicodeStart + num - 1);
+
+       startSpan(rtfEmitter);
+       rtfEmitter.characters(chars, 0, 1);
+       endSpan(rtfEmitter);
+      } else {
+       formatTextCallout(rtfEmitter, callout);
+      }
+    } catch (TransformerException e) {
+      System.out.println("Transformer Exception in graphic formatCallout");
+    }
+  }
+}
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/LineCountEmitter.java b/xsl/extensions/saxon63/com/nwalsh/saxon/LineCountEmitter.java
new file mode 100644 (file)
index 0000000..99d7b2d
--- /dev/null
@@ -0,0 +1,143 @@
+package com.nwalsh.saxon;
+
+import org.xml.sax.*;
+import javax.xml.transform.TransformerException;
+import com.icl.saxon.output.*;
+import com.icl.saxon.om.*;
+import com.icl.saxon.tinytree.TinyBuilder;
+import com.icl.saxon.expr.FragmentValue;
+import com.icl.saxon.Builder;
+
+/**
+ * <p>Saxon extension to count the lines in a result tree fragment.</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000 Norman Walsh.</p>
+ *
+ * <p>This class provides a
+ * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon 6.*</a>
+ * implementation to count the number of lines in a result tree
+ * fragment.</p>
+ *
+ * <p>The general design is this: the stylesheets construct a result tree
+ * fragment for some verbatim environment. That result tree fragment
+ * is "replayed" through the LineCountEmitter; the LineCountEmitter watches
+ * characters go by and counts the number of line feeds that it sees.
+ * That number is then returned.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @see Verbatim
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @version $Id$
+ *
+ */
+public class LineCountEmitter extends com.icl.saxon.output.Emitter {
+  /** The number of lines seen. */
+  protected int numLines = 0;
+
+  /** Construct a new LineCountEmitter. */
+  public LineCountEmitter() {
+    numLines = 0;
+  }
+
+  /** Reset the number of lines. */
+  public void reset() {
+    numLines = 0;
+  }
+
+  /** Return the number of lines. */
+  public int lineCount() {
+    return numLines;
+  }
+
+  /** Process characters. */
+  public void characters(char[] chars, int start, int len)
+    throws javax.xml.transform.TransformerException {
+
+    if (numLines == 0) {
+      // If there are any characters at all, there's at least one line
+      numLines++;
+    }
+
+    for (int count = start; count < start+len; count++) {
+      if (chars[count] == '\n') {
+       numLines++;
+      }
+    }
+  }
+
+  /** Discarded. */
+  public void comment(char[] chars, int start, int length)
+    throws javax.xml.transform.TransformerException {
+    // nop
+  }
+
+  /** Discarded. */
+  public void endDocument()
+    throws javax.xml.transform.TransformerException {
+    // nop
+  }
+
+  /** Discarded. */
+  public void endElement(int nameCode)
+    throws javax.xml.transform.TransformerException {
+    // nop
+  }
+
+  /** Discarded. */
+  public void processingInstruction(java.lang.String name,
+                                   java.lang.String data)
+    throws javax.xml.transform.TransformerException {
+    // nop
+  }
+
+  /** Discarded. */
+  public void setDocumentLocator(org.xml.sax.Locator locator) {
+    // nop
+  }
+
+  /** Discarded. */
+  public void setEscaping(boolean escaping)
+    throws javax.xml.transform.TransformerException {
+    // nop
+  }
+
+  /** Discarded. */
+  public void setNamePool(NamePool namePool) {
+    // nop
+  }
+
+  /** Discarded. */
+  public void setUnparsedEntity(java.lang.String name, java.lang.String uri)
+    throws javax.xml.transform.TransformerException {
+    // nop
+  }
+
+  /** Discarded. */
+  public void setWriter(java.io.Writer writer) {
+    // nop
+  }
+
+  /** Discarded. */
+  public void startDocument()
+    throws javax.xml.transform.TransformerException {
+    // nop
+  }
+
+  /** Discarded. */
+  public void startElement(int nameCode,
+                   org.xml.sax.Attributes attributes,
+                   int[] namespaces, int nscount)
+    throws javax.xml.transform.TransformerException {
+    // nop
+  }
+}
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/NumberLinesEmitter.java b/xsl/extensions/saxon63/com/nwalsh/saxon/NumberLinesEmitter.java
new file mode 100644 (file)
index 0000000..f8065e3
--- /dev/null
@@ -0,0 +1,323 @@
+package com.nwalsh.saxon;
+
+import java.util.Stack;
+import java.util.StringTokenizer;
+import org.xml.sax.*;
+import org.w3c.dom.*;
+import javax.xml.transform.TransformerException;
+import com.icl.saxon.output.*;
+import com.icl.saxon.om.*;
+import com.icl.saxon.tree.AttributeCollection;
+import com.icl.saxon.tinytree.TinyBuilder;
+import com.icl.saxon.expr.FragmentValue;
+import com.icl.saxon.Builder;
+
+/**
+ * <p>Saxon extension to decorate a result tree fragment with line numbers.</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000 Norman Walsh.</p>
+ *
+ * <p>This class provides the guts of a
+ * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon 6.*</a>
+ * implementation of line numbering for verbatim environments. (It is used
+ * by the Verbatim class.)</p>
+ *
+ * <p>The general design is this: the stylesheets construct a result tree
+ * fragment for some verbatim environment. The Verbatim class initializes
+ * a NumberLinesEmitter with information about what lines should be
+ * numbered and how. Then the result tree fragment
+ * is "replayed" through the NumberLinesEmitter; the NumberLinesEmitter
+ * builds a
+ * new result tree fragment from this event stream, decorated with line
+ * numbers,
+ * and that is returned.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @see Verbatim
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @version $Id$
+ *
+ */
+public class NumberLinesEmitter extends CopyEmitter {
+  /** A stack for the preserving information about open elements. */
+  protected Stack elementStack = null;
+
+  /** The current line number. */
+  protected int lineNumber = 0;
+
+  /** Is the next element absolutely the first element in the fragment? */
+  protected boolean firstElement = false;
+
+  /** The FO namespace name. */
+  protected static String foURI = "http://www.w3.org/1999/XSL/Format";
+
+  /** Every <code>modulus</code> line will be numbered. */
+  protected int modulus = 5;
+
+  /** Line numbers are <code>width</code> characters wide. */
+  protected int width = 3;
+
+  /** Line numbers are separated from the listing by <code>separator</code>. */
+  protected String separator = " ";
+
+  /** Is the stylesheet currently running an FO stylesheet? */
+  protected boolean foStylesheet = false;
+
+  /** <p>Constructor for the NumberLinesEmitter.</p>
+   *
+   * @param namePool The name pool to use for constructing elements and attributes.
+   * @param modulus The modulus to use for this listing.
+   * @param width The width to use for line numbers in this listing.
+   * @param separator The separator to use for this listing.
+   * @param foStylesheet Is this an FO stylesheet?
+   */
+  public NumberLinesEmitter(NamePool namePool,
+                           int modulus,
+                           int width,
+                           String separator,
+                           boolean foStylesheet) {
+    super(namePool);
+    elementStack = new Stack();
+    firstElement = true;
+
+    this.modulus = modulus;
+    this.width = width;
+    this.separator = separator;
+    this.foStylesheet = foStylesheet;
+  }
+
+  /** Process characters. */
+  public void characters(char[] chars, int start, int len)
+    throws TransformerException {
+
+    // If we hit characters, then there's no first element...
+    firstElement = false;
+
+    if (lineNumber == 0) {
+      // The first line is always numbered
+      formatLineNumber(++lineNumber);
+    }
+
+    // Walk through the text node looking for newlines
+    char[] newChars = new char[len];
+    int pos = 0;
+    for (int count = start; count < start+len; count++) {
+      if (chars[count] == '\n') {
+       // This is the tricky bit; if we find a newline, make sure
+       // it doesn't occur inside any markup.
+
+       if (pos > 0) {
+         // Output any characters that preceded this newline
+         rtfEmitter.characters(newChars, 0, pos);
+         pos = 0;
+       }
+
+       // Close all the open elements...
+       Stack tempStack = new Stack();
+       while (!elementStack.empty()) {
+         StartElementInfo elem = (StartElementInfo) elementStack.pop();
+         rtfEmitter.endElement(elem.getNameCode());
+         tempStack.push(elem);
+       }
+
+       // Copy the newline to the output
+       newChars[pos++] = chars[count];
+       rtfEmitter.characters(newChars, 0, pos);
+       pos = 0;
+
+       // Add the line number
+       formatLineNumber(++lineNumber);
+
+       // Now "reopen" the elements that we closed...
+       while (!tempStack.empty()) {
+         StartElementInfo elem = (StartElementInfo) tempStack.pop();
+         AttributeCollection attr = (AttributeCollection)elem.getAttributes();
+         AttributeCollection newAttr = new AttributeCollection(namePool);
+
+         for (int acount = 0; acount < attr.getLength(); acount++) {
+           String localName = attr.getLocalName(acount);
+           int nameCode = attr.getNameCode(acount);
+           String type = attr.getType(acount);
+           String value = attr.getValue(acount);
+           String uri = attr.getURI(acount);
+           String prefix = "";
+
+           if (localName.indexOf(':') > 0) {
+             prefix = localName.substring(0, localName.indexOf(':'));
+             localName = localName.substring(localName.indexOf(':')+1);
+           }
+
+           if (uri.equals("")
+               && ((foStylesheet
+                    && localName.equals("id"))
+                   || (!foStylesheet
+                       && (localName.equals("id")
+                           || localName.equals("name"))))) {
+             // skip this attribute
+           } else {
+             newAttr.addAttribute(prefix, uri, localName, type, value);
+           }
+         }
+
+         rtfEmitter.startElement(elem.getNameCode(),
+                          newAttr,
+                          elem.getNamespaces(),
+                          elem.getNSCount());
+
+         elementStack.push(elem);
+       }
+      } else {
+       newChars[pos++] = chars[count];
+      }
+    }
+
+    if (pos > 0) {
+      rtfEmitter.characters(newChars, 0, pos);
+      pos = 0;
+    }
+  }
+
+  /**
+   * <p>Add a formatted line number to the result tree fragment.</p>
+   *
+   * @param lineNumber The number of the current line.
+   */
+  protected void formatLineNumber(int lineNumber) 
+    throws TransformerException {
+
+    char ch = 160; // &nbsp;
+
+    String lno = "";
+    if (lineNumber == 1
+       || (modulus >= 1 && (lineNumber % modulus == 0))) {
+      lno = "" + lineNumber;
+    }
+
+    while (lno.length() < width) {
+      lno = ch + lno;
+    }
+
+    lno += separator;
+
+    char chars[] = new char[lno.length()];
+    for (int count = 0; count < lno.length(); count++) {
+      chars[count] = lno.charAt(count);
+    }
+
+    characters(chars, 0, lno.length());
+  }
+
+  /** Process end element events. */
+  public void endElement(int nameCode)
+    throws TransformerException {
+    if (!elementStack.empty()) {
+      // if we didn't push the very first element (an fo:block or
+      // pre or div surrounding the whole block), then the stack will
+      // be empty when we get to the end of the first element...
+      elementStack.pop();
+    }
+    rtfEmitter.endElement(nameCode);
+  }
+
+  /** Process start element events. */
+  public void startElement(int nameCode,
+                          org.xml.sax.Attributes attributes,
+                          int[] namespaces,
+                          int nscount)
+    throws TransformerException {
+
+    if (!skipThisElement(nameCode)) {
+      StartElementInfo sei = new StartElementInfo(nameCode, attributes,
+                                                 namespaces, nscount);
+      elementStack.push(sei);
+    }
+
+    firstElement = false;
+
+    rtfEmitter.startElement(nameCode, attributes, namespaces, nscount);
+  }
+
+  /**
+   * <p>Protect the outer-most block wrapper.</p>
+   *
+   * <p>Open elements in the result tree fragment are closed and reopened
+   * around callouts (so that callouts don't appear inside links or other
+   * environments). But if the result tree fragment is a single block
+   * (a div or pre in HTML, an fo:block in FO), that outer-most block is
+   * treated specially.</p>
+   *
+   * <p>This method returns true if the element in question is that
+   * outermost block.</p>
+   *
+   * @param nameCode The name code for the element
+   *
+   * @return True if the element is the outer-most block, false otherwise.
+   */
+  protected boolean skipThisElement(int nameCode) {
+    if (firstElement) {
+      int foBlockFingerprint = namePool.getFingerprint(foURI, "block");
+      int htmlPreFingerprint = namePool.getFingerprint("", "pre");
+      int htmlDivFingerprint = namePool.getFingerprint("", "div");
+
+      if ((foStylesheet && nameCode == foBlockFingerprint)
+         || (!foStylesheet && (nameCode == htmlPreFingerprint
+                               || nameCode == htmlDivFingerprint))) {
+       // Don't push the outer-most wrapping div, pre, or fo:block
+       return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * <p>A private class for maintaining the information required to call
+   * the startElement method.</p>
+   *
+   * <p>In order to close and reopen elements, information about those
+   * elements has to be maintained. This class is just the little record
+   * that we push on the stack to keep track of that info.</p>
+   */
+  private class StartElementInfo {
+    private int _nameCode;
+    org.xml.sax.Attributes _attributes;
+    int[] _namespaces;
+    int _nscount;
+
+    public StartElementInfo(int nameCode,
+                           org.xml.sax.Attributes attributes,
+                           int[] namespaces,
+                           int nscount) {
+      _nameCode = nameCode;
+      _attributes = attributes;
+      _namespaces = namespaces;
+      _nscount = nscount;
+    }
+
+    public int getNameCode() {
+      return _nameCode;
+    }
+
+    public org.xml.sax.Attributes getAttributes() {
+      return _attributes;
+    }
+
+    public int[] getNamespaces() {
+      return _namespaces;
+    }
+
+    public int getNSCount() {
+      return _nscount;
+    }
+  }
+}
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/Table.java b/xsl/extensions/saxon63/com/nwalsh/saxon/Table.java
new file mode 100644 (file)
index 0000000..8afe55d
--- /dev/null
@@ -0,0 +1,428 @@
+// Verbatim.java - Saxon extensions supporting DocBook verbatim environments
+
+package com.nwalsh.saxon;
+
+import java.util.Hashtable;
+import org.xml.sax.*;
+import org.w3c.dom.*;
+import javax.xml.transform.TransformerException;
+import com.icl.saxon.expr.*;
+import com.icl.saxon.om.*;
+import com.icl.saxon.pattern.*;
+import com.icl.saxon.Context;
+import com.icl.saxon.tree.*;
+import com.icl.saxon.functions.Extensions;
+
+/**
+ * <p>Saxon extensions supporting Tables</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000 Norman Walsh.</p>
+ *
+ * <p>This class provides a
+ * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon</a>
+ * implementation of some code to adjust CALS Tables to HTML
+ * Tables.</p>
+ *
+ * <p><b>Column Widths</b></p>
+ * <p>The <tt>adjustColumnWidths</tt> method takes a result tree
+ * fragment (assumed to contain the colgroup of an HTML Table)
+ * and returns the result tree fragment with the column widths
+ * adjusted to HTML terms.</p>
+ *
+ * <p><b>Convert Lengths</b></p>
+ * <p>The <tt>convertLength</tt> method takes a length specification
+ * of the form 9999.99xx (where "xx" is a unit) and returns that length
+ * as an integral number of pixels. For convenience, percentage lengths
+ * are returned unchanged.</p>
+ * <p>The recognized units are: inches (in), centimeters (cm),
+ * millimeters (mm), picas (pc, 1pc=12pt), points (pt), and pixels (px).
+ * A number with no units is assumed to be pixels.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @version $Id$
+ *
+ */
+public class Table {
+  /** The number of pixels per inch */
+  private static int pixelsPerInch = 96;
+
+  /** The nominal table width (6in by default). */
+  private static int nominalWidth = 6 * pixelsPerInch;
+
+  /** The default table width (100% by default). */
+  private static String tableWidth = "100%";
+
+  /** Is this an FO stylesheet? */
+  private static boolean foStylesheet = false;
+
+  /** The hash used to associate units with a length in pixels. */
+  protected static Hashtable unitHash = null;
+
+  /**
+   * <p>Constructor for Verbatim</p>
+   *
+   * <p>All of the methods are static, so the constructor does nothing.</p>
+   */
+  public Table() {
+  }
+
+  /** Initialize the internal hash table with proper values. */
+  protected static void initializeHash() {
+    unitHash = new Hashtable();
+    unitHash.put("in", new Float(pixelsPerInch));
+    unitHash.put("cm", new Float(pixelsPerInch / 2.54));
+    unitHash.put("mm", new Float(pixelsPerInch / 25.4));
+    unitHash.put("pc", new Float((pixelsPerInch / 72) * 12));
+    unitHash.put("pt", new Float(pixelsPerInch / 72));
+    unitHash.put("px", new Float(1));
+  }
+
+  /** Set the pixels-per-inch value. Only positive values are legal. */
+  public static void setPixelsPerInch(int value) {
+    if (value > 0) {
+      pixelsPerInch = value;
+      initializeHash();
+    }
+  }
+
+  /** Return the current pixels-per-inch value. */
+  public int getPixelsPerInch() {
+    return pixelsPerInch;
+  }
+
+  /**
+   * <p>Convert a length specification to a number of pixels.</p>
+   *
+   * <p>The specified length should be of the form [+/-]999.99xx,
+   * where xx is a valid unit.</p>
+   */
+  public static int convertLength(String length) {
+    // The format of length should be 999.999xx
+    int sign = 1;
+    String digits = "";
+    String units = "";
+    char lench[] = length.toCharArray();
+    float flength = 0;
+    boolean done = false;
+    int pos = 0;
+    float factor = 1;
+    int pixels = 0;
+
+    if (unitHash == null) {
+      initializeHash();
+    }
+
+    if (lench[pos] == '+' || lench[pos] == '-') {
+      if (lench[pos] == '-') {
+       sign = -1;
+      }
+      pos++;
+    }
+
+    while (!done) {
+      if (pos >= lench.length) {
+       done = true;
+      } else {
+       if ((lench[pos] > '9' || lench[pos] < '0') && lench[pos] != '.') {
+         done = true;
+         units = length.substring(pos);
+       } else {
+         digits += lench[pos++];
+       }
+      }
+    }
+
+    try {
+      flength = Float.parseFloat(digits);
+    } catch (NumberFormatException e) {
+      System.out.println(digits + " is not a number; 1 used instead.");
+      flength = 1;
+    }
+
+    Float f = null;
+
+    if (!units.equals("")) {
+      f = (Float) unitHash.get(units);
+      if (f == null) {
+       System.out.println(units + " is not a known unit; 1 used instead.");
+       factor = 1;
+      } else {
+       factor = f.floatValue();
+      }
+    } else {
+      factor = 1;
+    }
+
+    f = new Float(flength * factor);
+
+    pixels = f.intValue() * sign;
+
+    return pixels;
+  }
+
+  /**
+   * <p>Find the string value of a stylesheet variable or parameter</p>
+   *
+   * <p>Returns the string value of <code>varName</code> in the current
+   * <code>context</code>. Returns the empty string if the variable is
+   * not defined.</p>
+   *
+   * @param context The current stylesheet context
+   * @param varName The name of the variable (without the dollar sign)
+   *
+   * @return The string value of the variable
+   */
+  protected static String getVariable(Context context, String varName) 
+    throws TransformerException {
+    Value variable = null;
+    String varString = null;
+
+    try {
+      variable = Extensions.evaluate(context, "$" + varName);
+      varString = variable.asString();
+      return varString;
+    } catch (IllegalArgumentException e) {
+      System.out.println("Undefined variable: " + varName);
+      return "";
+    }
+  }
+
+  /**
+   * <p>Setup the parameters associated with column width calculations</p>
+   *
+   * <p>This method queries the stylesheet for the variables
+   * associated with table column widths. It is called automatically before
+   * column widths are adjusted. The context is used to retrieve the values,
+   * this allows templates to redefine these variables.</p>
+   *
+   * <p>The following variables are queried. If the variables do not
+   * exist, builtin defaults will be used (but you may also get a bunch
+   * of messages from the Java interpreter).</p>
+   *
+   * <dl>
+   * <dt><code>nominal.table.width</code></dt>
+   * <dd>The "normal" width for tables. This must be an absolute length.</dd>
+   * <dt><code>table.width</code></dt>
+   * <dd>The width for tables. This may be either an absolute
+   * length or a percentage.</dd>
+   * </dl>
+   *
+   * @param context The current stylesheet context
+   *
+   */
+  private static void setupColumnWidths(Context context) {
+    // Hardcoded defaults
+    nominalWidth = 6 * pixelsPerInch;
+    tableWidth = "100%";
+
+    String varString = null;
+
+    try {
+      // Get the stylesheet type
+      varString = getVariable(context, "stylesheet.result.type");
+      foStylesheet = varString.equals("fo");
+
+      // Get the nominal table width
+      varString = getVariable(context, "nominal.table.width");
+      nominalWidth = convertLength(varString);
+
+      // Get the table width
+      varString = getVariable(context, "table.width");
+      tableWidth = varString;
+    } catch (TransformerException e) {
+      //nop, can't happen
+    }
+  }
+
+  /**
+   * <p>Adjust column widths in an HTML table.</p>
+   *
+   * <p>The specification of column widths in CALS (a relative width
+   * plus an optional absolute width) are incompatible with HTML column
+   * widths. This method adjusts CALS column width specifiers in an
+   * attempt to produce equivalent HTML specifiers.</p>
+   *
+   * <p>In order for this method to work, the CALS width specifications
+   * should be placed in the "width" attribute of the &lt;col>s within
+   * a &lt;colgroup>. Then the colgroup result tree fragment is passed
+   * to this method.</p>
+   *
+   * <p>This method makes use of two parameters from the XSL stylesheet
+   * that calls it: <code>nominal.table.width</code> and
+   * <code>table.width</code>. The value of <code>nominal.table.width</code>
+   * must be an absolute distance. The value of <code>table.width</code>
+   * can be either absolute or relative.</p>
+   *
+   * <p>Presented with a mixture of relative and
+   * absolute lengths, the table width is used to calculate
+   * appropriate values. If the <code>table.width</code> is relative,
+   * the nominal width is used for this calculation.</p>
+   *
+   * <p>There are three possible combinations of values:</p>
+   *
+   * <ol>
+   * <li>There are no relative widths; in this case the absolute widths
+   * are used in the HTML table.</li>
+   * <li>There are no absolute widths; in this case the relative widths
+   * are used in the HTML table.</li>
+   * <li>There are a mixture of absolute and relative widths:
+   *   <ol>
+   *     <li>If the table width is absolute, all widths become absolute.</li>
+   *     <li>If the table width is relative, make all the widths absolute
+   *         relative to the nominal table width then turn them all
+   *         back into relative widths.</li>
+   *   </ol>
+   * </li>
+   * </ol>
+   *
+   * @param context The stylesheet context; supplied automatically by Saxon
+   * @param rtf The result tree fragment containing the colgroup.
+   *
+   * @return The result tree fragment containing the adjusted colgroup.
+   *
+   */
+  public static FragmentValue adjustColumnWidths (Context context,
+                                                 FragmentValue rtf) {
+    setupColumnWidths(context);
+
+    try {
+      NamePool namePool = context.getController().getNamePool();
+
+      ColumnScanEmitter csEmitter = new ColumnScanEmitter(namePool);
+      rtf.replay(csEmitter);
+
+      int numColumns = csEmitter.columnCount();
+      String widths[] = csEmitter.columnWidths();
+
+      float relTotal = 0;
+      float relParts[] = new float[numColumns];
+
+      float absTotal = 0;
+      float absParts[] = new float[numColumns];
+
+      for (int count = 0; count < numColumns; count++) {
+       String width = widths[count];
+
+       int pos = width.indexOf("*");
+       if (pos >= 0) {
+         String relPart = width.substring(0, pos);
+         String absPart = width.substring(pos+1);
+
+         try {
+           float rel = Float.parseFloat(relPart);
+           relTotal += rel;
+           relParts[count] = rel;
+         } catch (NumberFormatException e) {
+           System.out.println(relPart + " is not a valid relative unit.");
+         }
+
+         int pixels = 0;
+         if (absPart != null && !absPart.equals("")) {
+           pixels = convertLength(absPart);
+         }
+
+         absTotal += pixels;
+         absParts[count] = pixels;
+       } else {
+         relParts[count] = 0;
+
+         int pixels = 0;
+         if (width != null && !width.equals("")) {
+           pixels = convertLength(width);
+         }
+
+         absTotal += pixels;
+         absParts[count] = pixels;
+       }
+      }
+
+      // Ok, now we have the relative widths and absolute widths in
+      // two parallel arrays.
+      //
+      // - If there are no relative widths, output the absolute widths
+      // - If there are no absolute widths, output the relative widths
+      // - If there are a mixture of relative and absolute widths,
+      //   - If the table width is absolute, turn these all into absolute
+      //     widths.
+      //   - If the table width is relative, turn these all into absolute
+      //     widths in the nominalWidth and then turn them back into
+      //     percentages.
+
+      if (relTotal == 0) {
+       for (int count = 0; count < numColumns; count++) {
+         Float f = new Float(absParts[count]);
+         if (foStylesheet) {
+           int pixels = f.intValue();
+           float inches = (float) pixels / pixelsPerInch;
+           widths[count] = inches + "in";
+         } else {
+           widths[count] = Integer.toString(f.intValue());
+         }
+       }
+      } else if (absTotal == 0) {
+       for (int count = 0; count < numColumns; count++) {
+         float rel = relParts[count] / relTotal * 100;
+         Float f = new Float(rel);
+         widths[count] = Integer.toString(f.intValue()) + "%";
+       }
+      } else {
+       int pixelWidth = nominalWidth;
+
+       if (tableWidth.indexOf("%") <= 0) {
+         pixelWidth = convertLength(tableWidth);
+       }
+
+       if (pixelWidth <= absTotal) {
+         System.out.println("Table is wider than table width.");
+       } else {
+         pixelWidth -= absTotal;
+       }
+
+       absTotal = 0;
+       for (int count = 0; count < numColumns; count++) {
+         float rel = relParts[count] / relTotal * pixelWidth;
+         relParts[count] = rel + absParts[count];
+         absTotal += rel + absParts[count];
+       }
+
+       if (tableWidth.indexOf("%") <= 0) {
+         for (int count = 0; count < numColumns; count++) {
+           Float f = new Float(relParts[count]);
+           if (foStylesheet) {
+             int pixels = f.intValue();
+             float inches = (float) pixels / pixelsPerInch;
+             widths[count] = inches + "in";
+           } else {
+             widths[count] = Integer.toString(f.intValue());
+           }
+         }
+       } else {
+         for (int count = 0; count < numColumns; count++) {
+           float rel = relParts[count] / absTotal * 100;
+           Float f = new Float(rel);
+           widths[count] = Integer.toString(f.intValue()) + "%";
+         }
+       }
+      }
+
+      ColumnUpdateEmitter cuEmitter = new ColumnUpdateEmitter(namePool,
+                                                             widths);
+      rtf.replay(cuEmitter);
+      return cuEmitter.getResultTreeFragment();
+    } catch (TransformerException e) {
+      // This "can't" happen.
+      System.out.println("Transformer Exception in adjustColumnWidths");
+      return rtf;
+    }
+  }
+}
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/Text.java b/xsl/extensions/saxon63/com/nwalsh/saxon/Text.java
new file mode 100644 (file)
index 0000000..f5a95d7
--- /dev/null
@@ -0,0 +1,132 @@
+// Text - Saxon extension element for inserting text
+
+package com.nwalsh.saxon;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+import java.net.URL;
+import java.net.MalformedURLException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerConfigurationException;
+import com.icl.saxon.*;
+import com.icl.saxon.style.*;
+import com.icl.saxon.expr.*;
+import com.icl.saxon.output.*;
+import org.xml.sax.AttributeList;
+
+/**
+ * <p>Saxon extension element for inserting text
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000 Norman Walsh.</p>
+ *
+ * <p>This class provides a
+ * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon</a>
+ * extension element for inserting text into a result tree.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @version $Id$
+ *
+ */
+public class Text extends StyleElement {
+  /**
+   * <p>Constructor for Text</p>
+   *
+   * <p>Does nothing.</p>
+   */
+  public Text() {
+  }
+
+  /**
+   * <p>Is this element an instruction?</p>
+   *
+   * <p>Yes, it is.</p>
+   *
+   * @return true
+   */
+  public boolean isInstruction() {
+    return true;
+  }
+
+    /**
+    * <p>Can this element contain a template-body?</p>
+    *
+    * <p>Yes, it can, but only so that it can contain xsl:fallback.</p>
+    *
+    * @return true
+    */
+  public boolean mayContainTemplateBody() {
+    return true;
+  }
+
+  /**
+   * <p>Validate the arguments</p>
+   *
+   * <p>The element must have an href attribute.</p>
+   */
+  public void prepareAttributes() throws TransformerConfigurationException {
+    // Get mandatory href attribute
+    String fnAtt = getAttribute("href");
+    if (fnAtt == null) {
+      reportAbsence("href");
+    }
+  }
+
+  /** Validate that the element occurs in a reasonable place. */
+  public void validate() throws TransformerConfigurationException {
+    checkWithinTemplate();
+  }
+
+  /**
+   * <p>Insert the text of the file into the result tree</p>
+   *
+   * <p>Processing this element inserts the contents of the URL named
+   * by the href attribute into the result tree as plain text.</p>
+   *
+   */
+  public void process( Context context ) throws TransformerException {
+    Outputter out = context.getOutputter();
+
+    String hrefAtt = getAttribute("href");
+    Expression hrefExpr = makeAttributeValueTemplate(hrefAtt);
+    String href = hrefExpr.evaluateAsString(context);
+    URL fileURL = null;
+
+    try {
+      try {
+       fileURL = new URL(href);
+      } catch (MalformedURLException e1) {
+       try {
+         fileURL = new URL("file:" + href);
+       } catch (MalformedURLException e2) {
+         System.out.println("Cannot open " + href);
+         return;
+       }
+      }
+
+      InputStreamReader isr = new InputStreamReader(fileURL.openStream());
+      BufferedReader is = new BufferedReader(isr);
+
+      char chars[] = new char[4096];
+      int len = 0;
+      while ((len = is.read(chars)) > 0) {
+       out.writeContent(chars, 0, len);
+      }
+      is.close();
+    } catch (Exception e) {
+      System.out.println("Cannot read " + href);
+    }
+  }
+}
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/TextFactory.java b/xsl/extensions/saxon63/com/nwalsh/saxon/TextFactory.java
new file mode 100644 (file)
index 0000000..ccf5001
--- /dev/null
@@ -0,0 +1,67 @@
+// TextFactory - Saxon extension element factory
+
+package com.nwalsh.saxon;
+
+import com.icl.saxon.style.ExtensionElementFactory;
+import org.xml.sax.SAXException;
+
+/**
+ * <p>Saxon extension element factory
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000 Norman Walsh.</p>
+ *
+ * <p>This class provides a
+ * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon</a>
+ * extension element factory for the Text extension element
+ * family.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @version $Id$
+ *
+ * @see Text
+ *
+ */
+public class TextFactory implements ExtensionElementFactory {
+  /**
+   * <p>Constructor for TextFactory</p>
+   *
+   * <p>Does nothing.</p>
+   */
+  public TextFactory() {
+  }
+
+  /**
+   * <p>Return the class that implements a particular extension element.</p>
+   *
+   * @param localname The local name of the extension element.
+   *
+   * @return The class that handles that extension element.
+   *
+   * @exception SAXException("Unknown Text extension element")
+   */
+  public Class getExtensionClass(String localname) {
+    if (localname.equals("insertfile")) {
+      try {
+       return Class.forName("com.nwalsh.saxon.Text");
+      } catch (ClassNotFoundException e) {
+       return null;
+      }
+    }
+    return null;
+  }
+}
+
+
+
+
+
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/Verbatim.java b/xsl/extensions/saxon63/com/nwalsh/saxon/Verbatim.java
new file mode 100644 (file)
index 0000000..5823d88
--- /dev/null
@@ -0,0 +1,458 @@
+// Verbatim.java - Saxon extensions supporting DocBook verbatim environments
+
+package com.nwalsh.saxon;
+
+import java.util.Stack;
+import java.util.StringTokenizer;
+import org.xml.sax.*;
+import org.w3c.dom.*;
+import javax.xml.transform.TransformerException;
+import com.icl.saxon.expr.*;
+import com.icl.saxon.om.*;
+import com.icl.saxon.pattern.*;
+import com.icl.saxon.Context;
+import com.icl.saxon.tree.*;
+import com.icl.saxon.functions.Extensions;
+import com.nwalsh.saxon.NumberLinesEmitter;
+import com.nwalsh.saxon.CalloutEmitter;
+
+/**
+ * <p>Saxon extensions supporting DocBook verbatim environments</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000 Norman Walsh.</p>
+ *
+ * <p>This class provides a
+ * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon</a>
+ * implementation of two features that would be impractical to
+ * implement directly in XSLT: line numbering and callouts.</p>
+ *
+ * <p><b>Line Numbering</b></p>
+ * <p>The <tt>numberLines</tt> method takes a result tree
+ * fragment (assumed to contain the contents of a formatted verbatim
+ * element in DocBook: programlisting, screen, address, literallayout,
+ * or synopsis) and returns a result tree fragment decorated with
+ * line numbers.</p>
+ *
+ * <p><b>Callouts</b></p>
+ * <p>The <tt>insertCallouts</tt> method takes an
+ * <tt>areaspec</tt> and a result tree fragment
+ * (assumed to contain the contents of a formatted verbatim
+ * element in DocBook: programlisting, screen, address, literallayout,
+ * or synopsis) and returns a result tree fragment decorated with
+ * callouts.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @version $Id$
+ *
+ */
+public class Verbatim {
+  /** True if the stylesheet is producing formatting objects */
+  private static boolean foStylesheet = false;
+  /** The modulus for line numbering (every 'modulus' line is numbered). */
+  private static int modulus = 0;
+  /** The width (in characters) of line numbers (for padding). */
+  private static int width = 0;
+  /** The separator between the line number and the verbatim text. */
+  private static String separator = "";
+
+  /** True if callouts have been setup */
+  private static boolean calloutsSetup = false;
+  /** The default column for callouts that have only a line or line range */
+  private static int defaultColumn = 60;
+  /** The path to use for graphical callout decorations. */
+  private static String graphicsPath = null;
+  /** The extension to use for graphical callout decorations. */
+  private static String graphicsExt = null;
+  /** The largest callout number that can be represented graphically. */
+  private static int graphicsMax = 10;
+
+  /** The FormatCallout object to use for formatting callouts. */
+  private static FormatCallout fCallout = null;
+
+  /**
+   * <p>Constructor for Verbatim</p>
+   *
+   * <p>All of the methods are static, so the constructor does nothing.</p>
+   */
+  public Verbatim() {
+  }
+
+  /**
+   * <p>Find the string value of a stylesheet variable or parameter</p>
+   *
+   * <p>Returns the string value of <code>varName</code> in the current
+   * <code>context</code>. Returns the empty string if the variable is
+   * not defined.</p>
+   *
+   * @param context The current stylesheet context
+   * @param varName The name of the variable (without the dollar sign)
+   *
+   * @return The string value of the variable
+   */
+  protected static String getVariable(Context context, String varName) {
+    Value variable = null;
+    String varString = null;
+
+    try {
+      variable = Extensions.evaluate(context, "$" + varName);
+      varString = variable.asString();
+      return varString;
+    } catch (TransformerException te) {
+      System.out.println("Undefined variable: " + varName);
+      return "";
+    } catch (IllegalArgumentException iae) {
+      System.out.println("Undefined variable: " + varName);
+      return "";
+    }
+  }
+
+  /**
+   * <p>Setup the parameters associated with line numbering</p>
+   *
+   * <p>This method queries the stylesheet for the variables
+   * associated with line numbering. It is called automatically before
+   * lines are numbered. The context is used to retrieve the values,
+   * this allows templates to redefine these variables.</p>
+   *
+   * <p>The following variables are queried. If the variables do not
+   * exist, builtin defaults will be used (but you may also get a bunch
+   * of messages from the Java interpreter).</p>
+   *
+   * <dl>
+   * <dt><code>linenumbering.everyNth</code></dt>
+   * <dd>Specifies the lines that will be numbered. The first line is
+   * always numbered. (builtin default: 5).</dd>
+   * <dt><code>linenumbering.width</code></dt>
+   * <dd>Specifies the width of the numbers. If the specified width is too
+   * narrow for the largest number needed, it will automatically be made
+   * wider. (builtin default: 3).</dd>
+   * <dt><code>linenumbering.separator</code></dt>
+   * <dd>Specifies the string that separates line numbers from lines
+   * in the program listing. (builtin default: " ").</dd>
+   * <dt><code>stylesheet.result.type</code></dt>
+   * <dd>Specifies the stylesheet result type. The value is either 'fo'
+   * (for XSL Formatting Objects) or it isn't. (builtin default: html).</dd>
+   * </dl>
+   *
+   * @param context The current stylesheet context
+   *
+   */
+  private static void setupLineNumbering(Context context) {
+    // Hardcoded defaults
+    modulus = 5;
+    width = 3;
+    separator = " ";
+    foStylesheet = false;
+
+    String varString = null;
+
+    // Get the modulus
+    varString = getVariable(context, "linenumbering.everyNth");
+    try {
+      modulus = Integer.parseInt(varString);
+    } catch (NumberFormatException nfe) {
+      System.out.println("$linenumbering.everyNth is not a number: " + varString);
+    }
+
+    // Get the width
+    varString = getVariable(context, "linenumbering.width");
+    try {
+      width = Integer.parseInt(varString);
+    } catch (NumberFormatException nfe) {
+      System.out.println("$linenumbering.width is not a number: " + varString);
+    }
+
+    // Get the separator
+    varString = getVariable(context, "linenumbering.separator");
+    separator = varString;
+
+    // Get the stylesheet type
+    varString = getVariable(context, "stylesheet.result.type");
+    foStylesheet = (varString.equals("fo"));
+  }
+
+  /**
+   * <p>Number lines in a verbatim environment</p>
+   *
+   * <p>The extension function expects the following variables to be
+   * available in the calling context: $linenumbering.everyNth,
+   * $linenumbering.width, $linenumbering.separator, and
+   * $stylesheet.result.type.</p>
+   *
+   * <p>This method adds line numbers to a result tree fragment. Each
+   * newline that occurs in a text node is assumed to start a new line.
+   * The first line is always numbered, every subsequent 'everyNth' line
+   * is numbered (so if everyNth=5, lines 1, 5, 10, 15, etc. will be
+   * numbered. If there are fewer than everyNth lines in the environment,
+   * every line is numbered.</p>
+   *
+   * <p>Every line number will be right justified in a string 'width'
+   * characters long. If the line number of the last line in the
+   * environment is too long to fit in the specified width, the width
+   * is automatically increased to the smallest value that can hold the
+   * number of the last line. (In other words, if you specify the value 2
+   * and attempt to enumerate the lines of an environment that is 100 lines
+   * long, the value 3 will automatically be used for every line in the
+   * environment.)</p>
+   *
+   * <p>The 'separator' string is inserted between the line
+   * number and the original program listing. Lines that aren't numbered
+   * are preceded by a 'width' blank string and the separator.</p>
+   *
+   * <p>If inline markup extends across line breaks, markup changes are
+   * required. All the open elements are closed before the line break and
+   * "reopened" afterwards. The reopened elements will have the same
+   * attributes as the originals, except that 'name' and 'id' attributes
+   * are not duplicated if the stylesheet.result.type is "html" and
+   * 'id' attributes will not be duplicated if the result type is "fo".</p>
+   *
+   * @param rtf The result tree fragment of the verbatim environment.
+   *
+   * @return The modified result tree fragment.
+   */
+  public static FragmentValue numberLines (Context context,
+                                          FragmentValue rtf) {
+
+    setupLineNumbering(context);
+
+    try {
+      LineCountEmitter lcEmitter = new LineCountEmitter();
+      rtf.replay(lcEmitter);
+      int numLines = lcEmitter.lineCount();
+
+      int listingModulus = numLines < modulus ? 1 : modulus;
+
+      double log10numLines = Math.log(numLines) / Math.log(10);
+
+      int listingWidth = width < log10numLines+1
+       ? (int) Math.floor(log10numLines + 1)
+       : width;
+
+      NamePool namePool = context.getController().getNamePool();
+      NumberLinesEmitter nlEmitter = new NumberLinesEmitter(namePool,
+                                                           listingModulus,
+                                                           listingWidth,
+                                                           separator,
+                                                           foStylesheet);
+      rtf.replay(nlEmitter);
+      return nlEmitter.getResultTreeFragment();
+    } catch (TransformerException e) {
+      // This "can't" happen.
+      System.out.println("Transformer Exception in numberLines");
+      return rtf;
+    }
+  }
+
+  /**
+   * <p>Setup the parameters associated with callouts</p>
+   *
+   * <p>This method queries the stylesheet for the variables
+   * associated with line numbering. It is called automatically before
+   * callouts are processed. The context is used to retrieve the values,
+   * this allows templates to redefine these variables.</p>
+   *
+   * <p>The following variables are queried. If the variables do not
+   * exist, builtin defaults will be used (but you may also get a bunch
+   * of messages from the Java interpreter).</p>
+   *
+   * <dl>
+   * <dt><code>callout.graphics</code></dt>
+   * <dd>Are we using callout graphics? A value of 0 or "" is false,
+   * any other value is true. If callout graphics are not used, the
+   * parameters related to graphis are not queried.</dd>
+   * <dt><code>callout.graphics.path</code></dt>
+   * <dd>Specifies the path to callout graphics.</dd>
+   * <dt><code>callout.graphics.extension</code></dt>
+   * <dd>Specifies the extension ot use for callout graphics.</dd>
+   * <dt><code>callout.graphics.number.limit</code></dt>
+   * <dd>Identifies the largest number that can be represented as a
+   * graphic. Larger callout numbers will be represented using text.</dd>
+   * <dt><code>callout.defaultcolumn</code></dt>
+   * <dd>Specifies the default column for callout bullets that do not
+   * specify a column.</dd>
+   * <dt><code>stylesheet.result.type</code></dt>
+   * <dd>Specifies the stylesheet result type. The value is either 'fo'
+   * (for XSL Formatting Objects) or it isn't. (builtin default: html).</dd>
+   * </dl>
+   *
+   * @param context The current stylesheet context
+   *
+   */
+  private static void setupCallouts(Context context) {
+    NamePool namePool = context.getController().getNamePool();
+
+    boolean useGraphics = false;
+    boolean useUnicode = false;
+
+    int unicodeStart = 49;
+    int unicodeMax = 0;
+
+    // Hardcoded defaults
+    defaultColumn = 60;
+    graphicsPath = null;
+    graphicsExt = null;
+    graphicsMax = 0;
+    foStylesheet = false;
+    calloutsSetup = true;
+
+    Value variable = null;
+    String varString = null;
+
+    // Get the stylesheet type
+    varString = getVariable(context, "stylesheet.result.type");
+    foStylesheet = (varString.equals("fo"));
+
+    // Get the default column
+    varString = getVariable(context, "callout.defaultcolumn");
+    try {
+      defaultColumn = Integer.parseInt(varString);
+    } catch (NumberFormatException nfe) {
+      System.out.println("$callout.defaultcolumn is not a number: "
+                        + varString);
+    }
+
+    // Use graphics at all?
+    varString = getVariable(context, "callout.graphics");
+    useGraphics = !(varString.equals("0") || varString.equals(""));
+
+    // Use unicode at all?
+    varString = getVariable(context, "callout.unicode");
+    useUnicode = !(varString.equals("0") || varString.equals(""));
+
+    if (useGraphics) {
+      // Get the graphics path
+      varString = getVariable(context, "callout.graphics.path");
+      graphicsPath = varString;
+
+      // Get the graphics extension
+      varString = getVariable(context, "callout.graphics.extension");
+      graphicsExt = varString;
+
+      // Get the number limit
+      varString = getVariable(context, "callout.graphics.number.limit");
+      try {
+       graphicsMax = Integer.parseInt(varString);
+      } catch (NumberFormatException nfe) {
+       System.out.println("$callout.graphics.number.limit is not a number: "
+                          + varString);
+       graphicsMax = 0;
+      }
+
+      fCallout = new FormatGraphicCallout(namePool,
+                                         graphicsPath,
+                                         graphicsExt,
+                                         graphicsMax,
+                                         foStylesheet);
+    } else if (useUnicode) {
+      // Get the starting character
+      varString = getVariable(context, "callout.unicode.start.character");
+      try {
+       unicodeStart = Integer.parseInt(varString);
+      } catch (NumberFormatException nfe) {
+       System.out.println("$callout.unicode.start.character is not a number: "
+                          + varString);
+       unicodeStart = 48;
+      }
+
+      // Get the number limit
+      varString = getVariable(context, "callout.unicode.number.limit");
+      try {
+       unicodeMax = Integer.parseInt(varString);
+      } catch (NumberFormatException nfe) {
+       System.out.println("$callout.unicode.number.limit is not a number: "
+                          + varString);
+       unicodeStart = 0;
+      }
+
+      fCallout = new FormatUnicodeCallout(namePool,
+                                         unicodeStart,
+                                         unicodeMax,
+                                         foStylesheet);
+    } else {
+      fCallout = new FormatTextCallout(namePool, foStylesheet);
+    }
+  }
+
+  /**
+   * <p>Insert text callouts into a verbatim environment.</p>
+   *
+   * <p>This method examines the <tt>areaset</tt> and <tt>area</tt> elements
+   * in the supplied <tt>areaspec</tt> and decorates the supplied
+   * result tree fragment with appropriate callout markers.</p>
+   *
+   * <p>If a <tt>label</tt> attribute is supplied on an <tt>area</tt>,
+   * its content will be used for the label, otherwise the callout
+   * number will be used, surrounded by parenthesis. Callout numbers may
+   * also be represented as graphics. Callouts are
+   * numbered in document order. All of the <tt>area</tt>s in an
+   * <tt>areaset</tt> get the same number.</p>
+   *
+   * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are
+   * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed.
+   * If only a line is specified, the callout decoration appears in
+   * the defaultColumn. Lines will be padded with blanks to reach the
+   * necessary column, but callouts that are located beyond the last
+   * line of the verbatim environment will be ignored.</p>
+   *
+   * <p>Callouts are inserted before the character at the line/column
+   * where they are to occur.</p>
+   *
+   * <p>If graphical callouts are used, and the callout number is less
+   * than or equal to the $callout.graphics.number.limit, the following image
+   * will be generated for HTML:
+   *
+   * <pre>
+   * &lt;img src="$callout.graphics.path/999$callout.graphics.ext"
+   *         alt="conumber">
+   * </pre>
+   *
+   * If the $stylesheet.result.type is 'fo', the following image will
+   * be generated:
+   *
+   * <pre>
+   * &lt;fo:external-graphic src="$callout.graphics.path/999$callout.graphics.ext"/>
+   * </pre>
+   *
+   * <p>If the callout number exceeds $callout.graphics.number.limit,
+   * the callout will be the callout number surrounded by
+   * parenthesis.</p>
+   *
+   * @param context The stylesheet context.
+   * @param areaspecNodeSet The source node set that contains the areaspec.
+   * @param rtf The result tree fragment of the verbatim environment.
+   *
+   * @return The modified result tree fragment.
+   */
+
+  public static FragmentValue insertCallouts (Context context,
+                                             NodeSetIntent areaspecNodeSet,
+                                             FragmentValue rtf) {
+
+    setupCallouts(context);
+
+    try {
+      NamePool namePool = context.getController().getNamePool();
+      CalloutEmitter cEmitter = new CalloutEmitter(namePool,
+                                                  defaultColumn,
+                                                  foStylesheet,
+                                                  fCallout);
+      cEmitter.setupCallouts(areaspecNodeSet);
+      rtf.replay(cEmitter);
+      return cEmitter.getResultTreeFragment();
+    } catch (TransformerException e) {
+      // This "can't" happen.
+      System.out.println("Transformer Exception in insertCallouts");
+      return rtf;
+    }
+  }
+}
diff --git a/xsl/extensions/saxon63/com/nwalsh/saxon/package.html b/xsl/extensions/saxon63/com/nwalsh/saxon/package.html
new file mode 100644 (file)
index 0000000..b05a467
--- /dev/null
@@ -0,0 +1,48 @@
+<html>
+<head>
+<title>Norman Walsh's Saxon Extensions Package</title>
+</head>
+<body>
+<p>Norman Walsh's Saxon Extensions Package for Saxon 6.*</p>
+
+<p>This package implements Saxon extensions for XSLT.</p>
+
+<p><b>Copyright (C) 2000 Norman Walsh</b></p>
+<p>Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:</p>
+
+<p>The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.</p>
+
+<p>Except as contained in this notice, the names of individuals
+credited with contribution to this software shall not be used in
+advertising or otherwise to promote the sale, use or other dealings
+in this Software without prior written authorization from the
+individuals in question.</p>
+
+<p>Anything derived from this Software that is publically
+distributed will be identified with a different name and the
+version strings in any derived Software will be changed so that no
+possibility of confusion between the derived package and this
+Software will exist.</p>
+</blockquote>
+
+<blockquote>
+<p><b>Warranty</b></p>
+<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT.  IN NO EVENT SHALL NORMAN WALSH OR ANY OTHER
+CONTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.</p>
+</blockquote>
+
+</body>
+</html>