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

Last change on this file since 4153 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
Line 
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;
26import java.util.Set;
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;
50import org.openstreetmap.josm.data.validation.OsmValidator;
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;
61import org.openstreetmap.josm.tools.MultiMap;
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 */
78 protected static MultiMap<String, String> presetsValueData;
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 */
144 protected JList sourcesList;
145
146 protected static Entities entities = new Entities();
147
148 /**
149 * Constructor
150 */
151 public TagChecker() {
152 super(tr("Properties checker :"),
153 tr("This plugin checks for errors in property keys and values."));
154 }
155
156 @Override
157 public void initialize() throws Exception {
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 */
171 private static void initializeData() throws IOException {
172 spellCheckKeyData = new HashMap<String, String>();
173 String sources = Main.pref.get( PREF_SOURCES, "");
174 if (Main.pref.getBoolean(PREF_USE_DATA_FILE, true)) {
175 if (sources == null || sources.length() == 0) {
176 sources = DATA_FILE;
177 } else {
178 sources = DATA_FILE + ";" + sources;
179 }
180 }
181 if (Main.pref.getBoolean(PREF_USE_IGNORE_FILE, true)) {
182 if (sources == null || sources.length() == 0) {
183 sources = IGNORE_FILE;
184 } else {
185 sources = IGNORE_FILE + ";" + sources;
186 }
187 }
188 if (Main.pref.getBoolean(PREF_USE_SPELL_FILE, true)) {
189 if( sources == null || sources.length() == 0) {
190 sources = SPELL_FILE;
191 } else {
192 sources = SPELL_FILE + ";" + sources;
193 }
194 }
195
196 String errorSources = "";
197 if (sources.length() == 0)
198 return;
199 for (String source : sources.split(";")) {
200 try {
201 MirroredInputStream s = new MirroredInputStream(source, OsmValidator.getValidatorDir(), -1);
202 InputStreamReader r;
203 try {
204 r = new InputStreamReader(s, "UTF-8");
205 } catch (UnsupportedEncodingException e) {
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;
214 while ((line = reader.readLine()) != null && (tagcheckerfile || line.length() != 0)) {
215 if (line.startsWith("#")) {
216 if (line.startsWith("# JOSM TagChecker")) {
217 tagcheckerfile = true;
218 }
219 if (line.startsWith("# JOSM IgnoreTags")) {
220 ignorefile = true;
221 }
222 continue;
223 } else if (ignorefile) {
224 line = line.trim();
225 if (line.length() < 4) {
226 continue;
227 }
228
229 String key = line.substring(0, 2);
230 line = line.substring(2);
231
232 if (key.equals("S:")) {
233 ignoreDataStartsWith.add(line);
234 } else if (key.equals("E:")) {
235 ignoreDataEquals.add(line);
236 } else if (key.equals("F:")) {
237 ignoreDataEndsWith.add(line);
238 } else if (key.equals("K:")) {
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);
244 } else if (key.equals("T:")) {
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;
257 } else if (tagcheckerfile) {
258 if (line.length() > 0) {
259 CheckerData d = new CheckerData();
260 String err = d.getData(line);
261
262 if (err == null) {
263 checkerData.add(d);
264 } else {
265 System.err.println(tr("Invalid tagchecker line - {0}: {1}", err, line));
266 }
267 }
268 } else if (line.charAt(0) == '+') {
269 okValue = line.substring(1);
270 } else if (line.charAt(0) == '-' && okValue != null) {
271 spellCheckKeyData.put(line.substring(1), okValue);
272 } else {
273 System.err.println(tr("Invalid spellcheck line: {0}", line));
274 }
275 }
276 } catch (IOException e) {
277 errorSources += source + "\n";
278 }
279 }
280
281 if (errorSources.length() > 0)
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 */
290 public static void initializePresets() throws Exception {
291
292 if (!Main.pref.getBoolean(PREF_CHECK_VALUES, true))
293 return;
294
295 Collection<TaggingPreset> presets = TaggingPresetPreference.taggingPresets;
296 if (presets != null) {
297 presetsValueData = new MultiMap<String, String>();
298 for (String a : OsmPrimitive.getUninterestingKeys()) {
299 presetsValueData.putVoid(a);
300 }
301 // TODO directionKeys are no longer in OsmPrimitive (search pattern is used instead)
302 /* for(String a : OsmPrimitive.getDirectionKeys())
303 presetsValueData.add(a);
304 */
305 for (String a : Main.pref.getCollection(ValidatorPreference.PREFIX + ".knownkeys",
306 Arrays.asList(new String[]{"is_in", "int_ref", "fixme", "population"}))) {
307 presetsValueData.putVoid(a);
308 }
309 for (TaggingPreset p : presets) {
310 for(TaggingPreset.Item i : p.data) {
311 if (i instanceof TaggingPreset.Combo) {
312 TaggingPreset.Combo combo = (TaggingPreset.Combo) i;
313 if (combo.values != null) {
314 for(String value : combo.values.split(",")) {
315 presetsValueData.put(combo.key, value);
316 }
317 }
318 } else if (i instanceof TaggingPreset.Key) {
319 TaggingPreset.Key k = (TaggingPreset.Key) i;
320 presetsValueData.put(k.key, k.value);
321 } else if (i instanceof TaggingPreset.Text) {
322 TaggingPreset.Text k = (TaggingPreset.Text) i;
323 presetsValueData.putVoid(k.key);
324 } else if (i instanceof TaggingPreset.Check) {
325 TaggingPreset.Check k = (TaggingPreset.Check) i;
326 presetsValueData.put(k.key, "yes");
327 presetsValueData.put(k.key, "no");
328 }
329 }
330 }
331 }
332 }
333
334 @Override
335 public void visit(Node n) {
336 checkPrimitive(n);
337 }
338
339 @Override
340 public void visit(Relation n) {
341 checkPrimitive(n);
342 }
343
344 @Override
345 public void visit(Way w) {
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) {
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;
359 }
360 return false;
361 }
362
363 /**
364 * Checks the primitive properties
365 * @param p The primitive to check
366 */
367 private void checkPrimitive(OsmPrimitive p) {
368 // Just a collection to know if a primitive has been already marked with error
369 MultiMap<OsmPrimitive, String> withErrors = new MultiMap<OsmPrimitive, String>();
370
371 if (checkComplex) {
372 Map<String, String> props = (p.getKeys() == null) ? Collections.<String, String>emptyMap() : p.getKeys();
373 for (Entry<String, String> prop : props.entrySet()) {
374 boolean ignore = true;
375 String key1 = prop.getKey();
376 String value1 = prop.getValue();
377
378 for (IgnoreTwoKeyPair a : ignoreDataTwoKeyPair) {
379 if (key1.equals(a.key1) && value1.equals(a.value1)) {
380 ignore = false;
381 for (Entry<String, String> prop2 : props.entrySet()) {
382 String key2 = prop2.getKey();
383 String value2 = prop2.getValue();
384 for (IgnoreTwoKeyPair b : ignoreDataTwoKeyPair) {
385 if (key2.equals(b.key2) && value2.equals(b.value2)) {
386 ignore = true;
387 break;
388 }
389 }
390 if (ignore) {
391 break;
392 }
393 }
394 }
395 if (ignore) {
396 break;
397 }
398 }
399
400 if (!ignore) {
401 errors.add( new TestError(this, Severity.OTHER, tr("Suspicious tag/value combinations"),
402 tr("Suspicious tag/value combinations"), tr("Suspicious tag/value combinations"), 1272, p) );
403 withErrors.put(p, "TC");
404 }
405 }
406
407 Map<String, String> keys = p.getKeys();
408 for (CheckerData d : checkerData) {
409 if (d.match(p, keys)) {
410 errors.add( new TestError(this, d.getSeverity(), tr("Suspicious tag/value combinations"),
411 d.getDescription(), d.getDescriptionOrig(), d.getCode(), p) );
412 withErrors.put(p, "TC");
413 }
414 }
415 }
416
417 Map<String, String> props = (p.getKeys() == null) ? Collections.<String, String>emptyMap() : p.getKeys();
418 for (Entry<String, String> prop : props.entrySet()) {
419 String s = marktr("Key ''{0}'' invalid.");
420 String key = prop.getKey();
421 String value = prop.getValue();
422 if (checkValues && (containsLow(value)) && !withErrors.contains(p, "ICV")) {
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) );
425 withErrors.put(p, "ICV");
426 }
427 if (checkKeys && (containsLow(key)) && !withErrors.contains(p, "ICK")) {
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) );
430 withErrors.put(p, "ICK");
431 }
432 if (checkValues && (value!=null && value.length() > 255) && !withErrors.contains(p, "LV")) {
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) );
435 withErrors.put(p, "LV");
436 }
437 if (checkKeys && (key!=null && key.length() > 255) && !withErrors.contains(p, "LK")) {
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) );
440 withErrors.put(p, "LK");
441 }
442 if (checkValues && (value==null || value.trim().length() == 0) && !withErrors.contains(p, "EV")) {
443 errors.add( new TestError(this, Severity.WARNING, tr("Tags with empty values"),
444 tr(s, key), MessageFormat.format(s, key), EMPTY_VALUES, p) );
445 withErrors.put(p, "EV");
446 }
447 if (checkKeys && spellCheckKeyData.containsKey(key) && !withErrors.contains(p, "IPK")) {
448 errors.add( new TestError(this, Severity.WARNING, tr("Invalid property key"),
449 tr(s, key), MessageFormat.format(s, key), INVALID_KEY, p) );
450 withErrors.put(p, "IPK");
451 }
452 if (checkKeys && key.indexOf(" ") >= 0 && !withErrors.contains(p, "IPK")) {
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) );
455 withErrors.put(p, "IPK");
456 }
457 if (checkValues && value != null && (value.startsWith(" ") || value.endsWith(" ")) && !withErrors.contains(p, "SPACE")) {
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) );
460 withErrors.put(p, "SPACE");
461 }
462 if (checkValues && value != null && !value.equals(entities.unescape(value)) && !withErrors.contains(p, "HTML")) {
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) );
465 withErrors.put(p, "HTML");
466 }
467 if (checkValues && value != null && value.length() > 0 && presetsValueData != null) {
468 Set<String> values = presetsValueData.get(key);
469 if (values == null) {
470 boolean ignore = false;
471 for (String a : ignoreDataStartsWith) {
472 if (key.startsWith(a)) {
473 ignore = true;
474 }
475 }
476 for (String a : ignoreDataEquals) {
477 if(key.equals(a)) {
478 ignore = true;
479 }
480 }
481 for (String a : ignoreDataEndsWith) {
482 if(key.endsWith(a)) {
483 ignore = true;
484 }
485 }
486 if (!ignore) {
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) );
490 withErrors.put(p, "UPK");
491 }
492 } else if (values.size() > 0 && !values.contains(prop.getValue())) {
493 boolean ignore = false;
494 for (IgnoreKeyPair a : ignoreDataKeyPair) {
495 if (key.equals(a.key) && value.equals(a.value)) {
496 ignore = true;
497 }
498 }
499
500 for (IgnoreTwoKeyPair a : ignoreDataTwoKeyPair) {
501 if (key.equals(a.key2) && value.equals(a.value2)) {
502 ignore = true;
503 }
504 }
505
506 if (!ignore) {
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) );
510 withErrors.put(p, "UPV");
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));
521 withErrors.put(p, "FIXME");
522 }
523 }
524 }
525 }
526
527 @Override
528 public void startTest(ProgressMonitor monitor) {
529 super.startTest(monitor);
530 checkKeys = Main.pref.getBoolean(PREF_CHECK_KEYS, true);
531 if (isBeforeUpload) {
532 checkKeys = checkKeys && Main.pref.getBoolean(PREF_CHECK_KEYS_BEFORE_UPLOAD, true);
533 }
534
535 checkValues = Main.pref.getBoolean(PREF_CHECK_VALUES, true);
536 if (isBeforeUpload) {
537 checkValues = checkValues && Main.pref.getBoolean(PREF_CHECK_VALUES_BEFORE_UPLOAD, true);
538 }
539
540 checkComplex = Main.pref.getBoolean(PREF_CHECK_COMPLEX, true);
541 if (isBeforeUpload) {
542 checkComplex = checkValues && Main.pref.getBoolean(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, true);
543 }
544
545 checkFixmes = Main.pref.getBoolean(PREF_CHECK_FIXMES, true);
546 if (isBeforeUpload) {
547 checkFixmes = checkFixmes && Main.pref.getBoolean(PREF_CHECK_FIXMES_BEFORE_UPLOAD, true);
548 }
549 }
550
551 @Override
552 public void visit(Collection<OsmPrimitive> selection) {
553 if (checkKeys || checkValues || checkComplex || checkFixmes) {
554 super.visit(selection);
555 }
556 }
557
558 @Override
559 public void addGui(JPanel testPanel) {
560 GBC a = GBC.eol();
561 a.anchor = GridBagConstraints.EAST;
562
563 testPanel.add(new JLabel(name), GBC.eol().insets(3,0,0,0));
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
581 sourcesList = new JList(new DefaultListModel());
582
583 String sources = Main.pref.get( PREF_SOURCES );
584 if (sources != null && sources.length() > 0) {
585 for (String source : sources.split(";")) {
586 ((DefaultListModel)sourcesList.getModel()).addElement(source);
587 }
588 }
589
590 addSrcButton = new JButton(tr("Add"));
591 addSrcButton.addActionListener(new ActionListener() {
592 @Override
593 public void actionPerformed(ActionEvent e) {
594 String source = JOptionPane.showInputDialog(
595 Main.parent,
596 tr("TagChecker source"),
597 tr("TagChecker source"),
598 JOptionPane.QUESTION_MESSAGE);
599 if (source != null) {
600 ((DefaultListModel)sourcesList.getModel()).addElement(source);
601 }
602 sourcesList.clearSelection();
603 }
604 });
605
606 editSrcButton = new JButton(tr("Edit"));
607 editSrcButton.addActionListener(new ActionListener() {
608 @Override
609 public void actionPerformed(ActionEvent e) {
610 int row = sourcesList.getSelectedIndex();
611 if (row == -1 && sourcesList.getModel().getSize() == 1) {
612 sourcesList.setSelectedIndex(0);
613 row = 0;
614 }
615 if (row == -1) {
616 if (sourcesList.getModel().getSize() == 0) {
617 String source = JOptionPane.showInputDialog(Main.parent, tr("TagChecker source"), tr("TagChecker source"), JOptionPane.QUESTION_MESSAGE);
618 if (source != null) {
619 ((DefaultListModel)sourcesList.getModel()).addElement(source);
620 }
621 } else {
622 JOptionPane.showMessageDialog(
623 Main.parent,
624 tr("Please select the row to edit."),
625 tr("Information"),
626 JOptionPane.INFORMATION_MESSAGE
627 );
628 }
629 } else {
630 String source = (String)JOptionPane.showInputDialog(Main.parent,
631 tr("TagChecker source"),
632 tr("TagChecker source"),
633 JOptionPane.QUESTION_MESSAGE, null, null,
634 sourcesList.getSelectedValue());
635 if (source != null) {
636 ((DefaultListModel)sourcesList.getModel()).setElementAt(source, row);
637 }
638 }
639 sourcesList.clearSelection();
640 }
641 });
642
643 deleteSrcButton = new JButton(tr("Delete"));
644 deleteSrcButton.addActionListener(new ActionListener() {
645 @Override
646 public void actionPerformed(ActionEvent e) {
647 if (sourcesList.getSelectedIndex() == -1) {
648 JOptionPane.showMessageDialog(Main.parent, tr("Please select the row to delete."), tr("Information"), JOptionPane.QUESTION_MESSAGE);
649 } else {
650 ((DefaultListModel)sourcesList.getModel()).remove(sourcesList.getSelectedIndex());
651 }
652 }
653 });
654 sourcesList.setMinimumSize(new Dimension(300,50));
655 sourcesList.setVisibleRowCount(3);
656
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."));
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));
663 testPanel.add(new JScrollPane(sourcesList), GBC.eol().insets(23,0,0,0).fill(GridBagConstraints.HORIZONTAL));
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
670 ActionListener disableCheckActionListener = new ActionListener() {
671 @Override
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
712 public void handlePrefEnable() {
713 boolean selected = prefCheckKeys.isSelected() || prefCheckKeysBeforeUpload.isSelected()
714 || prefCheckComplex.isSelected() || prefCheckComplexBeforeUpload.isSelected();
715 sourcesList.setEnabled( selected );
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()
726 || prefCheckFixmesBeforeUpload.isSelected() || prefCheckComplexBeforeUpload.isSelected();
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 = "";
740 if (sourcesList.getModel().getSize() > 0) {
741 String sb = "";
742 for (int i = 0; i < sourcesList.getModel().getSize(); ++i) {
743 sb += ";"+sourcesList.getModel().getElementAt(i);
744 }
745 sources = sb.substring(1);
746 }
747 if (sources.length() == 0) {
748 sources = null;
749 }
750 return Main.pref.put(PREF_SOURCES, sources);
751 }
752
753 @Override
754 public Command fixError(TestError testError) {
755
756 List<Command> commands = new ArrayList<Command>(50);
757
758 Collection<? extends OsmPrimitive> primitives = testError.getPrimitives();
759 for (OsmPrimitive p : primitives) {
760 Map<String, String> tags = p.getKeys();
761 if (tags == null || tags.isEmpty()) {
762 continue;
763 }
764
765 for (Entry<String, String> prop: tags.entrySet()) {
766 String key = prop.getKey();
767 String value = prop.getValue();
768 if (value == null || value.trim().length() == 0) {
769 commands.add(new ChangePropertyCommand(Collections.singleton(p), key, null));
770 } else if (value.startsWith(" ") || value.endsWith(" ")) {
771 commands.add(new ChangePropertyCommand(Collections.singleton(p), key, value.trim()));
772 } else if (key.startsWith(" ") || key.endsWith(" ")) {
773 commands.add(new ChangePropertyKeyCommand(Collections.singleton(p), key, key.trim()));
774 } else {
775 String evalue = entities.unescape(value);
776 if (!evalue.equals(value)) {
777 commands.add(new ChangePropertyCommand(Collections.singleton(p), key, evalue));
778 } else {
779 String replacementKey = spellCheckKeyData.get(key);
780 if (replacementKey != null) {
781 commands.add(new ChangePropertyKeyCommand(Collections.singleton(p),
782 key, replacementKey));
783 }
784 }
785 }
786 }
787 }
788
789 if (commands.isEmpty())
790 return null;
791 if (commands.size() == 1)
792 return commands.get(0);
793
794 return new SequenceCommand(tr("Fix properties"), commands);
795 }
796
797 @Override
798 public boolean isFixable(TestError testError) {
799
800 if (testError.getTester() instanceof TagChecker) {
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;
837
838 private Pattern getPattern(String str) throws IllegalStateException, PatternSyntaxException {
839 if (str.endsWith("/i"))
840 return Pattern.compile(str.substring(1,str.length()-2), Pattern.CASE_INSENSITIVE);
841 if (str.endsWith("/"))
842 return Pattern.compile(str.substring(1,str.length()-1));
843
844 throw new IllegalStateException();
845 }
846 public CheckerElement(String exp) throws IllegalStateException, PatternSyntaxException {
847 Matcher m = Pattern.compile("(.+)([!=]=)(.+)").matcher(exp);
848 m.matches();
849
850 String n = m.group(1).trim();
851
852 if(n.equals("*")) {
853 tagAll = true;
854 } else {
855 tag = n.startsWith("/") ? getPattern(n) : n;
856 noMatch = m.group(2).equals("!=");
857 n = m.group(3).trim();
858 if (n.equals("*")) {
859 valueAll = true;
860 } else if (n.equals("BOOLEAN_TRUE")) {
861 valueBool = true;
862 value = OsmUtils.trueval;
863 } else if (n.equals("BOOLEAN_FALSE")) {
864 valueBool = true;
865 value = OsmUtils.falseval;
866 } else {
867 value = n.startsWith("/") ? getPattern(n) : n;
868 }
869 }
870 }
871
872 public boolean match(OsmPrimitive osm, Map<String, String> keys) {
873 for (Entry<String, String> prop: keys.entrySet()) {
874 String key = prop.getKey();
875 String val = valueBool ? OsmUtils.getNamedOsmBoolean(prop.getValue()) : prop.getValue();
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))))
878 return !noMatch;
879 }
880 return noMatch;
881 }
882 };
883
884 public String getData(String str) {
885 Matcher m = Pattern.compile(" *# *([^#]+) *$").matcher(str);
886 str = m.replaceFirst("").trim();
887 try {
888 description = m.group(1);
889 if (description != null && description.length() == 0) {
890 description = null;
891 }
892 } catch (IllegalStateException e) {
893 description = null;
894 }
895 String[] n = str.split(" *: *", 3);
896 if (n[0].equals("way")) {
897 type = OsmPrimitiveType.WAY;
898 } else if (n[0].equals("node")) {
899 type = OsmPrimitiveType.NODE;
900 } else if (n[0].equals("relation")) {
901 type = OsmPrimitiveType.RELATION;
902 } else if (n[0].equals("*")) {
903 type = null;
904 } else
905 return tr("Could not find element type");
906 if (n.length != 3)
907 return tr("Incorrect number of parameters");
908
909 if (n[1].equals("W")) {
910 severity = Severity.WARNING;
911 code = TAG_CHECK_WARN;
912 } else if (n[1].equals("E")) {
913 severity = Severity.ERROR;
914 code = TAG_CHECK_ERROR;
915 } else if(n[1].equals("I")) {
916 severity = Severity.OTHER;
917 code = TAG_CHECK_INFO;
918 } else
919 return tr("Could not find warning level");
920 for (String exp: n[2].split(" *&& *")) {
921 try {
922 data.add(new CheckerElement(exp));
923 } catch (IllegalStateException e) {
924 return tr("Illegal expression ''{0}''", exp);
925 }
926 catch (PatternSyntaxException e) {
927 return tr("Illegal regular expression ''{0}''", exp);
928 }
929 }
930 return null;
931 }
932
933 public boolean match(OsmPrimitive osm, Map<String, String> keys) {
934 if (type != null && OsmPrimitiveType.from(osm) != type)
935 return false;
936
937 for (CheckerElement ce : data) {
938 if (!ce.match(osm, keys))
939 return false;
940 }
941 return true;
942 }
943
944 public String getDescription() {
945 return tr(description);
946 }
947
948 public String getDescriptionOrig() {
949 return description;
950 }
951
952 public Severity getSeverity() {
953 return severity;
954 }
955
956 public int getCode() {
957 if (type == null)
958 return code;
959
960 return code + type.ordinal() + 1;
961 }
962 }
963}
Note: See TracBrowser for help on using the repository browser.