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.Attributes;
009: import org.xml.sax.SAXException;
010:
011: import org.w3c.dom.Attr;
012: import org.w3c.dom.Document;
013: import org.w3c.dom.DocumentFragment;
014: import org.w3c.dom.NamedNodeMap;
015: import org.w3c.dom.Node;
016: import org.w3c.dom.Element;
017: import org.w3c.dom.traversal.NodeIterator;
018:
019: import org.apache.xpath.objects.XObject;
020: import org.apache.xpath.XPath;
021: import org.apache.xpath.XPathContext;
022: import org.apache.xpath.NodeSet;
023: import org.apache.xalan.extensions.XSLProcessorContext;
024: import org.apache.xalan.extensions.ExpressionContext;
025: import org.apache.xalan.transformer.TransformerImpl;
026: import org.apache.xalan.templates.StylesheetRoot;
027: import org.apache.xalan.templates.ElemExtensionCall;
028: import org.apache.xalan.templates.OutputProperties;
029: import org.apache.xalan.res.XSLTErrorResources;
030: import org.apache.xml.utils.DOMBuilder;
031: import org.apache.xml.utils.AttList;
032: import org.apache.xml.utils.QName;
033:
034: import javax.xml.transform.stream.StreamResult;
035: import javax.xml.transform.TransformerException;
036: import javax.xml.parsers.DocumentBuilder;
037: import javax.xml.parsers.DocumentBuilderFactory;
038: import javax.xml.parsers.ParserConfigurationException;
039:
040: import com.nwalsh.xalan.Callout;
041: import com.nwalsh.xalan.Params;
042: import org.xml.sax.helpers.AttributesImpl;
043:
044: /**
045: * <p>Xalan extensions supporting DocBook verbatim environments</p>
046: *
047: * <p>$Id: Verbatim.java,v 1.1 2007-05-18 15:03:13 sinisa Exp $</p>
048: *
049: * <p>Copyright (C) 2001 Norman Walsh.</p>
050: *
051: * <p>This class provides a
052: * <a href="http://xml.apache.org/xalan-j/">Xalan</a>
053: * implementation of two features that would be impractical to
054: * implement directly in XSLT: line numbering and callouts.</p>
055: *
056: * <p><b>Line Numbering</b></p>
057: * <p>The <tt>numberLines</tt> family of functions takes a result tree
058: * fragment (assumed to contain the contents of a formatted verbatim
059: * element in DocBook: programlisting, screen, address, literallayout,
060: * or synopsis) and returns a result tree fragment decorated with
061: * line numbers.</p>
062: *
063: * <p><b>Callouts</b></p>
064: * <p>The <tt>insertCallouts</tt> family of functions takes an
065: * <tt>areaspec</tt> and a result tree fragment
066: * (assumed to contain the contents of a formatted verbatim
067: * element in DocBook: programlisting, screen, address, literallayout,
068: * or synopsis) and returns a result tree fragment decorated with
069: * callouts.</p>
070: *
071: * <p><b>Change Log:</b></p>
072: * <dl>
073: * <dt>1.0</dt>
074: * <dd><p>Initial release.</p></dd>
075: * </dl>
076: *
077: * @author Norman Walsh
078: * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
079: *
080: * @version $Id: Verbatim.java,v 1.1 2007-05-18 15:03:13 sinisa Exp $
081: *
082: */
083: public class Verbatim {
084: /** A stack to hold the open elements while walking through a RTF. */
085: private Stack elementStack = null;
086: /** A stack to hold the temporarily closed elements. */
087: private Stack tempStack = null;
088: /** The current line number. */
089: private int lineNumber = 0;
090: /** The current column number. */
091: private int colNumber = 0;
092: /** The modulus for line numbering (every 'modulus' line is numbered). */
093: private int modulus = 0;
094: /** The width (in characters) of line numbers (for padding). */
095: private int width = 0;
096: /** The separator between the line number and the verbatim text. */
097: private String separator = "";
098: /** The (sorted) array of callouts obtained from the areaspec. */
099: private Callout callout[] = null;
100: /** The number of callouts in the callout array. */
101: private int calloutCount = 0;
102: /** A pointer used to keep track of our position in the callout array. */
103: private int calloutPos = 0;
104: /** The path to use for graphical callout decorations. */
105: private String graphicsPath = null;
106: /** The extension to use for graphical callout decorations. */
107: private String graphicsExt = null;
108: /** The largest callout number that can be represented graphically. */
109: private int graphicsMax = 10;
110: /** Should graphic callouts use fo:external-graphics or imgs. */
111: private boolean graphicsFO = false;
112:
113: private static final String foURI = "http://www.w3.org/1999/XSL/Format";
114: private static final String xhURI = "http://www.w3.org/1999/xhtml";
115:
116: /**
117: * <p>Constructor for Verbatim</p>
118: *
119: * <p>All of the methods are static, so the constructor does nothing.</p>
120: */
121: public Verbatim() {
122: }
123:
124: /**
125: * <p>Number lines in a verbatim environment.</p>
126: *
127: * <p>This method adds line numbers to a result tree fragment. Each
128: * newline that occurs in a text node is assumed to start a new line.
129: * The first line is always numbered, every subsequent xalanMod line
130: * is numbered (so if xalanMod=5, lines 1, 5, 10, 15, etc. will be
131: * numbered. If there are fewer than xalanMod lines in the environment,
132: * every line is numbered.</p>
133: *
134: * <p>xalanMod is taken from the $linenumbering.everyNth parameter.</p>
135: *
136: * <p>Every line number will be right justified in a string xalanWidth
137: * characters long. If the line number of the last line in the
138: * environment is too long to fit in the specified width, the width
139: * is automatically increased to the smallest value that can hold the
140: * number of the last line. (In other words, if you specify the value 2
141: * and attempt to enumerate the lines of an environment that is 100 lines
142: * long, the value 3 will automatically be used for every line in the
143: * environment.)</p>
144: *
145: * <p>xalanWidth is taken from the $linenumbering.width parameter.</p>
146: *
147: * <p>The xalanSep string is inserted between the line
148: * number and the original program listing. Lines that aren't numbered
149: * are preceded by a xalanWidth blank string and the separator.</p>
150: *
151: * <p>xalanSep is taken from the $linenumbering.separator parameter.</p>
152: *
153: * <p>If inline markup extends across line breaks, markup changes are
154: * required. All the open elements are closed before the line break and
155: * "reopened" afterwards. The reopened elements will have the same
156: * attributes as the originals, except that 'name' and 'id' attributes
157: * are not duplicated.</p>
158: *
159: * @param context
160: * @param xalanNI
161: *
162: * @return The modified result tree fragment.
163: */
164: public DocumentFragment numberLines(ExpressionContext context,
165: NodeIterator xalanNI) {
166:
167: int xalanMod = Params.getInt(context, "linenumbering.everyNth");
168: int xalanWidth = Params.getInt(context, "linenumbering.width");
169: String xalanSep = Params.getString(context,
170: "linenumbering.separator");
171:
172: DocumentFragment xalanRTF = (DocumentFragment) xalanNI
173: .nextNode();
174: int numLines = countLineBreaks(xalanRTF) + 1;
175:
176: DocumentBuilderFactory docFactory = DocumentBuilderFactory
177: .newInstance();
178: DocumentBuilder docBuilder = null;
179:
180: try {
181: docBuilder = docFactory.newDocumentBuilder();
182: } catch (ParserConfigurationException e) {
183: System.out.println("PCE!");
184: return xalanRTF;
185: }
186: Document doc = docBuilder.newDocument();
187: DocumentFragment df = doc.createDocumentFragment();
188: DOMBuilder db = new DOMBuilder(doc, df);
189:
190: elementStack = new Stack();
191: lineNumber = 0;
192: modulus = numLines < xalanMod ? 1 : xalanMod;
193: width = xalanWidth;
194: separator = xalanSep;
195:
196: double log10numLines = Math.log(numLines) / Math.log(10);
197:
198: if (width < log10numLines + 1) {
199: width = (int) Math.floor(log10numLines + 1);
200: }
201:
202: lineNumberFragment(db, xalanRTF);
203: return df;
204: }
205:
206: /**
207: * <p>Count the number of lines in a verbatim environment.</p>
208: *
209: * <p>This method walks over the nodes of a DocumentFragment and
210: * returns the number of lines breaks that it contains.</p>
211: *
212: * @param node The root of the tree walk over.
213: */
214: private int countLineBreaks(Node node) {
215: int numLines = 0;
216:
217: if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE
218: || node.getNodeType() == Node.DOCUMENT_NODE
219: || node.getNodeType() == Node.ELEMENT_NODE) {
220: Node child = node.getFirstChild();
221: while (child != null) {
222: numLines += countLineBreaks(child);
223: child = child.getNextSibling();
224: }
225: } else if (node.getNodeType() == Node.TEXT_NODE) {
226: String text = node.getNodeValue();
227:
228: // Walk through the text node looking for newlines
229: int pos = 0;
230: for (int count = 0; count < text.length(); count++) {
231: if (text.charAt(count) == '\n') {
232: numLines++;
233: }
234: }
235: } else {
236: // nop
237: }
238:
239: return numLines;
240: }
241:
242: /**
243: * <p>Build a DocumentFragment with numbered lines.</p>
244: *
245: * <p>This is the method that actually does the work of numbering
246: * lines in a verbatim environment. It recursively walks through a
247: * tree of nodes, copying the structure into the rtf. Text nodes
248: * are examined for new lines and modified as requested by the
249: * global line numbering parameters.</p>
250: *
251: * <p>When called, rtf should be an empty DocumentFragment and node
252: * should be the first child of the result tree fragment that contains
253: * the existing, formatted verbatim text.</p>
254: *
255: * @param rtf The resulting verbatim environment with numbered lines.
256: * @param node The root of the tree to copy.
257: */
258: private void lineNumberFragment(DOMBuilder rtf, Node node) {
259: try {
260: if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE
261: || node.getNodeType() == Node.DOCUMENT_NODE) {
262: Node child = node.getFirstChild();
263: while (child != null) {
264: lineNumberFragment(rtf, child);
265: child = child.getNextSibling();
266: }
267: } else if (node.getNodeType() == Node.ELEMENT_NODE) {
268: String ns = node.getNamespaceURI();
269: String localName = node.getLocalName();
270: String name = ((Element) node).getTagName();
271:
272: rtf.startElement(ns, localName, name,
273: copyAttributes((Element) node));
274:
275: elementStack.push(node);
276:
277: Node child = node.getFirstChild();
278: while (child != null) {
279: lineNumberFragment(rtf, child);
280: child = child.getNextSibling();
281: }
282: } else if (node.getNodeType() == Node.TEXT_NODE) {
283: String text = node.getNodeValue();
284:
285: if (lineNumber == 0) {
286: // The first line is always numbered
287: formatLineNumber(rtf, ++lineNumber);
288: }
289:
290: // Walk through the text node looking for newlines
291: char chars[] = text.toCharArray();
292: int pos = 0;
293: for (int count = 0; count < text.length(); count++) {
294: if (text.charAt(count) == '\n') {
295: // This is the tricky bit; if we find a newline, make sure
296: // it doesn't occur inside any markup.
297:
298: if (pos > 0) {
299: rtf.characters(chars, 0, pos);
300: pos = 0;
301: }
302:
303: closeOpenElements(rtf);
304:
305: // Copy the newline to the output
306: chars[pos++] = text.charAt(count);
307: rtf.characters(chars, 0, pos);
308: pos = 0;
309:
310: // Add the line number
311: formatLineNumber(rtf, ++lineNumber);
312:
313: openClosedElements(rtf);
314: } else {
315: chars[pos++] = text.charAt(count);
316: }
317: }
318:
319: if (pos > 0) {
320: rtf.characters(chars, 0, pos);
321: }
322: } else if (node.getNodeType() == Node.COMMENT_NODE) {
323: String text = node.getNodeValue();
324: char chars[] = text.toCharArray();
325: rtf.comment(chars, 0, text.length());
326: } else if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
327: rtf.processingInstruction(node.getNodeName(), node
328: .getNodeValue());
329: } else {
330: System.out
331: .println("Warning: unexpected node type in lineNumberFragment");
332: }
333:
334: if (node.getNodeType() == Node.ELEMENT_NODE) {
335: String ns = node.getNamespaceURI();
336: String localName = node.getLocalName();
337: String name = ((Element) node).getTagName();
338: rtf.endElement(ns, localName, name);
339: elementStack.pop();
340: }
341: } catch (SAXException e) {
342: System.out.println("SAX Exception in lineNumberFragment");
343: }
344: }
345:
346: /**
347: * <p>Add a formatted line number to the result tree fragment.</p>
348: *
349: * <p>This method examines the global parameters that control line
350: * number presentation (modulus, width, and separator) and adds
351: * the appropriate text to the result tree fragment.</p>
352: *
353: * @param rtf The resulting verbatim environment with numbered lines.
354: * @param lineNumber The number of the current line.
355: */
356: private void formatLineNumber(DOMBuilder rtf, int lineNumber) {
357: char ch = 160;
358: String lno = "";
359: if (lineNumber == 1
360: || (modulus >= 1 && (lineNumber % modulus == 0))) {
361: lno = "" + lineNumber;
362: }
363:
364: while (lno.length() < width) {
365: lno = ch + lno;
366: }
367:
368: lno += separator;
369:
370: char chars[] = lno.toCharArray();
371: try {
372: rtf.characters(chars, 0, lno.length());
373: } catch (SAXException e) {
374: System.out.println("SAX Exception in formatLineNumber");
375: }
376: }
377:
378: /**
379: * <p>Insert text callouts into a verbatim environment.</p>
380: *
381: * <p>This method examines the <tt>areaset</tt> and <tt>area</tt> elements
382: * in the supplied <tt>areaspec</tt> and decorates the supplied
383: * result tree fragment with appropriate callout markers.</p>
384: *
385: * <p>If a <tt>label</tt> attribute is supplied on an <tt>area</tt>,
386: * its content will be used for the label, otherwise the callout
387: * number will be used, surrounded by parenthesis. Callouts are
388: * numbered in document order. All of the <tt>area</tt>s in an
389: * <tt>areaset</tt> get the same number.</p>
390: *
391: * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are
392: * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed.
393: * If only a line is specified, the callout decoration appears in
394: * the defaultColumn. Lines will be padded with blanks to reach the
395: * necessary column, but callouts that are located beyond the last
396: * line of the verbatim environment will be ignored.</p>
397: *
398: * <p>Callouts are inserted before the character at the line/column
399: * where they are to occur.</p>
400: *
401: * @param areaspecNodeSet The source node set that contains the areaspec.
402: * @param xalanRTF The result tree fragment of the verbatim environment.
403: * @param defaultColumn The column for callouts that specify only a line.
404: *
405: * @return The modified result tree fragment. */
406:
407: /**
408: * <p>Insert graphical callouts into a verbatim environment.</p>
409: *
410: * <p>This method examines the <tt>areaset</tt> and <tt>area</tt> elements
411: * in the supplied <tt>areaspec</tt> and decorates the supplied
412: * result tree fragment with appropriate callout markers.</p>
413: *
414: * <p>If a <tt>label</tt> attribute is supplied on an <tt>area</tt>,
415: * its content will be used for the label, otherwise the callout
416: * number will be used. Callouts are
417: * numbered in document order. All of the <tt>area</tt>s in an
418: * <tt>areaset</tt> get the same number.</p>
419: *
420: * <p>If the callout number is not greater than <tt>gMax</tt>, the
421: * callout generated will be:</p>
422: *
423: * <pre>
424: * <img src="$gPath/conumber$gExt" alt="conumber">
425: * </pre>
426: *
427: * <p>Otherwise, it will be the callout number surrounded by
428: * parenthesis.</p>
429: *
430: * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are
431: * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed.
432: * If only a line is specified, the callout decoration appears in
433: * the defaultColumn. Lines will be padded with blanks to reach the
434: * necessary column, but callouts that are located beyond the last
435: * line of the verbatim environment will be ignored.</p>
436: *
437: * <p>Callouts are inserted before the character at the line/column
438: * where they are to occur.</p>
439: *
440: * @param context
441: * @param areaspecNodeSet The source node set that contains the areaspec.
442: * @param xalanNI
443: *
444: * @return The modified result tree fragment.
445: */
446: public DocumentFragment insertCallouts(ExpressionContext context,
447: NodeIterator areaspecNodeSet, NodeIterator xalanNI) {
448: String type = Params.getString(context,
449: "stylesheet.result.type");
450: boolean useFO = type.equals("fo");
451: int defaultColumn = Params.getInt(context,
452: "callout.defaultcolumn");
453:
454: if (Params.getBoolean(context, "callout.graphics")) {
455: String gPath = Params.getString(context,
456: "callout.graphics.path");
457: String gExt = Params.getString(context,
458: "callout.graphics.extension");
459: int gMax = Params.getInt(context,
460: "callout.graphics.number.limit");
461: return insertGraphicCallouts(areaspecNodeSet, xalanNI,
462: defaultColumn, gPath, gExt, gMax, useFO);
463: } else if (Params.getBoolean(context, "callout.unicode")) {
464: int uStart = Params.getInt(context,
465: "callout.unicode.start.character");
466: int uMax = Params.getInt(context,
467: "callout.unicode.number.limit");
468: String uFont = Params.getString(context,
469: "callout.unicode.font");
470: return insertUnicodeCallouts(areaspecNodeSet, xalanNI,
471: defaultColumn, uFont, uStart, uMax, useFO);
472: } else if (Params.getBoolean(context, "callout.dingbats")) {
473: int dMax = 10;
474: return insertDingbatCallouts(areaspecNodeSet, xalanNI,
475: defaultColumn, dMax, useFO);
476: } else {
477: return insertTextCallouts(areaspecNodeSet, xalanNI,
478: defaultColumn, useFO);
479: }
480: }
481:
482: public DocumentFragment insertGraphicCallouts(
483: NodeIterator areaspecNodeSet, NodeIterator xalanNI,
484: int defaultColumn, String gPath, String gExt, int gMax,
485: boolean useFO) {
486: FormatGraphicCallout fgc = new FormatGraphicCallout(gPath,
487: gExt, gMax, useFO);
488: return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn,
489: fgc);
490: }
491:
492: public DocumentFragment insertUnicodeCallouts(
493: NodeIterator areaspecNodeSet, NodeIterator xalanNI,
494: int defaultColumn, String uFont, int uStart, int uMax,
495: boolean useFO) {
496: FormatUnicodeCallout fuc = new FormatUnicodeCallout(uFont,
497: uStart, uMax, useFO);
498: return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn,
499: fuc);
500: }
501:
502: public DocumentFragment insertDingbatCallouts(
503: NodeIterator areaspecNodeSet, NodeIterator xalanNI,
504: int defaultColumn, int gMax, boolean useFO) {
505: FormatDingbatCallout fdc = new FormatDingbatCallout(gMax, useFO);
506: return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn,
507: fdc);
508: }
509:
510: public DocumentFragment insertTextCallouts(
511: NodeIterator areaspecNodeSet, NodeIterator xalanNI,
512: int defaultColumn, boolean useFO) {
513: FormatTextCallout ftc = new FormatTextCallout(useFO);
514: return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn,
515: ftc);
516: }
517:
518: public DocumentFragment insertCallouts(
519: NodeIterator areaspecNodeSet, NodeIterator xalanNI,
520: int defaultColumn, FormatCallout fCallout) {
521:
522: DocumentFragment xalanRTF = (DocumentFragment) xalanNI
523: .nextNode();
524:
525: callout = new Callout[10];
526: calloutCount = 0;
527: calloutPos = 0;
528: lineNumber = 1;
529: colNumber = 1;
530:
531: // First we walk through the areaspec to calculate the position
532: // of the callouts
533: // <areaspec>
534: // <areaset id="ex.plco.const" coords="">
535: // <area id="ex.plco.c1" coords="4"/>
536: // <area id="ex.plco.c2" coords="8"/>
537: // </areaset>
538: // <area id="ex.plco.ret" coords="12"/>
539: // <area id="ex.plco.dest" coords="12"/>
540: // </areaspec>
541: int pos = 0;
542: int coNum = 0;
543: boolean inAreaSet = false;
544: Node node = areaspecNodeSet.nextNode();
545: node = node.getFirstChild();
546: while (node != null) {
547: if (node.getNodeType() == Node.ELEMENT_NODE) {
548: if (node.getNodeName().equals("areaset")) {
549: coNum++;
550: Node area = node.getFirstChild();
551: while (area != null) {
552: if (area.getNodeType() == Node.ELEMENT_NODE) {
553: if (area.getNodeName().equals("area")) {
554: addCallout(coNum, area, defaultColumn);
555: } else {
556: System.out
557: .println("Unexpected element in areaset: "
558: + area.getNodeName());
559: }
560: }
561: area = area.getNextSibling();
562: }
563: } else if (node.getNodeName().equalsIgnoreCase("area")) {
564: coNum++;
565: addCallout(coNum, node, defaultColumn);
566: } else {
567: System.out
568: .println("Unexpected element in areaspec: "
569: + node.getNodeName());
570: }
571: }
572:
573: node = node.getNextSibling();
574: }
575:
576: // Now sort them
577: java.util.Arrays.sort(callout, 0, calloutCount);
578:
579: DocumentBuilderFactory docFactory = DocumentBuilderFactory
580: .newInstance();
581: DocumentBuilder docBuilder = null;
582:
583: try {
584: docBuilder = docFactory.newDocumentBuilder();
585: } catch (ParserConfigurationException e) {
586: System.out.println("PCE 2!");
587: return xalanRTF;
588: }
589: Document doc = docBuilder.newDocument();
590: DocumentFragment df = doc.createDocumentFragment();
591: DOMBuilder db = new DOMBuilder(doc, df);
592:
593: elementStack = new Stack();
594: calloutFragment(db, xalanRTF, fCallout);
595: return df;
596: }
597:
598: /**
599: * <p>Build a FragmentValue with callout decorations.</p>
600: *
601: * <p>This is the method that actually does the work of adding
602: * callouts to a verbatim environment. It recursively walks through a
603: * tree of nodes, copying the structure into the rtf. Text nodes
604: * are examined for the position of callouts as described by the
605: * global callout parameters.</p>
606: *
607: * <p>When called, rtf should be an empty FragmentValue and node
608: * should be the first child of the result tree fragment that contains
609: * the existing, formatted verbatim text.</p>
610: *
611: * @param rtf The resulting verbatim environment with numbered lines.
612: * @param node The root of the tree to copy.
613: */
614: private void calloutFragment(DOMBuilder rtf, Node node,
615: FormatCallout fCallout) {
616: try {
617: if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE
618: || node.getNodeType() == Node.DOCUMENT_NODE) {
619: Node child = node.getFirstChild();
620: while (child != null) {
621: calloutFragment(rtf, child, fCallout);
622: child = child.getNextSibling();
623: }
624: } else if (node.getNodeType() == Node.ELEMENT_NODE) {
625: String ns = node.getNamespaceURI();
626: String localName = node.getLocalName();
627: String name = ((Element) node).getTagName();
628:
629: rtf.startElement(ns, localName, name,
630: copyAttributes((Element) node));
631:
632: elementStack.push(node);
633:
634: Node child = node.getFirstChild();
635: while (child != null) {
636: calloutFragment(rtf, child, fCallout);
637: child = child.getNextSibling();
638: }
639: } else if (node.getNodeType() == Node.TEXT_NODE) {
640: String text = node.getNodeValue();
641:
642: char chars[] = text.toCharArray();
643: int pos = 0;
644: for (int count = 0; count < text.length(); count++) {
645: if (calloutPos < calloutCount
646: && callout[calloutPos].getLine() == lineNumber
647: && callout[calloutPos].getColumn() == colNumber) {
648:
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 ("".equals(units) || 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: }
|