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.4 2005-08-30 08:14:58 draganr 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.4 2005-08-30 08:14:58 draganr 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: /** The XHTML namespace name. */
064: protected static String xhURI = "http://www.w3.org/1999/xhtml";
065:
066: /** The first line number will be <code>startinglinenumber</code>. */
067: protected int startinglinenumber = 1;
068:
069: /** Every <code>modulus</code> line will be numbered. */
070: protected int modulus = 5;
071:
072: /** Line numbers are <code>width</code> characters wide. */
073: protected int width = 3;
074:
075: /** Line numbers are separated from the listing by <code>separator</code>. */
076: protected String separator = " ";
077:
078: /** Is the stylesheet currently running an FO stylesheet? */
079: protected boolean foStylesheet = false;
080:
081: /** <p>Constructor for the NumberLinesEmitter.</p>
082: *
083: * @param namePool The name pool to use for constructing elements and attributes.
084: * @param modulus The modulus to use for this listing.
085: * @param width The width to use for line numbers in this listing.
086: * @param separator The separator to use for this listing.
087: * @param foStylesheet Is this an FO stylesheet?
088: */
089: public NumberLinesEmitter(Controller controller, NamePool namePool,
090: int startingLineNumber, int modulus, int width,
091: String separator, boolean foStylesheet) {
092: super (controller, namePool);
093: elementStack = new Stack();
094: firstElement = true;
095:
096: this .modulus = modulus;
097: this .startinglinenumber = startingLineNumber;
098: this .width = width;
099: this .separator = separator;
100: this .foStylesheet = foStylesheet;
101: }
102:
103: /** Process characters. */
104: public void characters(char[] chars, int start, int len)
105: throws TransformerException {
106:
107: // If we hit characters, then there's no first element...
108: firstElement = false;
109:
110: if (lineNumber == 0) {
111: // The first line is always numbered
112: lineNumber = startinglinenumber;
113: formatLineNumber(lineNumber);
114: }
115:
116: // Walk through the text node looking for newlines
117: char[] newChars = new char[len];
118: int pos = 0;
119: for (int count = start; count < start + len; count++) {
120: if (chars[count] == '\n') {
121: // This is the tricky bit; if we find a newline, make sure
122: // it doesn't occur inside any markup.
123:
124: if (pos > 0) {
125: // Output any characters that preceded this newline
126: rtfEmitter.characters(newChars, 0, pos);
127: pos = 0;
128: }
129:
130: // Close all the open elements...
131: Stack tempStack = new Stack();
132: while (!elementStack.empty()) {
133: StartElementInfo elem = (StartElementInfo) elementStack
134: .pop();
135: rtfEmitter.endElement(elem.getNameCode());
136: tempStack.push(elem);
137: }
138:
139: // Copy the newline to the output
140: newChars[pos++] = chars[count];
141: rtfEmitter.characters(newChars, 0, pos);
142: pos = 0;
143:
144: // Add the line number
145: formatLineNumber(++lineNumber);
146:
147: // Now "reopen" the elements that we closed...
148: while (!tempStack.empty()) {
149: StartElementInfo elem = (StartElementInfo) tempStack
150: .pop();
151: AttributeCollection attr = (AttributeCollection) elem
152: .getAttributes();
153: AttributeCollection newAttr = new AttributeCollection(
154: namePool);
155:
156: for (int acount = 0; acount < attr.getLength(); acount++) {
157: String localName = attr.getLocalName(acount);
158: int nameCode = attr.getNameCode(acount);
159: String type = attr.getType(acount);
160: String value = attr.getValue(acount);
161: String uri = attr.getURI(acount);
162: String prefix = "";
163:
164: if (localName.indexOf(':') > 0) {
165: prefix = localName.substring(0, localName
166: .indexOf(':'));
167: localName = localName.substring(localName
168: .indexOf(':') + 1);
169: }
170:
171: if (uri.equals("")
172: && ((foStylesheet && localName
173: .equals("id")) || (!foStylesheet && (localName
174: .equals("id") || localName
175: .equals("name"))))) {
176: // skip this attribute
177: } else {
178: newAttr.addAttribute(prefix, uri,
179: localName, type, value);
180: }
181: }
182:
183: rtfEmitter.startElement(elem.getNameCode(),
184: newAttr, elem.getNamespaces(), elem
185: .getNSCount());
186:
187: elementStack.push(elem);
188: }
189: } else {
190: newChars[pos++] = chars[count];
191: }
192: }
193:
194: if (pos > 0) {
195: rtfEmitter.characters(newChars, 0, pos);
196: pos = 0;
197: }
198: }
199:
200: /**
201: * <p>Add a formatted line number to the result tree fragment.</p>
202: *
203: * @param lineNumber The number of the current line.
204: */
205: protected void formatLineNumber(int lineNumber)
206: throws TransformerException {
207:
208: char ch = 160; //
209:
210: String lno = "";
211: if (lineNumber == 1
212: || (modulus >= 1 && (lineNumber % modulus == 0))) {
213: lno = "" + lineNumber;
214: }
215:
216: while (lno.length() < width) {
217: lno = ch + lno;
218: }
219:
220: lno += separator;
221:
222: char chars[] = new char[lno.length()];
223: for (int count = 0; count < lno.length(); count++) {
224: chars[count] = lno.charAt(count);
225: }
226:
227: characters(chars, 0, lno.length());
228: }
229:
230: /** Process end element events. */
231: public void endElement(int nameCode) throws TransformerException {
232: if (!elementStack.empty()) {
233: // if we didn't push the very first element (an fo:block or
234: // pre or div surrounding the whole block), then the stack will
235: // be empty when we get to the end of the first element...
236: elementStack.pop();
237: }
238: rtfEmitter.endElement(nameCode);
239: }
240:
241: /** Process start element events. */
242: public void startElement(int nameCode,
243: org.xml.sax.Attributes attributes, int[] namespaces,
244: int nscount) throws TransformerException {
245:
246: if (!skipThisElement(nameCode)) {
247: StartElementInfo sei = new StartElementInfo(nameCode,
248: attributes, namespaces, nscount);
249: elementStack.push(sei);
250: }
251:
252: firstElement = false;
253:
254: rtfEmitter.startElement(nameCode, attributes, namespaces,
255: nscount);
256: }
257:
258: /**
259: * <p>Protect the outer-most block wrapper.</p>
260: *
261: * <p>Open elements in the result tree fragment are closed and reopened
262: * around callouts (so that callouts don't appear inside links or other
263: * environments). But if the result tree fragment is a single block
264: * (a div or pre in HTML, an fo:block in FO), that outer-most block is
265: * treated specially.</p>
266: *
267: * <p>This method returns true if the element in question is that
268: * outermost block.</p>
269: *
270: * @param nameCode The name code for the element
271: *
272: * @return True if the element is the outer-most block, false otherwise.
273: */
274: protected boolean skipThisElement(int nameCode) {
275: // FIXME: This is such a gross hack...
276: if (firstElement) {
277: int this Fingerprint = namePool.getFingerprint(nameCode);
278: int foBlockFingerprint = namePool.getFingerprint(foURI,
279: "block");
280: int htmlPreFingerprint = namePool.getFingerprint("", "pre");
281: int htmlDivFingerprint = namePool.getFingerprint("", "div");
282: int xhtmlPreFingerprint = namePool.getFingerprint(xhURI,
283: "pre");
284: int xhtmlDivFingerprint = namePool.getFingerprint(xhURI,
285: "div");
286:
287: if ((foStylesheet && this Fingerprint == foBlockFingerprint)
288: || (!foStylesheet && (this Fingerprint == htmlPreFingerprint
289: || this Fingerprint == htmlDivFingerprint
290: || this Fingerprint == xhtmlPreFingerprint || this Fingerprint == xhtmlDivFingerprint))) {
291: // Don't push the outer-most wrapping div, pre, or fo:block
292: return true;
293: }
294: }
295:
296: return false;
297: }
298:
299: /**
300: * <p>A private class for maintaining the information required to call
301: * the startElement method.</p>
302: *
303: * <p>In order to close and reopen elements, information about those
304: * elements has to be maintained. This class is just the little record
305: * that we push on the stack to keep track of that info.</p>
306: */
307: private class StartElementInfo {
308: private int _nameCode;
309: org.xml.sax.Attributes _attributes;
310: int[] _namespaces;
311: int _nscount;
312:
313: public StartElementInfo(int nameCode,
314: org.xml.sax.Attributes attributes, int[] namespaces,
315: int nscount) {
316: _nameCode = nameCode;
317: _attributes = attributes;
318: _namespaces = namespaces;
319: _nscount = nscount;
320: }
321:
322: public int getNameCode() {
323: return _nameCode;
324: }
325:
326: public org.xml.sax.Attributes getAttributes() {
327: return _attributes;
328: }
329:
330: public int[] getNamespaces() {
331: return _namespaces;
332: }
333:
334: public int getNSCount() {
335: return _nscount;
336: }
337: }
338: }
|