KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > nwalsh > xalan > Verbatim


1 // Verbatim.java - Xalan extensions supporting DocBook verbatim environments
2

3 package com.nwalsh.xalan;
4
5 import java.util.Stack JavaDoc;
6 import java.util.StringTokenizer JavaDoc;
7
8 import org.xml.sax.*;
9 import org.xml.sax.helpers.AttributesImpl JavaDoc;
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 JavaDoc;
31 import javax.xml.transform.TransformerException JavaDoc;
32 import javax.xml.parsers.DocumentBuilder JavaDoc;
33 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
34 import javax.xml.parsers.ParserConfigurationException JavaDoc;
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  * <p>$Id: Verbatim.java,v 1.5 2003/12/17 01:01:34 nwalsh Exp $</p>
43  *
44  * <p>Copyright (C) 2001 Norman Walsh.</p>
45  *
46  * <p>This class provides a
47  * <a HREF="http://xml.apache.org/xalan">Xalan</a>
48  * implementation of two features that would be impractical to
49  * implement directly in XSLT: line numbering and callouts.</p>
50  *
51  * <p><b>Line Numbering</b></p>
52  * <p>The <tt>numberLines</tt> family of functions takes a result tree
53  * fragment (assumed to contain the contents of a formatted verbatim
54  * element in DocBook: programlisting, screen, address, literallayout,
55  * or synopsis) and returns a result tree fragment decorated with
56  * line numbers.</p>
57  *
58  * <p><b>Callouts</b></p>
59  * <p>The <tt>insertCallouts</tt> family of functions takes an
60  * <tt>areaspec</tt> and a result tree fragment
61  * (assumed to contain the contents of a formatted verbatim
62  * element in DocBook: programlisting, screen, address, literallayout,
63  * or synopsis) and returns a result tree fragment decorated with
64  * callouts.</p>
65  *
66  * <p><b>Change Log:</b></p>
67  * <dl>
68  * <dt>1.0</dt>
69  * <dd><p>Initial release.</p></dd>
70  * </dl>
71  *
72  * @author Norman Walsh
73  * <a HREF="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
74  *
75  * @version $Id: Verbatim.java,v 1.5 2003/12/17 01:01:34 nwalsh Exp $
76  *
77  */

