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 v, w = rbb['Beleuchtungsanlage']
164 sledrun_json['nightlight_possible'] = tristate_german_to_str(v)
166 sledrun_json['nightlight_description'] = w
169 v = rbb['Rodelverleih']
171 sledrun_json['sled_rental_direct'] = v != []
173 for name, comment in v:
175 name_code = mwparserfromhell.parse(name)
176 wiki_link = next(name_code.ifilter_wikilinks(), None)
177 if isinstance(wiki_link, Wikilink):
178 x['wr_page'] = wikilink_to_json(wiki_link)
181 if comment is not None:
182 x['comment'] = comment
184 sledrun_json['sled_rental'] = w
187 v = rbb['In Übersichtskarte']
189 sledrun_json['show_in_overview'] = v
193 sledrun_json['forum_id'] = v
197 sledrun_json['position'] = lonlat_to_json(v)
199 v = lonlat_ele_to_json(rbb['Position oben'], rbb['Höhe oben'])
201 sledrun_json['top'] = v
203 v = lonlat_ele_to_json(rbb['Position unten'], rbb['Höhe unten'])
205 sledrun_json['bottom'] = v
207 v = rbb['Telefonauskunft']
209 sledrun_json['info_phone'] = [{'phone': p, 'name': n} for p, n in v]
211 v = rbb['Öffentliche Anreise']
213 sledrun_json['public_transport'] = public_transport_german_to_str(v)
216 bb_iter = wikicode.ifilter_templates(recursive=False, matches=lambda t: t.name.strip() == 'Buttonleiste')
217 bb = next(bb_iter, None)
219 video = bb.get('video', None)
220 if isinstance(video, Parameter):
221 sledrun_json['videos'] = [{'url': video.value}]
224 for v in wikicode.get_sections(levels=[2], matches='Anreise mit öffentlichen Verkehrsmitteln',
225 include_headings=False):
226 w = next((w for w in v.nodes if isinstance(w, Tag) and w.wiki_markup == '*'), None)
228 x = str(Wikicode(v.nodes[:v.nodes.index(w)])).strip()
230 sledrun_json["public_transport_description"] = str(x)
232 public_transport_stops = []
233 public_transport_lines = []
236 if isinstance(w, Template):
237 if w.name == 'Haltestelle':
239 public_transport_stops.append(ya)
243 ya['municipality'] = str(z)
246 ya['name_local'] = str(z)
247 za = str_or_none(w.get(3, None))
248 zb = str_or_none(w.get(4, None))
249 z = lonlat_ele_to_json(opt_lonlat_from_str(za), opt_uint_from_str(zb))
252 elif w.name in ["Fahrplan Abfahrtsmonitor VVT"]:
253 ya['monitor_template'] = template_to_json(w)
254 elif w.name in ["Fahrplan Hinfahrt VVT"]:
255 ya['route_arrival_template'] = template_to_json(w)
256 elif w.name in ["Fahrplan Rückfahrt VVT"]:
257 ya['route_departure_template'] = template_to_json(w)
258 elif w.name in ["Fahrplan Linie VVT"]:
260 public_transport_stops.append(ya)
263 'timetable_template': template_to_json(w),
265 public_transport_lines.append(y)
267 public_transport_stops.append(ya)
268 if len(public_transport_stops) > 0:
269 sledrun_json['public_transport_stops'] = public_transport_stops
270 if len(public_transport_lines) > 0:
271 sledrun_json['public_transport_lines'] = public_transport_lines
275 car_section_list = wikicode.get_sections(levels=[2], matches='Anreise mit dem Auto')
276 if not car_section_list:
278 v = car_section_list[0]
280 description_nodes = dropwhile(lambda w: isinstance(w, Heading), v.nodes)
281 description_nodes = takewhile(lambda w: not (isinstance(w, Tag) and w.wiki_markup == '*'),
283 if description := str(Wikicode(list(description_nodes))).strip():
284 sledrun_json["car_description"] = description
287 for w in v.ifilter_templates(matches='Parkplatz'):
288 za = str_or_none(w.get(1, None))
289 zb = str_or_none(w.get(2, None))
290 z = lonlat_ele_to_json(opt_lonlat_from_str(za), opt_uint_from_str(zb))
292 x.append({'position': z})
294 sledrun_json['car_parking'] = x
297 for w in io.StringIO(str(v)):
298 match = re.match(r"\*\* von \'\'\'(.+)\'\'\'(.*): ([\d.,]+) km", w.rstrip())
300 ya, yb, yc = match.groups()
301 yc = float(yc.replace(',', '.'))
304 'route': (ya.strip() + ' ' + yb.strip()).strip(),
307 sledrun_json['car_distances'] = x
311 for v in wikicode.get_sections(levels=[2], matches='Allgemeines'):
312 def _gastronomy(value: str):
314 line_iter = io.StringIO(value)
315 line = next(line_iter, None)
316 while line is not None and line.rstrip() != "* '''Hütten''':":
317 line = next(line_iter, None)
320 while line is not None:
321 line = next(line_iter, None)
323 if line.startswith('** '):
325 wiki = mwparserfromhell.parse(line)
326 wiki_link = next(wiki.ifilter_wikilinks(), None)
327 if isinstance(wiki_link, Wikilink):
328 g['wr_page'] = wikilink_to_json(wiki_link)
329 ext_link = next(wiki.ifilter_external_links(), None)
330 if isinstance(ext_link, ExternalLink):
332 'url': str(ext_link.url),
333 'text': str(ext_link.title)
336 remaining = str(Wikicode(n for n in wiki.nodes
337 if isinstance(n, (Text, Tag)) and str(n).strip() != '*')).strip()
338 match = re.match(r'\((.+)\)', remaining)
340 remaining = match.group(1)
341 if len(remaining) > 0:
342 g['note'] = remaining
347 w = _gastronomy(str(v))
349 sledrun_json['gastronomy'] = w
351 def _sled_rental_description():
352 line_iter = io.StringIO(str(v))
353 line = next(line_iter, None)
355 while line is not None and (match := re.match(r"\* '''Rodelverleih''':(.*)", line)) is None:
356 line = next(line_iter, None)
359 result = [match.group(1)]
360 line = next(line_iter, None)
361 while line is not None and re.match(r"\* ", line) is None:
363 line = next(line_iter, None)
364 sledrun_json['sled_rental_description'] = ''.join(result).strip()
365 _sled_rental_description()
370 if isinstance(w, Tag) and str(w) == "'''Siehe auch'''":
375 if isinstance(w, ExternalLink):
376 link = {'url': w.url}
377 if w.title is not None:
378 link['text'] = w.title
380 elif isinstance(w, (Text, Tag)) and str(w).strip() in ['', '*', ':']:
386 sledrun_json['see_also'] = x
388 sledrun_json['allow_reports'] = True
391 sledrun_impressions_page = Page(self.site, self.current_page.title() + '/Impressionen')
392 if sledrun_impressions_page.exists():
393 impressions = sledrun_impressions_page.title()
395 text = create_sledrun_wiki(sledrun_json, map_json, impressions)
396 summary = 'Rodelbahnbeschreibung nach Konvertierung nach und von JSON.'
397 self.put_current(text, summary=summary)
400 def main(*args: str) -> None:
401 local_args = pywikibot.handle_args(args)
402 gen_factory = pagegenerators.GeneratorFactory()
403 gen_factory.handle_args(local_args)
404 gen = gen_factory.getCombinedGenerator(preload=True)
406 bot = SledrunWikiTextToJsonBot(generator=gen)
409 pywikibot.bot.suggest_help(missing_generator=True)
412 if __name__ == '__main__':