1(** GPX streaming writer using xmlm *) 2 3open Types 4 5(** Result binding operators *) 6let (let*) = Result.bind 7 8(** Writer state for streaming *) 9type writer_state = { 10 output : Xmlm.output; 11} 12 13(** Create a new writer state *) 14let make_writer output = { output } 15 16(** Utility functions *) 17 18let convert_attributes attrs = 19 List.map (fun (name, value) -> (("", name), value)) attrs 20 21let output_signal writer signal = 22 try 23 Xmlm.output writer.output signal; 24 Ok () 25 with 26 | Xmlm.Error ((line, col), error) -> 27 Error (Xml_error (Printf.sprintf "XML error at line %d, column %d: %s" 28 line col (Xmlm.error_message error))) 29 | exn -> 30 Error (Invalid_xml (Printexc.to_string exn)) 31 32let output_element_start writer name attrs = 33 output_signal writer (`El_start (("", name), attrs)) 34 35let output_element_end writer = 36 output_signal writer `El_end 37 38let output_data writer text = 39 if text <> "" then 40 output_signal writer (`Data text) 41 else 42 Ok () 43 44let output_text_element writer name text = 45 let* () = output_element_start writer name [] in 46 let* () = output_data writer text in 47 output_element_end writer 48 49let output_optional_text_element writer name = function 50 | Some text -> output_text_element writer name text 51 | None -> Ok () 52 53let output_float_element writer name f = 54 output_text_element writer name (Printf.sprintf "%.6f" f) 55 56let output_optional_float_element writer name = function 57 | Some f -> output_float_element writer name f 58 | None -> Ok () 59 60let output_int_element writer name i = 61 output_text_element writer name (string_of_int i) 62 63let output_optional_int_element writer name = function 64 | Some i -> output_int_element writer name i 65 | None -> Ok () 66 67let output_time_element writer name time = 68 output_text_element writer name (Ptime.to_rfc3339 time) 69 70let output_optional_time_element writer name = function 71 | Some time -> output_time_element writer name time 72 | None -> Ok () 73 74(** Write GPX header and DTD *) 75let write_header writer = 76 let* () = output_signal writer (`Dtd None) in 77 Ok () 78 79(** Write link element *) 80let write_link writer link = 81 let attrs = [(("" , "href"), link.href)] in 82 let* () = output_element_start writer "link" attrs in 83 let* () = output_optional_text_element writer "text" link.text in 84 let* () = output_optional_text_element writer "type" link.type_ in 85 output_element_end writer 86 87(** Write list of links *) 88let write_links writer links = 89 let rec loop = function 90 | [] -> Ok () 91 | link :: rest -> 92 let* () = write_link writer link in 93 loop rest 94 in 95 loop links 96 97(** Write extension content *) 98let rec write_extension_content writer = function 99 | Text text -> output_data writer text 100 | Elements extensions -> write_extensions writer extensions 101 | Mixed (text, extensions) -> 102 let* () = output_data writer text in 103 write_extensions writer extensions 104 105(** Write extensions *) 106and write_extensions writer extensions = 107 let rec loop = function 108 | [] -> Ok () 109 | ext :: rest -> 110 let* () = write_extension writer ext in 111 loop rest 112 in 113 loop extensions 114 115and write_extension writer ext = 116 let name = match ext.namespace with 117 | Some ns -> ns ^ ":" ^ ext.name 118 | None -> ext.name 119 in 120 let* () = output_element_start writer name (convert_attributes ext.attributes) in 121 let* () = write_extension_content writer ext.content in 122 output_element_end writer 123 124(** Write waypoint data (shared by wpt, rtept, trkpt) *) 125let write_waypoint_data writer element_name wpt = 126 let attrs = [ 127 (("", "lat"), Printf.sprintf "%.6f" (latitude_to_float wpt.lat)); 128 (("", "lon"), Printf.sprintf "%.6f" (longitude_to_float wpt.lon)); 129 ] in 130 let* () = output_element_start writer element_name attrs in 131 let* () = output_optional_float_element writer "ele" wpt.ele in 132 let* () = output_optional_time_element writer "time" wpt.time in 133 let* () = (match wpt.magvar with 134 | Some deg -> output_float_element writer "magvar" (degrees_to_float deg) 135 | None -> Ok ()) in 136 let* () = output_optional_float_element writer "geoidheight" wpt.geoidheight in 137 let* () = output_optional_text_element writer "name" wpt.name in 138 let* () = output_optional_text_element writer "cmt" wpt.cmt in 139 let* () = output_optional_text_element writer "desc" wpt.desc in 140 let* () = output_optional_text_element writer "src" wpt.src in 141 let* () = write_links writer wpt.links in 142 let* () = output_optional_text_element writer "sym" wpt.sym in 143 let* () = output_optional_text_element writer "type" wpt.type_ in 144 let* () = (match wpt.fix with 145 | Some fix -> output_text_element writer "fix" (fix_type_to_string fix) 146 | None -> Ok ()) in 147 let* () = output_optional_int_element writer "sat" wpt.sat in 148 let* () = output_optional_float_element writer "hdop" wpt.hdop in 149 let* () = output_optional_float_element writer "vdop" wpt.vdop in 150 let* () = output_optional_float_element writer "pdop" wpt.pdop in 151 let* () = output_optional_float_element writer "ageofdgpsdata" wpt.ageofdgpsdata in 152 let* () = output_optional_int_element writer "dgpsid" wpt.dgpsid in 153 let* () = (if wpt.extensions <> [] then 154 let* () = output_element_start writer "extensions" [] in 155 let* () = write_extensions writer wpt.extensions in 156 output_element_end writer 157 else Ok ()) in 158 output_element_end writer 159 160(** Write waypoint *) 161let write_waypoint writer wpt = 162 write_waypoint_data writer "wpt" wpt 163 164(** Write route point *) 165let write_route_point writer rtept = 166 write_waypoint_data writer "rtept" rtept 167 168(** Write track point *) 169let write_track_point writer trkpt = 170 write_waypoint_data writer "trkpt" trkpt 171 172(** Write person *) 173let write_person writer (p : person) = 174 let* () = output_element_start writer "author" [] in 175 let* () = output_optional_text_element writer "name" p.name in 176 let* () = output_optional_text_element writer "email" p.email in 177 let* () = (match p.link with 178 | Some link -> write_link writer link 179 | None -> Ok ()) in 180 output_element_end writer 181 182(** Write copyright *) 183let write_copyright writer (copyright : copyright) = 184 let attrs = [(("", "author"), copyright.author)] in 185 let* () = output_element_start writer "copyright" attrs in 186 let* () = (match copyright.year with 187 | Some year -> output_int_element writer "year" year 188 | None -> Ok ()) in 189 let* () = output_optional_text_element writer "license" copyright.license in 190 output_element_end writer 191 192(** Write bounds *) 193let write_bounds writer bounds = 194 let attrs = [ 195 (("", "minlat"), Printf.sprintf "%.6f" (latitude_to_float bounds.minlat)); 196 (("", "minlon"), Printf.sprintf "%.6f" (longitude_to_float bounds.minlon)); 197 (("", "maxlat"), Printf.sprintf "%.6f" (latitude_to_float bounds.maxlat)); 198 (("", "maxlon"), Printf.sprintf "%.6f" (longitude_to_float bounds.maxlon)); 199 ] in 200 let* () = output_element_start writer "bounds" attrs in 201 output_element_end writer 202 203(** Write metadata *) 204let write_metadata writer (metadata : metadata) = 205 let* () = output_element_start writer "metadata" [] in 206 let* () = output_optional_text_element writer "name" metadata.name in 207 let* () = output_optional_text_element writer "desc" metadata.desc in 208 let* () = (match metadata.author with 209 | Some author -> write_person writer author 210 | None -> Ok ()) in 211 let* () = (match metadata.copyright with 212 | Some copyright -> write_copyright writer copyright 213 | None -> Ok ()) in 214 let* () = write_links writer metadata.links in 215 let* () = output_optional_time_element writer "time" metadata.time in 216 let* () = output_optional_text_element writer "keywords" metadata.keywords in 217 let* () = (match metadata.bounds with 218 | Some bounds -> write_bounds writer bounds 219 | None -> Ok ()) in 220 let* () = (if metadata.extensions <> [] then 221 let* () = output_element_start writer "extensions" [] in 222 let* () = write_extensions writer metadata.extensions in 223 output_element_end writer 224 else Ok ()) in 225 output_element_end writer 226 227(** Write route *) 228let write_route writer (route : route) = 229 let* () = output_element_start writer "rte" [] in 230 let* () = output_optional_text_element writer "name" route.name in 231 let* () = output_optional_text_element writer "cmt" route.cmt in 232 let* () = output_optional_text_element writer "desc" route.desc in 233 let* () = output_optional_text_element writer "src" route.src in 234 let* () = write_links writer route.links in 235 let* () = output_optional_int_element writer "number" route.number in 236 let* () = output_optional_text_element writer "type" route.type_ in 237 let* () = (if route.extensions <> [] then 238 let* () = output_element_start writer "extensions" [] in 239 let* () = write_extensions writer route.extensions in 240 output_element_end writer 241 else Ok ()) in 242 let* () = 243 let rec loop = function 244 | [] -> Ok () 245 | rtept :: rest -> 246 let* () = write_route_point writer rtept in 247 loop rest 248 in 249 loop route.rtepts 250 in 251 output_element_end writer 252 253(** Write track segment *) 254let write_track_segment writer trkseg = 255 let* () = output_element_start writer "trkseg" [] in 256 let* () = 257 let rec loop = function 258 | [] -> Ok () 259 | trkpt :: rest -> 260 let* () = write_track_point writer trkpt in 261 loop rest 262 in 263 loop trkseg.trkpts 264 in 265 let* () = (if trkseg.extensions <> [] then 266 let* () = output_element_start writer "extensions" [] in 267 let* () = write_extensions writer trkseg.extensions in 268 output_element_end writer 269 else Ok ()) in 270 output_element_end writer 271 272(** Write track *) 273let write_track writer track = 274 let* () = output_element_start writer "trk" [] in 275 let* () = output_optional_text_element writer "name" track.name in 276 let* () = output_optional_text_element writer "cmt" track.cmt in 277 let* () = output_optional_text_element writer "desc" track.desc in 278 let* () = output_optional_text_element writer "src" track.src in 279 let* () = write_links writer track.links in 280 let* () = output_optional_int_element writer "number" track.number in 281 let* () = output_optional_text_element writer "type" track.type_ in 282 let* () = (if track.extensions <> [] then 283 let* () = output_element_start writer "extensions" [] in 284 let* () = write_extensions writer track.extensions in 285 output_element_end writer 286 else Ok ()) in 287 let* () = 288 let rec loop = function 289 | [] -> Ok () 290 | trkseg :: rest -> 291 let* () = write_track_segment writer trkseg in 292 loop rest 293 in 294 loop track.trksegs 295 in 296 output_element_end writer 297 298(** Write complete GPX document *) 299let write_gpx writer gpx = 300 let* () = write_header writer in 301 let attrs = [ 302 (("", "version"), gpx.version); 303 (("", "creator"), gpx.creator); 304 (("", "xmlns"), "http://www.topografix.com/GPX/1/1"); 305 (("http://www.w3.org/2000/xmlns/", "xsi"), "http://www.w3.org/2001/XMLSchema-instance"); 306 (("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation"), "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"); 307 ] in 308 let* () = output_element_start writer "gpx" attrs in 309 let* () = (match gpx.metadata with 310 | Some metadata -> write_metadata writer metadata 311 | None -> Ok ()) in 312 let* () = 313 let rec loop = function 314 | [] -> Ok () 315 | wpt :: rest -> 316 let* () = write_waypoint writer wpt in 317 loop rest 318 in 319 loop gpx.waypoints 320 in 321 let* () = 322 let rec loop = function 323 | [] -> Ok () 324 | rte :: rest -> 325 let* () = write_route writer rte in 326 loop rest 327 in 328 loop gpx.routes 329 in 330 let* () = 331 let rec loop = function 332 | [] -> Ok () 333 | trk :: rest -> 334 let* () = write_track writer trk in 335 loop rest 336 in 337 loop gpx.tracks 338 in 339 let* () = (if gpx.extensions <> [] then 340 let* () = output_element_start writer "extensions" [] in 341 let* () = write_extensions writer gpx.extensions in 342 output_element_end writer 343 else Ok ()) in 344 output_element_end writer 345 346(** Main writing function *) 347let write output gpx = 348 let writer = make_writer output in 349 write_gpx writer gpx 350 351(** Write to string *) 352let write_string gpx = 353 let buffer = Buffer.create 1024 in 354 let output = Xmlm.make_output (`Buffer buffer) in 355 let result = write output gpx in 356 match result with 357 | Ok () -> Ok (Buffer.contents buffer) 358 | Error e -> Error e