78 public class Verbatim {
79   /** A stack to hold the open elements while walking through a RTF. */
80   private Stack JavaDoc elementStack = null;
81   /** A stack to hold the temporarily closed elements. */
82   private Stack JavaDoc tempStack = null;
83   /** The current line number. */
84   private int lineNumber = 0;
85   /** The current column number. */
86   private int colNumber = 0;
87   /** The modulus for line numbering (every 'modulus' line is numbered). */
88   private int modulus = 0;
89   /** The width (in characters) of line numbers (for padding). */
90   private int width = 0;
91   /** The separator between the line number and the verbatim text. */
92   private String JavaDoc separator = "";
93   /** The (sorted) array of callouts obtained from the areaspec. */
94   private Callout callout[] = null;
95   /** The number of callouts in the callout array. */
96   private int calloutCount = 0;
97   /** A pointer used to keep track of our position in the callout array. */
98   private int calloutPos = 0;
99   /** The path to use for graphical callout decorations. */
100   private String JavaDoc graphicsPath = null;
101   /** The extension to use for graphical callout decorations. */
102   private String JavaDoc graphicsExt = null;
103   /** The largest callout number that can be represented graphically. */
104   private int graphicsMax = 10;
105   /** Should graphic callouts use fo:external-graphics or imgs. */
106   private boolean graphicsFO = false;
107
108   private static final String JavaDoc foURI = "http://www.w3.org/1999/XSL/Format";
109   private static final String JavaDoc xhURI = "http://www.w3.org/1999/xhtml";
110
111   /**
112    * <p>Constructor for Verbatim</p>
113    *
114    * <p>All of the methods are static, so the constructor does nothing.</p>
115    */

116   public Verbatim() {
117   }
118
119   /**
120    * <p>Number lines in a verbatim environment.</p>
121    *
122    * <p>This method adds line numbers to a result tree fragment. Each
123    * newline that occurs in a text node is assumed to start a new line.
124    * The first line is always numbered, every subsequent xalanMod line
125    * is numbered (so if xalanMod=5, lines 1, 5, 10, 15, etc. will be
126    * numbered. If there are fewer than xalanMod lines in the environment,
127    * every line is numbered.</p>
128    *
129    * <p>xalanMod is taken from the $linenumbering.everyNth parameter.</p>
130    *
131    * <p>Every line number will be right justified in a string xalanWidth
132    * characters long. If the line number of the last line in the
133    * environment is too long to fit in the specified width, the width
134    * is automatically increased to the smallest value that can hold the
135    * number of the last line. (In other words, if you specify the value 2
136    * and attempt to enumerate the lines of an environment that is 100 lines
137    * long, the value 3 will automatically be used for every line in the
138    * environment.)</p>
139    *
140    * <p>xalanWidth is taken from the $linenumbering.width parameter.</p>
141    *
142    * <p>The xalanSep string is inserted between the line
143    * number and the original program listing. Lines that aren't numbered
144    * are preceded by a xalanWidth blank string and the separator.</p>
145    *
146    * <p>xalanSep is taken from the $linenumbering.separator parameter.</p>
147    *
148    * <p>If inline markup extends across line breaks, markup changes are
149    * required. All the open elements are closed before the line break and
150    * "reopened" afterwards. The reopened elements will have the same
151    * attributes as the originals, except that 'name' and 'id' attributes
152    * are not duplicated.</p>
153    *
154    * @param xalanRTF The result tree fragment of the verbatim environment.
155    *
156    * @return The modified result tree fragment.
157    */

158   public DocumentFragment numberLines (ExpressionContext context,
159                        NodeIterator xalanNI) {
160
161     int xalanMod = Params.getInt(context, "linenumbering.everyNth");
162     int xalanWidth = Params.getInt(context, "linenumbering.width");
163     String JavaDoc xalanSep = Params.getString(context, "linenumbering.separator");
164
165     DocumentFragment xalanRTF = (DocumentFragment) xalanNI.nextNode();
166     int numLines = countLineBreaks(xalanRTF) + 1;
167
168     DocumentBuilderFactory JavaDoc docFactory = DocumentBuilderFactory.newInstance();
169     DocumentBuilder JavaDoc docBuilder = null;
170
171     try {
172       docBuilder = docFactory.newDocumentBuilder();
173     } catch (ParserConfigurationException JavaDoc e) {
174       System.out.println("PCE!");
175       return xalanRTF;
176     }
177     Document doc = docBuilder.newDocument();
178     DocumentFragment df = doc.createDocumentFragment();
179     DOMBuilder db = new DOMBuilder(doc, df);
180
181     elementStack = new Stack JavaDoc();
182     lineNumber = 0;
183     modulus = numLines < xalanMod ? 1 : xalanMod;
184     width = xalanWidth;
185     separator = xalanSep;
186
187     double log10numLines = Math.log(numLines) / Math.log(10);
188
189     if (width < log10numLines + 1) {
190       width = (int) Math.floor(log10numLines + 1);
191     }
192
193     lineNumberFragment(db, xalanRTF);
194     return df;
195   }
196
197   /**
198    * <p>Count the number of lines in a verbatim environment.</p>
199    *
200    * <p>This method walks over the nodes of a DocumentFragment and
201    * returns the number of lines breaks that it contains.</p>
202    *
203    * @param node The root of the tree walk over.
204    */

205   private int countLineBreaks(Node node) {
206     int numLines = 0;
207
208     if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE
209     || node.getNodeType() == Node.DOCUMENT_NODE
210     || node.getNodeType() == Node.ELEMENT_NODE) {
211       Node child = node.getFirstChild();
212       while (child != null) {
213     numLines += countLineBreaks(child);
214     child = child.getNextSibling();
215       }
216     } else if (node.getNodeType() == Node.TEXT_NODE) {
217       String JavaDoc text = node.getNodeValue();
218
219       // Walk through the text node looking for newlines
220
int pos = 0;
221       for (int count = 0; count < text.length(); count++) {
222     if (text.charAt(count) == '\n') {
223       numLines++;
224     }
225       }
226     } else {
227       // nop
228
}
229
230     return numLines;
231   }
232
233   /**
234    * <p>Build a DocumentFragment with numbered lines.</p>
235    *
236    * <p>This is the method that actually does the work of numbering
237    * lines in a verbatim environment. It recursively walks through a
238    * tree of nodes, copying the structure into the rtf. Text nodes
239    * are examined for new lines and modified as requested by the
240    * global line numbering parameters.</p>
241    *
242    * <p>When called, rtf should be an empty DocumentFragment and node
243    * should be the first child of the result tree fragment that contains
244    * the existing, formatted verbatim text.</p>
245    *
246    * @param rtf The resulting verbatim environment with numbered lines.
247    * @param node The root of the tree to copy.
248    */

