Index: /trunk/data_nodist/mapcss/relation.mapcss
===================================================================
--- /trunk/data_nodist/mapcss/relation.mapcss	(revision 4069)
+++ /trunk/data_nodist/mapcss/relation.mapcss	(revision 4069)
@@ -0,0 +1,12 @@
+meta {
+    title: "MapCSS tests for relations";
+}
+
+relation[test=role1] >[outer] way[test=role1_outer] { width: 4; color: navy; }
+
+relation[test=role1] >[role=inner][index=5] way { width: 4; color: lime; }
+
+area[landuse=quarry][test=parent] { fill-color : #aaa; }
+
+way[barrier=fence] < area[landuse=quarry][test=parent] { fill-color : #f70; }
+
Index: /trunk/data_nodist/mapcss/relation.osm
===================================================================
--- /trunk/data_nodist/mapcss/relation.osm	(revision 4069)
+++ /trunk/data_nodist/mapcss/relation.osm	(revision 4069)
@@ -0,0 +1,151 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version='0.6' generator='JOSM'>
+  <node id='-473' action='modify' timestamp='2011-03-09T17:19:29Z' visible='true' lat='41.7555767086837' lon='-93.6101935942561'>
+    <tag k='name' v='parent selector test' />
+  </node>
+  <node id='-470' action='modify' timestamp='2011-03-09T17:19:29Z' visible='true' lat='41.7576219697712' lon='-93.61021039502646'>
+    <tag k='name' v='role test' />
+  </node>
+  <node id='-455' action='modify' visible='true' lat='41.75449259481888' lon='-93.60684960580662' />
+  <node id='-454' action='modify' visible='true' lat='41.75444121700621' lon='-93.60735137701909' />
+  <node id='-453' action='modify' visible='true' lat='41.75491829511318' lon='-93.60713492669213' />
+  <node id='-452' action='modify' visible='true' lat='41.75430910244193' lon='-93.6062888026868' />
+  <node id='-451' action='modify' visible='true' lat='41.75422836562984' lon='-93.6078433095803' />
+  <node id='-450' action='modify' visible='true' lat='41.75530729463742' lon='-93.60710541073846' />
+  <node id='-437' visible='true' lat='41.75449626466106' lon='-93.60896491581994' />
+  <node id='-435' visible='true' lat='41.754444886851324' lon='-93.60946668703241' />
+  <node id='-434' visible='true' lat='41.754921964931036' lon='-93.60925023670545' />
+  <node id='-430' visible='true' lat='41.75431277229459' lon='-93.60840411270013' />
+  <node id='-428' visible='true' lat='41.754232035487156' lon='-93.60995861959364' />
+  <node id='-427' visible='true' lat='41.755310964433036' lon='-93.60922072075178' />
+  <node id='-388' action='modify' timestamp='2011-03-09T17:19:29Z' visible='true' lat='41.75556159626758' lon='-93.60468857775984' />
+  <node id='-384' action='modify' timestamp='2011-03-09T17:19:29Z' visible='true' lat='41.75400341768608' lon='-93.61030933581502' />
+  <node id='-377' action='modify' timestamp='2011-03-09T17:19:29Z' visible='true' lat='41.75550331915773' lon='-93.61033714240742' />
+  <node id='-372' action='modify' timestamp='2011-03-09T17:19:29Z' visible='true' lat='41.75406169615781' lon='-93.60466077116747' />
+  <node id='-320' action='modify' visible='true' lat='41.75677444751439' lon='-93.60775757557353' />
+  <node id='-315' action='modify' visible='true' lat='41.756833867460706' lon='-93.61006910764685' />
+  <node id='-313' action='modify' visible='true' lat='41.75637001463961' lon='-93.60956536870414' />
+  <node id='-294' visible='true' lat='41.75710047122399' lon='-93.60873191196228' />
+  <node id='-291' visible='true' lat='41.75677019798795' lon='-93.60818094749371' />
+  <node id='-289' visible='true' lat='41.75686561043072' lon='-93.60860400949636' />
+  <node id='-287' visible='true' lat='41.75671148256805' lon='-93.60939110159434' />
+  <node id='-285' visible='true' lat='41.756953683328916' lon='-93.6096075519213' />
+  <node id='-284' visible='true' lat='41.75704175610625' lon='-93.60927303777963' />
+  <node id='-280' visible='true' lat='41.75730597371307' lon='-93.60890900768433' />
+  <node id='-278' visible='true' lat='41.757232580042505' lon='-93.60840723647188' />
+  <node id='-276' visible='true' lat='41.75704175610625' lon='-93.60797433581799' />
+  <node id='-274' visible='true' lat='41.756916986302684' lon='-93.60787594930574' />
+  <node id='-272' visible='true' lat='41.75660873045396' lon='-93.60777756279349' />
+  <node id='-270' visible='true' lat='41.756344509976664' lon='-93.60827933400596' />
+  <node id='-268' visible='true' lat='41.7563591889206' lon='-93.60905658745268' />
+  <node id='-266' visible='true' lat='41.75651331762931' lon='-93.60989287280681' />
+  <node id='-264' visible='true' lat='41.7570931318372' lon='-93.60997158201661' />
+  <node id='-263' visible='true' lat='41.75732065243712' lon='-93.60947964945538' />
+  <node id='-260' action='modify' timestamp='2011-03-09T17:19:29Z' visible='true' lat='41.75601873272152' lon='-93.61036053636113' />
+  <node id='-259' action='modify' timestamp='2011-03-09T17:19:29Z' visible='true' lat='41.756077009363366' lon='-93.60471197171358' />
+  <node id='-258' action='modify' timestamp='2011-03-09T17:19:29Z' visible='true' lat='41.757576862377114' lon='-93.60473977830597' />
+  <node id='-257' action='modify' timestamp='2011-03-09T17:19:29Z' visible='true' lat='41.75751858709716' lon='-93.61038834295353' />
+  <way id='-449' action='modify' visible='true'>
+    <nd ref='-453' />
+    <nd ref='-454' />
+    <nd ref='-455' />
+    <nd ref='-453' />
+  </way>
+  <way id='-448' action='modify' visible='true'>
+    <nd ref='-450' />
+    <nd ref='-451' />
+    <nd ref='-452' />
+    <nd ref='-450' />
+    <tag k='barrier' v='fence' />
+  </way>
+  <way id='-436' action='modify' visible='true'>
+    <nd ref='-434' />
+    <nd ref='-435' />
+    <nd ref='-437' />
+    <nd ref='-434' />
+  </way>
+  <way id='-429' action='modify' visible='true'>
+    <nd ref='-427' />
+    <nd ref='-428' />
+    <nd ref='-430' />
+    <nd ref='-427' />
+  </way>
+  <way id='-397' action='modify' timestamp='2011-03-09T17:19:29Z' visible='true'>
+    <nd ref='-377' />
+    <nd ref='-388' />
+    <nd ref='-372' />
+    <nd ref='-384' />
+    <nd ref='-377' />
+  </way>
+  <way id='-322' visible='true'>
+    <nd ref='-320' />
+    <nd ref='-274' />
+    <nd ref='-276' />
+    <nd ref='-278' />
+    <nd ref='-280' />
+  </way>
+  <way id='-317' visible='true'>
+    <nd ref='-315' />
+    <nd ref='-266' />
+    <nd ref='-313' />
+    <nd ref='-268' />
+    <tag k='test' v='role1_outer' />
+  </way>
+  <way id='-309' action='modify' visible='true'>
+    <nd ref='-268' />
+    <nd ref='-270' />
+    <nd ref='-272' />
+    <nd ref='-320' />
+  </way>
+  <way id='-295' action='modify' visible='true'>
+    <nd ref='-291' />
+    <nd ref='-294' />
+    <nd ref='-284' />
+  </way>
+  <way id='-286' action='modify' visible='true'>
+    <nd ref='-284' />
+    <nd ref='-285' />
+    <nd ref='-287' />
+    <nd ref='-289' />
+    <nd ref='-291' />
+  </way>
+  <way id='-265' action='modify' visible='true'>
+    <nd ref='-280' />
+    <nd ref='-263' />
+    <nd ref='-264' />
+    <nd ref='-315' />
+    <tag k='test' v='role1_outer' />
+  </way>
+  <way id='-256' action='modify' timestamp='2011-03-09T17:19:29Z' visible='true'>
+    <nd ref='-257' />
+    <nd ref='-258' />
+    <nd ref='-259' />
+    <nd ref='-260' />
+    <nd ref='-257' />
+  </way>
+  <relation id='-457' action='modify' visible='true'>
+    <member type='way' ref='-448' role='outer' />
+    <member type='way' ref='-449' role='inner' />
+    <tag k='landuse' v='quarry' />
+    <tag k='test' v='parent' />
+    <tag k='type' v='multipolygon' />
+  </relation>
+  <relation id='-456' action='modify' visible='true'>
+    <member type='way' ref='-429' role='outer' />
+    <member type='way' ref='-436' role='inner' />
+    <tag k='landuse' v='quarry' />
+    <tag k='test' v='parent' />
+    <tag k='type' v='multipolygon' />
+  </relation>
+  <relation id='-297' action='modify' visible='true'>
+    <member type='way' ref='-317' role='outer' />
+    <member type='way' ref='-265' role='' />
+    <member type='way' ref='-322' role='' />
+    <member type='way' ref='-309' role='outer' />
+    <member type='way' ref='-295' role='inner' />
+    <member type='way' ref='-286' role='inner' />
+    <tag k='landuse' v='meadow' />
+    <tag k='test' v='role1' />
+    <tag k='type' v='multipolygon' />
+  </relation>
+</osm>
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/Environment.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/Environment.java	(revision 4068)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/Environment.java	(revision 4069)
@@ -2,23 +2,38 @@
 package org.openstreetmap.josm.gui.mappaint;
 
-import java.util.Collection;
-import java.util.List;
-
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Context;
+import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.LinkSelector;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
 
 public class Environment {
 
     public OsmPrimitive osm;
+
     public MultiCascade mc;
     public String layer;
     public StyleSource source;
+    private Context context = Context.PRIMITIVE;
 
     /**
-     * <p>When matching a child selector, the matching referrers will be stored.
-     * They can be accessed in {@link Instruction#execute(Environment)} to access
-     * tags from parent objects.</p>
+     * If not null, this is the matching parent object if an condition or an expression
+     * is evaluated in a {@link LinkSelector} (within a child selector)
      */
-    private List<OsmPrimitive> matchingReferrers = null;
+    public OsmPrimitive parent;
+    /**
+     * The same for parent selector. Only one of the 2 fields (parent or child) is not null in any environment.
+     */
+    public OsmPrimitive child;
+
+    /**
+     * index of node in parent way or member in parent relation. Must be != null in LINK context.
+     */
+    public Integer index = null;
+
+    /**
+     * Creates a new uninitialized environment
+     */
+    public Environment() {}
 
     public Environment(OsmPrimitive osm, MultiCascade mc, String layer, StyleSource source) {
@@ -29,14 +44,89 @@
     }
 
-    public Collection<OsmPrimitive> getMatchingReferrers() {
-        return matchingReferrers;
+    /**
+     * Creates a clone of the environment {@code other}
+     * 
+     * @param other the other environment. Must not be null.
+     */
+    public Environment(Environment other) throws IllegalArgumentException{
+        CheckParameterUtil.ensureParameterNotNull(other);
+        this.osm = other.osm;
+        this.mc = other.mc;
+        this.layer = other.layer;
+        this.parent = other.parent;
+        this.child = other.child;
+        this.source = other.source;
+        this.index = other.index;
+        this.context = other.getContext();
     }
 
-    public void setMatchingReferrers(List<OsmPrimitive> refs) {
-        matchingReferrers = refs;
+    public Environment withPrimitive(OsmPrimitive osm) {
+        Environment e = new Environment(this);
+        e.osm = osm;
+        return e;
     }
 
-    public void clearMatchingReferrers() {
-        matchingReferrers = null;
+    public Environment withParent(OsmPrimitive parent) {
+        Environment e = new Environment(this);
+        e.parent = parent;
+        return e;
+    }
+
+    public Environment withChild(OsmPrimitive child) {
+        Environment e = new Environment(this);
+        e.child = child;
+        return e;
+    }
+
+    public Environment withIndex(int index) {
+        Environment e = new Environment(this);
+        e.index = index;
+        return e;
+    }
+
+    public Environment withContext(Context context) {
+        Environment e = new Environment(this);
+        e.context = context == null ? Context.PRIMITIVE : context;
+        return e;
+    }
+
+    public Environment withLinkContext() {
+        Environment e = new Environment(this);
+        e.context = Context.LINK;
+        return e;
+    }
+
+    public boolean isLinkContext() {
+        return Context.LINK.equals(context);
+    }
+
+    public boolean hasParentRelation() {
+        return parent != null && parent instanceof Relation;
+    }
+
+    /**
+     * Replies the current context.
+     * 
+     * @return the current context
+     */
+    public Context getContext() {
+        return context == null ? Context.PRIMITIVE : context;
+    }
+
+    public String getRole() {
+        if (getContext().equals(Context.PRIMITIVE))
+            return null;
+
+        if (parent != null && parent instanceof Relation)
+            return ((Relation) parent).getMember(index).getRole();
+        if (child != null && osm instanceof Relation)
+            return ((Relation) osm).getMember(index).getRole();
+        return null;
+    }
+
+    public void clearSelectorMatchingInformation() {
+        parent = null;
+        child = null;
+        index = null;
     }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java	(revision 4068)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java	(revision 4069)
@@ -4,4 +4,5 @@
 import static org.openstreetmap.josm.tools.Utils.equal;
 
+import java.text.MessageFormat;
 import java.util.EnumSet;
 import java.util.regex.Matcher;
@@ -14,5 +15,5 @@
 import org.openstreetmap.josm.gui.mappaint.Cascade;
 import org.openstreetmap.josm.gui.mappaint.Environment;
-import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Op;
+import org.openstreetmap.josm.tools.Utils;
 
 abstract public class Condition {
@@ -20,10 +21,121 @@
     abstract public boolean applies(Environment e);
 
-    public static enum Op { EQ, NEQ, GREATER_OR_EQUAL, GREATER, LESS_OR_EQUAL, LESS,
-        REGEX, ONE_OF, BEGINS_WITH, ENDS_WITH, CONTAINS }
+    public static Condition create(String k, String v, Op op, Context context) {
+        switch (context) {
+        case PRIMITIVE:
+            return new KeyValueCondition(k, v, op);
+        case LINK:
+            if ("role".equalsIgnoreCase(k))
+                return new RoleCondition(v, op);
+            else if ("index".equalsIgnoreCase(k))
+                return new IndexCondition(v, op);
+            else
+                throw new MapCSSException(
+                        MessageFormat.format("Expected key ''role'' or ''index'' in link context. Got ''{0}''.", k));
+
+        default: throw new AssertionError();
+        }
+    }
+
+    public static Condition create(String k, boolean not, boolean yes, Context context) {
+        switch (context) {
+        case PRIMITIVE:
+            return new KeyCondition(k, not, yes);
+        case LINK:
+            if (yes)
+                throw new MapCSSException("Question mark operator ''?'' not supported in LINK context");
+            if (not)
+                return new RoleCondition(k, Op.NEQ);
+            else
+                return new RoleCondition(k, Op.EQ);
+
+        default: throw new AssertionError();
+        }
+    }
+
+    public static Condition create(String id, boolean not, Context context) {
+        return new PseudoClassCondition(id, not);
+    }
+
+    public static Condition create(Expression e, Context context) {
+        return new ExpressionCondition(e);
+    }
+
+    public static enum Op {
+        EQ, NEQ, GREATER_OR_EQUAL, GREATER, LESS_OR_EQUAL, LESS,
+        REGEX, ONE_OF, BEGINS_WITH, ENDS_WITH, CONTAINS;
+
+        public boolean eval(String testString, String prototypeString) {
+            if (testString == null && this != NEQ)
+                return false;
+            switch (this) {
+            case EQ:
+                return equal(testString, prototypeString);
+            case NEQ:
+                return !equal(testString, prototypeString);
+            case REGEX:
+                Pattern p = Pattern.compile(prototypeString);
+                Matcher m = p.matcher(testString);
+                return m.find();
+            case ONE_OF:
+                String[] parts = testString.split(";");
+                for (String part : parts) {
+                    if (equal(prototypeString, part.trim()))
+                        return true;
+                }
+                return false;
+            case BEGINS_WITH:
+                return testString.startsWith(prototypeString);
+            case ENDS_WITH:
+                return testString.endsWith(prototypeString);
+            case CONTAINS:
+                return testString.contains(prototypeString);
+            }
+
+            float test_float;
+            try {
+                test_float = Float.parseFloat(testString);
+            } catch (NumberFormatException e) {
+                return false;
+            }
+            float prototype_float = Float.parseFloat(prototypeString);
+
+            switch (this) {
+            case GREATER_OR_EQUAL:
+                return test_float >= prototype_float;
+            case GREATER:
+                return test_float > prototype_float;
+            case LESS_OR_EQUAL:
+                return test_float <= prototype_float;
+            case LESS:
+                return test_float < prototype_float;
+            default:
+                throw new AssertionError();
+            }
+        }
+    }
+
+    /**
+     * context, where the condition applies
+     */
+    public static enum Context {
+        /**
+         * normal primitive selector, e.g. way[highway=residential]
+         */
+        PRIMITIVE,
+
+        /**
+         * link between primitives, e.g. relation >[role=outer] way
+         */
+        LINK
+    }
 
     public final static EnumSet<Op> COMPARISON_OPERATERS =
-            EnumSet.of(Op.GREATER_OR_EQUAL, Op.GREATER, Op.LESS_OR_EQUAL, Op.LESS);
-
+        EnumSet.of(Op.GREATER_OR_EQUAL, Op.GREATER, Op.LESS_OR_EQUAL, Op.LESS);
+
+    /**
+     * <p>Represents a key/value condition which is either applied to a primitive.</p>
+     * 
+     */
     public static class KeyValueCondition extends Condition {
 
@@ -31,61 +143,21 @@
         public String v;
         public Op op;
-        private float v_float;
-
+
+        /**
+         * <p>Creates a key/value-condition.</p>
+         * 
+         * @param k the key
+         * @param v the value
+         * @param op the operation
+         */
         public KeyValueCondition(String k, String v, Op op) {
             this.k = k;
             this.v = v;
             this.op = op;
-            if (COMPARISON_OPERATERS.contains(op)) {
-                v_float = Float.parseFloat(v);
-            }
         }
 
         @Override
         public boolean applies(Environment env) {
-            String val = env.osm.get(k);
-            if (val == null && op != Op.NEQ)
-                return false;
-            switch (op) {
-                case EQ:
-                    return equal(val, v);
-                case NEQ:
-                    return !equal(val, v);
-                case REGEX:
-                    Pattern p = Pattern.compile(v);
-                    Matcher m = p.matcher(val);
-                    return m.find();
-                case ONE_OF:
-                    String[] parts = val.split(";");
-                    for (String part : parts) {
-                        if (equal(v, part.trim()))
-                            return true;
-                    }
-                    return false;
-                case BEGINS_WITH:
-                    return val.startsWith(v);
-                case ENDS_WITH:
-                    return val.endsWith(v);
-                case CONTAINS:
-                    return val.contains(v);
-            }
-            float val_float;
-            try {
-                val_float = Float.parseFloat(val);
-            } catch (NumberFormatException e) {
-                return false;
-            }
-            switch (op) {
-                case GREATER_OR_EQUAL:
-                    return val_float >= v_float;
-                case GREATER:
-                    return val_float > v_float;
-                case LESS_OR_EQUAL:
-                    return val_float <= v_float;
-                case LESS:
-                    return val_float < v_float;
-                default:
-                    throw new AssertionError();
-            }
+            return op.eval(env.osm.get(k), v);
         }
 
@@ -96,32 +168,94 @@
     }
 
+    public static class RoleCondition extends Condition {
+        public String role;
+        public Op op;
+
+        public RoleCondition(String role, Op op) {
+            this.role = role;
+            this.op = op;
+        }
+
+        @Override
+        public boolean applies(Environment env) {
+            String testRole = env.getRole();
+            if (testRole == null) return false;
+            return op.eval(testRole, role);
+        }
+    }
+
+    public static class IndexCondition extends Condition {
+        public String index;
+        public Op op;
+
+        public IndexCondition(String index, Op op) {
+            this.index = index;
+            this.op = op;
+        }
+
+        @Override
+        public boolean applies(Environment env) {
+            if (env.index == null) return false;
+            return op.eval(Integer.toString(env.index + 1), index);
+        }
+    }
+
+    /**
+     * <p>KeyCondition represent one of the following conditions in either the link or the
+     * primitive context:</p>
+     * <pre>
+     *     ["a label"]  PRIMITIVE:   the primitive has a tag "a label"
+     *                  LINK:        the parent is a relation and it has at least one member with the role
+     *                               "a label" referring to the child
+     * 
+     *     [!"a label"]  PRIMITIVE:  the primitive doesn't have a tag "a label"
+     *                   LINK:       the parent is a relation but doesn't have a member with the role
+     *                               "a label" referring to the child
+     *
+     *     ["a label"?]  PRIMITIVE:  the primitive has a tag "a label" whose value evaluates to a true-value
+     *                   LINK:       not supported
+     * </pre>
+     */
     public static class KeyCondition extends Condition {
 
-        private String k;
-        private boolean not;
-        private boolean yes;
-
-        public KeyCondition(String k, boolean not, boolean yes) {
-            this.k = k;
-            this.not = not;
-            this.yes = yes;
+        private String label;
+        private boolean exclamationMarkPresent;
+        private boolean questionMarkPresent;
+
+        /**
+         * 
+         * @param label
+         * @param exclamationMarkPresent
+         * @param questionMarkPresent
+         */
+        public KeyCondition(String label, boolean exclamationMarkPresent, boolean questionMarkPresent){
+            this.label = label;
+            this.exclamationMarkPresent = exclamationMarkPresent;
+            this.questionMarkPresent = questionMarkPresent;
         }
 
         @Override
         public boolean applies(Environment e) {
-            if (yes)
-                return OsmUtils.isTrue(e.osm.get(k)) ^ not;
-            else
-                return e.osm.hasKey(k) ^ not;
+            switch(e.getContext()) {
+            case PRIMITIVE:
+                if (questionMarkPresent)
+                    return OsmUtils.isTrue(e.osm.get(label)) ^ exclamationMarkPresent;
+                else
+                    return e.osm.hasKey(label) ^ exclamationMarkPresent;
+            case LINK:
+                Utils.ensure(false, "Illegal state: KeyCondition not supported in LINK context");
+                return false;
+            default: throw new AssertionError();
+            }
         }
 
         @Override
         public String toString() {
-            return "[" + (not ? "!" : "") + k + "]";
+            return "[" + (exclamationMarkPresent ? "!" : "") + label + "]";
         }
     }
 
     public static class PseudoClassCondition extends Condition {
-        
+
         String id;
         boolean not;
@@ -144,13 +278,12 @@
                     return true;
                 return false;
-            } else if (equal(id, "modified")) {
+            } else if (equal(id, "modified"))
                 return e.osm.isModified() || e.osm.isNewOrUndeleted();
-            } else if (equal(id, "new")) {
+            else if (equal(id, "new"))
                 return e.osm.isNew();
-            } else if (equal(id, "connection") && (e.osm instanceof Node)) {
+            else if (equal(id, "connection") && (e.osm instanceof Node))
                 return ((Node) e.osm).isConnectionNode();
-            } else if (equal(id, "tagged")) {
+            else if (equal(id, "tagged"))
                 return e.osm.isTagged();
-            }
             return true;
         }
@@ -161,5 +294,5 @@
         }
     }
-    
+
     public static class ExpressionCondition extends Condition {
 
@@ -181,4 +314,3 @@
         }
     }
-
 }
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Expression.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Expression.java	(revision 4068)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Expression.java	(revision 4069)
@@ -174,5 +174,5 @@
 
             public String parent_tag(String key) {
-                if (env.getMatchingReferrers() == null) {
+                if (env.parent == null) {
                     // we don't have a matched parent, so just search all referrers
                     for (OsmPrimitive parent : env.osm.getReferrers()) {
@@ -183,13 +183,19 @@
                     return null;
                 }
-                if (env.getMatchingReferrers().isEmpty())
-                    return null;
-                // use always the first matching referrer to have consistency
-                // in an expression and declaration block
-                return env.getMatchingReferrers().iterator().next().get(key);
+                return env.parent.get(key);
             }
 
             public boolean has_tag_key(String key) {
                 return env.osm.hasKey(key);
+            }
+
+            public Float index() {
+                if (env.index == null)
+                    return null;
+                return new Float(env.index + 1);
+            }
+
+            public String role() {
+                return env.getRole();
             }
 
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSException.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSException.java	(revision 4069)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSException.java	(revision 4069)
@@ -0,0 +1,28 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint.mapcss;
+
+public class MapCSSException extends RuntimeException {
+
+    protected String specialmessage;
+    protected Integer line;
+    protected Integer column;
+    
+    public MapCSSException(String specialmessage) {
+        this.specialmessage = specialmessage;
+    }
+
+    public void setColumn(int column) {
+        this.column = column;
+    }
+
+    public void setLine(int line) {
+        this.line = line;
+    }
+    
+    @Override
+    public String getMessage() {
+        if (line == null || column == null)
+            return specialmessage;
+        return String.format("Error at line %s, column %s: %s", line, column, specialmessage);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java	(revision 4068)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java	(revision 4069)
@@ -5,4 +5,5 @@
 
 import java.awt.Color;
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -24,4 +25,5 @@
 import org.openstreetmap.josm.gui.preferences.SourceEntry;
 import org.openstreetmap.josm.io.MirroredInputStream;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.LanguageInfo;
 import org.openstreetmap.josm.tools.Utils;
@@ -32,4 +34,5 @@
     final public List<MapCSSRule> rules;
     private Color backgroundColorOverride;
+    private String css = null;
 
     public MapCSSStyleSource(String url, String name, String shortdescription) {
@@ -40,4 +43,18 @@
     public MapCSSStyleSource(SourceEntry entry) {
         super(entry);
+        rules = new ArrayList<MapCSSRule>();
+    }
+
+    /**
+     * <p>Creates a new style source from the MapCSS styles supplied in
+     * {@code css}</p>
+     * 
+     * @param css the MapCSS style declaration. Must not be null.
+     * @throws IllegalArgumentException thrown if {@code css} is null
+     */
+    public MapCSSStyleSource(String css) throws IllegalArgumentException{
+        super(null, null, null);
+        CheckParameterUtil.ensureParameterNotNull(css);
+        this.css = css;
         rules = new ArrayList<MapCSSRule>();
     }
@@ -69,4 +86,7 @@
     @Override
     public InputStream getSourceInputStream() throws IOException {
+        if (css != null)
+            return new ByteArrayInputStream(css.getBytes("UTF-8"));
+
         MirroredInputStream in = new MirroredInputStream(url);
         InputStream zip = in.getZipEntry("mapcss", "style");
@@ -114,6 +134,5 @@
                 if ((s instanceof GeneralSelector)) {
                     GeneralSelector gs = (GeneralSelector) s;
-                    if (gs.base.equals(type))
-                     {
+                    if (gs.base.equals(type)) {
                         for (Condition cnd : gs.conds) {
                             if (!cnd.applies(env))
@@ -140,6 +159,6 @@
         for (MapCSSRule r : rules) {
             for (Selector s : r.selectors) {
-                env.clearMatchingReferrers();
-                if (s.matches(env)) { // as side effect env.matchingReferrers will be set (if s is a child selector)
+                env.clearSelectorMatchingInformation();
+                if (s.matches(env)) { // as side effect env.parent will be set (if s is a child selector)
                     if (s.getRange().contains(scale)) {
                         mc.range = Range.cut(mc.range, s.getRange());
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java	(revision 4068)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java	(revision 4069)
@@ -2,5 +2,4 @@
 package org.openstreetmap.josm.gui.mappaint.mapcss;
 
-import java.util.ArrayList;
 import java.util.List;
 
@@ -9,4 +8,5 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
 import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.gui.mappaint.Environment;
@@ -46,5 +46,8 @@
      */
     public static class ChildOrParentSelector implements Selector {
-        Selector a, b;
+        //static private final Logger logger = Logger.getLogger(ChildOrParentSelector.class.getName());
+        private final Selector left;
+        private final LinkSelector link;
+        private final Selector right;
         /** true, if this represents a parent selector (otherwise it is a child selector)
          */
@@ -57,7 +60,8 @@
          * @param parentSelector if true, this is a parent selector; otherwise a child selector
          */
-        public ChildOrParentSelector(Selector a, Selector b, boolean parentSelector) {
-            this.a = a;
-            this.b = b;
+        public ChildOrParentSelector(Selector a, LinkSelector link, Selector b, boolean parentSelector) {
+            this.left = a;
+            this.link = link;
+            this.right = b;
             this.parentSelector = parentSelector;
         }
@@ -65,32 +69,61 @@
         @Override
         public boolean matches(Environment e) {
-            if (!b.matches(e))
+            if (!right.matches(e))
                 return false;
 
-            Environment e2 = new Environment(null, e.mc, e.layer, e.source);
-            List<OsmPrimitive> matchingRefs = new ArrayList<OsmPrimitive>();
             if (!parentSelector) {
                 for (OsmPrimitive ref : e.osm.getReferrers()) {
-                    e2.osm = ref;
-                    if (a.matches(e2)) {
-                        matchingRefs.add(ref);
+                    if (!left.matches(e.withPrimitive(ref)))
+                        continue;
+                    if (ref instanceof Way) {
+                        List<Node> wayNodes = ((Way) ref).getNodes();
+                        for (int i=0; i<wayNodes.size(); i++) {
+                            if (wayNodes.get(i).equals(e.osm)) {
+                                if (link.matches(e.withParent(ref).withIndex(i).withLinkContext())) {
+                                    e.parent = ref;
+                                    e.index = i;
+                                    return true;
+                                }
+                            }
+                        }
+                    } else if (ref instanceof Relation) {
+                        List<RelationMember> members = ((Relation) ref).getMembers();
+                        for (int i=0; i<members.size(); i++) {
+                            RelationMember m = members.get(i);
+                            if (m.getMember().equals(e.osm)) {
+                                if (link.matches(e.withParent(ref).withIndex(i).withLinkContext())) {
+                                    e.parent = ref;
+                                    e.index = i;
+                                    return true;
+                                }
+                            }
+                        }
                     }
                 }
-                if (!matchingRefs.isEmpty()) {
-                    e.setMatchingReferrers(matchingRefs);
-                    return true;
-                }
             } else {
-                if (e.osm instanceof Relation) {
-                    for (OsmPrimitive chld : ((Relation) e.osm).getMemberPrimitives()) {
-                        e2.osm = chld;
-                        if (a.matches(e2))
-                            return true;
+                if (e.osm instanceof Way) {
+                    List<Node> wayNodes = ((Way) e.osm).getNodes();
+                    for (int i=0; i<wayNodes.size(); i++) {
+                        Node n = wayNodes.get(i);
+                        if (left.matches(e.withPrimitive(n))) {
+                            if (link.matches(e.withChild(n).withIndex(i).withLinkContext())) {
+                                e.child = n;
+                                e.index = i;
+                                return true;
+                            }
+                        }
                     }
-                } else if (e.osm instanceof Way) {
-                    for (Node n : ((Way) e.osm).getNodes()) {
-                        e2.osm = n;
-                        if (a.matches(e2))
-                            return true;
+                }
+                else if (e.osm instanceof Relation) {
+                    List<RelationMember> members = ((Relation) e.osm).getMembers();
+                    for (int i=0; i<members.size(); i++) {
+                        OsmPrimitive member = members.get(i).getMember();
+                        if (left.matches(e.withPrimitive(member))) {
+                            if (link.matches(e.withChild(member).withIndex(i).withLinkContext())) {
+                                e.child = member;
+                                e.index = i;
+                                return true;
+                            }
+                        }
                     }
                 }
@@ -101,10 +134,47 @@
         @Override
         public String getSubpart() {
-            return b.getSubpart();
+            return right.getSubpart();
         }
 
         @Override
         public Range getRange() {
-            return b.getRange();
+            return right.getRange();
+        }
+
+        @Override
+        public String toString() {
+            return left +" "+ (parentSelector? "<" : ">")+link+" " +right;
+        }
+    }
+
+    public static class LinkSelector implements Selector {
+        protected List<Condition> conditions;
+
+        public LinkSelector(List<Condition> conditions) {
+            this.conditions = conditions;
+        }
+
+        @Override
+        public boolean matches(Environment env) {
+            Utils.ensure(env.isLinkContext(), "Requires LINK context in environment, got ''{0}''", env.getContext());
+            for (Condition c: conditions) {
+                if (!c.applies(env)) return false;
+            }
+            return true;
+        }
+
+        @Override
+        public String getSubpart() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public Range getRange() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public String toString() {
+            return "LinkSelector{" + "conditions=" + conditions + '}';
         }
     }
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/parser/MapCSSParser.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/parser/MapCSSParser.java	(revision 4068)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/parser/MapCSSParser.java	(revision 4069)
@@ -8,4 +8,5 @@
 import org.openstreetmap.josm.gui.mappaint.Keyword;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Condition;
+import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Context;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Expression;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction;
@@ -15,6 +16,8 @@
 import org.openstreetmap.josm.gui.mappaint.mapcss.Expression.FunctionExpression;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Expression.LiteralExpression;
+import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSException;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
+import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.LinkSelector;
 import org.openstreetmap.josm.tools.Pair;
 
@@ -237,9 +240,16 @@
       }
       try {
-        r = rule();
-                       if (r != null) { sheet.rules.add(r); }
+        /*            try {*/
+                        r = rule();
+                           if (r != null) { sheet.rules.add(r); }
         w();
+      } catch (MapCSSException mex) {
+            error_skipto(RBRACE, mex);
+            w();
+/*                throw new ParseException(mex.getMessage());*/
+/*            }*/
+
       } catch (ParseException ex) {
-            error_skipto(RBRACE);
+            error_skipto(RBRACE, null);
             w();
       }
@@ -276,6 +286,10 @@
   final public Selector child_selector() throws ParseException {
     boolean parentSelector = false;
-    Selector sel1, sel2 = null;
-    sel1 = selector();
+    Condition c;
+    List<Condition> conditions = new ArrayList<Condition>();
+    Selector selLeft;
+    LinkSelector selLink = null;
+    Selector selRight = null;
+    selLeft = selector();
     w();
     switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
@@ -296,13 +310,41 @@
         throw new ParseException();
       }
+      label_5:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case LSQUARE:
+        case EXCLAMATION:
+        case COLON:
+          ;
+          break;
+        default:
+          jj_la1[11] = jj_gen;
+          break label_5;
+        }
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case LSQUARE:
+          c = condition(Context.LINK);
+          break;
+        case EXCLAMATION:
+        case COLON:
+          c = pseudoclass(Context.LINK);
+          break;
+        default:
+          jj_la1[12] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                                                                        conditions.add(c);
+      }
+          selLink = new LinkSelector(conditions);
       w();
-      sel2 = selector();
+      selRight = selector();
       w();
       break;
     default:
-      jj_la1[11] = jj_gen;
+      jj_la1[13] = jj_gen;
       ;
     }
-      {if (true) return sel2 != null ? new ChildOrParentSelector(sel1, sel2, parentSelector) : sel1;}
+      {if (true) return selRight != null ? new ChildOrParentSelector(selLeft, selLink, selRight, parentSelector) : selLeft;}
     throw new Error("Missing return statement in function");
   }
@@ -322,5 +364,5 @@
       break;
     default:
-      jj_la1[12] = jj_gen;
+      jj_la1[14] = jj_gen;
       jj_consume_token(-1);
       throw new ParseException();
@@ -331,8 +373,8 @@
       break;
     default:
-      jj_la1[13] = jj_gen;
+      jj_la1[15] = jj_gen;
       ;
     }
-    label_5:
+    label_6:
     while (true) {
       switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
@@ -343,21 +385,21 @@
         break;
       default:
-        jj_la1[14] = jj_gen;
-        break label_5;
+        jj_la1[16] = jj_gen;
+        break label_6;
       }
       switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
       case LSQUARE:
-        c = condition();
+        c = condition(Context.PRIMITIVE);
         break;
       case EXCLAMATION:
       case COLON:
-        c = pseudoclass();
+        c = pseudoclass(Context.PRIMITIVE);
         break;
       default:
-        jj_la1[15] = jj_gen;
+        jj_la1[17] = jj_gen;
         jj_consume_token(-1);
         throw new ParseException();
       }
-                                            conditions.add(c);
+                                                                              conditions.add(c);
     }
     switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
@@ -366,5 +408,5 @@
       break;
     default:
-      jj_la1[16] = jj_gen;
+      jj_la1[18] = jj_gen;
       ;
     }
@@ -392,15 +434,15 @@
           break;
         default:
-          jj_la1[17] = jj_gen;
+          jj_la1[19] = jj_gen;
           ;
         }
         break;
       default:
-        jj_la1[18] = jj_gen;
+        jj_la1[20] = jj_gen;
         ;
       }
       break;
     default:
-      jj_la1[19] = jj_gen;
+      jj_la1[21] = jj_gen;
       jj_consume_token(-1);
       throw new ParseException();
@@ -410,5 +452,5 @@
   }
 
-  final public Condition condition() throws ParseException {
+  final public Condition condition(Context context) throws ParseException {
     Condition c;
     Expression e;
@@ -416,13 +458,13 @@
     s();
     if (jj_2_1(2147483647)) {
-      c = simple_key_condition();
+      c = simple_key_condition(context);
       s();
       jj_consume_token(RSQUARE);
-                                                     {if (true) return c;}
+                                                            {if (true) return c;}
     } else if (jj_2_2(2147483647)) {
-      c = simple_key_value_condition();
+      c = simple_key_value_condition(context);
       s();
       jj_consume_token(RSQUARE);
-                                                           {if (true) return c;}
+                                                                  {if (true) return c;}
     } else {
       switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
@@ -438,8 +480,8 @@
         e = expression();
         jj_consume_token(RSQUARE);
-                                       {if (true) return new Condition.ExpressionCondition(e);}
+                                       {if (true) return Condition.create(e, context);}
         break;
       default:
-        jj_la1[20] = jj_gen;
+        jj_la1[22] = jj_gen;
         jj_consume_token(-1);
         throw new ParseException();
@@ -460,5 +502,5 @@
       t = jj_consume_token(IDENT);
                     s = t.image;
-      label_6:
+      label_7:
       while (true) {
         switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
@@ -467,6 +509,6 @@
           break;
         default:
-          jj_la1[21] = jj_gen;
-          break label_6;
+          jj_la1[23] = jj_gen;
+          break label_7;
         }
         jj_consume_token(COLON);
@@ -477,5 +519,5 @@
       break;
     default:
-      jj_la1[22] = jj_gen;
+      jj_la1[24] = jj_gen;
       jj_consume_token(-1);
       throw new ParseException();
@@ -484,5 +526,5 @@
   }
 
-  final public Condition simple_key_condition() throws ParseException {
+  final public Condition simple_key_condition(Context context) throws ParseException {
     boolean not = false;
     boolean yes = false;
@@ -494,5 +536,5 @@
       break;
     default:
-      jj_la1[23] = jj_gen;
+      jj_la1[25] = jj_gen;
       ;
     }
@@ -504,12 +546,12 @@
       break;
     default:
-      jj_la1[24] = jj_gen;
+      jj_la1[26] = jj_gen;
       ;
     }
-      {if (true) return new Condition.KeyCondition(key, not, yes);}
+      {if (true) return Condition.create(key, not, yes, context);}
     throw new Error("Missing return statement in function");
   }
 
-  final public Condition simple_key_value_condition() throws ParseException {
+  final public Condition simple_key_value_condition(Context context) throws ParseException {
     String key;
     String val;
@@ -564,5 +606,5 @@
           break;
         default:
-          jj_la1[25] = jj_gen;
+          jj_la1[27] = jj_gen;
           jj_consume_token(-1);
           throw new ParseException();
@@ -585,5 +627,5 @@
             break;
           default:
-            jj_la1[26] = jj_gen;
+            jj_la1[28] = jj_gen;
             jj_consume_token(-1);
             throw new ParseException();
@@ -613,5 +655,5 @@
           break;
         default:
-          jj_la1[27] = jj_gen;
+          jj_la1[29] = jj_gen;
           jj_consume_token(-1);
           throw new ParseException();
@@ -622,14 +664,14 @@
         break;
       default:
-        jj_la1[28] = jj_gen;
+        jj_la1[30] = jj_gen;
         jj_consume_token(-1);
         throw new ParseException();
       }
     }
-      {if (true) return new Condition.KeyValueCondition(key, val, op);}
+      {if (true) return Condition.create(key, val, op, context);}
     throw new Error("Missing return statement in function");
   }
 
-  final public Condition pseudoclass() throws ParseException {
+  final public Condition pseudoclass(Context context) throws ParseException {
     Token t;
     boolean not = false;
@@ -640,10 +682,10 @@
       break;
     default:
-      jj_la1[29] = jj_gen;
+      jj_la1[31] = jj_gen;
       ;
     }
     jj_consume_token(COLON);
     t = jj_consume_token(IDENT);
-      {if (true) return new Condition.PseudoClassCondition(t.image, not);}
+      {if (true) return Condition.create(t.image, not, context);}
     throw new Error("Missing return statement in function");
   }
@@ -660,5 +702,5 @@
       break;
     default:
-      jj_la1[30] = jj_gen;
+      jj_la1[32] = jj_gen;
       jj_consume_token(-1);
       throw new ParseException();
@@ -675,5 +717,5 @@
     jj_consume_token(LBRACE);
     w();
-    label_7:
+    label_8:
     while (true) {
       switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
@@ -682,6 +724,6 @@
         break;
       default:
-        jj_la1[31] = jj_gen;
-        break label_7;
+        jj_la1[33] = jj_gen;
+        break label_8;
       }
       key = jj_consume_token(IDENT);
@@ -703,5 +745,5 @@
           break;
         default:
-          jj_la1[32] = jj_gen;
+          jj_la1[34] = jj_gen;
           jj_consume_token(-1);
           throw new ParseException();
@@ -720,5 +762,5 @@
           break;
         default:
-          jj_la1[33] = jj_gen;
+          jj_la1[35] = jj_gen;
           jj_consume_token(-1);
           throw new ParseException();
@@ -781,5 +823,5 @@
         switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
         case PLUS:
-          label_8:
+          label_9:
           while (true) {
             jj_consume_token(PLUS);
@@ -794,11 +836,11 @@
               break;
             default:
-              jj_la1[34] = jj_gen;
-              break label_8;
+              jj_la1[36] = jj_gen;
+              break label_9;
             }
           }
           break;
         case STAR:
-          label_9:
+          label_10:
           while (true) {
             jj_consume_token(STAR);
@@ -813,11 +855,11 @@
               break;
             default:
-              jj_la1[35] = jj_gen;
-              break label_9;
+              jj_la1[37] = jj_gen;
+              break label_10;
             }
           }
           break;
         case MINUS:
-          label_10:
+          label_11:
           while (true) {
             jj_consume_token(MINUS);
@@ -832,11 +874,11 @@
               break;
             default:
-              jj_la1[36] = jj_gen;
-              break label_10;
+              jj_la1[38] = jj_gen;
+              break label_11;
             }
           }
           break;
         case SLASH:
-          label_11:
+          label_12:
           while (true) {
             jj_consume_token(SLASH);
@@ -851,6 +893,6 @@
               break;
             default:
-              jj_la1[37] = jj_gen;
-              break label_11;
+              jj_la1[39] = jj_gen;
+              break label_12;
             }
           }
@@ -887,5 +929,5 @@
             break;
           default:
-            jj_la1[38] = jj_gen;
+            jj_la1[40] = jj_gen;
             ;
           }
@@ -936,5 +978,5 @@
           break;
         default:
-          jj_la1[39] = jj_gen;
+          jj_la1[41] = jj_gen;
           jj_consume_token(-1);
           throw new ParseException();
@@ -942,10 +984,10 @@
         break;
       default:
-        jj_la1[40] = jj_gen;
+        jj_la1[42] = jj_gen;
         ;
       }
       break;
     default:
-      jj_la1[41] = jj_gen;
+      jj_la1[43] = jj_gen;
       jj_consume_token(-1);
       throw new ParseException();
@@ -984,5 +1026,5 @@
         break;
       default:
-        jj_la1[42] = jj_gen;
+        jj_la1[44] = jj_gen;
         jj_consume_token(-1);
         throw new ParseException();
@@ -1014,5 +1056,5 @@
       arg = expression();
                            args.add(arg);
-      label_12:
+      label_13:
       while (true) {
         switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
@@ -1021,6 +1063,6 @@
           break;
         default:
-          jj_la1[43] = jj_gen;
-          break label_12;
+          jj_la1[45] = jj_gen;
+          break label_13;
         }
         jj_consume_token(COMMA);
@@ -1031,5 +1073,5 @@
       break;
     default:
-      jj_la1[44] = jj_gen;
+      jj_la1[46] = jj_gen;
       ;
     }
@@ -1073,5 +1115,5 @@
       break;
     default:
-      jj_la1[45] = jj_gen;
+      jj_la1[47] = jj_gen;
       jj_consume_token(-1);
       throw new ParseException();
@@ -1080,12 +1122,23 @@
   }
 
-  void error_skipto(int kind) throws ParseException {
+  void error_skipto(int kind, MapCSSException me) throws ParseException {
     if (token.kind == EOF)
         throw new ParseException("Reached end of file while parsing");
-    ParseException e = generateParseException();
+
+    Exception e = null;
+    ParseException pe = generateParseException();
+
+    if (me != null) {
+        me.setLine(pe.currentToken.next.beginLine);
+        me.setColumn(pe.currentToken.next.beginColumn);
+        e = me;
+    } else {
+        e = new ParseException(pe.getMessage()); // prevent memory leak
+    }
+
     System.err.println("Skipping to the next rule, because of an error:");
     System.err.println(e);
     if (sheet != null) {
-        sheet.logError(new ParseException(e.getMessage()));
+        sheet.logError(e);
     }
     Token t;
@@ -1171,17 +1224,36 @@
   }
 
-  private boolean jj_3R_41() {
-    if (jj_scan_token(CARET)) return true;
-    if (jj_scan_token(EQUAL)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_40() {
-    if (jj_scan_token(TILDE)) return true;
-    if (jj_scan_token(EQUAL)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_14() {
+  private boolean jj_3R_25() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_39()) {
+    jj_scanpos = xsp;
+    if (jj_3R_40()) {
+    jj_scanpos = xsp;
+    if (jj_3R_41()) {
+    jj_scanpos = xsp;
+    if (jj_3R_42()) {
+    jj_scanpos = xsp;
+    if (jj_3R_43()) {
+    jj_scanpos = xsp;
+    if (jj_3R_44()) return true;
+    }
+    }
+    }
+    }
+    }
+    if (jj_3R_15()) return true;
+    xsp = jj_scanpos;
+    if (jj_3_3()) {
+    jj_scanpos = xsp;
+    if (jj_3R_45()) {
+    jj_scanpos = xsp;
+    if (jj_3R_46()) return true;
+    }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_15() {
     Token xsp;
     xsp = jj_scanpos;
@@ -1190,73 +1262,11 @@
   }
 
-  private boolean jj_3R_59() {
-    if (jj_3R_28()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_39() {
-    if (jj_scan_token(EQUAL)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_38() {
-    if (jj_scan_token(EXCLAMATION)) return true;
-    if (jj_scan_token(EQUAL)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_37() {
-    if (jj_scan_token(REGEX)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_24() {
-    Token xsp;
-    xsp = jj_scanpos;
-    if (jj_3R_38()) {
-    jj_scanpos = xsp;
-    if (jj_3R_39()) {
-    jj_scanpos = xsp;
-    if (jj_3R_40()) {
-    jj_scanpos = xsp;
-    if (jj_3R_41()) {
-    jj_scanpos = xsp;
-    if (jj_3R_42()) {
-    jj_scanpos = xsp;
-    if (jj_3R_43()) return true;
-    }
-    }
-    }
-    }
-    }
-    if (jj_3R_14()) return true;
-    xsp = jj_scanpos;
-    if (jj_3_3()) {
-    jj_scanpos = xsp;
-    if (jj_3R_44()) {
-    jj_scanpos = xsp;
-    if (jj_3R_45()) return true;
-    }
-    }
-    return false;
-  }
-
-  private boolean jj_3R_56() {
+  private boolean jj_3R_60() {
+    if (jj_3R_29()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_57() {
     if (jj_scan_token(COLON)) return true;
-    if (jj_scan_token(IDENT)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_57() {
-    Token xsp;
-    xsp = jj_scanpos;
-    if (jj_3R_74()) {
-    jj_scanpos = xsp;
-    if (jj_3R_75()) return true;
-    }
-    return false;
-  }
-
-  private boolean jj_3R_74() {
     if (jj_scan_token(IDENT)) return true;
     return false;
@@ -1266,60 +1276,58 @@
     if (jj_scan_token(EQUAL)) return true;
     if (jj_scan_token(TILDE)) return true;
-    if (jj_3R_14()) return true;
-    if (jj_3R_37()) return true;
+    if (jj_3R_15()) return true;
+    if (jj_3R_38()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_86() {
+    if (jj_scan_token(HEXCOLOR)) return true;
     return false;
   }
 
   private boolean jj_3R_85() {
-    if (jj_scan_token(HEXCOLOR)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_27() {
-    if (jj_3R_51()) return true;
+    if (jj_3R_29()) return true;
     return false;
   }
 
   private boolean jj_3R_84() {
-    if (jj_3R_28()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_83() {
     if (jj_scan_token(PLUS)) return true;
-    if (jj_3R_28()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_15() {
-    if (jj_3R_22()) return true;
-    if (jj_3R_14()) return true;
+    if (jj_3R_29()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_38() {
+    if (jj_scan_token(REGEX)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_16() {
+    if (jj_3R_23()) return true;
+    if (jj_3R_15()) return true;
     Token xsp;
     xsp = jj_scanpos;
     if (jj_3_4()) {
     jj_scanpos = xsp;
-    if (jj_3R_24()) {
-    jj_scanpos = xsp;
-    if (jj_3R_25()) return true;
-    }
-    }
+    if (jj_3R_25()) {
+    jj_scanpos = xsp;
+    if (jj_3R_26()) return true;
+    }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_83() {
+    if (jj_3R_56()) return true;
     return false;
   }
 
   private boolean jj_3R_82() {
-    if (jj_3R_55()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_81() {
     if (jj_scan_token(IDENT)) return true;
     return false;
   }
 
-  private boolean jj_3R_76() {
+  private boolean jj_3R_77() {
     Token xsp;
     xsp = jj_scanpos;
-    if (jj_3R_81()) {
-    jj_scanpos = xsp;
     if (jj_3R_82()) {
     jj_scanpos = xsp;
@@ -1328,71 +1336,157 @@
     if (jj_3R_84()) {
     jj_scanpos = xsp;
-    if (jj_3R_85()) return true;
-    }
-    }
-    }
-    }
-    return false;
-  }
-
-  private boolean jj_3R_55() {
+    if (jj_3R_85()) {
+    jj_scanpos = xsp;
+    if (jj_3R_86()) return true;
+    }
+    }
+    }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_58() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_75()) {
+    jj_scanpos = xsp;
+    if (jj_3R_76()) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_75() {
+    if (jj_scan_token(IDENT)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_28() {
+    if (jj_3R_52()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_24() {
+    if (jj_scan_token(QUESTION)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_22() {
+    if (jj_scan_token(EXCLAMATION)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_87() {
+    if (jj_scan_token(COMMA)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_20()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_14() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_22()) jj_scanpos = xsp;
+    if (jj_3R_23()) return true;
+    xsp = jj_scanpos;
+    if (jj_3R_24()) jj_scanpos = xsp;
+    return false;
+  }
+
+  private boolean jj_3R_56() {
     if (jj_scan_token(STRING)) return true;
     return false;
   }
 
-  private boolean jj_3R_23() {
-    if (jj_scan_token(QUESTION)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_50() {
-    Token xsp;
-    xsp = jj_scanpos;
-    if (jj_3R_58()) {
-    jj_scanpos = xsp;
-    if (jj_3R_59()) return true;
-    }
-    return false;
-  }
-
-  private boolean jj_3R_58() {
-    if (jj_scan_token(MINUS)) return true;
-    if (jj_3R_28()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_21() {
-    if (jj_scan_token(EXCLAMATION)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_86() {
-    if (jj_scan_token(COMMA)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_19()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_13() {
-    Token xsp;
-    xsp = jj_scanpos;
-    if (jj_3R_21()) jj_scanpos = xsp;
-    if (jj_3R_22()) return true;
-    xsp = jj_scanpos;
-    if (jj_3R_23()) jj_scanpos = xsp;
-    return false;
-  }
-
-  private boolean jj_3R_34() {
-    if (jj_3R_19()) return true;
+  private boolean jj_3R_35() {
+    if (jj_3R_20()) return true;
     Token xsp;
     while (true) {
       xsp = jj_scanpos;
-      if (jj_3R_86()) { jj_scanpos = xsp; break; }
-    }
-    return false;
-  }
-
-  private boolean jj_3R_28() {
+      if (jj_3R_87()) { jj_scanpos = xsp; break; }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_37() {
+    if (jj_scan_token(IDENT)) return true;
+    Token xsp;
+    while (true) {
+      xsp = jj_scanpos;
+      if (jj_3R_57()) { jj_scanpos = xsp; break; }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_36() {
+    if (jj_3R_56()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_23() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_36()) {
+    jj_scanpos = xsp;
+    if (jj_3R_37()) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_21() {
+    if (jj_scan_token(IDENT)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_scan_token(LPAR)) return true;
+    if (jj_3R_19()) return true;
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_35()) jj_scanpos = xsp;
+    if (jj_scan_token(RPAR)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_51() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_59()) {
+    jj_scanpos = xsp;
+    if (jj_3R_60()) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_59() {
+    if (jj_scan_token(MINUS)) return true;
+    if (jj_3R_29()) return true;
+    return false;
+  }
+
+  private boolean jj_3_2() {
+    if (jj_3R_16()) return true;
+    if (jj_3R_15()) return true;
+    if (jj_scan_token(RSQUARE)) return true;
+    return false;
+  }
+
+  private boolean jj_3_1() {
+    if (jj_3R_14()) return true;
+    if (jj_3R_15()) return true;
+    if (jj_scan_token(RSQUARE)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_62() {
+    if (jj_scan_token(LPAR)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_20()) return true;
+    if (jj_scan_token(RPAR)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_61() {
+    if (jj_3R_77()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_29() {
     Token xsp;
     xsp = jj_scanpos;
@@ -1404,193 +1498,147 @@
   }
 
-  private boolean jj_3R_36() {
-    if (jj_scan_token(IDENT)) return true;
-    Token xsp;
+  private boolean jj_3_7() {
+    if (jj_3R_21()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_54() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3_7()) {
+    jj_scanpos = xsp;
+    if (jj_3R_61()) {
+    jj_scanpos = xsp;
+    if (jj_3R_62()) return true;
+    }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_27() {
+    if (jj_scan_token(MINUS)) return true;
+    if (jj_3R_52()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_17() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_27()) {
+    jj_scanpos = xsp;
+    if (jj_3R_28()) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_74() {
+    if (jj_scan_token(QUESTION)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
+    if (jj_scan_token(COLON)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_73() {
+    if (jj_scan_token(PIPE)) return true;
+    if (jj_scan_token(PIPE)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_72() {
+    if (jj_scan_token(AMPERSAND)) return true;
+    if (jj_scan_token(AMPERSAND)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_71() {
+    if (jj_scan_token(LESS)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_52() {
+    if (jj_scan_token(UINT)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_70() {
+    if (jj_scan_token(EQUAL)) return true;
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(22)) jj_scanpos = xsp;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_69() {
+    if (jj_scan_token(GREATER)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_68() {
+    if (jj_scan_token(LESS_EQUAL)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_81() {
+    if (jj_scan_token(SLASH)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_67() {
+    if (jj_scan_token(GREATER_EQUAL)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_80() {
+    if (jj_scan_token(MINUS)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_66() {
+    Token xsp;
+    if (jj_3R_81()) return true;
     while (true) {
       xsp = jj_scanpos;
-      if (jj_3R_56()) { jj_scanpos = xsp; break; }
-    }
-    return false;
-  }
-
-  private boolean jj_3R_35() {
-    if (jj_3R_55()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_22() {
-    Token xsp;
-    xsp = jj_scanpos;
-    if (jj_3R_35()) {
-    jj_scanpos = xsp;
-    if (jj_3R_36()) return true;
-    }
-    return false;
-  }
-
-  private boolean jj_3R_20() {
-    if (jj_scan_token(IDENT)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_scan_token(LPAR)) return true;
-    if (jj_3R_18()) return true;
-    Token xsp;
-    xsp = jj_scanpos;
-    if (jj_3R_34()) jj_scanpos = xsp;
-    if (jj_scan_token(RPAR)) return true;
-    return false;
-  }
-
-  private boolean jj_3_2() {
-    if (jj_3R_15()) return true;
-    if (jj_3R_14()) return true;
-    if (jj_scan_token(RSQUARE)) return true;
-    return false;
-  }
-
-  private boolean jj_3_1() {
-    if (jj_3R_13()) return true;
-    if (jj_3R_14()) return true;
-    if (jj_scan_token(RSQUARE)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_26() {
-    if (jj_scan_token(MINUS)) return true;
-    if (jj_3R_51()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_16() {
-    Token xsp;
-    xsp = jj_scanpos;
-    if (jj_3R_26()) {
-    jj_scanpos = xsp;
-    if (jj_3R_27()) return true;
-    }
-    return false;
-  }
-
-  private boolean jj_3R_61() {
-    if (jj_scan_token(LPAR)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_19()) return true;
-    if (jj_scan_token(RPAR)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_60() {
-    if (jj_3R_76()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_51() {
-    if (jj_scan_token(UINT)) return true;
-    return false;
-  }
-
-  private boolean jj_3_7() {
-    if (jj_3R_20()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_53() {
-    Token xsp;
-    xsp = jj_scanpos;
-    if (jj_3_7()) {
-    jj_scanpos = xsp;
-    if (jj_3R_60()) {
-    jj_scanpos = xsp;
-    if (jj_3R_61()) return true;
-    }
-    }
-    return false;
-  }
-
-  private boolean jj_3R_73() {
-    if (jj_scan_token(QUESTION)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
-    if (jj_scan_token(COLON)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_72() {
-    if (jj_scan_token(PIPE)) return true;
-    if (jj_scan_token(PIPE)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_71() {
-    if (jj_scan_token(AMPERSAND)) return true;
-    if (jj_scan_token(AMPERSAND)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_70() {
-    if (jj_scan_token(LESS)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_69() {
-    if (jj_scan_token(EQUAL)) return true;
-    Token xsp;
-    xsp = jj_scanpos;
-    if (jj_scan_token(22)) jj_scanpos = xsp;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_68() {
-    if (jj_scan_token(GREATER)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_67() {
-    if (jj_scan_token(LESS_EQUAL)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_80() {
-    if (jj_scan_token(SLASH)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_66() {
-    if (jj_scan_token(GREATER_EQUAL)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
+      if (jj_3R_81()) { jj_scanpos = xsp; break; }
+    }
     return false;
   }
 
   private boolean jj_3R_79() {
-    if (jj_scan_token(MINUS)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
+    if (jj_scan_token(STAR)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
     return false;
   }
@@ -1607,8 +1655,8 @@
 
   private boolean jj_3R_78() {
-    if (jj_scan_token(STAR)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
+    if (jj_scan_token(PLUS)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
     return false;
   }
@@ -1624,9 +1672,41 @@
   }
 
-  private boolean jj_3R_77() {
-    if (jj_scan_token(PLUS)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
+  private boolean jj_3R_55() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_63()) {
+    jj_scanpos = xsp;
+    if (jj_3R_64()) {
+    jj_scanpos = xsp;
+    if (jj_3R_65()) {
+    jj_scanpos = xsp;
+    if (jj_3R_66()) {
+    jj_scanpos = xsp;
+    if (jj_3R_67()) {
+    jj_scanpos = xsp;
+    if (jj_3R_68()) {
+    jj_scanpos = xsp;
+    if (jj_3R_69()) {
+    jj_scanpos = xsp;
+    if (jj_3R_70()) {
+    jj_scanpos = xsp;
+    if (jj_3R_71()) {
+    jj_scanpos = xsp;
+    if (jj_3R_72()) {
+    jj_scanpos = xsp;
+    if (jj_3R_73()) {
+    jj_scanpos = xsp;
+    if (jj_3R_74()) return true;
+    }
+    }
+    }
+    }
+    }
+    }
+    }
+    }
+    }
+    }
+    }
     return false;
   }
@@ -1642,81 +1722,31 @@
   }
 
-  private boolean jj_3R_54() {
+  private boolean jj_3R_34() {
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
     Token xsp;
     xsp = jj_scanpos;
-    if (jj_3R_62()) {
-    jj_scanpos = xsp;
-    if (jj_3R_63()) {
-    jj_scanpos = xsp;
-    if (jj_3R_64()) {
-    jj_scanpos = xsp;
-    if (jj_3R_65()) {
-    jj_scanpos = xsp;
-    if (jj_3R_66()) {
-    jj_scanpos = xsp;
-    if (jj_3R_67()) {
-    jj_scanpos = xsp;
-    if (jj_3R_68()) {
-    jj_scanpos = xsp;
-    if (jj_3R_69()) {
-    jj_scanpos = xsp;
-    if (jj_3R_70()) {
-    jj_scanpos = xsp;
-    if (jj_3R_71()) {
-    jj_scanpos = xsp;
-    if (jj_3R_72()) {
-    jj_scanpos = xsp;
-    if (jj_3R_73()) return true;
-    }
-    }
-    }
-    }
-    }
-    }
-    }
-    }
-    }
-    }
-    }
-    return false;
-  }
-
-  private boolean jj_3R_62() {
-    Token xsp;
-    if (jj_3R_77()) return true;
-    while (true) {
-      xsp = jj_scanpos;
-      if (jj_3R_77()) { jj_scanpos = xsp; break; }
-    }
+    if (jj_3R_55()) jj_scanpos = xsp;
     return false;
   }
 
   private boolean jj_3R_33() {
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
-    Token xsp;
-    xsp = jj_scanpos;
-    if (jj_3R_54()) jj_scanpos = xsp;
+    if (jj_scan_token(MINUS)) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
     return false;
   }
 
   private boolean jj_3R_32() {
-    if (jj_scan_token(MINUS)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_31() {
     if (jj_scan_token(EXCLAMATION)) return true;
-    if (jj_3R_18()) return true;
-    if (jj_3R_53()) return true;
-    if (jj_3R_18()) return true;
+    if (jj_3R_19()) return true;
+    if (jj_3R_54()) return true;
+    if (jj_3R_19()) return true;
     return false;
   }
 
   private boolean jj_3_6() {
-    if (jj_3R_19()) return true;
+    if (jj_3R_20()) return true;
     Token xsp;
     xsp = jj_scanpos;
@@ -1728,12 +1758,12 @@
   }
 
-  private boolean jj_3R_19() {
+  private boolean jj_3R_20() {
     Token xsp;
     xsp = jj_scanpos;
-    if (jj_3R_31()) {
-    jj_scanpos = xsp;
     if (jj_3R_32()) {
     jj_scanpos = xsp;
-    if (jj_3R_33()) return true;
+    if (jj_3R_33()) {
+    jj_scanpos = xsp;
+    if (jj_3R_34()) return true;
     }
     }
@@ -1742,6 +1772,6 @@
 
   private boolean jj_3_5() {
-    if (jj_3R_17()) return true;
     if (jj_3R_18()) return true;
+    if (jj_3R_19()) return true;
     Token xsp;
     xsp = jj_scanpos;
@@ -1753,93 +1783,63 @@
   }
 
-  private boolean jj_3R_29() {
+  private boolean jj_3R_50() {
+    if (jj_scan_token(LESS)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_49() {
+    if (jj_scan_token(LESS_EQUAL)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_48() {
+    if (jj_scan_token(GREATER)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_46() {
+    if (jj_3R_58()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_47() {
+    if (jj_scan_token(GREATER_EQUAL)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_45() {
+    if (jj_3R_51()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_30() {
     if (jj_scan_token(COMMA)) return true;
-    if (jj_3R_14()) return true;
-    if (jj_3R_28()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_49() {
-    if (jj_scan_token(LESS)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_48() {
-    if (jj_scan_token(LESS_EQUAL)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_17() {
-    if (jj_3R_28()) return true;
-    Token xsp;
+    if (jj_3R_15()) return true;
     if (jj_3R_29()) return true;
-    while (true) {
-      xsp = jj_scanpos;
-      if (jj_3R_29()) { jj_scanpos = xsp; break; }
-    }
-    return false;
-  }
-
-  private boolean jj_3R_47() {
-    if (jj_scan_token(GREATER)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_45() {
-    if (jj_3R_57()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_46() {
-    if (jj_scan_token(GREATER_EQUAL)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_52() {
-    if (jj_scan_token(COMMENT_START)) return true;
-    if (jj_scan_token(COMMENT_END)) return true;
-    return false;
-  }
-
-  private boolean jj_3R_44() {
+    return false;
+  }
+
+  private boolean jj_3R_26() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_47()) {
+    jj_scanpos = xsp;
+    if (jj_3R_48()) {
+    jj_scanpos = xsp;
+    if (jj_3R_49()) {
+    jj_scanpos = xsp;
     if (jj_3R_50()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_75() {
-    if (jj_3R_55()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_30() {
-    Token xsp;
-    xsp = jj_scanpos;
-    if (jj_scan_token(9)) {
-    jj_scanpos = xsp;
-    if (jj_3R_52()) return true;
-    }
-    return false;
-  }
-
-  private boolean jj_3R_25() {
-    Token xsp;
-    xsp = jj_scanpos;
-    if (jj_3R_46()) {
-    jj_scanpos = xsp;
-    if (jj_3R_47()) {
-    jj_scanpos = xsp;
-    if (jj_3R_48()) {
-    jj_scanpos = xsp;
-    if (jj_3R_49()) return true;
-    }
-    }
-    }
-    if (jj_3R_14()) return true;
-    if (jj_3R_50()) return true;
+    }
+    }
+    }
+    if (jj_3R_15()) return true;
+    if (jj_3R_51()) return true;
     return false;
   }
 
   private boolean jj_3R_18() {
-    Token xsp;
+    if (jj_3R_29()) return true;
+    Token xsp;
+    if (jj_3R_30()) return true;
     while (true) {
       xsp = jj_scanpos;
@@ -1850,9 +1850,9 @@
 
   private boolean jj_3_3() {
-    if (jj_3R_16()) return true;
-    return false;
-  }
-
-  private boolean jj_3R_43() {
+    if (jj_3R_17()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_44() {
     if (jj_scan_token(STAR)) return true;
     if (jj_scan_token(EQUAL)) return true;
@@ -1860,6 +1860,59 @@
   }
 
+  private boolean jj_3R_53() {
+    if (jj_scan_token(COMMENT_START)) return true;
+    if (jj_scan_token(COMMENT_END)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_43() {
+    if (jj_scan_token(DOLLAR)) return true;
+    if (jj_scan_token(EQUAL)) return true;
+    return false;
+  }
+
   private boolean jj_3R_42() {
-    if (jj_scan_token(DOLLAR)) return true;
+    if (jj_scan_token(CARET)) return true;
+    if (jj_scan_token(EQUAL)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_41() {
+    if (jj_scan_token(TILDE)) return true;
+    if (jj_scan_token(EQUAL)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_76() {
+    if (jj_3R_56()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_31() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(9)) {
+    jj_scanpos = xsp;
+    if (jj_3R_53()) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_40() {
+    if (jj_scan_token(EQUAL)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_19() {
+    Token xsp;
+    while (true) {
+      xsp = jj_scanpos;
+      if (jj_3R_31()) { jj_scanpos = xsp; break; }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_39() {
+    if (jj_scan_token(EXCLAMATION)) return true;
     if (jj_scan_token(EQUAL)) return true;
     return false;
@@ -1877,5 +1930,5 @@
   private int jj_la;
   private int jj_gen;
-  final private int[] jj_la1 = new int[46];
+  final private int[] jj_la1 = new int[48];
   static private int[] jj_la1_0;
   static private int[] jj_la1_1;
@@ -1885,8 +1938,8 @@
    }
    private static void jj_la1_init_0() {
-      jj_la1_0 = new int[] {0x4,0xc,0xc,0x12,0x200,0x200,0x200,0x10000000,0x402,0x10000000,0x300000,0x300000,0x402,0x40000000,0x2804000,0x2804000,0x4000000,0x4,0x0,0x4,0x8081011e,0x2000000,0x12,0x800000,0x0,0x1c00400,0x1e,0x3c0000,0x1fc0400,0x800000,0x402,0x2,0x8002000,0x8002000,0x80000000,0x400,0x0,0x800,0x400000,0xa07c0c00,0xa07c0c00,0x8081011e,0x8001011e,0x10000000,0x8081011e,0x8000011e,};
+      jj_la1_0 = new int[] {0x4,0xc,0xc,0x12,0x200,0x200,0x200,0x10000000,0x402,0x10000000,0x300000,0x2804000,0x2804000,0x300000,0x402,0x40000000,0x2804000,0x2804000,0x4000000,0x4,0x0,0x4,0x8081011e,0x2000000,0x12,0x800000,0x0,0x1c00400,0x1e,0x3c0000,0x1fc0400,0x800000,0x402,0x2,0x8002000,0x8002000,0x80000000,0x400,0x0,0x800,0x400000,0xa07c0c00,0xa07c0c00,0x8081011e,0x8001011e,0x10000000,0x8081011e,0x8000011e,};
    }
    private static void jj_la1_init_1() {
-      jj_la1_1 = new int[] {0x1,0x0,0x1,0x0,0x0,0x20,0x20,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x1,0x1,0x0,0x0,0x0,0x4,0x18,0x1,0x0,0x18,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x7,0x7,0x1,0x0,0x0,0x1,0x0,};
+      jj_la1_1 = new int[] {0x1,0x0,0x1,0x0,0x0,0x20,0x20,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x1,0x1,0x0,0x0,0x0,0x4,0x18,0x1,0x0,0x18,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x7,0x7,0x1,0x0,0x0,0x1,0x0,};
    }
   final private JJCalls[] jj_2_rtns = new JJCalls[7];
@@ -1905,5 +1958,5 @@
     jj_ntk = -1;
     jj_gen = 0;
-    for (int i = 0; i < 46; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 48; i++) jj_la1[i] = -1;
     for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
   }
@@ -1920,5 +1973,5 @@
     jj_ntk = -1;
     jj_gen = 0;
-    for (int i = 0; i < 46; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 48; i++) jj_la1[i] = -1;
     for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
   }
@@ -1931,5 +1984,5 @@
     jj_ntk = -1;
     jj_gen = 0;
-    for (int i = 0; i < 46; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 48; i++) jj_la1[i] = -1;
     for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
   }
@@ -1942,5 +1995,5 @@
     jj_ntk = -1;
     jj_gen = 0;
-    for (int i = 0; i < 46; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 48; i++) jj_la1[i] = -1;
     for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
   }
@@ -1952,5 +2005,5 @@
     jj_ntk = -1;
     jj_gen = 0;
-    for (int i = 0; i < 46; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 48; i++) jj_la1[i] = -1;
     for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
   }
@@ -1962,5 +2015,5 @@
     jj_ntk = -1;
     jj_gen = 0;
-    for (int i = 0; i < 46; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 48; i++) jj_la1[i] = -1;
     for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
   }
@@ -2079,5 +2132,5 @@
       jj_kind = -1;
     }
-    for (int i = 0; i < 46; i++) {
+    for (int i = 0; i < 48; i++) {
       if (jj_la1[i] == jj_gen) {
         for (int j = 0; j < 32; j++) {
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/parser/MapCSSParser.jj
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/parser/MapCSSParser.jj	(revision 4068)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/parser/MapCSSParser.jj	(revision 4069)
@@ -13,4 +13,5 @@
 import org.openstreetmap.josm.gui.mappaint.Keyword;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Condition;
+import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Context;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Expression;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction;
@@ -20,6 +21,8 @@
 import org.openstreetmap.josm.gui.mappaint.mapcss.Expression.FunctionExpression;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Expression.LiteralExpression;
+import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSException;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
+import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.LinkSelector;
 import org.openstreetmap.josm.tools.Pair;
 
@@ -224,7 +227,10 @@
     (
         try {
-            r=rule() { if (r != null) { sheet.rules.add(r); } } w()
+                r=rule() { if (r != null) { sheet.rules.add(r); } } w()
+        } catch (MapCSSException mex) {
+            error_skipto(RBRACE, mex);
+            w();
         } catch (ParseException ex) {
-            error_skipto(RBRACE);
+            error_skipto(RBRACE, null);
             w();
         }
@@ -252,13 +258,20 @@
 {
     boolean parentSelector = false;
-    Selector sel1, sel2 = null;
-}
-{
-    sel1=selector() w()
-    (
-        ( <GREATER> { parentSelector = false; } | <LESS> { parentSelector = true; } ) w()
-        sel2=selector() w()
+    Condition c;
+    List<Condition> conditions = new ArrayList<Condition>();
+    Selector selLeft;
+    LinkSelector selLink = null;
+    Selector selRight = null;
+}
+{
+    selLeft=selector() w()
+    (
+        ( <GREATER> { parentSelector = false; } | <LESS> { parentSelector = true; } ) 
+        ( ( c=condition(Context.LINK) | c=pseudoclass(Context.LINK) ) { conditions.add(c); } )*
+        { selLink = new LinkSelector(conditions); }
+        w()
+        selRight=selector() w()
     )?
-    { return sel2 != null ? new ChildOrParentSelector(sel1, sel2, parentSelector) : sel1; }
+    { return selRight != null ? new ChildOrParentSelector(selLeft, selLink, selRight, parentSelector) : selLeft; }
 }
 
@@ -274,5 +287,5 @@
     ( base=<IDENT> | base=<STAR> )
     ( r=zoom() )?
-    ( ( c=condition() | c=pseudoclass() ) { conditions.add(c); } )*
+    ( ( c=condition(Context.PRIMITIVE) | c=pseudoclass(Context.PRIMITIVE) ) { conditions.add(c); } )*
     ( sub=subpart() )?
     { return new GeneralSelector(base.image, r, conditions, sub); }
@@ -294,5 +307,5 @@
 }
 
-Condition condition() :
+Condition condition(Context context) :
 {
     Condition c;
@@ -302,11 +315,11 @@
     <LSQUARE> s()
     (
-        LOOKAHEAD( simple_key_condition() s() <RSQUARE> )
-            c=simple_key_condition() s() <RSQUARE> { return c; }
+        LOOKAHEAD( simple_key_condition(context) s() <RSQUARE> )
+            c=simple_key_condition(context) s() <RSQUARE> { return c; }
         |
-        LOOKAHEAD( simple_key_value_condition() s() <RSQUARE> )
-            c=simple_key_value_condition() s() <RSQUARE> { return c; }
+        LOOKAHEAD( simple_key_value_condition(context) s() <RSQUARE> )
+            c=simple_key_value_condition(context) s() <RSQUARE> { return c; }
         |
-            e=expression() <RSQUARE> { return new Condition.ExpressionCondition(e); }
+            e=expression() <RSQUARE> { return Condition.create(e, context); }
     )
 }
@@ -323,5 +336,5 @@
 }
 
-Condition simple_key_condition() :
+Condition simple_key_condition(Context context) :
 {
     boolean not = false;
@@ -333,8 +346,8 @@
     key=tag_key()
     ( <QUESTION> { yes = true; } )?
-    { return new Condition.KeyCondition(key, not, yes); }
-}
-
-Condition simple_key_value_condition() :
+    { return Condition.create(key, not, yes, context); }
+}
+
+Condition simple_key_value_condition(Context context) :
 {
     String key;
@@ -385,8 +398,8 @@
             f=float_() { val=Float.toString(f); }
     )
-    { return new Condition.KeyValueCondition(key, val, op); }
-}
-
-Condition pseudoclass() :
+    { return Condition.create(key, val, op, context); }
+}
+
+Condition pseudoclass(Context context) :
 {
     Token t;
@@ -397,5 +410,5 @@
     <COLON>
     t=<IDENT>
-    { return new Condition.PseudoClassCondition(t.image, not); }
+    { return Condition.create(t.image, not, context); }
 }
 
@@ -550,12 +563,23 @@
 
 JAVACODE
-void error_skipto(int kind) {
+void error_skipto(int kind, MapCSSException me) {
     if (token.kind == EOF)
         throw new ParseException("Reached end of file while parsing");
-    ParseException e = generateParseException();
+        
+    Exception e = null;        
+    ParseException pe = generateParseException();
+
+    if (me != null) {
+        me.setLine(pe.currentToken.next.beginLine);
+        me.setColumn(pe.currentToken.next.beginColumn);
+        e = me;
+    } else {
+        e = new ParseException(pe.getMessage()); // prevent memory leak
+    }
+    
     System.err.println("Skipping to the next rule, because of an error:");
     System.err.println(e);
     if (sheet != null) {
-        sheet.logError(new ParseException(e.getMessage()));
+        sheet.logError(e);
     }
     Token t;
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/parser/MapCSSParserTokenManager.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/parser/MapCSSParserTokenManager.java	(revision 4068)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/parser/MapCSSParserTokenManager.java	(revision 4069)
@@ -6,4 +6,5 @@
 import org.openstreetmap.josm.gui.mappaint.Keyword;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Condition;
+import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Context;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Expression;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction;
@@ -13,6 +14,8 @@
 import org.openstreetmap.josm.gui.mappaint.mapcss.Expression.FunctionExpression;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Expression.LiteralExpression;
+import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSException;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
+import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.LinkSelector;
 import org.openstreetmap.josm.tools.Pair;
 
Index: /trunk/src/org/openstreetmap/josm/tools/Utils.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/Utils.java	(revision 4068)
+++ /trunk/src/org/openstreetmap/josm/tools/Utils.java	(revision 4069)
@@ -7,6 +7,10 @@
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.text.MessageFormat;
 import java.util.Collection;
 
+/**
+ * Basic utils, that can be useful in different parts of the program.
+ */
 public class Utils {
 
@@ -80,4 +84,11 @@
             return true;
         return (a != null && a.equals(b));
+    }
+
+    public static void ensure(boolean condition, String message, Object...data) {
+        if (!condition)
+            throw new AssertionError(
+                    MessageFormat.format(message,data)
+            );
     }
 
@@ -162,4 +173,7 @@
     }
 
+    public static Color complement(Color clr) {
+        return new Color(255 - clr.getRed(), 255 - clr.getGreen(), 255 - clr.getBlue(), clr.getAlpha());
+    }
 
     public static int copyStream(InputStream source, OutputStream destination) throws IOException {
@@ -172,10 +186,4 @@
         }
         return count;
-    }
-
-
-
-    public static Color complement(Color clr) {
-        return new Color(255 - clr.getRed(), 255 - clr.getGreen(), 255 - clr.getBlue(), clr.getAlpha());
     }
 
Index: /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/AllMappaintTests.groovy
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/AllMappaintTests.groovy	(revision 4068)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/AllMappaintTests.groovy	(revision 4069)
@@ -2,13 +2,16 @@
 package org.openstreetmap.josm.gui.mappaint
 
-import junit.framework.TestCase;
+import junit.framework.TestCase
 
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
+import org.junit.runner.RunWith
+import org.junit.runners.Suite
+import org.openstreetmap.josm.gui.mappaint.mapcss.AllMapCSSTests
 
 @RunWith(Suite.class)
 @Suite.SuiteClasses([
     LabelCompositionStrategyTest.class,
-    MapCSSWithExtendedTextDirectivesTest.class
+    MapCSSWithExtendedTextDirectivesTest.class,
+    AllMapCSSTests.class
+    
 ])
 public class AllMappaintTests extends TestCase{}
Index: /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/MapCSSWithExtendedTextDirectivesTest.groovy
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/MapCSSWithExtendedTextDirectivesTest.groovy	(revision 4068)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/MapCSSWithExtendedTextDirectivesTest.groovy	(revision 4069)
@@ -2,11 +2,10 @@
 package org.openstreetmap.josm.gui.mappaint
 
-import java.awt.Color;
+import java.awt.Color
 
-import org.junit.*;
-import org.openstreetmap.josm.fixtures.JOSMFixture 
-import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.DeriveLabelFromNameTagsCompositionStrategy 
-import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.StaticLabelCompositionStrategy 
-import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.TagLookupCompositionStrategy 
+import org.junit.*
+import org.openstreetmap.josm.fixtures.JOSMFixture
+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.DeriveLabelFromNameTagsCompositionStrategy
+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.TagLookupCompositionStrategy
 class MapCSSWithExtendedTextDirectivesTest {
     
Index: /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/AllMapCSSTests.groovy
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/AllMapCSSTests.groovy	(revision 4069)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/AllMapCSSTests.groovy	(revision 4069)
@@ -0,0 +1,17 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint.mapcss
+
+import junit.framework.TestCase
+
+import org.junit.runner.RunWith
+import org.junit.runners.Suite
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses([
+    KeyValueConditionTest.class,
+    ParsingLinkSelectorTest.class,
+    KeyConditionTest.class,
+    ChildOrParentSelectorTest
+])
+public class AllMapCSSTests extends TestCase{}
+
Index: /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/ChildOrParentSelectorTest.groovy
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/ChildOrParentSelectorTest.groovy	(revision 4069)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/ChildOrParentSelectorTest.groovy	(revision 4069)
@@ -0,0 +1,177 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint.mapcss;
+
+import static org.junit.Assert.*
+
+import java.util.logging.Logger
+
+import org.junit.*
+import org.openstreetmap.josm.data.coor.LatLon
+import org.openstreetmap.josm.data.osm.DataSet
+import org.openstreetmap.josm.data.osm.Node
+import org.openstreetmap.josm.data.osm.Relation
+import org.openstreetmap.josm.data.osm.RelationMember
+import org.openstreetmap.josm.data.osm.Way
+import org.openstreetmap.josm.fixtures.JOSMFixture
+import org.openstreetmap.josm.gui.mappaint.Environment
+import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector
+
+class ChildOrParentSelectorTest {
+    static private Logger logger = Logger.getLogger(ChildOrParentSelectorTest.class.getName());
+    
+    def shouldFail = new GroovyTestCase().&shouldFail
+    
+    def DataSet ds;
+    
+    @BeforeClass
+    public static void createJOSMFixture(){
+        JOSMFixture.createUnitTestFixture().init()
+    }
+    
+    @Before
+    public void setUp() {
+        ds = new DataSet()
+    }
+    
+    def relation(id) {
+        def r = new Relation(id,1)
+        ds.addPrimitive(r)
+        return r
+    }
+    
+    def node(id) {
+        def n = new Node(id,1)
+        n.setCoor(new LatLon(0,0))
+        ds.addPrimitive(n)
+        return n
+    }
+    
+    def way(id){
+        def w = new Way(id,1)
+        ds.addPrimitive(w)
+        return w
+    }
+    
+    def ChildOrParentSelector parse(css){
+         MapCSSStyleSource source = new MapCSSStyleSource(css)
+         source.loadStyleSource()
+         assert source.rules.size() == 1
+         assert source.rules[0].selectors.size() == 1
+         return source.rules[0].selectors[0]
+    }
+    
+    @Test
+    @Ignore
+    public void matches_1() {
+        def css = """
+           relation >[role="my_role"] node {}
+        """
+        ChildOrParentSelector selector = parse(css)
+        
+        Relation r = relation(1)
+        Node n = node(1)
+        r.addMember(new RelationMember("my_role", n))
+        Environment e = new Environment().withChild(n)
+        
+        assert selector.matches(e)        
+    }
+    
+    @Test
+    @Ignore
+    public void matches_2() {
+        def css = """
+           relation >["my_role"] node {}
+        """
+        ChildOrParentSelector selector = parse(css)
+        
+        Relation r = relation(1)
+        Node n = node(1)
+        r.addMember(new RelationMember("my_role", n))
+        Environment e = new Environment().withChild(n)
+        
+        assert selector.matches(e)
+    }
+        
+    @Test
+    @Ignore
+    public void matches_3() {
+        def css = """
+           relation >[!"my_role"] node {}
+        """
+        ChildOrParentSelector selector = parse(css)
+        
+        Relation r = relation(1)
+        Node n = node(1)
+        r.addMember(new RelationMember("my_role", n))
+        Environment e = new Environment().withChild(n)
+        
+        assert !selector.matches(e)
+    }
+    
+    @Test
+    @Ignore
+    public void matches_4() {
+        def css = """
+           way < relation {}
+        """        
+        ChildOrParentSelector selector = parse(css)
+        assert selector.parentSelector
+        
+    }
+    @Test
+    public void matches_5() {
+        def css = """
+           way <[role != "my_role"] relation {}
+        """
+        ChildOrParentSelector selector = parse(css)   
+        assert selector.parentSelector     
+        
+        Relation r = relation(1)
+        Way w1 = way(1)
+        w1.setNodes([node(11), node(12)])
+
+        Way w2 = way(2)
+        w2.setNodes([node(21), node(22)])
+        
+        Way w3 = way(3)
+        w3.setNodes([node(31), node(32)])
+
+        r.addMember(new RelationMember("my_role", w1))
+        r.addMember(new RelationMember("my_role", w2))
+        r.addMember(new RelationMember("another role", w3))
+        
+        Environment e = new Environment().withPrimitive(r)
+        assert selector.matches(e)
+    }
+    
+    @Test
+    public void matches_6() {
+        def css = """
+           relation >[role != "my_role"] way {}
+        """
+        ChildOrParentSelector selector = parse(css)     
+        
+        Relation r = relation(1)
+        Way w1 = way(1)
+        w1.setNodes([node(11), node(12)])
+
+        Way w2 = way(2)
+        w2.setNodes([node(21), node(22)])
+        
+        Way w3 = way(3)
+        w3.setNodes([node(31), node(32)])
+
+        r.addMember(new RelationMember("my_role", w1))
+        r.addMember(new RelationMember("my_role", w2))
+        r.addMember(new RelationMember("another role", w3))
+        
+        Environment e = new Environment().withPrimitive(w1)
+        assert !selector.matches(e)
+        
+        e = new Environment().withPrimitive(w2)
+        assert !selector.matches(e)
+        
+        e = new Environment().withPrimitive(w3)
+        assert selector.matches(e)
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/KeyConditionTest.groovy
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/KeyConditionTest.groovy	(revision 4069)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/KeyConditionTest.groovy	(revision 4069)
@@ -0,0 +1,102 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint.mapcss;
+
+import static org.junit.Assert.*
+
+import org.junit.*
+import org.openstreetmap.josm.data.coor.LatLon
+import org.openstreetmap.josm.data.osm.DataSet
+import org.openstreetmap.josm.data.osm.Node
+import org.openstreetmap.josm.data.osm.Relation
+import org.openstreetmap.josm.data.osm.RelationMember
+import org.openstreetmap.josm.fixtures.JOSMFixture
+import org.openstreetmap.josm.gui.mappaint.Environment
+import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Context
+
+class KeyConditionTest {
+
+    def shouldFail = new GroovyTestCase().&shouldFail
+    
+    def DataSet ds;
+    
+    @BeforeClass
+    public static void createJOSMFixture(){
+        JOSMFixture.createUnitTestFixture().init()
+    }
+    
+    @Before
+    public void setUp() {
+        ds = new DataSet()
+    }
+    
+    def relation(id) {
+        def r = new Relation(id,1)
+        ds.addPrimitive(r)
+        return r
+    }
+    
+    def node(id) {
+        def n = new Node(id,1)
+        n.setCoor(new LatLon(0,0))
+        ds.addPrimitive(n)
+        return n
+    }
+    
+    @Test
+    public void create() {
+        
+        // ["a label"]
+        Condition c = Condition.create("a key", false, false, Context.PRIMITIVE)
+        // ["a label"?]
+        c = Condition.create("a key", false, true, Context.PRIMITIVE)
+        // [!"a label"]
+        c = Condition.create("a key", true, false, Context.PRIMITIVE)
+        // [!"a label"?]
+        c = Condition.create("a key", true, true, Context.PRIMITIVE)
+       
+        // ["a label"]
+        c = Condition.create("a key", false, false, Context.LINK)
+        // [!"a label"]
+        c = Condition.create("a key", true, false, Context.LINK)
+        
+        shouldFail(MapCSSException) {
+            // ["a label"?]
+           c = Condition.create("a key", false, true, Context.LINK)
+        }
+        
+        shouldFail(MapCSSException) {
+            // [!"a label"?]
+            c = Condition.create("a key", true, true, Context.LINK)
+        }
+    }
+    
+    @Test
+    public void applies_1() {
+        Relation r = relation(1)
+        Node n = node(1)
+        r.addMember(new RelationMember("my_role", n))
+        
+        Environment e = new Environment().withPrimitive(n).withParent(r).withIndex(0).withLinkContext()
+        
+        Condition cond = Condition.create("my_role", false, false, Context.LINK)
+        assert cond.applies(e)        
+        
+        cond = Condition.create("my_role", true, false, Context.LINK)
+        assert !cond.applies(e)
+    }
+    
+    @Test
+    public void applies_2() {
+        Relation r = relation(1)
+        Node n = node(1)
+        r.addMember(new RelationMember("my_role", n))
+        
+        Environment e = new Environment().withPrimitive(n).withParent(r).withIndex(0).withLinkContext()
+        
+        Condition cond = Condition.create("another_role", false, false, Context.LINK)
+        assert !cond.applies(e)
+        
+        cond = Condition.create("another_role", true, false, Context.LINK)
+        assert cond.applies(e)
+    }    
+}
Index: /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/KeyValueConditionTest.groovy
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/KeyValueConditionTest.groovy	(revision 4069)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/KeyValueConditionTest.groovy	(revision 4069)
@@ -0,0 +1,91 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint.mapcss;
+
+import static org.junit.Assert.*
+
+import org.junit.*
+import org.openstreetmap.josm.data.coor.LatLon
+import org.openstreetmap.josm.data.osm.DataSet
+import org.openstreetmap.josm.data.osm.Node
+import org.openstreetmap.josm.data.osm.Relation
+import org.openstreetmap.josm.data.osm.RelationMember
+import org.openstreetmap.josm.fixtures.JOSMFixture
+import org.openstreetmap.josm.gui.mappaint.Environment
+import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Context
+import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Op
+
+
+class KeyValueConditionTest {
+
+    def shouldFail = new GroovyTestCase().&shouldFail
+    
+    def DataSet ds;
+    
+    @BeforeClass
+    public static void createJOSMFixture(){
+        JOSMFixture.createUnitTestFixture().init()
+    }
+    
+    @Before
+    public void setUp() {
+        ds = new DataSet()
+    }
+    
+    def relation(id) {
+        def r = new Relation(id,1)
+        ds.addPrimitive(r)
+        return r
+    }
+    
+    def node(id) {
+        def n = new Node(id,1)
+        n.setCoor(new LatLon(0,0))
+        ds.addPrimitive(n)
+        return n
+    }
+    
+    @Test
+    public void create() {
+        Condition c = Condition.create("a key", "a value", Op.EQ, Context.PRIMITIVE)
+        
+        c = Condition.create("role", "a role", Op.EQ, Context.LINK)
+        c = Condition.create("RoLe", "a role", Op.EQ, Context.LINK)
+        
+        shouldFail(MapCSSException) {
+            c = Condition.create("an arbitry tag", "a role", Op.EQ, Context.LINK)
+        }
+    }
+    
+    @Test
+    public void applies_1() {
+        Relation r = relation(1)
+        Node n = node(1)
+        r.addMember(new RelationMember("my_role", n))
+        
+        Environment e = new Environment().withPrimitive(n).withParent(r).withLinkContext().withIndex(0)
+        
+        Condition cond = new Condition.RoleCondition("my_role", Op.EQ)
+        assert cond.applies(e)        
+        
+        cond = new Condition.RoleCondition("another_role", Op.EQ)
+        assert !cond.applies(e)
+    }
+    
+    @Test
+    public void applies_2() {
+        Relation r = relation(1)
+        Node n = node(1)
+        r.addMember(new RelationMember("my_role", n))
+        
+        Environment e = new Environment().withPrimitive(n).withParent(r).withIndex(0).withLinkContext()
+        
+        Condition cond = Condition.create("role", "my_role", Op.NEQ, Context.LINK)
+        assert !cond.applies(e)
+        
+        cond = Condition.create("role", "another_role", Op.NEQ, Context.LINK)
+        assert cond.applies(e)
+    }
+    
+    
+    
+}
Index: /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/ParsingLinkSelectorTest.groovy
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/ParsingLinkSelectorTest.groovy	(revision 4069)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/ParsingLinkSelectorTest.groovy	(revision 4069)
@@ -0,0 +1,57 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint.mapcss;
+
+import static org.junit.Assert.*
+
+import org.junit.*
+import org.openstreetmap.josm.fixtures.JOSMFixture
+
+
+class ParsingLinkSelectorTest {
+
+    @BeforeClass
+    public static void createJOSMFixture(){
+        JOSMFixture.createUnitTestFixture().init()
+    }
+    
+    @Test
+    public void parseEmptyChildSelector() {
+        def css = """
+           relation > way {}
+        """
+        MapCSSStyleSource source = new MapCSSStyleSource(css)
+        source.loadStyleSource()        
+        assert source.rules.size() == 1
+    }
+    
+    @Test
+    public void parseEmptyParentSelector() {
+        def css = """
+           way < relation {}
+        """
+        MapCSSStyleSource source = new MapCSSStyleSource(css)
+        source.loadStyleSource()
+        assert source.rules.size() == 1
+    }
+    
+    
+    @Test
+    public void parseChildSelectorWithKeyValueCondition() {
+        def css = """
+           relation >[role="my_role"] way {}
+        """
+        MapCSSStyleSource source = new MapCSSStyleSource(css)
+        source.loadStyleSource()
+        assert source.rules.size() == 1
+    }
+    
+    @Test
+    public void parseChildSelectorWithKeyCondition() {
+        def css = """
+           relation >["my_role"] way{}
+        """
+        MapCSSStyleSource source = new MapCSSStyleSource(css)
+        source.loadStyleSource()
+        assert source.rules.size() == 1
+    }
+}
