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

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

fix #8434 - Full support of man_made=monitoring_station instead of man_made=measurement_station:

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