249   private void lineNumberFragment(DOMBuilder rtf,
250                   Node node) {
251     try {
252       if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE
253       || node.getNodeType() == Node.DOCUMENT_NODE) {
254     Node child = node.getFirstChild();
255     while (child != null) {
256       lineNumberFragment(rtf, child);
257       child = child.getNextSibling();
258     }
259       } else if (node.getNodeType() == Node.ELEMENT_NODE) {
260     String JavaDoc ns = node.getNamespaceURI();
261     String JavaDoc localName = node.getLocalName();
262     String JavaDoc name = ((Element) node).getTagName();
263
264     rtf.startElement(ns, localName, name,
265              copyAttributes((Element) node));
266
267     elementStack.push(node);
268
269     Node child = node.getFirstChild();
270     while (child != null) {
271       lineNumberFragment(rtf, child);
272       child = child.getNextSibling();
273     }
274       } else if (node.getNodeType() == Node.TEXT_NODE) {
275     String JavaDoc text = node.getNodeValue();
276
277     if (lineNumber == 0) {
278       // The first line is always numbered
279
formatLineNumber(rtf, ++lineNumber);
280     }
281
282     // Walk through the text node looking for newlines
283
char chars[] = text.toCharArray();
284     int pos = 0;
285     for (int count = 0; count < text.length(); count++) {
286       if (text.charAt(count) == '\n') {
287         // This is the tricky bit; if we find a newline, make sure
288
// it doesn't occur inside any markup.
289

290         if (pos > 0) {
291           rtf.characters(chars, 0, pos);
292           pos = 0;
293         }
294
295         closeOpenElements(rtf);
296
297         // Copy the newline to the output
298
chars[pos++] = text.charAt(count);
299         rtf.characters(chars, 0, pos);
300         pos = 0;
301
302         // Add the line number
303
formatLineNumber(rtf, ++lineNumber);
304
305         openClosedElements(rtf);
306       } else {
307         chars[pos++] = text.charAt(count);
308       }
309     }
310
311     if (pos > 0) {
312       rtf.characters(chars, 0, pos);
313     }
314       } else if (node.getNodeType() == Node.COMMENT_NODE) {
315     String JavaDoc text = node.getNodeValue();
316     char chars[] = text.toCharArray();
317     rtf.comment(chars, 0, text.length());
318       } else if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
319     rtf.processingInstruction(node.getNodeName(), node.getNodeValue());
320       } else {
321     System.out.println("Warning: unexpected node type in lineNumberFragment");
322       }
323
324       if (node.getNodeType() == Node.ELEMENT_NODE) {
325     String JavaDoc ns = node.getNamespaceURI();
326     String JavaDoc localName = node.getLocalName();
327     String JavaDoc name = ((Element) node).getTagName();
328     rtf.endElement(ns, localName, name);
329     elementStack.pop();
330       }
331     } catch (SAXException e) {
332       System.out.println("SAX Exception in lineNumberFragment");
333     }
334   }
335
336   /**
337    * <p>Add a formatted line number to the result tree fragment.</p>
338    *
339    * <p>This method examines the global parameters that control line
340    * number presentation (modulus, width, and separator) and adds
341    * the appropriate text to the result tree fragment.</p>
342    *
343    * @param rtf The resulting verbatim environment with numbered lines.
344    * @param lineNumber The number of the current line.
345    */

