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 template_to_json(value: Template) -> dict:
47 for p in value.params:
48 parameter.append({'value': str(p)})
50 'name': str(value.name),
51 'parameter': parameter
55 def wikilink_to_json(value: Wikilink) -> dict:
56 wl = {'title': str(value.title)}
57 if value.text is not None:
58 wl['text'] = str(value.text)
62 def external_link_to_json(value: ExternalLink) -> dict:
63 link = {'url': str(value.url)}
64 if value.title is not None:
65 link['text'] = str(value.title)
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
180 v, w = rbb['Beleuchtungstage']
182 sledrun_json['nightlight_weekdays_count'] = v
184 sledrun_json['nightlight_weekdays_comment'] = w
187 v = rbb['Rodelverleih']
189 sledrun_json['sled_rental_direct'] = v != []
191 for name, comment in v:
193 name_code = mwparserfromhell.parse(name)
194 wiki_link = next(name_code.ifilter_wikilinks(), None)
195 if isinstance(wiki_link, Wikilink):
196 x['wr_page'] = wikilink_to_json(wiki_link)
199 if comment is not None:
200 x['comment'] = comment
202 sledrun_json['sled_rental'] = w
206 v = rbb['Gütesiegel']
208 sledrun_json['cachet'] = len(v) > 0
211 v = rbb['In Übersichtskarte']
213 sledrun_json['show_in_overview'] = v
217 sledrun_json['forum_id'] = v
221 sledrun_json['position'] = lonlat_to_json(v)
223 v = lonlat_ele_to_json(rbb['Position oben'], rbb['Höhe oben'])
225 sledrun_json['top'] = v
227 v = lonlat_ele_to_json(rbb['Position unten'], rbb['Höhe unten'])
229 sledrun_json['bottom'] = v
231 v = rbb['Telefonauskunft']
233 sledrun_json['info_phone'] = [{'phone': p, 'name': n} for p, n in v]
235 v = rbb['Öffentliche Anreise']
237 sledrun_json['public_transport'] = public_transport_german_to_str(v)
240 bb_iter = wikicode.ifilter_templates(recursive=False, matches=lambda t: t.name.strip() == 'Buttonleiste')
241 bb = next(bb_iter, None)
243 video = bb.get('video', None)
244 if isinstance(video, Parameter):
245 sledrun_json['videos'] = [{'url': video.value}]
248 def _public_transport():
249 pt_sections = wikicode.get_sections(levels=[2], matches='Anreise mit öffentlichen Verkehrsmitteln',
250 include_headings=False)
251 if len(pt_sections) < 1:
254 node = next((node for node in pt.nodes if isinstance(node, Tag) and node.wiki_markup == '*'), None)
256 description = str(Wikicode(pt.nodes[:pt.nodes.index(node)])).strip()
258 sledrun_json["public_transport_description"] = str(description)
260 public_transport_stops = []
261 public_transport_lines = []
262 public_transport_links = []
264 for node in pt.nodes:
265 if isinstance(node, Template):
266 if node.name == 'Haltestelle':
268 public_transport_stops.append(ya)
270 z = node.get(1, None)
272 ya['municipality'] = str(z)
273 z = node.get(2, None)
275 ya['name_local'] = str(z)
276 za = str(node.get(3, '')).strip()
277 zb = str(node.get(4, '')).strip()
278 z = lonlat_ele_to_json(opt_lonlat_from_str(za), opt_uint_from_str(zb))
281 elif node.name in ["Fahrplan Abfahrtsmonitor VVT"]:
282 ya['monitor_template'] = template_to_json(node)
283 elif node.name in ["Fahrplan Hinfahrt VVT"]:
284 ya['route_arrival_template'] = template_to_json(node)
285 elif node.name in ["Fahrplan Rückfahrt VVT"]:
286 ya['route_departure_template'] = template_to_json(node)
287 elif node.name in ["Fahrplan Linie VVT"]:
289 public_transport_stops.append(ya)
292 'timetable_template': template_to_json(node),
294 public_transport_lines.append(y)
295 elif isinstance(node, ExternalLink):
296 public_transport_links.append(external_link_to_json(node))
298 public_transport_stops.append(ya)
299 if len(public_transport_stops) > 0:
300 sledrun_json['public_transport_stops'] = public_transport_stops
301 if len(public_transport_lines) > 0:
302 sledrun_json['public_transport_lines'] = public_transport_lines
303 if len(public_transport_links) > 0:
304 sledrun_json['public_transport_links'] = public_transport_links
308 car_section_list = wikicode.get_sections(levels=[2], matches='Anreise mit dem Auto')
309 if not car_section_list:
311 v = car_section_list[0]
313 description_nodes = dropwhile(lambda w: isinstance(w, Heading), v.nodes)
314 description_nodes = takewhile(lambda w: not (isinstance(w, Tag) and w.wiki_markup == '*'),
316 if description := str(Wikicode(list(description_nodes))).strip():
317 sledrun_json["car_description"] = description
320 for w in v.ifilter_templates(matches='Parkplatz'):
321 za = str(w.get(1, '')).strip()
322 zb = str(w.get(2, '')).strip()
323 z = lonlat_ele_to_json(opt_lonlat_from_str(za), opt_uint_from_str(zb))
325 x.append({'position': z})
327 sledrun_json['car_parking'] = x
330 for w in io.StringIO(str(v)):
331 match = re.match(r"\*\* von \'\'\'(.+)\'\'\'(.*): ([\d.,]+) km", w.rstrip())
333 ya, yb, yc = match.groups()
334 yc = float(yc.replace(',', '.'))
337 'route': (ya.strip() + ' ' + yb.strip()).strip(),
340 sledrun_json['car_distances'] = x
344 for v in wikicode.get_sections(levels=[2], matches='Allgemeines'):
345 def _gastronomy(value: str):
347 line_iter = io.StringIO(value)
348 line = next(line_iter, None)
349 while line is not None and line.rstrip() != "* '''Hütten''':":
350 line = next(line_iter, None)
353 while line is not None:
354 line = next(line_iter, None)
356 if line.startswith('** '):
358 wiki = mwparserfromhell.parse(line)
359 wiki_link = next(wiki.ifilter_wikilinks(), None)
360 if isinstance(wiki_link, Wikilink):
361 g['wr_page'] = wikilink_to_json(wiki_link)
362 ext_link = next(wiki.ifilter_external_links(), None)
363 if isinstance(ext_link, ExternalLink):
364 g['weblink'] = external_link_to_json(ext_link)
365 remaining = str(Wikicode(n for n in wiki.nodes
366 if isinstance(n, (Text, Tag)) and str(n).strip() != '*')).strip()
367 match = re.match(r'\((.+)\)', remaining)
369 remaining = match.group(1)
370 if len(remaining) > 0:
371 g['note'] = remaining
376 w = _gastronomy(str(v))
378 sledrun_json['gastronomy'] = w
380 def _sled_rental_description():
381 line_iter = io.StringIO(str(v))
382 line = next(line_iter, None)
384 while line is not None and (match := re.match(r"\* '''Rodelverleih''':(.*)", line)) is None:
385 line = next(line_iter, None)
388 result = [match.group(1)]
389 line = next(line_iter, None)
390 while line is not None and re.match(r"\* ", line) is None:
392 line = next(line_iter, None)
393 sledrun_json['sled_rental_description'] = ''.join(result).strip()
394 _sled_rental_description()
399 if isinstance(w, Tag) and str(w) == "'''Siehe auch'''":
404 if isinstance(w, ExternalLink):
405 x.append(external_link_to_json(w))
406 elif isinstance(w, (Text, Tag)) and str(w).strip() in ['', '*', ':']:
412 sledrun_json['see_also'] = x
414 sledrun_json['allow_reports'] = True
417 sledrun_impressions_page = Page(self.site, self.current_page.title() + '/Impressionen')
418 if sledrun_impressions_page.exists():
419 impressions = sledrun_impressions_page.title()
421 text = create_sledrun_wiki(sledrun_json, map_json, impressions)
422 summary = 'Rodelbahnbeschreibung nach Konvertierung nach und von JSON.'
423 self.put_current(text, summary=summary)
426 def main(*args: str) -> None:
427 local_args = pywikibot.handle_args(args)
428 gen_factory = pagegenerators.GeneratorFactory()
429 gen_factory.handle_args(local_args)
430 gen = gen_factory.getCombinedGenerator(preload=True)
432 bot = SledrunWikiTextToJsonBot(generator=gen)
435 pywikibot.bot.suggest_help(missing_generator=True)
438 if __name__ == '__main__':