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 itertools import takewhile, dropwhile
16 from typing import Any, Optional
18 import mwparserfromhell
19 from mwparserfromhell.nodes.extras import Parameter
22 from mwparserfromhell.nodes import Tag, Text, ExternalLink, Template, Wikilink, Heading
23 from mwparserfromhell.wikicode import Wikicode
24 from pywikibot import pagegenerators, Page
25 from pywikibot.bot import (
26 AutomaticTWSummaryBot,
32 from pywikibot.logging import warning
33 from pywikibot.site._namespace import BuiltinNamespace
35 from wrpylib.wrmwmarkup import create_sledrun_wiki, lonlat_to_json, lonlat_ele_to_json, parse_wrmap
36 from wrpylib.wrvalidators import rodelbahnbox_from_template, tristate_german_to_str, difficulty_german_to_str, \
37 avalanches_german_to_str, public_transport_german_to_str, opt_str_opt_comment_enum_to_str, opt_lonlat_from_str, \
40 from pywikibot.site import Namespace
42 docuReplacements = {'¶ms;': pagegenerators.parameterHelp}
45 def str_or_none(value: Any) -> Optional[str]:
51 def template_to_json(value: Template) -> dict:
53 for p in value.params:
54 parameter.append({'value': str(p)})
56 'name': str(value.name),
57 'parameter': parameter
61 def wikilink_to_json(value: Wikilink) -> dict:
62 wl = {'title': str(value.title)}
63 text = str_or_none(value.text)
69 class SledrunWikiTextToJsonBot(
74 AutomaticTWSummaryBot,
76 def treat_page(self) -> None:
77 """Load the given page, do some changes, and save it."""
78 wikitext_content_model = 'wikitext'
79 if self.current_page.content_model != wikitext_content_model:
80 warning(f"The content model of {self.current_page.title()} is {self.current_page.content_model} "
81 f"instead of {wikitext_content_model}.")
84 wikicode = mwparserfromhell.parse(self.current_page.text)
85 wikilink_list = wikicode.filter_wikilinks()
86 category_sledrun = 'Kategorie:Rodelbahn'
87 if sum(1 for c in wikilink_list if c.title == category_sledrun) == 0:
88 warning(f'The page {self.current_page.title()} does not have category {category_sledrun}.')
91 sledrun_json_page = Page(self.site, self.current_page.title() + '/Rodelbahn.json')
92 if sledrun_json_page.exists():
93 warning(f"{sledrun_json_page.title()} already exists, skipping {self.current_page.title()}.")
96 map_json_page = Page(self.site, self.current_page.title() + '/Landkarte.json')
97 if map_json_page.exists():
98 warning(f"{map_json_page.title()} already exists, skipping {self.current_page.title()}.")
102 v = wikicode.filter_tags(matches='wrmap')
104 map_json = parse_wrmap(str(v[0]))
107 "name": self.current_page.title(),
109 "entry_under_construction": sum(1 for c in wikilink_list if c.text == 'Kategorie:In Arbeit') > 0,
112 for v in wikicode.get_sections(levels=[2], matches='Allgemeines'):
113 for w in v.ifilter_text(recursive=False):
116 sledrun_json["description"] = str(x)
120 rbb_list = wikicode.filter_templates(recursive=False, matches=lambda t: t.name.strip() == 'Rodelbahnbox')
121 if len(rbb_list) == 1:
122 rbb = rodelbahnbox_from_template(rbb_list[0])
125 image_page = Page(self.site, v, ns=BuiltinNamespace.FILE)
126 if not image_page.exists():
127 warning(f"{image_page.title()} does not exist.")
128 sledrun_json['image'] = v
132 sledrun_json['length'] = v
134 v = rbb['Schwierigkeit']
136 sledrun_json['difficulty'] = difficulty_german_to_str(v)
140 sledrun_json['avalanches'] = avalanches_german_to_str(v)
142 v, w = rbb['Betreiber']
144 sledrun_json['has_operator'] = v
146 sledrun_json['operator'] = w
148 v = rbb['Aufstieg möglich']
150 sledrun_json['walkup_possible'] = v
152 v, w = rbb['Aufstieg getrennt']
154 sledrun_json['walkup_separate'] = tristate_german_to_str(v)
156 sledrun_json['walkup_comment'] = w # TODO
160 sledrun_json['walkup_time'] = v
162 def _walkup_support():
163 walkup_support_rbb = rbb['Aufstiegshilfe']
164 if walkup_support_rbb is not None:
166 for walkup_support_type, comment in walkup_support_rbb:
167 walkup_support = {'type': walkup_support_type}
168 if comment is not None:
169 walkup_support['comment']: comment
170 walkup_supports.append(walkup_support)
171 sledrun_json['walkup_supports'] = walkup_supports
174 v, w = rbb['Beleuchtungsanlage']
176 sledrun_json['nightlight_possible'] = tristate_german_to_str(v)
178 sledrun_json['nightlight_description'] = w
181 v = rbb['Rodelverleih']
183 sledrun_json['sled_rental_direct'] = v != []
185 for name, comment in v:
187 name_code = mwparserfromhell.parse(name)
188 wiki_link = next(name_code.ifilter_wikilinks(), None)
189 if isinstance(wiki_link, Wikilink):
190 x['wr_page'] = wikilink_to_json(wiki_link)
193 if comment is not None:
194 x['comment'] = comment
196 sledrun_json['sled_rental'] = w
200 v = rbb['Gütesiegel']
202 sledrun_json['cachet'] = len(v) > 0
205 v = rbb['In Übersichtskarte']
207 sledrun_json['show_in_overview'] = v
211 sledrun_json['forum_id'] = v
215 sledrun_json['position'] = lonlat_to_json(v)
217 v = lonlat_ele_to_json(rbb['Position oben'], rbb['Höhe oben'])
219 sledrun_json['top'] = v
221 v = lonlat_ele_to_json(rbb['Position unten'], rbb['Höhe unten'])
223 sledrun_json['bottom'] = v
225 v = rbb['Telefonauskunft']
227 sledrun_json['info_phone'] = [{'phone': p, 'name': n} for p, n in v]
229 v = rbb['Öffentliche Anreise']
231 sledrun_json['public_transport'] = public_transport_german_to_str(v)
234 bb_iter = wikicode.ifilter_templates(recursive=False, matches=lambda t: t.name.strip() == 'Buttonleiste')
235 bb = next(bb_iter, None)
237 video = bb.get('video', None)
238 if isinstance(video, Parameter):
239 sledrun_json['videos'] = [{'url': video.value}]
242 for v in wikicode.get_sections(levels=[2], matches='Anreise mit öffentlichen Verkehrsmitteln',
243 include_headings=False):
244 w = next((w for w in v.nodes if isinstance(w, Tag) and w.wiki_markup == '*'), None)
246 x = str(Wikicode(v.nodes[:v.nodes.index(w)])).strip()
248 sledrun_json["public_transport_description"] = str(x)
250 public_transport_stops = []
251 public_transport_lines = []
254 if isinstance(w, Template):
255 if w.name == 'Haltestelle':
257 public_transport_stops.append(ya)
261 ya['municipality'] = str(z)
264 ya['name_local'] = str(z)
265 za = str_or_none(w.get(3, None))
266 zb = str_or_none(w.get(4, None))
267 z = lonlat_ele_to_json(opt_lonlat_from_str(za), opt_uint_from_str(zb))
270 elif w.name in ["Fahrplan Abfahrtsmonitor VVT"]:
271 ya['monitor_template'] = template_to_json(w)
272 elif w.name in ["Fahrplan Hinfahrt VVT"]:
273 ya['route_arrival_template'] = template_to_json(w)
274 elif w.name in ["Fahrplan Rückfahrt VVT"]:
275 ya['route_departure_template'] = template_to_json(w)
276 elif w.name in ["Fahrplan Linie VVT"]:
278 public_transport_stops.append(ya)
281 'timetable_template': template_to_json(w),
283 public_transport_lines.append(y)
285 public_transport_stops.append(ya)
286 if len(public_transport_stops) > 0:
287 sledrun_json['public_transport_stops'] = public_transport_stops
288 if len(public_transport_lines) > 0:
289 sledrun_json['public_transport_lines'] = public_transport_lines
293 car_section_list = wikicode.get_sections(levels=[2], matches='Anreise mit dem Auto')
294 if not car_section_list:
296 v = car_section_list[0]
298 description_nodes = dropwhile(lambda w: isinstance(w, Heading), v.nodes)
299 description_nodes = takewhile(lambda w: not (isinstance(w, Tag) and w.wiki_markup == '*'),
301 if description := str(Wikicode(list(description_nodes))).strip():
302 sledrun_json["car_description"] = description
305 for w in v.ifilter_templates(matches='Parkplatz'):
306 za = str_or_none(w.get(1, None))
307 zb = str_or_none(w.get(2, None))
308 z = lonlat_ele_to_json(opt_lonlat_from_str(za), opt_uint_from_str(zb))
310 x.append({'position': z})
312 sledrun_json['car_parking'] = x
315 for w in io.StringIO(str(v)):
316 match = re.match(r"\*\* von \'\'\'(.+)\'\'\'(.*): ([\d.,]+) km", w.rstrip())
318 ya, yb, yc = match.groups()
319 yc = float(yc.replace(',', '.'))
322 'route': (ya.strip() + ' ' + yb.strip()).strip(),
325 sledrun_json['car_distances'] = x
329 for v in wikicode.get_sections(levels=[2], matches='Allgemeines'):
330 def _gastronomy(value: str):
332 line_iter = io.StringIO(value)
333 line = next(line_iter, None)
334 while line is not None and line.rstrip() != "* '''Hütten''':":
335 line = next(line_iter, None)
338 while line is not None:
339 line = next(line_iter, None)
341 if line.startswith('** '):
343 wiki = mwparserfromhell.parse(line)
344 wiki_link = next(wiki.ifilter_wikilinks(), None)
345 if isinstance(wiki_link, Wikilink):
346 g['wr_page'] = wikilink_to_json(wiki_link)
347 ext_link = next(wiki.ifilter_external_links(), None)
348 if isinstance(ext_link, ExternalLink):
350 'url': str(ext_link.url),
351 'text': str(ext_link.title)
354 remaining = str(Wikicode(n for n in wiki.nodes
355 if isinstance(n, (Text, Tag)) and str(n).strip() != '*')).strip()
356 match = re.match(r'\((.+)\)', remaining)
358 remaining = match.group(1)
359 if len(remaining) > 0:
360 g['note'] = remaining
365 w = _gastronomy(str(v))
367 sledrun_json['gastronomy'] = w
369 def _sled_rental_description():
370 line_iter = io.StringIO(str(v))
371 line = next(line_iter, None)
373 while line is not None and (match := re.match(r"\* '''Rodelverleih''':(.*)", line)) is None:
374 line = next(line_iter, None)
377 result = [match.group(1)]
378 line = next(line_iter, None)
379 while line is not None and re.match(r"\* ", line) is None:
381 line = next(line_iter, None)
382 sledrun_json['sled_rental_description'] = ''.join(result).strip()
383 _sled_rental_description()
388 if isinstance(w, Tag) and str(w) == "'''Siehe auch'''":
393 if isinstance(w, ExternalLink):
394 link = {'url': w.url}
395 if w.title is not None:
396 link['text'] = w.title
398 elif isinstance(w, (Text, Tag)) and str(w).strip() in ['', '*', ':']:
404 sledrun_json['see_also'] = x
406 sledrun_json['allow_reports'] = True
409 sledrun_impressions_page = Page(self.site, self.current_page.title() + '/Impressionen')
410 if sledrun_impressions_page.exists():
411 impressions = sledrun_impressions_page.title()
413 text = create_sledrun_wiki(sledrun_json, map_json, impressions)
414 summary = 'Rodelbahnbeschreibung nach Konvertierung nach und von JSON.'
415 self.put_current(text, summary=summary)
418 def main(*args: str) -> None:
419 local_args = pywikibot.handle_args(args)
420 gen_factory = pagegenerators.GeneratorFactory()
421 gen_factory.handle_args(local_args)
422 gen = gen_factory.getCombinedGenerator(preload=True)
424 bot = SledrunWikiTextToJsonBot(generator=gen)
427 pywikibot.bot.suggest_help(missing_generator=True)
430 if __name__ == '__main__':