Index: lib/OpenLayers.js
===================================================================
--- lib/OpenLayers.js	(revision 9361)
+++ lib/OpenLayers.js	(working copy)
@@ -203,6 +203,12 @@
             "OpenLayers/Layer/GML.js",
             "OpenLayers/Style.js",
             "OpenLayers/StyleMap.js",
+            "OpenLayers/Expression.js",
+            "OpenLayers/Expression/Binary.js",
+            "OpenLayers/Expression/PropertyName.js",
+            "OpenLayers/Expression/Literal.js",
+            "OpenLayers/Expression/Function.js",
+            "OpenLayers/ParameterValue.js",
             "OpenLayers/Rule.js",
             "OpenLayers/Filter.js",
             "OpenLayers/Filter/FeatureId.js",
@@ -230,6 +236,7 @@
             "OpenLayers/Format/WKT.js",
             "OpenLayers/Format/OSM.js",
             "OpenLayers/Format/GPX.js",
+            "OpenLayers/Format/Expression.js",
             "OpenLayers/Format/Filter.js",
             "OpenLayers/Format/Filter/v1.js",
             "OpenLayers/Format/Filter/v1_0_0.js",
Index: lib/OpenLayers/Expression.js
===================================================================
--- lib/OpenLayers/Expression.js	(revision 0)
+++ lib/OpenLayers/Expression.js	(revision 0)
@@ -0,0 +1,68 @@
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Class: OpenLayers.Expression
+ * A base class for all OGC expressions.  This provides basic definitions for
+ * dynamic compiliation and evaluation.
+ */
+OpenLayers.Expression = OpenLayers.Class({
+    
+    /**
+     * APIMethod: evaluate
+     * Evaluates this expression tree against a context and returns the result.
+     * 
+     * Parameters:
+     * ctx - {Object} The context.  A hash.  Usually {<OpenLayers.Feature.attributes>}.
+     * 
+     * Returns:
+     * {Object || String || Number} The result of evaluation.
+     */
+    evaluate : function(ctx, feature) {
+         throw "Abstract OpenLayers.Expression.evaluate called.";
+    },
+    
+    /**
+     * APIMethod: recompile
+     * Replace this object's evaluate method with a dynamically-compiled one.
+     * If any part of the tree is changed, the dynamically-compiled function
+     * will become stale--use this function to bring it back up to date.
+     * 
+     * The dynamically-compiled function does not depend on the dynamically-
+     * compiled functions in the subtree.
+     */
+    preCompile : function() {
+        this.evaluate = this.compileFunction();
+    },
+
+    /**
+     * APIMethod: compileFunction
+     * Compiles this expression tree (or subtree) into a JavaScript function.
+     * 
+     * Returns:
+     * {Function} A JavaScript function representing this expression.
+     */
+    compileFunction : function() {
+        var func;
+        eval("func=function(x,f){return " + this.compile() + ";};");
+        return func;
+    },
+    
+    /**
+     * Method: compile
+     * Create a JavaScript expression that computes the same result as this
+     * tree.  This method should be overridden by subclasses.  Expressions
+     * will have a context (bound to 'x' at runtime) and an OpenLayers.Feature
+     * (bound to 'f') available for evaluation.
+     * 
+     * Returns:
+     * {String} A string representing a JavaScript expression that computes
+     * the same result as this expression tree.
+     */
+    compile : function() {
+        throw "Abstract " + this.CLASS_NAME + ".compile called.";
+    },
+    
+    CLASS_NAME : "OpenLayers.Expression"
+});
\ No newline at end of file
Index: lib/OpenLayers/Expression/Binary.js
===================================================================
--- lib/OpenLayers/Expression/Binary.js	(revision 0)
+++ lib/OpenLayers/Expression/Binary.js	(revision 0)
@@ -0,0 +1,89 @@
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Class: OpenLayers.Expression.Binary
+ * A class that holds and computes infix expression (+, -, etc.).
+ */
+OpenLayers.Expression.Binary =
+    OpenLayers.Class(OpenLayers.Expression, {
+    
+    /**
+     * APIProperty: op
+     * {String} The operation to perform.  Use one of "+-/*" or see the contants
+     * {<OpenLayers.Expression.Binary.ADD>}
+     * {<OpenLayers.Expression.Binary.SUB>}
+     * {<OpenLayers.Expression.Binary.MUL>}
+     * {<OpenLayers.Expression.Binary.DIV>}
+     */
+    op : null,
+    
+    /**
+     * APIProperty: lhs
+     * {<OpenLayers.Expression>} The left-hand side of the infix expression.
+     */
+    lhs : null,
+    
+    /**
+     * APIProperty: rhs
+     * {<OpenLayers.Expression>} The right-hand side of the infix expression.
+     */
+    rhs : null,
+    
+    /**
+     * Contructor: OpenLayers.Expression.Binary
+     * Create a new infix expression.
+     * 
+     * Parameters:
+     * op - {String} The operation to perform.
+     * lhs - {<OpenLayers.Expression>} The left-hand side of the infix expression.
+     * rhs - {<OpenLayers.Expression>} The right-hand side of the infix 
+     *       expression.
+     */
+    initialize : function(op, lhs, rhs) {
+        this.op = op;
+        this.lhs = lhs;
+        this.rhs = rhs;
+    },
+    
+    /**
+     * APIMethod: evaluate
+     * See {<OpenLayers.Expression.Evaluate>}.
+     */
+    evaluate : function(context, feature) {
+        var lhs = this.lhs.evaluate(context, feature);
+        var rhs = this.rhs.evaluate(context, feature);
+        switch(this.op) {
+            case '+':
+                return lhs + rhs;
+            case '-':
+                return lhs - rhs;
+            case '*':
+                return lhs * rhs;
+            case '/':
+                return lhs / rhs;
+        }
+        
+        throw "Unexpected operator " + this.op;
+    },
+    
+    /**
+     * Method: compile
+     * Overridden from {<OpenLayers.Expression.compile>}.
+     */
+    compile : function() {
+        return "(" + this.lhs.compile() + ")" + this.op +
+               "(" + this.rhs.compile() + ")";
+    },
+    
+    CLASS_NAME : "OpenLayers.Expression.Binary"
+});
+
+/*
+ * Constants representing the possible binary operations.
+ */
+OpenLayers.Expression.Binary.ADD = "+";
+OpenLayers.Expression.Binary.SUB = "-";
+OpenLayers.Expression.Binary.MUL = "*";
+OpenLayers.Expression.Binary.DIV = "/";
\ No newline at end of file
Index: lib/OpenLayers/Expression/Function.js
===================================================================
--- lib/OpenLayers/Expression/Function.js	(revision 0)
+++ lib/OpenLayers/Expression/Function.js	(revision 0)
@@ -0,0 +1,116 @@
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Property: OpenLayers.Expression.FUNCTIONS
+ * A map of function names to arrays.
+ * 
+ * Arrays have the format: [number of arguments, name of native function]
+ * 
+ * If number of arguments is negative, it is considered infinite or unchecked.
+ */
+
+OpenLayers.Expression.FUNCTIONS = {
+    sin : [1, 'Math.sin'],
+    cos : [1, 'Math.cos'],
+    tan : [1, 'Math.tan'],
+    sqrt : [1, 'Math.sqrt'],
+    min : [-1, 'Math.min'],
+    max : [-1, 'Math.max'],
+    pow : [2, 'Math.pow']
+};
+
+/**
+ * Class: OpenLayers.Expression.Function
+ * An expression representing a function invocation.  Arity is not checked by
+ * a Format, it is checked here during construction.  Functions are implemented
+ * by global objects like Math.
+ */
+OpenLayers.Expression.Function = OpenLayers.Class(OpenLayers.Expression, {
+    /**
+     * APIProperty: name
+     * The common name of the function this expression should invoke.  This is
+     * an external identifier--internally, the function is implemented by some
+     * library.
+     */
+    name : null,
+    
+    /**
+     * APIProperty: args
+     * {Array} of {OpenLayers.Expression}.  Represents the tuple used to invoke
+     * the function.
+     */
+    args : null,
+    
+    /**
+     * Property: func
+     * The function to call.
+     */
+    
+    /**
+     * Constructor: OpenLayers.Expression.Function
+     * Creates a new instance with the given name and arguments.  The
+     * cardinality of the arguments is checked against the arity of the function
+     * (variadic functions are unchecked).
+     * 
+     * Parameters:
+     * name - {String} The common name of the function to invoke (see
+     *        {<OpenLayers.Expression.FUNCTIONS>}
+     * args - {Array} of {OpenLayers.Expression}.  The tuple to pass into the
+     *        function.  Checked for arity.
+     */
+    initialize : function(name, args) {
+        if(!(args instanceof Array))
+            args = [args];
+        
+        var funcinfo = OpenLayers.Expression.FUNCTIONS[name];
+        
+        if(funcinfo == null) {
+            throw "Undefined function " + name;
+        }
+        
+        if(funcinfo[0] >= 0 && funcinfo[0] != args.length) {
+            throw "Argument-length mismatch: " + args.length
+                  + " instead of " + funcinfo[0];
+        }
+        
+        this.name = name;
+        this.args = args.splice(0);
+        this.func = eval(funcinfo[1]);
+    },
+    
+    /**
+    * APIMethod: evaluate
+    * See {<OpenLayers.Expression.evaluate>}.
+    */
+    evaluate : function(context, feature) {
+        var res = new Array(this.args.length);
+        for(var i = 0, len = this.args.length; i < len; ++i) {
+            res[i] = this.args[i].evaluate(context, feature);
+        }
+        return this.func.apply(null, res);
+    },
+    
+    /**
+     * Method: compile
+     * See {<OpenLayers.Expression.compile>}.
+     */
+    compile : function() {
+        var output = OpenLayers.Expression.FUNCTIONS[this.name][1] + "(";
+        
+        if(this.args.length > 0) {
+            output += this.args[0].compile();
+        
+            for(var i = 1; i < this.args.length; ++ i) {
+                output += "," + this.args[i].compile();
+            }
+        }
+        
+        output += ")";
+        
+        return output;
+    },
+    
+    CLASS_NAME : "OpenLayers.Expression.Function"
+});
Index: lib/OpenLayers/Expression/Literal.js
===================================================================
--- lib/OpenLayers/Expression/Literal.js	(revision 0)
+++ lib/OpenLayers/Expression/Literal.js	(revision 0)
@@ -0,0 +1,52 @@
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Class: OpenLayers.Expresion.Literal
+ * Represents a literal value in an expression.
+ */
+OpenLayers.Expression.Literal = OpenLayers.Class(OpenLayers.Expression, {
+    /**
+     * APIProperty: value
+     * The value bound to this literal.  Can be a {String}, {Number}, or
+     * anything else.
+     */
+    value : null,
+    
+    /**
+     * Constructor: OpenLayers.Expression.Literal
+     * Constructs a new Literal bound to the given value.  Tries to cast a
+     * string to an integer if possible.
+     * 
+     * Properties:
+     * value - The value to bind to.
+     */
+    initialize : function(value) {
+        // Convert to number if possible
+        var num = Number(value);
+        this.value = isNaN(num) ? value : num; 
+    },
+
+    /**
+    * APIMethod: evaluate
+    * See {<OpenLayers.Expression.evaluate>}.
+    */
+    evaluate : function(context, feature) {
+        return this.value;
+    },
+    
+    /**
+    * Method: compile
+    * See {<OpenLayers.Expression.compile>}.
+    */
+    compile : function() {
+        if(typeof this.value == "string") {
+            return "'" + OpenLayers.Util.escapeJSString(this.value) + "'"; 
+        }
+        
+        return this.value.toString();
+    },
+    
+    CLASS_NAME : "OpenLayers.Expression.Literal"
+});
Index: lib/OpenLayers/Expression/PropertyName.js
===================================================================
--- lib/OpenLayers/Expression/PropertyName.js	(revision 0)
+++ lib/OpenLayers/Expression/PropertyName.js	(revision 0)
@@ -0,0 +1,50 @@
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Class: OpenLayers.Expression.PropertyName
+ * Represents a functor that extracts some property from an expression context
+ * during evaluation.
+ */
+OpenLayers.Expression.PropertyName = OpenLayers.Class(OpenLayers.Expression, {
+    /**
+     * APIProperty: name
+     * The key of the value to extract from the expression context during
+     * evaluation.
+     */
+    name : null,
+    
+    /**
+     * Constructor: OpenLayers.Expression.PropertyName
+     * Creates a new instance bound to the given name.
+     * 
+     * Properties:
+     * name - {String} The name to bind to.
+     */
+    initialize : function(name) {
+        this.name = name;
+    },
+    
+    /**
+    * APIMethod: evaluate
+    * See {<OpenLayers.Expression.evaluate>}.
+    */
+    evaluate : function(context, feature) {
+        return typeof context[this.name] == 'function' ?
+               context[this.name](feature) :
+               context[this.name];
+    },
+    
+    /**
+    * Method: compile
+    * See {<OpenLayers.Expression.compile>}.
+    */
+    compile : function() {
+        return "typeof x['" + OpenLayers.Util.escapeJSString(this.name) + "']=='function'" +
+               "?x['" + OpenLayers.Util.escapeJSString(this.name) + "'](f)" +
+               ":x['" + OpenLayers.Util.escapeJSString(this.name) + "']";
+    },
+    
+    CLASS_NAME : "OpenLayers.Expression.PropertyName"
+});
Index: lib/OpenLayers/Filter.js
===================================================================
--- lib/OpenLayers/Filter.js	(revision 9361)
+++ lib/OpenLayers/Filter.js	(working copy)
@@ -29,7 +29,7 @@
         OpenLayers.Util.extend(this, options);
     },
 
-    /** 
+    /**
      * APIMethod: destroy
      * Remove reference to anything added.
      */
@@ -62,5 +62,72 @@
         return null;
     },
     
