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