Edgewall Software

Ticket #85: pysmatch.2.diff

File pysmatch.2.diff, 13.2 KB (added by hodgestar+genshi@…, 5 years ago)

Patch against  http://svn.edgewall.org/repos/genshi/trunk r466 which implements a py:smatch which works with subprograms.

  • genshi/path.py

     
    3636 
    3737from genshi.core import Stream, Attrs, Namespace, QName 
    3838from genshi.core import START, END, TEXT, COMMENT, PI 
     39from genshi.template.core import EXPR 
    3940 
    4041__all__ = ['Path', 'PathSyntaxError'] 
    4142 
     
    620621    """Node test that matches any text event.""" 
    621622    __slots__ = [] 
    622623    def __call__(self, kind, data, pos, namespaces, variables): 
    623         return kind is TEXT 
     624        return kind is TEXT or kind is EXPR 
    624625    def __repr__(self): 
    625626        return 'text()' 
    626627 
  • genshi/template/tests/markup.py

     
    276276        finally: 
    277277            shutil.rmtree(dirname) 
    278278 
     279    def test_smatch_expr_text(self): 
     280        dirname = tempfile.mkdtemp(suffix='genshi_test') 
     281        try: 
     282            file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w') 
     283            try: 
     284                file1.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude" 
     285                                     xmlns:py="http://genshi.edgewall.org/"> 
     286                    <span py:smatch="greeting"> 
     287                        Hello ${select('./text()')} 
     288                    </span> 
     289                    <span>Some intervening text.</span> 
     290                    <greeting>${'Dude'}</greeting> 
     291                </html>""") 
     292            finally: 
     293                file1.close() 
     294 
     295            loader = TemplateLoader([dirname]) 
     296            tmpl = loader.load('tmpl1.html') 
     297            self.assertEqual("""<html> 
     298                    <span>Some intervening text.</span> 
     299                    <span> 
     300                        Hello Dude 
     301                    </span> 
     302                </html>""", tmpl.generate().render()) 
     303        finally: 
     304            shutil.rmtree(dirname) 
     305 
     306    def test_basic_smatch(self): 
     307        dirname = tempfile.mkdtemp(suffix='genshi_test') 
     308        try: 
     309            file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w') 
     310            try: 
     311                file1.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude" 
     312                                     xmlns:py="http://genshi.edgewall.org/"> 
     313                    <span py:smatch="greeting"> 
     314                        Hello ${select('@name')} 
     315                    </span> 
     316                    <span>Some intervening text.</span> 
     317                    <greeting name="Dude" /> 
     318                </html>""") 
     319            finally: 
     320                file1.close() 
     321 
     322            loader = TemplateLoader([dirname]) 
     323            tmpl = loader.load('tmpl1.html') 
     324            self.assertEqual("""<html> 
     325                    <span>Some intervening text.</span> 
     326                    <span> 
     327                        Hello Dude 
     328                    </span> 
     329                </html>""", tmpl.generate().render()) 
     330        finally: 
     331            shutil.rmtree(dirname) 
     332 
     333    def test_smatch_with_directives(self): 
     334        dirname = tempfile.mkdtemp(suffix='genshi_test') 
     335        try: 
     336            file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w') 
     337            try: 
     338                file1.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude" 
     339                                     xmlns:py="http://genshi.edgewall.org/"> 
     340                    <span py:smatch="//*[@name]" py:content="'Hello ' + str(select('@name'))" py:attrs="{'other':'greets'}" /> 
     341                    <span>Some intervening text.</span> 
     342                    <greeting name="Dude" py:content="foo" py:attrs="{'style':'foo'}"/> 
     343                </html>""") 
     344            finally: 
     345                file1.close() 
     346 
     347            loader = TemplateLoader([dirname]) 
     348            tmpl = loader.load('tmpl1.html') 
     349            self.assertEqual("""<html> 
     350                    <span>Some intervening text.</span> 
     351                    <span other="greets">Hello Dude</span> 
     352                </html>""", tmpl.generate().render()) 
     353        finally: 
     354            shutil.rmtree(dirname) 
     355 
     356    def test_smatch_with_replace(self): 
     357        dirname = tempfile.mkdtemp(suffix='genshi_test') 
     358        try: 
     359            file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w') 
     360            try: 
     361                file1.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude" 
     362                                     xmlns:py="http://genshi.edgewall.org/"> 
     363                    <span py:smatch="//*[@name]" py:replace="XML('&lt;span&gt;And now for something completely different.&lt;/span&gt;')" /> 
     364                    <span>Some intervening text.</span> 
     365                    <greeting name="Dude" /> 
     366                </html>""") 
     367            finally: 
     368                file1.close() 
     369 
     370            loader = TemplateLoader([dirname]) 
     371            tmpl = loader.load('tmpl1.html') 
     372            self.assertEqual("""<html> 
     373                    <span>Some intervening text.</span> 
     374                    <span>And now for something completely different.</span> 
     375                </html>""", tmpl.generate(XML=XML).render()) 
     376        finally: 
     377            shutil.rmtree(dirname) 
     378 
    279379    def test_fallback_when_include_found(self): 
    280380        dirname = tempfile.mkdtemp(suffix='genshi_test') 
    281381        try: 
  • genshi/template/markup.py

     
    4242 
    4343    directives = [('def', DefDirective), 
    4444                  ('match', MatchDirective), 
     45                  ('smatch', SmatchDirective), 
    4546                  ('when', WhenDirective), 
    4647                  ('otherwise', OtherwiseDirective), 
    4748                  ('for', ForDirective), 
     
    185186        return streams[0] 
    186187 
    187188    def _prepare(self, stream): 
    188         for kind, data, pos in Template._prepare(self, stream): 
     189        for kind, data, pos in self._smatch(Template._prepare(self, stream)): 
    189190            if kind is INCLUDE: 
    190191                data = data[0], list(self._prepare(data[1])) 
    191192            yield kind, data, pos 
     
    290291            else: # no matches 
    291292                yield event 
    292293 
     294    def _smatch(self, stream, smatch_templates=None, smatch_directives=[]): 
     295        """Internal stream filter that looks for and records any matches to py:smatch 
     296           directives. 
     297        """ 
     298        stream = iter(stream) 
     299        if smatch_templates is None: 
     300            smatch_templates = [] 
    293301 
     302        tail = [] 
     303        def _strip(stream): 
     304            depth = 1 
     305            while 1: 
     306                event = stream.next() 
     307                if event[0] is START: 
     308                    depth += 1 
     309                elif event[0] is END: 
     310                    depth -= 1 
     311                if depth > 0: 
     312                    yield event 
     313                else: 
     314                    tail[:] = [event] 
     315                    break 
     316 
     317        for kind, data, pos in stream: 
     318            event = (kind, data, pos) 
     319 
     320            if kind is SUB: 
     321                # look for SmatchDirectives 
     322                directives, substream = data 
     323                for idx, mt in enumerate(directives): 
     324                    if not mt.__class__ is SmatchDirective: 
     325                        continue 
     326                    # process and remove SmatchDirective 
     327                    smatch_templates.append((mt, directives[:idx] + directives[idx+1:], substream)) 
     328                    break 
     329                else: 
     330                    # Process substream looking for matches and new match templates 
     331                    newsubstream = list(self._smatch(substream, smatch_templates, directives)) 
     332                    if substream[0][0] != newsubstream[0][0]: 
     333                        # there's been a match at the top, directives will have been combined 
     334                        # into the new sub. 
     335                        for event in newsubstream: 
     336                            yield event 
     337                    else: 
     338                        yield (SUB, (directives, newsubstream), pos) 
     339 
     340            elif not kind is START and not kind is END: 
     341                # We (currently) only care about start and end events for matching 
     342                # We might care about namespace events in the future, though 
     343                yield event 
     344 
     345            else: 
     346                # look for matches 
     347                for idx, (mt, mt_directives, mt_substream) in enumerate(smatch_templates): 
     348                    if mt.test(event, mt.namespaces, None) is True: 
     349                        # matched! 
     350 
     351                        # Let the remaining match templates know about the event so 
     352                        # they get a chance to update their internal state 
     353                        for othermt in [sm[0] for sm in smatch_templates[idx+1:]]: 
     354                            othermt.test(event, othermt.namespaces, None, updateonly=True) 
     355 
     356                        # Consume and store all events until an end event 
     357                        # corresponding to this start event is encountered 
     358                        content = chain([event], 
     359                                        self._smatch(_strip(stream),smatch_templates), 
     360                                        tail) 
     361                        content = list(content) 
     362 
     363                        # Let all match templates know about end event 
     364                        for othermt in [sm[0] for sm in smatch_templates]: 
     365                            othermt.test(tail[0], othermt.namespaces, None, updateonly=True) 
     366 
     367                        # Create the select substream for accessing parts of 
     368                        # the matched element / substream. 
     369                        select_substream = Stream(content) 
     370 
     371                        staticmatch = StaticMatchDirective(mt_substream,mt_directives,select_substream,mt.namespaces) 
     372                        yield (SUB, (smatch_directives+[staticmatch], select_substream), pos) # apply staticmatch last 
     373 
     374                        break 
     375                else: 
     376                    yield (kind, data, pos) 
     377 
     378 
     379 
    294380INCLUDE = MarkupTemplate.INCLUDE 
  • genshi/template/directives.py

     
    2323 
    2424__all__ = ['AttrsDirective', 'ChooseDirective', 'ContentDirective', 
    2525           'DefDirective', 'ForDirective', 'IfDirective', 'MatchDirective', 
    26            'OtherwiseDirective', 'ReplaceDirective', 'StripDirective', 
    27            'WhenDirective', 'WithDirective'] 
     26           'OtherwiseDirective', 'ReplaceDirective', 
     27           'SmatchDirective','StaticMatchDirective', 
     28           'StripDirective','WhenDirective', 'WithDirective'] 
    2829 
    2930 
    3031class DirectiveMeta(type): 
     
    447448    attach = classmethod(attach) 
    448449 
    449450 
     451class SmatchDirective(Directive): 
     452    """Implementation of the `py:smatch` template directive. 
     453     
     454       The syntax for using py:smatch is the same as that for py:match. 
     455       The py:smatch XPath query is performed immediately after the 
     456       templated is parsed whereas the py:match query is performed when 
     457       the template is generated/rendered. In both cases the replacement 
     458       markup is only created on template generation. 
     459 
     460       py:smatch avoids performing XPath matches when the template is 
     461       generated at the cost of not being able to access the context in 
     462       the XPath expression or match XML not present in the template file 
     463       (for example, attributes generated by py:attr). 
     464    """ 
     465    __slots__ = ['path','namespaces','test'] 
     466 
     467    ATTRIBUTE = 'path' 
     468 
     469    def __init__(self, value, namespaces=None, filename=None, lineno=-1, 
     470                 offset=-1): 
     471        Directive.__init__(self, None, namespaces, filename, lineno, offset) 
     472        self.path = Path(value, filename, lineno) 
     473        self.test = self.path.test(ignore_context=True) 
     474        self.namespaces = namespaces or {} 
     475 
     476    def __call__(self, stream, ctxt, directives): 
     477        raise RuntimeError("All SmatchDirectives should have been " \ 
     478               "removed from the stream by the time directives are called.") 
     479 
     480    def __repr__(self): 
     481        return '<%s "%s">' % (self.__class__.__name__, self.path.source) 
     482 
     483 
     484class StaticMatchDirective(object): 
     485    """After a template has been parsed, a filter is run to replace 
     486       all SmatchDirectives with StaticMatchDirectives at the point 
     487       where there XPath expressions match the stream. 
     488        
     489       This is an unregistered directive. 
     490    """ 
     491    __slots__ = ['smatch_substream','smatch_directives','select_substream','namespaces'] 
     492 
     493    def __init__(self, smatch_substream, smatch_directives, select_substream, namespaces): 
     494        self.select_substream = select_substream 
     495        self.smatch_substream = smatch_substream 
     496        self.smatch_directives = smatch_directives 
     497        self.namespaces = namespaces 
     498 
     499    def __call__(self, stream, ctxt, directives): 
     500        def select(path): 
     501            return self.select_substream.select(path, self.namespaces, ctxt) 
     502 
     503        ctxt.push(dict(select=select)) 
     504        for event in _apply_directives(self.smatch_substream, ctxt, self.smatch_directives): 
     505            yield event 
     506        ctxt.pop() 
     507 
     508 
    450509class StripDirective(Directive): 
    451510    """Implementation of the `py:strip` template directive. 
    452511