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