Implemented test for bool_germen_from/to_german.
[philipp/winterrodeln/wrpylib.git] / tests / test_wrvalidators.py
index 24c361910e29da89a26e456e0888f670e21fcbd0..ff56cfe237c10e28de1c060bf5242232465713b0 100644 (file)
-#!/usr/bin/python2.7
+#!/usr/bin/python3.4
 # -*- coding: iso-8859-15 -*-
+import collections
 import wrpylib.wrvalidators
-import formencode
+import unittest
+from wrpylib.wrvalidators import *
 
+# optional converter
+# ------------------
 
-def test_NoneValidator():
-    v =  wrpylib.wrvalidators.NoneValidator(wrpylib.wrvalidators.Unicode())
-    assert v.to_python(u'') == None
-    assert v.from_python(None) == u''
-
-
-# test_NeinValidator
-
-
-# test_Unicode
-
-
-# test_UnicodeNone
-
-
-# test_Unsigned
-
-
-def test_UnsignedNone():
-    v = wrpylib.wrvalidators.UnsignedNone()
-    assert v.to_python(u'42') == 42
-    assert v.to_python(u'') == None
-    assert v.from_python(42) == u'42'
-    assert v.from_python(None) == u''
-
-
-# test_UnsignedNeinNone
-
-
-# test_Loop
-
-
-# test_DictValidator
-
-
-# test_GermanBoolNone
-
-
-def test_GermanTristateTuple():
-    v = wrpylib.wrvalidators.GermanTristateTuple()
-    assert v.to_python(u'') == (None, None)
-    assert v.to_python(u'Ja') == (True, False)
-    assert v.to_python(u'Nein') == (False, True)
-    assert v.to_python(u'Teilweise') == (True, True)
-    assert v.from_python((None, None)) == u''
-    assert v.from_python((False, True)) == u'Nein'
-    assert v.from_python((True, False)) == u'Ja'
-    assert v.from_python((True, True)) == u'Teilweise'
-
-
-def test_GermanTristateFloat():
-    v = wrpylib.wrvalidators.GermanTristateFloat()
-    assert v.to_python(u'') == None
-    assert v.to_python(u'Ja') == 1.0
-    assert v.to_python(u'Nein') == 0.0
-    assert v.to_python(u'Teilweise') == 0.5
-    assert v.from_python(None) == u''
-    assert v.from_python(0.0) == u'Nein'
-    assert v.from_python(1.0) == u'Ja'
-    assert v.from_python(0.5) == u'Teilweise'
-
-
-# test_ValueComment
-
-
-# test_SemicolonList
-
-
-def test_ValueCommentList():
-    v = wrpylib.wrvalidators.ValueCommentList()
-    assert v.to_python(u'abc') == [(u'abc', None)]
-    assert v.to_python(u'abc def') == [(u'abc def', None)]
-    assert v.to_python(u'value (comment)') == [(u'value', u'comment')]
-    assert v.to_python(u'value (comment)') == [(u'value', u'comment')]
-    assert v.to_python(u'value1 (comment); value2') == [(u'value1', u'comment'), (u'value2', None)]
-    assert v.to_python(u'value1 (comment1); value2; value3 (comment3)') == [(u'value1', u'comment1'), (u'value2', None), ('value3', 'comment3')]
-    assert v.to_python(u'value1 (comment1); [[link (linkcomment)]] (not easy)') == [(u'value1', u'comment1'), (u'[[link (linkcomment)]]', u'not easy')]
-
-
-# test_GenericDateTime
-
-
-# test_DateTimeNoSec
-
-
-# test_DateNone
-
-
-# test_Geo
-
-
-def test_GeoNone():
-    coord = u'47.076207 N 11.453553 E'
-    v = wrpylib.wrvalidators.GeoNone()
-    (lat, lon) = v.to_python(coord)
-    assert lat == 47.076207
-    assert lon == 11.453553
-    assert v.to_python(u'') == (None, None)
-
-    assert v.from_python((lat, lon)) == coord
-    assert v.from_python((None, None)) == u''
-
-
-# test_MultiGeo
-
-
-# test_AustrianPhoneNumber
-
-
-# test_AustrianPhoneNumberNone
-
-
-# test_AustrianPhoneNumberCommentLoop
-
-
-# test_GermanDifficulty
-
-
-# test_GermanAvalanches
-
-
-def test_GermanPublicTransport():
-    v = wrpylib.wrvalidators.GermanPublicTransport()
-    assert v.to_python(u'') is None
-    assert v.to_python(u'Sehr gut') == 1
-    assert v.to_python(u'Gut') == 2
-    assert v.to_python(u'Mittelmäßig') == 3
-    assert v.to_python(u'Schlecht') == 4
-    assert v.to_python(u'Nein') == 5
-    assert v.to_python(u'Ja') == 6
-
-    assert v.from_python(None) == u''
-    assert v.from_python(1) == u'Sehr gut'
-    assert v.from_python(2) == u'Gut'
-    assert v.from_python(3) == u'Mittelmäßig'
-    assert v.from_python(4) == u'Schlecht'
-    assert v.from_python(5) == u'Nein'
-    assert v.from_python(6) == u'Ja'
-    assert v.from_python(1l) == u'Sehr gut'
-
-
-# test_GermanTristateFloatComment
-
-
-# test_UnsignedCommentNone
-
-
-# test_GermanCachet
-
-
-# test_url
-
-
-def test_UrlNeinNone():
-    v = wrpylib.wrvalidators.UrlNeinNone()
-    assert v.to_python(u'') == None
-    assert v.to_python(u'Nein') == u'Nein'
-    assert v.to_python(u'http://www.höttingeralm.at') == u'http://www.höttingeralm.at'
-    assert v.from_python(None) == u''
-    assert v.from_python(u'Nein') == u'Nein'
-    assert v.from_python(u'http://www.höttingeralm.at') == u'http://www.höttingeralm.at'
-
-
-def test_ValueCommentListNeinLoopNone():
-    v = wrpylib.wrvalidators.ValueCommentListNeinLoopNone()
-    assert v.to_python(u'') == None
-    assert v.to_python(u'Nein') == u'Nein'
-    assert v.to_python(u'T-Mobile (gut); A1') == u'T-Mobile (gut); A1'
-    assert v.from_python(None) == u''
-    assert v.from_python(u'Nein') == u'Nein'
-    assert v.from_python(u'T-Mobile (gut); A1') == u'T-Mobile (gut); A1'
-
-
-# test_PhoneNumber
-    
-
-def test_PhoneCommentListNeinLoopNone():
-    v = wrpylib.wrvalidators.PhoneCommentListNeinLoopNone(comments_are_optional=True)
-    assert v.to_python(u'') == None
-    assert v.to_python(u'Nein') == u'Nein'
-    assert v.to_python(u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456') == u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456'
-    assert v.from_python(None) == u''
-    assert v.from_python(u'Nein') == u'Nein'
-    assert v.from_python(u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456') == u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456'
-
-
-def test_MaskedEmail():
-    v = wrpylib.wrvalidators.MaskedEmail()
-    assert v.to_python(u'') == (None, None)
-    assert v.to_python(u'abc.def@example.com') == (u'abc.def@example.com', False)
-    assert v.to_python(u'abc.def(at)example.com') == (u'abc.def@example.com', True)
-    assert v.from_python((None, None)) == u''
-    assert v.from_python((u'abc.def@example.com', False)) == u'abc.def@example.com'
-    assert v.from_python((u'abc.def@example.com', True)) == u'abc.def(at)example.com'
-
-
-def test_EmailCommentListNeinLoopNone():
-    v = wrpylib.wrvalidators.EmailCommentListNeinLoopNone()
-    assert v.to_python(u'') == None
-    assert v.to_python(u'Nein') == u'Nein'
-    assert v.to_python(u'first@example.com') == u'first@example.com'
-    assert v.to_python(u'first@example.com (Nur Winter); second@example.com') == u'first@example.com (Nur Winter); second@example.com'
-    assert v.from_python(None) == u''
-    assert v.from_python(u'Nein') == u'Nein'
-    assert v.from_python(u'first@example.com') == u'first@example.com'
-    assert v.from_python(u'first@example.com (Nur Winter); second@example.com') == u'first@example.com (Nur Winter); second@example.com'
-    testvalue = u'abc.def(at)example.com (comment)'
-    try:
-        v.to_python(testvalue)
-        assert False
-    except formencode.Invalid:
-        pass
-    try:
-        v.from_python(testvalue)
-        assert False
-    except formencode.Invalid:
-        pass
-    v = wrpylib.wrvalidators.EmailCommentListNeinLoopNone(allow_masked_email=True)
-    assert v.to_python(testvalue) == testvalue
-    assert v.from_python(testvalue) == testvalue
-
-
-# test_WikiPage
-
-
-# test_WikiPageList
-
-
-def test_WikiPageListLoopNone():
-    v = wrpylib.wrvalidators.WikiPageListLoopNone()
-    assert v.to_python(u'') == None
-    assert v.to_python(u'[[Birgitzer Alm]]; [[Kemater Alm]]') == u'[[Birgitzer Alm]]; [[Kemater Alm]]'
-    assert v.from_python(None) == u''
-    assert v.from_python(u'[[Birgitzer Alm]]; [[Kemater Alm]]') == u'[[Birgitzer Alm]]; [[Kemater Alm]]'
-
-
-# test_TupleSecondValidator
-
-
-def test_BoolUnicodeTupleValidator():
-    v = wrpylib.wrvalidators.BoolUnicodeTupleValidator()
-    assert v.to_python(u'') == (None, None)
-    assert v.to_python(u'Nein') == (False, None)
-    assert v.to_python(u'any text') == (True, u'any text')
-    assert v.from_python((None, None)) == u''
-    assert v.from_python((False, None)) == u'Nein'
-    assert v.from_python((True, u'any text')) == u'any text'
-
-
-
-
-def test_GermanLift():
-    v = wrpylib.wrvalidators.GermanLift()
-    assert v.to_python(u'') == (None, None)
-    assert v.to_python(u'Nein') == (False, None)
-    assert v.to_python(u'Sessellift (4 Euro)') == (True, u'Sessellift (4 Euro)')
-    assert v.from_python((None, None)) == u''
-    assert v.from_python((False, None)) == u'Nein'
-    assert v.from_python((True, u'Sessellift (4 Euro)')) == u'Sessellift (4 Euro)'
-
-
-def test_SledRental():
-    v = wrpylib.wrvalidators.SledRental()
-    assert v.to_python(u'') == (None, None)
-    assert v.to_python(u'Nein') == (False, None)
-    assert v.to_python(u'Ja') == (True, u'Ja')
-    assert v.to_python(u'Talstation (nur mit Ticket); Schneealm') == (True, u'Talstation (nur mit Ticket); Schneealm')
-    assert v.from_python((None, None)) == u''
-    assert v.from_python((False, None)) == u'Nein'
-    assert v.from_python((True, u'Talstation (nur mit Ticket); Schneealm')) == u'Talstation (nur mit Ticket); Schneealm'
-    assert v.from_python((True, u'Ja')) == u'Ja'
-
-
-def test_RodelbahnboxDictValidator():
-    v = wrpylib.wrvalidators.RodelbahnboxDictValidator()
-    other = {
-        u'Position': u'47.309820 N 9.986508 E',
-        u'Position oben': u'',
-        u'Höhe oben': u'1244',
-        u'Position unten': u'',
-        u'Höhe unten': u'806',
-        u'Länge': u'5045',
-        u'Schwierigkeit': u'',
-        u'Lawinen': u'gelegentlich',
-        u'Betreiber': u'',
-        u'Öffentliche Anreise': u'Ja',
-        u'Gehzeit': u'105',
-        u'Aufstieg möglich': u'Ja',
-        u'Aufstieg getrennt': u'Nein',
-        u'Aufstiegshilfe': u'Nein',
-        u'Beleuchtungsanlage': u'Nein',
-        u'Beleuchtungstage': u'',
-        u'Rodelverleih': u'Ja',
-        u'Gütesiegel': u'',
-        u'Webauskunft': u'',
-        u'Telefonauskunft': u'+43-664-1808482 (Bergkristallhütte)',
-        u'Bild': u'Rodelbahn Bergkristallhütte 2009-03-03.jpg',
-        u'In Übersichtskarte': u'Ja',
-        u'Forumid': u'72'}
-    python = v.to_python(other, None)
-    other2 = v.from_python(python, None)
-    assert other == other2
-
+class TestOpt(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(None, opt_from_str('', str_from_str))
+        self.assertEqual('abc', opt_from_str('abc', str_from_str))
+        self.assertEqual(None, opt_from_str('', int_from_str))
+        self.assertEqual(4, opt_from_str('4', int_from_str))
+
+    def test_to_str(self):
+        self.assertEqual('', opt_to_str(None, str_to_str))
+        self.assertEqual('abc', opt_to_str('abc', str_to_str))
+        self.assertEqual('', opt_to_str(None, int_to_str))
+        self.assertEqual('4', opt_to_str(4, int_to_str))
+
+
+# "no" converter
+# --------------
+
+class TestNoGermanConverter(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual((True, 'abc'), no_german_from_str('abc', req_str_from_str))
+        self.assertEqual((False, None), no_german_from_str('Nein', req_str_from_str))
+        with self.assertRaises(ValueError):
+            no_german_from_str('', req_str_from_str)
+
+    def test_to_str(self):
+        self.assertEqual('abc', no_german_to_str((True, 'abc'), str_to_str))
+        self.assertEqual('Nein', no_german_to_str((False, None), str_to_str))
+
+
+# "optional"/"no" converter
+# -------------------------
+
+class TestOptNoGerman(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual((True, 'abc'), opt_no_german_from_str('abc', str_from_str))
+        self.assertEqual((False, None), opt_no_german_from_str('Nein', str_from_str))
+        self.assertEqual((None, None), opt_no_german_from_str('', str_from_str))
+
+    def test_to_str(self):
+        self.assertEqual('abc', opt_no_german_to_str((True, 'abc'), str_to_str))
+        self.assertEqual('Nein', opt_no_german_to_str((False, None), str_to_str))
+        self.assertEqual('', opt_no_german_to_str((None, None), str_to_str))
+
+
+# choice converter
+# ----------------
+
+class TestCoice(unittest.TestCase):
+    def setUp(self):
+        self.choices = ['abc', 'def', 'ghi']
+
+    def from_str(self):
+        self.assertEqual('abc', choice_from_str('abc', self.choices))
+        self.assertEqual('ghi', choice_from_str('ghi', self.choices))
+        with self.assertRaises(ValueError):
+            choice_from_str('jkl', self.choices)
+
+
+# dictkey converter
+# -----------------
+
+class TestDictkey(unittest.TestCase):
+    def setUp(self):
+        self.choices = {'abc': '1', 'def': '2', 'ghi': '3'}
+
+    def test_from_str(self):
+        self.assertEqual('abc', dictkey_from_str('1', self.choices))
+        self.assertEqual('ghi', dictkey_from_str('3', self.choices))
+        with self.assertRaises(ValueError):
+            dictkey_from_str('4', self.choices)
+
+    def test_to_str(self):
+        self.assertEqual('1', dictkey_to_str('abc', self.choices))
+        self.assertEqual('3', dictkey_to_str('ghi', self.choices))
+
+
+# enum/"list" converter
+# ---------------------
+
+class TestEnumConverter(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual([], enum_from_str('', str_from_str))
+        self.assertEqual(['abc'], enum_from_str('abc', str_from_str))
+        self.assertEqual(['abc', 'def'], enum_from_str('abc; def', str_from_str))
+        self.assertEqual(['abc', 'def', 'ghi'], enum_from_str('abc; def;ghi', str_from_str))
+
+    def test_to_str(self):
+        self.assertEqual('abc; def; ghi', enum_to_str(['abc', 'def', 'ghi'], str_to_str))
+        self.assertEqual('abc', enum_to_str(['abc'], str_to_str))
+        self.assertEqual('', enum_to_str([''], str_to_str))
+        self.assertEqual('', enum_to_str([], str_to_str))
+
+
+# value/comment converter
+# -----------------------
+
+class TestValueCommentConverter(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(('abc', 'defg'), value_comment_from_str('abc (defg)', str_from_str, str_from_str))
+        self.assertEqual(('abc', ''), value_comment_from_str('abc ()', str_from_str, str_from_str))
+        self.assertEqual(('', 'def'), value_comment_from_str('(def)', str_from_str, str_from_str))
+        self.assertEqual(('ab', '(cd)'), value_comment_from_str('ab((cd))', str_from_str, str_from_str))
+        self.assertEqual(('ab', '(c(d)[(]))'), value_comment_from_str('ab((c(d)[(])))', str_from_str, str_from_str))
+        self.assertEqual(('ab(', 'cd'), value_comment_from_str('ab((cd)', str_from_str, str_from_str))
+        self.assertEqual(('abcd', 'ef'), value_comment_from_str('abcd  (ef) ', str_from_str, str_from_str))
+        self.assertEqual(('abc', ''), value_comment_from_str('abc', str_from_str, str_from_str, comment_optional=True))
+        with self.assertRaises(ValueError):
+            value_comment_from_str('abc (', str_from_str, str_from_str)
+        with self.assertRaises(ValueError):
+            value_comment_from_str('abc )', str_from_str, str_from_str)
+        with self.assertRaises(ValueError):
+            value_comment_from_str('abc (def)g', str_from_str, str_from_str)
+        with self.assertRaises(ValueError):
+            value_comment_from_str('abc (b))', str_from_str, str_from_str)
+        with self.assertRaises(ValueError):
+            value_comment_from_str('abc', str_from_str, str_from_str)
+
+    def test_to_str(self):
+        self.assertEqual('abc (defg)', value_comment_to_str(('abc', 'defg'), str_to_str, str_to_str))
+        self.assertEqual('abc ()', value_comment_to_str(('abc', ''), str_to_str, str_to_str))
+        self.assertEqual('(def)', value_comment_to_str(('', 'def'), str_to_str, str_to_str))
+        self.assertEqual('ab ((cd))', value_comment_to_str(('ab', '(cd)'), str_to_str, str_to_str))
+        self.assertEqual('ab ((c(d)[(])))', value_comment_to_str(('ab', '(c(d)[(]))'), str_to_str, str_to_str))
+        self.assertEqual('ab( (cd)', value_comment_to_str(('ab(', 'cd'), str_to_str, str_to_str))
+        self.assertEqual('abcd (ef)', value_comment_to_str(('abcd', 'ef'), str_to_str, str_to_str))
+        self.assertEqual('abc', value_comment_to_str(('abc', ''), str_to_str, str_to_str, comment_optional=True))
+
+
+# string converter
+# ----------------
+
+class TestStr(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual('', str_from_str(''))
+        self.assertEqual('abc', str_from_str('abc'))
+
+    def test_to_str(self):
+        self.assertEqual('', str_to_str(''))
+        self.assertEqual('abc', str_to_str('abc'))
+
+
+class TestReqStr(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual('abc', req_str_from_str('abc'))
+        self.assertEqual(' ', req_str_from_str(' '))
+        with self.assertRaises(ValueError):
+            req_str_from_str('')
+
+
+class TestOptStr(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual('abc', opt_str_from_str('abc'))
+        self.assertEqual(' ', opt_str_from_str(' '))
+        self.assertEqual(None, opt_str_from_str(''))
+
+    def test_to_str(self):
+        self.assertEqual('abc', opt_str_to_str('abc'))
+        self.assertEqual(' ', opt_str_to_str(' '))
+        self.assertEqual('', opt_str_to_str(None))
+
+
+# optional no or string converter
+# -------------------------------
+
+class TestOptNoOrStr(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual((False, None), opt_no_or_str_from_str('Nein'))
+        self.assertEqual((True, 'Nur Wochenende'), opt_no_or_str_from_str('Nur Wochenende'))
+        self.assertEqual((True, 'Ja'), opt_no_or_str_from_str('Ja'))
+        self.assertEqual((None, None), opt_no_or_str_from_str(''))
+
+    def test_to_str(self):
+        self.assertEqual('Nein', opt_no_or_str_to_str((False, None)))
+        self.assertEqual('Nur Wochenende', opt_no_or_str_to_str((True, 'Nur Wochenende')))
+        self.assertEqual('Ja', opt_no_or_str_to_str((True, 'Ja')))
+        self.assertEqual('', opt_no_or_str_to_str((None, None)))
+
+
+# integer converter
+# -----------------
+
+class TestInt(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(42, int_from_str('42'))
+        self.assertEqual(42, int_from_str('+42'))
+        self.assertEqual(-20, int_from_str('-20'))
+        self.assertEqual(0, int_from_str('0', min=0))
+        self.assertEqual(10, int_from_str('10', max=10))
+        with self.assertRaises(ValueError):
+            int_from_str('abc')
+        with self.assertRaises(ValueError):
+            int_from_str('')
+        with self.assertRaises(ValueError):
+            int_from_str('-1', min=0)
+        with self.assertRaises(ValueError):
+            int_from_str('11', max=10)
+        with self.assertRaises(ValueError):
+            int_from_str('10.0')
+        with self.assertRaises(ValueError):
+            int_from_str('0d')
+
+    def test_to_str(self):
+        self.assertEqual('20', int_to_str(20))
+        self.assertEqual('-20', int_to_str(-20))
+        self.assertEqual('0', int_to_str(0))
+
+
+class TestOptInt(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(42, opt_int_from_str('42'))
+        self.assertEqual(42, opt_int_from_str('+42'))
+        self.assertEqual(-20, opt_int_from_str('-20'))
+        self.assertEqual(None, opt_int_from_str(''))
+        with self.assertRaises(ValueError):
+            opt_int_from_str('abc')
+        with self.assertRaises(ValueError):
+            opt_int_from_str('10.0')
+        with self.assertRaises(ValueError):
+            opt_int_from_str('0d')
+
+    def test_to_str(self):
+        self.assertEqual('20', opt_int_to_str(20))
+        self.assertEqual('-20', opt_int_to_str(-20))
+        self.assertEqual('0', opt_int_to_str(0))
+        self.assertEqual('', opt_int_to_str(None))
+
+
+class TestOptUInt(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(42, opt_uint_from_str('42'))
+        self.assertEqual(0, opt_uint_from_str('0'))
+        self.assertEqual(None, opt_uint_from_str(''))
+        with self.assertRaises(ValueError):
+            opt_uint_from_str('-1')
+
+    def test_to_str(self):
+        self.assertEqual('20', opt_uint_to_str(20))
+        self.assertEqual('0', opt_uint_to_str(0))
+        self.assertEqual('', opt_uint_to_str(None))
+
+
+# bool converter
+# --------------
+
+class TestBoolGerman(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(True, bool_german_from_str('Ja'))
+        self.assertEqual(True, opt_bool_german_from_str('Ja'))
+        self.assertEqual(False, bool_german_from_str('Nein'))
+        self.assertEqual(False, opt_bool_german_from_str('Nein'))
+        self.assertEqual(None, opt_bool_german_from_str(''))
+        with self.assertRaises(ValueError):
+            bool_german_from_str('Vielleicht')
+            opt_bool_german_from_str('Vielleicht')
+            bool_german_from_str('')
+
+    def test_to_str(self):
+        self.assertEqual('Ja', bool_german_to_str(True))
+        self.assertEqual('Ja', opt_bool_german_to_str(True))
+        self.assertEqual('Nein', bool_german_to_str(False))
+        self.assertEqual('Nein', opt_bool_german_to_str(False))
+        self.assertEqual('', opt_bool_german_to_str(None))
+
+
+# tristate converter
+# ------------------
+
+class TestTristateGerman(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(1.0, tristate_german_from_str('Ja'))
+        self.assertEqual(0.5, tristate_german_from_str('Teilweise'))
+        self.assertEqual(0, tristate_german_from_str('Nein'))
+        with self.assertRaises(ValueError):
+            tristate_german_from_str('')
+        with self.assertRaises(ValueError):
+            tristate_german_from_str('Vielleicht')
+
+    def test_to_str(self):
+        self.assertEqual('Ja', tristate_german_to_str(1.0))
+        self.assertEqual('Teilweise', tristate_german_to_str(0.5))
+        self.assertEqual('Nein', tristate_german_to_str(0.0))
+
+
+class TestOptTristateGerman(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(1.0, opt_tristate_german_from_str('Ja'))
+        self.assertEqual(0.5, opt_tristate_german_from_str('Teilweise'))
+        self.assertEqual(0, opt_tristate_german_from_str('Nein'))
+        self.assertEqual(None, opt_tristate_german_from_str(''))
+        with self.assertRaises(ValueError):
+            opt_tristate_german_from_str('Vielleicht')
+
+    def test_to_str(self):
+        self.assertEqual('Ja', opt_tristate_german_to_str(1.0))
+        self.assertEqual('Teilweise', opt_tristate_german_to_str(0.5))
+        self.assertEqual('Nein', opt_tristate_german_to_str(0.0))
+        self.assertEqual('', opt_tristate_german_to_str(None))
+
+
+# tristate with comment converter
+# -------------------------------
+
+class TestOptTristateGermanComment(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual((1.0, None), opt_tristate_german_comment_from_str('Ja'))
+        self.assertEqual((0.5, None), opt_tristate_german_comment_from_str('Teilweise'))
+        self.assertEqual((0, None), opt_tristate_german_comment_from_str('Nein'))
+        self.assertEqual((0.5, 'nur ganz oben nicht'), opt_tristate_german_comment_from_str('Teilweise (nur ganz oben nicht)'))
+        self.assertEqual((None, None), opt_tristate_german_comment_from_str(''))
+        with self.assertRaises(ValueError):
+            opt_tristate_german_from_str('Vielleicht')
+        with self.assertRaises(ValueError):
+            opt_tristate_german_from_str('(Ja)')
+
+    def test_to_str(self):
+        self.assertEqual('Ja', opt_tristate_german_comment_to_str((1.0, None)))
+        self.assertEqual('Teilweise', opt_tristate_german_comment_to_str((0.5, None)))
+        self.assertEqual('Nein', opt_tristate_german_comment_to_str((0.0, None)))
+        self.assertEqual('', opt_tristate_german_comment_to_str((None, None)))
+
+
+# url converter
+# -------------
+
+class TestUrl(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual('http://www.winterrodeln.org/wiki/Arzler_Alm/', url_from_str('http://www.winterrodeln.org/wiki/Arzler_Alm/'))
+        self.assertEqual('http://www.winterrodeln.org/wiki/Nösslachhütte/', url_from_str('http://www.winterrodeln.org/wiki/Nösslachhütte/'))
+        self.assertEqual('https://www.winterrodeln.org/wiki/Nösslachhütte/', url_from_str('https://www.winterrodeln.org/wiki/Nösslachhütte/'))
+        with self.assertRaises(ValueError):
+            url_from_str('mailto:office@example.com')
+        with self.assertRaises(ValueError):
+            url_from_str('/wiki/Arzler_Alm/')
+
+    def test_to_str(self):
+        self.assertEqual('http://www.winterrodeln.org/wiki/Arzler_Alm/', url_to_str('http://www.winterrodeln.org/wiki/Arzler_Alm/'))
+        self.assertEqual('http://www.winterrodeln.org/wiki/Nösslachhütte/', url_to_str('http://www.winterrodeln.org/wiki/Nösslachhütte/'))
+        self.assertEqual('https://www.winterrodeln.org/wiki/Nösslachhütte/', url_to_str('https://www.winterrodeln.org/wiki/Nösslachhütte/'))
+
+
+# webauskunft converter
+# ---------------------
+
+class TestWebauskunft(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual((True, 'http://www.example.com/current'), webauskunft_from_str('http://www.example.com/current'))
+        self.assertEqual((None, None), webauskunft_from_str(''))
+        self.assertEqual((False, None), webauskunft_from_str('Nein'))
+
+    def test_to_str(self):
+        self.assertEqual('http://www.example.com/current', webauskunft_to_str((True, 'http://www.example.com/current')))
+        self.assertEqual('', webauskunft_to_str((None, None)))
+        self.assertEqual('Nein', webauskunft_to_str((False, None)))
+
+
+# wikipage converter
+# ------------------
+
+class TestWikipage(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual('[[Birgitzer Alm]]', wikipage_from_str('[[Birgitzer Alm]]'))
+        with self.assertRaises(ValueError):
+            wikipage_from_str('[[')
+        with self.assertRaises(ValueError):
+            wikipage_from_str('')
+        with self.assertRaises(ValueError):
+            wikipage_from_str('Birgitzer Alm')
+
+    def test_to_str(self):
+        self.assertEqual('[[Birgitzer Alm]]', wikipage_to_str('[[Birgitzer Alm]]'))
+
+
+class TestOptWikipageEnum(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(['[[Birgitzer Alm]]', '[[Kemater Alm]]'], opt_wikipage_enum_from_str('[[Birgitzer Alm]]; [[Kemater Alm]]'))
+        self.assertEqual(['[[Birgitzer Alm]]'], opt_wikipage_enum_from_str('[[Birgitzer Alm]]'))
+        self.assertEqual([], opt_wikipage_enum_from_str('Nein'))
+        self.assertEqual(None, opt_wikipage_enum_from_str(''))
+
+    def test_to_str(self):
+        self.assertEqual('[[Birgitzer Alm]]; [[Kemater Alm]]', opt_wikipage_enum_to_str(['[[Birgitzer Alm]]', '[[Kemater Alm]]']))
+        self.assertEqual('[[Birgitzer Alm]]', opt_wikipage_enum_to_str(['[[Birgitzer Alm]]']))
+        self.assertEqual('Nein', opt_wikipage_enum_to_str([]))
+        self.assertEqual('', opt_wikipage_enum_to_str(None))
+
+
+# email converter
+# ---------------
+
+class TestEmail(unittest.TestCase):
+    def setUp(self):
+        self.good_addresses = ['office@example.com', 'winter+rodeln@localhost', 'joe.doe@exämple.com']
+        self.bad_addresses = ['öffice@example.com', 'winter rodeln@localhost', 'www.winterrodeln.org', 'mailto:info@example.com', 'info@example.com.']
+
+    def test_from_str(self):
+        for value in self.good_addresses:
+            self.assertEqual(value, email_from_str(value))
+        for value in self.bad_addresses:
+            with self.assertRaises(ValueError):
+                email_from_str(value)
+
+    def test_to_str(self):
+        for value in self.good_addresses:
+            self.assertEqual(value, email_to_str(value))
+
+
+class TestMaskedEmail(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(('office@example.com', False), masked_email_from_str('office@example.com'))
+        self.assertEqual(('office@example.com', True), masked_email_from_str('office(at)example.com'))
+        with self.assertRaises(ValueError):
+            masked_email_from_str('office@example.com', masked_only=True)
+        with self.assertRaises(ValueError):
+            masked_email_from_str('off ice@example.com')
+
+    def test_to_str(self):
+        self.assertEqual('office@example.com', masked_email_to_str(('office@example.com', False)))
+        self.assertEqual('office(at)example.com', masked_email_to_str(('office@example.com', True)))
+        self.assertEqual('office()example.com', masked_email_to_str(('office@example.com', True), '()'))
+
+
+class TestEmails(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(None, emails_from_str(''))
+        self.assertEqual([], emails_from_str('Nein'))
+        self.assertEqual([(('info@example.com', False), None)], emails_from_str('info@example.com'))
+        self.assertEqual([(('info@example.com', True), None)], emails_from_str('info(at)example.com'))
+        self.assertEqual([(('info@example.com', False), 'Office')], emails_from_str('info@example.com (Office)'))
+        self.assertEqual([(('info@example.com', False), None), (('home@example.com', False), 'Privat')], emails_from_str('info@example.com; home@example.com (Privat)'))
+        with self.assertRaises(ValueError):
+            emails_from_str('nein')
+        with self.assertRaises(ValueError):
+            emails_from_str('info@example.com; ho me@example.com (Privat)')
+
+    def test_to_str(self):
+        self.assertEqual('', emails_to_str(None))
+        self.assertEqual('Nein', emails_to_str([]))
+        self.assertEqual('info@example.com', emails_to_str([(('info@example.com', False), None)]))
+        self.assertEqual('info@example.com (Office)', emails_to_str([(('info@example.com', False), 'Office')]))
+        self.assertEqual('info@example.com; home@example.com (Privat)', emails_to_str([(('info@example.com', False), None), (('home@example.com', False), 'Privat')]))
+
+
+# phone converter
+# ---------------
+
+class TestPhoneNumber(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual('+43-699-123456789', phone_number_from_str('+43-699-123456789'))
+        self.assertEqual('+43-69945', phone_number_from_str('+43-69945'))
+        self.assertEqual('+43-512-507-6418', phone_number_from_str('+43-512-507-6418'))
+        with self.assertRaises(ValueError):
+            phone_number_from_str('+43-')
+        with self.assertRaises(ValueError):
+            phone_number_from_str('0512123456789')
+
+    def test_to_str(self):
+        self.assertEqual('+43-699-123456789', phone_number_to_str('+43-699-123456789'))
+        self.assertEqual('+43-69945', phone_number_to_str('+43-69945'))
+        self.assertEqual('+43-512-507-6418', phone_number_to_str('+43-512-507-6418'))
+
+
+class TestOptPhoneCommentEnum(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(opt_phone_comment_enum_from_str(''), None)
+        self.assertEqual([], opt_phone_comment_enum_from_str('Nein'))
+        self.assertEqual([('+43-512-123456', 'untertags')], opt_phone_comment_enum_from_str('+43-512-123456 (untertags)'))
+        self.assertEqual([('+43-512-1234', 'untertags'), ('+43-664-123456', 'Alm')], opt_phone_comment_enum_from_str('+43-512-1234 (untertags); +43-664-123456 (Alm)'))
+        self.assertEqual([('+43-512-1234', None), ('+43-664-123456', 'Sommer')], opt_phone_comment_enum_from_str('+43-512-1234; +43-664-123456 (Sommer)', True))
+        with self.assertRaises(ValueError):
+            opt_phone_comment_enum_from_str('+43-512-123456+ (untertags)')
+        with self.assertRaises(ValueError):
+            opt_phone_comment_enum_from_str('+43-512-123456')
+
+    def test_to_str(self):
+        self.assertEqual('', opt_phone_comment_enum_to_str(None))
+        self.assertEqual('Nein', opt_phone_comment_enum_to_str([]))
+        self.assertEqual('+43-512-123456 (untertags)', opt_phone_comment_enum_to_str([('+43-512-123456', 'untertags')]))
+        self.assertEqual('+43-512-1234 (untertags); +43-664-123456 (Alm)', opt_phone_comment_enum_to_str([('+43-512-1234', 'untertags'), ('+43-664-123456', 'Alm')]))
+        self.assertEqual('+43-512-1234; +43-664-123456 (Sommer)', opt_phone_comment_enum_to_str([('+43-512-1234', None), ('+43-664-123456', 'Sommer')], True))
+
+
+# longitude/latitude converter
+# ----------------------------
+
+class TestLonLat(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(LonLat(11.453553, 47.076207), lonlat_from_str('47.076207 N 11.453553 E'))
+        with self.assertRaises(ValueError):
+            lonlat_from_str('47.076207 N 11.453553')
+
+    def test_to_str(self):
+        self.assertEqual('47.076207 N 11.453553 E', lonlat_to_str(LonLat(11.453553, 47.076207)))
+
+
+# difficulty converter
+# --------------------
+
+class TestDifficultyGerman(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(1, difficulty_german_from_str('leicht'))
+        self.assertEqual(2, difficulty_german_from_str('mittel'))
+        with self.assertRaises(ValueError):
+            difficulty_german_from_str('dontknow')
+        with self.assertRaises(ValueError):
+            difficulty_german_from_str('')
+
+    def test_to_str(self):
+        self.assertEqual('leicht', difficulty_german_to_str(1))
+
+
+# TODO: avalanches converter
+# --------------------
+
+
+
+# lift converter
+# --------------
+
+class TestLiftGermanValidator(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(lift_german_from_str(''), None)
+        self.assertEqual([], lift_german_from_str('Nein'))
+        self.assertEqual([('Sessellift', None)], lift_german_from_str('Sessellift'))
+        self.assertEqual([('Gondel', 'nur bis zur Hälfte')], lift_german_from_str('Gondel (nur bis zur Hälfte)'))
+        self.assertEqual([('Sessellift', None), ('Taxi', None)], lift_german_from_str('Sessellift; Taxi'))
+        self.assertEqual([('Sessellift', 'Wochenende'), ('Taxi', '6 Euro')], lift_german_from_str('Sessellift (Wochenende); Taxi (6 Euro)'))
+
+    def test_to_str(self):
+        self.assertEqual('', lift_german_to_str(None))
+        self.assertEqual('Nein', lift_german_to_str([]))
+        self.assertEqual('Sessellift', lift_german_to_str([('Sessellift', None)]))
+        self.assertEqual('Gondel (nur bis zur Hälfte)', lift_german_to_str([('Gondel', 'nur bis zur Hälfte')]))
+        self.assertEqual('Sessellift; Taxi', lift_german_to_str([('Sessellift', None), ('Taxi', None)]))
+        self.assertEqual('Sessellift (Wochenende); Taxi (6 Euro)', lift_german_to_str([('Sessellift', 'Wochenende'), ('Taxi', '6 Euro')]))
+
+
+# TODO: public transport converter
+# --------------------------
+
+
+# cachet converter
+# ----------------
+
+class TestSingleCachet(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(('Tiroler Naturrodelbahn-Gütesiegel', '2009', 'mittel'), single_cachet_german_from_str('Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'))
+        self.assertEqual(('Tiroler Naturrodelbahn-Gütesiegel', '2013', 'schwer'), single_cachet_german_from_str('Tiroler Naturrodelbahn-Gütesiegel 2013 schwer'))
+        with self.assertRaises(ValueError):
+            single_cachet_german_from_str('')
+        with self.assertRaises(ValueError):
+            single_cachet_german_from_str('Salzburger Naturrodelbahn-Gütesiegel 2013 schwer')
+        with self.assertRaises(ValueError):
+            single_cachet_german_from_str('Tiroler Naturrodelbahn-Gütesiegel 4013 schwer')
+        with self.assertRaises(ValueError):
+            single_cachet_german_from_str('Tiroler Naturrodelbahn-Gütesiegel 13 schwer')
+        with self.assertRaises(ValueError):
+            single_cachet_german_from_str('Tiroler Naturrodelbahn-Gütesiegel 2013 schwerer')
+
+    def test_to_str(self):
+        self.assertEqual('Tiroler Naturrodelbahn-Gütesiegel 2009 mittel', single_cachet_german_to_str(('Tiroler Naturrodelbahn-Gütesiegel', '2009', 'mittel')))
+        self.assertEqual('Tiroler Naturrodelbahn-Gütesiegel 2013 schwer', single_cachet_german_to_str(('Tiroler Naturrodelbahn-Gütesiegel', '2013', 'schwer')))
+
+
+class TestCachetGerman(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(cachet_german_from_str(''), None)
+        self.assertEqual([], cachet_german_from_str('Nein'))
+        self.assertEqual([('Tiroler Naturrodelbahn-Gütesiegel', '2009', 'mittel')], cachet_german_from_str('Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'))
+        self.assertEqual([('Tiroler Naturrodelbahn-Gütesiegel', '2013', 'schwer'), ('Tiroler Naturrodelbahn-Gütesiegel', '2009', 'mittel')], 
+                         cachet_german_from_str('Tiroler Naturrodelbahn-Gütesiegel 2013 schwer; Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'))
+        with self.assertRaises(ValueError):
+            cachet_german_from_str('Ja')
+        with self.assertRaises(ValueError):
+            cachet_german_from_str('Tiroler Naturrodelbahn-Gütesiegel 2013 schwer Tiroler Naturrodelbahn-Gütesiegel 2009 mittel')
+
+    def test_to_str(self):
+        self.assertEqual('', cachet_german_to_str(None))
+        self.assertEqual('Nein', cachet_german_to_str([]))
+        self.assertEqual('Tiroler Naturrodelbahn-Gütesiegel 2009 mittel', cachet_german_to_str([('Tiroler Naturrodelbahn-Gütesiegel', '2009', 'mittel')]))
+        self.assertEqual('Tiroler Naturrodelbahn-Gütesiegel 2013 schwer; Tiroler Naturrodelbahn-Gütesiegel 2009 mittel', cachet_german_to_str([('Tiroler Naturrodelbahn-Gütesiegel', '2013', 'schwer'), ('Tiroler Naturrodelbahn-Gütesiegel', '2009', 'mittel')]))
+
+
+# night light days converter
+# --------------------------
+
+class TestNightLightDays(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual((None, None), nightlightdays_from_str(''))
+        self.assertEqual((2, 'Mo, Di'), nightlightdays_from_str('2 (Mo, Di)'))
+        self.assertEqual((7, None), nightlightdays_from_str('7'))
+        self.assertEqual((0, None), nightlightdays_from_str('0'))
+        self.assertEqual((None, 'keine Ahnung'), nightlightdays_from_str('(keine Ahnung)'))
+        with self.assertRaises(ValueError):
+            nightlightdays_from_str('8')
+        with self.assertRaises(ValueError):
+            nightlightdays_from_str('5 (Montag')
+        with self.assertRaises(ValueError):
+            nightlightdays_from_str('5.2')
+
+    def test_to_str(self):
+        self.assertEqual('', nightlightdays_to_str((None, None)))
+        self.assertEqual('2 (Mo, Di)', nightlightdays_to_str((2, 'Mo, Di')))
+        self.assertEqual('7', nightlightdays_to_str((7, None)))
+        self.assertEqual('0', nightlightdays_to_str((0, None)))
+
+
+# string with optional comment enum/list converter
+# ------------------------------------------------
+
+class TestOptStrOptCommentEnum(unittest.TestCase):
+    def test_from_str(self):
+        self.assertEqual(None, opt_str_opt_comment_enum_from_str(''))
+        self.assertEqual([], opt_str_opt_comment_enum_from_str('Nein'))
+        self.assertEqual([('Talstation', None)], opt_str_opt_comment_enum_from_str('Talstation'))
+        self.assertEqual([('Talstation', 'unten')], opt_str_opt_comment_enum_from_str('Talstation (unten)'))
+        self.assertEqual([('Talstation', 'unten'), ('Mittelstation', None)], opt_str_opt_comment_enum_from_str('Talstation (unten); Mittelstation'))
+        with self.assertRaises(ValueError):
+            opt_str_opt_comment_enum_from_str('(unten)')
+        with self.assertRaises(ValueError):
+            opt_str_opt_comment_enum_from_str('Talstation (unten); ; Mittelstation')
+
+    def test_to_str(self):
+        self.assertEqual('', opt_str_opt_comment_enum_to_str(None))
+        self.assertEqual('Nein', opt_str_opt_comment_enum_to_str([]))
+        self.assertEqual('Talstation', opt_str_opt_comment_enum_to_str([('Talstation', None)]))
+        self.assertEqual('Talstation (unten)', opt_str_opt_comment_enum_to_str([('Talstation', 'unten')]))
+        self.assertEqual('Talstation (unten); Mittelstation', opt_str_opt_comment_enum_to_str([('Talstation', 'unten'), ('Mittelstation', None)]))
+
+
+# wikibox converter
+# -----------------
+
+class TestWikibox(unittest.TestCase):
+    def test_from_str(self):
+        value = '{{MyTemplate|apple=2|banana=5}}'
+        converter_dict = OrderedDict([('apple', opt_uint_converter), ('banana', opt_uint_converter)])
+        result = wikibox_from_str(value, 'MyTemplate', converter_dict)
+        self.assertEqual(2, result['apple'])
+        self.assertEqual(5, result['banana'])
+
+        value = '{{MyTemplate\n | apple = 2 \n| banana = 5 }}'
+        result = wikibox_from_str(value, 'MyTemplate', converter_dict)
+        self.assertEqual(2, result['apple'])
+        self.assertEqual(5, result['banana'])
+
+        with self.assertRaises(ValueError):
+            wikibox_from_str(value, 'myTemplate', converter_dict)
+        with self.assertRaises(ValueError):
+            value = '{{MyTemplate|apple=2|banana=five}}'
+            wikibox_from_str(value, 'MyTemplate', converter_dict)
+        with self.assertRaises(ValueError):
+            value = '{{MyTemplate|apple=2}}'
+            wikibox_from_str(value, 'MyTemplate', converter_dict)
+        with self.assertRaises(ValueError):
+            value = '{{MyTemplate|apple=2|banana=5|cherry=6}}'
+            wikibox_from_str(value, 'MyTemplate', converter_dict)
+
+    def test_to_str(self):
+        value = OrderedDict([('apple', 2), ('banana', 5)])
+        converter_dict = OrderedDict([('apple', opt_uint_converter), ('banana', opt_uint_converter)])
+        result = wikibox_to_str(value, 'MyTemplate', converter_dict)
+        self.assertEqual('{{MyTemplate|apple=2|banana=5}}', result)
+
+
+# Rodelbahnbox converter
+# ----------------------
+
+class TestRodelbahnbox(unittest.TestCase):
+    def setUp(self):
+        self.maxDiff = None
+        self.value = \
+'''{{Rodelbahnbox
+| Position             = 46.807218 N 12.806522 E
+| Position oben        = 46.799014 N 12.818658 E
+| Höhe oben            = 1046
+| Position unten       =
+| Höhe unten           =
+| Länge                = 3500
+| Schwierigkeit        = mittel
+| Lawinen              = kaum
+| Betreiber            = Bringungsgemeinschaft Kreithof-Dolomitenhütte
+| Öffentliche Anreise  = Schlecht
+| Aufstieg möglich     = Ja
+| Aufstieg getrennt    = Teilweise
+| Gehzeit              = 75
+| Aufstiegshilfe       = Taxi; Sonstige (PKW bis Kreithof)
+| Beleuchtungsanlage   = Ja
+| Beleuchtungstage     = 7
+| Rodelverleih         = Nein
+| Gütesiegel           = Tiroler Naturrodelbahn-Gütesiegel 2009 mittel
+| Webauskunft          = http://www.lienzerdolomiten.info/at/tobogorpt.html
+| Telefonauskunft      = +43-664-2253782 (Dolomitenhütte)
+| Bild                 = Dolomitenrodelbahn Tristach 2011-12-22 oberer Bereich.jpg
+| In Übersichtskarte   = Ja
+| Forumid              = 139
+}}'''
+
+    def test_from_str(self):
+        value = rodelbahnbox_from_str(self.value)
+        self.assertEqual(LonLat(12.806522, 46.807218), value['Position'])
+        self.assertEqual(LonLat(12.818658, 46.799014), value['Position oben'])
+        self.assertEqual(1046, value['Höhe oben'])
+        self.assertEqual(LonLat(None, None), value['Position unten'])
+        self.assertEqual(None, value['Höhe unten'])
+        self.assertEqual(3500, value['Länge'])
+        self.assertEqual(2, value['Schwierigkeit'])
+        self.assertEqual(1, value['Lawinen'])
+        self.assertEqual('Bringungsgemeinschaft Kreithof-Dolomitenhütte', value['Betreiber'])
+        self.assertEqual(4, value['Öffentliche Anreise'])
+        self.assertEqual(True, value['Aufstieg möglich'])
+        self.assertEqual((0.5, None), value['Aufstieg getrennt'])
+        self.assertEqual(75, value['Gehzeit'])
+        self.assertEqual([('Taxi', None), ('Sonstige', 'PKW bis Kreithof')], value['Aufstiegshilfe'])
+        self.assertEqual((1.0, None), value['Beleuchtungsanlage'])
+        self.assertEqual((7, None), value['Beleuchtungstage'])
+        self.assertEqual([], value['Rodelverleih'])
+        self.assertEqual([('Tiroler Naturrodelbahn-Gütesiegel', '2009', 'mittel')], value['Gütesiegel'])
+        self.assertEqual((True, 'http://www.lienzerdolomiten.info/at/tobogorpt.html'), value['Webauskunft'])
+        self.assertEqual([('+43-664-2253782', 'Dolomitenhütte')], value['Telefonauskunft'])
+        self.assertEqual('Dolomitenrodelbahn Tristach 2011-12-22 oberer Bereich.jpg', value['Bild'])
+        self.assertEqual(True, value['In Übersichtskarte'])
+        self.assertEqual(139, value['Forumid'])
+
+    def test_to_str(self):
+        value = OrderedDict([
+            ('Position', LonLat(12.806522, 46.807218)),
+            ('Position oben', LonLat(12.818658, 46.799014)),
+            ('Höhe oben', 1046),
+            ('Position unten', LonLat(None, None)),
+            ('Höhe unten', None),
+            ('Länge', 3500),
+            ('Schwierigkeit', 2),
+            ('Lawinen', 1),
+            ('Betreiber', 'Bringungsgemeinschaft Kreithof-Dolomitenhütte'),
+            ('Öffentliche Anreise', 4),
+            ('Aufstieg möglich', True),
+            ('Aufstieg getrennt', (0.5, None)),
+            ('Gehzeit', 75),
+            ('Aufstiegshilfe', [('Taxi', None), ('Sonstige', 'PKW bis Kreithof')]),
+            ('Beleuchtungsanlage', (1.0, None)),
+            ('Beleuchtungstage', (7, None)),
+            ('Rodelverleih', []),
+            ('Gütesiegel', [('Tiroler Naturrodelbahn-Gütesiegel', '2009', 'mittel')]),
+            ('Webauskunft', (True, 'http://www.lienzerdolomiten.info/at/tobogorpt.html')),
+            ('Telefonauskunft', [('+43-664-2253782', 'Dolomitenhütte')]),
+            ('Bild', 'Dolomitenrodelbahn Tristach 2011-12-22 oberer Bereich.jpg'),
+            ('In Übersichtskarte', True),
+            ('Forumid', 139)])
+        self.assertEqual(self.value, rodelbahnbox_to_str(value))
+
+
+# Gasthausbox converter
+# ---------------------
+
+class TestGasthausbox(unittest.TestCase):
+    def setUp(self):
+        self.maxDiff = None
+        self.value = \
+"""{{Gasthausbox
+| Position          = 47.123456 N 11.123456 E
+| Höhe              = 1808
+| Betreiber         = Max Mustermann
+| Sitzplätze        = 50
+| Übernachtung      = 20 Matrazenlager, 3 Doppelzimmer
+| Rauchfrei         = Ja
+| Rodelverleih      = 2 Euro (Ausweis erforderlich)
+| Handyempfang      = A1; T-Mobile A
+| Homepage          = http://www.birgitzeralm.at/
+| E-Mail            = Nein
+| Telefon           = +43-664-5487520; +43-512-123456 (wenn geschlossen)
+| Bild              = Gasthaus_Birgitzer_Alm_03.jpg
+| Rodelbahnen       = [[Birgitzer Alm (vom Adelshof)]]; [[Birgitzer Alm (von Birgitz)]]
+}}"""
+
+    def test_from_str(self):
+        value = gasthausbox_from_str(self.value)
+        self.assertEqual(LonLat(11.123456, 47.123456), value['Position'])
+        self.assertEqual(1808, value['Höhe'])
+        self.assertEqual('Max Mustermann', value['Betreiber'])
+        self.assertEqual(50, value['Sitzplätze'])
+        self.assertEqual((True, '20 Matrazenlager, 3 Doppelzimmer'), value['Übernachtung'])
+        self.assertEqual((True, '2 Euro (Ausweis erforderlich)'), value['Rodelverleih'])
+        self.assertEqual(1.0, value['Rauchfrei'])
+        self.assertEqual([('A1', None), ('T-Mobile A', None)], value['Handyempfang'])
+        self.assertEqual((True, 'http://www.birgitzeralm.at/'), value['Homepage'])
+        self.assertEqual([], value['E-Mail'])
+        self.assertEqual([('+43-664-5487520', None), ('+43-512-123456', 'wenn geschlossen')], value['Telefon'])
+        self.assertEqual('Gasthaus_Birgitzer_Alm_03.jpg', value['Bild'])
+        self.assertEqual(['[[Birgitzer Alm (vom Adelshof)]]', '[[Birgitzer Alm (von Birgitz)]]'], value['Rodelbahnen'])
+
+    def test_to_str(self):
+        value = OrderedDict([
+            ('Position', LonLat(11.123456, 47.123456)),
+            ('Höhe', 1808),
+            ('Betreiber', 'Max Mustermann'),
+            ('Sitzplätze', 50),
+            ('Übernachtung', (True, '20 Matrazenlager, 3 Doppelzimmer')),
+            ('Rodelverleih', (True, '2 Euro (Ausweis erforderlich)')),
+            ('Rauchfrei', 1.0),
+            ('Handyempfang', [('A1', None), ('T-Mobile A', None)]),
+            ('Homepage', (True, 'http://www.birgitzeralm.at/')),
+            ('E-Mail', []),
+            ('Telefon', [('+43-664-5487520', None), ('+43-512-123456', 'wenn geschlossen')]),
+            ('Bild', 'Gasthaus_Birgitzer_Alm_03.jpg'),
+            ('Rodelbahnen', ['[[Birgitzer Alm (vom Adelshof)]]', '[[Birgitzer Alm (von Birgitz)]]'])])
+        self.assertEqual(self.value, gasthausbox_to_str(value))