Source code: com/nwalsh/xalan/Table.java
1 // Verbatim.java - Xalan extensions supporting DocBook verbatim environments
2
3 package com.nwalsh.xalan;
4
5 import java.util.Hashtable;
6 import org.xml.sax.*;
7 import org.xml.sax.helpers.AttributesImpl;
8 import org.w3c.dom.*;
9 import org.w3c.dom.traversal.NodeIterator;
10
11 import javax.xml.transform.TransformerException;
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;
18 import javax.xml.parsers.DocumentBuilderFactory;
19 import javax.xml.parsers.ParserConfigurationException;
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 *
28 * <p>Copyright (C) 2000 Norman Walsh.</p>
29 *
30 * <p>This class provides a
31 * <a href="http://xml.apache.org/xalan/">Xalan</a>
32 * implementation of some code to adjust CALS Tables to HTML
33 * Tables.</p>
34 *
35 * <p><b>Column Widths</b></p>
36 * <p>The <tt>adjustColumnWidths</tt> method takes a result tree
37 * fragment (assumed to contain the colgroup of an HTML Table)
38 * and returns the result tree fragment with the column widths
39 * adjusted to HTML terms.</p>
40 *
41 * <p><b>Convert Lengths</b></p>
42 * <p>The <tt>convertLength</tt> method takes a length specification
43 * of the form 9999.99xx (where "xx" is a unit) and returns that length
44 * as an integral number of pixels. For convenience, percentage lengths
45 * are returned unchanged.</p>
46 * <p>The recognized units are: inches (in), centimeters (cm),
47 * millimeters (mm), picas (pc, 1pc=12pt), points (pt), and pixels (px).
48 * A number with no units is assumed to be pixels.</p>
49 *
50 * <p><b>Change Log:</b></p>
51 * <dl>
52 * <dt>1.0</dt>
53 * <dd><p>Initial release.</p></dd>
54 * </dl>
55 *
56 * @author Norman Walsh
57 * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
58 *
59 *
60 */
61 public class Table {
62 /** The number of pixels per inch */
63 private static int pixelsPerInch = 96;
64
65 /** The hash used to associate units with a length in pixels. */
66 protected static Hashtable unitHash = null;
67
68 /** The FO namespace name. */
69 protected static String foURI = "http://www.w3.org/1999/XSL/Format";
70
71 /**
72 * <p>Constructor for Verbatim</p>
73 *
74 * <p>All of the methods are static, so the constructor does nothing.</p>
75 */
76 public Table() {
77 }
78
79 /** Initialize the internal hash table with proper values. */
80 protected static void initializeHash() {
81 unitHash = new Hashtable();
82 unitHash.put("in", new Float(pixelsPerInch));
83 unitHash.put("cm", new Float(pixelsPerInch / 2.54));
84 unitHash.put("mm", new Float(pixelsPerInch / 25.4));
85 unitHash.put("pc", new Float((pixelsPerInch / 72) * 12));
86 unitHash.put("pt", new Float(pixelsPerInch / 72));
87 unitHash.put("px", new Float(1));
88 }
89
90 /** Set the pixels-per-inch value. Only positive values are legal. */
91 public static void setPixelsPerInch(int value) {
92 if (value > 0) {
93 pixelsPerInch = value;
94 initializeHash();
95 }
96 }
97
98 /** Return the current pixels-per-inch value. */
99 public int getPixelsPerInch() {
100 return pixelsPerInch;
101 }
102
103 /**
104 * <p>Convert a length specification to a number of pixels.</p>
105 *
106 * <p>The specified length should be of the form [+/-]999.99xx,
107 * where xx is a valid unit.</p>
108 */
109 public static int convertLength(String length) {
110 // The format of length should be 999.999xx
111 int sign = 1;
112 String digits = "";
113 String units = "";
114 char lench[] = length.toCharArray();
115 float flength = 0;
116 boolean done = false;
117 int pos = 0;
118 float factor = 1;
119 int pixels = 0;
120
121 if (unitHash == null) {
122 initializeHash();
123 }
124
125 if (lench[pos] == '+' || lench[pos] == '-') {
126 if (lench[pos] == '-') {
127 sign = -1;
128 }
129 pos++;
130 }
131
132 while (!done) {
133 if (pos >= lench.length) {
134 done = true;
135 } else {
136 if ((lench[pos] > '9' || lench[pos] < '0') && lench[pos] != '.') {
137 done = true;
138 units = length.substring(pos);
139 } else {
140 digits += lench[pos++];
141 }
142 }
143 }
144
145 try {
146 flength = Float.parseFloat(digits);
147 } catch (NumberFormatException e) {
148 System.out.println(digits + " is not a number; 1 used instead.");
149 flength = 1;
150 }
151
152 Float f = null;
153
154 if (!units.equals("")) {
155 f = (Float) unitHash.get(units);
156 if (f == null) {
157 System.out.println(units + " is not a known unit; 1 used instead.");
158 factor = 1;
159 } else {
160 factor = f.floatValue();
161 }
162 } else {
163 factor = 1;
164 }
165
166 f = new Float(flength * factor);
167
168 pixels = f.intValue() * sign;
169
170 return pixels;
171 }
172
173 /**
174 * <p>Adjust column widths in an HTML table.</p>
175 *
176 * <p>The specification of column widths in CALS (a relative width
177 * plus an optional absolute width) are incompatible with HTML column
178 * widths. This method adjusts CALS column width specifiers in an
179 * attempt to produce equivalent HTML specifiers.</p>
180 *
181 * <p>In order for this method to work, the CALS width specifications
182 * should be placed in the "width" attribute of the <col>s within
183 * a <colgroup>. Then the colgroup result tree fragment is passed
184 * to this method.</p>
185 *
186 * <p>This method makes use of two parameters from the XSL stylesheet
187 * that calls it: <code>nominal.table.width</code> and
188 * <code>table.width</code>. The value of <code>nominal.table.width</code>
189 * must be an absolute distance. The value of <code>table.width</code>
190 * can be either absolute or relative.</p>
191 *
192 * <p>Presented with a mixture of relative and
193 * absolute lengths, the table width is used to calculate
194 * appropriate values. If the <code>table.width</code> is relative,
195 * the nominal width is used for this calculation.</p>
196 *
197 * <p>There are three possible combinations of values:</p>
198 *
199 * <ol>
200 * <li>There are no relative widths; in this case the absolute widths
201 * are used in the HTML table.</li>
202 * <li>There are no absolute widths; in this case the relative widths
203 * are used in the HTML table.</li>
204 * <li>There are a mixture of absolute and relative widths:
205 * <ol>
206 * <li>If the table width is absolute, all widths become absolute.</li>
207 * <li>If the table width is relative, make all the widths absolute
208 * relative to the nominal table width then turn them all
209 * back into relative widths.</li>
210 * </ol>
211 * </li>
212 * </ol>
213 *
214 * @param context The stylesheet context; supplied automatically by Xalan
215 * @param rtf The result tree fragment containing the colgroup.
216 *
217 * @return The result tree fragment containing the adjusted colgroup.
218 *
219 */
220
221 public DocumentFragment adjustColumnWidths (ExpressionContext context,
222 NodeIterator xalanNI) {
223
224 int nominalWidth = convertLength(Params.getString(context,
225 "nominal.table.width"));
226 String tableWidth = Params.getString(context, "table.width");
227 String styleType = Params.getString(context, "stylesheet.result.type");
228 boolean foStylesheet = styleType.equals("fo");
229
230 DocumentFragment xalanRTF = (DocumentFragment) xalanNI.nextNode();
231 Element colgroup = (Element) xalanRTF.getFirstChild();
232
233 // N.B. ...stree.ElementImpl doesn't implement getElementsByTagName()
234
235 Node firstCol = null;
236 // If this is an FO tree, there might be no colgroup...
237 if (colgroup.getLocalName().equals("colgroup")) {
238 firstCol = colgroup.getFirstChild();
239 } else {
240 firstCol = colgroup;
241 }
242
243 // Count the number of columns...
244 Node child = firstCol;
245 int numColumns = 0;
246 while (child != null) {
247 if (child.getNodeType() == Node.ELEMENT_NODE
248 && (child.getNodeName().equals("col")
249 || (child.getNamespaceURI().equals(foURI)
250 && child.getLocalName().equals("table-column")))) {
251 numColumns++;
252 }
253
254 child = child.getNextSibling();
255 }
256
257 String widths[] = new String[numColumns];
258 Element columns[] = new Element[numColumns];
259 int colnum = 0;
260
261 child = firstCol;
262 while (child != null) {
263 if (child.getNodeType() == Node.ELEMENT_NODE
264 && (child.getNodeName().equals("col")
265 || (child.getNamespaceURI().equals(foURI)
266 && child.getLocalName().equals("table-column")))) {
267 Element col = (Element) child;
268
269 columns[colnum] = col;
270
271 if (foStylesheet) {
272 if (col.getAttribute("column-width") == null) {
273 widths[colnum] = "1*";
274 } else {
275 widths[colnum] = col.getAttribute("column-width");
276 }
277 } else {
278 if (col.getAttribute("width") == null) {
279 widths[colnum] = "1*";
280 } else {
281 widths[colnum] = col.getAttribute("width");
282 }
283 }
284
285 colnum++;
286 }
287 child = child.getNextSibling();
288 }
289
290 float relTotal = 0;
291 float relParts[] = new float[numColumns];
292
293 float absTotal = 0;
294 float absParts[] = new float[numColumns];
295
296 for (int count = 0; count < numColumns; count++) {
297 String width = widths[count];
298 int pos = width.indexOf("*");
299 if (pos >= 0) {
300 String relPart = width.substring(0, pos);
301 String absPart = width.substring(pos+1);
302
303 try {
304 float rel = Float.parseFloat(relPart);
305 relTotal += rel;
306 relParts[count] = rel;
307 } catch (NumberFormatException e) {
308 System.out.println(relPart + " is not a valid relative unit.");
309 }
310
311 int pixels = 0;
312 if (absPart != null && !absPart.equals("")) {
313 pixels = convertLength(absPart);
314 }
315
316 absTotal += pixels;
317 absParts[count] = pixels;
318 } else {
319 relParts[count] = 0;
320
321 int pixels = 0;
322 if (width != null && !width.equals("")) {
323 pixels = convertLength(width);
324 }
325
326 absTotal += pixels;
327 absParts[count] = pixels;
328 }
329 }
330
331 // Ok, now we have the relative widths and absolute widths in
332 // two parallel arrays.
333 //
334 // - If there are no relative widths, output the absolute widths
335 // - If there are no absolute widths, output the relative widths
336 // - If there are a mixture of relative and absolute widths,
337 // - If the table width is absolute, turn these all into absolute
338 // widths.
339 // - If the table width is relative, turn these all into absolute
340 // widths in the nominalWidth and then turn them back into
341 // percentages.
342
343 if (relTotal == 0) {
344 for (int count = 0; count < numColumns; count++) {
345 Float f = new Float(absParts[count]);
346 if (foStylesheet) {
347 int pixels = f.intValue();
348 float inches = (float) pixels / pixelsPerInch;
349 widths[count] = inches + "in";
350 } else {
351 widths[count] = Integer.toString(f.intValue());
352 }
353 }
354 } else if (absTotal == 0) {
355 for (int count = 0; count < numColumns; count++) {
356 float rel = relParts[count] / relTotal * 100;
357 Float f = new Float(rel);
358 widths[count] = Integer.toString(f.intValue()) + "%";
359 }
360 } else {
361 int pixelWidth = nominalWidth;
362
363 if (tableWidth.indexOf("%") <= 0) {
364 pixelWidth = convertLength(tableWidth);
365 }
366
367 if (pixelWidth <= absTotal) {
368 System.out.println("Table is wider than table width.");
369 } else {
370 pixelWidth -= absTotal;
371 }
372
373 absTotal = 0;
374 for (int count = 0; count < numColumns; count++) {
375 float rel = relParts[count] / relTotal * pixelWidth;
376 relParts[count] = rel + absParts[count];
377 absTotal += rel + absParts[count];
378 }
379
380 if (tableWidth.indexOf("%") <= 0) {
381 for (int count = 0; count < numColumns; count++) {
382 Float f = new Float(relParts[count]);
383 if (foStylesheet) {
384 int pixels = f.intValue();
385 float inches = (float) pixels / pixelsPerInch;
386 widths[count] = inches + "in";
387 } else {
388 widths[count] = Integer.toString(f.intValue());
389 }
390 }
391 } else {
392 for (int count = 0; count < numColumns; count++) {
393 float rel = relParts[count] / absTotal * 100;
394 Float f = new Float(rel);
395 widths[count] = Integer.toString(f.intValue()) + "%";
396 }
397 }
398 }
399
400 // Now rebuild the colgroup with the right widths
401
402 DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
403 DocumentBuilder docBuilder = null;
404
405 try {
406 docBuilder = docFactory.newDocumentBuilder();
407 } catch (ParserConfigurationException e) {
408 System.out.println("PCE!");
409 return xalanRTF;
410 }
411 Document doc = docBuilder.newDocument();
412 DocumentFragment df = doc.createDocumentFragment();
413 DOMBuilder rtf = new DOMBuilder(doc, df);
414
415 try {
416 String ns = colgroup.getNamespaceURI();
417 String localName = colgroup.getLocalName();
418 String name = colgroup.getTagName();
419
420 if (colgroup.getLocalName().equals("colgroup")) {
421 rtf.startElement(ns, localName, name,
422 copyAttributes(colgroup));
423 }
424
425 for (colnum = 0; colnum < numColumns; colnum++) {
426 Element col = columns[colnum];
427
428 NamedNodeMap domAttr = col.getAttributes();
429
430 AttributesImpl attr = new AttributesImpl();
431 for (int acount = 0; acount < domAttr.getLength(); acount++) {
432 Node a = domAttr.item(acount);
433 String a_ns = a.getNamespaceURI();
434 String a_localName = a.getLocalName();
435
436 if ((foStylesheet && !a_localName.equals("column-width"))
437 || !a_localName.equalsIgnoreCase("width")) {
438 attr.addAttribute(a.getNamespaceURI(),
439 a.getLocalName(),
440 a.getNodeName(),
441 "CDATA",
442 a.getNodeValue());
443 }
444 }
445
446 if (foStylesheet) {
447 attr.addAttribute("", "column-width", "column-width", "CDATA", widths[colnum]);
448 } else {
449 attr.addAttribute("", "width", "width", "CDATA", widths[colnum]);
450 }
451
452 rtf.startElement(col.getNamespaceURI(),
453 col.getLocalName(),
454 col.getTagName(),
455 attr);
456 rtf.endElement(col.getNamespaceURI(),
457 col.getLocalName(),
458 col.getTagName());
459 }
460
461 if (colgroup.getLocalName().equals("colgroup")) {
462 rtf.endElement(ns, localName, name);
463 }
464 } catch (SAXException se) {
465 System.out.println("SE!");
466 return xalanRTF;
467 }
468
469 return df;
470 }
471
472 private Attributes copyAttributes(Element node) {
473 AttributesImpl attrs = new AttributesImpl();
474 NamedNodeMap nnm = node.getAttributes();
475 for (int count = 0; count < nnm.getLength(); count++) {
476 Attr attr = (Attr) nnm.item(count);
477 String name = attr.getName();
478 if (name.startsWith("xmlns:") || name.equals("xmlns")) {
479 // Skip it; (don't ya just love it!!)
480 } else {
481 attrs.addAttribute(attr.getNamespaceURI(), attr.getName(),
482 attr.getName(), "CDATA", attr.getValue());
483 }
484 }
485 return attrs;
486 }
487 }