346   private void formatLineNumber(DOMBuilder rtf,
347                 int lineNumber) {
348     char ch = 160;
349     String JavaDoc lno = "";
350     if (lineNumber == 1
351     || (modulus >= 1 && (lineNumber % modulus == 0))) {
352       lno = "" + lineNumber;
353     }
354
355     while (lno.length() < width) {
356       lno = ch + lno;
357     }
358
359     lno += separator;
360
361     char chars[] = lno.toCharArray();
362     try {
363       rtf.characters(chars, 0, lno.length());
364     } catch (SAXException e) {
365       System.out.println("SAX Exception in formatLineNumber");
366     }
367   }
368
369   /**
370    * <p>Insert text callouts into a verbatim environment.</p>
371    *
372    * <p>This method examines the <tt>areaset</tt> and <tt>area</tt> elements
373    * in the supplied <tt>areaspec</tt> and decorates the supplied
374    * result tree fragment with appropriate callout markers.</p>
375    *
376    * <p>If a <tt>label</tt> attribute is supplied on an <tt>area</tt>,
377    * its content will be used for the label, otherwise the callout
378    * number will be used, surrounded by parenthesis. Callouts are
379    * numbered in document order. All of the <tt>area</tt>s in an
380    * <tt>areaset</tt> get the same number.</p>
381    *
382    * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are
383    * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed.
384    * If only a line is specified, the callout decoration appears in
385    * the defaultColumn. Lines will be padded with blanks to reach the
386    * necessary column, but callouts that are located beyond the last
387    * line of the verbatim environment will be ignored.</p>
388    *
389    * <p>Callouts are inserted before the character at the line/column
390    * where they are to occur.</p>
391    *
392    * @param areaspecNodeSet The source node set that contains the areaspec.
393    * @param xalanRTF The result tree fragment of the verbatim environment.
394    * @param defaultColumn The column for callouts that specify only a line.
395    *
396    * @return The modified result tree fragment. */

397
398   /**
399    * <p>Insert graphical 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. Callouts are
408    * numbered in document order. All of the <tt>area</tt>s in an
409    * <tt>areaset</tt> get the same number.</p>
410    *
411    * <p>If the callout number is not greater than <tt>gMax</tt>, the
412    * callout generated will be:</p>
413    *
414    * <pre>
415    * &lt;img SRC="$gPath/conumber$gExt" alt="conumber">
416    * </pre>
417    *
418    * <p>Otherwise, it will be the callout number surrounded by
419    * parenthesis.</p>
420    *
421    * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are
422    * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed.
423    * If only a line is specified, the callout decoration appears in
424    * the defaultColumn. Lines will be padded with blanks to reach the
425    * necessary column, but callouts that are located beyond the last
426    * line of the verbatim environment will be ignored.</p>
427    *
428    * <p>Callouts are inserted before the character at the line/column
429    * where they are to occur.</p>
430    *
431    * @param areaspecNodeSet The source node set that contains the areaspec.
432    * @param xalanRTF The result tree fragment of the verbatim environment.
433    * @param defaultColumn The column for callouts that specify only a line.
434    * @param gPath The path to use for callout graphics.
435    * @param gExt The extension to use for callout graphics.
436    * @param gMax The largest number that can be represented as a graphic.
437    * @param useFO Should fo:external-graphics be produced, as opposed to
438    * HTML imgs. This is bogus, the extension should figure it out, but I
439    * haven't figured out how to do that yet.
440    *
441    * @return The modified result tree fragment.
442    */

