@Override
    @NonNull
    public Collection<Geocache> parse(@NonNull final InputStream stream, @Nullable final DisposableHandler progressHandler) throws IOException, ParserException {
        // when importing a ZIP, reset the child waypoint state
        terraChildWaypoint = false;

        resetCache();
        final RootElement root = new RootElement(namespace, "gpx");
        final Element waypoint = root.getChild(namespace, "wpt");

        registerScriptUrl(root);

        root.getChild(namespace, "creator").setEndTextElementListener(body -> scriptUrl = body);

        // waypoint - attributes
        waypoint.setStartElementListener(attrs -> {
            try {
                if (attrs.getIndex("lat") > -1 && attrs.getIndex("lon") > -1) {
                    final String latitude = attrs.getValue("lat");
                    final String longitude = attrs.getValue("lon");
                    // latitude and longitude are required attributes, but we export them (0/0) for waypoints without coordinates
                    if (StringUtils.isNotBlank(latitude) && StringUtils.isNotBlank(longitude)) {
                        final Geopoint latLon = new Geopoint(Double.parseDouble(latitude),
                                Double.parseDouble(longitude));
                        final Geopoint pt0 = new Geopoint(0, 0);
                        if (!latLon.equals(pt0)) {
                            cache.setCoords(latLon);
                        }
                    }
                }
            } catch (final NumberFormatException e) {
                Log.w("Failed to parse waypoint's latitude and/or longitude", e);
            }
        });

        // waypoint
        waypoint.setEndElementListener(new EndElementListener() {

            @Override
            public void end() {
                // try to find geocode somewhere else
                if (StringUtils.isBlank(cache.getGeocode())) {
                    findGeoCode(name, true);
                    findGeoCode(desc, false);
                    findGeoCode(cmt, false);
                }

                // take the name as code, if nothing else is available
                if (StringUtils.isBlank(cache.getGeocode()) && StringUtils.isNotBlank(name)) {
                    cache.setGeocode(name.trim());
                }

                if (isValidForImport()) {
                    fixCache(cache);
                    if (listId != StoredList.TEMPORARY_LIST.id) {
                        cache.getLists().add(listId);
                    }
                    cache.setDetailed(true);
                    cache.setLogPasswordRequired(logPasswordRequired);
                    if (StringUtils.isNotBlank(descriptionPrefix)) {
                        cache.setDescription(descriptionPrefix + cache.getDescription());
                    }

                    createNoteFromGSAKUserdata();

                    cache.setAssignedEmoji(cacheAssignedEmoji);

                    final String geocode = cache.getGeocode();
                    if (result.contains(geocode)) {
                        Log.w("Duplicate geocode during GPX import: " + geocode);
                    }
                    // modify cache depending on the use case/connector
                    afterParsing(cache);

                    // finally store the cache in the database
                    result.add(geocode);
                    DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB));
                    DataStore.saveLogs(cache.getGeocode(), logs, false);

                    // avoid the cachecache using lots of memory for caches which the user did not actually look at
                    DataStore.removeCache(geocode, EnumSet.of(RemoveFlag.CACHE));
                    showProgressMessage(progressHandler, progressStream.getProgress());
                } else if (StringUtils.isNotBlank(cache.getName())
                        && (StringUtils.containsIgnoreCase(type, "waypoint") || terraChildWaypoint)) {
                    addWaypointToCache();
                }

                resetCache();
            }

            private void addWaypointToCache() {
                fixCache(cache);

                if (cache.getName().length() > 2 || StringUtils.isNotBlank(parentCacheCode)) {
                    if (StringUtils.isBlank(parentCacheCode)) {
                        if (StringUtils.containsIgnoreCase(scriptUrl, "extremcaching")) {
                            parentCacheCode = cache.getName().substring(2);
                        } else if (terraChildWaypoint) {
                            parentCacheCode = StringUtils.left(cache.getGeocode(), cache.getGeocode().length() - 1);
                        } else {
                            parentCacheCode = "GC" + cache.getName().substring(2).toUpperCase(Locale.US);
                        }
                    }

                    if ("GC_WayPoint1".equals(cache.getShortDescription())) {
                        cache.setShortDescription("");
                    }

                    final Geocache cacheForWaypoint = findParentCache();
                    if (cacheForWaypoint != null) {
                        final Waypoint waypoint = new Waypoint(cache.getShortDescription(), WaypointType.fromGPXString(sym, subtype), false);
                        if (wptUserDefined) {
                            waypoint.setUserDefined();
                        }
                        waypoint.setId(-1);
                        waypoint.setGeocode(parentCacheCode);
                        String cacheName = cache.getName();
                        if (wptUserDefined) {
                            // try to deduct original prefix from wpt name
                            if (StringUtils.endsWithIgnoreCase(cacheName, parentCacheCode.substring(2))) {
                                cacheName = cacheName.substring(0, cacheName.length() - parentCacheCode.length() + 2);
                            }
                            if (StringUtils.startsWithIgnoreCase(cacheName, Waypoint.PREFIX_OWN + "-")) {
                                cacheName = cacheName.substring(4);
                            }
                        }
                        waypoint.setPrefix(cacheForWaypoint.getWaypointPrefix(cacheName));
                        waypoint.setLookup("---");
                        // there is no lookup code in gpx file

                        waypoint.setCoords(cache.getCoords());

                        // set flag for user-modified coordinates of cache
                        if (waypoint.getWaypointType() == WaypointType.ORIGINAL) {
                            cacheForWaypoint.setUserModifiedCoords(true);
                        }

                        // user defined waypoint does not have original empty coordinates
                        if (wptEmptyCoordinates || (!waypoint.isUserDefined() && null == waypoint.getCoords())) {
                            waypoint.setOriginalCoordsEmpty(true);
                        }

                        final WaypointUserNoteCombiner wpCombiner = new WaypointUserNoteCombiner(waypoint);
                        wpCombiner.updateNoteAndUserNote(cache.getDescription());

                        waypoint.setVisited(wptVisited);
                        final List<Waypoint> mergedWayPoints = new ArrayList<>(cacheForWaypoint.getWaypoints());

                        final List<Waypoint> newPoints = new ArrayList<>();
                        newPoints.add(waypoint);
                        Waypoint.mergeWayPoints(newPoints, mergedWayPoints, true);
                        cacheForWaypoint.setWaypoints(newPoints, false);
                        DataStore.saveCache(cacheForWaypoint, EnumSet.of(SaveFlag.DB));
                        showProgressMessage(progressHandler, progressStream.getProgress());
                    }
                }
            }

        });

        // waypoint.time
        waypoint.getChild(namespace, "time").setEndTextElementListener(body -> {
            try {
                cache.setHidden(parseDate(body));
            } catch (final Exception e) {
                Log.w("Failed to parse cache date", e);
            }
        });

        // waypoint.name
        waypoint.getChild(namespace, "name").setEndTextElementListener(body -> {
            name = body;

            String content = body.trim();

            // extremcaching.com manipulates the GC code by adding GC in front of ECxxx
            if (StringUtils.startsWithIgnoreCase(content, "GCEC") && StringUtils.containsIgnoreCase(scriptUrl, "extremcaching")) {
                content = content.substring(2);
            }

            cache.setName(content);

            findGeoCode(cache.getName(), true);
        });

        // waypoint.desc
        waypoint.getChild(namespace, "desc").setEndTextElementListener(body -> {
            desc = body;
            cache.setShortDescription(validate(body));
        });

        // waypoint.cmt
        waypoint.getChild(namespace, "cmt").setEndTextElementListener(body -> {
            cmt = body;
            cache.setDescription(validate(body));
        });

        // waypoint.getType()
        waypoint.getChild(namespace, "type").setEndTextElementListener(body -> {
            final String[] content = StringUtils.split(body, '|');
            if (content.length > 0) {
                type = content[0].toLowerCase(Locale.US).trim();
                if (content.length > 1) {
                    subtype = content[1].toLowerCase(Locale.US).trim();
                }
            }
        });

        // waypoint.sym
        waypoint.getChild(namespace, "sym").setEndTextElementListener(body -> {
            sym = body.toLowerCase(Locale.US);
            if (sym.contains("geocache") && sym.contains("found")) {
                cache.setFound(true);
                cache.setDNF(false);
            }
        });

        // waypoint.url and waypoint.urlname (name for waymarks)
        registerUrlAndUrlName(waypoint);

        // for GPX 1.0, cache info comes from waypoint node (so called private children)
        // for GPX 1.1 from extensions node
        final Element extensionNode = getNodeForExtension(waypoint);
        if (extensionNode != null) {
            registerExtensions(extensionNode);
        } else {
            //  only to support other formats for GPX1.1, standard is extension
            registerExtensions(waypoint);
        }

        try {
            progressStream = new ProgressInputStream(stream);
            final BufferedReader reader = new BufferedReader(new InputStreamReader(progressStream, StandardCharsets.UTF_8));
            Xml.parse(new InvalidXMLCharacterFilterReader(reader), root.getContentHandler());
            return DataStore.loadCaches(result, EnumSet.of(LoadFlag.DB_MINIMAL));
        } catch (final SAXException e) {
            throw new ParserException("Cannot parse .gpx file as GPX " + version + ": could not parse XML", e);
        }
    }
prefix of the long description. used for adding alternative geocodes.
main/src/main/java/cgeo/geocaching/files/GPXParser.java