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:
14 from itertools import takewhile, dropwhile
15 from typing import Optional
17 import mwparserfromhell
18 from mwparserfromhell.nodes.extras import Parameter
21 from mwparserfromhell.nodes import Tag, Text, ExternalLink, Template, Wikilink, Heading
22 from mwparserfromhell.wikicode import Wikicode
23 from pywikibot import pagegenerators, Page
24 from pywikibot.bot import (
25 AutomaticTWSummaryBot,
31 from pywikibot.logging import warning
32 from pywikibot.site._namespace import BuiltinNamespace
34 from wrpylib.wrmwmarkup import create_sledrun_wiki, lonlat_to_json, lonlat_ele_to_json, parse_wrmap
35 from wrpylib.wrvalidators import rodelbahnbox_from_template, tristate_german_to_str, difficulty_german_to_str, \
36 avalanches_german_to_str, public_transport_german_to_str, opt_lonlat_from_str, \
38 from wrpylib.lib_sledrun_wikitext_to_json import optional_set, get_sledrun_description
40 docuReplacements = {'¶ms;': pagegenerators.parameterHelp}
43 def template_to_json(value: Template) -> dict:
45 for p in value.params:
46 parameter.append({'value': str(p)})
48 'name': str(value.name),
49 'parameter': parameter
53 def wikilink_to_json(value: Wikilink) -> dict:
54 wl = {'title': str(value.title)}
55 if value.text is not None:
56 wl['text'] = str(value.text)
60 def external_link_to_json(value: ExternalLink) -> dict:
61 link = {'url': str(value.url)}
62 if value.title is not None:
63 link['text'] = str(value.title)
67 class SledrunWikiTextToJsonBot(
72 AutomaticTWSummaryBot,
74 def treat_page(self) -> None:
75 """Load the given page, do some changes, and save it."""
76 wikitext_content_model = 'wikitext'
77 if self.current_page.content_model != wikitext_content_model:
78 warning(f"The content model of {self.current_page.title()} is {self.current_page.content_model} "
79 f"instead of {wikitext_content_model}.")
82 wikicode = mwparserfromhell.parse(self.current_page.text)
83 wikilink_list = wikicode.filter_wikilinks()
84 category_sledrun = 'Kategorie:Rodelbahn'
85 if sum(1 for c in wikilink_list if c.title == category_sledrun) == 0:
86 warning(f'The page {self.current_page.title()} does not have category {category_sledrun}.')
89 sledrun_json_page = Page(self.site, self.current_page.title() + '/Rodelbahn.json')
90 if sledrun_json_page.exists():
91 warning(f"{sledrun_json_page.title()} already exists, skipping {self.current_page.title()}.")
94 map_json_page = Page(self.site, self.current_page.title() + '/Landkarte.json')
95 if map_json_page.exists():
96 warning(f"{map_json_page.title()} already exists, skipping {self.current_page.title()}.")
100 v = wikicode.filter_tags(matches='wrmap')
102 map_json = parse_wrmap(str(v[0]))
105 "name": self.current_page.title(),
107 "entry_under_construction": sum(1 for c in wikilink_list if c.text == 'Kategorie:In Arbeit') > 0,
110 optional_set(sledrun_json, 'description', get_sledrun_description(wikicode))
112 rbb_list = wikicode.filter_templates(recursive=False, matches=lambda t: t.name.strip() == 'Rodelbahnbox')
113 if len(rbb_list) == 1:
114 rbb = rodelbahnbox_from_template(rbb_list[0])
117 image_page = Page(self.site, v, ns=BuiltinNamespace.FILE)
118 if not image_page.exists():
119 warning(f"{image_page.title()} does not exist.")
120 sledrun_json['image'] = v
124 sledrun_json['length'] = v
126 v = rbb['Schwierigkeit']
128 sledrun_json['difficulty'] = difficulty_german_to_str(v)
132 sledrun_json['avalanches'] = avalanches_german_to_str(v)
134 v, w = rbb['Betreiber']
136 sledrun_json['has_operator'] = v
138 sledrun_json['operator'] = w
140 v = rbb['Aufstieg möglich']
142 sledrun_json['walkup_possible'] = v
144 v, w = rbb['Aufstieg getrennt']
146 sledrun_json['walkup_separate'] = tristate_german_to_str(v)
148 sledrun_json['walkup_comment'] = w # TODO
152 sledrun_json['walkup_time'] = v
154 def _walkup_support():
155 walkup_support_rbb = rbb['Aufstiegshilfe']
156 if walkup_support_rbb is not None:
158 for walkup_support_type, comment in walkup_support_rbb:
159 walkup_support = {'type': walkup_support_type}
160 if comment is not None:
161 walkup_support['comment'] = comment
162 walkup_supports.append(walkup_support)
163 sledrun_json['walkup_supports'] = walkup_supports
166 v, w = rbb['Beleuchtungsanlage']
168 sledrun_json['nightlight_possible'] = tristate_german_to_str(v)
170 sledrun_json['nightlight_possible_comment'] = w
172 v, w = rbb['Beleuchtungstage']
174 sledrun_json['nightlight_weekdays_count'] = v
176 sledrun_json['nightlight_weekdays_comment'] = w
179 v = rbb['Rodelverleih']
181 sledrun_json['sled_rental_direct'] = v != []
183 for name, comment in v:
185 name_code = mwparserfromhell.parse(name)
186 wiki_link = next(name_code.ifilter_wikilinks(), None)
187 if isinstance(wiki_link, Wikilink):
188 x['wr_page'] = wikilink_to_json(wiki_link)
191 if comment is not None:
192 x['comment'] = comment
194 sledrun_json['sled_rental'] = w
198 v = rbb['Gütesiegel']
200 sledrun_json['cachet'] = len(v) > 0
203 v = rbb['In Übersichtskarte']
205 sledrun_json['show_in_overview'] = v
209 sledrun_json['forum_id'] = v
213 sledrun_json['position'] = lonlat_to_json(v)
215 v = lonlat_ele_to_json(rbb['Position oben'], rbb['Höhe oben'])
217 sledrun_json['top'] = v
219 v = lonlat_ele_to_json(rbb['Position unten'], rbb['Höhe unten'])
221 sledrun_json['bottom'] = v
223 v = rbb['Telefonauskunft']
225 sledrun_json['info_phone'] = [{'phone': p, 'name': n} for p, n in v]
227 v, w = rbb['Webauskunft']
230 sledrun_json['info_web'] = [{'url': w}]
232 sledrun_json['info_web'] = []
234 v = rbb['Öffentliche Anreise']
236 sledrun_json['public_transport'] = public_transport_german_to_str(v)
239 bb_iter = wikicode.ifilter_templates(recursive=False, matches=lambda t: t.name.strip() == 'Buttonleiste')
240 bb = next(bb_iter, None)
242 video = bb.get('video', None)
243 if isinstance(video, Parameter):
244 sledrun_json['videos'] = [{'url': video.value}]
247 def _public_transport():
248 pt_sections = wikicode.get_sections(levels=[2], matches='Anreise mit öffentlichen Verkehrsmitteln',
249 include_headings=False)
250 if len(pt_sections) < 1:
253 node = next((node for node in pt.nodes if isinstance(node, Tag) and node.wiki_markup == '*'), None)
255 description = str(Wikicode(pt.nodes[:pt.nodes.index(node)])).strip()
257 sledrun_json["public_transport_description"] = str(description)
259 public_transport_stops = []
260 public_transport_lines = []
261 public_transport_links = []
263 for node in pt.nodes:
264 if isinstance(node, Template):
265 if node.name == 'Haltestelle':
267 public_transport_stops.append(ya)
269 z = node.get(1, None)
271 ya['municipality'] = str(z)
272 z = node.get(2, None)
274 ya['name_local'] = str(z)
275 za = str(node.get(3, '')).strip()
276 zb = str(node.get(4, '')).strip()
277 z = lonlat_ele_to_json(opt_lonlat_from_str(za), opt_uint_from_str(zb))
280 elif node.name in ["Fahrplan Abfahrtsmonitor VVT"]:
281 ya['monitor_template'] = template_to_json(node)
282 elif node.name in ["Fahrplan Hinfahrt VVT"]:
283 ya['route_arrival_template'] = template_to_json(node)
284 elif node.name in ["Fahrplan Rückfahrt VVT"]:
285 ya['route_departure_template'] = template_to_json(node)
286 elif node.name in ["Fahrplan Linie VVT"]:
288 public_transport_stops.append(ya)
291 'timetable_template': template_to_json(node),
293 public_transport_lines.append(y)
294 elif isinstance(node, ExternalLink):
295 public_transport_links.append(external_link_to_json(node))
297 public_transport_stops.append(ya)
298 if len(public_transport_stops) > 0:
299 sledrun_json['public_transport_stops'] = public_transport_stops
300 if len(public_transport_lines) > 0:
301 sledrun_json['public_transport_lines'] = public_transport_lines
302 if len(public_transport_links) > 0:
303 sledrun_json['public_transport_links'] = public_transport_links
307 car_section_list = wikicode.get_sections(levels=[2], matches='Anreise mit dem Auto')
308 if not car_section_list:
310 v = car_section_list[0]
312 description_nodes = dropwhile(lambda w: isinstance(w, Heading), v.nodes)
313 description_nodes = takewhile(lambda w: not (isinstance(w, Tag) and w.wiki_markup == '*'),
315 if description := str(Wikicode(list(description_nodes))).strip():
316 sledrun_json["car_description"] = description
319 for w in v.ifilter_templates(matches='Parkplatz'):
320 za = str(w.get(1, '')).strip()
321 zb = str(w.get(2, '')).strip()
322 z = lonlat_ele_to_json(opt_lonlat_from_str(za), opt_uint_from_str(zb))
324 x.append({'position': z})
326 sledrun_json['car_parking'] = x
329 for w in io.StringIO(str(v)):
330 match = re.match(r"\*\* von \'\'\'(.+)\'\'\'(.*): ([\d.,]+) km", w.rstrip())
332 ya, yb, yc = match.groups()
333 yc = float(yc.replace(',', '.'))
336 'route': (ya.strip() + ' ' + yb.strip()).strip(),
339 sledrun_json['car_distances'] = x
343 for v in wikicode.get_sections(levels=[2], matches='Allgemeines'):
344 def _nightlight(value: str) -> Optional[str]:
345 line_iter = io.StringIO(value)
346 line = next(line_iter, None)
347 while line is not None and not line.startswith("* '''Beleuchtung''':"):
348 line = next(line_iter, None)
351 line = line.replace("* '''Beleuchtung''':", "").strip()
355 w = _nightlight(str(v))
357 sledrun_json['nightlight_description'] = w
359 def _gastronomy(value: str):
361 line_iter = io.StringIO(value)
362 line = next(line_iter, None)
363 while line is not None and line.rstrip() != "* '''Hütten''':":
364 line = next(line_iter, None)
367 while line is not None:
368 line = next(line_iter, None)
370 if line.startswith('** '):
372 wiki = mwparserfromhell.parse(line)
373 wiki_link = next(wiki.ifilter_wikilinks(), None)
374 if isinstance(wiki_link, Wikilink):
375 g['wr_page'] = wikilink_to_json(wiki_link)
376 ext_link = next(wiki.ifilter_external_links(), None)
377 if isinstance(ext_link, ExternalLink):
378 g['weblink'] = external_link_to_json(ext_link)
379 remaining = str(Wikicode(n for n in wiki.nodes
380 if isinstance(n, (Text, Tag)) and str(n).strip() != '*')).strip()
381 match = re.match(r'\((.+)\)', remaining)
383 remaining = match.group(1)
384 if len(remaining) > 0:
385 g['note'] = remaining
390 w = _gastronomy(str(v))
392 sledrun_json['gastronomy'] = w
394 def _sled_rental_description():
395 line_iter = io.StringIO(str(v))
396 line = next(line_iter, None)
398 while line is not None and (match := re.match(r"\* '''Rodelverleih''':(.*)", line)) is None:
399 line = next(line_iter, None)
402 result = [match.group(1)]
403 line = next(line_iter, None)
404 while line is not None and re.match(r"\* ", line) is None:
406 line = next(line_iter, None)
407 sledrun_json['sled_rental_description'] = ''.join(result).strip()
408 _sled_rental_description()
413 if isinstance(w, Tag) and str(w) == "'''Siehe auch'''":
418 if isinstance(w, ExternalLink):
419 x.append(external_link_to_json(w))
420 elif isinstance(w, (Text, Tag)) and str(w).strip() in ['', '*', ':']:
426 sledrun_json['see_also'] = x
428 sledrun_json['allow_reports'] = True
431 sledrun_impressions_page = Page(self.site, self.current_page.title() + '/Impressionen')
432 if sledrun_impressions_page.exists():
433 impressions = sledrun_impressions_page.title()
435 text = create_sledrun_wiki(sledrun_json, map_json, impressions)
436 summary = 'Rodelbahnbeschreibung nach Konvertierung nach und von JSON.'
437 self.put_current(text, summary=summary)
440 def main(*args: str) -> None:
441 local_args = pywikibot.handle_args(args)
442 gen_factory = pagegenerators.GeneratorFactory()
443 gen_factory.handle_args(local_args)
444 gen = gen_factory.getCombinedGenerator(preload=True)
446 bot = SledrunWikiTextToJsonBot(generator=gen)
449 pywikibot.bot.suggest_help(missing_generator=True)
452 if __name__ == '__main__':