Updated desciption of function parse_wrmap.
[philipp/winterrodeln/wrpylib.git] / wrpylib / mwmarkup.py
index 79df791cefd69ebd622fedb8fa94329253dba4aa..beffbd26b8c9ea03ee8f8469f1a4fba4f0e675cc 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/python2.6
+#!/usr/bin/python2.7
 # -*- coding: iso-8859-15 -*-
 # $Id$
 # $HeadURL$
@@ -10,6 +10,7 @@ that convinced me. However, here are the links:
 * mwlib http://code.pediapress.com/wiki/wiki
 """
 import re
+import xml.etree.ElementTree
 
 
 def find_template(wikitext, template_title):
@@ -27,7 +28,8 @@ def find_template(wikitext, template_title):
     >>> print wikitext.__getslice__(*find_template(wikitext, u'Color'))
     {{Color|red|red text}}
 
-    The search is done with regular expression.
+    The search is done with regular expression. It gives wrong results when parsing a template
+    containing the characters "}}"
 
     :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).
@@ -35,15 +37,14 @@ def find_template(wikitext, template_title):
         (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)
+    match = re.search(u"\{\{" + template_title + "\s*(\|[^\}]*)?\}\}", 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'}.
+    (template_title, parameters) where parameters is a Python dictionary {u'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.
@@ -97,7 +98,164 @@ def create_template(template_title, anonym_params=[], named_param_keys=[], named
         as_table_keylen = 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(as_table_keylen)
-        parts.append(key + equal_char + named_param_values[i])
+        if as_table: 
+            key = key.ljust(as_table_keylen)
+            parts.append((key + equal_char + named_param_values[i]).rstrip())
+        else:
+            parts.append(key + equal_char + named_param_values[i])
     return pipe_char.join(parts) + end_char
 
+
+def find_tag(wikitext, tagname, pos=0):
+    """Returns the tuple (start, end) of the first occurence of the tag '<tag ...>...</tag>'
+    or '<tag ... />'.
+    (None, None) is returned if the tag is not found.
+    If you are sure that the wikitext contains the tag, the tag could be extracted like follows:
+
+    >>> wikitext = u'This is a <tag>mytag</tag> tag.'
+    >>> start, end = find_template(wikitext, u'tag')
+    >>> print wikitext[start:end]
+    <tag>mytag</tag>
+
+    :param wikitext: The text (preferalbe unicode) that has the template in it.
+    :param tagname: Name of the tag, e.g. u'tag' for <tag>.
+    :param pos: position within wikitext to start searching the tag.
+    :return:
+        (start, content, endtag, end). start is the position of '<' of the tag,
+        content is the beginning of the content (after '>'), enttag is the
+        beginning of the end tag ('</') and end is one position after the end tag.
+        For single tags, (start, None, None, end) is returned.
+        If the tag is not found (or only the start tag is present,
+        (None, None, None, None) is returned.
+    """
+    # Find start tag
+    regexp_starttag = re.compile(u"<{0}.*?(/?)>".format(tagname), re.DOTALL)
+    match_starttag = regexp_starttag.search(wikitext, pos)
+    if match_starttag is None:
+        return None, None, None, None
+
+    # does the tag have content?
+    if len(match_starttag.group(1)) == 1: # group(1) is either '' or '/'.
+        # single tag
+        return match_starttag.start(), None, None, match_starttag.end()
+
+    # tag with content
+    regexp_endtag = re.compile(u'</{0}>'.format(tagname), re.DOTALL)
+    match_endtag = regexp_endtag.search(wikitext, match_starttag.end())
+    if match_endtag is None:
+        # No closing tag - error in wikitext
+        return None, None, None, None
+    return match_starttag.start(), match_starttag.end(), match_endtag.start(), match_endtag.end()
+
+
+def parse_googlemap(wikitext, detail=False):
+    """Parses the (unicode) u'<googlemap ...>content</googlemap>' of the googlemap extension
+    out of a page. If wikitext does not contain the googlemap extension text None is returned.
+    If the googlemap contains invalid formatted lines, a RuntimeError is raised.
+
+    :param wikitext: wikitext containing the template. Example:
+    :param detail: bool. If True, start and end position of <googlemap>...</googlemap> is
+        returned additionally.
+
+    wikitext = '''
+    <googlemap version="0.9" lat="47.113291" lon="11.272337" zoom="15">
+    (Parkplatz)47.114958,11.266026
+    Parkplatz
+    
+    (Gasthaus) 47.114715, 11.266262, Alt Bärnbad (Gasthaus)
+    6#FF014E9A
+    47.114715,11.266262
+    47.114135,11.268381
+    47.113421,11.269322
+    47.11277,11.269979
+    47.112408,11.271119
+    </googlemap>
+    '''
+    :returns: the tuple (center, zoom, coords, paths).
+        center is the tuple (lon, lat) of the google maps or (None, None) if not provided
+        zoom is the google zoom level as integer or None if not provided
+        coords is a list of (lon, lat, symbol, title) tuples.
+        paths is a list of (style, coords) tuples.
+        coords is again a list of (lot, lat, symbol, title) tuples.
+        If detail is True, (center, zoom, coords, paths, start, end) is returned."""
+
+    def is_coord(line):
+        """Returns True if the line contains a coordinate."""
+        match = re.search('[0-9]{1,2}\.[0-9]+, ?[0-9]{1,2}\.[0-9]+', line)
+        return not match is None
+
+    def is_path(line):
+        """Returns True if the line contains a path style definition."""
+        match = re.match('[0-9]#[0-9a-fA-F]{8}', line)
+        return not match is None
+
+    def parse_coord(line):
+        """Returns (lon, lat, symbol, title). If symbol or text is not present, None is returned."""
+        match = re.match(u'\(([^)]+)\) ?([0-9]{1,2}\.[0-9]+), ?([0-9]{1,2}\.[0-9]+), ?(.*)', line)
+        if not match is None: return (float(match.group(3)), float(match.group(2)), match.group(1), match.group(4))
+        match = re.match(u'\(([^)]+)\) ?([0-9]{1,2}\.[0-9]+), ?([0-9]{1,2}\.[0-9]+)', line)
+        if not match is None: return (float(match.group(3)), float(match.group(2)), match.group(1), None)
+        match = re.match(u'([0-9]{1,2}\.[0-9]+), ?([0-9]{1,2}\.[0-9]+), ?(.*)', line)
+        if not match is None: return (float(match.group(2)), float(match.group(1)), None, match.group(3))
+        match = re.match(u'([0-9]{1,2}\.[0-9]+), ?([0-9]{1,2}\.[0-9]+)', line)
+        if not match is None: return (float(match.group(2)), float(match.group(1)), None, None)
+        return RuntimeError(u'Could not parse line ' + line)
+
+    start, content, endtag, end = find_tag(wikitext, 'googlemap')
+    if content is None:
+        return None
+    gm = xml.etree.ElementTree.XML((wikitext[start:content]+wikitext[endtag:end]).encode('UTF8'))
+    zoom = gm.get('zoom')
+    lon = gm.get('lon')
+    lat = gm.get('lat')
+    if not zoom is None: zoom = int(zoom)
+    if not lon is None: lon = float(lon)
+    if not lat is None: lat = float(lat)
+    center = (lon, lat)
+
+    coords = []
+    paths = []
+    lines = wikitext[content:endtag].split("\n")
+    i = 0
+    while i < len(lines):
+        line = lines[i].strip()
+        i += 1
+
+        # Skip whitespace
+        if len(line) == 0: continue
+
+        # Handle a path
+        if is_path(line):
+            match = re.match(u'([0-9]#[0-9a-fA-F]{8})', line)
+            style =  match.group(1)
+            local_coords = []
+            while i < len(lines):
+                line = lines[i].strip()
+                i += 1
+                if is_path(line):
+                    i -= 1
+                    break
+                if is_coord(line):
+                    lon, lat, symbol, title = parse_coord(line)
+                    local_coords.append((lon, lat, symbol, title))
+            paths.append((style, local_coords))
+            continue
+
+        # Handle a coordinate
+        if is_coord(line):
+            lon, lat, symbol, title = parse_coord(line)
+            while i < len(lines):
+                line = lines[i].strip()
+                i += 1
+                if is_path(line) or is_coord(line):
+                    i -= 1
+                    break
+                if len(line) > 0 and title is None: title = line
+            coords.append((lon, lat, symbol, title))
+            continue
+
+        raise RuntimeError(u'Unknown line syntax: ' + line)
+    if detail:
+        return (center, zoom, coords, paths, start, end)
+    return (center, zoom, coords, paths)
+