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