1 # -*- coding: iso-8859-15 -*-
5 import xml.dom.minidom as minidom
6 from xml.parsers.expat import ExpatError
9 class GermanBool(formencode.FancyValidator):
10 "Converts German bool values to the python bool type. 'Ja' and 'Nein' are supported."
12 def __init__(self, yes = [u'Ja'], no = [u'Nein'], **keywords):
13 "The yes and no arguments specify the valid possibilities. The first possibility is the default one."
14 formencode.FancyValidator.__init__(self, **keywords)
18 def _to_python(self, value, state):
19 self.assert_string(value, state)
20 if value in self.yes: return True
21 if value in self.no: return False
24 raise formencode.Invalid(u"'%s' is not a valid boolean value, use one of %s" % (value, all), value, state)
26 def from_python(self, value):
27 if value is None: return u''
28 if value: return self.yes[0]
32 class GenericDateTimeConverter(formencode.FancyValidator):
33 """Converts generic date/time information to datetime classes with a user defined format.
34 '2009-03-22 20:36:15' would be specified as '%Y-%m-%d %H:%M:%S'."""
36 def __init__(self, date_time_format = '%Y-%m-%d %H:%M:%S', **keywords):
37 formencode.FancyValidator.__init__(self, **keywords)
38 self.date_time_format = date_time_format
40 def _to_python(self, value, state):
41 self.assert_string(value, state)
42 try: return datetime.datetime.strptime(value, self.date_time_format)
43 except ValueError, e: raise formencode.Invalid(str(e), value, state)
45 def _from_python(self, value, state):
46 if value is None: return u''
47 return value.strftime(self.date_time_format)
50 class DateTimeNoSecConverter(GenericDateTimeConverter):
51 def __init__(self, **keywords):
52 GenericDateTimeConverter.__init__(self, '%Y-%m-%d %H:%M', **keywords)
55 class DateConverter(GenericDateTimeConverter):
56 "Converts date information to date classes with the format '%Y-%m-%d'."
58 def __init__(self, **keywords):
59 GenericDateTimeConverter.__init__(self, '%Y-%m-%d', **keywords)
61 def _to_python(self, value, state):
62 GenericDateTimeConverter._to_python(self, value, state).date()
65 class GermanTristate(formencode.FancyValidator):
66 """Does the following conversion:
68 u'Ja' -> (True, False)
69 u'Teilweise' -> (True, True)
70 u'Nein' -> (False, True)"""
72 def __init__(self, yes_python = (True, False), no_python = (False, True), partly_python = (True, True), yes_text = [u'Ja'], no_text = [u'Nein'], partly_text = [u'Teilweise'], **keywords):
73 formencode.FancyValidator.__init__(self, if_empty = (None, None), **keywords)
74 self.yes_python = yes_python
75 self.no_python = no_python
76 self.partly_python = partly_python
77 self.yes_text = yes_text
78 self.no_text = no_text
79 self.partly_text = partly_text
81 def _to_python(self, value, state):
82 self.assert_string(value, state)
83 if value in self.yes_text: return self.yes_python
84 if value in self.no_text: return self.no_python
85 if value in self.partly_text: return self.partly_python
86 all = self.yes_text[:]
87 all.extend(self.no_text)
88 all.extend(self.partly_text)
89 raise formencode.Invalid(u"'%s' is not a valid value, use one of %s" % (value, all), value, state)
91 def _from_python(self, value, state):
92 if value == (None, None): return ''
93 if value == self.yes_python: return self.yes_text[0]
94 if value == self.no_python: return self.no_text[0]
95 if value == self.partly_python: return self.partly_text[0]
96 raise formencode.Invalid(u"Invalid representation of a tristate value: '%s'" % (value,), value, state)
99 class Geo(formencode.FancyValidator):
100 "Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet."
102 def __init__(self, **keywords):
103 formencode.FancyValidator.__init__(self, if_empty = (None, None), **keywords)
105 def _to_python(self, value, state):
106 r = re.match(u'(\d+\.\d+) N (\d+\.\d+) E', value)
107 if r is None: raise formencode.Invalid(u"Coordinates '%s' have not a format like '47.076207 N 11.453553 E'" % value, value, state)
108 return (float(r.groups()[0]), float(r.groups()[1]))
110 def _from_python(self, value, state):
111 if value == (None, None): return ''
112 latitude, longitude = value
113 return u'%.6f N %.6f E' % (latitude, longitude)
116 class MultiGeo(formencode.FancyValidator):
117 "Formats multiple coordinates, even in multiple lines to [(latitude, longitude, elevation), ...] or [(latitude, longitude, None), ...] tuplets."
119 # Valid for input_format
120 FORMAT_GUESS = 0 # guesses the input format; default for input_format
121 FORMAT_NONE = -1 # indicates missing formats
123 # Valid for input_format and output_format
124 FORMAT_GEOCACHING = 1 # e.g. "N 47° 13.692 E 011° 25.535"
125 FORMAT_WINTERRODELN = 2 # e.g. "47.222134 N 11.467211 E"
126 FORMAT_GMAPPLUGIN = 3 # e.g. "47.232922, 11.452239"
127 FORMAT_GPX = 4 # e.g. "<trkpt lat="47.181289" lon="11.408827"><ele>1090.57</ele></trkpt>"
129 input_format = FORMAT_GUESS
130 output_format = FORMAT_WINTERRODELN
131 last_input_format = FORMAT_NONE
133 def __init__(self, input_format = FORMAT_GUESS, output_format = FORMAT_WINTERRODELN, **keywords):
134 self.input_format = input_format
135 self.output_format = output_format
136 formencode.FancyValidator.__init__(self, if_empty = (None, None, None), **keywords)
138 def _to_python(self, value, state):
139 input_format = self.input_format
140 if not input_format in [self.FORMAT_GUESS, self.FORMAT_GEOCACHING, self.FORMAT_WINTERRODELN, self.FORMAT_GMAPPLUGIN, self.FORMAT_GPX]:
141 raise formencode.Invalid(u"input_format %d is not recognized" % input_format, value, state) # Shouldn't it be an other type of runtime error?
142 lines = [line.strip() for line in value.split("\n") if len(line.strip()) > 0]
146 if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GEOCACHING:
147 r = re.match(u'N ?(\d+)° ?(\d+\.\d+) +E ?(\d+)° ?(\d+\.\d+)', line)
150 result.append((float(g[0]) + float(g[1])/60, float(g[2]) + float(g[3])/60, None))
151 last_input_format = self.FORMAT_WINTERRODELN
154 if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_WINTERRODELN:
155 r = re.match(u'(\d+\.\d+) N (\d+\.\d+) E', line)
157 result.append((float(r.groups()[0]), float(r.groups()[1]), None))
158 last_input_format = self.FORMAT_WINTERRODELN
161 if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GMAPPLUGIN:
162 r = re.match(u'(\d+\.\d+), ?(\d+\.\d+)', line)
164 result.append((float(r.groups()[0]), float(r.groups()[1]), None))
165 last_input_format = self.FORMAT_GMAPPLUGIN
168 if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GPX:
170 xml = minidom.parseString(line)
171 coord = xml.documentElement
172 lat = float(coord.getAttribute('lat'))
173 lon = float(coord.getAttribute('lon'))
174 try: ele = float(coord.childNodes[0].childNodes[0].nodeValue)
175 except (IndexError, ValueError): ele = None
176 result.append((lat, lon, ele))
177 last_input_format = self.FORMAT_GPX
179 except (ExpatError, IndexError, ValueError): pass
181 raise formencode.Invalid(u"Coordinates '%s' have no known format" % line, value, state)
185 def _from_python(self, value, state):
186 output_format = self.output_format
188 for latitude, longitude, height in value:
189 if output_format == self.FORMAT_GEOCACHING:
191 result.append(u'N %02d° %02.3f E %03d° %02.3f' % (latitude, latitude % 1 * 60, longitude, longitude % 1 * 60))
193 elif output_format == self.FORMAT_WINTERRODELN:
194 result.append(u'%.6f N %.6f E' % (latitude, longitude))
196 elif output_format == self.FORMAT_GMAPPLUGIN:
197 result.append(u'%.6f, %.6f' % (latitude, longitude))
199 elif output_format == self.FORMAT_GPX:
200 if not height is None: result.append(u'<trkpt lat="%.6f" lon="%.6f"><ele>%.2f</ele></trkpt>' % (latitude, longitude, height))
201 else: result.append(u'<trkpt lat="%.6f" lon="%.6f"/>' % (latitude, longitude))
204 raise formencode.Invalid(u"output_format %d is not recognized" % output_format, value, state) # Shouldn't it be an other type of runtime error?
206 return "\n".join(result)
209 class AustrianPhoneNumber(formencode.FancyValidator):
211 Validates and converts phone numbers to +##/###/####### or +##/###/#######-### (having an extension)
212 @param default_cc country code for prepending if none is provided, defaults to 43 (Austria)
214 >>> v = AustrianPhoneNumber()
215 >>> v.to_python(u'0512/12345678')
217 >>> v.to_python(u'+43/512/12345678')
219 >>> v.to_python(u'0512/1234567-89') # 89 is the extension
220 u'+43/512/1234567-89'
221 >>> v.to_python(u'+43/512/1234567-89')
222 u'+43/512/1234567-89'
223 >>> v.to_python(u'0512 / 12345678') # Exception
224 >>> v.to_python(u'0512-12345678') # Exception
226 # Inspired by formencode.national.InternationalPhoneNumber
228 default_cc = 43 # Default country code
229 messages = {'phoneFormat': "'%%(value)s' is an invalid format. Please enter a number in the form +43/###/####### or 0###/########."}
231 def _to_python(self, value, state):
232 self.assert_string(value, state)
233 m = re.match(u'^(?:\+(\d+)/)?([\d/]+)(?:-(\d+))?$', value)
235 # u'+43/512/1234567-89' => (u'43', u'512/1234567', u'89')
236 # u'+43/512/1234/567-89' => (u'43', u'512/1234/567', u'89')
237 # u'+43/512/1234/567' => (u'43', u'512/1234/567', None)
238 # u'0512/1234567' => (None, u'0512/1234567', None)
239 if m is None: raise formencode.Invalid(self.message('phoneFormat', state) % {'value': value}, value, state)
240 (country, phone, extension) = m.groups()
243 if phone.find(u'//') > -1 or phone.count('/') == 0: raise formencode.Invalid(self.message('phoneFormat', state) % {'value': value}, value, state)
247 if phone[0] != '0': raise formencode.Invalid(self.message('phoneFormat', state) % {'value': value}, value, state)
249 country = unicode(self.default_cc)
251 if extension is None: return '+%s/%s' % (country, phone)
252 return '+%s/%s-%s' % (country, phone, extension)
255 class PhoneInfo(formencode.FancyValidator):
256 "Validates a info of the form '0644/1234567 (Schnee Alm)'"
257 messages = {'infoFormat': "'%%(value)s' is no valid format, please use a form like '0644/1234567 (Schnee Alm)'"}
259 def _to_python(self, value, state):
260 self.assert_string(value, state)
261 m = re.match('^([-\d/\+]{5,}) \((.+)\)', value)
262 if m is None: raise formencode.Invalid(self.message('infoFormat', state) % {'value': value}, value, state)
263 (phone, info) = m.groups()
266 phone = AustrianPhoneNumber().to_python(phone)
268 return "%s (%s)" % (phone, info)
271 class ValueCommentList(formencode.FancyValidator):
272 """A value-comment list looks like one of the following lines:
274 value (optional comment)
276 value1; value2 (optional comment)
277 value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
278 value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
279 This function returns the value-comment list as list of tuples:
280 [(u'value1', u'comment1'), (u'value2', None)]
281 If no comment is present, None is specified."""
282 messages = {'infoFormat': "'%%(value)s' is no valid format, please use a form like 'value1 (optional comment1); value2 (optional comment2)'"}
284 def _to_python(self, value, state):
285 self.assert_string(value, state)
286 value_options = [s.strip() for s in value.split(';')]
288 for value_option in value_options:
289 left = value_option.find('(')
290 right = value_option.rfind(')')
291 if left < 0 and right < 0:
292 result.append((value_option, None))
293 elif left >= 0 and right >= 0 and left < right:
294 result.append((value_option[:left].strip(), value_option[left+1:right].strip()))
295 else: raise formencode.Invalid(self.message('infoFormat', state) % {'value': value}, value, state)
298 def _from_python(self, value, state):
301 if c is None: result.append(v)
302 else: result.append('%s (%s)' % (v, c))
303 return "; ".join(result)