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