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 || ">" || tag || '>'
 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) || ""target data"?>"
 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) || "" || tag || '>'
 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'