#!/usr/bin/python3.4 # -*- coding: iso-8859-15 -*- # $Id$ # $HeadURL$ """ A converter is a Python variable (may be a class, class instance or anything else) that has the member functions from_str and to_str. From string takes a string "from the outside", checks it and returns a Python variable representing that value in Python. It reports error by raising ValueError. to_str does the opposite, however, it can assume that the value it has to convert to a string is valid. If it gets an invalid value, the behavior is undefined. """ import datetime import email.headerregistry import urllib.parse import re import xml.dom.minidom as minidom from xml.parsers.expat import ExpatError from collections import OrderedDict, namedtuple import mwparserfromhell import formencode import formencode.national from wrpylib.mwmarkup import template_to_table # Meta converter types and functions # ---------------------------------- FromToConverter = namedtuple('FromToConverter', ['from_str', 'to_str']) def opt_from_str(value, from_str, none=None): return none if value == '' else from_str(value) def opt_to_str(value, to_str, none=None): return '' if value == none else to_str(value) def choice_from_str(value, choices): if value not in choices: raise ValueError('{} is an invalid value') return value def dictkey_from_str(value, key_str_dict): 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)) # Basic type converter functions # ------------------------------ def str_from_str(value): return value def str_to_str(value): return 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) def req_str_from_str(value): if value == '': raise ValueError('missing required value') return str_from_str(value) def int_from_str(value, min=None, max=None): 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) opt_int_converter = FromToConverter(opt_int_from_str, opt_int_to_str) IntConverter = FromToConverter(int_from_str, int_to_str) # Complex types # ------------- def enum_from_str(value, from_str=req_str_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=opt_str_to_str, separator='; '): return separator.join(map(to_str, value)) # Specific converter functions # ---------------------------- 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_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) def meter_from_str(value): return int_from_str(value, min=0) def meter_to_str(value): return int_to_str(value) def opt_meter_from_str(value): return opt_from_str(value, meter_from_str) def opt_meter_to_str(value): return opt_to_str(value, meter_to_str) opt_meter_converter = FromToConverter(opt_meter_from_str, opt_meter_to_str) def minutes_from_str(value): return int_from_str(value, min=0) def minutes_to_str(value): return int_to_str(value) def opt_minutes_from_str(value): return opt_from_str(value, minutes_from_str) def opt_minutes_to_str(value): return opt_to_str(value, minutes_to_str) opt_minutes_converter = FromToConverter(opt_minutes_from_str, opt_minutes_to_str) 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) class MultiGeo(formencode.FancyValidator): "Formats multiple coordinates, even in multiple lines to [(latitude, longitude, elevation), ...] or [(latitude, longitude, None), ...] tuplets." # Valid for input_format FORMAT_GUESS = 0 # guesses the input format; default for input_format FORMAT_NONE = -1 # indicates missing formats # Valid for input_format and output_format FORMAT_GEOCACHING = 1 # e.g. "N 47° 13.692 E 011° 25.535" FORMAT_WINTERRODELN = 2 # e.g. "47.222134 N 11.467211 E" FORMAT_GMAPPLUGIN = 3 # e.g. "47.232922, 11.452239" FORMAT_GPX = 4 # e.g. "1090.57" input_format = FORMAT_GUESS output_format = FORMAT_WINTERRODELN last_input_format = FORMAT_NONE def __init__(self, input_format = FORMAT_GUESS, output_format = FORMAT_WINTERRODELN, **keywords): self.input_format = input_format self.output_format = output_format formencode.FancyValidator.__init__(self, if_empty = (None, None, None), **keywords) def to_python(self, value, state=None): self.assert_string(value, state) input_format = self.input_format if not input_format in [self.FORMAT_GUESS, self.FORMAT_GEOCACHING, self.FORMAT_WINTERRODELN, self.FORMAT_GMAPPLUGIN, self.FORMAT_GPX]: raise formencode.Invalid("input_format %d is not recognized" % input_format, value, state) # Shouldn't it be an other type of runtime error? lines = [line.strip() for line in value.split("\n") if len(line.strip()) > 0] result = [] for line in lines: if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GEOCACHING: r = re.match('N ?(\d+)° ?(\d+\.\d+) +E ?(\d+)° ?(\d+\.\d+)', line) if not r is None: g = r.groups() result.append((float(g[0]) + float(g[1])/60, float(g[2]) + float(g[3])/60, None)) last_input_format = self.FORMAT_WINTERRODELN continue if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_WINTERRODELN: r = re.match('(\d+\.\d+) N (\d+\.\d+) E', line) if not r is None: result.append((float(r.groups()[0]), float(r.groups()[1]), None)) last_input_format = self.FORMAT_WINTERRODELN continue if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GMAPPLUGIN: r = re.match('(\d+\.\d+), ?(\d+\.\d+)', line) if not r is None: result.append((float(r.groups()[0]), float(r.groups()[1]), None)) last_input_format = self.FORMAT_GMAPPLUGIN continue if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GPX: try: xml = minidom.parseString(line) coord = xml.documentElement lat = float(coord.getAttribute('lat')) lon = float(coord.getAttribute('lon')) try: ele = float(coord.childNodes[0].childNodes[0].nodeValue) except (IndexError, ValueError): ele = None result.append((lat, lon, ele)) last_input_format = self.FORMAT_GPX continue except (ExpatError, IndexError, ValueError): pass raise formencode.Invalid("Coordinates '%s' have no known format" % line, value, state) return result def from_python(self, value, state=None): output_format = self.output_format result = [] for latitude, longitude, height in value: if output_format == self.FORMAT_GEOCACHING: degree = latitude result.append('N %02d° %02.3f E %03d° %02.3f' % (latitude, latitude % 1 * 60, longitude, longitude % 1 * 60)) elif output_format == self.FORMAT_WINTERRODELN: result.append('%.6f N %.6f E' % (latitude, longitude)) elif output_format == self.FORMAT_GMAPPLUGIN: result.append('%.6f, %.6f' % (latitude, longitude)) elif output_format == self.FORMAT_GPX: if not height is None: result.append('%.2f' % (latitude, longitude, height)) else: result.append('' % (latitude, longitude)) else: raise formencode.Invalid("output_format %d is not recognized" % output_format, value, state) # Shouldn't it be an other type of runtime error? return "\n".join(result) 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_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) 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) def value_comment_from_str(value, value_from_str=str_from_str, comment_from_str=str_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=str_to_str, comment_to_str=str_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) 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) def no_german_from_str(value, from_str=req_str_from_str, use_tuple=True, no_value=None): 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=str_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) def opt_no_german_from_str(value, from_str=str_from_str, use_tuple=True, no_value=None, none=(None, None)): return opt_from_str(value, lambda v: no_german_from_str(v, from_str, use_tuple, no_value), none) def opt_no_german_to_str(value, to_str=str_to_str, use_tuple=True, no_value=None, none=(None, None)): return opt_to_str(value, lambda v: no_german_to_str(v, to_str, use_tuple, no_value), none) def night_light_from_str(value): """'Beleuchtungsanlage' Tristate with optional comment: '' <=> (None, None) 'Ja' <=> (1.0, None) 'Teilweise' <=> (0.5, None) 'Nein' <=> (0.0, None) 'Ja (aber schmal)' <=> (1.0, 'aber schmal') 'Teilweise (oben)' <=> (0.5, 'oben') 'Nein (aber breit)' <=> (0.0, 'aber breit') """ return 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) 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) 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 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) class Url(formencode.FancyValidator): """Validates an URL. In contrast to fromencode.validators.URL, umlauts are allowed.""" # formencode 1.2.5 to formencode 1.3.0a1 sometimes raise ValueError instead of Invalid exceptions # https://github.com/formencode/formencode/pull/61 urlv = formencode.validators.URL() def to_python(self, value, state=None): self.assert_string(value, state) v = value v = v.replace('ä', 'a') v = v.replace('ö', 'o') v = v.replace('ü', 'u') v = v.replace('ß', 'ss') v = self.urlv.to_python(v, state) return value def from_python(self, value, state=None): return value 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 telefonauskunft_from_str(value): return opt_no_german_from_str(value, lambda val: enum_from_str(val, lambda v: value_comment_from_str(v, phone_number_from_str, req_str_from_str, False)), False, [], None) def telefonauskunft_to_str(value): return opt_no_german_to_str(value, lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, phone_number_to_str, str_to_str)), False, [], None) telefonauskunft_converter = FromToConverter(telefonauskunft_from_str, telefonauskunft_to_str) 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) class MaskedEmail(formencode.FancyValidator): """A masked email address as defined here is an email address that has the `@` character replacted by the text `(at)`. So instead of `abd.def@example.com` it would be `abc.def(at)example.com`. This validator takes either a normal or a masked email address in it's to_python method and returns the normal email address as well as a bool indicating whether the email address was masked. u'' <=> (None, None) u'abc.def@example.com' <=> (u'abc.def@example.com', False) u'abc.def(at)example.com' <=> (u'abc.def@example.com', True) """ def __init__(self, *args, **kw): if 'strip' not in kw: kw['strip'] = True if 'not_empty' not in kw: kw['not_empty'] = False if 'if_empty' not in kw: kw['if_empty'] = (None, None) self.at = '(at)' formencode.FancyValidator.__init__(self, *args, **kw) def _to_python(self, value, state=None): email = value.replace(self.at, '@') masked = value != email val_email = formencode.validators.Email() return val_email.to_python(email, state), masked def _from_python(self, value, state=None): email, masked = value if email is None: return '' val_email = formencode.validators.Email() email = val_email.from_python(email, state) if masked: email = email.replace('@', self.at) return email ''' class EmailCommentListNeinLoopNone(NoneValidator): """Converts a semicolon-separated list of email addresses with optional comments to itself. The special value of u'Nein' indicates that there are no email addresses. The empty string translates to None: u'' <=> None u'Nein' <=> u'Nein' u'first@example.com' <=> u'first@example.com' u'first@example.com (Nur Winter); second@example.com' <=> u'first@example.com (Nur Winter); second@example.com' If the parameter allow_masked_email is true, the following gives no error: u'abc.def(at)example.com (comment)' <=> u'abc.def(at)example.com (comment)' """ def __init__(self, allow_masked_email=False): NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList(MaskedEmail() if allow_masked_email else formencode.validators.Email())))) ''' class WikiPage(formencode.FancyValidator): """Validates wiki page name like u'[[Birgitzer Alm]]'. The page is not checked for existance. An empty string is an error. u'[[Birgitzer Alm]]' <=> u'[[Birgitzer Alm]]' """ def to_python(self, value, state=None): self.assert_string(value, state) if not value.startswith('[[') or not value.endswith(']]'): raise formencode.Invalid('No valid wiki page name', value, state) return value def from_python(self, value, state=None): return value ''' class WikiPageList(SemicolonList): """Validates a list of wiki pages like u'[[Birgitzer Alm]]; [[Kemater Alm]]'. u'[[Birgitzer Alm]]; [[Kemater Alm]]' <=> [u'[[Birgitzer Alm]]', u'[[Kemater Alm]]'] u'[[Birgitzer Alm]]' <=> [u'[[Birgitzer Alm]]'] u'' <=> [] """ def __init__(self): SemicolonList.__init__(self, WikiPage()) ''' ''' class WikiPageListLoopNone(NoneValidator): """Validates a list of wiki pages like u'[[Birgitzer Alm]]; [[Kemater Alm]]' as string. u'[[Birgitzer Alm]]; [[Kemater Alm]]' <=> u'[[Birgitzer Alm]]; [[Kemater Alm]]' u'[[Birgitzer Alm]]' <=> u'[[Birgitzer Alm]]' u'' <=> None """ def __init__(self): NoneValidator.__init__(self, Loop(WikiPageList())) ''' 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=[], none=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=[], none=None) lift_german_converter = FromToConverter(lift_german_from_str, lift_german_to_str) def sledrental_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 sledrental_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) sledrental_converter = FromToConverter(sledrental_from_str, sledrental_to_str) class ValueErrorList(ValueError): pass def box_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 box_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 box_from_str(value, name, converter_dict): template = template_from_str(value, name) return box_from_template(template, name, converter_dict) def box_to_str(value, name, converter_dict): return str(box_to_template(value, name, converter_dict)) 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_meter_converter), # '2000' ('Position unten', opt_lonlat_converter), # '47.583333 N 15.75 E' ('Höhe unten', opt_meter_converter), # '1200' ('Länge', opt_meter_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_minutes_converter), # 90 ('Aufstiegshilfe', lift_german_converter), # 'Gondel (unterer Teil)' ('Beleuchtungsanlage', opt_tristate_german_comment_converter), ('Beleuchtungstage', nightlightdays_converter), # '3 (Montag, Mittwoch, Freitag)' ('Rodelverleih', sledrental_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', telefonauskunft_converter), # '+43-664-5487520 (Mitterer Alm)' ('Bild', opt_str_converter), ('In Übersichtskarte', opt_bool_german_converter), ('Forumid', opt_int_converter) ]) def rodelbahnbox_from_template(template): return box_from_template(template, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT) def rodelbahnbox_to_template(value): return box_to_template(value, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT) def rodelbahnbox_from_str(value): return box_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_TEMPLATE_NAME = 'Gasthausbox' ''' GASTHAUSBOX_DICT = OrderedDict([ ('Position', opt_lonlat_converter), # '47.583333 N 15.75 E' ('Höhe', opt_meter_converter), ('Betreiber', opt_str_converter), ('Sitzplätze', opt_int_converter), ('Übernachtung', BoolUnicodeTupleValidator()), ('Rauchfrei', opt_tristate_german_validator), ('Rodelverleih', BoolUnicodeTupleValidator()), ('Handyempfang', ValueCommentListNeinLoopNone()), ('Homepage', webauskunft_converter), ('E-Mail', EmailCommentListNeinLoopNone(allow_masked_email=True)), ('Telefon', PhoneCommentListNeinLoopNone(comments_are_optional=True)), ('Bild', opt_str_converter), ('Rodelbahnen', WikiPageListLoopNone())]) ''' 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(')', '')