[298] | 1 | // License: GPL. Copyright 2007 by Immanuel Scholz and others
|
---|
[319] | 2 | package org.openstreetmap.josm.actions.search;
|
---|
| 3 |
|
---|
[1213] | 4 | import static org.openstreetmap.josm.tools.I18n.marktr;
|
---|
[888] | 5 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 6 |
|
---|
[319] | 7 | import java.io.PushbackReader;
|
---|
| 8 | import java.io.StringReader;
|
---|
[3556] | 9 | import java.text.Normalizer;
|
---|
[4372] | 10 | import java.util.Collection;
|
---|
[4679] | 11 | import java.util.Date;
|
---|
[1213] | 12 | import java.util.regex.Matcher;
|
---|
| 13 | import java.util.regex.Pattern;
|
---|
| 14 | import java.util.regex.PatternSyntaxException;
|
---|
[319] | 15 |
|
---|
[1603] | 16 | import org.openstreetmap.josm.Main;
|
---|
[2993] | 17 | import org.openstreetmap.josm.actions.search.PushbackTokenizer.Range;
|
---|
[2645] | 18 | import org.openstreetmap.josm.actions.search.PushbackTokenizer.Token;
|
---|
[4375] | 19 | import org.openstreetmap.josm.data.Bounds;
|
---|
[319] | 20 | import org.openstreetmap.josm.data.osm.Node;
|
---|
| 21 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
[2863] | 22 | import org.openstreetmap.josm.data.osm.OsmUtils;
|
---|
[343] | 23 | import org.openstreetmap.josm.data.osm.Relation;
|
---|
[1603] | 24 | import org.openstreetmap.josm.data.osm.RelationMember;
|
---|
[582] | 25 | import org.openstreetmap.josm.data.osm.Way;
|
---|
[1499] | 26 | import org.openstreetmap.josm.tools.DateUtils;
|
---|
[4085] | 27 | import org.openstreetmap.josm.tools.Geometry;
|
---|
[319] | 28 |
|
---|
| 29 | /**
|
---|
[2645] | 30 | Implements a google-like search.
|
---|
| 31 | <br>
|
---|
| 32 | Grammar:
|
---|
| 33 | <pre>
|
---|
| 34 | expression =
|
---|
| 35 | fact | expression
|
---|
| 36 | fact expression
|
---|
| 37 | fact
|
---|
| 38 |
|
---|
| 39 | fact =
|
---|
| 40 | ( expression )
|
---|
| 41 | -fact
|
---|
[2863] | 42 | term?
|
---|
[2645] | 43 | term=term
|
---|
| 44 | term:term
|
---|
| 45 | term
|
---|
| 46 | </pre>
|
---|
| 47 |
|
---|
| 48 | @author Imi
|
---|
[319] | 49 | */
|
---|
| 50 | public class SearchCompiler {
|
---|
| 51 |
|
---|
[1169] | 52 | private boolean caseSensitive = false;
|
---|
[1213] | 53 | private boolean regexSearch = false;
|
---|
[2510] | 54 | private static String rxErrorMsg = marktr("The regex \"{0}\" had a parse error at offset {1}, full error:\n\n{2}");
|
---|
[3305] | 55 | private static String rxErrorMsgNoPos = marktr("The regex \"{0}\" had a parse error, full error:\n\n{1}");
|
---|
[1169] | 56 | private PushbackTokenizer tokenizer;
|
---|
[513] | 57 |
|
---|
[1213] | 58 | public SearchCompiler(boolean caseSensitive, boolean regexSearch, PushbackTokenizer tokenizer) {
|
---|
[1169] | 59 | this.caseSensitive = caseSensitive;
|
---|
[1213] | 60 | this.regexSearch = regexSearch;
|
---|
[1169] | 61 | this.tokenizer = tokenizer;
|
---|
| 62 | }
|
---|
[319] | 63 |
|
---|
[1169] | 64 | abstract public static class Match {
|
---|
[2510] | 65 | abstract public boolean match(OsmPrimitive osm);
|
---|
[4372] | 66 |
|
---|
| 67 | /**
|
---|
| 68 | * Tests whether one of the primitives matches.
|
---|
| 69 | */
|
---|
| 70 | protected boolean existsMatch(Collection<? extends OsmPrimitive> primitives) {
|
---|
| 71 | for (OsmPrimitive p : primitives) {
|
---|
[4546] | 72 | if (match(p))
|
---|
[4372] | 73 | return true;
|
---|
| 74 | }
|
---|
| 75 | return false;
|
---|
| 76 | }
|
---|
| 77 |
|
---|
| 78 | /**
|
---|
| 79 | * Tests whether all primitives match.
|
---|
| 80 | */
|
---|
| 81 | protected boolean forallMatch(Collection<? extends OsmPrimitive> primitives) {
|
---|
| 82 | for (OsmPrimitive p : primitives) {
|
---|
[4546] | 83 | if (!match(p))
|
---|
[4372] | 84 | return false;
|
---|
| 85 | }
|
---|
| 86 | return true;
|
---|
| 87 | }
|
---|
[1169] | 88 | }
|
---|
[319] | 89 |
|
---|
[2791] | 90 | public static class Always extends Match {
|
---|
[2912] | 91 | public static Always INSTANCE = new Always();
|
---|
[1169] | 92 | @Override public boolean match(OsmPrimitive osm) {
|
---|
| 93 | return true;
|
---|
| 94 | }
|
---|
| 95 | }
|
---|
[319] | 96 |
|
---|
[2791] | 97 | public static class Never extends Match {
|
---|
| 98 | @Override
|
---|
| 99 | public boolean match(OsmPrimitive osm) {
|
---|
| 100 | return false;
|
---|
| 101 | }
|
---|
| 102 | }
|
---|
| 103 |
|
---|
[3355] | 104 | public static class Not extends Match {
|
---|
[1169] | 105 | private final Match match;
|
---|
| 106 | public Not(Match match) {this.match = match;}
|
---|
[2510] | 107 | @Override public boolean match(OsmPrimitive osm) {
|
---|
[1169] | 108 | return !match.match(osm);
|
---|
| 109 | }
|
---|
| 110 | @Override public String toString() {return "!"+match;}
|
---|
[4546] | 111 | public Match getMatch() {
|
---|
| 112 | return match;
|
---|
| 113 | }
|
---|
[1169] | 114 | }
|
---|
[319] | 115 |
|
---|
[2863] | 116 | private static class BooleanMatch extends Match {
|
---|
| 117 | private final String key;
|
---|
| 118 | private final boolean defaultValue;
|
---|
| 119 |
|
---|
| 120 | public BooleanMatch(String key, boolean defaultValue) {
|
---|
| 121 | this.key = key;
|
---|
| 122 | this.defaultValue = defaultValue;
|
---|
| 123 | }
|
---|
| 124 | @Override
|
---|
| 125 | public boolean match(OsmPrimitive osm) {
|
---|
| 126 | Boolean ret = OsmUtils.getOsmBoolean(osm.get(key));
|
---|
| 127 | if (ret == null)
|
---|
| 128 | return defaultValue;
|
---|
| 129 | else
|
---|
| 130 | return ret;
|
---|
| 131 | }
|
---|
| 132 | }
|
---|
| 133 |
|
---|
[4546] | 134 | public static class And extends Match {
|
---|
| 135 | private final Match lhs;
|
---|
| 136 | private final Match rhs;
|
---|
[1169] | 137 | public And(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
|
---|
[2510] | 138 | @Override public boolean match(OsmPrimitive osm) {
|
---|
[1169] | 139 | return lhs.match(osm) && rhs.match(osm);
|
---|
| 140 | }
|
---|
| 141 | @Override public String toString() {return lhs+" && "+rhs;}
|
---|
[4546] | 142 | public Match getLhs() {
|
---|
| 143 | return lhs;
|
---|
| 144 | }
|
---|
| 145 | public Match getRhs() {
|
---|
| 146 | return rhs;
|
---|
| 147 | }
|
---|
[1169] | 148 | }
|
---|
[319] | 149 |
|
---|
[4546] | 150 | public static class Or extends Match {
|
---|
| 151 | private final Match lhs;
|
---|
| 152 | private final Match rhs;
|
---|
[1169] | 153 | public Or(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
|
---|
[2510] | 154 | @Override public boolean match(OsmPrimitive osm) {
|
---|
[1169] | 155 | return lhs.match(osm) || rhs.match(osm);
|
---|
| 156 | }
|
---|
| 157 | @Override public String toString() {return lhs+" || "+rhs;}
|
---|
[4546] | 158 | public Match getLhs() {
|
---|
| 159 | return lhs;
|
---|
| 160 | }
|
---|
| 161 | public Match getRhs() {
|
---|
| 162 | return rhs;
|
---|
| 163 | }
|
---|
[1169] | 164 | }
|
---|
[319] | 165 |
|
---|
[1169] | 166 | private static class Id extends Match {
|
---|
| 167 | private long id;
|
---|
[2973] | 168 | public Id(long id) {
|
---|
| 169 | this.id = id;
|
---|
| 170 | }
|
---|
[1169] | 171 | @Override public boolean match(OsmPrimitive osm) {
|
---|
[3140] | 172 | return id == 0?osm.isNew():osm.getUniqueId() == id;
|
---|
[1169] | 173 | }
|
---|
| 174 | @Override public String toString() {return "id="+id;}
|
---|
| 175 | }
|
---|
[319] | 176 |
|
---|
[2604] | 177 | private static class ChangesetId extends Match {
|
---|
| 178 | private long changesetid;
|
---|
| 179 | public ChangesetId(long changesetid) {this.changesetid = changesetid;}
|
---|
| 180 | @Override public boolean match(OsmPrimitive osm) {
|
---|
| 181 | return osm.getChangesetId() == changesetid;
|
---|
| 182 | }
|
---|
| 183 | @Override public String toString() {return "changeset="+changesetid;}
|
---|
| 184 | }
|
---|
| 185 |
|
---|
[2695] | 186 | private static class Version extends Match {
|
---|
| 187 | private long version;
|
---|
| 188 | public Version(long version) {this.version = version;}
|
---|
| 189 | @Override public boolean match(OsmPrimitive osm) {
|
---|
| 190 | return osm.getVersion() == version;
|
---|
| 191 | }
|
---|
| 192 | @Override public String toString() {return "version="+version;}
|
---|
| 193 | }
|
---|
| 194 |
|
---|
[2510] | 195 | private static class KeyValue extends Match {
|
---|
| 196 | private final String key;
|
---|
| 197 | private final Pattern keyPattern;
|
---|
| 198 | private final String value;
|
---|
| 199 | private final Pattern valuePattern;
|
---|
| 200 | private final boolean caseSensitive;
|
---|
[1213] | 201 |
|
---|
[2510] | 202 | public KeyValue(String key, String value, boolean regexSearch, boolean caseSensitive) throws ParseError {
|
---|
| 203 | this.caseSensitive = caseSensitive;
|
---|
[1607] | 204 | if (regexSearch) {
|
---|
[2510] | 205 | int searchFlags = regexFlags(caseSensitive);
|
---|
| 206 |
|
---|
| 207 | try {
|
---|
| 208 | this.keyPattern = Pattern.compile(key, searchFlags);
|
---|
[3303] | 209 | } catch (PatternSyntaxException e) {
|
---|
| 210 | throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
|
---|
| 211 | } catch (Exception e) {
|
---|
[3305] | 212 | throw new ParseError(tr(rxErrorMsgNoPos, key, e.getMessage()));
|
---|
[3303] | 213 | }
|
---|
| 214 | try {
|
---|
[2510] | 215 | this.valuePattern = Pattern.compile(value, searchFlags);
|
---|
| 216 | } catch (PatternSyntaxException e) {
|
---|
| 217 | throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
|
---|
[3303] | 218 | } catch (Exception e) {
|
---|
[3305] | 219 | throw new ParseError(tr(rxErrorMsgNoPos, value, e.getMessage()));
|
---|
[2510] | 220 | }
|
---|
| 221 | this.key = key;
|
---|
| 222 | this.value = value;
|
---|
| 223 |
|
---|
| 224 | } else if (caseSensitive) {
|
---|
| 225 | this.key = key;
|
---|
| 226 | this.value = value;
|
---|
| 227 | this.keyPattern = null;
|
---|
| 228 | this.valuePattern = null;
|
---|
| 229 | } else {
|
---|
| 230 | this.key = key.toLowerCase();
|
---|
| 231 | this.value = value;
|
---|
| 232 | this.keyPattern = null;
|
---|
| 233 | this.valuePattern = null;
|
---|
| 234 | }
|
---|
| 235 | }
|
---|
| 236 |
|
---|
| 237 | @Override public boolean match(OsmPrimitive osm) {
|
---|
| 238 |
|
---|
| 239 | if (keyPattern != null) {
|
---|
[1843] | 240 | if (!osm.hasKeys())
|
---|
[1213] | 241 | return false;
|
---|
| 242 |
|
---|
| 243 | /* The string search will just get a key like
|
---|
| 244 | * 'highway' and look that up as osm.get(key). But
|
---|
| 245 | * since we're doing a regex match we'll have to loop
|
---|
| 246 | * over all the keys to see if they match our regex,
|
---|
| 247 | * and only then try to match against the value
|
---|
| 248 | */
|
---|
| 249 |
|
---|
[2906] | 250 | for (String k: osm.keySet()) {
|
---|
| 251 | String v = osm.get(k);
|
---|
[1213] | 252 |
|
---|
[2510] | 253 | Matcher matcherKey = keyPattern.matcher(k);
|
---|
[1213] | 254 | boolean matchedKey = matcherKey.find();
|
---|
| 255 |
|
---|
| 256 | if (matchedKey) {
|
---|
[2510] | 257 | Matcher matcherValue = valuePattern.matcher(v);
|
---|
[1213] | 258 | boolean matchedValue = matcherValue.find();
|
---|
| 259 |
|
---|
| 260 | if (matchedValue)
|
---|
| 261 | return true;
|
---|
| 262 | }
|
---|
| 263 | }
|
---|
| 264 | } else {
|
---|
[2636] | 265 | String mv = null;
|
---|
[1213] | 266 |
|
---|
[1814] | 267 | if (key.equals("timestamp")) {
|
---|
[2636] | 268 | mv = DateUtils.fromDate(osm.getTimestamp());
|
---|
[1814] | 269 | } else {
|
---|
[2636] | 270 | mv = osm.get(key);
|
---|
[1814] | 271 | }
|
---|
[1213] | 272 |
|
---|
[2636] | 273 | if (mv == null)
|
---|
[1213] | 274 | return false;
|
---|
| 275 |
|
---|
[2636] | 276 | String v1 = caseSensitive ? mv : mv.toLowerCase();
|
---|
| 277 | String v2 = caseSensitive ? value : value.toLowerCase();
|
---|
[1213] | 278 |
|
---|
[3556] | 279 | v1 = Normalizer.normalize(v1, Normalizer.Form.NFC);
|
---|
| 280 | v2 = Normalizer.normalize(v2, Normalizer.Form.NFC);
|
---|
[2636] | 281 | return v1.indexOf(v2) != -1;
|
---|
[1213] | 282 | }
|
---|
| 283 |
|
---|
| 284 | return false;
|
---|
[1169] | 285 | }
|
---|
| 286 | @Override public String toString() {return key+"="+value;}
|
---|
| 287 | }
|
---|
[319] | 288 |
|
---|
[2645] | 289 | public static class ExactKeyValue extends Match {
|
---|
[1641] | 290 |
|
---|
| 291 | private enum Mode {
|
---|
| 292 | ANY, ANY_KEY, ANY_VALUE, EXACT, NONE, MISSING_KEY,
|
---|
| 293 | ANY_KEY_REGEXP, ANY_VALUE_REGEXP, EXACT_REGEXP, MISSING_KEY_REGEXP;
|
---|
| 294 | }
|
---|
| 295 |
|
---|
| 296 | private final String key;
|
---|
| 297 | private final String value;
|
---|
| 298 | private final Pattern keyPattern;
|
---|
| 299 | private final Pattern valuePattern;
|
---|
| 300 | private final Mode mode;
|
---|
| 301 |
|
---|
| 302 | public ExactKeyValue(boolean regexp, String key, String value) throws ParseError {
|
---|
[3556] | 303 | if ("".equals(key))
|
---|
[1641] | 304 | throw new ParseError(tr("Key cannot be empty when tag operator is used. Sample use: key=value"));
|
---|
| 305 | this.key = key;
|
---|
[2645] | 306 | this.value = value == null?"":value;
|
---|
[2993] | 307 | if ("".equals(this.value) && "*".equals(key)) {
|
---|
[1641] | 308 | mode = Mode.NONE;
|
---|
[2993] | 309 | } else if ("".equals(this.value)) {
|
---|
[1641] | 310 | if (regexp) {
|
---|
| 311 | mode = Mode.MISSING_KEY_REGEXP;
|
---|
| 312 | } else {
|
---|
| 313 | mode = Mode.MISSING_KEY;
|
---|
| 314 | }
|
---|
[2993] | 315 | } else if ("*".equals(key) && "*".equals(this.value)) {
|
---|
[1641] | 316 | mode = Mode.ANY;
|
---|
| 317 | } else if ("*".equals(key)) {
|
---|
| 318 | if (regexp) {
|
---|
| 319 | mode = Mode.ANY_KEY_REGEXP;
|
---|
| 320 | } else {
|
---|
| 321 | mode = Mode.ANY_KEY;
|
---|
| 322 | }
|
---|
[2993] | 323 | } else if ("*".equals(this.value)) {
|
---|
[1641] | 324 | if (regexp) {
|
---|
| 325 | mode = Mode.ANY_VALUE_REGEXP;
|
---|
| 326 | } else {
|
---|
| 327 | mode = Mode.ANY_VALUE;
|
---|
| 328 | }
|
---|
| 329 | } else {
|
---|
| 330 | if (regexp) {
|
---|
| 331 | mode = Mode.EXACT_REGEXP;
|
---|
| 332 | } else {
|
---|
| 333 | mode = Mode.EXACT;
|
---|
| 334 | }
|
---|
| 335 | }
|
---|
| 336 |
|
---|
| 337 | if (regexp && key.length() > 0 && !key.equals("*")) {
|
---|
[3304] | 338 | try {
|
---|
| 339 | keyPattern = Pattern.compile(key, regexFlags(false));
|
---|
| 340 | } catch (PatternSyntaxException e) {
|
---|
| 341 | throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
|
---|
| 342 | } catch (Exception e) {
|
---|
[3305] | 343 | throw new ParseError(tr(rxErrorMsgNoPos, key, e.getMessage()));
|
---|
[3304] | 344 | }
|
---|
[1641] | 345 | } else {
|
---|
| 346 | keyPattern = null;
|
---|
| 347 | }
|
---|
[2993] | 348 | if (regexp && this.value.length() > 0 && !this.value.equals("*")) {
|
---|
[1786] | 349 | try {
|
---|
[3304] | 350 | valuePattern = Pattern.compile(this.value, regexFlags(false));
|
---|
[1786] | 351 | } catch (PatternSyntaxException e) {
|
---|
[3304] | 352 | throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
|
---|
| 353 | } catch (Exception e) {
|
---|
[3305] | 354 | throw new ParseError(tr(rxErrorMsgNoPos, value, e.getMessage()));
|
---|
[1786] | 355 | }
|
---|
[1641] | 356 | } else {
|
---|
| 357 | valuePattern = null;
|
---|
| 358 | }
|
---|
| 359 | }
|
---|
| 360 |
|
---|
| 361 | @Override
|
---|
[2510] | 362 | public boolean match(OsmPrimitive osm) {
|
---|
[1641] | 363 |
|
---|
[1843] | 364 | if (!osm.hasKeys())
|
---|
[1641] | 365 | return mode == Mode.NONE;
|
---|
| 366 |
|
---|
| 367 | switch (mode) {
|
---|
[2407] | 368 | case NONE:
|
---|
| 369 | return false;
|
---|
| 370 | case MISSING_KEY:
|
---|
| 371 | return osm.get(key) == null;
|
---|
| 372 | case ANY:
|
---|
| 373 | return true;
|
---|
| 374 | case ANY_VALUE:
|
---|
| 375 | return osm.get(key) != null;
|
---|
| 376 | case ANY_KEY:
|
---|
| 377 | for (String v:osm.getKeys().values()) {
|
---|
| 378 | if (v.equals(value))
|
---|
| 379 | return true;
|
---|
| 380 | }
|
---|
| 381 | return false;
|
---|
| 382 | case EXACT:
|
---|
| 383 | return value.equals(osm.get(key));
|
---|
| 384 | case ANY_KEY_REGEXP:
|
---|
| 385 | for (String v:osm.getKeys().values()) {
|
---|
| 386 | if (valuePattern.matcher(v).matches())
|
---|
| 387 | return true;
|
---|
| 388 | }
|
---|
| 389 | return false;
|
---|
| 390 | case ANY_VALUE_REGEXP:
|
---|
| 391 | case EXACT_REGEXP:
|
---|
[2906] | 392 | for (String key: osm.keySet()) {
|
---|
| 393 | if (keyPattern.matcher(key).matches()) {
|
---|
[2407] | 394 | if (mode == Mode.ANY_VALUE_REGEXP
|
---|
[2906] | 395 | || valuePattern.matcher(osm.get(key)).matches())
|
---|
[1641] | 396 | return true;
|
---|
| 397 | }
|
---|
[2407] | 398 | }
|
---|
| 399 | return false;
|
---|
| 400 | case MISSING_KEY_REGEXP:
|
---|
| 401 | for (String k:osm.keySet()) {
|
---|
| 402 | if (keyPattern.matcher(k).matches())
|
---|
| 403 | return false;
|
---|
| 404 | }
|
---|
| 405 | return true;
|
---|
[1641] | 406 | }
|
---|
| 407 | throw new AssertionError("Missed state");
|
---|
| 408 | }
|
---|
| 409 |
|
---|
| 410 | @Override
|
---|
| 411 | public String toString() {
|
---|
| 412 | return key + '=' + value;
|
---|
| 413 | }
|
---|
| 414 |
|
---|
| 415 | }
|
---|
| 416 |
|
---|
[2510] | 417 | private static class Any extends Match {
|
---|
| 418 | private final String search;
|
---|
| 419 | private final Pattern searchRegex;
|
---|
| 420 | private final boolean caseSensitive;
|
---|
[1213] | 421 |
|
---|
[2510] | 422 | public Any(String s, boolean regexSearch, boolean caseSensitive) throws ParseError {
|
---|
[3556] | 423 | s = Normalizer.normalize(s, Normalizer.Form.NFC);
|
---|
[2510] | 424 | this.caseSensitive = caseSensitive;
|
---|
[1213] | 425 | if (regexSearch) {
|
---|
[1607] | 426 | try {
|
---|
[2510] | 427 | this.searchRegex = Pattern.compile(s, regexFlags(caseSensitive));
|
---|
[1607] | 428 | } catch (PatternSyntaxException e) {
|
---|
| 429 | throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
|
---|
[3303] | 430 | } catch (Exception e) {
|
---|
[3305] | 431 | throw new ParseError(tr(rxErrorMsgNoPos, s, e.getMessage()));
|
---|
[1213] | 432 | }
|
---|
[2510] | 433 | this.search = s;
|
---|
| 434 | } else if (caseSensitive) {
|
---|
| 435 | this.search = s;
|
---|
| 436 | this.searchRegex = null;
|
---|
[1213] | 437 | } else {
|
---|
[2510] | 438 | this.search = s.toLowerCase();
|
---|
| 439 | this.searchRegex = null;
|
---|
[1213] | 440 | }
|
---|
[2510] | 441 | }
|
---|
[1213] | 442 |
|
---|
[2510] | 443 | @Override public boolean match(OsmPrimitive osm) {
|
---|
[3556] | 444 | if (!osm.hasKeys() && osm.getUser() == null)
|
---|
[2510] | 445 | return search.equals("");
|
---|
| 446 |
|
---|
[2906] | 447 | for (String key: osm.keySet()) {
|
---|
| 448 | String value = osm.get(key);
|
---|
[2510] | 449 | if (searchRegex != null) {
|
---|
[1213] | 450 |
|
---|
[3556] | 451 | value = Normalizer.normalize(value, Normalizer.Form.NFC);
|
---|
[1213] | 452 |
|
---|
| 453 | Matcher keyMatcher = searchRegex.matcher(key);
|
---|
| 454 | Matcher valMatcher = searchRegex.matcher(value);
|
---|
| 455 |
|
---|
| 456 | boolean keyMatchFound = keyMatcher.find();
|
---|
| 457 | boolean valMatchFound = valMatcher.find();
|
---|
| 458 |
|
---|
| 459 | if (keyMatchFound || valMatchFound)
|
---|
| 460 | return true;
|
---|
| 461 | } else {
|
---|
[2962] | 462 | if (!caseSensitive) {
|
---|
[2906] | 463 | key = key.toLowerCase();
|
---|
| 464 | value = value.toLowerCase();
|
---|
| 465 | }
|
---|
[1213] | 466 |
|
---|
[3556] | 467 | value = Normalizer.normalize(value, Normalizer.Form.NFC);
|
---|
[1213] | 468 |
|
---|
| 469 | if (key.indexOf(search) != -1 || value.indexOf(search) != -1)
|
---|
| 470 | return true;
|
---|
| 471 | }
|
---|
[1169] | 472 | }
|
---|
| 473 | return false;
|
---|
| 474 | }
|
---|
[2510] | 475 | @Override public String toString() {
|
---|
| 476 | return search;
|
---|
| 477 | }
|
---|
[1169] | 478 | }
|
---|
[319] | 479 |
|
---|
[1169] | 480 | private static class ExactType extends Match {
|
---|
[1625] | 481 | private final Class<?> type;
|
---|
| 482 | public ExactType(String type) throws ParseError {
|
---|
| 483 | if ("node".equals(type)) {
|
---|
| 484 | this.type = Node.class;
|
---|
| 485 | } else if ("way".equals(type)) {
|
---|
| 486 | this.type = Way.class;
|
---|
| 487 | } else if ("relation".equals(type)) {
|
---|
| 488 | this.type = Relation.class;
|
---|
[1814] | 489 | } else
|
---|
[1625] | 490 | throw new ParseError(tr("Unknown primitive type: {0}. Allowed values are node, way or relation",
|
---|
| 491 | type));
|
---|
| 492 | }
|
---|
[1169] | 493 | @Override public boolean match(OsmPrimitive osm) {
|
---|
[1625] | 494 | return osm.getClass() == type;
|
---|
[1169] | 495 | }
|
---|
| 496 | @Override public String toString() {return "type="+type;}
|
---|
| 497 | }
|
---|
[515] | 498 |
|
---|
[1169] | 499 | private static class UserMatch extends Match {
|
---|
[2863] | 500 | private String user;
|
---|
[2070] | 501 | public UserMatch(String user) {
|
---|
[2567] | 502 | if (user.equals("anonymous")) {
|
---|
| 503 | this.user = null;
|
---|
[2070] | 504 | } else {
|
---|
[2863] | 505 | this.user = user;
|
---|
[2070] | 506 | }
|
---|
| 507 | }
|
---|
[2567] | 508 |
|
---|
[1169] | 509 | @Override public boolean match(OsmPrimitive osm) {
|
---|
[2863] | 510 | if (osm.getUser() == null)
|
---|
| 511 | return user == null;
|
---|
| 512 | else
|
---|
| 513 | return osm.getUser().hasName(user);
|
---|
[1169] | 514 | }
|
---|
[2567] | 515 |
|
---|
[2070] | 516 | @Override public String toString() {
|
---|
[2863] | 517 | return "user=" + user == null ? "" : user;
|
---|
[2070] | 518 | }
|
---|
[1169] | 519 | }
|
---|
[522] | 520 |
|
---|
[3305] | 521 | private static class RoleMatch extends Match {
|
---|
| 522 | private String role;
|
---|
| 523 | public RoleMatch(String role) {
|
---|
| 524 | if (role == null) {
|
---|
| 525 | this.role = "";
|
---|
| 526 | } else {
|
---|
| 527 | this.role = role;
|
---|
| 528 | }
|
---|
| 529 | }
|
---|
| 530 |
|
---|
| 531 | @Override public boolean match(OsmPrimitive osm) {
|
---|
| 532 | for (OsmPrimitive ref: osm.getReferrers()) {
|
---|
| 533 | if (ref instanceof Relation && !ref.isIncomplete() && !ref.isDeleted()) {
|
---|
| 534 | for (RelationMember m : ((Relation) ref).getMembers()) {
|
---|
| 535 | if (m.getMember() == osm) {
|
---|
| 536 | String testRole = m.getRole();
|
---|
| 537 | if(role.equals(testRole == null ? "" : testRole))
|
---|
| 538 | return true;
|
---|
| 539 | }
|
---|
| 540 | }
|
---|
| 541 | }
|
---|
| 542 | }
|
---|
| 543 | return false;
|
---|
| 544 | }
|
---|
| 545 |
|
---|
| 546 | @Override public String toString() {
|
---|
| 547 | return "role=" + role;
|
---|
| 548 | }
|
---|
| 549 | }
|
---|
| 550 |
|
---|
[4346] | 551 | private abstract static class CountRange extends Match {
|
---|
| 552 |
|
---|
[4679] | 553 | private long minCount;
|
---|
| 554 | private long maxCount;
|
---|
[4346] | 555 |
|
---|
[4679] | 556 | public CountRange(long minCount, long maxCount) {
|
---|
[4346] | 557 | this.minCount = Math.min(minCount, maxCount);
|
---|
| 558 | this.maxCount = Math.max(minCount, maxCount);
|
---|
| 559 | }
|
---|
| 560 |
|
---|
[4679] | 561 | protected abstract Long getCount(OsmPrimitive osm);
|
---|
[4346] | 562 |
|
---|
| 563 | protected abstract String getCountString();
|
---|
| 564 |
|
---|
| 565 | @Override
|
---|
| 566 | public boolean match(OsmPrimitive osm) {
|
---|
[4679] | 567 | Long count = getCount(osm);
|
---|
[4546] | 568 | if (count == null)
|
---|
[4346] | 569 | return false;
|
---|
[4546] | 570 | else
|
---|
[4346] | 571 | return (count >= minCount) && (count <= maxCount);
|
---|
[1389] | 572 | }
|
---|
[4346] | 573 |
|
---|
| 574 | @Override
|
---|
| 575 | public String toString() {
|
---|
| 576 | return getCountString() + "=" + minCount + "-" + maxCount;
|
---|
[1389] | 577 | }
|
---|
| 578 | }
|
---|
[319] | 579 |
|
---|
[4346] | 580 |
|
---|
| 581 |
|
---|
| 582 | private static class NodeCountRange extends CountRange {
|
---|
| 583 |
|
---|
[4679] | 584 | public NodeCountRange(long minCount, long maxCount) {
|
---|
[4346] | 585 | super(minCount, maxCount);
|
---|
| 586 | }
|
---|
| 587 |
|
---|
| 588 | @Override
|
---|
[4679] | 589 | protected Long getCount(OsmPrimitive osm) {
|
---|
[4546] | 590 | if (!(osm instanceof Way))
|
---|
[4346] | 591 | return null;
|
---|
[4546] | 592 | else
|
---|
[4679] | 593 | return (long) ((Way) osm).getNodesCount();
|
---|
[2357] | 594 | }
|
---|
[4346] | 595 |
|
---|
| 596 | @Override
|
---|
| 597 | protected String getCountString() {
|
---|
| 598 | return "nodes";
|
---|
[2357] | 599 | }
|
---|
| 600 | }
|
---|
| 601 |
|
---|
[4346] | 602 | private static class TagCountRange extends CountRange {
|
---|
| 603 |
|
---|
[4679] | 604 | public TagCountRange(long minCount, long maxCount) {
|
---|
[4346] | 605 | super(minCount, maxCount);
|
---|
| 606 | }
|
---|
| 607 |
|
---|
| 608 | @Override
|
---|
[4679] | 609 | protected Long getCount(OsmPrimitive osm) {
|
---|
| 610 | return (long) osm.getKeys().size();
|
---|
[4346] | 611 | }
|
---|
| 612 |
|
---|
| 613 | @Override
|
---|
| 614 | protected String getCountString() {
|
---|
| 615 | return "tags";
|
---|
| 616 | }
|
---|
| 617 | }
|
---|
| 618 |
|
---|
[4679] | 619 | private static class TimestampRange extends CountRange {
|
---|
| 620 |
|
---|
| 621 | public TimestampRange(long minCount, long maxCount) {
|
---|
| 622 | super(minCount, maxCount);
|
---|
| 623 | }
|
---|
| 624 |
|
---|
| 625 | @Override
|
---|
| 626 | protected Long getCount(OsmPrimitive osm) {
|
---|
| 627 | return osm.getTimestamp().getTime();
|
---|
| 628 | }
|
---|
| 629 |
|
---|
| 630 | @Override
|
---|
| 631 | protected String getCountString() {
|
---|
| 632 | return "timestamp";
|
---|
| 633 | }
|
---|
| 634 |
|
---|
| 635 | }
|
---|
| 636 |
|
---|
[4347] | 637 | private static class New extends Match {
|
---|
| 638 | @Override public boolean match(OsmPrimitive osm) {
|
---|
| 639 | return osm.isNew();
|
---|
| 640 | }
|
---|
| 641 | @Override public String toString() {
|
---|
| 642 | return "new";
|
---|
| 643 | }
|
---|
| 644 | }
|
---|
| 645 |
|
---|
[1169] | 646 | private static class Modified extends Match {
|
---|
| 647 | @Override public boolean match(OsmPrimitive osm) {
|
---|
[3336] | 648 | return osm.isModified() || osm.isNewOrUndeleted();
|
---|
[1169] | 649 | }
|
---|
| 650 | @Override public String toString() {return "modified";}
|
---|
| 651 | }
|
---|
| 652 |
|
---|
| 653 | private static class Selected extends Match {
|
---|
| 654 | @Override public boolean match(OsmPrimitive osm) {
|
---|
[2264] | 655 | return Main.main.getCurrentDataSet().isSelected(osm);
|
---|
[1169] | 656 | }
|
---|
| 657 | @Override public String toString() {return "selected";}
|
---|
| 658 | }
|
---|
| 659 |
|
---|
| 660 | private static class Incomplete extends Match {
|
---|
| 661 | @Override public boolean match(OsmPrimitive osm) {
|
---|
[2578] | 662 | return osm.isIncomplete();
|
---|
[1169] | 663 | }
|
---|
| 664 | @Override public String toString() {return "incomplete";}
|
---|
| 665 | }
|
---|
[517] | 666 |
|
---|
[1375] | 667 | private static class Untagged extends Match {
|
---|
| 668 | @Override public boolean match(OsmPrimitive osm) {
|
---|
[3317] | 669 | return !osm.isTagged() && !osm.isIncomplete();
|
---|
[1375] | 670 | }
|
---|
| 671 | @Override public String toString() {return "untagged";}
|
---|
| 672 | }
|
---|
[1607] | 673 |
|
---|
[3818] | 674 | private static class Closed extends Match {
|
---|
| 675 | @Override public boolean match(OsmPrimitive osm) {
|
---|
| 676 | return osm instanceof Way && ((Way) osm).isClosed();
|
---|
| 677 | }
|
---|
| 678 | @Override public String toString() {return "closed";}
|
---|
| 679 | }
|
---|
| 680 |
|
---|
[4546] | 681 | public static class Parent extends Match {
|
---|
| 682 | private final Match child;
|
---|
| 683 | public Parent(Match m) {
|
---|
| 684 | if (m == null) {
|
---|
| 685 | // "parent" (null) should mean the same as "parent()"
|
---|
| 686 | // (Always). I.e. match everything
|
---|
| 687 | child = new Always();
|
---|
| 688 | } else {
|
---|
| 689 | child = m;
|
---|
| 690 | }
|
---|
| 691 | }
|
---|
[2510] | 692 | @Override public boolean match(OsmPrimitive osm) {
|
---|
[1603] | 693 | boolean isParent = false;
|
---|
[1375] | 694 |
|
---|
[1603] | 695 | if (osm instanceof Way) {
|
---|
[1862] | 696 | for (Node n : ((Way)osm).getNodes()) {
|
---|
[1603] | 697 | isParent |= child.match(n);
|
---|
[1814] | 698 | }
|
---|
[1603] | 699 | } else if (osm instanceof Relation) {
|
---|
[1925] | 700 | for (RelationMember member : ((Relation)osm).getMembers()) {
|
---|
[2645] | 701 | isParent |= child.match(member.getMember());
|
---|
[1603] | 702 | }
|
---|
| 703 | }
|
---|
| 704 | return isParent;
|
---|
| 705 | }
|
---|
| 706 | @Override public String toString() {return "parent(" + child + ")";}
|
---|
[4546] | 707 | public Match getChild() {
|
---|
| 708 | return child;
|
---|
| 709 | }
|
---|
[1603] | 710 | }
|
---|
| 711 |
|
---|
[4546] | 712 | public static class Child extends Match {
|
---|
[2273] | 713 | private final Match parent;
|
---|
| 714 |
|
---|
[2565] | 715 | public Child(Match m) {
|
---|
[1603] | 716 | // "child" (null) should mean the same as "child()"
|
---|
| 717 | // (Always). I.e. match everything
|
---|
[2273] | 718 | if (m == null) {
|
---|
[1603] | 719 | parent = new Always();
|
---|
[2273] | 720 | } else {
|
---|
| 721 | parent = m;
|
---|
[1814] | 722 | }
|
---|
[2273] | 723 | }
|
---|
[1603] | 724 |
|
---|
[2510] | 725 | @Override public boolean match(OsmPrimitive osm) {
|
---|
[1603] | 726 | boolean isChild = false;
|
---|
[2565] | 727 | for (OsmPrimitive p : osm.getReferrers()) {
|
---|
[1603] | 728 | isChild |= parent.match(p);
|
---|
| 729 | }
|
---|
| 730 | return isChild;
|
---|
| 731 | }
|
---|
| 732 | @Override public String toString() {return "child(" + parent + ")";}
|
---|
[4546] | 733 |
|
---|
| 734 | public Match getParent() {
|
---|
| 735 | return parent;
|
---|
| 736 | }
|
---|
[1603] | 737 | }
|
---|
[4546] | 738 |
|
---|
[4085] | 739 | /**
|
---|
| 740 | * Matches on the area of a closed way.
|
---|
[4546] | 741 | *
|
---|
[4085] | 742 | * @author Ole Jørgen Brønner
|
---|
| 743 | */
|
---|
[4346] | 744 | private static class Area extends CountRange {
|
---|
[1603] | 745 |
|
---|
[4679] | 746 | public Area(long minCount, long maxCount) {
|
---|
[4346] | 747 | super(minCount, maxCount);
|
---|
| 748 | }
|
---|
| 749 |
|
---|
| 750 | @Override
|
---|
[4679] | 751 | protected Long getCount(OsmPrimitive osm) {
|
---|
[4546] | 752 | if (!(osm instanceof Way && ((Way) osm).isClosed()))
|
---|
[4346] | 753 | return null;
|
---|
| 754 | Way way = (Way) osm;
|
---|
[4679] | 755 | return (long) Geometry.closedWayArea(way);
|
---|
[4085] | 756 | }
|
---|
| 757 |
|
---|
| 758 | @Override
|
---|
[4346] | 759 | protected String getCountString() {
|
---|
| 760 | return "area";
|
---|
[4085] | 761 | }
|
---|
| 762 | }
|
---|
| 763 |
|
---|
[4372] | 764 | /**
|
---|
[4375] | 765 | * Matches data within bounds.
|
---|
[4372] | 766 | */
|
---|
[4375] | 767 | private abstract static class InArea extends Match {
|
---|
[4372] | 768 |
|
---|
[4375] | 769 | protected abstract Bounds getBounds();
|
---|
| 770 | protected final boolean all;
|
---|
| 771 | protected final Bounds bounds;
|
---|
[4372] | 772 |
|
---|
| 773 | /**
|
---|
| 774 | * @param all if true, all way nodes or relation members have to be within source area;if false, one suffices.
|
---|
| 775 | */
|
---|
[4375] | 776 | public InArea(boolean all) {
|
---|
[4372] | 777 | this.all = all;
|
---|
[4375] | 778 | this.bounds = getBounds();
|
---|
[4372] | 779 | }
|
---|
| 780 |
|
---|
| 781 | @Override
|
---|
| 782 | public boolean match(OsmPrimitive osm) {
|
---|
[4546] | 783 | if (!osm.isUsable())
|
---|
[4372] | 784 | return false;
|
---|
[4546] | 785 | else if (osm instanceof Node)
|
---|
[4375] | 786 | return bounds.contains(((Node) osm).getCoor());
|
---|
[4546] | 787 | else if (osm instanceof Way) {
|
---|
[4372] | 788 | Collection<Node> nodes = ((Way) osm).getNodes();
|
---|
| 789 | return all ? forallMatch(nodes) : existsMatch(nodes);
|
---|
| 790 | } else if (osm instanceof Relation) {
|
---|
| 791 | Collection<OsmPrimitive> primitives = ((Relation) osm).getMemberPrimitives();
|
---|
| 792 | return all ? forallMatch(primitives) : existsMatch(primitives);
|
---|
[4546] | 793 | } else
|
---|
[4372] | 794 | return false;
|
---|
| 795 | }
|
---|
| 796 | }
|
---|
| 797 |
|
---|
[4375] | 798 | /**
|
---|
| 799 | * Matches data in source area ("downloaded area").
|
---|
| 800 | */
|
---|
| 801 | private static class InDataSourceArea extends InArea {
|
---|
| 802 |
|
---|
| 803 | public InDataSourceArea(boolean all) {
|
---|
| 804 | super(all);
|
---|
| 805 | }
|
---|
| 806 |
|
---|
| 807 | @Override
|
---|
| 808 | protected Bounds getBounds() {
|
---|
| 809 | return new Bounds(Main.main.getCurrentDataSet().getDataSourceArea().getBounds2D());
|
---|
| 810 | }
|
---|
| 811 | }
|
---|
| 812 |
|
---|
| 813 | /**
|
---|
| 814 | * Matches data in current map view.
|
---|
| 815 | */
|
---|
| 816 | private static class InView extends InArea {
|
---|
| 817 |
|
---|
| 818 | public InView(boolean all) {
|
---|
| 819 | super(all);
|
---|
| 820 | }
|
---|
| 821 |
|
---|
| 822 | @Override
|
---|
| 823 | protected Bounds getBounds() {
|
---|
| 824 | return Main.map.mapView.getRealBounds();
|
---|
| 825 | }
|
---|
| 826 | }
|
---|
| 827 |
|
---|
[1169] | 828 | public static class ParseError extends Exception {
|
---|
| 829 | public ParseError(String msg) {
|
---|
| 830 | super(msg);
|
---|
| 831 | }
|
---|
[2993] | 832 | public ParseError(Token expected, Token found) {
|
---|
| 833 | this(tr("Unexpected token. Expected {0}, found {1}", expected, found));
|
---|
| 834 | }
|
---|
[1169] | 835 | }
|
---|
[319] | 836 |
|
---|
[1213] | 837 | public static Match compile(String searchStr, boolean caseSensitive, boolean regexSearch)
|
---|
[4546] | 838 | throws ParseError {
|
---|
[1213] | 839 | return new SearchCompiler(caseSensitive, regexSearch,
|
---|
[1169] | 840 | new PushbackTokenizer(
|
---|
[1814] | 841 | new PushbackReader(new StringReader(searchStr))))
|
---|
| 842 | .parse();
|
---|
[1169] | 843 | }
|
---|
[319] | 844 |
|
---|
[1169] | 845 | public Match parse() throws ParseError {
|
---|
[2645] | 846 | Match m = parseExpression();
|
---|
| 847 | if (!tokenizer.readIfEqual(Token.EOF))
|
---|
[1641] | 848 | throw new ParseError(tr("Unexpected token: {0}", tokenizer.nextToken()));
|
---|
[2661] | 849 | if (m == null)
|
---|
| 850 | return new Always();
|
---|
[1169] | 851 | return m;
|
---|
| 852 | }
|
---|
[513] | 853 |
|
---|
[2645] | 854 | private Match parseExpression() throws ParseError {
|
---|
| 855 | Match factor = parseFactor();
|
---|
| 856 | if (factor == null)
|
---|
| 857 | return null;
|
---|
| 858 | if (tokenizer.readIfEqual(Token.OR))
|
---|
| 859 | return new Or(factor, parseExpression(tr("Missing parameter for OR")));
|
---|
| 860 | else {
|
---|
| 861 | Match expression = parseExpression();
|
---|
| 862 | if (expression == null)
|
---|
| 863 | return factor;
|
---|
| 864 | else
|
---|
| 865 | return new And(factor, expression);
|
---|
[1169] | 866 | }
|
---|
| 867 | }
|
---|
[513] | 868 |
|
---|
[2645] | 869 | private Match parseExpression(String errorMessage) throws ParseError {
|
---|
| 870 | Match expression = parseExpression();
|
---|
| 871 | if (expression == null)
|
---|
| 872 | throw new ParseError(errorMessage);
|
---|
| 873 | else
|
---|
| 874 | return expression;
|
---|
[1169] | 875 | }
|
---|
[513] | 876 |
|
---|
[2645] | 877 | private Match parseFactor() throws ParseError {
|
---|
| 878 | if (tokenizer.readIfEqual(Token.LEFT_PARENT)) {
|
---|
| 879 | Match expression = parseExpression();
|
---|
| 880 | if (!tokenizer.readIfEqual(Token.RIGHT_PARENT))
|
---|
[2993] | 881 | throw new ParseError(Token.RIGHT_PARENT, tokenizer.nextToken());
|
---|
[2645] | 882 | return expression;
|
---|
| 883 | } else if (tokenizer.readIfEqual(Token.NOT))
|
---|
| 884 | return new Not(parseFactor(tr("Missing operator for NOT")));
|
---|
| 885 | else if (tokenizer.readIfEqual(Token.KEY)) {
|
---|
| 886 | String key = tokenizer.getText();
|
---|
| 887 | if (tokenizer.readIfEqual(Token.EQUALS))
|
---|
[2993] | 888 | return new ExactKeyValue(regexSearch, key, tokenizer.readTextOrNumber());
|
---|
| 889 | else if (tokenizer.readIfEqual(Token.COLON)) {
|
---|
[2973] | 890 | if ("id".equals(key))
|
---|
| 891 | return new Id(tokenizer.readNumber(tr("Primitive id expected")));
|
---|
[2993] | 892 | else if ("tags".equals(key)) {
|
---|
[3046] | 893 | Range range = tokenizer.readRange(tr("Range of numbers expected"));
|
---|
[4679] | 894 | return new TagCountRange(range.getStart(), range.getEnd());
|
---|
[2995] | 895 | } else if ("nodes".equals(key)) {
|
---|
[3046] | 896 | Range range = tokenizer.readRange(tr("Range of numbers expected"));
|
---|
[4679] | 897 | return new NodeCountRange(range.getStart(), range.getEnd());
|
---|
[4089] | 898 | } else if ("areasize".equals(key)) {
|
---|
[4085] | 899 | Range range = tokenizer.readRange(tr("Range of numbers expected"));
|
---|
[4679] | 900 | return new Area(range.getStart(), range.getEnd());
|
---|
| 901 | } else if ("timestamp".equals(key)) {
|
---|
| 902 | String rangeS = " " + tokenizer.readTextOrNumber() + " "; // add leading/trailing space in order to get expected split (e.g. "a--" => {"a", ""})
|
---|
| 903 | String[] rangeA = rangeS.split("/");
|
---|
| 904 | if (rangeA.length == 1) {
|
---|
| 905 | return new KeyValue(key, rangeS, regexSearch, caseSensitive);
|
---|
| 906 | } else if (rangeA.length == 2) {
|
---|
| 907 | String rangeA1 = rangeA[0].trim();
|
---|
| 908 | String rangeA2 = rangeA[1].trim();
|
---|
| 909 | long minDate = DateUtils.fromString(rangeA1.isEmpty() ? "1980" : rangeA1).getTime(); // if min timestap is empty: use lowest possible date
|
---|
| 910 | long maxDate = rangeA2.isEmpty() ? new Date().getTime() : DateUtils.fromString(rangeA2).getTime(); // if max timestamp is empty: use "now"
|
---|
| 911 | return new TimestampRange(minDate, maxDate);
|
---|
| 912 | } else {
|
---|
[4694] | 913 | /* I18n: Don't translate timestamp keyword */ throw new ParseError(tr("Expecting <i>min</i>/<i>max</i>'' after ''timestamp''"));
|
---|
[4679] | 914 | }
|
---|
[2993] | 915 | } else if ("changeset".equals(key))
|
---|
| 916 | return new ChangesetId(tokenizer.readNumber(tr("Changeset id expected")));
|
---|
| 917 | else if ("version".equals(key))
|
---|
| 918 | return new Version(tokenizer.readNumber(tr("Version expected")));
|
---|
[2973] | 919 | else
|
---|
[2993] | 920 | return parseKV(key, tokenizer.readTextOrNumber());
|
---|
| 921 | } else if (tokenizer.readIfEqual(Token.QUESTION_MARK))
|
---|
[2863] | 922 | return new BooleanMatch(key, false);
|
---|
[4347] | 923 | else if ("new".equals(key))
|
---|
| 924 | return new New();
|
---|
[2645] | 925 | else if ("modified".equals(key))
|
---|
| 926 | return new Modified();
|
---|
| 927 | else if ("incomplete".equals(key))
|
---|
| 928 | return new Incomplete();
|
---|
| 929 | else if ("untagged".equals(key))
|
---|
| 930 | return new Untagged();
|
---|
| 931 | else if ("selected".equals(key))
|
---|
| 932 | return new Selected();
|
---|
[3818] | 933 | else if ("closed".equals(key))
|
---|
| 934 | return new Closed();
|
---|
[2645] | 935 | else if ("child".equals(key))
|
---|
| 936 | return new Child(parseFactor());
|
---|
| 937 | else if ("parent".equals(key))
|
---|
| 938 | return new Parent(parseFactor());
|
---|
[4377] | 939 | else if ("indownloadedarea".equals(key))
|
---|
[4372] | 940 | return new InDataSourceArea(false);
|
---|
[4377] | 941 | else if ("allindownloadedarea".equals(key))
|
---|
[4372] | 942 | return new InDataSourceArea(true);
|
---|
[4377] | 943 | else if ("inview".equals(key))
|
---|
[4375] | 944 | return new InView(false);
|
---|
[4377] | 945 | else if ("allinview".equals(key))
|
---|
[4375] | 946 | return new InView(true);
|
---|
[2645] | 947 | else
|
---|
| 948 | return new Any(key, regexSearch, caseSensitive);
|
---|
| 949 | } else
|
---|
| 950 | return null;
|
---|
[1169] | 951 | }
|
---|
[319] | 952 |
|
---|
[2645] | 953 | private Match parseFactor(String errorMessage) throws ParseError {
|
---|
| 954 | Match fact = parseFactor();
|
---|
| 955 | if (fact == null)
|
---|
| 956 | throw new ParseError(errorMessage);
|
---|
| 957 | else
|
---|
| 958 | return fact;
|
---|
[1169] | 959 | }
|
---|
[319] | 960 |
|
---|
[2645] | 961 | private Match parseKV(String key, String value) throws ParseError {
|
---|
| 962 | if (value == null) {
|
---|
| 963 | value = "";
|
---|
[1641] | 964 | }
|
---|
[1814] | 965 | if (key.equals("type"))
|
---|
[1169] | 966 | return new ExactType(value);
|
---|
[1814] | 967 | else if (key.equals("user"))
|
---|
[1169] | 968 | return new UserMatch(value);
|
---|
[3305] | 969 | else if (key.equals("role"))
|
---|
| 970 | return new RoleMatch(value);
|
---|
[2695] | 971 | else
|
---|
[2510] | 972 | return new KeyValue(key, value, regexSearch, caseSensitive);
|
---|
[1169] | 973 | }
|
---|
[1607] | 974 |
|
---|
[2510] | 975 | private static int regexFlags(boolean caseSensitive) {
|
---|
[1607] | 976 | int searchFlags = 0;
|
---|
| 977 |
|
---|
| 978 | // Enables canonical Unicode equivalence so that e.g. the two
|
---|
| 979 | // forms of "\u00e9gal" and "e\u0301gal" will match.
|
---|
| 980 | //
|
---|
| 981 | // It makes sense to match no matter how the character
|
---|
| 982 | // happened to be constructed.
|
---|
| 983 | searchFlags |= Pattern.CANON_EQ;
|
---|
| 984 |
|
---|
| 985 | // Make "." match any character including newline (/s in Perl)
|
---|
| 986 | searchFlags |= Pattern.DOTALL;
|
---|
| 987 |
|
---|
| 988 | // CASE_INSENSITIVE by itself only matches US-ASCII case
|
---|
| 989 | // insensitively, but the OSM data is in Unicode. With
|
---|
| 990 | // UNICODE_CASE casefolding is made Unicode-aware.
|
---|
[1814] | 991 | if (!caseSensitive) {
|
---|
[1607] | 992 | searchFlags |= (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
|
---|
[1814] | 993 | }
|
---|
[1607] | 994 |
|
---|
| 995 | return searchFlags;
|
---|
| 996 | }
|
---|
[319] | 997 | }
|
---|