// 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.Controller; 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; /** *

Saxon extensions supporting DocBook verbatim environments

* *

$Id: Verbatim.java,v 1.3 2003/08/27 14:24:59 nwalsh Exp $

* *

Copyright (C) 2000 Norman Walsh.

* *

This class provides a * Saxon * implementation of two features that would be impractical to * implement directly in XSLT: line numbering and callouts.

* *

Line Numbering

*

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

* *

Callouts

*

The insertCallouts method takes an * areaspec 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.

* *

Change Log:

*
*
1.0
*

Initial release.

*
* * @author Norman Walsh * ndw@nwalsh.com * * @version $Id: Verbatim.java,v 1.3 2003/08/27 14:24:59 nwalsh Exp $ * */ 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 starting line number. */ private static int startinglinenumber = 1; /** 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; /** *

Constructor for Verbatim

* *

All of the methods are static, so the constructor does nothing.

*/ public Verbatim() { } /** *

Find the string value of a stylesheet variable or parameter

* *

Returns the string value of varName in the current * context. Returns the empty string if the variable is * not defined.

* * @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 ""; } } /** *

Setup the parameters associated with line numbering

* *

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.

* *

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

* *
*
linenumbering.everyNth
*
Specifies the lines that will be numbered. The first line is * always numbered. (builtin default: 5).
*
linenumbering.width
*
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).
*
linenumbering.separator
*
Specifies the string that separates line numbers from lines * in the program listing. (builtin default: " ").
*
linenumbering.startinglinenumber
*
Specifies the initial line number * in the program listing. (builtin default: "1").
*
stylesheet.result.type
*
Specifies the stylesheet result type. The value is either 'fo' * (for XSL Formatting Objects) or it isn't. (builtin default: html).
*
* * @param context The current stylesheet context * */ private static void setupLineNumbering(Context context) { // Hardcoded defaults modulus = 5; width = 3; startinglinenumber = 1; 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 startinglinenumber varString = getVariable(context, "linenumbering.startinglinenumber"); try { startinglinenumber = Integer.parseInt(varString); } catch (NumberFormatException nfe) { System.out.println("$linenumbering.startinglinenumber 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")); } /** *

Number lines in a verbatim environment

* *

The extension function expects the following variables to be * available in the calling context: $linenumbering.everyNth, * $linenumbering.width, $linenumbering.separator, and * $stylesheet.result.type.

* *

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.

* *

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

* *

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.

* *

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

* * @param rtf The result tree fragment of the verbatim environment. * * @return The modified result tree fragment. */ public static NodeSetValue numberLines (Context context, NodeSetValue rtf_ns) { FragmentValue rtf = (FragmentValue) rtf_ns; 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; Controller controller = context.getController(); NamePool namePool = controller.getNamePool(); NumberLinesEmitter nlEmitter = new NumberLinesEmitter(controller, namePool, startinglinenumber, 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; } } /** *

Setup the parameters associated with callouts

* *

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.

* *

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

* *
*
callout.graphics
*
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.
*
callout.graphics.path
*
Specifies the path to callout graphics.
*
callout.graphics.extension
*
Specifies the extension ot use for callout graphics.
*
callout.graphics.number.limit
*
Identifies the largest number that can be represented as a * graphic. Larger callout numbers will be represented using text.
*
callout.defaultcolumn
*
Specifies the default column for callout bullets that do not * specify a column.
*
stylesheet.result.type
*
Specifies the stylesheet result type. The value is either 'fo' * (for XSL Formatting Objects) or it isn't. (builtin default: html).
*
* * @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; String unicodeFont = ""; // 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; } // Get the font unicodeFont = getVariable(context, "callout.unicode.font"); if (unicodeFont == null) { unicodeFont = ""; } fCallout = new FormatUnicodeCallout(namePool, unicodeFont, unicodeStart, unicodeMax, foStylesheet); } else { fCallout = new FormatTextCallout(namePool, foStylesheet); } } /** *

Insert text callouts into a verbatim environment.

* *

This method examines the areaset and area elements * in the supplied areaspec and decorates the supplied * result tree fragment with appropriate callout markers.

* *

If a label attribute is supplied on an area, * 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 areas in an * areaset get the same number.

* *

Only the linecolumn and linerange units are * supported. If no unit is specifed, linecolumn 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.

* *

Callouts are inserted before the character at the line/column * where they are to occur.

* *

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: * *

   * <img src="$callout.graphics.path/999$callout.graphics.ext"
   *         alt="conumber">
   * 
* * If the $stylesheet.result.type is 'fo', the following image will * be generated: * *
   * <fo:external-graphic src="$callout.graphics.path/999$callout.graphics.ext"/>
   * 
* *

If the callout number exceeds $callout.graphics.number.limit, * the callout will be the callout number surrounded by * parenthesis.

* * @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 NodeSetValue insertCallouts (Context context, NodeList areaspecNodeList, NodeSetValue rtf_ns) { FragmentValue rtf = (FragmentValue) rtf_ns; setupCallouts(context); try { Controller controller = context.getController(); NamePool namePool = controller.getNamePool(); CalloutEmitter cEmitter = new CalloutEmitter(controller, namePool, defaultColumn, foStylesheet, fCallout); cEmitter.setupCallouts(areaspecNodeList); rtf.replay(cEmitter); return cEmitter.getResultTreeFragment(); } catch (TransformerException e) { // This "can't" happen. System.out.println("Transformer Exception in insertCallouts"); return rtf; } } }