ooRexx logo
./code/tryxmldom.rex/var/www/html/ooRexx/wip /** Just a little program to excercise (some of ) the xmlDOM classes. Transforms the XML result of a google maps directions query such as:
  http://maps.googleapis.com/maps/api/directionsxml?origin=Uithoorn,NL&destination=Nieuw+Vennep,NL&mode=bicycling
  
into a GPX file, that can be downloaded/used on smartphones/tablets with apps such as Viewranger, OSMAnd+ and MyTrails. @param xmlFile - the name of the xml-file to proces @return gpxStream - The GPX file to STDOUT (,which can be redirected to a file of course). */ parse arg xmlFile signal on user domException name domException --trace i xmlStream = .stream~new(xmlFile) parser = .xmlParser~new(.dom1Builder~new) dom = parser~parseStream(xmlStream) -- One leg from Google is treated as one GPX track doc = dom~documentElement -- Use the travel mode as content for the tag in GPX and elements travel_mode = doc~getElementsByTagName("travel_mode")~item(0)~firstChild~nodeValue -- Google requires to show the following 2 items copyrights = doc~getElementsByTagName("copyrights") warnings = doc~getElementsByTagName("warning") -- Retrieve from and to address startAddress = doc~getElementsByTagName("start_address")~item(0)~firstChild~nodeValue endAddress = doc~getElementsByTagName("end_address")~item(0)~firstChild~nodeValue -- Get the latitude and longitude boundaries bounds = doc~getElementsByTagName("bounds")~item(0) maxlat = bounds~getElementsByTagName("northeast")~item(0)~childNodes~item(0)~firstChild~nodeValue maxlon = bounds~getElementsByTagName("northeast")~item(0)~childNodes~item(1)~firstChild~nodeValue minlat = bounds~getElementsByTagName("southwest")~item(0)~childNodes~item(0)~firstChild~nodeValue minlon = bounds~getElementsByTagName("southwest")~item(0)~childNodes~item(1)~firstChild~nodeValue -- Now process the route(s) info, assumption is here just 1 leg legs = doc~getElementsByTagName("route")~item(0)~getElementsByTagName("leg") do l=0 to legs~length-1 leg = legs~item(l) legData = .directory~new legData~polyLines = .array~new legData~directions = .array~new legData~routePoints = .array~new legData~distances = .array~new legData~durations = .array~new -- get the encoded polyline strings for each step in this leg encodedPolylines = leg~getElementsByTagName("polyline") -- get the driving instructions for each step in this leg stepDirections = leg~getElementsByTagName("html_instructions") -- get the latitude and longitude for each step start location in this leg stepStartLocations = leg~getElementsByTagName("start_location") -- get the distance for each step in this leg stepDistances = leg~getElementsByTagName("distance") -- get the theoretical duration for each step in this leg stepDurations = leg~getElementsByTagName("duration") -- now process each step steps = leg~getElementsByTagName("step") do s=0 to steps~length-1 -- decode the Google encoded polylin string encPoly = encodedPolylines~item(s)~firstChild~firstChild~nodeValue polyline = decodePoly(encPoly) -- and add to the polyline array legData~polyLines~append(polyLine) -- add the directions for each step legData~directions~append(stepDirections~item(s)~firstChild~nodeValue) -- add latitude longitude info for each turning point in this leg lat = stepStartLocations~item(s)~childNodes~item(0)~firstChild~nodeValue lon = stepStartLocations~item(s)~childNodes~item(1)~firstChild~nodeValue legData~routePoints~append(lat lon) -- append the distance and duration info for each step legData~distances~append(stepDistances~item(s)~childNodes~item(1)~firstChild~nodeValue) legData~durations~append(stepDurations~item(s)~childNodes~item(0)~firstChild~nodeValue) end end out = .xmlDOMImplementation~new~createDocument -- append the retrieved xml PI as first child out~appendChild(dom~firstChild) -- create the gpx-tag and its attributes out~appendChild(out~createElement("gpx")) gpx = out~childNodes~item(1) attr = out~createAttribute("version", "1.1") attr~ownerElement = gpx gpx~setAttributeNode(attr) attr = out~createAttribute("creator", "rji@xs4all.nl") attr~ownerElement = gpx gpx~setAttributeNode(attr) attr = out~createAttribute("xmlns", "http://www.topografix.com/GPX/1/1") attr~ownerElement = gpx gpx~setAttributeNode(attr) attr = out~createAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") attr~ownerElement = gpx gpx~setAttributeNode(attr) attr = out~createAttribute("xsi:schemaLocation","http://www.topografix.com/GPX/1/1/gpx.xsd") attr~ownerElement = gpx gpx~setAttributeNode(attr) -- handle the gpx metadata metadata = gpx~appendChild(out~createElement("metadata")) -- First the copyright notice(s) text = '' do i=0 to copyrights~length-1 text ||= copyrights~item(i)~firstChild~nodeValue || .endofline end text = text~substr(1,text~length-2) textNode = out~createTextNode(text) copyright = out~createElement("copyright") copyright~appendChild(textNode) metadata~appendChild(copyright) -- Second the author info author = out~createElement("author") name = out~createElement("name") text = out~createTextNode("Ruurd J. Idenburg") name~appendChild(text) author~appendChild(name) link = out~createElement("link") attr = out~createAttribute("href", "http://www.idenburg.net") attr~ownerElement = link link~setAttributeNode(attr) author~appendChild(link) metadata~appendChild(author) -- Third the description, i.e the warning(s) desc = out~createElement("desc") text = '' do i=0 to warnings~length-1 text ||= warnings~item(i)~firstChild~nodeValue || .endofline end text = text~substr(1,text~length-2) textNode = out~createTextNode(text) desc~appendChild(textNode) metadata~appendChild(desc) -- Fourth the name, in this case the input filename name = out~createElement("name") text = out~createTextNode(xmlStream~string) name~appendChild(text) metadata~insertBefore(name,metadata~childNodes~item(0)) -- Fifth the keywords, in this case the travel_mode keywords = out~createElement("keywords") text = out~createTextNode(travel_mode) keywords~appendChild(text) metadata~appendChild(keywords) -- Sixth the geographical bounds of the route/track bounds = out~createElement("bounds") attr = out~createAttribute("minlat", minlat) attr~ownerElement = bounds bounds~setAttributeNode(attr) attr = out~createAttribute("minlon", minlon) attr~ownerElement = bounds bounds~setAttributeNode(attr) attr = out~createAttribute("maxlat", maxlat) attr~ownerElement = bounds bounds~setAttributeNode(attr) attr = out~createAttribute("maxlon", maxlon) attr~ownerElement = bounds bounds~setAttributeNode(attr) metadata~appendChild(bounds) -- gpx metadata is handled, now process the route (rte-tag) rte = out~createElement("rte") -- append route as next child to gpx-tag gpx~appendChild(rte) -- the name of the route name = out~createElement("name") rte~appendChild(name) text = out~createTextNode(startAddress"-"endAddress) name~appendChild(text) -- type is travel_mode type = out~createElement("type") rte~appendChild(type) text = out~createTextNode(travel_mode) type~appendChild(text) -- Now do the route points and driving instructions do i=1 to legData~routePoints~items routePoint = legData~routePoints[i] rtept = out~createElement("rtept") -- latitude and longitude are attributes attr = out~createAttribute("lat", routePoint~word(1)) attr~ownerElement = rtept rtept~setAttributeNode(attr) attr = out~createAttribute("lon", routePoint~word(2)) attr~ownerElement = rtept rtept~setAttributeNode(attr) rte~appendChild(rtept) -- cmt-tag contains driving instructions and distance to next turning point cmt = out~createElement("cmt") text = out~createTextNode(doEntities(legData~directions[i])"; Go" legData~distances[i]"." ) cmt~appendChild(text) rtept~appendChild(cmt) end -- Route info handled now do the track(s) -- Create the trk-tag as next child of gpx-tag trk = out~createElement("trk") gpx~appendChild(trk) -- track name name = out~createElement("name") trk~appendChild(name) text = out~createTextNode(startAddress"-"endAddress) name~appendChild(text) -- track type,i.e travel_mode type = out~createElement("type") trk~appendChild(type) text = out~createTextNode(travel_mode) type~appendChild(text) -- a track can have multiple track segments trkseg = out~createElement("trkseg") trk~appendChild(trkseg) -- do the track points do i=1 to legData~polyLines~items -- each polyline is an array of lat lon pairs polyLine = legData~polyLines[i] do j=1 to polyLine~items -- each track point is a lat lon pair trackPoint = polyLine[j] trkpt = out~createElement("trkpt") attr = out~createAttribute("lat", trackPoint~word(1)) attr~ownerElement = trkpt trkpt~setAttributeNode(attr) attr = out~createAttribute("lon", trackPoint~word(2)) attr~ownerElement = trkpt trkpt~setAttributeNode(attr) trkseg~appendChild(trkpt) end end -- End of GPX DOM build, now create the GPX file call domWriter out~childNodes, .queue~new exit /** Encodes entities for xml text @param text - the text with plain entities @return text - the encoded text */ doEntities: Procedure parse arg text text = text~changeStr('&',"&") -- must be first text = text~changeStr('<',"<") text = text~changeStr('>',">") text = text~changeStr("'","'") text = text~changeStr('"',""") return text /** Walks the DOM tree recursively and generates the GPX tags and their contents @param nodes - a xmlNodeList to start the treewalk @param tagStack - a .queue instance to keep track of nested tags @return gpxStream - the gpx contents to STDOUT */ ::routine domWriter use strict arg nodes, tagStack indent = tagStack~items*2 do n=0 to nodes~length-1 node = nodes~item(n) select -- 1 elementNode when node~nodeType=.xmlNode~elementNode then do tag = node~tagName attributes = '' if node~attributes~length>0 then do attrDir = node~attributes~toOorexxDirectory do i over attrDir attrName = attrDir[i]~name attrValue = attrDir[i]~value delimiter = '"' if value~pos('"')>0 then delimiter = "'" if (delimiter='"') then attributes = attributes attrName'="'attrValue'"' else attributes = attributes attrName"='"attrValue"'" end end if attributes~length>0 then attributes = ' 'attributes if node~childNodes~length>0 then do say ' '~copies(indent) || '<'tag || attributes || '>' tagStack~push(tag) call domWriter node~childNodes, tagStack end else do say ' '~copies(indent) || '<'tag || attributes || ">' end end -- 2 attributeNode is handled above in elementNode -- 3 textNode when node~nodeType=.xmlNode~textNode then do say ' '~copies(indent) || node~data end -- 4 CDATASectionNode when node~nodeType=.xmlNode~cdataSectionNode then do say ' '~copies(indent) || "" end -- 5 entityReferenceNode when node~nodeType=.xmlNode~entityReferenceNode then do nop end -- 6 entityNode when node~nodeType=.xmlNode~entityNode then do nop end -- 7 PINode when node~nodeType=.xmlNode~processingInstructionNode then do target = node~target data = node~data say ' '~copies(indent) || "" end -- 8 commentNode when node~nodeType=.xmlNode~commentNode then do data = node~data say ' '~copies(indent) || "" end -- 9 documentNode handled by the caller -- 10 documentTypeNode when node~nodeType=.xmlNode~documentTypeNode then do nop end -- 11 documentFragmentNode when node~nodeType=.xmlNode~documentFragmentNode then do nop end -- 12 notationNode when node~nodeType=.xmlNode~notationNode then do nop end otherwise do --say '====================='node~nodeType nop end end end if tagStack~items>0 then do tag = tagStack~pull indent = tagStack~items*2 say ' '~copies(indent) || "' end return /** Exception catcher */ domException: say condition('A') say condition('D') exit ::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.