Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/nwalsh/xalan/Verbatim.java


1   // Verbatim.java - Xalan extensions supporting DocBook verbatim environments
2   
3   package com.nwalsh.xalan;
4   
5   import java.util.Stack;
6   import java.util.StringTokenizer;
7   
8   import org.xml.sax.*;
9   import org.xml.sax.helpers.AttributesImpl;
10  import org.w3c.dom.*;
11  import org.w3c.dom.traversal.NodeIterator;
12  import org.apache.xerces.dom.*;
13  
14  import org.apache.xpath.objects.XObject;
15  import org.apache.xpath.XPath;
16  import org.apache.xpath.XPathContext;
17  import org.apache.xpath.NodeSet;
18  import org.apache.xpath.DOMHelper;
19  import org.apache.xalan.extensions.XSLProcessorContext;
20  import org.apache.xalan.extensions.ExpressionContext;
21  import org.apache.xalan.transformer.TransformerImpl;
22  import org.apache.xalan.templates.StylesheetRoot;
23  import org.apache.xalan.templates.ElemExtensionCall;
24  import org.apache.xalan.templates.OutputProperties;
25  import org.apache.xalan.res.XSLTErrorResources;
26  import org.apache.xml.utils.DOMBuilder;
27  import org.apache.xml.utils.AttList;
28  import org.apache.xml.utils.QName;
29  
30  import javax.xml.transform.stream.StreamResult;
31  import javax.xml.transform.TransformerException;
32  import javax.xml.parsers.DocumentBuilder;
33  import javax.xml.parsers.DocumentBuilderFactory;
34  import javax.xml.parsers.ParserConfigurationException;
35  
36  import com.nwalsh.xalan.Callout;
37  import com.nwalsh.xalan.Params;
38  
39  /**
40   * <p>Xalan extensions supporting DocBook verbatim environments</p>
41   *
42   *
43   * <p>Copyright (C) 2001 Norman Walsh.</p>
44   *
45   * <p>This class provides a
46   * <a href="http://xml.apache.org/xalan">Xalan</a>
47   * implementation of two features that would be impractical to
48   * implement directly in XSLT: line numbering and callouts.</p>
49   *
50   * <p><b>Line Numbering</b></p>
51   * <p>The <tt>numberLines</tt> family of functions takes a result tree
52   * fragment (assumed to contain the contents of a formatted verbatim
53   * element in DocBook: programlisting, screen, address, literallayout,
54   * or synopsis) and returns a result tree fragment decorated with
55   * line numbers.</p>
56   *
57   * <p><b>Callouts</b></p>
58   * <p>The <tt>insertCallouts</tt> family of functions takes an
59   * <tt>areaspec</tt> and a result tree fragment
60   * (assumed to contain the contents of a formatted verbatim
61   * element in DocBook: programlisting, screen, address, literallayout,
62   * or synopsis) and returns a result tree fragment decorated with
63   * callouts.</p>
64   *
65   * <p><b>Change Log:</b></p>
66   * <dl>
67   * <dt>1.0</dt>
68   * <dd><p>Initial release.</p></dd>
69   * </dl>
70   *
71   * @author Norman Walsh
72   * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
73   *
74   *
75   */
76  public class Verbatim {
77    /** A stack to hold the open elements while walking through a RTF. */
78    private Stack elementStack = null;
79    /** A stack to hold the temporarily closed elements. */
80    private Stack tempStack = null;
81    /** The current line number. */
82    private int lineNumber = 0;
83    /** The current column number. */
84    private int colNumber = 0;
85    /** The modulus for line numbering (every 'modulus' line is numbered). */
86    private int modulus = 0;
87    /** The width (in characters) of line numbers (for padding). */
88    private int width = 0;
89    /** The separator between the line number and the verbatim text. */
90    private String separator = "";
91    /** The (sorted) array of callouts obtained from the areaspec. */
92    private Callout callout[] = null;
93    /** The number of callouts in the callout array. */
94    private int calloutCount = 0;
95    /** A pointer used to keep track of our position in the callout array. */
96    private int calloutPos = 0;
97    /** The path to use for graphical callout decorations. */
98    private String graphicsPath = null;
99    /** The extension to use for graphical callout decorations. */
100   private String graphicsExt = null;
101   /** The largest callout number that can be represented graphically. */
102   private int graphicsMax = 10;
103   /** Should graphic callouts use fo:external-graphics or imgs. */
104   private boolean graphicsFO = false;
105 
106   private static final String foURI = "http://www.w3.org/1999/XSL/Format";
107   private static final String xhURI = "http://www.w3.org/1999/xhtml";
108 
109   /**
110    * <p>Constructor for Verbatim</p>
111    *
112    * <p>All of the methods are static, so the constructor does nothing.</p>
113    */
114   public Verbatim() {
115   }
116 
117   /**
118    * <p>Number lines in a verbatim environment.</p>
119    *
120    * <p>This method adds line numbers to a result tree fragment. Each
121    * newline that occurs in a text node is assumed to start a new line.
122    * The first line is always numbered, every subsequent xalanMod line
123    * is numbered (so if xalanMod=5, lines 1, 5, 10, 15, etc. will be
124    * numbered. If there are fewer than xalanMod lines in the environment,
125    * every line is numbered.</p>
126    *
127    * <p>xalanMod is taken from the $linenumbering.everyNth parameter.</p>
128    *
129    * <p>Every line number will be right justified in a string xalanWidth
130    * characters long. If the line number of the last line in the
131    * environment is too long to fit in the specified width, the width
132    * is automatically increased to the smallest value that can hold the
133    * number of the last line. (In other words, if you specify the value 2
134    * and attempt to enumerate the lines of an environment that is 100 lines
135    * long, the value 3 will automatically be used for every line in the
136    * environment.)</p>
137    *
138    * <p>xalanWidth is taken from the $linenumbering.width parameter.</p>
139    *
140    * <p>The xalanSep string is inserted between the line
141    * number and the original program listing. Lines that aren't numbered
142    * are preceded by a xalanWidth blank string and the separator.</p>
143    *
144    * <p>xalanSep is taken from the $linenumbering.separator parameter.</p>
145    *
146    * <p>If inline markup extends across line breaks, markup changes are
147    * required. All the open elements are closed before the line break and
148    * "reopened" afterwards. The reopened elements will have the same
149    * attributes as the originals, except that 'name' and 'id' attributes
150    * are not duplicated.</p>
151    *
152    * @param xalanRTF The result tree fragment of the verbatim environment.
153    *
154    * @return The modified result tree fragment.
155    */
156   public DocumentFragment numberLines (ExpressionContext context,
157                NodeIterator xalanNI) {
158 
159     int xalanMod = Params.getInt(context, "linenumbering.everyNth");
160     int xalanWidth = Params.getInt(context, "linenumbering.width");
161     String xalanSep = Params.getString(context, "linenumbering.separator");
162 
163     DocumentFragment xalanRTF = (DocumentFragment) xalanNI.nextNode();
164     int numLines = countLineBreaks(xalanRTF) + 1;
165 
166     DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
167     DocumentBuilder docBuilder = null;
168 
169     try {
170       docBuilder = docFactory.newDocumentBuilder();
171     } catch (ParserConfigurationException e) {
172       System.out.println("PCE!");
173       return xalanRTF;
174     }
175     Document doc = docBuilder.newDocument();
176     DocumentFragment df = doc.createDocumentFragment();
177     DOMBuilder db = new DOMBuilder(doc, df);
178 
179     elementStack = new Stack();
180     lineNumber = 0;
181     modulus = numLines < xalanMod ? 1 : xalanMod;
182     width = xalanWidth;
183     separator = xalanSep;
184 
185     double log10numLines = Math.log(numLines) / Math.log(10);
186 
187     if (width < log10numLines + 1) {
188       width = (int) Math.floor(log10numLines + 1);
189     }
190 
191     lineNumberFragment(db, xalanRTF);
192     return df;
193   }
194 
195   /**
196    * <p>Count the number of lines in a verbatim environment.</p>
197    *
198    * <p>This method walks over the nodes of a DocumentFragment and
199    * returns the number of lines breaks that it contains.</p>
200    *
201    * @param node The root of the tree walk over.
202    */
203   private int countLineBreaks(Node node) {
204     int numLines = 0;
205 
206     if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE
207   || node.getNodeType() == Node.DOCUMENT_NODE
208   || node.getNodeType() == Node.ELEMENT_NODE) {
209       Node child = node.getFirstChild();
210       while (child != null) {
211   numLines += countLineBreaks(child);
212   child = child.getNextSibling();
213       }
214     } else if (node.getNodeType() == Node.TEXT_NODE) {
215       String text = node.getNodeValue();
216 
217       // Walk through the text node looking for newlines
218       int pos = 0;
219       for (int count = 0; count < text.length(); count++) {
220   if (text.charAt(count) == '\n') {
221     numLines++;
222   }
223       }
224     } else {
225       // nop
226     }
227 
228     return numLines;
229   }
230 
231   /**
232    * <p>Build a DocumentFragment with numbered lines.</p>
233    *
234    * <p>This is the method that actually does the work of numbering
235    * lines in a verbatim environment. It recursively walks through a
236    * tree of nodes, copying the structure into the rtf. Text nodes
237    * are examined for new lines and modified as requested by the
238    * global line numbering parameters.</p>
239    *
240    * <p>When called, rtf should be an empty DocumentFragment and node
241    * should be the first child of the result tree fragment that contains
242    * the existing, formatted verbatim text.</p>
243    *
244    * @param rtf The resulting verbatim environment with numbered lines.
245    * @param node The root of the tree to copy.
246    */
247   private void lineNumberFragment(DOMBuilder rtf,
248           Node node) {
249     try {
250       if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE
251     || node.getNodeType() == Node.DOCUMENT_NODE) {
252   Node child = node.getFirstChild();
253   while (child != null) {
254     lineNumberFragment(rtf, child);
255     child = child.getNextSibling();
256   }
257       } else if (node.getNodeType() == Node.ELEMENT_NODE) {
258   String ns = node.getNamespaceURI();
259   String localName = node.getLocalName();
260   String name = ((Element) node).getTagName();
261 
262   rtf.startElement(ns, localName, name,
263        copyAttributes((Element) node));
264 
265   elementStack.push(node);
266 
267   Node child = node.getFirstChild();
268   while (child != null) {
269     lineNumberFragment(rtf, child);
270     child = child.getNextSibling();
271   }
272       } else if (node.getNodeType() == Node.TEXT_NODE) {
273   String text = node.getNodeValue();
274 
275   if (lineNumber == 0) {
276     // The first line is always numbered
277     formatLineNumber(rtf, ++lineNumber);
278   }
279 
280   // Walk through the text node looking for newlines
281   char chars[] = text.toCharArray();
282   int pos = 0;
283   for (int count = 0; count < text.length(); count++) {
284     if (text.charAt(count) == '\n') {
285       // This is the tricky bit; if we find a newline, make sure
286       // it doesn't occur inside any markup.
287 
288       if (pos > 0) {
289         rtf.characters(chars, 0, pos);
290         pos = 0;
291       }
292 
293       closeOpenElements(rtf);
294 
295       // Copy the newline to the output
296       chars[pos++] = text.charAt(count);
297       rtf.characters(chars, 0, pos);
298       pos = 0;
299 
300       // Add the line number
301       formatLineNumber(rtf, ++lineNumber);
302 
303       openClosedElements(rtf);
304     } else {
305       chars[pos++] = text.charAt(count);
306     }
307   }
308 
309   if (pos > 0) {
310     rtf.characters(chars, 0, pos);
311   }
312       } else if (node.getNodeType() == Node.COMMENT_NODE) {
313   String text = node.getNodeValue();
314   char chars[] = text.toCharArray();
315   rtf.comment(chars, 0, text.length());
316       } else if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
317   rtf.processingInstruction(node.getNodeName(), node.getNodeValue());
318       } else {
319   System.out.println("Warning: unexpected node type in lineNumberFragment");
320       }
321 
322       if (node.getNodeType() == Node.ELEMENT_NODE) {
323   String ns = node.getNamespaceURI();
324   String localName = node.getLocalName();
325   String name = ((Element) node).getTagName();
326   rtf.endElement(ns, localName, name);
327   elementStack.pop();
328       }
329     } catch (SAXException e) {
330       System.out.println("SAX Exception in lineNumberFragment");
331     }
332   }
333 
334   /**
335    * <p>Add a formatted line number to the result tree fragment.</p>
336    *
337    * <p>This method examines the global parameters that control line
338    * number presentation (modulus, width, and separator) and adds
339    * the appropriate text to the result tree fragment.</p>
340    *
341    * @param rtf The resulting verbatim environment with numbered lines.
342    * @param lineNumber The number of the current line.
343    */
344   private void formatLineNumber(DOMBuilder rtf,
345         int lineNumber) {
346     char ch = 160;
347     String lno = "";
348     if (lineNumber == 1
349   || (modulus >= 1 && (lineNumber % modulus == 0))) {
350       lno = "" + lineNumber;
351     }
352 
353     while (lno.length() < width) {
354       lno = ch + lno;
355     }
356 
357     lno += separator;
358 
359     char chars[] = lno.toCharArray();
360     try {
361       rtf.characters(chars, 0, lno.length());
362     } catch (SAXException e) {
363       System.out.println("SAX Exception in formatLineNumber");
364     }
365   }
366 
367   /**
368    * <p>Insert text callouts into a verbatim environment.</p>
369    *
370    * <p>This method examines the <tt>areaset</tt> and <tt>area</tt> elements
371    * in the supplied <tt>areaspec</tt> and decorates the supplied
372    * result tree fragment with appropriate callout markers.</p>
373    *
374    * <p>If a <tt>label</tt> attribute is supplied on an <tt>area</tt>,
375    * its content will be used for the label, otherwise the callout
376    * number will be used, surrounded by parenthesis. Callouts are
377    * numbered in document order. All of the <tt>area</tt>s in an
378    * <tt>areaset</tt> get the same number.</p>
379    *
380    * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are
381    * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed.
382    * If only a line is specified, the callout decoration appears in
383    * the defaultColumn. Lines will be padded with blanks to reach the
384    * necessary column, but callouts that are located beyond the last
385    * line of the verbatim environment will be ignored.</p>
386    *
387    * <p>Callouts are inserted before the character at the line/column
388    * where they are to occur.</p>
389    *
390    * @param areaspecNodeSet The source node set that contains the areaspec.
391    * @param xalanRTF The result tree fragment of the verbatim environment.
392    * @param defaultColumn The column for callouts that specify only a line.
393    *
394    * @return The modified result tree fragment.  */
395 
396   /**
397    * <p>Insert graphical callouts into a verbatim environment.</p>
398    *
399    * <p>This method examines the <tt>areaset</tt> and <tt>area</tt> elements
400    * in the supplied <tt>areaspec</tt> and decorates the supplied
401    * result tree fragment with appropriate callout markers.</p>
402    *
403    * <p>If a <tt>label</tt> attribute is supplied on an <tt>area</tt>,
404    * its content will be used for the label, otherwise the callout
405    * number will be used. Callouts are
406    * numbered in document order. All of the <tt>area</tt>s in an
407    * <tt>areaset</tt> get the same number.</p>
408    *
409    * <p>If the callout number is not greater than <tt>gMax</tt>, the
410    * callout generated will be:</p>
411    *
412    * <pre>
413    * &lt;img src="$gPath/conumber$gExt" alt="conumber">
414    * </pre>
415    *
416    * <p>Otherwise, it will be the callout number surrounded by
417    * parenthesis.</p>
418    *
419    * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are
420    * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed.
421    * If only a line is specified, the callout decoration appears in
422    * the defaultColumn. Lines will be padded with blanks to reach the
423    * necessary column, but callouts that are located beyond the last
424    * line of the verbatim environment will be ignored.</p>
425    *
426    * <p>Callouts are inserted before the character at the line/column
427    * where they are to occur.</p>
428    *
429    * @param areaspecNodeSet The source node set that contains the areaspec.
430    * @param xalanRTF The result tree fragment of the verbatim environment.
431    * @param defaultColumn The column for callouts that specify only a line.
432    * @param gPath The path to use for callout graphics.
433    * @param gExt The extension to use for callout graphics.
434    * @param gMax The largest number that can be represented as a graphic.
435    * @param useFO Should fo:external-graphics be produced, as opposed to
436    * HTML imgs. This is bogus, the extension should figure it out, but I
437    * haven't figured out how to do that yet.
438    *
439    * @return The modified result tree fragment.
440    */
441 
442   public DocumentFragment insertCallouts (ExpressionContext context,
443             NodeIterator areaspecNodeSet,
444             NodeIterator xalanNI) {
445     String type = Params.getString(context, "stylesheet.result.type");
446     boolean useFO = type.equals("fo");
447     int defaultColumn = Params.getInt(context, "callout.defaultcolumn");
448 
449     if (Params.getBoolean(context, "callout.graphics")) {
450       String gPath = Params.getString(context, "callout.graphics.path");
451       String gExt = Params.getString(context, "callout.graphics.extension");
452       int gMax = Params.getInt(context, "callout.graphics.number.limit");
453       return insertGraphicCallouts(areaspecNodeSet, xalanNI, defaultColumn,
454            gPath, gExt, gMax, useFO);
455     } else if (Params.getBoolean(context, "callout.unicode")) {
456       int uStart = Params.getInt(context, "callout.unicode.start.character");
457       int uMax = Params.getInt(context, "callout.unicode.number.limit");
458       return insertUnicodeCallouts(areaspecNodeSet, xalanNI, defaultColumn,
459            uStart, uMax, useFO);
460     } else if (Params.getBoolean(context, "callout.dingbats")) {
461       int dMax = 10;
462       return insertDingbatCallouts(areaspecNodeSet, xalanNI, defaultColumn,
463            dMax, useFO);
464     } else {
465       return insertTextCallouts(areaspecNodeSet, xalanNI, defaultColumn, useFO);
466     }
467   }
468 
469   public DocumentFragment insertGraphicCallouts (NodeIterator areaspecNodeSet,
470              NodeIterator xalanNI,
471              int defaultColumn,
472              String gPath,
473              String gExt,
474              int gMax,
475              boolean useFO) {
476     FormatGraphicCallout fgc = new FormatGraphicCallout(gPath,gExt,gMax,useFO);
477     return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, fgc);
478   }
479 
480   public DocumentFragment insertUnicodeCallouts (NodeIterator areaspecNodeSet,
481              NodeIterator xalanNI,
482              int defaultColumn,
483              int uStart,
484              int uMax,
485              boolean useFO) {
486     FormatUnicodeCallout fuc = new FormatUnicodeCallout(uStart, uMax, useFO);
487     return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, fuc);
488   }
489 
490   public DocumentFragment insertDingbatCallouts (NodeIterator areaspecNodeSet,
491              NodeIterator xalanNI,
492              int defaultColumn,
493              int gMax,
494              boolean useFO) {
495     FormatDingbatCallout fdc = new FormatDingbatCallout(gMax,useFO);
496     return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, fdc);
497   }
498 
499   public DocumentFragment insertTextCallouts (NodeIterator areaspecNodeSet,
500                 NodeIterator xalanNI,
501                 int defaultColumn,
502                 boolean useFO) {
503     FormatTextCallout ftc = new FormatTextCallout(useFO);
504     return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, ftc);
505   }
506 
507   public DocumentFragment insertCallouts (NodeIterator areaspecNodeSet,
508             NodeIterator xalanNI,
509             int defaultColumn,
510             FormatCallout fCallout) {
511 
512     DocumentFragment xalanRTF = (DocumentFragment) xalanNI.nextNode();
513 
514     callout = new Callout[10];
515     calloutCount = 0;
516     calloutPos = 0;
517     lineNumber = 1;
518     colNumber = 1;
519 
520     // First we walk through the areaspec to calculate the position
521     // of the callouts
522     //  <areaspec>
523     //  <areaset id="ex.plco.const" coords="">
524     //    <area id="ex.plco.c1" coords="4"/>
525     //    <area id="ex.plco.c2" coords="8"/>
526     //  </areaset>
527     //  <area id="ex.plco.ret" coords="12"/>
528     //  <area id="ex.plco.dest" coords="12"/>
529     //  </areaspec>
530     int pos = 0;
531     int coNum = 0;
532     boolean inAreaSet = false;
533     Node node = areaspecNodeSet.nextNode();
534     node = node.getFirstChild();
535     while (node != null) {
536       if (node.getNodeType() == Node.ELEMENT_NODE) {
537   if (node.getNodeName().equals("areaset")) {
538     coNum++;
539     Node area = node.getFirstChild();
540     while (area != null) {
541       if (area.getNodeType() == Node.ELEMENT_NODE) {
542         if (area.getNodeName().equals("area")) {
543     addCallout(coNum, area, defaultColumn);
544         } else {
545     System.out.println("Unexpected element in areaset: "
546            + area.getNodeName());
547         }
548       }
549       area = area.getNextSibling();
550     }
551   } else if (node.getNodeName().equalsIgnoreCase("area")) {
552     coNum++;
553     addCallout(coNum, node, defaultColumn);
554   } else {
555     System.out.println("Unexpected element in areaspec: "
556            + node.getNodeName());
557   }
558       }
559 
560       node = node.getNextSibling();
561     }
562 
563     // Now sort them
564     java.util.Arrays.sort(callout, 0, calloutCount);
565 
566     DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
567     DocumentBuilder docBuilder = null;
568 
569     try {
570       docBuilder = docFactory.newDocumentBuilder();
571     } catch (ParserConfigurationException e) {
572       System.out.println("PCE 2!");
573       return xalanRTF;
574     }
575     Document doc = docBuilder.newDocument();
576     DocumentFragment df = doc.createDocumentFragment();
577     DOMBuilder db = new DOMBuilder(doc, df);
578 
579     elementStack = new Stack();
580     calloutFragment(db, xalanRTF, fCallout);
581     return df;
582   }
583 
584   /**
585    * <p>Build a FragmentValue with callout decorations.</p>
586    *
587    * <p>This is the method that actually does the work of adding
588    * callouts to a verbatim environment. It recursively walks through a
589    * tree of nodes, copying the structure into the rtf. Text nodes
590    * are examined for the position of callouts as described by the
591    * global callout parameters.</p>
592    *
593    * <p>When called, rtf should be an empty FragmentValue and node
594    * should be the first child of the result tree fragment that contains
595    * the existing, formatted verbatim text.</p>
596    *
597    * @param rtf The resulting verbatim environment with numbered lines.
598    * @param node The root of the tree to copy.
599    */
600   private void calloutFragment(DOMBuilder rtf,
601              Node node,
602              FormatCallout fCallout) {
603     try {
604       if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE
605   || node.getNodeType() == Node.DOCUMENT_NODE) {
606   Node child = node.getFirstChild();
607   while (child != null) {
608     calloutFragment(rtf, child, fCallout);
609     child = child.getNextSibling();
610   }
611       } else if (node.getNodeType() == Node.ELEMENT_NODE) {
612   String ns = node.getNamespaceURI();
613   String localName = node.getLocalName();
614   String name = ((Element) node).getTagName();
615 
616   rtf.startElement(ns, localName, name,
617        copyAttributes((Element) node));
618 
619   elementStack.push(node);
620 
621   Node child = node.getFirstChild();
622   while (child != null) {
623     calloutFragment(rtf, child, fCallout);
624     child = child.getNextSibling();
625   }
626       } else if (node.getNodeType() == Node.TEXT_NODE) {
627   String text = node.getNodeValue();
628 
629   char chars[] = text.toCharArray();
630   int pos = 0;
631   for (int count = 0; count < text.length(); count++) {
632     if (calloutPos < calloutCount
633         && callout[calloutPos].getLine() == lineNumber
634         && callout[calloutPos].getColumn() == colNumber) {
635       if (pos > 0) {
636         rtf.characters(chars, 0, pos);
637         pos = 0;
638       }
639 
640       closeOpenElements(rtf);
641 
642       while (calloutPos < calloutCount
643        && callout[calloutPos].getLine() == lineNumber
644        && callout[calloutPos].getColumn() == colNumber) {
645         fCallout.formatCallout(rtf, callout[calloutPos]);
646         calloutPos++;
647       }
648 
649       openClosedElements(rtf);
650     }
651 
652     if (text.charAt(count) == '\n') {
653       // What if we need to pad this line?
654       if (calloutPos < calloutCount
655     && callout[calloutPos].getLine() == lineNumber
656     && callout[calloutPos].getColumn() > colNumber) {
657 
658         if (pos > 0) {
659     rtf.characters(chars, 0, pos);
660     pos = 0;
661         }
662 
663         closeOpenElements(rtf);
664 
665         while (calloutPos < calloutCount
666          && callout[calloutPos].getLine() == lineNumber
667          && callout[calloutPos].getColumn() > colNumber) {
668     formatPad(rtf, callout[calloutPos].getColumn() - colNumber);
669     colNumber = callout[calloutPos].getColumn();
670     while (calloutPos < calloutCount
671            && callout[calloutPos].getLine() == lineNumber
672            && callout[calloutPos].getColumn() == colNumber) {
673       fCallout.formatCallout(rtf, callout[calloutPos]);
674       calloutPos++;
675     }
676         }
677 
678         openClosedElements(rtf);
679       }
680 
681       lineNumber++;
682       colNumber = 1;
683     } else {
684       colNumber++;
685     }
686     chars[pos++] = text.charAt(count);
687   }
688 
689   if (pos > 0) {
690     rtf.characters(chars, 0, pos);
691   }
692       } else if (node.getNodeType() == Node.COMMENT_NODE) {
693   String text = node.getNodeValue();
694   char chars[] = text.toCharArray();
695   rtf.comment(chars, 0, text.length());
696       } else if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
697   rtf.processingInstruction(node.getNodeName(), node.getNodeValue());
698       } else {
699   System.out.println("Warning: unexpected node type in calloutFragment: " + node.getNodeType() + ": " + node.getNodeName());
700       }
701 
702       if (node.getNodeType() == Node.ELEMENT_NODE) {
703   String ns = node.getNamespaceURI();
704   String localName = node.getLocalName();
705   String name = ((Element) node).getTagName();
706   rtf.endElement(ns, localName, name);
707   elementStack.pop();
708       } else {
709   // nop
710       }
711     } catch (SAXException e) {
712       System.out.println("SAX Exception in calloutFragment");
713     }
714   }
715 
716   /**
717    * <p>Add a callout to the global callout array</p>
718    *
719    * <p>This method examines a callout <tt>area</tt> and adds it to
720    * the global callout array if it can be interpreted.</p>
721    *
722    * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are
723    * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed.
724    * If only a line is specified, the callout decoration appears in
725    * the <tt>defaultColumn</tt>.</p>
726    *
727    * @param coNum The callout number.
728    * @param node The <tt>area</tt>.
729    * @param defaultColumn The default column for callouts.
730    */
731   private void addCallout (int coNum,
732          Node node,
733          int defaultColumn) {
734     Element area = (Element) node;
735 
736     String units = area.getAttribute("units");
737     String otherUnits = area.getAttribute("otherunits");
738     String coords = area.getAttribute("coords");
739     int type = 0;
740     String otherType = null;
741 
742     if (units == null || units.equals("linecolumn")) {
743       type = Callout.LINE_COLUMN; // the default
744     } else if (units.equals("linerange")) {
745       type = Callout.LINE_RANGE;
746     } else if (units.equals("linecolumnpair")) {
747       type = Callout.LINE_COLUMN_PAIR;
748     } else if (units.equals("calspair")) {
749       type = Callout.CALS_PAIR;
750     } else {
751       type = Callout.OTHER;
752       otherType = otherUnits;
753     }
754 
755     if (type != Callout.LINE_COLUMN
756   && type != Callout.LINE_RANGE) {
757       System.out.println("Only linecolumn and linerange units are supported");
758       return;
759     }
760 
761     if (coords == null) {
762       System.out.println("Coords must be specified");
763       return;
764     }
765 
766     // Now let's see if we can interpret the coordinates...
767     StringTokenizer st = new StringTokenizer(coords);
768     int tokenCount = 0;
769     int c1 = 0;
770     int c2 = 0;
771     while (st.hasMoreTokens()) {
772       tokenCount++;
773       if (tokenCount > 2) {
774   System.out.println("Unparseable coordinates");
775   return;
776       }
777       try {
778   String token = st.nextToken();
779   int coord = Integer.parseInt(token);
780   c2 = coord;
781   if (tokenCount == 1) {
782     c1 = coord;
783   }
784       } catch (NumberFormatException e) {
785   System.out.println("Unparseable coordinate");
786   return;
787       }
788     }
789 
790     // Make sure we aren't going to blow past the end of our array
791     if (calloutCount == callout.length) {
792       Callout bigger[] = new Callout[calloutCount+10];
793       for (int count = 0; count < callout.length; count++) {
794   bigger[count] = callout[count];
795       }
796       callout = bigger;
797     }
798 
799     // Ok, add the callout
800     if (tokenCount == 2) {
801       if (type == Callout.LINE_RANGE) {
802   for (int count = c1; count <= c2; count++) {
803     callout[calloutCount++] = new Callout(coNum, area,
804             count, defaultColumn,
805             type);
806   }
807       } else {
808   // assume linecolumn
809   callout[calloutCount++] = new Callout(coNum, area, c1, c2, type);
810       }
811     } else {
812       // if there's only one number, assume it's the line
813       callout[calloutCount++] = new Callout(coNum, area, c1, defaultColumn, type);
814     }
815   }
816 
817   /**
818    * <p>Add blanks to the result tree fragment.</p>
819    *
820    * <p>This method adds <tt>numBlanks</tt> to the result tree fragment.
821    * It's used to pad lines when callouts occur after the last existing
822    * characater in a line.</p>
823    *
824    * @param rtf The resulting verbatim environment with numbered lines.
825    * @param numBlanks The number of blanks to add.
826    */
827   private void formatPad(DOMBuilder rtf,
828        int numBlanks) {
829     char chars[] = new char[numBlanks];
830     for (int count = 0; count < numBlanks; count++) {
831       chars[count] = ' ';
832     }
833 
834     try {
835       rtf.characters(chars, 0, numBlanks);
836     } catch (SAXException e) {
837       System.out.println("SAX Exception in formatCallout");
838     }
839   }
840 
841   private void closeOpenElements(DOMBuilder rtf)
842     throws SAXException {
843     // Close all the open elements...
844     tempStack = new Stack();
845     while (!elementStack.empty()) {
846       Node elem = (Node) elementStack.pop();
847 
848       String ns = elem.getNamespaceURI();
849       String localName = elem.getLocalName();
850       String name = ((Element) elem).getTagName();
851 
852       // If this is the bottom of the stack and it's an fo:block
853       // or an HTML pre or div, don't duplicate it...
854       if (elementStack.empty()
855     && (((ns != null)
856          && ns.equals(foURI)
857          && localName.equals("block"))
858         || (((ns == null)
859        && localName.equalsIgnoreCase("pre"))
860       || ((ns != null)
861           && ns.equals(xhURI)
862           && localName.equals("pre")))
863         || (((ns == null)
864        && localName.equalsIgnoreCase("div"))
865       || ((ns != null)
866           && ns.equals(xhURI)
867           && localName.equals("div"))))) {
868   elementStack.push(elem);
869   break;
870       } else {
871   rtf.endElement(ns, localName, name);
872   tempStack.push(elem);
873       }
874     }
875   }
876 
877   private void openClosedElements(DOMBuilder rtf)
878     throws SAXException {
879     // Now "reopen" the elements that we closed...
880     while (!tempStack.empty()) {
881       Node elem = (Node) tempStack.pop();
882 
883       String ns = elem.getNamespaceURI();
884       String localName = elem.getLocalName();
885       String name = ((Element) elem).getTagName();
886       NamedNodeMap domAttr = elem.getAttributes();
887 
888       AttributesImpl attr = new AttributesImpl();
889       for (int acount = 0; acount < domAttr.getLength(); acount++) {
890   Node a = domAttr.item(acount);
891 
892   if (((ns == null || ns == "http://www.w3.org/1999/xhtml")
893        && localName.equalsIgnoreCase("a"))
894       || (a.getLocalName().equalsIgnoreCase("id"))) {
895     // skip this attribute
896   } else {
897     attr.addAttribute(a.getNamespaceURI(),
898           a.getLocalName(),
899           a.getNodeName(),
900           "CDATA",
901           a.getNodeValue());
902   }
903       }
904 
905       rtf.startElement(ns, localName, name, attr);
906       elementStack.push(elem);
907     }
908 
909     tempStack = null;
910   }
911 
912   private Attributes copyAttributes(Element node) {
913     AttributesImpl attrs = new AttributesImpl();
914     NamedNodeMap nnm = node.getAttributes();
915     for (int count = 0; count < nnm.getLength(); count++) {
916       Attr attr = (Attr) nnm.item(count);
917       String name = attr.getName();
918       if (name.startsWith("xmlns:") || name.equals("xmlns")) {
919   // Skip it; (don't ya just love it!!)
920       } else {
921   attrs.addAttribute(attr.getNamespaceURI(), attr.getName(),
922          attr.getName(), "CDATA", attr.getValue());
923       }
924     }
925     return attrs;
926   }
927 }