Index: genshi/path.py
===================================================================
--- genshi/path.py	(revision 466)
+++ genshi/path.py	(working copy)
@@ -36,6 +36,7 @@
 
 from genshi.core import Stream, Attrs, Namespace, QName
 from genshi.core import START, END, TEXT, COMMENT, PI
+from genshi.template.core import EXPR
 
 __all__ = ['Path', 'PathSyntaxError']
 
@@ -620,7 +621,7 @@
     """Node test that matches any text event."""
     __slots__ = []
     def __call__(self, kind, data, pos, namespaces, variables):
-        return kind is TEXT
+        return kind is TEXT or kind is EXPR
     def __repr__(self):
         return 'text()'
 
Index: genshi/template/tests/markup.py
===================================================================
--- genshi/template/tests/markup.py	(revision 466)
+++ genshi/template/tests/markup.py	(working copy)
@@ -276,6 +276,106 @@
         finally:
             shutil.rmtree(dirname)
 
+    def test_smatch_expr_text(self):
+        dirname = tempfile.mkdtemp(suffix='genshi_test')
+        try:
+            file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w')
+            try:
+                file1.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude"
+                                     xmlns:py="http://genshi.edgewall.org/">
+                    <span py:smatch="greeting">
+                        Hello ${select('./text()')}
+                    </span>
+                    <span>Some intervening text.</span>
+                    <greeting>${'Dude'}</greeting>
+                </html>""")
+            finally:
+                file1.close()
+
+            loader = TemplateLoader([dirname])
+            tmpl = loader.load('tmpl1.html')
+            self.assertEqual("""<html>
+                    <span>Some intervening text.</span>
+                    <span>
+                        Hello Dude
+                    </span>
+                </html>""", tmpl.generate().render())
+        finally:
+            shutil.rmtree(dirname)
+
+    def test_basic_smatch(self):
+        dirname = tempfile.mkdtemp(suffix='genshi_test')
+        try:
+            file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w')
+            try:
+                file1.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude"
+                                     xmlns:py="http://genshi.edgewall.org/">
+                    <span py:smatch="greeting">
+                        Hello ${select('@name')}
+                    </span>
+                    <span>Some intervening text.</span>
+                    <greeting name="Dude" />
+                </html>""")
+            finally:
+                file1.close()
+
+            loader = TemplateLoader([dirname])
+            tmpl = loader.load('tmpl1.html')
+            self.assertEqual("""<html>
+                    <span>Some intervening text.</span>
+                    <span>
+                        Hello Dude
+                    </span>
+                </html>""", tmpl.generate().render())
+        finally:
+            shutil.rmtree(dirname)
+
+    def test_smatch_with_directives(self):
+        dirname = tempfile.mkdtemp(suffix='genshi_test')
+        try:
+            file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w')
+            try:
+                file1.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude"
+                                     xmlns:py="http://genshi.edgewall.org/">
+                    <span py:smatch="//*[@name]" py:content="'Hello ' + str(select('@name'))" py:attrs="{'other':'greets'}" />
+                    <span>Some intervening text.</span>
+                    <greeting name="Dude" py:content="foo" py:attrs="{'style':'foo'}"/>
+                </html>""")
+            finally:
+                file1.close()
+
+            loader = TemplateLoader([dirname])
+            tmpl = loader.load('tmpl1.html')
+            self.assertEqual("""<html>
+                    <span>Some intervening text.</span>
+                    <span other="greets">Hello Dude</span>
+                </html>""", tmpl.generate().render())
+        finally:
+            shutil.rmtree(dirname)
+
+    def test_smatch_with_replace(self):
+        dirname = tempfile.mkdtemp(suffix='genshi_test')
+        try:
+            file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w')
+            try:
+                file1.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude"
+                                     xmlns:py="http://genshi.edgewall.org/">
+                    <span py:smatch="//*[@name]" py:replace="XML('&lt;span&gt;And now for something completely different.&lt;/span&gt;')" />
+                    <span>Some intervening text.</span>
+                    <greeting name="Dude" />
+                </html>""")
+            finally:
+                file1.close()
+
+            loader = TemplateLoader([dirname])
+            tmpl = loader.load('tmpl1.html')
+            self.assertEqual("""<html>
+                    <span>Some intervening text.</span>
+                    <span>And now for something completely different.</span>
+                </html>""", tmpl.generate(XML=XML).render())
+        finally:
+            shutil.rmtree(dirname)
+
     def test_fallback_when_include_found(self):
         dirname = tempfile.mkdtemp(suffix='genshi_test')
         try:
