]> ToastFreeware Gitweb - philipp/winterrodeln/wradmin.git/blob - wradmin/model/validators.py
gasthaus_list is rendered now.
[philipp/winterrodeln/wradmin.git] / wradmin / model / validators.py
1 #!/usr/bin/python3.4
2 """This file contains "validators" that convert between string and python (database) representation
3 of properties used in the "Rodelbahnbox" and "Gasthausbox".
4 The "to_python" method has to get a unicode argument.
5 You can run tests with
6 >>> nosetests --with-pylons=test.ini
7
8 TODO: Replace by wrpylib.wrvalidators
9 """
10 import formencode
11 import formencode.national
12 import datetime
13 import re
14 import xml.dom.minidom as minidom
15 from xml.parsers.expat import ExpatError
16
17
18 class NoneValidator(formencode.FancyValidator):
19     """Takes a validator and makes it possible that empty strings are mapped to None."""
20     def __init__(self, validator, python_none=None):
21         self.validator = validator
22         self.python_none = python_none
23     
24     def to_python(self, value):
25         self.assert_string(value, None)
26         if value == '': return self.python_none
27         return self.validator.to_python(value)
28     
29     def from_python(self, value):
30         if value == self.python_none: return ''
31         return self.validator.from_python(value)
32
33
34 class NeinValidator(formencode.FancyValidator):
35     """Take an arbitrary validator and adds the possibility that the
36     string can be u'Nein'.
37     Example together with an UnsignedNone validator:
38     >>> v = NeinValidator(UnsignedNone())
39     >>> v.to_python(u'')
40     None
41     >>> v.to_python(u'34')
42     34
43     >>> v.to_python(u'Nein')
44     u'Nein'
45     """
46     def __init__(self, validator, python_no='Nein'):
47         self.validator = validator
48         self.python_no = python_no
49     
50     def to_python(self, value):
51         self.assert_string(value, None)
52         if value == 'Nein': return self.python_no
53         return self.validator.to_python(value)
54     
55     def from_python(self, value):
56         if value == self.python_no: return 'Nein'
57         return self.validator.from_python(value)
58
59
60 class Unicode(formencode.FancyValidator):
61     """Converts an unicode string to an unicode string:
62     u'any string' <=> u'any string'"""
63     def to_python(self, value):
64         self.assert_string(value, None)
65         return str(value)
66
67     def from_python(self, value):
68         return str(value)
69
70
71 class UnicodeNone(NoneValidator):
72     """Converts an unicode string to an unicode string:
73     u'' <=> None
74     u'any string' <=> u'any string'"""
75     def __init__(self):
76         NoneValidator.__init__(self, Unicode())
77
78
79 class Unsigned(formencode.FancyValidator):
80     """Converts an unsigned number to a string and vice versa:
81     u'0'  <=>  0
82     u'1'  <=>  1
83     u'45' <=> 45
84     """
85     def __init__(self, max=None):
86         self.iv = formencode.validators.Int(min=0, max=max)
87
88     def to_python(self, value):
89         self.assert_string(value, None)
90         return self.iv.to_python(value)
91     
92     def from_python(self, value):
93         return str(value)
94
95
96 class UnsignedNone(NoneValidator):
97     """Converts an unsigned number to a string and vice versa:
98     u''   <=> None
99     u'0'  <=>  0
100     u'1'  <=>  1
101     u'45' <=> 45
102     """
103     def __init__(self, max=None):
104         NoneValidator.__init__(self, Unsigned(max))
105
106
107 class UnsignedNeinNone(NoneValidator):
108     """ Translates a number of Nein to a number.
109     u''     <=> None
110     u'Nein' <=> 0
111     u'1'    <=> 1
112     u'2'    <=> 2
113     ...
114     """
115     def __init__(self):
116         NoneValidator.__init__(self, UnsignedNone())
117
118
119 class Loop(formencode.FancyValidator):
120     """Takes a validator and calls from_python(to_python(value))."""
121     def __init__(self, validator):
122         self.validator = validator
123
124     def to_python(self, value):
125         self.assert_string(value, None)
126         return self.validator.from_python(self.validator.to_python(value))
127     
128     def from_python(self, value):
129         # we don't call self.validator.to_python(self.validator.from_python(value))
130         # here because our to_python implementation basically leaves the input untouches
131         # and so should from_python do.
132         return self.validator.from_python(self.validator.to_python(value))
133
134
135 class DictValidator(formencode.FancyValidator):
136     """Translates strings to other values via a python directory.
137     >>> boolValidator = DictValidator({u'': None, u'Ja': True, u'Nein': False})
138     >>> boolValidator.to_python(u'')
139     None
140     >>> boolValidator.to_python(u'Ja')
141     True
142     """
143     def __init__(self, dict):
144         self.dict = dict
145     
146     def to_python(self, value):
147         self.assert_string(value, None)
148         if value not in self.dict: raise formencode.Invalid("Key not found in dict.", value, None)
149         return self.dict[value]
150     
151     def from_python(self, value):
152         for k, v in self.dict.items():
153             if type(v) == type(value) and v == value: return k
154         raise formencode.Invalid('Invalid value', value, None)
155
156
157 class GermanBoolNone(DictValidator):
158     """Converts German bool values to the python bool type:
159     u''     <=> None
160     u'Ja'   <=> True
161     u'Nein' <=> False
162     """
163     def __init__(self):
164         DictValidator.__init__(self, {'': None, 'Ja': True, 'Nein': False})
165
166
167 class GermanTristateTuple(DictValidator):
168     """Does the following conversion:
169     u''          <=> (None, None)
170     u'Ja'        <=> (True, False)
171     u'Teilweise' <=> (True,  True)
172     u'Nein'      <=> (False, True)"""
173     def __init__(self, yes_python = (True, False), no_python = (False, True), partly_python = (True, True), none_python = (None, None)):
174         DictValidator.__init__(self, {'': none_python, 'Ja': yes_python, 'Nein': no_python, 'Teilweise': partly_python})
175
176
177 class GermanTristateFloat(GermanTristateTuple):
178     """Converts the a property with the possible values 0.0, 0.5, 1.0 or None
179     to a German text:
180     u''          <=> None
181     u'Ja'        <=> 1.0
182     u'Teilweise' <=> 0.5
183     u'Nein'      <=> 0.0"""
184     def __init__(self):
185         GermanTristateTuple.__init__(self, yes_python=1.0, no_python=0.0, partly_python=0.5, none_python=None)
186
187
188 class ValueComment(formencode.FancyValidator):
189     """Converts value with a potentially optional comment to a python tuple:
190     u''                <=> (None, None)
191     u'value'           <=> (u'value', None)
192     u'value (comment)' <=> (u'value', u'comment')"""
193     def __init__(self, value_validator=UnicodeNone(), comment_validator=UnicodeNone(), comment_is_optional=True):
194         self.value_validator = value_validator
195         self.comment_validator = comment_validator
196         self.comment_is_optional = comment_is_optional
197     
198     def to_python(self, value):
199         self.assert_string(value, None)
200         if value == '':
201             v = value
202             c = value
203         else:
204             left = value.find('(')
205             right = value.rfind(')')
206             if left < 0 and right < 0:
207                 if not self.comment_is_optional: raise formencode.Invalid('Mandatory comment not present', value, None)
208                 v = value
209                 c = ''
210             elif left >= 0 and right >= 0 and left < right:
211                 v = value[:left].strip()
212                 c = value[left+1:right].strip()
213             else: raise formencode.Invalid('Invalid format', value, None)
214         return self.value_validator.to_python(v), self.comment_validator.to_python(c)
215
216     def from_python(self, value):
217         assert len(value) == 2
218         v = self.value_validator.from_python(value[0])
219         c = self.comment_validator.from_python(value[1])
220         if len(c) > 0:
221             if len(v) > 0: return '%s (%s)' % (v, c)
222             else: return '(%s)' % c
223         return v
224
225
226 class SemicolonList(formencode.FancyValidator):
227     """Applies a given validator to a semicolon separated list of values and returns a python list.
228     For an empty string an empty list is returned."""
229     def __init__(self, validator=Unicode()):
230         self.validator = validator
231     
232     def to_python(self, value):
233         self.assert_string(value, None)
234         return [self.validator.to_python(s.strip()) for s in value.split(';')]
235     
236     def from_python(self, value):
237         return "; ".join([self.validator.from_python(s) for s in value])
238         
239     
240 class ValueCommentList(SemicolonList):
241     """A value-comment list looks like one of the following lines:
242         value
243         value (optional comment)
244         value1; value2
245         value1; value2 (optional comment)
246         value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
247         value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
248     This function returns the value-comment list as list of tuples:
249         [(u'value1', u'comment1'), (u'value2', None)]
250     If no comment is present, None is specified.
251     For an empty string, [] is returned."""    
252     def __init__(self, value_validator=Unicode(), comments_are_optional=True):
253         SemicolonList.__init__(self, ValueComment(value_validator, comment_is_optional=comments_are_optional))
254
255
256 class GenericDateTime(formencode.FancyValidator):
257     """Converts a generic date/time information to a datetime class with a user defined format.
258     '2009-03-22 20:36:15' would be specified as '%Y-%m-%d %H:%M:%S'."""
259     
260     def __init__(self, date_time_format = '%Y-%m-%d %H:%M:%S', **keywords):
261         formencode.FancyValidator.__init__(self, **keywords)
262         self.date_time_format = date_time_format
263     
264     def to_python(self, value, state=None):
265         self.assert_string(value, None)
266         try: return datetime.datetime.strptime(value, self.date_time_format)
267         except ValueError as e: raise formencode.Invalid(str(e), value, None)
268     
269     def from_python(self, value, state=None):
270         return value.strftime(self.date_time_format)
271
272
273 class DateTimeNoSec(GenericDateTime):
274     def __init__(self, **keywords):
275         GenericDateTime.__init__(self, '%Y-%m-%d %H:%M', **keywords)
276
277
278 class DateNone(NoneValidator):
279     """Converts date information to date classes with the format '%Y-%m-%d' or None."""
280     def __init__(self):
281         NoneValidator.__init__(self, GenericDateTime('%Y-%m-%d'))
282
283
284 class Geo(formencode.FancyValidator):
285     """Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet."""
286     def to_python(self, value):
287         self.assert_string(value, None)
288         r = re.match('(\d+\.\d+) N (\d+\.\d+) E', value)
289         if r is None: raise formencode.Invalid("Coordinates '%s' have not a format like '47.076207 N 11.453553 E'" % value, value, None)
290         return (float(r.groups()[0]), float(r.groups()[1]))
291     
292     def from_python(self, value):
293         latitude, longitude = value
294         return '%.6f N %.6f E' % (latitude, longitude)
295
296
297 class GeoNone(NoneValidator):
298     """Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet."""
299     def __init__(self):
300         NoneValidator.__init__(self, Geo(), (None, None))
301
302
303 class MultiGeo(formencode.FancyValidator):
304     "Formats multiple coordinates, even in multiple lines to [(latitude, longitude, elevation), ...] or [(latitude, longitude, None), ...] tuplets."
305     
306     # Valid for input_format
307     FORMAT_GUESS = 0         # guesses the input format; default for input_format
308     FORMAT_NONE = -1          # indicates missing formats
309     
310     # Valid for input_format and output_format
311     FORMAT_GEOCACHING = 1    # e.g. "N 47° 13.692 E 011° 25.535"
312     FORMAT_WINTERRODELN = 2  # e.g. "47.222134 N 11.467211 E"
313     FORMAT_GMAPPLUGIN = 3    # e.g. "47.232922, 11.452239"
314     FORMAT_GPX = 4           # e.g. "<trkpt lat="47.181289" lon="11.408827"><ele>1090.57</ele></trkpt>"
315     
316     input_format = FORMAT_GUESS
317     output_format = FORMAT_WINTERRODELN
318     last_input_format = FORMAT_NONE
319
320     def __init__(self, input_format = FORMAT_GUESS, output_format = FORMAT_WINTERRODELN, **keywords):
321         self.input_format = input_format
322         self.output_format = output_format
323         formencode.FancyValidator.__init__(self, if_empty = (None, None, None), **keywords)
324     
325     def to_python(self, value):
326         self.assert_string(value, None)
327         input_format = self.input_format
328         if not input_format in [self.FORMAT_GUESS, self.FORMAT_GEOCACHING, self.FORMAT_WINTERRODELN, self.FORMAT_GMAPPLUGIN, self.FORMAT_GPX]:
329             raise formencode.Invalid("input_format %d is not recognized" % input_format, value, None) # Shouldn't it be an other type of runtime error?
330         lines = [line.strip() for line in value.split("\n") if len(line.strip()) > 0]
331         
332         result = []
333         for line in lines:
334             if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GEOCACHING:
335                 r = re.match('N ?(\d+)° ?(\d+\.\d+) +E ?(\d+)° ?(\d+\.\d+)', line)
336                 if not r is None:
337                     g = r.groups()
338                     result.append((float(g[0]) + float(g[1])/60, float(g[2]) + float(g[3])/60, None))
339                     last_input_format = self.FORMAT_WINTERRODELN
340                     continue
341                     
342             if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_WINTERRODELN:
343                 r = re.match('(\d+\.\d+) N (\d+\.\d+) E', line)
344                 if not r is None:
345                     result.append((float(r.groups()[0]), float(r.groups()[1]), None))
346                     last_input_format = self.FORMAT_WINTERRODELN
347                     continue
348                 
349             if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GMAPPLUGIN:
350                 r = re.match('(\d+\.\d+), ?(\d+\.\d+)', line)
351                 if not r is None:
352                     result.append((float(r.groups()[0]), float(r.groups()[1]), None))
353                     last_input_format = self.FORMAT_GMAPPLUGIN
354                     continue
355                 
356             if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GPX:
357                 try:
358                     xml = minidom.parseString(line)
359                     coord = xml.documentElement
360                     lat = float(coord.getAttribute('lat'))
361                     lon = float(coord.getAttribute('lon'))
362                     try: ele = float(coord.childNodes[0].childNodes[0].nodeValue)
363                     except (IndexError, ValueError): ele = None
364                     result.append((lat, lon, ele))
365                     last_input_format = self.FORMAT_GPX
366                     continue
367                 except (ExpatError, IndexError, ValueError): pass
368
369             raise formencode.Invalid("Coordinates '%s' have no known format" % line, value, None)
370             
371         return result
372     
373     def from_python(self, value):
374         output_format = self.output_format
375         result = []
376         for latitude, longitude, height in value:
377             if output_format == self.FORMAT_GEOCACHING:
378                 degree = latitude
379                 result.append('N %02d° %02.3f E %03d° %02.3f' % (latitude, latitude % 1 * 60, longitude, longitude % 1 * 60))
380                 
381             elif output_format == self.FORMAT_WINTERRODELN:
382                 result.append('%.6f N %.6f E' % (latitude, longitude))
383
384             elif output_format == self.FORMAT_GMAPPLUGIN:
385                 result.append('%.6f, %.6f' % (latitude, longitude))
386                 
387             elif output_format == self.FORMAT_GPX:
388                 if not height is None: result.append('<trkpt lat="%.6f" lon="%.6f"><ele>%.2f</ele></trkpt>' % (latitude, longitude, height))
389                 else: result.append('<trkpt lat="%.6f" lon="%.6f"/>' % (latitude, longitude))
390             
391             else:
392                 raise formencode.Invalid("output_format %d is not recognized" % output_format, value, None) # Shouldn't it be an other type of runtime error?
393             
394         return "\n".join(result)
395
396
397 # deprecated
398 class AustrianPhoneNumber(formencode.FancyValidator):
399     """
400     Validates and converts phone numbers to +##/###/####### or +##/###/#######-### (having an extension)
401     @param  default_cc      country code for prepending if none is provided, defaults to 43 (Austria)
402     ::
403         >>> v = AustrianPhoneNumber()
404         >>> v.to_python(u'0512/12345678')
405         u'+43/512/12345678'
406         >>> v.to_python(u'+43/512/12345678')
407         u'+43/512/12345678'
408         >>> v.to_python(u'0512/1234567-89') # 89 is the extension
409         u'+43/512/1234567-89'
410         >>> v.to_python(u'+43/512/1234567-89')
411         u'+43/512/1234567-89'
412         >>> v.to_python(u'0512 / 12345678') # Exception
413         >>> v.to_python(u'0512-12345678') # Exception
414     """
415     # Inspired by formencode.national.InternationalPhoneNumber
416
417     default_cc = 43 # Default country code
418     messages = {'phoneFormat': "'%%(value)s' is an invalid format. Please enter a number in the form +43/###/####### or 0###/########."}
419
420     def to_python(self, value):
421         self.assert_string(value, None)
422         m = re.match('^(?:\+(\d+)/)?([\d/]+)(?:-(\d+))?$', value)
423         # This will separate 
424         #     u'+43/512/1234567-89'  => (u'43', u'512/1234567', u'89')
425         #     u'+43/512/1234/567-89' => (u'43', u'512/1234/567', u'89')
426         #     u'+43/512/1234/567'    => (u'43', u'512/1234/567', None)
427         #     u'0512/1234567'        => (None, u'0512/1234567', None)
428         if m is None: raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, None)
429         (country, phone, extension) = m.groups()
430         
431         # Phone
432         if phone.find('//') > -1 or phone.count('/') == 0: raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, None)
433         
434         # Country
435         if country is None:
436             if phone[0] != '0': raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, None)
437             phone = phone[1:]
438             country = str(self.default_cc)
439         
440         if extension is None: return '+%s/%s' % (country, phone)
441         return '+%s/%s-%s' % (country, phone, extension)
442
443
444 # Deprecated
445 class AustrianPhoneNumberNone(NoneValidator):
446     def __init__(self):
447         NoneValidator.__init__(self, AustrianPhoneNumber())
448
449
450 # Deprecated
451 class AustrianPhoneNumberCommentLoop(NoneValidator):
452     def __init__(self):
453         NoneValidator.__init__(self, Loop(ValueComment(AustrianPhoneNumber())))
454
455
456 class GermanDifficulty(DictValidator):
457     """Converts the difficulty represented in a number from 1 to 3 (or None)
458     to a German representation:
459     u''       <=> None
460     u'leicht' <=> 1
461     u'mittel' <=> 2
462     u'schwer' <=> 3"""
463     def __init__(self):
464         DictValidator.__init__(self, {'': None, 'leicht': 1, 'mittel': 2, 'schwer': 3})
465
466
467 class GermanAvalanches(DictValidator):
468     """Converts the avalanches property represented as number from 1 to 4 (or None)
469     to a German representation:
470     u''             <=> None
471     u'kaum'         <=> 1
472     u'selten'       <=> 2
473     u'gelegentlich' <=> 3
474     u'häufig'       <=> 4"""
475     def __init__(self):
476         DictValidator.__init__(self, {'': None, 'kaum': 1, 'selten': 2, 'gelegentlich': 3, 'häufig': 4})
477
478
479 class GermanPublicTransport(DictValidator):
480     """Converts the public_transport property represented as number from 1 to 6 (or None)
481     to a German representation:
482     u''            <=> None
483     u'Sehr gut'    <=> 1
484     u'Gut'         <=> 2
485     u'Mittelmäßig' <=> 3
486     u'Schlecht'    <=> 4
487     u'Nein'        <=> 5
488     u'Ja'          <=> 6"""
489     def __init__(self):
490         DictValidator.__init__(self, {'': None, 'Sehr gut': 1, 'Gut': 2, 'Mittelmäßig': 3, 'Schlecht': 4, 'Nein': 5, 'Ja': 6})
491
492
493 class GermanTristateFloatComment(ValueComment):
494     """Converts the a property with the possible values 0.0, 0.5, 1.0 or None and an optional comment
495     in parenthesis to a German text:
496     u''                  <=> (None, None)
497     u'Ja'                <=> (1.0,  None)
498     u'Teilweise'         <=> (0.5,  None)
499     u'Nein'              <=> (0.0,  None)
500     u'Ja (aber schmal)'  <=> (1.0,  u'aber schmal')
501     u'Teilweise (oben)'  <=> (0.5,  u'oben')
502     u'Nein (aber breit)' <=> (0.0,  u'aber breit')
503     """
504     def __init__(self):
505         ValueComment.__init__(self, GermanTristateFloat())
506
507
508 class UnsignedCommentNone(NoneValidator):
509     """Converts the a property with unsigned values an optional comment
510     in parenthesis to a text:
511     u''           <=> (None, None)
512     u'2 (Mo, Di)' <=> (2,  u'Mo, Di')
513     u'7'          <=> (7,  None)
514     u'0'          <=> (0,  None)
515     """
516     def __init__(self, max=None):
517         NoneValidator.__init__(self, ValueComment(Unsigned(max=max)), (None, None))
518
519
520 class GermanCachet(formencode.FancyValidator):
521     """Converts a "Gütesiegel":
522     u'' <=> None
523     u'Nein' <=> 'Nein'
524     u'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel' <=> u'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'"""
525     def to_python(self, value):
526         self.assert_string(value, None)
527         if value == '': return None
528         elif value == 'Nein': return value
529         elif value.startswith('Tiroler Naturrodelbahn-Gütesiegel '):
530             p = value.split(" ")
531             Unsigned().to_python(p[2]) # check if year can be parsed
532             if not p[3] in ['leicht', 'mittel', 'schwer']: raise formencode.Invalid("Unbekannter Schwierigkeitsgrad", value, None)
533             return value
534         else: raise formencode.Invalid("Unbekanntes Gütesiegel", value, None)
535     
536     def from_python(self, value):
537         if value == None: return ''
538         assert value != ''
539         return self.to_python(self, value)
540
541
542 class Url(formencode.FancyValidator):
543     """Validates an URL. In contrast to fromencode.validators.URL, umlauts are allowed."""
544     urlv = formencode.validators.URL()    
545     def to_python(self, value):
546         self.assert_string(value, None)
547         v = value
548         v = v.replace('ä', 'a')
549         v = v.replace('ö', 'o')
550         v = v.replace('ü', 'u')
551         v = v.replace('ß', 'ss')
552         v = self.urlv.to_python(v)
553         return value
554     
555     def from_python(self, value):
556         return value
557
558
559 class UrlNeinNone(NoneValidator):
560     """Validates an URL. In contrast to fromencode.validators.URL, umlauts are allowed.
561     The special value u"Nein" is allowed."""
562     def __init__(self):
563         NoneValidator.__init__(self, NeinValidator(Url()))
564
565
566 class ValueCommentListNeinLoopNone(NoneValidator):
567     """Translates a semicolon separated list of values with optional comments in paranthesis or u'Nein' to itself.
568     An empty string is translated to None:
569     u''                   <=> None
570     u'Nein'               <=> u'Nein'
571     u'T-Mobile (gut); A1' <=> u'T-Mobile (gut); A1'"""
572     def __init__(self):
573         NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList())))
574
575
576 class PhoneNumber(formencode.FancyValidator):
577     """Telefonnumber in international format, e.g. u'+43-699-1234567'"""
578     def __init__(self, default_cc=43):
579         self.validator = formencode.national.InternationalPhoneNumber(default_cc=lambda: default_cc)
580
581     def to_python(self, value):
582         return str(self.validator.to_python(value))
583
584     def from_python(self, value):
585         return self.validator.from_python(value)
586
587
588 class PhoneCommentListNeinLoopNone(NoneValidator):
589     """List with semicolon-separated phone numbers in international format with optional comment or 'Nein' as string:
590     u''                                                       <=> None
591     u'Nein'                                                   <=> u'Nein'
592     u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456' <=> u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456'
593     """
594     def __init__(self, comments_are_optional):
595         NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList(PhoneNumber(default_cc=43), comments_are_optional=comments_are_optional))))
596
597
598 class EmailCommentListNeinLoopNone(NoneValidator):
599     """Converts a semicolon-separated list of email addresses with optional comments to itself.
600     The special value of u'Nein' indicates that there are no email addresses.
601     The empty string translates to None:
602     u''                                                   <=> None
603     u'Nein'                                               <=> u'Nein'
604     u'first@example.com'                                  <=> u'first@example.com'
605     u'first@example.com (Nur Winter); second@example.com' <=> u'first@example.com (Nur Winter); second@example.com'
606     """
607     def __init__(self):
608         NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList(formencode.validators.Email()))))
609
610
611 class WikiPage(formencode.FancyValidator):
612     """Validates wiki page name like u'[[Birgitzer Alm]]'.
613     The page is not checked for existance.
614     An empty string is an error.
615     u'[[Birgitzer Alm]]' <=> u'[[Birgitzer Alm]]'
616     """
617     def to_python(self, value):
618         self.assert_string(value, None)
619         if not value.startswith('[[') or not value.endswith(']]'): 
620             raise formencode.Invalid('No valid wiki page name', value, None)
621         return value
622     
623     def from_python(self, value):
624         return value
625
626
627 class WikiPageList(SemicolonList):
628     """Validates a list of wiki pages like u'[[Birgitzer Alm]]; [[Kemater Alm]]'.
629     u'[[Birgitzer Alm]]; [[Kemater Alm]]' <=> [u'[[Birgitzer Alm]]', u'[[Kemater Alm]]']
630     u'[[Birgitzer Alm]]'                  <=> [u'[[Birgitzer Alm]]']
631     u''                                   <=> []
632     """
633     def __init__(self):
634         SemicolonList.__init__(self, WikiPage())
635
636
637 class WikiPageListLoopNone(NoneValidator):
638     """Validates a list of wiki pages like u'[[Birgitzer Alm]]; [[Kemater Alm]]' as string.
639     u'[[Birgitzer Alm]]; [[Kemater Alm]]' <=> u'[[Birgitzer Alm]]; [[Kemater Alm]]'
640     u'[[Birgitzer Alm]]'                  <=> u'[[Birgitzer Alm]]'
641     u''                                   <=> None
642     """
643     def __init__(self):
644         NoneValidator.__init__(self, Loop(WikiPageList()))
645
646
647 class TupleSecondValidator(formencode.FancyValidator):
648     """Does not really validate anything but puts the string through
649     a validator in the second part of a tuple.
650     Examples with an Unsigned() validator and the True argument:
651     u'6' <=> (True, 6)
652     u'2' <=> (True, 2)"""
653     def __init__(self, first=True, validator=UnicodeNone()):
654         self.first = first
655         self.validator = validator
656     
657     def to_python(self, value):
658         self.assert_string(value, None)
659         return self.first, self.validator.to_python(value)
660     
661     def from_python(self, value):
662         assert value[0] == self.first
663         return self.validator.from_python(value[1])
664
665
666 class BoolUnicodeTupleValidator(NoneValidator):
667     """Translates an unparsed string or u'Nein' to a tuple:
668     u''         <=> (None, None)
669     u'Nein'     <=> (False, None)
670     u'any text' <=> (True, u'any text')
671     """
672     def __init__(self, validator=UnicodeNone()):
673         NoneValidator.__init__(self, NeinValidator(TupleSecondValidator(True, validator), (False, None)), (None, None))
674
675
676 class GermanLift(BoolUnicodeTupleValidator):
677     """Checks a lift_details property. It is a value comment property with the following
678     values allowed:
679     u'Sessellift'
680     u'Gondel'
681     u'Linienbus'
682     u'Taxi'
683     u'Sonstige'
684     Alternatively, the value u'Nein' is allowed.
685     An empty string maps to (None, None).
686     
687     Examples:
688     u''                                       <=> (None, None)
689     u'Nein'                                   <=> (False, None)
690     u'Sessellift                              <=> (True, u'Sessellift')
691     u'Gondel (nur bis zur Hälfte)'            <=> (True, u'Gondel (nur bis zur Hälfte)')
692     u'Sessellift; Taxi'                       <=> (True, u'Sessellift; Taxi')
693     u'Sessellift (Wochenende); Taxi (6 Euro)' <=> (True, u'Sessellift (Wochenende); Taxi (6 Euro)')
694     """
695     def __init__(self):
696         BoolUnicodeTupleValidator.__init__(self, Loop(ValueCommentList(DictValidator({'Sessellift': 'Sessellift', 'Gondel': 'Gondel', 'Linienbus': 'Linienbus', 'Taxi': 'Taxi', 'Sonstige': 'Sonstige'}))))
697         
698
699 class SledRental(BoolUnicodeTupleValidator):
700     """The value can be an empty string, u'Nein' or a comma-separated list of unicode strings with optional comments.
701     u''                                       <=> (None, None)
702     u'Nein'                                   <=> (False, None)
703     u'Talstation (nur mit Ticket); Schneealm' <=> (True, u'Talstation (nur mit Ticket); Schneealm')"""
704     def __init__(self):
705         BoolUnicodeTupleValidator.__init__(self, Loop(ValueCommentList()))