ooRexx logo
   1: #!/usr/bin/env/rexx
   2: /**
   3:   Just a little program to excercise (some of) the xmlDOM classes.
   4: 
   5:   Transforms the XML result of a google maps directions query such as:
   6:   
   7:   http://maps.googleapis.com/maps/api/directionsxml?origin=Uithoorn,NL&destination=Nieuw+Vennep,NL&mode=bicycling
   8:   
9: into a GPX file, that can be downloaded/used on smartphones/tablets with apps such as Viewranger, OSMAnd+ and MyTrails. 10: @param xmlFile - the name of the xml-file to proces 11: @return gpxStream - The GPX file to STDOUT (,which can be redirected to a file of course). 12: */ 13: parse arg xmlFile 14: signal on user domException name domException 15: --trace i 16: xmlStream = .stream~new(xmlFile) 17: parser = .xmlParser~new(.dom1Builder~new) 18: dom = parser~parseStream(xmlStream) 19: -- One leg from Google is treated as one GPX track 20: doc = dom~documentElement 21: -- Use the travel mode as content for the tag in GPX and elements 22: travel_mode = doc~getElementsByTagName("travel_mode")~item(0)~firstChild~nodeValue 23: -- Google requires to show the following 2 items 24: copyrights = doc~getElementsByTagName("copyrights") 25: warnings = doc~getElementsByTagName("warning") 26: -- Retrieve from and to address 27: startAddress = doc~getElementsByTagName("start_address")~item(0)~firstChild~nodeValue 28: endAddress = doc~getElementsByTagName("end_address")~item(0)~firstChild~nodeValue 29: -- Get the latitude and longitude boundaries 30: bounds = doc~getElementsByTagName("bounds")~item(0) 31: maxlat = bounds~getElementsByTagName("northeast")~item(0)~childNodes~item(0)~firstChild~nodeValue 32: maxlon = bounds~getElementsByTagName("northeast")~item(0)~childNodes~item(1)~firstChild~nodeValue 33: minlat = bounds~getElementsByTagName("southwest")~item(0)~childNodes~item(0)~firstChild~nodeValue 34: minlon = bounds~getElementsByTagName("southwest")~item(0)~childNodes~item(1)~firstChild~nodeValue 35: -- Now process the route(s) info, assumption is here just 1 leg 36: legs = doc~getElementsByTagName("route")~item(0)~getElementsByTagName("leg") 37: do l=0 to legs~length-1 38: leg = legs~item(l) 39: legData = .directory~new 40: legData~polyLines = .array~new 41: legData~directions = .array~new 42: legData~routePoints = .array~new 43: legData~distances = .array~new 44: legData~durations = .array~new 45: -- get the encoded polyline strings for each step in this leg 46: encodedPolylines = leg~getElementsByTagName("polyline") 47: -- get the driving instructions for each step in this leg 48: stepDirections = leg~getElementsByTagName("html_instructions") 49: -- get the latitude and longitude for each step start location in this leg 50: stepStartLocations = leg~getElementsByTagName("start_location") 51: -- get the distance for each step in this leg 52: stepDistances = leg~getElementsByTagName("distance") 53: -- get the theoretical duration for each step in this leg 54: stepDurations = leg~getElementsByTagName("duration") 55: -- now process each step 56: steps = leg~getElementsByTagName("step") 57: do s=0 to steps~length-1 58: -- decode the Google encoded polylin string 59: encPoly = encodedPolylines~item(s)~firstChild~firstChild~nodeValue 60: polyline = decodePoly(encPoly) 61: -- and add to the polyline array 62: legData~polyLines~append(polyLine) 63: -- add the directions for each step 64: legData~directions~append(stepDirections~item(s)~firstChild~nodeValue) 65: -- add latitude longitude info for each turning point in this leg 66: lat = stepStartLocations~item(s)~childNodes~item(0)~firstChild~nodeValue 67: lon = stepStartLocations~item(s)~childNodes~item(1)~firstChild~nodeValue 68: legData~routePoints~append(lat lon) 69: -- append the distance and duration info for each step 70: legData~distances~append(stepDistances~item(s)~childNodes~item(1)~firstChild~nodeValue) 71: legData~durations~append(stepDurations~item(s)~childNodes~item(0)~firstChild~nodeValue) 72: end 73: end 74: out = .xmlDOMImplementation~new~createDocument 75: -- append the retrieved xml PI as first child 76: out~appendChild(dom~firstChild) 77: -- create the gpx-tag and its attributes 78: out~appendChild(out~createElement("gpx")) 79: gpx = out~childNodes~item(1) 80: attr = out~createAttribute("version", "1.1") 81: attr~ownerElement = gpx 82: gpx~setAttributeNode(attr) 83: attr = out~createAttribute("creator", "rji@xs4all.nl") 84: attr~ownerElement = gpx 85: gpx~setAttributeNode(attr) 86: attr = out~createAttribute("xmlns", "http://www.topografix.com/GPX/1/1") 87: attr~ownerElement = gpx 88: gpx~setAttributeNode(attr) 89: attr = out~createAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") 90: attr~ownerElement = gpx 91: gpx~setAttributeNode(attr) 92: attr = out~createAttribute("xsi:schemaLocation","http://www.topografix.com/GPX/1/1/gpx.xsd") 93: attr~ownerElement = gpx 94: gpx~setAttributeNode(attr) 95: -- handle the gpx metadata 96: metadata = gpx~appendChild(out~createElement("metadata")) 97: -- First the copyright notice(s) 98: text = '' 99: do i=0 to copyrights~length-1 100: text ||= copyrights~item(i)~firstChild~nodeValue || .endofline 101: end 102: text = text~substr(1,text~length-2) 103: textNode = out~createTextNode(text) 104: copyright = out~createElement("copyright") 105: copyright~appendChild(textNode) 106: metadata~appendChild(copyright) 107: -- Second the author info 108: author = out~createElement("author") 109: name = out~createElement("name") 110: text = out~createTextNode("Ruurd J. Idenburg") 111: name~appendChild(text) 112: author~appendChild(name) 113: link = out~createElement("link") 114: attr = out~createAttribute("href", "http://www.idenburg.net") 115: attr~ownerElement = link 116: link~setAttributeNode(attr) 117: author~appendChild(link) 118: metadata~appendChild(author) 119: -- Third the description, i.e the warning(s) 120: desc = out~createElement("desc") 121: text = '' 122: do i=0 to warnings~length-1 123: text ||= warnings~item(i)~firstChild~nodeValue || .endofline 124: end 125: text = text~substr(1,text~length-2) 126: textNode = out~createTextNode(text) 127: desc~appendChild(textNode) 128: metadata~appendChild(desc) 129: -- Fourth the name, in this case the input filename 130: name = out~createElement("name") 131: text = out~createTextNode(xmlStream~string) 132: name~appendChild(text) 133: metadata~insertBefore(name,metadata~childNodes~item(0)) 134: -- Fifth the keywords, in this case the travel_mode 135: keywords = out~createElement("keywords") 136: text = out~createTextNode(travel_mode) 137: keywords~appendChild(text) 138: metadata~appendChild(keywords) 139: -- Sixth the geographical bounds of the route/track 140: bounds = out~createElement("bounds") 141: attr = out~createAttribute("minlat", minlat) 142: attr~ownerElement = bounds 143: bounds~setAttributeNode(attr) 144: attr = out~createAttribute("minlon", minlon) 145: attr~ownerElement = bounds 146: bounds~setAttributeNode(attr) 147: attr = out~createAttribute("maxlat", maxlat) 148: attr~ownerElement = bounds 149: bounds~setAttributeNode(attr) 150: attr = out~createAttribute("maxlon", maxlon) 151: attr~ownerElement = bounds 152: bounds~setAttributeNode(attr) 153: metadata~appendChild(bounds) 154: -- gpx metadata is handled, now process the route (rte-tag) 155: rte = out~createElement("rte") 156: -- append route as next child to gpx-tag 157: gpx~appendChild(rte) 158: -- the name of the route 159: name = out~createElement("name") 160: rte~appendChild(name) 161: text = out~createTextNode(startAddress"-"endAddress) 162: name~appendChild(text) 163: -- type is travel_mode 164: type = out~createElement("type") 165: rte~appendChild(type) 166: text = out~createTextNode(travel_mode) 167: type~appendChild(text) 168: -- Now do the route points and driving instructions 169: do i=1 to legData~routePoints~items 170: routePoint = legData~routePoints[i] 171: rtept = out~createElement("rtept") 172: -- latitude and longitude are attributes 173: attr = out~createAttribute("lat", routePoint~word(1)) 174: attr~ownerElement = rtept 175: rtept~setAttributeNode(attr) 176: attr = out~createAttribute("lon", routePoint~word(2)) 177: attr~ownerElement = rtept 178: rtept~setAttributeNode(attr) 179: rte~appendChild(rtept) 180: -- cmt-tag contains driving instructions and distance to next turning point 181: cmt = out~createElement("cmt") 182: text = out~createTextNode(doEntities(legData~directions[i])"; Go" legData~distances[i]"." ) 183: cmt~appendChild(text) 184: rtept~appendChild(cmt) 185: end 186: -- Route info handled now do the track(s) 187: -- Create the trk-tag as next child of gpx-tag 188: trk = out~createElement("trk") 189: gpx~appendChild(trk) 190: -- track name 191: name = out~createElement("name") 192: trk~appendChild(name) 193: text = out~createTextNode(startAddress"-"endAddress) 194: name~appendChild(text) 195: -- track type,i.e travel_mode 196: type = out~createElement("type") 197: trk~appendChild(type) 198: text = out~createTextNode(travel_mode) 199: type~appendChild(text) 200: -- a track can have multiple track segments 201: trkseg = out~createElement("trkseg") 202: trk~appendChild(trkseg) 203: -- do the track points 204: do i=1 to legData~polyLines~items 205: -- each polyline is an array of lat lon pairs 206: polyLine = legData~polyLines[i] 207: do j=1 to polyLine~items 208: -- each track point is a lat lon pair 209: trackPoint = polyLine[j] 210: trkpt = out~createElement("trkpt") 211: attr = out~createAttribute("lat", trackPoint~word(1)) 212: attr~ownerElement = trkpt 213: trkpt~setAttributeNode(attr) 214: attr = out~createAttribute("lon", trackPoint~word(2)) 215: attr~ownerElement = trkpt 216: trkpt~setAttributeNode(attr) 217: trkseg~appendChild(trkpt) 218: end 219: end 220: -- End of GPX DOM build, now create the GPX file 221: call domWriter out~childNodes, .queue~new 222: exit 223: 224: /** 225: Encodes entities for xml text 226: @param text - the text with plain entities 227: @return text - the encoded text 228: */ 229: doEntities: Procedure 230: parse arg text 231: text = text~changeStr('&',"&") -- must be first 232: text = text~changeStr('<',"<") 233: text = text~changeStr('>',">") 234: text = text~changeStr("'","'") 235: text = text~changeStr('"',""") 236: return text 237: /** 238: Walks the DOM tree recursively and generates the GPX tags and their contents 239: @param nodes - a xmlNodeList to start the treewalk 240: @param tagStack - a .queue instance to keep track of nested tags 241: @return gpxStream - the gpx contents to STDOUT 242: */ 243: ::routine domWriter 244: use strict arg nodes, tagStack 245: indent = tagStack~items*2 246: do n=0 to nodes~length-1 247: node = nodes~item(n) 248: select 249: -- 1 elementNode 250: when node~nodeType=.xmlNode~elementNode then do 251: tag = node~tagName 252: attributes = '' 253: if node~attributes~length>0 then do 254: attrDir = node~attributes~toOorexxDirectory 255: do i over attrDir 256: attrName = attrDir[i]~name 257: attrValue = attrDir[i]~value 258: delimiter = '"' 259: if value~pos('"')>0 then delimiter = "'" 260: if (delimiter='"') 261: then attributes = attributes attrName'="'attrValue'"' 262: else attributes = attributes attrName"='"attrValue"'" 263: end 264: end 265: if attributes~length>0 then attributes = ' 'attributes 266: if node~childNodes~length>0 then do 267: say ' '~copies(indent) || '<'tag || attributes || '>' 268: tagStack~push(tag) 269: call domWriter node~childNodes, tagStack 270: end 271: else do 272: say ' '~copies(indent) || '<'tag || attributes || ">' 273: end 274: end 275: -- 2 attributeNode is handled above in elementNode 276: -- 3 textNode 277: when node~nodeType=.xmlNode~textNode then do 278: say ' '~copies(indent) || node~data 279: end 280: -- 4 CDATASectionNode 281: when node~nodeType=.xmlNode~cdataSectionNode then do 282: say ' '~copies(indent) || "" 283: end 284: -- 5 entityReferenceNode 285: when node~nodeType=.xmlNode~entityReferenceNode then do 286: nop 287: end 288: -- 6 entityNode 289: when node~nodeType=.xmlNode~entityNode then do 290: nop 291: end 292: -- 7 PINode 293: when node~nodeType=.xmlNode~processingInstructionNode then do 294: target = node~target 295: data = node~data 296: say ' '~copies(indent) || "" 297: end 298: -- 8 commentNode 299: when node~nodeType=.xmlNode~commentNode then do 300: data = node~data 301: say ' '~copies(indent) || "" 302: end 303: -- 9 documentNode handled by the caller 304: -- 10 documentTypeNode 305: when node~nodeType=.xmlNode~documentTypeNode then do 306: nop 307: end 308: -- 11 documentFragmentNode 309: when node~nodeType=.xmlNode~documentFragmentNode then do 310: nop 311: end 312: -- 12 notationNode 313: when node~nodeType=.xmlNode~notationNode then do 314: nop 315: end 316: otherwise do 317: --say '====================='node~nodeType 318: nop 319: end 320: end 321: end 322: if tagStack~items>0 then do 323: tag = tagStack~pull 324: indent = tagStack~items*2 325: say ' '~copies(indent) || "' 326: end 327: return 328: /** 329: Exception catcher 330: */ 331: domException: 332: say condition('A') 333: say condition('D') 334: exit 335: 336: ::requires 'xmlDOM.cls'
All content © Ruurd Idenburg, 2007–2025, except where marked otherwise. All rights reserved. This page is primarily for non-commercial use only. The Idenburg website records no personal information and sets no ‘cookies’. This site is hosted on my on server at my home, falling under Dutch (privacy) laws.

This page updated on Wed, 28 May 2025 10:38:18 +0200.