source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java@ 6554

Last change on this file since 6554 was 6554, checked in by simon04, 10 years ago

see #9414 fix #9409 - extend MapCSS condition syntax to allow the comparison of two key values

The syntax is [key1 = *key2] where * is inspired by the C de-reference operator, and = stands for any of =/!=/~=/^=/$=/*=/=~/!~.

  • Property svn:eol-style set to native
File size: 11.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import static org.openstreetmap.josm.tools.Utils.equal;
5
6import java.text.MessageFormat;
7import java.util.EnumSet;
8import java.util.regex.Pattern;
9
10import org.openstreetmap.josm.data.osm.Node;
11import org.openstreetmap.josm.data.osm.OsmUtils;
12import org.openstreetmap.josm.data.osm.Relation;
13import org.openstreetmap.josm.data.osm.Tag;
14import org.openstreetmap.josm.data.osm.Way;
15import org.openstreetmap.josm.gui.mappaint.Cascade;
16import org.openstreetmap.josm.gui.mappaint.Environment;
17import org.openstreetmap.josm.tools.Predicates;
18import org.openstreetmap.josm.tools.Utils;
19
20abstract public class Condition {
21
22 abstract public boolean applies(Environment e);
23
24 public static Condition create(String k, String v, Op op, Context context, boolean considerValAsKey) {
25 switch (context) {
26 case PRIMITIVE:
27 return new KeyValueCondition(k, v, op, considerValAsKey);
28 case LINK:
29 if (considerValAsKey)
30 throw new MapCSSException("''considerValAsKey'' not supported in LINK context");
31 if ("role".equalsIgnoreCase(k))
32 return new RoleCondition(v, op);
33 else if ("index".equalsIgnoreCase(k))
34 return new IndexCondition(v, op);
35 else
36 throw new MapCSSException(
37 MessageFormat.format("Expected key ''role'' or ''index'' in link context. Got ''{0}''.", k));
38
39 default: throw new AssertionError();
40 }
41 }
42
43 public static Condition create(String k, boolean not, KeyMatchType matchType, Context context) {
44 switch (context) {
45 case PRIMITIVE:
46 return new KeyCondition(k, not, matchType);
47 case LINK:
48 if (matchType != null)
49 throw new MapCSSException("Question mark operator ''?'' and regexp match not supported in LINK context");
50 if (not)
51 return new RoleCondition(k, Op.NEQ);
52 else
53 return new RoleCondition(k, Op.EQ);
54
55 default: throw new AssertionError();
56 }
57 }
58
59 public static Condition create(String id, boolean not, Context context) {
60 return new PseudoClassCondition(id, not);
61 }
62
63 public static Condition create(Expression e, Context context) {
64 return new ExpressionCondition(e);
65 }
66
67 public static enum Op {
68 EQ, NEQ, GREATER_OR_EQUAL, GREATER, LESS_OR_EQUAL, LESS,
69 REGEX, NREGEX, ONE_OF, BEGINS_WITH, ENDS_WITH, CONTAINS;
70
71 public boolean eval(String testString, String prototypeString) {
72 if (testString == null && this != NEQ)
73 return false;
74 switch (this) {
75 case EQ:
76 return equal(testString, prototypeString);
77 case NEQ:
78 return !equal(testString, prototypeString);
79 case REGEX:
80 case NREGEX:
81 final boolean contains = Pattern.compile(prototypeString).matcher(testString).find();
82 return REGEX.equals(this) ? contains : !contains;
83 case ONE_OF:
84 String[] parts = testString.split(";");
85 for (String part : parts) {
86 if (equal(prototypeString, part.trim()))
87 return true;
88 }
89 return false;
90 case BEGINS_WITH:
91 return testString.startsWith(prototypeString);
92 case ENDS_WITH:
93 return testString.endsWith(prototypeString);
94 case CONTAINS:
95 return testString.contains(prototypeString);
96 }
97
98 float test_float;
99 try {
100 test_float = Float.parseFloat(testString);
101 } catch (NumberFormatException e) {
102 return false;
103 }
104 float prototype_float = Float.parseFloat(prototypeString);
105
106 switch (this) {
107 case GREATER_OR_EQUAL:
108 return test_float >= prototype_float;
109 case GREATER:
110 return test_float > prototype_float;
111 case LESS_OR_EQUAL:
112 return test_float <= prototype_float;
113 case LESS:
114 return test_float < prototype_float;
115 default:
116 throw new AssertionError();
117 }
118 }
119 }
120
121 /**
122 * context, where the condition applies
123 */
124 public static enum Context {
125 /**
126 * normal primitive selector, e.g. way[highway=residential]
127 */
128 PRIMITIVE,
129
130 /**
131 * link between primitives, e.g. relation >[role=outer] way
132 */
133 LINK
134 }
135
136 public final static EnumSet<Op> COMPARISON_OPERATERS =
137 EnumSet.of(Op.GREATER_OR_EQUAL, Op.GREATER, Op.LESS_OR_EQUAL, Op.LESS);
138
139 /**
140 * <p>Represents a key/value condition which is either applied to a primitive.</p>
141 *
142 */
143 public static class KeyValueCondition extends Condition {
144
145 public final String k;
146 public final String v;
147 public final Op op;
148 public boolean considerValAsKey;
149
150 /**
151 * <p>Creates a key/value-condition.</p>
152 *
153 * @param k the key
154 * @param v the value
155 * @param op the operation
156 * @param considerValAsKey whether to consider {@code v} as another key and compare the values of key {@code k} and key {@code v}.
157 */
158 public KeyValueCondition(String k, String v, Op op, boolean considerValAsKey) {
159 this.k = k;
160 this.v = v;
161 this.op = op;
162 this.considerValAsKey = considerValAsKey;
163 }
164
165 @Override
166 public boolean applies(Environment env) {
167 return op.eval(env.osm.get(k), considerValAsKey ? env.osm.get(v) : v);
168 }
169
170 public Tag asTag() {
171 return new Tag(k, v);
172 }
173
174 @Override
175 public String toString() {
176 return "[" + k + "'" + op + "'" + v + "]";
177 }
178 }
179
180 public static class RoleCondition extends Condition {
181 public final String role;
182 public final Op op;
183
184 public RoleCondition(String role, Op op) {
185 this.role = role;
186 this.op = op;
187 }
188
189 @Override
190 public boolean applies(Environment env) {
191 String testRole = env.getRole();
192 if (testRole == null) return false;
193 return op.eval(testRole, role);
194 }
195 }
196
197 public static class IndexCondition extends Condition {
198 public final String index;
199 public final Op op;
200
201 public IndexCondition(String index, Op op) {
202 this.index = index;
203 this.op = op;
204 }
205
206 @Override
207 public boolean applies(Environment env) {
208 if (env.index == null) return false;
209 return op.eval(Integer.toString(env.index + 1), index);
210 }
211 }
212
213 public static enum KeyMatchType {
214 EQ, TRUE, FALSE, REGEX
215 }
216
217 /**
218 * <p>KeyCondition represent one of the following conditions in either the link or the
219 * primitive context:</p>
220 * <pre>
221 * ["a label"] PRIMITIVE: the primitive has a tag "a label"
222 * LINK: the parent is a relation and it has at least one member with the role
223 * "a label" referring to the child
224 *
225 * [!"a label"] PRIMITIVE: the primitive doesn't have a tag "a label"
226 * LINK: the parent is a relation but doesn't have a member with the role
227 * "a label" referring to the child
228 *
229 * ["a label"?] PRIMITIVE: the primitive has a tag "a label" whose value evaluates to a true-value
230 * LINK: not supported
231 *
232 * ["a label"?!] PRIMITIVE: the primitive has a tag "a label" whose value evaluates to a false-value
233 * LINK: not supported
234 * </pre>
235 */
236 public static class KeyCondition extends Condition {
237
238 public final String label;
239 public final boolean negateResult;
240 public final KeyMatchType matchType;
241
242 public KeyCondition(String label, boolean negateResult, KeyMatchType matchType){
243 this.label = label;
244 this.negateResult = negateResult;
245 this.matchType = matchType;
246 }
247
248 @Override
249 public boolean applies(Environment e) {
250 switch(e.getContext()) {
251 case PRIMITIVE:
252 if (KeyMatchType.TRUE.equals(matchType))
253 return OsmUtils.isTrue(e.osm.get(label)) ^ negateResult;
254 else if (KeyMatchType.FALSE.equals(matchType))
255 return OsmUtils.isFalse(e.osm.get(label)) ^ negateResult;
256 else if (KeyMatchType.REGEX.equals(matchType))
257 return Utils.exists(e.osm.keySet(), Predicates.stringContainsPattern(Pattern.compile(label))) ^ negateResult;
258 else
259 return e.osm.hasKey(label) ^ negateResult;
260 case LINK:
261 Utils.ensure(false, "Illegal state: KeyCondition not supported in LINK context");
262 return false;
263 default: throw new AssertionError();
264 }
265 }
266
267 public Tag asTag() {
268 return new Tag(label);
269 }
270
271 @Override
272 public String toString() {
273 return "[" + (negateResult ? "!" : "") + label + "]";
274 }
275 }
276
277 public static class PseudoClassCondition extends Condition {
278
279 public final String id;
280 public final boolean not;
281
282 public PseudoClassCondition(String id, boolean not) {
283 this.id = id;
284 this.not = not;
285 }
286
287 @Override
288 public boolean applies(Environment e) {
289 return not ^ appliesImpl(e);
290 }
291
292 public boolean appliesImpl(Environment e) {
293 if (equal(id, "closed")) {
294 if (e.osm instanceof Way && ((Way) e.osm).isClosed())
295 return true;
296 if (e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon())
297 return true;
298 return false;
299 } else if (equal(id, "modified"))
300 return e.osm.isModified() || e.osm.isNewOrUndeleted();
301 else if (equal(id, "new"))
302 return e.osm.isNew();
303 else if (equal(id, "connection") && (e.osm instanceof Node))
304 return ((Node) e.osm).isConnectionNode();
305 else if (equal(id, "tagged"))
306 return e.osm.isTagged();
307 return true;
308 }
309
310 @Override
311 public String toString() {
312 return ":" + (not ? "!" : "") + id;
313 }
314 }
315
316 public static class ExpressionCondition extends Condition {
317
318 private Expression e;
319
320 public ExpressionCondition(Expression e) {
321 this.e = e;
322 }
323
324 @Override
325 public boolean applies(Environment env) {
326 Boolean b = Cascade.convertTo(e.evaluate(env), Boolean.class);
327 return b != null && b;
328 }
329
330 @Override
331 public String toString() {
332 return "[" + e + "]";
333 }
334 }
335}
Note: See TracBrowser for help on using the repository browser.