Source code: com/nwalsh/saxon/Table.java
1 // Verbatim.java - Saxon extensions supporting DocBook verbatim environments
2
3 package com.nwalsh.saxon;
4
5 import java.util.Hashtable;
6 import org.xml.sax.*;
7 import org.w3c.dom.*;
8 import javax.xml.transform.TransformerException;
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 *
21 * <p>Copyright (C) 2000 Norman Walsh.</p>
22 *
23 * <p>This class provides a
24 * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon</a>
25 * implementation of some code to adjust CALS Tables to HTML
26 * Tables.</p>
27 *
28 * <p><b>Column Widths</b></p>
29 * <p>The <tt>adjustColumnWidths</tt> method takes a result tree
30 * fragment (assumed to contain the colgroup of an HTML Table)
31 * and returns the result tree fragment with the column widths
32 * adjusted to HTML terms.</p>
33 *
34 * <p><b>Convert Lengths</b></p>
35 * <p>The <tt>convertLength</tt> method takes a length specification
36 * of the form 9999.99xx (where "xx" is a unit) and returns that length
37 * as an integral number of pixels. For convenience, percentage lengths
38 * are returned unchanged.</p>
39 * <p>The recognized units are: inches (in), centimeters (cm),
40 * millimeters (mm), picas (pc, 1pc=12pt), points (pt), and pixels (px).
41 * A number with no units is assumed to be pixels.</p>
42 *
43 * <p><b>Change Log:</b></p>
44 * <dl>
45 * <dt>1.0</dt>
46 * <dd><p>Initial release.</p></dd>
47 * </dl>
48 *
49 * @author Norman Walsh
50 * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
51 *
52 *
53 */
54 public class Table {
55 /** The number of pixels per inch */
56 private static int pixelsPerInch = 96;
57
58 /** The nominal table width (6in by default). */
59 private static int nominalWidth = 6 * pixelsPerInch;
60
61 /** The default table width (100% by default). */
62 private static String tableWidth = "100%";
63
64 /** Is this an FO stylesheet? */
65 private static boolean foStylesheet = false;
66
67 /** The hash used to associate units with a length in pixels. */
68 protected static Hashtable unitHash = null;
69
70 /**
71 * <p>Constructor for Verbatim</p>
72 *
73 * <p>All of the methods are static, so the constructor does nothing.</p>
74 */
75 public Table() {
76 }
77
78 /** Initialize the internal hash table with proper values. */
79 protected static void initializeHash() {
80 unitHash = new Hashtable();
81 unitHash.put("in", new Float(pixelsPerInch));
82 unitHash.put("cm", new Float(pixelsPerInch / 2.54));
83 unitHash.put("mm", new Float(pixelsPerInch / 25.4));
84 unitHash.put("pc", new Float((pixelsPerInch / 72) * 12));
85 unitHash.put("pt", new Float(pixelsPerInch / 72));
86 unitHash.put("px", new Float(1));
87 }
88
89 /** Set the pixels-per-inch value. Only positive values are legal. */
90 public static void setPixelsPerInch(int value) {
91 if (value > 0) {
92 pixelsPerInch = value;
93 initializeHash();
94 }
95 }
96
97 /** Return the current pixels-per-inch value. */
98 public int getPixelsPerInch() {
99 return pixelsPerInch;
100 }
101
102 /**
103 * <p>Convert a length specification to a number of pixels.</p>
104 *
105 * <p>The specified length should be of the form [+/-]999.99xx,
106 * where xx is a valid unit.</p>
107 */
108 public static int convertLength(String length) {
109 // The format of length should be 999.999xx
110 int sign = 1;
111 String digits = "";
112 String units = "";
113 char lench[] = length.toCharArray();
114 float flength = 0;
115 boolean done = false;
116 int pos = 0;
117 float factor = 1;
118 int pixels = 0;
119
120 if (unitHash == null) {
121 initializeHash();
122 }
123
124 if (lench[pos] == '+' || lench[pos] == '-') {
125 if (lench[pos] == '-') {
126 sign = -1;
127 }
128 pos++;
129 }
130
131 while (!done) {
132 if (pos >= lench.length) {
133 done = true;
134 } else {
135 if ((lench[pos] > '9' || lench[pos] < '0') && lench[pos] != '.') {
136 done = true;
137 units = length.substring(pos);
138 } else {
139 digits += lench[pos++];
140 }
141 }
142 }
143
144 try {
145 flength = Float.parseFloat(digits);
146 } catch (NumberFormatException e) {
147 System.out.println(digits + " is not a number; 1 used instead.");
148 flength = 1;
149 }
150
151 Float f = null;
152
153 if (!units.equals("")) {
154 f = (Float) unitHash.get(units);
155 if (f == null) {
156 System.out.println(units + " is not a known unit; 1 used instead.");
157 factor = 1;
158 } else {
159 factor = f.floatValue();
160 }
161 } else {
162 factor = 1;
163 }
164
165 f = new Float(flength * factor);
166
167 pixels = f.intValue() * sign;
168
169 return pixels;
170 }
171
172 /**
173 * <p>Find the string value of a stylesheet variable or parameter</p>
174 *
175 * <p>Returns the string value of <code>varName</code> in the current
176 * <code>context</code>. Returns the empty string if the variable is
177 * not defined.</p>
178 *
179 * @param context The current stylesheet context
180 * @param varName The name of the variable (without the dollar sign)
181 *
182 * @return The string value of the variable
183 */
184 protected static String getVariable(Context context, String varName)
185 throws TransformerException {
186 Value variable = null;
187 String varString = null;
188
189 try {
190 variable = Extensions.evaluate(context, "$" + varName);
191 varString = variable.asString();
192 return varString;
193 } catch (IllegalArgumentException e) {
194 System.out.println("Undefined variable: " + varName);
195 return "";
196 }
197 }
198
199 /**
200 * <p>Setup the parameters associated with column width calculations</p>
201 *
202 * <p>This method queries the stylesheet for the variables
203 * associated with table column widths. It is called automatically before
204 * column widths are adjusted. The context is used to retrieve the values,
205 * this allows templates to redefine these variables.</p>
206 *
207 * <p>The following variables are queried. If the variables do not
208 * exist, builtin defaults will be used (but you may also get a bunch
209 * of messages from the Java interpreter).</p>
210 *
211 * <dl>
212 * <dt><code>nominal.table.width</code></dt>
213 * <dd>The "normal" width for tables. This must be an absolute length.</dd>
214 * <dt><code>table.width</code></dt>
215 * <dd>The width for tables. This may be either an absolute
216 * length or a percentage.</dd>
217 * </dl>
218 *
219 * @param context The current stylesheet context
220 *
221 */
222 private static void setupColumnWidths(Context context) {
223 // Hardcoded defaults
224 nominalWidth = 6 * pixelsPerInch;
225 tableWidth = "100%";
226
227 String varString = null;
228
229 try {
230 // Get the stylesheet type
231 varString = getVariable(context, "stylesheet.result.type");
232 foStylesheet = varString.equals("fo");
233
234 // Get the nominal table width
235 varString = getVariable(context, "nominal.table.width");
236 nominalWidth = convertLength(varString);
237
238 // Get the table width
239 varString = getVariable(context, "table.width");
240 tableWidth = varString;
241 } catch (TransformerException e) {
242 //nop, can't happen
243 }
244 }
245
246 /**
247 * <p>Adjust column widths in an HTML table.</p>
248 *
249 * <p>The specification of column widths in CALS (a relative width
250 * plus an optional absolute width) are incompatible with HTML column
251 * widths. This method adjusts CALS column width specifiers in an
252 * attempt to produce equivalent HTML specifiers.</p>
253 *
254 * <p>In order for this method to work, the CALS width specifications
255 * should be placed in the "width" attribute of the <col>s within
256 * a <colgroup>. Then the colgroup result tree fragment is passed
257 * to this method.</p>
258 *
259 * <p>This method makes use of two parameters from the XSL stylesheet
260 * that calls it: <code>nominal.table.width</code> and
261 * <code>table.width</code>. The value of <code>nominal.table.width</code>
262 * must be an absolute distance. The value of <code>table.width</code>
263 * can be either absolute or relative.</p>
264 *
265 * <p>Presented with a mixture of relative and
266 * absolute lengths, the table width is used to calculate
267 * appropriate values. If the <code>table.width</code> is relative,
268 * the nominal width is used for this calculation.</p>
269 *
270 * <p>There are three possible combinations of values:</p>
271 *
272 * <ol>
273 * <li>There are no relative widths; in this case the absolute widths
274 * are used in the HTML table.</li>
275 * <li>There are no absolute widths; in this case the relative widths
276 * are used in the HTML table.</li>
277 * <li>There are a mixture of absolute and relative widths:
278 * <ol>
279 * <li>If the table width is absolute, all widths become absolute.</li>
280 * <li>If the table width is relative, make all the widths absolute
281 * relative to the nominal table width then turn them all
282 * back into relative widths.</li>
283 * </ol>
284 * </li>
285 * </ol>
286 *
287 * @param context The stylesheet context; supplied automatically by Saxon
288 * @param rtf The result tree fragment containing the colgroup.
289 *
290 * @return The result tree fragment containing the adjusted colgroup.
291 *
292 */
293 public static NodeSetValue adjustColumnWidths (Context context,
294 NodeSetValue rtf_ns) {
295
296 FragmentValue rtf = (FragmentValue) rtf_ns;
297
298 setupColumnWidths(context);
299
300 try {
301 Controller controller = context.getController();
302 NamePool namePool = controller.getNamePool();
303
304 ColumnScanEmitter csEmitter = new ColumnScanEmitter(namePool);
305 rtf.replay(csEmitter);
306
307 int numColumns = csEmitter.columnCount();
308 String widths[] = csEmitter.columnWidths();
309
310 float relTotal = 0;
311 float relParts[] = new float[numColumns];
312
313 float absTotal = 0;
314 float absParts[] = new float[numColumns];
315
316 for (int count = 0; count < numColumns; count++) {
317 String width = widths[count];
318
319 int pos = width.indexOf("*");
320 if (pos >= 0) {
321 String relPart = width.substring(0, pos);
322 String absPart = width.substring(pos+1);
323
324 try {
325 float rel = Float.parseFloat(relPart);
326 relTotal += rel;
327 relParts[count] = rel;
328 } catch (NumberFormatException e) {
329 System.out.println(relPart + " is not a valid relative unit.");
330 }
331
332 int pixels = 0;
333 if (absPart != null && !absPart.equals("")) {
334 pixels = convertLength(absPart);
335 }
336
337 absTotal += pixels;
338 absParts[count] = pixels;
339 } else {
340 relParts[count] = 0;
341
342 int pixels = 0;
343 if (width != null && !width.equals("")) {
344 pixels = convertLength(width);
345 }
346
347 absTotal += pixels;
348 absParts[count] = pixels;
349 }
350 }
351
352 // Ok, now we have the relative widths and absolute widths in
353 // two parallel arrays.
354 //
355 // - If there are no relative widths, output the absolute widths
356 // - If there are no absolute widths, output the relative widths
357 // - If there are a mixture of relative and absolute widths,
358 // - If the table width is absolute, turn these all into absolute
359 // widths.
360 // - If the table width is relative, turn these all into absolute
361 // widths in the nominalWidth and then turn them back into
362 // percentages.
363
364 if (relTotal == 0) {
365 for (int count = 0; count < numColumns; count++) {
366 Float f = new Float(absParts[count]);
367 if (foStylesheet) {
368 int pixels = f.intValue();
369 float inches = (float) pixels / pixelsPerInch;
370 widths[count] = inches + "in";
371 } else {
372 widths[count] = Integer.toString(f.intValue());
373 }
374 }
375 } else if (absTotal == 0) {
376 for (int count = 0; count < numColumns; count++) {
377 float rel = relParts[count] / relTotal * 100;
378 Float f = new Float(rel);
379 widths[count] = Integer.toString(f.intValue()) + "%";
380 }
381 } else {
382 int pixelWidth = nominalWidth;
383
384 if (tableWidth.indexOf("%") <= 0) {
385 pixelWidth = convertLength(tableWidth);
386 }
387
388 if (pixelWidth <= absTotal) {
389 System.out.println("Table is wider than table width.");
390 } else {
391 pixelWidth -= absTotal;
392 }
393
394 absTotal = 0;
395 for (int count = 0; count < numColumns; count++) {
396 float rel = relParts[count] / relTotal * pixelWidth;
397 relParts[count] = rel + absParts[count];
398 absTotal += rel + absParts[count];
399 }
400
401 if (tableWidth.indexOf("%") <= 0) {
402 for (int count = 0; count < numColumns; count++) {
403 Float f = new Float(relParts[count]);
404 if (foStylesheet) {
405 int pixels = f.intValue();
406 float inches = (float) pixels / pixelsPerInch;
407 widths[count] = inches + "in";
408 } else {
409 widths[count] = Integer.toString(f.intValue());
410 }
411 }
412 } else {
413 for (int count = 0; count < numColumns; count++) {
414 float rel = relParts[count] / absTotal * 100;
415 Float f = new Float(rel);
416 widths[count] = Integer.toString(f.intValue()) + "%";
417 }
418 }
419 }
420
421 ColumnUpdateEmitter cuEmitter = new ColumnUpdateEmitter(controller,
422 namePool,
423 widths);
424
425 rtf.replay(cuEmitter);
426 return cuEmitter.getResultTreeFragment();
427 } catch (TransformerException e) {
428 // This "can't" happen.
429 System.out.println("Transformer Exception in adjustColumnWidths");
430 return rtf;
431 }
432 }
433 }