diff --git a/gpx/src/io.ts b/gpx/src/io.ts index aed93beb..d033700c 100644 --- a/gpx/src/io.ts +++ b/gpx/src/io.ts @@ -1,11 +1,11 @@ -import { XMLParser } from "fast-xml-parser"; +import { XMLParser, XMLBuilder } from "fast-xml-parser"; import { GPXFile } from "./types"; export function parseGPX(gpxData: string): GPXFile { const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "", - removeNSPrefix: true, + attributesGroupName: 'attributes', isArray: (name: string) => { return name === 'trk' || name === 'trkseg' || name === 'trkpt' || name === 'wpt'; }, @@ -18,7 +18,7 @@ export function parseGPX(gpxData: string): GPXFile { transformTagName(tagName: string) { if (tagName === 'power') { // Transform the simple tag to the more complex tag, the nested tag is then handled by the tagValueProcessor - return 'PowerExtension'; + return 'gpxpx:PowerExtension'; } return tagName; }, @@ -32,30 +32,69 @@ export function parseGPX(gpxData: string): GPXFile { return new Date(tagValue); } - if (tagName === 'hr' || tagName === 'cad' || tagName === 'atemp' || tagName === 'PowerInWatts' || tagName === 'opacity' || tagName === 'weight') { + if (tagName === 'gpxtpx:hr' || tagName === 'gpxtpx:cad' || tagName === 'gpxtpx:atemp' || tagName === 'gpxpx:PowerInWatts' || tagName === 'opacity' || tagName === 'weight') { return parseFloat(tagValue); } - if (tagName === 'PowerExtension') { + if (tagName === 'gpxpx:PowerExtension') { // Finish the transformation of the simple tag to the more complex tag // Note that this only targets the transformed tag, since it must be a leaf node return { - 'PowerInWatts': parseFloat(tagValue) + 'gpxpx:PowerInWatts': parseFloat(tagValue) }; } } return tagValue; }, - transformAttributeName(attributeName) { - if (attributeName !== 'lat' && attributeName !== 'lon' && attributeName !== 'creator' && attributeName !== 'href') { - return `@_${attributeName}`; - } - return attributeName; - }, }); const parsed = parser.parse(gpxData); return parsed.gpx; } + + +export function buildGPX(gpx: GPXFile): string { + const builder = new XMLBuilder({ + format: true, + ignoreAttributes: false, + attributeNamePrefix: "", + attributesGroupName: 'attributes', + suppressEmptyNode: true, + tagValueProcessor: (tagName: string, tagValue: unknown): string => { + if (tagValue instanceof Date) { + return tagValue.toISOString(); + } + return tagValue.toString(); + }, + }); + + gpx.attributes.creator = 'https://gpx.studio'; + gpx.attributes['version'] = '1.1'; + gpx.attributes['xmlns'] = 'http://www.topografix.com/GPX/1/1'; + gpx.attributes['xmlns:xsi'] = 'http://www.w3.org/2001/XMLSchema-instance'; + gpx.attributes['xsi:schemaLocation'] = 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/PowerExtension/v1 http://www.garmin.com/xmlschemas/PowerExtensionv1.xsd http://www.topografix.com/GPX/gpx_style/0/2 http://www.topografix.com/GPX/gpx_style/0/2/gpx_style.xsd'; + gpx.attributes['xmlns:gpxtpx'] = 'http://www.garmin.com/xmlschemas/TrackPointExtension/v1'; + gpx.attributes['xmlns:gpxx'] = 'http://www.garmin.com/xmlschemas/GpxExtensions/v3'; + gpx.attributes['xmlns:gpxpx'] = 'http://www.garmin.com/xmlschemas/PowerExtension/v1'; + gpx.attributes['xmlns:gpx_style'] = 'http://www.topografix.com/GPX/gpx_style/0/2'; + gpx.metadata.author = { + name: 'gpx.studio', + link: { + attributes: { + href: 'https://gpx.studio', + } + } + }; + + return builder.build({ + "?xml": { + attributes: { + version: "1.0", + encoding: "UTF-8", + } + }, + gpx + }); +} \ No newline at end of file diff --git a/gpx/src/types.ts b/gpx/src/types.ts index 952b7f33..064f5b74 100644 --- a/gpx/src/types.ts +++ b/gpx/src/types.ts @@ -1,10 +1,15 @@ export type GPXFile = { - creator: string; + attributes: GPXFileAttributes; metadata: Metadata; wpt: Waypoint[]; trk: Track[]; }; +export type GPXFileAttributes = { + creator: string; + [key: string]: string; +}; + export type Metadata = { name?: string; desc?: string; @@ -14,14 +19,17 @@ export type Metadata = { }; export type Link = { - href: string; + attributes: LinkAttributes; text?: string; type?: string; }; +export type LinkAttributes = { + href: string; +}; + export type Waypoint = { - lat: number; - lon: number; + attributes: Coordinates; ele?: number; time?: Date; name?: string; @@ -32,6 +40,11 @@ export type Waypoint = { type?: string; }; +export type Coordinates = { + lat: number; + lon: number; +}; + export type Track = { name?: string; cmt?: string; @@ -44,7 +57,7 @@ export type Track = { }; export type TrackExtensions = { - line?: LineStyleExtension; + 'gpx_style:line'?: LineStyleExtension; }; export type LineStyleExtension = { @@ -58,29 +71,28 @@ export type TrackSegment = { }; export type TrackPoint = { - lat: number; - lon: number; + attributes: Coordinates; ele?: number; time?: Date; extensions?: TrackPointExtensions; }; export type TrackPointExtensions = { - TrackPointExtension?: TrackPointExtension; - PowerExtension?: PowerExtension; + 'gpxtpx:TrackPointExtension'?: TrackPointExtension; + 'gpxpx:PowerExtension'?: PowerExtension; }; export type TrackPointExtension = { - hr?: number; - cad?: number; - atemp?: number; - Extensions?: { + 'gpxtpx:hr'?: number; + 'gpxtpx:cad'?: number; + 'gpxtpx:atemp'?: number; + 'gpxtpx:Extensions'?: { surface?: string; }; } export type PowerExtension = { - PowerInWatts?: number; + 'gpxpx:PowerInWatts'?: number; } export type Author = { diff --git a/gpx/test-data/simple.gpx b/gpx/test-data/simple.gpx index d4633de6..be565954 100644 --- a/gpx/test-data/simple.gpx +++ b/gpx/test-data/simple.gpx @@ -1,256 +1,260 @@ - - - simple - - gpx.studio - - - - - simple - Cycling - - - 109.0 - - - 110.8 - - - 110.3 - - - 110.0 - - - 110.3 - - - 109.3 - - - 107.0 - - - 106.0 - - - 108.5 - - - 109.8 - - - 110.8 - - - 112.0 - - - 112.8 - - - 113.5 - - - 114.3 - - - 115.3 - - - 114.8 - - - 114.3 - - - 114.3 - - - 114.5 - - - 115.0 - - - 115.8 - - - 115.8 - - - 116.0 - - - 115.8 - - - 115.5 - - - 114.5 - - - 112.5 - - - 110.8 - - - 109.0 - - - 106.3 - - - 104.3 - - - 104.0 - - - 103.8 - - - 103.3 - - - 103.5 - - - 103.8 - - - 104.8 - - - 105.8 - - - 106.8 - - - 107.8 - - - 108.5 - - - 109.3 - - - 110.0 - - - 110.5 - - - 110.8 - - - 111.8 - - - 112.8 - - - 113.3 - - - 113.5 - - - 113.5 - - - 113.8 - - - 114.8 - - - 115.8 - - - 118.5 - - - 119.5 - - - 121.3 - - - 122.0 - - - 122.8 - - - 123.5 - - - 126.3 - - - 128.0 - - - 129.0 - - - 130.0 - - - 130.8 - - - 131.8 - - - 132.3 - - - 132.8 - - - 135.8 - - - 135.5 - - - 132.5 - - - 134.0 - - - 136.8 - - - 137.5 - - - 137.3 - - - 137.5 - - - 137.3 - - - 134.8 - - - 132.3 - - - 129.5 - - - + + + simple + + gpx.studio + + + + + simple + Cycling + + + 109.0 + + + 110.8 + + + 110.3 + + + 110.0 + + + 110.3 + + + 109.3 + + + 107.0 + + + 106.0 + + + 108.5 + + + 109.8 + + + 110.8 + + + 112.0 + + + 112.8 + + + 113.5 + + + 114.3 + + + 115.3 + + + 114.8 + + + 114.3 + + + 114.3 + + + 114.5 + + + 115.0 + + + 115.8 + + + 115.8 + + + 116.0 + + + 115.8 + + + 115.5 + + + 114.5 + + + 112.5 + + + 110.8 + + + 109.0 + + + 106.3 + + + 104.3 + + + 104.0 + + + 103.8 + + + 103.3 + + + 103.5 + + + 103.8 + + + 104.8 + + + 105.8 + + + 106.8 + + + 107.8 + + + 108.5 + + + 109.3 + + + 110.0 + + + 110.5 + + + 110.8 + + + 111.8 + + + 112.8 + + + 113.3 + + + 113.5 + + + 113.5 + + + 113.8 + + + 114.8 + + + 115.8 + + + 118.5 + + + 119.5 + + + 121.3 + + + 122.0 + + + 122.8 + + + 123.5 + + + 126.3 + + + 128.0 + + + 129.0 + + + 130.0 + + + 130.8 + + + 131.8 + + + 132.3 + + + 132.8 + + + 135.8 + + + 135.5 + + + 132.5 + + + 134.0 + + + 136.8 + + + 137.5 + + + 137.3 + + + 137.5 + + + 137.3 + + + 134.8 + + + 132.3 + + + 129.5 + + + \ No newline at end of file diff --git a/gpx/test-data/with_segments.gpx b/gpx/test-data/with_segments.gpx index 0c45a9b7..05690acb 100644 --- a/gpx/test-data/with_segments.gpx +++ b/gpx/test-data/with_segments.gpx @@ -1,249 +1,253 @@ - - - with_segments - - gpx.studio - - - - - with_segments - Cycling - - - 109.0 - - - 110.8 - - - 110.3 - - - 110.0 - - - 110.3 - - - 109.3 - - - 107.0 - - - 106.0 - - - 108.5 - - - 109.8 - - - 110.8 - - - 112.0 - - - 112.8 - - - 113.5 - - - 114.3 - - - 115.3 - - - 114.8 - - - 114.3 - - - 114.3 - - - 114.5 - - - 115.0 - - - 115.8 - - - 115.8 - - - 116.0 - - - 115.8 - - - 115.5 - - - 114.5 - - - 112.5 - - - 110.8 - - - 109.0 - - - 106.3 - - - 104.3 - - - 104.0 - - - 103.8 - - - 103.3 - - - 103.5 - - - 103.8 - - - 104.8 - - - 105.8 - - - 106.8 - - - 107.8 - - - 108.5 - - - 109.3 - - - 110.0 - - - 110.5 - - - 110.8 - - - 111.8 - - - 112.8 - - - 113.3 - - - - - 115.5 - - - 115.8 - - - 118.5 - - - 119.5 - - - 121.3 - - - 122.0 - - - 122.8 - - - 123.5 - - - 126.3 - - - 128.0 - - - 129.0 - - - 130.0 - - - 130.8 - - - 131.8 - - - 132.3 - - - 132.8 - - - 135.8 - - - 135.5 - - - 132.5 - - - 134.0 - - - 136.8 - - - 137.5 - - - 137.3 - - - 137.5 - - - 137.3 - - - 134.8 - - - 132.3 - - - 129.5 - - - + + + with_segments + + gpx.studio + + + + + with_segments + Cycling + + + 109.0 + + + 110.8 + + + 110.3 + + + 110.0 + + + 110.3 + + + 109.3 + + + 107.0 + + + 106.0 + + + 108.5 + + + 109.8 + + + 110.8 + + + 112.0 + + + 112.8 + + + 113.5 + + + 114.3 + + + 115.3 + + + 114.8 + + + 114.3 + + + 114.3 + + + 114.5 + + + 115.0 + + + 115.8 + + + 115.8 + + + 116.0 + + + 115.8 + + + 115.5 + + + 114.5 + + + 112.5 + + + 110.8 + + + 109.0 + + + 106.3 + + + 104.3 + + + 104.0 + + + 103.8 + + + 103.3 + + + 103.5 + + + 103.8 + + + 104.8 + + + 105.8 + + + 106.8 + + + 107.8 + + + 108.5 + + + 109.3 + + + 110.0 + + + 110.5 + + + 110.8 + + + 111.8 + + + 112.8 + + + 113.3 + + + + + 115.5 + + + 115.8 + + + 118.5 + + + 119.5 + + + 121.3 + + + 122.0 + + + 122.8 + + + 123.5 + + + 126.3 + + + 128.0 + + + 129.0 + + + 130.0 + + + 130.8 + + + 131.8 + + + 132.3 + + + 132.8 + + + 135.8 + + + 135.5 + + + 132.5 + + + 134.0 + + + 136.8 + + + 137.5 + + + 137.3 + + + 137.5 + + + 137.3 + + + 134.8 + + + 132.3 + + + 129.5 + + + \ No newline at end of file diff --git a/gpx/test-data/with_time.gpx b/gpx/test-data/with_time.gpx index b69a2841..f39d90ad 100644 --- a/gpx/test-data/with_time.gpx +++ b/gpx/test-data/with_time.gpx @@ -1,336 +1,340 @@ - - - with_time - - gpx.studio - - - - - with_time - Cycling - - - 109.0 - - - - 110.8 - - - - 110.3 - - - - 110.0 - - - - 110.3 - - - - 109.3 - - - - 107.0 - - - - 106.0 - - - - 108.5 - - - - 109.8 - - - - 110.8 - - - - 112.0 - - - - 112.8 - - - - 113.5 - - - - 114.3 - - - - 115.3 - - - - 114.8 - - - - 114.3 - - - - 114.3 - - - - 114.5 - - - - 115.0 - - - - 115.8 - - - - 115.8 - - - - 116.0 - - - - 115.8 - - - - 115.5 - - - - 114.5 - - - - 112.5 - - - - 110.8 - - - - 109.0 - - - - 106.3 - - - - 104.3 - - - - 104.0 - - - - 103.8 - - - - 103.3 - - - - 103.5 - - - - 103.8 - - - - 104.8 - - - - 105.8 - - - - 106.8 - - - - 107.8 - - - - 108.5 - - - - 109.3 - - - - 110.0 - - - - 110.5 - - - - 110.8 - - - - 111.8 - - - - 112.8 - - - - 113.3 - - - - 113.5 - - - - 113.5 - - - - 113.8 - - - - 114.8 - - - - 115.8 - - - - 118.5 - - - - 119.5 - - - - 121.3 - - - - 122.0 - - - - 122.8 - - - - 123.5 - - - - 126.3 - - - - 128.0 - - - - 129.0 - - - - 130.0 - - - - 130.8 - - - - 131.8 - - - - 132.3 - - - - 132.8 - - - - 135.8 - - - - 135.5 - - - - 132.5 - - - - 134.0 - - - - 136.8 - - - - 137.5 - - - - 137.3 - - - - 137.5 - - - - 137.3 - - - - 134.8 - - - - 132.3 - - - - 129.5 - - - - + + + with_time + + gpx.studio + + + + + with_time + Cycling + + + 109.0 + + + + 110.8 + + + + 110.3 + + + + 110.0 + + + + 110.3 + + + + 109.3 + + + + 107.0 + + + + 106.0 + + + + 108.5 + + + + 109.8 + + + + 110.8 + + + + 112.0 + + + + 112.8 + + + + 113.5 + + + + 114.3 + + + + 115.3 + + + + 114.8 + + + + 114.3 + + + + 114.3 + + + + 114.5 + + + + 115.0 + + + + 115.8 + + + + 115.8 + + + + 116.0 + + + + 115.8 + + + + 115.5 + + + + 114.5 + + + + 112.5 + + + + 110.8 + + + + 109.0 + + + + 106.3 + + + + 104.3 + + + + 104.0 + + + + 103.8 + + + + 103.3 + + + + 103.5 + + + + 103.8 + + + + 104.8 + + + + 105.8 + + + + 106.8 + + + + 107.8 + + + + 108.5 + + + + 109.3 + + + + 110.0 + + + + 110.5 + + + + 110.8 + + + + 111.8 + + + + 112.8 + + + + 113.3 + + + + 113.5 + + + + 113.5 + + + + 113.8 + + + + 114.8 + + + + 115.8 + + + + 118.5 + + + + 119.5 + + + + 121.3 + + + + 122.0 + + + + 122.8 + + + + 123.5 + + + + 126.3 + + + + 128.0 + + + + 129.0 + + + + 130.0 + + + + 130.8 + + + + 131.8 + + + + 132.3 + + + + 132.8 + + + + 135.8 + + + + 135.5 + + + + 132.5 + + + + 134.0 + + + + 136.8 + + + + 137.5 + + + + 137.3 + + + + 137.5 + + + + 137.3 + + + + 134.8 + + + + 132.3 + + + + 129.5 + + + + \ No newline at end of file diff --git a/gpx/test-data/with_waypoint.gpx b/gpx/test-data/with_waypoint.gpx index 6e641e2b..c908b051 100644 --- a/gpx/test-data/with_waypoint.gpx +++ b/gpx/test-data/with_waypoint.gpx @@ -1,263 +1,267 @@ - - - with_waypoint - - gpx.studio - - - - - 122.0 - Waypoint - Comment - Description - Bike Trail - - - with_waypoint - Cycling - - - 109.0 - - - 110.8 - - - 110.3 - - - 110.0 - - - 110.3 - - - 109.3 - - - 107.0 - - - 106.0 - - - 108.5 - - - 109.8 - - - 110.8 - - - 112.0 - - - 112.8 - - - 113.5 - - - 114.3 - - - 115.3 - - - 114.8 - - - 114.3 - - - 114.3 - - - 114.5 - - - 115.0 - - - 115.8 - - - 115.8 - - - 116.0 - - - 115.8 - - - 115.5 - - - 114.5 - - - 112.5 - - - 110.8 - - - 109.0 - - - 106.3 - - - 104.3 - - - 104.0 - - - 103.8 - - - 103.3 - - - 103.5 - - - 103.8 - - - 104.8 - - - 105.8 - - - 106.8 - - - 107.8 - - - 108.5 - - - 109.3 - - - 110.0 - - - 110.5 - - - 110.8 - - - 111.8 - - - 112.8 - - - 113.3 - - - 113.5 - - - 113.5 - - - 113.8 - - - 114.8 - - - 115.8 - - - 118.5 - - - 119.5 - - - 121.3 - - + + + with_waypoint + + gpx.studio + + + + 122.0 - - - 122.8 - - - 123.5 - - - 126.3 - - - 128.0 - - - 129.0 - - - 130.0 - - - 130.8 - - - 131.8 - - - 132.3 - - - 132.8 - - - 135.8 - - - 135.5 - - - 132.5 - - - 134.0 - - - 136.8 - - - 137.5 - - - 137.3 - - - 137.5 - - - 137.3 - - - 134.8 - - - 132.3 - - - 129.5 - - - + Waypoint + Comment + Description + Bike Trail + + + with_waypoint + Cycling + + + 109.0 + + + 110.8 + + + 110.3 + + + 110.0 + + + 110.3 + + + 109.3 + + + 107.0 + + + 106.0 + + + 108.5 + + + 109.8 + + + 110.8 + + + 112.0 + + + 112.8 + + + 113.5 + + + 114.3 + + + 115.3 + + + 114.8 + + + 114.3 + + + 114.3 + + + 114.5 + + + 115.0 + + + 115.8 + + + 115.8 + + + 116.0 + + + 115.8 + + + 115.5 + + + 114.5 + + + 112.5 + + + 110.8 + + + 109.0 + + + 106.3 + + + 104.3 + + + 104.0 + + + 103.8 + + + 103.3 + + + 103.5 + + + 103.8 + + + 104.8 + + + 105.8 + + + 106.8 + + + 107.8 + + + 108.5 + + + 109.3 + + + 110.0 + + + 110.5 + + + 110.8 + + + 111.8 + + + 112.8 + + + 113.3 + + + 113.5 + + + 113.5 + + + 113.8 + + + 114.8 + + + 115.8 + + + 118.5 + + + 119.5 + + + 121.3 + + + 122.0 + + + 122.8 + + + 123.5 + + + 126.3 + + + 128.0 + + + 129.0 + + + 130.0 + + + 130.8 + + + 131.8 + + + 132.3 + + + 132.8 + + + 135.8 + + + 135.5 + + + 132.5 + + + 134.0 + + + 136.8 + + + 137.5 + + + 137.3 + + + 137.5 + + + 137.3 + + + 134.8 + + + 132.3 + + + 129.5 + + + \ No newline at end of file diff --git a/gpx/test/io.test.ts b/gpx/test/io.test.ts index dd8e1fd5..a08281e1 100644 --- a/gpx/test/io.test.ts +++ b/gpx/test/io.test.ts @@ -1,17 +1,17 @@ import * as fs from 'fs'; -import { parseGPX } from '../src/io'; +import { parseGPX, buildGPX } from '../src/io'; -describe("Parsing tests", () => { +describe("Parsing", () => { it("Simple", () => { const path = "test-data/simple.gpx"; const data = fs.readFileSync(path, 'utf8'); const result = parseGPX(data); - expect(result.creator).toBe("https://gpx.studio"); + expect(result.attributes.creator).toBe("https://gpx.studio"); expect(result.metadata.name).toBe("simple"); expect(result.metadata.author.name).toBe("gpx.studio"); - expect(result.metadata.author.link.href).toBe("https://gpx.studio"); + expect(result.metadata.author.link.attributes.href).toBe("https://gpx.studio"); expect(result.trk.length).toBe(1); @@ -25,13 +25,14 @@ describe("Parsing tests", () => { for (let i = 0; i < segment.trkpt.length; i++) { const point = segment.trkpt[i]; - expect(point).toHaveProperty('lat'); - expect(point).toHaveProperty('lon'); + expect(point).toHaveProperty('attributes'); + expect(point.attributes).toHaveProperty('lat'); + expect(point.attributes).toHaveProperty('lon'); expect(point).toHaveProperty('ele'); } - expect(segment.trkpt[0].lat).toBe(50.790867); - expect(segment.trkpt[0].lon).toBe(4.404968); + expect(segment.trkpt[0].attributes.lat).toBe(50.790867); + expect(segment.trkpt[0].attributes.lon).toBe(4.404968); expect(segment.trkpt[0].ele).toBe(109.0); }); @@ -74,8 +75,8 @@ describe("Parsing tests", () => { expect(result.wpt.length).toBe(1); const waypoint = result.wpt[0]; - expect(waypoint.lat).toBe(50.7836710064975); - expect(waypoint.lon).toBe(4.410764082658738); + expect(waypoint.attributes.lat).toBe(50.7836710064975); + expect(waypoint.attributes.lon).toBe(4.410764082658738); expect(waypoint.ele).toBe(122.0); expect(waypoint.name).toBe("Waypoint"); expect(waypoint.cmt).toBe("Comment"); @@ -109,12 +110,12 @@ describe("Parsing tests", () => { for (let i = 0; i < segment.trkpt.length; i++) { expect(segment.trkpt[i]).toHaveProperty('extensions'); - expect(segment.trkpt[i].extensions).toHaveProperty('TrackPointExtension'); - expect(segment.trkpt[i].extensions.TrackPointExtension).toHaveProperty('hr'); + expect(segment.trkpt[i].extensions).toHaveProperty('gpxtpx:TrackPointExtension'); + expect(segment.trkpt[i].extensions['gpxtpx:TrackPointExtension']).toHaveProperty('gpxtpx:hr'); } - expect(segment.trkpt[0].extensions.TrackPointExtension.hr).toBe(150); - expect(segment.trkpt[segment.trkpt.length - 1].extensions.TrackPointExtension.hr).toBe(160); + expect(segment.trkpt[0].extensions['gpxtpx:TrackPointExtension']['gpxtpx:hr']).toBe(150); + expect(segment.trkpt[segment.trkpt.length - 1].extensions['gpxtpx:TrackPointExtension']['gpxtpx:hr']).toBe(160); }); it("Cadence", () => { @@ -127,12 +128,12 @@ describe("Parsing tests", () => { for (let i = 0; i < segment.trkpt.length; i++) { expect(segment.trkpt[i]).toHaveProperty('extensions'); - expect(segment.trkpt[i].extensions).toHaveProperty('TrackPointExtension'); - expect(segment.trkpt[i].extensions.TrackPointExtension).toHaveProperty('cad'); + expect(segment.trkpt[i].extensions).toHaveProperty('gpxtpx:TrackPointExtension'); + expect(segment.trkpt[i].extensions['gpxtpx:TrackPointExtension']).toHaveProperty('gpxtpx:cad'); } - expect(segment.trkpt[0].extensions.TrackPointExtension.cad).toBe(80); - expect(segment.trkpt[segment.trkpt.length - 1].extensions.TrackPointExtension.cad).toBe(90); + expect(segment.trkpt[0].extensions['gpxtpx:TrackPointExtension']['gpxtpx:cad']).toBe(80); + expect(segment.trkpt[segment.trkpt.length - 1].extensions['gpxtpx:TrackPointExtension']['gpxtpx:cad']).toBe(90); }); it("Temperature", () => { @@ -145,12 +146,12 @@ describe("Parsing tests", () => { for (let i = 0; i < segment.trkpt.length; i++) { expect(segment.trkpt[i]).toHaveProperty('extensions'); - expect(segment.trkpt[i].extensions).toHaveProperty('TrackPointExtension'); - expect(segment.trkpt[i].extensions.TrackPointExtension).toHaveProperty('atemp'); + expect(segment.trkpt[i].extensions).toHaveProperty('gpxtpx:TrackPointExtension'); + expect(segment.trkpt[i].extensions['gpxtpx:TrackPointExtension']).toHaveProperty('gpxtpx:atemp'); } - expect(segment.trkpt[0].extensions.TrackPointExtension.atemp).toBe(21); - expect(segment.trkpt[segment.trkpt.length - 1].extensions.TrackPointExtension.atemp).toBe(22); + expect(segment.trkpt[0].extensions['gpxtpx:TrackPointExtension']['gpxtpx:atemp']).toBe(21); + expect(segment.trkpt[segment.trkpt.length - 1].extensions['gpxtpx:TrackPointExtension']['gpxtpx:atemp']).toBe(22); }); it("Power 1", () => { @@ -163,12 +164,12 @@ describe("Parsing tests", () => { for (let i = 0; i < segment.trkpt.length; i++) { expect(segment.trkpt[i]).toHaveProperty('extensions'); - expect(segment.trkpt[i].extensions).toHaveProperty('PowerExtension'); - expect(segment.trkpt[i].extensions.PowerExtension).toHaveProperty('PowerInWatts'); + expect(segment.trkpt[i].extensions).toHaveProperty('gpxpx:PowerExtension'); + expect(segment.trkpt[i].extensions['gpxpx:PowerExtension']).toHaveProperty('gpxpx:PowerInWatts'); } - expect(segment.trkpt[0].extensions.PowerExtension.PowerInWatts).toBe(200); - expect(segment.trkpt[segment.trkpt.length - 1].extensions.PowerExtension.PowerInWatts).toBe(210); + expect(segment.trkpt[0].extensions['gpxpx:PowerExtension']['gpxpx:PowerInWatts']).toBe(200); + expect(segment.trkpt[segment.trkpt.length - 1].extensions['gpxpx:PowerExtension']['gpxpx:PowerInWatts']).toBe(210); }); it("Power 2", () => { @@ -181,12 +182,12 @@ describe("Parsing tests", () => { for (let i = 0; i < segment.trkpt.length; i++) { expect(segment.trkpt[i]).toHaveProperty('extensions'); - expect(segment.trkpt[i].extensions).toHaveProperty('PowerExtension'); - expect(segment.trkpt[i].extensions.PowerExtension).toHaveProperty('PowerInWatts'); + expect(segment.trkpt[i].extensions).toHaveProperty('gpxpx:PowerExtension'); + expect(segment.trkpt[i].extensions['gpxpx:PowerExtension']).toHaveProperty('gpxpx:PowerInWatts'); } - expect(segment.trkpt[0].extensions.PowerExtension.PowerInWatts).toBe(200); - expect(segment.trkpt[segment.trkpt.length - 1].extensions.PowerExtension.PowerInWatts).toBe(210); + expect(segment.trkpt[0].extensions['gpxpx:PowerExtension']['gpxpx:PowerInWatts']).toBe(200); + expect(segment.trkpt[segment.trkpt.length - 1].extensions['gpxpx:PowerExtension']['gpxpx:PowerInWatts']).toBe(210); }); it("Surface", () => { @@ -199,13 +200,13 @@ describe("Parsing tests", () => { for (let i = 0; i < segment.trkpt.length; i++) { expect(segment.trkpt[i]).toHaveProperty('extensions'); - expect(segment.trkpt[i].extensions).toHaveProperty('TrackPointExtension'); - expect(segment.trkpt[i].extensions.TrackPointExtension).toHaveProperty('Extensions'); - expect(segment.trkpt[i].extensions.TrackPointExtension.Extensions).toHaveProperty('surface'); + expect(segment.trkpt[i].extensions).toHaveProperty('gpxtpx:TrackPointExtension'); + expect(segment.trkpt[i].extensions['gpxtpx:TrackPointExtension']).toHaveProperty('gpxtpx:Extensions'); + expect(segment.trkpt[i].extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions']).toHaveProperty('surface'); } - expect(segment.trkpt[0].extensions.TrackPointExtension.Extensions.surface).toBe("asphalt"); - expect(segment.trkpt[segment.trkpt.length - 1].extensions.TrackPointExtension.Extensions.surface).toBe("cobblestone"); + expect(segment.trkpt[0].extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions'].surface).toBe("asphalt"); + expect(segment.trkpt[segment.trkpt.length - 1].extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions'].surface).toBe("cobblestone"); }); it("Track style", () => { @@ -216,14 +217,148 @@ describe("Parsing tests", () => { const track = result.trk[0]; expect(track).toHaveProperty('extensions'); - expect(track.extensions).toHaveProperty('line'); + expect(track.extensions).toHaveProperty('gpx_style:line'); - expect(track.extensions.line).toHaveProperty('color'); - expect(track.extensions.line).toHaveProperty('opacity'); - expect(track.extensions.line).toHaveProperty('weight'); + expect(track.extensions['gpx_style:line']).toHaveProperty('color'); + expect(track.extensions['gpx_style:line']).toHaveProperty('opacity'); + expect(track.extensions['gpx_style:line']).toHaveProperty('weight'); - expect(track.extensions.line.color).toBe("2d3ee9"); - expect(track.extensions.line.opacity).toBe(0.5); - expect(track.extensions.line.weight).toBe(5); + expect(track.extensions['gpx_style:line'].color).toBe("2d3ee9"); + expect(track.extensions['gpx_style:line'].opacity).toBe(0.5); + expect(track.extensions['gpx_style:line'].weight).toBe(5); + }); +}); + +describe("Building", () => { + it("Simple", () => { + const path = "test-data/simple.gpx"; + const data = fs.readFileSync(path, 'utf8'); + const original = parseGPX(data); + + const built = buildGPX(original); + const rebuilt = parseGPX(built); + + expect(rebuilt).toEqual(original); + }); + + it("Multiple tracks", () => { + const path = "test-data/with_tracks.gpx"; + const data = fs.readFileSync(path, 'utf8'); + const original = parseGPX(data); + + const built = buildGPX(original); + const rebuilt = parseGPX(built); + + expect(rebuilt).toEqual(original); + }); + + it("Multiple segments", () => { + const path = "test-data/with_segments.gpx"; + const data = fs.readFileSync(path, 'utf8'); + const original = parseGPX(data); + + const built = buildGPX(original); + const rebuilt = parseGPX(built); + + expect(rebuilt).toEqual(original); + }); + + it("Waypoint", () => { + const path = "test-data/with_waypoint.gpx"; + const data = fs.readFileSync(path, 'utf8'); + const original = parseGPX(data); + + const built = buildGPX(original); + const rebuilt = parseGPX(built); + + expect(rebuilt).toEqual(original); + }); + + it("Time", () => { + const path = "test-data/with_time.gpx"; + const data = fs.readFileSync(path, 'utf8'); + const original = parseGPX(data); + + const built = buildGPX(original); + const rebuilt = parseGPX(built); + + expect(rebuilt).toEqual(original); + }); + + it("Heart rate", () => { + const path = "test-data/with_hr.gpx"; + const data = fs.readFileSync(path, 'utf8'); + const original = parseGPX(data); + + const built = buildGPX(original); + const rebuilt = parseGPX(built); + + expect(rebuilt).toEqual(original); + }); + + it("Cadence", () => { + const path = "test-data/with_cad.gpx"; + const data = fs.readFileSync(path, 'utf8'); + const original = parseGPX(data); + + const built = buildGPX(original); + const rebuilt = parseGPX(built); + + expect(rebuilt).toEqual(original); + }); + + it("Temperature", () => { + const path = "test-data/with_temp.gpx"; + const data = fs.readFileSync(path, 'utf8'); + const original = parseGPX(data); + + const built = buildGPX(original); + const rebuilt = parseGPX(built); + + expect(rebuilt).toEqual(original); + }); + + it("Power 1", () => { + const path = "test-data/with_power_1.gpx"; + const data = fs.readFileSync(path, 'utf8'); + const original = parseGPX(data); + + const built = buildGPX(original); + const rebuilt = parseGPX(built); + + expect(rebuilt).toEqual(original); + }); + + it("Power 2", () => { + const path = "test-data/with_power_2.gpx"; + const data = fs.readFileSync(path, 'utf8'); + const original = parseGPX(data); + + const built = buildGPX(original); + const rebuilt = parseGPX(built); + + expect(rebuilt).toEqual(original); + }); + + it("Surface", () => { + const path = "test-data/with_surface.gpx"; + const data = fs.readFileSync(path, 'utf8'); + const original = parseGPX(data); + + const built = buildGPX(original); + const rebuilt = parseGPX(built); + + expect(rebuilt).toEqual(original); + }); + + it("Track style", () => { + const path = "test-data/with_style.gpx"; + const data = fs.readFileSync(path, 'utf8'); + const original = parseGPX(data); + + const built = buildGPX(original); + const rebuilt = parseGPX(built); + + expect(rebuilt).toEqual(original); }); }); \ No newline at end of file