KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > nwalsh > saxon > NumberLinesEmitter


1 package com.nwalsh.saxon;
2
3 import java.util.Stack JavaDoc;
4 import java.util.StringTokenizer JavaDoc;
5 import org.xml.sax.*;
6 import org.w3c.dom.*;
7 import javax.xml.transform.TransformerException JavaDoc;
8 import com.icl.saxon.output.*;
9 import com.icl.saxon.om.*;
10 import com.icl.saxon.Controller;
11 import com.icl.saxon.tree.AttributeCollection;
12 import com.icl.saxon.expr.FragmentValue;
13
14 /**
15  * <p>Saxon extension to decorate a result tree fragment with line numbers.</p>
16  *
17  * <p>$Id: NumberLinesEmitter.java,v 1.3 2003/08/27 14:24:59 nwalsh Exp $</p>
18  *
19  * <p>Copyright (C) 2000 Norman Walsh.</p>
20  *
21  * <p>This class provides the guts of a
22  * <a HREF="http://users.iclway.co.uk/mhkay/saxon/">Saxon 6.*</a>
23  * implementation of line numbering for verbatim environments. (It is used
24  * by the Verbatim class.)</p>
25  *
26  * <p>The general design is this: the stylesheets construct a result tree
27  * fragment for some verbatim environment. The Verbatim class initializes
28  * a NumberLinesEmitter with information about what lines should be
29  * numbered and how. Then the result tree fragment
30  * is "replayed" through the NumberLinesEmitter; the NumberLinesEmitter
31  * builds a
32  * new result tree fragment from this event stream, decorated with line
33  * numbers,
34  * and that is returned.</p>
35  *
36  * <p><b>Change Log:</b></p>
37  * <dl>
38  * <dt>1.0</dt>
39  * <dd><p>Initial release.</p></dd>
40  * </dl>
41  *
42  * @see Verbatim
43  *
44  * @author Norman Walsh
45  * <a HREF="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
46  *
47  * @version $Id: NumberLinesEmitter.java,v 1.3 2003/08/27 14:24:59 nwalsh Exp $
48  *
49  */

