KickJava   Java API By Example, From Geeks To Geeks.

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


1 // Verbatim.java - Saxon extensions supporting DocBook verbatim environments
2

3 package com.nwalsh.saxon;
4
5 import java.util.Hashtable JavaDoc;
6 import org.xml.sax.*;
7 import org.w3c.dom.*;
8 import javax.xml.transform.TransformerException JavaDoc;
9 import com.icl.saxon.Controller;
10 import com.icl.saxon.expr.*;
11 import com.icl.saxon.om.*;
12 import com.icl.saxon.pattern.*;
13 import com.icl.saxon.Context;
14 import com.icl.saxon.tree.*;
15 import com.icl.saxon.functions.Extensions;
16
17 /**
18  * <p>Saxon extensions supporting Tables</p>
19  *
20  * <p>$Id: Table.java,v 1.2 2002/11/15 13:50:25 nwalsh Exp $</p>
21  *
22  * <p>Copyright (C) 2000 Norman Walsh.</p>
23  *
24  * <p>This class provides a
25  * <a HREF="http://users.iclway.co.uk/mhkay/saxon/">Saxon</a>
26  * implementation of some code to adjust CALS Tables to HTML
27  * Tables.</p>
28  *
29  * <p><b>Column Widths</b></p>
30  * <p>The <tt>adjustColumnWidths</tt> method takes a result tree
31  * fragment (assumed to contain the colgroup of an HTML Table)
32  * and returns the result tree fragment with the column widths
33  * adjusted to HTML terms.</p>
34  *
35  * <p><b>Convert Lengths</b></p>
36  * <p>The <tt>convertLength</tt> method takes a length specification
37  * of the form 9999.99xx (where "xx" is a unit) and returns that length
38  * as an integral number of pixels. For convenience, percentage lengths
39  * are returned unchanged.</p>
40  * <p>The recognized units are: inches (in), centimeters (cm),
41  * millimeters (mm), picas (pc, 1pc=12pt), points (pt), and pixels (px).
42  * A number with no units is assumed to be pixels.</p>
43  *
44  * <p><b>Change Log:</b></p>
45  * <dl>
46  * <dt>1.0</dt>
47  * <dd><p>Initial release.</p></dd>
48  * </dl>
49  *
50  * @author Norman Walsh
51  * <a HREF="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
52  *
53  * @version $Id: Table.java,v 1.2 2002/11/15 13:50:25 nwalsh Exp $
54  *
55  */