443
444   public DocumentFragment insertCallouts (ExpressionContext context,
445                       NodeIterator areaspecNodeSet,
446                       NodeIterator xalanNI) {
447     String JavaDoc type = Params.getString(context, "stylesheet.result.type");
448     boolean useFO = type.equals("fo");
449     int defaultColumn = Params.getInt(context, "callout.defaultcolumn");
450
451     if (Params.getBoolean(context, "callout.graphics")) {
452       String JavaDoc gPath = Params.getString(context, "callout.graphics.path");
453       String JavaDoc gExt = Params.getString(context, "callout.graphics.extension");
454       int gMax = Params.getInt(context, "callout.graphics.number.limit");
455       return insertGraphicCallouts(areaspecNodeSet, xalanNI, defaultColumn,
456                    gPath, gExt, gMax, useFO);
457     } else if (Params.getBoolean(context, "callout.unicode")) {
458       int uStart = Params.getInt(context, "callout.unicode.start.character");
459       int uMax = Params.getInt(context, "callout.unicode.number.limit");
460       String JavaDoc uFont = Params.getString(context, "callout.unicode.font");
461       return insertUnicodeCallouts(areaspecNodeSet, xalanNI, defaultColumn,
462                    uFont, uStart, uMax, useFO);
463     } else if (Params.getBoolean(context, "callout.dingbats")) {
464       int dMax = 10;
465       return insertDingbatCallouts(areaspecNodeSet, xalanNI, defaultColumn,
466                    dMax, useFO);
467     } else {
468       return insertTextCallouts(areaspecNodeSet, xalanNI, defaultColumn, useFO);
469     }
470   }
471
472   public DocumentFragment insertGraphicCallouts (NodeIterator areaspecNodeSet,
473                          NodeIterator xalanNI,
474                          int defaultColumn,
475                          String JavaDoc gPath,
476                          String JavaDoc gExt,
477                          int gMax,
478                          boolean useFO) {
479     FormatGraphicCallout fgc = new FormatGraphicCallout(gPath,gExt,gMax,useFO);
480     return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, fgc);
481   }
482
483   public DocumentFragment insertUnicodeCallouts (NodeIterator areaspecNodeSet,
484                          NodeIterator xalanNI,
485                          int defaultColumn,
486                          String JavaDoc uFont,
487                          int uStart,
488                          int uMax,
489                          boolean useFO) {
490     FormatUnicodeCallout fuc = new FormatUnicodeCallout(uFont, uStart, uMax, useFO);
491     return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, fuc);
492   }
493
494   public DocumentFragment insertDingbatCallouts (NodeIterator areaspecNodeSet,
495                          NodeIterator xalanNI,
496                          int defaultColumn,
497                          int gMax,
498                          boolean useFO) {
499     FormatDingbatCallout fdc = new FormatDingbatCallout(gMax,useFO);
500     return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, fdc);
501   }
502
503   public DocumentFragment insertTextCallouts (NodeIterator areaspecNodeSet,
504                           NodeIterator xalanNI,
505                           int defaultColumn,
506                           boolean useFO) {
507     FormatTextCallout ftc = new FormatTextCallout(useFO);
508     return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, ftc);
509   }
510
511   public DocumentFragment insertCallouts (NodeIterator areaspecNodeSet,
512                       NodeIterator xalanNI,
513                       int defaultColumn,
514                       FormatCallout fCallout) {
515
516     DocumentFragment xalanRTF = (DocumentFragment) xalanNI.nextNode();
517
518     callout = new Callout[10];
519     calloutCount = 0;
520     calloutPos = 0;
521     lineNumber = 1;
522     colNumber = 1;
523
524     // First we walk through the areaspec to calculate the position
525
// of the callouts
526
// <areaspec>
527
// <areaset id="ex.plco.const" coords="">
528
// <area id="ex.plco.c1" coords="4"/>
529
// <area id="ex.plco.c2" coords="8"/>
530
// </areaset>
531
// <area id="ex.plco.ret" coords="12"/>
532
// <area id="ex.plco.dest" coords="12"/>
533
// </areaspec>
534
int pos = 0;
535     int coNum = 0;
536     boolean inAreaSet = false;
537     Node node = areaspecNodeSet.nextNode();
538     node = node.getFirstChild();
539     while (node != null) {
540       if (node.getNodeType() == Node.ELEMENT_NODE) {
541     if (node.getNodeName().equals("areaset")) {
542       coNum++;
543       Node area = node.getFirstChild();
544       while (area != null) {
545         if (area.getNodeType() == Node.ELEMENT_NODE) {
546           if (area.getNodeName().equals("area")) {
547         addCallout(coNum, area, defaultColumn);
548           } else {
549         System.out.println("Unexpected element in areaset: "
550                    + area.getNodeName());
551           }
552         }
553         area = area.getNextSibling();
554       }
555     } else if (node.getNodeName().equalsIgnoreCase("area")) {
556       coNum++;
557       addCallout(coNum, node, defaultColumn);
558     } else {
559       System.out.println("Unexpected element in areaspec: "
560                  + node.getNodeName());
561     }
562       }
563
564       node = node.getNextSibling();
565     }
566
567     // Now sort them
568
java.util.Arrays.sort(callout, 0, calloutCount);
569
570     DocumentBuilderFactory JavaDoc docFactory = DocumentBuilderFactory.newInstance();
571     DocumentBuilder JavaDoc docBuilder = null;
572
573     try {
574       docBuilder = docFactory.newDocumentBuilder();
575     } catch (ParserConfigurationException JavaDoc e) {
576       System.out.println("PCE 2!");
577       return xalanRTF;
578     }
579     Document doc = docBuilder.newDocument();
580     DocumentFragment df = doc.createDocumentFragment();
581     DOMBuilder db = new DOMBuilder(doc, df);
582
583     elementStack = new Stack JavaDoc();
584     calloutFragment(db, xalanRTF, fCallout);
585     return df;
586   }
587
588   /**
589    * <p>Build a FragmentValue with callout decorations.</p>
590    *
591    * <p>This is the method that actually does the work of adding
592    * callouts to a verbatim environment. It recursively walks through a
593    * tree of nodes, copying the structure into the rtf. Text nodes
594    * are examined for the position of callouts as described by the
595    * global callout parameters.</p>
596    *
597    * <p>When called, rtf should be an empty FragmentValue and node
598    * should be the first child of the result tree fragment that contains
599    * the existing, formatted verbatim text.</p>
600    *
601    * @param rtf The resulting verbatim environment with numbered lines.
602    * @param node The root of the tree to copy.
603    */

