Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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; // &nbsp;
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 }