# HG changeset patch # User cmlenz # Date 1187868943 0 # Node ID 509b3a5e765ea56a2edef6898ea2b8fa100f7191 # Parent 9ae986bcba9a1dc40cae0348b33299c6e30676ce Add runtime optimization hints for match templates. diff --git a/doc/xml-templates.txt b/doc/xml-templates.txt --- a/doc/xml-templates.txt +++ b/doc/xml-templates.txt @@ -333,6 +333,43 @@ +When used this way, the ``py:match`` directive can also be annotated with a +couple of optimization hints. For example, the following informs the matching +engine that the match should only be applied once: + +.. code-block:: genshi + + + + + ${select("*|text()")} + + + + +The following optimization hints are recognized: + ++---------------+-----------+-----------------------------------------------+ +| Attribute | Default | Description | ++===============+===========+===============================================+ +| ``once`` | ``false`` | Whether the engine should stop looking for | +| | | more matching elements after the first match. | +| | | Use this on match templates that match | +| | | elements that can only occur once in the | +| | | stream, such as the ```` or ```` | +| | | elements in an HTML template, or elements | +| | | with a specific ID. | ++---------------+-----------+-----------------------------------------------+ +| ``recursive`` | ``true`` | Whether the match template should be applied | +| | | to its own output. Note that ``once`` implies | +| | | non-recursive behavior, so this attribute | +| | | only needs to be set for match templates that | +| | | don't also have ``once`` set. | ++---------------+-----------+-----------------------------------------------+ + +.. note:: The ``py:match`` optimization hints were added in the 0.5 release. In + earlier versions, the attributes have no effect. + Variable Binding ================ diff --git a/examples/bench/genshi/base.html b/examples/bench/genshi/base.html --- a/examples/bench/genshi/base.html +++ b/examples/bench/genshi/base.html @@ -6,12 +6,12 @@ Hello, ${name}!

- + ${select('*')} """ - __slots__ = ['path', 'namespaces'] + __slots__ = ['path', 'namespaces', 'hints'] - def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): + def __init__(self, value, template, hints=None, namespaces=None, + lineno=-1, offset=-1): Directive.__init__(self, None, template, namespaces, lineno, offset) self.path = Path(value, template.filepath, lineno) self.namespaces = namespaces or {} + self.hints = hints or () def attach(cls, template, stream, value, namespaces, pos): + hints = [] if type(value) is dict: + if value.get('once', '').lower() == 'true': + hints.append('match_once') + if value.get('recursive', '').lower() == 'false': + hints.append('not_recursive') value = value.get('path') - return super(MatchDirective, cls).attach(template, stream, value, - namespaces, pos) + return cls(value, template, frozenset(hints), namespaces, *pos[1:]), \ + stream attach = classmethod(attach) def __call__(self, stream, ctxt, directives): ctxt._match_templates.append((self.path.test(ignore_context=True), - self.path, list(stream), self.namespaces, - directives)) + self.path, list(stream), self.hints, + self.namespaces, directives)) return [] def __repr__(self): diff --git a/genshi/template/markup.py b/genshi/template/markup.py --- a/genshi/template/markup.py +++ b/genshi/template/markup.py @@ -261,10 +261,13 @@ yield event continue - for idx, (test, path, template, namespaces, directives) in \ - enumerate(match_templates): + for idx, (test, path, template, hints, namespaces, directives) \ + in enumerate(match_templates): if test(event, namespaces, ctxt) is True: + if 'match_once' in hints: + del match_templates[idx] + idx -= 1 # Let the remaining match templates know about the event so # they get a chance to update their internal state @@ -273,11 +276,12 @@ # Consume and store all events until an end event # corresponding to this start event is encountered - content = chain([event], - self._match(_strip(stream), ctxt, - [match_templates[idx]]), - tail) - content = list(self._include(content, ctxt)) + inner = _strip(stream) + if 'match_once' not in hints \ + and 'not_recursive' not in hints: + inner = self._match(inner, ctxt, [match_templates[idx]]) + content = list(self._include(chain([event], inner, tail), + ctxt)) for test in [mt[0] for mt in match_templates]: test(tail[0], namespaces, ctxt, updateonly=True) @@ -290,11 +294,12 @@ # Recursively process the output template = _apply_directives(template, ctxt, directives) + remaining = match_templates + if 'match_once' not in hints: + remaining = remaining[:idx] + remaining[idx + 1:] for event in self._match(self._eval(self._flatten(template, ctxt), - ctxt), ctxt, - match_templates[:idx] + - match_templates[idx + 1:]): + ctxt), ctxt, remaining): yield event ctxt.pop() diff --git a/genshi/template/tests/directives.py b/genshi/template/tests/directives.py --- a/genshi/template/tests/directives.py +++ b/genshi/template/tests/directives.py @@ -842,6 +842,54 @@ """, str(tmpl.generate())) + def test_match_with_once_attribute(self): + tmpl = MarkupTemplate(""" + +
+ ${select("*")} +
+
+ +

Foo

+ + +

Bar

+ + """) + self.assertEqual(""" + +
+

Foo

+
+ + +

Bar

+ + """, str(tmpl.generate())) + + def test_match_with_recursive_attribute(self): + tmpl = MarkupTemplate(""" + +
+ ${select('*')} +
+
+ + + + + +
""") + self.assertEqual(""" + +
+ + + +
+
+
""", str(tmpl.generate())) + # FIXME #def test_match_after_step(self): # tmpl = MarkupTemplate("""