Skip to content

Instantly share code, notes, and snippets.

@juxtin
Created December 20, 2014 06:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save juxtin/07a32f9c51fbee54f49c to your computer and use it in GitHub Desktop.
Save juxtin/07a32f9c51fbee54f49c to your computer and use it in GitHub Desktop.
Parse Garmin TCX files with Clojure
(ns clj-tcx.core
(:require [clojure.data.xml :as xml]
[clojure.java.io :as io]
[clojure.walk :refer [postwalk]]))
(defmulti tcx-walker
"A multimethod that converts xml.Elments into Clojure maps and
LazySeqs into either bare strings or vectors depending on context.
Calls identity otherwise. Should be used to walk a parsed XML tree
to make it look more like an idiomatic Clojure data structure."
type)
(defmethod tcx-walker clojure.data.xml.Element
[{:keys [tag content]}]
(if (and (vector? content) ;; if this is
(pos? (count content)) ;; a non-empty vector
(every? map? content) ;; of k-v pairs
(apply distinct? (mapcat keys content))) ;; where keys are distinct
;; then this is a vector of k-v pairs representing a single
;; map, and we want to merge them together
{tag (apply merge content)}
;; otherwise it's just a named value
{tag content}))
(defmethod tcx-walker clojure.lang.LazySeq
[coll]
(if (and (= 1 (count coll)) ;; if this looks like
(string? (first coll))) ;; ("val")
;; then it's just a needlessly wrapped value
(first coll) ;; and we can unwrap it
;; otherwise it should be a vector
(vec coll)))
;; default is just identity
(defmethod tcx-walker :default [x] x)
(defn xml->map
"Recursively walks the given XML data, converting it into (more)
idiomatic Clojure maps and vectors."
[parsed-xml]
(postwalk tcx-walker parsed-xml))
(defn xml-file->map
[file]
(-> file
slurp
xml/parse-str
xml->map))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment