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
- 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.
- Re-export or re-download the GPX from the source — truncated/unbalanced XML cannot be repaired reliably in place.
- If unescaped characters in a track name are the cause, fix the producing app or escape them (&).
- 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
- Pre-validate GPX with xmllint or a Nokogiri errors check before starting the import job.
- Reject suspiciously small files (truncated uploads) at upload time.
- Confirm content, not just extension, when users rename files.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- zip has no entries
- Could not detect required columns: latitude, longitude, time
- Import source cannot be nil
- Invalid JSON format in data file: %{message}
- entry exceeds #{MAX_EXTRACTED_SIZE} bytes
AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21).
Data as JSON: /api/errors/b80f2e30786a1246.
Report an issue: GitHub.