X-Git-Url: https://git.toastfreeware.priv.at/philipp/winterrodeln/wrpylib.git/blobdiff_plain/65a93c0cd3c4e50bee9f77a0300c6415e05c3365..b700736258c3d7e43fcb7f870ee5d13330bdbe58:/wrpylib/wrvalidators.py diff --git a/wrpylib/wrvalidators.py b/wrpylib/wrvalidators.py index bad2e85..b332a48 100644 --- a/wrpylib/wrvalidators.py +++ b/wrpylib/wrvalidators.py @@ -1,704 +1,821 @@ -#!/usr/bin/python2.6 -# -*- coding: iso-8859-15 -*- +#!/usr/bin/python3.4 +# -*- coding: utf-8 -*- # $Id$ # $HeadURL$ -"""This file contains "validators" that convert between string and python (database) representation -of properties used in the "Rodelbahnbox" and "Gasthausbox". -The "to_python" method has to get a unicode argument. """ -import formencode -import formencode.national -import datetime +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 -import xml.dom.minidom as minidom -from xml.parsers.expat import ExpatError - - -class NoneValidator(formencode.FancyValidator): - """Takes a validator and makes it possible that empty strings are mapped to None.""" - def __init__(self, validator, python_none=None): - self.validator = validator - self.python_none = python_none - - def to_python(self, value): - self.assert_string(value, None) - if value == u'': return self.python_none - return self.validator.to_python(value) - - def from_python(self, value): - if value == self.python_none: return u'' - return self.validator.from_python(value) - - -class NeinValidator(formencode.FancyValidator): - """Take an arbitrary validator and adds the possibility that the - string can be u'Nein'. - Example together with an UnsignedNone validator: - >>> v = NeinValidator(UnsignedNone()) - >>> v.to_python(u'') - None - >>> v.to_python(u'34') - 34 - >>> v.to_python(u'Nein') - u'Nein' - """ - def __init__(self, validator, python_no=u'Nein'): - self.validator = validator - self.python_no = python_no - - def to_python(self, value): - self.assert_string(value, None) - if value == u'Nein': return self.python_no - return self.validator.to_python(value) - - def from_python(self, value): - if value == self.python_no: return u'Nein' - return self.validator.from_python(value) - - -class Unicode(formencode.FancyValidator): - """Converts an unicode string to an unicode string: - u'any string' <=> u'any string'""" - def to_python(self, value): - self.assert_string(value, None) - return unicode(value) - - def from_python(self, value): - return unicode(value) - - -class UnicodeNone(NoneValidator): - """Converts an unicode string to an unicode string: - u'' <=> None - u'any string' <=> u'any string'""" - def __init__(self): - NoneValidator.__init__(self, Unicode()) - - -class Unsigned(formencode.FancyValidator): - """Converts an unsigned number to a string and vice versa: - u'0' <=> 0 - u'1' <=> 1 - u'45' <=> 45 - """ - def __init__(self, max=None): - self.iv = formencode.validators.Int(min=0, max=max) - - def to_python(self, value): - self.assert_string(value, None) - return self.iv.to_python(value) - - def from_python(self, value): - return unicode(value) - - -class UnsignedNone(NoneValidator): - """Converts an unsigned number to a string and vice versa: - u'' <=> None - u'0' <=> 0 - u'1' <=> 1 - u'45' <=> 45 - """ - def __init__(self, max=None): - NoneValidator.__init__(self, Unsigned(max)) +from collections import OrderedDict, namedtuple +import mwparserfromhell -class UnsignedNeinNone(NoneValidator): - """ Translates a number of Nein to a number. - u'' <=> None - u'Nein' <=> 0 - u'1' <=> 1 - u'2' <=> 2 - ... - """ - def __init__(self): - NoneValidator.__init__(self, UnsignedNone()) - - -class Loop(formencode.FancyValidator): - """Takes a validator and calls from_python(to_python(value)).""" - def __init__(self, validator): - self.validator = validator - - def to_python(self, value): - self.assert_string(value, None) - return self.validator.from_python(self.validator.to_python(value)) - - def from_python(self, value): - # we don't call self.validator.to_python(self.validator.from_python(value)) - # here because our to_python implementation basically leaves the input untouches - # and so should from_python do. - return self.validator.from_python(self.validator.to_python(value)) - - -class DictValidator(formencode.FancyValidator): - """Translates strings to other values via a python directory. - >>> boolValidator = DictValidator({u'': None, u'Ja': True, u'Nein': False}) - >>> boolValidator.to_python(u'') - None - >>> boolValidator.to_python(u'Ja') - True - """ - def __init__(self, dict): - self.dict = dict - - def to_python(self, value): - self.assert_string(value, None) - if not self.dict.has_key(value): raise formencode.Invalid("Key not found in dict.", value, None) - return self.dict[value] - - def from_python(self, value): - for k, v in self.dict.iteritems(): - if type(v) == type(value) and v == value: return k - raise formencode.Invalid('Invalid value', value, None) - - -class GermanBoolNone(DictValidator): - """Converts German bool values to the python bool type: - u'' <=> None - u'Ja' <=> True - u'Nein' <=> False - """ - def __init__(self): - DictValidator.__init__(self, {u'': None, u'Ja': True, u'Nein': False}) - - -class GermanTristateTuple(DictValidator): - """Does the following conversion: - u'' <=> (None, None) - u'Ja' <=> (True, False) - u'Teilweise' <=> (True, True) - u'Nein' <=> (False, True)""" - def __init__(self, yes_python = (True, False), no_python = (False, True), partly_python = (True, True), none_python = (None, None)): - DictValidator.__init__(self, {u'': none_python, u'Ja': yes_python, u'Nein': no_python, u'Teilweise': partly_python}) - - -class GermanTristateFloat(GermanTristateTuple): - """Converts the a property with the possible values 0.0, 0.5, 1.0 or None - to a German text: - u'' <=> None - u'Ja' <=> 1.0 - u'Teilweise' <=> 0.5 - u'Nein' <=> 0.0""" - def __init__(self): - GermanTristateTuple.__init__(self, yes_python=1.0, no_python=0.0, partly_python=0.5, none_python=None) - - -class ValueComment(formencode.FancyValidator): - """Converts value with a potentially optional comment to a python tuple: - u'' <=> (None, None) - u'value' <=> (u'value', None) - u'value (comment)' <=> (u'value', u'comment')""" - def __init__(self, value_validator=UnicodeNone(), comment_validator=UnicodeNone(), comment_is_optional=True): - self.value_validator = value_validator - self.comment_validator = comment_validator - self.comment_is_optional = comment_is_optional - - def to_python(self, value): - self.assert_string(value, None) - if value == u'': - v = value - c = value +from wrpylib.mwmarkup import format_template_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.""" + comment = '' + if value.endswith(')'): + open_brackets = 0 + for i, char in enumerate(value[::-1]): + if char == ')': + open_brackets += 1 + elif char == '(': + open_brackets -= 1 + if open_brackets == 0: + comment = value[-i:-1] + value = value[:-i-1] + if len(value) > 1 and value[-1] != ' ': + raise ValueError('there has to be a space before the opening bracket of the comment') + value = value[:-1] + break else: - left = value.find('(') - right = value.rfind(')') - if left < 0 and right < 0: - if not self.comment_is_optional: raise formencode.Invalid(u'Mandatory comment not present', value, None) - v = value - c = u'' - elif left >= 0 and right >= 0 and left < right: - v = value[:left].strip() - c = value[left+1:right].strip() - else: raise formencode.Invalid(u'Invalid format', value, None) - return self.value_validator.to_python(v), self.comment_validator.to_python(c) - - def from_python(self, value): - assert len(value) == 2 - v = self.value_validator.from_python(value[0]) - c = self.comment_validator.from_python(value[1]) - if len(c) > 0: - if len(v) > 0: return u'%s (%s)' % (v, c) - else: return u'(%s)' % c - return v - - -class SemicolonList(formencode.FancyValidator): - """Applies a given validator to a semicolon separated list of values and returns a python list. - For an empty string an empty list is returned.""" - def __init__(self, validator=Unicode()): - self.validator = validator - - def to_python(self, value): - self.assert_string(value, None) - return [self.validator.to_python(s.strip()) for s in value.split(';')] - - def from_python(self, value): - return "; ".join([self.validator.from_python(s) for s in value]) - - -class ValueCommentList(SemicolonList): - """A value-comment list looks like one of the following lines: - value - value (optional comment) - value1; value2 - value1; value2 (optional comment) - value1 (optional comment1); value2 (optional comment2); value3 (otional comment3) - value1 (optional comment1); value2 (optional comment2); value3 (otional comment3) - This function returns the value-comment list as list of tuples: - [(u'value1', u'comment1'), (u'value2', None)] - If no comment is present, None is specified. - For an empty string, [] is returned.""" - def __init__(self, value_validator=Unicode(), comments_are_optional=True): - SemicolonList.__init__(self, ValueComment(value_validator, comment_is_optional=comments_are_optional)) - - -class GenericDateTime(formencode.FancyValidator): - """Converts a generic date/time information to a datetime class with a user defined format. - '2009-03-22 20:36:15' would be specified as '%Y-%m-%d %H:%M:%S'.""" - - def __init__(self, date_time_format = '%Y-%m-%d %H:%M:%S', **keywords): - formencode.FancyValidator.__init__(self, **keywords) - self.date_time_format = date_time_format - - def to_python(self, value, state=None): - self.assert_string(value, None) - try: return datetime.datetime.strptime(value, self.date_time_format) - except ValueError, e: raise formencode.Invalid(str(e), value, None) - - def from_python(self, value, state=None): - return value.strftime(self.date_time_format) - - -class DateTimeNoSec(GenericDateTime): - def __init__(self, **keywords): - GenericDateTime.__init__(self, '%Y-%m-%d %H:%M', **keywords) - - -class DateNone(NoneValidator): - """Converts date information to date classes with the format '%Y-%m-%d' or None.""" - def __init__(self): - NoneValidator.__init__(self, GenericDateTime('%Y-%m-%d')) - - -class Geo(formencode.FancyValidator): - """Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet.""" - def to_python(self, value): - self.assert_string(value, None) - r = re.match(u'(\d+\.\d+) N (\d+\.\d+) E', value) - if r is None: raise formencode.Invalid(u"Coordinates '%s' have not a format like '47.076207 N 11.453553 E'" % value, value, None) - return (float(r.groups()[0]), float(r.groups()[1])) - - def from_python(self, value): - latitude, longitude = value - return u'%.6f N %.6f E' % (latitude, longitude) - - -class GeoNone(NoneValidator): - """Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet.""" - def __init__(self): - NoneValidator.__init__(self, Geo(), (None, None)) - - -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): - self.assert_string(value, None) - 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(u"input_format %d is not recognized" % input_format, value, None) # 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(u'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(u'(\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(u'(\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(u"Coordinates '%s' have no known format" % line, value, None) - - return result - - def from_python(self, value): - output_format = self.output_format - result = [] - for latitude, longitude, height in value: - if output_format == self.FORMAT_GEOCACHING: - degree = latitude - result.append(u'N %02d° %02.3f E %03d° %02.3f' % (latitude, latitude % 1 * 60, longitude, longitude % 1 * 60)) - - elif output_format == self.FORMAT_WINTERRODELN: - result.append(u'%.6f N %.6f E' % (latitude, longitude)) - - elif output_format == self.FORMAT_GMAPPLUGIN: - result.append(u'%.6f, %.6f' % (latitude, longitude)) - - elif output_format == self.FORMAT_GPX: - if not height is None: result.append(u'%.2f' % (latitude, longitude, height)) - else: result.append(u'' % (latitude, longitude)) - - else: - raise formencode.Invalid(u"output_format %d is not recognized" % output_format, value, None) # Shouldn't it be an other type of runtime error? - - return "\n".join(result) - - -# deprecated -class AustrianPhoneNumber(formencode.FancyValidator): - """ - Validates and converts phone numbers to +##/###/####### or +##/###/#######-### (having an extension) - @param default_cc country code for prepending if none is provided, defaults to 43 (Austria) - :: - >>> v = AustrianPhoneNumber() - >>> v.to_python(u'0512/12345678') - u'+43/512/12345678' - >>> v.to_python(u'+43/512/12345678') - u'+43/512/12345678' - >>> v.to_python(u'0512/1234567-89') # 89 is the extension - u'+43/512/1234567-89' - >>> v.to_python(u'+43/512/1234567-89') - u'+43/512/1234567-89' - >>> v.to_python(u'0512 / 12345678') # Exception - >>> v.to_python(u'0512-12345678') # Exception - """ - # Inspired by formencode.national.InternationalPhoneNumber - - default_cc = 43 # Default country code - messages = {'phoneFormat': "'%%(value)s' is an invalid format. Please enter a number in the form +43/###/####### or 0###/########."} - - def to_python(self, value): - self.assert_string(value, None) - m = re.match(u'^(?:\+(\d+)/)?([\d/]+)(?:-(\d+))?$', value) - # This will separate - # u'+43/512/1234567-89' => (u'43', u'512/1234567', u'89') - # u'+43/512/1234/567-89' => (u'43', u'512/1234/567', u'89') - # u'+43/512/1234/567' => (u'43', u'512/1234/567', None) - # u'0512/1234567' => (None, u'0512/1234567', None) - if m is None: raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, None) - (country, phone, extension) = m.groups() - - # Phone - if phone.find(u'//') > -1 or phone.count('/') == 0: raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, None) - - # Country - if country is None: - if phone[0] != '0': raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, None) - phone = phone[1:] - country = unicode(self.default_cc) - - if extension is None: return '+%s/%s' % (country, phone) - return '+%s/%s-%s' % (country, phone, extension) - - -# Deprecated -class AustrianPhoneNumberNone(NoneValidator): - def __init__(self): - NoneValidator.__init__(self, AustrianPhoneNumber()) - - -# Deprecated -class AustrianPhoneNumberCommentLoop(NoneValidator): - def __init__(self): - NoneValidator.__init__(self, Loop(ValueComment(AustrianPhoneNumber()))) - - -class GermanDifficulty(DictValidator): - """Converts the difficulty represented in a number from 1 to 3 (or None) - to a German representation: - u'' <=> None - u'leicht' <=> 1 - u'mittel' <=> 2 - u'schwer' <=> 3""" - def __init__(self): - DictValidator.__init__(self, {u'': None, u'leicht': 1, u'mittel': 2, u'schwer': 3}) - - -class GermanAvalanches(DictValidator): - """Converts the avalanches property represented as number from 1 to 4 (or None) - to a German representation: - u'' <=> None - u'kaum' <=> 1 - u'selten' <=> 2 - u'gelegentlich' <=> 3 - u'häufig' <=> 4""" - def __init__(self): - DictValidator.__init__(self, {u'': None, u'kaum': 1, u'selten': 2, u'gelegentlich': 3, u'häufig': 4}) - - -class GermanPublicTransport(DictValidator): - """Converts the public_transport property represented as number from 1 to 6 (or None) - to a German representation: - u'' <=> None - u'Sehr gut' <=> 1 - u'Gut' <=> 2 - u'Mittelmäßig' <=> 3 - u'Schlecht' <=> 4 - u'Nein' <=> 5 - u'Ja' <=> 6""" - def __init__(self): - DictValidator.__init__(self, {u'': None, u'Sehr gut': 1, u'Gut': 2, u'Mittelmäßig': 3, u'Schlecht': 4, u'Nein': 5, u'Ja': 6}) - - -class GermanTristateFloatComment(ValueComment): - """Converts the a property with the possible values 0.0, 0.5, 1.0 or None and an optional comment - in parenthesis to a German text: - u'' <=> (None, None) - u'Ja' <=> (1.0, None) - u'Teilweise' <=> (0.5, None) - u'Nein' <=> (0.0, None) - u'Ja (aber schmal)' <=> (1.0, u'aber schmal') - u'Teilweise (oben)' <=> (0.5, u'oben') - u'Nein (aber breit)' <=> (0.0, u'aber breit') - """ - def __init__(self): - ValueComment.__init__(self, GermanTristateFloat()) + if open_brackets > 0: + raise ValueError('bracket mismatch') + if not comment_optional: + raise ValueError('mandatory comment not found') + else: + if not comment_optional: + raise ValueError('mandatory comment not found in "{}"'.format(value)) + return value_from_str(value), comment_from_str(comment) -class UnsignedCommentNone(NoneValidator): - """Converts the a property with unsigned values an optional comment - in parenthesis to a text: - u'' <=> (None, None) - u'2 (Mo, Di)' <=> (2, u'Mo, Di') - u'7' <=> (7, None) - u'0' <=> (0, None) - """ - def __init__(self, max=None): - NoneValidator.__init__(self, ValueComment(Unsigned(max=max)), (None, None)) - - -class GermanCachet(formencode.FancyValidator): - """Converts a "Gütesiegel": - u'' <=> None - u'Nein' <=> 'Nein' - u'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel' <=> u'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'""" - def to_python(self, value): - self.assert_string(value, None) - if value == u'': return None - elif value == u'Nein': return value - elif value.startswith(u'Tiroler Naturrodelbahn-Gütesiegel '): - p = value.split(" ") - Unsigned().to_python(p[2]) # check if year can be parsed - if not p[3] in ['leicht', 'mittel', 'schwer']: raise formencode.Invalid("Unbekannter Schwierigkeitsgrad", value, None) - return value - else: raise formencode.Invalid(u"Unbekanntes Gütesiegel", value, None) - - def from_python(self, value): - if value == None: return u'' - assert value != u'' - return self.to_python(value) - - -class Url(formencode.FancyValidator): - """Validates an URL. In contrast to fromencode.validators.URL, umlauts are allowed.""" - urlv = formencode.validators.URL() - def to_python(self, value): - self.assert_string(value, None) - v = value - v = v.replace(u'ä', u'a') - v = v.replace(u'ö', u'o') - v = v.replace(u'ü', u'u') - v = v.replace(u'ß', u'ss') - v = self.urlv.to_python(v) - return value - - def from_python(self, value): - return value - - -class UrlNeinNone(NoneValidator): - """Validates an URL. In contrast to fromencode.validators.URL, umlauts are allowed. - The special value u"Nein" is allowed.""" - def __init__(self): - NoneValidator.__init__(self, NeinValidator(Url())) - - -class ValueCommentListNeinLoopNone(NoneValidator): - """Translates a semicolon separated list of values with optional comments in paranthesis or u'Nein' to itself. - An empty string is translated to None: - u'' <=> None - u'Nein' <=> u'Nein' - u'T-Mobile (gut); A1' <=> u'T-Mobile (gut); A1'""" - def __init__(self): - NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList()))) - - -class PhoneNumber(formencode.FancyValidator): - """Telefonnumber in international format, e.g. u'+43-699-1234567'""" - def __init__(self, default_cc=43): - self.validator = formencode.national.InternationalPhoneNumber(default_cc=lambda: default_cc) - - def to_python(self, value): - return unicode(self.validator.to_python(value)) - - def from_python(self, value): - return self.validator.from_python(value) - - -class PhoneCommentListNeinLoopNone(NoneValidator): - """List with semicolon-separated phone numbers in international format with optional comment or 'Nein' as string: - u'' <=> None - u'Nein' <=> u'Nein' - u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456' <=> u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456' +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): """ - def __init__(self, comments_are_optional): - NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList(PhoneNumber(default_cc=43), comments_are_optional=comments_are_optional)))) - - -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' + '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 """ - def __init__(self): - NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList(formencode.validators.Email())))) + 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) -class WikiPage(formencode.FancyValidator): - """Validates wiki page name like u'[[Birgitzer Alm]]'. +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. - u'[[Birgitzer Alm]]' <=> u'[[Birgitzer Alm]]' - """ - def to_python(self, value): - self.assert_string(value, None) - if not value.startswith('[[') or not value.endswith(']]'): - raise formencode.Invalid('No valid wiki page name', value, None) - return value - - def from_python(self, value): - 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'' <=> [] + '[[Birgitzer Alm]]' => '[[Birgitzer Alm]]' """ - def __init__(self): - SemicolonList.__init__(self, WikiPage()) + if not value.startswith('[[') or not value.endswith(']]'): + raise ValueError('No valid wiki page name "{}"'.format(value)) + return value -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())) - - -class TupleSecondValidator(formencode.FancyValidator): - """Does not really validate anything but puts the string through - a validator in the second part of a tuple. - Examples with an Unsigned() validator and the True argument: - u'6' <=> (True, 6) - u'2' <=> (True, 2)""" - def __init__(self, first=True, validator=UnicodeNone()): - self.first = first - self.validator = validator - - def to_python(self, value): - self.assert_string(value, None) - return self.first, self.validator.to_python(value) - - def from_python(self, value): - assert value[0] == self.first - return self.validator.from_python(value[1]) - - -class BoolUnicodeTupleValidator(NoneValidator): - """Translates an unparsed string or u'Nein' to a tuple: - u'' <=> (None, None) - u'Nein' <=> (False, None) - u'any text' <=> (True, u'any text') +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 """ - def __init__(self, validator=UnicodeNone()): - NoneValidator.__init__(self, NeinValidator(TupleSecondValidator(True, validator), (False, None)), (None, 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) -class GermanLift(BoolUnicodeTupleValidator): +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: - u'Sessellift' - u'Gondel' - u'Linienbus' - u'Taxi' - u'Sonstige' + 'Sessellift' + 'Gondel' + 'Linienbus' + 'Taxi' + 'Sonstige' Alternatively, the value u'Nein' is allowed. An empty string maps to (None, None). - + Examples: - u'' <=> (None, None) - u'Nein' <=> (False, None) - u'Sessellift <=> (True, u'Sessellift') - u'Gondel (nur bis zur Hälfte)' <=> (True, u'Gondel (nur bis zur Hälfte)') - u'Sessellift; Taxi' <=> (True, u'Sessellift; Taxi') - u'Sessellift (Wochenende); Taxi (6 Euro)' <=> (True, u'Sessellift (Wochenende); Taxi (6 Euro)') + '' <=> 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')] """ - def __init__(self): - BoolUnicodeTupleValidator.__init__(self, Loop(ValueCommentList(DictValidator({u'Sessellift': u'Sessellift', u'Gondel': u'Gondel', u'Linienbus': u'Linienbus', u'Taxi': u'Taxi', u'Sonstige': u'Sonstige'})))) - - -class SledRental(BoolUnicodeTupleValidator): - """The value can be an empty string, u'Nein' or a comma-separated list of unicode strings with optional comments. - u'' <=> (None, None) - u'Nein' <=> (False, None) - u'Talstation (nur mit Ticket); Schneealm' <=> (True, u'Talstation (nur mit Ticket); Schneealm')""" - def __init__(self): - BoolUnicodeTupleValidator.__init__(self, Loop(ValueCommentList())) + 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, converter_dict): + """Returns an ordered dict.""" + 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, 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): + """Returns an ordered dict.""" + return wikibox_from_template(template, RODELBAHNBOX_DICT) + + +def rodelbahnbox_to_template(value): + return wikibox_to_template(value, RODELBAHNBOX_DICT, RODELBAHNBOX_TEMPLATE_NAME) + + +def rodelbahnbox_from_str(value): + """Returns an ordered dict.""" + return wikibox_from_str(value, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT) + + +def rodelbahnbox_to_str(value): + template = rodelbahnbox_to_template(value) + format_template_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): + """Returns an ordered dict.""" + 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): + """Returns an ordered dict.""" + return wikibox_from_str(value, GASTHAUSBOX_TEMPLATE_NAME, GASTHAUSBOX_DICT) + + +def gasthausbox_to_str(value): + template = gasthausbox_to_template(value) + format_template_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(')', '')