Package org.apache.fop.fo.expr

Source Code of org.apache.fop.fo.expr.PropertyParser

/*
* 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: PropertyParser.java 620272 2008-02-10 12:31:46Z adelmelle $ */

package org.apache.fop.fo.expr;

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;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

/**
* 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-parent", new FromParentFunction());
        FUNCTION_TABLE.put("from-nearest-specified-value",
                          new NearestSpecPropFunction());
        FUNCTION_TABLE.put("proportional-column-width",
                          new PPColWidthFunction());
        FUNCTION_TABLE.put("label-end", new LabelEndFunction());
        FUNCTION_TABLE.put("body-start", new BodyStartFunction());
        FUNCTION_TABLE.put("rgb-icc", new ICCColorFunction());
        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 propInfo 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 source-resolution and
                    //the default resolution of 72dpi
                    prop = FixedLength.getInstance(
                            numPart, unitPart,
                            propInfo.getPropertyList().getFObj()
                                    .getUserAgent().getSourceResolution() / 72.0f);
                } 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);
            if (function.nbArgs() < 0) {
                // Negative nbArgs --> function with variable number of arguments
                prop = function.eval(parseVarArgs(function), propInfo);
            } else {
                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.
     */
    Property[] parseArgs(Function function) throws PropertyException {
        int nbArgs = function.nbArgs();
        Property[] args = new Property[nbArgs];
        Property prop;
        int i = 0;
        if (currentToken == TOK_RPAR) {
            // No args: func()
            next();
        } else {
            while (true) {

                prop = parseAdditiveExpr();
                if (i < nbArgs) {
                    args[i++] = prop;
                }
                // ignore extra args
                if (currentToken != TOK_COMMA) {
                    break;
                }
                next();
            }
            expectRpar();
        }
        if (i == nbArgs - 1 && function.padArgsWithPropertyName()) {
            args[i++] = StringProperty.getInstance(
                            propInfo.getPropertyMaker().getName());
        }
        if (nbArgs != i) {
            throw new PropertyException("Expected " + nbArgs
                                        + ", but got " + i + " args for function");
        }
        return args;
    }
   
    /**
     *
     * 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.
     *
     * The method differs from parseArgs in that it accepts a variable
     * number of arguments.
     *
     * @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.
     *
     * TODO Merge this with parseArgs?
     */
    Property[] parseVarArgs(Function function) throws PropertyException {
        // For variable argument functions the minimum number of arguments is returned as a
        // negative integer from the nbArgs method
        int nbArgs = -function.nbArgs();
        List args = new LinkedList();
        Property prop;
        if (currentToken == TOK_RPAR) {
            // No args: func()
            next();
        } else {
            while (true) {
                prop = parseAdditiveExpr();
                args.add(prop);
                // ignore extra args
                if (currentToken != TOK_COMMA) {
                    break;
                }
                next();
            }
            expectRpar();
        }
        if (nbArgs > args.size()) {
            throw new PropertyException("Expected at least " + nbArgs
                                        + ", but got " + args.size() + " args for function");
        }
        Property[] propArray = new Property[args.size()];
        args.toArray(propArray);
        return propArray;
    }


    /**
     * 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());
    }

}
TOP

Related Classes of org.apache.fop.fo.expr.PropertyParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.