3 User script for pywikibot (https://gerrit.wikimedia.org/r/pywikibot/core.git), tested with version 6.6.1.
4 Put it in directory scripts/userscripts.
6 Create a sledrun JSON page from a sledrun wikitext page (including map).
8 The following generators and filters are supported:
15 from typing import Any, Optional
17 import mwparserfromhell
19 from mwparserfromhell.nodes import Tag, Text, ExternalLink, Template, Wikilink
20 from mwparserfromhell.wikicode import Wikicode
21 from pywikibot import pagegenerators, Page
22 from pywikibot.bot import (
23 AutomaticTWSummaryBot,
29 from pywikibot.logging import warning
30 from pywikibot.site._namespace import BuiltinNamespace
32 from wrpylib.wrmwmarkup import create_sledrun_wiki, lonlat_to_json, lonlat_ele_to_json, parse_wrmap
33 from wrpylib.wrvalidators import rodelbahnbox_from_template, tristate_german_to_str, difficulty_german_to_str, \
34 avalanches_german_to_str, public_transport_german_to_str, opt_str_opt_comment_enum_to_str, opt_lonlat_from_str, \
37 from pywikibot.site import Namespace
39 docuReplacements = {'¶ms;': pagegenerators.parameterHelp}
42 def str_or_none(value: Any) -> Optional[str]:
48 def template_to_json(value: Template) -> dict:
50 for p in value.params:
51 parameter.append({'value': str(p)})
53 'name': str(value.name),
54 'parameter': parameter
58 class SledrunWikiTextToJsonBot(
63 AutomaticTWSummaryBot,
65 def treat_page(self) -> None:
66 """Load the given page, do some changes, and save it."""
67 wikitext_content_model = 'wikitext'
68 if self.current_page.content_model != wikitext_content_model:
69 warning(f"The content model of {self.current_page.title()} is {self.current_page.content_model} "
70 f"instead of {wikitext_content_model}.")
73 wikicode = mwparserfromhell.parse(self.current_page.text)
74 wikilink_list = wikicode.filter_wikilinks()
75 category_sledrun = 'Kategorie:Rodelbahn'
76 if sum(1 for c in wikilink_list if c.title == category_sledrun) == 0:
77 warning(f'The page {self.current_page.title()} does not have category {category_sledrun}.')
80 sledrun_json_page = Page(self.site, self.current_page.title() + '/Rodelbahn.json')
81 if sledrun_json_page.exists():
82 warning(f"{sledrun_json_page.title()} already exists, skipping {self.current_page.title()}.")
85 map_json_page = Page(self.site, self.current_page.title() + '/Landkarte.json')
86 if map_json_page.exists():
87 warning(f"{map_json_page.title()} already exists, skipping {self.current_page.title()}.")
91 v = wikicode.filter_tags(matches='wrmap')
93 map_json = parse_wrmap(str(v[0]))
96 "name": self.current_page.title(),
98 "entry_under_construction": sum(1 for c in wikilink_list if c.text == 'Kategorie:In Arbeit') > 0,
101 for v in wikicode.get_sections(levels=[2], matches='Allgemeines'):
102 for w in v.ifilter_text(recursive=False):
105 sledrun_json["description"] = str(x)
109 rbb_list = wikicode.filter_templates(recursive=False, matches=lambda t: t.name.strip() == 'Rodelbahnbox')
110 if len(rbb_list) == 1:
111 rbb = rodelbahnbox_from_template(rbb_list[0])
114 image_page = Page(self.site, v, ns=BuiltinNamespace.FILE)
115 if not image_page.exists():
116 warning(f"{image_page.title()} does not exist.")
117 sledrun_json['image'] = v
121 sledrun_json['length'] = v
123 v = rbb['Schwierigkeit']
125 sledrun_json['difficulty'] = difficulty_german_to_str(v)
129 sledrun_json['avalanches'] = avalanches_german_to_str(v)
131 v, w = rbb['Betreiber']
133 sledrun_json['has_operator'] = v
135 sledrun_json['operator'] = w
137 v = rbb['Aufstieg möglich']
139 sledrun_json['walkup_possible'] = v
141 v, w = rbb['Aufstieg getrennt']
143 sledrun_json['walkup_separate'] = tristate_german_to_str(v)
145 sledrun_json['walkup_comment'] = w # TODO
149 sledrun_json['walkup_time'] = v
151 v, w = rbb['Beleuchtungsanlage']
153 sledrun_json['nightlight_possible'] = tristate_german_to_str(v)
155 sledrun_json['nightlight_description'] = w
157 v = rbb['Rodelverleih']
159 sledrun_json['sled_rental_direct'] = v != []
160 sledrun_json['sled_rental_description'] = opt_str_opt_comment_enum_to_str(v)
162 v = rbb['In Übersichtskarte']
164 sledrun_json['show_in_overview'] = v
168 sledrun_json['forum_id'] = v
172 sledrun_json['position'] = lonlat_to_json(v)
174 v = lonlat_ele_to_json(rbb['Position oben'], rbb['Höhe oben'])
176 sledrun_json['top'] = v
178 v = lonlat_ele_to_json(rbb['Position unten'], rbb['Höhe unten'])
180 sledrun_json['bottom'] = v
182 v = rbb['Telefonauskunft']
184 sledrun_json['info_phone'] = [{'phone': p, 'name': n} for p, n in v]
186 v = rbb['Öffentliche Anreise']
188 sledrun_json['public_transport'] = public_transport_german_to_str(v)
190 for v in wikicode.get_sections(levels=[2], matches='Anreise mit öffentlichen Verkehrsmitteln',
191 include_headings=False):
192 w = next((w for w in v.nodes if isinstance(w, Tag) and w.wiki_markup == '*'), None)
194 x = str(Wikicode(v.nodes[:v.nodes.index(w)])).strip()
196 sledrun_json["public_transport_description"] = str(x)
198 public_transport_stops = []
199 public_transport_lines = []
202 if isinstance(w, Template):
203 if w.name == 'Haltestelle':
205 public_transport_stops.append(ya)
209 ya['municipality'] = str(z)
212 ya['name_local'] = str(z)
213 za = str_or_none(w.get(3, None))
214 zb = str_or_none(w.get(4, None))
215 z = lonlat_ele_to_json(opt_lonlat_from_str(za), opt_uint_from_str(zb))
218 elif w.name in ["Fahrplan Abfahrtsmonitor VVT"]:
219 ya['monitor_template'] = template_to_json(w)
220 elif w.name in ["Fahrplan Hinfahrt VVT"]:
221 ya['route_arrival_template'] = template_to_json(w)
222 elif w.name in ["Fahrplan Rückfahrt VVT"]:
223 ya['route_departure_template'] = template_to_json(w)
224 elif w.name in ["Fahrplan Linie VVT"]:
226 public_transport_stops.append(ya)
229 'timetable_template': template_to_json(w),
231 public_transport_lines.append(y)
233 public_transport_stops.append(ya)
234 if len(public_transport_stops) > 0:
235 sledrun_json['public_transport_stops'] = public_transport_stops
236 if len(public_transport_lines) > 0:
237 sledrun_json['public_transport_lines'] = public_transport_lines
240 for v in wikicode.get_sections(levels=[2], matches='Anreise mit dem Auto'):
241 for w in v.ifilter_text(recursive=False):
244 sledrun_json["car_description"] = str(x)
247 for w in v.ifilter_templates(matches='Parkplatz'):
248 za = str_or_none(w.get(1, None))
249 zb = str_or_none(w.get(2, None))
250 z = lonlat_ele_to_json(opt_lonlat_from_str(za), opt_uint_from_str(zb))
252 x.append({'position': z})
254 sledrun_json['car_parking'] = x
257 for w in io.StringIO(str(v)):
258 match = re.match(r"\*\* von \'\'\'(.+)\'\'\'(.*): ([\d.,]+) km", w.rstrip())
260 ya, yb, yc = match.groups()
261 yc = float(yc.replace(',', '.'))
264 'route': (ya.strip() + ' ' + yb.strip()).strip(),
267 sledrun_json['car_distances'] = x
270 for v in wikicode.get_sections(levels=[2], matches='Allgemeines'):
271 def _gastronomy(value: str):
273 line_iter = io.StringIO(value)
274 line = next(line_iter, None)
275 while line is not None and line.rstrip() != "* '''Hütten''':":
276 line = next(line_iter, None)
279 while line is not None:
280 line = next(line_iter, None)
282 if line.startswith('** '):
284 wiki = mwparserfromhell.parse(line)
285 wiki_link = next(wiki.ifilter_wikilinks(), None)
286 if isinstance(wiki_link, Wikilink):
288 'title': str(wiki_link.title),
290 text = str_or_none(wiki_link.text)
294 ext_link = next(wiki.ifilter_external_links(), None)
295 if isinstance(ext_link, ExternalLink):
297 'url': str(ext_link.url),
298 'text': str(ext_link.title)
301 remaining = str(Wikicode(n for n in wiki.nodes
302 if isinstance(n, (Text, Tag)) and str(n).strip() is not '*')).\
304 match = re.match(r'\((.+)\)', remaining)
306 remaining = match.group(1)
307 if len(remaining) > 0:
308 g['note'] = remaining
313 w = _gastronomy(str(v))
315 sledrun_json['gastronomy'] = w
320 if isinstance(w, Tag) and str(w) == "'''Siehe auch'''":
325 if isinstance(w, ExternalLink):
326 link = {'url': w.url}
327 if w.title is not None:
328 link['text'] = w.title
330 elif isinstance(w, (Text, Tag)) and str(w).strip() in ['', '*', ':']:
336 sledrun_json['see_also'] = x
338 sledrun_json['allow_reports'] = True
340 text = create_sledrun_wiki(sledrun_json, map_json)
341 summary = 'Rodelbahnbeschreibung nach Konvertierung nach und von JSON.'
342 self.put_current(text, summary=summary)
345 def main(*args: str) -> None:
346 local_args = pywikibot.handle_args(args)
347 gen_factory = pagegenerators.GeneratorFactory()
348 gen_factory.handle_args(local_args)
349 gen = gen_factory.getCombinedGenerator(preload=True)
351 bot = SledrunWikiTextToJsonBot(generator=gen)
354 pywikibot.bot.suggest_help(missing_generator=True)
357 if __name__ == '__main__':