source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java@ 4682

Last change on this file since 4682 was 4035, checked in by stoecker, 13 years ago

fix validator texts and degrade strange suspicous tag/value message

  • Property svn:eol-style set to native
File size: 42.2 KB
RevLine 
[3669]1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.data.validation.tests;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.Dimension;
8import java.awt.GridBagConstraints;
9import java.awt.GridBagLayout;
10import java.awt.event.ActionEvent;
11import java.awt.event.ActionListener;
12import java.io.BufferedReader;
13import java.io.FileNotFoundException;
14import java.io.IOException;
15import java.io.InputStreamReader;
16import java.io.UnsupportedEncodingException;
17import java.text.MessageFormat;
18import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.Collection;
21import java.util.Collections;
22import java.util.HashMap;
23import java.util.List;
24import java.util.Map;
25import java.util.Map.Entry;
[3674]26import java.util.Set;
[3669]27import java.util.regex.Matcher;
28import java.util.regex.Pattern;
29import java.util.regex.PatternSyntaxException;
30
31import javax.swing.DefaultListModel;
32import javax.swing.JButton;
33import javax.swing.JCheckBox;
34import javax.swing.JLabel;
35import javax.swing.JList;
36import javax.swing.JOptionPane;
37import javax.swing.JPanel;
38import javax.swing.JScrollPane;
39
40import org.openstreetmap.josm.Main;
41import org.openstreetmap.josm.command.ChangePropertyCommand;
42import org.openstreetmap.josm.command.Command;
43import org.openstreetmap.josm.command.SequenceCommand;
44import org.openstreetmap.josm.data.osm.Node;
45import org.openstreetmap.josm.data.osm.OsmPrimitive;
46import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
47import org.openstreetmap.josm.data.osm.OsmUtils;
48import org.openstreetmap.josm.data.osm.Relation;
49import org.openstreetmap.josm.data.osm.Way;
[3707]50import org.openstreetmap.josm.data.validation.OsmValidator;
[3669]51import org.openstreetmap.josm.data.validation.Severity;
52import org.openstreetmap.josm.data.validation.Test;
53import org.openstreetmap.josm.data.validation.TestError;
54import org.openstreetmap.josm.data.validation.util.Entities;
55import org.openstreetmap.josm.gui.preferences.ValidatorPreference;
56import org.openstreetmap.josm.gui.preferences.TaggingPresetPreference;
57import org.openstreetmap.josm.gui.progress.ProgressMonitor;
58import org.openstreetmap.josm.gui.tagging.TaggingPreset;
59import org.openstreetmap.josm.io.MirroredInputStream;
60import org.openstreetmap.josm.tools.GBC;
[3674]61import org.openstreetmap.josm.tools.MultiMap;
[3669]62
63/**
64 * Check for misspelled or wrong properties
65 *
66 * @author frsantos
67 */
68public class TagChecker extends Test
69{
70 /** The default data files */
71 public static final String DATA_FILE = "resource://data/tagchecker.cfg";
72 public static final String IGNORE_FILE = "resource://data/ignoretags.cfg";
73 public static final String SPELL_FILE = "resource://data/words.cfg";
74
75 /** The spell check key substitutions: the key should be substituted by the value */
76 protected static Map<String, String> spellCheckKeyData;
77 /** The spell check preset values */
[3674]78 protected static MultiMap<String, String> presetsValueData;
[3669]79 /** The TagChecker data */
80 protected static List<CheckerData> checkerData = new ArrayList<CheckerData>();
81 protected static List<String> ignoreDataStartsWith = new ArrayList<String>();
82 protected static List<String> ignoreDataEquals = new ArrayList<String>();
83 protected static List<String> ignoreDataEndsWith = new ArrayList<String>();
84 protected static List<IgnoreKeyPair> ignoreDataKeyPair = new ArrayList<IgnoreKeyPair>();
85 protected static List<IgnoreTwoKeyPair> ignoreDataTwoKeyPair = new ArrayList<IgnoreTwoKeyPair>();
86
87 /** The preferences prefix */
88 protected static final String PREFIX = ValidatorPreference.PREFIX + "." + TagChecker.class.getSimpleName();
89
90 public static final String PREF_CHECK_VALUES = PREFIX + ".checkValues";
91 public static final String PREF_CHECK_KEYS = PREFIX + ".checkKeys";
92 public static final String PREF_CHECK_COMPLEX = PREFIX + ".checkComplex";
93 public static final String PREF_CHECK_FIXMES = PREFIX + ".checkFixmes";
94
95 public static final String PREF_SOURCES = PREFIX + ".sources";
96 public static final String PREF_USE_DATA_FILE = PREFIX + ".usedatafile";
97 public static final String PREF_USE_IGNORE_FILE = PREFIX + ".useignorefile";
98 public static final String PREF_USE_SPELL_FILE = PREFIX + ".usespellfile";
99
100 public static final String PREF_CHECK_KEYS_BEFORE_UPLOAD = PREF_CHECK_KEYS + "BeforeUpload";
101 public static final String PREF_CHECK_VALUES_BEFORE_UPLOAD = PREF_CHECK_VALUES + "BeforeUpload";
102 public static final String PREF_CHECK_COMPLEX_BEFORE_UPLOAD = PREF_CHECK_COMPLEX + "BeforeUpload";
103 public static final String PREF_CHECK_FIXMES_BEFORE_UPLOAD = PREF_CHECK_FIXMES + "BeforeUpload";
104
105 protected boolean checkKeys = false;
106 protected boolean checkValues = false;
107 protected boolean checkComplex = false;
108 protected boolean checkFixmes = false;
109
110 protected JCheckBox prefCheckKeys;
111 protected JCheckBox prefCheckValues;
112 protected JCheckBox prefCheckComplex;
113 protected JCheckBox prefCheckFixmes;
114 protected JCheckBox prefCheckPaint;
115
116 protected JCheckBox prefCheckKeysBeforeUpload;
117 protected JCheckBox prefCheckValuesBeforeUpload;
118 protected JCheckBox prefCheckComplexBeforeUpload;
119 protected JCheckBox prefCheckFixmesBeforeUpload;
120 protected JCheckBox prefCheckPaintBeforeUpload;
121
122 protected JCheckBox prefUseDataFile;
123 protected JCheckBox prefUseIgnoreFile;
124 protected JCheckBox prefUseSpellFile;
125
126 protected JButton addSrcButton;
127 protected JButton editSrcButton;
128 protected JButton deleteSrcButton;
129
130 protected static int EMPTY_VALUES = 1200;
131 protected static int INVALID_KEY = 1201;
132 protected static int INVALID_VALUE = 1202;
133 protected static int FIXME = 1203;
134 protected static int INVALID_SPACE = 1204;
135 protected static int INVALID_KEY_SPACE = 1205;
136 protected static int INVALID_HTML = 1206; /* 1207 was PAINT */
137 protected static int LONG_VALUE = 1208;
138 protected static int LONG_KEY = 1209;
139 protected static int LOW_CHAR_VALUE = 1210;
140 protected static int LOW_CHAR_KEY = 1211;
141 /** 1250 and up is used by tagcheck */
142
143 /** List of sources for spellcheck data */
[3671]144 protected JList sourcesList;
[3669]145
[3671]146 protected static Entities entities = new Entities();
[3669]147
148 /**
149 * Constructor
150 */
[3671]151 public TagChecker() {
[3669]152 super(tr("Properties checker :"),
153 tr("This plugin checks for errors in property keys and values."));
154 }
155
156 @Override
[3671]157 public void initialize() throws Exception {
[3669]158 initializeData();
159 initializePresets();
160 }
161
162 /**
163 * Reads the spellcheck file into a HashMap.
164 * The data file is a list of words, beginning with +/-. If it starts with +,
165 * the word is valid, but if it starts with -, the word should be replaced
166 * by the nearest + word before this.
167 *
168 * @throws FileNotFoundException
169 * @throws IOException
170 */
[3671]171 private static void initializeData() throws IOException {
[3669]172 spellCheckKeyData = new HashMap<String, String>();
173 String sources = Main.pref.get( PREF_SOURCES, "");
[3671]174 if (Main.pref.getBoolean(PREF_USE_DATA_FILE, true)) {
175 if (sources == null || sources.length() == 0) {
[3669]176 sources = DATA_FILE;
[3671]177 } else {
[3669]178 sources = DATA_FILE + ";" + sources;
[3671]179 }
[3669]180 }
[3671]181 if (Main.pref.getBoolean(PREF_USE_IGNORE_FILE, true)) {
182 if (sources == null || sources.length() == 0) {
[3669]183 sources = IGNORE_FILE;
[3671]184 } else {
[3669]185 sources = IGNORE_FILE + ";" + sources;
[3671]186 }
[3669]187 }
[3671]188 if (Main.pref.getBoolean(PREF_USE_SPELL_FILE, true)) {
189 if( sources == null || sources.length() == 0) {
[3669]190 sources = SPELL_FILE;
[3671]191 } else {
[3669]192 sources = SPELL_FILE + ";" + sources;
[3671]193 }
[3669]194 }
195
196 String errorSources = "";
[3671]197 if (sources.length() == 0)
[3669]198 return;
[3671]199 for (String source : sources.split(";")) {
200 try {
[3707]201 MirroredInputStream s = new MirroredInputStream(source, OsmValidator.getValidatorDir(), -1);
[3669]202 InputStreamReader r;
[3671]203 try {
[3669]204 r = new InputStreamReader(s, "UTF-8");
[3671]205 } catch (UnsupportedEncodingException e) {
[3669]206 r = new InputStreamReader(s);
207 }
208 BufferedReader reader = new BufferedReader(r);
209
210 String okValue = null;
211 boolean tagcheckerfile = false;
212 boolean ignorefile = false;
213 String line;
[3671]214 while ((line = reader.readLine()) != null && (tagcheckerfile || line.length() != 0)) {
215 if (line.startsWith("#")) {
216 if (line.startsWith("# JOSM TagChecker")) {
[3669]217 tagcheckerfile = true;
[3671]218 }
219 if (line.startsWith("# JOSM IgnoreTags")) {
[3669]220 ignorefile = true;
[3671]221 }
[3669]222 continue;
[3671]223 } else if (ignorefile) {
[3669]224 line = line.trim();
[3671]225 if (line.length() < 4) {
[3669]226 continue;
[3671]227 }
[3669]228
229 String key = line.substring(0, 2);
230 line = line.substring(2);
231
[3671]232 if (key.equals("S:")) {
[3669]233 ignoreDataStartsWith.add(line);
[3671]234 } else if (key.equals("E:")) {
[3669]235 ignoreDataEquals.add(line);
[3671]236 } else if (key.equals("F:")) {
[3669]237 ignoreDataEndsWith.add(line);
[3671]238 } else if (key.equals("K:")) {
[3669]239 IgnoreKeyPair tmp = new IgnoreKeyPair();
240 int mid = line.indexOf("=");
241 tmp.key = line.substring(0, mid);
242 tmp.value = line.substring(mid+1);
243 ignoreDataKeyPair.add(tmp);
[3671]244 } else if (key.equals("T:")) {
[3669]245 IgnoreTwoKeyPair tmp = new IgnoreTwoKeyPair();
246 int mid = line.indexOf("=");
247 int split = line.indexOf("|");
248 tmp.key1 = line.substring(0, mid);
249 tmp.value1 = line.substring(mid+1, split);
250 line = line.substring(split+1);
251 mid = line.indexOf("=");
252 tmp.key2 = line.substring(0, mid);
253 tmp.value2 = line.substring(mid+1);
254 ignoreDataTwoKeyPair.add(tmp);
255 }
256 continue;
[3671]257 } else if (tagcheckerfile) {
258 if (line.length() > 0) {
[3669]259 CheckerData d = new CheckerData();
260 String err = d.getData(line);
261
[3671]262 if (err == null) {
[3669]263 checkerData.add(d);
[3671]264 } else {
[3669]265 System.err.println(tr("Invalid tagchecker line - {0}: {1}", err, line));
[3671]266 }
[3669]267 }
[3671]268 } else if (line.charAt(0) == '+') {
[3669]269 okValue = line.substring(1);
[3671]270 } else if (line.charAt(0) == '-' && okValue != null) {
[3669]271 spellCheckKeyData.put(line.substring(1), okValue);
[3671]272 } else {
[3669]273 System.err.println(tr("Invalid spellcheck line: {0}", line));
274 }
275 }
[3671]276 } catch (IOException e) {
[3669]277 errorSources += source + "\n";
278 }
279 }
280
[3671]281 if (errorSources.length() > 0)
[3669]282 throw new IOException( tr("Could not access data file(s):\n{0}", errorSources) );
283 }
284
285 /**
286 * Reads the presets data.
287 *
288 * @throws Exception
289 */
[3671]290 public static void initializePresets() throws Exception {
291
292 if (!Main.pref.getBoolean(PREF_CHECK_VALUES, true))
[3669]293 return;
294
295 Collection<TaggingPreset> presets = TaggingPresetPreference.taggingPresets;
[3671]296 if (presets != null) {
[3674]297 presetsValueData = new MultiMap<String, String>();
[3671]298 for (String a : OsmPrimitive.getUninterestingKeys()) {
[3674]299 presetsValueData.putVoid(a);
[3671]300 }
[3669]301 // TODO directionKeys are no longer in OsmPrimitive (search pattern is used instead)
302 /* for(String a : OsmPrimitive.getDirectionKeys())
303 presetsValueData.add(a);
304 */
[3671]305 for (String a : Main.pref.getCollection(ValidatorPreference.PREFIX + ".knownkeys",
306 Arrays.asList(new String[]{"is_in", "int_ref", "fixme", "population"}))) {
[3674]307 presetsValueData.putVoid(a);
[3671]308 }
309 for (TaggingPreset p : presets) {
310 for(TaggingPreset.Item i : p.data) {
311 if (i instanceof TaggingPreset.Combo) {
[3669]312 TaggingPreset.Combo combo = (TaggingPreset.Combo) i;
[3671]313 if (combo.values != null) {
314 for(String value : combo.values.split(",")) {
[3674]315 presetsValueData.put(combo.key, value);
[3671]316 }
[3669]317 }
[3671]318 } else if (i instanceof TaggingPreset.Key) {
[3669]319 TaggingPreset.Key k = (TaggingPreset.Key) i;
[3674]320 presetsValueData.put(k.key, k.value);
[3671]321 } else if (i instanceof TaggingPreset.Text) {
[3669]322 TaggingPreset.Text k = (TaggingPreset.Text) i;
[3674]323 presetsValueData.putVoid(k.key);
[3671]324 } else if (i instanceof TaggingPreset.Check) {
[3669]325 TaggingPreset.Check k = (TaggingPreset.Check) i;
[3674]326 presetsValueData.put(k.key, "yes");
327 presetsValueData.put(k.key, "no");
[3669]328 }
329 }
330 }
331 }
332 }
333
334 @Override
[3671]335 public void visit(Node n) {
[3669]336 checkPrimitive(n);
337 }
338
339 @Override
[3671]340 public void visit(Relation n) {
[3669]341 checkPrimitive(n);
342 }
343
344 @Override
[3671]345 public void visit(Way w) {
[3669]346 checkPrimitive(w);
347 }
348
349 /**
350 * Checks given string (key or value) if it contains characters with code below 0x20 (either newline or some other special characters)
351 * @param s string to check
352 */
353 private boolean containsLow(String s) {
[3671]354 if (s == null)
355 return false;
356 for (int i = 0; i < s.length(); i++) {
357 if (s.charAt(i) < 0x20)
358 return true;
[3669]359 }
360 return false;
361 }
362
363 /**
364 * Checks the primitive properties
365 * @param p The primitive to check
366 */
[3671]367 private void checkPrimitive(OsmPrimitive p) {
[3669]368 // Just a collection to know if a primitive has been already marked with error
[3674]369 MultiMap<OsmPrimitive, String> withErrors = new MultiMap<OsmPrimitive, String>();
[3669]370
[3671]371 if (checkComplex) {
[3669]372 Map<String, String> props = (p.getKeys() == null) ? Collections.<String, String>emptyMap() : p.getKeys();
[3671]373 for (Entry<String, String> prop : props.entrySet()) {
[3669]374 boolean ignore = true;
375 String key1 = prop.getKey();
376 String value1 = prop.getValue();
377
[3671]378 for (IgnoreTwoKeyPair a : ignoreDataTwoKeyPair) {
379 if (key1.equals(a.key1) && value1.equals(a.value1)) {
[3669]380 ignore = false;
[3671]381 for (Entry<String, String> prop2 : props.entrySet()) {
[3669]382 String key2 = prop2.getKey();
383 String value2 = prop2.getValue();
[3671]384 for (IgnoreTwoKeyPair b : ignoreDataTwoKeyPair) {
385 if (key2.equals(b.key2) && value2.equals(b.value2)) {
[3669]386 ignore = true;
387 break;
388 }
389 }
[3671]390 if (ignore) {
[3669]391 break;
[3671]392 }
[3669]393 }
394 }
[3671]395 if (ignore) {
[3669]396 break;
[3671]397 }
[3669]398 }
399
[3671]400 if (!ignore) {
[4035]401 errors.add( new TestError(this, Severity.OTHER, tr("Suspicious tag/value combinations"),
[4023]402 tr("Suspicious tag/value combinations"), tr("Suspicious tag/value combinations"), 1272, p) );
[3674]403 withErrors.put(p, "TC");
[3669]404 }
405 }
406
407 Map<String, String> keys = p.getKeys();
[3671]408 for (CheckerData d : checkerData) {
409 if (d.match(p, keys)) {
[4023]410 errors.add( new TestError(this, d.getSeverity(), tr("Suspicious tag/value combinations"),
[3669]411 d.getDescription(), d.getDescriptionOrig(), d.getCode(), p) );
[3674]412 withErrors.put(p, "TC");
[3669]413 }
414 }
415 }
416
417 Map<String, String> props = (p.getKeys() == null) ? Collections.<String, String>emptyMap() : p.getKeys();
[3671]418 for (Entry<String, String> prop : props.entrySet()) {
[3669]419 String s = marktr("Key ''{0}'' invalid.");
420 String key = prop.getKey();
421 String value = prop.getValue();
[3671]422 if (checkValues && (containsLow(value)) && !withErrors.contains(p, "ICV")) {
[3669]423 errors.add( new TestError(this, Severity.WARNING, tr("Tag value contains character with code less than 0x20"),
424 tr(s, key), MessageFormat.format(s, key), LOW_CHAR_VALUE, p) );
[3674]425 withErrors.put(p, "ICV");
[3669]426 }
[3671]427 if (checkKeys && (containsLow(key)) && !withErrors.contains(p, "ICK")) {
[3669]428 errors.add( new TestError(this, Severity.WARNING, tr("Tag key contains character with code less than 0x20"),
429 tr(s, key), MessageFormat.format(s, key), LOW_CHAR_KEY, p) );
[3674]430 withErrors.put(p, "ICK");
[3669]431 }
[3671]432 if (checkValues && (value!=null && value.length() > 255) && !withErrors.contains(p, "LV")) {
[3669]433 errors.add( new TestError(this, Severity.ERROR, tr("Tag value longer than allowed"),
434 tr(s, key), MessageFormat.format(s, key), LONG_VALUE, p) );
[3674]435 withErrors.put(p, "LV");
[3669]436 }
[3671]437 if (checkKeys && (key!=null && key.length() > 255) && !withErrors.contains(p, "LK")) {
[3669]438 errors.add( new TestError(this, Severity.ERROR, tr("Tag key longer than allowed"),
439 tr(s, key), MessageFormat.format(s, key), LONG_KEY, p) );
[3674]440 withErrors.put(p, "LK");
[3669]441 }
[3671]442 if (checkValues && (value==null || value.trim().length() == 0) && !withErrors.contains(p, "EV")) {
[3669]443 errors.add( new TestError(this, Severity.WARNING, tr("Tags with empty values"),
444 tr(s, key), MessageFormat.format(s, key), EMPTY_VALUES, p) );
[3674]445 withErrors.put(p, "EV");
[3669]446 }
[3671]447 if (checkKeys && spellCheckKeyData.containsKey(key) && !withErrors.contains(p, "IPK")) {
[3669]448 errors.add( new TestError(this, Severity.WARNING, tr("Invalid property key"),
449 tr(s, key), MessageFormat.format(s, key), INVALID_KEY, p) );
[3674]450 withErrors.put(p, "IPK");
[3669]451 }
[3671]452 if (checkKeys && key.indexOf(" ") >= 0 && !withErrors.contains(p, "IPK")) {
[3669]453 errors.add( new TestError(this, Severity.WARNING, tr("Invalid white space in property key"),
454 tr(s, key), MessageFormat.format(s, key), INVALID_KEY_SPACE, p) );
[3674]455 withErrors.put(p, "IPK");
[3669]456 }
[3671]457 if (checkValues && value != null && (value.startsWith(" ") || value.endsWith(" ")) && !withErrors.contains(p, "SPACE")) {
[3669]458 errors.add( new TestError(this, Severity.OTHER, tr("Property values start or end with white space"),
459 tr(s, key), MessageFormat.format(s, key), INVALID_SPACE, p) );
[3674]460 withErrors.put(p, "SPACE");
[3669]461 }
[3671]462 if (checkValues && value != null && !value.equals(entities.unescape(value)) && !withErrors.contains(p, "HTML")) {
[3669]463 errors.add( new TestError(this, Severity.OTHER, tr("Property values contain HTML entity"),
464 tr(s, key), MessageFormat.format(s, key), INVALID_HTML, p) );
[3674]465 withErrors.put(p, "HTML");
[3669]466 }
[3671]467 if (checkValues && value != null && value.length() > 0 && presetsValueData != null) {
[3674]468 Set<String> values = presetsValueData.get(key);
[3671]469 if (values == null) {
[3669]470 boolean ignore = false;
[3671]471 for (String a : ignoreDataStartsWith) {
472 if (key.startsWith(a)) {
[3669]473 ignore = true;
[3671]474 }
[3669]475 }
[3671]476 for (String a : ignoreDataEquals) {
477 if(key.equals(a)) {
[3669]478 ignore = true;
[3671]479 }
[3669]480 }
[3671]481 for (String a : ignoreDataEndsWith) {
482 if(key.endsWith(a)) {
[3669]483 ignore = true;
[3671]484 }
[3669]485 }
[3671]486 if (!ignore) {
[3669]487 String i = marktr("Key ''{0}'' not in presets.");
488 errors.add( new TestError(this, Severity.OTHER, tr("Presets do not contain property key"),
489 tr(i, key), MessageFormat.format(i, key), INVALID_VALUE, p) );
[3674]490 withErrors.put(p, "UPK");
[3669]491 }
[3671]492 } else if (values.size() > 0 && !values.contains(prop.getValue())) {
[3669]493 boolean ignore = false;
[3671]494 for (IgnoreKeyPair a : ignoreDataKeyPair) {
495 if (key.equals(a.key) && value.equals(a.value)) {
[3669]496 ignore = true;
[3671]497 }
[3669]498 }
499
[3671]500 for (IgnoreTwoKeyPair a : ignoreDataTwoKeyPair) {
501 if (key.equals(a.key2) && value.equals(a.value2)) {
[3669]502 ignore = true;
[3671]503 }
[3669]504 }
505
[3671]506 if (!ignore) {
[3669]507 String i = marktr("Value ''{0}'' for key ''{1}'' not in presets.");
508 errors.add( new TestError(this, Severity.OTHER, tr("Presets do not contain property value"),
509 tr(i, prop.getValue(), key), MessageFormat.format(i, prop.getValue(), key), INVALID_VALUE, p) );
[3674]510 withErrors.put(p, "UPV");
[3669]511 }
512 }
513 }
514 if (checkFixmes && value != null && value.length() > 0) {
515 if ((value.toLowerCase().contains("fixme")
516 || value.contains("check and delete")
517 || key.contains("todo") || key.toLowerCase().contains("fixme"))
518 && !withErrors.contains(p, "FIXME")) {
519 errors.add(new TestError(this, Severity.OTHER,
520 tr("FIXMES"), FIXME, p));
[3674]521 withErrors.put(p, "FIXME");
[3669]522 }
523 }
524 }
525 }
526
527 @Override
[3671]528 public void startTest(ProgressMonitor monitor) {
[3669]529 super.startTest(monitor);
530 checkKeys = Main.pref.getBoolean(PREF_CHECK_KEYS, true);
[3671]531 if (isBeforeUpload) {
[3669]532 checkKeys = checkKeys && Main.pref.getBoolean(PREF_CHECK_KEYS_BEFORE_UPLOAD, true);
[3671]533 }
[3669]534
535 checkValues = Main.pref.getBoolean(PREF_CHECK_VALUES, true);
[3671]536 if (isBeforeUpload) {
[3669]537 checkValues = checkValues && Main.pref.getBoolean(PREF_CHECK_VALUES_BEFORE_UPLOAD, true);
[3671]538 }
[3669]539
540 checkComplex = Main.pref.getBoolean(PREF_CHECK_COMPLEX, true);
[3671]541 if (isBeforeUpload) {
[3669]542 checkComplex = checkValues && Main.pref.getBoolean(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, true);
[3671]543 }
[3669]544
545 checkFixmes = Main.pref.getBoolean(PREF_CHECK_FIXMES, true);
[3671]546 if (isBeforeUpload) {
[3669]547 checkFixmes = checkFixmes && Main.pref.getBoolean(PREF_CHECK_FIXMES_BEFORE_UPLOAD, true);
[3671]548 }
[3669]549 }
550
551 @Override
[3671]552 public void visit(Collection<OsmPrimitive> selection) {
553 if (checkKeys || checkValues || checkComplex || checkFixmes) {
[3669]554 super.visit(selection);
[3671]555 }
[3669]556 }
557
558 @Override
[3671]559 public void addGui(JPanel testPanel) {
[3669]560 GBC a = GBC.eol();
561 a.anchor = GridBagConstraints.EAST;
562
[3671]563 testPanel.add(new JLabel(name), GBC.eol().insets(3,0,0,0));
[3669]564
565 prefCheckKeys = new JCheckBox(tr("Check property keys."), Main.pref.getBoolean(PREF_CHECK_KEYS, true));
566 prefCheckKeys.setToolTipText(tr("Validate that property keys are valid checking against list of words."));
567 testPanel.add(prefCheckKeys, GBC.std().insets(20,0,0,0));
568
569 prefCheckKeysBeforeUpload = new JCheckBox();
570 prefCheckKeysBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_KEYS_BEFORE_UPLOAD, true));
571 testPanel.add(prefCheckKeysBeforeUpload, a);
572
573 prefCheckComplex = new JCheckBox(tr("Use complex property checker."), Main.pref.getBoolean(PREF_CHECK_COMPLEX, true));
574 prefCheckComplex.setToolTipText(tr("Validate property values and tags using complex rules."));
575 testPanel.add(prefCheckComplex, GBC.std().insets(20,0,0,0));
576
577 prefCheckComplexBeforeUpload = new JCheckBox();
578 prefCheckComplexBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, true));
579 testPanel.add(prefCheckComplexBeforeUpload, a);
580
[3671]581 sourcesList = new JList(new DefaultListModel());
[3669]582
583 String sources = Main.pref.get( PREF_SOURCES );
[3671]584 if (sources != null && sources.length() > 0) {
585 for (String source : sources.split(";")) {
586 ((DefaultListModel)sourcesList.getModel()).addElement(source);
587 }
[3669]588 }
589
590 addSrcButton = new JButton(tr("Add"));
[3671]591 addSrcButton.addActionListener(new ActionListener() {
592 @Override
[3669]593 public void actionPerformed(ActionEvent e) {
594 String source = JOptionPane.showInputDialog(
595 Main.parent,
596 tr("TagChecker source"),
597 tr("TagChecker source"),
[3671]598 JOptionPane.QUESTION_MESSAGE);
599 if (source != null) {
600 ((DefaultListModel)sourcesList.getModel()).addElement(source);
601 }
602 sourcesList.clearSelection();
[3669]603 }
604 });
605
606 editSrcButton = new JButton(tr("Edit"));
[3671]607 editSrcButton.addActionListener(new ActionListener() {
608 @Override
[3669]609 public void actionPerformed(ActionEvent e) {
[3671]610 int row = sourcesList.getSelectedIndex();
611 if (row == -1 && sourcesList.getModel().getSize() == 1) {
612 sourcesList.setSelectedIndex(0);
[3669]613 row = 0;
614 }
[3671]615 if (row == -1) {
616 if (sourcesList.getModel().getSize() == 0) {
[3669]617 String source = JOptionPane.showInputDialog(Main.parent, tr("TagChecker source"), tr("TagChecker source"), JOptionPane.QUESTION_MESSAGE);
[3671]618 if (source != null) {
619 ((DefaultListModel)sourcesList.getModel()).addElement(source);
620 }
621 } else {
[3669]622 JOptionPane.showMessageDialog(
623 Main.parent,
624 tr("Please select the row to edit."),
625 tr("Information"),
626 JOptionPane.INFORMATION_MESSAGE
627 );
628 }
[3671]629 } else {
[3669]630 String source = (String)JOptionPane.showInputDialog(Main.parent,
631 tr("TagChecker source"),
632 tr("TagChecker source"),
633 JOptionPane.QUESTION_MESSAGE, null, null,
[3671]634 sourcesList.getSelectedValue());
635 if (source != null) {
636 ((DefaultListModel)sourcesList.getModel()).setElementAt(source, row);
637 }
[3669]638 }
[3671]639 sourcesList.clearSelection();
[3669]640 }
641 });
642
643 deleteSrcButton = new JButton(tr("Delete"));
[3671]644 deleteSrcButton.addActionListener(new ActionListener() {
645 @Override
[3669]646 public void actionPerformed(ActionEvent e) {
[3671]647 if (sourcesList.getSelectedIndex() == -1) {
[3669]648 JOptionPane.showMessageDialog(Main.parent, tr("Please select the row to delete."), tr("Information"), JOptionPane.QUESTION_MESSAGE);
[3671]649 } else {
650 ((DefaultListModel)sourcesList.getModel()).remove(sourcesList.getSelectedIndex());
[3669]651 }
652 }
653 });
[3671]654 sourcesList.setMinimumSize(new Dimension(300,50));
655 sourcesList.setVisibleRowCount(3);
[3669]656
[3671]657 sourcesList.setToolTipText(tr("The sources (URL or filename) of spell check (see http://wiki.openstreetmap.org/index.php/User:JLS/speller) or tag checking data files."));
[3669]658 addSrcButton.setToolTipText(tr("Add a new source to the list."));
659 editSrcButton.setToolTipText(tr("Edit the selected source."));
660 deleteSrcButton.setToolTipText(tr("Delete the selected source from the list."));
661
662 testPanel.add(new JLabel(tr("Data sources")), GBC.eol().insets(23,0,0,0));
[3671]663 testPanel.add(new JScrollPane(sourcesList), GBC.eol().insets(23,0,0,0).fill(GridBagConstraints.HORIZONTAL));
[3669]664 final JPanel buttonPanel = new JPanel(new GridBagLayout());
665 testPanel.add(buttonPanel, GBC.eol().fill(GridBagConstraints.HORIZONTAL));
666 buttonPanel.add(addSrcButton, GBC.std().insets(0,5,0,0));
667 buttonPanel.add(editSrcButton, GBC.std().insets(5,5,5,0));
668 buttonPanel.add(deleteSrcButton, GBC.std().insets(0,5,0,0));
669
[3671]670 ActionListener disableCheckActionListener = new ActionListener() {
671 @Override
[3669]672 public void actionPerformed(ActionEvent e) {
673 handlePrefEnable();
674 }
675 };
676 prefCheckKeys.addActionListener(disableCheckActionListener);
677 prefCheckKeysBeforeUpload.addActionListener(disableCheckActionListener);
678 prefCheckComplex.addActionListener(disableCheckActionListener);
679 prefCheckComplexBeforeUpload.addActionListener(disableCheckActionListener);
680
681 handlePrefEnable();
682
683 prefCheckValues = new JCheckBox(tr("Check property values."), Main.pref.getBoolean(PREF_CHECK_VALUES, true));
684 prefCheckValues.setToolTipText(tr("Validate that property values are valid checking against presets."));
685 testPanel.add(prefCheckValues, GBC.std().insets(20,0,0,0));
686
687 prefCheckValuesBeforeUpload = new JCheckBox();
688 prefCheckValuesBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_VALUES_BEFORE_UPLOAD, true));
689 testPanel.add(prefCheckValuesBeforeUpload, a);
690
691 prefCheckFixmes = new JCheckBox(tr("Check for FIXMES."), Main.pref.getBoolean(PREF_CHECK_FIXMES, true));
692 prefCheckFixmes.setToolTipText(tr("Looks for nodes or ways with FIXME in any property value."));
693 testPanel.add(prefCheckFixmes, GBC.std().insets(20,0,0,0));
694
695 prefCheckFixmesBeforeUpload = new JCheckBox();
696 prefCheckFixmesBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_FIXMES_BEFORE_UPLOAD, true));
697 testPanel.add(prefCheckFixmesBeforeUpload, a);
698
699 prefUseDataFile = new JCheckBox(tr("Use default data file."), Main.pref.getBoolean(PREF_USE_DATA_FILE, true));
700 prefUseDataFile.setToolTipText(tr("Use the default data file (recommended)."));
701 testPanel.add(prefUseDataFile, GBC.eol().insets(20,0,0,0));
702
703 prefUseIgnoreFile = new JCheckBox(tr("Use default tag ignore file."), Main.pref.getBoolean(PREF_USE_IGNORE_FILE, true));
704 prefUseIgnoreFile.setToolTipText(tr("Use the default tag ignore file (recommended)."));
705 testPanel.add(prefUseIgnoreFile, GBC.eol().insets(20,0,0,0));
706
707 prefUseSpellFile = new JCheckBox(tr("Use default spellcheck file."), Main.pref.getBoolean(PREF_USE_SPELL_FILE, true));
708 prefUseSpellFile.setToolTipText(tr("Use the default spellcheck file (recommended)."));
709 testPanel.add(prefUseSpellFile, GBC.eol().insets(20,0,0,0));
710 }
711
[3671]712 public void handlePrefEnable() {
[3669]713 boolean selected = prefCheckKeys.isSelected() || prefCheckKeysBeforeUpload.isSelected()
[3671]714 || prefCheckComplex.isSelected() || prefCheckComplexBeforeUpload.isSelected();
715 sourcesList.setEnabled( selected );
[3669]716 addSrcButton.setEnabled(selected);
717 editSrcButton.setEnabled(selected);
718 deleteSrcButton.setEnabled(selected);
719 }
720
721 @Override
722 public boolean ok()
723 {
724 enabled = prefCheckKeys.isSelected() || prefCheckValues.isSelected() || prefCheckComplex.isSelected() || prefCheckFixmes.isSelected();
725 testBeforeUpload = prefCheckKeysBeforeUpload.isSelected() || prefCheckValuesBeforeUpload.isSelected()
[3671]726 || prefCheckFixmesBeforeUpload.isSelected() || prefCheckComplexBeforeUpload.isSelected();
[3669]727
728 Main.pref.put(PREF_CHECK_VALUES, prefCheckValues.isSelected());
729 Main.pref.put(PREF_CHECK_COMPLEX, prefCheckComplex.isSelected());
730 Main.pref.put(PREF_CHECK_KEYS, prefCheckKeys.isSelected());
731 Main.pref.put(PREF_CHECK_FIXMES, prefCheckFixmes.isSelected());
732 Main.pref.put(PREF_CHECK_VALUES_BEFORE_UPLOAD, prefCheckValuesBeforeUpload.isSelected());
733 Main.pref.put(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, prefCheckComplexBeforeUpload.isSelected());
734 Main.pref.put(PREF_CHECK_KEYS_BEFORE_UPLOAD, prefCheckKeysBeforeUpload.isSelected());
735 Main.pref.put(PREF_CHECK_FIXMES_BEFORE_UPLOAD, prefCheckFixmesBeforeUpload.isSelected());
736 Main.pref.put(PREF_USE_DATA_FILE, prefUseDataFile.isSelected());
737 Main.pref.put(PREF_USE_IGNORE_FILE, prefUseIgnoreFile.isSelected());
738 Main.pref.put(PREF_USE_SPELL_FILE, prefUseSpellFile.isSelected());
739 String sources = "";
[3671]740 if (sourcesList.getModel().getSize() > 0) {
[3669]741 String sb = "";
[3671]742 for (int i = 0; i < sourcesList.getModel().getSize(); ++i) {
743 sb += ";"+sourcesList.getModel().getElementAt(i);
744 }
[3669]745 sources = sb.substring(1);
746 }
[3671]747 if (sources.length() == 0) {
[3669]748 sources = null;
[3671]749 }
[3669]750 return Main.pref.put(PREF_SOURCES, sources);
751 }
752
753 @Override
[3671]754 public Command fixError(TestError testError) {
755
[3669]756 List<Command> commands = new ArrayList<Command>(50);
757
[3674]758 Collection<? extends OsmPrimitive> primitives = testError.getPrimitives();
[3671]759 for (OsmPrimitive p : primitives) {
[3669]760 Map<String, String> tags = p.getKeys();
[3671]761 if (tags == null || tags.isEmpty()) {
[3669]762 continue;
[3671]763 }
[3669]764
[3671]765 for (Entry<String, String> prop: tags.entrySet()) {
[3669]766 String key = prop.getKey();
767 String value = prop.getValue();
[3671]768 if (value == null || value.trim().length() == 0) {
[3674]769 commands.add(new ChangePropertyCommand(Collections.singleton(p), key, null));
[3671]770 } else if (value.startsWith(" ") || value.endsWith(" ")) {
[3674]771 commands.add(new ChangePropertyCommand(Collections.singleton(p), key, value.trim()));
[3671]772 } else if (key.startsWith(" ") || key.endsWith(" ")) {
[3674]773 commands.add(new ChangePropertyKeyCommand(Collections.singleton(p), key, key.trim()));
[3671]774 } else {
[3669]775 String evalue = entities.unescape(value);
[3671]776 if (!evalue.equals(value)) {
[3674]777 commands.add(new ChangePropertyCommand(Collections.singleton(p), key, evalue));
[3671]778 } else {
[3669]779 String replacementKey = spellCheckKeyData.get(key);
[3671]780 if (replacementKey != null) {
[3674]781 commands.add(new ChangePropertyKeyCommand(Collections.singleton(p),
[3671]782 key, replacementKey));
[3669]783 }
784 }
785 }
786 }
787 }
788
[3671]789 if (commands.isEmpty())
[3669]790 return null;
[3671]791 if (commands.size() == 1)
[3669]792 return commands.get(0);
[3671]793
794 return new SequenceCommand(tr("Fix properties"), commands);
[3669]795 }
796
797 @Override
[3671]798 public boolean isFixable(TestError testError) {
799
800 if (testError.getTester() instanceof TagChecker) {
[3669]801 int code = testError.getCode();
802 return code == INVALID_KEY || code == EMPTY_VALUES || code == INVALID_SPACE || code == INVALID_KEY_SPACE || code == INVALID_HTML;
803 }
804
805 return false;
806 }
807
808 protected static class IgnoreTwoKeyPair {
809 public String key1;
810 public String value1;
811 public String key2;
812 public String value2;
813 }
814
815 protected static class IgnoreKeyPair {
816 public String key;
817 public String value;
818 }
819
820 protected static class CheckerData {
821 private String description;
822 private List<CheckerElement> data = new ArrayList<CheckerElement>();
823 private OsmPrimitiveType type;
824 private int code;
825 protected Severity severity;
826 protected static int TAG_CHECK_ERROR = 1250;
827 protected static int TAG_CHECK_WARN = 1260;
828 protected static int TAG_CHECK_INFO = 1270;
829
830 private static class CheckerElement {
831 public Object tag;
832 public Object value;
833 public boolean noMatch;
834 public boolean tagAll = false;
835 public boolean valueAll = false;
836 public boolean valueBool = false;
[3673]837
[3671]838 private Pattern getPattern(String str) throws IllegalStateException, PatternSyntaxException {
839 if (str.endsWith("/i"))
[3669]840 return Pattern.compile(str.substring(1,str.length()-2), Pattern.CASE_INSENSITIVE);
[3671]841 if (str.endsWith("/"))
[3669]842 return Pattern.compile(str.substring(1,str.length()-1));
[3671]843
[3669]844 throw new IllegalStateException();
845 }
[3671]846 public CheckerElement(String exp) throws IllegalStateException, PatternSyntaxException {
[3669]847 Matcher m = Pattern.compile("(.+)([!=]=)(.+)").matcher(exp);
848 m.matches();
849
850 String n = m.group(1).trim();
[3671]851
[3673]852 if(n.equals("*")) {
[3669]853 tagAll = true;
[3673]854 } else {
[3669]855 tag = n.startsWith("/") ? getPattern(n) : n;
856 noMatch = m.group(2).equals("!=");
857 n = m.group(3).trim();
[3673]858 if (n.equals("*")) {
[3669]859 valueAll = true;
[3673]860 } else if (n.equals("BOOLEAN_TRUE")) {
[3669]861 valueBool = true;
862 value = OsmUtils.trueval;
[3673]863 } else if (n.equals("BOOLEAN_FALSE")) {
[3669]864 valueBool = true;
865 value = OsmUtils.falseval;
[3673]866 } else {
867 value = n.startsWith("/") ? getPattern(n) : n;
[3669]868 }
[3673]869 }
[3669]870 }
[3671]871
[3669]872 public boolean match(OsmPrimitive osm, Map<String, String> keys) {
[3671]873 for (Entry<String, String> prop: keys.entrySet()) {
[3669]874 String key = prop.getKey();
875 String val = valueBool ? OsmUtils.getNamedOsmBoolean(prop.getValue()) : prop.getValue();
[3671]876 if ((tagAll || (tag instanceof Pattern ? ((Pattern) tag).matcher(key).matches() : key.equals(tag)))
877 && (valueAll || (value instanceof Pattern ? ((Pattern) value).matcher(val).matches() : val.equals(value))))
[3669]878 return !noMatch;
879 }
880 return noMatch;
881 }
882 };
883
[3671]884 public String getData(String str) {
[3669]885 Matcher m = Pattern.compile(" *# *([^#]+) *$").matcher(str);
886 str = m.replaceFirst("").trim();
[3671]887 try {
[3669]888 description = m.group(1);
[3671]889 if (description != null && description.length() == 0) {
[3669]890 description = null;
[3671]891 }
892 } catch (IllegalStateException e) {
[3669]893 description = null;
894 }
895 String[] n = str.split(" *: *", 3);
[3671]896 if (n[0].equals("way")) {
[3669]897 type = OsmPrimitiveType.WAY;
[3671]898 } else if (n[0].equals("node")) {
[3669]899 type = OsmPrimitiveType.NODE;
[3671]900 } else if (n[0].equals("relation")) {
[3669]901 type = OsmPrimitiveType.RELATION;
[3671]902 } else if (n[0].equals("*")) {
[3669]903 type = null;
[3671]904 } else
[3669]905 return tr("Could not find element type");
906 if (n.length != 3)
907 return tr("Incorrect number of parameters");
908
[3671]909 if (n[1].equals("W")) {
[3669]910 severity = Severity.WARNING;
911 code = TAG_CHECK_WARN;
[3671]912 } else if (n[1].equals("E")) {
[3669]913 severity = Severity.ERROR;
914 code = TAG_CHECK_ERROR;
[3671]915 } else if(n[1].equals("I")) {
[3669]916 severity = Severity.OTHER;
917 code = TAG_CHECK_INFO;
[3671]918 } else
[3669]919 return tr("Could not find warning level");
[3671]920 for (String exp: n[2].split(" *&& *")) {
921 try {
[3669]922 data.add(new CheckerElement(exp));
[3671]923 } catch (IllegalStateException e) {
[3669]924 return tr("Illegal expression ''{0}''", exp);
925 }
[3671]926 catch (PatternSyntaxException e) {
[3669]927 return tr("Illegal regular expression ''{0}''", exp);
928 }
929 }
930 return null;
931 }
[3671]932
933 public boolean match(OsmPrimitive osm, Map<String, String> keys) {
[3669]934 if (type != null && OsmPrimitiveType.from(osm) != type)
935 return false;
936
[3671]937 for (CheckerElement ce : data) {
938 if (!ce.match(osm, keys))
[3669]939 return false;
940 }
941 return true;
942 }
[3671]943
944 public String getDescription() {
[3669]945 return tr(description);
946 }
[3671]947
948 public String getDescriptionOrig() {
[3669]949 return description;
950 }
[3671]951
952 public Severity getSeverity() {
[3669]953 return severity;
954 }
955
956 public int getCode() {
[3671]957 if (type == null)
[3669]958 return code;
[3671]959
960 return code + type.ordinal() + 1;
[3669]961 }
962 }
963}
Note: See TracBrowser for help on using the repository browser.