Reading data from a GPX file

Hi,
I own a gps equipped heat rate monitor and want to extract the three fields from each report : time, elevation and heart rate. Here is the GPX file in a zip archive:
suuntoapp-Hiking-2026-01-29T09-57-12Z-track.gpx.zip (41.6 KB)

I have an Applescript which does what I want using multiple changes to the text item delimiters but is is slow taking just over three minutes to run on a 2013 MacbookPro.

I realise that the GPX format is a light weight form of XML and suspect that a dedicated XML parser could be used but I know nothing about them.

Here is my script but note when I say my script the handler that does all the hard work was written by forum user StefanK. See post https://www.macscripter.net/t/using-offset/50568/2

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions


set thefile to POSIX path of (choose file with prompt "Please choose a file:" of type {"public.text"})


set fileHandle to open for access thefile
set tFile to read fileHandle
close access fileHandle
set tGPXData to text 1 thru -1 of tFile -- One very long string of <tags> and values with some spaces

######### Parse the Data creating a list of lists  #########
set tid to AppleScript's text item delimiters
set AppleScript's text item delimiters to tab
set {tid, text item delimiters} to {text item delimiters, {"<trkpt"}}
set tDataSet to {}


repeat with i from 2 to (count of text items of tGPXData)
	set tRec to text item i of tGPXData
	--set tTime to ReadRecTime(tRec)
	set tTime to GetSubText(tRec, "<time>", "</time>")
	set tTime to GetSubText(tTime, "T", "Z")
	
	set tElevation to GetSubText(tRec, "<ele>", "</ele>")
	
	set tHeartRate to GetSubText(tRec, "<gpxtpx:hr>", "</gpxtpx:hr>")
	
	
	set DataRec to {PlotTime:tTime, Elevation:tElevation, HeartRate:tHeartRate}
	set end of tDataSet to DataRec
	
	
end repeat

display dialog "Debug stop"



on GetSubText(SomeText, StartItem, enditem)
	copy SomeText to OrigText
	set tid to text item delimiters
	if StartItem is not 1 then
		set text item delimiters to StartItem
		try
			set SomeText to text items 2 thru -1 of SomeText as text
		end try
	end if
	if enditem is not -1 then
		set text item delimiters to enditem
		set SomeText to text item 1 of SomeText
	end if
	set text item delimiters to tid
	if SomeText is OrigText then return ""
	return SomeText
end GetSubText

Any thoughts including :“don’t do it that way!”
best wishes
Simon

Simon. I’ve included my suggestion below. It uses ASObjC and regular expressions to parse the data. The timing result on my M2 Mac mini was 0.6 second. I don’t have any knowledge of XML parsers.

use framework "Foundation"
use scripting additions

set theFile to POSIX path of (choose file)
set theData to getData(theFile)

on getData(theFile)
	set theString to current application's NSString's stringWithContentsOfFile:theFile encoding:(current application's NSUTF8StringEncoding) |error|:(missing value)
	
	set allMatches to current application's NSMutableArray's new()
	set thePattern to "<ele>(.*?)</ele><time>(.*?)</time>.*?<gpxtpx:hr>(.*?)</gpxtpx:hr>"
	set theRegex to current application's NSRegularExpression's regularExpressionWithPattern:thePattern options:0 |error|:(missing value)
	set regexResults to theRegex's matchesInString:theString options:0 range:{location:0, |length|:theString's |length|()}
	repeat with aMatch in regexResults
		set theMatches to current application's NSMutableArray's new()
		set theRange to (aMatch's rangeAtIndex:1)
		if theRange's |length| > 0 then (theMatches's addObject:(theString's substringWithRange:theRange))
		set theRange to (aMatch's rangeAtIndex:2)
		if theRange's |length| > 0 then (theMatches's addObject:(theString's substringWithRange:theRange))
		set theRange to (aMatch's rangeAtIndex:3)
		if theRange's |length| > 0 then (theMatches's addObject:(theString's substringWithRange:theRange))
		(allMatches's addObject:theMatches)
	end repeat
	
	return allMatches as list
end getData

My suggestion outputs a list of lists and returns all time data. I’ll revise it tomorrow to return the data in the same format as your existing script. If this isn’t of interest, please let me know and I’ll skip it.

BTW, I tested your existing script, and it took 7.1 second to run. So, I don’t know that my suggestion is all that much of an improvement. Also, I’m not sure my script will run on a 2013 Mac computer, but it’s easy to test.

I cleaned up your script. it is very fast now.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

property tGPXData : missing value
property tDataSet : {}

local cc, psteps, thefile
set thefile to (choose file with prompt "Please choose a file:" of type {"gpx"})
set my tGPXData to read thefile

######### Parse the Data creating a list of lists  #########
set tid to text item delimiters
set text item delimiters to "<trkpt"
set my tDataSet to {}
set my tGPXData to text items of my tGPXData
set cc to count my tGPXData
set progress description to "GPX data reader…"
set progress additional description to ""
set progress total steps to 100
set psteps to cc div progress total steps
repeat with i from 2 to cc
	set tRec to item i of my tGPXData
	set tTime to GetSubText(tRec, "<time>", "</time>")
	set tTime to GetSubText(tTime, "T", "Z")
	set tElevation to GetSubText(tRec, "<ele>", "</ele>")
	set tHeartRate to GetSubText(tRec, "<gpxtpx:hr>", "</gpxtpx:hr>")
	
	set end of my tDataSet to {PlotTime:tTime, Elevation:tElevation, HeartRate:tHeartRate}
	if (i mod psteps) = 0 then
		set progress completed steps to progress completed steps + 1
		set progress additional description to "(" & i & " of " & cc & ")"
	end if
end repeat

display dialog "Debug stop" giving up after 1


on GetSubText(SomeText, StartItem, enditem)
	local tid, OrigText
	copy SomeText to OrigText
	set tid to text item delimiters
	if StartItem is not 1 then
		set text item delimiters to StartItem
		try
			set SomeText to text items 2 thru -1 of SomeText as text
		end try
	end if
	if enditem is not -1 then
		set text item delimiters to enditem
		set SomeText to text item 1 of SomeText
	end if
	set text item delimiters to tid
	if SomeText is OrigText then return ""
	return SomeText
end GetSubText

Your script runs on my 2011 era MBP with Sierra in under 4 seconds using Script Debugger (no Script Geek). With a newer M4 mini it is only a tenth of a second faster, so it is well within the range of how fast you can select the file and change windows.

Using NSDictionary with the location adds a couple more seconds on the MBP, and a couple more tenths of a second on the M4, although I’m not very good with regular expressions. The script takes quite a bit less time to run than starting everything up and clicking some windows.

Edit to add that @robertFern ’s script is a little over 5 times faster - the progress indicator doesn’t stand a chance.