ooRexx logo tryxmldom.rex
/**
  Just a little program to excercise (some of ) the xmlDOM classes.
 
  Transforms the XML result of a google maps directions query such as:
  <pre>
  http://maps.googleapis.com/maps/api/directionsxml?origin=Uithoorn,NL&destination=Nieuw+Vennep,NL&mode=bicycling
  </pre>
  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 <type> tag in GPX <rte> and <trk> 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('&',"&amp;") -- must be first
  text = text~changeStr('<',"&lt;")
  text = text~changeStr('>',"&gt;")
  text = text~changeStr("'","&apos;")
  text = text~changeStr('"',"&quot;")
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 || "></" || tag || '>'
         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) || "<![CDATA[" || node~data || "]]>"
      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) || "<?"target data"?>"
      end
      -- 8 commentNode
      when node~nodeType=.xmlNode~commentNode then do
        data = node~data
        say ' '~copies(indent) || "<!--"data"-->"
      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) || "</" || tag || '>'
  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–2018, 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 CloudVPS, a Dutch company, falling under Dutch (privacy) laws.

This page updated on Thu, 28 Apr 2016 14:59:56 +0200 by Ruurd Idenburg.