+    /**
+     * APIMethod: preCompile
+     * Turns this object's evaluate method into a JavaScript function that
+     * evaluates the entire filter tree.  Call this when the filter is to be
+     * evaluated many times and quickly.
+     */
+    preCompile: function() {
+        this.evaluate = this.compileFunction();
+    },
+    
+    /**
+     * Method: compileFunction
+     * Turns this filter into an equivalent JavaScript function.
+     *
+     * Returns:
+     * A function (a -> Boolean) representing the evaulation of the filter.
+     */
+    compileFunction: function() {
+        OpenLayers.Filter.Enclosure=[];
+        var e = OpenLayers.Filter.Enclosure;
+        var f;
+        eval("f=function(x){return " + this.compile() + ";}");
+        OpenLayers.Filter.Enclosure=null;
+        return f;
+    },
+    
+    /**
+     * Method: compile
+     * Return the JavaScript expression responsible for evaluating this sub
+     * filter.  When the browser finally executes this expression, a context
+     * object will be bound to the name 'x' in function scope.  If complex
+     * objects need to be stored (e.g. geometries), an enclosed array is bound
+     * to the name 'e' during execution and to 'OpenLayers.Filter.Enclosure'
+     * during compilation.  
+     * 
+     * Returns:
+     * {String} A JavaScript expression.
+     */
+    compile: function() {
+        throw "Abstract OpenLayers.Filter.Compile called";
+    },
+    
     CLASS_NAME: "OpenLayers.Filter"
 });
+
+/**
+ * Property: Enclosure
+ * {Array}
+ * Filter compilation enclosure.
+ */
+OpenLayers.Filter.Enclosure = null;
+
+/**
+ * Function: BetweenHelper
+ * Helper function for filter compilation.
+ */
+OpenLayers.Filter.BetweenHelper = function(val, lower, higher) {
+    return val >= lower && val <=  higher;
+};
+
+/**
+ * Function: EqualsIgnoreCaseHelper
+ * Helper function for collating filters.
+ */
+OpenLayers.Filter.EqualsIgnoreCaseHelper = function(lhs, rhs) {
+    if(typeof lhs != "string" || typeof rhs != "string")
+        return lhs == rhs;
+    return lhs.toUpperCase()==rhs.toUpperCase();
+};
Index: lib/OpenLayers/Filter/Comparison.js
===================================================================
--- lib/OpenLayers/Filter/Comparison.js	(revision 9361)
+++ lib/OpenLayers/Filter/Comparison.js	(working copy)
@@ -31,20 +31,18 @@
     type: null,
     
     /**
-     * APIProperty: property
-     * {String}
-     * name of the context property to compare
+     * APIProperty: lhs
+     * {<OpenLayers.Expression.AbstractExpression>}
+     * The left-hand side of the comparison.
      */
-    property: null,
+    lhs: null,
     
     /**
-     * APIProperty: value
-     * {Number} or {String}
-     * comparison value for binary comparisons. In the case of a String, this
-     * can be a combination of text and propertyNames in the form
-     * "literal ${propertyName}"
+     * APIProperty: rhs
+     * {<OpenLayers.Expression.AbstractExpression>}
+     * The right-hand side of the comparison.
      */
-    value: null,
+    rhs: null,
     
     /**
      * Property: matchCase
@@ -60,21 +58,38 @@
     
     /**
      * APIProperty: lowerBoundary
-     * {Number} or {String}
-     * lower boundary for between comparisons. In the case of a String, this
-     * can be a combination of text and propertyNames in the form
-     * "literal ${propertyName}"
+     * {<OpenLayers.Expression.Literal>}
+     * lower boundary for between comparisons.
      */
     lowerBoundary: null,
     
     /**
      * APIProperty: upperBoundary
-     * {Number} or {String}
-     * upper boundary for between comparisons. In the case of a String, this
-     * can be a combination of text and propertyNames in the form
-     * "literal ${propertyName}"
+     * {<OpenLayers.Expression.Literal>}
+     * upper boundary for between comparisons.
      */
     upperBoundary: null,
+    
+    /**
+     * APIProperty: wildCard
+     * {string}
+     * Multi-character wildcard character as defined in OGC Filter spec.
+     */
+    wildCard: "*",
+    
+    /**
+     * APIProperty: singleChar
+     * {string}
+     * Single character wildcard character as defined in OGC Filter spec.
+     */
+    singleChar: ".",
+    
+    /**
+     * APIProperty: escapeChar
+     * {string}
+     * Escape character as defined in OGC Filter spec.
+     */
+    escapeChar: "!",
 
     /** 
      * Constructor: OpenLayers.Filter.Comparison
@@ -89,8 +104,27 @@
      */
     initialize: function(options) {
         OpenLayers.Filter.prototype.initialize.apply(this, [options]);
-    },
+        
+        // Read backwards-compatibility settings
+        if('property' in this) {
+            this.lhs = new OpenLayers.Expression.PropertyName(this.property);
+        }
+        
+        if('value' in this) {
+            this.rhs = new OpenLayers.Expression.Literal(this.value);
+        }
+        
+        if(typeof this.lowerBoundary != "undefined"
+            && !(this.lowerBoundary instanceof OpenLayers.Expression.Literal)) {
+            this.lowerBoundary = new OpenLayers.Expression.Literal(this.lowerBoundary);
+        }
 
+        if(typeof this.upperBoundary != "undefined"
+            && !(this.upperBoundary instanceof OpenLayers.Expression.Literal)) {
+            this.upperBoundary = new OpenLayers.Expression.Literal(this.upperBoundary);
+        }
+     },
+
     /**
      * APIMethod: evaluate
      * Evaluates this filter in a specific context.  Should be implemented by
@@ -102,54 +136,117 @@
      * Returns:
      * {Boolean} The filter applies.
      */
