source: josm/trunk/src/org/openstreetmap/josm/tools/TextTagParser.java@ 8723

Last change on this file since 8723 was 8513, checked in by Don-vip, 9 years ago

checkstyle: blocks

  • Property svn:eol-style set to native
File size: 11.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.GridBagLayout;
8import java.util.Arrays;
9import java.util.HashMap;
10import java.util.Map;
11import java.util.Map.Entry;
12import java.util.regex.Matcher;
13import java.util.regex.Pattern;
14
15import javax.swing.JLabel;
16import javax.swing.JOptionPane;
17import javax.swing.JPanel;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.gui.ExtendedDialog;
21import org.openstreetmap.josm.gui.help.HelpUtil;
22import org.openstreetmap.josm.gui.widgets.UrlLabel;
23import org.openstreetmap.josm.io.XmlWriter;
24import org.openstreetmap.josm.tools.LanguageInfo.LocaleType;
25
26/**
27 * Class that helps to parse tags from arbitrary text
28 */
29public final class TextTagParser {
30
31 // properties need JOSM restart to apply, modified rarely enough
32 private static final int MAX_KEY_LENGTH = Main.pref.getInteger("tags.paste.max-key-length", 50);
33 private static final int MAX_KEY_COUNT = Main.pref.getInteger("tags.paste.max-key-count", 30);
34 private static final String KEY_PATTERN = Main.pref.get("tags.paste.tag-pattern", "[0-9a-zA-Z:_]*");
35 private static final int MAX_VALUE_LENGTH = 255;
36
37 private TextTagParser() {
38 // Hide default constructor for utils classes
39 }
40
41 public static class TextAnalyzer {
42 private boolean quotesStarted = false;
43 private boolean esc = false;
44 private StringBuilder s = new StringBuilder(200);
45 private int pos;
46 private String data;
47 private int n;
48
49 public TextAnalyzer(String text) {
50 pos = 0;
51 data = text;
52 n = data.length();
53 }
54
55 /**
56 * Read tags from "Free format"
57 */
58 Map<String, String> getFreeParsedTags() {
59 String k, v;
60 Map<String, String> tags = new HashMap<>();
61
62 while (true) {
63 skipEmpty();
64 if (pos == n) {
65 break;
66 }
67 k = parseString("\n\r\t= ");
68 if (pos == n) {
69 tags.clear();
70 break;
71 }
72 skipSign();
73 if (pos == n) {
74 tags.clear();
75 break;
76 }
77 v = parseString("\n\r\t ");
78 tags.put(k, v);
79 }
80 return tags;
81 }
82
83 private String parseString(String stopChars) {
84 char[] stop = stopChars.toCharArray();
85 Arrays.sort(stop);
86 char c;
87 while (pos < n) {
88 c = data.charAt(pos);
89 if (esc) {
90 esc = false;
91 s.append(c); // \" \\
92 } else if (c == '\\') {
93 esc = true;
94 } else if (c == '\"' && !quotesStarted) { // opening "
95 if (!s.toString().trim().isEmpty()) { // we had ||some text"||
96 s.append(c); // just add ", not open
97 } else {
98 s.delete(0, s.length()); // forget that empty characthers and start reading "....
99 quotesStarted = true;
100 }
101 } else if (c == '\"' && quotesStarted) { // closing "
102 quotesStarted = false;
103 pos++;
104 break;
105 } else if (!quotesStarted && (Arrays.binarySearch(stop, c) >= 0)) {
106 // stop-symbol found
107 pos++;
108 break;
109 } else {
110 // skip non-printable characters
111 if (c >= 32) s.append(c);
112 }
113 pos++;
114 }
115
116 String res = s.toString();
117 s.delete(0, s.length());
118 return res.trim();
119 }
120
121 private void skipSign() {
122 char c;
123 boolean signFound = false;
124 while (pos < n) {
125 c = data.charAt(pos);
126 if (c == '\t' || c == '\n' || c == ' ') {
127 pos++;
128 } else if (c == '=') {
129 if (signFound) break; // a = =qwerty means "a"="=qwerty"
130 signFound = true;
131 pos++;
132 } else {
133 break;
134 }
135 }
136 }
137
138 private void skipEmpty() {
139 char c;
140 while (pos < n) {
141 c = data.charAt(pos);
142 if (c == '\t' || c == '\n' || c == '\r' || c == ' ') {
143 pos++;
144 } else {
145 break;
146 }
147 }
148 }
149 }
150
151 protected static String unescape(String k) {
152 if (!(k.startsWith("\"") && k.endsWith("\""))) {
153 if (k.contains("=")) {
154 // '=' not in quotes will be treated as an error!
155 return null;
156 } else {
157 return k;
158 }
159 }
160 String text = k.substring(1, k.length()-1);
161 return (new TextAnalyzer(text)).parseString("\r\t\n");
162 }
163
164 /**
165 * Try to find tag-value pairs in given text
166 * @param text - text in which tags are looked for
167 * @param splitRegex - text is splitted into parts with this delimiter
168 * @param tagRegex - each part is matched against this regex
169 * @param unescapeTextInQuotes - if true, matched tag and value will be analyzed more thoroughly
170 */
171 public static Map<String, String> readTagsByRegexp(String text, String splitRegex, String tagRegex, boolean unescapeTextInQuotes) {
172 String[] lines = text.split(splitRegex);
173 Pattern p = Pattern.compile(tagRegex);
174 Map<String, String> tags = new HashMap<>();
175 String k = null, v = null;
176 for (String line: lines) {
177 if (line.trim().isEmpty()) continue; // skip empty lines
178 Matcher m = p.matcher(line);
179 if (m.matches()) {
180 k = m.group(1).trim();
181 v = m.group(2).trim();
182 if (unescapeTextInQuotes) {
183 k = unescape(k);
184 v = unescape(v);
185 if (k == null || v == null) return null;
186 }
187 tags.put(k, v);
188 } else {
189 return null;
190 }
191 }
192 if (!tags.isEmpty()) {
193 return tags;
194 } else {
195 return null;
196 }
197 }
198
199 public static Map<String, String> getValidatedTagsFromText(String buf) {
200 Map<String, String> tags = readTagsFromText(buf);
201 return validateTags(tags) ? tags : null;
202 }
203
204 /**
205 * Apply different methods to extract tag-value pairs from arbitrary text
206 * @param buf buffer
207 * @return null if no format is suitable
208 */
209 public static Map<String, String> readTagsFromText(String buf) {
210 Map<String, String> tags;
211
212 // Format
213 // tag1\tval1\ntag2\tval2\n
214 tags = readTagsByRegexp(buf, "[\\r\\n]+", ".*?([a-zA-Z0-9:_]+).*\\t(.*?)", false);
215 // try "tag\tvalue\n" format
216 if (tags != null) return tags;
217
218 // Format
219 // a=b \n c=d \n "a b"=hello
220 // SORRY: "a=b" = c is not supported fror now, only first = will be considered
221 // a = "b=c" is OK
222 // a = b=c - this method of parsing fails intentionally
223 tags = readTagsByRegexp(buf, "[\\n\\t\\r]+", "(.*?)=(.*?)", true);
224 // try format t1=v1\n t2=v2\n ...
225 if (tags != null) return tags;
226
227 // JSON-format
228 String bufJson = buf.trim();
229 // trim { }, if there are any
230 if (bufJson.startsWith("{") && bufJson.endsWith("}"))
231 bufJson = bufJson.substring(1, bufJson.length()-1);
232 tags = readTagsByRegexp(bufJson, "[\\s]*,[\\s]*",
233 "[\\s]*(\\\".*?[^\\\\]\\\")"+"[\\s]*:[\\s]*"+"(\\\".*?[^\\\\]\\\")[\\s]*", true);
234 if (tags != null) return tags;
235
236 // Free format
237 // a 1 "b" 2 c=3 d 4 e "5"
238 return new TextAnalyzer(buf).getFreeParsedTags();
239 }
240
241 /**
242 * Check tags for correctness and display warnings if needed
243 * @param tags - map key-&gt;value to check
244 * @return true if the tags should be pasted
245 */
246 public static boolean validateTags(Map<String, String> tags) {
247 int r;
248 int s = tags.size();
249 if (s > MAX_KEY_COUNT) {
250 // Use trn() even if for english it makes no sense, as s > 30
251 r = warning(trn("There was {0} tag found in the buffer, it is suspicious!",
252 "There were {0} tags found in the buffer, it is suspicious!", s,
253 s), "", "tags.paste.toomanytags");
254 if (r == 2 || r == 3) return false; if (r == 4) return true;
255 }
256 for (Entry<String, String> entry : tags.entrySet()) {
257 String key = entry.getKey();
258 String value = entry.getValue();
259 if (key.length() > MAX_KEY_LENGTH) {
260 r = warning(tr("Key is too long (max {0} characters):", MAX_KEY_LENGTH), key+"="+value, "tags.paste.keytoolong");
261 if (r == 2 || r == 3) return false; if (r == 4) return true;
262 }
263 if (!key.matches(KEY_PATTERN)) {
264 r = warning(tr("Suspicious characters in key:"), key, "tags.paste.keydoesnotmatch");
265 if (r == 2 || r == 3) return false; if (r == 4) return true;
266 }
267 if (value.length() > MAX_VALUE_LENGTH) {
268 r = warning(tr("Value is too long (max {0} characters):", MAX_VALUE_LENGTH), value, "tags.paste.valuetoolong");
269 if (r == 2 || r == 3) return false; if (r == 4) return true;
270 }
271 }
272 return true;
273 }
274
275 private static int warning(String text, String data, String code) {
276 ExtendedDialog ed = new ExtendedDialog(
277 Main.parent,
278 tr("Do you want to paste these tags?"),
279 new String[]{tr("Ok"), tr("Cancel"), tr("Clear buffer"), tr("Ignore warnings")});
280 ed.setButtonIcons(new String[]{"ok", "cancel", "dialogs/delete", "pastetags"});
281 ed.setContent("<html><b>"+text + "</b><br/><br/><div width=\"300px\">"+XmlWriter.encode(data, true)+"</html>");
282 ed.setDefaultButton(2);
283 ed.setCancelButton(2);
284 ed.setIcon(JOptionPane.WARNING_MESSAGE);
285 ed.toggleEnable(code);
286 ed.showDialog();
287 int r = ed.getValue();
288 if (r == 0) r = 2;
289 // clean clipboard if user asked
290 if (r == 3) Utils.copyToClipboard("");
291 return r;
292 }
293
294 /**
295 * Shows message that the buffer can not be pasted, allowing user to clean the buffer
296 * @param helpTopic the help topic of the parent action
297 * TODO: Replace by proper HelpAwareOptionPane instead of self-made help link
298 */
299 public static void showBadBufferMessage(String helpTopic) {
300 String msg = tr("<html><p> Sorry, it is impossible to paste tags from buffer. It does not contain any JOSM object"
301 + " or suitable text. </p></html>");
302 JPanel p = new JPanel(new GridBagLayout());
303 p.add(new JLabel(msg), GBC.eop());
304 String helpUrl = HelpUtil.getHelpTopicUrl(HelpUtil.buildAbsoluteHelpTopic(helpTopic, LocaleType.DEFAULT));
305 if (helpUrl != null) {
306 p.add(new UrlLabel(helpUrl), GBC.eop());
307 }
308
309 ExtendedDialog ed = new ExtendedDialog(
310 Main.parent,
311 tr("Warning"),
312 new String[]{tr("Ok"), tr("Clear buffer")});
313
314 ed.setButtonIcons(new String[]{"ok", "dialogs/delete"});
315
316 ed.setContent(p);
317 ed.setDefaultButton(1);
318 ed.setCancelButton(1);
319 ed.setIcon(JOptionPane.WARNING_MESSAGE);
320 ed.toggleEnable("tags.paste.cleanbadbuffer");
321 ed.showDialog();
322
323 int r = ed.getValue();
324 // clean clipboard if user asked
325 if (r == 2) Utils.copyToClipboard("");
326 }
327}
Note: See TracBrowser for help on using the repository browser.