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

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

fix #7386 - Major rework of preferences GUI settings in order to speed up preferences dialog startup time. The building of each preferences tab is delayed until this tab is selected. Plugins that use preferences will need to make some (minor) changes.

  • Property svn:eol-style set to native
File size: 42.3 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.ChangePropertyKeyCommand;
43import org.openstreetmap.josm.command.Command;
44import org.openstreetmap.josm.command.SequenceCommand;
45import org.openstreetmap.josm.data.osm.Node;
46import org.openstreetmap.josm.data.osm.OsmPrimitive;
47import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
48import org.openstreetmap.josm.data.osm.OsmUtils;
49import org.openstreetmap.josm.data.osm.Relation;
50import org.openstreetmap.josm.data.osm.Way;
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.map.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 final List<CheckerData> checkerData = new ArrayList<CheckerData>();
81 protected static final List<String> ignoreDataStartsWith = new ArrayList<String>();
82 protected static final List<String> ignoreDataEquals = new ArrayList<String>();
83 protected static final List<String> ignoreDataEndsWith = new ArrayList<String>();
84 protected static final List<IgnoreKeyPair> ignoreDataKeyPair = new ArrayList<IgnoreKeyPair>();
85 protected static final 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 final int EMPTY_VALUES = 1200;
131 protected static final int INVALID_KEY = 1201;
132 protected static final int INVALID_VALUE = 1202;
133 protected static final int FIXME = 1203;
134 protected static final int INVALID_SPACE = 1204;
135 protected static final int INVALID_KEY_SPACE = 1205;
136 protected static final int INVALID_HTML = 1206; /* 1207 was PAINT */
137 protected static final int LONG_VALUE = 1208;
138 protected static final int LONG_KEY = 1209;
139 protected static final int LOW_CHAR_VALUE = 1210;
140 protected static final 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 final 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);
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 final int TAG_CHECK_ERROR = 1250;
827 protected static final int TAG_CHECK_WARN = 1260;
828 protected static final 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.