604   private void calloutFragment(DOMBuilder rtf,
605                    Node node,
606                    FormatCallout fCallout) {
607     try {
608       if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE
609     || node.getNodeType() == Node.DOCUMENT_NODE) {
610     Node child = node.getFirstChild();
611     while (child != null) {
612       calloutFragment(rtf, child, fCallout);
613       child = child.getNextSibling();
614     }
615       } else if (node.getNodeType() == Node.ELEMENT_NODE) {
616     String JavaDoc ns = node.getNamespaceURI();
617     String JavaDoc localName = node.getLocalName();
618     String JavaDoc name = ((Element) node).getTagName();
619
620     rtf.startElement(ns, localName, name,
621              copyAttributes((Element) node));
622
623     elementStack.push(node);
624
625     Node child = node.getFirstChild();
626     while (child != null) {
627       calloutFragment(rtf, child, fCallout);
628       child = child.getNextSibling();
629     }
630       } else if (node.getNodeType() == Node.TEXT_NODE) {
631     String JavaDoc text = node.getNodeValue();
632
633     char chars[] = text.toCharArray();
634     int pos = 0;
635     for (int count = 0; count < text.length(); count++) {
636       if (calloutPos < calloutCount
637           && callout[calloutPos].getLine() == lineNumber
638           && callout[calloutPos].getColumn() == colNumber) {
639         if (pos > 0) {
640           rtf.characters(chars, 0, pos);
641           pos = 0;
642         }
643
644         closeOpenElements(rtf);
645
646         while (calloutPos < calloutCount
647            && callout[calloutPos].getLine() == lineNumber
648            && callout[calloutPos].getColumn() == colNumber) {
649           fCallout.formatCallout(rtf, callout[calloutPos]);
650           calloutPos++;
651         }
652
653         openClosedElements(rtf);
654       }
655
656       if (text.charAt(count) == '\n') {
657         // What if we need to pad this line?
658
if (calloutPos < calloutCount
659         && callout[calloutPos].getLine() == lineNumber
660         && callout[calloutPos].getColumn() > colNumber) {
661
662           if (pos > 0) {
663         rtf.characters(chars, 0, pos);
664         pos = 0;
665           }
666
667           closeOpenElements(rtf);
668
669           while (calloutPos < calloutCount
670              && callout[calloutPos].getLine() == lineNumber
671              && callout[calloutPos].getColumn() > colNumber) {
672         formatPad(rtf, callout[calloutPos].getColumn() - colNumber);
673         colNumber = callout[calloutPos].getColumn();
674         while (calloutPos < calloutCount
675                && callout[calloutPos].getLine() == lineNumber
676                && callout[calloutPos].getColumn() == colNumber) {
677           fCallout.formatCallout(rtf, callout[calloutPos]);
678           calloutPos++;
679         }
680           }
681
682           openClosedElements(rtf);
683         }
684
685         lineNumber++;
686         colNumber = 1;
687       } else {
688         colNumber++;
689       }
690       chars[pos++] = text.charAt(count);
691     }
692
693     if (pos > 0) {
694       rtf.characters(chars, 0, pos);
695     }
696       } else if (node.getNodeType() == Node.COMMENT_NODE) {
697     String JavaDoc text = node.getNodeValue();
698     char chars[] = text.toCharArray();
699     rtf.comment(chars, 0, text.length());
700       } else if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
701     rtf.processingInstruction(node.getNodeName(), node.getNodeValue());
702       } else {
703     System.out.println("Warning: unexpected node type in calloutFragment: " + node.getNodeType() + ": " + node.getNodeName());
704       }
705
706       if (node.getNodeType() == Node.ELEMENT_NODE) {
707     String JavaDoc ns = node.getNamespaceURI();
708     String JavaDoc localName = node.getLocalName();
709     String JavaDoc name = ((Element) node).getTagName();
710     rtf.endElement(ns, localName, name);
711     elementStack.pop();
712       } else {
713     // nop
714
}
715     } catch (SAXException e) {
716       System.out.println("SAX Exception in calloutFragment");
717     }
718   }
719
720   /**
721    * <p>Add a callout to the global callout array</p>
722    *
723    * <p>This method examines a callout <tt>area</tt> and adds it to
724    * the global callout array if it can be interpreted.</p>
725    *
726    * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are
727    * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed.
728    * If only a line is specified, the callout decoration appears in
729    * the <tt>defaultColumn</tt>.</p>
730    *
731    * @param coNum The callout number.
732    * @param node The <tt>area</tt>.
733    * @param defaultColumn The default column for callouts.
734    */

