KickJava   Java API By Example, From Geeks To Geeks.

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


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.Controller;
9 import com.icl.saxon.om.NamePool;
10 import com.icl.saxon.output.Emitter;
11 import com.icl.saxon.tree.AttributeCollection;
12
13 /**
14  * <p>Saxon extension to decorate a result tree fragment with callouts.</p>
15  *
16  * <p>$Id: CalloutEmitter.java,v 1.2 2002/03/21 13:27:51 nwalsh Exp $</p>
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 callouts 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 CalloutEmitter with information about the callouts that should be applied
28  * to the verbatim environment in question. Then the result tree fragment
29  * is "replayed" through the CalloutEmitter; the CalloutEmitter builds a
30  * new result tree fragment from this event stream, decorated with callouts,
31  * and that is returned.</p>
32  *
33  * <p><b>Change Log:</b></p>
34  * <dl>
35  * <dt>1.0</dt>
36  * <dd><p>Initial release.</p></dd>
37  * </dl>
38  *
39  * @see Verbatim
40  *
41  * @author Norman Walsh
42  * <a HREF="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
43  *
44  * @version $Id: CalloutEmitter.java,v 1.2 2002/03/21 13:27:51 nwalsh Exp $
45  *
46  */

47 public class CalloutEmitter extends CopyEmitter {
48   /** A stack for the preserving information about open elements. */
49   protected Stack JavaDoc elementStack = null;
50
51   /** A stack for holding information about temporarily closed elements. */
52   protected Stack JavaDoc tempStack = null;
53
54   /** Is the next element absolutely the first element in the fragment? */
55   protected boolean firstElement = false;
56
57   /** The FO namespace name. */
58   protected static String JavaDoc foURI = "http://www.w3.org/1999/XSL/Format";
59
60   /** The XHTML namespace name. */
61   protected static String JavaDoc xhURI = "http://www.w3.org/1999/xhtml";
62
63   /** The default column for callouts that specify only a line. */
64   protected int defaultColumn = 60;
65
66   /** Is the stylesheet currently running an FO stylesheet? */
67   protected boolean foStylesheet = false;
68
69   /** The current line number. */
70   private static int lineNumber = 0;
71
72   /** The current column number. */
73   private static int colNumber = 0;
74
75   /** The (sorted) array of callouts obtained from the areaspec. */
76   private static Callout callout[] = null;
77
78   /** The number of callouts in the callout array. */
79   private static int calloutCount = 0;
80
81   /** A pointer used to keep track of our position in the callout array. */
82   private static int calloutPos = 0;
83
84   /** The FormatCallout object to use for formatting callouts. */
85   private static FormatCallout fCallout = null;
86
87   /** <p>Constructor for the CalloutEmitter.</p>
88    *
89    * @param namePool The name pool to use for constructing elements and attributes.
90    * @param graphicsPath The path to callout number graphics.
91    * @param graphicsExt The extension for callout number graphics.
92    * @param graphicsMax The largest callout number that can be represented as a graphic.
93    * @param defaultColumn The default column for callouts.
94    * @param foStylesheet Is this an FO stylesheet?
95    */

96   public CalloutEmitter(Controller controller,
97             NamePool namePool,
98             int defaultColumn,
99             boolean foStylesheet,
100             FormatCallout fCallout) {
101     super(controller, namePool);
102     elementStack = new Stack JavaDoc();
103     firstElement = true;
104
105     this.defaultColumn = defaultColumn;
106     this.foStylesheet = foStylesheet;
107     this.fCallout = fCallout;
108   }
109
110   /**
111    * <p>Examine the areaspec and determine the number and position of
112    * callouts.</p>
113    *
114    * <p>The <code><a HREF="http://docbook.org/tdg/html/areaspec.html">areaspecNodeSet</a></code>
115    * is examined and a sorted list of the callouts is constructed.</p>
116    *
117    * <p>This data structure is used to augment the result tree fragment
118    * with callout bullets.</p>
119    *
120    * @param areaspecNodeSet The source document &lt;areaspec&gt; element.
121    *
122    */

123   public void setupCallouts (NodeList areaspecNodeList) {
124     callout = new Callout[10];
125     calloutCount = 0;
126     calloutPos = 0;
127     lineNumber = 1;
128     colNumber = 1;
129
130     // First we walk through the areaspec to calculate the position
131
// of the callouts
132
// <areaspec>
133
// <areaset id="ex.plco.const" coords="">
134
// <area id="ex.plco.c1" coords="4"/>
135
// <area id="ex.plco.c2" coords="8"/>
136
// </areaset>
137
// <area id="ex.plco.ret" coords="12"/>
138
// <area id="ex.plco.dest" coords="12"/>
139
// </areaspec>
140
int pos = 0;
141     int coNum = 0;
142     boolean inAreaSet = false;
143     Node areaspec = areaspecNodeList.item(0);
144     NodeList children = areaspec.getChildNodes();
145
146     for (int count = 0; count < children.getLength(); count++) {
147       Node node = children.item(count);
148       if (node.getNodeType() == Node.ELEMENT_NODE) {
149     if (node.getNodeName().equalsIgnoreCase("areaset")) {
150       coNum++;
151       NodeList areas = node.getChildNodes();
152       for (int acount = 0; acount < areas.getLength(); acount++) {
153         Node area = areas.item(acount);
154         if (area.getNodeType() == Node.ELEMENT_NODE) {
155           if (area.getNodeName().equalsIgnoreCase("area")) {
156         addCallout(coNum, area, defaultColumn);
157           } else {
158         System.out.println("Unexpected element in areaset: "
159                    + area.getNodeName());
160           }
161         }
162       }
163     } else if (node.getNodeName().equalsIgnoreCase("area")) {
164       coNum++;
165       addCallout(coNum, node, defaultColumn);
166     } else {
167       System.out.println("Unexpected element in areaspec: "
168                  + node.getNodeName());
169     }
170       }
171     }
172
173     // Now sort them
174
java.util.Arrays.sort(callout, 0, calloutCount);
175   }
176
177   /** Process characters. */
178   public void characters(char[] chars, int start, int len)
179     throws TransformerException JavaDoc {
180
181     // If we hit characters, then there's no first element...
182
firstElement = false;
183
184     if (lineNumber == 0) {
185       // if there are any text nodes, there's at least one line
186
lineNumber++;
187       colNumber = 1;
188     }
189
190     // Walk through the text node looking for callout positions
191
char[] newChars = new char[len];
192     int pos = 0;
193     for (int count = start; count < start+len; count++) {
194       if (calloutPos < calloutCount
195       && callout[calloutPos].getLine() == lineNumber
196       && callout[calloutPos].getColumn() == colNumber) {
197     if (pos > 0) {
198       rtfEmitter.characters(newChars, 0, pos);
199       pos = 0;
200     }
201
202     closeOpenElements(rtfEmitter);
203
204     while (calloutPos < calloutCount
205            && callout[calloutPos].getLine() == lineNumber
206            && callout[calloutPos].getColumn() == colNumber) {
207       fCallout.formatCallout(rtfEmitter, callout[calloutPos]);
208       calloutPos++;
209     }
210
211     openClosedElements(rtfEmitter);
212       }
213
214       if (chars[count] == '\n') {
215     // What if we need to pad this line?
216
if (calloutPos < calloutCount
217         && callout[calloutPos].getLine() == lineNumber
218         && callout[calloutPos].getColumn() > colNumber) {
219
220       if (pos > 0) {
221         rtfEmitter.characters(newChars, 0, pos);
222         pos = 0;
223       }
224
225       closeOpenElements(rtfEmitter);
226
227       while (calloutPos < calloutCount
228          && callout[calloutPos].getLine() == lineNumber
229          && callout[calloutPos].getColumn() > colNumber) {
230         formatPad(callout[calloutPos].getColumn() - colNumber);
231         colNumber = callout[calloutPos].getColumn();
232         while (calloutPos < calloutCount
233            && callout[calloutPos].getLine() == lineNumber
234            && callout[calloutPos].getColumn() == colNumber) {
235           fCallout.formatCallout(rtfEmitter, callout[calloutPos]);
236           calloutPos++;
237         }
238       }
239
240       openClosedElements(rtfEmitter);
241     }
242
243     lineNumber++;
244     colNumber = 1;
245       } else {
246     colNumber++;
247       }
248       newChars[pos++] = chars[count];
249     }
250
251     if (pos > 0) {
252       rtfEmitter.characters(newChars, 0, pos);
253     }
254   }
255
256   /**
257    * <p>Add blanks to the result tree fragment.</p>
258    *
259    * <p>This method adds <tt>numBlanks</tt> to the result tree fragment.
260    * It's used to pad lines when callouts occur after the last existing
261    * characater in a line.</p>
262    *
263    * @param numBlanks The number of blanks to add.
264    */

265   protected void formatPad(int numBlanks) {
266     char chars[] = new char[numBlanks];
267     for (int count = 0; count < numBlanks; count++) {
268       chars[count] = ' ';
269     }
270
271     try {
272       rtfEmitter.characters(chars, 0, numBlanks);
273     } catch (TransformerException JavaDoc e) {
274       System.out.println("Transformer Exception in formatPad");
275     }
276   }
277
278   /**
279    * <p>Add a callout to the global callout array</p>
280    *
281    * <p>This method examines a callout <tt>area</tt> and adds it to
282    * the global callout array if it can be interpreted.</p>
283    *
284    * <p>Only the <tt>linecolumn</tt> and <tt>linerange</tt> units are
285    * supported. If no unit is specifed, <tt>linecolumn</tt> is assumed.
286    * If only a line is specified, the callout decoration appears in
287    * the <tt>defaultColumn</tt>.</p>
288    *
289    * @param coNum The callout number.
290    * @param node The <tt>area</tt>.
291    * @param defaultColumn The default column for callouts.
292    */

293   protected void addCallout (int coNum,
294                  Node node,
295                  int defaultColumn) {
296
297     Element area = (Element) node;
298     String JavaDoc units = null;
299     String JavaDoc coords = null;
300
301     if (area.hasAttribute("units")) {
302       units = area.getAttribute("units");
303     }
304
305     if (area.hasAttribute("coords")) {
306       coords = area.getAttribute("coords");
307     }
308
309     if (units != null
310     && !units.equalsIgnoreCase("linecolumn")
311     && !units.equalsIgnoreCase("linerange")) {
312       System.out.println("Only linecolumn and linerange units are supported");
313       return;
314     }
315
316     if (coords == null) {
317       System.out.println("Coords must be specified");
318       return;
319     }
320
321     // Now let's see if we can interpret the coordinates...
322
StringTokenizer JavaDoc st = new StringTokenizer JavaDoc(coords);
323     int tokenCount = 0;
324     int c1 = 0;
325     int c2 = 0;
326     while (st.hasMoreTokens()) {
327       tokenCount++;
328       if (tokenCount > 2) {
329     System.out.println("Unparseable coordinates");
330     return;
331       }
332       try {
333     String JavaDoc token = st.nextToken();
334     int coord = Integer.parseInt(token);
335     c2 = coord;
336     if (tokenCount == 1) {
337       c1 = coord;
338     }
339       } catch (NumberFormatException JavaDoc e) {
340     System.out.println("Unparseable coordinate");
341     return;
342       }
343     }
344
345     // Make sure we aren't going to blow past the end of our array
346
if (calloutCount == callout.length) {
347       Callout bigger[] = new Callout[calloutCount+10];
348       for (int count = 0; count < callout.length; count++) {
349     bigger[count] = callout[count];
350       }
351       callout = bigger;
352     }
353
354     // Ok, add the callout
355
if (tokenCount == 2) {
356       if (units != null && units.equalsIgnoreCase("linerange")) {
357     for (int count = c1; count <= c2; count++) {
358       callout[calloutCount++] = new Callout(coNum, area,
359                         count, defaultColumn);
360     }
361       } else {
362     // assume linecolumn
363
callout[calloutCount++] = new Callout(coNum, area, c1, c2);
364       }
365     } else {
366       // if there's only one number, assume it's the line
367
callout[calloutCount++] = new Callout(coNum, area, c1, defaultColumn);
368     }
369   }
370
371   /** Process end element events. */
372   public void endElement(int nameCode)
373     throws TransformerException JavaDoc {
374
375     if (!elementStack.empty()) {
376       // if we didn't push the very first element (an fo:block or
377
// pre or div surrounding the whole block), then the stack will
378
// be empty when we get to the end of the first element...
379
elementStack.pop();
380     }
381     rtfEmitter.endElement(nameCode);
382   }
383
384   /** Process start element events. */
385   public void startElement(int nameCode,
386                org.xml.sax.Attributes JavaDoc attributes,
387                int[] namespaces,
388                int nscount)
389     throws TransformerException JavaDoc {
390
391     if (!skipThisElement(nameCode)) {
392       StartElementInfo sei = new StartElementInfo(nameCode, attributes,
393                           namespaces, nscount);
394       elementStack.push(sei);
395     }
396
397     firstElement = false;
398
399     rtfEmitter.startElement(nameCode, attributes, namespaces, nscount);
400   }
401
402   /**
403    * <p>Protect the outer-most block wrapper.</p>
404    *
405    * <p>Open elements in the result tree fragment are closed and reopened
406    * around callouts (so that callouts don't appear inside links or other
407    * environments). But if the result tree fragment is a single block
408    * (a div or pre in HTML, an fo:block in FO), that outer-most block is
409    * treated specially.</p>
410    *
411    * <p>This method returns true if the element in question is that
412    * outermost block.</p>
413    *
414    * @param nameCode The name code for the element
415    *
416    * @return True if the element is the outer-most block, false otherwise.
417    */

418   protected boolean skipThisElement(int nameCode) {
419     // FIXME: This is such a gross hack...
420
if (firstElement) {
421       int thisFingerprint = namePool.getFingerprint(nameCode);
422       int foBlockFingerprint = namePool.getFingerprint(foURI, "block");
423       int htmlPreFingerprint = namePool.getFingerprint("", "pre");
424       int htmlDivFingerprint = namePool.getFingerprint("", "div");
425       int xhtmlPreFingerprint = namePool.getFingerprint(xhURI, "pre");
426       int xhtmlDivFingerprint = namePool.getFingerprint(xhURI, "div");
427
428       if ((foStylesheet && thisFingerprint == foBlockFingerprint)
429       || (!foStylesheet && (thisFingerprint == htmlPreFingerprint
430                 || thisFingerprint == htmlDivFingerprint
431                 || thisFingerprint == xhtmlPreFingerprint
432                 || thisFingerprint == xhtmlDivFingerprint))) {
433     // Don't push the outer-most wrapping div, pre, or fo:block
434
return true;
435       }
436     }
437
438     return false;
439   }
440
441   private void closeOpenElements(Emitter rtfEmitter)
442     throws TransformerException JavaDoc {
443     // Close all the open elements...
444
tempStack = new Stack JavaDoc();
445     while (!elementStack.empty()) {
446       StartElementInfo elem = (StartElementInfo) elementStack.pop();
447       rtfEmitter.endElement(elem.getNameCode());
448       tempStack.push(elem);
449     }
450   }
451
452   private void openClosedElements(Emitter rtfEmitter)
453     throws TransformerException JavaDoc {
454     // Now "reopen" the elements that we closed...
455
while (!tempStack.empty()) {
456       StartElementInfo elem = (StartElementInfo) tempStack.pop();
457       AttributeCollection attr = (AttributeCollection) elem.getAttributes();
458       AttributeCollection newAttr = new AttributeCollection(namePool);
459
460       for (int acount = 0; acount < attr.getLength(); acount++) {
461     String JavaDoc localName = attr.getLocalName(acount);
462     int nameCode = attr.getNameCode(acount);
463     String JavaDoc type = attr.getType(acount);
464     String JavaDoc value = attr.getValue(acount);
465     String JavaDoc uri = attr.getURI(acount);
466     String JavaDoc prefix = "";
467
468     if (localName.indexOf(':') > 0) {
469       prefix = localName.substring(0, localName.indexOf(':'));
470       localName = localName.substring(localName.indexOf(':')+1);
471     }
472
473     if (uri.equals("")
474         && ((foStylesheet
475          && localName.equals("id"))
476         || (!foStylesheet
477             && (localName.equals("id")
478             || localName.equals("name"))))) {
479       // skip this attribute
480
} else {
481       newAttr.addAttribute(prefix, uri, localName, type, value);
482     }
483       }
484
485       rtfEmitter.startElement(elem.getNameCode(),
486                   newAttr,
487                   elem.getNamespaces(),
488                   elem.getNSCount());
489
490       elementStack.push(elem);
491     }
492   }
493
494   /**
495    * <p>A private class for maintaining the information required to call
496    * the startElement method.</p>
497    *
498    * <p>In order to close and reopen elements, information about those
499    * elements has to be maintained. This class is just the little record
500    * that we push on the stack to keep track of that info.</p>
501    */

502   private class StartElementInfo {
503     private int _nameCode;
504     org.xml.sax.Attributes JavaDoc _attributes;
505     int[] _namespaces;
506     int _nscount;
507
508     public StartElementInfo(int nameCode,
509                 org.xml.sax.Attributes JavaDoc attributes,
510                 int[] namespaces,
511                 int nscount) {
512       _nameCode = nameCode;
513       _attributes = attributes;
514       _namespaces = namespaces;
515       _nscount = nscount;
516     }
517
518     public int getNameCode() {
519       return _nameCode;
520     }
521
522     public org.xml.sax.Attributes JavaDoc getAttributes() {
523       return _attributes;
524     }
525
526     public int[] getNamespaces() {
527       return _namespaces;
528     }
529
530     public int getNSCount() {
531       return _nscount;
532     }
533   }
534 }
535
Popular Tags