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, \
39 docuReplacements = {'¶ms;': pagegenerators.parameterHelp}
42 def template_to_json(value: Template) -> dict:
44 for p in value.params:
45 parameter.append({'value': str(p)})
47 'name': str(value.name),
48 'parameter': parameter
52 def wikilink_to_json(value: Wikilink) -> dict:
53 wl = {'title': str(value.title)}
54 if value.text is not None:
55 wl['text'] = str(value.text)
59 def external_link_to_json(value: ExternalLink) -> dict:
60 link = {'url': str(value.url)}
61 if value.title is not None:
62 link['text'] = str(value.title)
66 class SledrunWikiTextToJsonBot(
71 AutomaticTWSummaryBot,
73 def treat_page(self) -> None:
74 """Load the given page, do some changes, and save it."""
75 wikitext_content_model = 'wikitext'
76 if self.current_page.content_model != wikitext_content_model:
77 warning(f"The content model of {self.current_page.title()} is {self.current_page.content_model} "
78 f"instead of {wikitext_content_model}.")
81 wikicode = mwparserfromhell.parse(self.current_page.text)
82 wikilink_list = wikicode.filter_wikilinks()
83 category_sledrun = 'Kategorie:Rodelbahn'
84 if sum(1 for c in wikilink_list if c.title == category_sledrun) == 0:
85 warning(f'The page {self.current_page.title()} does not have category {category_sledrun}.')
88 sledrun_json_page = Page(self.site, self.current_page.title() + '/Rodelbahn.json')
89 if sledrun_json_page.exists():
90 warning(f"{sledrun_json_page.title()} already exists, skipping {self.current_page.title()}.")
93 map_json_page = Page(self.site, self.current_page.title() + '/Landkarte.json')
94 if map_json_page.exists():
95 warning(f"{map_json_page.title()} already exists, skipping {self.current_page.title()}.")
99 v = wikicode.filter_tags(matches='wrmap')
101 map_json = parse_wrmap(str(v[0]))
104 "name": self.current_page.title(),
106 "entry_under_construction": sum(1 for c in wikilink_list if c.text == 'Kategorie:In Arbeit') > 0,
109 for v in wikicode.get_sections(levels=[2], matches='Allgemeines'):
110 for w in v.ifilter_text(recursive=False):
113 sledrun_json["description"] = str(x)
117 rbb_list = wikicode.filter_templates(recursive=False, matches=lambda t: t.name.strip() == 'Rodelbahnbox')
118 if len(rbb_list) == 1:
119 rbb = rodelbahnbox_from_template(rbb_list[0])
122 image_page = Page(self.site, v, ns=BuiltinNamespace.FILE)
123 if not image_page.exists():
124 warning(f"{image_page.title()} does not exist.")
125 sledrun_json['image'] = v
129 sledrun_json['length'] = v
131 v = rbb['Schwierigkeit']
133 sledrun_json['difficulty'] = difficulty_german_to_str(v)
137 sledrun_json['avalanches'] = avalanches_german_to_str(v)
139 v, w = rbb['Betreiber']
141 sledrun_json['has_operator'] = v
143 sledrun_json['operator'] = w
145 v = rbb['Aufstieg möglich']
147 sledrun_json['walkup_possible'] = v
149 v, w = rbb['Aufstieg getrennt']
151 sledrun_json['walkup_separate'] = tristate_german_to_str(v)
153 sledrun_json['walkup_comment'] = w # TODO
157 sledrun_json['walkup_time'] = v
159 def _walkup_support():
160 walkup_support_rbb = rbb['Aufstiegshilfe']
161 if walkup_support_rbb is not None:
163 for walkup_support_type, comment in walkup_support_rbb:
164 walkup_support = {'type': walkup_support_type}
165 if comment is not None:
166 walkup_support['comment'] = comment
167 walkup_supports.append(walkup_support)
168 sledrun_json['walkup_supports'] = walkup_supports
171 v, w = rbb['Beleuchtungsanlage']
173 sledrun_json['nightlight_possible'] = tristate_german_to_str(v)
175 sledrun_json['nightlight_possible_comment'] = w
177 v, w = rbb['Beleuchtungstage']
179 sledrun_json['nightlight_weekdays_count'] = v
181 sledrun_json['nightlight_weekdays_comment'] = w
184 v = rbb['Rodelverleih']
186 sledrun_json['sled_rental_direct'] = v != []
188 for name, comment in v:
190 name_code = mwparserfromhell.parse(name)
191 wiki_link = next(name_code.ifilter_wikilinks(), None)
192 if isinstance(wiki_link, Wikilink):
193 x['wr_page'] = wikilink_to_json(wiki_link)
196 if comment is not None:
197 x['comment'] = comment
199 sledrun_json['sled_rental'] = w
203 v = rbb['Gütesiegel']
205 sledrun_json['cachet'] = len(v) > 0
208 v = rbb['In Übersichtskarte']
210 sledrun_json['show_in_overview'] = v
214 sledrun_json['forum_id'] = v
218 sledrun_json['position'] = lonlat_to_json(v)
220 v = lonlat_ele_to_json(rbb['Position oben'], rbb['Höhe oben'])
222 sledrun_json['top'] = v
224 v = lonlat_ele_to_json(rbb['Position unten'], rbb['Höhe unten'])
226 sledrun_json['bottom'] = v
228 v = rbb['Telefonauskunft']
230 sledrun_json['info_phone'] = [{'phone': p, 'name': n} for p, n in v]
232 v = rbb['Öffentliche Anreise']
234 sledrun_json['public_transport'] = public_transport_german_to_str(v)
237 bb_iter = wikicode.ifilter_templates(recursive=False, matches=lambda t: t.name.strip() == 'Buttonleiste')
238 bb = next(bb_iter, None)
240 video = bb.get('video', None)
241 if isinstance(video, Parameter):
242 sledrun_json['videos'] = [{'url': video.value}]
245 def _public_transport():
246 pt_sections = wikicode.get_sections(levels=[2], matches='Anreise mit öffentlichen Verkehrsmitteln',
247 include_headings=False)
248 if len(pt_sections) < 1:
251 node = next((node for node in pt.nodes if isinstance(node, Tag) and node.wiki_markup == '*'), None)
253 description = str(Wikicode(pt.nodes[:pt.nodes.index(node)])).strip()
255 sledrun_json["public_transport_description"] = str(description)
257 public_transport_stops = []
258 public_transport_lines = []
259 public_transport_links = []
261 for node in pt.nodes:
262 if isinstance(node, Template):
263 if node.name == 'Haltestelle':
265 public_transport_stops.append(ya)
267 z = node.get(1, None)
269 ya['municipality'] = str(z)
270 z = node.get(2, None)
272 ya['name_local'] = str(z)
273 za = str(node.get(3, '')).strip()
274 zb = str(node.get(4, '')).strip()
275 z = lonlat_ele_to_json(opt_lonlat_from_str(za), opt_uint_from_str(zb))
278 elif node.name in ["Fahrplan Abfahrtsmonitor VVT"]:
279 ya['monitor_template'] = template_to_json(node)
280 elif node.name in ["Fahrplan Hinfahrt VVT"]:
281 ya['route_arrival_template'] = template_to_json(node)
282 elif node.name in ["Fahrplan Rückfahrt VVT"]:
283 ya['route_departure_template'] = template_to_json(node)
284 elif node.name in ["Fahrplan Linie VVT"]:
286 public_transport_stops.append(ya)
289 'timetable_template': template_to_json(node),
291 public_transport_lines.append(y)
292 elif isinstance(node, ExternalLink):
293 public_transport_links.append(external_link_to_json(node))
295 public_transport_stops.append(ya)
296 if len(public_transport_stops) > 0:
297 sledrun_json['public_transport_stops'] = public_transport_stops
298 if len(public_transport_lines) > 0:
299 sledrun_json['public_transport_lines'] = public_transport_lines
300 if len(public_transport_links) > 0:
301 sledrun_json['public_transport_links'] = public_transport_links
305 car_section_list = wikicode.get_sections(levels=[2], matches='Anreise mit dem Auto')
306 if not car_section_list:
308 v = car_section_list[0]
310 description_nodes = dropwhile(lambda w: isinstance(w, Heading), v.nodes)
311 description_nodes = takewhile(lambda w: not (isinstance(w, Tag) and w.wiki_markup == '*'),
313 if description := str(Wikicode(list(description_nodes))).strip():
314 sledrun_json["car_description"] = description
317 for w in v.ifilter_templates(matches='Parkplatz'):
318 za = str(w.get(1, '')).strip()
319 zb = str(w.get(2, '')).strip()
320 z = lonlat_ele_to_json(opt_lonlat_from_str(za), opt_uint_from_str(zb))
322 x.append({'position': z})
324 sledrun_json['car_parking'] = x
327 for w in io.StringIO(str(v)):
328 match = re.match(r"\*\* von \'\'\'(.+)\'\'\'(.*): ([\d.,]+) km", w.rstrip())
330 ya, yb, yc = match.groups()
331 yc = float(yc.replace(',', '.'))
334 'route': (ya.strip() + ' ' + yb.strip()).strip(),
337 sledrun_json['car_distances'] = x
341 for v in wikicode.get_sections(levels=[2], matches='Allgemeines'):
342 def _nightlight(value: str) -> Optional[str]:
343 line_iter = io.StringIO(value)
344 line = next(line_iter, None)
345 while line is not None and not line.startswith("* '''Beleuchtung''':"):
346 line = next(line_iter, None)
349 line = line.replace("* '''Beleuchtung''':", "").strip()
353 w = _nightlight(str(v))
355 sledrun_json['nightlight_description'] = w
357 def _gastronomy(value: str):
359 line_iter = io.StringIO(value)
360 line = next(line_iter, None)
361 while line is not None and line.rstrip() != "* '''Hütten''':":
362 line = next(line_iter, None)
365 while line is not None:
366 line = next(line_iter, None)
368 if line.startswith('** '):
370 wiki = mwparserfromhell.parse(line)
371 wiki_link = next(wiki.ifilter_wikilinks(), None)
372 if isinstance(wiki_link, Wikilink):
373 g['wr_page'] = wikilink_to_json(wiki_link)
374 ext_link = next(wiki.ifilter_external_links(), None)
375 if isinstance(ext_link, ExternalLink):
376 g['weblink'] = external_link_to_json(ext_link)
377 remaining = str(Wikicode(n for n in wiki.nodes
378 if isinstance(n, (Text, Tag)) and str(n).strip() != '*')).strip()
379 match = re.match(r'\((.+)\)', remaining)
381 remaining = match.group(1)
382 if len(remaining) > 0:
383 g['note'] = remaining
388 w = _gastronomy(str(v))
390 sledrun_json['gastronomy'] = w
392 def _sled_rental_description():
393 line_iter = io.StringIO(str(v))
394 line = next(line_iter, None)
396 while line is not None and (match := re.match(r"\* '''Rodelverleih''':(.*)", line)) is None:
397 line = next(line_iter, None)
400 result = [match.group(1)]
401 line = next(line_iter, None)
402 while line is not None and re.match(r"\* ", line) is None:
404 line = next(line_iter, None)
405 sledrun_json['sled_rental_description'] = ''.join(result).strip()
406 _sled_rental_description()
411 if isinstance(w, Tag) and str(w) == "'''Siehe auch'''":
416 if isinstance(w, ExternalLink):
417 x.append(external_link_to_json(w))
418 elif isinstance(w, (Text, Tag)) and str(w).strip() in ['', '*', ':']:
424 sledrun_json['see_also'] = x
426 sledrun_json['allow_reports'] = True
429 sledrun_impressions_page = Page(self.site, self.current_page.title() + '/Impressionen')
430 if sledrun_impressions_page.exists():
431 impressions = sledrun_impressions_page.title()
433 text = create_sledrun_wiki(sledrun_json, map_json, impressions)
434 summary = 'Rodelbahnbeschreibung nach Konvertierung nach und von JSON.'
435 self.put_current(text, summary=summary)
438 def main(*args: str) -> None:
439 local_args = pywikibot.handle_args(args)
440 gen_factory = pagegenerators.GeneratorFactory()
441 gen_factory.handle_args(local_args)
442 gen = gen_factory.getCombinedGenerator(preload=True)
444 bot = SledrunWikiTextToJsonBot(generator=gen)
447 pywikibot.bot.suggest_help(missing_generator=True)
450 if __name__ == '__main__':