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

Last change on this file since 6494 was 6494, checked in by Don-vip, 10 years ago

fix #9430, fix #9445 - fix validator tests lifecycle

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