// Verbatim.java - Xalan extensions supporting DocBook verbatim environments package com.nwalsh.xalan; import java.util.Stack; import java.util.StringTokenizer; import org.xml.sax.*; import org.xml.sax.helpers.AttributesImpl; import org.w3c.dom.*; import org.w3c.dom.traversal.NodeIterator; import org.apache.xerces.dom.*; import org.apache.xpath.objects.XObject; import org.apache.xpath.XPath; import org.apache.xpath.XPathContext; import org.apache.xpath.NodeSet; import org.apache.xpath.DOMHelper; import org.apache.xalan.extensions.XSLProcessorContext; import org.apache.xalan.extensions.ExpressionContext; import org.apache.xalan.transformer.TransformerImpl; import org.apache.xalan.templates.StylesheetRoot; import org.apache.xalan.templates.ElemExtensionCall; import org.apache.xalan.templates.OutputProperties; import org.apache.xalan.res.XSLTErrorResources; import org.apache.xml.utils.DOMBuilder; import org.apache.xml.utils.AttList; import org.apache.xml.utils.QName; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.TransformerException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import com.nwalsh.xalan.Callout; import com.nwalsh.xalan.Params; /** * <p>Xalan extensions supporting DocBook verbatim environments</p> * * <p>$Id: Verbatim.java,v 1.5 2003/12/17 01:01:34 nwalsh Exp $</p> * * <p>Copyright (C) 2001 Norman Walsh.</p> * * <p>This class provides a * <a href="http://xml.apache.org/xalan">Xalan</a> * implementation of two features that would be impractical to * implement directly in XSLT: line numbering and callouts.</p> * * <p><b>Line Numbering</b></p> * <p>The <tt>numberLines</tt> family of functions takes a result tree * fragment (assumed to contain the contents of a formatted verbatim * element in DocBook: programlisting, screen, address, literallayout, * or synopsis) and returns a result tree fragment decorated with * line numbers.</p> * * <p><b>Callouts</b></p> * <p>The <tt>insertCallouts</tt> family of functions takes an * <tt>areaspec</tt> and a result tree fragment * (assumed to contain the contents of a formatted verbatim * element in DocBook: programlisting, screen, address, literallayout, * or synopsis) and returns a result tree fragment decorated with * callouts.</p> * * <p><b>Change Log:</b></p> * <dl> * <dt>1.0</dt> * <dd><p>Initial release.</p></dd> * </dl> * * @author Norman Walsh * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a> * * @version $Id: Verbatim.java,v 1.5 2003/12/17 01:01:34 nwalsh Exp $ * */ public class Verbatim { /** A stack to hold the open elements while walking through a RTF. */ private Stack elementStack = null; /** A stack to hold the temporarily closed elements. */ private Stack tempStack = null; /** The current line number. */ private int lineNumber = 0; /** The current column number. */ private int colNumber = 0; /** The modulus for line numbering (every 'modulus' line is numbered). */ private int modulus = 0; /** The width (in characters) of line numbers (for padding). */ private int width = 0; /** The separator between the line number and the verbatim text. */ private String separator = ""; /** The (sorted) array of callouts obtained from the areaspec. */ private Callout callout[] = null; /** The number of callouts in the callout array. */ private int calloutCount = 0; /** A pointer used to keep track of our position in the callout array. */ private int calloutPos = 0; /** The path to use for graphical callout decorations. */ private String graphicsPath = null; /** The extension to use for graphical callout decorations. */ private String graphicsExt = null; /** The largest callout number that can be represented graphically. */ private int graphicsMax = 10; /** Should graphic callouts use fo:external-graphics or imgs. */ private boolean graphicsFO = false; private static final String foURI = "http://www.w3.org/1999/XSL/Format"; private static final String xhURI = "http://www.w3.org/1999/xhtml"; /** * <p>Constructor for Verbatim</p> * * <p>All of the methods are static, so the constructor does nothing.</p> */ public Verbatim() { } /** * <p>Number lines in a verbatim environment.</p> * * <p>This method adds line numbers to a result tree fragment. Each * newline that occurs in a text node is assumed to start a new line. * The first line is always numbered, every subsequent xalanMod line * is numbered (so if xalanMod=5, lines 1, 5, 10, 15, etc. will be * numbered. If there are fewer than xalanMod lines in the environment, * every line is numbered.</p> * * <p>xalanMod is taken from the $linenumbering.everyNth parameter.</p> * * <p>Every line number will be right justified in a string xalanWidth * characters long. If the line number of the last line in the * environment is too long to fit in the specified width, the width * is automatically increased to the smallest value that can hold the * number of the last line. (In other words, if you specify the value 2 * and attempt to enumerate the lines of an environment that is 100 lines * long, the value 3 will automatically be used for every line in the * environment.)</p> * * <p>xalanWidth is taken from the $linenumbering.width parameter.</p> * * <p>The xalanSep string is inserted between the line * number and the original program listing. Lines that aren't numbered * are preceded by a xalanWidth blank string and the separator.</p> * * <p>xalanSep is taken from the $linenumbering.separator parameter.</p> * * <p>If inline markup extends across line breaks, markup changes are * required. All the open elements are closed before the line break and * "reopened" afterwards. The reopened elements will have the same * attributes as the originals, except that 'name' and 'id' attributes * are not duplicated.</p> * * @param xalanRTF The result tree fragment of the verbatim environment. * * @return The modified result tree fragment. */ public DocumentFragment numberLines (ExpressionContext context, NodeIterator xalanNI) { int xalanMod = Params.getInt(context, "linenumbering.everyNth"); int xalanWidth = Params.getInt(context, "linenumbering.width"); String xalanSep = Params.getString(context, "linenumbering.separator"); DocumentFragment xalanRTF = (DocumentFragment) xalanNI.nextNode(); int numLines = countLineBreaks(xalanRTF) + 1; DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = null; try { docBuilder = docFactory.newDocumentBuilder(); } catch (ParserConfigurationException e) { System.out.println("PCE!"); return xalanRTF; } Document doc = docBuilder.newDocument(); DocumentFragment df = doc.createDocumentFragment(); DOMBuilder db = new DOMBuilder(doc, df); elementStack = new Stack(); lineNumber = 0; modulus = numLines < xalanMod ? 1 : xalanMod; width = xalanWidth; separator = xalanSep; double log10numLines = Math.log(numLines) / Math.log(10); if (width < log10numLines + 1) { width = (int) Math.floor(log10numLines + 1); } lineNumberFragment(db, xalanRTF); return df; } /** * <p>Count the number of lines in a verbatim environment.</p> * * <p>This method walks over the nodes of a DocumentFragment and * returns the number of lines breaks that it contains.</p> * * @param node The root of the tree walk over. */ private int countLineBreaks(Node node) { int numLines = 0; if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE || node.getNodeType() == Node.DOCUMENT_NODE || node.getNodeType() == Node.ELEMENT_NODE) { Node child = node.getFirstChild(); while (child != null) { numLines += countLineBreaks(child); child = child.getNextSibling(); } } else if (node.getNodeType() == Node.TEXT_NODE) { String text = node.getNodeValue(); // Walk through the text node looking for newlines int pos = 0; for (int count = 0; count < text.length(); count++) { if (text.charAt(count) == '\n') { numLines++; } } } else { // nop } return numLines; } /** * <p>Build a DocumentFragment with numbered lines.</p> * * <p>This is the method that actually does the work of numbering * lines in a verbatim environment. It recursively walks through a * tree of nodes, copying the structure into the rtf. Text nodes * are examined for new lines and modified as requested by the * global line numbering parameters.</p> * * <p>When called, rtf should be an empty DocumentFragment and node * should be the first child of the result tree fragment that contains * the existing, formatted verbatim text.</p> * * @param rtf The resulting verbatim environment with numbered lines. * @param node The root of the tree to copy. */ private void lineNumberFragment(DOMBuilder rtf, Node node) { try { if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE || node.getNodeType() == Node.DOCUMENT_NODE) { Node child = node.getFirstChild(); while (child != null) { lineNumberFragment(rtf, child); child = child.getNextSibling(); } } else if (node.getNodeType() == Node.ELEMENT_NODE) { String ns = node.getNamespaceURI(); String localName = node.getLocalName(); String name = ((Element) node).getTagName(); rtf.startElement(ns, localName, name, copyAttributes((Element) node)); elementStack.push(node); Node child = node.getFirstChild(); while (child != null) { lineNumberFragment(rtf, child); child = child.getNextSibling(); } } else if (node.getNodeType() == Node.TEXT_NODE) { String text = node.getNodeValue(); if (lineNumber == 0) { // The first line is always numbered formatLineNumber(rtf, ++lineNumber); } // Walk through the text node looking for newlines char chars[] = text.toCharArray(); int pos = 0; for (int count = 0; count < text.length(); count++) { if (text.charAt(count) == '\n') { // This is the tricky bit; if we find a newline, make sure // it doesn't occur inside any markup. if (pos > 0) { rtf.characters(chars, 0, pos); pos = 0; } closeOpenElements(rtf); // Copy the newline to the output chars[pos++] = text.charAt(count); rtf.characters(chars, 0, pos); pos = 0; // Add the line number formatLineNumber(rtf, ++lineNumber); openClosedElements(rtf); } else { chars[pos++] = text.charAt(count); } } if (pos > 0) { rtf.characters(chars, 0, pos); } } else if (node.getNodeType() == Node.COMMENT_NODE) { String text = node.getNodeValue(); char chars[] = text.toCharArray(); rtf.comment(chars, 0, text.length()); } else if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) { rtf.processingInstruction(node.getNodeName(), node.getNodeValue()); } else { System.out.println("Warning: unexpected node type in lineNumberFragment"); } if (node.getNodeType() == Node.ELEMENT_NODE) { String ns = node.getNamespaceURI(); String localName = node.getLocalName(); String name = ((Element) node).getTagName(); rtf.endElement(ns, localName, name); elementStack.pop(); } } catch (SAXException e) { System.out.println("SAX Exception in lineNumberFragment"); } } /** * <p>Add a formatted line number to the result tree fragment.</p> * * <p>This method examines the global parameters that control line * number presentation (modulus, width, and separator) and adds * the appropriate text to the result tree fragment.</p> * * @param rtf The resulting verbatim environment with numbered lines. * @param lineNumber The number of the current line. */ private void formatLineNumber(DOMBuilder rtf, int lineNumber) { char ch = 160; String lno = ""; if (lineNumber == 1 || (modulus >= 1 && (lineNumber % modulus == 0))) { lno = "" + lineNumber; } while (lno.length() < width) { lno = ch + lno; } lno += separator; char chars[] = lno.toCharArray(); try { rtf.characters(chars, 0, lno.length()); } catch (SAXException e) { System.out.println("SAX Exception in formatLineNumber"); } } /** * <p>Insert text callouts into a verbatim environment.</p> * * <p>This method examines the <tt>areaset</tt> and <tt>area</tt> elements * in the supplied <tt>areaspec</tt> and decorates the supplied * result tree fragment with appropriate callout markers.</p> * * <p>If a <tt>label</tt> attribute is supplied on an <tt>area</tt>, * its content will be used for the label, otherwise the callout * number will be used, surrounded by parenthesis. Callouts are * numbered in document order. All of the <tt>area</tt>s in an * <tt>areaset</tt> get the same number.</p> * * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed. * If only a line is specified, the callout decoration appears in * the defaultColumn. Lines will be padded with blanks to reach the * necessary column, but callouts that are located beyond the last * line of the verbatim environment will be ignored.</p> * * <p>Callouts are inserted before the character at the line/column * where they are to occur.</p> * * @param areaspecNodeSet The source node set that contains the areaspec. * @param xalanRTF The result tree fragment of the verbatim environment. * @param defaultColumn The column for callouts that specify only a line. * * @return The modified result tree fragment. */ /** * <p>Insert graphical callouts into a verbatim environment.</p> * * <p>This method examines the <tt>areaset</tt> and <tt>area</tt> elements * in the supplied <tt>areaspec</tt> and decorates the supplied * result tree fragment with appropriate callout markers.</p> * * <p>If a <tt>label</tt> attribute is supplied on an <tt>area</tt>, * its content will be used for the label, otherwise the callout * number will be used. Callouts are * numbered in document order. All of the <tt>area</tt>s in an * <tt>areaset</tt> get the same number.</p> * * <p>If the callout number is not greater than <tt>gMax</tt>, the * callout generated will be:</p> * * <pre> * <img src="$gPath/conumber$gExt" alt="conumber"> * </pre> * * <p>Otherwise, it will be the callout number surrounded by * parenthesis.</p> * * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed. * If only a line is specified, the callout decoration appears in * the defaultColumn. Lines will be padded with blanks to reach the * necessary column, but callouts that are located beyond the last * line of the verbatim environment will be ignored.</p> * * <p>Callouts are inserted before the character at the line/column * where they are to occur.</p> * * @param areaspecNodeSet The source node set that contains the areaspec. * @param xalanRTF The result tree fragment of the verbatim environment. * @param defaultColumn The column for callouts that specify only a line. * @param gPath The path to use for callout graphics. * @param gExt The extension to use for callout graphics. * @param gMax The largest number that can be represented as a graphic. * @param useFO Should fo:external-graphics be produced, as opposed to * HTML imgs. This is bogus, the extension should figure it out, but I * haven't figured out how to do that yet. * * @return The modified result tree fragment. */ public DocumentFragment insertCallouts (ExpressionContext context, NodeIterator areaspecNodeSet, NodeIterator xalanNI) { String type = Params.getString(context, "stylesheet.result.type"); boolean useFO = type.equals("fo"); int defaultColumn = Params.getInt(context, "callout.defaultcolumn"); if (Params.getBoolean(context, "callout.graphics")) { String gPath = Params.getString(context, "callout.graphics.path"); String gExt = Params.getString(context, "callout.graphics.extension"); int gMax = Params.getInt(context, "callout.graphics.number.limit"); return insertGraphicCallouts(areaspecNodeSet, xalanNI, defaultColumn, gPath, gExt, gMax, useFO); } else if (Params.getBoolean(context, "callout.unicode")) { int uStart = Params.getInt(context, "callout.unicode.start.character"); int uMax = Params.getInt(context, "callout.unicode.number.limit"); String uFont = Params.getString(context, "callout.unicode.font"); return insertUnicodeCallouts(areaspecNodeSet, xalanNI, defaultColumn, uFont, uStart, uMax, useFO); } else if (Params.getBoolean(context, "callout.dingbats")) { int dMax = 10; return insertDingbatCallouts(areaspecNodeSet, xalanNI, defaultColumn, dMax, useFO); } else { return insertTextCallouts(areaspecNodeSet, xalanNI, defaultColumn, useFO); } } public DocumentFragment insertGraphicCallouts (NodeIterator areaspecNodeSet, NodeIterator xalanNI, int defaultColumn, String gPath, String gExt, int gMax, boolean useFO) { FormatGraphicCallout fgc = new FormatGraphicCallout(gPath,gExt,gMax,useFO); return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, fgc); } public DocumentFragment insertUnicodeCallouts (NodeIterator areaspecNodeSet, NodeIterator xalanNI, int defaultColumn, String uFont, int uStart, int uMax, boolean useFO) { FormatUnicodeCallout fuc = new FormatUnicodeCallout(uFont, uStart, uMax, useFO); return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, fuc); } public DocumentFragment insertDingbatCallouts (NodeIterator areaspecNodeSet, NodeIterator xalanNI, int defaultColumn, int gMax, boolean useFO) { FormatDingbatCallout fdc = new FormatDingbatCallout(gMax,useFO); return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, fdc); } public DocumentFragment insertTextCallouts (NodeIterator areaspecNodeSet, NodeIterator xalanNI, int defaultColumn, boolean useFO) { FormatTextCallout ftc = new FormatTextCallout(useFO); return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, ftc); } public DocumentFragment insertCallouts (NodeIterator areaspecNodeSet, NodeIterator xalanNI, int defaultColumn, FormatCallout fCallout) { DocumentFragment xalanRTF = (DocumentFragment) xalanNI.nextNode(); callout = new Callout[10]; calloutCount = 0; calloutPos = 0; lineNumber = 1; colNumber = 1; // First we walk through the areaspec to calculate the position // of the callouts // <areaspec> // <areaset id="ex.plco.const" coords=""> // <area id="ex.plco.c1" coords="4"/> // <area id="ex.plco.c2" coords="8"/> // </areaset> // <area id="ex.plco.ret" coords="12"/> // <area id="ex.plco.dest" coords="12"/> // </areaspec> int pos = 0; int coNum = 0; boolean inAreaSet = false; Node node = areaspecNodeSet.nextNode(); node = node.getFirstChild(); while (node != null) { if (node.getNodeType() == Node.ELEMENT_NODE) { if (node.getNodeName().equals("areaset")) { coNum++; Node area = node.getFirstChild(); while (area != null) { if (area.getNodeType() == Node.ELEMENT_NODE) { if (area.getNodeName().equals("area")) { addCallout(coNum, area, defaultColumn); } else { System.out.println("Unexpected element in areaset: " + area.getNodeName()); } } area = area.getNextSibling(); } } else if (node.getNodeName().equalsIgnoreCase("area")) { coNum++; addCallout(coNum, node, defaultColumn); } else { System.out.println("Unexpected element in areaspec: " + node.getNodeName()); } } node = node.getNextSibling(); } // Now sort them java.util.Arrays.sort(callout, 0, calloutCount); DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = null; try { docBuilder = docFactory.newDocumentBuilder(); } catch (ParserConfigurationException e) { System.out.println("PCE 2!"); return xalanRTF; } Document doc = docBuilder.newDocument(); DocumentFragment df = doc.createDocumentFragment(); DOMBuilder db = new DOMBuilder(doc, df); elementStack = new Stack(); calloutFragment(db, xalanRTF, fCallout); return df; } /** * <p>Build a FragmentValue with callout decorations.</p> * * <p>This is the method that actually does the work of adding * callouts to a verbatim environment. It recursively walks through a * tree of nodes, copying the structure into the rtf. Text nodes * are examined for the position of callouts as described by the * global callout parameters.</p> * * <p>When called, rtf should be an empty FragmentValue and node * should be the first child of the result tree fragment that contains * the existing, formatted verbatim text.</p> * * @param rtf The resulting verbatim environment with numbered lines. * @param node The root of the tree to copy. */ private void calloutFragment(DOMBuilder rtf, Node node, FormatCallout fCallout) { try { if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE || node.getNodeType() == Node.DOCUMENT_NODE) { Node child = node.getFirstChild(); while (child != null) { calloutFragment(rtf, child, fCallout); child = child.getNextSibling(); } } else if (node.getNodeType() == Node.ELEMENT_NODE) { String ns = node.getNamespaceURI(); String localName = node.getLocalName(); String name = ((Element) node).getTagName(); rtf.startElement(ns, localName, name, copyAttributes((Element) node)); elementStack.push(node); Node child = node.getFirstChild(); while (child != null) { calloutFragment(rtf, child, fCallout); child = child.getNextSibling(); } } else if (node.getNodeType() == Node.TEXT_NODE) { String text = node.getNodeValue(); char chars[] = text.toCharArray(); int pos = 0; for (int count = 0; count < text.length(); count++) { if (calloutPos < calloutCount && callout[calloutPos].getLine() == lineNumber && callout[calloutPos].getColumn() == colNumber) { if (pos > 0) { rtf.characters(chars, 0, pos); pos = 0; } closeOpenElements(rtf); while (calloutPos < calloutCount && callout[calloutPos].getLine() == lineNumber && callout[calloutPos].getColumn() == colNumber) { fCallout.formatCallout(rtf, callout[calloutPos]); calloutPos++; } openClosedElements(rtf); } if (text.charAt(count) == '\n') { // What if we need to pad this line? if (calloutPos < calloutCount && callout[calloutPos].getLine() == lineNumber && callout[calloutPos].getColumn() > colNumber) { if (pos > 0) { rtf.characters(chars, 0, pos); pos = 0; } closeOpenElements(rtf); while (calloutPos < calloutCount && callout[calloutPos].getLine() == lineNumber && callout[calloutPos].getColumn() > colNumber) { formatPad(rtf, callout[calloutPos].getColumn() - colNumber); colNumber = callout[calloutPos].getColumn(); while (calloutPos < calloutCount && callout[calloutPos].getLine() == lineNumber && callout[calloutPos].getColumn() == colNumber) { fCallout.formatCallout(rtf, callout[calloutPos]); calloutPos++; } } openClosedElements(rtf); } lineNumber++; colNumber = 1; } else { colNumber++; } chars[pos++] = text.charAt(count); } if (pos > 0) { rtf.characters(chars, 0, pos); } } else if (node.getNodeType() == Node.COMMENT_NODE) { String text = node.getNodeValue(); char chars[] = text.toCharArray(); rtf.comment(chars, 0, text.length()); } else if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) { rtf.processingInstruction(node.getNodeName(), node.getNodeValue()); } else { System.out.println("Warning: unexpected node type in calloutFragment: " + node.getNodeType() + ": " + node.getNodeName()); } if (node.getNodeType() == Node.ELEMENT_NODE) { String ns = node.getNamespaceURI(); String localName = node.getLocalName(); String name = ((Element) node).getTagName(); rtf.endElement(ns, localName, name); elementStack.pop(); } else { // nop } } catch (SAXException e) { System.out.println("SAX Exception in calloutFragment"); } } /** * <p>Add a callout to the global callout array</p> * * <p>This method examines a callout <tt>area</tt> and adds it to * the global callout array if it can be interpreted.</p> * * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed. * If only a line is specified, the callout decoration appears in * the <tt>defaultColumn</tt>.</p> * * @param coNum The callout number. * @param node The <tt>area</tt>. * @param defaultColumn The default column for callouts. */ private void addCallout (int coNum, Node node, int defaultColumn) { Element area = (Element) node; String units = area.getAttribute("units"); String otherUnits = area.getAttribute("otherunits"); String coords = area.getAttribute("coords"); int type = 0; String otherType = null; if (units == null || units.equals("linecolumn")) { type = Callout.LINE_COLUMN; // the default } else if (units.equals("linerange")) { type = Callout.LINE_RANGE; } else if (units.equals("linecolumnpair")) { type = Callout.LINE_COLUMN_PAIR; } else if (units.equals("calspair")) { type = Callout.CALS_PAIR; } else { type = Callout.OTHER; otherType = otherUnits; } if (type != Callout.LINE_COLUMN && type != Callout.LINE_RANGE) { System.out.println("Only linecolumn and linerange units are supported"); return; } if (coords == null) { System.out.println("Coords must be specified"); return; } // Now let's see if we can interpret the coordinates... StringTokenizer st = new StringTokenizer(coords); int tokenCount = 0; int c1 = 0; int c2 = 0; while (st.hasMoreTokens()) { tokenCount++; if (tokenCount > 2) { System.out.println("Unparseable coordinates"); return; } try { String token = st.nextToken(); int coord = Integer.parseInt(token); c2 = coord; if (tokenCount == 1) { c1 = coord; } } catch (NumberFormatException e) { System.out.println("Unparseable coordinate"); return; } } // Make sure we aren't going to blow past the end of our array if (calloutCount == callout.length) { Callout bigger[] = new Callout[calloutCount+10]; for (int count = 0; count < callout.length; count++) { bigger[count] = callout[count]; } callout = bigger; } // Ok, add the callout if (tokenCount == 2) { if (type == Callout.LINE_RANGE) { for (int count = c1; count <= c2; count++) { callout[calloutCount++] = new Callout(coNum, area, count, defaultColumn, type); } } else { // assume linecolumn callout[calloutCount++] = new Callout(coNum, area, c1, c2, type); } } else { // if there's only one number, assume it's the line callout[calloutCount++] = new Callout(coNum, area, c1, defaultColumn, type); } } /** * <p>Add blanks to the result tree fragment.</p> * * <p>This method adds <tt>numBlanks</tt> to the result tree fragment. * It's used to pad lines when callouts occur after the last existing * characater in a line.</p> * * @param rtf The resulting verbatim environment with numbered lines. * @param numBlanks The number of blanks to add. */ private void formatPad(DOMBuilder rtf, int numBlanks) { char chars[] = new char[numBlanks]; for (int count = 0; count < numBlanks; count++) { chars[count] = ' '; } try { rtf.characters(chars, 0, numBlanks); } catch (SAXException e) { System.out.println("SAX Exception in formatCallout"); } } private void closeOpenElements(DOMBuilder rtf) throws SAXException { // Close all the open elements... tempStack = new Stack(); while (!elementStack.empty()) { Node elem = (Node) elementStack.pop(); String ns = elem.getNamespaceURI(); String localName = elem.getLocalName(); String name = ((Element) elem).getTagName(); // If this is the bottom of the stack and it's an fo:block // or an HTML pre or div, don't duplicate it... if (elementStack.empty() && (((ns != null) && ns.equals(foURI) && localName.equals("block")) || (((ns == null) && localName.equalsIgnoreCase("pre")) || ((ns != null) && ns.equals(xhURI) && localName.equals("pre"))) || (((ns == null) && localName.equalsIgnoreCase("div")) || ((ns != null) && ns.equals(xhURI) && localName.equals("div"))))) { elementStack.push(elem); break; } else { rtf.endElement(ns, localName, name); tempStack.push(elem); } } } private void openClosedElements(DOMBuilder rtf) throws SAXException { // Now "reopen" the elements that we closed... while (!tempStack.empty()) { Node elem = (Node) tempStack.pop(); String ns = elem.getNamespaceURI(); String localName = elem.getLocalName(); String name = ((Element) elem).getTagName(); NamedNodeMap domAttr = elem.getAttributes(); AttributesImpl attr = new AttributesImpl(); for (int acount = 0; acount < domAttr.getLength(); acount++) { Node a = domAttr.item(acount); if (((ns == null || ns == "http://www.w3.org/1999/xhtml") && localName.equalsIgnoreCase("a")) || (a.getLocalName().equalsIgnoreCase("id"))) { // skip this attribute } else { attr.addAttribute(a.getNamespaceURI(), a.getLocalName(), a.getNodeName(), "CDATA", a.getNodeValue()); } } rtf.startElement(ns, localName, name, attr); elementStack.push(elem); } tempStack = null; } private Attributes copyAttributes(Element node) { AttributesImpl attrs = new AttributesImpl(); NamedNodeMap nnm = node.getAttributes(); for (int count = 0; count < nnm.getLength(); count++) { Attr attr = (Attr) nnm.item(count); String name = attr.getName(); if (name.startsWith("xmlns:") || name.equals("xmlns")) { // Skip it; (don't ya just love it!!) } else { attrs.addAttribute(attr.getNamespaceURI(), attr.getName(), attr.getName(), "CDATA", attr.getValue()); } } return attrs; } }