001: // Verbatim.java - Xalan extensions supporting DocBook verbatim environments
002:
003: package com.nwalsh.xalan;
004:
005: import java.util.Hashtable;
006: import org.xml.sax.*;
007: import org.xml.sax.helpers.AttributesImpl;
008: import org.w3c.dom.*;
009: import org.w3c.dom.traversal.NodeIterator;
010:
011: import javax.xml.transform.TransformerException;
012:
013: import org.apache.xpath.objects.XObject;
014: import org.apache.xpath.XPathContext;
015: import org.apache.xalan.extensions.ExpressionContext;
016: import org.apache.xml.utils.DOMBuilder;
017: import javax.xml.parsers.DocumentBuilder;
018: import javax.xml.parsers.DocumentBuilderFactory;
019: import javax.xml.parsers.ParserConfigurationException;
020: import org.apache.xml.utils.QName;
021: import org.apache.xpath.DOMHelper;
022: import org.apache.xml.utils.AttList;
023:
024: /**
025: * <p>Xalan extensions supporting Tables</p>
026: *
027: * <p>$Id: Table.java,v 1.4 2005-08-30 08:14:58 draganr Exp $</p>
028: *
029: * <p>Copyright (C) 2000 Norman Walsh.</p>
030: *
031: * <p>This class provides a
032: * <a href="http://xml.apache.org/xalan/">Xalan</a>
033: * implementation of some code to adjust CALS Tables to HTML
034: * Tables.</p>
035: *
036: * <p><b>Column Widths</b></p>
037: * <p>The <tt>adjustColumnWidths</tt> method takes a result tree
038: * fragment (assumed to contain the colgroup of an HTML Table)
039: * and returns the result tree fragment with the column widths
040: * adjusted to HTML terms.</p>
041: *
042: * <p><b>Convert Lengths</b></p>
043: * <p>The <tt>convertLength</tt> method takes a length specification
044: * of the form 9999.99xx (where "xx" is a unit) and returns that length
045: * as an integral number of pixels. For convenience, percentage lengths
046: * are returned unchanged.</p>
047: * <p>The recognized units are: inches (in), centimeters (cm),
048: * millimeters (mm), picas (pc, 1pc=12pt), points (pt), and pixels (px).
049: * A number with no units is assumed to be pixels.</p>
050: *
051: * <p><b>Change Log:</b></p>
052: * <dl>
053: * <dt>1.0</dt>
054: * <dd><p>Initial release.</p></dd>
055: * </dl>
056: *
057: * @author Norman Walsh
058: * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
059: *
060: * @version $Id: Table.java,v 1.4 2005-08-30 08:14:58 draganr Exp $
061: *
062: */
063: public class Table {
064: /** The number of pixels per inch */
065: private static int pixelsPerInch = 96;
066:
067: /** The hash used to associate units with a length in pixels. */
068: protected static Hashtable unitHash = null;
069:
070: /** The FO namespace name. */
071: protected static String foURI = "http://www.w3.org/1999/XSL/Format";
072:
073: /**
074: * <p>Constructor for Verbatim</p>
075: *
076: * <p>All of the methods are static, so the constructor does nothing.</p>
077: */
078: public Table() {
079: }
080:
081: /** Initialize the internal hash table with proper values. */
082: protected static void initializeHash() {
083: unitHash = new Hashtable();
084: unitHash.put("in", new Float(pixelsPerInch));
085: unitHash.put("cm", new Float(pixelsPerInch / 2.54));
086: unitHash.put("mm", new Float(pixelsPerInch / 25.4));
087: unitHash.put("pc", new Float((pixelsPerInch / 72) * 12));
088: unitHash.put("pt", new Float(pixelsPerInch / 72));
089: unitHash.put("px", new Float(1));
090: }
091:
092: /** Set the pixels-per-inch value. Only positive values are legal. */
093: public static void setPixelsPerInch(int value) {
094: if (value > 0) {
095: pixelsPerInch = value;
096: initializeHash();
097: }
098: }
099:
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 length) {
112: // The format of length should be 999.999xx
113: int sign = 1;
114: String digits = "";
115: String 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')
139: && lench[pos] != '.') {
140: done = true;
141: units = length.substring(pos);
142: } else {
143: digits += lench[pos++];
144: }
145: }
146: }
147:
148: try {
149: flength = Float.parseFloat(digits);
150: } catch (NumberFormatException e) {
151: System.out.println(digits
152: + " is not a number; 1 used instead.");
153: flength = 1;
154: }
155:
156: Float f = null;
157:
158: if (!units.equals("")) {
159: f = (Float) unitHash.get(units);
160: if (f == null) {
161: System.out.println(units
162: + " is not a known unit; 1 used instead.");
163: factor = 1;
164: } else {
165: factor = f.floatValue();
166: }
167: } else {
168: factor = 1;
169: }
170:
171: f = new Float(flength * factor);
172:
173: pixels = f.intValue() * sign;
174:
175: return pixels;
176: }
177:
178: /**
179: * <p>Adjust column widths in an HTML table.</p>
180: *
181: * <p>The specification of column widths in CALS (a relative width
182: * plus an optional absolute width) are incompatible with HTML column
183: * widths. This method adjusts CALS column width specifiers in an
184: * attempt to produce equivalent HTML specifiers.</p>
185: *
186: * <p>In order for this method to work, the CALS width specifications
187: * should be placed in the "width" attribute of the <col>s within
188: * a <colgroup>. Then the colgroup result tree fragment is passed
189: * to this method.</p>
190: *
191: * <p>This method makes use of two parameters from the XSL stylesheet
192: * that calls it: <code>nominal.table.width</code> and
193: * <code>table.width</code>. The value of <code>nominal.table.width</code>
194: * must be an absolute distance. The value of <code>table.width</code>
195: * can be either absolute or relative.</p>
196: *
197: * <p>Presented with a mixture of relative and
198: * absolute lengths, the table width is used to calculate
199: * appropriate values. If the <code>table.width</code> is relative,
200: * the nominal width is used for this calculation.</p>
201: *
202: * <p>There are three possible combinations of values:</p>
203: *
204: * <ol>
205: * <li>There are no relative widths; in this case the absolute widths
206: * are used in the HTML table.</li>
207: * <li>There are no absolute widths; in this case the relative widths
208: * are used in the HTML table.</li>
209: * <li>There are a mixture of absolute and relative widths:
210: * <ol>
211: * <li>If the table width is absolute, all widths become absolute.</li>
212: * <li>If the table width is relative, make all the widths absolute
213: * relative to the nominal table width then turn them all
214: * back into relative widths.</li>
215: * </ol>
216: * </li>
217: * </ol>
218: *
219: * @param context The stylesheet context; supplied automatically by Xalan
220: * @param rtf The result tree fragment containing the colgroup.
221: *
222: * @return The result tree fragment containing the adjusted colgroup.
223: *
224: */
225:
226: public DocumentFragment adjustColumnWidths(
227: ExpressionContext context, NodeIterator xalanNI) {
228:
229: int nominalWidth = convertLength(Params.getString(context,
230: "nominal.table.width"));
231: String tableWidth = Params.getString(context, "table.width");
232: String styleType = Params.getString(context,
233: "stylesheet.result.type");
234: boolean foStylesheet = styleType.equals("fo");
235:
236: DocumentFragment xalanRTF = (DocumentFragment) xalanNI
237: .nextNode();
238: Element colgroup = (Element) xalanRTF.getFirstChild();
239:
240: // N.B. ...stree.ElementImpl doesn't implement getElementsByTagName()
241:
242: Node firstCol = null;
243: // If this is an FO tree, there might be no colgroup...
244: if (colgroup.getLocalName().equals("colgroup")) {
245: firstCol = colgroup.getFirstChild();
246: } else {
247: firstCol = colgroup;
248: }
249:
250: // Count the number of columns...
251: Node child = firstCol;
252: int numColumns = 0;
253: while (child != null) {
254: if (child.getNodeType() == Node.ELEMENT_NODE
255: && (child.getNodeName().equals("col") || (child
256: .getNamespaceURI().equals(foURI) && child
257: .getLocalName().equals("table-column")))) {
258: numColumns++;
259: }
260:
261: child = child.getNextSibling();
262: }
263:
264: String widths[] = new String[numColumns];
265: Element columns[] = new Element[numColumns];
266: int colnum = 0;
267:
268: child = firstCol;
269: while (child != null) {
270: if (child.getNodeType() == Node.ELEMENT_NODE
271: && (child.getNodeName().equals("col") || (child
272: .getNamespaceURI().equals(foURI) && child
273: .getLocalName().equals("table-column")))) {
274: Element col = (Element) child;
275:
276: columns[colnum] = col;
277:
278: if (foStylesheet) {
279: if (col.getAttribute("column-width") == null) {
280: widths[colnum] = "1*";
281: } else {
282: widths[colnum] = col
283: .getAttribute("column-width");
284: }
285: } else {
286: if (col.getAttribute("width") == null) {
287: widths[colnum] = "1*";
288: } else {
289: widths[colnum] = col.getAttribute("width");
290: }
291: }
292:
293: colnum++;
294: }
295: child = child.getNextSibling();
296: }
297:
298: float relTotal = 0;
299: float relParts[] = new float[numColumns];
300:
301: float absTotal = 0;
302: float absParts[] = new float[numColumns];
303:
304: for (int count = 0; count < numColumns; count++) {
305: String width = widths[count];
306: int pos = width.indexOf("*");
307: if (pos >= 0) {
308: String relPart = width.substring(0, pos);
309: String absPart = width.substring(pos + 1);
310:
311: try {
312: float rel = Float.parseFloat(relPart);
313: relTotal += rel;
314: relParts[count] = rel;
315: } catch (NumberFormatException e) {
316: System.out.println(relPart
317: + " is not a valid relative unit.");
318: }
319:
320: int pixels = 0;
321: if (absPart != null && !absPart.equals("")) {
322: pixels = convertLength(absPart);
323: }
324:
325: absTotal += pixels;
326: absParts[count] = pixels;
327: } else {
328: relParts[count] = 0;
329:
330: int pixels = 0;
331: if (width != null && !width.equals("")) {
332: pixels = convertLength(width);
333: }
334:
335: absTotal += pixels;
336: absParts[count] = pixels;
337: }
338: }
339:
340: // Ok, now we have the relative widths and absolute widths in
341: // two parallel arrays.
342: //
343: // - If there are no relative widths, output the absolute widths
344: // - If there are no absolute widths, output the relative widths
345: // - If there are a mixture of relative and absolute widths,
346: // - If the table width is absolute, turn these all into absolute
347: // widths.
348: // - If the table width is relative, turn these all into absolute
349: // widths in the nominalWidth and then turn them back into
350: // percentages.
351:
352: if (relTotal == 0) {
353: for (int count = 0; count < numColumns; count++) {
354: Float f = new Float(absParts[count]);
355: if (foStylesheet) {
356: int pixels = f.intValue();
357: float inches = (float) pixels / pixelsPerInch;
358: widths[count] = inches + "in";
359: } else {
360: widths[count] = Integer.toString(f.intValue());
361: }
362: }
363: } else if (absTotal == 0) {
364: for (int count = 0; count < numColumns; count++) {
365: float rel = relParts[count] / relTotal * 100;
366: Float f = new Float(rel);
367: widths[count] = Integer.toString(f.intValue());
368: }
369: widths = correctRoundingError(widths);
370: } else {
371: int pixelWidth = nominalWidth;
372:
373: if (tableWidth.indexOf("%") <= 0) {
374: pixelWidth = convertLength(tableWidth);
375: }
376:
377: if (pixelWidth <= absTotal) {
378: System.out.println("Table is wider than table width.");
379: } else {
380: pixelWidth -= absTotal;
381: }
382:
383: absTotal = 0;
384: for (int count = 0; count < numColumns; count++) {
385: float rel = relParts[count] / relTotal * pixelWidth;
386: relParts[count] = rel + absParts[count];
387: absTotal += rel + absParts[count];
388: }
389:
390: if (tableWidth.indexOf("%") <= 0) {
391: for (int count = 0; count < numColumns; count++) {
392: Float f = new Float(relParts[count]);
393: if (foStylesheet) {
394: int pixels = f.intValue();
395: float inches = (float) pixels / pixelsPerInch;
396: widths[count] = inches + "in";
397: } else {
398: widths[count] = Integer.toString(f.intValue());
399: }
400: }
401: } else {
402: for (int count = 0; count < numColumns; count++) {
403: float rel = relParts[count] / absTotal * 100;
404: Float f = new Float(rel);
405: widths[count] = Integer.toString(f.intValue());
406: }
407: widths = correctRoundingError(widths);
408: }
409: }
410:
411: // Now rebuild the colgroup with the right widths
412:
413: DocumentBuilderFactory docFactory = DocumentBuilderFactory
414: .newInstance();
415: DocumentBuilder docBuilder = null;
416:
417: try {
418: docBuilder = docFactory.newDocumentBuilder();
419: } catch (ParserConfigurationException e) {
420: System.out.println("PCE!");
421: return xalanRTF;
422: }
423: Document doc = docBuilder.newDocument();
424: DocumentFragment df = doc.createDocumentFragment();
425: DOMBuilder rtf = new DOMBuilder(doc, df);
426:
427: try {
428: String ns = colgroup.getNamespaceURI();
429: String localName = colgroup.getLocalName();
430: String name = colgroup.getTagName();
431:
432: if (colgroup.getLocalName().equals("colgroup")) {
433: rtf.startElement(ns, localName, name,
434: copyAttributes(colgroup));
435: }
436:
437: for (colnum = 0; colnum < numColumns; colnum++) {
438: Element col = columns[colnum];
439:
440: NamedNodeMap domAttr = col.getAttributes();
441:
442: AttributesImpl attr = new AttributesImpl();
443: for (int acount = 0; acount < domAttr.getLength(); acount++) {
444: Node a = domAttr.item(acount);
445: String a_ns = a.getNamespaceURI();
446: String a_localName = a.getLocalName();
447:
448: if ((foStylesheet && !a_localName
449: .equals("column-width"))
450: || !a_localName.equalsIgnoreCase("width")) {
451: attr.addAttribute(a.getNamespaceURI(), a
452: .getLocalName(), a.getNodeName(),
453: "CDATA", a.getNodeValue());
454: }
455: }
456:
457: if (foStylesheet) {
458: attr.addAttribute("", "column-width",
459: "column-width", "CDATA", widths[colnum]);
460: } else {
461: attr.addAttribute("", "width", "width", "CDATA",
462: widths[colnum]);
463: }
464:
465: rtf.startElement(col.getNamespaceURI(), col
466: .getLocalName(), col.getTagName(), attr);
467: rtf.endElement(col.getNamespaceURI(), col
468: .getLocalName(), col.getTagName());
469: }
470:
471: if (colgroup.getLocalName().equals("colgroup")) {
472: rtf.endElement(ns, localName, name);
473: }
474: } catch (SAXException se) {
475: System.out.println("SE!");
476: return xalanRTF;
477: }
478:
479: return df;
480: }
481:
482: private Attributes copyAttributes(Element node) {
483: AttributesImpl attrs = new AttributesImpl();
484: NamedNodeMap nnm = node.getAttributes();
485: for (int count = 0; count < nnm.getLength(); count++) {
486: Attr attr = (Attr) nnm.item(count);
487: String name = attr.getName();
488: if (name.startsWith("xmlns:") || name.equals("xmlns")) {
489: // Skip it; (don't ya just love it!!)
490: } else {
491: attrs.addAttribute(attr.getNamespaceURI(), attr
492: .getName(), attr.getName(), "CDATA", attr
493: .getValue());
494: }
495: }
496: return attrs;
497: }
498:
499: /**
500: * Correct rounding errors introduced in calculating the width of each
501: * column. Make sure they sum to 100% in the end.
502: */
503: protected String[] correctRoundingError(String widths[]) {
504: int totalWidth = 0;
505:
506: for (int count = 0; count < widths.length; count++) {
507: try {
508: int width = Integer.parseInt(widths[count]);
509: totalWidth += width;
510: } catch (NumberFormatException nfe) {
511: // nop; "can't happen"
512: }
513: }
514:
515: float totalError = 100 - totalWidth;
516: float columnError = totalError / widths.length;
517: float error = 0;
518:
519: for (int count = 0; count < widths.length; count++) {
520: try {
521: int width = Integer.parseInt(widths[count]);
522: error = error + columnError;
523: if (error >= 1.0) {
524: int adj = (int) Math.round(Math.floor(error));
525: error = error - (float) Math.floor(error);
526: width = width + adj;
527: widths[count] = Integer.toString(width) + "%";
528: } else {
529: widths[count] = Integer.toString(width) + "%";
530: }
531: } catch (NumberFormatException nfe) {
532: // nop; "can't happen"
533: }
534: }
535:
536: return widths;
537: }
538: }
|