Freika/dawarich · error · Nokogiri::XML::SyntaxError

GPX parse error: %{message}

Error message

GPX parse error: %{message}

What it means

Raised as Nokogiri::XML::SyntaxError by the SAX handler inside Gpx::TrackImporter when Nokogiri's parser reports a well-formedness error while streaming the GPX file. The handler's error callback (invoked by Nokogiri::XML::SAX::Parser on syntax problems) converts it into this RuntimeError with the parser message attached. It means the XML itself is malformed: broken nesting, unclosed tags, invalid characters, or premature truncation.

Source

Thrown at app/services/gpx/track_importer.rb:161

      attrs_h = attrs.each_with_object({}) { |a, h| h[a.localname] = a.value }
      if name == 'trkpt' && @stack.nil?
        @stack = [attrs_h]
        @text = +''
      elsif @stack
        @stack.last[name] = attrs_h
        @stack.push(attrs_h)
        @text = +''
      end
    end

    def characters(string)
      return if @capturing_trk_field && @capture_depth.positive?

      @text << string if @stack || @capturing_trk_field
    end

    def error(message)
      raise Nokogiri::XML::SyntaxError, I18n.t('services.gpx.track_importer.parse_error', message:)
    end

    def end_element_namespace(name, _prefix = nil, _uri = nil)
      if @capturing_trk_field
        if @capture_depth.positive?
          @capture_depth -= 1
          return
        end

        if name == @capturing_trk_field
          assign_trk_identity(@text.strip, @capturing_trk_field)
          @capturing_trk_field = nil
          @text = +''
          return
        end
      end

      return if %w[trk trkseg].include?(name)

View on GitHub (pinned to 97fad417c5)

Solutions

  1. Validate the file standalone first: xmllint --noout file.gpx (or Nokogiri::XML(File.read(path)) { |c| c.errors }) to get the exact syntax error and line.
  2. Re-export or re-download the GPX from the source — truncated/unbalanced XML cannot be repaired reliably in place.
  3. If unescaped characters in a track name are the cause, fix the producing app or escape them (&amp;).
  4. Confirm the file is actually GPX/XML (head -c 400 file) before retrying the import.

Example fix

# before
Gpx::TrackImporter.new(import, user_id, path).call # -> SyntaxError: GPX parse error: ...

# after: pre-validate and give an actionable message
errors = Nokogiri::XML(File.read(path)).errors
if errors.any?
  import.update!(status: :failed, error_message: errors.first.message)
else
  Gpx::TrackImporter.new(import, user_id, path).call
end
Defensive patterns

Strategy: validation

Validate before calling

doc = Nokogiri::XML(File.read(path))
doc.errors.empty? || raise("GPX file is not valid XML: #{doc.errors.first.message}")

Type guard

def wellformed_xml?(path)
  Nokogiri::XML(File.read(path)).errors.empty?
rescue StandardError
  false
end

Try / catch

begin
  Gpx::TrackImporter.new(import, user_id, path).call
rescue Nokogiri::XML::SyntaxError => e
  import.update!(status: :failed, error_message: "File is corrupted or not valid GPX: #{e.message}")
end

Prevention

When it happens

Trigger: Importing a .gpx whose XML is truncated (interrupted download/export, partial upload), files with raw '&' or '<' inside text (unescaped ampersands in track names like 'R&D Trail'), wrong encoding declared vs actual bytes, or a non-XML file (HTML error page from a proxy, JSON) renamed .gpx.

Common situations: Watch/strava exports interrupted mid-write, files edited by hand and tags left unbalanced, cloud storage proxies returning an HTML login page where GPX was expected, BOM or latin-1 bytes in a file declaring UTF-8.

Understand the failure class

Related errors


AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21). Data as JSON: /api/errors/b80f2e30786a1246. Report an issue: GitHub.