Ticket #85: pysmatch.2.diff
| File pysmatch.2.diff, 13.2 KB (added by hodgestar+genshi@…, 17 years ago) |
|---|
-
genshi/path.py
36 36 37 37 from genshi.core import Stream, Attrs, Namespace, QName 38 38 from genshi.core import START, END, TEXT, COMMENT, PI 39 from genshi.template.core import EXPR 39 40 40 41 __all__ = ['Path', 'PathSyntaxError'] 41 42 … … 620 621 """Node test that matches any text event.""" 621 622 __slots__ = [] 622 623 def __call__(self, kind, data, pos, namespaces, variables): 623 return kind is TEXT 624 return kind is TEXT or kind is EXPR 624 625 def __repr__(self): 625 626 return 'text()' 626 627 -
genshi/template/tests/markup.py
276 276 finally: 277 277 shutil.rmtree(dirname) 278 278 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('<span>And now for something completely different.</span>')" /> 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 279 379 def test_fallback_when_include_found(self): 280 380 dirname = tempfile.mkdtemp(suffix='genshi_test') 281 381 try: -
genshi/template/markup.py
42 42 43 43 directives = [('def', DefDirective), 44 44 ('match', MatchDirective), 45 ('smatch', SmatchDirective), 45 46 ('when', WhenDirective), 46 47 ('otherwise', OtherwiseDirective), 47 48 ('for', ForDirective), … … 185 186 return streams[0] 186 187 187 188 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)): 189 190 if kind is INCLUDE: 190 191 data = data[0], list(self._prepare(data[1])) 191 192 yield kind, data, pos … … 290 291 else: # no matches 291 292 yield event 292 293 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 = [] 293 301 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 294 380 INCLUDE = MarkupTemplate.INCLUDE -
genshi/template/directives.py
23 23 24 24 __all__ = ['AttrsDirective', 'ChooseDirective', 'ContentDirective', 25 25 'DefDirective', 'ForDirective', 'IfDirective', 'MatchDirective', 26 'OtherwiseDirective', 'ReplaceDirective', 'StripDirective', 27 'WhenDirective', 'WithDirective'] 26 'OtherwiseDirective', 'ReplaceDirective', 27 'SmatchDirective','StaticMatchDirective', 28 'StripDirective','WhenDirective', 'WithDirective'] 28 29 29 30 30 31 class DirectiveMeta(type): … … 447 448 attach = classmethod(attach) 448 449 449 450 451 class 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 484 class 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 450 509 class StripDirective(Directive): 451 510 """Implementation of the `py:strip` template directive. 452 511
