changeset 2576:f4c371b00fec

refactor the ID generation/writing code
author Johannes Berg <johannes AT sipsolutions DOT net>
date Fri, 27 Jul 2007 11:37:37 +0200
parents d1ec881c6f86
children 32bba35902b9
files MoinMoin/formatter/__init__.py MoinMoin/formatter/_tests/test_formatter.py MoinMoin/formatter/text_html.py MoinMoin/macro/TableOfContents.py MoinMoin/parser/text_moin_wiki.py MoinMoin/wikiutil.py
diffstat 6 files changed, 107 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/formatter/__init__.py	Fri Jul 27 11:37:15 2007 +0200
+++ b/MoinMoin/formatter/__init__.py	Fri Jul 27 11:37:37 2007 +0200
@@ -364,9 +364,40 @@
     def comment(self, text, **kw):
         return ""
 
+    # ID handling #################################################
+
+    def sanitize_to_id(self, text):
+        '''
+        Take 'text' and return something that is a valid ID
+        for this formatter.
+        The default returns the first non-space character of the string.
+
+        Because of the way this is used, it must be idempotent,
+        i.e. calling it on an already sanitized id must yield the
+        original id.
+        '''
+        return text.strip()[:1]
+
     def make_id_unique(self, id):
-        id = self.request.make_unique_id(id, self.request.include_id)
-        if self.request.include_id:
-            id = '%s.%s' % (
-                wikiutil.anchor_name_from_text(self.request.include_id), id)
+        '''
+        Take an ID and make it unique in the current namespace.
+        '''
+        ns = self.request.include_id
+        if not ns is None:
+            ns = self.sanitize_to_id(ns)
+        id = self.sanitize_to_id(id)
+        id = self.request.make_unique_id(id, ns)
         return id
+
+    def qualify_id(self, id):
+        '''
+        Take an ID and return a string that is qualified by
+        the current namespace; this default implementation
+        is suitable if the dot ('.') is valid in IDs for your
+        formatter.
+        '''
+        ns = self.request.include_id
+        if not ns is None:
+            ns = self.sanitize_to_id(ns)
+            return '%s.%s' % (ns, id)
+        return id
--- a/MoinMoin/formatter/_tests/test_formatter.py	Fri Jul 27 11:37:15 2007 +0200
+++ b/MoinMoin/formatter/_tests/test_formatter.py	Fri Jul 27 11:37:37 2007 +0200
@@ -77,6 +77,47 @@
         return self.request.redirectedOutput(page.send_page, content_only=1)
 
 
