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