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'
If you feel inclined to make corrections, suggestions etc., please mail me any.
All content © Ruurd Idenburg, 2007–, 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 a VPS(Virtual Private System) rented from Transip.nl, a Dutch company, falling under Dutch (privacy) laws (I think).

This page updated on by Ruurd Idenburg.