source: josm/trunk/src/org/openstreetmap/josm/io/imagery/ImageryReader.java@ 4313

Last change on this file since 4313 was 4313, checked in by stoecker, 13 years ago

typo

File size: 14.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io.imagery;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.Utils.equal;
6
7import java.io.BufferedReader;
8import java.io.IOException;
9import java.io.InputStream;
10import java.io.InputStreamReader;
11import java.io.UnsupportedEncodingException;
12import java.util.ArrayList;
13import java.util.Arrays;
14import java.util.List;
15import java.util.Stack;
16
17import javax.xml.parsers.ParserConfigurationException;
18import javax.xml.parsers.SAXParserFactory;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.data.Bounds;
22import org.openstreetmap.josm.data.imagery.ImageryInfo;
23import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
24import org.openstreetmap.josm.io.MirroredInputStream;
25import org.openstreetmap.josm.io.UTFInputStreamReader;
26import org.openstreetmap.josm.tools.Utils;
27import org.xml.sax.Attributes;
28import org.xml.sax.InputSource;
29import org.xml.sax.SAXException;
30import org.xml.sax.helpers.DefaultHandler;
31
32public class ImageryReader {
33
34 private String source;
35
36 private enum State {
37 INIT, // initial state, should always be at the bottom of the stack
38 IMAGERY, // inside the imagery element
39 ENTRY, // inside an entry
40 ENTRY_ATTRIBUTE, // note we are inside an entry attribute to collect the character data
41 SUPPORTED_PROJECTIONS,
42 PR,
43 UNKNOWN, // element is not recognized in the current context
44 }
45
46 public ImageryReader(String source) throws IOException {
47 this.source = source;
48 }
49
50 public List<ImageryInfo> parse() throws SAXException, IOException {
51 if (isXml(source)) {
52 Parser parser = new Parser();
53 try {
54 SAXParserFactory factory = SAXParserFactory.newInstance();
55 factory.setNamespaceAware(true);
56 InputStream in = new MirroredInputStream(source);
57 InputSource is = new InputSource(UTFInputStreamReader.create(in, "UTF-8"));
58 factory.newSAXParser().parse(is, parser);
59 return parser.entries;
60 } catch (SAXException e) {
61 throw e;
62 } catch (ParserConfigurationException e) {
63 e.printStackTrace(); // broken SAXException chaining
64 throw new SAXException(e);
65 }
66 } else {
67 return readCSV(source);
68 }
69 }
70
71 /**
72 * Probe the file to see if it is xml or the traditional csv format.
73 *
74 * If the first non-whitespace character is a '<', decide for
75 * xml, otherwise csv.
76 */
77 private boolean isXml(String source) {
78 MirroredInputStream in = null;
79 try {
80 in = new MirroredInputStream(source);
81 InputStreamReader reader = UTFInputStreamReader.create(in, null);
82 WHILE: while (true) {
83 int c = reader.read();
84 switch (c) {
85 case -1:
86 break WHILE;
87 case ' ':
88 case '\t':
89 case '\n':
90 case '\r':
91 continue;
92 case '<':
93 return true;
94 default:
95 return false;
96 }
97 }
98 } catch (IOException ex) {
99 ex.printStackTrace();
100 } finally {
101 Utils.close(in);
102 }
103 Main.warn(tr("Warning: Could not detect type of imagery source ''{0}''. Using default (xml).", source));
104 return true;
105 }
106
107 private List<ImageryInfo> readCSV(String source) {
108 List<ImageryInfo> entries = new ArrayList<ImageryInfo>();
109 MirroredInputStream s = null;
110 try {
111 s = new MirroredInputStream(source);
112 try {
113 InputStreamReader r;
114 try
115 {
116 r = new InputStreamReader(s, "UTF-8");
117 }
118 catch (UnsupportedEncodingException e)
119 {
120 r = new InputStreamReader(s);
121 }
122 BufferedReader reader = new BufferedReader(r);
123 String line;
124 while((line = reader.readLine()) != null)
125 {
126 String val[] = line.split(";");
127 if(!line.startsWith("#") && val.length >= 3) {
128 boolean defaultEntry = "true".equals(val[0]);
129 String name = tr(val[1]);
130 String url = val[2];
131 String eulaAcceptanceRequired = null;
132
133 if (val.length >= 4 && !val[3].isEmpty()) {
134 // 4th parameter optional for license agreement (EULA)
135 eulaAcceptanceRequired = val[3];
136 }
137
138 ImageryInfo info = new ImageryInfo(name, url, eulaAcceptanceRequired);
139
140 info.setDefaultEntry(defaultEntry);
141
142 if (val.length >= 5 && !val[4].isEmpty()) {
143 // 5th parameter optional for bounds
144 try {
145 info.setBounds(new Bounds(val[4], ","));
146 } catch (IllegalArgumentException e) {
147 Main.warn(e.toString());
148 }
149 }
150 if (val.length >= 6 && !val[5].isEmpty()) {
151 info.setAttributionText(val[5]);
152 }
153 if (val.length >= 7 && !val[6].isEmpty()) {
154 info.setAttributionLinkURL(val[6]);
155 }
156 if (val.length >= 8 && !val[7].isEmpty()) {
157 info.setTermsOfUseURL(val[7]);
158 }
159 if (val.length >= 9 && !val[8].isEmpty()) {
160 info.setAttributionImage(val[8]);
161 }
162
163 entries.add(info);
164 }
165 }
166 } finally {
167 Utils.close(s);
168 }
169 return entries;
170 } catch (IOException ex) {
171 ex.printStackTrace();
172 } finally {
173 Utils.close(s);
174 }
175 return entries;
176 }
177
178 private class Parser extends DefaultHandler {
179 private StringBuffer accumulator = new StringBuffer();
180
181 private Stack<State> states;
182
183 List<ImageryInfo> entries;
184
185 /**
186 * Skip the current entry because it has mandatory attributes
187 * that this version of JOSM cannot process.
188 */
189 boolean skipEntry;
190
191 ImageryInfo entry;
192 Bounds bounds;
193 List<String> supported_srs;
194
195 @Override public void startDocument() {
196 accumulator = new StringBuffer();
197 skipEntry = false;
198 states = new Stack<State>();
199 states.push(State.INIT);
200 entries = new ArrayList<ImageryInfo>();
201 entry = null;
202 bounds = null;
203 supported_srs = null;
204 }
205
206 @Override
207 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
208 accumulator.setLength(0);
209 State newState = null;
210 switch (states.peek()) {
211 case INIT:
212 if (qName.equals("imagery")) {
213 newState = State.IMAGERY;
214 }
215 break;
216 case IMAGERY:
217 if (qName.equals("entry")) {
218 entry = new ImageryInfo();
219 skipEntry = false;
220 newState = State.ENTRY;
221 }
222 break;
223 case ENTRY:
224 if (Arrays.asList(new String[] {
225 "name",
226 "type",
227 "default",
228 "url",
229 "eula",
230 "min-zoom",
231 "max-zoom",
232 "attribution-text",
233 "attribution-url",
234 "logo-image",
235 "logo-url",
236 "terms-of-use-text",
237 "terms-of-use-url",
238 }).contains(qName)) {
239 newState = State.ENTRY_ATTRIBUTE;
240 } else if (qName.equals("bounds")) {
241 try {
242 bounds = new Bounds(
243 atts.getValue("min-lat") + "," +
244 atts.getValue("min-lon") + "," +
245 atts.getValue("max-lat") + "," +
246 atts.getValue("max-lon"), ",");
247 } catch (IllegalArgumentException e) {
248 break;
249 }
250 newState = State.ENTRY_ATTRIBUTE;
251 } else if (qName.equals("supported-projections")) {
252 supported_srs = new ArrayList<String>();
253 newState = State.SUPPORTED_PROJECTIONS;
254 }
255 break;
256 case SUPPORTED_PROJECTIONS:
257 if (qName.equals("pr")) {
258 newState = State.PR;
259 }
260 break;
261 }
262 /**
263 * Did not recognize the element, so the new state is UNKNOWN.
264 * This includes the case where we are already inside an unknown
265 * element, i.e. we do not try to understand the inner content
266 * of an unknown element, but wait till it's over.
267 */
268 if (newState == null) {
269 newState = State.UNKNOWN;
270 }
271 states.push(newState);
272 if (newState == State.UNKNOWN && equal(atts.getValue("mandatory"), "true")) {
273 skipEntry = true;
274 }
275 return;
276 }
277
278 @Override
279 public void characters(char[] ch, int start, int length) {
280 accumulator.append(ch, start, length);
281 }
282
283 @Override
284 public void endElement(String namespaceURI, String qName, String rqName) {
285 switch (states.pop()) {
286 case INIT:
287 throw new RuntimeException("parsing error: more closing than opening elements");
288 case ENTRY:
289 if (qName.equals("entry")) {
290 if (!skipEntry) {
291 entries.add(entry);
292 }
293 entry = null;
294 }
295 break;
296 case ENTRY_ATTRIBUTE:
297 if (qName.equals("name")) {
298 entry.setName(accumulator.toString());
299 } else if (qName.equals("type")) {
300 boolean found = false;
301 for (ImageryType type : ImageryType.values()) {
302 if (equal(accumulator.toString(), type.getUrlString())) {
303 entry.setImageryType(type);
304 found = true;
305 break;
306 }
307 }
308 if (!found) {
309 skipEntry = true;
310 }
311 } else if (qName.equals("default")) {
312 if (accumulator.toString().equals("true")) {
313 entry.setDefaultEntry(true);
314 } else if (accumulator.toString().equals("false")) {
315 entry.setDefaultEntry(false);
316 } else {
317 skipEntry = true;
318 }
319 } else if (qName.equals("url")) {
320 entry.setUrl(accumulator.toString());
321 } else if (qName.equals("eula")) {
322 entry.setEulaAcceptanceRequired(accumulator.toString());
323 } else if (qName.equals("min-zoom") || qName.equals("max-zoom")) {
324 Integer val = null;
325 try {
326 val = Integer.parseInt(accumulator.toString());
327 } catch(NumberFormatException e) {
328 val = null;
329 }
330 if (val == null) {
331 skipEntry = true;
332 } else {
333 if (qName.equals("min-zoom")) {
334 entry.setDefaultMinZoom(val);
335 } else {
336 entry.setDefaultMaxZoom(val);
337 entry.setMaxZoom(val);
338 }
339 }
340 } else if (qName.equals("bounds")) {
341 entry.setBounds(bounds);
342 bounds = null;
343 } else if (qName.equals("attribution-text")) {
344 entry.setAttributionText(accumulator.toString());
345 } else if (qName.equals("attribution-url")) {
346 entry.setAttributionLinkURL(accumulator.toString());
347 } else if (qName.equals("logo-image")) {
348 entry.setAttributionImage(accumulator.toString());
349 } else if (qName.equals("logo-url")) {
350 // TODO: it should be possible to specify the link for the logo
351 } else if (qName.equals("terms-of-use-text")) {
352 // TODO: it should be possible to configure the terms of use display text
353 } else if (qName.equals("terms-of-use-url")) {
354 entry.setTermsOfUseURL(accumulator.toString());
355 }
356 break;
357 case PR:
358 supported_srs.add(accumulator.toString());
359 break;
360 case SUPPORTED_PROJECTIONS:
361 entry.setServerProjections(supported_srs);
362 supported_srs = null;
363 break;
364 }
365 }
366 }
367}
Note: See TracBrowser for help on using the repository browser.