Ticket #85: pysmatch.diff
| File pysmatch.diff, 10.8 KB (added by hodgestar+genshi@…, 17 years ago) |
|---|
-
genshi/template/tests/markup.py
276 276 finally: 277 277 shutil.rmtree(dirname) 278 278 279 def test_basic_smatch(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('@name')} 288 </span> 289 <span>Some intervening text.</span> 290 <greeting name="Dude" /> 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_smatch_with_directives(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="//*[@name]" py:content="'Hello ' + str(select('@name'))" py:attrs="{'other':'greets'}" /> 314 <span>Some intervening text.</span> 315 <greeting name="Dude" /> 316 </html>""") 317 finally: 318 file1.close() 319 320 loader = TemplateLoader([dirname]) 321 tmpl = loader.load('tmpl1.html') 322 self.assertEqual("""<html> 323 <span>Some intervening text.</span> 324 <span other="greets">Hello Dude</span> 325 </html>""", tmpl.generate().render()) 326 finally: 327 shutil.rmtree(dirname) 328 329 def test_smatch_with_replace(self): 330 dirname = tempfile.mkdtemp(suffix='genshi_test') 331 try: 332 file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w') 333 try: 334 file1.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude" 335 xmlns:py="http://genshi.edgewall.org/"> 336 <span py:smatch="//*[@name]" py:replace="XML('<span>And now for something completely different.</span>')" /> 337 <span>Some intervening text.</span> 338 <greeting name="Dude" /> 339 </html>""") 340 finally: 341 file1.close() 342 343 loader = TemplateLoader([dirname]) 344 tmpl = loader.load('tmpl1.html') 345 self.assertEqual("""<html> 346 <span>Some intervening text.</span> 347 <span>And now for something completely different.</span> 348 </html>""", tmpl.generate(XML=XML).render()) 349 finally: 350 shutil.rmtree(dirname) 351 279 352 def test_fallback_when_include_found(self): 280 353 dirname = tempfile.mkdtemp(suffix='genshi_test') 281 354 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 294 else: # no matches 291 295 yield event 292 296 297 def _smatch(self, stream, smatch_templates=None): 298 """Internal stream filter that looks for and records any matches to py:smatch 299 directives. 300 """ 301 if smatch_templates is None: 302 smatch_templates = [] 293 303 304 tail = [] 305 def _strip(stream): 306 depth = 1 307 while 1: 308 event = stream.next() 309 if event[0] is START: 310 depth += 1 311 elif event[0] is END: 312 depth -= 1 313 if depth > 0: 314 yield event 315 else: 316 tail[:] = [event] 317 break 318 319 for kind, data, pos in stream: 320 event = (kind, data, pos) 321 322 if kind is SUB: 323 # look for SmatchDirectives 324 directives, substream = data 325 for idx, mt in enumerate(directives): 326 if not mt.__class__ is SmatchDirective: 327 continue 328 # process and remove SmatchDirective 329 smatch_templates.append((mt, directives[:idx] + directives[idx+1:], substream)) 330 break 331 else: 332 yield event 333 334 elif not kind is START and not kind is END: 335 # We (currently) only care about start and end events for matching 336 # We might care about namespace events in the future, though 337 yield event 338 339 else: 340 # look for matches 341 for idx, (mt, mt_directives, mt_substream) in enumerate(smatch_templates): 342 if mt.test(event, mt.namespaces, None) is True: 343 # matched! 344 345 # Let the remaining match templates know about the event so 346 # they get a chance to update their internal state 347 for othermt in [sm[0] for sm in smatch_templates[idx+1:]]: 348 othermt.test(event, othermt.namespaces, None, updateonly=True) 349 350 # Consume and store all events until an end event 351 # corresponding to this start event is encountered 352 content = chain([event], 353 self._smatch(_strip(stream),smatch_templates), 354 tail) 355 content = list(content) 356 357 # Let all match templates know about end event 358 for othermt in [sm[0] for sm in smatch_templates]: 359 othermt.test(tail[0], othermt.namespaces, None, updateonly=True) 360 361 # Create the select substream for accessing parts of 362 # the matched element / substream. 363 select_substream = Stream(content) 364 365 staticmatch = StaticMatchDirective(mt_substream,mt_directives,select_substream,mt.namespaces) 366 yield (SUB, ([staticmatch], select_substream), pos) 367 368 break 369 else: 370 yield (kind, data, pos) 371 372 373 294 374 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
