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'