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
18 from mwparserfromhell.nodes import Template
20 from wrpylib.mwmarkup import format_template_table
23 T = TypeVar("T") # use for generic type annotations
24 E = TypeVar("E") # use for generic type annotations
25 N = TypeVar("N") # use for generic type annotations
28 # FromToConverter type
29 # --------------------
31 # namedtuple that groups corresponding *_from_str and *_to_str functions.
32 FromToConverter = namedtuple('FromToConverter', ['from_str', 'to_str'])
38 def opt_from_str(value: str, from_str: Callable[[str], T], empty: E = None) -> Union[T, E]:
39 """Makes the converter `from_str` "optional"
40 by replacing the empty string with a predefined value (default: None)."""
41 return empty if value == '' else from_str(value)
44 def opt_to_str(value: Union[T, E], to_str: Callable[[T], str], empty: E = None) -> str:
45 return '' if value == empty else to_str(value)
51 def no_german_from_str(value: str, from_str: Callable[[str], T], use_tuple: bool = True, no_value: N = None) \
52 -> Union[Tuple[bool, Union[T, N]], T, N]:
53 """Makes it possible to have "Nein" as special value. If use_tuple is True, a tuple is returned. The first
54 entry of the tuple is False in case the value is "Nein", otherwise the first value is True. The second value is
55 no_value in case of the value being "Nein", otherwise it is the result of from_str(value).
56 If use_tuple is False, no_value is returned in case the value is "Nein", otherwise the result of from_str(value)."""
58 return (False, no_value) if use_tuple else no_value
59 return (True, from_str(value)) if use_tuple else from_str(value)
62 def no_german_to_str(value: Union[Tuple[bool, Union[T, N]], T, N], to_str: Callable[[T], str], use_tuple: bool = True,
63 no_value: N = None) -> str:
67 return to_str(value[1])
74 # "optional"/"no" converter
75 # -------------------------
77 def opt_no_german_from_str(value: str, from_str: Callable[[str], T], use_tuple: bool = True, no_value: N = None,
78 empty: E = (None, None)) -> Union[Tuple[bool, Union[T, N]], T, N, E]:
80 'abc' -> (True, from_str('abc')) or from_str('abc')
81 'Nein' -> (False, no_value) or no_value
84 return opt_from_str(value, lambda v: no_german_from_str(v, from_str, use_tuple, no_value), empty)
87 def opt_no_german_to_str(value: Union[Tuple[bool, Union[T, N]], T, N, E], to_str: Callable[[T], str], use_tuple=True,
88 no_value: N = None, empty: E = (None, None)) -> str:
90 (True, 'abc') -> to_value('abc')
91 (False, no_value) -> 'Nein'
94 return opt_to_str(value, lambda v: no_german_to_str(v, to_str, use_tuple, no_value), empty)
100 def choice_from_str(value, choices):
101 """Returns the value if it is a member of the choices iterable."""
102 if value not in choices:
103 raise ValueError(f"'{value}' is an invalid value")
110 def dictkey_from_str(value: str, key_str_dict: Dict[T, str]) -> T:
111 """Returns the key of an entry in the key_str_dict if the value of the entry corresponds to the given value."""
113 return dict(list(zip(key_str_dict.values(), key_str_dict.keys())))[value]
115 raise ValueError(f"Invalid value '{value}'")
118 def dictkey_to_str(value: T, key_str_dict: Dict[T, str]) -> str:
120 return key_str_dict[value]
122 raise ValueError(f"Invalid value '{value}'")
125 # enum/"list" converter
126 # ---------------------
128 def enum_from_str(value: str, from_str: Callable[[str], T], separator: str = ';', min_len: int = 0) -> List[T]:
129 """Semicolon separated list of entries with the same "type"."""
130 values = value.split(separator)
131 if len(values) == 1 and values[0] == '':
133 if len(values) < min_len:
134 raise ValueError(f'at least {min_len} entry/entries have to be in the enumeration')
135 return list(map(from_str, map(str.strip, values)))
138 def enum_to_str(value: List[T], to_str: Callable[[T], str], separator='; ') -> str:
139 return separator.join(map(to_str, value))
142 # value/comment converter
143 # -----------------------
145 def value_comment_from_str(value: str, value_from_str: Callable[[str], T], comment_from_str: Callable[[str], E],
146 comment_optional: bool = False) -> Tuple[T, E]:
147 """Makes it possible to have a mandatory comment in parenthesis at the end of the string."""
149 if value.endswith(')'):
151 for i, char in enumerate(value[::-1]):
156 if open_brackets == 0:
157 comment = value[-i:-1]
159 if len(value) > 1 and value[-1] != ' ':
160 raise ValueError('there has to be a space before the opening bracket of the comment')
164 if open_brackets > 0:
165 raise ValueError('bracket mismatch')
166 if not comment_optional:
167 raise ValueError('mandatory comment not found')
169 if not comment_optional:
170 raise ValueError(f'mandatory comment not found in "{value}"')
171 return value_from_str(value), comment_from_str(comment)
174 def value_comment_to_str(value: Tuple[T, E], value_to_str: Callable[[T], str], comment_to_str: Callable[[E], str],
175 comment_optional: bool = False) -> str:
176 left = value_to_str(value[0])
177 comment = comment_to_str(value[1])
178 if len(comment) > 0 or not comment_optional:
179 comment = f'({comment})'
182 if len(comment) == 0:
184 return f'{left} {comment}'
190 def str_from_str(value: str) -> str:
191 """Converter that takes any string and returns it as string without validation.
192 In other words, this function does nothing and just returns its argument."""
196 def str_to_str(value: str) -> str:
200 def req_str_from_str(value: str) -> str:
202 raise ValueError('missing required value')
203 return str_from_str(value)
206 def opt_str_from_str(value: str) -> Optional[str]:
207 return opt_from_str(value, str_from_str)
210 def opt_str_to_str(value: Optional[str]) -> str:
211 return opt_to_str(value, str_to_str)
214 opt_str_converter = FromToConverter(opt_str_from_str, opt_str_to_str)
217 # optional no or string converter
218 # -------------------------------
220 def opt_no_or_str_from_str(value: str) -> Tuple[Optional[bool], Optional[str]]:
222 'Nein' => (False, None); 'Nur Wochenende' => (True, 'Nur Wochenende'); 'Ja' => (True, 'Ja'); '' => (None, None)"""
223 return opt_no_german_from_str(value, req_str_from_str)
226 def opt_no_or_str_to_str(value: Tuple[Optional[bool], Optional[str]]) -> str:
227 return opt_no_german_to_str(value, str_to_str)
230 opt_no_or_str_converter = FromToConverter(opt_no_or_str_from_str, opt_no_or_str_to_str)
236 def int_from_str(value: str, minimum: Optional[int] = None, maximum: Optional[int] = None) -> int:
237 """Converter that takes a string representation of an integer and returns the integer.
238 :param value: string representation of an integer
239 :param minimum: If not None, the integer has to be at least min
240 :param maximum: If not None, the integer has to be no more than max
243 if minimum is not None and value < minimum:
244 raise ValueError(f'{value} must be >= than {minimum}')
245 if maximum is not None and value > maximum:
246 raise ValueError(f'{value} must be <= than {maximum}')
250 def int_to_str(value: int) -> str:
254 def opt_int_from_str(value: str, minimum: Optional[int] = None, maximum: Optional[int] = None) -> Optional[int]:
255 return opt_from_str(value, lambda val: int_from_str(val, minimum, maximum))
258 def opt_int_to_str(value: Optional[int]) -> str:
259 return opt_to_str(value, int_to_str)
262 def opt_uint_from_str(value: str, minimum: int = 0, maximum: Optional[int] = None) -> Optional[int]:
263 """Optional positive integer."""
264 return opt_int_from_str(value, minimum, maximum)
267 def opt_uint_to_str(value: Optional[int]) -> str:
268 return opt_int_to_str(value)
271 opt_uint_converter = FromToConverter(opt_uint_from_str, opt_uint_to_str)
277 BOOL_GERMAN = OrderedDict([(False, 'Nein'), (True, 'Ja')])
280 def bool_german_from_str(value: str) -> bool:
281 return dictkey_from_str(value, BOOL_GERMAN)
284 def bool_german_to_str(value: bool) -> str:
285 return dictkey_to_str(value, BOOL_GERMAN)
288 def opt_bool_german_from_str(value: str) -> Optional[bool]:
289 return opt_from_str(value, bool_german_from_str)
292 def opt_bool_german_to_str(value: Optional[bool]) -> str:
293 return opt_to_str(value, bool_german_to_str)
296 opt_bool_german_converter = FromToConverter(opt_bool_german_from_str, opt_bool_german_to_str)
302 TRISTATE_GERMAN = OrderedDict([(0.0, 'Nein'), (0.5, 'Teilweise'), (1.0, 'Ja')])
305 def tristate_german_from_str(value: str) -> float:
306 return dictkey_from_str(value, TRISTATE_GERMAN)
309 def tristate_german_to_str(value: float) -> str:
310 return dictkey_to_str(value, TRISTATE_GERMAN)
313 def opt_tristate_german_from_str(value: str) -> Optional[float]:
314 return opt_from_str(value, tristate_german_from_str)
317 def opt_tristate_german_to_str(value: Optional[float]) -> str:
318 return opt_to_str(value, tristate_german_to_str)
321 opt_tristate_german_converter = FromToConverter(opt_tristate_german_from_str, opt_tristate_german_to_str)
324 # tristate with comment converter
325 # -------------------------------
327 def opt_tristate_german_comment_from_str(value: str) -> Tuple[Optional[float], Optional[str]]:
328 """Ja, Nein or Teilweise, optionally with comment in parentheses."""
329 return value_comment_from_str(value, opt_tristate_german_from_str, opt_str_from_str, True)
332 def opt_tristate_german_comment_to_str(value: Tuple[Optional[float], Optional[str]]) -> str:
333 return value_comment_to_str(value, opt_tristate_german_to_str, opt_str_to_str, True)
336 opt_tristate_german_comment_converter = FromToConverter(opt_tristate_german_comment_from_str,
337 opt_tristate_german_comment_to_str)
343 def url_from_str(value: str) -> str:
344 result = urllib.parse.urlparse(value)
345 if result.scheme not in ['http', 'https']:
346 raise ValueError('scheme has to be http or https')
347 if not result.netloc:
348 raise ValueError('url does not contain netloc')
352 def url_to_str(value: str) -> str:
356 # webauskunft converter
357 # ---------------------
359 def webauskunft_from_str(value: str) -> Tuple[Optional[bool], Optional[str]]:
360 """Converts a URL or 'Nein' to a tuple
361 'http://www.example.com/' -> (True, 'http://www.example.com/')
362 'Nein' -> (False, None)
365 :param value: URL or 'Nein'
368 return opt_no_german_from_str(value, url_from_str)
371 def webauskunft_to_str(value: Tuple[Optional[bool], Optional[str]]) -> str:
372 return opt_no_german_to_str(value, url_to_str)
375 webauskunft_converter = FromToConverter(webauskunft_from_str, webauskunft_to_str)
381 def wikipage_from_str(value: str) -> str:
382 """Validates wiki page name like '[[Birgitzer Alm]]'.
383 The page is not checked for existence.
384 An empty string is an error.
385 '[[Birgitzer Alm]]' => '[[Birgitzer Alm]]'
387 if re.match(r'\[\[[^\[\]]+]]$', value) is None:
388 raise ValueError(f'No valid wiki page name "{value}"')
392 def wikipage_to_str(value: str) -> str:
396 def opt_wikipage_enum_from_str(value: str) -> Optional[List[str]]:
397 """Validates a list of wiki pages like '[[Birgitzer Alm]]; [[Kemater Alm]]'.
398 '[[Birgitzer Alm]]; [[Kemater Alm]]' => ['[[Birgitzer Alm]]', '[[Kemater Alm]]']
399 '[[Birgitzer Alm]]' => ['[[Birgitzer Alm]]']
403 return opt_no_german_from_str(value, lambda val: enum_from_str(val, wikipage_from_str), False, [], None)
406 def opt_wikipage_enum_to_str(value: Optional[List[str]]) -> str:
407 return opt_no_german_to_str(value, lambda val: enum_to_str(val, wikipage_to_str), False, [], None)
410 opt_wikipage_enum_converter = FromToConverter(opt_wikipage_enum_from_str, opt_wikipage_enum_to_str)
416 def email_from_str(value: str) -> str:
417 """Takes an email address like 'office@example.com', checks it for correctness and returns it again as string."""
419 email.headerregistry.Address(addr_spec=value)
420 except HeaderParseError as e:
421 raise ValueError(f'Invalid email address: {value}', e)
425 def email_to_str(value: str) -> str:
429 def masked_email_from_str(value: str, mask='(at)', masked_only=False) -> Tuple[str, bool]:
430 """Converts an email address that is possibly masked. Returns a tuple. The first parameter is the un-masked
431 email address as string, the second is a boolean telling whether the address was masked."""
432 unmasked = value.replace(mask, '@')
433 was_masked = unmasked != value
434 if masked_only and not was_masked:
435 raise ValueError('E-Mail address not masked')
436 return email_from_str(unmasked), was_masked
439 def masked_email_to_str(value: Tuple[str, bool], mask='(at)') -> str:
440 """Value is a tuple. The first entry is the email address, the second one is a boolean telling whether the
441 email address should be masked."""
442 email_, do_masking = value
443 email_ = email_to_str(email_)
445 email_ = email_.replace('@', mask)
449 def emails_from_str(value: str) -> Optional[List[Tuple[str, str]]]:
450 return opt_no_german_from_str(
453 enum_from_str(val, lambda v: value_comment_from_str(v, masked_email_from_str, opt_str_from_str, True)),
457 def emails_to_str(value: Optional[List[Tuple[str, str]]]) -> str:
458 return opt_no_german_to_str(
460 lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, masked_email_to_str, opt_str_to_str, True)),
464 emails_converter = FromToConverter(emails_from_str, emails_to_str)
470 def phone_number_from_str(value: str) -> str:
471 match = re.match(r'\+\d+(-\d+)*$', value)
473 raise ValueError('invalid format of phone number - use something like +43-699-1234567')
477 def phone_number_to_str(value: str) -> str:
481 def opt_phone_comment_enum_from_str(value: str, comment_optional: bool = False) -> Optional[List[Tuple[str, str]]]:
482 return opt_no_german_from_str(
484 lambda val: enum_from_str(
487 value_comment_from_str(
489 phone_number_from_str,
490 opt_str_from_str if comment_optional else req_str_from_str,
496 def opt_phone_comment_enum_to_str(value: Optional[List[Tuple[str, str]]], comment_optional: bool = False) -> str:
497 return opt_no_german_to_str(
499 lambda val: enum_to_str(
500 val, lambda v: value_comment_to_str(
501 v, phone_number_to_str, opt_str_to_str if comment_optional else str_to_str, comment_optional)),
505 opt_phone_comment_enum_converter = FromToConverter(opt_phone_comment_enum_from_str, opt_phone_comment_enum_to_str)
508 opt_phone_comment_opt_enum_converter = FromToConverter(lambda value: opt_phone_comment_enum_from_str(value, True),
509 lambda value: opt_phone_comment_enum_to_str(value, True))
512 # longitude/latitude converter
513 # ----------------------------
515 class LonLat(NamedTuple):
520 def lonlat_from_str(value: str) -> LonLat:
521 """Converts a Winterrodeln geo string like '47.076207 N 11.453553 E' (being '<latitude> N <longitude> E'
522 to the LonLat(lon, lat) named tuple."""
523 r = re.match(r'(\d+\.\d+) N (\d+\.\d+) E', value)
525 raise ValueError(f"Coordinates '{value}' have not a format like '47.076207 N 11.453553 E'")
526 return LonLat(float(r.groups()[1]), float(r.groups()[0]))
529 def lonlat_to_str(value: LonLat) -> str:
530 return f'{value.lat:.6f} N {value.lon:.6f} E'
533 def opt_lonlat_from_str(value: str) -> Optional[LonLat]:
534 return opt_from_str(value, lonlat_from_str, None)
537 def opt_lonlat_to_str(value: Optional[LonLat]) -> str:
538 return opt_to_str(value, lonlat_to_str, None)
541 opt_lonlat_converter = FromToConverter(opt_lonlat_from_str, opt_lonlat_to_str)
544 # difficulty converter
545 # --------------------
547 DIFFICULTY_GERMAN = OrderedDict([(1, 'leicht'), (2, 'mittel'), (3, 'schwer')])
550 def difficulty_german_from_str(value: str) -> int:
551 return dictkey_from_str(value, DIFFICULTY_GERMAN)
554 def difficulty_german_to_str(value: int) -> str:
555 return dictkey_to_str(value, DIFFICULTY_GERMAN)
558 def opt_difficulty_german_from_str(value: str) -> Optional[int]:
559 return opt_from_str(value, difficulty_german_from_str)
562 def opt_difficulty_german_to_str(value: Optional[int]) -> str:
563 return opt_to_str(value, difficulty_german_to_str)
566 opt_difficulty_german_converter = FromToConverter(opt_difficulty_german_from_str, opt_difficulty_german_to_str)
569 # avalanches converter
570 # --------------------
572 AVALANCHES_GERMAN = OrderedDict([(1, 'kaum'), (2, 'selten'), (3, 'gelegentlich'), (4, 'häufig')])
575 def avalanches_german_from_str(value: str) -> int:
576 return dictkey_from_str(value, AVALANCHES_GERMAN)
579 def avalanches_german_to_str(value: int) -> str:
580 return dictkey_to_str(value, AVALANCHES_GERMAN)
583 def opt_avalanches_german_from_str(value: str) -> Optional[int]:
584 return opt_from_str(value, avalanches_german_from_str)
587 def opt_avalanches_german_to_str(value: Optional[int]) -> str:
588 return opt_to_str(value, avalanches_german_to_str)
591 opt_avalanches_german_converter = FromToConverter(opt_avalanches_german_from_str, opt_avalanches_german_to_str)
597 LIFT_GERMAN = ['Sessellift', 'Gondel', 'Linienbus', 'Taxi', 'Sonstige']
600 def lift_german_from_str(value) -> Optional[List[Tuple[str, Optional[str]]]]:
601 """Checks a lift_details property. It is a value comment property with the following
608 Alternatively, the value u'Nein' is allowed.
609 An empty string maps to (None, None).
614 'Sessellift <=> [('Sessellift', None)]
615 'Gondel (nur bis zur Hälfte)' <=> [('Gondel', 'nur bis zur Hälfte')]
616 'Sessellift; Taxi' <=> [('Sessellift', None), ('Taxi', None)]
617 'Sessellift (Wochenende); Taxi (6 Euro)' <=> [('Sessellift', 'Wochenende'), ('Taxi', '6 Euro')]
619 return opt_no_german_from_str(
624 lambda value_comment:
625 value_comment_from_str(
627 lambda v: choice_from_str(v, LIFT_GERMAN),
629 comment_optional=True)
630 ), use_tuple=False, no_value=[], empty=None)
633 def lift_german_to_str(value: Optional[List[Tuple[str, Optional[str]]]]) -> str:
634 return opt_no_german_to_str(
639 lambda value_comment:
640 value_comment_to_str(
644 comment_optional=True)
646 use_tuple=False, no_value=[], empty=None)
649 lift_german_converter = FromToConverter(lift_german_from_str, lift_german_to_str)
652 # public transport converter
653 # --------------------------
655 PUBLIC_TRANSPORT_GERMAN = OrderedDict(
656 [(1, 'Sehr gut'), (2, 'Gut'), (3, 'Mittelmäßig'), (4, 'Schlecht'), (5, 'Nein'), (6, 'Ja')])
659 def public_transport_german_from_str(value: str) -> int:
660 return dictkey_from_str(value, PUBLIC_TRANSPORT_GERMAN)
663 def public_transport_german_to_str(value: int) -> str:
664 return dictkey_to_str(value, PUBLIC_TRANSPORT_GERMAN)
667 def opt_public_transport_german_from_str(value: str) -> Optional[int]:
668 return opt_from_str(value, public_transport_german_from_str)
671 def opt_public_transport_german_to_str(value: Optional[int]) -> str:
672 return opt_to_str(value, public_transport_german_to_str)
675 opt_public_transport_german_converter = FromToConverter(opt_public_transport_german_from_str,
676 opt_public_transport_german_to_str)
682 CACHET_REGEXP = [r'(Tiroler Naturrodelbahn-Gütesiegel) ([12]\d{3}) (leicht|mittel|schwer)$']
685 def single_cachet_german_from_str(value: str) -> Tuple[str, str, str]:
686 for pattern in CACHET_REGEXP:
687 match_ = re.match(pattern, value)
689 return match_.groups()
690 raise ValueError(f"'{value}' is no valid cachet")
693 def single_cachet_german_to_str(value: Tuple[str, str, str]) -> str:
694 return ' '.join(value)
697 def cachet_german_from_str(value) -> Optional[List[Tuple[str, str, str]]]:
698 """Converts a "Gütesiegel":
701 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel' => [('Tiroler Naturrodelbahn-Gütesiegel', '2009', 'mittel')]"""
702 return opt_no_german_from_str(value, lambda val: enum_from_str(val, single_cachet_german_from_str), False, [], None)
705 def cachet_german_to_str(value):
706 return opt_no_german_to_str(value, lambda val: enum_to_str(val, single_cachet_german_to_str), False, [], None)
709 cachet_german_converter = FromToConverter(cachet_german_from_str, cachet_german_to_str)
712 # night light days converter
713 # --------------------------
715 def nightlightdays_from_str(value: str) -> Tuple[Optional[int], Optional[str]]:
716 return value_comment_from_str(
719 opt_from_str(val, lambda v: int_from_str(v, minimum=0, maximum=7)), opt_str_from_str, comment_optional=True)
722 def nightlightdays_to_str(value: Tuple[Optional[int], Optional[str]]) -> str:
723 return value_comment_to_str(value, lambda val: opt_to_str(val, int_to_str), opt_str_to_str, comment_optional=True)
726 nightlightdays_converter = FromToConverter(nightlightdays_from_str, nightlightdays_to_str)
729 # string with optional comment enum/list converter
730 # ------------------------------------------------
732 def opt_str_opt_comment_enum_from_str(value: str) -> Optional[List[Tuple[str, Optional[str]]]]:
733 """The value can be an empty string, 'Nein' or a semicolon-separated list of strings with optional comments.
736 'Talstation (nur mit Ticket); Schneealm' => [('Talstation', 'nur mit Ticket'), ('Schneealm', None)]"""
737 return opt_no_german_from_str(
743 value_comment_from_str(v, req_str_from_str, opt_str_from_str, True)
747 def opt_str_opt_comment_enum_to_str(value: Optional[List[Tuple[str, Optional[str]]]]) -> str:
748 return opt_no_german_to_str(
753 value_comment_to_str(v, str_to_str, opt_str_to_str, True)
757 opt_str_opt_comment_enum_converter = FromToConverter(opt_str_opt_comment_enum_from_str, opt_str_opt_comment_enum_to_str)
763 class ValueErrorList(ValueError):
767 def wikibox_from_template(template: Template, converter_dict: dict) -> dict:
768 """Returns an ordered dict."""
769 result = OrderedDict()
770 exceptions_dict = OrderedDict()
772 for key, converter in converter_dict.items():
774 if not template.has(key):
775 raise ValueError(f'Missing parameter "{key}"')
776 result[key] = converter.from_str(str(template.get(key).value.strip()))
777 except ValueError as e:
778 exceptions_dict[key] = e
779 # check if keys are superfluous
780 superfluous_keys = {str(p.name.strip()) for p in template.params} - set(converter_dict.keys())
781 for key in superfluous_keys:
782 exceptions_dict[key] = ValueError(f'Superfluous parameter: "{key}"')
783 if len(exceptions_dict) > 0:
784 raise ValueErrorList(f'{len(exceptions_dict)} error(s) occurred when parsing template parameters.',
789 def wikibox_to_template(value: dict, name: str, converter_dict: dict) -> Template:
790 template = Template(name)
791 for key, converter in converter_dict.items():
792 template.add(key, converter.to_str(value[key]))
796 def template_from_str(value: str, name: str) -> Template:
797 wikicode = mwparserfromhell.parse(value)
798 template_list = wikicode.filter_templates(recursive=False, matches=lambda t: t.name.strip() == name)
799 if len(template_list) == 0:
800 raise ValueError(f'No "{name}" template was found')
801 if len(template_list) > 1:
802 raise ValueError(f'{len(template_list)} "{name}" templates were found')
803 return template_list[0]
806 def wikibox_from_str(value: str, name: str, converter_dict: dict) -> dict:
807 template = template_from_str(value, name)
808 return wikibox_from_template(template, converter_dict)
811 def wikibox_to_str(value: dict, name: str, converter_dict: dict) -> str:
812 return str(wikibox_to_template(value, name, converter_dict))
815 # Rodelbahnbox converter
816 # ----------------------
818 RODELBAHNBOX_TEMPLATE_NAME = 'Rodelbahnbox'
821 RODELBAHNBOX_DICT = OrderedDict([
822 ('Position', opt_lonlat_converter), # '47.583333 N 15.75 E'
823 ('Position oben', opt_lonlat_converter), # '47.583333 N 15.75 E'
824 ('Höhe oben', opt_uint_converter), # '2000'
825 ('Position unten', opt_lonlat_converter), # '47.583333 N 15.75 E'
826 ('Höhe unten', opt_uint_converter), # '1200'
827 ('Länge', opt_uint_converter), # 3500
828 ('Schwierigkeit', opt_difficulty_german_converter), # 'mittel'
829 ('Lawinen', opt_avalanches_german_converter), # 'kaum'
830 ('Betreiber', opt_no_or_str_converter), # 'Max Mustermann'
831 ('Öffentliche Anreise', opt_public_transport_german_converter), # 'Mittelmäßig'
832 ('Aufstieg möglich', opt_bool_german_converter), # 'Ja'
833 ('Aufstieg getrennt', opt_tristate_german_comment_converter), # 'Ja'
834 ('Gehzeit', opt_uint_converter), # 90
835 ('Aufstiegshilfe', lift_german_converter), # 'Gondel (unterer Teil)'
836 ('Beleuchtungsanlage', opt_tristate_german_comment_converter),
837 ('Beleuchtungstage', nightlightdays_converter), # '3 (Montag, Mittwoch, Freitag)'
838 ('Rodelverleih', opt_str_opt_comment_enum_converter), # 'Talstation Serlesbahnan'
839 ('Gütesiegel', cachet_german_converter), # 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'
840 ('Webauskunft', webauskunft_converter), # 'http://www.nösslachhütte.at/page9.php'
841 ('Telefonauskunft', opt_phone_comment_enum_converter), # '+43-664-5487520 (Mitterer Alm)'
842 ('Bild', opt_str_converter),
843 ('In Übersichtskarte', opt_bool_german_converter),
844 ('Forumid', opt_uint_converter)
848 def rodelbahnbox_from_template(template: Template) -> dict:
849 """Returns an ordered dict."""
850 return wikibox_from_template(template, RODELBAHNBOX_DICT)
853 def rodelbahnbox_to_template(value: dict) -> Template:
854 return wikibox_to_template(value, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT)
857 def rodelbahnbox_from_str(value: str) -> Dict:
858 """Returns an ordered dict."""
859 return wikibox_from_str(value, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT)
862 def rodelbahnbox_to_str(value: Dict) -> str:
863 template = rodelbahnbox_to_template(value)
864 format_template_table(template, 20)
868 # Gasthausbox converter
869 # ---------------------
871 GASTHAUSBOX_TEMPLATE_NAME = 'Gasthausbox'
874 GASTHAUSBOX_DICT = OrderedDict([
875 ('Position', opt_lonlat_converter), # '47.583333 N 15.75 E'
876 ('Höhe', opt_uint_converter),
877 ('Betreiber', opt_str_converter),
878 ('Sitzplätze', opt_uint_converter),
879 ('Übernachtung', opt_no_or_str_converter),
880 ('Rauchfrei', opt_tristate_german_converter),
881 ('Rodelverleih', opt_no_or_str_converter),
882 ('Handyempfang', opt_str_opt_comment_enum_converter),
883 ('Homepage', webauskunft_converter),
884 ('E-Mail', emails_converter),
885 ('Telefon', opt_phone_comment_opt_enum_converter),
886 ('Bild', opt_str_converter),
887 ('Rodelbahnen', opt_wikipage_enum_converter)])
890 def gasthausbox_from_template(template: Template) -> dict:
891 """Returns an ordered dict."""
892 return wikibox_from_template(template, GASTHAUSBOX_DICT)
895 def gasthausbox_to_template(value: dict) -> Template:
896 return wikibox_to_template(value, GASTHAUSBOX_TEMPLATE_NAME, GASTHAUSBOX_DICT)
899 def gasthausbox_from_str(value: str) -> dict:
900 """Returns an ordered dict."""
901 return wikibox_from_str(value, GASTHAUSBOX_TEMPLATE_NAME, GASTHAUSBOX_DICT)
904 def gasthausbox_to_str(value: dict) -> str:
905 template = gasthausbox_to_template(value)
906 format_template_table(template, 17)
910 # Helper function to make page title pretty
911 # -----------------------------------------
913 def sledrun_page_title_to_pretty_url(page_title: str) -> str:
914 """Converts a page_title from the page_title column of wrsledruncache to name_url.
915 name_url is not used by MediaWiki but by new applications like wrweb."""
916 return page_title.lower().replace(' ', '-').replace('_', '-').replace('(', '').replace(')', '')