001: // Verbatim.java - Saxon extensions supporting DocBook verbatim environments
002:
003: package com.nwalsh.saxon;
004:
005: import java.util.Stack;
006: import java.util.StringTokenizer;
007: import org.xml.sax.*;
008: import org.w3c.dom.*;
009: import javax.xml.transform.TransformerException;
010: import com.icl.saxon.Controller;
011: import com.icl.saxon.expr.*;
012: import com.icl.saxon.om.*;
013: import com.icl.saxon.pattern.*;
014: import com.icl.saxon.Context;
015: import com.icl.saxon.tree.*;
016: import com.icl.saxon.functions.Extensions;
017: import com.nwalsh.saxon.NumberLinesEmitter;
018: import com.nwalsh.saxon.CalloutEmitter;
019:
020: /**
021: * <p>Saxon extensions supporting DocBook verbatim environments</p>
022: *
023: * <p>$Id: Verbatim.java,v 1.4 2005-08-30 08:14:58 draganr Exp $</p>
024: *
025: * <p>Copyright (C) 2000 Norman Walsh.</p>
026: *
027: * <p>This class provides a
028: * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon</a>
029: * implementation of two features that would be impractical to
030: * implement directly in XSLT: line numbering and callouts.</p>
031: *
032: * <p><b>Line Numbering</b></p>
033: * <p>The <tt>numberLines</tt> method takes a result tree
034: * fragment (assumed to contain the contents of a formatted verbatim
035: * element in DocBook: programlisting, screen, address, literallayout,
036: * or synopsis) and returns a result tree fragment decorated with
037: * line numbers.</p>
038: *
039: * <p><b>Callouts</b></p>
040: * <p>The <tt>insertCallouts</tt> method takes an
041: * <tt>areaspec</tt> and a result tree fragment
042: * (assumed to contain the contents of a formatted verbatim
043: * element in DocBook: programlisting, screen, address, literallayout,
044: * or synopsis) and returns a result tree fragment decorated with
045: * callouts.</p>
046: *
047: * <p><b>Change Log:</b></p>
048: * <dl>
049: * <dt>1.0</dt>
050: * <dd><p>Initial release.</p></dd>
051: * </dl>
052: *
053: * @author Norman Walsh
054: * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
055: *
056: * @version $Id: Verbatim.java,v 1.4 2005-08-30 08:14:58 draganr Exp $
057: *
058: */
059: public class Verbatim {
060: /** True if the stylesheet is producing formatting objects */
061: private static boolean foStylesheet = false;
062: /** The modulus for line numbering (every 'modulus' line is numbered). */
063: private static int modulus = 0;
064: /** The width (in characters) of line numbers (for padding). */
065: private static int width = 0;
066: /** The starting line number. */
067: private static int startinglinenumber = 1;
068: /** The separator between the line number and the verbatim text. */
069: private static String separator = "";
070:
071: /** True if callouts have been setup */
072: private static boolean calloutsSetup = false;
073: /** The default column for callouts that have only a line or line range */
074: private static int defaultColumn = 60;
075: /** The path to use for graphical callout decorations. */
076: private static String graphicsPath = null;
077: /** The extension to use for graphical callout decorations. */
078: private static String graphicsExt = null;
079: /** The largest callout number that can be represented graphically. */
080: private static int graphicsMax = 10;
081:
082: /** The FormatCallout object to use for formatting callouts. */
083: private static FormatCallout fCallout = null;
084:
085: /**
086: * <p>Constructor for Verbatim</p>
087: *
088: * <p>All of the methods are static, so the constructor does nothing.</p>
089: */
090: public Verbatim() {
091: }
092:
093: /**
094: * <p>Find the string value of a stylesheet variable or parameter</p>
095: *
096: * <p>Returns the string value of <code>varName</code> in the current
097: * <code>context</code>. Returns the empty string if the variable is
098: * not defined.</p>
099: *
100: * @param context The current stylesheet context
101: * @param varName The name of the variable (without the dollar sign)
102: *
103: * @return The string value of the variable
104: */
105: protected static String getVariable(Context context, String varName) {
106: Value variable = null;
107: String varString = null;
108:
109: try {
110: variable = Extensions.evaluate(context, "$" + varName);
111: varString = variable.asString();
112: return varString;
113: } catch (TransformerException te) {
114: System.out.println("Undefined variable: " + varName);
115: return "";
116: } catch (IllegalArgumentException iae) {
117: System.out.println("Undefined variable: " + varName);
118: return "";
119: }
120: }
121:
122: /**
123: * <p>Setup the parameters associated with line numbering</p>
124: *
125: * <p>This method queries the stylesheet for the variables
126: * associated with line numbering. It is called automatically before
127: * lines are numbered. The context is used to retrieve the values,
128: * this allows templates to redefine these variables.</p>
129: *
130: * <p>The following variables are queried. If the variables do not
131: * exist, builtin defaults will be used (but you may also get a bunch
132: * of messages from the Java interpreter).</p>
133: *
134: * <dl>
135: * <dt><code>linenumbering.everyNth</code></dt>
136: * <dd>Specifies the lines that will be numbered. The first line is
137: * always numbered. (builtin default: 5).</dd>
138: * <dt><code>linenumbering.width</code></dt>
139: * <dd>Specifies the width of the numbers. If the specified width is too
140: * narrow for the largest number needed, it will automatically be made
141: * wider. (builtin default: 3).</dd>
142: * <dt><code>linenumbering.separator</code></dt>
143: * <dd>Specifies the string that separates line numbers from lines
144: * in the program listing. (builtin default: " ").</dd>
145: * <dt><code>linenumbering.startinglinenumber</code></dt>
146: * <dd>Specifies the initial line number
147: * in the program listing. (builtin default: "1").</dd>
148: * <dt><code>stylesheet.result.type</code></dt>
149: * <dd>Specifies the stylesheet result type. The value is either 'fo'
150: * (for XSL Formatting Objects) or it isn't. (builtin default: html).</dd>
151: * </dl>
152: *
153: * @param context The current stylesheet context
154: *
155: */
156: private static void setupLineNumbering(Context context) {
157: // Hardcoded defaults
158: modulus = 5;
159: width = 3;
160: startinglinenumber = 1;
161: separator = " ";
162: foStylesheet = false;
163:
164: String varString = null;
165:
166: // Get the modulus
167: varString = getVariable(context, "linenumbering.everyNth");
168: try {
169: modulus = Integer.parseInt(varString);
170: } catch (NumberFormatException nfe) {
171: System.out
172: .println("$linenumbering.everyNth is not a number: "
173: + varString);
174: }
175:
176: // Get the width
177: varString = getVariable(context, "linenumbering.width");
178: try {
179: width = Integer.parseInt(varString);
180: } catch (NumberFormatException nfe) {
181: System.out.println("$linenumbering.width is not a number: "
182: + varString);
183: }
184:
185: // Get the startinglinenumber
186: varString = getVariable(context,
187: "linenumbering.startinglinenumber");
188: try {
189: startinglinenumber = Integer.parseInt(varString);
190: } catch (NumberFormatException nfe) {
191: System.out
192: .println("$linenumbering.startinglinenumber is not a number: "
193: + varString);
194: }
195:
196: // Get the separator
197: varString = getVariable(context, "linenumbering.separator");
198: separator = varString;
199:
200: // Get the stylesheet type
201: varString = getVariable(context, "stylesheet.result.type");
202: foStylesheet = (varString.equals("fo"));
203: }
204:
205: /**
206: * <p>Number lines in a verbatim environment</p>
207: *
208: * <p>The extension function expects the following variables to be
209: * available in the calling context: $linenumbering.everyNth,
210: * $linenumbering.width, $linenumbering.separator, and
211: * $stylesheet.result.type.</p>
212: *
213: * <p>This method adds line numbers to a result tree fragment. Each
214: * newline that occurs in a text node is assumed to start a new line.
215: * The first line is always numbered, every subsequent 'everyNth' line
216: * is numbered (so if everyNth=5, lines 1, 5, 10, 15, etc. will be
217: * numbered. If there are fewer than everyNth lines in the environment,
218: * every line is numbered.</p>
219: *
220: * <p>Every line number will be right justified in a string 'width'
221: * characters long. If the line number of the last line in the
222: * environment is too long to fit in the specified width, the width
223: * is automatically increased to the smallest value that can hold the
224: * number of the last line. (In other words, if you specify the value 2
225: * and attempt to enumerate the lines of an environment that is 100 lines
226: * long, the value 3 will automatically be used for every line in the
227: * environment.)</p>
228: *
229: * <p>The 'separator' string is inserted between the line
230: * number and the original program listing. Lines that aren't numbered
231: * are preceded by a 'width' blank string and the separator.</p>
232: *
233: * <p>If inline markup extends across line breaks, markup changes are
234: * required. All the open elements are closed before the line break and
235: * "reopened" afterwards. The reopened elements will have the same
236: * attributes as the originals, except that 'name' and 'id' attributes
237: * are not duplicated if the stylesheet.result.type is "html" and
238: * 'id' attributes will not be duplicated if the result type is "fo".</p>
239: *
240: * @param rtf The result tree fragment of the verbatim environment.
241: *
242: * @return The modified result tree fragment.
243: */
244: public static NodeSetValue numberLines(Context context,
245: NodeSetValue rtf_ns) {
246:
247: FragmentValue rtf = (FragmentValue) rtf_ns;
248:
249: setupLineNumbering(context);
250:
251: try {
252: LineCountEmitter lcEmitter = new LineCountEmitter();
253: rtf.replay(lcEmitter);
254: int numLines = lcEmitter.lineCount();
255:
256: int listingModulus = numLines < modulus ? 1 : modulus;
257:
258: double log10numLines = Math.log(numLines) / Math.log(10);
259:
260: int listingWidth = width < log10numLines + 1 ? (int) Math
261: .floor(log10numLines + 1) : width;
262:
263: Controller controller = context.getController();
264: NamePool namePool = controller.getNamePool();
265: NumberLinesEmitter nlEmitter = new NumberLinesEmitter(
266: controller, namePool, startinglinenumber,
267: listingModulus, listingWidth, separator,
268: foStylesheet);
269: rtf.replay(nlEmitter);
270: return nlEmitter.getResultTreeFragment();
271: } catch (TransformerException e) {
272: // This "can't" happen.
273: System.out.println("Transformer Exception in numberLines");
274: return rtf;
275: }
276: }
277:
278: /**
279: * <p>Setup the parameters associated with callouts</p>
280: *
281: * <p>This method queries the stylesheet for the variables
282: * associated with line numbering. It is called automatically before
283: * callouts are processed. The context is used to retrieve the values,
284: * this allows templates to redefine these variables.</p>
285: *
286: * <p>The following variables are queried. If the variables do not
287: * exist, builtin defaults will be used (but you may also get a bunch
288: * of messages from the Java interpreter).</p>
289: *
290: * <dl>
291: * <dt><code>callout.graphics</code></dt>
292: * <dd>Are we using callout graphics? A value of 0 or "" is false,
293: * any other value is true. If callout graphics are not used, the
294: * parameters related to graphis are not queried.</dd>
295: * <dt><code>callout.graphics.path</code></dt>
296: * <dd>Specifies the path to callout graphics.</dd>
297: * <dt><code>callout.graphics.extension</code></dt>
298: * <dd>Specifies the extension ot use for callout graphics.</dd>
299: * <dt><code>callout.graphics.number.limit</code></dt>
300: * <dd>Identifies the largest number that can be represented as a
301: * graphic. Larger callout numbers will be represented using text.</dd>
302: * <dt><code>callout.defaultcolumn</code></dt>
303: * <dd>Specifies the default column for callout bullets that do not
304: * specify a column.</dd>
305: * <dt><code>stylesheet.result.type</code></dt>
306: * <dd>Specifies the stylesheet result type. The value is either 'fo'
307: * (for XSL Formatting Objects) or it isn't. (builtin default: html).</dd>
308: * </dl>
309: *
310: * @param context The current stylesheet context
311: *
312: */
313: private static void setupCallouts(Context context) {
314: NamePool namePool = context.getController().getNamePool();
315:
316: boolean useGraphics = false;
317: boolean useUnicode = false;
318:
319: int unicodeStart = 49;
320: int unicodeMax = 0;
321:
322: String unicodeFont = "";
323:
324: // Hardcoded defaults
325: defaultColumn = 60;
326: graphicsPath = null;
327: graphicsExt = null;
328: graphicsMax = 0;
329: foStylesheet = false;
330: calloutsSetup = true;
331:
332: Value variable = null;
333: String varString = null;
334:
335: // Get the stylesheet type
336: varString = getVariable(context, "stylesheet.result.type");
337: foStylesheet = (varString.equals("fo"));
338:
339: // Get the default column
340: varString = getVariable(context, "callout.defaultcolumn");
341: try {
342: defaultColumn = Integer.parseInt(varString);
343: } catch (NumberFormatException nfe) {
344: System.out
345: .println("$callout.defaultcolumn is not a number: "
346: + varString);
347: }
348:
349: // Use graphics at all?
350: varString = getVariable(context, "callout.graphics");
351: useGraphics = !(varString.equals("0") || varString.equals(""));
352:
353: // Use unicode at all?
354: varString = getVariable(context, "callout.unicode");
355: useUnicode = !(varString.equals("0") || varString.equals(""));
356:
357: if (useGraphics) {
358: // Get the graphics path
359: varString = getVariable(context, "callout.graphics.path");
360: graphicsPath = varString;
361:
362: // Get the graphics extension
363: varString = getVariable(context,
364: "callout.graphics.extension");
365: graphicsExt = varString;
366:
367: // Get the number limit
368: varString = getVariable(context,
369: "callout.graphics.number.limit");
370: try {
371: graphicsMax = Integer.parseInt(varString);
372: } catch (NumberFormatException nfe) {
373: System.out
374: .println("$callout.graphics.number.limit is not a number: "
375: + varString);
376: graphicsMax = 0;
377: }
378:
379: fCallout = new FormatGraphicCallout(namePool, graphicsPath,
380: graphicsExt, graphicsMax, foStylesheet);
381: } else if (useUnicode) {
382: // Get the starting character
383: varString = getVariable(context,
384: "callout.unicode.start.character");
385: try {
386: unicodeStart = Integer.parseInt(varString);
387: } catch (NumberFormatException nfe) {
388: System.out
389: .println("$callout.unicode.start.character is not a number: "
390: + varString);
391: unicodeStart = 48;
392: }
393:
394: // Get the number limit
395: varString = getVariable(context,
396: "callout.unicode.number.limit");
397: try {
398: unicodeMax = Integer.parseInt(varString);
399: } catch (NumberFormatException nfe) {
400: System.out
401: .println("$callout.unicode.number.limit is not a number: "
402: + varString);
403: unicodeStart = 0;
404: }
405:
406: // Get the font
407: unicodeFont = getVariable(context, "callout.unicode.font");
408: if (unicodeFont == null) {
409: unicodeFont = "";
410: }
411:
412: fCallout = new FormatUnicodeCallout(namePool, unicodeFont,
413: unicodeStart, unicodeMax, foStylesheet);
414: } else {
415: fCallout = new FormatTextCallout(namePool, foStylesheet);
416: }
417: }
418:
419: /**
420: * <p>Insert text callouts into a verbatim environment.</p>
421: *
422: * <p>This method examines the <tt>areaset</tt> and <tt>area</tt> elements
423: * in the supplied <tt>areaspec</tt> and decorates the supplied
424: * result tree fragment with appropriate callout markers.</p>
425: *
426: * <p>If a <tt>label</tt> attribute is supplied on an <tt>area</tt>,
427: * its content will be used for the label, otherwise the callout
428: * number will be used, surrounded by parenthesis. Callout numbers may
429: * also be represented as graphics. Callouts are
430: * numbered in document order. All of the <tt>area</tt>s in an
431: * <tt>areaset</tt> get the same number.</p>
432: *
433: * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are
434: * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed.
435: * If only a line is specified, the callout decoration appears in
436: * the defaultColumn. Lines will be padded with blanks to reach the
437: * necessary column, but callouts that are located beyond the last
438: * line of the verbatim environment will be ignored.</p>
439: *
440: * <p>Callouts are inserted before the character at the line/column
441: * where they are to occur.</p>
442: *
443: * <p>If graphical callouts are used, and the callout number is less
444: * than or equal to the $callout.graphics.number.limit, the following image
445: * will be generated for HTML:
446: *
447: * <pre>
448: * <img src="$callout.graphics.path/999$callout.graphics.ext"
449: * alt="conumber">
450: * </pre>
451: *
452: * If the $stylesheet.result.type is 'fo', the following image will
453: * be generated:
454: *
455: * <pre>
456: * <fo:external-graphic src="$callout.graphics.path/999$callout.graphics.ext"/>
457: * </pre>
458: *
459: * <p>If the callout number exceeds $callout.graphics.number.limit,
460: * the callout will be the callout number surrounded by
461: * parenthesis.</p>
462: *
463: * @param context The stylesheet context.
464: * @param areaspecNodeSet The source node set that contains the areaspec.
465: * @param rtf The result tree fragment of the verbatim environment.
466: *
467: * @return The modified result tree fragment.
468: */
469:
470: public static NodeSetValue insertCallouts(Context context,
471: NodeList areaspecNodeList, NodeSetValue rtf_ns) {
472:
473: FragmentValue rtf = (FragmentValue) rtf_ns;
474:
475: setupCallouts(context);
476:
477: try {
478: Controller controller = context.getController();
479: NamePool namePool = controller.getNamePool();
480: CalloutEmitter cEmitter = new CalloutEmitter(controller,
481: namePool, defaultColumn, foStylesheet, fCallout);
482: cEmitter.setupCallouts(areaspecNodeList);
483: rtf.replay(cEmitter);
484: return cEmitter.getResultTreeFragment();
485: } catch (TransformerException e) {
486: // This "can't" happen.
487: System.out
488: .println("Transformer Exception in insertCallouts");
489: return rtf;
490: }
491: }
492: }
|