f54e8ebf0a993b4e92ffd3630e1ba8f4658f09c6
[philipp/winterrodeln/wrpylib.git] / wrpylib / wrmwmarkup.py
1 #!/usr/bin/python2.7
2 # -*- coding: iso-8859-15 -*-
3 # $Id$
4 # $HeadURL$
5 """This module contains winterrodeln specific functions that are prcocessing the MediaWiki markup.
6 """
7 import re
8 import xml.etree.ElementTree
9 import formencode
10 import wrpylib.wrvalidators
11 import wrpylib.mwmarkup
12
13
14 def _conv(fnct, value, fieldname):
15     """Internal function.
16     Like one of the to_xxx functions (e.g. to_bool), but adds the field name to the error message"""
17     try: return fnct(value)
18     except formencode.Invalid as e: raise formencode.Invalid(u"Conversion error in field '%s': %s" % (fieldname, unicode(e)), e.value, e.state)
19
20
21 def rodelbahnbox_to_sledrun(wikitext, sledrun=None):
22     """Converts a sledrun wiki page containing the {{Rodelbahnbox}}
23     to a sledrun. sledrun may be an instance of WrSledrunCache or an "empty" class (object()) (default).
24     Raises a formencode.Invalid exception if the format is not OK or the Rodelbahnbox is not found.
25     :return: (start, end, sledrun) tuple of the Rodelbahnbox."""
26     if sledrun is None:
27         class Sledrun(object): pass
28         sledrun = Sledrun()
29
30     # match Rodelbahnbox
31     start, end = wrpylib.mwmarkup.find_template(wikitext, u'Rodelbahnbox')
32     if start is None: raise formencode.Invalid(u"Rodelbahnbox nicht gefunden", wikitext, None)
33     template_title, properties = wrpylib.mwmarkup.split_template(wikitext[start:end])
34     
35     # process properties
36     for key, value in properties.iteritems():
37         if   key == u'Position': sledrun.position_latitude, sledrun.position_longitude = _conv(wrpylib.wrvalidators.GeoNone().to_python, value, key) # '47.583333 N 15.75 E'
38         elif key == u'Position oben': sledrun.top_latitude, sledrun.top_longitude = _conv(wrpylib.wrvalidators.GeoNone().to_python, value, key) # '47.583333 N 15.75 E'
39         elif key == u'Höhe oben': sledrun.top_elevation = _conv(wrpylib.wrvalidators.UnsignedNone().to_python, value, key) # '2000'
40         elif key == u'Position unten': sledrun.bottom_latitude, sledrun.bottom_longitude = _conv(wrpylib.wrvalidators.GeoNone().to_python, value, key) # '47.583333 N 15.75 E'
41         elif key == u'Höhe unten': sledrun.bottom_elevation = _conv(wrpylib.wrvalidators.UnsignedNone().to_python, value, key) # '1200'
42         elif key == u'Länge': sledrun.length = _conv(wrpylib.wrvalidators.UnsignedNone().to_python, value, key) # 3500
43         elif key == u'Schwierigkeit': sledrun.difficulty = _conv(wrpylib.wrvalidators.GermanDifficulty().to_python, value, key) # 'mittel' elif key == u'Lawinen': sledrun.avalanches = _conv(wrpylib.wrvalidators.GermanAvalanches().to_python, value, key) # 'kaum'
44         elif key == u'Lawinen': sledrun.avalanches = _conv(wrpylib.wrvalidators.GermanAvalanches().to_python, value, key) # 'kaum'
45         elif key == u'Betreiber': sledrun.operator = _conv(wrpylib.wrvalidators.UnicodeNone().to_python, value, key) # 'Max Mustermann'
46         elif key == u'Öffentliche Anreise': sledrun.public_transport = _conv(wrpylib.wrvalidators.GermanPublicTransport().to_python, value, key) # 'Mittelmäßig'
47         elif key == u'Aufstieg möglich': sledrun.walkup_possible = _conv(wrpylib.wrvalidators.GermanBoolNone().to_python, value, key) # 'Ja'
48         elif key == u'Aufstieg getrennt': sledrun.walkup_separate, sledrun.walkup_separate_comment = _conv(wrpylib.wrvalidators.GermanTristateFloatComment().to_python, value, key) # 'Ja'
49         elif key == u'Gehzeit': sledrun.walkup_time = _conv(wrpylib.wrvalidators.UnsignedNone().to_python, value, key) # 90
50         elif key == u'Aufstiegshilfe': sledrun.lift, sledrun.lift_details = _conv(wrpylib.wrvalidators.GermanLift().to_python, value, key) # 'Gondel (unterer Teil)'
51         elif key == u'Beleuchtungsanlage': sledrun.night_light, sledrun.night_light_comment = _conv(wrpylib.wrvalidators.GermanTristateFloatComment().to_python, value, key)
52         elif key == u'Beleuchtungstage': sledrun.night_light_days, sledrun.night_light_days_comment = _conv(wrpylib.wrvalidators.UnsignedCommentNone(7).to_python, value, key) # '3 (Montag, Mittwoch, Freitag)'
53         elif key == u'Rodelverleih': sledrun.sled_rental, sledrun.sled_rental_comment = _conv(wrpylib.wrvalidators.SledRental().to_python, value, key) # 'Talstation Serlesbahnan'
54         elif key == u'Gütesiegel': sledrun.cachet = _conv(wrpylib.wrvalidators.GermanCachet().to_python, value, key) # 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'
55         elif key == u'Webauskunft': sledrun.information_web = _conv(wrpylib.wrvalidators.UrlNeinNone().to_python, value, key) # 'http://www.nösslachhütte.at/page9.php'
56         elif key == u'Telefonauskunft': sledrun.information_phone = _conv(wrpylib.wrvalidators.PhoneCommentListNeinLoopNone(comments_are_optional=False).to_python, value, key) # '+43-664-5487520 (Mitterer Alm)'
57         elif key == u'Bild': sledrun.image = _conv(wrpylib.wrvalidators.UnicodeNone().to_python, value, key)
58         elif key == u'In Übersichtskarte': sledrun.show_in_overview = _conv(wrpylib.wrvalidators.GermanBoolNone().to_python, value, key)
59         elif key == u'Forumid': sledrun.forum_id = _conv(wrpylib.wrvalidators.UnsignedNeinNone().to_python, value, key)
60         else: raise formencode.Invalid(u"Unbekannte Eigenschaft der Rodelbahnbox: '%s' (mit Wert '%s')" % (key, value), value, None)
61     return start, end, sledrun
62
63
64 def sledrun_to_rodelbahnbox(sledrun, version):
65     """Converts a sledrun class to the {{Rodelbahnbox}} representation.
66     The sledrun class has to have properties like position_latitude, ...
67     See the table sledruncache for field (column) values.
68     :param sledrun: an arbitrary class that contains the right properties
69     :param version: a string specifying the version of the rodelbahnbox zu produce.
70         Version '1.3' and '1.4' are supported."""
71     keys = []
72     values = []
73     keys.append(u'Position')
74     values.append(wrpylib.wrvalidators.GeoNone().from_python((sledrun.position_latitude, sledrun.position_longitude)))
75     keys.append(u'Position oben')
76     values.append(wrpylib.wrvalidators.GeoNone().from_python((sledrun.top_latitude, sledrun.top_longitude)))
77     keys.append(u'Höhe oben')
78     values.append(wrpylib.wrvalidators.UnsignedNone().from_python(sledrun.top_elevation))
79     keys.append(u'Position unten')
80     values.append(wrpylib.wrvalidators.GeoNone().from_python((sledrun.bottom_latitude, sledrun.bottom_longitude)))
81     keys.append(u'Höhe unten')
82     values.append(wrpylib.wrvalidators.UnsignedNone().from_python(sledrun.bottom_elevation))
83     keys.append(u'Länge')
84     values.append(wrpylib.wrvalidators.UnsignedNone().from_python(sledrun.length))
85     keys.append(u'Schwierigkeit')
86     values.append(wrpylib.wrvalidators.GermanDifficulty().from_python(sledrun.difficulty))
87     keys.append(u'Lawinen')
88     values.append(wrpylib.wrvalidators.GermanAvalanches().from_python(sledrun.avalanches))
89     keys.append(u'Betreiber')
90     values.append(wrpylib.wrvalidators.UnicodeNone().from_python(sledrun.operator))
91     keys.append(u'Öffentliche Anreise')
92     values.append(wrpylib.wrvalidators.GermanPublicTransport().from_python(sledrun.public_transport))
93     if version == '1.4':
94         keys.append(u'Aufstieg möglich')
95         values.append(wrpylib.wrvalidators.GermanBoolNone().from_python(sledrun.walkup_possible))
96     keys.append(u'Aufstieg getrennt')
97     values.append(wrpylib.wrvalidators.GermanTristateFloatComment().from_python((sledrun.walkup_separate, sledrun.walkup_separate_comment)))
98     keys.append(u'Gehzeit')
99     values.append(wrpylib.wrvalidators.UnsignedNone().from_python(sledrun.walkup_time))
100     keys.append(u'Aufstiegshilfe')
101     values.append(wrpylib.wrvalidators.GermanLift().from_python((sledrun.lift, sledrun.lift_details)))
102     keys.append(u'Beleuchtungsanlage')
103     values.append(wrpylib.wrvalidators.GermanTristateFloatComment().from_python((sledrun.night_light, sledrun.night_light_comment)))
104     keys.append(u'Beleuchtungstage')
105     values.append(wrpylib.wrvalidators.UnsignedCommentNone(max=7).from_python((sledrun.night_light_days, sledrun.night_light_days_comment)))
106     keys.append(u'Rodelverleih')
107     values.append(wrpylib.wrvalidators.SledRental().from_python((sledrun.sled_rental, sledrun.sled_rental_comment)))
108     keys.append(u'Gütesiegel')
109     values.append(wrpylib.wrvalidators.GermanCachet().from_python(sledrun.cachet))
110     keys.append(u'Webauskunft')
111     values.append(wrpylib.wrvalidators.UrlNeinNone().from_python(sledrun.information_web))
112     keys.append(u'Telefonauskunft')
113     values.append(wrpylib.wrvalidators.PhoneCommentListNeinLoopNone(comments_are_optional=False).from_python(sledrun.information_phone))
114     keys.append(u'Bild')
115     values.append(wrpylib.wrvalidators.UnicodeNone().from_python(sledrun.image))
116     keys.append(u'In Übersichtskarte')
117     values.append(wrpylib.wrvalidators.GermanBoolNone().from_python(sledrun.show_in_overview))
118     keys.append(u'Forumid')
119     values.append(wrpylib.wrvalidators.UnsignedNeinNone().from_python(sledrun.forum_id))
120     return wrpylib.mwmarkup.create_template(u'Rodelbahnbox', [], keys, values, True, 20)
121
122
123 def gasthausbox_to_inn(wikitext, inn=None):
124     """Converts a inn wiki page containing a {{Gasthausbox}} to an inn.
125     raises a formencode.Invalid exception if an error occurs.
126     :return: (start, end, inn) tuple."""
127     if inn is None:
128         class Inn(object): pass
129         inn = Inn()
130
131     # Match Gasthausbox
132     start, end = wrpylib.mwmarkup.find_template(wikitext, u'Gasthausbox')
133     if start is None: raise formencode.Invalid(u"No 'Gasthausbox' found", wikitext, None)
134     template_title, properties = wrpylib.mwmarkup.split_template(wikitext[start:end])
135
136     # Process properties
137     for key, value in properties.iteritems():
138         if   key == u'Position': inn.position_latitude, inn.position_longitude = _conv(wrpylib.wrvalidators.GeoNone().to_python, value, key) # '47.583333 N 15.75 E'
139         elif key == u'Höhe': inn.position_elevation = _conv(wrpylib.wrvalidators.UnsignedNone().to_python, value, key)
140         elif key == u'Betreiber': inn.operator = _conv(wrpylib.wrvalidators.UnicodeNone().to_python, value, key)
141         elif key == u'Sitzplätze': inn.seats = _conv(wrpylib.wrvalidators.UnsignedNone().to_python, value, key)
142         elif key == u'Übernachtung': inn.overnight, inn.overnight_comment = _conv(wrpylib.wrvalidators.BoolUnicodeTupleValidator().to_python, value, key)
143         elif key == u'Rauchfrei': inn.nonsmoker_area, inn.smoker_area = _conv(wrpylib.wrvalidators.GermanTristateTuple().to_python, value, key)
144         elif key == u'Rodelverleih': inn.sled_rental, inn.sled_rental_comment = _conv(wrpylib.wrvalidators.BoolUnicodeTupleValidator().to_python, value, key)
145         elif key == u'Handyempfang': inn.mobile_provider = _conv(wrpylib.wrvalidators.ValueCommentListNeinLoopNone().to_python, value, key)
146         elif key == u'Homepage': inn.homepage = _conv(wrpylib.wrvalidators.UrlNeinNone().to_python, value, key)
147         elif key == u'E-Mail': inn.email_list = _conv(wrpylib.wrvalidators.EmailCommentListNeinLoopNone(allow_masked_email=True).to_python, value, key)
148         elif key == u'Telefon': inn.phone_list = _conv(wrpylib.wrvalidators.PhoneCommentListNeinLoopNone(comments_are_optional=True).to_python, value, key)
149         elif key == u'Bild': inn.image = _conv(wrpylib.wrvalidators.UnicodeNone().to_python, value, key)
150         elif key == u'Rodelbahnen': inn.sledding_list = _conv(wrpylib.wrvalidators.WikiPageListLoopNone().to_python, value, key)
151         else: raise formencode.Invalid(u"Unbekannte Eigenschaft der Gasthausbox: '%s' (mit Wert '%s')" % (key, value), value, None)
152     return start, end, inn
153
154
155 def inn_to_gasthausbox(inn):
156     """Converts the inn class to the {{Gasthausbox}} representation."""
157     keys = []
158     values = []
159     keys.append(u'Position')
160     values.append(wrpylib.wrvalidators.GeoNone().from_python((inn.position_latitude, inn.position_longitude)))
161     keys.append(u'Höhe')
162     values.append(wrpylib.wrvalidators.UnsignedNone().from_python(inn.position_elevation))
163     keys.append(u'Betreiber')
164     values.append(wrpylib.wrvalidators.UnicodeNone().from_python(inn.operator))
165     keys.append(u'Sitzplätze')
166     values.append(wrpylib.wrvalidators.UnsignedNone().from_python(inn.seats))
167     keys.append(u'Übernachtung')
168     values.append(wrpylib.wrvalidators.BoolUnicodeTupleValidator().from_python((inn.overnight, inn.overnight_comment)))
169     keys.append(u'Rauchfrei')
170     values.append(wrpylib.wrvalidators.GermanTristateTuple().from_python((inn.nonsmoker_area, inn.smoker_area)))
171     keys.append(u'Rodelverleih')
172     values.append(wrpylib.wrvalidators.BoolUnicodeTupleValidator().from_python((inn.sled_rental, inn.sled_rental_comment)))
173     keys.append(u'Handyempfang')
174     values.append(wrpylib.wrvalidators.ValueCommentListNeinLoopNone().from_python(inn.mobile_provider))
175     keys.append(u'Homepage')
176     values.append(wrpylib.wrvalidators.UrlNeinNone().from_python(inn.homepage))
177     keys.append(u'E-Mail')
178     values.append(wrpylib.wrvalidators.EmailCommentListNeinLoopNone(allow_masked_email=True).from_python(inn.email_list))
179     keys.append(u'Telefon')
180     values.append(wrpylib.wrvalidators.PhoneCommentListNeinLoopNone(comments_are_optional=True).from_python(inn.phone_list))
181     keys.append(u'Bild')
182     values.append(wrpylib.wrvalidators.UnicodeNone().from_python(inn.image))
183     keys.append(u'Rodelbahnen')
184     values.append(wrpylib.wrvalidators.WikiPageListLoopNone().from_python(inn.sledding_list))
185     result = [u'{{Gasthausbox']
186     return wrpylib.mwmarkup.create_template(u'Gasthausbox', [], keys, values, True)
187
188
189 def find_template_latlon_ele(wikitext, template_title):
190     """Finds the first occurance of the '{{template_title|47.076207 N 11.453553 E|1890}}' template
191     and returns the tuple (start, end, lat, lon, ele) or (None, None, None, None, None) if the
192     template was not found. If the template has no valid format, an exception is thrown."""
193     start, end = wrpylib.mwmarkup.find_template(wikitext, template_title)
194     if start is None: return (None,) * 5
195     title, params = wrpylib.mwmarkup.split_template(wikitext[start:end])
196     lat, lon = wrpylib.wrvalidators.GeoNone().to_python(params[u'1'].strip())
197     ele = wrpylib.wrvalidators.UnsignedNone().to_python(params[u'2'].strip())
198     return start, end, lat, lon, ele
199
200
201 def create_template_latlon_ele(template_title, lat, lon, ele):
202     geo = wrpylib.wrvalidators.GeoNone().from_python((lat, lon))
203     if len(geo) == 0: geo = u' '
204     ele = wrpylib.wrvalidators.UnsignedNone().from_python(ele)
205     if len(ele) == 0: ele = u' '
206     return wrpylib.mwmarkup.create_template(template_title, [geo, ele])
207
208
209 def find_template_PositionOben(wikitext):
210     """Same as find_template_latlon_ele with template '{{Position oben|47.076207 N 11.453553 E|1890}}'"""
211     return find_template_latlon_ele(wikitext, u'Position oben')
212
213
214 def create_template_PositionOben(lat, lon, ele):
215     return create_template_latlon_ele(u'Position, oben', lat, lon, ele)
216
217
218 def find_template_PositionUnten(wikitext):
219     """Same as find_template_latlon_ele with template '{{Position unten|47.076207 N 11.453553 E|1890}}'"""
220     return find_template_latlon_ele(wikitext, u'Position unten')
221
222
223 def find_template_unsigned(wikitext, template_title):
224     """Finds the first occurance of the '{{template_title|1890}}' template
225     and returns the tuple (start, end, unsigned_value) or (None, None, None) if the
226     template was not found. If the template has no valid format, an exception is thrown."""
227     start, end = wrpylib.mwmarkup.find_template(wikitext, template_title)
228     if start is None: return (None,) * 3
229     title, params = wrpylib.mwmarkup.split_template(wikitext[start:end])
230     unsigned_value = wrpylib.wrvalidators.UnsignedNone().to_python(params[u'1'].strip())
231     return start, end, unsigned_value
232
233
234 def create_template_unsigned(template_title, unsigned):
235     unsigned = wrpylib.wrvalidators.UnsignedNone().from_python(unsigned)
236     if len(unsigned) == 0: unsigned = u' '
237     return wrpylib.mwmarkup.create_template(template_title, [unsigned])
238
239
240 def find_template_Hoehenunterschied(wikitext):
241     """Same as find_template_unsigned with template '{{Höhenunterschied|350}}'"""
242     return find_template_unsigned(wikitext, u'Höhenunterschied')
243
244
245 def create_template_Hoehenunterschied(ele_diff):
246     return create_template_unsigned(u'Höhenunterschied', ele_diff)
247
248
249 def find_template_Bahnlaenge(wikitext):
250     """Same as find_template_unsigned with template '{{Bahnlänge|4500}}'"""
251     return find_template_unsigned(wikitext, u'Bahnlänge')
252
253
254 def create_template_Bahnlaenge(length):
255     return create_template_unsigned(u'Bahnlänge', length)
256
257
258 def find_template_Gehzeit(wikitext):
259     """Same as find_template_unsigned with template '{{Gehzeit|60}}'"""
260     return find_template_unsigned(wikitext, u'Gehzeit')
261
262
263 def create_template_Gehzeit(walkup_time):
264     return create_template_unsigned(u'Gehzeit', walkup_time)
265
266
267 def find_template_Forumlink(wikitext):
268     """Same as find_template_unsigned with template '{{Forumlink|26}}'"""
269     start, end = wrpylib.mwmarkup.find_template(wikitext, u'Forumlink')
270     if start is None: return (None,) * 3
271     title, params = wrpylib.mwmarkup.split_template(wikitext[start:end])
272     forumid = params[u'1'].strip()
273     if forumid == u'<nummer einfügen>': unsigned_value = None
274     else: unsigned_value = wrpylib.wrvalidators.UnsignedNone().to_python(forumid)
275     return start, end, unsigned_value
276     # return find_template_unsigned(wikitext, u'Forumlink')
277
278
279 def find_template_Parkplatz(wikitext):
280     """Same as find_template_latlon_ele with template '{{Parkplatz|47.076207 N 11.453553 E|1890}}'"""
281     return find_template_latlon_ele(wikitext, u'Parkplatz')
282
283
284 def find_template_Haltestelle(wikitext):
285     """Finds the first occurance of the '{{Haltestelle|Ortsname|Haltestellenname|47.076207 N 11.453553 E|1890}}' template
286     and returns the tuple (start, end, city, stop, lat, lon, ele) or (None, None, None, None, None, None, None) if the
287     template was not found. If the template has no valid format, an exception is thrown."""
288     start, end = wrpylib.mwmarkup.find_template(wikitext, u'Haltestelle')
289     if start is None: return (None,) * 7
290     title, params = wrpylib.mwmarkup.split_template(wikitext[start:end])
291     city = wrpylib.wrvalidators.UnicodeNone().to_python(params[u'1'].strip())
292     stop = wrpylib.wrvalidators.UnicodeNone().to_python(params[u'2'].strip())
293     lat, lon = wrpylib.wrvalidators.GeoNone().to_python(params[u'3'].strip())
294     ele = wrpylib.wrvalidators.UnsignedNone().to_python(params[u'4'].strip())
295     return start, end, city, stop, lat, lon, ele
296
297
298 def find_all_templates(wikitext, find_func):
299     """Returns a list of return values of find_func that searches for a template.
300     Example:
301     >>> find_all_tempaltes(wikitext, find_template_Haltestelle)
302     Returns an empty list if the template was not found at all.
303     """
304     results = []
305     result = find_func(wikitext)
306     start, end = result[:2]
307     while start is not None:
308         results.append(result)
309         result = find_func(wikitext[end:])
310         if result[0] is None:
311             start = None
312         else:
313             start = result[0] + end
314             end  += result[1]
315             result = (start, end) + result[2:]
316     return results
317
318
319 def parse_googlemap(wikitext):
320     """Parses the (unicode) u'<googlemap ...>content</googlemap>' of the googlemap extension
321     out of a page. If wikitext does not contain the googlemaps extension text None is returned.
322     If the googlemap contains invalid formatted lines, a RuntimeError is raised.
323
324     :param wikitext: wikitext containing the template. Example:
325
326     wikitext = '''
327     <googlemap version="0.9" lat="47.113291" lon="11.272337" zoom="15">
328     (Parkplatz)47.114958,11.266026
329     Parkplatz
330     
331     (Gasthaus) 47.114715, 11.266262, Alt Bärnbad (Gasthaus)
332     6#FF014E9A
333     47.114715,11.266262
334     47.114135,11.268381
335     47.113421,11.269322
336     47.11277,11.269979
337     47.112408,11.271119
338     </googlemap>
339     '''
340     :returns: (attributes, GeoJSON as Python datatypes)
341     """
342     center, zoom, coords, paths = wrpylib.mwmarkup.parse_googlemap(wikitext)
343     json_features = []
344
345     # point
346     for point in coords:
347         lon, lat, symbol, title = point
348         properties = {'type': symbol}
349         if title: properties['name'] = title
350         json_features.append({
351             'type': 'feature',
352             'geometry': {'type': 'Point', 'coordinates': [lon, lat]},
353             'properties': properties})
354         
355     # path
356     for path in paths:
357         style, entries = path
358         properties = {'type': 'line'}
359         json_features.append({
360             'type': 'feature',
361             'geometry': {
362                 'type': 'LineString',
363                 'coordinates': [[lon, lat] for lon, lat, symbol, title in entries]},
364             'properties': properties})
365
366     json = {'type': 'FeatureCollection', 'features': json_features}
367     return {'lon': center[0], 'lat': center[1], 'zoom': zoom}, json
368
369
370 def parse_wrmap_coordinates(coords):
371     '''gets a string coordinates and returns an array of lon/lat coordinate pairs, e.g.
372     47.12 N 11.87 E
373     47.13 N 11.70 E
374     ->
375     [[11.87, 47.12], [11.70, 47.13]]'''
376     result = []
377     pos = 0
378     for match in re.finditer(r'\s*(\d+\.?\d*)\s*N?\s+(\d+\.?\d*)\s*E?\s*', coords):
379         if match.start() != pos:
380             break
381         result.append([float(match.groups()[1]), float(match.groups()[0])])
382         pos = match.end()
383     else:
384         if pos == len(coords):
385             return result
386     raise RuntimeError('Wrong coordinate format: {}'.format(coords))
387
388
389 def parse_wrmap(wikitext):
390     """Parses the (unicode) u'<wrmap ...>content</wrmap>' of the Winterrodeln wrmap extension
391     out of a page. If wikitext does not contain the googlemaps extension text None is returned.
392     If the googlemap contains invalid formatted lines, a RuntimeError is raised.
393
394     :param wikitext: wikitext containing the template. Example:
395
396     wikitext = u'''
397     <wrmap lat="47.2417134" lon="11.21408895" zoom="14" width="700" height="400">
398     <gasthaus name="Rosskogelhütte" wiki="Rosskogelhütte">47.240689 11.190454</gasthaus>
399     <parkplatz>47.245789 11.238971</parkplatz>
400     <haltestelle name="Oberperfuss Rangger Köpfl Lift">47.245711 11.238283</haltestelle>
401     <rodelbahn>
402         47.238587 11.203360
403         47.244951 11.230868
404         47.245470 11.237853
405     </rodelbahn>
406     </wrmap>
407     '''
408     :returns: (attributes, GeoJSON as Python datatypes)
409     """
410     # parse XML
411     try:
412         wrmap_xml = xml.etree.ElementTree.fromstring(wikitext.encode('utf-8'))
413     except xml.etree.ElementTree.ParseError as e:
414         row, column = e.position
415         raise RuntimeError("XML parse error on row {}, column {}: {}".format(row, column, e))
416     # assert(in_array($tagname, array('wrmap', 'wrgmap'))); # TODO
417
418     # convert XML to geojson (http://www.geojson.org/geojson-spec.html)
419     json_features = []
420     point_type = {'gasthaus': 'inn', 'haltestelle': 'busstop', 'parkplatz': 'carpark', 'achtung': 'attention', 'punkt': 'point'}
421     line_type = {'rodelbahn': 'sledrun', 'gehweg': 'walk', 'alternative': 'alternative', 'lift': 'lift', 'linie': 'line'}
422     for feature in wrmap_xml:
423         # determine feature type
424         is_point = point_type.has_key(feature.tag)
425         is_line = line_type.has_key(feature.tag)
426         if (not is_point and not is_line):
427             raise RuntimeError('Unknown element <{}>.'.format(feature.tag))
428
429         # point
430         if is_point:
431             properties = {'type': point_type[feature.tag]}
432             allowed_properties = set(['name', 'wiki'])
433             wrong_properties = set(feature.attrib.keys()) - allowed_properties
434             if len(wrong_properties) > 0:
435                 raise RuntimeError("The attribute '{}' is not allowed at <{}>.".format(list(wrong_properties)[0], feature.tag))
436             properties.update(feature.attrib)
437             coordinates = parse_wrmap_coordinates(feature.text)
438             if len(coordinates) != 1:
439                 raise RuntimeError('The element <{}> has to have exactly one coordinate pair.'.format(feature.tag))
440             json_features.append({
441                 'type': 'feature',
442                 'geometry': {'type': 'Point', 'coordinates': coordinates[0]},
443                 'properties': properties})
444
445         # line
446         if is_line:
447             properties = {'type': line_type[feature.tag]}
448             allowed_properties = set(['farbe', 'dicke'])
449             wrong_properties = set(feature.attrib.keys()) - allowed_properties
450             if len(wrong_properties) > 0:
451                 raise RuntimeError("The attribute '{}' is not allowed at <{}>.".format(list(wrong_properties)[0], feature.tag))
452             if feature.attrib.has_key('farbe'): 
453                 if not re.match('#[0-9a-fA-F]{6}$', feature.attrib['farbe']):
454                     raise RuntimeError('The attribute "farbe" has to have a format like "#a0bb43".')
455                 properties['strokeColor'] = feature.attrib['farbe'] # e.g. #a200b7
456             if feature.attrib.has_key('dicke'):
457                 try:
458                     properties['strokeWidth'] = int(feature.attrib['dicke']) # e.g. 6
459                 except ValueError:
460                     raise RuntimeError('The attribute "dicke" has to be an integer.')
461             json_features.append({
462                 'type': 'feature',
463                 'geometry': {'type': 'LineString', 'coordinates': parse_wrmap_coordinates(feature.text)},
464                 'properties': properties})
465
466     json = {
467         'type': 'FeatureCollection',
468         'features': json_features}
469
470     # attributes # TODO: check
471     attributes = {}
472     attributes['lat']    = float(wrmap_xml.attrib.get('lat', 47.267648)) # center lat
473     attributes['lon']    = float(wrmap_xml.attrib.get('lon', 11.404655)) # center lon
474     attributes['zoom']   = int(wrmap_xml.attrib.get('zoom', 10))         # Google Zoom Level
475     attributes['width']  = int(wrmap_xml.attrib['width']) if wrmap_xml.attrib.has_key('width') else None # None corresponds to 100%
476     attributes['height'] = int(wrmap_xml.attrib.get('height', 450))      # map height in px
477     # show_sledruns = (wrmap_xml.tag == 'wrgmap')
478
479     return attributes, json
480
481
482 def create_wrmap(geojson):
483     """Creates a <wrmap> wikitext from geojson."""
484     pass
485
486