
com.nwalsh.saxon.Verbatim Maven / Gradle / Ivy
Show all versions of docbook-xsl-saxon Show documentation
// Verbatim.java - Saxon extensions supporting DocBook verbatim environments
package com.nwalsh.saxon;
import org.w3c.dom.NodeList;
import javax.xml.transform.TransformerException;
import com.icl.saxon.Controller;
import com.icl.saxon.expr.Value;
import com.icl.saxon.expr.FragmentValue;
import com.icl.saxon.expr.NodeSetValue;
import com.icl.saxon.om.NamePool;
import com.icl.saxon.Context;
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 9327 2012-05-02 17:57:07Z mzjn $
*
* 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
* [email protected]
*
* @version $Id: Verbatim.java 9327 2012-05-02 17:57:07Z mzjn $
*
*/
public class Verbatim {
/** True if the stylesheet is producing formatting objects */
private static boolean foStylesheet = false;
/** True if the stylesheet is producing XHTML */
private static boolean xhStylesheet = 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 size of the callout icon. */
private static String iconSize = null;
/** 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 context The current stylesheet context.
* @param rtf_ns 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;
iconSize = "7pt";
foStylesheet = false;
xhStylesheet = false;
calloutsSetup = true;
Value variable = null;
String varString = null;
// Get the stylesheet type
varString = getVariable(context, "stylesheet.result.type");
foStylesheet = (varString.equals("fo"));
xhStylesheet = (varString.equals("xhtml"));
// 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;
}
// Get the callout icon size (used for FO)
if (foStylesheet) {
varString = getVariable(context, "callout.icon.size");
iconSize = varString;
}
fCallout = new FormatGraphicCallout(namePool,
graphicsPath,
graphicsExt,
graphicsMax,
iconSize,
foStylesheet,
xhStylesheet);
} 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,
xhStylesheet);
} else {
fCallout = new FormatTextCallout(namePool, foStylesheet, xhStylesheet);
}
}
/**
* 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 areaspecNodeList The source node set that contains the areaspec.
* @param rtf_ns 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;
}
}
}