123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- /* $Id$ */
-
- package org.apache.fop.fo.expr;
-
- import java.util.HashMap;
- import java.util.List;
-
- import org.apache.xmlgraphics.util.UnitConv;
-
- import org.apache.fop.datatypes.Length;
- import org.apache.fop.datatypes.LengthBase;
- import org.apache.fop.datatypes.Numeric;
- import org.apache.fop.datatypes.PercentBase;
- import org.apache.fop.fo.properties.ColorProperty;
- import org.apache.fop.fo.properties.FixedLength;
- import org.apache.fop.fo.properties.ListProperty;
- import org.apache.fop.fo.properties.NumberProperty;
- import org.apache.fop.fo.properties.PercentLength;
- import org.apache.fop.fo.properties.Property;
- import org.apache.fop.fo.properties.StringProperty;
-
- /**
- * Class to parse XSL-FO property expressions.
- * This class is heavily based on the epxression parser in James Clark's
- * XT, an XSLT processor.
- */
- public final class PropertyParser extends PropertyTokenizer {
- private PropertyInfo propInfo; // Maker and propertyList related info
-
- private static final String RELUNIT = "em";
- private static final HashMap FUNCTION_TABLE = new HashMap();
-
- static {
- // Initialize the HashMap of XSL-defined functions
- FUNCTION_TABLE.put("ceiling", new CeilingFunction());
- FUNCTION_TABLE.put("floor", new FloorFunction());
- FUNCTION_TABLE.put("round", new RoundFunction());
- FUNCTION_TABLE.put("min", new MinFunction());
- FUNCTION_TABLE.put("max", new MaxFunction());
- FUNCTION_TABLE.put("abs", new AbsFunction());
- FUNCTION_TABLE.put("rgb", new RGBColorFunction());
- FUNCTION_TABLE.put("system-color", new SystemColorFunction());
- FUNCTION_TABLE.put("from-table-column", new FromTableColumnFunction());
- FUNCTION_TABLE.put("inherited-property-value", new InheritedPropFunction());
- FUNCTION_TABLE.put("from-nearest-specified-value", new FromNearestSpecifiedValueFunction());
- FUNCTION_TABLE.put("from-parent", new FromParentFunction());
- FUNCTION_TABLE.put("proportional-column-width", new ProportionalColumnWidthFunction());
- FUNCTION_TABLE.put("label-end", new LabelEndFunction());
- FUNCTION_TABLE.put("body-start", new BodyStartFunction());
- FUNCTION_TABLE.put("rgb-icc", new RGBICCColorFunction());
- FUNCTION_TABLE.put("rgb-named-color", new RGBNamedColorFunction()); //since XSL-FO 2.0
- FUNCTION_TABLE.put("cie-lab-color", new CIELabColorFunction()); //since XSL-FO 2.0
- FUNCTION_TABLE.put("cmyk", new CMYKColorFunction()); //non-standard!!!
-
- /**
- * * NOT YET IMPLEMENTED!!!
- * FUNCTION_TABLE.put("system-font", new SystemFontFunction());
- * FUNCTION_TABLE.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.
- */
- public static Property parse(String expr, PropertyInfo propInfo)
- throws PropertyException {
- try {
- return new PropertyParser(expr, propInfo).parseProperty();
- } catch (PropertyException exc) {
- exc.setPropertyInfo(propInfo);
- throw exc;
- }
- }
-
-
- /**
- * Private constructor. Called by the static parse() method.
- * @param propExpr The specified value (attribute on the xml element).
- * @param pInfo A PropertyInfo object representing the context in
- * which the property expression is to be evaluated.
- */
- private PropertyParser(String propExpr, PropertyInfo pInfo) {
- super(propExpr);
- this.propInfo = pInfo;
- }
-
- /**
- * Parse the property expression described in the instance variables.
- * 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.
- */
- private Property parseProperty() throws PropertyException {
- next();
- if (currentToken == TOK_EOF) {
- // if prop value is empty string, force to StringProperty
- return StringProperty.getInstance("");
- }
- ListProperty propList = null;
- while (true) {
- Property prop = parseAdditiveExpr();
- if (currentToken == TOK_EOF) {
- if (propList != null) {
- propList.addProperty(prop);
- return propList;
- } else {
- return prop;
- }
- } else {
- if (propList == null) {
- propList = new ListProperty(prop);
- } else {
- propList.addProperty(prop);
- }
- }
- // throw new PropertyException("unexpected token");
- }
- // return prop;
- }
-
- /**
- * Try to parse an addition or subtraction expression and return the
- * resulting Property.
- */
- private Property parseAdditiveExpr() throws PropertyException {
- // Evaluate and put result on the operand stack
- Property prop = parseMultiplicativeExpr();
- loop:
- while (true) {
- switch (currentToken) {
- case TOK_PLUS:
- next();
- prop = evalAddition(prop.getNumeric(),
- parseMultiplicativeExpr().getNumeric());
- break;
- case TOK_MINUS:
- next();
- prop = evalSubtraction(prop.getNumeric(),
- parseMultiplicativeExpr().getNumeric());
- break;
- default:
- break loop;
- }
- }
- return prop;
- }
-
- /**
- * Try to parse a multiply, divide or modulo expression and return
- * the resulting Property.
- */
- private Property parseMultiplicativeExpr() throws PropertyException {
- Property prop = parseUnaryExpr();
- loop:
- while (true) {
- switch (currentToken) {
- case TOK_DIV:
- next();
- prop = evalDivide(prop.getNumeric(),
- parseUnaryExpr().getNumeric());
- break;
- case TOK_MOD:
- next();
- prop = evalModulo(prop.getNumber(),
- parseUnaryExpr().getNumber());
- break;
- case TOK_MULTIPLY:
- next();
- prop = evalMultiply(prop.getNumeric(),
- parseUnaryExpr().getNumeric());
- break;
- default:
- break loop;
- }
- }
- return prop;
- }
-
- /**
- * Try to parse a unary minus expression and return the
- * resulting Property.
- */
- private Property parseUnaryExpr() throws PropertyException {
- if (currentToken == TOK_MINUS) {
- next();
- return evalNegate(parseUnaryExpr().getNumeric());
- }
- return parsePrimaryExpr();
- }
-
-
- /**
- * Checks that the current token is a right parenthesis
- * and throws an exception if this isn't the case.
- */
- private void expectRpar() throws PropertyException {
- if (currentToken != TOK_RPAR) {
- throw new PropertyException("expected )");
- }
- next();
- }
-
- /**
- * Try to parse a primary expression and return the
- * resulting Property.
- * A primary expression is either a parenthesized expression or an
- * expression representing a primitive Property 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;
- if (currentToken == TOK_COMMA) {
- //Simply skip commas, for example for font-family
- next();
- }
- switch (currentToken) {
- case TOK_LPAR:
- next();
- prop = parseAdditiveExpr();
- expectRpar();
- return prop;
-
- case TOK_LITERAL:
- prop = StringProperty.getInstance(currentTokenValue);
- break;
-
- case TOK_NCNAME:
- // Interpret this in context of the property or do it later?
- prop = new NCnameProperty(currentTokenValue);
- break;
-
- case TOK_FLOAT:
- prop = NumberProperty.getInstance(new Double(currentTokenValue));
- break;
-
- case TOK_INTEGER:
- prop = NumberProperty.getInstance(new Integer(currentTokenValue));
- break;
-
- case TOK_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.
- */
- double pcval = Double.parseDouble(
- currentTokenValue.substring(0, currentTokenValue.length() - 1)) / 100.0;
- PercentBase pcBase = this.propInfo.getPercentBase();
- if (pcBase != null) {
- if (pcBase.getDimension() == 0) {
- prop = NumberProperty.getInstance(pcval * pcBase.getBaseValue());
- } else if (pcBase.getDimension() == 1) {
- if (pcBase instanceof LengthBase) {
- if (pcval == 0.0) {
- prop = FixedLength.ZERO_FIXED_LENGTH;
- break;
- }
-
- //If the base of the percentage is known
- //and absolute, it can be resolved by the
- //parser
- Length base = ((LengthBase)pcBase).getBaseLength();
- if (base != null && base.isAbsolute()) {
- prop = FixedLength.getInstance(pcval * base.getValue());
- break;
- }
- }
- prop = new PercentLength(pcval, pcBase);
- } else {
- throw new PropertyException("Illegal percent dimension value");
- }
- } else {
- // WARNING? Interpret as a decimal fraction, eg. 50% = .5
- prop = NumberProperty.getInstance(pcval);
- }
- break;
-
- case TOK_NUMERIC:
- // A number plus a valid unit name.
- int numLen = currentTokenValue.length() - currentUnitLength;
- String unitPart = currentTokenValue.substring(numLen);
- double numPart = Double.parseDouble(currentTokenValue.substring(0, numLen));
- if (RELUNIT.equals(unitPart)) {
- prop = (Property) NumericOp.multiply(
- NumberProperty.getInstance(numPart),
- propInfo.currentFontSize());
- } else {
- if ("px".equals(unitPart)) {
- //pass the ratio between target-resolution and
- //the default resolution of 72dpi
- float resolution = propInfo.getPropertyList().getFObj()
- .getUserAgent().getSourceResolution();
- prop = FixedLength.getInstance(
- numPart, unitPart,
- UnitConv.IN2PT / resolution);
- } else {
- //use default resolution of 72dpi
- prop = FixedLength.getInstance(numPart, unitPart);
- }
- }
- break;
-
- case TOK_COLORSPEC:
- prop = ColorProperty.getInstance(propInfo.getUserAgent(), currentTokenValue);
- break;
-
- case TOK_FUNCTION_LPAR:
- Function function = (Function)FUNCTION_TABLE.get(currentTokenValue);
- if (function == null) {
- throw new PropertyException("no such function: "
- + currentTokenValue);
- }
- next();
- // Push new function (for function context: getPercentBase())
- propInfo.pushFunction(function);
- prop = function.eval(parseArgs(function), propInfo);
- propInfo.popFunction();
- return prop;
-
- default:
- // TODO: add the token or the expr to the error message.
- throw new PropertyException("syntax error");
- }
- next();
- return prop;
- }
-
- /**
- * 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 function The function object for which the arguments are collected.
- * @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 or if another argument parsing error occurs.
- */
- Property[] parseArgs(Function function) throws PropertyException {
- int numReq = function.getRequiredArgsCount(); // # required args
- int numOpt = function.getOptionalArgsCount(); // # optional args
- boolean hasVar = function.hasVariableArgs(); // has variable args
- List<Property> args = new java.util.ArrayList<Property>(numReq + numOpt);
- if (currentToken == TOK_RPAR) {
- // No args: func()
- next();
- } else {
- while (true) {
- Property p = parseAdditiveExpr();
- int i = args.size();
- if ( ( i < numReq ) || ( ( i - numReq ) < numOpt ) || hasVar ) {
- args.add ( p );
- } else {
- throw new PropertyException ( "Unexpected function argument at index " + i );
- }
- // ignore extra args
- if (currentToken != TOK_COMMA) {
- break;
- }
- next();
- }
- expectRpar();
- }
- int numArgs = args.size();
- if ( numArgs < numReq ) {
- throw new PropertyException("Expected " + numReq + " required arguments, but only " + numArgs + " specified");
- } else {
- for ( int i = 0; i < numOpt; i++ ) {
- if ( args.size() < ( numReq + i + 1 ) ) {
- args.add ( function.getOptionalArgDefault ( i, propInfo ) );
- }
- }
- }
- return (Property[]) args.toArray ( new Property [ args.size() ] );
- }
-
- /**
- * 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 (Property) NumericOp.addition(op1, 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 (Property) NumericOp.subtraction(op1, 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 (Property) NumericOp.negate(op);
- }
-
- /**
- * 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 (Property) NumericOp.multiply(op1, 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 (Property) NumericOp.divide(op1, 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 NumberProperty.getInstance(op1.doubleValue() % op2.doubleValue());
- }
-
- }
|