diff --git a/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java b/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java
index 7b0f5a4..9e81921 100644
|
a
|
b
|
public class MapCSSTagChecker extends Test.TagTest {
|
| 420 | 420 | static String determineArgument(Selector.GeneralSelector matchingSelector, int index, String type, OsmPrimitive p) { |
| 421 | 421 | try { |
| 422 | 422 | final Condition c = matchingSelector.getConditions().get(index); |
| 423 | | final Tag tag = c instanceof Condition.KeyCondition |
| 424 | | ? ((Condition.KeyCondition) c).asTag(p) |
| 425 | | : c instanceof Condition.SimpleKeyValueCondition |
| 426 | | ? ((Condition.SimpleKeyValueCondition) c).asTag() |
| 427 | | : c instanceof Condition.KeyValueCondition |
| 428 | | ? ((Condition.KeyValueCondition) c).asTag() |
| | 423 | final Tag tag = c instanceof Condition.ToTagConvertable |
| | 424 | ? ((Condition.ToTagConvertable) c).asTag(p) |
| 429 | 425 | : null; |
| 430 | 426 | if (tag == null) { |
| 431 | 427 | return null; |
diff --git a/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java b/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java
index e155438..3feb797 100644
|
a
|
b
|
import java.lang.reflect.InvocationTargetException;
|
| 5 | 5 | import java.lang.reflect.Method; |
| 6 | 6 | import java.text.MessageFormat; |
| 7 | 7 | import java.util.Arrays; |
| 8 | | import java.util.Collection; |
| 9 | 8 | import java.util.EnumSet; |
| 10 | 9 | import java.util.Map; |
| 11 | 10 | import java.util.Objects; |
| 12 | 11 | import java.util.Set; |
| | 12 | import java.util.function.BiFunction; |
| | 13 | import java.util.function.IntFunction; |
| 13 | 14 | import java.util.function.Predicate; |
| 14 | 15 | import java.util.regex.Pattern; |
| 15 | 16 | |
| … |
… |
import org.openstreetmap.josm.gui.mappaint.ElemStyles;
|
| 27 | 28 | import org.openstreetmap.josm.gui.mappaint.Environment; |
| 28 | 29 | import org.openstreetmap.josm.tools.CheckParameterUtil; |
| 29 | 30 | import org.openstreetmap.josm.tools.Predicates; |
| 30 | | import org.openstreetmap.josm.tools.SubclassFilteredCollection; |
| 31 | 31 | import org.openstreetmap.josm.tools.Utils; |
| 32 | 32 | |
| | 33 | /** |
| | 34 | * This is a condition that needs to be fulfilled in order to apply a MapCSS style. |
| | 35 | */ |
| 33 | 36 | @FunctionalInterface |
| 34 | 37 | public interface Condition { |
| 35 | 38 | |
| | 39 | /** |
| | 40 | * Checks if the condition applies in the given MapCSS {@link Environment}. |
| | 41 | * @param e The environment to check. May not be <code>null</code>. |
| | 42 | * @return <code>true</code> if the condition applies. |
| | 43 | */ |
| 36 | 44 | boolean applies(Environment e); |
| 37 | 45 | |
| | 46 | /** |
| | 47 | * Create a new condition that checks the key and the value of the object. |
| | 48 | * @param k The key. |
| | 49 | * @param v The reference value |
| | 50 | * @param op The operation to use when comparing the value |
| | 51 | * @param context The type of context to use. |
| | 52 | * @param considerValAsKey whether to consider {@code v} as another key and compare the values of key {@code k} and key {@code v}. |
| | 53 | * @return The new condition. |
| | 54 | */ |
| 38 | 55 | static Condition createKeyValueCondition(String k, String v, Op op, Context context, boolean considerValAsKey) { |
| 39 | 56 | switch (context) { |
| 40 | 57 | case PRIMITIVE: |
| … |
… |
public interface Condition {
|
| 58 | 75 | } |
| 59 | 76 | } |
| 60 | 77 | |
| | 78 | /** |
| | 79 | * Create a condition in which the key and the value need to match a given regexp |
| | 80 | * @param k The key regexp |
| | 81 | * @param v The value regexp |
| | 82 | * @param op The operation to use when comparing the key and the value. |
| | 83 | * @return The new condition. |
| | 84 | */ |
| 61 | 85 | static Condition createRegexpKeyRegexpValueCondition(String k, String v, Op op) { |
| 62 | 86 | return new RegexpKeyValueRegexpCondition(k, v, op); |
| 63 | 87 | } |
| 64 | 88 | |
| | 89 | /** |
| | 90 | * Creates a condition that checks the given key. |
| | 91 | * @param k The key to test for |
| | 92 | * @param not <code>true</code> to invert the match |
| | 93 | * @param matchType The match type to check for. |
| | 94 | * @param context The context this rule is found in. |
| | 95 | * @return the new condition. |
| | 96 | */ |
| 65 | 97 | static Condition createKeyCondition(String k, boolean not, KeyMatchType matchType, Context context) { |
| 66 | 98 | switch (context) { |
| 67 | 99 | case PRIMITIVE: |
| … |
… |
public interface Condition {
|
| 78 | 110 | } |
| 79 | 111 | } |
| 80 | 112 | |
| | 113 | /** |
| | 114 | * Create a new pseudo class condition |
| | 115 | * @param id The id of the pseudo class |
| | 116 | * @param not <code>true</code> to invert the condition |
| | 117 | * @param context The context the class is found in. |
| | 118 | * @return The new condition |
| | 119 | */ |
| 81 | 120 | static PseudoClassCondition createPseudoClassCondition(String id, boolean not, Context context) { |
| 82 | 121 | return PseudoClassCondition.createPseudoClassCondition(id, not, context); |
| 83 | 122 | } |
| 84 | 123 | |
| | 124 | /** |
| | 125 | * Create a new class condition |
| | 126 | * @param id The id of the class to match |
| | 127 | * @param not <code>true</code> to invert the condition |
| | 128 | * @param context Ignored |
| | 129 | * @return The new condition |
| | 130 | */ |
| 85 | 131 | static ClassCondition createClassCondition(String id, boolean not, Context context) { |
| 86 | 132 | return new ClassCondition(id, not); |
| 87 | 133 | } |
| 88 | 134 | |
| | 135 | /** |
| | 136 | * Create a new condition that a expression needs to be fulfilled |
| | 137 | * @param e the expression to check |
| | 138 | * @param context Ignored |
| | 139 | * @return The new condition |
| | 140 | */ |
| 89 | 141 | static ExpressionCondition createExpressionCondition(Expression e, Context context) { |
| 90 | 142 | return new ExpressionCondition(e); |
| 91 | 143 | } |
| … |
… |
public interface Condition {
|
| 95 | 147 | */ |
| 96 | 148 | enum Op { |
| 97 | 149 | /** The value equals the given reference. */ |
| 98 | | EQ, |
| | 150 | EQ(Objects::equals), |
| 99 | 151 | /** The value does not equal the reference. */ |
| 100 | | NEQ, |
| | 152 | NEQ(EQ), |
| 101 | 153 | /** The value is greater than or equal to the given reference value (as float). */ |
| 102 | | GREATER_OR_EQUAL, |
| | 154 | GREATER_OR_EQUAL(comparisonResult -> comparisonResult >= 0), |
| 103 | 155 | /** The value is greater than the given reference value (as float). */ |
| 104 | | GREATER, |
| | 156 | GREATER(comparisonResult -> comparisonResult > 0), |
| 105 | 157 | /** The value is less than or equal to the given reference value (as float). */ |
| 106 | | LESS_OR_EQUAL, |
| | 158 | LESS_OR_EQUAL(comparisonResult -> comparisonResult <= 0), |
| 107 | 159 | /** The value is less than the given reference value (as float). */ |
| 108 | | LESS, |
| | 160 | LESS(comparisonResult -> comparisonResult < 0), |
| 109 | 161 | /** The reference is treated as regular expression and the value needs to match it. */ |
| 110 | | REGEX, |
| | 162 | REGEX((test, prototype) -> Pattern.compile(prototype).matcher(test).find()), |
| 111 | 163 | /** The reference is treated as regular expression and the value needs to not match it. */ |
| 112 | | NREGEX, |
| | 164 | NREGEX(REGEX), |
| 113 | 165 | /** The reference is treated as a list separated by ';'. Spaces around the ; are ignored. |
| 114 | 166 | * The value needs to be equal one of the list elements. */ |
| 115 | | ONE_OF, |
| | 167 | ONE_OF((test, prototype) -> Arrays.asList(test.split("\\s*;\\s*")).contains(prototype)), |
| 116 | 168 | /** The value needs to begin with the reference string. */ |
| 117 | | BEGINS_WITH, |
| | 169 | BEGINS_WITH((test, prototype) -> test.startsWith(prototype)), |
| 118 | 170 | /** The value needs to end with the reference string. */ |
| 119 | | ENDS_WITH, |
| | 171 | ENDS_WITH((test, prototype) -> test.endsWith(prototype)), |
| 120 | 172 | /** The value needs to contain the reference string. */ |
| 121 | | CONTAINS; |
| | 173 | CONTAINS((test, prototype) -> test.contains(prototype)); |
| 122 | 174 | |
| 123 | 175 | static final Set<Op> NEGATED_OPS = EnumSet.of(NEQ, NREGEX); |
| 124 | 176 | |
| | 177 | private final BiFunction<String, String, Boolean> function; |
| | 178 | |
| | 179 | private final boolean negated; |
| | 180 | |
| | 181 | /** |
| | 182 | * Create a new string operation. |
| | 183 | * @param func The function to apply during {@link #eval(String, String)}. |
| | 184 | */ |
| | 185 | Op(BiFunction<String, String, Boolean> func) { |
| | 186 | this.function = func; |
| | 187 | negated = false; |
| | 188 | } |
| | 189 | |
| | 190 | /** |
| | 191 | * Create a new float operation that compares two float values |
| | 192 | * @param comparatorResult A function to mapt the result of the comparison |
| | 193 | */ |
| | 194 | Op(IntFunction<Boolean> comparatorResult) { |
| | 195 | this.function = (test, prototype) -> { |
| | 196 | float testFloat; |
| | 197 | try { |
| | 198 | testFloat = Float.parseFloat(test); |
| | 199 | } catch (NumberFormatException e) { |
| | 200 | return false; |
| | 201 | } |
| | 202 | float prototypeFloat = Float.parseFloat(prototype); |
| | 203 | |
| | 204 | int res = Float.compare(testFloat, prototypeFloat); |
| | 205 | return comparatorResult.apply(res); |
| | 206 | }; |
| | 207 | negated = false; |
| | 208 | } |
| | 209 | |
| | 210 | /** |
| | 211 | * Create a new Op by negating an other op. |
| | 212 | * @param negate |
| | 213 | */ |
| | 214 | Op(Op negate) { |
| | 215 | this.function = (a, b) -> !negate.function.apply(a, b); |
| | 216 | negated = true; |
| | 217 | } |
| | 218 | |
| 125 | 219 | /** |
| 126 | 220 | * Evaluates a value against a reference string. |
| 127 | 221 | * @param testString The value. May be <code>null</code> |
| … |
… |
public interface Condition {
|
| 129 | 223 | * @return <code>true</code> if and only if this operation matches for the given value/reference pair. |
| 130 | 224 | */ |
| 131 | 225 | public boolean eval(String testString, String prototypeString) { |
| 132 | | if (testString == null && !NEGATED_OPS.contains(this)) |
| 133 | | return false; |
| 134 | | switch (this) { |
| 135 | | case EQ: |
| 136 | | return Objects.equals(testString, prototypeString); |
| 137 | | case NEQ: |
| 138 | | return !Objects.equals(testString, prototypeString); |
| 139 | | case REGEX: |
| 140 | | case NREGEX: |
| 141 | | final boolean contains = Pattern.compile(prototypeString).matcher(testString).find(); |
| 142 | | return REGEX.equals(this) ? contains : !contains; |
| 143 | | case ONE_OF: |
| 144 | | return testString != null && Arrays.asList(testString.split("\\s*;\\s*")).contains(prototypeString); |
| 145 | | case BEGINS_WITH: |
| 146 | | return testString != null && testString.startsWith(prototypeString); |
| 147 | | case ENDS_WITH: |
| 148 | | return testString != null && testString.endsWith(prototypeString); |
| 149 | | case CONTAINS: |
| 150 | | return testString != null && testString.contains(prototypeString); |
| 151 | | case GREATER_OR_EQUAL: |
| 152 | | case GREATER: |
| 153 | | case LESS_OR_EQUAL: |
| 154 | | case LESS: |
| 155 | | // See below |
| 156 | | break; |
| 157 | | default: |
| 158 | | throw new AssertionError(); |
| 159 | | } |
| 160 | | |
| 161 | | float testFloat; |
| 162 | | try { |
| 163 | | testFloat = Float.parseFloat(testString); |
| 164 | | } catch (NumberFormatException e) { |
| 165 | | return false; |
| 166 | | } |
| 167 | | float prototypeFloat = Float.parseFloat(prototypeString); |
| 168 | | |
| 169 | | switch (this) { |
| 170 | | case GREATER_OR_EQUAL: |
| 171 | | return testFloat >= prototypeFloat; |
| 172 | | case GREATER: |
| 173 | | return testFloat > prototypeFloat; |
| 174 | | case LESS_OR_EQUAL: |
| 175 | | return testFloat <= prototypeFloat; |
| 176 | | case LESS: |
| 177 | | return testFloat < prototypeFloat; |
| 178 | | default: |
| 179 | | throw new AssertionError(); |
| 180 | | } |
| | 226 | if (testString == null) |
| | 227 | return negated; |
| | 228 | else |
| | 229 | return function.apply(testString, prototypeString); |
| 181 | 230 | } |
| 182 | 231 | } |
| 183 | 232 | |
| … |
… |
public interface Condition {
|
| 201 | 250 | * |
| 202 | 251 | * Extra class for performance reasons. |
| 203 | 252 | */ |
| 204 | | class SimpleKeyValueCondition implements Condition { |
| | 253 | class SimpleKeyValueCondition implements Condition, ToTagConvertable { |
| 205 | 254 | /** |
| 206 | 255 | * The key to search for. |
| 207 | 256 | */ |
| … |
… |
public interface Condition {
|
| 226 | 275 | return v.equals(e.osm.get(k)); |
| 227 | 276 | } |
| 228 | 277 | |
| 229 | | public Tag asTag() { |
| | 278 | @Override |
| | 279 | public Tag asTag(OsmPrimitive primitive) { |
| 230 | 280 | return new Tag(k, v); |
| 231 | 281 | } |
| 232 | 282 | |
| … |
… |
public interface Condition {
|
| 241 | 291 | * <p>Represents a key/value condition which is either applied to a primitive.</p> |
| 242 | 292 | * |
| 243 | 293 | */ |
| 244 | | class KeyValueCondition implements Condition { |
| | 294 | class KeyValueCondition implements Condition, ToTagConvertable { |
| 245 | 295 | /** |
| 246 | 296 | * The key to search for. |
| 247 | 297 | */ |
| … |
… |
public interface Condition {
|
| 257 | 307 | /** |
| 258 | 308 | * If this flag is set, {@link #v} is treated as a key and the value is the value set for that key. |
| 259 | 309 | */ |
| 260 | | public boolean considerValAsKey; |
| | 310 | public final boolean considerValAsKey; |
| 261 | 311 | |
| 262 | 312 | /** |
| 263 | 313 | * <p>Creates a key/value-condition.</p> |
| … |
… |
public interface Condition {
|
| 279 | 329 | return op.eval(env.osm.get(k), considerValAsKey ? env.osm.get(v) : v); |
| 280 | 330 | } |
| 281 | 331 | |
| 282 | | public Tag asTag() { |
| | 332 | @Override |
| | 333 | public Tag asTag(OsmPrimitive primitive) { |
| 283 | 334 | return new Tag(k, v); |
| 284 | 335 | } |
| 285 | 336 | |
| … |
… |
public interface Condition {
|
| 289 | 340 | } |
| 290 | 341 | } |
| 291 | 342 | |
| | 343 | /** |
| | 344 | * This condition requires a fixed key to match a given regexp |
| | 345 | */ |
| 292 | 346 | class KeyValueRegexpCondition extends KeyValueCondition { |
| 293 | | |
| 294 | | public final Pattern pattern; |
| 295 | 347 | protected static final Set<Op> SUPPORTED_OPS = EnumSet.of(Op.REGEX, Op.NREGEX); |
| 296 | 348 | |
| | 349 | final Pattern pattern; |
| | 350 | |
| 297 | 351 | public KeyValueRegexpCondition(String k, String v, Op op, boolean considerValAsKey) { |
| 298 | 352 | super(k, v, op, considerValAsKey); |
| 299 | 353 | CheckParameterUtil.ensureThat(!considerValAsKey, "considerValAsKey is not supported"); |
| … |
… |
public interface Condition {
|
| 318 | 372 | } |
| 319 | 373 | } |
| 320 | 374 | |
| | 375 | /** |
| | 376 | * A condition that checks that a key with the matching pattern has a value with the matching pattern. |
| | 377 | */ |
| 321 | 378 | class RegexpKeyValueRegexpCondition extends KeyValueRegexpCondition { |
| 322 | 379 | |
| 323 | 380 | public final Pattern keyPattern; |
| 324 | 381 | |
| | 382 | /** |
| | 383 | * Create a condition in which the key and the value need to match a given regexp |
| | 384 | * @param k The key regexp |
| | 385 | * @param v The value regexp |
| | 386 | * @param op The operation to use when comparing the key and the value. |
| | 387 | */ |
| 325 | 388 | public RegexpKeyValueRegexpCondition(String k, String v, Op op) { |
| 326 | 389 | super(k, v, op, false); |
| 327 | 390 | this.keyPattern = Pattern.compile(k); |
| … |
… |
public interface Condition {
|
| 418 | 481 | * LINK: not supported |
| 419 | 482 | * </pre> |
| 420 | 483 | */ |
| 421 | | class KeyCondition implements Condition { |
| | 484 | class KeyCondition implements Condition, ToTagConvertable { |
| 422 | 485 | |
| 423 | 486 | /** |
| 424 | 487 | * The key name. |
| … |
… |
public interface Condition {
|
| 483 | 546 | * @param p The primitive to get the value from. |
| 484 | 547 | * @return The tag. |
| 485 | 548 | */ |
| | 549 | @Override |
| 486 | 550 | public Tag asTag(OsmPrimitive p) { |
| 487 | 551 | String key = label; |
| 488 | 552 | if (KeyMatchType.REGEX.equals(matchType)) { |
| 489 | | final Collection<String> matchingKeys = SubclassFilteredCollection.filter(p.keySet(), containsPattern); |
| 490 | | if (!matchingKeys.isEmpty()) { |
| 491 | | key = matchingKeys.iterator().next(); |
| 492 | | } |
| | 553 | key = p.keySet().stream().filter(containsPattern).findAny().orElse(key); |
| 493 | 554 | } |
| 494 | 555 | return new Tag(key, p.get(key)); |
| 495 | 556 | } |
| … |
… |
public interface Condition {
|
| 512 | 573 | |
| 513 | 574 | @Override |
| 514 | 575 | public boolean applies(Environment env) { |
| 515 | | return env != null && env.getCascade(env.layer) != null && (not ^ env.getCascade(env.layer).containsKey(id)); |
| | 576 | Cascade cascade = env.getCascade(env.layer); |
| | 577 | return cascade != null && (not ^ cascade.containsKey(id)); |
| 516 | 578 | } |
| 517 | 579 | |
| 518 | 580 | @Override |
| … |
… |
public interface Condition {
|
| 702 | 764 | this.not = not; |
| 703 | 765 | } |
| 704 | 766 | |
| | 767 | /** |
| | 768 | * Create a new pseudo class condition |
| | 769 | * @param id The id of the pseudo class |
| | 770 | * @param not <code>true</code> to invert the condition |
| | 771 | * @param context The context the class is found in. |
| | 772 | * @return The new condition |
| | 773 | */ |
| 705 | 774 | public static PseudoClassCondition createPseudoClassCondition(String id, boolean not, Context context) { |
| 706 | 775 | CheckParameterUtil.ensureThat(!"sameTags".equals(id) || Context.LINK.equals(context), "sameTags only supported in LINK context"); |
| 707 | 776 | if ("open_end".equals(id)) { |
| … |
… |
public interface Condition {
|
| 752 | 821 | } |
| 753 | 822 | } |
| 754 | 823 | |
| | 824 | /** |
| | 825 | * A condition that is fulfilled whenever the expression is evaluated to be true. |
| | 826 | */ |
| 755 | 827 | class ExpressionCondition implements Condition { |
| 756 | 828 | |
| 757 | 829 | private final Expression e; |
| … |
… |
public interface Condition {
|
| 775 | 847 | return '[' + e.toString() + ']'; |
| 776 | 848 | } |
| 777 | 849 | } |
| | 850 | |
| | 851 | /** |
| | 852 | * This is a condition that can be converted to a tag |
| | 853 | * @author Michael Zangl |
| | 854 | * @since xxx |
| | 855 | */ |
| | 856 | public interface ToTagConvertable { |
| | 857 | /** |
| | 858 | * Converts the current condition to a tag |
| | 859 | * @param primitive A primitive to use as context. May be ignored. |
| | 860 | * @return A tag with the key/value of this condition. |
| | 861 | */ |
| | 862 | public Tag asTag(OsmPrimitive primitive); |
| | 863 | } |
| 778 | 864 | } |
diff --git a/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java b/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java
index 5c5cb14..2c1146a 100644
|
a
|
b
|
public interface Selector {
|
| 464 | 464 | */ |
| 465 | 465 | @Override |
| 466 | 466 | public boolean matches(Environment env) { |
| | 467 | CheckParameterUtil.ensureParameterNotNull(env, "env"); |
| 467 | 468 | if (conds == null) return true; |
| 468 | 469 | for (Condition c : conds) { |
| 469 | 470 | try { |