package org.apache.fop.fo.expr;
-import org.apache.fop.fo.Property;
-import org.apache.fop.fo.ListProperty;
-import org.apache.fop.fo.LengthProperty;
-import org.apache.fop.fo.NumberProperty;
-import org.apache.fop.fo.StringProperty;
-import org.apache.fop.fo.ColorTypeProperty;
-import org.apache.fop.datatypes.*;
-
-import java.util.Hashtable;
+import org.apache.fop.fo.PropertyConsts;
+import org.apache.fop.fo.Properties;
+import org.apache.fop.fo.PropNames;
+
+import org.apache.fop.fo.expr.PropertyValue;
+import org.apache.fop.fo.expr.PropertyValueList;
+import org.apache.fop.datatypes.Numeric;
+import org.apache.fop.datatypes.Literal;
+import org.apache.fop.datatypes.NCName;
+import org.apache.fop.datatypes.Percentage;
+import org.apache.fop.datatypes.Ems;
+import org.apache.fop.datatypes.IntegerType;
+import org.apache.fop.datatypes.Length;
+import org.apache.fop.datatypes.Time;
+import org.apache.fop.datatypes.Frequency;
+import org.apache.fop.datatypes.Angle;
+import org.apache.fop.datatypes.Bool;
+import org.apache.fop.datatypes.Inherit;
+import org.apache.fop.datatypes.Auto;
+import org.apache.fop.datatypes.None;
+import org.apache.fop.datatypes.ColorType;
+import org.apache.fop.datatypes.StringType;
+import org.apache.fop.datatypes.MimeType;
+import org.apache.fop.datatypes.UriType;
+import org.apache.fop.datatypes.FromParent;
+import org.apache.fop.datatypes.FromNearestSpecified;
+//import org.apache.fop.datatypes.*;
+
+import java.util.HashMap;
/**
* Class to parse XSL FO property expression.
- * This class is heavily based on the epxression parser in James Clark's
+ * This class is heavily based on the expression parser in James Clark's
* XT, an XSLT processor.
+ *
+ * PropertyParser objects are re-usable. The constructor simply creates the
+ * object. To parse an expression, the public method <i>Parse</i> is
+ * called.
*/
public class PropertyParser extends PropertyTokenizer {
- private PropertyInfo propInfo; // Maker and propertyList related info
-
- static private final String RELUNIT = "em";
- static private final Numeric negOne = new Numeric(new Double(-1.0));
- static final private Hashtable functionTable = new Hashtable();
-
- static {
- // Initialize the Hashtable of XSL-defined functions
- functionTable.put("ceiling", new CeilingFunction());
- functionTable.put("floor", new FloorFunction());
- functionTable.put("round", new RoundFunction());
- functionTable.put("min", new MinFunction());
- functionTable.put("max", new MaxFunction());
- functionTable.put("abs", new AbsFunction());
- functionTable.put("rgb", new RGBColorFunction());
- functionTable.put("from-table-column", new FromTableColumnFunction());
- functionTable.put("inherited-property-value",
- new InheritedPropFunction());
- functionTable.put("from-parent", new FromParentFunction());
- functionTable.put("from-nearest-specified-value",
- new NearestSpecPropFunction());
- functionTable.put("proportional-column-width",
- new PPColWidthFunction());
- functionTable.put("label-end", new LabelEndFunction());
- functionTable.put("body-start", new BodyStartFunction());
- // NOTE: used from code generated for corresponding properties
- functionTable.put("_fop-property-value", new FopPropValFunction());
-
- /**
- * * NOT YET IMPLEMENTED!!!
- * functionTable.put("icc-color", new ICCcolorFunction());
- * functionTable.put("system-color", new SystemColorFunction());
- * functionTable.put("system-font", new SystemFontFunction());
- *
- * functionTable.put("merge-property-values", new MergePropsFunction());
- */
- }
-
/**
- * Public entrypoint to the Property expression parser.
- * @param expr The specified value (attribute on the xml element).
- * @param propInfo A PropertyInfo object representing the context in
- * which the property expression is to be evaluated.
- * @return A Property object holding the parsed result.
- * @throws PropertyException If the "expr" cannot be parsed as a Property.
+ * This is an attempt to ensure that the restriction on the application of
+ * from-parent() and from-nearest-specified-value() functions to shorthand
+ * properties (Section 5.10.4 Property Value Functions) is maintained.
+ * I.e. if a shorthand property is the subject of one of these functions,
+ * ist is valid only if "...the expression only consists of the
+ * [from-parent or from-nearest-specified-value] function with an argument
+ * matching the property being computed..."
*/
- public static Property parse(String expr, PropertyInfo propInfo)
- throws PropertyException {
- return new PropertyParser(expr, propInfo).parseProperty();
- }
-
-
+ //private int elementsSeen = 0;
/**
- * Private constructor. Called by the static parse() method.
- * @param propExpr The specified value (attribute on the xml element).
- * @param propInfo A PropertyInfo object representing the context in
- * which the property expression is to be evaluated.
+ * Used in conjunction with <i>elementsSeen</i>.
*/
- private PropertyParser(String propExpr, PropertyInfo pInfo) {
- super(propExpr);
- this.propInfo = pInfo;
+ //private String restrictedValueFunctSeen = null;
+
+ public PropertyParser() {
+ super();
}
/**
* Parse the property expression described in the instance variables.
+ * <p>
* Note: If the property expression String is empty, a StringProperty
* object holding an empty String is returned.
- * @return A Property object holding the parsed result.
- * @throws PropertyException If the "expr" cannot be parsed as a Property.
+ * @param property an <tt>int</tt> containing the property index.
+ * which the property expression is to be evaluated.
+ * @param expr The specified value (attribute on the xml element).
+ * @return A PropertyValue holding the parsed result.
+ * @throws PropertyException If the "expr" cannot be parsed as a
+ * PropertyValue.
*/
- private Property parseProperty() throws PropertyException {
- next();
- if (currentToken == TOK_EOF) {
- // if prop value is empty string, force to StringProperty
- return new StringProperty("");
+ public PropertyValue parse(int property, String expr)
+ throws PropertyException
+ {
+ synchronized (this) {
+ // make sure this parser is available
+ if (expr != null) // the parser is currently active
+ throw new PropertyException
+ ("PropertyParser is currently active.");
+ initialize(property, expr);
}
- ListProperty propList = null;
+
+ next();
+ if (currentToken == EOF)
+ // prop value is empty
+ throw new PropertyException
+ ("No token recognized in :" + expr + ":");
+
+ PropertyValueList propList = new PropertyValueList(property);
while (true) {
- Property prop = parseAdditiveExpr();
- if (currentToken == TOK_EOF) {
- if (propList != null) {
- propList.addProperty(prop);
+ PropertyValue prop = parseAdditiveExpr();
+ if (currentToken == EOF) {
+ // end of the expression - add to list and go
+ if (propList.size() != 0) {
+ propList.add(prop);
return propList;
- } else
+ } else { // list is empty
return prop;
- } else {
- if (propList == null) {
- propList = new ListProperty(prop);
- } else {
- propList.addProperty(prop);
}
}
- // throw new PropertyException("unexpected token");
+ // throw away commas separating arguments. These can occur
+ // in font-family and voice-family. Commas are regarded here
+ // as separators of list and sublist elements.
+ // See 7.16.5 "text-shadow" in the 1.0 Recommendation for an
+ // example of sublists.
+ if (currentToken == COMMA) {
+ next();
+ propList.add(prop);
+ } else { // whitespace separates list elements; make a sublist
+ propList.add(parseSublist(prop));
+ if (currentToken == EOF)
+ return propList;
+ }
+ }
+ }
+
+ /**
+ * <p>Parse a property values sublist - a list of whitespace separated
+ * <tt>PropertyValue</tt>s.
+ * </p><p>
+ * Property value expressions for various properties may contaiin lists
+ * of values, which may be separated by whitespace or by commas. See,
+ * e.g., 7.6.17 "voice-family" and 7.8.2 "font-family". The shorthands
+ * may also contain lists of elements, generally (or exclusively)
+ * whitespace separated. 7.16.5 "text-shadow" allows whitespace
+ * separated length doubles or triples to be specified for individual
+ * shadow effects, with multiple shadow effects, each separated by
+ * commmas.
+ * @param initialValue a <tt>PropertyValue</tt> to assign as the initial
+ * value of the sublist. The detection of this value, which is
+ * whitespace separated from a subsequent value, has been the
+ * trigger for the creation of the sublist.
+ * @return a <tt>PropertyValueList</tt> containing the sublist. The
+ * indicatior for the end of the sublist is the end of the expression,
+ * or a comma.
+ */
+ PropertyValueList parseSublist(PropertyValue initialValue)
+ throws PropertyException
+ {
+ PropertyValueList sublist = new PropertyValueList(property);
+ sublist.add(initialValue);
+ while (true) {
+ PropertyValue prop = parseAdditiveExpr();
+ if (currentToken == EOF) {
+ // end of the expression - add to sublist and go
+ sublist.add(prop);
+ return sublist;
+ }
+ // Comma separates next element - end of sublist
+ if (currentToken == COMMA) {
+ next();
+ sublist.add(prop);
+ return sublist;
+ } else { // whitespace separates next elements; add to sublist
+ sublist.add(prop);
+ }
+ }
+ }
+
+ /**
+ * Reset the parser by resetting the tokenizer to null (or equivalent)
+ * values.
+ */
+ public void resetParser() {
+ synchronized (this) {
+ //elementsSeen = 0;
+ //restrictedValueFunctSeen = null;
+ reset();
}
- // return prop;
}
/**
* Try to parse an addition or subtraction expression and return the
- * resulting Property.
+ * resulting PropertyValue.
*/
- private Property parseAdditiveExpr() throws PropertyException {
+ private PropertyValue parseAdditiveExpr() throws PropertyException {
// Evaluate and put result on the operand stack
- Property prop = parseMultiplicativeExpr();
+ PropertyValue prop = parseMultiplicativeExpr();
loop:
for (; ; ) {
switch (currentToken) {
- case TOK_PLUS:
+ case PLUS:
next();
- prop = evalAddition(prop.getNumeric(),
- parseMultiplicativeExpr().getNumeric());
+ ((Numeric)prop).add((Numeric)parseMultiplicativeExpr());
break;
- case TOK_MINUS:
+ case MINUS:
next();
- prop =
- evalSubtraction(prop.getNumeric(),
- parseMultiplicativeExpr().getNumeric());
+ ((Numeric)prop).subtract((Numeric)parseMultiplicativeExpr());
break;
default:
break loop;
/**
* Try to parse a multiply, divide or modulo expression and return
- * the resulting Property.
+ * the resulting PropertyValue.
*/
- private Property parseMultiplicativeExpr() throws PropertyException {
- Property prop = parseUnaryExpr();
+ private PropertyValue parseMultiplicativeExpr() throws PropertyException {
+ PropertyValue prop = parseUnaryExpr();
loop:
for (; ; ) {
switch (currentToken) {
- case TOK_DIV:
+ case DIV:
next();
- prop = evalDivide(prop.getNumeric(),
- parseUnaryExpr().getNumeric());
+ ((Numeric)prop).divide((Numeric)parseUnaryExpr());
break;
- case TOK_MOD:
+ case MOD:
next();
- prop = evalModulo(prop.getNumber(),
- parseUnaryExpr().getNumber());
+ ((Numeric)prop).mod((Numeric)parseUnaryExpr());
break;
- case TOK_MULTIPLY:
+ case MULTIPLY:
next();
- prop = evalMultiply(prop.getNumeric(),
- parseUnaryExpr().getNumeric());
+ ((Numeric)prop).multiply((Numeric)parseUnaryExpr());
break;
default:
break loop;
/**
* Try to parse a unary minus expression and return the
- * resulting Property.
+ * resulting PropertyValue.
*/
- private Property parseUnaryExpr() throws PropertyException {
- if (currentToken == TOK_MINUS) {
+ private PropertyValue parseUnaryExpr() throws PropertyException {
+ if (currentToken == MINUS) {
next();
- return evalNegate(parseUnaryExpr().getNumeric());
+ return ((Numeric)parseUnaryExpr()).negate();
}
return parsePrimaryExpr();
}
* and throws an exception if this isn't the case.
*/
private final void expectRpar() throws PropertyException {
- if (currentToken != TOK_RPAR)
+ if (currentToken != RPAR)
throw new PropertyException("expected )");
next();
}
/**
* Try to parse a primary expression and return the
- * resulting Property.
+ * resulting PropertyValue.
* A primary expression is either a parenthesized expression or an
- * expression representing a primitive Property datatype, such as a
+ * expression representing a primitive PropertyValue datatype, such as a
* string literal, an NCname, a number or a unit expression, or a
* function call expression.
*/
- private Property parsePrimaryExpr() throws PropertyException {
- Property prop;
+ private PropertyValue parsePrimaryExpr() throws PropertyException {
+ PropertyValue prop;
+ //if (restrictedValueFunctSeen != null)
+ //throw new PropertyException
+ //(restrictedValueFunctSeen
+ //+ " already seen with shorthand argument");
switch (currentToken) {
- case TOK_LPAR:
+ case LPAR:
next();
prop = parseAdditiveExpr();
expectRpar();
+ // Do this here, rather than breaking, because expectRpar()
+ // consumes the right parenthesis and calls next().
+ //elementsSeen++;
return prop;
- case TOK_LITERAL:
- prop = new StringProperty(currentTokenValue);
+ case LITERAL:
+ prop = new Literal(property, currentTokenValue);
break;
- case TOK_NCNAME:
+ case NCNAME:
// Interpret this in context of the property or do it later?
- prop = new NCnameProperty(currentTokenValue);
+ prop = new NCName(property, currentTokenValue);
break;
- case TOK_FLOAT:
- prop = new NumberProperty(new Double(currentTokenValue));
+ case FLOAT:
+ // Do I need to differentiate here between floats and integers?
+ prop = new Numeric
+ (property, (new Double(currentTokenValue)).doubleValue());
break;
- case TOK_INTEGER:
- prop = new NumberProperty(new Integer(currentTokenValue));
+ case INTEGER:
+ prop = IntegerType.makeInteger
+ (property, (new Long(currentTokenValue)).longValue());
break;
- case TOK_PERCENT:
+ case PERCENT:
/*
- * Get the length base value object from the Maker. If null, then
- * this property can't have % values. Treat it as a real number.
+ * Generate a Percentage object with the percentage number.
+ * The constructor converts this to a straight multiplicative
+ * factor by dividing by 100.
*/
- double pcval =
- new Double(currentTokenValue.substring(0, currentTokenValue.length() - 1)).doubleValue()
- / 100.0;
- // LengthBase lbase = this.propInfo.getPercentLengthBase();
- PercentBase pcBase = this.propInfo.getPercentBase();
- if (pcBase != null) {
- if (pcBase.getDimension() == 0) {
- prop = new NumberProperty(pcval * pcBase.getBaseValue());
- } else if (pcBase.getDimension() == 1) {
- prop = new LengthProperty(new PercentLength(pcval,
- pcBase));
- } else {
- throw new PropertyException("Illegal percent dimension value");
- }
- } else {
- // WARNING? Interpret as a decimal fraction, eg. 50% = .5
- prop = new NumberProperty(pcval);
- }
+ prop = Percentage.makePercentage
+ (property, (new Double(currentTokenValue)).doubleValue());
break;
- case TOK_NUMERIC:
- // A number plus a valid unit name.
- int numLen = currentTokenValue.length() - currentUnitLength;
- String unitPart = currentTokenValue.substring(numLen);
- Double numPart = new Double(currentTokenValue.substring(0,
- numLen));
- Length length = null;
- if (unitPart.equals(RELUNIT)) {
- length = new Length(numPart.doubleValue(),
- propInfo.currentFontSize());
- } else
- length = new Length(numPart.doubleValue(), unitPart);
- if (length == null) {
- throw new PropertyException("unrecognized unit name: "
- + currentTokenValue);
- } else
- prop = new LengthProperty(length);
+ case ABSOLUTE_LENGTH:
+ prop = Length.makeLength(property,
+ (new Double(currentTokenValue)).doubleValue(),
+ currentUnit);
+ break;
+ case TIME:
+ prop = Time.makeTime(property,
+ (new Double(currentTokenValue)).doubleValue(),
+ currentUnit);
+ break;
+ case FREQ:
+ prop = Frequency.makeFrequency(property,
+ (new Double(currentTokenValue)).doubleValue(),
+ currentUnit);
+ break;
+ case ANGLE:
+ prop = Angle.makeAngle(property,
+ (new Double(currentTokenValue)).doubleValue(),
+ currentUnit);
+ break;
+ case RELATIVE_LENGTH:
+ prop = Ems.makeEms(property,
+ (new Double(currentTokenValue)).doubleValue());
break;
- case TOK_COLORSPEC:
- prop = new ColorTypeProperty(new ColorType(currentTokenValue));
+ case COLORSPEC:
+ prop = new ColorType(property, currentTokenValue);
break;
- case TOK_FUNCTION_LPAR: {
- Function function =
- (Function)functionTable.get(currentTokenValue);
- if (function == null) {
+ case BOOL:
+ prop = new Bool(property, currentTokenValue);
+ break;
+
+ case AUTO:
+ prop = new Auto(property);
+ break;
+
+ case NONE:
+ prop = new None(property);
+ break;
+
+ case INHERIT:
+ throw new PropertyException("INHERIT not supported");
+ //break;
+
+ case URI:
+ prop = new UriType(property, currentTokenValue);
+ break;
+
+ case MIMETYPE:
+ prop = new MimeType(property, currentTokenValue);
+ break;
+
+ case FUNCTION_LPAR: {
+ // N.B. parseArgs() invokes expectRpar at the end of argument
+ // processing, so, like LPAR processing, next() is not called
+ // and the return from this method must be premature
+ prop = null;
+ // Numeric functions
+ if (currentTokenValue.equals("floor")) {
+ PropertyValue[] args = parseArgs(1);
+ prop = new Numeric
+ (property, ((Numeric)args[0]).floor());
+ }
+ else if (currentTokenValue.equals("ceiling")) {
+ PropertyValue[] args = parseArgs(1);
+ prop = new Numeric
+ (property, ((Numeric)args[0]).ceiling());
+ }
+ else if (currentTokenValue.equals("round")) {
+ PropertyValue[] args = parseArgs(1);
+ prop = new Numeric
+ (property, ((Numeric)args[0]).round());
+ }
+ else if (currentTokenValue.equals("min")) {
+ PropertyValue[] args = parseArgs(2);
+ prop = new Numeric
+ (property, ((Numeric)args[0]).min((Numeric)args[1]));
+ }
+ else if (currentTokenValue.equals("max")) {
+ PropertyValue[] args = parseArgs(2);
+ prop = new Numeric
+ (property, ((Numeric)args[0]).max((Numeric)args[1]));
+ }
+ else if (currentTokenValue.equals("abs")) {
+ PropertyValue[] args = parseArgs(1);
+ prop = new Numeric
+ (property, ((Numeric)args[0]).abs());
+ }
+
+ // Color functions
+ else if (currentTokenValue.equals("rgb")) {
+ PropertyValue[] args = parseArgs(3);
+ prop = new ColorType
+ (property, ((Numeric)args[0]).asInt(),
+ ((Numeric)args[1]).asInt(),
+ ((Numeric)args[2]).asInt());
+ }
+ else if (currentTokenValue.equals("rgb-icc")) {
+ PropertyValue[] args = parseArgs(6);
+ throw new PropertyException
+ ("rgb-icc function is not supported.");
+ }
+ else if (currentTokenValue.equals("system-color")) {
+ PropertyValue[] args = parseArgs(1);
+ prop = new ColorType
+ (property, ((StringType)args[0]).getString());
+ }
+
+ // Font function
+ else if (currentTokenValue.equals("system-font")) {
+ PropertyValue[] args = parseArgs(1, 2);
+ throw new PropertyException
+ ("system-font function is not supported.");
+ }
+
+ // Property value functions
+ else if (currentTokenValue.equals("inherited-property-value")) {
+ int propindex = property;
+ PropertyValue[] args = parseArgs(0, 1);
+ if (args.length != 0)
+ propindex = PropertyConsts.getPropertyIndex(
+ ((StringType)args[0]).getString());
+ if (PropertyConsts.inheritance(propindex) == Properties.NO)
+ throw new PropertyException
+ ("inherited-property-value: "
+ + PropNames.getPropertyName(propindex)
+ + " is not inherited.");
+ prop = new Inherit(property, propindex);
+ }
+ else if (currentTokenValue.equals("label-end")) {
+ PropertyValue[] args = parseArgs(0);
+ throw new PropertyException
+ ("label-end function is not supported.");
+ }
+ else if (currentTokenValue.equals("body-start")) {
+ PropertyValue[] args = parseArgs(0);
+ throw new PropertyException
+ ("body-start function is not supported.");
+ }
+ // N.B. see comments on classes FromNearestSpecified and
+ // FromParent for explanation of this section
+ else if (currentTokenValue.equals("from-parent") ||
+ currentTokenValue.equals("from-nearest-specified-value"))
+ {
+ // Preset the return value in case of a shorthand property
+ if (currentTokenValue.equals("from-parent"))
+ prop = new FromParent(property);
+ else
+ prop = new FromNearestSpecified(property);
+
+ PropertyValue[] args = parseArgs(0, 1);
+ if (args.length == 0) {
+ if (! PropertyConsts.isShorthand(property)) {
+ // develop the function value and return it as
+ // a property.
+ throw new PropertyException
+ (currentTokenValue +
+ " function is not supported.");
+ }
+ // else a shorthand - do nothing; prop has been set
+ // to the appropriate pseudo-propertyValue
+ } else { // one argument - it must be a property name
+ if ( ! (args[0] instanceof NCName))
+ throw new PropertyException
+ (currentTokenValue + " function requires"
+ + " property name arg.");
+ // else arg[0] is an NCName
+ NCName ncname = (NCName)args[0];
+ String propname = ncname.getNCName();
+ int nameindex = PropertyConsts.getPropertyIndex(propname);
+ if (PropertyConsts.isShorthand(nameindex)) {
+ // the argument is a shorthand property -
+ // it must be the same as the property being
+ // assigned to.
+ // see 5.10.4 Property Value Functions
+ if ( ! (nameindex == property))
+ throw new PropertyException
+ (currentTokenValue +
+ " argument " + propname +
+ " does not match property " +
+ PropNames.getPropertyName(property));
+ // else perform shorthand processing
+ // i.e. do nothing; prop has been set to the correct
+ // pseudo-propertyValue
+ }
+ else { // An NCName but not a shorthand
+ // Perform normal from-parent processing
+ throw new PropertyException
+ (currentTokenValue +
+ " function is not supported.");
+ }
+ }
+ }
+ else if (currentTokenValue.equals("from-table-column")) {
+ PropertyValue[] args = parseArgs(0, 1);
+ throw new PropertyException
+ ("from-table-column function is not supported.");
+ }
+ else if (currentTokenValue.equals("proportional-column-width")) {
+ PropertyValue[] args = parseArgs(1);
+ throw new PropertyException
+ ("proportional-column-width "
+ + "function is not supported.");
+ }
+ else if (currentTokenValue.equals("merge-property-values")) {
+ PropertyValue[] args = parseArgs(0, 1);
+ throw new PropertyException
+ ("merge-property-values function is not supported.");
+ }
+ else
throw new PropertyException("no such function: "
+ currentTokenValue);
- }
- next();
- // Push new function (for function context: getPercentBase())
- propInfo.pushFunction(function);
- prop = function.eval(parseArgs(function.nbArgs()), propInfo);
- propInfo.popFunction();
+ //elementsSeen++;
return prop;
}
default:
throw new PropertyException("syntax error");
}
next();
+ //elementsSeen++;
return prop;
}
* may itself be an expression. This method consumes the closing right
* parenthesis of the argument list.
* @param nbArgs The number of arguments expected by the function.
- * @return An array of Property objects representing the arguments
- * found.
- * @throws PropertyException If the number of arguments found isn't equal
- * to the number expected.
+ * @return <tt>PropertyValueList</tt> of <tt>PropertyValue</tt> objects
+ * representing the arguments found.
+ * @exception PropertyException
+ */
+ PropertyValue[] parseArgs(int nbArgs) throws PropertyException {
+ return parseArgs(nbArgs, nbArgs);
+ }
+
+ /**
+ * Parse a comma separated list of function arguments. Each argument
+ * may itself be an expression. This method consumes the closing right
+ * parenthesis of the argument list.
+ * @param minArgs The minimum number of arguments expected by the function.
+ * @param maxArgs The maximum number of arguments expected by the function.
+ * @return <tt>PropertyValueList</tt> of <tt>PropertyValue</tt> objects
+ * representing the arguments found. N.B. The actual number of arguments
+ * returned is guaranteed to be between minArgs and maxArgs, inclusive,
+ * but the actual list of args found is terminated by the end of the
+ * array, or the first null element.
+ * @exception PropertyException
*/
- Property[] parseArgs(int nbArgs) throws PropertyException {
- Property[] args = new Property[nbArgs];
- Property prop;
+ PropertyValue[] parseArgs(int minArgs, int maxArgs)
+ throws PropertyException
+ {
+ PropertyValue[] args = new PropertyValue[maxArgs];
+ PropertyValue prop;
int i = 0;
- if (currentToken == TOK_RPAR) {
+ if (currentToken == RPAR) {
// No args: func()
next();
} else {
while (true) {
-
prop = parseAdditiveExpr();
- if (i < nbArgs) {
+ if (i < maxArgs) {
args[i++] = prop;
}
// ignore extra args
- if (currentToken != TOK_COMMA)
+ if (currentToken != COMMA)
break;
next();
}
expectRpar();
}
- if (nbArgs != i) {
+ if (minArgs > i || i > maxArgs) {
throw new PropertyException("Wrong number of args for function");
}
return args;
}
-
- /**
- * Evaluate an addition operation. If either of the arguments is null,
- * this means that it wasn't convertible to a Numeric value.
- * @param op1 A Numeric object (Number or Length-type object)
- * @param op2 A Numeric object (Number or Length-type object)
- * @return A new NumericProperty object holding an object which represents
- * the sum of the two operands.
- * @throws PropertyException If either operand is null.
- */
- private Property evalAddition(Numeric op1,
- Numeric op2) throws PropertyException {
- if (op1 == null || op2 == null)
- throw new PropertyException("Non numeric operand in addition");
- return new NumericProperty(op1.add(op2));
- }
-
- /**
- * Evaluate a subtraction operation. If either of the arguments is null,
- * this means that it wasn't convertible to a Numeric value.
- * @param op1 A Numeric object (Number or Length-type object)
- * @param op2 A Numeric object (Number or Length-type object)
- * @return A new NumericProperty object holding an object which represents
- * the difference of the two operands.
- * @throws PropertyException If either operand is null.
- */
- private Property evalSubtraction(Numeric op1,
- Numeric op2) throws PropertyException {
- if (op1 == null || op2 == null)
- throw new PropertyException("Non numeric operand in subtraction");
- return new NumericProperty(op1.subtract(op2));
- }
-
- /**
- * Evaluate a unary minus operation. If the argument is null,
- * this means that it wasn't convertible to a Numeric value.
- * @param op A Numeric object (Number or Length-type object)
- * @return A new NumericProperty object holding an object which represents
- * the negative of the operand (multiplication by *1).
- * @throws PropertyException If the operand is null.
- */
- private Property evalNegate(Numeric op) throws PropertyException {
- if (op == null)
- throw new PropertyException("Non numeric operand to unary minus");
- return new NumericProperty(op.multiply(negOne));
- }
-
- /**
- * Evaluate a multiplication operation. If either of the arguments is null,
- * this means that it wasn't convertible to a Numeric value.
- * @param op1 A Numeric object (Number or Length-type object)
- * @param op2 A Numeric object (Number or Length-type object)
- * @return A new NumericProperty object holding an object which represents
- * the product of the two operands.
- * @throws PropertyException If either operand is null.
- */
- private Property evalMultiply(Numeric op1,
- Numeric op2) throws PropertyException {
- if (op1 == null || op2 == null)
- throw new PropertyException("Non numeric operand in multiplication");
- return new NumericProperty(op1.multiply(op2));
- }
-
-
- /**
- * Evaluate a division operation. If either of the arguments is null,
- * this means that it wasn't convertible to a Numeric value.
- * @param op1 A Numeric object (Number or Length-type object)
- * @param op2 A Numeric object (Number or Length-type object)
- * @return A new NumericProperty object holding an object which represents
- * op1 divided by op2.
- * @throws PropertyException If either operand is null.
- */
- private Property evalDivide(Numeric op1,
- Numeric op2) throws PropertyException {
- if (op1 == null || op2 == null)
- throw new PropertyException("Non numeric operand in division");
- return new NumericProperty(op1.divide(op2));
- }
-
- /**
- * Evaluate a modulo operation. If either of the arguments is null,
- * this means that it wasn't convertible to a Number value.
- * @param op1 A Number object
- * @param op2 A Number object
- * @return A new NumberProperty object holding an object which represents
- * op1 mod op2.
- * @throws PropertyException If either operand is null.
- */
- private Property evalModulo(Number op1,
- Number op2) throws PropertyException {
- if (op1 == null || op2 == null)
- throw new PropertyException("Non number operand to modulo");
- return new NumberProperty(op1.doubleValue() % op2.doubleValue());
- }
-
}
package org.apache.fop.fo.expr;
-
+import org.apache.fop.datatypes.Numeric;
+import org.apache.fop.datatypes.Length;
+import org.apache.fop.datatypes.Frequency;
+import org.apache.fop.datatypes.Time;
/**
* Class to tokenize XSL FO property expression.
*/
class PropertyTokenizer {
- static final int TOK_EOF = 0;
- static final int TOK_NCNAME = TOK_EOF + 1;
- static final int TOK_MULTIPLY = TOK_NCNAME + 1;
- static final int TOK_LPAR = TOK_MULTIPLY + 1;
- static final int TOK_RPAR = TOK_LPAR + 1;
- static final int TOK_LITERAL = TOK_RPAR + 1;
- static final int TOK_NUMBER = TOK_LITERAL + 1;
- static final int TOK_FUNCTION_LPAR = TOK_NUMBER + 1;
- static final int TOK_PLUS = TOK_FUNCTION_LPAR + 1;
- static final int TOK_MINUS = TOK_PLUS + 1;
- static final int TOK_MOD = TOK_MINUS + 1;
- static final int TOK_DIV = TOK_MOD + 1;
- static final int TOK_NUMERIC = TOK_DIV + 1;
- static final int TOK_COMMA = TOK_NUMERIC + 1;
- static final int TOK_PERCENT = TOK_COMMA + 1;
- static final int TOK_COLORSPEC = TOK_PERCENT + 1;
- static final int TOK_FLOAT = TOK_COLORSPEC + 1;
- static final int TOK_INTEGER = TOK_FLOAT + 1;
+ /*
+ * Maintain the numbering of this list in (X)Emacs by issuing
+ * a shell command on the region with replacement (M-1 M-|). Use
+ * the perl command:
+ * perl -p -e 'BEGIN{$n=0};$n++ if s/= [0-9]+/= $n/'
+ *
+ * in vi, set mark `a' at the last line and
+ * !'aperl... etc
+ */
+ static final int
+ EOF = 0
+ ,NCNAME = 1
+ ,MULTIPLY = 2
+ ,LPAR = 3
+ ,RPAR = 4
+ ,LITERAL = 5
+ ,FUNCTION_LPAR = 6
+ ,PLUS = 7
+ ,MINUS = 8
+ ,MOD = 9
+ ,DIV = 10
+ ,COMMA = 11
+ ,PERCENT = 12
+ ,COLORSPEC = 13
+ ,FLOAT = 14
+ ,INTEGER = 15
+ ,ABSOLUTE_LENGTH = 16
+ ,RELATIVE_LENGTH = 17
+ ,TIME = 18
+ ,FREQ = 19
+ ,ANGLE = 20
+ ,INHERIT = 21
+ ,AUTO = 22
+ ,NONE = 23
+ ,BOOL = 24
+ ,URI = 25
+ ,MIMETYPE = 26
+ // NO_UNIT is a transient token for internal use only. It is
+ // never set as the end result of parsing a token.
+ ,NO_UNIT = 27
+ //,NSPREFIX = 28
+ //,WHITESPACE = 29
+ ;
- int currentToken = TOK_EOF;
+ /*
+ * Absolute unit type constants
+ */
+ int currentToken = EOF;
String currentTokenValue = null;
protected int currentUnitLength = 0;
+ protected int currentUnit;
+ protected String uri;
private int currentTokenStartIndex = 0;
- private /* final */ String expr;
+ private String expr = null;
private int exprIndex = 0;
private int exprLength;
- private boolean recognizeOperator = false;
+ protected int property;
+ protected PropertyTokenizer() {}
/**
- * Construct a new PropertyTokenizer object to tokenize the passed
- * String.
- * @param s The Property expressio to tokenize.
+ * Initialize this tokenizer to tokenize the passed
+ * String as a value of the passed property.
+ * It is assumed that the subclass has made any necessary
+ * synchronization arrangements.
+ * @param property an <tt>int</tt> containing the property index.
+ * @param s The Property expression to tokenize.
*/
- PropertyTokenizer(String s) {
- this.expr = s;
- this.exprLength = s.length();
+ protected void initialize(int property, String s) {
+ expr = s;
+ exprLength = s.length();
+ this.property = property;
+ }
+
+ /**
+ * Reset the tokenizer to null (or equivalent) values.
+ * Synchronization is achieved in the subclass.
+ */
+ protected void reset() {
+ expr = null;
+ exprIndex = 0;
+ exprLength = 0;
+ currentToken = EOF;
+ currentTokenValue = null;
+ property = 0;
}
/**
* This sets the following package visible variables:
* currentToken An enumerated value identifying the recognized token
* currentTokenValue A String containing the token contents
- * currentUnitLength If currentToken = TOK_NUMERIC, the number of
- * characters in the unit name.
+ * currentUnit If currentToken = ABSOLUTE_LENGTH, TIME or FREQUENCY,
+ * an enumerated value identifying the unit.
* @throws PropertyException If un unrecognized token is encountered.
*/
void next() throws PropertyException {
currentTokenValue = null;
currentTokenStartIndex = exprIndex;
- boolean currentMaybeOperator = recognizeOperator;
boolean bSawDecimal;
- recognizeOperator = true;
for (; ; ) {
if (exprIndex >= exprLength) {
- currentToken = TOK_EOF;
+ currentToken = EOF;
return;
}
char c = expr.charAt(exprIndex++);
case '\t':
case '\r':
case '\n':
- currentTokenStartIndex = exprIndex;
+ // Whitespace characters are valid within strings.
+ // in font family names, sequences of whitespace are
+ // compressed into a single space. (Rec 7.8.2)
+ //scanWhitespace();
+ //currentToken = WHITESPACE;
+ //currentTokenValue = expr.substring(currentTokenStartIndex,
+ // exprIndex);
+ //return;
break;
case ',':
- recognizeOperator = false;
- currentToken = TOK_COMMA;
+ currentToken = COMMA;
return;
case '+':
- recognizeOperator = false;
- currentToken = TOK_PLUS;
+ currentToken = PLUS;
return;
case '-':
- recognizeOperator = false;
- currentToken = TOK_MINUS;
+ currentToken = MINUS;
return;
case '(':
- currentToken = TOK_LPAR;
- recognizeOperator = false;
+ currentToken = LPAR;
return;
case ')':
- currentToken = TOK_RPAR;
+ currentToken = RPAR;
return;
case '"':
case '\'':
}
currentTokenValue = expr.substring(currentTokenStartIndex
+ 1, exprIndex++);
- currentToken = TOK_LITERAL;
+ currentToken = LITERAL;
return;
case '*':
- /*
- * if (currentMaybeOperator) {
- * recognizeOperator = false;
- */
- currentToken = TOK_MULTIPLY;
- /*
- * }
- * else
- * throw new PropertyException("illegal operator *");
- */
+ currentToken = MULTIPLY;
return;
case '0':
case '1':
} else
bSawDecimal = false;
if (exprIndex < exprLength && expr.charAt(exprIndex) == '%') {
+ currentToken = PERCENT;
+ currentTokenValue = expr.substring(currentTokenStartIndex,
+ exprIndex);
exprIndex++;
- currentToken = TOK_PERCENT;
+ return;
} else {
// Check for possible unit name following number
- currentUnitLength = exprIndex;
- scanName();
- currentUnitLength = exprIndex - currentUnitLength;
- currentToken = (currentUnitLength > 0) ? TOK_NUMERIC
- : (bSawDecimal ? TOK_FLOAT : TOK_INTEGER);
+ currentToken = scanUnitName();
+ if (currentToken == NO_UNIT)
+ currentToken = bSawDecimal ? FLOAT : INTEGER;
}
currentTokenValue = expr.substring(currentTokenStartIndex,
exprIndex);
if (exprIndex < exprLength
&& expr.charAt(exprIndex) == '%') {
exprIndex++;
- currentToken = TOK_PERCENT;
+ currentToken = PERCENT;
} else {
// Check for possible unit name following number
- currentUnitLength = exprIndex;
- scanName();
- currentUnitLength = exprIndex - currentUnitLength;
- currentToken = (currentUnitLength > 0) ? TOK_NUMERIC
- : TOK_FLOAT;
+ currentToken = scanUnitName();
+ if (currentToken == NO_UNIT)
+ currentToken = FLOAT;
}
currentTokenValue = expr.substring(currentTokenStartIndex,
exprIndex);
case '#': // Start of color value
if (exprIndex < exprLength
&& isHexDigit(expr.charAt(exprIndex))) {
+ int len;
++exprIndex;
scanHexDigits();
- currentToken = TOK_COLORSPEC;
+ currentToken = COLORSPEC;
currentTokenValue = expr.substring(currentTokenStartIndex,
exprIndex);
// Probably should have some multiple of 3 for length!
- return;
- } else
+ len = exprIndex - currentTokenStartIndex;
+ if (len == 4 || len == 7) return;
+ throw new PropertyException("color not 3 or 6 hex digits");
+ } else {
throw new PropertyException("illegal character '#'");
+ }
default:
--exprIndex;
scanName();
if (exprIndex == currentTokenStartIndex)
- throw new PropertyException("illegal character");
+ // Not a name - must be a <string>
+ throw new PropertyException
+ ("illegal character '"
+ + expr.charAt(exprIndex) + "'");
currentTokenValue = expr.substring(currentTokenStartIndex,
- exprIndex);
- // if (currentMaybeOperator) {
+ exprIndex);
if (currentTokenValue.equals("mod")) {
- currentToken = TOK_MOD;
+ currentToken = MOD;
return;
- } else if (currentTokenValue.equals("div")) {
- currentToken = TOK_DIV;
+ }
+ if (currentTokenValue.equals("div")) {
+ currentToken = DIV;
return;
}
- /*
- * else
- * throw new PropertyException("unrecognized operator name");
- * recognizeOperator = false;
- * return;
- * }
- */
+ if (currentTokenValue.equals("inherit")) {
+ currentToken = INHERIT;
+ return;
+ }
+ if (currentTokenValue.equals("auto")) {
+ currentToken = AUTO;
+ return;
+ }
+ if (currentTokenValue.equals("none")) {
+ currentToken = NONE;
+ return;
+ }
+ if (currentTokenValue.equals("true")
+ || currentTokenValue.equals("false")) {
+ currentToken = BOOL;
+ return;
+ }
+ // Quick and dirty url "parsing". Assume that a
+ // URI-SPECIFICATION must be the only component of a
+ // property value expression
+ if (currentTokenValue.equals("url")
+ && expr.charAt(exprIndex) == '(') {
+ if (! scanUrl()) {
+ throw new PropertyException
+ ("Invalid url expression :" +
+ expr.substring(exprIndex));
+ }
+ currentToken = URI;
+ return;
+ }
+ if (currentTokenValue.equals("content-type")) {
+ // content-type attribute value. Must be followed
+ // by a mime type
+ if (expr.charAt(exprIndex) == ':') {
+ int mimeptr = ++exprIndex;
+ scanMimeType();
+ currentToken = MIMETYPE;
+ currentTokenValue =
+ expr.substring(mimeptr, exprIndex);
+ return;
+ }
+ // else it's just a name
+ }
+ if (currentTokenValue.equals("namespace-prefix")) {
+ // content-type attribute value. Must be followed
+ // by a declared namespace-prefix or null
+ if (expr.charAt(exprIndex) == ':') {
+ int nsptr = ++exprIndex;
+ scanName(); // Allowed to be empty
+ currentToken = NCNAME;
+ currentTokenValue =
+ expr.substring(nsptr, exprIndex);
+ return;
+ }
+ // else it's just a name
+ }
if (followingParen()) {
- currentToken = TOK_FUNCTION_LPAR;
- recognizeOperator = false;
+ currentToken = FUNCTION_LPAR;
} else {
- currentToken = TOK_NCNAME;
- recognizeOperator = false;
+ currentToken = NCNAME;
}
return;
}
}
}
+ /**
+ * Attempt to recognize a valid UnitName token in the input expression.
+ * @return token value appropriate to UnitName: ABSOLUTE_LENGTH,
+ * RELATIVE_LENGTH or NO_UNIT.
+ * @exception PropertyException if an NCName not a UnitName recognized.
+ */
+ private int scanUnitName() throws PropertyException {
+ String unit;
+ currentUnitLength = exprIndex;
+ scanName();
+ if ((currentUnitLength = exprIndex - currentUnitLength) > 0) {
+ unit = expr.substring(currentUnitLength, exprIndex);
+ if (unit.equals("em")) return RELATIVE_LENGTH;
+ if (unit.equals("cm")) {
+ currentUnit = Length.CM;
+ return ABSOLUTE_LENGTH;
+ }
+ if (unit.equals("mm")) {
+ currentUnit = Length.MM;
+ return ABSOLUTE_LENGTH;
+ }
+ if (unit.equals("in")) {
+ currentUnit = Length.IN;
+ return ABSOLUTE_LENGTH;
+ }
+ if (unit.equals("pt")) {
+ currentUnit = Length.PT;
+ return ABSOLUTE_LENGTH;
+ }
+ if (unit.equals("pc")) {
+ currentUnit = Length.PC;
+ return ABSOLUTE_LENGTH;
+ }
+ if (unit.equals("px")) {
+ currentUnit = Length.PX;
+ return ABSOLUTE_LENGTH;
+ }
+ if (unit.equals("s")) {
+ currentUnit = Time.SEC;
+ return TIME;
+ }
+ if (unit.equals("ms")) {
+ currentUnit = Time.MSEC;
+ return TIME;
+ }
+ if (unit.equals("Hz")) {
+ currentUnit = Frequency.HZ;
+ return FREQ;
+ }
+ if (unit.equals("kHz")) {
+ currentUnit = Frequency.KHZ;
+ return FREQ;
+ }
+ // Not a UnitName
+ throw new PropertyException
+ ("NCName following a number is not a UnitName");
+ } else { // No NCName found
+ return NO_UNIT;
+ }
+ }
+
/**
* Attempt to recognize a valid NAME token in the input expression.
*/
exprIndex++;
}
+ /**
+ * Scan to the end of a sequence of whitespace characters in the
+ * input expression.
+ */
+ private void scanWhitespace() {
+ while (exprIndex < exprLength && isSpace(expr.charAt(exprIndex)))
+ exprIndex++;
+ }
+
/**
* Attempt to recognize a valid sequence of hexadecimal digits in the
* input expression.
}
/**
- * Return a boolean value indicating whether the following non-whitespace
+ * Attempt to recognize a mime-type. Working definition here:
+ * NCName/NCName (NCName as recognized by scanName()).
+ */
+ private void scanMimeType() throws PropertyException {
+ int part1 = exprIndex;
+ scanName();
+ if (part1 != exprIndex) {
+ if (expr.charAt(exprIndex) == '/') {
+ int part2 = ++exprIndex;
+ scanName();
+ if (part2 != exprIndex)
+ return;
+ }
+ }
+ throw new PropertyException("Mime type expected; found:" +
+ expr.substring(part1));
+ }
+
+ /**
+ * @return a boolean value indicating whether the following non-whitespace
* character is an opening parenthesis.
*/
private boolean followingParen() {
return false;
}
+ /**
+ * Primitive URI extractor. Assumes that the only contents of a
+ * URI-SPECIFICATION property type is a complete uri-specification.
+ * No checking is done on the syntactical validity of the URI.
+ * @return a boolean indicating whether the remainder of the
+ * characters form the body of a <tt>url(...)</tt> specification.
+ * As a side-effect, sets the <tt>protected</tt> field <i>uri</i>
+ * and sets <i>exprIndex</i> past the end of the expression, when
+ * returning a <tt>true</tt> value.
+ */
+ private boolean scanUrl() {
+ char ch;
+ String str = expr.substring(exprIndex).trim();
+ if (str.charAt(str.length() - 1) != ')') return false;
+ // Remove closing parenthesis and trim
+ str = str.substring(0, str.length() - 1).trim();
+ if ((ch = str.charAt(0)) == '"' || ch == '\'') {
+ if (str.charAt(str.length() - 1) != ch) return false;
+ str = str.substring(1, str.length() - 1);
+ }
+ uri = str.trim();
+ exprIndex = expr.length();
+ return true;
+ }
static private final String nameStartChars =
"_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";