Changeset 14705 in josm for trunk/src/org/openstreetmap
- Timestamp:
- 2019-01-19T18:09:55+01:00 (6 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java
r14704 r14705 74 74 private static final Map<String, String> harmonizedKeys = new HashMap<>(); 75 75 /** The spell check preset values which are not stored in TaggingPresets */ 76 private static volatile MultiMap<String,String> additionalPresetsValueData;77 /** The spell check preset values which are not stored in TaggingPresets */78 private static volatile MultiMap<String, String> oftenUsed ValueData= new MultiMap<>();76 private static volatile HashSet<String> additionalPresetsValueData; 77 /** often used tags which are not in presets */ 78 private static volatile MultiMap<String, String> oftenUsedTags = new MultiMap<>(); 79 79 80 80 /** The TagChecker data */ … … 113 113 public static final String PREF_SOURCES = PREFIX + ".source"; 114 114 115 private static final String BEFORE_UPLOAD = "BeforeUpload"; 115 116 /** 116 117 * The preference key to check keys - used before upload 117 118 */ 118 public static final String PREF_CHECK_KEYS_BEFORE_UPLOAD = PREF_CHECK_KEYS + "BeforeUpload";119 public static final String PREF_CHECK_KEYS_BEFORE_UPLOAD = PREF_CHECK_KEYS + BEFORE_UPLOAD; 119 120 /** 120 121 * The preference key to check values - used before upload 121 122 */ 122 public static final String PREF_CHECK_VALUES_BEFORE_UPLOAD = PREF_CHECK_VALUES + "BeforeUpload";123 public static final String PREF_CHECK_VALUES_BEFORE_UPLOAD = PREF_CHECK_VALUES + BEFORE_UPLOAD; 123 124 /** 124 125 * The preference key to run complex tests - used before upload 125 126 */ 126 public static final String PREF_CHECK_COMPLEX_BEFORE_UPLOAD = PREF_CHECK_COMPLEX + "BeforeUpload";127 public static final String PREF_CHECK_COMPLEX_BEFORE_UPLOAD = PREF_CHECK_COMPLEX + BEFORE_UPLOAD; 127 128 /** 128 129 * The preference key to search for fixmes - used before upload 129 130 */ 130 public static final String PREF_CHECK_FIXMES_BEFORE_UPLOAD = PREF_CHECK_FIXMES + "BeforeUpload";131 public static final String PREF_CHECK_FIXMES_BEFORE_UPLOAD = PREF_CHECK_FIXMES + BEFORE_UPLOAD; 131 132 132 133 private static final int MAX_LEVENSHTEIN_DISTANCE = 2; … … 166 167 protected static final int MISSPELLED_VALUE_NO_FIX = 1215; 167 168 // CHECKSTYLE.ON: SingleSpaceSeparator 168 // 1250 and up is used by tagcheck169 // 1250 and up is used by TagCheckLevel in class CheckerData 169 170 170 171 protected EditableList sourcesList; … … 210 211 211 212 /** 212 * Reads the spell check file into a HashMap.213 * Reads the spell-check file into a HashMap. 213 214 * The data file is a list of words, beginning with +/-. If it starts with +, 214 215 * the word is valid, but if it starts with -, the word should be replaced … … 225 226 harmonizedKeys.clear(); 226 227 ignoreForLevenshtein.clear(); 228 oftenUsedTags.clear(); 227 229 228 230 StringBuilder errorSources = new StringBuilder(); … … 252 254 } 253 255 } else if (ignorefile) { 254 line = line.trim(); 255 if (line.length() < 4) { 256 continue; 257 } 258 try { 259 String key = line.substring(0, 2); 260 line = line.substring(2); 261 262 switch (key) { 263 case "S:": 264 ignoreDataStartsWith.add(line); 265 break; 266 case "E:": 267 ignoreDataEquals.add(line); 268 break; 269 case "F:": 270 ignoreDataEndsWith.add(line); 271 break; 272 case "K:": 273 Tag tag = Tag.ofString(line); 274 ignoreDataTag.add(tag); 275 oftenUsedValueData.put(tag.getKey(), tag.getValue()); 276 break; 277 default: 278 if (!key.startsWith(";")) { 279 Logging.warn("Unsupported TagChecker key: " + key); 280 } 281 } 282 } catch (IllegalArgumentException e) { 283 Logging.error("Invalid line in {0} : {1}", source, e.getMessage()); 284 Logging.trace(e); 285 } 256 parseIgnoreFileLine(source, line); 286 257 } else if (tagcheckerfile) { 287 258 if (!line.isEmpty()) { … … 299 270 } else if (line.charAt(0) == '-' && okValue != null) { 300 271 String hk = harmonizeKey(line.substring(1)); 301 if (!okValue.equals(hk)) { 302 if (harmonizedKeys.put(hk, okValue) != null) { 303 Logging.debug(tr("Line was ignored: {0}", line)); 304 } 272 if (!okValue.equals(hk) && harmonizedKeys.put(hk, okValue) != null) { 273 Logging.debug(tr("Line was ignored: {0}", line)); 305 274 } 306 275 } else { … … 325 294 326 295 /** 296 * Parse a line found in a configuration file 297 * @param source name of configuration file 298 * @param line the line to parse 299 */ 300 private static void parseIgnoreFileLine(String source, String line) { 301 line = line.trim(); 302 if (line.length() < 4) { 303 return; 304 } 305 try { 306 String key = line.substring(0, 2); 307 line = line.substring(2); 308 309 switch (key) { 310 case "S:": 311 ignoreDataStartsWith.add(line); 312 break; 313 case "E:": 314 ignoreDataEquals.add(line); 315 break; 316 case "F:": 317 ignoreDataEndsWith.add(line); 318 break; 319 case "K:": 320 Tag tag = Tag.ofString(line); 321 ignoreDataTag.add(tag); 322 oftenUsedTags.put(tag.getKey(), tag.getValue()); 323 break; 324 default: 325 if (!key.startsWith(";")) { 326 Logging.warn("Unsupported TagChecker key: " + key); 327 } 328 } 329 } catch (IllegalArgumentException e) { 330 Logging.error("Invalid line in {0} : {1}", source, e.getMessage()); 331 Logging.trace(e); 332 } 333 } 334 335 /** 327 336 * Reads the presets data. 328 337 * … … 335 344 Collection<TaggingPreset> presets = TaggingPresets.getTaggingPresets(); 336 345 if (!presets.isEmpty()) { 337 additionalPresetsValueData = new MultiMap<>(); 338 for (String a : AbstractPrimitive.getUninterestingKeys()) { 339 additionalPresetsValueData.putVoid(a); 340 } 341 // TODO directionKeys are no longer in OsmPrimitive (search pattern is used instead) 342 for (String a : Config.getPref().getList(ValidatorPrefHelper.PREFIX + ".knownkeys", 343 Arrays.asList("is_in", "int_ref", "fixme", "population"))) { 344 additionalPresetsValueData.putVoid(a); 345 } 346 initAdditionalPresetsValueData(); 346 347 for (TaggingPreset p : presets) { 347 348 for (TaggingPresetItem i : p.data) { … … 358 359 } 359 360 361 private static void initAdditionalPresetsValueData() { 362 additionalPresetsValueData = new HashSet<>(); 363 for (String a : AbstractPrimitive.getUninterestingKeys()) { 364 additionalPresetsValueData.add(a); 365 } 366 for (String a : Config.getPref().getList(ValidatorPrefHelper.PREFIX + ".knownkeys", 367 Arrays.asList("is_in", "int_ref", "fixme", "population"))) { 368 additionalPresetsValueData.add(a); 369 } 370 } 371 360 372 private static void addPresetValue(KeyedItem ky) { 361 373 if (ky.key != null && ky.getValues() != null) { … … 386 398 if (res != null) 387 399 return res; 388 return additionalPresetsValueData.get(key); 400 if (additionalPresetsValueData.contains(key)) 401 return Collections.emptySet(); 402 return null; 389 403 } 390 404 … … 494 508 String key = prop.getKey(); 495 509 String value = prop.getValue(); 496 if (checkValues && (containsLow(value)) && !withErrors.contains(p, "ICV")) { 497 errors.add(TestError.builder(this, Severity.WARNING, LOW_CHAR_VALUE) 498 .message(tr("Tag value contains character with code less than 0x20"), s, key) 499 .primitives(p) 500 .build()); 501 withErrors.put(p, "ICV"); 502 } 503 if (checkKeys && (containsLow(key)) && !withErrors.contains(p, "ICK")) { 504 errors.add(TestError.builder(this, Severity.WARNING, LOW_CHAR_KEY) 505 .message(tr("Tag key contains character with code less than 0x20"), s, key) 506 .primitives(p) 507 .build()); 508 withErrors.put(p, "ICK"); 509 } 510 if (checkValues && (value != null && value.length() > Tagged.MAX_TAG_LENGTH) && !withErrors.contains(p, "LV")) { 511 errors.add(TestError.builder(this, Severity.ERROR, LONG_VALUE) 512 .message(tr("Tag value longer than {0} characters ({1} characters)", Tagged.MAX_TAG_LENGTH, value.length()), s, key) 513 .primitives(p) 514 .build()); 515 withErrors.put(p, "LV"); 516 } 517 if (checkKeys && (key != null && key.length() > Tagged.MAX_TAG_LENGTH) && !withErrors.contains(p, "LK")) { 518 errors.add(TestError.builder(this, Severity.ERROR, LONG_KEY) 519 .message(tr("Tag key longer than {0} characters ({1} characters)", Tagged.MAX_TAG_LENGTH, key.length()), s, key) 520 .primitives(p) 521 .build()); 522 withErrors.put(p, "LK"); 523 } 524 if (checkValues && (value == null || value.trim().isEmpty()) && !withErrors.contains(p, "EV")) { 525 errors.add(TestError.builder(this, Severity.WARNING, EMPTY_VALUES) 526 .message(tr("Tags with empty values"), s, key) 527 .primitives(p) 528 .build()); 529 withErrors.put(p, "EV"); 530 } 531 if (checkKeys && key != null && key.indexOf(' ') >= 0 && !withErrors.contains(p, "IPK")) { 532 errors.add(TestError.builder(this, Severity.WARNING, INVALID_KEY_SPACE) 533 .message(tr("Invalid white space in property key"), s, key) 534 .primitives(p) 535 .build()); 536 withErrors.put(p, "IPK"); 537 } 538 if (checkValues && value != null && (value.startsWith(" ") || value.endsWith(" ")) && !withErrors.contains(p, "SPACE")) { 539 errors.add(TestError.builder(this, Severity.WARNING, INVALID_SPACE) 540 .message(tr("Property values start or end with white space"), s, key) 541 .primitives(p) 542 .build()); 543 withErrors.put(p, "SPACE"); 544 } 545 if (checkValues && value != null && value.contains(" ") && !withErrors.contains(p, "SPACE")) { 546 errors.add(TestError.builder(this, Severity.WARNING, MULTIPLE_SPACES) 547 .message(tr("Property values contain multiple white spaces"), s, key) 548 .primitives(p) 549 .build()); 550 withErrors.put(p, "SPACE"); 551 } 552 if (checkValues && value != null && !value.equals(Entities.unescape(value)) && !withErrors.contains(p, "HTML")) { 553 errors.add(TestError.builder(this, Severity.OTHER, INVALID_HTML) 554 .message(tr("Property values contain HTML entity"), s, key) 555 .primitives(p) 556 .build()); 557 withErrors.put(p, "HTML"); 558 } 559 if (checkValues && key != null && value != null && !value.isEmpty() && additionalPresetsValueData != null 560 && !isTagIgnored(key, value)) { 561 if (!isKeyInPresets(key)) { 562 String prettifiedKey = harmonizeKey(key); 563 String fixedKey = isKeyInPresets(prettifiedKey) ? prettifiedKey : harmonizedKeys.get(prettifiedKey); 564 if (fixedKey != null && !"".equals(fixedKey) && !fixedKey.equals(key)) { 565 // misspelled preset key 566 final TestError.Builder error = TestError.builder(this, Severity.WARNING, MISSPELLED_KEY) 567 .message(tr("Misspelled property key"), marktr("Key ''{0}'' looks like ''{1}''."), key, fixedKey) 568 .primitives(p); 569 if (p.hasKey(fixedKey)) { 570 errors.add(error.build()); 571 } else { 572 errors.add(error.fix(() -> new ChangePropertyKeyCommand(p, key, fixedKey)).build()); 573 } 574 withErrors.put(p, "WPK"); 575 } else { 576 errors.add(TestError.builder(this, Severity.OTHER, INVALID_VALUE) 577 .message(tr("Presets do not contain property key"), marktr("Key ''{0}'' not in presets."), key) 578 .primitives(p) 579 .build()); 580 withErrors.put(p, "UPK"); 581 } 582 } else if (!isTagInPresets(key, value)) { 583 tryGuess(p, key, value, withErrors); 584 } 510 511 if (checkKeys) { 512 checkSingleTagKeySimple(withErrors, p, s, key); 513 } 514 if (checkValues) { 515 checkSingleTagValueSimple(withErrors, p, s, key, value); 516 checkSingleTagComplex(withErrors, p, key, value); 585 517 } 586 518 if (checkFixmes && key != null && value != null && !value.isEmpty() && isFixme(key, value) && !withErrors.contains(p, "FIXME")) { … … 594 526 } 595 527 528 private void checkSingleTagValueSimple(MultiMap<OsmPrimitive, String> withErrors, OsmPrimitive p, String s, String key, String value) { 529 if (!checkValues || value == null) 530 return; 531 if ((containsLow(value)) && !withErrors.contains(p, "ICV")) { 532 errors.add(TestError.builder(this, Severity.WARNING, LOW_CHAR_VALUE) 533 .message(tr("Tag value contains character with code less than 0x20"), s, key) 534 .primitives(p) 535 .build()); 536 withErrors.put(p, "ICV"); 537 } 538 if ((value.length() > Tagged.MAX_TAG_LENGTH) && !withErrors.contains(p, "LV")) { 539 errors.add(TestError.builder(this, Severity.ERROR, LONG_VALUE) 540 .message(tr("Tag value longer than {0} characters ({1} characters)", Tagged.MAX_TAG_LENGTH, value.length()), s, key) 541 .primitives(p) 542 .build()); 543 withErrors.put(p, "LV"); 544 } 545 if ((value.trim().isEmpty()) && !withErrors.contains(p, "EV")) { 546 errors.add(TestError.builder(this, Severity.WARNING, EMPTY_VALUES) 547 .message(tr("Tags with empty values"), s, key) 548 .primitives(p) 549 .build()); 550 withErrors.put(p, "EV"); 551 } 552 final String ERR_TYPE_SPACE = "SPACE"; 553 if ((value.startsWith(" ") || value.endsWith(" ")) && !withErrors.contains(p, ERR_TYPE_SPACE)) { 554 errors.add(TestError.builder(this, Severity.WARNING, INVALID_SPACE) 555 .message(tr("Property values start or end with white space"), s, key) 556 .primitives(p) 557 .build()); 558 withErrors.put(p, ERR_TYPE_SPACE); 559 } 560 if (value.contains(" ") && !withErrors.contains(p, ERR_TYPE_SPACE)) { 561 errors.add(TestError.builder(this, Severity.WARNING, MULTIPLE_SPACES) 562 .message(tr("Property values contain multiple white spaces"), s, key) 563 .primitives(p) 564 .build()); 565 withErrors.put(p, ERR_TYPE_SPACE); 566 } 567 if (!value.equals(Entities.unescape(value)) && !withErrors.contains(p, "HTML")) { 568 errors.add(TestError.builder(this, Severity.OTHER, INVALID_HTML) 569 .message(tr("Property values contain HTML entity"), s, key) 570 .primitives(p) 571 .build()); 572 withErrors.put(p, "HTML"); 573 } 574 } 575 576 private void checkSingleTagKeySimple(MultiMap<OsmPrimitive, String> withErrors, OsmPrimitive p, String s, String key) { 577 if (!checkKeys || key == null) 578 return; 579 if ((containsLow(key)) && !withErrors.contains(p, "ICK")) { 580 errors.add(TestError.builder(this, Severity.WARNING, LOW_CHAR_KEY) 581 .message(tr("Tag key contains character with code less than 0x20"), s, key) 582 .primitives(p) 583 .build()); 584 withErrors.put(p, "ICK"); 585 } 586 if (key.length() > Tagged.MAX_TAG_LENGTH && !withErrors.contains(p, "LK")) { 587 errors.add(TestError.builder(this, Severity.ERROR, LONG_KEY) 588 .message(tr("Tag key longer than {0} characters ({1} characters)", Tagged.MAX_TAG_LENGTH, key.length()), s, key) 589 .primitives(p) 590 .build()); 591 withErrors.put(p, "LK"); 592 } 593 if (key.indexOf(' ') >= 0 && !withErrors.contains(p, "IPK")) { 594 errors.add(TestError.builder(this, Severity.WARNING, INVALID_KEY_SPACE) 595 .message(tr("Invalid white space in property key"), s, key) 596 .primitives(p) 597 .build()); 598 withErrors.put(p, "IPK"); 599 } 600 } 601 602 private void checkSingleTagComplex(MultiMap<OsmPrimitive, String> withErrors, OsmPrimitive p, String key, String value) { 603 if (!checkValues || key == null || value == null || value.isEmpty()) 604 return; 605 if (additionalPresetsValueData != null && !isTagIgnored(key, value)) { 606 if (!isKeyInPresets(key)) { 607 spellCheckKey(withErrors, p, key); 608 } else if (!isTagInPresets(key, value)) { 609 if (oftenUsedTags.contains(key, value)) { 610 // tag is quite often used but not in presets 611 errors.add(TestError.builder(this, Severity.OTHER, INVALID_VALUE) 612 .message(tr("Presets do not contain property value"), 613 marktr("Value ''{0}'' for key ''{1}'' not in presets, but is known."), value, key) 614 .primitives(p) 615 .build()); 616 withErrors.put(p, "UPV"); 617 } else { 618 tryGuess(p, key, value, withErrors); 619 } 620 } 621 } 622 } 623 624 private void spellCheckKey(MultiMap<OsmPrimitive, String> withErrors, OsmPrimitive p, String key) { 625 String prettifiedKey = harmonizeKey(key); 626 String fixedKey = isKeyInPresets(prettifiedKey) ? prettifiedKey : harmonizedKeys.get(prettifiedKey); 627 if (fixedKey != null && !"".equals(fixedKey) && !fixedKey.equals(key)) { 628 // misspelled preset key 629 final TestError.Builder error = TestError.builder(this, Severity.WARNING, MISSPELLED_KEY) 630 .message(tr("Misspelled property key"), marktr("Key ''{0}'' looks like ''{1}''."), key, fixedKey) 631 .primitives(p); 632 if (p.hasKey(fixedKey)) { 633 errors.add(error.build()); 634 } else { 635 errors.add(error.fix(() -> new ChangePropertyKeyCommand(p, key, fixedKey)).build()); 636 } 637 withErrors.put(p, "WPK"); 638 } else { 639 errors.add(TestError.builder(this, Severity.OTHER, INVALID_VALUE) 640 .message(tr("Presets do not contain property key"), marktr("Key ''{0}'' not in presets."), key) 641 .primitives(p) 642 .build()); 643 withErrors.put(p, "UPK"); 644 } 645 } 646 596 647 private void tryGuess(OsmPrimitive p, String key, String value, MultiMap<OsmPrimitive, String> withErrors) { 597 648 // try to fix common typos and check again if value is still unknown 598 649 final String harmonizedValue = harmonizeValue(value); 650 if (harmonizedValue == null || harmonizedValue.isEmpty()) 651 return; 599 652 String fixedValue = null; 600 653 Set<String> presetValues = getPresetValues(key); 601 Set<String> oftenUsedValues = oftenUsed ValueData.get(key);654 Set<String> oftenUsedValues = oftenUsedTags.get(key); 602 655 for (Set<String> possibleValues: Arrays.asList(presetValues, oftenUsedValues)) { 603 656 if (possibleValues != null && possibleValues.contains(harmonizedValue)) { … … 657 710 } 658 711 } 659 if (fixedValue != null ) {712 if (fixedValue != null && !fixedValue.equals(value)) { 660 713 final String newValue = fixedValue; 661 714 // misspelled preset value … … 677 730 } 678 731 679 private boolean isNum(String harmonizedValue) {732 private static boolean isNum(String harmonizedValue) { 680 733 try { 681 734 Double.parseDouble(harmonizedValue); … … 879 932 880 933 protected static class CheckerElement { 881 publicObject tag;882 publicObject value;883 publicboolean noMatch;884 publicboolean tagAll;885 publicboolean valueAll;886 publicboolean valueBool;934 Object tag; 935 Object value; 936 boolean noMatch; 937 boolean tagAll; 938 boolean valueAll; 939 boolean valueBool; 887 940 888 941 private static Pattern getPattern(String str) { … … 895 948 } 896 949 897 publicCheckerElement(String exp) {950 CheckerElement(String exp) { 898 951 Matcher m = Pattern.compile("(.+)([!=]=)(.+)").matcher(exp); 899 952 m.matches(); … … 907 960 noMatch = "!=".equals(m.group(2)); 908 961 n = m.group(3).trim(); 909 if ("*".equals(n)) {910 valueAll = true;911 } else if ("BOOLEAN_TRUE".equals(n)) {962 switch (n) { 963 case "*": valueAll = true; break; 964 case "BOOLEAN_TRUE": 912 965 valueBool = true; 913 966 value = OsmUtils.TRUE_VALUE; 914 } else if ("BOOLEAN_FALSE".equals(n)) { 967 break; 968 case "BOOLEAN_FALSE": 915 969 valueBool = true; 916 970 value = OsmUtils.FALSE_VALUE; 917 } else { 971 break; 972 default: 918 973 value = n.startsWith("/") ? getPattern(n) : n; 919 974 } … … 921 976 } 922 977 923 publicboolean match(Map<String, String> keys) {978 boolean match(Map<String, String> keys) { 924 979 for (Entry<String, String> prop: keys.entrySet()) { 925 980 String key = prop.getKey();
Note:
See TracChangeset
for help on using the changeset viewer.