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

Last change on this file since 19049 was 19049, checked in by stoecker, 6 weeks ago

reimport previously exported dgpsid

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