Changeset 14696 in josm for trunk/src/org/openstreetmap


Ignore:
Timestamp:
2019-01-13T17:24:13+01:00 (6 years ago)
Author:
GerdP
Message:

fix #17199

  • Don't suggest different spelling when all preset values for a key are numerical
  • Add layer to ignore list, is checked in MapCSSTagChecker
  • Add start_date end end_date to ignore list
  • apply modified version of 17055-use-ignore.patch: + the lines from ignoretags.cfg starting with K: are treated as "often used". + instead of "... is not in the presets ..." use "... is unknown ..." in messages
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java

    r14615 r14696  
    7575    /** The spell check preset values which are not stored in TaggingPresets */
    7676    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> oftenUsedValueData = new MultiMap<>();
     79
    7780    /** The TagChecker data */
    7881    private static final List<CheckerData> checkerData = new ArrayList<>();
     
    8184    private static final List<String> ignoreDataEndsWith = new ArrayList<>();
    8285    private static final List<Tag> ignoreDataTag = new ArrayList<>();
     86    /** tag keys that have only numerical values in the presets */
     87    private static final Set<String> ignoreForLevenshtein = new HashSet<>();
    8388
    8489    /** The preferences prefix */
     
    178183        initializeData();
    179184        initializePresets();
     185        analysePresets();
     186    }
     187
     188    /**
     189     * Add presets that contain only numerical values to the ignore list
     190     */
     191    private void analysePresets() {
     192        for (String key : TaggingPresets.getPresetKeys()) {
     193            if (isKeyIgnored(key))
     194                continue;
     195            boolean allNumerical = true;
     196            Set<String> values = TaggingPresets.getPresetValues(key);
     197            if (values.isEmpty())
     198                allNumerical = false;
     199            for (String val : values) {
     200                if (!isNum(val)) {
     201                    allNumerical = false;
     202                    break;
     203                }
     204            }
     205            if (allNumerical) {
     206                ignoreForLevenshtein.add(key);
     207            }
     208        }
    180209    }
    181210
     
    195224        ignoreDataTag.clear();
    196225        harmonizedKeys.clear();
     226        ignoreForLevenshtein.clear();
    197227
    198228        StringBuilder errorSources = new StringBuilder();
     
    241271                                break;
    242272                            case "K:":
    243                                 ignoreDataTag.add(Tag.ofString(line));
     273                                Tag tag = Tag.ofString(line);
     274                                ignoreDataTag.add(tag);
     275                                oftenUsedValueData.put(tag.getKey(), tag.getValue());
    244276                                break;
    245277                            default:
     
    389421
    390422    /**
     423     * Determines if the given tag key is ignored for checks "key/tag not in presets".
     424     * @param key key
     425     * @return true if the given key is ignored
     426     */
     427    private static boolean isKeyIgnored(String key) {
     428        if (ignoreDataEquals.contains(key)) {
     429            return true;
     430        }
     431        for (String a : ignoreDataStartsWith) {
     432            if (key.startsWith(a)) {
     433                return true;
     434            }
     435        }
     436        for (String a : ignoreDataEndsWith) {
     437            if (key.endsWith(a)) {
     438                return true;
     439            }
     440        }
     441        return false;
     442    }
     443
     444    /**
    391445     * Determines if the given tag is ignored for checks "key/tag not in presets".
    392446     * @param key key
     
    396450     */
    397451    public static boolean isTagIgnored(String key, String value) {
    398         if (ignoreDataEquals.contains(key)) {
     452        if (isKeyIgnored(key))
    399453            return true;
    400         }
    401         for (String a : ignoreDataStartsWith) {
    402             if (key.startsWith(a)) {
    403                 return true;
    404             }
    405         }
    406         for (String a : ignoreDataEndsWith) {
    407             if (key.endsWith(a)) {
    408                 return true;
    409             }
    410         }
    411 
    412454        if (!isTagInPresets(key, value)) {
    413455            for (Tag a : ignoreDataTag) {
     
    536578                    }
    537579                } else if (!isTagInPresets(key, value)) {
    538                     // try to fix common typos and check again if value is still unknown
    539                     final String harmonizedValue = harmonizeValue(prop.getValue());
    540                     String fixedValue = null;
    541                     Set<String> possibleValues = getPresetValues(key);
    542                     List<String> fixVals = new ArrayList<>();
    543                     int maxPresetValueLen = 0;
    544                     if (possibleValues.contains(harmonizedValue)) {
    545                         fixedValue = harmonizedValue;
    546                     } else {
    547                         // use Levenshtein distance to find typical typos
    548                         int minDist = MAX_LEVENSHTEIN_DISTANCE + 1;
    549                         String closest = null;
    550                         for (String possibleVal : possibleValues) {
    551                             if (possibleVal.isEmpty())
    552                                 continue;
    553                             maxPresetValueLen = Math.max(maxPresetValueLen, possibleVal.length());
    554                             if (harmonizedValue.length() < 3 && possibleVal.length() >= harmonizedValue.length() + MAX_LEVENSHTEIN_DISTANCE) {
    555                                 // don't suggest fix value when given value is short and lengths are too different
    556                                 // for example surface=u would result in surface=mud
    557                                 continue;
    558                             }
    559                             int dist = Utils.getLevenshteinDistance(possibleVal, harmonizedValue);
    560                             if (dist >= harmonizedValue.length()) {
    561                                 // short value, all characters are different. Don't warn, might say Value '10' for key 'fee' looks like 'no'.
    562                                 continue;
    563                             }
    564                             if (dist < minDist) {
    565                                 closest = possibleVal;
    566                                 minDist = dist;
    567                                 fixVals.clear();
    568                                 fixVals.add(possibleVal);
    569                             } else if (dist == minDist) {
    570                                 fixVals.add(possibleVal);
    571                             }
    572                         }
    573                         if (minDist <= MAX_LEVENSHTEIN_DISTANCE && maxPresetValueLen > MAX_LEVENSHTEIN_DISTANCE
    574                                 && (harmonizedValue.length() > 3 || minDist < MAX_LEVENSHTEIN_DISTANCE)) {
    575                             if (fixVals.size() < 2) {
    576                                 fixedValue = closest;
    577                             } else {
    578                                 Collections.sort(fixVals);
    579                                 // misspelled preset value with multiple good alternatives
    580                                 errors.add(TestError.builder(this, Severity.WARNING, MISSPELLED_VALUE_NO_FIX)
    581                                         .message(tr("Misspelled property value"),
    582                                                 marktr("Value ''{0}'' for key ''{1}'' looks like one of {2}."),
    583                                                 prop.getValue(), key, fixVals)
    584                                         .primitives(p).build());
    585                                 withErrors.put(p, "WPV");
    586                                 continue;
    587                             }
    588                         }
     580                    tryGuess(p, key, value, withErrors);
     581                }
     582            }
     583            if (checkFixmes && key != null && value != null && !value.isEmpty() && isFixme(key, value) && !withErrors.contains(p, "FIXME")) {
     584                errors.add(TestError.builder(this, Severity.OTHER, FIXME)
     585                        .message(tr("FIXMES"))
     586                        .primitives(p)
     587                        .build());
     588                withErrors.put(p, "FIXME");
     589            }
     590        }
     591    }
     592
     593    private void tryGuess(OsmPrimitive p, String key, String value, MultiMap<OsmPrimitive, String> withErrors) {
     594        // try to fix common typos and check again if value is still unknown
     595        final String harmonizedValue = harmonizeValue(value);
     596        String fixedValue = null;
     597        Set<String> presetValues = getPresetValues(key);
     598        Set<String> oftenUsedValues = oftenUsedValueData.get(key);
     599        for (Set<String> possibleValues: Arrays.asList(presetValues, oftenUsedValues)) {
     600            if (possibleValues != null && possibleValues.contains(harmonizedValue)) {
     601                fixedValue = harmonizedValue;
     602                break;
     603            }
     604        }
     605        if (fixedValue == null && !ignoreForLevenshtein.contains(key)) {
     606            int maxPresetValueLen = 0;
     607            List<String> fixVals = new ArrayList<>();
     608            // use Levenshtein distance to find typical typos
     609            int minDist = MAX_LEVENSHTEIN_DISTANCE + 1;
     610            String closest = null;
     611            for (Set<String> possibleValues: Arrays.asList(presetValues, oftenUsedValues)) {
     612                if (possibleValues == null)
     613                    continue;
     614                for (String possibleVal : possibleValues) {
     615                    if (possibleVal.isEmpty())
     616                        continue;
     617                    maxPresetValueLen = Math.max(maxPresetValueLen, possibleVal.length());
     618                    if (harmonizedValue.length() < 3 && possibleVal.length() >= harmonizedValue.length() + MAX_LEVENSHTEIN_DISTANCE) {
     619                        // don't suggest fix value when given value is short and lengths are too different
     620                        // for example surface=u would result in surface=mud
     621                        continue;
    589622                    }
    590                     if (fixedValue != null && possibleValues.contains(fixedValue)) {
    591                         final String newValue = fixedValue;
    592                         // misspelled preset value
    593                         errors.add(TestError.builder(this, Severity.WARNING, MISSPELLED_VALUE)
    594                                 .message(tr("Misspelled property value"),
    595                                         marktr("Value ''{0}'' for key ''{1}'' looks like ''{2}''."), prop.getValue(), key, newValue)
    596                                 .primitives(p)
    597                                 .build());
    598                         withErrors.put(p, "WPV");
    599                     } else {
    600                         // unknown preset value
    601                         errors.add(TestError.builder(this, Severity.OTHER, INVALID_VALUE)
    602                                 .message(tr("Presets do not contain property value"),
    603                                         marktr("Value ''{0}'' for key ''{1}'' not in presets."), prop.getValue(), key)
    604                                 .primitives(p)
    605                                 .build());
    606                         withErrors.put(p, "UPV");
     623                    int dist = Utils.getLevenshteinDistance(possibleVal, harmonizedValue);
     624                    if (dist >= harmonizedValue.length()) {
     625                        // short value, all characters are different. Don't warn, might say Value '10' for key 'fee' looks like 'no'.
     626                        continue;
    607627                    }
    608                 }
    609             }
    610             if (checkFixmes && key != null && value != null && !value.isEmpty() && isFixme(key, value) && !withErrors.contains(p, "FIXME")) {
    611                errors.add(TestError.builder(this, Severity.OTHER, FIXME)
    612                 .message(tr("FIXMES"))
    613                 .primitives(p)
    614                 .build());
    615                withErrors.put(p, "FIXME");
    616             }
     628                    if (dist < minDist) {
     629                        closest = possibleVal;
     630                        minDist = dist;
     631                        fixVals.clear();
     632                        fixVals.add(possibleVal);
     633                    } else if (dist == minDist) {
     634                        fixVals.add(possibleVal);
     635                    }
     636                }
     637            }
     638
     639            if (minDist <= MAX_LEVENSHTEIN_DISTANCE && maxPresetValueLen > MAX_LEVENSHTEIN_DISTANCE
     640                    && (harmonizedValue.length() > 3 || minDist < MAX_LEVENSHTEIN_DISTANCE)) {
     641                if (fixVals.size() < 2) {
     642                    fixedValue = closest;
     643                } else {
     644                    Collections.sort(fixVals);
     645                    // misspelled preset value with multiple good alternatives
     646                    errors.add(TestError.builder(this, Severity.WARNING, MISSPELLED_VALUE_NO_FIX)
     647                            .message(tr("Unknown property value"),
     648                                    marktr("Value ''{0}'' for key ''{1}'' is unknown, maybe one of {2} is meant?"),
     649                                    value, key, fixVals)
     650                            .primitives(p).build());
     651                    withErrors.put(p, "WPV");
     652                    return;
     653                }
     654            }
     655        }
     656        if (fixedValue != null) {
     657            final String newValue = fixedValue;
     658            // misspelled preset value
     659            errors.add(TestError.builder(this, Severity.WARNING, MISSPELLED_VALUE)
     660                    .message(tr("Unknown property value"),
     661                            marktr("Value ''{0}'' for key ''{1}'' is unknown, maybe ''{2}'' is meant?"), value, key, newValue)
     662                    .primitives(p)
     663                    .build());
     664            withErrors.put(p, "WPV");
     665        } else {
     666            // unknown preset value
     667            errors.add(TestError.builder(this, Severity.OTHER, INVALID_VALUE)
     668                    .message(tr("Presets do not contain property value"),
     669                            marktr("Value ''{0}'' for key ''{1}'' not in presets."), value, key)
     670                    .primitives(p)
     671                    .build());
     672            withErrors.put(p, "UPV");
     673        }
     674    }
     675
     676    private boolean isNum(String harmonizedValue) {
     677        try {
     678            Double.parseDouble(harmonizedValue);
     679            return true;
     680        } catch (NumberFormatException e) {
     681            return false;
    617682        }
    618683    }
Note: See TracChangeset for help on using the changeset viewer.