-    evaluate: function(context) {
+    evaluate: function(context, feature) {
         var result = false;
+        var lhs, rhs;
+        
+        lhs = this.lhs.evaluate(context, feature);
+
+        if(this.type!=OpenLayers.Filter.Comparison.BETWEEN) {
+            rhs = this.rhs.evaluate(context, feature);
+        }
+            
         switch(this.type) {
             case OpenLayers.Filter.Comparison.EQUAL_TO:
-                var got = context[this.property];
-                var exp = this.value;
                 if(!this.matchCase &&
-                   typeof got == "string" && typeof exp == "string") {
-                    result = (got.toUpperCase() == exp.toUpperCase());
+                   typeof lhs == "string" && typeof rhs == "string") {
+                    result = (lhs.toUpperCase() == rhs.toUpperCase());
                 } else {
-                    result = (got == exp);
+                    result = (lhs == rhs);
                 }
                 break;
             case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:
-                var got = context[this.property];
-                var exp = this.value;
                 if(!this.matchCase &&
-                   typeof got == "string" && typeof exp == "string") {
-                    result = (got.toUpperCase() != exp.toUpperCase());
+                   typeof lhs == "string" && typeof rhs == "string") {
+                    result = (lhs.toUpperCase() != rhs.toUpperCase());
                 } else {
-                    result = (got != exp);
+                    result = (lhs != rhs);
                 }
                 break;
             case OpenLayers.Filter.Comparison.LESS_THAN:
-                result = context[this.property] < this.value;
+                result = lhs < rhs;
                 break;
             case OpenLayers.Filter.Comparison.GREATER_THAN:
-                result = context[this.property] > this.value;
+                result = lhs > rhs;
                 break;
             case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:
-                result = context[this.property] <= this.value;
+                result = lhs <= rhs;
                 break;
             case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:
-                result = context[this.property] >= this.value;
+                result = lhs >= rhs;
                 break;
             case OpenLayers.Filter.Comparison.BETWEEN:
-                result = (context[this.property] >= this.lowerBoundary) &&
-                    (context[this.property] <= this.upperBoundary);
+                result = lhs >= this.lowerBoundary.evaluate(context, feature) &&
+                         lhs <= this.upperBoundary.evaluate(context, feature);
                 break;
             case OpenLayers.Filter.Comparison.LIKE:
-                var regexp = new RegExp(this.value, "gi");
-                result = regexp.test(context[this.property]);
+                if(!this.regexp) {
+                    this.regexp = new RegExp(
+                            this.value2regex(this.wildCard, this.singleChar, this.escapeChar, rhs), "gi");
+                }
+                
+                result = this.regexp.test(lhs);
                 break;
         }
         return result;
     },
     
     /**
+     * Method: compile
+     * See {<OpenLayers.Filter.compile>}.
+     */
+    compile : function() {
+        var output;
+        
+        switch(this.type) {
+        case OpenLayers.Filter.Comparison.EQUAL_TO:
+            if(this.matchCase) {
+                output = this.lhs.compile() + "==" + this.rhs.compile();
+                break;
+            }
+            
+            output = "OpenLayers.Filter.EqualsIgnoreCaseHelper(" + this.lhs.compile() + "," + this.rhs.compile() + ")";
+            break;
+        case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:
+            if(this.matchCase) {
+                output = this.lhs.compile() + "!=" + this.rhs.compile();
+                break;
+            }
+            
+            output = "!OpenLayers.Filter.EqualsIgnoreCaseHelper(" + this.lhs.compile() + "," + this.rhs.compile() + ")";
+            break;
+        case OpenLayers.Filter.Comparison.LESS_THAN:
+            output = this.lhs.compile() + "<" + this.rhs.compile();
+            break;
+        case OpenLayers.Filter.Comparison.GREATER_THAN:
+            output = this.lhs.compile() + ">" + this.rhs.compile();
+            break;
+        case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:
+            output = this.lhs.compile() + "<=" + this.rhs.compile();
+            break;
+        case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:
+            output = this.lhs.compile() + ">=" + this.rhs.compile();
+            break;
+        case OpenLayers.Filter.Comparison.BETWEEN:
+            output = "OpenLayers.Filter.BetweenHelper(" + this.lhs.compile()
+                + "," + (this.lowerBoundary.compile ? this.lowerBoundary.compile() : this.lowerBoundary)
+                + "," + (this.upperBoundary.compile ? this.upperBoundary.compile() : this.upperBoundary) + ")";
+            break;
+        case OpenLayers.Filter.Comparison.LIKE:
+            if(this.rhs instanceof OpenLayers.Expression.Literal) {n
+                var pattern = this.value2regex(this.wildCard, this.singleChar, this.escapeChar, this.rhs.value);
+                result = "new RegExp('" + OpenLayers.Util.escapeJSString(pattern) + "', 'gi').test("
+                    + this.lhs.compile() + ")" ;
+                break;
+            }
+            
+            throw "Unhandled case";
+        }
+        
+        return output;
+    },
+    
+    /**
      * APIMethod: value2regex
      * Converts the value of this rule into a regular expression string,
      * according to the wildcard characters specified. This method has to
@@ -167,32 +264,47 @@
      * Returns:
      * {String} regular expression string
      */
-    value2regex: function(wildCard, singleChar, escapeChar) {
-        if (wildCard == ".") {
-            var msg = "'.' is an unsupported wildCard character for "+
-                    "OpenLayers.Filter.Comparison";
-            OpenLayers.Console.error(msg);
-            return null;
-        }
-        
-
+    value2regex: function(wildCard, singleChar, escapeChar, value) {
         // set UMN MapServer defaults for unspecified parameters
         wildCard = wildCard ? wildCard : "*";
         singleChar = singleChar ? singleChar : ".";
         escapeChar = escapeChar ? escapeChar : "!";
         
-        this.value = this.value.replace(
-                new RegExp("\\"+escapeChar+"(.|$)", "g"), "\\$1");
-        this.value = this.value.replace(
-                new RegExp("\\"+singleChar, "g"), ".");
-        this.value = this.value.replace(
-                new RegExp("\\"+wildCard, "g"), ".*");
-        this.value = this.value.replace(
-                new RegExp("\\\\.\\*", "g"), "\\"+wildCard);
-        this.value = this.value.replace(
-                new RegExp("\\\\\\.", "g"), "\\"+singleChar);
+        if(typeof value == "undefined")
+            value = this.rhs.value;
         
-        return this.value;
+        var output = "";
+        
+        for(var i = 0, len = value.length; i<len; ++i) {
+            var c = value.charAt(i);
+            switch(c) {
+                case wildCard:
+                    output += ".*";
+                    break;
+                case singleChar:
+                    output += ".";
+                    break;
+                case escapeChar:
+                    ++i;
+                    if(i==len)
+                        break;
+                    c = value.charAt(i);
+                    // Protect RegExp chars, but don't introduce accidental escape sequences
+                    if("^$.*?+:\\()[]{}|".indexOf(c) < 0) {
+                        output += c;
+                    } else {
+                        output += "\\";
+                        output += c;
+                    }
+                    
+                    break;
+                default:
+                    output += c;
+                    break;
+            }
+        }
+
+        return output;
     },
     
     /**
@@ -206,6 +318,8 @@
      */
     regex2value: function() {
         
+        return this.rhs.value;
+        
         var value = this.value;
         
         // replace ! with !!
Index: lib/OpenLayers/Filter/FeatureId.js
===================================================================
--- lib/OpenLayers/Filter/FeatureId.js	(revision 9361)
+++ lib/OpenLayers/Filter/FeatureId.js	(working copy)
@@ -19,7 +19,7 @@
 
     /** 
      * APIProperty: fids
-     * {Array(String)} Feature Ids to evaluate this rule against. To be passed
+     * {Array(String)} Feature Ids to evaluate this rule against.
      * To be passed inside the params object.
      */
     fids: null,
@@ -38,6 +38,11 @@
     initialize: function(options) {
         this.fids = [];
         OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+        var fidMap = {};
+        for(var i = 0, len = this.fids.length; i < len; ++i) {
+            fidMap[this.fids[i]] = null;
+        }
+        this.fids = fidMap;
     },
 
     /**
@@ -53,16 +58,22 @@
      * {Boolean} true if the rule applies, false if it does not
      */
     evaluate: function(feature) {
-        for (var i=0, len=this.fids.length; i<len; i++) {
-            var fid = feature.fid || feature.id;
-            if (fid == this.fids[i]) {
-                return true;
-            }
-        }
-        return false;
+        return (feature.fid || feature.id) in this.fids;
     },
     
     /**
+     * Method: compile
+     * See {<OpenLayers.Filter.compile>}.
+     */
+    compile: function() {
+        var i = OpenLayers.Filter.Enclosure.length;
+        
+        OpenLayers.Filter.Enclosure[i] = OpenLayers.Util.extend({}, this.fids);
+        
+        return "(x.fid || x.id) in e[" + i + "]";
+    },
+    
+    /**
      * APIMethod: clone
      * Clones this filter.
      * 
@@ -72,7 +83,7 @@
     clone: function() {
         var filter = new OpenLayers.Filter.FeatureId();
         OpenLayers.Util.extend(filter, this);
-        filter.fids = this.fids.slice();
+        filter.fids = OpenLayers.Util.extend({}, this.fids);
         return filter;
     },
     
Index: lib/OpenLayers/Filter/Logical.js
===================================================================
--- lib/OpenLayers/Filter/Logical.js	(revision 9361)
+++ lib/OpenLayers/Filter/Logical.js	(working copy)
@@ -67,11 +67,11 @@
      * Returns:
      * {Boolean} The filter applies.
      */
-    evaluate: function(context) {
+    evaluate: function(context, feature) {
         switch(this.type) {
             case OpenLayers.Filter.Logical.AND:
                 for (var i=0, len=this.filters.length; i<len; i++) {
-                    if (this.filters[i].evaluate(context) == false) {
+                    if (this.filters[i].evaluate(context, feature) == false) {
                         return false;
                     }
                 }
@@ -79,18 +79,45 @@
                 
             case OpenLayers.Filter.Logical.OR:
                 for (var i=0, len=this.filters.length; i<len; i++) {
-                    if (this.filters[i].evaluate(context) == true) {
+                    if (this.filters[i].evaluate(context, feature) == true) {
                         return true;
                     }
                 }
                 return false;
             
             case OpenLayers.Filter.Logical.NOT:
-                return (!this.filters[0].evaluate(context));
+                return (!this.filters[0].evaluate(context, feature));
         }
     },
     
     /**
+     * Method: compile
+     * See {<OpenLayers.Filter.compile>}.
+     */
+    compile: function() {
+        if(this.filters.length==0) {
+            switch(this.type) {
+                case OpenLayers.Filter.Logical.AND:
+                    return "true";
+                case OpenLayers.Filter.Logical.OR:
+                    return "false";
+            }
+            
+            throw "Unexpected filter type " + this.type;
+        }
+        
+        if(this.type==OpenLayers.Filter.Logical.NOT)
+            return "!(" + this.filters[0].compile() + ")";
+        
+        var output="("+this.filters[0].compile()+")";
+        for(var i=1, len=this.filters.length; i<len; ++i) {
+            output+=this.type + "("+this.filters[i].compile()+")";
+        }
+        
+        return output;
+    },
+    
+    /**
      * APIMethod: clone
      * Clones this filter.
      * 
@@ -111,7 +138,9 @@
     CLASS_NAME: "OpenLayers.Filter.Logical"
 });
 
-
 OpenLayers.Filter.Logical.AND = "&&";
 OpenLayers.Filter.Logical.OR  = "||";
 OpenLayers.Filter.Logical.NOT = "!";
+
+OpenLayers.Filter.Logical.TRUE = new OpenLayers.Filter.Logical({type: OpenLayers.Filter.Logical.AND});
+OpenLayers.Filter.Logical.FALSE = new OpenLayers.Filter.Logical({type: OpenLayers.Filter.Logical.OR});
\ No newline at end of file
Index: lib/OpenLayers/Filter/Spatial.js
===================================================================
--- lib/OpenLayers/Filter/Spatial.js	(revision 9361)
+++ lib/OpenLayers/Filter/Spatial.js	(working copy)
@@ -103,6 +103,37 @@
         }
         return intersect;
     },
+    
+    /**
+    * Method: compile
+    * See {<OpenLayers.Filter.compile>}.
+    */
+    compile: function() {
+        var output;
+        
+        switch(this.type) {
+            case OpenLayers.Filter.Spatial.BBOX:
+            case OpenLayers.Filter.Spatial.INTERSECTS:
+                var i = OpenLayers.Filter.Enclosure.length;
+                
+                if(this.value.toGeometry) {
+                    OpenLayers.Filter.Enclosure[i] = this.value.toGeometry().clone();
+                } else {
+                    OpenLayers.Filter.Enclosure[i] = this.value.clone();
+                }
+                
+                output = "x['" + OpenLayers.Util.escapeJSString(this.property)
+                    + "'].intersects(e[" + i + "])";
+                break;
+            default:
+                OpenLayers.Console.error(
+                        OpenLayers.i18n("filterEvaluateNotImplemented"));
+                output = "false";
+                break;
+        }
+        
+        return output;
+    },
 
     /**
      * APIMethod: clone
Index: lib/OpenLayers/Format/Expression.js
===================================================================
--- lib/OpenLayers/Format/Expression.js	(revision 0)
+++ lib/OpenLayers/Format/Expression.js	(revision 0)
@@ -0,0 +1,325 @@
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+(function(){
+
+// Imports
+var XML = OpenLayers.Format.XML;
+var Expression = OpenLayers.Expression;
+var PropertyName = Expression.PropertyName;
+var Binary = Expression.Binary;
+var Function = Expression.Function;
+var Literal = Expression.Literal;
+
+OpenLayers.Format.Expression = OpenLayers.Class(XML, {
+    /**
+     * Property: namespaces
+     * Map of prefix -> namespaces for these kinds of documents.
+     */
+    namespaces : { ogc : "http://www.opengis.net/ogc" },
+    
+    /**
+     * Property: defaultPrefix
+     * The default XML element name prefix to use.
+     */
+    defaultPrefix : "ogc",
+    
+    /**
+     * APIMethod: read
+     * Read in XML or a DOM node and produce an {<OpenLayers.Expression>}
+     *
+     * Parameters:
+     * data - {String} or {Node} The data to be parsed.
+     * 
+     * Returns:
+     * {OpenLayers.Expression} or null if a parsing error occurs.
+     */
+    read : function(data) {
+        if(typeof data == "string") {
+            data = XML.prototype.read.call(this, data);
+        }
+        
+        if(!(data && data.nodeName)) {
+            return null;
+        }
+        
+        if(data.nodeType == 9) { // document
+            data = data.documentElement;
+        
+            if(!data) {
+                return null;
+            }
+        }
+        
+        if(data.namespaceURI != this.namespaces.ogc)
+            return null;
+        
+        var group = this.readers[this.namespaceAlias[data.namespaceURI]];
+        
+        if(!group) {
+            return null;
+        }
+        
+        var parser = group[data.nodeName.split(':').pop()];
+        
+        if(!parser) {
+            return null;
+        }
+        
+        return parser.call(this, data);
+    },
+    
+    /**
+     * Method: readBinaryOperatorType
+     * Reads a node to produce an infix expression.
+     * 
+     * Parameters:
+     * node - {Node} The node to be parsed.
+     * op - The operation this node represents.
+     * 
+     * Returns:
+     * {OpenLayers.Expression.Binary} An infix expression.
+     */
+    readBinaryOperatorType : function(node, op) {
+        var lhs;
+        var rhs;
+        var el;
+        
+        el = this.getChildEl(node);
+        
+        if(!el) {
+            return null;
+        }
+        
+        lhs = this.read(el);
+        
+        if(!lhs) {
+            return null;
+        }
+        
+        el = this.getNextEl(el);
+        
+        if(!el) {
+            return null;
+        }
+        
+        rhs = this.read(el);
+        
+        if(!rhs) {
+            return null;
+        }
+        
+        if(this.getNextEl(el)) {
+            return null;
+        }
+        
+        return new Binary(op, lhs, rhs);
+    },
+    
+    /**
+     * Property: readers
+     * Element-reading functions, grouped by namespace.  Each reading function
+     * should take a node.
+     */
+    readers : {
+        ogc : {
+            Add : function(node) {
+                return this.readBinaryOperatorType(node, "+");
+            },
+    
+            Sub : function(node) {
+                return this.readBinaryOperatorType(node, "-");
+            },
+    
+            Mul : function(node) {
+                return this.readBinaryOperatorType(node, "*");
+            },
+    
+            Div : function(node) {
+                return this.readBinaryOperatorType(node, "/");
+            },
+            
+            PropertyName : function(node) {
+                var val = this.getChildValue(node);
+                
+                if(!val) {
+                    return null;
+                }
+                
+                return new PropertyName(val);
+            },
+            
+            Function : function(node) {
+                var name = node.attributes.getAttribute("name");
+                
+                if(!name) {
+                    return null;
+                }
+                
+                var expr, exprs=[], el;
+                
+                for(el = this.getChildEl(node); el; el = this.getNextEl(el)) {
+                    expr = this.parse(el);
+                    
+                    if(!expr) {
+                        return null;
+                    }
+                    
+                    exprs.push(expr);
+                }
+                
+                
+                return new Function(name, exprs);
+            },
+            
+            Literal : function(node) {
+                var val = OpenLayers.Format.XML.prototype.getChildValue(node);
+                
+                if(val == null) {
+                    return null;
+                }
+                
+                return new Literal(val);
+            }
+        }
+    },
+    
+    /**
+     * APIMethod: write
+     * Writes out the given expression to a String.
+     * 
+     * Paremeter:
+     * expr - {<OpenLayers.Expression>}
+     * 
+     * Returns:
+     * {Node} The XML version of the OGC Expression.
+     */
+    write : function(expr) {
+        var node = this.writeNode(this.getElemName(expr), expr);
+        return XML.prototype.write.call(this, node);
+    },
+
+    /**
+     * APIMethod: writeNode
+     * Writes out the given expression to a Node.  With one argument,
+     * writes an expression and determines the name autometically.  With
+     * two arguments, supplies the element name to use.
+     * 
+     * Parameters:
+     * name - {String} The name of the element to use or
+     *        {<OpenLayers.Expression>} the expression to write.
+     * expr - {<OpenLayers.Expression>} the expression to write.
+     * 
+     * Returns:
+     * {Node} The DOM tree representing the given expression.
+     */
+    writeNode : function(name, expr) {
+        if(arguments.length == 1) { // Called with just an expression
+            expr = name;
+            return this.writeNode(this.getElemName(expr), expr);
+        }
+        
+        return XML.prototype.writeNode.call(this, name, expr)
+    },
+    
+    /**
+     * Method: getElemName
+     * Returns the XML element name that corresponds to the given expression
+     * object.
+     * 
+     * Parameters:
+     * expr - {<OpenLayers.Expression>} The expression to examine.
+     * 
+     * Returns:
+     * {String} The prefixed XML element name.
+     */
+    getElemName : function(expr) {
+        var elemName = ELEMENT_NAMES[expr.op || expr.CLASS_NAME];
+        if(!elemName) {
+            throw "Unexpected object of class " + expr.CLASS_NAME;
+        }
+        return elemName;        
+    },
+    
+    /**
+     * Method: writeBinaryExpression
+     * Creates a Node representing the given binary expression.
+     * 
+     * Parameters:
+     * expr - {<OpenLayers.Expression.Binary>} The expression to use.
+     * name - The prefixed XML element name.
+     * 
+     * Returns:
+     * {Node} The node representing the expression.
+     */
+    writeBinaryExpression : function(expr, name) {
+        var node = this.createElementNSPlus(name);
+        node.appendChild(this.writeNode(this.getElemName(expr.lhs), expr.lhs));
+        node.appendChild(this.writeNode(this.getElemName(expr.rhs), expr.rhs));
+        return node;
+    },
+    
+    /**
+     * Property: writers
+     * Node-writing functions grouped by namespace prefix.
+     */
+    writers : {
+        ogc : {
+            Add : function (expr) {
+                return this.writeBinaryExpression(expr, "ogc:Add");
+            },
+            
+            Sub : function (expr) {
+                return this.writeBinaryExpression(expr, "ogc:Sub");
+            },
+            
+            Mul : function (expr) {
+                return this.writeBinaryExpression(expr, "ogc:Mul");
+            },
+            
+            Div : function (expr) {
+                return this.writeBinaryExpression(expr, "ogc:Div");
+            },
+            
+            Function : function (expr) {
+                var node = this.createElementNSPlus("ogc:Function");
+
+                node.setAttribute("name", expr.name);
+
+                for(var i = 0, len = expr.args.length; i < len; ++i) {
+                    node.appendChild(this.writeNode(
+                            this.getElemName(expr.args[i]), expr.args[i]));
+                }
+                
+                return node;
+            },
+            
+            PropertyName : function (expr) {
+                var node = this.createElementNSPlus("ogc:PropertyName");
+                node.appendChild(this.createTextNode(expr.name));
+                return node;
+            },
+            
+            Literal : function (expr) {
+                var node = this.createElementNSPlus("ogc:Literal");
+                node.appendChild(this.createTextNode(expr.value));
+                return node;                
+            }
+        }
+    },
+    
+    CLASS_NAME : "OpenLayers.Format.Expression"
+});
+
+// Private constant map of class/operator names to prefixed XML element names
+var ELEMENT_NAMES = {
+        "+": "ogc:Add",
+        "-": "ogc:Sub",
+        "*": "ogc:Mul",
+        "/": "ogc:Div",
+        "OpenLayers.Expression.Function": "ogc:Function",
+        "OpenLayers.Expression.PropertyName": "ogc:PropertyName",
+        "OpenLayers.Expression.Literal": "ogc:Literal"
+};
+
+})();
\ No newline at end of file
Index: lib/OpenLayers/Format/Filter/v1.js
===================================================================
--- lib/OpenLayers/Format/Filter/v1.js	(revision 9361)
+++ lib/OpenLayers/Format/Filter/v1.js	(working copy)
@@ -39,6 +39,12 @@
     schemaLocation: null,
     
     /**
+     * Property: exprFormat
+     * {OpenLayers.Format.Expression} Object to help parse expressions.
+     */
+    exprFormat: new OpenLayers.Format.Expression(),
+    
+    /**
      * Constructor: OpenLayers.Format.Filter.v1
      * Instances of this class are not created directly.  Use the
      *     <OpenLayers.Format.Filter> constructor instead.
@@ -61,11 +67,122 @@
      * {<OpenLayers.Filter>} A filter object.
      */
     read: function(data) {
-        var obj = {};
-        this.readers.ogc["Filter"].apply(this, [data, obj]);
-        return obj.filter;
+         if(typeof data == "string") {
+             data = OpenLayers.Format.XML.prototype.read.call(this, data);
+         }
+     
+         if(!(data && data.nodeType)) {
+             return null;
+         }
+         
+         if(data.nodeType == 9) { // document
+             data = data.documentElement;
+         }
+         
+         var obj = {};
+         this.readers.ogc.Filter.call(this, data, obj);
+         return obj.filter;
     },
     
+    readBinaryComparisonOpType : function(node, type) {
+        var lhsEl = this.getChildEl(node), rhsEl;
+        
+        if(!lhsEl) {
+            return null;
+        }
+        
+        rhsEl = this.getNextEl(lhsEl);
+        
+        if(!rhsEl || this.getNextEl(rhsEl)) {
+            return null;
+        }
+        
+        var lhs = this.exprFormat.read(lhsEl);
+
+        var rhs = this.exprFormat.read(rhsEl);
+        
+        if(!(lhs && rhs)) {
+            return null;
+        }
+        
+        return new OpenLayers.Filter.Comparison({lhs:lhs, rhs:rhs, type:type});
+    },    
+
+    readPropertyIsBetweenType : function(node) {
+        var lhsEl = this.getChildEl(node), upperEl, lowerEl;
+        
+        if(!lhsEl) {
+            return null;
+        }
+        
+        lowerEl = this.getNextEl(lhsEl, "LowerBoundary", this.namespaces.ogc);
+        
+        if(!lowerEl) {
+            return null;
+        }
+        
+        upperEl = this.getNextEl(lowerEl, "UpperBoundary", this.namespaces.ogc);
+        
+        if(!upperEl || this.getNextEl(upperEl)) {
+            return null;
+        }
+        
+        var lhs = this.exprFormat.read(lhsEl);
+
+        var lower = this.exprFormat.read(this.getChildEl(lowerEl));
+        var upper = this.exprFormat.read(this.getChildEl(upperEl));
+        
+        if(!(lhs && lower && upper)) {
+            return null;
+        }
+        
+        return new OpenLayers.Filter.Comparison({
+            type:OpenLayers.Filter.Comparison.BETWEEN,
+            lhs:lhs,
+            lowerBoundary: lower,
+            upperBoundary: upper});
+    },
+    
+    readPropertyIsLikeType : function(node) {
+        var propNameEl = this.getChildEl(node, "PropertyName", this.namespaces.ogc);
+        var literalEl = this.getNextEl(propNameEl, "Literal", this.namespaces.ogc);
+        
+        if(!(propNameEl && literalEl)) {
+            return null;
+        }
+        
+        var propName = this.getChildValue(propNameEl);
+        var literal = this.getChildValue(literalEl);
+        
+        if(!(propName && literal)) {
+            return null;
+        }
+        
+        var wildCard = node.attributes.getNamedItem('wildCard');
+        var singleChar = node.attributes.getNamedItem('singleChar');
+        var escape = node.attributes.getNamedItem('escape');
+        
+        if(!(wildCard && singleChar && escape)) {
+            return null;
+        }
+        
+        wildCard = wildCard.value;
+        singleChar = singleChar.value;
+        escape = escape.value;
+        
+        if(!(wildCard && singleChar && escape)) {
+            return null;
+        }
+        
+        return new OpenLayers.Filter.Comparison({
+            type:OpenLayers.Filter.Comparison.LIKE,
+            lhs:new OpenLayers.Expression.PropertyName(propName),
+            rhs:new OpenLayers.Expression.Literal(literal),
+            wildCard:wildCard,
+            singleChar:singleChar,
+            escapeChar:escape});
+    },
+    
     /**
      * Property: readers
      * Contains public functions, grouped by namespace prefix, that will
@@ -121,67 +238,38 @@
                 this.readChildNodes(node, filter);
                 obj.filters.push(filter);
             },
+            "PropertyIsEqualTo": function(node, obj) {
+                var filter = this.readBinaryComparisonOpType(node, OpenLayers.Filter.Comparison.EQUAL_TO);
+                obj.filters.push(filter);
+            },
+            "PropertyIsNotEqualTo": function(node, obj) {
+                var filter = this.readBinaryComparisonOpType(node, OpenLayers.Filter.Comparison.NOT_EQUAL_TO);
+                obj.filters.push(filter);
+            },
             "PropertyIsLessThan": function(node, obj) {
-                var filter = new OpenLayers.Filter.Comparison({
-                    type: OpenLayers.Filter.Comparison.LESS_THAN
-                });
-                this.readChildNodes(node, filter);
+                var filter = this.readBinaryComparisonOpType(node, OpenLayers.Filter.Comparison.LESS_THAN);
                 obj.filters.push(filter);
             },
             "PropertyIsGreaterThan": function(node, obj) {
-                var filter = new OpenLayers.Filter.Comparison({
-                    type: OpenLayers.Filter.Comparison.GREATER_THAN
-                });
-                this.readChildNodes(node, filter);
+                var filter = this.readBinaryComparisonOpType(node, OpenLayers.Filter.Comparison.GREATER_THAN);
                 obj.filters.push(filter);
             },
             "PropertyIsLessThanOrEqualTo": function(node, obj) {
-                var filter = new OpenLayers.Filter.Comparison({
-                    type: OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO
-                });
-                this.readChildNodes(node, filter);
+                var filter = this.readBinaryComparisonOpType(node, OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO);
                 obj.filters.push(filter);
             },
             "PropertyIsGreaterThanOrEqualTo": function(node, obj) {
-                var filter = new OpenLayers.Filter.Comparison({
-                    type: OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO
-                });
-                this.readChildNodes(node, filter);
+                var filter = this.readBinaryComparisonOpType(node, OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO);
                 obj.filters.push(filter);
             },
             "PropertyIsBetween": function(node, obj) {
-                var filter = new OpenLayers.Filter.Comparison({
-                    type: OpenLayers.Filter.Comparison.BETWEEN
-                });
-                this.readChildNodes(node, filter);
+                var filter = this.readPropertyIsBetweenType(node);
                 obj.filters.push(filter);
             },
             "PropertyIsLike": function(node, obj) {
-                var filter = new OpenLayers.Filter.Comparison({
-                    type: OpenLayers.Filter.Comparison.LIKE
-                });
-                this.readChildNodes(node, filter);
-                var wildCard = node.getAttribute("wildCard");
-                var singleChar = node.getAttribute("singleChar");
-                var esc = node.getAttribute("escape");
-                filter.value2regex(wildCard, singleChar, esc);
+                var filter = this.readPropertyIsLikeType(node);
                 obj.filters.push(filter);
             },
-            "Literal": function(node, obj) {
-                obj.value = OpenLayers.String.numericIf(
-                    this.getChildValue(node));
-            },
-            "PropertyName": function(node, filter) {
-                filter.property = this.getChildValue(node);
-            },
-            "LowerBoundary": function(node, filter) {
-                filter.lowerBoundary = OpenLayers.String.numericIf(
-                    this.readOgcExpression(node));
-            },
-            "UpperBoundary": function(node, filter) {
-                filter.upperBoundary = OpenLayers.String.numericIf(
-                    this.readOgcExpression(node));
-            },
             "Intersects": function(node, obj) {
                 this.readSpatial(node, obj, OpenLayers.Filter.Spatial.INTERSECTS);
             },
@@ -192,11 +280,19 @@
                 this.readSpatial(node, obj, OpenLayers.Filter.Spatial.CONTAINS);
             },
             "DWithin": function(node, obj) {
-                this.readSpatial(node, obj, OpenLayers.Filter.Spatial.DWITHIN);
+                this.readDistanceBuffer(node, obj, OpenLayers.Filter.Spatial.DWITHIN);
             },
             "Distance": function(node, obj) {
                 obj.distance = parseInt(this.getChildValue(node));
                 obj.distanceUnits = node.getAttribute("units");
+            },
+            "BBOX": function(node, obj) {
+                var el = this.getChildEl(node, "PropertyName", this.namespaces.ogc);
+                var propName = this.getChildValue(el);
+                el = this.getNextEl(node, "Box", this.namespaces.gml);
+                var val = this.readNode(el);
+                var filter = new OpenLayers.Filter.Spatial({property:propName, value:val.components[0], type:type});
+                obj.filters.push(filter);
             }
         }
     },
@@ -215,35 +311,32 @@
      * {<OpenLayers.Filter.Spatial>} The created filter.
      */
     readSpatial: function(node, obj, type) {
-        var filter = new OpenLayers.Filter.Spatial({
-            type: type
-        });
-        this.readChildNodes(node, filter);
-        filter.value = filter.components[0];
-        delete filter.components;
+        var el = this.getChildEl(node, "PropertyName", this.namespaces.ogc);
+        var propName = this.getChildValue(el);
+        el = this.getNextEl(el);
+        var val = this.readNode(el);
+        var filter = new OpenLayers.Filter.Spatial({property:propName, value:val.components[0], type:type});
         obj.filters.push(filter);
     },
 
-    /**
-     * Method: readOgcExpression
-     * Limited support for OGC expressions.
-     *
-     * Parameters:
-     * node - {DOMElement} A DOM element that contains an ogc:expression.
-     *
-     * Returns:
-     * {String} A value to be used in a symbolizer.
-     */
-    readOgcExpression: function(node) {
-        var obj = {};
-        this.readChildNodes(node, obj);
-        var value = obj.value;
-        if(!value) {
-            value = this.getChildValue(node);
-        }
-        return value;
+    readDistanceBuffer: function(node, obj, type) {
+        var propNameEl = this.getChildEl(node, "PropertyName", this.namespaces.ogc);
+        var propName = this.getChildValue(propNameEl);
+        var nextEl = this.getNextEl(propNameEl);
+        var val = this.readNode(nextEl);
+        
+        nextEl = this.getNextEl(nextEl, "Distance", this.namespaces.ogc);
+        
+        var filter = new OpenLayers.Filter.Spatial({
+            property:propName,
+            value:val.components[0],
+            type:type,
+            distance:parseFloat(this.getChildValue(nextEl)),
+            distanceUnits:nextEl.getAttribute("units")});
+        
+        obj.filters.push(filter);
     },
-
+    
     /**
      * Method: write
      *
@@ -257,6 +350,13 @@
         return this.writers.ogc["Filter"].apply(this, [filter]);
     },
     
+    writeBinaryComparisonOpType : function(filter, type) {
+        var node = this.createElementNSPlus(type);
+        node.appendChild(this.exprFormat.writeNode(filter.lhs));
+        node.appendChild(this.exprFormat.writeNode(filter.rhs));
+        return node;        
+    },
+    
     /**
      * Property: writers
      * As a compliment to the readers property, this structure contains public
@@ -312,38 +412,27 @@
                 );
                 return node;
             },
+            "PropertyIsEqualTo": function(filter) {
+                return this.writeBinaryComparisonOpType(filter, "ogc:PropertyIsEqualTo");
+            },
+            "PropertyIsNotEqualTo": function(filter) {
+                return this.writeBinaryComparisonOpType(filter, "ogc:PropertyIsNotEqualTo");
+            },
             "PropertyIsLessThan": function(filter) {
-                var node = this.createElementNSPlus("ogc:PropertyIsLessThan");
-                // no ogc:expression handling for now
-                this.writeNode("PropertyName", filter, node);
-                this.writeNode("Literal", filter.value, node);                
-                return node;
+                return this.writeBinaryComparisonOpType(filter, "ogc:PropertyIsLessThan");
             },
             "PropertyIsGreaterThan": function(filter) {
-                var node = this.createElementNSPlus("ogc:PropertyIsGreaterThan");
-                // no ogc:expression handling for now
-                this.writeNode("PropertyName", filter, node);
-                this.writeNode("Literal", filter.value, node);
-                return node;
+                return this.writeBinaryComparisonOpType(filter, "ogc:PropertyIsGreaterThan");
             },
             "PropertyIsLessThanOrEqualTo": function(filter) {
-                var node = this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");
-                // no ogc:expression handling for now
-                this.writeNode("PropertyName", filter, node);
-                this.writeNode("Literal", filter.value, node);
-                return node;
+                return this.writeBinaryComparisonOpType(filter, "ogc:PropertyIsLessThanOrEqualTo");
             },
             "PropertyIsGreaterThanOrEqualTo": function(filter) {
-                var node = this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");
-                // no ogc:expression handling for now
-                this.writeNode("PropertyName", filter, node);
-                this.writeNode("Literal", filter.value, node);
-                return node;
+                return this.writeBinaryComparisonOpType(filter, "ogc:PropertyIsGreaterThanOrEqualTo");
             },
             "PropertyIsBetween": function(filter) {
                 var node = this.createElementNSPlus("ogc:PropertyIsBetween");
-                // no ogc:expression handling for now
-                this.writeNode("PropertyName", filter, node);
+                node.appendChild(this.exprFormat.writeNode(filter.lhs));
                 this.writeNode("LowerBoundary", filter, node);
                 this.writeNode("UpperBoundary", filter, node);
                 return node;
@@ -354,34 +443,18 @@
                         wildCard: "*", singleChar: ".", escape: "!"
                     }
                 });
-                // no ogc:expression handling for now
-                this.writeNode("PropertyName", filter, node);
-                // convert regex string to ogc string
-                this.writeNode("Literal", filter.regex2value(), node);
+                node.appendChild(this.exprFormat.writeNode(filter.lhs));
+                node.appendChild(this.exprFormat.writeNode(filter.rhs));
                 return node;
             },
-            "PropertyName": function(filter) {
-                // no ogc:expression handling for now
-                return this.createElementNSPlus("ogc:PropertyName", {
-                    value: filter.property
-                });
-            },
-            "Literal": function(value) {
-                // no ogc:expression handling for now
-                return this.createElementNSPlus("ogc:Literal", {
-                    value: value
-                });
-            },
             "LowerBoundary": function(filter) {
-                // no ogc:expression handling for now
                 var node = this.createElementNSPlus("ogc:LowerBoundary");
-                this.writeNode("Literal", filter.lowerBoundary, node);
+                node.appendChild(this.exprFormat.writeNode(filter.lowerBoundary));
                 return node;
             },
             "UpperBoundary": function(filter) {
-                // no ogc:expression handling for now
                 var node = this.createElementNSPlus("ogc:UpperBoundary");
-                this.writeNode("Literal", filter.upperBoundary, node);
+                node.appendChild(this.exprFormat.writeNode(filter.upperBoundary));
                 return node;
             },
             "INTERSECTS": function(filter) {
Index: lib/OpenLayers/Format/Filter/v1_0_0.js
===================================================================
--- lib/OpenLayers/Format/Filter/v1_0_0.js	(revision 9361)
+++ lib/OpenLayers/Format/Filter/v1_0_0.js	(working copy)
@@ -54,22 +54,7 @@
      *     from the parent.
      */
     readers: {
-        "ogc": OpenLayers.Util.applyDefaults({
-            "PropertyIsEqualTo": function(node, obj) {
-                var filter = new OpenLayers.Filter.Comparison({
-                    type: OpenLayers.Filter.Comparison.EQUAL_TO
-                });
-                this.readChildNodes(node, filter);
-                obj.filters.push(filter);
-            },
-            "PropertyIsNotEqualTo": function(node, obj) {
-                var filter = new OpenLayers.Filter.Comparison({
-                    type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO
-                });
-                this.readChildNodes(node, filter);
-                obj.filters.push(filter);
-            }
-        }, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),
+        "ogc": OpenLayers.Format.Filter.v1.prototype.readers["ogc"],
         "gml": OpenLayers.Format.GML.v2.prototype.readers["gml"],
         "feature": OpenLayers.Format.GML.v2.prototype.readers["feature"]        
     },
@@ -82,30 +67,16 @@
      */
     writers: {
         "ogc": OpenLayers.Util.applyDefaults({
-            "PropertyIsEqualTo": function(filter) {
-                var node = this.createElementNSPlus("ogc:PropertyIsEqualTo");
-                // no ogc:expression handling for now
-                this.writeNode("PropertyName", filter, node);
-                this.writeNode("Literal", filter.value, node);
-                return node;
-            },
-            "PropertyIsNotEqualTo": function(filter) {
-                var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo");
-                // no ogc:expression handling for now
-                this.writeNode("PropertyName", filter, node);
-                this.writeNode("Literal", filter.value, node);
-                return node;
-            },
             "BBOX": function(filter) {
                 var node = this.createElementNSPlus("ogc:BBOX");
-                this.writeNode("PropertyName", filter, node);
+                node.appendChild(this.exprFormat.writeNode(new OpenLayers.Expression.PropertyName(filter.property)));
                 var box = this.writeNode("gml:Box", filter.value, node);
                 if(filter.projection) {
                     box.setAttribute("srsName", filter.projection);
                 }
                 return node;
-            }}, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),
-            
+            }
+        }, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),    
         "gml": OpenLayers.Format.GML.v2.prototype.writers["gml"],
         "feature": OpenLayers.Format.GML.v2.prototype.writers["feature"]
         
@@ -125,7 +96,7 @@
      */
     writeSpatial: function(filter, name) {
         var node = this.createElementNSPlus("ogc:"+name);
-        this.writeNode("PropertyName", filter, node);
+        node.appendChild(this.exprFormat.writeNode(new OpenLayers.Expression.PropertyName(filter.property)));
         var child;
         if(filter.value instanceof OpenLayers.Geometry) {
             child = this.writeNode("feature:_geometry", filter.value).firstChild;
Index: lib/OpenLayers/Format/Filter/v1_1_0.js
===================================================================
--- lib/OpenLayers/Format/Filter/v1_1_0.js	(revision 9361)
+++ lib/OpenLayers/Format/Filter/v1_1_0.js	(working copy)
@@ -51,6 +51,38 @@
         );
     },
 
+    readBinaryComparisonOpType : function(node, type) {
+        var lhsEl = this.getChildEl(node), rhsEl;
+        
+        if(!lhsEl) {
+            return null;
+        }
+        
+        rhsEl = this.getNextEl(lhsEl);
+        
+        if(!rhsEl || this.getNextEl(rhsEl)) {
+            return null;
+        }
+        
+        var lhs = this.exprFormat.read(lhsEl);
+
+        var rhs = this.exprFormat.read(rhsEl);
+
+        if(!(lhs && rhs)) {
+            return null;
+        }
+        
+        var matchCase = node.getAttribute("matchCase");
+
+        return new OpenLayers.Filter.Comparison({
+            lhs:lhs,
+            rhs:rhs,
+            type:type,
+            matchCase: matchCase == null ||
+                       (matchCase === "true" || matchCase === "1")
+        });
+    },    
+    
     /**
      * Property: readers
      * Contains public functions, grouped by namespace prefix, that will
@@ -60,29 +92,20 @@
      *     from the parent.
      */
     readers: {
-        "ogc": OpenLayers.Util.applyDefaults({
-            "PropertyIsEqualTo": function(node, obj) {
-                var matchCase = node.getAttribute("matchCase");
-                var filter = new OpenLayers.Filter.Comparison({
-                    type: OpenLayers.Filter.Comparison.EQUAL_TO,
-                    matchCase: !(matchCase === "false" || matchCase === "0")
-                });
-                this.readChildNodes(node, filter);
-                obj.filters.push(filter);
-            },
-            "PropertyIsNotEqualTo": function(node, obj) {
-                var matchCase = node.getAttribute("matchCase");
-                var filter = new OpenLayers.Filter.Comparison({
-                    type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
-                    matchCase: !(matchCase === "false" || matchCase === "0")
-                });
-                this.readChildNodes(node, filter);
-                obj.filters.push(filter);
-            }
-        }, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),
+        "ogc": OpenLayers.Format.Filter.v1.prototype.readers["ogc"],
         "gml": OpenLayers.Format.GML.v3.prototype.readers["gml"],
         "feature": OpenLayers.Format.GML.v3.prototype.readers["feature"]        
     },
+    
+    
+    writeBinaryComparisonOpType : function(filter, type) {
+        var node = this.createElementNSPlus(type, !filter.matchCase ? {
+            attributes: {matchCase : false}
+        } : null);
+        node.appendChild(this.exprFormat.writeNode(filter.lhs));
+        node.appendChild(this.exprFormat.writeNode(filter.rhs));
+        return node;        
+    },
 
     /**
      * Property: writers
@@ -92,27 +115,9 @@
      */
     writers: {
         "ogc": OpenLayers.Util.applyDefaults({
-            "PropertyIsEqualTo": function(filter) {
-                var node = this.createElementNSPlus("ogc:PropertyIsEqualTo", {
-                    attributes: {matchCase: filter.matchCase}
-                });
-                // no ogc:expression handling for now
-                this.writeNode("PropertyName", filter, node);
-                this.writeNode("Literal", filter.value, node);
-                return node;
-            },
-            "PropertyIsNotEqualTo": function(filter) {
-                var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo", {
-                    attributes: {matchCase: filter.matchCase}
-                });
-                // no ogc:expression handling for now
-                this.writeNode("PropertyName", filter, node);
-                this.writeNode("Literal", filter.value, node);
-                return node;
-            },
             "BBOX": function(filter) {
                 var node = this.createElementNSPlus("ogc:BBOX");
-                this.writeNode("PropertyName", filter, node);
+                node.appendChild(this.exprFormat.writeNode(new OpenLayers.Expression.PropertyName(filter.property)));
                 var box = this.writeNode("gml:Envelope", filter.value);
                 if(filter.projection) {
                     box.setAttribute("srsName", filter.projection);
@@ -139,7 +144,7 @@
      */
     writeSpatial: function(filter, name) {
         var node = this.createElementNSPlus("ogc:"+name);
-        this.writeNode("PropertyName", filter, node);
+        node.appendChild(this.exprFormat.writeNode(new OpenLayers.Expression.PropertyName(filter.property)));
         var child;
         if(filter.value instanceof OpenLayers.Geometry) {
             child = this.writeNode("feature:_geometry", filter.value).firstChild;
Index: lib/OpenLayers/Format/SLD/v1.js
===================================================================
--- lib/OpenLayers/Format/SLD/v1.js	(revision 9361)
+++ lib/OpenLayers/Format/SLD/v1.js	(working copy)
@@ -84,6 +84,18 @@
      * {Object} An object representing the SLD.
      */
     read: function(data, options) {
+        if(typeof data == "string") {
+            data = OpenLayers.Format.XML.prototype.read.call(this, data);
+        }
+
+        if(!(data && data.nodeType)) {
+            return null;
+        }
+
+        if(data.nodeType == 9) { // document
+            data = data.documentElement;
+        }
+        
         options = OpenLayers.Util.applyDefaults(options, this.options);
         var sld = {
             namedLayers: options.namedLayersAsArray === true ? [] : {}
@@ -180,17 +192,7 @@
                 rule.symbolizer["Text"] = symbolizer;
             },
             "Label": function(node, symbolizer) {
-                // only supporting literal or property name
-                var obj = {};
-                this.readChildNodes(node, obj);
-                if(obj.property) {
-                    symbolizer.label = "${" + obj.property + "}";
-                } else {
-                    var value = this.readOgcExpression(node);
-                    if(value) {
-                        symbolizer.label = value;
-                    }
-                }
+                symbolizer.label = this.readParameterValueType(node);
             },
             "Font": function(node, symbolizer) {
                 this.readChildNodes(node, symbolizer);
@@ -204,7 +206,7 @@
                 symbolizer.haloOpacity = obj.fillOpacity;
             },
             "Radius": function(node, symbolizer) {
-                var radius = this.readOgcExpression(node);
+                var radius = this.readParameterValueType(node);
                 if(radius != null) {
                     // radius is only used for halo
                     symbolizer.haloRadius = radius;
@@ -243,8 +245,7 @@
                 var cssProperty = node.getAttribute("name");
                 var symProperty = this.cssMap[cssProperty];
                 if(symProperty) {
-                    // Limited support for parsing of OGC expressions
-                    var value = this.readOgcExpression(node);
+                    var value = this.readParameterValueType(node);
                     // always string, could be an empty string
                     if(value) {
                         symbolizer[symProperty] = value;
@@ -294,21 +295,21 @@
                 graphic.graphicName = this.getChildValue(node);
             },
             "Opacity": function(node, obj) {
-                var opacity = this.readOgcExpression(node);
+                var opacity = this.readParameterValueType(node);
                 // always string, could be empty string
                 if(opacity) {
                     obj.opacity = opacity;
                 }
             },
             "Size": function(node, obj) {
-                var size = this.readOgcExpression(node);
+                var size = this.readParameterValueType(node);
                 // always string, could be empty string
                 if(size) {
                     obj.size = size;
                 }
             },
             "Rotation": function(node, obj) {
-                var rotation = this.readOgcExpression(node);
+                var rotation = this.readParameterValueType(node);
                 // always string, could be empty string
                 if(rotation) {
                     obj.rotation = rotation;
@@ -344,6 +345,55 @@
         "font-style": "fontStyle"
     },
     
+    readParameterValueType : function(node) {
+        var components=[];
+        
+        for(var n = node.firstChild; n; n = n.nextSibling) {
+            switch(n.nodeType) {
+            case 1: // element
+                var expr = this.exprFormat.read(this.getChildEl(node));
+                components.push(expr);
+                
+                break;
+            case 3: // text
+                var val = Number(n.nodeValue);
+                components.push(isNaN(val) ? n.nodeValue : val);
+                break;
+            }
+        }
+        
+        var output = new OpenLayers.ParameterValue(components); 
+        
+        return output;
+    },
+    
+    writeParameterValueType : function(name, param) {
+        var node = this.createElementNSPlus(name);
+        
+        if(typeof param != "object") {
+            node.appendChild(this.createTextNode(param));
+            return node;
+        }
+        
+        var components=param.components;
+        
+        for(var i = 0, len = components.length; i < len; ++i) {
+            var c = components[i];
+            var type = typeof c;
+            var n;
+            
+            if(type == "object") {
+                n = this.exprFormat.writeNode(c);
+            } else {
+                n = this.createTextNode(c);
+            }
+            
+            node.appendChild(n);
+        }
+        
+        return node;
+    },
+    
     /**
      * Method: getCssProperty
      * Given a symbolizer property, get the corresponding CSS property
@@ -652,11 +702,9 @@
                 return node;
             },
             "CssParameter": function(obj) {
-                // not handling ogc:expressions for now
-                return this.createElementNSPlus("CssParameter", {
-                    attributes: {name: this.getCssProperty(obj.key)},
-                    value: obj.symbolizer[obj.key]
-                });
+                var node = this.writeParameterValueType("CssParameter", obj.symbolizer[obj.key]);
+                node.setAttribute("name", this.getCssProperty(obj.key));
+                return node;
             },
             "TextSymbolizer": function(symbolizer) {
                 var node = this.createElementNSPlus("TextSymbolizer");
@@ -718,32 +766,7 @@
                 return node;
             },
             "Label": function(label) {
-                // only the simplest of ogc:expression handled
-                // {label: "some text and a ${propertyName}"}
-                var node = this.createElementNSPlus("Label");
-                var tokens = label.split("${");
-                node.appendChild(this.createTextNode(tokens[0]));
-                var item, last;
-                for(var i=1, len=tokens.length; i<len; i++) {
-                    item = tokens[i];
-                    last = item.indexOf("}"); 
-                    if(last > 0) {
-                        this.writeNode(
-                            "ogc:PropertyName",
-                            {property: item.substring(0, last)},
-                            node
-                        );
-                        node.appendChild(
-                            this.createTextNode(item.substring(++last))
-                        );
-                    } else {
-                        // no ending }, so this is a literal ${
-                        node.appendChild(
-                            this.createTextNode("${" + item)
-                        );
-                    }
-                }
-                return node;
+                return this.writeParameterValueType("Label", label);
             },
             "Halo": function(symbolizer) {
                 var node = this.createElementNSPlus("Halo");
@@ -759,9 +782,7 @@
                 return node;
             },
             "Radius": function(value) {
-                return node = this.createElementNSPlus("Radius", {
-                    value: value
-                });
+                return this.writeParameterValueType("Radius", value);
             },
             "PolygonSymbolizer": function(symbolizer) {
                 var node = this.createElementNSPlus("PolygonSymbolizer");
@@ -848,19 +869,13 @@
                 });
             },
             "Opacity": function(value) {
-                return this.createElementNSPlus("Opacity", {
-                    value: value
-                });
+                return this.writeParameterValueType("Opacity", value);
             },
             "Size": function(value) {
-                return this.createElementNSPlus("Size", {
-                    value: value
-                });
+                return this.writeParameterValueType("Size", value);
             },
             "Rotation": function(value) {
-                return this.createElementNSPlus("Rotation", {
-                    value: value
-                });
+                return this.writeParameterValueType("Rotation", value);
             },
             "OnlineResource": function(href) {
                 return this.createElementNSPlus("OnlineResource", {
Index: lib/OpenLayers/ParameterValue.js
===================================================================
--- lib/OpenLayers/ParameterValue.js	(revision 0)
+++ lib/OpenLayers/ParameterValue.js	(revision 0)
@@ -0,0 +1,182 @@
+/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+(function(){
+
+// Imports
+var PropertyName = OpenLayers.Expression.PropertyName;
+var Util = OpenLayers.Util;
+
+/**
+ * Class: OpenLayers.ParameterValue
+ * This class represents a ParameterValue as in the SLD spec.
+ * 
+ * TODO: Make mutable?
+ */
+OpenLayers.ParameterValue = OpenLayers.Class({
+    /**
+     * Property: components
+     * {Array(<OpenLayers.Expression> || String || Number} The components of the
+     * parameter -- will be concatenated if appropriate.
+     */
+    components : null,
+    
+    /**
+     * Property: cached
+     * {Boolean} True if this is static and has a cached value.
+     */
+    cached : null,
+    
+    /**
+     * Property: dynamic
+     * {Boolean} True if this has no context-sensitive components.
+     */
+    dynamic : null,
+    
+    /**
+     * Constructor: {<OpenLayers.ParameterValue>}
+     * Constructs a new instance, taking ownership of the given components.
+     * This also calculates if this is dynamic, and precalculates the right
+     * value if so.
+     * 
+     * Do not modify the array after constructing this instance.
+     * 
+     * Parameters:
+     * components - {Array}
+     */
+    initialize : function(components) {
+        this.components = components;
+        this.dynamic = this.isDynamic();
+        
+        if(!this.dynamic) {
+            this.cached = this.evaluate();
+            this.evaluate = function() { return this.cached; }
+        }
+    },
+    
+    /**
+     * APIMethod: evaluate
+     * Evaluates this ParameterValue against the given context and feature
+     * and concatenates the results if there is more than one component.
+     * 
+     * Parameters:
+     * ctx - {Object} The context for evaluation.
+     * feature - {<OpenLayers.Feature>} The feature being evaluated.
+     * 
+     * Returns:
+     * {String || Number || Object} The result of evaluation.
+     */
+    evaluate : function(ctx, feature) {
+        if(this.components.length == 1) {
+            var c = this.components[0];
+            return c.evaluate ? c.evaluate(ctx, feature) : c;
+        }
+
+        var output = "";
+        for(var i = 0, len = this.components.length; i < len; ++i) {
+            if(typeof this.components[i] == "object") {
+                output += this.components[i].evaluate(ctx, feature).toString();
+            } else {
+                output += this.components[i].toString();
+            }
+        }
+        
+        return output;
+    },
+    
+    /**
+     * APIMethod: compile
+     */
+    compile : function(ctx) {
+        /*
+         * If there's only one component, try to return it directly in its native type
+         * (especially if it's a number).
+         */
+        
+        if(this.components.length == 1) {
+            var c = this.components[0];
+            
+            if(typeof c == "number") {
+                return c.toString();
+            }
+            
+            return c.compile ? c.compile()
+                    : "'" + Util.escapeJSString(c) + "'";
+        }
+        
+        /*
+         * Otherwise, always cast to a string. 
+         */
+        var output = "''";
+        for(var i = 0, len = this.components.length; i < len; ++i) {
+            var type = typeof this.components[i];
+
+            output += "+";
+            
+            if(type == "string" || type == "number") {
+                output += "'" + Util.escapeJSString(this.components[i].toString()) + "'";
+            } else {
+                output += this.components[i].compile();
+            }
+        }
+        
+        return output;
+    },
+    
+    precompile : function() {
+        this.evaluate = this.compileFunction();
+    },
+    
+    compileFunction : function() {
+        var func;
+        eval("func=function(x){return " + this.compile() + ";}");
+        return func;
+    },
+    
+    isDynamic : function() {
+        for(var i = 0, len = this.components.length; i < len; ++i) {
+            if(typeof this.components[i] != "object") {
+                continue;
+            }
+            
+            if(isComponentDynamic(this.components[i])) {
+                return true;
+            }
+        }
+        
+        return false;
+    },
+    
+    CLASS_NAME : "OpenLayers.ParameterValue"
+});
+
+var isComponentDynamic = function(c) {
+    var stack = [c];
+    
+    while(stack.length) {
+        var d = stack.pop();
+        
+        if(d instanceof PropertyName) {
+            return true;
+        }
+    
+        if(d.args) {
+            for(var i = 0, len = d.args.length; i < len; ++i) {
+                stack.push(d.args[i]);
+            }
+        } else {
+            if(d.lhs) {
+                stack.push(d.lhs);
+            }
+            
+            if(d.rhs) {
+                stack.push(d.rhs);
+            }
+        }
+    }
+    
+    return false;
+}
+
+})();
\ No newline at end of file
Index: lib/OpenLayers/Rule.js
===================================================================
--- lib/OpenLayers/Rule.js	(revision 9361)
+++ lib/OpenLayers/Rule.js	(working copy)
@@ -104,7 +104,9 @@
         this.symbolizer = {};
         OpenLayers.Util.extend(this, options);
         this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+        OpenLayers.Rule.createPropertyNames(this.symbolizer);
     },
+    
 
     /** 
      * APIMethod: destroy
@@ -152,7 +154,7 @@
             if(this.filter.CLASS_NAME == "OpenLayers.Filter.FeatureId") {
                 applies = this.filter.evaluate(feature);
             } else {
-                applies = this.filter.evaluate(context);
+                applies = this.filter.evaluate(context, feature);
             }
         }
 
@@ -187,9 +189,10 @@
      */
     clone: function() {
         var options = OpenLayers.Util.extend({}, this);
+        var key, value, type;
         // clone symbolizer
         options.symbolizer = {};
-        for(var key in this.symbolizer) {
+        for(key in this.symbolizer) {
             value = this.symbolizer[key];
             type = typeof value;
             if(type === "object") {
@@ -198,12 +201,150 @@
                 options.symbolizer[key] = value;
             }
         }
+        
         // clone filter
         options.filter = this.filter && this.filter.clone();
         // clone context
         options.context = this.context && OpenLayers.Util.extend({}, this.context);
         return new OpenLayers.Rule(options);
     },
+    
+    /**
+     * Apply this rule's symbolizer to an given style hash, using feature as a
+     * context.
+     */
+    apply: function(context, feature, style, pref) {
+        symbolizer = this.symbolizer[pref] || this.symbolizer;
+        return OpenLayers.Rule.apply(context || this.getContext(feature), feature, style, symbolizer);
+    },
         
     CLASS_NAME: "OpenLayers.Rule"
-});
\ No newline at end of file
+});
+
+/**
+ * APIFunction: OpenLayers.Rule.apply
+ * Apply a symbolizer of primitives and {<OpenLayers.ParameterValue>}s to a
+ * given style in a given context.  Both a context and a feature can be passed
+ * in--this is for certain use cases where the context object can contain
+ * functors on {<OpenLayers.Feature>}.  (This was evinced from the unit tests,
+ * so it may not be in current usage.)
+ * 
+ * TODO: Why does this need to have side effects?  Just apply everything to a
+ * new empty hash and return that, let the caller apply it....
+ * 
+ * Parameters:
+ * ctx - {Object} The context for evaluation.  A hash of primitives and functors
+ *       ({<OpenLayers.Feature>} -> primitive)
+ * feature - {<OpenLayers.Feature>} The feature being styled.
+ * style - {Object} The style object to which to apply attributes.  (E.g. an
+ *         empty hash.)
+ * symbolizer - {Object} The symbolizer containing context-sensitive attributes.
+ *              Corresponds to an SLD symbolizer.
+ * 
+ * Returns:
+ * {Object} The 'style' parameter itself, post-modification.
+ */
+OpenLayers.Rule.apply = function(ctx, feature, style, symbolizer) {
+    var key; // index in symbolizer
+    var value; // value extracted from symbolizer
+    
+    for(key in symbolizer) {
+        value = symbolizer[key];
+        
+        if(value instanceof OpenLayers.ParameterValue) {
+            style[key] = symbolizer[key].evaluate(ctx, feature);
+        } else {
+            style[key] = symbolizer[key];
+        }
+    }    
+    
+    return style;
+};
+
+/**
+ * APIFunction: OpenLayers.Rule.createPropertyNames
+ * Given a hash of strings, hashes, and {<OpenLayers.ParameterValue>} objects,
+ * turn its descendent string values into {<OpenLayers.ParameterValue>} objects
+ * as necessary.
+ * 
+ * Parameters:
+ * obj - {Object} A hash of strings and {Object}s
+ * 
+ * Returns:
+ * {Object} - The same hash with its string values turned into
+ * {<OpenLayers.ParameterValue>}s as necessary.
+ */
+OpenLayers.Rule.createPropertyNames = function(obj) {
+    for(var key in obj) {
+        var val = obj[key];
+        var type = typeof val;
+        
+        if(type === "object") {
+            if(val instanceof OpenLayers.ParameterValue) {
+                continue;
+            }
+            
+            this.createPropertyNames(val);
+        } else if(type === "string") {
+            obj[key] = OpenLayers.Rule.convertToParameterValue(val);
+        }
+    }
+};
+
+/**
+ * APIFunction: OpenLayers.Rule.convertToParameterValue
+ * Converts a string with '${literals}' to either a ParameterValue or an
+ * identical string (if no literals are found).  Components of the
+ * ParameterValue are split at literals, so if no literals are found, that
+ * indicates the string has no substitutions and can be used as is.  Otherwise,
+ * the string is split into primitive strings and {<OpenLayers.Expression.PropertyName>}
+ * objects.
+ * 
+ * Parameters:
+ * val - {String} The string to convert.
+ * 
+ * Returns:
+ * {<OpenLayers.ParameterValue>} or {String} - the converted value.
+ */
+OpenLayers.Rule.convertToParameterValue = function(val) {
+    var i, startBrace, endBrace, name, component=0, components=[];
+    for(i = 0; i < val.length;) {
+        startBrace = val.indexOf("${", i);
+        
+        if(startBrace < 0) {
+            components.push(val.substring(i));
+            break;
+        }
+
+        endBrace = val.indexOf("}", startBrace + 2);
+
+        if(endBrace < 0) {
+            components.push(val.substring(i));
+            break;
+        }
+        
+        // Don't push empty components
+        if(i != startBrace) {
+            components.push(val.substring(i, startBrace));
+        }
+        
+        name = val.substring(startBrace+2, endBrace);
+        components.push(new OpenLayers.Expression.PropertyName(name));
+        
+        i = endBrace + 1;
+    }
+    
+    if(components.length == 0) {
+        return "";
+    }
+    
+    /* If there is only one, primitive component, then there were no
+     * substitutions, just return it.
+     */
+    if(components.length == 1 && typeof components[0] == "string") {
+        var num = Number(components[0]);
+        return isNaN(num) ? components[0] : num;
+    }
+    
+    return new OpenLayers.ParameterValue(components);
+};
\ No newline at end of file
Index: lib/OpenLayers/Style.js
===================================================================
--- lib/OpenLayers/Style.js	(revision 9361)
+++ lib/OpenLayers/Style.js	(working copy)
@@ -77,15 +77,7 @@
      * missing symbolizer properties if the symbolizer has stroke, fill or
      * graphic set to true. Default is false.
      */
-    defaultsPerSymbolizer: false,
-    
-    /**
-     * Property: propertyStyles
-     * {Hash of Boolean} cache of style properties that need to be parsed for
-     * propertyNames. Property names are keys, values won't be used.
-     */
-    propertyStyles: null,
-    
+    defaultsPerSymbolizer: false,    
 
     /** 
      * Constructor: OpenLayers.Style
@@ -146,8 +138,12 @@
      * {Object} symbolizer hash
      */
     createSymbolizer: function(feature) {
-        var style = this.defaultsPerSymbolizer ? {} : this.createLiterals(
-            OpenLayers.Util.extend({}, this.defaultStyle), feature);
+        var style = this.defaultsPerSymbolizer ? {} :
+            OpenLayers.Rule.apply(
+                    this.context || feature.attributes || feature.data,
+                    feature,
+                    {},
+                    this.defaultStyle);
         
         var rules = this.rules;
 
@@ -201,7 +197,7 @@
                 this.getSymbolizerPrefix(feature.geometry) :
                 OpenLayers.Style.SYMBOLIZER_PREFIXES[0];
 
-        var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer;
+        var symbolizer = rule.apply(this.context, feature, style, symbolizerPrefix);
         
         if(this.defaultsPerSymbolizer === true) {
             var defaults = this.defaultStyle;
@@ -237,94 +233,10 @@
             }
         }
 
-        // merge the style with the current style
-        return this.createLiterals(
-                OpenLayers.Util.extend(style, symbolizer), feature);
+        return symbolizer;
     },
     
     /**
-     * Method: createLiterals
-     * creates literals for all style properties that have an entry in
-     * <this.propertyStyles>.
-     * 
-     * Parameters:
-     * style   - {Object} style to create literals for. Will be modified
-     *           inline.
-     * feature - {Object}
-     * 
-     * Returns:
-     * {Object} the modified style
-     */
-    createLiterals: function(style, feature) {
-        var context = this.context || feature.attributes || feature.data;
-        
-        for (var i in this.propertyStyles) {
-            style[i] = OpenLayers.Style.createLiteral(style[i], context, feature);
-        }
-        return style;
-    },
-    
-    /**
-     * Method: findPropertyStyles
-     * Looks into all rules for this style and the defaultStyle to collect
-     * all the style hash property names containing ${...} strings that have
-     * to be replaced using the createLiteral method before returning them.
-     * 
-     * Returns:
-     * {Object} hash of property names that need createLiteral parsing. The
-     * name of the property is the key, and the value is true;
-     */
-    findPropertyStyles: function() {
-        var propertyStyles = {};
-
-        // check the default style
-        var style = this.defaultStyle;
-        this.addPropertyStyles(propertyStyles, style);
-
-        // walk through all rules to check for properties in their symbolizer
-        var rules = this.rules;
-        var symbolizer, value;
-        for (var i=0, len=rules.length; i<len; i++) {
-            symbolizer = rules[i].symbolizer;
-            for (var key in symbolizer) {
-                value = symbolizer[key];
-                if (typeof value == "object") {
-                    // symbolizer key is "Point", "Line" or "Polygon"
-                    this.addPropertyStyles(propertyStyles, value);
-                } else {
-                    // symbolizer is a hash of style properties
-                    this.addPropertyStyles(propertyStyles, symbolizer);
-                    break;
-                }
-            }
-        }
-        return propertyStyles;
-    },
-    
-    /**
-     * Method: addPropertyStyles
-     * 
-     * Parameters:
-     * propertyStyles - {Object} hash to add new property styles to. Will be
-     *                  modified inline
-     * symbolizer     - {Object} search this symbolizer for property styles
-     * 
-     * Returns:
-     * {Object} propertyStyles hash
-     */
-    addPropertyStyles: function(propertyStyles, symbolizer) {
-        var property;
-        for (var key in symbolizer) {
-            property = symbolizer[key];
-            if (typeof property == "string" &&
-                    property.match(/\$\{\w+\}/)) {
-                propertyStyles[key] = true;
-            }
-        }
-        return propertyStyles;
-    },
-    
-    /**
      * APIMethod: addRules
      * Adds rules to this style.
      * 
@@ -333,7 +245,6 @@
      */
     addRules: function(rules) {
         this.rules = this.rules.concat(rules);
-        this.propertyStyles = this.findPropertyStyles();
     },
     
     /**
@@ -344,8 +255,8 @@
      * style - {Object} Hash of style properties
      */
     setDefaultStyle: function(style) {
-        this.defaultStyle = style; 
-        this.propertyStyles = this.findPropertyStyles();
+        this.defaultStyle = style;
+        OpenLayers.Rule.createPropertyNames(this.defaultStyle);
     },
         
     /**
Index: lib/OpenLayers/Util.js
===================================================================
--- lib/OpenLayers/Util.js	(revision 9361)
+++ lib/OpenLayers/Util.js	(working copy)
@@ -645,7 +645,6 @@
  * Function: Try
  * Execute functions until one of them doesn't throw an error. 
  *     Capitalized because "try" is a reserved word in JavaScript.
- *     Taken directly from OpenLayers.Util.Try()
  * 
  * Parameters:
  * [*] - {Function} Any number of parameters may be passed to Try()
@@ -1655,3 +1654,19 @@
 
     return scrollbarWidth;
 };
+
+/**
+ * APIFunction: escapeJSString
+ * Turns a string into a Javascript-escaped string for inclusion into a quoted
+ * string (for metaprogramming).  You must (eventually) place this string into
+ * single quotes, not double quotes.
+ * 
+ * Parameters:
+ * s - {String} The string to quote (you must place it between quotes yourself)
+ * 
+ * Returns:
+ * {String} The escaped string.
+ */
+OpenLayers.Util.escapeJSString = function(s) {
+    return s.replace("\\","\\\\").replace("\n","\\n").replace("'", "\\'");
+};
\ No newline at end of file
Index: tests/Expression/Binary.html
===================================================================
--- tests/Expression/Binary.html	(revision 0)
+++ tests/Expression/Binary.html	(revision 0)
@@ -0,0 +1,43 @@
+<html>
+<head>
+<script src="../../lib/OpenLayers.js"></script>
+<script type="text/javascript">
+function test_Binary(t) {
+    t.plan(8);
+    
+    var expr = new OpenLayers.Expression.Binary(
+            "+",
+            new OpenLayers.Expression.Literal(22),
+            new OpenLayers.Expression.Literal(20));
+    t.eq(expr.evaluate({}), 42, "addition evaluates correctly");
+
+    expr.op="-";
+    t.eq(expr.evaluate({}), 2, "subtraction evaluates correctly");
+
+    expr.op="*";
+    t.eq(expr.evaluate({}), 440, "multiplication evaluates correctly");
+
+    expr.op="/";
+    t.eq(expr.evaluate({}), 1.1, "division evaluates correctly");
+
+    expr.op="+";
+    expr.preCompile();
+    t.eq(expr.evaluate({}), 42, "addition evaluates correctly (precompiled)");    
+
+    expr.op="-";
+    expr.preCompile();
+    t.eq(expr.evaluate({}), 2, "subtraction evaluates correctly (precompiled)");    
+
+    expr.op="*";
+    expr.preCompile();
+    t.eq(expr.evaluate({}), 440, "multiplication evaluates correctly (precompiled)");    
+
+    expr.op="/";
+    expr.preCompile();
+    t.eq(expr.evaluate({}), 1.1, "division evaluates correctly (precompiled)");    
+}
+</script>
+</head>
+<body>
+</body>
+</html>
\ No newline at end of file
Index: tests/Expression/Function.html
===================================================================
--- tests/Expression/Function.html	(revision 0)
+++ tests/Expression/Function.html	(revision 0)
@@ -0,0 +1,74 @@
+<html>
+<head>
+<script src="../../lib/OpenLayers.js"></script>
+<script type="text/javascript">
+function test_invalid_arity(t) {
+    t.plan(0);
+    
+    var expr, exc;
+
+    try {
+        exc = null;
+        expr = new OpenLayers.Expression.Function("sin", []);
+    } catch(e) {
+        exc = e;
+    }
+
+    if(exc == null) {
+        t.fail("exception not thrown");
+    }
+}
+
+function test_invalid_name(t) {
+    t.plan(0);
+    
+    var expr, exc;
+
+    try {
+        exc = null;
+        expr = new OpenLayers.Expression.Function("foo", []);
+    } catch(e) {
+        exc = e;
+    }
+
+    if(exc == null) {
+        t.fail("exception not thrown");
+    }
+}
+
+function test_evaluate(t) {
+    t.plan(2);
+    
+    var expr, exc;
+
+    exc = null;
+    expr = new OpenLayers.Expression.Function(
+            "cos",
+            [new OpenLayers.Expression.Literal(0)]);
+    t.eq(expr.evaluate(), 1, "evaluation is correct");
+    expr.preCompile();
+    t.eq(expr.evaluate(), 1, "evaluation is correct (precompiled)");    
+}
+
+function test_multiple_arguments(t) {
+    t.plan(2);
+    
+    var expr, exc;
+
+    exc = null;
+    expr = new OpenLayers.Expression.Function(
+            "max",
+            [new OpenLayers.Expression.Literal(10),
+             new OpenLayers.Expression.Literal(30),
+             new OpenLayers.Expression.Literal(20)]);
+
+    t.eq(expr.evaluate(), 30, "evaluation is correct");
+    expr.preCompile();
+    t.eq(expr.evaluate(), 30, "evaluation is correct (precompiled)");
+}
+
+</script>
+</head>
+<body>
+</body>
+</html>
\ No newline at end of file
Index: tests/Expression/Literal.html
===================================================================
--- tests/Expression/Literal.html	(revision 0)
+++ tests/Expression/Literal.html	(revision 0)
@@ -0,0 +1,23 @@
+<html>
+<head>
+<script src="../../lib/OpenLayers.js"></script>
+<script type="text/javascript">
+function test_Literal(t) {
+    t.plan(4);
+    
+    var expr;
+    expr = new OpenLayers.Expression.Literal("10");
+    t.eq(expr.evaluate(), 10, "string cast to number");
+    expr.preCompile();
+    t.eq(expr.evaluate(), 10, "string cast to number (precompiled)");
+
+    expr = new OpenLayers.Expression.Literal("10a");        
+    t.eq(expr.evaluate(), "10a", "string not cast to number");
+    expr.preCompile();
+    t.eq(expr.evaluate(), "10a", "string not cast to number (precompiled)");
+}
+</script>
+</head>
+<body>
+</body>
+</html>
\ No newline at end of file
Index: tests/Expression/PropertyName.html
===================================================================
--- tests/Expression/PropertyName.html	(revision 0)
+++ tests/Expression/PropertyName.html	(revision 0)
@@ -0,0 +1,21 @@
+<html>
+<head>
+<script src="../../lib/OpenLayers.js"></script>
+<script type="text/javascript">
+function test_PropertyName(t) {
+    t.plan(4);
+    
+    var expr, exc;
+
+    expr = new OpenLayers.Expression.PropertyName("foo");
+    t.eq(expr.evaluate({}), undefined, "undefined property returns undefined");
+    t.eq(expr.evaluate({foo:314}), 314, "evaluate returns correct value");
+    expr.preCompile();
+    t.eq(expr.evaluate({}), undefined, "undefined property returns undefined (precompiled)");
+    t.eq(expr.evaluate({foo:314}), 314, "evaluate returns correct value (precompiled)");
+}
+</script>
+</head>
+<body>
+</body>
+</html>
\ No newline at end of file
Index: tests/Filter/Comparison.html
===================================================================
--- tests/Filter/Comparison.html	(revision 9361)
+++ tests/Filter/Comparison.html	(working copy)
@@ -26,46 +26,24 @@
         t.plan(4);
         
         var filter = new OpenLayers.Filter.Comparison({
-                property: "foo",
-                value: "*b?r\\*\\?*",
+                lhs: new OpenLayers.Expression.PropertyName("foo"),
+                rhs: new OpenLayers.Expression.Literal("*b?r\\*\\?*"),
                 type: OpenLayers.Filter.Comparison.LIKE});
-        filter.value2regex("*", "?", "\\");
-        t.eq(filter.value, ".*b.r\\*\\?.*", "Regular expression generated correctly.");
         
-        filter.value = "%b.r!%!.%";
-        filter.value2regex("%", ".", "!");
-        t.eq(filter.value, ".*b.r\\%\\..*", "Regular expression with different wildcard and escape chars generated correctly.");
+        var re = filter.value2regex("*", "?", "\\", "*b?r\\*\\?*");
+        t.eq(re, ".*b.r\\*\\?.*", "Regular expression generated correctly.");
+        
+        re = filter.value2regex("%", ".", "!", "%b.r!%!.%");
+        t.eq(re, ".*b.r%\\..*", "Regular expression with different wildcard and escape chars generated correctly.");
     
-        filter.value = "!!";
-        filter.value2regex();
-        t.eq(filter.value, "\\!", "!! successfully unescaped to \\!");
+        re = filter.value2regex(null, null, null, "!!");
+        t.eq(re, "!", "!! successfully unescaped to !");
         
         // Big one.
-        filter.value = "!!c!!!d!e";
-        filter.value2regex();
-        t.eq(filter.value, "\\!c\\!\\d\\e", "!!c!!!d!e successfully unescaped to \\!c\\!\\d\\e");
+        re = filter.value2regex(null, null, null, "!!c!!!d!e");
+        t.eq(re, "!c!de", "!!c!!!d!e successfully unescaped to !c!de");
     }
-    
-    function test_regex2value(t) {
-        t.plan(8);
-        
-        function r2v(regex) {
-            return OpenLayers.Filter.Comparison.prototype.regex2value.call(
-                {value: regex}
-            );
-        }
-        
-        t.eq(r2v("foo"), "foo", "doesn't change string without special chars");
-        t.eq(r2v("foo.*foo"), "foo*foo", "wildCard replaced");
-        t.eq(r2v("foo.foo"), "foo.foo", "singleChar replaced");
-        t.eq(r2v("foo\\\\foo"), "foo\\foo", "escape removed");
-        t.eq(r2v("foo!foo"), "foo!!foo", "escapes !");
-        t.eq(r2v("foo\\*foo"), "foo!*foo", "replaces escape on *");
-        t.eq(r2v("foo\\.foo"), "foo!.foo", "replaces escape on .");
-        t.eq(r2v("foo\\\\.foo"), "foo\\.foo", "unescapes only \\ before .");
-        
-    }
-    
+
     function test_evaluate(t) {
         
         var cases = [{
@@ -166,12 +144,14 @@
             expect: false
         }];
         
-        t.plan(cases.length);
+        t.plan(cases.length*2);
         
         var c;
         for(var i=0; i<cases.length; ++i) {
             c = cases[i];
             t.eq(c.filter.evaluate(c.context), c.expect, "case " + i + ": " + c.filter.type);
+            c.filter.preCompile();
+            t.eq(c.filter.evaluate(c.context), c.expect, "case " + i + ": " + c.filter.type + " (precompiled)");
         }
         
     }
Index: tests/Filter/FeatureId.html
===================================================================
--- tests/Filter/FeatureId.html	(revision 9361)
+++ tests/Filter/FeatureId.html	(working copy)
@@ -23,7 +23,7 @@
     }
     
     function test_evaluate(t) {
-        t.plan(3);
+        t.plan(6);
         
         var filter = new OpenLayers.Filter.FeatureId(
                 {fids: ["fid_1", "fid_3"]});
@@ -39,6 +39,17 @@
             t.eq(result, filterResults[i], "feature "+i+" evaluates to "+result.toString()+" correctly.");
             feature.destroy();
         }
+
+        filter.preCompile();
+
+        for (var i in filterResults) {
+            var feature = new OpenLayers.Feature.Vector();
+            feature.fid = i;
+            var result = filter.evaluate(feature);
+            t.eq(result, filterResults[i], "feature "+i+" evaluates to "+result.toString()+" correctly.");
+            feature.destroy();
+        }
+        
     }
 
     function test_clone(t) {
@@ -52,9 +63,9 @@
         var clone = filter.clone();
         
         // modify the original
-        filter.fids.push(4);
+        filter.fids[4]=null;
         
-        t.eq(clone.fids.length, 3, "clone has proper fids length");
+        t.ok(!('4' in clone.fids), "clone is not affected by changes to original");
         
         filter.destroy();
 
Index: tests/Filter/Logical.html
===================================================================
--- tests/Filter/Logical.html	(revision 9361)
+++ tests/Filter/Logical.html	(working copy)
@@ -27,7 +27,7 @@
         
         var filter = new OpenLayers.Filter.Logical({
                 type: OpenLayers.Filter.Logical.NOT});
-        filter.filters.push(new OpenLayers.Filter());
+        filter.filters.push(OpenLayers.Filter.Logical.TRUE);
         
         var feature = new OpenLayers.Feature.Vector();
 
Index: tests/Format/Expression.html
===================================================================
--- tests/Format/Expression.html	(revision 0)
+++ tests/Format/Expression.html	(revision 0)
@@ -0,0 +1,31 @@
+<html> 
+<head> 
+    <script src="../../lib/OpenLayers.js"></script> 
+    <script type="text/javascript">
+
+    function test_initialize(t) { 
+        t.plan(8); 
+         
+        var options = {'foo': 'bar'}; 
+        var format = new OpenLayers.Format.Expression(); 
+        t.ok(format instanceof OpenLayers.Format.Expression, 
+             "new OpenLayers.Format.Expression returns object" ); 
+        t.eq(typeof format.read, "function", "format has a read function"); 
+        var expr = format.read(
+                "<Literal xmlns='http://www.opengis.net/ogc'>314</Literal>");
+        t.ok(expr != null, "result of read() is not null");
+        t.ok(expr instanceof OpenLayers.Expression.Literal,
+                "read returns OpenLayers.Expression.Literal for input document");
+        t.eq(expr.evaluate(), 314, "expression evaluates to correct value");
+        expr = format.read("<Literal>314</Literal>");
+        t.ok(expr == null, "format ignores wrong namespace");
+        expr = format.read("<Add xmlns='http://www.opengis.net/ogc'><Literal>300</Literal><Literal>33</Literal></Add>");
+        t.ok(expr != null, "format reads more complicated expression");
+        t.eq(expr.evaluate(), 333, "expression evaluates to correct value");
+    }
+
+    </script> 
+</head> 
+<body> 
+</body> 
+</html> 
Index: tests/Format/Filter.html
===================================================================
--- tests/Format/Filter.html	(revision 9361)
+++ tests/Format/Filter.html	(working copy)
@@ -11,7 +11,7 @@
         t.ok(format instanceof OpenLayers.Format.Filter, 
              "new OpenLayers.Format.Filter returns object" ); 
         t.eq(format.foo, "bar", "constructor sets options correctly"); 
-        t.eq(typeof format.read, "function", "format has a read function"); 
+        t.eq(typeof format.read, "function", "format has a read function");
     }
 
     </script> 
Index: tests/Format/Filter/v1.html
===================================================================
--- tests/Format/Filter/v1.html	(revision 9361)
+++ tests/Format/Filter/v1.html	(working copy)
@@ -34,8 +34,7 @@
         t.xml_eq(node, str, "filter correctly written");
         
         // test reading
-        var doc = (new OpenLayers.Format.XML).read(str);
-        var got = format.read(doc.firstChild);
+        var got = format.read(str);
         t.eq(got.type, filter.type, "read correct type");
         t.eq(got.property, filter.property, "read correct property");
         t.geom_eq(got.value, filter.value, "read correct value");
Index: tests/Format/Filter/v1_0_0.html
===================================================================
--- tests/Format/Filter/v1_0_0.html	(revision 9361)
+++ tests/Format/Filter/v1_0_0.html	(working copy)
@@ -144,8 +144,7 @@
         t.xml_eq(node, str, "filter correctly written");
         
         // test reading
-        var doc = (new OpenLayers.Format.XML).read(str);
-        var got = format.read(doc.firstChild);
+        var got = format.read(str);
         t.eq(got.type, filter.type, "read correct type");
         t.eq(got.property, filter.property, "read correct property");
         t.eq(got.value.toArray(), filter.value.toArray(), "read correct value");
Index: tests/Format/Filter/v1_1_0.html
===================================================================
--- tests/Format/Filter/v1_1_0.html	(revision 9361)
+++ tests/Format/Filter/v1_1_0.html	(working copy)
@@ -25,7 +25,7 @@
                         '<ogc:Literal>5000</ogc:Literal>' +
                     '</ogc:PropertyIsLessThanOrEqualTo>' +
                 '</ogc:Not>' +
-                '<ogc:PropertyIsEqualTo matchCase="true">' +
+                '<ogc:PropertyIsEqualTo>' +
                     '<ogc:PropertyName>cat</ogc:PropertyName>' +
                     '<ogc:Literal>dog</ogc:Literal>' +
                 '</ogc:PropertyIsEqualTo>' +
Index: tests/Format/SLD.html
===================================================================
--- tests/Format/SLD.html	(revision 9361)
+++ tests/Format/SLD.html	(working copy)
@@ -26,7 +26,7 @@
         t.eq(userStyles[0].name, "foo", "SLD correctly reads a UserStyle named 'foo'");
         t.eq(userStyles[0].rules.length, 1, "The number of rules for the UserStyle is correct");
         t.eq(userStyles[0].rules[0].name, "bar", "The first rule's name is 'bar'");
-        t.eq(userStyles[0].rules[0].symbolizer.Polygon.fillColor, "blue", "The fillColor for the Polygon symbolizer is correct");
+        t.eq(userStyles[0].rules[0].symbolizer.Polygon.fillColor.evaluate(), "blue", "The fillColor for the Polygon symbolizer is correct");
     }
 
     </script> 
