# HG changeset patch
# User cmlenz
# Date 1151879943 0
# Node ID ed370ebfa7944a90ffec2e8fb5ea5214f8541ec8
# Parent 35b9e9318fb15bdbe56dc924f14a86269e62a137
Fix for #7: match templates no longer process their own output.
diff --git a/markup/template.py b/markup/template.py
--- a/markup/template.py
+++ b/markup/template.py
@@ -407,51 +407,6 @@
Hello Dude
-
- A match template can produce the same kind of element that it matched
- without entering an infinite recursion:
-
- >>> tmpl = Template('''
- ...
- ... ${select('*/text()')}
- ...
- ... Hey Joe
- ... ''')
- >>> print tmpl.generate()
-
-
- Hey Joe
-
-
-
- Match directives are applied recursively, meaning that they are also
- applied to any content they may have produced themselves:
-
- >>> tmpl = Template('''
- ...
- ...
- ... ${select('*/*')}
- ...
- ...
- ...
- ...
- ...
- ...
- ...
- ... ''')
- >>> print tmpl.generate()
-
-
-
-
-
"""
__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()
diff --git a/markup/tests/template.py b/markup/tests/template.py
--- 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('''
+
+ ${select('*/text()')}
+
+ Hey Joe
+ ''')
+ self.assertEqual("""
+
+ Hey Joe
+
+ """, 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('''
+
+
+ ${select('*/*')}
+
+
+
+
+
+
+
+ ''')
+ self.assertEqual("""
+
+
+
+ """, 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('''
+
+
+ ${select('*/*')}
+
+
+ ${select('*/*')}
+
+
+
+ Foo
+
+ ''')
+ self.assertEqual("""
+
+ Foo
+
+
+ """, 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__':