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

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

Rework console output:

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