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 * <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 }