]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Initial version of experimental property expression parsing.
authorPeter Bernard West <pbwest@apache.org>
Tue, 7 May 2002 05:37:16 +0000 (05:37 +0000)
committerPeter Bernard West <pbwest@apache.org>
Tue, 7 May 2002 05:37:16 +0000 (05:37 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/FOP_0-20-0_Alt-Design@194777 13f79535-47bb-0310-9956-ffa450edef68

src/org/apache/fop/fo/expr/PropertyParser.java
src/org/apache/fop/fo/expr/PropertyTokenizer.java

index 2d5a89c61cdbe1a8549570e1620f6739f054616c..e4945c4fac3344c8418461974079c4c905c4e81e 100644 (file)
 
 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;
@@ -151,27 +206,24 @@ public class PropertyParser extends PropertyTokenizer {
 
     /**
      * 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;
@@ -182,12 +234,12 @@ public class PropertyParser extends PropertyTokenizer {
 
     /**
      * 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();
     }
@@ -198,111 +250,286 @@ public class PropertyParser extends PropertyTokenizer {
      * 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;
     }
 
@@ -311,132 +538,53 @@ public class PropertyParser extends PropertyTokenizer {
      * 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());
-    }
-
 }
index 025c686951988ad9dc72f347afa23dc13943c039..590b9b42ccb09e2e040914c8a3abde323dff74bb 100644 (file)
@@ -7,7 +7,10 @@
 
 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.
@@ -16,44 +19,92 @@ package org.apache.fop.fo.expr;
  */
 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;
     }
 
     /**
@@ -61,19 +112,17 @@ class PropertyTokenizer {
      * 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++);
@@ -82,26 +131,29 @@ class PropertyTokenizer {
             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 '\'':
@@ -112,19 +164,10 @@ class PropertyTokenizer {
                 }
                 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':
@@ -148,15 +191,16 @@ class PropertyTokenizer {
                 } 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);
@@ -170,14 +214,12 @@ class PropertyTokenizer {
                     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);
@@ -188,50 +230,165 @@ class PropertyTokenizer {
             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.
      */
@@ -250,6 +407,15 @@ class PropertyTokenizer {
             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.
@@ -260,7 +426,26 @@ class PropertyTokenizer {
     }
 
     /**
-     * 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() {
@@ -281,6 +466,30 @@ class PropertyTokenizer {
         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";