56 public class Table {
57   /** The number of pixels per inch */
58   private static int pixelsPerInch = 96;
59
60   /** The nominal table width (6in by default). */
61   private static int nominalWidth = 6 * pixelsPerInch;
62
63   /** The default table width (100% by default). */
64   private static String JavaDoc tableWidth = "100%";
65
66   /** Is this an FO stylesheet? */
67   private static boolean foStylesheet = false;
68
69   /** The hash used to associate units with a length in pixels. */
70   protected static Hashtable JavaDoc unitHash = null;
71
72   /**
73    * <p>Constructor for Verbatim</p>
74    *
75    * <p>All of the methods are static, so the constructor does nothing.</p>
76    */

77   public Table() {
78   }
79
80   /** Initialize the internal hash table with proper values. */
81   protected static void initializeHash() {
82     unitHash = new Hashtable JavaDoc();
83     unitHash.put("in", new Float JavaDoc(pixelsPerInch));
84     unitHash.put("cm", new Float JavaDoc(pixelsPerInch / 2.54));
85     unitHash.put("mm", new Float JavaDoc(pixelsPerInch / 25.4));
86     unitHash.put("pc", new Float JavaDoc((pixelsPerInch / 72) * 12));
87     unitHash.put("pt", new Float JavaDoc(pixelsPerInch / 72));
88     unitHash.put("px", new Float JavaDoc(1));
89   }
90
91   /** Set the pixels-per-inch value. Only positive values are legal. */
92   public static void setPixelsPerInch(int value) {
93     if (value > 0) {
94       pixelsPerInch = value;
95       initializeHash();
96     }
97   }
98
99   /** Return the current pixels-per-inch value. */
100   public int getPixelsPerInch() {
101     return pixelsPerInch;
102   }
103
104   /**
105    * <p>Convert a length specification to a number of pixels.</p>
106    *
107    * <p>The specified length should be of the form [+/-]999.99xx,
108    * where xx is a valid unit.</p>
109    */

110   public static int convertLength(String JavaDoc length) {
111     // The format of length should be 999.999xx
112
int sign = 1;
113     String JavaDoc digits = "";
114     String JavaDoc units = "";
115     char lench[] = length.toCharArray();
116     float flength = 0;
117     boolean done = false;
118     int pos = 0;
119     float factor = 1;
120     int pixels = 0;
121
122     if (unitHash == null) {
123       initializeHash();
124     }
125
126     if (lench[pos] == '+' || lench[pos] == '-') {
127       if (lench[pos] == '-') {
128     sign = -1;
129       }
130       pos++;
131     }
132
133     while (!done) {
134       if (pos >= lench.length) {
135     done = true;
136       } else {
137     if ((lench[pos] > '9' || lench[pos] < '0') && lench[pos] != '.') {
138       done = true;
139       units = length.substring(pos);
140     } else {
141       digits += lench[pos++];
142     }
143       }
144     }
145
146     try {
147       flength = Float.parseFloat(digits);
148     } catch (NumberFormatException JavaDoc e) {
149       System.out.println(digits + " is not a number; 1 used instead.");
150       flength = 1;
151     }
152
153     Float JavaDoc f = null;
154
155     if (!units.equals("")) {
156       f = (Float JavaDoc) unitHash.get(units);
157       if (f == null) {
158     System.out.println(units + " is not a known unit; 1 used instead.");
159     factor = 1;
160       } else {
161     factor = f.floatValue();
162       }
163     } else {
164       factor = 1;
165     }
166
167     f = new Float JavaDoc(flength * factor);
168
169     pixels = f.intValue() * sign;
170
171     return pixels;
172   }
173
174   /**
175    * <p>Find the string value of a stylesheet variable or parameter</p>
176    *
177    * <p>Returns the string value of <code>varName</code> in the current
178    * <code>context</code>. Returns the empty string if the variable is
179    * not defined.</p>
180    *
181    * @param context The current stylesheet context
182    * @param varName The name of the variable (without the dollar sign)
183    *
184    * @return The string value of the variable
185    */

186   protected static String JavaDoc getVariable(Context context, String JavaDoc varName)
187     throws TransformerException JavaDoc {
188     Value variable = null;
189     String JavaDoc varString = null;
190
191     try {
192       variable = Extensions.evaluate(context, "$" + varName);
193       varString = variable.asString();
194       return varString;
195     } catch (IllegalArgumentException JavaDoc e) {
196       System.out.println("Undefined variable: " + varName);
197       return "";
198     }
199   }
200
201   /**
202    * <p>Setup the parameters associated with column width calculations</p>
203    *
204    * <p>This method queries the stylesheet for the variables
205    * associated with table column widths. It is called automatically before
206    * column widths are adjusted. The context is used to retrieve the values,
207    * this allows templates to redefine these variables.</p>
208    *
209    * <p>The following variables are queried. If the variables do not
210    * exist, builtin defaults will be used (but you may also get a bunch
211    * of messages from the Java interpreter).</p>
212    *
213    * <dl>
214    * <dt><code>nominal.table.width</code></dt>
215    * <dd>The "normal" width for tables. This must be an absolute length.</dd>
216    * <dt><code>table.width</code></dt>
217    * <dd>The width for tables. This may be either an absolute
218    * length or a percentage.</dd>
219    * </dl>
220    *
221    * @param context The current stylesheet context
222    *
223    */

224   private static void setupColumnWidths(Context context) {
225     // Hardcoded defaults
226
nominalWidth = 6 * pixelsPerInch;
227     tableWidth = "100%";
228
229     String JavaDoc varString = null;
230
231     try {
232       // Get the stylesheet type
233
varString = getVariable(context, "stylesheet.result.type");
234       foStylesheet = varString.equals("fo");
235
236       // Get the nominal table width
237
varString = getVariable(context, "nominal.table.width");
238       nominalWidth = convertLength(varString);
239
240       // Get the table width
241
varString = getVariable(context, "table.width");
242       tableWidth = varString;
243     } catch (TransformerException JavaDoc e) {
244       //nop, can't happen
245
}
246   }
247
248   /**
249    * <p>Adjust column widths in an HTML table.</p>
250    *
251    * <p>The specification of column widths in CALS (a relative width
252    * plus an optional absolute width) are incompatible with HTML column
253    * widths. This method adjusts CALS column width specifiers in an
254    * attempt to produce equivalent HTML specifiers.</p>
255    *
256    * <p>In order for this method to work, the CALS width specifications
257    * should be placed in the "width" attribute of the &lt;col>s within
258    * a &lt;colgroup>. Then the colgroup result tree fragment is passed
259    * to this method.</p>
260    *
261    * <p>This method makes use of two parameters from the XSL stylesheet
262    * that calls it: <code>nominal.table.width</code> and
263    * <code>table.width</code>. The value of <code>nominal.table.width</code>
264    * must be an absolute distance. The value of <code>table.width</code>
265    * can be either absolute or relative.</p>
266    *
267    * <p>Presented with a mixture of relative and
268    * absolute lengths, the table width is used to calculate
269    * appropriate values. If the <code>table.width</code> is relative,
270    * the nominal width is used for this calculation.</p>
271    *
272    * <p>There are three possible combinations of values:</p>
273    *
274    * <ol>
275    * <li>There are no relative widths; in this case the absolute widths
276    * are used in the HTML table.</li>
277    * <li>There are no absolute widths; in this case the relative widths
278    * are used in the HTML table.</li>
279    * <li>There are a mixture of absolute and relative widths:
280    * <ol>
281    * <li>If the table width is absolute, all widths become absolute.</li>
282    * <li>If the table width is relative, make all the widths absolute
283    * relative to the nominal table width then turn them all
284    * back into relative widths.</li>
285    * </ol>
286    * </li>
287    * </ol>
288    *
289    * @param context The stylesheet context; supplied automatically by Saxon
290    * @param rtf The result tree fragment containing the colgroup.
291    *
292    * @return The result tree fragment containing the adjusted colgroup.
293    *
294    */

295   public static NodeSetValue adjustColumnWidths (Context context,
296                          NodeSetValue rtf_ns) {
297
298     FragmentValue rtf = (FragmentValue) rtf_ns;
299
300     setupColumnWidths(context);
301
302     try {
303       Controller controller = context.getController();
304       NamePool namePool = controller.getNamePool();
305
306       ColumnScanEmitter csEmitter = new ColumnScanEmitter(namePool);
307       rtf.replay(csEmitter);
308
309       int numColumns = csEmitter.columnCount();
310       String JavaDoc widths[] = csEmitter.columnWidths();
311
312       float relTotal = 0;
313       float relParts[] = new float[numColumns];
314
315       float absTotal = 0;
316       float absParts[] = new float[numColumns];
317
318       for (int count = 0; count < numColumns; count++) {
319     String JavaDoc width = widths[count];
320
321     int pos = width.indexOf("*");
322     if (pos >= 0) {
323       String JavaDoc relPart = width.substring(0, pos);
324       String JavaDoc absPart = width.substring(pos+1);
325
326       try {
327         float rel = Float.parseFloat(relPart);
328         relTotal += rel;
329         relParts[count] = rel;
330       } catch (NumberFormatException JavaDoc e) {
331         System.out.println(relPart + " is not a valid relative unit.");
332       }
333
334       int pixels = 0;
335       if (absPart != null && !absPart.equals("")) {
336         pixels = convertLength(absPart);
337       }
338
339       absTotal += pixels;
340       absParts[count] = pixels;
341     } else {
342       relParts[count] = 0;
343
344       int pixels = 0;
345       if (width != null && !width.equals("")) {
346         pixels = convertLength(width);
347       }
348
349       absTotal += pixels;
350       absParts[count] = pixels;
351     }
352       }
353
354       // Ok, now we have the relative widths and absolute widths in
355
// two parallel arrays.
356
//
357
// - If there are no relative widths, output the absolute widths
358
// - If there are no absolute widths, output the relative widths
359
// - If there are a mixture of relative and absolute widths,
360
// - If the table width is absolute, turn these all into absolute
361
// widths.
362
// - If the table width is relative, turn these all into absolute
363
// widths in the nominalWidth and then turn them back into
364
// percentages.
365

366       if (relTotal == 0) {
367     for (int count = 0; count < numColumns; count++) {
368       Float JavaDoc f = new Float JavaDoc(absParts[count]);
369       if (foStylesheet) {
370         int pixels = f.intValue();
371         float inches = (float) pixels / pixelsPerInch;
372         widths[count] = inches + "in";
373       } else {
374         widths[count] = Integer.toString(f.intValue());
375       }
376     }
377       } else if (absTotal == 0) {
378     for (int count = 0; count < numColumns; count++) {
379       float rel = relParts[count] / relTotal * 100;
380       Float JavaDoc f = new Float JavaDoc(rel);
381       widths[count] = Integer.toString(f.intValue());
382     }
383     widths = correctRoundingError(widths);
384       } else {
385     int pixelWidth = nominalWidth;
386
387     if (tableWidth.indexOf("%") <= 0) {
388       pixelWidth = convertLength(tableWidth);
389     }
390
391     if (pixelWidth <= absTotal) {
392       System.out.println("Table is wider than table width.");
393     } else {
394       pixelWidth -= absTotal;
395     }
396
397     absTotal = 0;
398     for (int count = 0; count < numColumns; count++) {
399       float rel = relParts[count] / relTotal * pixelWidth;
400       relParts[count] = rel + absParts[count];
401       absTotal += rel + absParts[count];
402     }
403
404     if (tableWidth.indexOf("%") <= 0) {
405       for (int count = 0; count < numColumns; count++) {
406         Float JavaDoc f = new Float JavaDoc(relParts[count]);
407         if (foStylesheet) {
408           int pixels = f.intValue();
409           float inches = (float) pixels / pixelsPerInch;
410           widths[count] = inches + "in";
411         } else {
412           widths[count] = Integer.toString(f.intValue());
413         }
414       }
415     } else {
416       for (int count = 0; count < numColumns; count++) {
417         float rel = relParts[count] / absTotal * 100;
418         Float JavaDoc f = new Float JavaDoc(rel);
419         widths[count] = Integer.toString(f.intValue());
420       }
421       widths = correctRoundingError(widths);
422     }
423       }
424
425       ColumnUpdateEmitter cuEmitter = new ColumnUpdateEmitter(controller,
426                                   namePool,
427                                   widths);
428
429       rtf.replay(cuEmitter);
430       return cuEmitter.getResultTreeFragment();
431     } catch (TransformerException JavaDoc e) {
432       // This "can't" happen.
433
System.out.println("Transformer Exception in adjustColumnWidths");
434       return rtf;
435     }
436   }
437
438   /**
439    * Correct rounding errors introduced in calculating the width of each
440    * column. Make sure they sum to 100% in the end.
441    */

442   protected static String JavaDoc[] correctRoundingError(String JavaDoc widths[]) {
443     int totalWidth = 0;
444
445     for (int count = 0; count < widths.length; count++) {
446       try {
447     int width = Integer.parseInt(widths[count]);
448     totalWidth += width;
449       } catch (NumberFormatException JavaDoc nfe) {
450     // nop; "can't happen"
451
}
452     }
453
454     float totalError = 100 - totalWidth;
455     float columnError = totalError / widths.length;
456     float error = 0;
457
458     for (int count = 0; count < widths.length; count++) {
459       try {
460     int width = Integer.parseInt(widths[count]);
461     error = error + columnError;
462     if (error >= 1.0) {
463       int adj = (int) Math.round(Math.floor(error));
464       error = error - (float) Math.floor(error);
465       width = width + adj;
466       widths[count] = Integer.toString(width) + "%";
467     } else {
468       widths[count] = Integer.toString(width) + "%";
469     }
470       } catch (NumberFormatException JavaDoc nfe) {
471     // nop; "can't happen"
472
}
473     }
474
475     return widths;
476   }
477 }
478
Popular Tags