KickJava   Java API By Example, From Geeks To Geeks.

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


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

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

63 public class Table {
64   /** The number of pixels per inch */
65   private static int pixelsPerInch = 96;
66
67   /** The hash used to associate units with a length in pixels. */
68   protected static Hashtable JavaDoc unitHash = null;
69
70   /** The FO namespace name. */
71   protected static String JavaDoc foURI = "http://www.w3.org/1999/XSL/Format";
72
73   /**
74    * <p>Constructor for Verbatim</p>
75    *
76    * <p>All of the methods are static, so the constructor does nothing.</p>
77    */

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

111   public static int convertLength(String JavaDoc length) {
112     // The format of length should be 999.999xx
113
int sign = 1;
114     String JavaDoc digits = "";
115     String JavaDoc units = "";
116     char lench[] = length.toCharArray();
117     float flength = 0;
118     boolean done = false;
119     int pos = 0;
120     float factor = 1;
121     int pixels = 0;
122
123     if (unitHash == null) {
124       initializeHash();
125     }
126
127     if (lench[pos] == '+' || lench[pos] == '-') {
128       if (lench[pos] == '-') {
129     sign = -1;
130       }
131       pos++;
132     }
133
134     while (!done) {
135       if (pos >= lench.length) {
136     done = true;
137       } else {
138     if ((lench[pos] > '9' || lench[pos] < '0') && 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 JavaDoc e) {
150       System.out.println(digits + " is not a number; 1 used instead.");
151       flength = 1;
152     }
153
154     Float JavaDoc f = null;
155
156     if (!units.equals("")) {
157       f = (Float JavaDoc) unitHash.get(units);
158       if (f == null) {
159     System.out.println(units + " is not a known unit; 1 used instead.");
160     factor = 1;
161       } else {
162     factor = f.floatValue();
163       }
164     } else {
165       factor = 1;
166     }
167
168     f = new Float JavaDoc(flength * factor);
169
170     pixels = f.intValue() * sign;
171
172     return pixels;
173   }
174
175   /**
176    * <p>Adjust column widths in an HTML table.</p>
177    *
178    * <p>The specification of column widths in CALS (a relative width
179    * plus an optional absolute width) are incompatible with HTML column
180    * widths. This method adjusts CALS column width specifiers in an
181    * attempt to produce equivalent HTML specifiers.</p>
182    *
183    * <p>In order for this method to work, the CALS width specifications
184    * should be placed in the "width" attribute of the &lt;col>s within
185    * a &lt;colgroup>. Then the colgroup result tree fragment is passed
186    * to this method.</p>
187    *
188    * <p>This method makes use of two parameters from the XSL stylesheet
189    * that calls it: <code>nominal.table.width</code> and
190    * <code>table.width</code>. The value of <code>nominal.table.width</code>
191    * must be an absolute distance. The value of <code>table.width</code>
192    * can be either absolute or relative.</p>
193    *
194    * <p>Presented with a mixture of relative and
195    * absolute lengths, the table width is used to calculate
196    * appropriate values. If the <code>table.width</code> is relative,
197    * the nominal width is used for this calculation.</p>
198    *
199    * <p>There are three possible combinations of values:</p>
200    *
201    * <ol>
202    * <li>There are no relative widths; in this case the absolute widths
203    * are used in the HTML table.</li>
204    * <li>There are no absolute widths; in this case the relative widths
205    * are used in the HTML table.</li>
206    * <li>There are a mixture of absolute and relative widths:
207    * <ol>
208    * <li>If the table width is absolute, all widths become absolute.</li>
209    * <li>If the table width is relative, make all the widths absolute
210    * relative to the nominal table width then turn them all
211    * back into relative widths.</li>
212    * </ol>
213    * </li>
214    * </ol>
215    *
216    * @param context The stylesheet context; supplied automatically by Xalan
217    * @param rtf The result tree fragment containing the colgroup.
218    *
219    * @return The result tree fragment containing the adjusted colgroup.
220    *
221    */

222
223   public DocumentFragment adjustColumnWidths (ExpressionContext context,
224                           NodeIterator xalanNI) {
225
226     int nominalWidth = convertLength(Params.getString(context,
227                               "nominal.table.width"));
228     String JavaDoc tableWidth = Params.getString(context, "table.width");
229     String JavaDoc styleType = Params.getString(context, "stylesheet.result.type");
230     boolean foStylesheet = styleType.equals("fo");
231
232     DocumentFragment xalanRTF = (DocumentFragment) xalanNI.nextNode();
233     Element colgroup = (Element) xalanRTF.getFirstChild();
234
235     // N.B. ...stree.ElementImpl doesn't implement getElementsByTagName()
236

237     Node firstCol = null;
238     // If this is an FO tree, there might be no colgroup...
239
if (colgroup.getLocalName().equals("colgroup")) {
240       firstCol = colgroup.getFirstChild();
241     } else {
242       firstCol = colgroup;
243     }
244
245     // Count the number of columns...
246
Node child = firstCol;
247     int numColumns = 0;
248     while (child != null) {
249       if (child.getNodeType() == Node.ELEMENT_NODE
250       && (child.getNodeName().equals("col")
251           || (child.getNamespaceURI().equals(foURI)
252           && child.getLocalName().equals("table-column")))) {
253     numColumns++;
254       }
255
256       child = child.getNextSibling();
257     }
258
259     String JavaDoc widths[] = new String JavaDoc[numColumns];
260     Element columns[] = new Element[numColumns];
261     int colnum = 0;
262
263     child = firstCol;
264     while (child != null) {
265       if (child.getNodeType() == Node.ELEMENT_NODE
266       && (child.getNodeName().equals("col")
267           || (child.getNamespaceURI().equals(foURI)
268           && child.getLocalName().equals("table-column")))) {
269     Element col = (Element) child;
270
271     columns[colnum] = col;
272
273     if (foStylesheet) {
274       if (col.getAttribute("column-width") == null) {
275         widths[colnum] = "1*";
276       } else {
277         widths[colnum] = col.getAttribute("column-width");
278       }
279     } else {
280       if (col.getAttribute("width") == null) {
281         widths[colnum] = "1*";
282       } else {
283         widths[colnum] = col.getAttribute("width");
284       }
285     }
286
287     colnum++;
288       }
289       child = child.getNextSibling();
290     }
291
292     float relTotal = 0;
293     float relParts[] = new float[numColumns];
294
295     float absTotal = 0;
296     float absParts[] = new float[numColumns];
297
298     for (int count = 0; count < numColumns; count++) {
299       String JavaDoc width = widths[count];
300       int pos = width.indexOf("*");
301       if (pos >= 0) {
302     String JavaDoc relPart = width.substring(0, pos);
303     String JavaDoc absPart = width.substring(pos+1);
304
305     try {
306       float rel = Float.parseFloat(relPart);
307       relTotal += rel;
308       relParts[count] = rel;
309     } catch (NumberFormatException JavaDoc e) {
310       System.out.println(relPart + " is not a valid relative unit.");
311     }
312
313     int pixels = 0;
314     if (absPart != null && !absPart.equals("")) {
315       pixels = convertLength(absPart);
316     }
317
318     absTotal += pixels;
319     absParts[count] = pixels;
320       } else {
321     relParts[count] = 0;
322
323     int pixels = 0;
324     if (width != null && !width.equals("")) {
325       pixels = convertLength(width);
326     }
327
328     absTotal += pixels;
329     absParts[count] = pixels;
330       }
331     }
332
333     // Ok, now we have the relative widths and absolute widths in
334
// two parallel arrays.
335
//
336
// - If there are no relative widths, output the absolute widths
337
// - If there are no absolute widths, output the relative widths
338
// - If there are a mixture of relative and absolute widths,
339
// - If the table width is absolute, turn these all into absolute
340
// widths.
341
// - If the table width is relative, turn these all into absolute
342
// widths in the nominalWidth and then turn them back into
343
// percentages.
344

345     if (relTotal == 0) {
346       for (int count = 0; count < numColumns; count++) {
347     Float JavaDoc f = new Float JavaDoc(absParts[count]);
348     if (foStylesheet) {
349       int pixels = f.intValue();
350       float inches = (float) pixels / pixelsPerInch;
351       widths[count] = inches + "in";
352     } else {
353       widths[count] = Integer.toString(f.intValue());
354     }
355       }
356     } else if (absTotal == 0) {
357       for (int count = 0; count < numColumns; count++) {
358     float rel = relParts[count] / relTotal * 100;
359     Float JavaDoc f = new Float JavaDoc(rel);
360     widths[count] = Integer.toString(f.intValue());
361       }
362       widths = correctRoundingError(widths);
363     } else {
364       int pixelWidth = nominalWidth;
365
366       if (tableWidth.indexOf("%") <= 0) {
367     pixelWidth = convertLength(tableWidth);
368       }
369
370       if (pixelWidth <= absTotal) {
371     System.out.println("Table is wider than table width.");
372       } else {
373     pixelWidth -= absTotal;
374       }
375
376       absTotal = 0;
377       for (int count = 0; count < numColumns; count++) {
378     float rel = relParts[count] / relTotal * pixelWidth;
379     relParts[count] = rel + absParts[count];
380     absTotal += rel + absParts[count];
381       }
382
383       if (tableWidth.indexOf("%") <= 0) {
384     for (int count = 0; count < numColumns; count++) {
385       Float JavaDoc f = new Float JavaDoc(relParts[count]);
386       if (foStylesheet) {
387         int pixels = f.intValue();
388         float inches = (float) pixels / pixelsPerInch;
389         widths[count] = inches + "in";
390       } else {
391         widths[count] = Integer.toString(f.intValue());
392       }
393     }
394       } else {
395     for (int count = 0; count < numColumns; count++) {
396       float rel = relParts[count] / absTotal * 100;
397       Float JavaDoc f = new Float JavaDoc(rel);
398       widths[count] = Integer.toString(f.intValue());
399     }
400     widths = correctRoundingError(widths);
401       }
402     }
403
404     // Now rebuild the colgroup with the right widths
405

406     DocumentBuilderFactory JavaDoc docFactory = DocumentBuilderFactory.newInstance();
407     DocumentBuilder JavaDoc docBuilder = null;
408
409     try {
410       docBuilder = docFactory.newDocumentBuilder();
411     } catch (ParserConfigurationException JavaDoc e) {
412       System.out.println("PCE!");
413       return xalanRTF;
414     }
415     Document doc = docBuilder.newDocument();
416     DocumentFragment df = doc.createDocumentFragment();
417     DOMBuilder rtf = new DOMBuilder(doc, df);
418
419     try {
420       String JavaDoc ns = colgroup.getNamespaceURI();
421       String JavaDoc localName = colgroup.getLocalName();
422       String JavaDoc name = colgroup.getTagName();
423
424       if (colgroup.getLocalName().equals("colgroup")) {
425     rtf.startElement(ns, localName, name,
426              copyAttributes(colgroup));
427       }
428
429       for (colnum = 0; colnum < numColumns; colnum++) {
430     Element col = columns[colnum];
431
432     NamedNodeMap domAttr = col.getAttributes();
433
434     AttributesImpl JavaDoc attr = new AttributesImpl JavaDoc();
435     for (int acount = 0; acount < domAttr.getLength(); acount++) {
436       Node a = domAttr.item(acount);
437       String JavaDoc a_ns = a.getNamespaceURI();
438       String JavaDoc a_localName = a.getLocalName();
439
440       if ((foStylesheet && !a_localName.equals("column-width"))
441           || !a_localName.equalsIgnoreCase("width")) {
442         attr.addAttribute(a.getNamespaceURI(),
443                   a.getLocalName(),
444                   a.getNodeName(),
445                   "CDATA",
446                   a.getNodeValue());
447       }
448     }
449
450     if (foStylesheet) {
451       attr.addAttribute("", "column-width", "column-width", "CDATA", widths[colnum]);
452     } else {
453       attr.addAttribute("", "width", "width", "CDATA", widths[colnum]);
454     }
455
456     rtf.startElement(col.getNamespaceURI(),
457              col.getLocalName(),
458              col.getTagName(),
459              attr);
460     rtf.endElement(col.getNamespaceURI(),
461                col.getLocalName(),
462                col.getTagName());
463       }
464
465       if (colgroup.getLocalName().equals("colgroup")) {
466     rtf.endElement(ns, localName, name);
467       }
468     } catch (SAXException se) {
469       System.out.println("SE!");
470       return xalanRTF;
471     }
472
473     return df;
474   }
475
476   private Attributes copyAttributes(Element node) {
477     AttributesImpl JavaDoc attrs = new AttributesImpl JavaDoc();
478     NamedNodeMap nnm = node.getAttributes();
479     for (int count = 0; count < nnm.getLength(); count++) {
480       Attr attr = (Attr) nnm.item(count);
481       String JavaDoc name = attr.getName();
482       if (name.startsWith("xmlns:") || name.equals("xmlns")) {
483     // Skip it; (don't ya just love it!!)
484
} else {
485     attrs.addAttribute(attr.getNamespaceURI(), attr.getName(),
486                attr.getName(), "CDATA", attr.getValue());
487       }
488     }
489     return attrs;
490   }
491
492   /**
493    * Correct rounding errors introduced in calculating the width of each
494    * column. Make sure they sum to 100% in the end.
495    */

496   protected String JavaDoc[] correctRoundingError(String JavaDoc widths[]) {
497     int totalWidth = 0;
498
499     for (int count = 0; count < widths.length; count++) {
500       try {
501     int width = Integer.parseInt(widths[count]);
502     totalWidth += width;
503       } catch (NumberFormatException JavaDoc nfe) {
504     // nop; "can't happen"
505
}
506     }
507
508     float totalError = 100 - totalWidth;
509     float columnError = totalError / widths.length;
510     float error = 0;
511
512     for (int count = 0; count < widths.length; count++) {
513       try {
514     int width = Integer.parseInt(widths[count]);
515     error = error + columnError;
516     if (error >= 1.0) {
517       int adj = (int) Math.round(Math.floor(error));
518       error = error - (float) Math.floor(error);
519       width = width + adj;
520       widths[count] = Integer.toString(width) + "%";
521     } else {
522       widths[count] = Integer.toString(width) + "%";
523     }
524       } catch (NumberFormatException JavaDoc nfe) {
525     // nop; "can't happen"
526
}
527     }
528
529     return widths;
530   }
531 }
532
Popular Tags