Added functions to generate and parse templates.
authorphilipp <philipp@7aebc617-e5e2-0310-91dc-80fb5f6d2477>
Fri, 4 Feb 2011 21:19:37 +0000 (21:19 +0000)
committerphilipp <philipp@7aebc617-e5e2-0310-91dc-80fb5f6d2477>
Fri, 4 Feb 2011 21:19:37 +0000 (21:19 +0000)
git-svn-id: http://www.winterrodeln.org/svn/servermediawiki/trunk/wrpylib@755 7aebc617-e5e2-0310-91dc-80fb5f6d2477

setup.py
tests/test_mwmarkup.py [new file with mode: 0644]
tests/test_wrmwmarkup.py
wrpylib/mwmarkup.py
wrpylib/wrmwmarkup.py

index 51c76fd20acd812704ba46e6ede663d8ab8cb758..fe9bdbc801d483def8eb3b2b0710b8b2cf8df99b 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@
 from distutils.core import setup
 
 setup(name='wrpylib',
-    version='0.0.0',
+    version='0.0.1',
     description='Winterrodeln Python Library',
     author='Philipp Spitzer',
     author_email='philipp.spitzer@winterrodeln.org',
diff --git a/tests/test_mwmarkup.py b/tests/test_mwmarkup.py
new file mode 100644 (file)
index 0000000..15dff31
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/python2.6
+# -*- coding: iso-8859-15 -*-
+import wrpylib.wrmwmarkup
+import formencode
+
+
+def test_rodelbahnbox_to_sledrun():
+    wikitext = u'''== Allgemeines ==
+    {{Rodelbahnbox
+    | Position             = 47.309820 N 9.986508 E
+    | Position oben        = 
+    | Höhe oben            = 1244
+    | Position unten       = 
+    | Höhe unten           = 806
+    | Länge                = 5045
+    | Schwierigkeit        = 
+    | Lawinen              = gelegentlich
+    | Betreiber            = 
+    | Öffentliche Anreise  = Ja
+    | Gehzeit              = 105
+    | Aufstieg getrennt    = Nein
+    | Aufstiegshilfe       = Nein
+    | Beleuchtungsanlage   = Nein
+    | Beleuchtungstage     = 
+    | Rodelverleih         = Ja
+    | Gütesiegel           = 
+    | Webauskunft          = 
+    | Telefonauskunft      = +43-664-1808482 (Bergkristallhütte)
+    | Bild                 = Rodelbahn Bergkristallhütte 2009-03-03.jpg
+    | In Übersichtskarte   = Ja
+    | Forumid              = 72
+    }}
+    Die Rodelbahn zur Bergkristallhütte ist durchaus abwechslungsreich.'''
+    class Sledrun(object): pass
+    sledrun = Sledrun()
+    wrpylib.wrmwmarkup.rodelbahnbox_to_sledrun(wikitext, sledrun)
+    wrpylib.wrmwmarkup.sledrun_to_rodelbahnbox(sledrun, '1.3')
+
+
+
+def test_gasthausbox_to_inn():
+    wikitext = u'''{{Gasthausbox
+    | Position          = 47.295549 N 9.986970 E
+    | Höhe              = 1250
+    | Betreiber         = 
+    | Sitzplätze        = 
+    | Übernachtung      = 
+    | Rauchfrei         = Nein
+    | Rodelverleih      = 
+    | Handyempfang      = A1; T-Mobile/Telering
+    | Homepage          = http://www.bergkristallhuette.com/
+    | E-Mail            = bergkristallhuette@gmx.at
+    | Telefon           = +43-664-1808482
+    | Bild              = Bergkritsallhütte 2009-02-07.JPG
+    | Rodelbahnen       = [[Bergkristallhütte]]
+    }}
+    Die Bergkristallhütte ist Teil des Boden-Vorsäß.'''
+    class Inn(object): pass
+    inn = Inn()
+    wrpylib.wrmwmarkup.gasthausbox_to_inn(wikitext, inn)
+    wrpylib.wrmwmarkup.inn_to_gasthausbox(inn)
+
index 18af9bcdea438f25f919d64abf573f9607ec877b..03f0ce870bdb8bf69595aba8b9c049f0c3f891fe 100644 (file)
@@ -1,10 +1,9 @@
 #!/usr/bin/python2.6
 # -*- coding: iso-8859-15 -*-
-import wrpylib.wrmwmarkup
-import formencode
+import wrpylib.mwmarkup
 
 
-def test_rodelbahnbox_to_sledrun():
+def test_find_template():
     wikitext = u'''== Allgemeines ==
     {{Rodelbahnbox
     | Position             = 47.309820 N 9.986508 E
@@ -31,32 +30,54 @@ def test_rodelbahnbox_to_sledrun():
     | Forumid              = 72
     }}
     Die Rodelbahn zur Bergkristallhütte ist durchaus abwechslungsreich.'''
-    class Sledrun(object): pass
-    sledrun = Sledrun()
-    wrpylib.wrmwmarkup.rodelbahnbox_to_sledrun(wikitext, sledrun)
-    wrpylib.wrmwmarkup.sledrun_to_rodelbahnbox(sledrun)
+    start, end = wrpylib.mwmarkup.find_template(wikitext, u'Rodelbahnbox')
+    assert start == wikitext.find(u'{{')
+    assert end == wikitext.find(u'}}')+2
 
 
-
-def test_gasthausbox_to_inn():
-    wikitext = u'''{{Gasthausbox
-    | Position          = 47.295549 N 9.986970 E
-    | Höhe              = 1250
-    | Betreiber         = 
-    | Sitzplätze        = 
-    | Übernachtung      = 
-    | Rauchfrei         = Nein
-    | Rodelverleih      = 
-    | Handyempfang      = A1; T-Mobile/Telering
-    | Homepage          = http://www.bergkristallhuette.com/
-    | E-Mail            = bergkristallhuette@gmx.at
-    | Telefon           = +43-664-1808482
-    | Bild              = Bergkritsallhütte 2009-02-07.JPG
-    | Rodelbahnen       = [[Bergkristallhütte]]
+def test_split_template():
+    wikitext = u'''== Allgemeines ==
+    {{Rodelbahnbox
+    | Position             = 47.309820 N 9.986508 E
+    | Position oben        = 
+    | Höhe oben            = 1244
+    | Position unten       = 
+    | Höhe unten           = 806
+    | Länge                = 5045
+    | Schwierigkeit        = 
+    | Lawinen              = gelegentlich
+    | Betreiber            = 
+    | Öffentliche Anreise  = Ja
+    | Gehzeit              = 105
+    | Aufstieg getrennt    = Nein
+    | Aufstiegshilfe       = Nein
+    | Beleuchtungsanlage   = Nein
+    | Beleuchtungstage     = 
+    | Rodelverleih         = Ja
+    | Gütesiegel           = 
+    | Webauskunft          = 
+    | Telefonauskunft      = +43-664-1808482 (Bergkristallhütte)
+    | Bild                 = Rodelbahn Bergkristallhütte 2009-03-03.jpg
+    | In Übersichtskarte   = Ja
+    | Forumid              = 72
     }}
-    Die Bergkristallhütte ist Teil des Boden-Vorsäß.'''
-    class Inn(object): pass
-    inn = Inn()
-    wrpylib.wrmwmarkup.gasthausbox_to_inn(wikitext, inn)
-    wrpylib.wrmwmarkup.inn_to_gasthausbox(inn)
+    Die Rodelbahn zur Bergkristallhütte ist durchaus abwechslungsreich.'''
+    start, end = wrpylib.mwmarkup.find_template(wikitext, u'Rodelbahnbox')
+    template_title, parameters = wrpylib.mwmarkup.split_template(wikitext[start:end])
+    assert template_title == u'Rodelbahnbox'
+    assert len(parameters) == 22
+    assert parameters['Position'] == u'47.309820 N 9.986508 E'
+    assert parameters['Telefonauskunft'] == u'+43-664-1808482 (Bergkristallhütte)'
+    assert parameters['Schwierigkeit'] == u''
+
+
+def test_create_template():
+    wikitext = u'''{{Nicetemplate|Red|Bold|Position=Top|Alignment=Right}}'''
+    template_title, parameters = wrpylib.mwmarkup.split_template(wikitext)
+    print parameters
+    keys = [u'1', u'2', u'Position', u'Alignment']
+    values = [parameters[k] for k in keys]
+    wikitext_generated = wrpylib.mwmarkup.create_template(template_title, values[:2], keys[2:], values[2:])
+    wikitext_table = wrpylib.mwmarkup.create_template(template_title, values[:2], keys[2:], values[2:], True)
+    assert wikitext_generated == wikitext
 
index 41b9b3999cc18aef86333deb5e9812690cd5f014..46e11e472c622e41814223b7fb6425fc70641801 100644 (file)
@@ -9,4 +9,92 @@ that convinced me. However, here are the links:
 * py-wikimarkup https://github.com/dcramer/py-wikimarkup
 * mwlib http://code.pediapress.com/wiki/wiki
 """
+import re
+
+
+def find_template(wikitext, template_title):
+    """Returns the tuple (start, end) of the first occurence of the template '{{template ...}} within wikitext'.
+    (None, None) is returned if the template is not found.
+    If you are sure that the wikitext contains the template, the template could be extracted like follows:
+
+    >>> wikitext = u'This is a {{Color|red|red text}} template.'
+    >>> start, end = find_template(wikitext, u'Color')
+    >>> print wikitext[start:end]
+    {{Color|red|red text}}
+
+    or just:
+
+    >>> print wikitext.__getslice__(*find_template(wikitext, u'Color'))
+    {{Color|red|red text}}
+
+    The search is done with regular expression.
+
+    :param wikitext: The text (preferalbe unicode) that has the template in it.
+    :param template_title: The page title of the template with or without namespace (but as in the wikitext).
+    :return: 
+        (start, end) of the first occurence with start >= 0 and end > start.
+        (None, None) if the template is not found.
+    """ 
+    match = re.search(u"\{\{" + template_title + "[^\}]*\}\}", wikitext,  re.DOTALL)
+    if match is None: return None, None
+    return match.start(), match.end()
+
+
+
+def split_template(template):
+    """Takes a template, like u'{{Color|red|text=Any text}}' and translates it to a Python tuple
+    (template_title, parameters) where parameters is a Python dictionary {1: u'red', u'text'=u'Any text'}.
+    Anonymous parameters get integer keys (converted to unicode) starting with 1 
+    like in MediaWiki, named parameters are unicode strings.
+    Whitespace is stripped.
+    If an unexpected format is encountered, a ValueError is raised."""
+    if not template.startswith(u'{{'): raise ValueError(u'Template does not start with "{{"')
+    if not template.endswith(u'}}'): raise ValueError(u'Template does not end with "}}"')
+    parts = template[2:-2].split(u'|')
+
+    # template name
+    template_title = parts[0].strip()
+    if len(template_title) == 0: raise ValueError(u'Empty template tilte.')
+    del parts[0]
+
+    # anonymous parameters
+    params = {} # result dictionary
+    param_num = 1
+    while len(parts) > 0:
+        equalsign_pos = parts[0].find(u'=')
+        if equalsign_pos >= 0: break # named parameter
+        params[unicode(param_num)] = parts[0].strip()
+        del parts[0]
+        param_num += 1
+
+    # named or numbered parameters
+    while len(parts) > 0:
+        equalsign_pos = parts[0].find(u'=')
+        if equalsign_pos < 0: raise ValueError(u'Anonymous parameter after named parameter.')
+        key, sep, value = parts[0].partition(u'=')
+        key = key.strip()
+        if len(key) == 0: raise ValueError(u'Empty key.')
+        if params.has_key(key): raise ValueError(u'Duplicate key: "{0}"'.format(key))
+        params[key] = value.strip()
+        del parts[0]
+
+    return template_title, params
+
+
+def create_template(template_title, anonym_params=[], named_param_keys=[], named_param_values=[], as_table=False):
+    """Formats a MediaWiki template.
+    :param template_title: Unicode string with the template name
+    :param anonym_params: list with parameters without keys
+    :param named_param_keys: list with keys of named parameters
+    :param named_param_values: list with values of named parameters, corresponding to named_param_keys.
+    :return: unicode template"""
+    pipe_char, equal_char, end_char = (u'\n| ', u' = ', u'\n}}') if as_table else (u'|', u'=', u'}}')
+    parts = [u"{{" + template_title]
+    parts += anonym_params
+    if as_table: max_key_len = max([len(k) for k in named_param_keys])
+    for i in xrange(len(named_param_keys)):
+        key = named_param_keys[i]
+        if as_table: key = key.ljust(max_key_len)
+        parts.append(key + equal_char + named_param_values[i])
+    return pipe_char.join(parts) + end_char
 
index 37f445db85fecc40cd100e6ac01d771810836dc1..8b0f5dc81b07946a5a7e723f9b5434c1cf2c2ba7 100644 (file)
@@ -10,35 +10,27 @@ import wrpylib.wrvalidators
 
 
 def _conv(fnct, value, fieldname):
-    "Like one of the to_xxx functions (e.g. to_bool), but adds the field name to the error message"
+    """Internal function.
+    Like one of the to_xxx functions (e.g. to_bool), but adds the field name to the error message"""
     try: return fnct(value)
-    except formencode.Invalid, e: raise formencode.Invalid(u"Conversion error in field '%s': %s" % (fieldname, unicode_e(e)), e.value, e.state)
+    except formencode.Invalid as e: raise formencode.Invalid(u"Conversion error in field '%s': %s" % (fieldname, unicode_e(e)), e.value, e.state)
 
 
-def rodelbahnbox_to_sledrun(wikitext, sledrun):
+def rodelbahnbox_to_sledrun(wikitext, sledrun=None):
     """Converts a sledrun wiki page containing the {{Rodelbahnbox}}
-    to a sledrun. sledrun may be an instance of WrSledrunCache.
-    Raises a formencode.Invalid exception if the format is not OK.
-    :return: (start, end) tuple of the Rodelbahnbox."""
-    # Match Rodelbahnbox
-    regexp = re.compile(u"\{\{(Rodelbahnbox[^\}]*)\}\}", re.DOTALL)
-    match = regexp.search(wikitext)
-    if not match:
-        raise formencode.Invalid(u"Rodelbahnbox nicht gefunden", wikitext, None)
-    box = match.group(1)
+    to a sledrun. sledrun may be an instance of WrSledrunCache or an "empty" class (object()) (default).
+    Raises a formencode.Invalid exception if the format is not OK or the Rodelbahnbox is not found.
+    :return: (start, end, sledrun) tuple of the Rodelbahnbox."""
+    if sledrun is None: sledrun = object()
+
+    # match Rodelbahnbox
+    start, end = wrpylib.mwmarkup.find_template(wikitext, u'Rodelbahnbox')
+    if start is None: raise formencode.Invalid(u"Rodelbahnbox nicht gefunden", wikitext, None)
+    template_name, properties = wrpylib.mwmarkup.split_template(wikitext[start:end])
     
-    # Process Rodelbahnbox
-    for property in box.split('|'):
-        property = property.strip()
-        if property == u'Rodelbahnbox': continue
-        equalsign_pos = property.find('=')
-        if equalsign_pos < 0:
-            raise formencode.Invalid(u"Die Eigenschaft '%s' hat ein unerwartetes Format." % property, wikitext, None)
-        key = property[:equalsign_pos].strip()
-        value = property[equalsign_pos+1:].strip()
-        if key in [u'Rodelbahnnummer', u'Lift']:
-            errors.append("Eigenschaft '%s' wird nicht mehr unterstuetzt, siehe %s." % (key, 'http://www.winterrodeln.org/wiki/Vorlage:Rodelbahnbox'))
-        elif key == u'Position': sledrun.position_latitude, sledrun.position_longitude = _conv(wrpylib.wrvalidators.GeoNone().to_python, value, key) # '47.583333 N 15.75 E'
+    # process properties
+    for key, value in properties.iteritems():
+        if   key == u'Position': sledrun.position_latitude, sledrun.position_longitude = _conv(wrpylib.wrvalidators.GeoNone().to_python, value, key) # '47.583333 N 15.75 E'
         elif key == u'Position oben': sledrun.top_latitude, sledrun.top_longitude = _conv(wrpylib.wrvalidators.GeoNone().to_python, value, key) # '47.583333 N 15.75 E'
         elif key == u'Höhe oben': sledrun.top_elevation = _conv(wrpylib.wrvalidators.UnsignedNone().to_python, value, key) # '2000'
         elif key == u'Position unten': sledrun.bottom_latitude, sledrun.bottom_longitude = _conv(wrpylib.wrvalidators.GeoNone().to_python, value, key) # '47.583333 N 15.75 E'
@@ -49,7 +41,7 @@ def rodelbahnbox_to_sledrun(wikitext, sledrun):
         elif key == u'Betreiber': sledrun.operator = _conv(wrpylib.wrvalidators.UnicodeNone().to_python, value, key) # 'Max Mustermann'
         elif key == u'Öffentliche Anreise': sledrun.public_transport = _conv(wrpylib.wrvalidators.GermanPublicTransport().to_python, value, key) # 'Mittelmäßig'
         elif key == u'Gehzeit': sledrun.walkup_time = _conv(wrpylib.wrvalidators.UnsignedNone().to_python, value, key) # 90
-        elif key == u'Aufstieg möglich': sledrun.walkup_separate, sledrun.walkup_separate_comment = _conv(wrpylib.wrvalidators.GermanTristateFloatComment().to_python, value, key) # 'Ja'
+        elif key == u'Aufstieg möglich': sledrun.walkup_possible = _conv(wrpylib.wrvalidators.GermanBoolNone().to_python, value, key) # 'Ja'
         elif key == u'Aufstieg getrennt': sledrun.walkup_separate, sledrun.walkup_separate_comment = _conv(wrpylib.wrvalidators.GermanTristateFloatComment().to_python, value, key) # 'Ja'
         elif key == u'Aufstiegshilfe': sledrun.lift, sledrun.lift_details = _conv(wrpylib.wrvalidators.GermanLift().to_python, value, key) # 'Gondel (unterer Teil)'
         elif key == u'Beleuchtungsanlage': sledrun.night_light, sledrun.night_light_comment = _conv(wrpylib.wrvalidators.GermanTristateFloatComment().to_python, value, key)
@@ -62,14 +54,16 @@ def rodelbahnbox_to_sledrun(wikitext, sledrun):
         elif key == u'In Übersichtskarte': sledrun.show_in_overview = _conv(wrpylib.wrvalidators.GermanBoolNone().to_python, value, key)
         elif key == u'Forumid': sledrun.forum_id = _conv(wrpylib.wrvalidators.UnsignedNeinNone().to_python, value, key)
         else: raise formencode.Invalid(u"Unbekannte Eigenschaft der Rodelbahnbox: '%s' (mit Wert '%s')" % (key, value), value, None)
-    return match.span()
-
+    return start, end, sledrun
 
 
-def sledrun_to_rodelbahnbox(sledrun):
+def sledrun_to_rodelbahnbox(sledrun, version):
     """Converts a sledrun class to the {{Rodelbahnbox}} representation.
     The sledrun class has to have properties like position_latitude, ...
-    See the table sledruncache for field (column) values."""
+    See the table sledruncache for field (column) values.
+    :param sledrun: an arbitrary class that contains the right properties
+    :param version: a string specifying the version of the rodelbahnbox zu produce.
+        Version '1.3' and '1.4' are supported."""
     keys = []
     values = []
     keys.append(u'Position')
@@ -92,6 +86,9 @@ def sledrun_to_rodelbahnbox(sledrun):
     values.append(wrpylib.wrvalidators.UnicodeNone().from_python(sledrun.operator))
     keys.append(u'Öffentliche Anreise')
     values.append(wrpylib.wrvalidators.GermanPublicTransport().from_python(sledrun.public_transport))
+    if version == '1.4':
+        keys.append(u'Aufstieg möglich')
+        values.append(wrpylib.wrvalidators.GermanBoolNone().from_python(sledrun.walkup_possible))
     keys.append(u'Gehzeit')
     values.append(wrpylib.wrvalidators.UnsignedNone().from_python(sledrun.walkup_time))
     keys.append(u'Aufstieg getrennt')