// Verbatim.java - Saxon extensions supporting DocBook verbatim environments
package com.nwalsh.saxon;
import java.util.Hashtable;
import org.xml.sax.*;
import org.w3c.dom.*;
import javax.xml.transform.TransformerException;
import com.icl.saxon.Controller;
import com.icl.saxon.expr.*;
import com.icl.saxon.om.*;
import com.icl.saxon.pattern.*;
import com.icl.saxon.Context;
import com.icl.saxon.tree.*;
import com.icl.saxon.functions.Extensions;
/**
*
Saxon extensions supporting Tables
*
* $Id: Table.java 5907 2006-04-27 08:26:47Z xmldoc $
*
* Copyright (C) 2000 Norman Walsh.
*
* This class provides a
* Saxon
* implementation of some code to adjust CALS Tables to HTML
* Tables.
*
* Column Widths
* The adjustColumnWidths method takes a result tree
* fragment (assumed to contain the colgroup of an HTML Table)
* and returns the result tree fragment with the column widths
* adjusted to HTML terms.
*
* Convert Lengths
* The convertLength method takes a length specification
* of the form 9999.99xx (where "xx" is a unit) and returns that length
* as an integral number of pixels. For convenience, percentage lengths
* are returned unchanged.
* The recognized units are: inches (in), centimeters (cm),
* millimeters (mm), picas (pc, 1pc=12pt), points (pt), and pixels (px).
* A number with no units is assumed to be pixels.
*
* Change Log:
*
* - 1.0
* Initial release.
*
*
* @author Norman Walsh
* ndw@nwalsh.com
*
* @version $Id: Table.java 5907 2006-04-27 08:26:47Z xmldoc $
*
*/
public class Table {
/** The number of pixels per inch */
private static int pixelsPerInch = 96;
/** The nominal table width (6in by default). */
private static int nominalWidth = 6 * pixelsPerInch;
/** The default table width (100% by default). */
private static String tableWidth = "100%";
/** Is this an FO stylesheet? */
private static boolean foStylesheet = false;
/** The hash used to associate units with a length in pixels. */
protected static Hashtable unitHash = null;
/**
* Constructor for Verbatim
*
* All of the methods are static, so the constructor does nothing.
*/
public Table() {
}
/** Initialize the internal hash table with proper values. */
protected static void initializeHash() {
unitHash = new Hashtable();
unitHash.put("in", new Float(pixelsPerInch));
unitHash.put("cm", new Float(pixelsPerInch / 2.54));
unitHash.put("mm", new Float(pixelsPerInch / 25.4));
unitHash.put("pc", new Float((pixelsPerInch / 72) * 12));
unitHash.put("pt", new Float(pixelsPerInch / 72));
unitHash.put("px", new Float(1));
}
/** Set the pixels-per-inch value. Only positive values are legal. */
public static void setPixelsPerInch(int value) {
if (value > 0) {
pixelsPerInch = value;
initializeHash();
}
}
/** Return the current pixels-per-inch value. */
public int getPixelsPerInch() {
return pixelsPerInch;
}
/**
* Convert a length specification to a number of pixels.
*
* The specified length should be of the form [+/-]999.99xx,
* where xx is a valid unit.
*/
public static int convertLength(String length) {
// The format of length should be 999.999xx
int sign = 1;
String digits = "";
String units = "";
char lench[] = length.toCharArray();
float flength = 0;
boolean done = false;
int pos = 0;
float factor = 1;
int pixels = 0;
if (unitHash == null) {
initializeHash();
}
if (lench[pos] == '+' || lench[pos] == '-') {
if (lench[pos] == '-') {
sign = -1;
}
pos++;
}
while (!done) {
if (pos >= lench.length) {
done = true;
} else {
if ((lench[pos] > '9' || lench[pos] < '0') && lench[pos] != '.') {
done = true;
units = length.substring(pos);
} else {
digits += lench[pos++];
}
}
}
try {
flength = Float.parseFloat(digits);
} catch (NumberFormatException e) {
System.out.println(digits + " is not a number; 1 used instead.");
flength = 1;
}
Float f = null;
if (!units.equals("")) {
f = (Float) unitHash.get(units);
if (f == null) {
System.out.println(units + " is not a known unit; 1 used instead.");
factor = 1;
} else {
factor = f.floatValue();
}
} else {
factor = 1;
}
f = new Float(flength * factor);
pixels = f.intValue() * sign;
return pixels;
}
/**
* Find the string value of a stylesheet variable or parameter
*
* Returns the string value of varName
in the current
* context
. Returns the empty string if the variable is
* not defined.
*
* @param context The current stylesheet context
* @param varName The name of the variable (without the dollar sign)
*
* @return The string value of the variable
*/
protected static String getVariable(Context context, String varName)
throws TransformerException {
Value variable = null;
String varString = null;
try {
variable = Extensions.evaluate(context, "$" + varName);
varString = variable.asString();
return varString;
} catch (IllegalArgumentException e) {
System.out.println("Undefined variable: " + varName);
return "";
}
}
/**
* Setup the parameters associated with column width calculations
*
* This method queries the stylesheet for the variables
* associated with table column widths. It is called automatically before
* column widths are adjusted. The context is used to retrieve the values,
* this allows templates to redefine these variables.
*
* The following variables are queried. If the variables do not
* exist, builtin defaults will be used (but you may also get a bunch
* of messages from the Java interpreter).
*
*
* nominal.table.width
* - The "normal" width for tables. This must be an absolute length.
* table.width
* - The width for tables. This may be either an absolute
* length or a percentage.
*
*
* @param context The current stylesheet context
*
*/
private static void setupColumnWidths(Context context) {
// Hardcoded defaults
nominalWidth = 6 * pixelsPerInch;
tableWidth = "100%";
String varString = null;
try {
// Get the stylesheet type
varString = getVariable(context, "stylesheet.result.type");
foStylesheet = varString.equals("fo");
// Get the nominal table width
varString = getVariable(context, "nominal.table.width");
nominalWidth = convertLength(varString);
// Get the table width
varString = getVariable(context, "table.width");
tableWidth = varString;
} catch (TransformerException e) {
//nop, can't happen
}
}
/**
* Adjust column widths in an HTML table.
*
* The specification of column widths in CALS (a relative width
* plus an optional absolute width) are incompatible with HTML column
* widths. This method adjusts CALS column width specifiers in an
* attempt to produce equivalent HTML specifiers.
*
* In order for this method to work, the CALS width specifications
* should be placed in the "width" attribute of the <col>s within
* a <colgroup>. Then the colgroup result tree fragment is passed
* to this method.
*
* This method makes use of two parameters from the XSL stylesheet
* that calls it: nominal.table.width
and
* table.width
. The value of nominal.table.width
* must be an absolute distance. The value of table.width
* can be either absolute or relative.
*
* Presented with a mixture of relative and
* absolute lengths, the table width is used to calculate
* appropriate values. If the table.width
is relative,
* the nominal width is used for this calculation.
*
* There are three possible combinations of values:
*
*
* - There are no relative widths; in this case the absolute widths
* are used in the HTML table.
* - There are no absolute widths; in this case the relative widths
* are used in the HTML table.
* - There are a mixture of absolute and relative widths:
*
* - If the table width is absolute, all widths become absolute.
* - If the table width is relative, make all the widths absolute
* relative to the nominal table width then turn them all
* back into relative widths.
*
*
*
*
* @param context The stylesheet context; supplied automatically by Saxon
* @param rtf_ns The result tree fragment containing the colgroup.
*
* @return The result tree fragment containing the adjusted colgroup.
*
*/
public static NodeSetValue adjustColumnWidths (Context context,
NodeSetValue rtf_ns) {
FragmentValue rtf = (FragmentValue) rtf_ns;
setupColumnWidths(context);
try {
Controller controller = context.getController();
NamePool namePool = controller.getNamePool();
ColumnScanEmitter csEmitter = new ColumnScanEmitter(namePool);
rtf.replay(csEmitter);
int numColumns = csEmitter.columnCount();
String widths[] = csEmitter.columnWidths();
float relTotal = 0;
float relParts[] = new float[numColumns];
float absTotal = 0;
float absParts[] = new float[numColumns];
for (int count = 0; count < numColumns; count++) {
String width = widths[count];
int pos = width.indexOf("*");
if (pos >= 0) {
String relPart = width.substring(0, pos);
String absPart = width.substring(pos+1);
try {
float rel = Float.parseFloat(relPart);
relTotal += rel;
relParts[count] = rel;
} catch (NumberFormatException e) {
System.out.println(relPart + " is not a valid relative unit.");
}
int pixels = 0;
if (absPart != null && !absPart.equals("")) {
pixels = convertLength(absPart);
}
absTotal += pixels;
absParts[count] = pixels;
} else {
relParts[count] = 0;
int pixels = 0;
if (width != null && !width.equals("")) {
pixels = convertLength(width);
}
absTotal += pixels;
absParts[count] = pixels;
}
}
// Ok, now we have the relative widths and absolute widths in
// two parallel arrays.
//
// - If there are no relative widths, output the absolute widths
// - If there are no absolute widths, output the relative widths
// - If there are a mixture of relative and absolute widths,
// - If the table width is absolute, turn these all into absolute
// widths.
// - If the table width is relative, turn these all into absolute
// widths in the nominalWidth and then turn them back into
// percentages.
if (relTotal == 0) {
for (int count = 0; count < numColumns; count++) {
Float f = new Float(absParts[count]);
if (foStylesheet) {
int pixels = f.intValue();
float inches = (float) pixels / pixelsPerInch;
widths[count] = inches + "in";
} else {
widths[count] = Integer.toString(f.intValue());
}
}
} else if (absTotal == 0) {
for (int count = 0; count < numColumns; count++) {
float rel = relParts[count] / relTotal * 100;
Float f = new Float(rel);
widths[count] = Integer.toString(f.intValue());
}
widths = correctRoundingError(widths);
} else {
int pixelWidth = nominalWidth;
if (tableWidth.indexOf("%") <= 0) {
pixelWidth = convertLength(tableWidth);
}
if (pixelWidth <= absTotal) {
System.out.println("Table is wider than table width.");
} else {
pixelWidth -= absTotal;
}
absTotal = 0;
for (int count = 0; count < numColumns; count++) {
float rel = relParts[count] / relTotal * pixelWidth;
relParts[count] = rel + absParts[count];
absTotal += rel + absParts[count];
}
if (tableWidth.indexOf("%") <= 0) {
for (int count = 0; count < numColumns; count++) {
Float f = new Float(relParts[count]);
if (foStylesheet) {
int pixels = f.intValue();
float inches = (float) pixels / pixelsPerInch;
widths[count] = inches + "in";
} else {
widths[count] = Integer.toString(f.intValue());
}
}
} else {
for (int count = 0; count < numColumns; count++) {
float rel = relParts[count] / absTotal * 100;
Float f = new Float(rel);
widths[count] = Integer.toString(f.intValue());
}
widths = correctRoundingError(widths);
}
}
ColumnUpdateEmitter cuEmitter = new ColumnUpdateEmitter(controller,
namePool,
widths);
rtf.replay(cuEmitter);
return cuEmitter.getResultTreeFragment();
} catch (TransformerException e) {
// This "can't" happen.
System.out.println("Transformer Exception in adjustColumnWidths");
return rtf;
}
}
/**
* Correct rounding errors introduced in calculating the width of each
* column. Make sure they sum to 100% in the end.
*/
protected static String[] correctRoundingError(String widths[]) {
int totalWidth = 0;
for (int count = 0; count < widths.length; count++) {
try {
int width = Integer.parseInt(widths[count]);
totalWidth += width;
} catch (NumberFormatException nfe) {
// nop; "can't happen"
}
}
float totalError = 100 - totalWidth;
float columnError = totalError / widths.length;
float error = 0;
for (int count = 0; count < widths.length; count++) {
try {
int width = Integer.parseInt(widths[count]);
error = error + columnError;
if (error >= 1.0) {
int adj = (int) Math.round(Math.floor(error));
error = error - (float) Math.floor(error);
width = width + adj;
widths[count] = Integer.toString(width) + "%";
} else {
widths[count] = Integer.toString(width) + "%";
}
} catch (NumberFormatException nfe) {
// nop; "can't happen"
}
}
return widths;
}
}