735   private void addCallout (int coNum,
736                Node node,
737                int defaultColumn) {
738     Element area = (Element) node;
739
740     String JavaDoc units = area.getAttribute("units");
741     String JavaDoc otherUnits = area.getAttribute("otherunits");
742     String JavaDoc coords = area.getAttribute("coords");
743     int type = 0;
744     String JavaDoc otherType = null;
745
746     if (units == null || units.equals("linecolumn")) {
747       type = Callout.LINE_COLUMN; // the default
748
} else if (units.equals("linerange")) {
749       type = Callout.LINE_RANGE;
750     } else if (units.equals("linecolumnpair")) {
751       type = Callout.LINE_COLUMN_PAIR;
752     } else if (units.equals("calspair")) {
753       type = Callout.CALS_PAIR;
754     } else {
755       type = Callout.OTHER;
756       otherType = otherUnits;
757     }
758
759     if (type != Callout.LINE_COLUMN
760     && type != Callout.LINE_RANGE) {
761       System.out.println("Only linecolumn and linerange units are supported");
762       return;
763     }
764
765     if (coords == null) {
766       System.out.println("Coords must be specified");
767       return;
768     }
769
770     // Now let's see if we can interpret the coordinates...
771
StringTokenizer JavaDoc st = new StringTokenizer JavaDoc(coords);
772     int tokenCount = 0;
773     int c1 = 0;
774     int c2 = 0;
775     while (st.hasMoreTokens()) {
776       tokenCount++;
777       if (tokenCount > 2) {
778     System.out.println("Unparseable coordinates");
779     return;
780       }
781       try {
782     String JavaDoc token = st.nextToken();
783     int coord = Integer.parseInt(token);
784     c2 = coord;
785     if (tokenCount == 1) {
786       c1 = coord;
787     }
788       } catch (NumberFormatException JavaDoc e) {
789     System.out.println("Unparseable coordinate");
790     return;
791       }
792     }
793
794     // Make sure we aren't going to blow past the end of our array
795
if (calloutCount == callout.length) {
796       Callout bigger[] = new Callout[calloutCount+10];
797       for (int count = 0; count < callout.length; count++) {
798     bigger[count] = callout[count];
799       }
800       callout = bigger;
801     }
802
803     // Ok, add the callout
804
if (tokenCount == 2) {
805       if (type == Callout.LINE_RANGE) {
806     for (int count = c1; count <= c2; count++) {
807       callout[calloutCount++] = new Callout(coNum, area,
808                         count, defaultColumn,
809                         type);
810     }
811       } else {
812     // assume linecolumn
813
callout[calloutCount++] = new Callout(coNum, area, c1, c2, type);
814       }
815     } else {
816       // if there's only one number, assume it's the line
817
callout[calloutCount++] = new Callout(coNum, area, c1, defaultColumn, type);
818     }
819   }
820
821   /**
822    * <p>Add blanks to the result tree fragment.</p>
823    *
824    * <p>This method adds <tt>numBlanks</tt> to the result tree fragment.
825    * It's used to pad lines when callouts occur after the last existing
826    * characater in a line.</p>
827    *
828    * @param rtf The resulting verbatim environment with numbered lines.
829    * @param numBlanks The number of blanks to add.
830    */

