Mercurial > genshi > mirror
changeset 602:d7b957e92ea9 trunk
Add runtime optimization hints for match templates.
author | cmlenz |
---|---|
date | Thu, 23 Aug 2007 11:35:43 +0000 |
parents | 59fbd7586454 |
children | 80e346366c83 |
files | doc/xml-templates.txt examples/bench/genshi/base.html genshi/template/directives.py genshi/template/markup.py genshi/template/tests/directives.py |
diffstat | 5 files changed, 119 insertions(+), 18 deletions(-) [+] |
line wrap: on
line diff
--- a/doc/xml-templates.txt +++ b/doc/xml-templates.txt @@ -333,6 +333,43 @@ <greeting name="Dude" /> </div> +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 + + <py:match path="body" once="true"> + <body py:attrs="select('@*')"> + <div id="header">...</div> + ${select("*|text()")} + <div id="footer">...</div> + </body> + </py:match> + +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 ``<head>`` or ``<body>`` | +| | | 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 ================
--- a/examples/bench/genshi/base.html +++ b/examples/bench/genshi/base.html @@ -6,12 +6,12 @@ Hello, ${name}! </p> - <body py:match="body"> + <py:match path="body" once="true"><body> <div id="header"> <h1>${title}</h1> </div> ${select('*')} <div id="footer" /> - </body> + </body></py:match> </html>
--- a/genshi/template/directives.py +++ b/genshi/template/directives.py @@ -14,6 +14,10 @@ """Implementation of the various template directives.""" import compiler +try: + frozenset +except NameError: + from sets import ImmutableSet as frozenset from genshi.core import QName, Stream from genshi.path import Path @@ -423,24 +427,31 @@ </span> </div> """ - __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):
--- 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()
--- a/genshi/template/tests/directives.py +++ b/genshi/template/tests/directives.py @@ -842,6 +842,54 @@ </body> </html>""", str(tmpl.generate())) + def test_match_with_once_attribute(self): + tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> + <py:match path="body" once="true"><body> + <div id="wrap"> + ${select("*")} + </div> + </body></py:match> + <body> + <p>Foo</p> + </body> + <body> + <p>Bar</p> + </body> + </html>""") + self.assertEqual("""<html> + <body> + <div id="wrap"> + <p>Foo</p> + </div> + </body> + <body> + <p>Bar</p> + </body> + </html>""", str(tmpl.generate())) + + def test_match_with_recursive_attribute(self): + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> + <py:match path="elem" recursive="false"><elem> + <div class="elem"> + ${select('*')} + </div> + </elem></py:match> + <elem> + <subelem> + <elem/> + </subelem> + </elem> + </doc>""") + self.assertEqual("""<doc> + <elem> + <div class="elem"> + <subelem> + <elem/> + </subelem> + </div> + </elem> + </doc>""", str(tmpl.generate())) + # FIXME #def test_match_after_step(self): # tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/">