2 This module contains functions that convert winterrodeln specific strings (like geographic coordinates) from string
3 to appropriate python types and the other way round.
4 Functions that take strings to convert it to python types are called *_from_str(value, [...]) and are supposed to
5 validate the string. In case of errors, a ValueError (or a subclass thereof) is returned.
6 Functions that take python types and convert it to Winterrodeln strings are called *_to_str(value, [...]) and can
7 assume that the value they get is valid. If it is not, the behavior is undefined.
8 The namedtuple FromToConverter groups corresponding *_from_str and *_to_str converters.
10 import email.headerregistry
13 from collections import OrderedDict, namedtuple
14 from email.errors import HeaderParseError
15 from typing import Tuple, Optional, List, Callable, Union, TypeVar, Dict, NamedTuple
17 import mwparserfromhell # https://github.com/earwig/mwparserfromhell
19 from wrpylib.mwmarkup import format_template_table
22 T = TypeVar("T") # use for generic type annotations
23 E = TypeVar("E") # use for generic type annotations
24 N = TypeVar("N") # use for generic type annotations
27 # FromToConverter type
28 # --------------------
30 # namedtuple that groups corresponding *_from_str and *_to_str functions.
31 FromToConverter = namedtuple('FromToConverter', ['from_str', 'to_str'])
37 def opt_from_str(value: str, from_str: Callable[[str], T], empty: E = None) -> Union[T, E]:
38 """Makes the converter `from_str` "optional"
39 by replacing the empty string with a predefined value (default: None)."""
40 return empty if value == '' else from_str(value)
43 def opt_to_str(value: Union[T, E], to_str: Callable[[T], str], empty: E = None) -> str:
44 return '' if value == empty else to_str(value)
50 def no_german_from_str(value: str, from_str: Callable[[str], T], use_tuple: bool = True, no_value: N = None) \
51 -> Union[Tuple[bool, Union[T, N]], T, N]:
52 """Makes it possible to have "Nein" as special value. If use_tuple is True, a tuple is returned. The first
53 entry of the tuple is False in case the value is "Nein", otherwise the first value is True. The second value is
54 no_value in case of the value being "Nein", otherwise it is the result of from_str(value).
55 If use_tuple is False, no_value is returned in case the value is "Nein", otherwise the result of from_str(value)."""
57 return (False, no_value) if use_tuple else no_value
58 return (True, from_str(value)) if use_tuple else from_str(value)
61 def no_german_to_str(value: Union[Tuple[bool, Union[T, N]], T, N], to_str: Callable[[T], str], use_tuple: bool = True,
62 no_value: N = None) -> str:
66 return to_str(value[1])
73 # "optional"/"no" converter
74 # -------------------------
76 def opt_no_german_from_str(value: str, from_str: Callable[[str], T], use_tuple: bool = True, no_value: N = None,
77 empty: E = (None, None)) -> Union[Tuple[bool, Union[T, N]], T, N, E]:
79 'abc' -> (True, from_str('abc')) or from_str('abc')
80 'Nein' -> (False, no_value) or no_value
83 return opt_from_str(value, lambda v: no_german_from_str(v, from_str, use_tuple, no_value), empty)
86 def opt_no_german_to_str(value: Union[Tuple[bool, Union[T, N]], T, N, E], to_str: Callable[[T], str], use_tuple=True,
87 no_value: N = None, empty: E = (None, None)) -> str:
89 (True, 'abc') -> to_value('abc')
90 (False, no_value) -> 'Nein'
93 return opt_to_str(value, lambda v: no_german_to_str(v, to_str, use_tuple, no_value), empty)
99 def choice_from_str(value, choices):
100 """Returns the value if it is a member of the choices iterable."""
101 if value not in choices:
102 raise ValueError(f"'{value}' is an invalid value")
109 def dictkey_from_str(value: str, key_str_dict: Dict[T, str]) -> T:
110 """Returns the key of an entry in the key_str_dict if the value of the entry corresponds to the given value."""
112 return dict(list(zip(key_str_dict.values(), key_str_dict.keys())))[value]
114 raise ValueError(f"Invalid value '{value}'")
117 def dictkey_to_str(value: T, key_str_dict: Dict[T, str]) -> str:
119 return key_str_dict[value]
121 raise ValueError(f"Invalid value '{value}'")
124 # enum/"list" converter
125 # ---------------------
127 def enum_from_str(value: str, from_str: Callable[[str], T], separator: str = ';', min_len: int = 0) -> List[T]:
128 """Semicolon separated list of entries with the same "type"."""
129 values = value.split(separator)
130 if len(values) == 1 and values[0] == '':
132 if len(values) < min_len:
133 raise ValueError(f'at least {min_len} entry/entries have to be in the enumeration')
134 return list(map(from_str, map(str.strip, values)))
137 def enum_to_str(value: List[T], to_str: Callable[[T], str], separator='; ') -> str:
138 return separator.join(map(to_str, value))
141 # value/comment converter
142 # -----------------------
144 def value_comment_from_str(value: str, value_from_str: Callable[[str], T], comment_from_str: Callable[[str], E],
145 comment_optional: bool = False) -> Tuple[T, E]:
146 """Makes it possible to have a mandatory comment in parenthesis at the end of the string."""
148 if value.endswith(')'):
150 for i, char in enumerate(value[::-1]):
155 if open_brackets == 0:
156 comment = value[-i:-1]
158 if len(value) > 1 and value[-1] != ' ':
159 raise ValueError('there has to be a space before the opening bracket of the comment')
163 if open_brackets > 0:
164 raise ValueError('bracket mismatch')
165 if not comment_optional:
166 raise ValueError('mandatory comment not found')
168 if not comment_optional:
169 raise ValueError(f'mandatory comment not found in "{value}"')
170 return value_from_str(value), comment_from_str(comment)
173 def value_comment_to_str(value: Tuple[T, E], value_to_str: Callable[[T], str], comment_to_str: Callable[[E], str],
174 comment_optional: bool = False) -> str:
175 left = value_to_str(value[0])
176 comment = comment_to_str(value[1])
177 if len(comment) > 0 or not comment_optional:
178 comment = f'({comment})'
181 if len(comment) == 0:
183 return f'{left} {comment}'
189 def str_from_str(value: str) -> str:
190 """Converter that takes any string and returns it as string without validation.
191 In other words, this function does nothing and just returns its argument."""
195 def str_to_str(value: str) -> str:
199 def req_str_from_str(value: str) -> str:
201 raise ValueError('missing required value')
202 return str_from_str(value)
205 def opt_str_from_str(value: str) -> Optional[str]:
206 return opt_from_str(value, str_from_str)
209 def opt_str_to_str(value: Optional[str]) -> str:
210 return opt_to_str(value, str_to_str)
213 opt_str_converter = FromToConverter(opt_str_from_str, opt_str_to_str)
216 # optional no or string converter
217 # -------------------------------
219 def opt_no_or_str_from_str(value: str) -> Tuple[Optional[bool], Optional[str]]:
221 'Nein' => (False, None); 'Nur Wochenende' => (True, 'Nur Wochenende'); 'Ja' => (True, 'Ja'); '' => (None, None)"""
222 return opt_no_german_from_str(value, req_str_from_str)
225 def opt_no_or_str_to_str(value: Tuple[Optional[bool], Optional[str]]) -> str:
226 return opt_no_german_to_str(value, str_to_str)
229 opt_no_or_str_converter = FromToConverter(opt_no_or_str_from_str, opt_no_or_str_to_str)
235 def int_from_str(value: str, minimum: Optional[int] = None, maximum: Optional[int] = None) -> int:
236 """Converter that takes a string representation of an integer and returns the integer.
237 :param value: string representation of an integer
238 :param minimum: If not None, the integer has to be at least min
239 :param maximum: If not None, the integer has to be no more than max
242 if minimum is not None and value < minimum:
243 raise ValueError(f'{value} must be >= than {minimum}')
244 if maximum is not None and value > maximum:
245 raise ValueError(f'{value} must be <= than {maximum}')
249 def int_to_str(value: int) -> str:
253 def opt_int_from_str(value: str, minimum: Optional[int] = None, maximum: Optional[int] = None) -> Optional[int]:
254 return opt_from_str(value, lambda val: int_from_str(val, minimum, maximum))
257 def opt_int_to_str(value: Optional[int]) -> str:
258 return opt_to_str(value, int_to_str)
261 def opt_uint_from_str(value: str, minimum: int = 0, maximum: Optional[int] = None) -> Optional[int]:
262 """Optional positive integer."""
263 return opt_int_from_str(value, minimum, maximum)
266 def opt_uint_to_str(value: Optional[int]) -> str:
267 return opt_int_to_str(value)
270 opt_uint_converter = FromToConverter(opt_uint_from_str, opt_uint_to_str)
276 BOOL_GERMAN = OrderedDict([(False, 'Nein'), (True, 'Ja')])
279 def bool_german_from_str(value: str) -> bool:
280 return dictkey_from_str(value, BOOL_GERMAN)
283 def bool_german_to_str(value: bool) -> str:
284 return dictkey_to_str(value, BOOL_GERMAN)
287 def opt_bool_german_from_str(value: str) -> Optional[bool]:
288 return opt_from_str(value, bool_german_from_str)
291 def opt_bool_german_to_str(value: Optional[bool]) -> str:
292 return opt_to_str(value, bool_german_to_str)
295 opt_bool_german_converter = FromToConverter(opt_bool_german_from_str, opt_bool_german_to_str)
301 TRISTATE_GERMAN = OrderedDict([(0.0, 'Nein'), (0.5, 'Teilweise'), (1.0, 'Ja')])
304 def tristate_german_from_str(value: str) -> float:
305 return dictkey_from_str(value, TRISTATE_GERMAN)
308 def tristate_german_to_str(value: float) -> str:
309 return dictkey_to_str(value, TRISTATE_GERMAN)
312 def opt_tristate_german_from_str(value: str) -> Optional[float]:
313 return opt_from_str(value, tristate_german_from_str)
316 def opt_tristate_german_to_str(value: Optional[float]) -> str:
317 return opt_to_str(value, tristate_german_to_str)
320 opt_tristate_german_converter = FromToConverter(opt_tristate_german_from_str, opt_tristate_german_to_str)
323 # tristate with comment converter
324 # -------------------------------
326 def opt_tristate_german_comment_from_str(value: str) -> Tuple[Optional[float], Optional[str]]:
327 """Ja, Nein or Teilweise, optionally with comment in parenthesis."""
328 return value_comment_from_str(value, opt_tristate_german_from_str, opt_str_from_str, True)
331 def opt_tristate_german_comment_to_str(value: Tuple[Optional[float], Optional[str]]) -> str:
332 return value_comment_to_str(value, opt_tristate_german_to_str, opt_str_to_str, True)
335 opt_tristate_german_comment_converter = FromToConverter(opt_tristate_german_comment_from_str,
336 opt_tristate_german_comment_to_str)
342 def url_from_str(value: str) -> str:
343 result = urllib.parse.urlparse(value)
344 if result.scheme not in ['http', 'https']:
345 raise ValueError('scheme has to be http or https')
346 if not result.netloc:
347 raise ValueError('url does not contain netloc')
351 def url_to_str(value: str) -> str:
355 # webauskunft converter
356 # ---------------------
358 def webauskunft_from_str(value: str) -> Tuple[Optional[bool], Optional[str]]:
359 """Converts a URL or 'Nein' to a tuple
360 'http://www.example.com/' -> (True, 'http://www.example.com/')
361 'Nein' -> (False, None)
364 :param value: URL or 'Nein'
367 return opt_no_german_from_str(value, url_from_str)
370 def webauskunft_to_str(value: Tuple[Optional[bool], Optional[str]]) -> str:
371 return opt_no_german_to_str(value, url_to_str)
374 webauskunft_converter = FromToConverter(webauskunft_from_str, webauskunft_to_str)
380 def wikipage_from_str(value: str) -> str:
381 """Validates wiki page name like '[[Birgitzer Alm]]'.
382 The page is not checked for existence.
383 An empty string is an error.
384 '[[Birgitzer Alm]]' => '[[Birgitzer Alm]]'
386 if re.match(r'\[\[[^\[\]]+]]$', value) is None:
387 raise ValueError(f'No valid wiki page name "{value}"')
391 def wikipage_to_str(value: str) -> str:
395 def opt_wikipage_enum_from_str(value: str) -> Optional[List[str]]:
396 """Validates a list of wiki pages like '[[Birgitzer Alm]]; [[Kemater Alm]]'.
397 '[[Birgitzer Alm]]; [[Kemater Alm]]' => ['[[Birgitzer Alm]]', '[[Kemater Alm]]']
398 '[[Birgitzer Alm]]' => ['[[Birgitzer Alm]]']
402 return opt_no_german_from_str(value, lambda val: enum_from_str(val, wikipage_from_str), False, [], None)
405 def opt_wikipage_enum_to_str(value: Optional[List[str]]) -> str:
406 return opt_no_german_to_str(value, lambda val: enum_to_str(val, wikipage_to_str), False, [], None)
409 opt_wikipage_enum_converter = FromToConverter(opt_wikipage_enum_from_str, opt_wikipage_enum_to_str)
415 def email_from_str(value: str) -> str:
416 """Takes an email address like 'office@example.com', checks it for correctness and returns it again as string."""
418 email.headerregistry.Address(addr_spec=value)
419 except HeaderParseError as e:
420 raise ValueError(f'Invalid email address: {value}', e)
424 def email_to_str(value: str) -> str:
428 def masked_email_from_str(value: str, mask='(at)', masked_only=False) -> Tuple[str, bool]:
429 """Converts an email address that is possibly masked. Returns a tuple. The first parameter is the un-masked
430 email address as string, the second is a boolean telling whether the address was masked."""
431 unmasked = value.replace(mask, '@')
432 was_masked = unmasked != value
433 if masked_only and not was_masked:
434 raise ValueError('E-Mail address not masked')
435 return email_from_str(unmasked), was_masked
438 def masked_email_to_str(value: Tuple[str, bool], mask='(at)') -> str:
439 """Value is a tuple. The first entry is the email address, the second one is a boolean telling whether the
440 email address should be masked."""
441 email_, do_masking = value
442 email_ = email_to_str(email_)
444 email_ = email_.replace('@', mask)
448 def emails_from_str(value: str) -> Optional[List[Tuple[str, str]]]:
449 return opt_no_german_from_str(
452 enum_from_str(val, lambda v: value_comment_from_str(v, masked_email_from_str, opt_str_from_str, True)),
456 def emails_to_str(value: Optional[List[Tuple[str, str]]]) -> str:
457 return opt_no_german_to_str(
459 lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, masked_email_to_str, opt_str_to_str, True)),
463 emails_converter = FromToConverter(emails_from_str, emails_to_str)
469 def phone_number_from_str(value: str) -> str:
470 match = re.match(r'\+\d+(-\d+)*$', value)
472 raise ValueError('invalid format of phone number - use something like +43-699-1234567')
476 def phone_number_to_str(value: str) -> str:
480 def opt_phone_comment_enum_from_str(value: str, comment_optional: bool = False) -> Optional[List[Tuple[str, str]]]:
481 return opt_no_german_from_str(
483 lambda val: enum_from_str(
486 value_comment_from_str(
488 phone_number_from_str,
489 opt_str_from_str if comment_optional else req_str_from_str,
495 def opt_phone_comment_enum_to_str(value: Optional[List[Tuple[str, str]]], comment_optional: bool = False) -> str:
496 return opt_no_german_to_str(
498 lambda val: enum_to_str(
499 val, lambda v: value_comment_to_str(
500 v, phone_number_to_str, opt_str_to_str if comment_optional else str_to_str, comment_optional)),
504 opt_phone_comment_enum_converter = FromToConverter(opt_phone_comment_enum_from_str, opt_phone_comment_enum_to_str)
507 opt_phone_comment_opt_enum_converter = FromToConverter(lambda value: opt_phone_comment_enum_from_str(value, True),
508 lambda value: opt_phone_comment_enum_to_str(value, True))
511 # longitude/latitude converter
512 # ----------------------------
514 class LonLat(NamedTuple):
519 def lonlat_from_str(value: str) -> LonLat:
520 """Converts a Winterrodeln geo string like '47.076207 N 11.453553 E' (being '<latitude> N <longitude> E'
521 to the LonLat(lon, lat) named tuple."""
522 r = re.match(r'(\d+\.\d+) N (\d+\.\d+) E', value)
524 raise ValueError(f"Coordinates '{value}' have not a format like '47.076207 N 11.453553 E'")
525 return LonLat(float(r.groups()[1]), float(r.groups()[0]))
528 def lonlat_to_str(value: LonLat) -> str:
529 return f'{value.lat:.6f} N {value.lon:.6f} E'
532 def opt_lonlat_from_str(value: str) -> Optional[LonLat]:
533 return opt_from_str(value, lonlat_from_str, None)
536 def opt_lonlat_to_str(value: Optional[LonLat]) -> str:
537 return opt_to_str(value, lonlat_to_str, None)
540 opt_lonlat_converter = FromToConverter(opt_lonlat_from_str, opt_lonlat_to_str)
543 # difficulty converter
544 # --------------------
546 DIFFICULTY_GERMAN = OrderedDict([(1, 'leicht'), (2, 'mittel'), (3, 'schwer')])
549 def difficulty_german_from_str(value: str) -> int:
550 return dictkey_from_str(value, DIFFICULTY_GERMAN)
553 def difficulty_german_to_str(value: int) -> str:
554 return dictkey_to_str(value, DIFFICULTY_GERMAN)
557 def opt_difficulty_german_from_str(value: str) -> Optional[int]:
558 return opt_from_str(value, difficulty_german_from_str)
561 def opt_difficulty_german_to_str(value: Optional[int]) -> str:
562 return opt_to_str(value, difficulty_german_to_str)
565 opt_difficulty_german_converter = FromToConverter(opt_difficulty_german_from_str, opt_difficulty_german_to_str)
568 # avalanches converter
569 # --------------------
571 AVALANCHES_GERMAN = OrderedDict([(1, 'kaum'), (2, 'selten'), (3, 'gelegentlich'), (4, 'häufig')])
574 def avalanches_german_from_str(value: str) -> int:
575 return dictkey_from_str(value, AVALANCHES_GERMAN)
578 def avalanches_german_to_str(value: int) -> str:
579 return dictkey_to_str(value, AVALANCHES_GERMAN)
582 def opt_avalanches_german_from_str(value: str) -> Optional[int]:
583 return opt_from_str(value, avalanches_german_from_str)
586 def opt_avalanches_german_to_str(value: Optional[int]) -> str:
587 return opt_to_str(value, avalanches_german_to_str)
590 opt_avalanches_german_converter = FromToConverter(opt_avalanches_german_from_str, opt_avalanches_german_to_str)
596 LIFT_GERMAN = ['Sessellift', 'Gondel', 'Linienbus', 'Taxi', 'Sonstige']
599 def lift_german_from_str(value) -> Optional[List[Tuple[str, Optional[str]]]]:
600 """Checks a lift_details property. It is a value comment property with the following
607 Alternatively, the value u'Nein' is allowed.
608 An empty string maps to (None, None).
613 'Sessellift <=> [('Sessellift', None)]
614 'Gondel (nur bis zur Hälfte)' <=> [('Gondel', 'nur bis zur Hälfte')]
615 'Sessellift; Taxi' <=> [('Sessellift', None), ('Taxi', None)]
616 'Sessellift (Wochenende); Taxi (6 Euro)' <=> [('Sessellift', 'Wochenende'), ('Taxi', '6 Euro')]
618 return opt_no_german_from_str(
623 lambda value_comment:
624 value_comment_from_str(
626 lambda v: choice_from_str(v, LIFT_GERMAN),
628 comment_optional=True)
629 ), use_tuple=False, no_value=[], empty=None)
632 def lift_german_to_str(value: Optional[List[Tuple[str, Optional[str]]]]) -> str:
633 return opt_no_german_to_str(
638 lambda value_comment:
639 value_comment_to_str(
643 comment_optional=True)
645 use_tuple=False, no_value=[], empty=None)
648 lift_german_converter = FromToConverter(lift_german_from_str, lift_german_to_str)
651 # public transport converter
652 # --------------------------
654 PUBLIC_TRANSPORT_GERMAN = OrderedDict(
655 [(1, 'Sehr gut'), (2, 'Gut'), (3, 'Mittelmäßig'), (4, 'Schlecht'), (5, 'Nein'), (6, 'Ja')])
658 def public_transport_german_from_str(value: str) -> int:
659 return dictkey_from_str(value, PUBLIC_TRANSPORT_GERMAN)
662 def public_transport_german_to_str(value: int) -> str:
663 return dictkey_to_str(value, PUBLIC_TRANSPORT_GERMAN)
666 def opt_public_transport_german_from_str(value: str) -> Optional[int]:
667 return opt_from_str(value, public_transport_german_from_str)
670 def opt_public_transport_german_to_str(value: Optional[int]) -> str:
671 return opt_to_str(value, public_transport_german_to_str)
674 opt_public_transport_german_converter = FromToConverter(opt_public_transport_german_from_str,
675 opt_public_transport_german_to_str)
681 CACHET_REGEXP = [r'(Tiroler Naturrodelbahn-Gütesiegel) ([12]\d{3}) (leicht|mittel|schwer)$']
684 def single_cachet_german_from_str(value: str) -> Tuple[str, str, str]:
685 for pattern in CACHET_REGEXP:
686 match_ = re.match(pattern, value)
688 return match_.groups()
689 raise ValueError(f"'{value}' is no valid cachet")
692 def single_cachet_german_to_str(value: Tuple[str, str, str]) -> str:
693 return ' '.join(value)
696 def cachet_german_from_str(value):
697 """Converts a "Gütesiegel":
700 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel' => [('Tiroler Naturrodelbahn-Gütesiegel', '2009', 'mittel')]"""
701 return opt_no_german_from_str(value, lambda val: enum_from_str(val, single_cachet_german_from_str), False, [], None)
704 def cachet_german_to_str(value):
705 return opt_no_german_to_str(value, lambda val: enum_to_str(val, single_cachet_german_to_str), False, [], None)
708 cachet_german_converter = FromToConverter(cachet_german_from_str, cachet_german_to_str)
711 # night light days converter
712 # --------------------------
714 def nightlightdays_from_str(value: str) -> Tuple[int, Optional[str]]:
715 return value_comment_from_str(
718 opt_from_str(val, lambda v: int_from_str(v, minimum=0, maximum=7)), opt_str_from_str, comment_optional=True)
721 def nightlightdays_to_str(value: Tuple[int, Optional[str]]) -> str:
722 return value_comment_to_str(value, lambda val: opt_to_str(val, int_to_str), opt_str_to_str, comment_optional=True)
725 nightlightdays_converter = FromToConverter(nightlightdays_from_str, nightlightdays_to_str)
728 # string with optional comment enum/list converter
729 # ------------------------------------------------
731 def opt_str_opt_comment_enum_from_str(value: str) -> Optional[List[Tuple[str, Optional[str]]]]:
732 """The value can be an empty string, 'Nein' or a semicolon-separated list of strings with optional comments.
735 'Talstation (nur mit Ticket); Schneealm' => [('Talstation', 'nur mit Ticket'), ('Schneealm', None)]"""
736 return opt_no_german_from_str(
742 value_comment_from_str(v, req_str_from_str, opt_str_from_str, True)
746 def opt_str_opt_comment_enum_to_str(value: Optional[List[Tuple[str, Optional[str]]]]) -> str:
747 return opt_no_german_to_str(
752 value_comment_to_str(v, str_to_str, opt_str_to_str, True)
756 opt_str_opt_comment_enum_converter = FromToConverter(opt_str_opt_comment_enum_from_str, opt_str_opt_comment_enum_to_str)
762 class ValueErrorList(ValueError):
766 def wikibox_from_template(template, converter_dict):
767 """Returns an ordered dict."""
768 result = OrderedDict()
769 exceptions_dict = OrderedDict()
771 for key, converter in converter_dict.items():
773 if not template.has(key):
774 raise ValueError(f'Missing parameter "{key}"')
775 result[key] = converter.from_str(str(template.get(key).value.strip()))
776 except ValueError as e:
777 exceptions_dict[key] = e
778 # check if keys are superfluous
779 superfluous_keys = {str(p.name.strip()) for p in template.params} - set(converter_dict.keys())
780 for key in superfluous_keys:
781 exceptions_dict[key] = ValueError(f'Superfluous parameter: "{key}"')
782 if len(exceptions_dict) > 0:
783 raise ValueErrorList(f'{len(exceptions_dict)} error(s) occurred when parsing template parameters.',
788 def wikibox_to_template(value, name, converter_dict) -> mwparserfromhell.nodes.template.Template:
789 template = mwparserfromhell.nodes.template.Template(name)
790 for key, converter in converter_dict.items():
791 template.add(key, converter.to_str(value[key]))
795 def template_from_str(value, name):
796 wikicode = mwparserfromhell.parse(value)
797 template_list = wikicode.filter_templates(recursive=False, matches=lambda t: t.name.strip() == name)
798 if len(template_list) == 0:
799 raise ValueError(f'No "{name}" template was found')
800 if len(template_list) > 1:
801 raise ValueError(f'{len(template_list)} "{name}" templates were found')
802 return template_list[0]
805 def wikibox_from_str(value, name, converter_dict):
806 template = template_from_str(value, name)
807 return wikibox_from_template(template, converter_dict)
810 def wikibox_to_str(value, name, converter_dict):
811 return str(wikibox_to_template(value, name, converter_dict))
814 # Rodelbahnbox converter
815 # ----------------------
817 RODELBAHNBOX_TEMPLATE_NAME = 'Rodelbahnbox'
820 RODELBAHNBOX_DICT = OrderedDict([
821 ('Position', opt_lonlat_converter), # '47.583333 N 15.75 E'
822 ('Position oben', opt_lonlat_converter), # '47.583333 N 15.75 E'
823 ('Höhe oben', opt_uint_converter), # '2000'
824 ('Position unten', opt_lonlat_converter), # '47.583333 N 15.75 E'
825 ('Höhe unten', opt_uint_converter), # '1200'
826 ('Länge', opt_uint_converter), # 3500
827 ('Schwierigkeit', opt_difficulty_german_converter), # 'mittel'
828 ('Lawinen', opt_avalanches_german_converter), # 'kaum'
829 ('Betreiber', opt_no_or_str_converter), # 'Max Mustermann'
830 ('Öffentliche Anreise', opt_public_transport_german_converter), # 'Mittelmäßig'
831 ('Aufstieg möglich', opt_bool_german_converter), # 'Ja'
832 ('Aufstieg getrennt', opt_tristate_german_comment_converter), # 'Ja'
833 ('Gehzeit', opt_uint_converter), # 90
834 ('Aufstiegshilfe', lift_german_converter), # 'Gondel (unterer Teil)'
835 ('Beleuchtungsanlage', opt_tristate_german_comment_converter),
836 ('Beleuchtungstage', nightlightdays_converter), # '3 (Montag, Mittwoch, Freitag)'
837 ('Rodelverleih', opt_str_opt_comment_enum_converter), # 'Talstation Serlesbahnan'
838 ('Gütesiegel', cachet_german_converter), # 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'
839 ('Webauskunft', webauskunft_converter), # 'http://www.nösslachhütte.at/page9.php'
840 ('Telefonauskunft', opt_phone_comment_enum_converter), # '+43-664-5487520 (Mitterer Alm)'
841 ('Bild', opt_str_converter),
842 ('In Übersichtskarte', opt_bool_german_converter),
843 ('Forumid', opt_uint_converter)
847 def rodelbahnbox_from_template(template):
848 """Returns an ordered dict."""
849 return wikibox_from_template(template, RODELBAHNBOX_DICT)
852 def rodelbahnbox_to_template(value):
853 return wikibox_to_template(value, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT)
856 def rodelbahnbox_from_str(value: str) -> Dict:
857 """Returns an ordered dict."""
858 return wikibox_from_str(value, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT)
861 def rodelbahnbox_to_str(value: Dict) -> str:
862 template = rodelbahnbox_to_template(value)
863 format_template_table(template, 20)
867 # Gasthausbox converter
868 # ---------------------
870 GASTHAUSBOX_TEMPLATE_NAME = 'Gasthausbox'
873 GASTHAUSBOX_DICT = OrderedDict([
874 ('Position', opt_lonlat_converter), # '47.583333 N 15.75 E'
875 ('Höhe', opt_uint_converter),
876 ('Betreiber', opt_str_converter),
877 ('Sitzplätze', opt_uint_converter),
878 ('Übernachtung', opt_no_or_str_converter),
879 ('Rauchfrei', opt_tristate_german_converter),
880 ('Rodelverleih', opt_no_or_str_converter),
881 ('Handyempfang', opt_str_opt_comment_enum_converter),
882 ('Homepage', webauskunft_converter),
883 ('E-Mail', emails_converter),
884 ('Telefon', opt_phone_comment_opt_enum_converter),
885 ('Bild', opt_str_converter),
886 ('Rodelbahnen', opt_wikipage_enum_converter)])
889 def gasthausbox_from_template(template):
890 """Returns an ordered dict."""
891 return wikibox_from_template(template, GASTHAUSBOX_DICT)
894 def gasthausbox_to_template(value):
895 return wikibox_to_template(value, GASTHAUSBOX_TEMPLATE_NAME, GASTHAUSBOX_DICT)
898 def gasthausbox_from_str(value):
899 """Returns an ordered dict."""
900 return wikibox_from_str(value, GASTHAUSBOX_TEMPLATE_NAME, GASTHAUSBOX_DICT)
903 def gasthausbox_to_str(value):
904 template = gasthausbox_to_template(value)
905 format_template_table(template, 17)
909 # Helper function to make page title pretty
910 # -----------------------------------------
912 def sledrun_page_title_to_pretty_url(page_title):
913 """Converts a page_title from the page_title column of wrsledruncache to name_url.
914 name_url is not used by MediaWiki but by new applications like wrweb."""
915 return page_title.lower().replace(' ', '-').replace('_', '-').replace('(', '').replace(')', '')