831   private void formatPad(DOMBuilder rtf,
832              int numBlanks) {
833     char chars[] = new char[numBlanks];
834     for (int count = 0; count < numBlanks; count++) {
835       chars[count] = ' ';
836     }
837
838     try {
839       rtf.characters(chars, 0, numBlanks);
840     } catch (SAXException e) {
841       System.out.println("SAX Exception in formatCallout");
842     }
843   }
844
845   private void closeOpenElements(DOMBuilder rtf)
846     throws SAXException {
847     // Close all the open elements...
848
tempStack = new Stack JavaDoc();
849     while (!elementStack.empty()) {
850       Node elem = (Node) elementStack.pop();
851
852       String JavaDoc ns = elem.getNamespaceURI();
853       String JavaDoc localName = elem.getLocalName();
854       String JavaDoc name = ((Element) elem).getTagName();
855
856       // If this is the bottom of the stack and it's an fo:block
857
// or an HTML pre or div, don't duplicate it...
858
if (elementStack.empty()
859       && (((ns != null)
860            && ns.equals(foURI)
861            && localName.equals("block"))
862           || (((ns == null)
863            && localName.equalsIgnoreCase("pre"))
864           || ((ns != null)
865               && ns.equals(xhURI)
866               && localName.equals("pre")))
867           || (((ns == null)
868            && localName.equalsIgnoreCase("div"))
869           || ((ns != null)
870               && ns.equals(xhURI)
871               && localName.equals("div"))))) {
872     elementStack.push(elem);
873     break;
874       } else {
875     rtf.endElement(ns, localName, name);
876     tempStack.push(elem);
877       }
878     }
879   }
880
881   private void openClosedElements(DOMBuilder rtf)
882     throws SAXException {
883     // Now "reopen" the elements that we closed...
884
while (!tempStack.empty()) {
885       Node elem = (Node) tempStack.pop();
886
887       String JavaDoc ns = elem.getNamespaceURI();
888       String JavaDoc localName = elem.getLocalName();
889       String JavaDoc name = ((Element) elem).getTagName();
890       NamedNodeMap domAttr = elem.getAttributes();
891
892       AttributesImpl JavaDoc attr = new AttributesImpl JavaDoc();
893       for (int acount = 0; acount < domAttr.getLength(); acount++) {
894     Node a = domAttr.item(acount);
895
896     if (((ns == null || ns == "http://www.w3.org/1999/xhtml")
897          && localName.equalsIgnoreCase("a"))
898         || (a.getLocalName().equalsIgnoreCase("id"))) {
899       // skip this attribute
900
} else {
901       attr.addAttribute(a.getNamespaceURI(),
902                 a.getLocalName(),
903                 a.getNodeName(),
904                 "CDATA",
905                 a.getNodeValue());
906     }
907       }
908
909       rtf.startElement(ns, localName, name, attr);
910       elementStack.push(elem);
911     }
912
913     tempStack = null;
914   }
915
916   private Attributes copyAttributes(Element node) {
917     AttributesImpl JavaDoc attrs = new AttributesImpl JavaDoc();
918     NamedNodeMap nnm = node.getAttributes();
919     for (int count = 0; count < nnm.getLength(); count++) {
920       Attr attr = (Attr) nnm.item(count);
921       String JavaDoc name = attr.getName();
922       if (name.startsWith("xmlns:") || name.equals("xmlns")) {
923     // Skip it; (don't ya just love it!!)
924
} else {
925     attrs.addAttribute(attr.getNamespaceURI(), attr.getName(),
926                attr.getName(), "CDATA", attr.getValue());
927       }
928     }
929     return attrs;
930   }
931 }
932
Popular Tags