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

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

refactor of some GUI/widgets classes (impacts some plugins):

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