Index: genshi/template/markup.py
===================================================================
--- genshi/template/markup.py	(revision 466)
+++ genshi/template/markup.py	(working copy)
@@ -42,6 +42,7 @@
 
     directives = [('def', DefDirective),
                   ('match', MatchDirective),
+                  ('smatch', SmatchDirective),
                   ('when', WhenDirective),
                   ('otherwise', OtherwiseDirective),
                   ('for', ForDirective),
@@ -185,7 +186,7 @@
         return streams[0]
 
     def _prepare(self, stream):
-        for kind, data, pos in Template._prepare(self, stream):
+        for kind, data, pos in self._smatch(Template._prepare(self, stream)):
             if kind is INCLUDE:
                 data = data[0], list(self._prepare(data[1]))
             yield kind, data, pos
@@ -290,5 +291,90 @@
             else: # no matches
                 yield event
 
+    def _smatch(self, stream, smatch_templates=None, smatch_directives=[]):
+        """Internal stream filter that looks for and records any matches to py:smatch
+           directives.
+        """
+        stream = iter(stream)
+        if smatch_templates is None:
+            smatch_templates = []
 
+        tail = []
+        def _strip(stream):
+            depth = 1
+            while 1:
+                event = stream.next()
+                if event[0] is START:
+                    depth += 1
+                elif event[0] is END:
+                    depth -= 1
+                if depth > 0:
+                    yield event
+                else:
+                    tail[:] = [event]
+                    break
+
+        for kind, data, pos in stream:
+            event = (kind, data, pos)
+
+            if kind is SUB:
+                # look for SmatchDirectives
+                directives, substream = data
+                for idx, mt in enumerate(directives):
+                    if not mt.__class__ is SmatchDirective:
+                        continue
+                    # process and remove SmatchDirective
+                    smatch_templates.append((mt, directives[:idx] + directives[idx+1:], substream))
+                    break
+                else:
+                    # Process substream looking for matches and new match templates
+                    newsubstream = list(self._smatch(substream, smatch_templates, directives))
+                    if substream[0][0] != newsubstream[0][0]:
+                        # there's been a match at the top, directives will have been combined
+                        # into the new sub.
+                        for event in newsubstream:
+                            yield event
+                    else:
+                        yield (SUB, (directives, newsubstream), pos)
+
+            elif not kind is START and not kind is END:
+                # We (currently) only care about start and end events for matching
+                # We might care about namespace events in the future, though
+                yield event
+
+            else:
+                # look for matches
+                for idx, (mt, mt_directives, mt_substream) in enumerate(smatch_templates):
+                    if mt.test(event, mt.namespaces, None) is True:
+                        # matched!
+
+                        # Let the remaining match templates know about the event so
+                        # they get a chance to update their internal state
+                        for othermt in [sm[0] for sm in smatch_templates[idx+1:]]:
+                            othermt.test(event, othermt.namespaces, None, updateonly=True)
+
+                        # Consume and store all events until an end event
+                        # corresponding to this start event is encountered
+                        content = chain([event],
+                                        self._smatch(_strip(stream),smatch_templates),
+                                        tail)
+                        content = list(content)
+
+                        # Let all match templates know about end event
+                        for othermt in [sm[0] for sm in smatch_templates]:
+                            othermt.test(tail[0], othermt.namespaces, None, updateonly=True)
+
+                        # Create the select substream for accessing parts of
+                        # the matched element / substream.
+                        select_substream = Stream(content)
+
+                        staticmatch = StaticMatchDirective(mt_substream,mt_directives,select_substream,mt.namespaces)
+                        yield (SUB, (smatch_directives+[staticmatch], select_substream), pos) # apply staticmatch last
+
+                        break
+                else:
+                    yield (kind, data, pos)
+
+
+
 INCLUDE = MarkupTemplate.INCLUDE
Index: genshi/template/directives.py
===================================================================
--- genshi/template/directives.py	(revision 466)
+++ genshi/template/directives.py	(working copy)
@@ -23,8 +23,9 @@
 
 __all__ = ['AttrsDirective', 'ChooseDirective', 'ContentDirective',
            'DefDirective', 'ForDirective', 'IfDirective', 'MatchDirective',
-           'OtherwiseDirective', 'ReplaceDirective', 'StripDirective',
-           'WhenDirective', 'WithDirective']
+           'OtherwiseDirective', 'ReplaceDirective',
+           'SmatchDirective','StaticMatchDirective',
+           'StripDirective','WhenDirective', 'WithDirective']
 
 
 class DirectiveMeta(type):
@@ -447,6 +448,64 @@
     attach = classmethod(attach)
 
 
+class SmatchDirective(Directive):
+    """Implementation of the `py:smatch` template directive.
+    
+       The syntax for using py:smatch is the same as that for py:match.
+       The py:smatch XPath query is performed immediately after the
+       templated is parsed whereas the py:match query is performed when
+       the template is generated/rendered. In both cases the replacement
+       markup is only created on template generation.
+
+       py:smatch avoids performing XPath matches when the template is
+       generated at the cost of not being able to access the context in
+       the XPath expression or match XML not present in the template file
+       (for example, attributes generated by py:attr).
+    """
+    __slots__ = ['path','namespaces','test']
+
+    ATTRIBUTE = 'path'
+
+    def __init__(self, value, namespaces=None, filename=None, lineno=-1,
+                 offset=-1):
+        Directive.__init__(self, None, namespaces, filename, lineno, offset)
+        self.path = Path(value, filename, lineno)
+        self.test = self.path.test(ignore_context=True)
+        self.namespaces = namespaces or {}
+
+    def __call__(self, stream, ctxt, directives):
+        raise RuntimeError("All SmatchDirectives should have been " \
+               "removed from the stream by the time directives are called.")
+
+    def __repr__(self):
+        return '<%s "%s">' % (self.__class__.__name__, self.path.source)
+
+
+class StaticMatchDirective(object):
+    """After a template has been parsed, a filter is run to replace
+       all SmatchDirectives with StaticMatchDirectives at the point
+       where there XPath expressions match the stream.
+       
+       This is an unregistered directive.
+    """
+    __slots__ = ['smatch_substream','smatch_directives','select_substream','namespaces']
+
+    def __init__(self, smatch_substream, smatch_directives, select_substream, namespaces):
+        self.select_substream = select_substream
+        self.smatch_substream = smatch_substream
+        self.smatch_directives = smatch_directives
+        self.namespaces = namespaces
+
+    def __call__(self, stream, ctxt, directives):
+        def select(path):
+            return self.select_substream.select(path, self.namespaces, ctxt)
+
+        ctxt.push(dict(select=select))
+        for event in _apply_directives(self.smatch_substream, ctxt, self.smatch_directives):
+            yield event
+        ctxt.pop()
+
+
 class StripDirective(Directive):
     """Implementation of the `py:strip` template directive.
     
