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 2001/07/10 16:58:38 nwalsh 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 2001/07/10 16:58:38 nwalsh 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: } else {
370: int pixelWidth = nominalWidth;
371:
372: if (tableWidth.indexOf("%") <= 0) {
373: pixelWidth = convertLength(tableWidth);
374: }
375:
376: if (pixelWidth <= absTotal) {
377: System.out.println("Table is wider than table width.");
378: } else {
379: pixelWidth -= absTotal;
380: }
381:
382: absTotal = 0;
383: for (int count = 0; count < numColumns; count++) {
384: float rel = relParts[count] / relTotal * pixelWidth;
385: relParts[count] = rel + absParts[count];
386: absTotal += rel + absParts[count];
387: }
388:
389: if (tableWidth.indexOf("%") <= 0) {
390: for (int count = 0; count < numColumns; count++) {
391: Float f = new Float(relParts[count]);
392: if (foStylesheet) {
393: int pixels = f.intValue();
394: float inches = (float) pixels / pixelsPerInch;
395: widths[count] = inches + "in";
396: } else {
397: widths[count] = Integer.toString(f.intValue());
398: }
399: }
400: } else {
401: for (int count = 0; count < numColumns; count++) {
402: float rel = relParts[count] / absTotal * 100;
403: Float f = new Float(rel);
404: widths[count] = Integer.toString(f.intValue())
405: + "%";
406: }
407: }
408: }
409:
410: // Now rebuild the colgroup with the right widths
411:
412: DocumentBuilderFactory docFactory = DocumentBuilderFactory
413: .newInstance();
414: DocumentBuilder docBuilder = null;
415:
416: try {
417: docBuilder = docFactory.newDocumentBuilder();
418: } catch (ParserConfigurationException e) {
419: System.out.println("PCE!");
420: return xalanRTF;
421: }
422: Document doc = docBuilder.newDocument();
423: DocumentFragment df = doc.createDocumentFragment();
424: DOMBuilder rtf = new DOMBuilder(doc, df);
425:
426: try {
427: String ns = colgroup.getNamespaceURI();
428: String localName = colgroup.getLocalName();
429: String name = colgroup.getTagName();
430:
431: if (colgroup.getLocalName().equals("colgroup")) {
432: rtf.startElement(ns, localName, name,
433: copyAttributes(colgroup));
434: }
435:
436: for (colnum = 0; colnum < numColumns; colnum++) {
437: Element col = columns[colnum];
438:
439: NamedNodeMap domAttr = col.getAttributes();
440:
441: AttributesImpl attr = new AttributesImpl();
442: for (int acount = 0; acount < domAttr.getLength(); acount++) {
443: Node a = domAttr.item(acount);
444: String a_ns = a.getNamespaceURI();
445: String a_localName = a.getLocalName();
446:
447: if ((foStylesheet && !a_localName
448: .equals("column-width"))
449: || !a_localName.equalsIgnoreCase("width")) {
450: attr.addAttribute(a.getNamespaceURI(), a
451: .getLocalName(), a.getNodeName(),
452: "CDATA", a.getNodeValue());
453: }
454: }
455:
456: if (foStylesheet) {
457: attr.addAttribute("", "column-width",
458: "column-width", "CDATA", widths[colnum]);
459: } else {
460: attr.addAttribute("", "width", "width", "CDATA",
461: widths[colnum]);
462: }
463:
464: rtf.startElement(col.getNamespaceURI(), col
465: .getLocalName(), col.getTagName(), attr);
466: rtf.endElement(col.getNamespaceURI(), col
467: .getLocalName(), col.getTagName());
468: }
469:
470: if (colgroup.getLocalName().equals("colgroup")) {
471: rtf.endElement(ns, localName, name);
472: }
473: } catch (SAXException se) {
474: System.out.println("SE!");
475: return xalanRTF;
476: }
477:
478: return df;
479: }
480:
481: private Attributes copyAttributes(Element node) {
482: AttributesImpl attrs = new AttributesImpl();
483: NamedNodeMap nnm = node.getAttributes();
484: for (int count = 0; count < nnm.getLength(); count++) {
485: Attr attr = (Attr) nnm.item(count);
486: String name = attr.getName();
487: if (name.startsWith("xmlns:") || name.equals("xmlns")) {
488: // Skip it; (don't ya just love it!!)
489: } else {
490: attrs.addAttribute(attr.getNamespaceURI(), attr
491: .getName(), attr.getName(), "CDATA", attr
492: .getValue());
493: }
494: }
495: return attrs;
496: }
497: }
|