Source code: com/nwalsh/saxon/NumberLinesEmitter.java
1 package com.nwalsh.saxon;
2
3 import java.util.Stack;
4 import java.util.StringTokenizer;
5 import org.xml.sax.*;
6 import org.w3c.dom.*;
7 import javax.xml.transform.TransformerException;
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 *
18 * <p>Copyright (C) 2000 Norman Walsh.</p>
19 *
20 * <p>This class provides the guts of a
21 * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon 6.*</a>
22 * implementation of line numbering for verbatim environments. (It is used
23 * by the Verbatim class.)</p>
24 *
25 * <p>The general design is this: the stylesheets construct a result tree
26 * fragment for some verbatim environment. The Verbatim class initializes
27 * a NumberLinesEmitter with information about what lines should be
28 * numbered and how. Then the result tree fragment
29 * is "replayed" through the NumberLinesEmitter; the NumberLinesEmitter
30 * builds a
31 * new result tree fragment from this event stream, decorated with line
32 * numbers,
33 * and that is returned.</p>
34 *
35 * <p><b>Change Log:</b></p>
36 * <dl>
37 * <dt>1.0</dt>
38 * <dd><p>Initial release.</p></dd>
39 * </dl>
40 *
41 * @see Verbatim
42 *
43 * @author Norman Walsh
44 * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
45 *
46 *
47 */
48 public class NumberLinesEmitter extends CopyEmitter {
49 /** A stack for the preserving information about open elements. */
50 protected Stack elementStack = null;
51
52 /** The current line number. */
53 protected int lineNumber = 0;
54
55 /** Is the next element absolutely the first element in the fragment? */
56 protected boolean firstElement = false;
57
58 /** The FO namespace name. */
59 protected static String foURI = "http://www.w3.org/1999/XSL/Format";
60
61 /** Every <code>modulus</code> line will be numbered. */
62 protected int modulus = 5;
63
64 /** Line numbers are <code>width</code> characters wide. */
65 protected int width = 3;
66
67 /** Line numbers are separated from the listing by <code>separator</code>. */
68 protected String separator = " ";
69
70 /** Is the stylesheet currently running an FO stylesheet? */
71 protected boolean foStylesheet = false;
72
73 /** <p>Constructor for the NumberLinesEmitter.</p>
74 *
75 * @param namePool The name pool to use for constructing elements and attributes.
76 * @param modulus The modulus to use for this listing.
77 * @param width The width to use for line numbers in this listing.
78 * @param separator The separator to use for this listing.
79 * @param foStylesheet Is this an FO stylesheet?
80 */
81 public NumberLinesEmitter(Controller controller,
82 NamePool namePool,
83 int modulus,
84 int width,
85 String separator,
86 boolean foStylesheet) {
87 super(controller,namePool);
88 elementStack = new Stack();
89 firstElement = true;
90
91 this.modulus = modulus;
92 this.width = width;
93 this.separator = separator;
94 this.foStylesheet = foStylesheet;
95 }
96
97 /** Process characters. */
98 public void characters(char[] chars, int start, int len)
99 throws TransformerException {
100
101 // If we hit characters, then there's no first element...
102 firstElement = false;
103
104 if (lineNumber == 0) {
105 // The first line is always numbered
106 formatLineNumber(++lineNumber);
107 }
108
109 // Walk through the text node looking for newlines
110 char[] newChars = new char[len];
111 int pos = 0;
112 for (int count = start; count < start+len; count++) {
113 if (chars[count] == '\n') {
114 // This is the tricky bit; if we find a newline, make sure
115 // it doesn't occur inside any markup.
116
117 if (pos > 0) {
118 // Output any characters that preceded this newline
119 rtfEmitter.characters(newChars, 0, pos);
120 pos = 0;
121 }
122
123 // Close all the open elements...
124 Stack tempStack = new Stack();
125 while (!elementStack.empty()) {
126 StartElementInfo elem = (StartElementInfo) elementStack.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.pop();
142 AttributeCollection attr = (AttributeCollection)elem.getAttributes();
143 AttributeCollection newAttr = new AttributeCollection(namePool);
144
145 for (int acount = 0; acount < attr.getLength(); acount++) {
146 String localName = attr.getLocalName(acount);
147 int nameCode = attr.getNameCode(acount);
148 String type = attr.getType(acount);
149 String value = attr.getValue(acount);
150 String uri = attr.getURI(acount);
151 String prefix = "";
152
153 if (localName.indexOf(':') > 0) {
154 prefix = localName.substring(0, localName.indexOf(':'));
155 localName = localName.substring(localName.indexOf(':')+1);
156 }
157
158 if (uri.equals("")
159 && ((foStylesheet
160 && localName.equals("id"))
161 || (!foStylesheet
162 && (localName.equals("id")
163 || localName.equals("name"))))) {
164 // skip this attribute
165 } else {
166 newAttr.addAttribute(prefix, uri, localName, type, value);
167 }
168 }
169
170 rtfEmitter.startElement(elem.getNameCode(),
171 newAttr,
172 elem.getNamespaces(),
173 elem.getNSCount());
174
175 elementStack.push(elem);
176 }
177 } else {
178 newChars[pos++] = chars[count];
179 }
180 }
181
182 if (pos > 0) {
183 rtfEmitter.characters(newChars, 0, pos);
184 pos = 0;
185 }
186 }
187
188 /**
189 * <p>Add a formatted line number to the result tree fragment.</p>
190 *
191 * @param lineNumber The number of the current line.
192 */
193 protected void formatLineNumber(int lineNumber)
194 throws TransformerException {
195
196 char ch = 160; //
197
198 String lno = "";
199 if (lineNumber == 1
200 || (modulus >= 1 && (lineNumber % modulus == 0))) {
201 lno = "" + lineNumber;
202 }
203
204 while (lno.length() < width) {
205 lno = ch + lno;
206 }
207
208 lno += separator;
209
210 char chars[] = new char[lno.length()];
211 for (int count = 0; count < lno.length(); count++) {
212 chars[count] = lno.charAt(count);
213 }
214
215 characters(chars, 0, lno.length());
216 }
217
218 /** Process end element events. */
219 public void endElement(int nameCode)
220 throws TransformerException {
221 if (!elementStack.empty()) {
222 // if we didn't push the very first element (an fo:block or
223 // pre or div surrounding the whole block), then the stack will
224 // be empty when we get to the end of the first element...
225 elementStack.pop();
226 }
227 rtfEmitter.endElement(nameCode);
228 }
229
230 /** Process start element events. */
231 public void startElement(int nameCode,
232 org.xml.sax.Attributes attributes,
233 int[] namespaces,
234 int nscount)
235 throws TransformerException {
236
237 if (!skipThisElement(nameCode)) {
238 StartElementInfo sei = new StartElementInfo(nameCode, attributes,
239 namespaces, nscount);
240 elementStack.push(sei);
241 }
242
243 firstElement = false;
244
245 rtfEmitter.startElement(nameCode, attributes, namespaces, nscount);
246 }
247
248 /**
249 * <p>Protect the outer-most block wrapper.</p>
250 *
251 * <p>Open elements in the result tree fragment are closed and reopened
252 * around callouts (so that callouts don't appear inside links or other
253 * environments). But if the result tree fragment is a single block
254 * (a div or pre in HTML, an fo:block in FO), that outer-most block is
255 * treated specially.</p>
256 *
257 * <p>This method returns true if the element in question is that
258 * outermost block.</p>
259 *
260 * @param nameCode The name code for the element
261 *
262 * @return True if the element is the outer-most block, false otherwise.
263 */
264 protected boolean skipThisElement(int nameCode) {
265 if (firstElement) {
266 int thisFingerprint = namePool.getFingerprint(nameCode);
267 int foBlockFingerprint = namePool.getFingerprint(foURI, "block");
268 int htmlPreFingerprint = namePool.getFingerprint("", "pre");
269 int htmlDivFingerprint = namePool.getFingerprint("", "div");
270
271 if ((foStylesheet && thisFingerprint == foBlockFingerprint)
272 || (!foStylesheet && (thisFingerprint == htmlPreFingerprint
273 || thisFingerprint == htmlDivFingerprint))) {
274 // Don't push the outer-most wrapping div, pre, or fo:block
275 return true;
276 }
277 }
278
279 return false;
280 }
281
282 /**
283 * <p>A private class for maintaining the information required to call
284 * the startElement method.</p>
285 *
286 * <p>In order to close and reopen elements, information about those
287 * elements has to be maintained. This class is just the little record
288 * that we push on the stack to keep track of that info.</p>
289 */
290 private class StartElementInfo {
291 private int _nameCode;
292 org.xml.sax.Attributes _attributes;
293 int[] _namespaces;
294 int _nscount;
295
296 public StartElementInfo(int nameCode,
297 org.xml.sax.Attributes attributes,
298 int[] namespaces,
299 int nscount) {
300 _nameCode = nameCode;
301 _attributes = attributes;
302 _namespaces = namespaces;
303 _nscount = nscount;
304 }
305
306 public int getNameCode() {
307 return _nameCode;
308 }
309
310 public org.xml.sax.Attributes getAttributes() {
311 return _attributes;
312 }
313
314 public int[] getNamespaces() {
315 return _namespaces;
316 }
317
318 public int getNSCount() {
319 return _nscount;
320 }
321 }
322 }