source: josm/trunk/src/org/openstreetmap/josm/io/GpxParser.java@ 18817

Last change on this file since 18817 was 18817, checked in by taylor.smock, 10 months ago

Fix #22652: Stop parsing gpx files when wpt elements do not have valid coordinates

This does move the parsing code out of GpxReader into GpxParser and refactors it
so that it is (hopefully) easier to understand and debug.

File size: 30.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import static org.openstreetmap.josm.data.gpx.GpxConstants.META_AUTHOR_EMAIL;
5import static org.openstreetmap.josm.data.gpx.GpxConstants.META_AUTHOR_LINK;
6import static org.openstreetmap.josm.data.gpx.GpxConstants.META_AUTHOR_NAME;
7import static org.openstreetmap.josm.data.gpx.GpxConstants.META_BOUNDS;
8import static org.openstreetmap.josm.data.gpx.GpxConstants.META_COPYRIGHT_AUTHOR;
9import static org.openstreetmap.josm.data.gpx.GpxConstants.META_COPYRIGHT_LICENSE;
10import static org.openstreetmap.josm.data.gpx.GpxConstants.META_COPYRIGHT_YEAR;
11import static org.openstreetmap.josm.data.gpx.GpxConstants.META_DESC;
12import static org.openstreetmap.josm.data.gpx.GpxConstants.META_KEYWORDS;
13import static org.openstreetmap.josm.data.gpx.GpxConstants.META_LINKS;
14import static org.openstreetmap.josm.data.gpx.GpxConstants.META_NAME;
15import static org.openstreetmap.josm.data.gpx.GpxConstants.META_TIME;
16import static org.openstreetmap.josm.data.gpx.GpxConstants.PT_TIME;
17import static org.openstreetmap.josm.tools.I18n.tr;
18
19import java.time.DateTimeException;
20import java.util.ArrayDeque;
21import java.util.ArrayList;
22import java.util.Collection;
23import java.util.Deque;
24import java.util.HashMap;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.Map;
28import java.util.Optional;
29
30import org.openstreetmap.josm.data.Bounds;
31import org.openstreetmap.josm.data.coor.LatLon;
32import org.openstreetmap.josm.data.gpx.GpxConstants;
33import org.openstreetmap.josm.data.gpx.GpxData;
34import org.openstreetmap.josm.data.gpx.GpxExtensionCollection;
35import org.openstreetmap.josm.data.gpx.GpxLink;
36import org.openstreetmap.josm.data.gpx.GpxRoute;
37import org.openstreetmap.josm.data.gpx.GpxTrack;
38import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
39import org.openstreetmap.josm.data.gpx.IGpxTrackSegment;
40import org.openstreetmap.josm.data.gpx.WayPoint;
41import org.openstreetmap.josm.tools.Logging;
42import org.openstreetmap.josm.tools.UncheckedParseException;
43import org.openstreetmap.josm.tools.date.DateUtils;
44import org.xml.sax.Attributes;
45import org.xml.sax.SAXException;
46import org.xml.sax.helpers.DefaultHandler;
47
48/**
49 * A parser for gpx files
50 */
51class GpxParser extends DefaultHandler {
52 private enum State {
53 INIT,
54 GPX,
55 METADATA,
56 WPT,
57 RTE,
58 TRK,
59 EXT,
60 AUTHOR,
61 LINK,
62 TRKSEG,
63 COPYRIGHT
64 }
65
66 private String version;
67 private GpxData data;
68 private Collection<IGpxTrackSegment> currentTrack;
69 private Map<String, Object> currentTrackAttr;
70 private Collection<WayPoint> currentTrackSeg;
71 private GpxRoute currentRoute;
72 private WayPoint currentWayPoint;
73
74 private State currentState = State.INIT;
75
76 private GpxLink currentLink;
77 private GpxExtensionCollection currentExtensionCollection;
78 private GpxExtensionCollection currentTrackExtensionCollection;
79 private Deque<State> states;
80 private final Deque<String[]> elements = new ArrayDeque<>();
81
82 private StringBuilder accumulator = new StringBuilder();
83
84 private boolean nokiaSportsTrackerBug;
85
86 @Override
87 public void startDocument() {
88 accumulator = new StringBuilder();
89 states = new ArrayDeque<>();
90 data = new GpxData(true);
91 currentExtensionCollection = new GpxExtensionCollection();
92 currentTrackExtensionCollection = new GpxExtensionCollection();
93 }
94
95 @Override
96 public void startPrefixMapping(String prefix, String uri) {
97 data.getNamespaces().add(new GpxData.XMLNamespace(prefix, uri));
98 }
99
100 /**
101 * Convert the specified key's value to a number
102 * @param attributes The attributes to get the value from
103 * @param key The key to use
104 * @return A valid double, or {@link Double#NaN}
105 */
106 private static double parseCoordinates(Attributes attributes, String key) {
107 String val = attributes.getValue(key);
108 if (val != null) {
109 return parseCoordinates(val);
110 } else {
111 // Some software do not respect GPX schema and use "minLat" / "minLon" instead of "minlat" / "minlon"
112 return parseCoordinates(attributes.getValue(key.replaceFirst("l", "L")));
113 }
114 }
115
116 /**
117 * Convert a string coordinate to a double
118 * @param s The string to convert to double
119 * @return A valid double, or {@link Double#NaN}
120 */
121 private static double parseCoordinates(String s) {
122 if (s != null) {
123 try {
124 return Double.parseDouble(s);
125 } catch (NumberFormatException ex) {
126 Logging.trace(ex);
127 }
128 }
129 return Double.NaN;
130 }
131
132 /**
133 * Convert coordinates in attributes to a {@link LatLon} object
134 * @param attributes The attributes to parse
135 * @return The {@link LatLon}, warning: it may be invalid, use {@link LatLon#isValid()}
136 */
137 private static LatLon parseLatLon(Attributes attributes) {
138 return new LatLon(
139 parseCoordinates(attributes, "lat"),
140 parseCoordinates(attributes, "lon"));
141 }
142
143 @Override
144 public void startElement(String namespaceURI, String localName, String qName, Attributes attributes) throws SAXException {
145 elements.push(new String[] {namespaceURI, localName, qName});
146 switch(currentState) {
147 case INIT:
148 startElementInit(attributes);
149 break;
150 case GPX:
151 startElementGpx(localName, attributes);
152 break;
153 case METADATA:
154 startElementMetadata(localName, attributes);
155 break;
156 case AUTHOR:
157 startElementAuthor(localName, attributes);
158 break;
159 case TRK:
160 startElementTrk(localName, attributes);
161 break;
162 case TRKSEG:
163 startElementTrkSeg(localName, attributes);
164 break;
165 case WPT:
166 startElementWpt(localName, attributes);
167 break;
168 case RTE:
169 startElementRte(localName, attributes);
170 break;
171 case EXT:
172 startElementExt(namespaceURI, qName, attributes);
173 break;
174 default: // Do nothing
175 }
176 accumulator.setLength(0);
177 }
178
179 /**
180 * Start the root element
181 * @param attributes The attributes attached to the element. If there are no attributes, it shall be an empty Attributes object.
182 */
183 private void startElementInit(Attributes attributes) {
184 states.push(currentState);
185 currentState = State.GPX;
186 data.creator = attributes.getValue("creator");
187 version = attributes.getValue("version");
188 if (version != null && version.startsWith("1.0")) {
189 version = "1.0";
190 } else if (!"1.1".equals(version)) {
191 // unknown version, assume 1.1
192 version = "1.1";
193 }
194 String schemaLocation = attributes.getValue(GpxConstants.XML_URI_XSD, "schemaLocation");
195 if (schemaLocation != null) {
196 String[] schemaLocations = schemaLocation.split(" ", -1);
197 for (int i = 0; i < schemaLocations.length - 1; i += 2) {
198 final String schemaURI = schemaLocations[i];
199 final String schemaXSD = schemaLocations[i + 1];
200 data.getNamespaces().stream().filter(xml -> xml.getURI().equals(schemaURI))
201 .forEach(xml -> xml.setLocation(schemaXSD));
202 }
203 }
204 }
205
206 /**
207 * Start the root gpx element
208 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
209 * @param attributes The attributes attached to the element. If there are no attributes, it shall be an empty Attributes object.
210 */
211 private void startElementGpx(String localName, Attributes attributes) {
212 switch (localName) {
213 case "metadata":
214 states.push(currentState);
215 currentState = State.METADATA;
216 break;
217 case "wpt":
218 states.push(currentState);
219 currentState = State.WPT;
220 currentWayPoint = new WayPoint(parseLatLon(attributes));
221 break;
222 case "rte":
223 states.push(currentState);
224 currentState = State.RTE;
225 currentRoute = new GpxRoute();
226 break;
227 case "trk":
228 states.push(currentState);
229 currentState = State.TRK;
230 currentTrack = new ArrayList<>();
231 currentTrackAttr = new HashMap<>();
232 break;
233 case "extensions":
234 states.push(currentState);
235 currentState = State.EXT;
236 break;
237 case "gpx":
238 if (attributes.getValue("creator") != null && attributes.getValue("creator").startsWith("Nokia Sports Tracker")) {
239 nokiaSportsTrackerBug = true;
240 }
241 break;
242 default: // Do nothing
243 }
244 }
245
246 /**
247 * Start a metadata element
248 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
249 * @param attributes The attributes attached to the element. If there are no attributes, it shall be an empty Attributes object.
250 * @see #endElementMetadata(String)
251 */
252 private void startElementMetadata(String localName, Attributes attributes) {
253 switch (localName) {
254 case "author":
255 states.push(currentState);
256 currentState = State.AUTHOR;
257 break;
258 case "extensions":
259 states.push(currentState);
260 currentState = State.EXT;
261 break;
262 case "copyright":
263 states.push(currentState);
264 currentState = State.COPYRIGHT;
265 data.put(META_COPYRIGHT_AUTHOR, attributes.getValue("author"));
266 break;
267 case "link":
268 states.push(currentState);
269 currentState = State.LINK;
270 currentLink = new GpxLink(attributes.getValue("href"));
271 break;
272 case "bounds":
273 data.put(META_BOUNDS, new Bounds(
274 parseCoordinates(attributes, "minlat"),
275 parseCoordinates(attributes, "minlon"),
276 parseCoordinates(attributes, "maxlat"),
277 parseCoordinates(attributes, "maxlon")));
278 break;
279 default: // Do nothing
280 }
281 }
282
283 /**
284 * Start an author element
285 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
286 * @param attributes The attributes attached to the element. If there are no attributes, it shall be an empty Attributes object.
287 * @see #endElementAuthor(String)
288 */
289 private void startElementAuthor(String localName, Attributes attributes) {
290 switch (localName) {
291 case "link":
292 states.push(currentState);
293 currentState = State.LINK;
294 currentLink = new GpxLink(attributes.getValue("href"));
295 break;
296 case "email":
297 data.put(META_AUTHOR_EMAIL, attributes.getValue("id") + '@' + attributes.getValue("domain"));
298 break;
299 default: // Do nothing
300 }
301 }
302
303 /**
304 * Start a trk element
305 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
306 * @param attributes The attributes attached to the element. If there are no attributes, it shall be an empty Attributes object.
307 * @see #endElementTrk(String)
308 */
309 private void startElementTrk(String localName, Attributes attributes) {
310 switch (localName) {
311 case "trkseg":
312 states.push(currentState);
313 currentState = State.TRKSEG;
314 currentTrackSeg = new ArrayList<>();
315 break;
316 case "link":
317 states.push(currentState);
318 currentState = State.LINK;
319 currentLink = new GpxLink(attributes.getValue("href"));
320 break;
321 case "extensions":
322 states.push(currentState);
323 currentState = State.EXT;
324 break;
325 default: // Do nothing
326 }
327 }
328
329 /**
330 * Start a trkseg element
331 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
332 * @param attributes The attributes attached to the element. If there are no attributes, it shall be an empty Attributes object.
333 * @see #endElementTrkseg(String)
334 */
335 private void startElementTrkSeg(String localName, Attributes attributes) {
336 switch (localName) {
337 case "trkpt":
338 states.push(currentState);
339 currentState = State.WPT;
340 currentWayPoint = new WayPoint(parseLatLon(attributes));
341 break;
342 case "extensions":
343 states.push(currentState);
344 currentState = State.EXT;
345 break;
346 default: // Do nothing
347 }
348 }
349
350 /**
351 * Start the wpt element
352 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
353 * @param attributes The attributes attached to the element. If there are no attributes, it shall be an empty Attributes object.
354 * @see #endElementWpt(String)
355 */
356 private void startElementWpt(String localName, Attributes attributes) {
357 switch (localName) {
358 case "link":
359 states.push(currentState);
360 currentState = State.LINK;
361 currentLink = new GpxLink(attributes.getValue("href"));
362 break;
363 case "extensions":
364 states.push(currentState);
365 currentState = State.EXT;
366 break;
367 default: // Do nothing
368 }
369 }
370
371 /**
372 * Start the rte element
373 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
374 * @param attributes The attributes attached to the element. If there are no attributes, it shall be an empty Attributes object.
375 */
376 private void startElementRte(String localName, Attributes attributes) {
377 switch (localName) {
378 case "link":
379 states.push(currentState);
380 currentState = State.LINK;
381 currentLink = new GpxLink(attributes.getValue("href"));
382 break;
383 case "rtept":
384 states.push(currentState);
385 currentState = State.WPT;
386 currentWayPoint = new WayPoint(parseLatLon(attributes));
387 break;
388 case "extensions":
389 states.push(currentState);
390 currentState = State.EXT;
391 break;
392 default: // Do nothing
393 }
394 }
395
396 /**
397 * Start an ext element
398 * @param namespaceURI The Namespace URI, or the empty string if the element has no Namespace URI or if Namespace
399 * processing is not being performed.
400 * @param qName The qualified name (with prefix), or the empty string if qualified names are not available.
401 * @param attributes The attributes attached to the element. If there are no attributes, it shall be an empty Attributes object.
402 */
403 private void startElementExt(String namespaceURI, String qName, Attributes attributes) {
404 if (states.peekLast() == State.TRK) {
405 currentTrackExtensionCollection.openChild(namespaceURI, qName, attributes);
406 } else {
407 currentExtensionCollection.openChild(namespaceURI, qName, attributes);
408 }
409 }
410
411 @Override
412 public void characters(char[] ch, int start, int length) {
413 /*
414 * Remove illegal characters generated by the Nokia Sports Tracker device.
415 * Don't do this crude substitution for all files, since it would destroy
416 * certain unicode characters.
417 */
418 if (nokiaSportsTrackerBug) {
419 for (int i = 0; i < ch.length; ++i) {
420 if (ch[i] == 1) {
421 ch[i] = 32;
422 }
423 }
424 nokiaSportsTrackerBug = false;
425 }
426
427 accumulator.append(ch, start, length);
428 }
429
430 /**
431 * Get the current attributes
432 * @return The current attributes, if available
433 */
434 private Optional<Map<String, Object>> getAttr() {
435 switch (currentState) {
436 case RTE: return Optional.ofNullable(currentRoute.attr);
437 case METADATA: return Optional.ofNullable(data.attr);
438 case WPT: return Optional.ofNullable(currentWayPoint.attr);
439 case TRK: return Optional.ofNullable(currentTrackAttr);
440 default: return Optional.empty();
441 }
442 }
443
444 @Override
445 public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
446 elements.pop();
447 switch (currentState) {
448 case GPX: // GPX 1.0
449 case METADATA: // GPX 1.1
450 endElementMetadata(localName);
451 break;
452 case AUTHOR:
453 endElementAuthor(localName);
454 break;
455 case COPYRIGHT:
456 endElementCopyright(localName);
457 break;
458 case LINK:
459 endElementLink(localName);
460 break;
461 case WPT:
462 endElementWpt(localName);
463 break;
464 case TRKSEG:
465 endElementTrkseg(localName);
466 break;
467 case TRK:
468 endElementTrk(localName);
469 break;
470 case EXT:
471 endElementExt(localName, qName);
472 break;
473 default:
474 endElementDefault(localName);
475 }
476 accumulator.setLength(0);
477 }
478
479 /**
480 * End the metadata element
481 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
482 * @see #startElementMetadata(String, Attributes)
483 */
484 private void endElementMetadata(String localName) {
485 switch (localName) {
486 case "name":
487 data.put(META_NAME, accumulator.toString());
488 break;
489 case "desc":
490 data.put(META_DESC, accumulator.toString());
491 break;
492 case "time":
493 data.put(META_TIME, accumulator.toString());
494 break;
495 case "keywords":
496 data.put(META_KEYWORDS, accumulator.toString());
497 break;
498 case "author":
499 if ("1.0".equals(version)) {
500 // author is a string in 1.0, but complex element in 1.1
501 data.put(META_AUTHOR_NAME, accumulator.toString());
502 }
503 break;
504 case "email":
505 if ("1.0".equals(version)) {
506 data.put(META_AUTHOR_EMAIL, accumulator.toString());
507 }
508 break;
509 case "url":
510 case "urlname":
511 data.put(localName, accumulator.toString());
512 break;
513 case "metadata":
514 case "gpx":
515 if ((currentState == State.METADATA && "metadata".equals(localName)) ||
516 (currentState == State.GPX && "gpx".equals(localName))) {
517 convertUrlToLink(data.attr);
518 data.getExtensions().addAll(currentExtensionCollection);
519 currentExtensionCollection.clear();
520 currentState = states.pop();
521 }
522 break;
523 case "bounds":
524 // do nothing, has been parsed on startElement
525 break;
526 default:
527 }
528 }
529
530 /**
531 * End the author element
532 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
533 * @see #startElementAuthor(String, Attributes)
534 */
535 private void endElementAuthor(String localName) {
536 switch (localName) {
537 case "author":
538 currentState = states.pop();
539 break;
540 case "name":
541 data.put(META_AUTHOR_NAME, accumulator.toString());
542 break;
543 case "email":
544 // do nothing, has been parsed on startElement
545 break;
546 case "link":
547 data.put(META_AUTHOR_LINK, currentLink);
548 break;
549 default: // Do nothing
550 }
551 }
552
553 /**
554 * End the copyright element
555 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
556 */
557 private void endElementCopyright(String localName) {
558 switch (localName) {
559 case "copyright":
560 currentState = states.pop();
561 break;
562 case "year":
563 data.put(META_COPYRIGHT_YEAR, accumulator.toString());
564 break;
565 case "license":
566 data.put(META_COPYRIGHT_LICENSE, accumulator.toString());
567 break;
568 default: // Do nothing
569 }
570 }
571
572 /**
573 * End a link element
574 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
575 */
576 @SuppressWarnings("unchecked")
577 private void endElementLink(String localName) {
578 switch (localName) {
579 case "text":
580 currentLink.text = accumulator.toString();
581 break;
582 case "type":
583 currentLink.type = accumulator.toString();
584 break;
585 case "link":
586 if (currentLink.uri == null && !accumulator.toString().isEmpty()) {
587 currentLink = new GpxLink(accumulator.toString());
588 }
589 currentState = states.pop();
590 break;
591 default: // Do nothing
592 }
593 if (currentState == State.AUTHOR) {
594 data.put(META_AUTHOR_LINK, currentLink);
595 } else if (currentState != State.LINK) {
596 getAttr().ifPresent(attr ->
597 ((Collection<GpxLink>) attr.computeIfAbsent(META_LINKS, e -> new LinkedList<GpxLink>())).add(currentLink));
598 }
599 }
600
601 /**
602 * End a wpt element
603 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
604 * @throws SAXException If a waypoint does not have valid coordinates
605 * @see #startElementWpt(String, Attributes)
606 */
607 private void endElementWpt(String localName) throws SAXException {
608 switch (localName) {
609 case "ele":
610 case "magvar":
611 case "name":
612 case "src":
613 case "geoidheight":
614 case "type":
615 case "sym":
616 case "url":
617 case "urlname":
618 case "cmt":
619 case "desc":
620 case "fix":
621 currentWayPoint.put(localName, accumulator.toString());
622 break;
623 case "hdop":
624 case "vdop":
625 case "pdop":
626 try {
627 currentWayPoint.put(localName, Float.valueOf(accumulator.toString()));
628 } catch (NumberFormatException e) {
629 currentWayPoint.put(localName, 0f);
630 }
631 break;
632 case PT_TIME:
633 try {
634 currentWayPoint.setInstant(DateUtils.parseInstant(accumulator.toString()));
635 } catch (UncheckedParseException | DateTimeException e) {
636 Logging.error(e);
637 }
638 break;
639 case "rtept":
640 currentState = states.pop();
641 convertUrlToLink(currentWayPoint.attr);
642 currentRoute.routePoints.add(currentWayPoint);
643 break;
644 case "trkpt":
645 currentState = states.pop();
646 convertUrlToLink(currentWayPoint.attr);
647 currentTrackSeg.add(currentWayPoint);
648 break;
649 case "wpt":
650 currentState = states.pop();
651 convertUrlToLink(currentWayPoint.attr);
652 currentWayPoint.getExtensions().addAll(currentExtensionCollection);
653 if (!currentWayPoint.isLatLonKnown()) {
654 currentExtensionCollection.clear();
655 throw new SAXException(tr("{0} element does not have valid latitude and/or longitude.", "wpt"));
656 }
657 data.waypoints.add(currentWayPoint);
658 currentExtensionCollection.clear();
659 break;
660 default: // Do nothing
661 }
662 }
663
664 /**
665 * End a trkseg element
666 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
667 * @see #startElementTrkSeg(String, Attributes)
668 */
669 private void endElementTrkseg(String localName) {
670 if ("trkseg".equals(localName)) {
671 currentState = states.pop();
672 if (!currentTrackSeg.isEmpty()) {
673 GpxTrackSegment seg = new GpxTrackSegment(currentTrackSeg);
674 if (!currentExtensionCollection.isEmpty()) {
675 seg.getExtensions().addAll(currentExtensionCollection);
676 }
677 currentTrack.add(seg);
678 }
679 currentExtensionCollection.clear();
680 }
681 }
682
683 /**
684 * End a trk element
685 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
686 * @see #startElementTrk(String, Attributes)
687 */
688 private void endElementTrk(String localName) {
689 switch (localName) {
690 case "trk":
691 currentState = states.pop();
692 convertUrlToLink(currentTrackAttr);
693 GpxTrack trk = new GpxTrack(new ArrayList<>(currentTrack), currentTrackAttr);
694 if (!currentTrackExtensionCollection.isEmpty()) {
695 trk.getExtensions().addAll(currentTrackExtensionCollection);
696 }
697 data.addTrack(trk);
698 currentTrackExtensionCollection.clear();
699 break;
700 case "name":
701 case "cmt":
702 case "desc":
703 case "src":
704 case "type":
705 case "number":
706 case "url":
707 case "urlname":
708 currentTrackAttr.put(localName, accumulator.toString());
709 break;
710 default: // Do nothing
711 }
712 }
713
714 /**
715 * End an ext element
716 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
717 * @param qName The qualified name (with prefix), or the empty string if qualified names are not available.
718 * @see #startElementExt(String, String, Attributes)
719 */
720 private void endElementExt(String localName, String qName) {
721 if ("extensions".equals(localName)) {
722 currentState = states.pop();
723 } else if (currentExtensionCollection != null) {
724 String acc = accumulator.toString().trim();
725 if (states.peekLast() == State.TRK) {
726 currentTrackExtensionCollection.closeChild(qName, acc); //a segment inside the track can have an extension too
727 } else {
728 currentExtensionCollection.closeChild(qName, acc);
729 }
730 }
731 }
732
733 /**
734 * End the default element
735 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
736 */
737 private void endElementDefault(String localName) {
738 switch (localName) {
739 case "wpt":
740 currentState = states.pop();
741 break;
742 case "rte":
743 currentState = states.pop();
744 convertUrlToLink(currentRoute.attr);
745 data.addRoute(currentRoute);
746 break;
747 default: // Do nothing
748 }
749 }
750
751 @Override
752 public void endDocument() throws SAXException {
753 if (!states.isEmpty())
754 throw new SAXException(tr("Parse error: invalid document structure for GPX document."));
755
756 data.getExtensions().stream("josm", "from-server").findAny()
757 .ifPresent(ext -> data.fromServer = "true".equals(ext.getValue()));
758
759 data.getExtensions().stream("josm", "layerPreferences")
760 .forEach(prefs -> prefs.getExtensions().stream("josm", "entry").forEach(prefEntry -> {
761 Object key = prefEntry.get("key");
762 Object val = prefEntry.get("value");
763 if (key != null && val != null) {
764 data.getLayerPrefs().put(key.toString(), val.toString());
765 }
766 }));
767 data.endUpdate();
768 }
769
770 /**
771 * Get the parsed {@link GpxData}
772 * @return The data
773 */
774 GpxData getData() {
775 return this.data;
776 }
777
778 /**
779 * convert url/urlname to link element (GPX 1.0 -&gt; GPX 1.1).
780 * @param attr attributes
781 */
782 private static void convertUrlToLink(Map<String, Object> attr) {
783 String url = (String) attr.get("url");
784 String urlname = (String) attr.get("urlname");
785 if (url != null) {
786 GpxLink link = new GpxLink(url);
787 link.text = urlname;
788 @SuppressWarnings("unchecked")
789 Collection<GpxLink> links = (Collection<GpxLink>) attr.computeIfAbsent(META_LINKS, e -> new LinkedList<>());
790 links.add(link);
791 }
792 }
793
794 /**
795 * Attempt to finish parsing
796 * @throws SAXException If there are additional parsing errors
797 */
798 void tryToFinish() throws SAXException {
799 List<String[]> remainingElements = new ArrayList<>(elements);
800 for (int i = remainingElements.size() - 1; i >= 0; i--) {
801 String[] e = remainingElements.get(i);
802 endElement(e[0], e[1], e[2]);
803 }
804 endDocument();
805 }
806}
Note: See TracBrowser for help on using the repository browser.