Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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 &lt;col>s within
183    * a &lt;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 }