001: // Verbatim.java - Saxon extensions supporting DocBook verbatim environments
002:
003: package com.nwalsh.saxon;
004:
005: import java.util.Hashtable;
006: import org.xml.sax.*;
007: import org.w3c.dom.*;
008: import javax.xml.transform.TransformerException;
009: import com.icl.saxon.Controller;
010: import com.icl.saxon.expr.*;
011: import com.icl.saxon.om.*;
012: import com.icl.saxon.pattern.*;
013: import com.icl.saxon.Context;
014: import com.icl.saxon.tree.*;
015: import com.icl.saxon.functions.Extensions;
016:
017: /**
018: * <p>Saxon extensions supporting Tables</p>
019: *
020: * <p>$Id: Table.java,v 1.4 2005-08-30 08:14:58 draganr Exp $</p>
021: *
022: * <p>Copyright (C) 2000 Norman Walsh.</p>
023: *
024: * <p>This class provides a
025: * <a href="http://users.iclway.co.uk/mhkay/saxon/">Saxon</a>
026: * implementation of some code to adjust CALS Tables to HTML
027: * Tables.</p>
028: *
029: * <p><b>Column Widths</b></p>
030: * <p>The <tt>adjustColumnWidths</tt> method takes a result tree
031: * fragment (assumed to contain the colgroup of an HTML Table)
032: * and returns the result tree fragment with the column widths
033: * adjusted to HTML terms.</p>
034: *
035: * <p><b>Convert Lengths</b></p>
036: * <p>The <tt>convertLength</tt> method takes a length specification
037: * of the form 9999.99xx (where "xx" is a unit) and returns that length
038: * as an integral number of pixels. For convenience, percentage lengths
039: * are returned unchanged.</p>
040: * <p>The recognized units are: inches (in), centimeters (cm),
041: * millimeters (mm), picas (pc, 1pc=12pt), points (pt), and pixels (px).
042: * A number with no units is assumed to be pixels.</p>
043: *
044: * <p><b>Change Log:</b></p>
045: * <dl>
046: * <dt>1.0</dt>
047: * <dd><p>Initial release.</p></dd>
048: * </dl>
049: *
050: * @author Norman Walsh
051: * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
052: *
053: * @version $Id: Table.java,v 1.4 2005-08-30 08:14:58 draganr Exp $
054: *
055: */
056: public class Table {
057: /** The number of pixels per inch */
058: private static int pixelsPerInch = 96;
059:
060: /** The nominal table width (6in by default). */
061: private static int nominalWidth = 6 * pixelsPerInch;
062:
063: /** The default table width (100% by default). */
064: private static String tableWidth = "100%";
065:
066: /** Is this an FO stylesheet? */
067: private static boolean foStylesheet = false;
068:
069: /** The hash used to associate units with a length in pixels. */
070: protected static Hashtable unitHash = null;
071:
072: /**
073: * <p>Constructor for Verbatim</p>
074: *
075: * <p>All of the methods are static, so the constructor does nothing.</p>
076: */
077: public Table() {
078: }
079:
080: /** Initialize the internal hash table with proper values. */
081: protected static void initializeHash() {
082: unitHash = new Hashtable();
083: unitHash.put("in", new Float(pixelsPerInch));
084: unitHash.put("cm", new Float(pixelsPerInch / 2.54));
085: unitHash.put("mm", new Float(pixelsPerInch / 25.4));
086: unitHash.put("pc", new Float((pixelsPerInch / 72) * 12));
087: unitHash.put("pt", new Float(pixelsPerInch / 72));
088: unitHash.put("px", new Float(1));
089: }
090:
091: /** Set the pixels-per-inch value. Only positive values are legal. */
092: public static void setPixelsPerInch(int value) {
093: if (value > 0) {
094: pixelsPerInch = value;
095: initializeHash();
096: }
097: }
098:
099: /** 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 length) {
111: // The format of length should be 999.999xx
112: int sign = 1;
113: String digits = "";
114: String 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')
138: && lench[pos] != '.') {
139: done = true;
140: units = length.substring(pos);
141: } else {
142: digits += lench[pos++];
143: }
144: }
145: }
146:
147: try {
148: flength = Float.parseFloat(digits);
149: } catch (NumberFormatException e) {
150: System.out.println(digits
151: + " is not a number; 1 used instead.");
152: flength = 1;
153: }
154:
155: Float f = null;
156:
157: if (!units.equals("")) {
158: f = (Float) unitHash.get(units);
159: if (f == null) {
160: System.out.println(units
161: + " is not a known unit; 1 used instead.");
162: factor = 1;
163: } else {
164: factor = f.floatValue();
165: }
166: } else {
167: factor = 1;
168: }
169:
170: f = new Float(flength * factor);
171:
172: pixels = f.intValue() * sign;
173:
174: return pixels;
175: }
176:
177: /**
178: * <p>Find the string value of a stylesheet variable or parameter</p>
179: *
180: * <p>Returns the string value of <code>varName</code> in the current
181: * <code>context</code>. Returns the empty string if the variable is
182: * not defined.</p>
183: *
184: * @param context The current stylesheet context
185: * @param varName The name of the variable (without the dollar sign)
186: *
187: * @return The string value of the variable
188: */
189: protected static String getVariable(Context context, String varName)
190: throws TransformerException {
191: Value variable = null;
192: String varString = null;
193:
194: try {
195: variable = Extensions.evaluate(context, "$" + varName);
196: varString = variable.asString();
197: return varString;
198: } catch (IllegalArgumentException e) {
199: System.out.println("Undefined variable: " + varName);
200: return "";
201: }
202: }
203:
204: /**
205: * <p>Setup the parameters associated with column width calculations</p>
206: *
207: * <p>This method queries the stylesheet for the variables
208: * associated with table column widths. It is called automatically before
209: * column widths are adjusted. The context is used to retrieve the values,
210: * this allows templates to redefine these variables.</p>
211: *
212: * <p>The following variables are queried. If the variables do not
213: * exist, builtin defaults will be used (but you may also get a bunch
214: * of messages from the Java interpreter).</p>
215: *
216: * <dl>
217: * <dt><code>nominal.table.width</code></dt>
218: * <dd>The "normal" width for tables. This must be an absolute length.</dd>
219: * <dt><code>table.width</code></dt>
220: * <dd>The width for tables. This may be either an absolute
221: * length or a percentage.</dd>
222: * </dl>
223: *
224: * @param context The current stylesheet context
225: *
226: */
227: private static void setupColumnWidths(Context context) {
228: // Hardcoded defaults
229: nominalWidth = 6 * pixelsPerInch;
230: tableWidth = "100%";
231:
232: String varString = null;
233:
234: try {
235: // Get the stylesheet type
236: varString = getVariable(context, "stylesheet.result.type");
237: foStylesheet = varString.equals("fo");
238:
239: // Get the nominal table width
240: varString = getVariable(context, "nominal.table.width");
241: nominalWidth = convertLength(varString);
242:
243: // Get the table width
244: varString = getVariable(context, "table.width");
245: tableWidth = varString;
246: } catch (TransformerException e) {
247: //nop, can't happen
248: }
249: }
250:
251: /**
252: * <p>Adjust column widths in an HTML table.</p>
253: *
254: * <p>The specification of column widths in CALS (a relative width
255: * plus an optional absolute width) are incompatible with HTML column
256: * widths. This method adjusts CALS column width specifiers in an
257: * attempt to produce equivalent HTML specifiers.</p>
258: *
259: * <p>In order for this method to work, the CALS width specifications
260: * should be placed in the "width" attribute of the <col>s within
261: * a <colgroup>. Then the colgroup result tree fragment is passed
262: * to this method.</p>
263: *
264: * <p>This method makes use of two parameters from the XSL stylesheet
265: * that calls it: <code>nominal.table.width</code> and
266: * <code>table.width</code>. The value of <code>nominal.table.width</code>
267: * must be an absolute distance. The value of <code>table.width</code>
268: * can be either absolute or relative.</p>
269: *
270: * <p>Presented with a mixture of relative and
271: * absolute lengths, the table width is used to calculate
272: * appropriate values. If the <code>table.width</code> is relative,
273: * the nominal width is used for this calculation.</p>
274: *
275: * <p>There are three possible combinations of values:</p>
276: *
277: * <ol>
278: * <li>There are no relative widths; in this case the absolute widths
279: * are used in the HTML table.</li>
280: * <li>There are no absolute widths; in this case the relative widths
281: * are used in the HTML table.</li>
282: * <li>There are a mixture of absolute and relative widths:
283: * <ol>
284: * <li>If the table width is absolute, all widths become absolute.</li>
285: * <li>If the table width is relative, make all the widths absolute
286: * relative to the nominal table width then turn them all
287: * back into relative widths.</li>
288: * </ol>
289: * </li>
290: * </ol>
291: *
292: * @param context The stylesheet context; supplied automatically by Saxon
293: * @param rtf The result tree fragment containing the colgroup.
294: *
295: * @return The result tree fragment containing the adjusted colgroup.
296: *
297: */
298: public static NodeSetValue adjustColumnWidths(Context context,
299: NodeSetValue rtf_ns) {
300:
301: FragmentValue rtf = (FragmentValue) rtf_ns;
302:
303: setupColumnWidths(context);
304:
305: try {
306: Controller controller = context.getController();
307: NamePool namePool = controller.getNamePool();
308:
309: ColumnScanEmitter csEmitter = new ColumnScanEmitter(
310: namePool);
311: rtf.replay(csEmitter);
312:
313: int numColumns = csEmitter.columnCount();
314: String widths[] = csEmitter.columnWidths();
315:
316: float relTotal = 0;
317: float relParts[] = new float[numColumns];
318:
319: float absTotal = 0;
320: float absParts[] = new float[numColumns];
321:
322: for (int count = 0; count < numColumns; count++) {
323: String width = widths[count];
324:
325: int pos = width.indexOf("*");
326: if (pos >= 0) {
327: String relPart = width.substring(0, pos);
328: String absPart = width.substring(pos + 1);
329:
330: try {
331: float rel = Float.parseFloat(relPart);
332: relTotal += rel;
333: relParts[count] = rel;
334: } catch (NumberFormatException e) {
335: System.out.println(relPart
336: + " is not a valid relative unit.");
337: }
338:
339: int pixels = 0;
340: if (absPart != null && !absPart.equals("")) {
341: pixels = convertLength(absPart);
342: }
343:
344: absTotal += pixels;
345: absParts[count] = pixels;
346: } else {
347: relParts[count] = 0;
348:
349: int pixels = 0;
350: if (width != null && !width.equals("")) {
351: pixels = convertLength(width);
352: }
353:
354: absTotal += pixels;
355: absParts[count] = pixels;
356: }
357: }
358:
359: // Ok, now we have the relative widths and absolute widths in
360: // two parallel arrays.
361: //
362: // - If there are no relative widths, output the absolute widths
363: // - If there are no absolute widths, output the relative widths
364: // - If there are a mixture of relative and absolute widths,
365: // - If the table width is absolute, turn these all into absolute
366: // widths.
367: // - If the table width is relative, turn these all into absolute
368: // widths in the nominalWidth and then turn them back into
369: // percentages.
370:
371: if (relTotal == 0) {
372: for (int count = 0; count < numColumns; count++) {
373: Float f = new Float(absParts[count]);
374: if (foStylesheet) {
375: int pixels = f.intValue();
376: float inches = (float) pixels / pixelsPerInch;
377: widths[count] = inches + "in";
378: } else {
379: widths[count] = Integer.toString(f.intValue());
380: }
381: }
382: } else if (absTotal == 0) {
383: for (int count = 0; count < numColumns; count++) {
384: float rel = relParts[count] / relTotal * 100;
385: Float f = new Float(rel);
386: widths[count] = Integer.toString(f.intValue());
387: }
388: widths = correctRoundingError(widths);
389: } else {
390: int pixelWidth = nominalWidth;
391:
392: if (tableWidth.indexOf("%") <= 0) {
393: pixelWidth = convertLength(tableWidth);
394: }
395:
396: if (pixelWidth <= absTotal) {
397: System.out
398: .println("Table is wider than table width.");
399: } else {
400: pixelWidth -= absTotal;
401: }
402:
403: absTotal = 0;
404: for (int count = 0; count < numColumns; count++) {
405: float rel = relParts[count] / relTotal * pixelWidth;
406: relParts[count] = rel + absParts[count];
407: absTotal += rel + absParts[count];
408: }
409:
410: if (tableWidth.indexOf("%") <= 0) {
411: for (int count = 0; count < numColumns; count++) {
412: Float f = new Float(relParts[count]);
413: if (foStylesheet) {
414: int pixels = f.intValue();
415: float inches = (float) pixels
416: / pixelsPerInch;
417: widths[count] = inches + "in";
418: } else {
419: widths[count] = Integer.toString(f
420: .intValue());
421: }
422: }
423: } else {
424: for (int count = 0; count < numColumns; count++) {
425: float rel = relParts[count] / absTotal * 100;
426: Float f = new Float(rel);
427: widths[count] = Integer.toString(f.intValue());
428: }
429: widths = correctRoundingError(widths);
430: }
431: }
432:
433: ColumnUpdateEmitter cuEmitter = new ColumnUpdateEmitter(
434: controller, namePool, widths);
435:
436: rtf.replay(cuEmitter);
437: return cuEmitter.getResultTreeFragment();
438: } catch (TransformerException e) {
439: // This "can't" happen.
440: System.out
441: .println("Transformer Exception in adjustColumnWidths");
442: return rtf;
443: }
444: }
445:
446: /**
447: * Correct rounding errors introduced in calculating the width of each
448: * column. Make sure they sum to 100% in the end.
449: */
450: protected static String[] correctRoundingError(String widths[]) {
451: int totalWidth = 0;
452:
453: for (int count = 0; count < widths.length; count++) {
454: try {
455: int width = Integer.parseInt(widths[count]);
456: totalWidth += width;
457: } catch (NumberFormatException nfe) {
458: // nop; "can't happen"
459: }
460: }
461:
462: float totalError = 100 - totalWidth;
463: float columnError = totalError / widths.length;
464: float error = 0;
465:
466: for (int count = 0; count < widths.length; count++) {
467: try {
468: int width = Integer.parseInt(widths[count]);
469: error = error + columnError;
470: if (error >= 1.0) {
471: int adj = (int) Math.round(Math.floor(error));
472: error = error - (float) Math.floor(error);
473: width = width + adj;
474: widths[count] = Integer.toString(width) + "%";
475: } else {
476: widths[count] = Integer.toString(width) + "%";
477: }
478: } catch (NumberFormatException nfe) {
479: // nop; "can't happen"
480: }
481: }
482:
483: return widths;
484: }
485: }
|