Skip to content

Commit

Permalink
Merge pull request #8 from We-Gold/null-empty-feature
Browse files Browse the repository at this point in the history
Remove Null or Empty Fields by Default
  • Loading branch information
We-Gold authored Jun 30, 2024
2 parents b8d6552 + 3183372 commit 94f6468
Show file tree
Hide file tree
Showing 7 changed files with 1,563 additions and 1,438 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ fetch("./somefile.gpx")
})
```

_`parseGPX` has an additional optional argument `removeEmptyFields` which removes empty or null values from the output. It is true by default. This argument is also available in `parseGPXWithCustomParser`._

### Use the Parsed GPX

```js
Expand Down
50 changes: 41 additions & 9 deletions lib/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import {
* Converts the given GPX XML to a JavaScript Object with the ability to convert to GeoJSON.
*
* @param gpxSource A string containing the source GPX XML
* @param removeEmptyFields Whether or not to remove null or undefined fields from the output
* @returns A ParsedGPX with all of the parsed data and a method to convert to GeoJSON
*/
export const parseGPX = (gpxSource: string) => {
export const parseGPX = (gpxSource: string, removeEmptyFields: boolean = true) => {
const parseMethod = (gpxSource: string): Document | null => {
// Verify that we are in a browser
if (typeof document == undefined) return null
Expand All @@ -28,7 +29,7 @@ export const parseGPX = (gpxSource: string) => {
return domParser.parseFromString(gpxSource, "text/xml")
}

return parseGPXWithCustomParser(gpxSource, parseMethod)
return parseGPXWithCustomParser(gpxSource, parseMethod, removeEmptyFields)
}

/**
Expand All @@ -37,11 +38,13 @@ export const parseGPX = (gpxSource: string) => {
*
* @param gpxSource A string containing the source GPX XML
* @param parseGPXToXML An optional method that parses gpx to a usable XML format
* @param removeEmptyFields Whether or not to remove null or undefined fields from the output
* @returns A ParsedGPX with all of the parsed data and a method to convert to GeoJSON
*/
export const parseGPXWithCustomParser = (
gpxSource: string,
parseGPXToXML: (gpxSource: string) => Document | null
parseGPXToXML: (gpxSource: string) => Document | null,
removeEmptyFields: boolean = true
): [null, Error] | [ParsedGPX, null] => {
// Parse the GPX string using the given parse method
const parsedSource = parseGPXToXML(gpxSource)
Expand Down Expand Up @@ -123,7 +126,7 @@ export const parseGPXWithCustomParser = (
time: null,
}

const rawElevation = parseFloat(getElementValue(waypoint, "ele"))
const rawElevation = parseFloat(getElementValue(waypoint, "ele") ?? "")
point.elevation = isNaN(rawElevation) ? null : rawElevation

const rawTime = getElementValue(waypoint, "time")
Expand Down Expand Up @@ -260,7 +263,7 @@ export const parseGPXWithCustomParser = (
point.extensions = extensions
}

const rawElevation = parseFloat(getElementValue(trackPoint, "ele"))
const rawElevation = parseFloat(getElementValue(trackPoint, "ele") ?? "")
point.elevation = isNaN(rawElevation) ? null : rawElevation

const rawTime = getElementValue(trackPoint, "time")
Expand All @@ -276,7 +279,14 @@ export const parseGPXWithCustomParser = (
output.tracks.push(track)
}

return [new ParsedGPX(output), null]
if (removeEmptyFields) {
deleteNullFields(output.metadata)
deleteNullFields(output.waypoints)
deleteNullFields(output.tracks)
deleteNullFields(output.routes)
}

return [new ParsedGPX(output, removeEmptyFields), null]
}

const parseExtensions = (
Expand Down Expand Up @@ -317,13 +327,13 @@ const parseExtensions = (
* @param tag The tag of the child element that contains the desired data (e.g. "time" or "name")
* @returns A string containing the desired value
*/
const getElementValue = (parent: Element, tag: string): string => {
const getElementValue = (parent: Element, tag: string): string | null => {
const element = parent.querySelector(tag)

// Extract and return the value within the parent element
if (element !== null) {
return element.firstChild?.textContent ?? element.innerHTML ?? ""
} else return ""
return element.firstChild?.textContent ?? element.innerHTML ?? null
} else return null
}

/**
Expand Down Expand Up @@ -353,3 +363,25 @@ const querySelectDirectDescendant = (
} else return null
}
}

export const deleteNullFields = <T>(object: T) => {
// Return non-object values as-is
if (typeof object !== 'object' || object === null || object === undefined) {
return
}

// Remove null fields from arrays
if (Array.isArray(object)) {
object.forEach(deleteNullFields)
return
}

// Recursively remove null fields from object
for (const [key, value] of Object.entries(object)) {
if (value == null || value == undefined) {
delete (object as { [key: string]: any })[key]
} else {
deleteNullFields(value)
}
}
}
8 changes: 7 additions & 1 deletion lib/parsed_gpx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
WaypointFeature,
} from "./types"

import { deleteNullFields } from "./parse"

/**
* Represents a parsed GPX object.
* All internal data is accessible, and can be converted to GeoJSON.
Expand All @@ -19,13 +21,15 @@ export class ParsedGPX {
public waypoints: Waypoint[]
public tracks: Track[]
public routes: Route[]
private removeEmptyFields: boolean

constructor({ xml, metadata, waypoints, tracks, routes }: ParsedGPXInputs) {
constructor({ xml, metadata, waypoints, tracks, routes }: ParsedGPXInputs, removeEmptyFields: boolean = true) {
this.xml = xml
this.metadata = metadata
this.waypoints = waypoints
this.tracks = tracks
this.routes = routes
this.removeEmptyFields = removeEmptyFields
}

/**
Expand Down Expand Up @@ -107,6 +111,8 @@ export class ParsedGPX {
GeoJSON.features.push(feature)
}

if (this.removeEmptyFields) deleteNullFields(GeoJSON)

return GeoJSON
}
}
34 changes: 17 additions & 17 deletions lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
export type MetaData = {
name: string
description: string
name: string | null
description: string | null
link: Link | null
author: Author | null
time: string
time: string | null
}

export type Waypoint = {
name: string
symbol: string
comment: string
description: string
name: string | null
symbol: string | null
comment: string | null
description: string | null
latitude: number
longitude: number
elevation: number | null
time: Date | null
}

export type Track = {
name: string
comment: string
description: string
src: string
number: string
name: string | null
comment: string | null
description: string | null
src: string | null
number: string | null
link: Link | null
type: string | null
points: Point[]
Expand All @@ -32,11 +32,11 @@ export type Track = {
}

export type Route = {
name: string
comment: string
description: string
src: string
number: string
name: string | null
comment: string | null
description: string | null
src: string | null
number: string | null
link: Link | null
type: string | null
points: Point[]
Expand Down
Loading

0 comments on commit 94f6468

Please sign in to comment.