Index: tests/Format/SLD/v1_0_0.html
===================================================================
--- tests/Format/SLD/v1_0_0.html	(revision 9361)
+++ tests/Format/SLD/v1_0_0.html	(working copy)
@@ -125,7 +125,7 @@
         '</StyledLayerDescriptor>';
 
     function test_read(t) {
-        t.plan(22);
+        t.plan(21);
         
         var xml = new OpenLayers.Format.XML();
         var sldxml = xml.read(sld);
@@ -165,15 +165,15 @@
         var symbolizer = rule.symbolizer;
         t.ok(symbolizer, "(AAA161) first rule has a symbolizer");
         var poly = symbolizer["Polygon"];
-        t.eq(poly.fillColor, "#ffffff", "(AAA161) first rule has proper fill");
-        t.eq(poly.strokeColor, "#000000", "(AAA161) first rule has proper stroke");
+        t.eq(poly.fillColor.evaluate(), "#ffffff", "(AAA161) first rule has proper fill");
+        t.eq(poly.strokeColor.evaluate(), "#000000", "(AAA161) first rule has proper stroke");
         var text = symbolizer["Text"];
-        t.eq(text.label, "${FOO}", "(AAA161) first rule has proper text label");
-        t.eq(layer.userStyles[0].propertyStyles["label"], true, "label added to propertyStyles");
-        t.eq(text.fontFamily, "Arial", "(AAA161) first rule has proper font family");
-        t.eq(text.fillColor, "#000000", "(AAA161) first rule has proper text fill");
-        t.eq(text.haloRadius, "3", "(AAA161) first rule has proper halo radius");
-        t.eq(text.haloColor, "#ffffff", "(AAA161) first rule has proper halo color");
+        t.eq(text.label.components[0].name, "FOO", "(AAA161) first rule has proper text label");
+        text = symbolizer["Text"];
+        t.eq(text.fontFamily.evaluate(), "Arial", "(AAA161) first rule has proper font family");
+        t.eq(text.fillColor.evaluate(), "#000000", "(AAA161) first rule has proper text fill");
+        t.eq(text.haloRadius.evaluate(), 3, "(AAA161) first rule has proper halo radius");
+        t.eq(text.haloColor.evaluate(), "#ffffff", "(AAA161) first rule has proper halo color");
         
         
         // check the first user style
@@ -302,7 +302,12 @@
         var parser = new OpenLayers.Format.SLD.v1_0_0();
         var symbolizer = {
             "Text": {
-                "label": "This is the ${city} in ${state}.",
+                "label": new OpenLayers.ParameterValue([
+                    "This is the ",
+                    new OpenLayers.Expression.PropertyName("city"),
+                    " in ",
+                    new OpenLayers.Expression.PropertyName("state"),
+                    "."]),
                 "fontFamily": "Arial",
                 "fontSize": 10,
                 "fillColor": "blue",
@@ -355,6 +360,7 @@
         var rule = new OpenLayers.Rule({
             name: "test",
             filter: new OpenLayers.Filter.Spatial({
+                property: "",
                 type: OpenLayers.Filter.Spatial.BBOX,
                 value: new OpenLayers.Bounds(0, 0, 10, 10)
             })
@@ -380,6 +386,62 @@
         
     }
 
+    var dynamicSld = 
+        "<Rule xmlns='http://www.opengis.net/sld' xmlns:ogc='http://www.opengis.net/ogc'>" +
+		    "<Name>test</Name>" +
+		    "<TextSymbolizer>" +
+		        "<Label>" +
+		            "<ogc:Add>" +
+		                "<ogc:PropertyName>foo</ogc:PropertyName>" +
+		                "<ogc:Literal>10</ogc:Literal>" +
+		            "</ogc:Add>" +
+		        "</Label>" +
+		    "</TextSymbolizer>" +
+		"</Rule>";
+ 
+    function test_readDynamicSymbolizer(t) {
+        t.plan(5);
+        var format = new OpenLayers.Format.SLD.v1_0_0();
+
+        var rule = format.readNode(
+            (new OpenLayers.Format.XML()).read(dynamicSld).documentElement,
+            {rules: []}
+        ).rules[0];
+        
+        t.ok(rule instanceof OpenLayers.Rule,
+             "format returned OpenLayers.Rule");
+        t.ok(rule.symbolizer.Text != null, "rule has a text symbolizer");
+        var text = rule.symbolizer.Text;
+        t.eq(text.label.components.length, 1, "text label has one component");
+        t.ok(text.label.components[0] instanceof OpenLayers.Expression.Binary,
+                "first component is OpenLayers.Expression.Add");
+        var sym = rule.apply({foo: 32}, null, {}, "Text");
+        t.eq(sym.label, 42, "rule correctly applies");
+    }
+
+    function test_writeDynamicSymbolizer(t) {
+        t.plan(1);
+        var format = new OpenLayers.Format.SLD.v1_0_0();
+        var rule = new OpenLayers.Rule({
+            name: "test",
+            symbolizer: {Text:{}}
+        });
+        
+        rule.symbolizer.Text.label = new OpenLayers.ParameterValue([
+            new OpenLayers.Expression.Binary(
+                "+",
+                new OpenLayers.Expression.PropertyName("foo"),
+                new OpenLayers.Expression.Literal(10)
+            )
+        ]);
+
+        var node = format.writeNode("sld:Rule", rule);
+
+        t.xml_eq(node,
+                 dynamicSld,
+                 "rule with dynamic symbolizer correctly written");
+    }
+
     </script> 
 </head> 
 <body>
Index: tests/list-tests.html
===================================================================
--- tests/list-tests.html	(revision 9361)
+++ tests/list-tests.html	(working copy)
@@ -36,6 +36,10 @@
     <li>Control/Split.html</li>
     <li>Control/WMSGetFeatureInfo.html</li>
     <li>Events.html</li>
+    <li>Expression/Binary.html</li>
+    <li>Expression/Literal.html</li>
+    <li>Expression/PropertyName.html</li>
+    <li>Expression/Function.html</li>
     <li>Extras.html</li>
     <li>Feature.html</li>
     <li>Feature/Vector.html</li>
@@ -59,6 +63,7 @@
     <li>Format/Text.html</li>
     <li>Format/SLD.html</li>
     <li>Format/SLD/v1_0_0.html</li>
+    <li>Format/Expression.html</li>
     <li>Format/Filter.html</li>
     <li>Format/Filter/v1.html</li>
     <li>Format/Filter/v1_0_0.html</li>
Index: tests/Style.html
===================================================================
--- tests/Style.html	(revision 9361)
+++ tests/Style.html	(working copy)
@@ -267,7 +267,7 @@
         t.eq(style.createSymbolizer(feature).externalGraphic, "foo10.png", "correctly evaluated symbolizer without rule");
     };
         
-    function test_Style_findPropertyStyles(t) {
+    function test_Style_createPropertyNames(t) {
         t.plan(4);
         var rule1 = new OpenLayers.Rule({symbolizer: {
             pointRadius: 3,
@@ -280,12 +280,15 @@
             strokeOpacity: 1,
             strokeColor: "${foo}"
         });
-        style.addRules([rule1, rule2]);
-        var propertyStyles = style.findPropertyStyles();
-        t.ok(propertyStyles.externalGraphic, "detected externalGraphic from rule correctly");
-        t.ok(propertyStyles.strokeWidth, "detected strokeWidth from Point symbolizer correctly");
-        t.ok(propertyStyles.strokeColor, "detected strokeColor from style correctly");
-        t.eq(typeof propertyStyles.pointRadius, "undefined", "correctly detected pointRadius as non-property style");
+
+        t.ok(rule1.symbolizer.externalGraphic instanceof OpenLayers.ParameterValue,
+                "detected externalGraphic from rule correctly");
+        t.ok(rule2.symbolizer.Point.strokeWidth instanceof OpenLayers.ParameterValue,
+                "detected strokeWidth from Point symbolizer correctly");
+        t.ok(style.defaultStyle.strokeColor instanceof OpenLayers.ParameterValue,
+                "detected strokeColor from style correctly");
+        t.eq(typeof rule1.symbolizer.pointRadius, "number",
+                "correctly detected pointRadius as non-property style");
     }
     
     function test_createLiteral(t) {
