changeset 602:509b3a5e765e

Add runtime optimization hints for match templates.
author cmlenz
date Thu, 23 Aug 2007 11:35:43 +0000
parents 9ae986bcba9a
children ab167005a363
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/">
Copyright (C) 2012-2017 Edgewall Software