# HG changeset patch
# User cmlenz
# Date 1187868943 0
# Node ID d7b957e92ea96d4bad00cf30884f4cf50daed11c
# Parent 59fbd7586454dff68e9e1e31920434ffeece9593
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('*')}
-
+
diff --git a/genshi/template/directives.py b/genshi/template/directives.py
--- 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 @@
"""
- __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 @@