[4240] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.io.imagery;
|
---|
| 3 |
|
---|
| 4 | import java.io.IOException;
|
---|
| 5 | import java.io.InputStream;
|
---|
| 6 | import java.util.ArrayList;
|
---|
| 7 | import java.util.Arrays;
|
---|
| 8 | import java.util.List;
|
---|
[7083] | 9 | import java.util.Objects;
|
---|
[4240] | 10 | import java.util.Stack;
|
---|
| 11 |
|
---|
| 12 | import javax.xml.parsers.ParserConfigurationException;
|
---|
| 13 |
|
---|
[6643] | 14 | import org.openstreetmap.josm.Main;
|
---|
[4240] | 15 | import org.openstreetmap.josm.data.imagery.ImageryInfo;
|
---|
[4423] | 16 | import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
|
---|
[4240] | 17 | import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
|
---|
[4423] | 18 | import org.openstreetmap.josm.data.imagery.Shape;
|
---|
[7248] | 19 | import org.openstreetmap.josm.io.CachedFile;
|
---|
[4240] | 20 | import org.openstreetmap.josm.io.UTFInputStreamReader;
|
---|
[8091] | 21 | import org.openstreetmap.josm.tools.LanguageInfo;
|
---|
[8287] | 22 | import org.openstreetmap.josm.tools.Utils;
|
---|
[4240] | 23 | import org.xml.sax.Attributes;
|
---|
| 24 | import org.xml.sax.InputSource;
|
---|
| 25 | import org.xml.sax.SAXException;
|
---|
| 26 | import org.xml.sax.helpers.DefaultHandler;
|
---|
| 27 |
|
---|
| 28 | public class ImageryReader {
|
---|
| 29 |
|
---|
| 30 | private String source;
|
---|
| 31 |
|
---|
[4874] | 32 | private enum State {
|
---|
[4240] | 33 | INIT, // initial state, should always be at the bottom of the stack
|
---|
| 34 | IMAGERY, // inside the imagery element
|
---|
| 35 | ENTRY, // inside an entry
|
---|
| 36 | ENTRY_ATTRIBUTE, // note we are inside an entry attribute to collect the character data
|
---|
[4439] | 37 | PROJECTIONS,
|
---|
| 38 | CODE,
|
---|
[4423] | 39 | BOUNDS,
|
---|
| 40 | SHAPE,
|
---|
[4240] | 41 | UNKNOWN, // element is not recognized in the current context
|
---|
| 42 | }
|
---|
| 43 |
|
---|
[4423] | 44 | public ImageryReader(String source) {
|
---|
[4240] | 45 | this.source = source;
|
---|
| 46 | }
|
---|
| 47 |
|
---|
| 48 | public List<ImageryInfo> parse() throws SAXException, IOException {
|
---|
[4430] | 49 | Parser parser = new Parser();
|
---|
[4240] | 50 | try {
|
---|
[7248] | 51 | try (InputStream in = new CachedFile(source)
|
---|
| 52 | .setMaxAge(1*CachedFile.DAYS)
|
---|
| 53 | .setCachingStrategy(CachedFile.CachingStrategy.IfModifiedSince)
|
---|
| 54 | .getInputStream()) {
|
---|
[7033] | 55 | InputSource is = new InputSource(UTFInputStreamReader.create(in));
|
---|
[8287] | 56 | Utils.newSafeSAXParser().parse(is, parser);
|
---|
[7033] | 57 | return parser.entries;
|
---|
| 58 | }
|
---|
[4430] | 59 | } catch (SAXException e) {
|
---|
| 60 | throw e;
|
---|
| 61 | } catch (ParserConfigurationException e) {
|
---|
[6643] | 62 | Main.error(e); // broken SAXException chaining
|
---|
[4430] | 63 | throw new SAXException(e);
|
---|
[4240] | 64 | }
|
---|
| 65 | }
|
---|
| 66 |
|
---|
[4874] | 67 | private static class Parser extends DefaultHandler {
|
---|
[4240] | 68 | private StringBuffer accumulator = new StringBuffer();
|
---|
| 69 |
|
---|
| 70 | private Stack<State> states;
|
---|
| 71 |
|
---|
[8285] | 72 | private List<ImageryInfo> entries;
|
---|
[4240] | 73 |
|
---|
| 74 | /**
|
---|
| 75 | * Skip the current entry because it has mandatory attributes
|
---|
| 76 | * that this version of JOSM cannot process.
|
---|
| 77 | */
|
---|
[8285] | 78 | private boolean skipEntry;
|
---|
[4240] | 79 |
|
---|
[8285] | 80 | private ImageryInfo entry;
|
---|
| 81 | private ImageryBounds bounds;
|
---|
| 82 | private Shape shape;
|
---|
[8091] | 83 | // language of last element, does only work for simple ENTRY_ATTRIBUTE's
|
---|
[8285] | 84 | private String lang;
|
---|
| 85 | private List<String> projections;
|
---|
[4240] | 86 |
|
---|
[8285] | 87 | @Override
|
---|
| 88 | public void startDocument() {
|
---|
[4240] | 89 | accumulator = new StringBuffer();
|
---|
| 90 | skipEntry = false;
|
---|
[7005] | 91 | states = new Stack<>();
|
---|
[4240] | 92 | states.push(State.INIT);
|
---|
[7005] | 93 | entries = new ArrayList<>();
|
---|
[4240] | 94 | entry = null;
|
---|
| 95 | bounds = null;
|
---|
[4439] | 96 | projections = null;
|
---|
[4240] | 97 | }
|
---|
| 98 |
|
---|
| 99 | @Override
|
---|
| 100 | public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
|
---|
| 101 | accumulator.setLength(0);
|
---|
| 102 | State newState = null;
|
---|
| 103 | switch (states.peek()) {
|
---|
[4874] | 104 | case INIT:
|
---|
[7012] | 105 | if ("imagery".equals(qName)) {
|
---|
[4874] | 106 | newState = State.IMAGERY;
|
---|
| 107 | }
|
---|
| 108 | break;
|
---|
| 109 | case IMAGERY:
|
---|
[7012] | 110 | if ("entry".equals(qName)) {
|
---|
[4874] | 111 | entry = new ImageryInfo();
|
---|
| 112 | skipEntry = false;
|
---|
| 113 | newState = State.ENTRY;
|
---|
| 114 | }
|
---|
| 115 | break;
|
---|
| 116 | case ENTRY:
|
---|
| 117 | if (Arrays.asList(new String[] {
|
---|
[4240] | 118 | "name",
|
---|
[7186] | 119 | "id",
|
---|
[4240] | 120 | "type",
|
---|
[8065] | 121 | "description",
|
---|
[4240] | 122 | "default",
|
---|
| 123 | "url",
|
---|
| 124 | "eula",
|
---|
| 125 | "min-zoom",
|
---|
| 126 | "max-zoom",
|
---|
| 127 | "attribution-text",
|
---|
| 128 | "attribution-url",
|
---|
| 129 | "logo-image",
|
---|
| 130 | "logo-url",
|
---|
| 131 | "terms-of-use-text",
|
---|
| 132 | "terms-of-use-url",
|
---|
[4405] | 133 | "country-code",
|
---|
[4713] | 134 | "icon",
|
---|
[4874] | 135 | }).contains(qName)) {
|
---|
| 136 | newState = State.ENTRY_ATTRIBUTE;
|
---|
[8091] | 137 | lang = atts.getValue("lang");
|
---|
[7012] | 138 | } else if ("bounds".equals(qName)) {
|
---|
[4874] | 139 | try {
|
---|
| 140 | bounds = new ImageryBounds(
|
---|
| 141 | atts.getValue("min-lat") + "," +
|
---|
| 142 | atts.getValue("min-lon") + "," +
|
---|
| 143 | atts.getValue("max-lat") + "," +
|
---|
| 144 | atts.getValue("max-lon"), ",");
|
---|
| 145 | } catch (IllegalArgumentException e) {
|
---|
| 146 | break;
|
---|
[4240] | 147 | }
|
---|
[4874] | 148 | newState = State.BOUNDS;
|
---|
[7012] | 149 | } else if ("projections".equals(qName)) {
|
---|
[7005] | 150 | projections = new ArrayList<>();
|
---|
[4874] | 151 | newState = State.PROJECTIONS;
|
---|
| 152 | }
|
---|
| 153 | break;
|
---|
| 154 | case BOUNDS:
|
---|
[7012] | 155 | if ("shape".equals(qName)) {
|
---|
[4874] | 156 | shape = new Shape();
|
---|
| 157 | newState = State.SHAPE;
|
---|
| 158 | }
|
---|
| 159 | break;
|
---|
| 160 | case SHAPE:
|
---|
[7012] | 161 | if ("point".equals(qName)) {
|
---|
[4874] | 162 | try {
|
---|
| 163 | shape.addPoint(atts.getValue("lat"), atts.getValue("lon"));
|
---|
| 164 | } catch (IllegalArgumentException e) {
|
---|
| 165 | break;
|
---|
[4423] | 166 | }
|
---|
[4874] | 167 | }
|
---|
| 168 | break;
|
---|
| 169 | case PROJECTIONS:
|
---|
[7012] | 170 | if ("code".equals(qName)) {
|
---|
[4874] | 171 | newState = State.CODE;
|
---|
| 172 | }
|
---|
| 173 | break;
|
---|
[4240] | 174 | }
|
---|
| 175 | /**
|
---|
[4310] | 176 | * Did not recognize the element, so the new state is UNKNOWN.
|
---|
[4240] | 177 | * This includes the case where we are already inside an unknown
|
---|
| 178 | * element, i.e. we do not try to understand the inner content
|
---|
| 179 | * of an unknown element, but wait till it's over.
|
---|
| 180 | */
|
---|
| 181 | if (newState == null) {
|
---|
| 182 | newState = State.UNKNOWN;
|
---|
| 183 | }
|
---|
| 184 | states.push(newState);
|
---|
[7083] | 185 | if (newState == State.UNKNOWN && "true".equals(atts.getValue("mandatory"))) {
|
---|
[4240] | 186 | skipEntry = true;
|
---|
| 187 | }
|
---|
| 188 | return;
|
---|
| 189 | }
|
---|
| 190 |
|
---|
| 191 | @Override
|
---|
| 192 | public void characters(char[] ch, int start, int length) {
|
---|
| 193 | accumulator.append(ch, start, length);
|
---|
| 194 | }
|
---|
| 195 |
|
---|
| 196 | @Override
|
---|
| 197 | public void endElement(String namespaceURI, String qName, String rqName) {
|
---|
| 198 | switch (states.pop()) {
|
---|
[4874] | 199 | case INIT:
|
---|
| 200 | throw new RuntimeException("parsing error: more closing than opening elements");
|
---|
| 201 | case ENTRY:
|
---|
[7012] | 202 | if ("entry".equals(qName)) {
|
---|
[4874] | 203 | if (!skipEntry) {
|
---|
| 204 | entries.add(entry);
|
---|
| 205 | }
|
---|
| 206 | entry = null;
|
---|
| 207 | }
|
---|
| 208 | break;
|
---|
| 209 | case ENTRY_ATTRIBUTE:
|
---|
[7012] | 210 | switch(qName) {
|
---|
| 211 | case "name":
|
---|
[8091] | 212 | entry.setName(lang == null ? LanguageInfo.getJOSMLocaleCode(null) : lang, accumulator.toString());
|
---|
[7012] | 213 | break;
|
---|
[8065] | 214 | case "description":
|
---|
[8091] | 215 | entry.setDescription(lang, accumulator.toString());
|
---|
[8065] | 216 | break;
|
---|
[7186] | 217 | case "id":
|
---|
| 218 | entry.setId(accumulator.toString());
|
---|
| 219 | break;
|
---|
[7012] | 220 | case "type":
|
---|
[4874] | 221 | boolean found = false;
|
---|
| 222 | for (ImageryType type : ImageryType.values()) {
|
---|
[7083] | 223 | if (Objects.equals(accumulator.toString(), type.getTypeString())) {
|
---|
[4874] | 224 | entry.setImageryType(type);
|
---|
| 225 | found = true;
|
---|
| 226 | break;
|
---|
[4240] | 227 | }
|
---|
| 228 | }
|
---|
[4874] | 229 | if (!found) {
|
---|
| 230 | skipEntry = true;
|
---|
| 231 | }
|
---|
[7012] | 232 | break;
|
---|
| 233 | case "default":
|
---|
| 234 | switch (accumulator.toString()) {
|
---|
| 235 | case "true":
|
---|
[4874] | 236 | entry.setDefaultEntry(true);
|
---|
[7012] | 237 | break;
|
---|
| 238 | case "false":
|
---|
[4874] | 239 | entry.setDefaultEntry(false);
|
---|
[7012] | 240 | break;
|
---|
| 241 | default:
|
---|
[4874] | 242 | skipEntry = true;
|
---|
| 243 | }
|
---|
[7012] | 244 | break;
|
---|
| 245 | case "url":
|
---|
[4874] | 246 | entry.setUrl(accumulator.toString());
|
---|
[7012] | 247 | break;
|
---|
| 248 | case "eula":
|
---|
[4874] | 249 | entry.setEulaAcceptanceRequired(accumulator.toString());
|
---|
[7012] | 250 | break;
|
---|
| 251 | case "min-zoom":
|
---|
| 252 | case "max-zoom":
|
---|
[4874] | 253 | Integer val = null;
|
---|
| 254 | try {
|
---|
| 255 | val = Integer.parseInt(accumulator.toString());
|
---|
| 256 | } catch(NumberFormatException e) {
|
---|
| 257 | val = null;
|
---|
| 258 | }
|
---|
| 259 | if (val == null) {
|
---|
| 260 | skipEntry = true;
|
---|
| 261 | } else {
|
---|
[7012] | 262 | if ("min-zoom".equals(qName)) {
|
---|
[4874] | 263 | entry.setDefaultMinZoom(val);
|
---|
[4240] | 264 | } else {
|
---|
[4874] | 265 | entry.setDefaultMaxZoom(val);
|
---|
[4240] | 266 | }
|
---|
| 267 | }
|
---|
[7012] | 268 | break;
|
---|
| 269 | case "attribution-text":
|
---|
[4874] | 270 | entry.setAttributionText(accumulator.toString());
|
---|
[7012] | 271 | break;
|
---|
| 272 | case "attribution-url":
|
---|
[4874] | 273 | entry.setAttributionLinkURL(accumulator.toString());
|
---|
[7012] | 274 | break;
|
---|
| 275 | case "logo-image":
|
---|
[4874] | 276 | entry.setAttributionImage(accumulator.toString());
|
---|
[7012] | 277 | break;
|
---|
| 278 | case "logo-url":
|
---|
[4874] | 279 | entry.setAttributionImageURL(accumulator.toString());
|
---|
[7012] | 280 | break;
|
---|
| 281 | case "terms-of-use-text":
|
---|
[4874] | 282 | entry.setTermsOfUseText(accumulator.toString());
|
---|
[7012] | 283 | break;
|
---|
| 284 | case "terms-of-use-url":
|
---|
[4874] | 285 | entry.setTermsOfUseURL(accumulator.toString());
|
---|
[7012] | 286 | break;
|
---|
| 287 | case "country-code":
|
---|
[4874] | 288 | entry.setCountryCode(accumulator.toString());
|
---|
[7012] | 289 | break;
|
---|
| 290 | case "icon":
|
---|
[4874] | 291 | entry.setIcon(accumulator.toString());
|
---|
[7012] | 292 | break;
|
---|
[4874] | 293 | }
|
---|
| 294 | break;
|
---|
| 295 | case BOUNDS:
|
---|
| 296 | entry.setBounds(bounds);
|
---|
| 297 | bounds = null;
|
---|
| 298 | break;
|
---|
| 299 | case SHAPE:
|
---|
| 300 | bounds.addShape(shape);
|
---|
| 301 | shape = null;
|
---|
| 302 | break;
|
---|
| 303 | case CODE:
|
---|
| 304 | projections.add(accumulator.toString());
|
---|
| 305 | break;
|
---|
| 306 | case PROJECTIONS:
|
---|
| 307 | entry.setServerProjections(projections);
|
---|
| 308 | projections = null;
|
---|
| 309 | break;
|
---|
[4240] | 310 | }
|
---|
| 311 | }
|
---|
| 312 | }
|
---|
| 313 | }
|
---|