#!/usr/bin/python3.4 # -*- coding: iso-8859-15 -*- # $Id$ # $HeadURL$ """ This module contains functions that convert winterrodeln specific strings (like geographic coordinates) from string to appropriate python types and the other way round. Functions that take strings to convert it to python types are called *_from_str(value, [...]) and are supposed to validate the string. In case of errors, a ValueError (or a subclass thereof) is returned. Functions that take python types and convert it to Winterrodeln strings are called *_to_str(value, [...]) and can assume that the value they get is valid. If it is not, the behavior is undefined. The namedtuple FromToConverter groups corresponding *_from_str and *_to_str converters. """ import email.headerregistry import urllib.parse import re from collections import OrderedDict, namedtuple import mwparserfromhell from wrpylib.mwmarkup import template_to_table # FromToConverter type # -------------------- # namedtuple that groups corresponding *_from_str and *_to_str functions. FromToConverter = namedtuple('FromToConverter', ['from_str', 'to_str']) # optional converter # ------------------ def opt_from_str(value, from_str, empty=None): """Makes the converter `from_str` "optional" by replacing the empty string with a predefined value (default: None).""" return empty if value == '' else from_str(value) def opt_to_str(value, to_str, empty=None): return '' if value == empty else to_str(value) # "no" converter # -------------- def no_german_from_str(value, from_str, use_tuple=True, no_value=None): """Makes it possible to have "Nein" as special value. If use_tuple is True, a tuple is returned. The first entry of the tuple is False in case the value is "Nein", otherwiese the first value is True. The second value is no_value in case of the value being "Nein", otherwise it is the result of from_str(value). If use_tuple is False, no_value is returned in case the value is "Nein", otherwise the result of from_str(value).""" if value == 'Nein': return (False, no_value) if use_tuple else no_value return (True, from_str(value)) if use_tuple else from_str(value) def no_german_to_str(value, to_str, use_tuple=True, no_value=None): if use_tuple: if not value[0]: return 'Nein' return to_str(value[1]) else: if value == no_value: return 'Nein' return to_str(value) # "optional"/"no" converter # ------------------------- def opt_no_german_from_str(value, from_str, use_tuple=True, no_value=None, empty=(None, None)): return opt_from_str(value, lambda v: no_german_from_str(v, from_str, use_tuple, no_value), empty) def opt_no_german_to_str(value, to_str, use_tuple=True, no_value=None, empty=(None, None)): return opt_to_str(value, lambda v: no_german_to_str(v, to_str, use_tuple, no_value), empty) # choice converter # ---------------- def choice_from_str(value, choices): """Returns the value if it is a member of the choices iterable.""" if value not in choices: raise ValueError('{} is an invalid value') return value # dictkey converter # ----------------- def dictkey_from_str(value, key_str_dict): """Returns the key of an entry in the key_str_dict if the value of the entry corresponds to the given value.""" try: return dict(list(zip(key_str_dict.values(), key_str_dict.keys())))[value] except KeyError: raise ValueError("Invalid value '{}'".format(value)) def dictkey_to_str(value, key_str_dict): try: return key_str_dict[value] except KeyError: raise ValueError("Invalid value '{}'".format(value)) # enum/"list" converter # --------------------- def enum_from_str(value, from_str, separator=';', min_len=0): """Semicolon separated list of entries with the same "type".""" values = value.split(separator) if len(values) == 1 and values[0] == '': values = [] if len(values) < min_len: raise ValueError('at least {} entry/entries have to be in the enumeration'.format(min_len)) return list(map(from_str, map(str.strip, values))) def enum_to_str(value, to_str, separator='; '): return separator.join(map(to_str, value)) # value/comment converter # ----------------------- def value_comment_from_str(value, value_from_str, comment_from_str, comment_optional=False): """Makes it possible to have a mandatory comment in parenthesis at the end of the string.""" open_brackets = 0 comment = '' comment_end_pos = None for i, char in enumerate(value[::-1]): if char == ')': open_brackets += 1 if open_brackets == 1: comment_end_pos = i if len(value[-1-comment_end_pos:].rstrip()) > 1: raise ValueError('invalid characters after comment') elif char == '(': open_brackets -= 1 if open_brackets == 0: comment = value[-i:-1-comment_end_pos] value = value[:-i-1].rstrip() break else: if open_brackets > 0: raise ValueError('bracket mismatch') if not comment_optional: raise ValueError('mandatory comment not found') return value_from_str(value), comment_from_str(comment) def value_comment_to_str(value, value_to_str, comment_to_str, comment_optional=False): left = value_to_str(value[0]) comment = comment_to_str(value[1]) if len(comment) > 0 or not comment_optional: comment = '({})'.format(comment) if len(left) == 0: return comment if len(comment) == 0: return left return '{} {}'.format(left, comment) # string converter # ---------------- def str_from_str(value): """Converter that takes any string and returns it as string without validation. In other words, this function does nothing and just returns its argument.""" return value def str_to_str(value): return value def req_str_from_str(value): if value == '': raise ValueError('missing required value') return str_from_str(value) def opt_str_from_str(value): return opt_from_str(value, str_from_str) def opt_str_to_str(value): return opt_to_str(value, str_to_str) opt_str_converter = FromToConverter(opt_str_from_str, opt_str_to_str) # optional no or string converter # ------------------------------- def opt_no_or_str_from_str(value): """ 'Nein' => (False, None); 'Nur Wochenende' => (True, 'Nur Wochenende'); 'Ja' => (True, 'Ja'); '' => (None, None)""" return opt_no_german_from_str(value, req_str_from_str) def opt_no_or_str_to_str(value): return opt_no_german_to_str(value, str_to_str) opt_no_or_str_converter = FromToConverter(opt_no_or_str_from_str, opt_no_or_str_to_str) # integer converter # ----------------- def int_from_str(value, min=None, max=None): """Converter that takes a string representation of an integer and returns the integer. :param value: string representation of an integer :param min: If not None, the integer has to be at least min :param max: If not None, the integer has to be no more than max """ value = int(value) if min is not None and value < min: raise ValueError('{} must be >= than {}'.format(value, min)) if max is not None and value > max: raise ValueError('{} must be <= than {}'.format(value, max)) return value def int_to_str(value): return str(value) def opt_int_from_str(value, min=None, max=None): return opt_from_str(value, lambda val: int_from_str(val, min, max)) def opt_int_to_str(value): return opt_to_str(value, int_to_str) def opt_uint_from_str(value, min=0, max=None): """Optional positive integer.""" return opt_int_from_str(value, min, max) def opt_uint_to_str(value): return opt_int_to_str(value) opt_uint_converter = FromToConverter(opt_uint_from_str, opt_uint_to_str) # bool converter # -------------- BOOL_GERMAN = OrderedDict([(False, 'Nein'), (True, 'Ja')]) def bool_german_from_str(value): return dictkey_from_str(value, BOOL_GERMAN) def bool_german_to_str(value): return dictkey_to_str(value, BOOL_GERMAN) def opt_bool_german_from_str(value): return opt_from_str(value, bool_german_from_str) def opt_bool_german_to_str(value): return opt_to_str(value, bool_german_to_str) opt_bool_german_converter = FromToConverter(opt_bool_german_from_str, opt_bool_german_to_str) # tristate converter # ------------------ TRISTATE_GERMAN = OrderedDict([(0.0, 'Nein'), (0.5, 'Teilweise'), (1.0, 'Ja')]) def tristate_german_from_str(value): return dictkey_from_str(value, TRISTATE_GERMAN) def tristate_german_to_str(value): return dictkey_to_str(value, TRISTATE_GERMAN) def opt_tristate_german_from_str(value): return opt_from_str(value, tristate_german_from_str) def opt_tristate_german_to_str(value): return opt_to_str(value, tristate_german_to_str) opt_tristate_german_converter = FromToConverter(opt_tristate_german_from_str, opt_tristate_german_to_str) # tristate with comment converter # ------------------------------- def opt_tristate_german_comment_from_str(value): """Ja, Nein or Vielleicht, optionally with comment in parenthesis.""" return value_comment_from_str(value, opt_tristate_german_from_str, opt_str_from_str, True) def opt_tristate_german_comment_to_str(value): return value_comment_to_str(value, opt_tristate_german_to_str, opt_str_to_str, True) opt_tristate_german_comment_converter = FromToConverter(opt_tristate_german_comment_from_str, opt_tristate_german_comment_to_str) # url converter # ------------- def url_from_str(value): result = urllib.parse.urlparse(value) if result.scheme not in ['http', 'https']: raise ValueError('scheme has to be http or https') if not result.netloc: raise ValueError('url does not contain netloc') return value def url_to_str(value): return value # webauskunft converter # --------------------- def webauskunft_from_str(value): return opt_no_german_from_str(value, url_from_str) def webauskunft_to_str(value): return opt_no_german_to_str(value, url_to_str) webauskunft_converter = FromToConverter(webauskunft_from_str, webauskunft_to_str) # wikipage converter # ------------------ def wikipage_from_str(value): """Validates wiki page name like '[[Birgitzer Alm]]'. The page is not checked for existance. An empty string is an error. '[[Birgitzer Alm]]' => '[[Birgitzer Alm]]' """ if not value.startswith('[[') or not value.endswith(']]'): raise ValueError('No valid wiki page name "{}"'.format(value)) return value def wikipage_to_str(value): return value def opt_wikipage_enum_from_str(value): """Validates a list of wiki pages like '[[Birgitzer Alm]]; [[Kemater Alm]]'. '[[Birgitzer Alm]]; [[Kemater Alm]]' => ['[[Birgitzer Alm]]', '[[Kemater Alm]]'] '[[Birgitzer Alm]]' => ['[[Birgitzer Alm]]'] 'Nein' => [] '' => None """ return opt_no_german_from_str(value, lambda val: enum_from_str(val, wikipage_from_str), False, [], None) def opt_wikipage_enum_to_str(value): return opt_no_german_to_str(value, lambda val: enum_to_str(val, wikipage_to_str), False, [], None) opt_wikipage_enum_converter = FromToConverter(opt_wikipage_enum_from_str, opt_wikipage_enum_to_str) # email converter # --------------- def email_from_str(value): """Takes an email address like 'office@example.com', checks it for correctness and returns it again as string.""" try: email.headerregistry.Address(addr_spec=value) except email.errors.HeaderParseError as e: raise ValueError('Invalid email address: {}'.format(value), e) return value def email_to_str(value): return str(value) def masked_email_from_str(value, mask='(at)', masked_only=False): """Converts an email address that is possibly masked. Returns a tuple. The first parameter is the un-masked email address as string, the second is a boolean telling whether the address was masked.""" unmasked = value.replace(mask, '@') was_masked = unmasked != value if masked_only and not was_masked: raise ValueError('E-Mail address not masked') return email_from_str(unmasked), was_masked def masked_email_to_str(value, mask='(at)'): """Value is a tuple. The first entry is the email address, the second one is a boolean telling whether the email address should be masked.""" email, do_masking = value email = email_to_str(email) if do_masking: email = email.replace('@', mask) return email def emails_from_str(value): return opt_no_german_from_str(value, lambda val: enum_from_str(val, lambda v: value_comment_from_str(v, masked_email_from_str, opt_str_from_str, True)), False, [], None) def emails_to_str(value): return opt_no_german_to_str(value, lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, masked_email_to_str, opt_str_to_str, True)), False, [], None) emails_converter = FromToConverter(emails_from_str, emails_to_str) # phone converter # --------------- def phone_number_from_str(value): match = re.match(r'\+\d+(-\d+)*$', value) if match is None: raise ValueError('invalid format of phone number - use something like +43-699-1234567') return value def phone_number_to_str(value): return value def opt_phone_comment_enum_from_str(value, comment_optional=False): return opt_no_german_from_str(value, lambda val: enum_from_str(val, lambda v: value_comment_from_str(v, phone_number_from_str, opt_str_from_str if comment_optional else req_str_from_str, comment_optional)), False, [], None) def opt_phone_comment_enum_to_str(value, comment_optional=False): return opt_no_german_to_str(value, lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, phone_number_to_str, opt_str_to_str if comment_optional else str_to_str, comment_optional)), False, [], None) opt_phone_comment_enum_converter = FromToConverter(opt_phone_comment_enum_from_str, opt_phone_comment_enum_to_str) opt_phone_comment_opt_enum_converter = FromToConverter(lambda value: opt_phone_comment_enum_from_str(value, True), lambda value: opt_phone_comment_enum_to_str(value, True)) # longitude/latitude converter # ---------------------------- LonLat = namedtuple('LonLat', ['lon', 'lat']) lonlat_none = LonLat(None, None) def lonlat_from_str(value): """Converts a winterrodeln geo string like '47.076207 N 11.453553 E' (being ' N E' to the LonLat(lon, lat) named tupel.""" r = re.match('(\d+\.\d+) N (\d+\.\d+) E', value) if r is None: raise ValueError("Coordinates '{}' have not a format like '47.076207 N 11.453553 E'".format(value)) return LonLat(float(r.groups()[1]), float(r.groups()[0])) def lonlat_to_str(value): return '{:.6f} N {:.6f} E'.format(value.lat, value.lon) def opt_lonlat_from_str(value): return opt_from_str(value, lonlat_from_str, lonlat_none) def opt_lonlat_to_str(value): return opt_to_str(value, lonlat_to_str, lonlat_none) opt_lonlat_converter = FromToConverter(opt_lonlat_from_str, opt_lonlat_to_str) # difficulty converter # -------------------- DIFFICULTY_GERMAN = OrderedDict([(1, 'leicht'), (2, 'mittel'), (3, 'schwer')]) def difficulty_german_from_str(value): return dictkey_from_str(value, DIFFICULTY_GERMAN) def difficulty_german_to_str(value): return dictkey_to_str(value, DIFFICULTY_GERMAN) def opt_difficulty_german_from_str(value): return opt_from_str(value, difficulty_german_from_str) def opt_difficulty_german_to_str(value): return opt_to_str(value, difficulty_german_to_str) opt_difficulty_german_converter = FromToConverter(opt_difficulty_german_from_str, opt_difficulty_german_to_str) # avalanches converter # -------------------- AVALANCHES_GERMAN = OrderedDict([(1, 'kaum'), (2, 'selten'), (3, 'gelegentlich'), (4, 'häufig')]) def avalanches_german_from_str(value): return dictkey_from_str(value, AVALANCHES_GERMAN) def avalanches_german_to_str(value): return dictkey_to_str(value, AVALANCHES_GERMAN) def opt_avalanches_german_from_str(value): return opt_from_str(value, avalanches_german_from_str) def opt_avalanches_german_to_str(value): return opt_to_str(value, avalanches_german_to_str) opt_avalanches_german_converter = FromToConverter(opt_avalanches_german_from_str, opt_avalanches_german_to_str) # lift converter # -------------- LIFT_GERMAN = ['Sessellift', 'Gondel', 'Linienbus', 'Taxi', 'Sonstige'] def lift_german_from_str(value): """Checks a lift_details property. It is a value comment property with the following values allowed: 'Sessellift' 'Gondel' 'Linienbus' 'Taxi' 'Sonstige' Alternatively, the value u'Nein' is allowed. An empty string maps to (None, None). Examples: '' <=> None 'Nein' <=> [] 'Sessellift <=> [('Sessellift', None)] 'Gondel (nur bis zur Hälfte)' <=> [('Gondel', 'nur bis zur Hälfte')] 'Sessellift; Taxi' <=> [('Sessellift', None), ('Taxi', None)] 'Sessellift (Wochenende); Taxi (6 Euro)' <=> [('Sessellift', 'Wochenende'), ('Taxi', '6 Euro')] """ return opt_no_german_from_str(value, lambda value_enum: enum_from_str(value_enum, lambda value_comment: value_comment_from_str(value_comment, lambda v: choice_from_str(v, LIFT_GERMAN), opt_str_from_str, comment_optional=True)), use_tuple=False, no_value=[], empty=None) def lift_german_to_str(value): return opt_no_german_to_str(value, lambda value_enum: enum_to_str(value_enum, lambda value_comment: value_comment_to_str(value_comment, str_to_str, opt_str_to_str, comment_optional=True)), use_tuple=False, no_value=[], empty=None) lift_german_converter = FromToConverter(lift_german_from_str, lift_german_to_str) # public transport converter # -------------------------- PUBLIC_TRANSPORT_GERMAN = OrderedDict([(1, 'Sehr gut'), (2, 'Gut'), (3, 'Mittelmäßig'), (4, 'Schlecht'), (5, 'Nein'), (6, 'Ja')]) def public_transport_german_from_str(value): return dictkey_from_str(value, PUBLIC_TRANSPORT_GERMAN) def public_transport_german_to_str(value): return dictkey_to_str(value, PUBLIC_TRANSPORT_GERMAN) def opt_public_transport_german_from_str(value): return opt_from_str(value, public_transport_german_from_str) def opt_public_transport_german_to_str(value): return opt_to_str(value, public_transport_german_to_str) opt_public_transport_german_converter = FromToConverter(opt_public_transport_german_from_str, opt_public_transport_german_to_str) # cachet converter # ---------------- CACHET_REGEXP = [r'(Tiroler Naturrodelbahn-Gütesiegel) ([12]\d{3}) (leicht|mittel|schwer)$'] def single_cachet_german_from_str(value): for pattern in CACHET_REGEXP: match = re.match(pattern, value) if match: return match.groups() raise ValueError("'{}' is no valid cachet".format(value)) def single_cachet_german_to_str(value): return ' '.join(value) def cachet_german_from_str(value): """Converts a "Gütesiegel": '' => None 'Nein' => [] 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel' => [('Tiroler Naturrodelbahn-Gütesiegel', '2009', 'mittel')]""" return opt_no_german_from_str(value, lambda val: enum_from_str(val, single_cachet_german_from_str), False, [], None) def cachet_german_to_str(value): return opt_no_german_to_str(value, lambda val: enum_to_str(val, single_cachet_german_to_str), False, [], None) cachet_german_converter = FromToConverter(cachet_german_from_str, cachet_german_to_str) # night light days converter # -------------------------- def nightlightdays_from_str(value): return value_comment_from_str(value, lambda val: opt_from_str(val, lambda v: int_from_str(v, min=0, max=7)), opt_str_from_str, comment_optional=True) def nightlightdays_to_str(value): return value_comment_to_str(value, lambda val: opt_to_str(val, int_to_str), opt_str_to_str, comment_optional=True) nightlightdays_converter = FromToConverter(nightlightdays_from_str, nightlightdays_to_str) # string with optional comment enum/list converter # ------------------------------------------------ def opt_str_opt_comment_enum_from_str(value): """The value can be an empty string, 'Nein' or a semicolon-separated list of strings with optional comments. '' => None 'Nein' => [] 'Talstation (nur mit Ticket); Schneealm' => [('Talstation', 'nur mit Ticket'), ('Schneealm', None)]""" return opt_no_german_from_str(value, lambda val: enum_from_str(val, lambda v: value_comment_from_str(v, req_str_from_str, opt_str_from_str, True)), False, [], None) def opt_str_opt_comment_enum_to_str(value): return opt_no_german_to_str(value, lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, str_to_str, opt_str_to_str, True)), False, [], None) opt_str_opt_comment_enum_converter = FromToConverter(opt_str_opt_comment_enum_from_str, opt_str_opt_comment_enum_to_str) # wikibox converter # ----------------- class ValueErrorList(ValueError): pass def wikibox_from_template(template, name, converter_dict): if template.name.strip() != name: raise ValueError('Box name has to be "{}"'.format(name)) result = OrderedDict() exceptions_dict = OrderedDict() # check values for key, converter in converter_dict.items(): try: if not template.has(key): raise ValueError('Missing parameter "{}"'.format(key)) result[key] = converter.from_str(str(template.get(key).value.strip())) except ValueError as e: exceptions_dict[key] = e # check if keys are superfluous superfluous_keys = {str(p.name.strip()) for p in template.params} - set(converter_dict.keys()) for key in superfluous_keys: exceptions_dict[key] = ValueError('Superfluous parameter: "{}"'.format(key)) if len(exceptions_dict) > 0: raise ValueErrorList('{} error(s) occurred when parsing template parameters.'.format(len(exceptions_dict)), exceptions_dict) return result def wikibox_to_template(value, name, converter_dict): template = mwparserfromhell.nodes.template.Template(name) for key, converter in converter_dict.items(): template.add(key, converter.to_str(value[key])) return template def template_from_str(value, name): wikicode = mwparserfromhell.parse(value) template_list = wikicode.filter_templates(name) if len(name) == 0: raise ValueError('No "{}" template was found'.format(name)) if len(template_list) > 1: raise ValueError('{} "{}" templates were found'.format(len(template_list), name)) return template_list[0] def wikibox_from_str(value, name, converter_dict): template = template_from_str(value, name) return wikibox_from_template(template, name, converter_dict) def wikibox_to_str(value, name, converter_dict): return str(wikibox_to_template(value, name, converter_dict)) # Rodelbahnbox converter # ---------------------- RODELBAHNBOX_TEMPLATE_NAME = 'Rodelbahnbox' RODELBAHNBOX_DICT = OrderedDict([ ('Position', opt_lonlat_converter), # '47.583333 N 15.75 E' ('Position oben', opt_lonlat_converter), # '47.583333 N 15.75 E' ('Höhe oben', opt_uint_converter), # '2000' ('Position unten', opt_lonlat_converter), # '47.583333 N 15.75 E' ('Höhe unten', opt_uint_converter), # '1200' ('Länge', opt_uint_converter), # 3500 ('Schwierigkeit', opt_difficulty_german_converter), # 'mittel' ('Lawinen', opt_avalanches_german_converter), # 'kaum' ('Betreiber', opt_str_converter), # 'Max Mustermann' ('Öffentliche Anreise', opt_public_transport_german_converter), # 'Mittelmäßig' ('Aufstieg möglich', opt_bool_german_converter), # 'Ja' ('Aufstieg getrennt', opt_tristate_german_comment_converter), # 'Ja' ('Gehzeit', opt_uint_converter), # 90 ('Aufstiegshilfe', lift_german_converter), # 'Gondel (unterer Teil)' ('Beleuchtungsanlage', opt_tristate_german_comment_converter), ('Beleuchtungstage', nightlightdays_converter), # '3 (Montag, Mittwoch, Freitag)' ('Rodelverleih', opt_str_opt_comment_enum_converter), # 'Talstation Serlesbahnan' ('Gütesiegel', cachet_german_converter), # 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel' ('Webauskunft', webauskunft_converter), # 'http://www.nösslachhütte.at/page9.php' ('Telefonauskunft', opt_phone_comment_enum_converter), # '+43-664-5487520 (Mitterer Alm)' ('Bild', opt_str_converter), ('In Übersichtskarte', opt_bool_german_converter), ('Forumid', opt_uint_converter) ]) def rodelbahnbox_from_template(template): return wikibox_from_template(template, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT) def rodelbahnbox_to_template(value): return wikibox_to_template(value, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT) def rodelbahnbox_from_str(value): return wikibox_from_str(value, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT) def rodelbahnbox_to_str(value): template = rodelbahnbox_to_template(value) template_to_table(template, 20) return str(template) # Gasthausbox converter # --------------------- GASTHAUSBOX_TEMPLATE_NAME = 'Gasthausbox' GASTHAUSBOX_DICT = OrderedDict([ ('Position', opt_lonlat_converter), # '47.583333 N 15.75 E' ('Höhe', opt_uint_converter), ('Betreiber', opt_str_converter), ('Sitzplätze', opt_uint_converter), ('Übernachtung', opt_no_or_str_converter), ('Rauchfrei', opt_tristate_german_converter), ('Rodelverleih', opt_no_or_str_converter), ('Handyempfang', opt_str_opt_comment_enum_converter), ('Homepage', webauskunft_converter), ('E-Mail', emails_converter), ('Telefon', opt_phone_comment_opt_enum_converter), ('Bild', opt_str_converter), ('Rodelbahnen', opt_wikipage_enum_converter)]) def gasthausbox_from_template(template): return wikibox_from_template(template, GASTHAUSBOX_TEMPLATE_NAME, GASTHAUSBOX_DICT) def gasthausbox_to_template(value): return wikibox_to_template(value, GASTHAUSBOX_TEMPLATE_NAME, GASTHAUSBOX_DICT) def gasthausbox_from_str(value): return wikibox_from_str(value, GASTHAUSBOX_TEMPLATE_NAME, GASTHAUSBOX_DICT) def gasthausbox_to_str(value): template = gasthausbox_to_template(value) template_to_table(template, 17) return str(template) # Helper function to make page title pretty # ----------------------------------------- def sledrun_page_title_to_pretty_url(page_title): """Converts a page_title from the page_title column of wrsledruncache to name_url. name_url is not used by MediaWiki but by new applications like wrweb.""" return page_title.lower().replace(' ', '-').replace('_', '-').replace('(', '').replace(')', '')