001: package com.nwalsh.saxon;
002:
003: import java.util.Stack;
004: import java.util.StringTokenizer;
005: import org.xml.sax.*;
006: import org.w3c.dom.*;
007: import javax.xml.transform.TransformerException;
008: import com.icl.saxon.output.*;
009: import com.icl.saxon.om.*;
010: import com.icl.saxon.Controller;
011: import com.icl.saxon.tree.AttributeCollection;
012: import com.icl.saxon.expr.FragmentValue;
013:
014: /**
015: * <p>Saxon extension to decorate a result tree fragment with line numbers.</p>
016: *
017: * <p>$Id: NumberLinesEmitter.java,v 1.1 2001/07/16 21:23:57 nwalsh Exp $</p>
018: *
019: * <p>Copyright (C) 2000 Norman Walsh.</p>
020: *
021: * <p>This class provides the guts of a
022: * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon 6.*</a>
023: * implementation of line numbering for verbatim environments. (It is used
024: * by the Verbatim class.)</p>
025: *
026: * <p>The general design is this: the stylesheets construct a result tree
027: * fragment for some verbatim environment. The Verbatim class initializes
028: * a NumberLinesEmitter with information about what lines should be
029: * numbered and how. Then the result tree fragment
030: * is "replayed" through the NumberLinesEmitter; the NumberLinesEmitter
031: * builds a
032: * new result tree fragment from this event stream, decorated with line
033: * numbers,
034: * and that is returned.</p>
035: *
036: * <p><b>Change Log:</b></p>
037: * <dl>
038: * <dt>1.0</dt>
039: * <dd><p>Initial release.</p></dd>
040: * </dl>
041: *
042: * @see Verbatim
043: *
044: * @author Norman Walsh
045: * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
046: *
047: * @version $Id: NumberLinesEmitter.java,v 1.1 2001/07/16 21:23:57 nwalsh Exp $
048: *
049: */
050: public class NumberLinesEmitter extends CopyEmitter {
051: /** A stack for the preserving information about open elements. */
052: protected Stack elementStack = null;
053:
054: /** The current line number. */
055: protected int lineNumber = 0;
056:
057: /** Is the next element absolutely the first element in the fragment? */
058: protected boolean firstElement = false;
059:
060: /** The FO namespace name. */
061: protected static String foURI = "http://www.w3.org/1999/XSL/Format";
062:
063: /** Every <code>modulus</code> line will be numbered. */
064: protected int modulus = 5;
065:
066: /** Line numbers are <code>width</code> characters wide. */
067: protected int width = 3;
068:
069: /** Line numbers are separated from the listing by <code>separator</code>. */
070: protected String separator = " ";
071:
072: /** Is the stylesheet currently running an FO stylesheet? */
073: protected boolean foStylesheet = false;
074:
075: /** <p>Constructor for the NumberLinesEmitter.</p>
076: *
077: * @param namePool The name pool to use for constructing elements and attributes.
078: * @param modulus The modulus to use for this listing.
079: * @param width The width to use for line numbers in this listing.
080: * @param separator The separator to use for this listing.
081: * @param foStylesheet Is this an FO stylesheet?
082: */
083: public NumberLinesEmitter(Controller controller, NamePool namePool,
084: int modulus, int width, String separator,
085: boolean foStylesheet) {
086: super (controller, namePool);
087: elementStack = new Stack();
088: firstElement = true;
089:
090: this .modulus = modulus;
091: this .width = width;
092: this .separator = separator;
093: this .foStylesheet = foStylesheet;
094: }
095:
096: /** Process characters. */
097: public void characters(char[] chars, int start, int len)
098: throws TransformerException {
099:
100: // If we hit characters, then there's no first element...
101: firstElement = false;
102:
103: if (lineNumber == 0) {
104: // The first line is always numbered
105: formatLineNumber(++lineNumber);
106: }
107:
108: // Walk through the text node looking for newlines
109: char[] newChars = new char[len];
110: int pos = 0;
111: for (int count = start; count < start + len; count++) {
112: if (chars[count] == '\n') {
113: // This is the tricky bit; if we find a newline, make sure
114: // it doesn't occur inside any markup.
115:
116: if (pos > 0) {
117: // Output any characters that preceded this newline
118: rtfEmitter.characters(newChars, 0, pos);
119: pos = 0;
120: }
121:
122: // Close all the open elements...
123: Stack tempStack = new Stack();
124: while (!elementStack.empty()) {
125: StartElementInfo elem = (StartElementInfo) elementStack
126: .pop();
127: rtfEmitter.endElement(elem.getNameCode());
128: tempStack.push(elem);
129: }
130:
131: // Copy the newline to the output
132: newChars[pos++] = chars[count];
133: rtfEmitter.characters(newChars, 0, pos);
134: pos = 0;
135:
136: // Add the line number
137: formatLineNumber(++lineNumber);
138:
139: // Now "reopen" the elements that we closed...
140: while (!tempStack.empty()) {
141: StartElementInfo elem = (StartElementInfo) tempStack
142: .pop();
143: AttributeCollection attr = (AttributeCollection) elem
144: .getAttributes();
145: AttributeCollection newAttr = new AttributeCollection(
146: namePool);
147:
148: for (int acount = 0; acount < attr.getLength(); acount++) {
149: String localName = attr.getLocalName(acount);
150: int nameCode = attr.getNameCode(acount);
151: String type = attr.getType(acount);
152: String value = attr.getValue(acount);
153: String uri = attr.getURI(acount);
154: String prefix = "";
155:
156: if (localName.indexOf(':') > 0) {
157: prefix = localName.substring(0, localName
158: .indexOf(':'));
159: localName = localName.substring(localName
160: .indexOf(':') + 1);
161: }
162:
163: if (uri.equals("")
164: && ((foStylesheet && localName
165: .equals("id")) || (!foStylesheet && (localName
166: .equals("id") || localName
167: .equals("name"))))) {
168: // skip this attribute
169: } else {
170: newAttr.addAttribute(prefix, uri,
171: localName, type, value);
172: }
173: }
174:
175: rtfEmitter.startElement(elem.getNameCode(),
176: newAttr, elem.getNamespaces(), elem
177: .getNSCount());
178:
179: elementStack.push(elem);
180: }
181: } else {
182: newChars[pos++] = chars[count];
183: }
184: }
185:
186: if (pos > 0) {
187: rtfEmitter.characters(newChars, 0, pos);
188: pos = 0;
189: }
190: }
191:
192: /**
193: * <p>Add a formatted line number to the result tree fragment.</p>
194: *
195: * @param lineNumber The number of the current line.
196: */
197: protected void formatLineNumber(int lineNumber)
198: throws TransformerException {
199:
200: char ch = 160; //
201:
202: String lno = "";
203: if (lineNumber == 1
204: || (modulus >= 1 && (lineNumber % modulus == 0))) {
205: lno = "" + lineNumber;
206: }
207:
208: while (lno.length() < width) {
209: lno = ch + lno;
210: }
211:
212: lno += separator;
213:
214: char chars[] = new char[lno.length()];
215: for (int count = 0; count < lno.length(); count++) {
216: chars[count] = lno.charAt(count);
217: }
218:
219: characters(chars, 0, lno.length());
220: }
221:
222: /** Process end element events. */
223: public void endElement(int nameCode) throws TransformerException {
224: if (!elementStack.empty()) {
225: // if we didn't push the very first element (an fo:block or
226: // pre or div surrounding the whole block), then the stack will
227: // be empty when we get to the end of the first element...
228: elementStack.pop();
229: }
230: rtfEmitter.endElement(nameCode);
231: }
232:
233: /** Process start element events. */
234: public void startElement(int nameCode,
235: org.xml.sax.Attributes attributes, int[] namespaces,
236: int nscount) throws TransformerException {
237:
238: if (!skipThisElement(nameCode)) {
239: StartElementInfo sei = new StartElementInfo(nameCode,
240: attributes, namespaces, nscount);
241: elementStack.push(sei);
242: }
243:
244: firstElement = false;
245:
246: rtfEmitter.startElement(nameCode, attributes, namespaces,
247: nscount);
248: }
249:
250: /**
251: * <p>Protect the outer-most block wrapper.</p>
252: *
253: * <p>Open elements in the result tree fragment are closed and reopened
254: * around callouts (so that callouts don't appear inside links or other
255: * environments). But if the result tree fragment is a single block
256: * (a div or pre in HTML, an fo:block in FO), that outer-most block is
257: * treated specially.</p>
258: *
259: * <p>This method returns true if the element in question is that
260: * outermost block.</p>
261: *
262: * @param nameCode The name code for the element
263: *
264: * @return True if the element is the outer-most block, false otherwise.
265: */
266: protected boolean skipThisElement(int nameCode) {
267: if (firstElement) {
268: int this Fingerprint = namePool.getFingerprint(nameCode);
269: int foBlockFingerprint = namePool.getFingerprint(foURI,
270: "block");
271: int htmlPreFingerprint = namePool.getFingerprint("", "pre");
272: int htmlDivFingerprint = namePool.getFingerprint("", "div");
273:
274: if ((foStylesheet && this Fingerprint == foBlockFingerprint)
275: || (!foStylesheet && (this Fingerprint == htmlPreFingerprint || this Fingerprint == htmlDivFingerprint))) {
276: // Don't push the outer-most wrapping div, pre, or fo:block
277: return true;
278: }
279: }
280:
281: return false;
282: }
283:
284: /**
285: * <p>A private class for maintaining the information required to call
286: * the startElement method.</p>
287: *
288: * <p>In order to close and reopen elements, information about those
289: * elements has to be maintained. This class is just the little record
290: * that we push on the stack to keep track of that info.</p>
291: */
292: private class StartElementInfo {
293: private int _nameCode;
294: org.xml.sax.Attributes _attributes;
295: int[] _namespaces;
296: int _nscount;
297:
298: public StartElementInfo(int nameCode,
299: org.xml.sax.Attributes attributes, int[] namespaces,
300: int nscount) {
301: _nameCode = nameCode;
302: _attributes = attributes;
303: _namespaces = namespaces;
304: _nscount = nscount;
305: }
306:
307: public int getNameCode() {
308: return _nameCode;
309: }
310:
311: public org.xml.sax.Attributes getAttributes() {
312: return _attributes;
313: }
314:
315: public int[] getNamespaces() {
316: return _namespaces;
317: }
318:
319: public int getNSCount() {
320: return _nscount;
321: }
322: }
323: }
|