50 public class NumberLinesEmitter extends CopyEmitter {
51   /** A stack for the preserving information about open elements. */
52   protected Stack JavaDoc elementStack = null;
53
54   /** The current line number. */
55   protected int lineNumber = 0;
56
57   /** Is the next element absolutely the first element in the fragment? */
58   protected boolean firstElement = false;
59
60   /** The FO namespace name. */
61   protected static String JavaDoc foURI = "http://www.w3.org/1999/XSL/Format";
62
63   /** The XHTML namespace name. */
64   protected static String JavaDoc xhURI = "http://www.w3.org/1999/xhtml";
65
66   /** The first line number will be <code>startinglinenumber</code>. */
67   protected int startinglinenumber = 1;
68
69   /** Every <code>modulus</code> line will be numbered. */
70   protected int modulus = 5;
71
72   /** Line numbers are <code>width</code> characters wide. */
73   protected int width = 3;
74
75   /** Line numbers are separated from the listing by <code>separator</code>. */
76   protected String JavaDoc separator = " ";
77
78   /** Is the stylesheet currently running an FO stylesheet? */
79   protected boolean foStylesheet = false;
80
81   /** <p>Constructor for the NumberLinesEmitter.</p>
82    *
83    * @param namePool The name pool to use for constructing elements and attributes.
84    * @param modulus The modulus to use for this listing.
85    * @param width The width to use for line numbers in this listing.
86    * @param separator The separator to use for this listing.
87    * @param foStylesheet Is this an FO stylesheet?
88    */

89   public NumberLinesEmitter(Controller controller,
90                 NamePool namePool,
91                 int startingLineNumber,
92                 int modulus,
93                 int width,
94                 String JavaDoc separator,
95                 boolean foStylesheet) {
96     super(controller,namePool);
97     elementStack = new Stack JavaDoc();
98     firstElement = true;
99
100     this.modulus = modulus;
101     this.startinglinenumber = startingLineNumber;
102     this.width = width;
103     this.separator = separator;
104     this.foStylesheet = foStylesheet;
105   }
106
107   /** Process characters. */
108   public void characters(char[] chars, int start, int len)
109     throws TransformerException JavaDoc {
110
111     // If we hit characters, then there's no first element...
112
firstElement = false;
113
114     if (lineNumber == 0) {
115       // The first line is always numbered
116
lineNumber = startinglinenumber;
117       formatLineNumber(lineNumber);
118     }
119
120     // Walk through the text node looking for newlines
121
char[] newChars = new char[len];
122     int pos = 0;
123     for (int count = start; count < start+len; count++) {
124       if (chars[count] == '\n') {
125     // This is the tricky bit; if we find a newline, make sure
126
// it doesn't occur inside any markup.
127

128     if (pos > 0) {
129       // Output any characters that preceded this newline
130
rtfEmitter.characters(newChars, 0, pos);
131       pos = 0;
132     }
133
134     // Close all the open elements...
135
Stack JavaDoc tempStack = new Stack JavaDoc();
136     while (!elementStack.empty()) {
137       StartElementInfo elem = (StartElementInfo) elementStack.pop();
138       rtfEmitter.endElement(elem.getNameCode());
139       tempStack.push(elem);
140     }
141
142     // Copy the newline to the output
143
newChars[pos++] = chars[count];
144     rtfEmitter.characters(newChars, 0, pos);
145     pos = 0;
146
147     // Add the line number
148
formatLineNumber(++lineNumber);
149
150     // Now "reopen" the elements that we closed...
151
while (!tempStack.empty()) {
152       StartElementInfo elem = (StartElementInfo) tempStack.pop();
153       AttributeCollection attr = (AttributeCollection)elem.getAttributes();
154       AttributeCollection newAttr = new AttributeCollection(namePool);
155
156       for (int acount = 0; acount < attr.getLength(); acount++) {
157         String JavaDoc localName = attr.getLocalName(acount);
158         int nameCode = attr.getNameCode(acount);
159         String JavaDoc type = attr.getType(acount);
160         String JavaDoc value = attr.getValue(acount);
161         String JavaDoc uri = attr.getURI(acount);
162         String JavaDoc prefix = "";
163
164         if (localName.indexOf(':') > 0) {
165           prefix = localName.substring(0, localName.indexOf(':'));
166           localName = localName.substring(localName.indexOf(':')+1);
167         }
168
169         if (uri.equals("")
170         && ((foStylesheet
171              && localName.equals("id"))
172             || (!foStylesheet
173             && (localName.equals("id")
174                 || localName.equals("name"))))) {
175           // skip this attribute
176
} else {
177           newAttr.addAttribute(prefix, uri, localName, type, value);
178         }
179       }
180
181       rtfEmitter.startElement(elem.getNameCode(),
182                newAttr,
183                elem.getNamespaces(),
184                elem.getNSCount());
185
186       elementStack.push(elem);
187     }
188       } else {
189     newChars[pos++] = chars[count];
190       }
191     }
192
193     if (pos > 0) {
194       rtfEmitter.characters(newChars, 0, pos);
195       pos = 0;
196     }
197   }
198
199   /**
200    * <p>Add a formatted line number to the result tree fragment.</p>
201    *
202    * @param lineNumber The number of the current line.
203    */

204   protected void formatLineNumber(int lineNumber)
205     throws TransformerException JavaDoc {
206
207     char ch = 160; // &nbsp;
208

209     String JavaDoc lno = "";
210     if (lineNumber == 1
211     || (modulus >= 1 && (lineNumber % modulus == 0))) {
212       lno = "" + lineNumber;
213     }
214
215     while (lno.length() < width) {
216       lno = ch + lno;
217     }
218
219     lno += separator;
220
221     char chars[] = new char[lno.length()];
222     for (int count = 0; count < lno.length(); count++) {
223       chars[count] = lno.charAt(count);
224     }
225
226     characters(chars, 0, lno.length());
227   }
228
229   /** Process end element events. */
230   public void endElement(int nameCode)
231     throws TransformerException JavaDoc {
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 JavaDoc attributes,
244                int[] namespaces,
245                int nscount)
246     throws TransformerException JavaDoc {
247
248     if (!skipThisElement(nameCode)) {
249       StartElementInfo sei = new StartElementInfo(nameCode, attributes,
250                           namespaces, nscount);
251       elementStack.push(sei);
252     }
253
254     firstElement = false;
255
256     rtfEmitter.startElement(nameCode, attributes, namespaces, nscount);
257   }
258
259   /**
260    * <p>Protect the outer-most block wrapper.</p>
261    *
262    * <p>Open elements in the result tree fragment are closed and reopened
263    * around callouts (so that callouts don't appear inside links or other
264    * environments). But if the result tree fragment is a single block
265    * (a div or pre in HTML, an fo:block in FO), that outer-most block is
266    * treated specially.</p>
267    *
268    * <p>This method returns true if the element in question is that
269    * outermost block.</p>
270    *
271    * @param nameCode The name code for the element
272    *
273    * @return True if the element is the outer-most block, false otherwise.
274    */

275   protected boolean skipThisElement(int nameCode) {
276     // FIXME: This is such a gross hack...
277
if (firstElement) {
278       int thisFingerprint = namePool.getFingerprint(nameCode);
279       int foBlockFingerprint = namePool.getFingerprint(foURI, "block");
280       int htmlPreFingerprint = namePool.getFingerprint("", "pre");
281       int htmlDivFingerprint = namePool.getFingerprint("", "div");
282       int xhtmlPreFingerprint = namePool.getFingerprint(xhURI, "pre");
283       int xhtmlDivFingerprint = namePool.getFingerprint(xhURI, "div");
284
285       if ((foStylesheet && thisFingerprint == foBlockFingerprint)
286       || (!foStylesheet && (thisFingerprint == htmlPreFingerprint
287                 || thisFingerprint == htmlDivFingerprint
288                 || thisFingerprint == xhtmlPreFingerprint
289                 || thisFingerprint == xhtmlDivFingerprint))) {
290     // Don't push the outer-most wrapping div, pre, or fo:block
291
return true;
292       }
293     }
294
295     return false;
296   }
297
298   /**
299    * <p>A private class for maintaining the information required to call
300    * the startElement method.</p>
301    *
302    * <p>In order to close and reopen elements, information about those
303    * elements has to be maintained. This class is just the little record
304    * that we push on the stack to keep track of that info.</p>
305    */

306   private class StartElementInfo {
307     private int _nameCode;
308     org.xml.sax.Attributes JavaDoc _attributes;
309     int[] _namespaces;
310     int _nscount;
311
312     public StartElementInfo(int nameCode,
313                 org.xml.sax.Attributes JavaDoc attributes,
314                 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 JavaDoc 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 }
339
Popular Tags