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