package com.nwalsh.saxon; import java.util.Stack; import java.util.StringTokenizer; import org.xml.sax.*; import org.w3c.dom.*; import javax.xml.transform.TransformerException; import com.icl.saxon.output.*; import com.icl.saxon.om.*; import com.icl.saxon.Controller; import com.icl.saxon.tree.AttributeCollection; import com.icl.saxon.expr.FragmentValue; /** *

Saxon extension to decorate a result tree fragment with line numbers.

* *

$Id: NumberLinesEmitter.java,v 1.3 2003/08/27 14:24:59 nwalsh Exp $

* *

Copyright (C) 2000 Norman Walsh.

* *

This class provides the guts of a * Saxon 6.* * implementation of line numbering for verbatim environments. (It is used * by the Verbatim class.)

* *

The general design is this: the stylesheets construct a result tree * fragment for some verbatim environment. The Verbatim class initializes * a NumberLinesEmitter with information about what lines should be * numbered and how. Then the result tree fragment * is "replayed" through the NumberLinesEmitter; the NumberLinesEmitter * builds a * new result tree fragment from this event stream, decorated with line * numbers, * and that is returned.

* *

Change Log:

*
*
1.0
*

Initial release.

*
* * @see Verbatim * * @author Norman Walsh * ndw@nwalsh.com * * @version $Id: NumberLinesEmitter.java,v 1.3 2003/08/27 14:24:59 nwalsh Exp $ * */ public class NumberLinesEmitter extends CopyEmitter { /** A stack for the preserving information about open elements. */ protected Stack elementStack = null; /** The current line number. */ protected int lineNumber = 0; /** Is the next element absolutely the first element in the fragment? */ protected boolean firstElement = false; /** The FO namespace name. */ protected static String foURI = "http://www.w3.org/1999/XSL/Format"; /** The XHTML namespace name. */ protected static String xhURI = "http://www.w3.org/1999/xhtml"; /** The first line number will be startinglinenumber. */ protected int startinglinenumber = 1; /** Every modulus line will be numbered. */ protected int modulus = 5; /** Line numbers are width characters wide. */ protected int width = 3; /** Line numbers are separated from the listing by separator. */ protected String separator = " "; /** Is the stylesheet currently running an FO stylesheet? */ protected boolean foStylesheet = false; /**

Constructor for the NumberLinesEmitter.

* * @param namePool The name pool to use for constructing elements and attributes. * @param modulus The modulus to use for this listing. * @param width The width to use for line numbers in this listing. * @param separator The separator to use for this listing. * @param foStylesheet Is this an FO stylesheet? */ public NumberLinesEmitter(Controller controller, NamePool namePool, int startingLineNumber, int modulus, int width, String separator, boolean foStylesheet) { super(controller,namePool); elementStack = new Stack(); firstElement = true; this.modulus = modulus; this.startinglinenumber = startingLineNumber; this.width = width; this.separator = separator; this.foStylesheet = foStylesheet; } /** Process characters. */ public void characters(char[] chars, int start, int len) throws TransformerException { // If we hit characters, then there's no first element... firstElement = false; if (lineNumber == 0) { // The first line is always numbered lineNumber = startinglinenumber; formatLineNumber(lineNumber); } // Walk through the text node looking for newlines char[] newChars = new char[len]; int pos = 0; for (int count = start; count < start+len; count++) { if (chars[count] == '\n') { // This is the tricky bit; if we find a newline, make sure // it doesn't occur inside any markup. if (pos > 0) { // Output any characters that preceded this newline rtfEmitter.characters(newChars, 0, pos); pos = 0; } // Close all the open elements... Stack tempStack = new Stack(); while (!elementStack.empty()) { StartElementInfo elem = (StartElementInfo) elementStack.pop(); rtfEmitter.endElement(elem.getNameCode()); tempStack.push(elem); } // Copy the newline to the output newChars[pos++] = chars[count]; rtfEmitter.characters(newChars, 0, pos); pos = 0; // Add the line number formatLineNumber(++lineNumber); // Now "reopen" the elements that we closed... while (!tempStack.empty()) { StartElementInfo elem = (StartElementInfo) tempStack.pop(); AttributeCollection attr = (AttributeCollection)elem.getAttributes(); AttributeCollection newAttr = new AttributeCollection(namePool); for (int acount = 0; acount < attr.getLength(); acount++) { String localName = attr.getLocalName(acount); int nameCode = attr.getNameCode(acount); String type = attr.getType(acount); String value = attr.getValue(acount); String uri = attr.getURI(acount); String prefix = ""; if (localName.indexOf(':') > 0) { prefix = localName.substring(0, localName.indexOf(':')); localName = localName.substring(localName.indexOf(':')+1); } if (uri.equals("") && ((foStylesheet && localName.equals("id")) || (!foStylesheet && (localName.equals("id") || localName.equals("name"))))) { // skip this attribute } else { newAttr.addAttribute(prefix, uri, localName, type, value); } } rtfEmitter.startElement(elem.getNameCode(), newAttr, elem.getNamespaces(), elem.getNSCount()); elementStack.push(elem); } } else { newChars[pos++] = chars[count]; } } if (pos > 0) { rtfEmitter.characters(newChars, 0, pos); pos = 0; } } /** *

Add a formatted line number to the result tree fragment.

* * @param lineNumber The number of the current line. */ protected void formatLineNumber(int lineNumber) throws TransformerException { 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[] = new char[lno.length()]; for (int count = 0; count < lno.length(); count++) { chars[count] = lno.charAt(count); } characters(chars, 0, lno.length()); } /** Process end element events. */ public void endElement(int nameCode) throws TransformerException { if (!elementStack.empty()) { // if we didn't push the very first element (an fo:block or // pre or div surrounding the whole block), then the stack will // be empty when we get to the end of the first element... elementStack.pop(); } rtfEmitter.endElement(nameCode); } /** Process start element events. */ public void startElement(int nameCode, org.xml.sax.Attributes attributes, int[] namespaces, int nscount) throws TransformerException { if (!skipThisElement(nameCode)) { StartElementInfo sei = new StartElementInfo(nameCode, attributes, namespaces, nscount); elementStack.push(sei); } firstElement = false; rtfEmitter.startElement(nameCode, attributes, namespaces, nscount); } /** *

Protect the outer-most block wrapper.

* *

Open elements in the result tree fragment are closed and reopened * around callouts (so that callouts don't appear inside links or other * environments). But if the result tree fragment is a single block * (a div or pre in HTML, an fo:block in FO), that outer-most block is * treated specially.

* *

This method returns true if the element in question is that * outermost block.

* * @param nameCode The name code for the element * * @return True if the element is the outer-most block, false otherwise. */ protected boolean skipThisElement(int nameCode) { // FIXME: This is such a gross hack... if (firstElement) { int thisFingerprint = namePool.getFingerprint(nameCode); int foBlockFingerprint = namePool.getFingerprint(foURI, "block"); int htmlPreFingerprint = namePool.getFingerprint("", "pre"); int htmlDivFingerprint = namePool.getFingerprint("", "div"); int xhtmlPreFingerprint = namePool.getFingerprint(xhURI, "pre"); int xhtmlDivFingerprint = namePool.getFingerprint(xhURI, "div"); if ((foStylesheet && thisFingerprint == foBlockFingerprint) || (!foStylesheet && (thisFingerprint == htmlPreFingerprint || thisFingerprint == htmlDivFingerprint || thisFingerprint == xhtmlPreFingerprint || thisFingerprint == xhtmlDivFingerprint))) { // Don't push the outer-most wrapping div, pre, or fo:block return true; } } return false; } /** *

A private class for maintaining the information required to call * the startElement method.

* *

In order to close and reopen elements, information about those * elements has to be maintained. This class is just the little record * that we push on the stack to keep track of that info.

*/ private class StartElementInfo { private int _nameCode; org.xml.sax.Attributes _attributes; int[] _namespaces; int _nscount; public StartElementInfo(int nameCode, org.xml.sax.Attributes attributes, int[] namespaces, int nscount) { _nameCode = nameCode; _attributes = attributes; _namespaces = namespaces; _nscount = nscount; } public int getNameCode() { return _nameCode; } public org.xml.sax.Attributes getAttributes() { return _attributes; } public int[] getNamespaces() { return _namespaces; } public int getNSCount() { return _nscount; } } }