./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 || ">" || 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) || ""
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) || ""
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'