changeset 36:ed370ebfa794 trunk

Fix for #7: match templates no longer process their own output.
author cmlenz
date Sun, 02 Jul 2006 22:39:03 +0000
parents 35b9e9318fb1
children 37557b8fb925
files markup/template.py markup/tests/template.py
diffstat 2 files changed, 91 insertions(+), 55 deletions(-) [+]
line wrap: on
line diff
--- a/markup/template.py
+++ b/markup/template.py
@@ -407,51 +407,6 @@
         Hello Dude
       </span>
     </div>
-    
-    A match template can produce the same kind of element that it matched
-    without entering an infinite recursion:
-    
-    >>> tmpl = Template('''<doc xmlns:py="http://purl.org/kid/ns#">
-    ...   <elem py:match="elem">
-    ...     <div class="elem">${select('*/text()')}</div>
-    ...   </elem>
-    ...   <elem>Hey Joe</elem>
-    ... </doc>''')
-    >>> print tmpl.generate()
-    <doc>
-      <elem>
-        <div class="elem">Hey Joe</div>
-      </elem>
-    </doc>
-    
-    Match directives are applied recursively, meaning that they are also
-    applied to any content they may have produced themselves:
-    
-    >>> tmpl = Template('''<doc xmlns:py="http://purl.org/kid/ns#">
-    ...   <elem py:match="elem">
-    ...     <div class="elem">
-    ...       ${select('*/*')}
-    ...     </div>
-    ...   </elem>
-    ...   <elem>
-    ...     <subelem>
-    ...       <elem/>
-    ...     </subelem>
-    ...   </elem>
-    ... </doc>''')
-    >>> print tmpl.generate()
-    <doc>
-      <elem>
-        <div class="elem">
-          <subelem>
-          <elem>
-        <div class="elem">
-        </div>
-      </elem>
-        </subelem>
-        </div>
-      </elem>
-    </doc>
     """
     __slots__ = ['path', 'stream']
 
@@ -798,10 +753,13 @@
             raise TemplateSyntaxError(err, self.filename, pos[1],
                                       pos[2] + (err.offset or 0))
 
-    def _match(self, stream, ctxt=None):
+    def _match(self, stream, ctxt=None, match_templates=None):
         """Internal stream filter that applies any defined match templates
         to the stream.
         """
+        if match_templates is None:
+            match_templates = ctxt._match_templates
+
         for kind, data, pos in stream:
 
             # We (currently) only care about start and end events for matching
@@ -810,12 +768,7 @@
                 yield kind, data, pos
                 continue
 
-            for idx, (test, path, template) in enumerate(ctxt._match_templates):
-                if (kind, data, pos) in template[::len(template)]:
-                    # This is the event this match template produced itself, so
-                    # matching it again would result in an infinite loop 
-                    continue
-
+            for idx, (test, path, template) in enumerate(match_templates):
                 result = test(kind, data, pos)
 
                 if result:
@@ -835,10 +788,12 @@
                         test(*event)
 
                     content = list(self._flatten(content, ctxt))
-
                     ctxt.push(select=lambda path: Stream(content).select(path))
-                    for event in self._match(self._eval(iter(template), ctxt),
-                                             ctxt):
+
+                    template = self._match(self._eval(iter(template), ctxt),
+                                           ctxt, match_templates[:idx] +
+                                           match_templates[idx + 1:])
+                    for event in template:
                         yield event
                     ctxt.pop()
 
--- a/markup/tests/template.py
+++ b/markup/tests/template.py
@@ -20,7 +20,87 @@
                             TemplateSyntaxError
 
 
+class MatchDirectiveTestCase(unittest.TestCase):
+    """Tests for the `py:match` template directive."""
+
+    def test_without_strip(self):
+        """
+        Verify that a match template can produce the same kind of element that
+        it matched without entering an infinite recursion.
+        """
+        tmpl = Template('''<doc xmlns:py="http://purl.org/kid/ns#">
+          <elem py:match="elem">
+            <div class="elem">${select('*/text()')}</div>
+          </elem>
+          <elem>Hey Joe</elem>
+        </doc>''')
+        self.assertEqual("""<doc>
+          <elem>
+            <div class="elem">Hey Joe</div>
+          </elem>
+        </doc>""", str(tmpl.generate()))
+
+    def test_recursive_match_1(self):
+        """
+        Match directives are applied recursively, meaning that they are also
+        applied to any content they may have produced themselves:
+        """
+        tmpl = Template('''<doc xmlns:py="http://purl.org/kid/ns#">
+          <elem py:match="elem">
+            <div class="elem">
+              ${select('*/*')}
+            </div>
+          </elem>
+          <elem>
+            <subelem>
+              <elem/>
+            </subelem>
+          </elem>
+        </doc>''')
+        self.assertEqual("""<doc>
+          <elem>
+            <div class="elem">
+              <subelem>
+              <elem>
+            <div class="elem">
+            </div>
+          </elem>
+            </subelem>
+            </div>
+          </elem>
+        </doc>""", str(tmpl.generate()))
+
+    def test_recursive_match_2(self):
+        """
+        When two or more match templates match the same element and also
+        themselves output the element they match, avoiding recursion is even
+        more complex, but should work.
+        """
+        tmpl = Template('''<html xmlns:py="http://purl.org/kid/ns#">
+          <body py:match="body">
+            <div id="header"/>
+            ${select('*/*')}
+          </body>
+          <body py:match="body">
+            ${select('*/*')}
+            <div id="footer"/>
+          </body>
+          <body>
+            <h1>Foo</h1>
+          </body>
+        </html>''')
+        self.assertEqual("""<html>
+          <body>
+            <div id="header"/><h1>Foo</h1>
+            <div id="footer"/>
+          </body>
+        </html>""", str(tmpl.generate()))
+
+
 class TemplateTestCase(unittest.TestCase):
+    """Tests for basic template processing, expression evaluation and error
+    reporting.
+    """
 
     def test_interpolate_string(self):
         parts = list(Template._interpolate('bla'))
@@ -105,6 +185,7 @@
     suite = unittest.TestSuite()
     suite.addTest(doctest.DocTestSuite(Template.__module__))
     suite.addTest(unittest.makeSuite(TemplateTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(MatchDirectiveTestCase, 'test'))
     return suite
 
 if __name__ == '__main__':
Copyright (C) 2012-2017 Edgewall Software