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
199 v = rbb['In Übersichtskarte']
201 sledrun_json['show_in_overview'] = v
205 sledrun_json['forum_id'] = v
209 sledrun_json['position'] = lonlat_to_json(v)
211 v = lonlat_ele_to_json(rbb['Position oben'], rbb['Höhe oben'])
213 sledrun_json['top'] = v
215 v = lonlat_ele_to_json(rbb['Position unten'], rbb['Höhe unten'])
217 sledrun_json['bottom'] = v
219 v = rbb['Telefonauskunft']
221 sledrun_json['info_phone'] = [{'phone': p, 'name': n} for p, n in v]
223 v = rbb['Öffentliche Anreise']
225 sledrun_json['public_transport'] = public_transport_german_to_str(v)
228 bb_iter = wikicode.ifilter_templates(recursive=False, matches=lambda t: t.name.strip() == 'Buttonleiste')
229 bb = next(bb_iter, None)
231 video = bb.get('video', None)
232 if isinstance(video, Parameter):
233 sledrun_json['videos'] = [{'url': video.value}]
236 for v in wikicode.get_sections(levels=[2], matches='Anreise mit öffentlichen Verkehrsmitteln',
237 include_headings=False):
238 w = next((w for w in v.nodes if isinstance(w, Tag) and w.wiki_markup == '*'), None)
240 x = str(Wikicode(v.nodes[:v.nodes.index(w)])).strip()
242 sledrun_json["public_transport_description"] = str(x)
244 public_transport_stops = []
245 public_transport_lines = []
248 if isinstance(w, Template):
249 if w.name == 'Haltestelle':
251 public_transport_stops.append(ya)
255 ya['municipality'] = str(z)
258 ya['name_local'] = str(z)
259 za = str_or_none(w.get(3, None))
260 zb = str_or_none(w.get(4, None))
261 z = lonlat_ele_to_json(opt_lonlat_from_str(za), opt_uint_from_str(zb))
264 elif w.name in ["Fahrplan Abfahrtsmonitor VVT"]:
265 ya['monitor_template'] = template_to_json(w)
266 elif w.name in ["Fahrplan Hinfahrt VVT"]:
267 ya['route_arrival_template'] = template_to_json(w)
268 elif w.name in ["Fahrplan Rückfahrt VVT"]:
269 ya['route_departure_template'] = template_to_json(w)
270 elif w.name in ["Fahrplan Linie VVT"]:
272 public_transport_stops.append(ya)
275 'timetable_template': template_to_json(w),
277 public_transport_lines.append(y)
279 public_transport_stops.append(ya)
280 if len(public_transport_stops) > 0:
281 sledrun_json['public_transport_stops'] = public_transport_stops
282 if len(public_transport_lines) > 0:
283 sledrun_json['public_transport_lines'] = public_transport_lines
287 car_section_list = wikicode.get_sections(levels=[2], matches='Anreise mit dem Auto')
288 if not car_section_list:
290 v = car_section_list[0]
292 description_nodes = dropwhile(lambda w: isinstance(w, Heading), v.nodes)
293 description_nodes = takewhile(lambda w: not (isinstance(w, Tag) and w.wiki_markup == '*'),
295 if description := str(Wikicode(list(description_nodes))).strip():
296 sledrun_json["car_description"] = description
299 for w in v.ifilter_templates(matches='Parkplatz'):
300 za = str_or_none(w.get(1, None))
301 zb = str_or_none(w.get(2, None))
302 z = lonlat_ele_to_json(opt_lonlat_from_str(za), opt_uint_from_str(zb))
304 x.append({'position': z})
306 sledrun_json['car_parking'] = x
309 for w in io.StringIO(str(v)):
310 match = re.match(r"\*\* von \'\'\'(.+)\'\'\'(.*): ([\d.,]+) km", w.rstrip())
312 ya, yb, yc = match.groups()
313 yc = float(yc.replace(',', '.'))
316 'route': (ya.strip() + ' ' + yb.strip()).strip(),
319 sledrun_json['car_distances'] = x
323 for v in wikicode.get_sections(levels=[2], matches='Allgemeines'):
324 def _gastronomy(value: str):
326 line_iter = io.StringIO(value)
327 line = next(line_iter, None)
328 while line is not None and line.rstrip() != "* '''Hütten''':":
329 line = next(line_iter, None)
332 while line is not None:
333 line = next(line_iter, None)
335 if line.startswith('** '):
337 wiki = mwparserfromhell.parse(line)
338 wiki_link = next(wiki.ifilter_wikilinks(), None)
339 if isinstance(wiki_link, Wikilink):
340 g['wr_page'] = wikilink_to_json(wiki_link)
341 ext_link = next(wiki.ifilter_external_links(), None)
342 if isinstance(ext_link, ExternalLink):
344 'url': str(ext_link.url),
345 'text': str(ext_link.title)
348 remaining = str(Wikicode(n for n in wiki.nodes
349 if isinstance(n, (Text, Tag)) and str(n).strip() != '*')).strip()
350 match = re.match(r'\((.+)\)', remaining)
352 remaining = match.group(1)
353 if len(remaining) > 0:
354 g['note'] = remaining
359 w = _gastronomy(str(v))
361 sledrun_json['gastronomy'] = w
363 def _sled_rental_description():
364 line_iter = io.StringIO(str(v))
365 line = next(line_iter, None)
367 while line is not None and (match := re.match(r"\* '''Rodelverleih''':(.*)", line)) is None:
368 line = next(line_iter, None)
371 result = [match.group(1)]
372 line = next(line_iter, None)
373 while line is not None and re.match(r"\* ", line) is None:
375 line = next(line_iter, None)
376 sledrun_json['sled_rental_description'] = ''.join(result).strip()
377 _sled_rental_description()
382 if isinstance(w, Tag) and str(w) == "'''Siehe auch'''":
387 if isinstance(w, ExternalLink):
388 link = {'url': w.url}
389 if w.title is not None:
390 link['text'] = w.title
392 elif isinstance(w, (Text, Tag)) and str(w).strip() in ['', '*', ':']:
398 sledrun_json['see_also'] = x
400 sledrun_json['allow_reports'] = True
403 sledrun_impressions_page = Page(self.site, self.current_page.title() + '/Impressionen')
404 if sledrun_impressions_page.exists():
405 impressions = sledrun_impressions_page.title()
407 text = create_sledrun_wiki(sledrun_json, map_json, impressions)
408 summary = 'Rodelbahnbeschreibung nach Konvertierung nach und von JSON.'
409 self.put_current(text, summary=summary)
412 def main(*args: str) -> None:
413 local_args = pywikibot.handle_args(args)
414 gen_factory = pagegenerators.GeneratorFactory()
415 gen_factory.handle_args(local_args)
416 gen = gen_factory.getCombinedGenerator(preload=True)
418 bot = SledrunWikiTextToJsonBot(generator=gen)
421 pywikibot.bot.suggest_help(missing_generator=True)
424 if __name__ == '__main__':