+class TestIdIdempotency:
+    def test_sanitize_to_id_idempotent(self):
+        def _verify(formatter, id):
+            origid = formatter.sanitize_to_id(id)
+            id = origid
+            for i in xrange(3):
+                id = formatter.sanitize_to_id(id)
+                assert id == origid
+
+        formatters = wikiutil.getPlugins("formatter", self.request.cfg)
+
+        testids = [
+            r"tho/zeequeen&angu\za",
+            r"quuirahz\iphohsaij,i",
+            r"ashuifa+it[ohchieque",
+            r"ohyie-lakoo`duaghaib",
+            r"eixaepumuqu[ie\ba|eh",
+            r"theegieque;zahmeitie",
+            r"pahcooje&rahkeiz$oez",
+            r"ohjeeng*iequao%fai?p",
+            r"ahfoodahmepooquepee;",
+            r"ubed_aex;ohwebeixah%",
+            r"eitiekicaejuelae=g^u",
+            r"",
+            r'  ',
+            r'--123',
+            r'__$$',
+            r'@@',
+            u'\xf6\xf6llasdf\xe4',
+        ]
+
+        for f_name in formatters:
+            try:
+                formatter = wikiutil.importPlugin(self.request.cfg, "formatter",
+                                                  f_name, "Formatter")
+                f = formatter(self.request)
+                for id in testids:
+                    yield _verify, f, id
+            except wikiutil.PluginAttributeError:
+                pass
+
 coverage_modules = ['MoinMoin.formatter',
                     'MoinMoin.formatter.text_html',
                     'MoinMoin.formatter.text_gedit',
--- a/MoinMoin/formatter/text_html.py	Fri Jul 27 11:37:15 2007 +0200
+++ b/MoinMoin/formatter/text_html.py	Fri Jul 27 11:37:37 2007 +0200
@@ -338,11 +338,13 @@
         id = None
         if not is_unique:
             if attr and 'id' in attr:
-                attr['id'] = self.make_id_unique(attr['id'])
-                id = attr['id']
+                id = self.make_id_unique(attr['id'])
+                id = self.qualify_id(id)
+                attr['id'] = id
             if 'id' in kw:
-                kw['id'] = self.make_id_unique(kw['id'])
-                id = kw['id']
+                id = self.make_id_unique(kw['id'])
+                id = self.qualify_id(id)
+                kw['id'] = id
         else:
             if attr and 'id' in attr:
                 id = attr['id']
@@ -561,7 +563,8 @@
         # line-numbered code sections (from line_achordef() method).
         #return '<a id="%s"></a>' % (id, ) # do not use - this breaks PRE sections for IE
         id = self.make_id_unique(id)
-        return '<span class="anchor" id="%s"></span>' % wikiutil.escape(id, 1)
+        id = self.qualify_id(id)
+        return '<span class="anchor" id="%s"></span>' % id
 
     def line_anchordef(self, lineno):
         if line_anchors:
@@ -585,11 +588,8 @@
         """
         attrs = self._langAttr()
         if name:
-            if self.request.include_id:
-                attrs['href'] = '#%s.%s' % (
-                    wikiutil.anchor_name_from_text(self.request.include_id), name)
-            else:
-                attrs['href'] = '#%s' % name
+            name = self.sanitize_to_id(name)
+            attrs['href'] = '#' + self.qualify_id(name)
         if 'href' in kw:
             del kw['href']
         if on:
@@ -915,7 +915,7 @@
         must be unique within the document.  The show, start, and step are
         used for line numbering.
 
-B        Note this is not like most formatter methods, it can not take any
+        Note this is not like most formatter methods, it can not take any
         extra keyword arguments.
 
         Call once with on=1 to start the region, and a second time
@@ -924,7 +924,9 @@
         _ = self.request.getText
         res = []
         if on:
-            ci = self.make_id_unique('CA-%s' % code_id)
+            code_id = self.sanitize_to_id('CA-%s' % code_id)
+            ci = self.qualify_id(self.make_id_unique(code_id))
+
             # Open a code area
             self._in_code_area = 1
             self._in_code_line = 0
@@ -1361,3 +1363,5 @@
             return self._open(tag, **kw)
         return self._close(tag)
 
+    def sanitize_to_id(self, text):
+        return wikiutil.anchor_name_from_text(text)
--- a/MoinMoin/macro/TableOfContents.py	Fri Jul 27 11:37:15 2007 +0200
+++ b/MoinMoin/macro/TableOfContents.py	Fri Jul 27 11:37:37 2007 +0200
@@ -57,9 +57,9 @@
 
     def heading(self, on, depth, **kw):
         id = kw.get('id', None)
+        self.in_heading = on
         if not id is None:
-            id = self.request.make_unique_id(id, self.request.include_id)
-        self.in_heading = on
+            id = self.request._tocfm_orig_formatter.make_id_unique(id)
         if on:
             self.collected_headings.append([depth, id, u''])
         return ''
@@ -143,7 +143,9 @@
     pname = macro.formatter.page.page_name
 
     macro.request.push_unique_ids()
+
     macro.request._tocfm_collected_headings = []
+    macro.request._tocfm_orig_formatter = macro.formatter
 
     tocfm = TOCFormatter(macro.request)
     p = Page(macro.request, pname, formatter=tocfm, rev=macro.request.rev)
@@ -152,8 +154,6 @@
                                             count_hit=False,
                                             omit_footnotes=True)
 
-    macro.request.pop_unique_ids()
-
     _ = macro.request.getText
 
     result = [
@@ -164,17 +164,17 @@
     ]
 
     lastlvl = 0
-    old_incl_id = macro.request.include_id
-    macro.request.include_id = None
 
     for lvl, id, txt in macro.request._tocfm_collected_headings:
         if txt is None:
             incl_id = id
             continue
-        if lvl > maxdepth or not id:
+        if lvl > maxdepth or id is None:
             continue
-        if incl_id:
-            id = '%s.%s' % (wikiutil.anchor_name_from_text(incl_id), id)
+
+        # will be reset by pop_unique_ids below
+        macro.request.include_id = incl_id
+
         need_li = lastlvl >= lvl
         while lastlvl > lvl:
             result.extend([
@@ -205,7 +205,7 @@
         result.append(macro.formatter.number_list(0))
         lastlvl -= 1
 
-    macro.request.include_id = old_incl_id
+    macro.request.pop_unique_ids()
 
     result.append(macro.formatter.div(0))
     return ''.join(result)
--- a/MoinMoin/parser/text_moin_wiki.py	Fri Jul 27 11:37:15 2007 +0200
+++ b/MoinMoin/parser/text_moin_wiki.py	Fri Jul 27 11:37:37 2007 +0200
@@ -764,11 +764,10 @@
         depth = min(5, level)
 
         title_text = h[level:-level].strip()
-        id = wikiutil.anchor_name_from_text(title_text)
 
         return ''.join([
             self._closeP(),
-            self.formatter.heading(1, depth, id=id),
+            self.formatter.heading(1, depth, id=title_text),
             self.formatter.text(title_text),
             self.formatter.heading(0, depth),
         ])
--- a/MoinMoin/wikiutil.py	Fri Jul 27 11:37:15 2007 +0200
+++ b/MoinMoin/wikiutil.py	Fri Jul 27 11:37:37 2007 +0200
@@ -2100,6 +2100,10 @@
     return lines
 
 def anchor_name_from_text(text):
+    '''
+    Generate an anchor name from the given text
+    This function generates valid HTML IDs.
+    '''
     quoted = urllib.quote_plus(text.encode('utf-7'))
     res = quoted.replace('%', '.').replace('+', '').replace('_', '')
     if not res[:1].isalpha():