Changeset 868
- Timestamp:
- Jun 9, 2008, 8:39:46 AM (15 years ago)
- Location:
- trunk/genshi/filters
- Files:
-
- 2 edited
-
tests/transform.py (modified) (2 diffs)
-
transform.py (modified) (28 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/genshi/filters/tests/transform.py
r784 r868 13 13 14 14 import doctest 15 from pprint import pprint 15 16 import unittest 16 17 18 from genshi import HTML 19 from genshi.builder import Element 20 from genshi.core import START, END, TEXT, QName, Attrs 21 from genshi.filters.transform import Transformer, StreamBuffer, ENTER, EXIT, \ 22 OUTSIDE, INSIDE, ATTR, BREAK 17 23 import genshi.filters.transform 24 25 26 FOO = '<root>ROOT<foo name="foo">FOO</foo></root>' 27 FOOBAR = '<root>ROOT<foo name="foo" size="100">FOO</foo><bar name="bar">BAR</bar></root>' 28 29 30 def _simplify(stream, with_attrs=False): 31 """Simplify a marked stream.""" 32 def _generate(): 33 for mark, (kind, data, pos) in stream: 34 if kind is START: 35 if with_attrs: 36 data = (unicode(data[0]), dict((unicode(k), v) 37 for k, v in data[1])) 38 else: 39 data = unicode(data[0]) 40 elif kind is END: 41 data = unicode(data) 42 elif kind is ATTR: 43 kind = ATTR 44 data = dict((unicode(k), v) for k, v in data[1]) 45 yield mark, kind, data 46 return list(_generate()) 47 48 49 def _transform(html, transformer, with_attrs=False): 50 """Apply transformation returning simplified marked stream.""" 51 if isinstance(html, basestring): 52 html = HTML(html) 53 stream = transformer(html, keep_marks=True) 54 return _simplify(stream, with_attrs) 55 56 57 class SelectTest(unittest.TestCase): 58 """Test .select()""" 59 def _select(self, select): 60 html = HTML(FOOBAR) 61 if isinstance(select, basestring): 62 select = [select] 63 transformer = Transformer(select[0]) 64 for sel in select[1:]: 65 transformer = transformer.select(sel) 66 return _transform(html, transformer) 67 68 def test_select_single_element(self): 69 self.assertEqual( 70 self._select('foo'), 71 [(None, START, u'root'), 72 (None, TEXT, u'ROOT'), 73 (ENTER, START, u'foo'), 74 (INSIDE, TEXT, u'FOO'), 75 (EXIT, END, u'foo'), 76 (None, START, u'bar'), 77 (None, TEXT, u'BAR'), 78 (None, END, u'bar'), 79 (None, END, u'root')], 80 ) 81 82 def test_select_context(self): 83 self.assertEqual( 84 self._select('.'), 85 [(ENTER, START, u'root'), 86 (INSIDE, TEXT, u'ROOT'), 87 (INSIDE, START, u'foo'), 88 (INSIDE, TEXT, u'FOO'), 89 (INSIDE, END, u'foo'), 90 (INSIDE, START, u'bar'), 91 (INSIDE, TEXT, u'BAR'), 92 (INSIDE, END, u'bar'), 93 (EXIT, END, u'root')] 94 ) 95 96 def test_select_inside_select(self): 97 self.assertEqual( 98 self._select(['.', 'foo']), 99 [(None, START, u'root'), 100 (None, TEXT, u'ROOT'), 101 (ENTER, START, u'foo'), 102 (INSIDE, TEXT, u'FOO'), 103 (EXIT, END, u'foo'), 104 (None, START, u'bar'), 105 (None, TEXT, u'BAR'), 106 (None, END, u'bar'), 107 (None, END, u'root')], 108 ) 109 110 def test_select_text(self): 111 self.assertEqual( 112 self._select('*/text()'), 113 [(None, START, u'root'), 114 (None, TEXT, u'ROOT'), 115 (None, START, u'foo'), 116 (OUTSIDE, TEXT, u'FOO'), 117 (None, END, u'foo'), 118 (None, START, u'bar'), 119 (OUTSIDE, TEXT, u'BAR'), 120 (None, END, u'bar'), 121 (None, END, u'root')], 122 ) 123 124 def test_select_attr(self): 125 self.assertEqual( 126 self._select('foo/@name'), 127 [(None, START, u'root'), 128 (None, TEXT, u'ROOT'), 129 (ATTR, ATTR, {'name': u'foo'}), 130 (None, START, u'foo'), 131 (None, TEXT, u'FOO'), 132 (None, END, u'foo'), 133 (None, START, u'bar'), 134 (None, TEXT, u'BAR'), 135 (None, END, u'bar'), 136 (None, END, u'root')] 137 ) 138 139 def test_select_text_context(self): 140 self.assertEqual( 141 list(Transformer('.')(HTML('foo'), keep_marks=True)), 142 [('OUTSIDE', ('TEXT', u'foo', (None, 1, 0)))], 143 ) 144 145 146 class InvertTest(unittest.TestCase): 147 def _invert(self, select): 148 return _transform(FOO, Transformer(select).invert()) 149 150 def test_invert_element(self): 151 self.assertEqual( 152 self._invert('foo'), 153 [(OUTSIDE, START, u'root'), 154 (OUTSIDE, TEXT, u'ROOT'), 155 (None, START, u'foo'), 156 (None, TEXT, u'FOO'), 157 (None, END, u'foo'), 158 (OUTSIDE, END, u'root')] 159 ) 160 161 def test_invert_inverted_element(self): 162 self.assertEqual( 163 _transform(FOO, Transformer('foo').invert().invert()), 164 [(None, START, u'root'), 165 (None, TEXT, u'ROOT'), 166 (OUTSIDE, START, u'foo'), 167 (OUTSIDE, TEXT, u'FOO'), 168 (OUTSIDE, END, u'foo'), 169 (None, END, u'root')] 170 ) 171 172 def test_invert_text(self): 173 self.assertEqual( 174 self._invert('foo/text()'), 175 [(OUTSIDE, START, u'root'), 176 (OUTSIDE, TEXT, u'ROOT'), 177 (OUTSIDE, START, u'foo'), 178 (None, TEXT, u'FOO'), 179 (OUTSIDE, END, u'foo'), 180 (OUTSIDE, END, u'root')] 181 ) 182 183 def test_invert_attribute(self): 184 self.assertEqual( 185 self._invert('foo/@name'), 186 [(OUTSIDE, START, u'root'), 187 (OUTSIDE, TEXT, u'ROOT'), 188 (None, ATTR, {'name': u'foo'}), 189 (OUTSIDE, START, u'foo'), 190 (OUTSIDE, TEXT, u'FOO'), 191 (OUTSIDE, END, u'foo'), 192 (OUTSIDE, END, u'root')] 193 ) 194 195 def test_invert_context(self): 196 self.assertEqual( 197 self._invert('.'), 198 [(None, START, u'root'), 199 (None, TEXT, u'ROOT'), 200 (None, START, u'foo'), 201 (None, TEXT, u'FOO'), 202 (None, END, u'foo'), 203 (None, END, u'root')] 204 ) 205 206 def test_invert_text_context(self): 207 self.assertEqual( 208 _simplify(Transformer('.').invert()(HTML('foo'), keep_marks=True)), 209 [(None, 'TEXT', u'foo')], 210 ) 211 212 213 214 class EndTest(unittest.TestCase): 215 def test_end(self): 216 stream = _transform(FOO, Transformer('foo').end()) 217 self.assertEqual( 218 stream, 219 [(OUTSIDE, START, u'root'), 220 (OUTSIDE, TEXT, u'ROOT'), 221 (OUTSIDE, START, u'foo'), 222 (OUTSIDE, TEXT, u'FOO'), 223 (OUTSIDE, END, u'foo'), 224 (OUTSIDE, END, u'root')] 225 ) 226 227 228 class EmptyTest(unittest.TestCase): 229 def _empty(self, select): 230 return _transform(FOO, Transformer(select).empty()) 231 232 def test_empty_element(self): 233 self.assertEqual( 234 self._empty('foo'), 235 [(None, START, u'root'), 236 (None, TEXT, u'ROOT'), 237 (ENTER, START, u'foo'), 238 (EXIT, END, u'foo'), 239 (None, END, u'root')], 240 ) 241 242 def test_empty_text(self): 243 self.assertEqual( 244 self._empty('foo/text()'), 245 [(None, START, u'root'), 246 (None, TEXT, u'ROOT'), 247 (None, START, u'foo'), 248 (OUTSIDE, TEXT, u'FOO'), 249 (None, END, u'foo'), 250 (None, END, u'root')] 251 ) 252 253 def test_empty_attr(self): 254 self.assertEqual( 255 self._empty('foo/@name'), 256 [(None, START, u'root'), 257 (None, TEXT, u'ROOT'), 258 (ATTR, ATTR, {'name': u'foo'}), 259 (None, START, u'foo'), 260 (None, TEXT, u'FOO'), 261 (None, END, u'foo'), 262 (None, END, u'root')] 263 ) 264 265 def test_empty_context(self): 266 self.assertEqual( 267 self._empty('.'), 268 [(ENTER, START, u'root'), 269 (EXIT, END, u'root')] 270 ) 271 272 def test_empty_text_context(self): 273 self.assertEqual( 274 _simplify(Transformer('.')(HTML('foo'), keep_marks=True)), 275 [(OUTSIDE, TEXT, u'foo')], 276 ) 277 278 279 class RemoveTest(unittest.TestCase): 280 def _remove(self, select): 281 return _transform(FOO, Transformer(select).remove()) 282 283 def test_remove_element(self): 284 self.assertEqual( 285 self._remove('foo|bar'), 286 [(None, START, u'root'), 287 (None, TEXT, u'ROOT'), 288 (None, END, u'root')] 289 ) 290 291 def test_remove_text(self): 292 self.assertEqual( 293 self._remove('//text()'), 294 [(None, START, u'root'), 295 (None, START, u'foo'), 296 (None, END, u'foo'), 297 (None, END, u'root')] 298 ) 299 300 def test_remove_attr(self): 301 self.assertEqual( 302 self._remove('foo/@name'), 303 [(None, START, u'root'), 304 (None, TEXT, u'ROOT'), 305 (None, START, u'foo'), 306 (None, TEXT, u'FOO'), 307 (None, END, u'foo'), 308 (None, END, u'root')] 309 ) 310 311 def test_remove_context(self): 312 self.assertEqual( 313 self._remove('.'), 314 [], 315 ) 316 317 def test_remove_text_context(self): 318 self.assertEqual( 319 _transform('foo', Transformer('.').remove()), 320 [], 321 ) 322 323 324 class UnwrapText(unittest.TestCase): 325 def _unwrap(self, select): 326 return _transform(FOO, Transformer(select).unwrap()) 327 328 def test_unwrap_element(self): 329 self.assertEqual( 330 self._unwrap('foo'), 331 [(None, START, u'root'), 332 (None, TEXT, u'ROOT'), 333 (INSIDE, TEXT, u'FOO'), 334 (None, END, u'root')] 335 ) 336 337 def test_unwrap_text(self): 338 self.assertEqual( 339 self._unwrap('foo/text()'), 340 [(None, START, u'root'), 341 (None, TEXT, u'ROOT'), 342 (None, START, u'foo'), 343 (OUTSIDE, TEXT, u'FOO'), 344 (None, END, u'foo'), 345 (None, END, u'root')] 346 ) 347 348 def test_unwrap_attr(self): 349 self.assertEqual( 350 self._unwrap('foo/@name'), 351 [(None, START, u'root'), 352 (None, TEXT, u'ROOT'), 353 (ATTR, ATTR, {'name': u'foo'}), 354 (None, START, u'foo'), 355 (None, TEXT, u'FOO'), 356 (None, END, u'foo'), 357 (None, END, u'root')] 358 ) 359 360 def test_unwrap_adjacent(self): 361 self.assertEqual( 362 _transform(FOOBAR, Transformer('foo|bar').unwrap()), 363 [(None, START, u'root'), 364 (None, TEXT, u'ROOT'), 365 (INSIDE, TEXT, u'FOO'), 366 (INSIDE, TEXT, u'BAR'), 367 (None, END, u'root')] 368 ) 369 370 def test_unwrap_root(self): 371 self.assertEqual( 372 self._unwrap('.'), 373 [(INSIDE, TEXT, u'ROOT'), 374 (INSIDE, START, u'foo'), 375 (INSIDE, TEXT, u'FOO'), 376 (INSIDE, END, u'foo')] 377 ) 378 379 def test_unwrap_text_root(self): 380 self.assertEqual( 381 _transform('foo', Transformer('.').unwrap()), 382 [(OUTSIDE, TEXT, 'foo')], 383 ) 384 385 386 class WrapTest(unittest.TestCase): 387 def _wrap(self, select, wrap='wrap'): 388 return _transform(FOO, Transformer(select).wrap(wrap)) 389 390 def test_wrap_element(self): 391 self.assertEqual( 392 self._wrap('foo'), 393 [(None, START, u'root'), 394 (None, TEXT, u'ROOT'), 395 (None, START, u'wrap'), 396 (ENTER, START, u'foo'), 397 (INSIDE, TEXT, u'FOO'), 398 (EXIT, END, u'foo'), 399 (None, END, u'wrap'), 400 (None, END, u'root')] 401 ) 402 403 def test_wrap_adjacent_elements(self): 404 self.assertEqual( 405 _transform(FOOBAR, Transformer('foo|bar').wrap('wrap')), 406 [(None, START, u'root'), 407 (None, TEXT, u'ROOT'), 408 (None, START, u'wrap'), 409 (ENTER, START, u'foo'), 410 (INSIDE, TEXT, u'FOO'), 411 (EXIT, END, u'foo'), 412 (None, END, u'wrap'), 413 (None, START, u'wrap'), 414 (ENTER, START, u'bar'), 415 (INSIDE, TEXT, u'BAR'), 416 (EXIT, END, u'bar'), 417 (None, END, u'wrap'), 418 (None, END, u'root')] 419 ) 420 421 def test_wrap_text(self): 422 self.assertEqual( 423 self._wrap('foo/text()'), 424 [(None, START, u'root'), 425 (None, TEXT, u'ROOT'), 426 (None, START, u'foo'), 427 (None, START, u'wrap'), 428 (OUTSIDE, TEXT, u'FOO'), 429 (None, END, u'wrap'), 430 (None, END, u'foo'), 431 (None, END, u'root')] 432 ) 433 434 def test_wrap_root(self): 435 self.assertEqual( 436 self._wrap('.'), 437 [(None, START, u'wrap'), 438 (ENTER, START, u'root'), 439 (INSIDE, TEXT, u'ROOT'), 440 (INSIDE, START, u'foo'), 441 (INSIDE, TEXT, u'FOO'), 442 (INSIDE, END, u'foo'), 443 (EXIT, END, u'root'), 444 (None, END, u'wrap')] 445 ) 446 447 def test_wrap_text_root(self): 448 self.assertEqual( 449 _transform('foo', Transformer('.').wrap('wrap')), 450 [(None, START, u'wrap'), 451 (OUTSIDE, TEXT, u'foo'), 452 (None, END, u'wrap')], 453 ) 454 455 def test_wrap_with_element(self): 456 element = Element('a', href='http://localhost') 457 self.assertEqual( 458 _transform('foo', Transformer('.').wrap(element), with_attrs=True), 459 [(None, START, (u'a', {u'href': u'http://localhost'})), 460 (OUTSIDE, TEXT, u'foo'), 461 (None, END, u'a')] 462 ) 463 464 465 class FilterTest(unittest.TestCase): 466 def _filter(self, select, html=FOOBAR): 467 """Returns a list of lists of filtered elements.""" 468 output = [] 469 def filtered(stream): 470 interval = [] 471 output.append(interval) 472 for event in stream: 473 interval.append(event) 474 yield event 475 _transform(html, Transformer(select).filter(filtered)) 476 simplified = [] 477 for sub in output: 478 simplified.append(_simplify([(None, event) for event in sub])) 479 return simplified 480 481 def test_filter_element(self): 482 self.assertEqual( 483 self._filter('foo'), 484 [[(None, START, u'foo'), 485 (None, TEXT, u'FOO'), 486 (None, END, u'foo')]] 487 ) 488 489 def test_filter_adjacent_elements(self): 490 self.assertEqual( 491 self._filter('foo|bar'), 492 [[(None, START, u'foo'), 493 (None, TEXT, u'FOO'), 494 (None, END, u'foo')], 495 [(None, START, u'bar'), 496 (None, TEXT, u'BAR'), 497 (None, END, u'bar')]] 498 ) 499 500 def test_filter_text(self): 501 self.assertEqual( 502 self._filter('*/text()'), 503 [[(None, TEXT, u'FOO')], 504 [(None, TEXT, u'BAR')]] 505 ) 506 def test_filter_root(self): 507 self.assertEqual( 508 self._filter('.'), 509 [[(None, START, u'root'), 510 (None, TEXT, u'ROOT'), 511 (None, START, u'foo'), 512 (None, TEXT, u'FOO'), 513 (None, END, u'foo'), 514 (None, START, u'bar'), 515 (None, TEXT, u'BAR'), 516 (None, END, u'bar'), 517 (None, END, u'root')]] 518 ) 519 520 def test_filter_text_root(self): 521 self.assertEqual( 522 self._filter('.', 'foo'), 523 [[(None, TEXT, u'foo')]]) 524 525 526 class MapTest(unittest.TestCase): 527 def _map(self, select, kind=None): 528 data = [] 529 def record(d): 530 data.append(d) 531 return d 532 _transform(FOOBAR, Transformer(select).map(record, kind)) 533 return data 534 535 def test_map_element(self): 536 self.assertEqual( 537 self._map('foo'), 538 [(QName(u'foo'), Attrs([(QName(u'name'), u'foo'), 539 (QName(u'size'), u'100')])), 540 u'FOO', 541 QName(u'foo')] 542 ) 543 544 def test_map_with_text_kind(self): 545 self.assertEqual( 546 self._map('.', TEXT), 547 [u'ROOT', u'FOO', u'BAR'] 548 ) 549 550 def test_map_with_root_and_end_kind(self): 551 self.assertEqual( 552 self._map('.', END), 553 [QName(u'foo'), QName(u'bar'), QName(u'root')] 554 ) 555 556 def test_map_with_attribute(self): 557 self.assertEqual( 558 self._map('foo/@name'), 559 [(QName(u'foo@*'), Attrs([('name', u'foo')]))] 560 ) 561 562 563 class SubstituteTest(unittest.TestCase): 564 def _substitute(self, select, pattern, replace): 565 return _transform(FOOBAR, Transformer(select).substitute(pattern, replace)) 566 567 def test_substitute_foo(self): 568 self.assertEqual( 569 self._substitute('foo', 'FOO|BAR', 'FOOOOO'), 570 [(None, START, u'root'), 571 (None, TEXT, u'ROOT'), 572 (ENTER, START, u'foo'), 573 (INSIDE, TEXT, u'FOOOOO'), 574 (EXIT, END, u'foo'), 575 (None, START, u'bar'), 576 (None, TEXT, u'BAR'), 577 (None, END, u'bar'), 578 (None, END, u'root')] 579 ) 580 581 def test_substitute_foobar_with_group(self): 582 self.assertEqual( 583 self._substitute('foo|bar', '(FOO|BAR)', r'(\1)'), 584 [(None, START, u'root'), 585 (None, TEXT, u'ROOT'), 586 (ENTER, START, u'foo'), 587 (INSIDE, TEXT, u'(FOO)'), 588 (EXIT, END, u'foo'), 589 (ENTER, START, u'bar'), 590 (INSIDE, TEXT, u'(BAR)'), 591 (EXIT, END, u'bar'), 592 (None, END, u'root')] 593 ) 594 595 596 class RenameTest(unittest.TestCase): 597 def _rename(self, select): 598 return _transform(FOOBAR, Transformer(select).rename('foobar')) 599 600 def test_rename_root(self): 601 self.assertEqual( 602 self._rename('.'), 603 [(ENTER, START, u'foobar'), 604 (INSIDE, TEXT, u'ROOT'), 605 (INSIDE, START, u'foo'), 606 (INSIDE, TEXT, u'FOO'), 607 (INSIDE, END, u'foo'), 608 (INSIDE, START, u'bar'), 609 (INSIDE, TEXT, u'BAR'), 610 (INSIDE, END, u'bar'), 611 (EXIT, END, u'foobar')] 612 ) 613 614 def test_rename_element(self): 615 self.assertEqual( 616 self._rename('foo|bar'), 617 [(None, START, u'root'), 618 (None, TEXT, u'ROOT'), 619 (ENTER, START, u'foobar'), 620 (INSIDE, TEXT, u'FOO'), 621 (EXIT, END, u'foobar'), 622 (ENTER, START, u'foobar'), 623 (INSIDE, TEXT, u'BAR'), 624 (EXIT, END, u'foobar'), 625 (None, END, u'root')] 626 ) 627 628 def test_rename_text(self): 629 self.assertEqual( 630 self._rename('foo/text()'), 631 [(None, START, u'root'), 632 (None, TEXT, u'ROOT'), 633 (None, START, u'foo'), 634 (OUTSIDE, TEXT, u'FOO'), 635 (None, END, u'foo'), 636 (None, START, u'bar'), 637 (None, TEXT, u'BAR'), 638 (None, END, u'bar'), 639 (None, END, u'root')] 640 ) 641 642 643 class ContentTestMixin(object): 644 def _apply(self, select, content=None, html=FOOBAR): 645 class Injector(object): 646 count = 0 647 648 def __iter__(self): 649 self.count += 1 650 return iter(HTML('CONTENT %i' % self.count)) 651 652 if isinstance(html, basestring): 653 html = HTML(html) 654 if content is None: 655 content = Injector() 656 elif isinstance(content, basestring): 657 content = HTML(content) 658 return _transform(html, getattr(Transformer(select), self.operation) 659 (content)) 660 661 662 class ReplaceTest(unittest.TestCase, ContentTestMixin): 663 operation = 'replace' 664 665 def test_replace_element(self): 666 self.assertEqual( 667 self._apply('foo'), 668 [(None, START, u'root'), 669 (None, TEXT, u'ROOT'), 670 (None, TEXT, u'CONTENT 1'), 671 (None, START, u'bar'), 672 (None, TEXT, u'BAR'), 673 (None, END, u'bar'), 674 (None, END, u'root')] 675 ) 676 677 def test_replace_text(self): 678 self.assertEqual( 679 self._apply('text()'), 680 [(None, START, u'root'), 681 (None, TEXT, u'CONTENT 1'), 682 (None, START, u'foo'), 683 (None, TEXT, u'FOO'), 684 (None, END, u'foo'), 685 (None, START, u'bar'), 686 (None, TEXT, u'BAR'), 687 (None, END, u'bar'), 688 (None, END, u'root')] 689 ) 690 691 def test_replace_context(self): 692 self.assertEqual( 693 self._apply('.'), 694 [(None, TEXT, u'CONTENT 1')], 695 ) 696 697 def test_replace_text_context(self): 698 self.assertEqual( 699 self._apply('.', html='foo'), 700 [(None, TEXT, u'CONTENT 1')], 701 ) 702 703 def test_replace_adjacent_elements(self): 704 self.assertEqual( 705 self._apply('*'), 706 [(None, START, u'root'), 707 (None, TEXT, u'ROOT'), 708 (None, TEXT, u'CONTENT 1'), 709 (None, TEXT, u'CONTENT 2'), 710 (None, END, u'root')], 711 ) 712 713 def test_replace_all(self): 714 self.assertEqual( 715 self._apply('*|text()'), 716 [(None, START, u'root'), 717 (None, TEXT, u'CONTENT 1'), 718 (None, TEXT, u'CONTENT 2'), 719 (None, TEXT, u'CONTENT 3'), 720 (None, END, u'root')], 721 ) 722 723 def test_replace_with_callback(self): 724 count = [0] 725 def content(): 726 count[0] += 1 727 yield '%2i.' % count[0] 728 self.assertEqual( 729 self._apply('*', content), 730 [(None, START, u'root'), 731 (None, TEXT, u'ROOT'), 732 (None, TEXT, u' 1.'), 733 (None, TEXT, u' 2.'), 734 (None, END, u'root')] 735 ) 736 737 738 class BeforeTest(unittest.TestCase, ContentTestMixin): 739 operation = 'before' 740 741 def test_before_element(self): 742 self.assertEqual( 743 self._apply('foo'), 744 [(None, START, u'root'), 745 (None, TEXT, u'ROOT'), 746 (None, TEXT, u'CONTENT 1'), 747 (ENTER, START, u'foo'), 748 (INSIDE, TEXT, u'FOO'), 749 (EXIT, END, u'foo'), 750 (None, START, u'bar'), 751 (None, TEXT, u'BAR'), 752 (None, END, u'bar'), 753 (None, END, u'root')] 754 ) 755 756 def test_before_text(self): 757 self.assertEqual( 758 self._apply('text()'), 759 [(None, START, u'root'), 760 (None, TEXT, u'CONTENT 1'), 761 (OUTSIDE, TEXT, u'ROOT'), 762 (None, START, u'foo'), 763 (None, TEXT, u'FOO'), 764 (None, END, u'foo'), 765 (None, START, u'bar'), 766 (None, TEXT, u'BAR'), 767 (None, END, u'bar'), 768 (None, END, u'root')] 769 ) 770 771 def test_before_context(self): 772 self.assertEqual( 773 self._apply('.'), 774 [(None, TEXT, u'CONTENT 1'), 775 (ENTER, START, u'root'), 776 (INSIDE, TEXT, u'ROOT'), 777 (INSIDE, START, u'foo'), 778 (INSIDE, TEXT, u'FOO'), 779 (INSIDE, END, u'foo'), 780 (INSIDE, START, u'bar'), 781 (INSIDE, TEXT, u'BAR'), 782 (INSIDE, END, u'bar'), 783 (EXIT, END, u'root')] 784 ) 785 786 def test_before_text_context(self): 787 self.assertEqual( 788 self._apply('.', html='foo'), 789 [(None, TEXT, u'CONTENT 1'), 790 (OUTSIDE, TEXT, u'foo')] 791 ) 792 793 def test_before_adjacent_elements(self): 794 self.assertEqual( 795 self._apply('*'), 796 [(None, START, u'root'), 797 (None, TEXT, u'ROOT'), 798 (None, TEXT, u'CONTENT 1'), 799 (ENTER, START, u'foo'), 800 (INSIDE, TEXT, u'FOO'), 801 (EXIT, END, u'foo'), 802 (None, TEXT, u'CONTENT 2'), 803 (ENTER, START, u'bar'), 804 (INSIDE, TEXT, u'BAR'), 805 (EXIT, END, u'bar'), 806 (None, END, u'root')] 807 808 ) 809 810 def test_before_all(self): 811 self.assertEqual( 812 self._apply('*|text()'), 813 [(None, START, u'root'), 814 (None, TEXT, u'CONTENT 1'), 815 (OUTSIDE, TEXT, u'ROOT'), 816 (None, TEXT, u'CONTENT 2'), 817 (ENTER, START, u'foo'), 818 (INSIDE, TEXT, u'FOO'), 819 (EXIT, END, u'foo'), 820 (None, TEXT, u'CONTENT 3'), 821 (ENTER, START, u'bar'), 822 (INSIDE, TEXT, u'BAR'), 823 (EXIT, END, u'bar'), 824 (None, END, u'root')] 825 ) 826 827 def test_before_with_callback(self): 828 count = [0] 829 def content(): 830 count[0] += 1 831 yield '%2i.' % count[0] 832 self.assertEqual( 833 self._apply('foo/text()', content), 834 [(None, 'START', u'root'), 835 (None, 'TEXT', u'ROOT'), 836 (None, 'START', u'foo'), 837 (None, 'TEXT', u' 1.'), 838 ('OUTSIDE', 'TEXT', u'FOO'), 839 (None, 'END', u'foo'), 840 (None, 'START', u'bar'), 841 (None, 'TEXT', u'BAR'), 842 (None, 'END', u'bar'), 843 (None, 'END', u'root')] 844 ) 845 846 847 class AfterTest(unittest.TestCase, ContentTestMixin): 848 operation = 'after' 849 850 def test_after_element(self): 851 self.assertEqual( 852 self._apply('foo'), 853 [(None, START, u'root'), 854 (None, TEXT, u'ROOT'), 855 (ENTER, START, u'foo'), 856 (INSIDE, TEXT, u'FOO'), 857 (EXIT, END, u'foo'), 858 (None, TEXT, u'CONTENT 1'), 859 (None, START, u'bar'), 860 (None, TEXT, u'BAR'), 861 (None, END, u'bar'), 862 (None, END, u'root')] 863 ) 864 865 def test_after_text(self): 866 self.assertEqual( 867 self._apply('text()'), 868 [(None, START, u'root'), 869 (OUTSIDE, TEXT, u'ROOT'), 870 (None, TEXT, u'CONTENT 1'), 871 (None, START, u'foo'), 872 (None, TEXT, u'FOO'), 873 (None, END, u'foo'), 874 (None, START, u'bar'), 875 (None, TEXT, u'BAR'), 876 (None, END, u'bar'), 877 (None, END, u'root')] 878 ) 879 880 def test_after_context(self): 881 self.assertEqual( 882 self._apply('.'), 883 [(ENTER, START, u'root'), 884 (INSIDE, TEXT, u'ROOT'), 885 (INSIDE, START, u'foo'), 886 (INSIDE, TEXT, u'FOO'), 887 (INSIDE, END, u'foo'), 888 (INSIDE, START, u'bar'), 889 (INSIDE, TEXT, u'BAR'), 890 (INSIDE, END, u'bar'), 891 (EXIT, END, u'root'), 892 (None, TEXT, u'CONTENT 1')] 893 ) 894 895 def test_after_text_context(self): 896 self.assertEqual( 897 self._apply('.', html='foo'), 898 [(OUTSIDE, TEXT, u'foo'), 899 (None, TEXT, u'CONTENT 1')] 900 ) 901 902 def test_after_adjacent_elements(self): 903 self.assertEqual( 904 self._apply('*'), 905 [(None, START, u'root'), 906 (None, TEXT, u'ROOT'), 907 (ENTER, START, u'foo'), 908 (INSIDE, TEXT, u'FOO'), 909 (EXIT, END, u'foo'), 910 (None, TEXT, u'CONTENT 1'), 911 (ENTER, START, u'bar'), 912 (INSIDE, TEXT, u'BAR'), 913 (EXIT, END, u'bar'), 914 (None, TEXT, u'CONTENT 2'), 915 (None, END, u'root')] 916 917 ) 918 919 def test_after_all(self): 920 self.assertEqual( 921 self._apply('*|text()'), 922 [(None, START, u'root'), 923 (OUTSIDE, TEXT, u'ROOT'), 924 (None, TEXT, u'CONTENT 1'), 925 (ENTER, START, u'foo'), 926 (INSIDE, TEXT, u'FOO'), 927 (EXIT, END, u'foo'), 928 (None, TEXT, u'CONTENT 2'), 929 (ENTER, START, u'bar'), 930 (INSIDE, TEXT, u'BAR'), 931 (EXIT, END, u'bar'), 932 (None, TEXT, u'CONTENT 3'), 933 (None, END, u'root')] 934 ) 935 936 def test_after_with_callback(self): 937 count = [0] 938 def content(): 939 count[0] += 1 940 yield '%2i.' % count[0] 941 self.assertEqual( 942 self._apply('foo/text()', content), 943 [(None, 'START', u'root'), 944 (None, 'TEXT', u'ROOT'), 945 (None, 'START', u'foo'), 946 ('OUTSIDE', 'TEXT', u'FOO'), 947 (None, 'TEXT', u' 1.'), 948 (None, 'END', u'foo'), 949 (None, 'START', u'bar'), 950 (None, 'TEXT', u'BAR'), 951 (None, 'END', u'bar'), 952 (None, 'END', u'root')] 953 ) 954 955 956 class PrependTest(unittest.TestCase, ContentTestMixin): 957 operation = 'prepend' 958 959 def test_prepend_element(self): 960 self.assertEqual( 961 self._apply('foo'), 962 [(None, START, u'root'), 963 (None, TEXT, u'ROOT'), 964 (ENTER, START, u'foo'), 965 (None, TEXT, u'CONTENT 1'), 966 (INSIDE, TEXT, u'FOO'), 967 (EXIT, END, u'foo'), 968 (None, START, u'bar'), 969 (None, TEXT, u'BAR'), 970 (None, END, u'bar'), 971 (None, END, u'root')] 972 ) 973 974 def test_prepend_text(self): 975 self.assertEqual( 976 self._apply('text()'), 977 [(None, START, u'root'), 978 (OUTSIDE, TEXT, u'ROOT'), 979 (None, START, u'foo'), 980 (None, TEXT, u'FOO'), 981 (None, END, u'foo'), 982 (None, START, u'bar'), 983 (None, TEXT, u'BAR'), 984 (None, END, u'bar'), 985 (None, END, u'root')] 986 ) 987 988 def test_prepend_context(self): 989 self.assertEqual( 990 self._apply('.'), 991 [(ENTER, START, u'root'), 992 (None, TEXT, u'CONTENT 1'), 993 (INSIDE, TEXT, u'ROOT'), 994 (INSIDE, START, u'foo'), 995 (INSIDE, TEXT, u'FOO'), 996 (INSIDE, END, u'foo'), 997 (INSIDE, START, u'bar'), 998 (INSIDE, TEXT, u'BAR'), 999 (INSIDE, END, u'bar'), 1000 (EXIT, END, u'root')], 1001 ) 1002 1003 def test_prepend_text_context(self): 1004 self.assertEqual( 1005 self._apply('.', html='foo'), 1006 [(OUTSIDE, TEXT, u'foo')] 1007 ) 1008 1009 def test_prepend_adjacent_elements(self): 1010 self.assertEqual( 1011 self._apply('*'), 1012 [(None, START, u'root'), 1013 (None, TEXT, u'ROOT'), 1014 (ENTER, START, u'foo'), 1015 (None, TEXT, u'CONTENT 1'), 1016 (INSIDE, TEXT, u'FOO'), 1017 (EXIT, END, u'foo'), 1018 (ENTER, START, u'bar'), 1019 (None, TEXT, u'CONTENT 2'), 1020 (INSIDE, TEXT, u'BAR'), 1021 (EXIT, END, u'bar'), 1022 (None, END, u'root')] 1023 1024 ) 1025 1026 def test_prepend_all(self): 1027 self.assertEqual( 1028 self._apply('*|text()'), 1029 [(None, START, u'root'), 1030 (OUTSIDE, TEXT, u'ROOT'), 1031 (ENTER, START, u'foo'), 1032 (None, TEXT, u'CONTENT 1'), 1033 (INSIDE, TEXT, u'FOO'), 1034 (EXIT, END, u'foo'), 1035 (ENTER, START, u'bar'), 1036 (None, TEXT, u'CONTENT 2'), 1037 (INSIDE, TEXT, u'BAR'), 1038 (EXIT, END, u'bar'), 1039 (None, END, u'root')] 1040 ) 1041 1042 def test_prepend_with_callback(self): 1043 count = [0] 1044 def content(): 1045 count[0] += 1 1046 yield '%2i.' % count[0] 1047 self.assertEqual( 1048 self._apply('foo', content), 1049 [(None, 'START', u'root'), 1050 (None, 'TEXT', u'ROOT'), 1051 (ENTER, 'START', u'foo'), 1052 (None, 'TEXT', u' 1.'), 1053 (INSIDE, 'TEXT', u'FOO'), 1054 (EXIT, 'END', u'foo'), 1055 (None, 'START', u'bar'), 1056 (None, 'TEXT', u'BAR'), 1057 (None, 'END', u'bar'), 1058 (None, 'END', u'root')] 1059 ) 1060 1061 1062 class AppendTest(unittest.TestCase, ContentTestMixin): 1063 operation = 'append' 1064 1065 def test_append_element(self): 1066 self.assertEqual( 1067 self._apply('foo'), 1068 [(None, START, u'root'), 1069 (None, TEXT, u'ROOT'), 1070 (ENTER, START, u'foo'), 1071 (INSIDE, TEXT, u'FOO'), 1072 (None, TEXT, u'CONTENT 1'), 1073 (EXIT, END, u'foo'), 1074 (None, START, u'bar'), 1075 (None, TEXT, u'BAR'), 1076 (None, END, u'bar'), 1077 (None, END, u'root')] 1078 ) 1079 1080 def test_append_text(self): 1081 self.assertEqual( 1082 self._apply('text()'), 1083 [(None, START, u'root'), 1084 (OUTSIDE, TEXT, u'ROOT'), 1085 (None, START, u'foo'), 1086 (None, TEXT, u'FOO'), 1087 (None, END, u'foo'), 1088 (None, START, u'bar'), 1089 (None, TEXT, u'BAR'), 1090 (None, END, u'bar'), 1091 (None, END, u'root')] 1092 ) 1093 1094 def test_append_context(self): 1095 self.assertEqual( 1096 self._apply('.'), 1097 [(ENTER, START, u'root'), 1098 (INSIDE, TEXT, u'ROOT'), 1099 (INSIDE, START, u'foo'), 1100 (INSIDE, TEXT, u'FOO'), 1101 (INSIDE, END, u'foo'), 1102 (INSIDE, START, u'bar'), 1103 (INSIDE, TEXT, u'BAR'), 1104 (INSIDE, END, u'bar'), 1105 (None, TEXT, u'CONTENT 1'), 1106 (EXIT, END, u'root')], 1107 ) 1108 1109 def test_append_text_context(self): 1110 self.assertEqual( 1111 self._apply('.', html='foo'), 1112 [(OUTSIDE, TEXT, u'foo')] 1113 ) 1114 1115 def test_append_adjacent_elements(self): 1116 self.assertEqual( 1117 self._apply('*'), 1118 [(None, START, u'root'), 1119 (None, TEXT, u'ROOT'), 1120 (ENTER, START, u'foo'), 1121 (INSIDE, TEXT, u'FOO'), 1122 (None, TEXT, u'CONTENT 1'), 1123 (EXIT, END, u'foo'), 1124 (ENTER, START, u'bar'), 1125 (INSIDE, TEXT, u'BAR'), 1126 (None, TEXT, u'CONTENT 2'), 1127 (EXIT, END, u'bar'), 1128 (None, END, u'root')] 1129 1130 ) 1131 1132 def test_append_all(self): 1133 self.assertEqual( 1134 self._apply('*|text()'), 1135 [(None, START, u'root'), 1136 (OUTSIDE, TEXT, u'ROOT'), 1137 (ENTER, START, u'foo'), 1138 (INSIDE, TEXT, u'FOO'), 1139 (None, TEXT, u'CONTENT 1'), 1140 (EXIT, END, u'foo'), 1141 (ENTER, START, u'bar'), 1142 (INSIDE, TEXT, u'BAR'), 1143 (None, TEXT, u'CONTENT 2'), 1144 (EXIT, END, u'bar'), 1145 (None, END, u'root')] 1146 ) 1147 1148 def test_append_with_callback(self): 1149 count = [0] 1150 def content(): 1151 count[0] += 1 1152 yield '%2i.' % count[0] 1153 self.assertEqual( 1154 self._apply('foo', content), 1155 [(None, 'START', u'root'), 1156 (None, 'TEXT', u'ROOT'), 1157 (ENTER, 'START', u'foo'), 1158 (INSIDE, 'TEXT', u'FOO'), 1159 (None, 'TEXT', u' 1.'), 1160 (EXIT, 'END', u'foo'), 1161 (None, 'START', u'bar'), 1162 (None, 'TEXT', u'BAR'), 1163 (None, 'END', u'bar'), 1164 (None, 'END', u'root')] 1165 ) 1166 1167 1168 1169 class AttrTest(unittest.TestCase): 1170 def _attr(self, select, name, value): 1171 return _transform(FOOBAR, Transformer(select).attr(name, value), 1172 with_attrs=True) 1173 1174 def test_set_existing_attr(self): 1175 self.assertEqual( 1176 self._attr('foo', 'name', 'FOO'), 1177 [(None, START, (u'root', {})), 1178 (None, TEXT, u'ROOT'), 1179 (ENTER, START, (u'foo', {u'name': 'FOO', u'size': '100'})), 1180 (INSIDE, TEXT, u'FOO'), 1181 (EXIT, END, u'foo'), 1182 (None, START, (u'bar', {u'name': u'bar'})), 1183 (None, TEXT, u'BAR'), 1184 (None, END, u'bar'), 1185 (None, END, u'root')] 1186 ) 1187 1188 def test_set_new_attr(self): 1189 self.assertEqual( 1190 self._attr('foo', 'title', 'FOO'), 1191 [(None, START, (u'root', {})), 1192 (None, TEXT, u'ROOT'), 1193 (ENTER, START, (u'foo', {u'name': u'foo', u'title': 'FOO', u'size': '100'})), 1194 (INSIDE, TEXT, u'FOO'), 1195 (EXIT, END, u'foo'), 1196 (None, START, (u'bar', {u'name': u'bar'})), 1197 (None, TEXT, u'BAR'), 1198 (None, END, u'bar'), 1199 (None, END, u'root')] 1200 ) 1201 1202 def test_attr_from_function(self): 1203 def set(name, event): 1204 self.assertEqual(name, 'name') 1205 return event[1][1].get('name').upper() 1206 1207 self.assertEqual( 1208 self._attr('foo|bar', 'name', set), 1209 [(None, START, (u'root', {})), 1210 (None, TEXT, u'ROOT'), 1211 (ENTER, START, (u'foo', {u'name': 'FOO', u'size': '100'})), 1212 (INSIDE, TEXT, u'FOO'), 1213 (EXIT, END, u'foo'), 1214 (ENTER, START, (u'bar', {u'name': 'BAR'})), 1215 (INSIDE, TEXT, u'BAR'), 1216 (EXIT, END, u'bar'), 1217 (None, END, u'root')] 1218 ) 1219 1220 def test_remove_attr(self): 1221 self.assertEqual( 1222 self._attr('foo', 'name', None), 1223 [(None, START, (u'root', {})), 1224 (None, TEXT, u'ROOT'), 1225 (ENTER, START, (u'foo', {u'size': '100'})), 1226 (INSIDE, TEXT, u'FOO'), 1227 (EXIT, END, u'foo'), 1228 (None, START, (u'bar', {u'name': u'bar'})), 1229 (None, TEXT, u'BAR'), 1230 (None, END, u'bar'), 1231 (None, END, u'root')] 1232 ) 1233 1234 def test_remove_attr_with_function(self): 1235 def set(name, event): 1236 return None 1237 1238 self.assertEqual( 1239 self._attr('foo', 'name', set), 1240 [(None, START, (u'root', {})), 1241 (None, TEXT, u'ROOT'), 1242 (ENTER, START, (u'foo', {u'size': '100'})), 1243 (INSIDE, TEXT, u'FOO'), 1244 (EXIT, END, u'foo'), 1245 (None, START, (u'bar', {u'name': u'bar'})), 1246 (None, TEXT, u'BAR'), 1247 (None, END, u'bar'), 1248 (None, END, u'root')] 1249 ) 1250 1251 1252 class BufferTestMixin(object): 1253 def _apply(self, select, with_attrs=False): 1254 buffer = StreamBuffer() 1255 events = buffer.events 1256 1257 class Trace(object): 1258 last = None 1259 trace = [] 1260 1261 def __call__(self, stream): 1262 for event in stream: 1263 if events and hash(tuple(events)) != self.last: 1264 self.last = hash(tuple(events)) 1265 self.trace.append(list(events)) 1266 yield event 1267 1268 trace = Trace() 1269 output = _transform(FOOBAR, getattr(Transformer(select), self.operation) 1270 (buffer).apply(trace), with_attrs=with_attrs) 1271 simplified = [] 1272 for interval in trace.trace: 1273 simplified.append(_simplify([(None, e) for e in interval], 1274 with_attrs=with_attrs)) 1275 return output, simplified 1276 1277 1278 class CopyTest(unittest.TestCase, BufferTestMixin): 1279 operation = 'copy' 1280 1281 def test_copy_element(self): 1282 self.assertEqual( 1283 self._apply('foo')[1], 1284 [[(None, START, u'foo'), 1285 (None, TEXT, u'FOO'), 1286 (None, END, u'foo')]] 1287 ) 1288 1289 def test_copy_adjacent_elements(self): 1290 self.assertEqual( 1291 self._apply('foo|bar')[1], 1292 [[(None, START, u'foo'), 1293 (None, TEXT, u'FOO'), 1294 (None, END, u'foo')], 1295 [(None, START, u'bar'), 1296 (None, TEXT, u'BAR'), 1297 (None, END, u'bar')]] 1298 ) 1299 1300 def test_copy_all(self): 1301 self.assertEqual( 1302 self._apply('*|text()')[1], 1303 [[(None, TEXT, u'ROOT')], 1304 [(None, START, u'foo'), 1305 (None, TEXT, u'FOO'), 1306 (None, END, u'foo')], 1307 [(None, START, u'bar'), 1308 (None, TEXT, u'BAR'), 1309 (None, END, u'bar')]] 1310 ) 1311 1312 def test_copy_text(self): 1313 self.assertEqual( 1314 self._apply('*/text()')[1], 1315 [[(None, TEXT, u'FOO')], 1316 [(None, TEXT, u'BAR')]] 1317 ) 1318 1319 def test_copy_context(self): 1320 self.assertEqual( 1321 self._apply('.')[1], 1322 [[(None, START, u'root'), 1323 (None, TEXT, u'ROOT'), 1324 (None, START, u'foo'), 1325 (None, TEXT, u'FOO'), 1326 (None, END, u'foo'), 1327 (None, START, u'bar'), 1328 (None, TEXT, u'BAR'), 1329 (None, END, u'bar'), 1330 (None, END, u'root')]] 1331 ) 1332 1333 def test_copy_attribute(self): 1334 self.assertEqual( 1335 self._apply('foo/@name', with_attrs=True)[1], 1336 [[(None, ATTR, {'name': u'foo'})]] 1337 ) 1338 1339 def test_copy_attributes(self): 1340 self.assertEqual( 1341 self._apply('foo/@*', with_attrs=True)[1], 1342 [[(None, ATTR, {u'name': u'foo', u'size': u'100'})]] 1343 ) 1344 1345 1346 class CutTest(unittest.TestCase, BufferTestMixin): 1347 operation = 'cut' 1348 1349 def test_cut_element(self): 1350 self.assertEqual( 1351 self._apply('foo'), 1352 ([(None, START, u'root'), 1353 (None, TEXT, u'ROOT'), 1354 (None, START, u'bar'), 1355 (None, TEXT, u'BAR'), 1356 (None, END, u'bar'), 1357 (None, END, u'root')], 1358 [[(None, START, u'foo'), 1359 (None, TEXT, u'FOO'), 1360 (None, END, u'foo')]]) 1361 ) 1362 1363 def test_cut_adjacent_elements(self): 1364 self.assertEqual( 1365 self._apply('foo|bar'), 1366 ([(None, START, u'root'), 1367 (None, TEXT, u'ROOT'), 1368 (BREAK, BREAK, None), 1369 (None, END, u'root')], 1370 [[(None, START, u'foo'), 1371 (None, TEXT, u'FOO'), 1372 (None, END, u'foo')], 1373 [(None, START, u'bar'), 1374 (None, TEXT, u'BAR'), 1375 (None, END, u'bar')]]) 1376 ) 1377 1378 def test_cut_all(self): 1379 self.assertEqual( 1380 self._apply('*|text()'), 1381 ([(None, 'START', u'root'), 1382 ('BREAK', 'BREAK', None), 1383 ('BREAK', 'BREAK', None), 1384 (None, 'END', u'root')], 1385 [[(None, 'TEXT', u'ROOT')], 1386 [(None, 'START', u'foo'), 1387 (None, 'TEXT', u'FOO'), 1388 (None, 'END', u'foo')], 1389 [(None, 'START', u'bar'), 1390 (None, 'TEXT', u'BAR'), 1391 (None, 'END', u'bar')]]) 1392 ) 1393 1394 def test_cut_text(self): 1395 self.assertEqual( 1396 self._apply('*/text()'), 1397 ([(None, 'START', u'root'), 1398 (None, 'TEXT', u'ROOT'), 1399 (None, 'START', u'foo'), 1400 (None, 'END', u'foo'), 1401 (None, 'START', u'bar'), 1402 (None, 'END', u'bar'), 1403 (None, 'END', u'root')], 1404 [[(None, 'TEXT', u'FOO')], 1405 [(None, 'TEXT', u'BAR')]]) 1406 ) 1407 1408 def test_cut_context(self): 1409 self.assertEqual( 1410 self._apply('.')[1], 1411 [[(None, 'START', u'root'), 1412 (None, 'TEXT', u'ROOT'), 1413 (None, 'START', u'foo'), 1414 (None, 'TEXT', u'FOO'), 1415 (None, 'END', u'foo'), 1416 (None, 'START', u'bar'), 1417 (None, 'TEXT', u'BAR'), 1418 (None, 'END', u'bar'), 1419 (None, 'END', u'root')]] 1420 ) 1421 1422 def test_cut_attribute(self): 1423 self.assertEqual( 1424 self._apply('foo/@name', with_attrs=True), 1425 ([(None, START, (u'root', {})), 1426 (None, TEXT, u'ROOT'), 1427 (None, START, (u'foo', {u'size': u'100'})), 1428 (None, TEXT, u'FOO'), 1429 (None, END, u'foo'), 1430 (None, START, (u'bar', {u'name': u'bar'})), 1431 (None, TEXT, u'BAR'), 1432 (None, END, u'bar'), 1433 (None, END, u'root')], 1434 [[(None, ATTR, {u'name': u'foo'})]]) 1435 ) 1436 1437 def test_cut_attributes(self): 1438 self.assertEqual( 1439 self._apply('foo/@*', with_attrs=True), 1440 ([(None, START, (u'root', {})), 1441 (None, TEXT, u'ROOT'), 1442 (None, START, (u'foo', {})), 1443 (None, TEXT, u'FOO'), 1444 (None, END, u'foo'), 1445 (None, START, (u'bar', {u'name': u'bar'})), 1446 (None, TEXT, u'BAR'), 1447 (None, END, u'bar'), 1448 (None, END, u'root')], 1449 [[(None, ATTR, {u'name': u'foo', u'size': u'100'})]]) 1450 ) 1451 1452 # XXX Test this when the XPath implementation is fixed (#233). 1453 # def test_cut_attribute_or_attribute(self): 1454 # self.assertEqual( 1455 # self._apply('foo/@name | foo/@size', with_attrs=True), 1456 # ([(None, START, (u'root', {})), 1457 # (None, TEXT, u'ROOT'), 1458 # (None, START, (u'foo', {})), 1459 # (None, TEXT, u'FOO'), 1460 # (None, END, u'foo'), 1461 # (None, START, (u'bar', {u'name': u'bar'})), 1462 # (None, TEXT, u'BAR'), 1463 # (None, END, u'bar'), 1464 # (None, END, u'root')], 1465 # [[(None, ATTR, {u'name': u'foo', u'size': u'100'})]]) 1466 # ) 1467 1468 18 1469 19 1470 … … 22 1473 from genshi.core import Markup 23 1474 from genshi.builder import tag 24 suite = doctest.DocTestSuite(genshi.filters.transform, 25 optionflags=doctest.NORMALIZE_WHITESPACE, 26 extraglobs={'HTML': HTML, 'tag': tag, 27 'Markup': Markup}) 1475 suite = unittest.TestSuite() 1476 for test in (SelectTest, InvertTest, EndTest, 1477 EmptyTest, RemoveTest, UnwrapText, WrapTest, FilterTest, 1478 MapTest, SubstituteTest, RenameTest, ReplaceTest, BeforeTest, 1479 AfterTest, PrependTest, AppendTest, AttrTest, CopyTest, CutTest): 1480 suite.addTest(unittest.makeSuite(test, 'test')) 1481 suite.addTest(doctest.DocTestSuite( 1482 genshi.filters.transform, optionflags=doctest.NORMALIZE_WHITESPACE, 1483 extraglobs={'HTML': HTML, 'tag': tag, 'Markup': Markup})) 28 1484 return suite 1485 29 1486 30 1487 if __name__ == '__main__': -
trunk/genshi/filters/transform.py
r858 r868 56 56 57 57 __all__ = ['Transformer', 'StreamBuffer', 'InjectorTransformation', 'ENTER', 58 'EXIT', 'INSIDE', 'OUTSIDE' ]58 'EXIT', 'INSIDE', 'OUTSIDE', 'BREAK'] 59 59 60 60 … … 86 86 """Stream augmentation mark indicating that a selected element is being 87 87 exited.""" 88 89 BREAK = TransformMark('BREAK') 90 """Stream augmentation mark indicating a break between two otherwise contiguous 91 blocks of marked events. 92 93 This is used primarily by the cut() transform to provide later transforms with 94 an opportunity to operate on the cut buffer. 95 """ 96 97 98 class PushBackStream(object): 99 """Allows a single event to be pushed back onto the stream and re-consumed. 100 """ 101 def __init__(self, stream): 102 self.stream = iter(stream) 103 self.peek = None 104 105 def push(self, event): 106 assert self.peek is None 107 self.peek = event 108 109 def __iter__(self): 110 while True: 111 if self.peek is not None: 112 peek = self.peek 113 self.peek = None 114 yield peek 115 else: 116 try: 117 event = self.stream.next() 118 yield event 119 except StopIteration: 120 if self.peek is None: 121 raise 88 122 89 123 … … 151 185 self.transforms = [SelectTransformation(path)] 152 186 153 def __call__(self, stream ):187 def __call__(self, stream, keep_marks=False): 154 188 """Apply the transform filter to the marked stream. 155 189 156 190 :param stream: the marked event stream to filter 191 :param keep_marks: Do not strip transformer selection marks from the 192 stream. Useful for testing. 157 193 :return: the transformed stream 158 194 :rtype: `Stream` … … 161 197 for link in self.transforms: 162 198 transforms = link(transforms) 163 return Stream(self._unmark(transforms), 199 if not keep_marks: 200 transforms = self._unmark(transforms) 201 return Stream(transforms, 164 202 serializer=getattr(stream, 'serializer', None)) 165 203 … … 330 368 text.</body></html> 331 369 332 :param content: Either an iterable of events or a string to insert. 370 :param content: Either a callable, an iterable of events, or a string 371 to insert. 333 372 :rtype: `Transformer` 334 373 """ … … 347 386 <em>body</em> text.</body></html> 348 387 349 :param content: Either an iterable of events or a string to insert. 388 :param content: Either a callable, an iterable of events, or a string 389 to insert. 350 390 :rtype: `Transformer` 351 391 """ … … 363 403 rock text.</body></html> 364 404 365 :param content: Either an iterable of events or a string to insert. 405 :param content: Either a callable, an iterable of events, or a string 406 to insert. 366 407 :rtype: `Transformer` 367 408 """ … … 379 420 Some <em>body</em> text.</body></html> 380 421 381 :param content: Either an iterable of events or a string to insert. 422 :param content: Either a callable, an iterable of events, or a string 423 to insert. 382 424 :rtype: `Transformer` 383 425 """ … … 393 435 text. Some new body text.</body></html> 394 436 395 :param content: Either an iterable of events or a string to insert. 437 :param content: Either a callable, an iterable of events, or a string 438 to insert. 396 439 :rtype: `Transformer` 397 440 """ … … 444 487 """Copy selection into buffer. 445 488 446 The buffer is replaced by each contiguousselection before being passed489 The buffer is replaced by each *contiguous* selection before being passed 447 490 to the next transformation. If accumulate=True, further selections will 448 491 be appended to the buffer rather than replacing it. … … 487 530 stored 488 531 :rtype: `Transformer` 489 :note: this transformation will buffer the entire input stream 532 note: Copy (and cut) copy each individual selected object into the 533 buffer before passing to the next transform. For example, the 534 XPath ``*|text()`` will select all elements and text, each 535 instance of which will be copied to the buffer individually 536 before passing to the next transform. This has implications for 537 how ``StreamBuffer`` objects can be used, so some 538 experimentation may be required. 539 490 540 """ 491 541 return self.apply(CopyTransformation(buffer, accumulate)) … … 635 685 def _unmark(self, stream): 636 686 for mark, event in stream: 637 if event[0] is not None: 687 kind = event[0] 688 if not (kind is None or kind is ATTR or kind is BREAK): 638 689 yield event 639 690 … … 687 738 # XXX Selected *attributes* are given a "kind" of None to 688 739 # indicate they are not really part of the stream. 689 yield ATTR, ( None, (QName(event[1][0] + '@*'), result), event[2])740 yield ATTR, (ATTR, (QName(event[1][0] + '@*'), result), event[2]) 690 741 yield None, event 742 elif isinstance(result, tuple): 743 yield OUTSIDE, result 691 744 elif result: 745 # XXX Assume everything else is "text"? 692 746 yield None, (TEXT, unicode(result), (None, -1, -1)) 693 747 else: … … 735 789 """ 736 790 for mark, event in stream: 737 if mark not in (INSIDE, OUTSIDE): 738 yield mark, event 791 yield mark, event 792 if mark is ENTER: 793 for mark, event in stream: 794 if mark is EXIT: 795 yield mark, event 796 break 739 797 740 798 … … 781 839 yield None, prefix 782 840 yield mark, event 783 while True: 784 try: 785 mark, event = stream.next() 786 except StopIteration: 787 yield None, element[-1] 841 start = mark 842 stopped = False 843 for mark, event in stream: 844 if start is ENTER and mark is EXIT: 845 yield mark, event 846 stopped = True 847 break 788 848 if not mark: 789 849 break 790 850 yield mark, event 851 else: 852 stopped = True 791 853 yield None, element[-1] 792 yield mark, event 854 if not stopped: 855 yield mark, event 793 856 else: 794 857 yield mark, event … … 819 882 class FilterTransformation(object): 820 883 """Apply a normal stream filter to the selection. The filter is called once 821 for each contiguous block of marked events."""884 for each selection.""" 822 885 823 886 def __init__(self, filter): … … 841 904 queue = [] 842 905 for mark, event in stream: 843 if mark :906 if mark is ENTER: 844 907 queue.append(event) 845 else: 908 for mark, event in stream: 909 queue.append(event) 910 if mark is EXIT: 911 break 846 912 for queue_event in flush(queue): 847 913 yield queue_event 848 yield None, event 849 for event in flush(queue): 850 yield event 914 elif mark is OUTSIDE: 915 stopped = True 916 queue.append(event) 917 for mark, event in stream: 918 if mark is not OUTSIDE: 919 break 920 queue.append(event) 921 else: 922 stopped = True 923 for queue_event in flush(queue): 924 yield queue_event 925 if not stopped: 926 yield None, event 927 else: 928 yield mark, event 929 for queue_event in flush(queue): 930 yield queue_event 851 931 852 932 … … 883 963 Refer to the documentation for ``re.sub()`` for details. 884 964 """ 885 def __init__(self, pattern, replace, count= 1):965 def __init__(self, pattern, replace, count=0): 886 966 """Create the transform. 887 967 … … 957 1037 958 1038 def _inject(self): 959 for event in _ensure(self.content): 1039 content = self.content 1040 if callable(content): 1041 content = content() 1042 for event in _ensure(content): 960 1043 yield None, event 961 1044 … … 969 1052 :param stream: The marked event stream to filter 970 1053 """ 1054 stream = PushBackStream(stream) 971 1055 for mark, event in stream: 972 1056 if mark is not None: 1057 start = mark 973 1058 for subevent in self._inject(): 974 1059 yield subevent 975 while True: 976 mark, event = stream.next() 977 if mark is None: 978 yield mark, event 1060 for mark, event in stream: 1061 if start is ENTER: 1062 if mark is EXIT: 1063 break 1064 elif mark != start: 1065 stream.push((mark, event)) 979 1066 break 980 1067 else: … … 990 1077 :param stream: The marked event stream to filter 991 1078 """ 1079 stream = PushBackStream(stream) 992 1080 for mark, event in stream: 993 1081 if mark is not None: 1082 start = mark 994 1083 for subevent in self._inject(): 995 1084 yield subevent 996 1085 yield mark, event 997 while True:998 mark, event = stream.next()999 if not mark:1086 for mark, event in stream: 1087 if mark != start and start is not ENTER: 1088 stream.push((mark, event)) 1000 1089 break 1001 1090 yield mark, event 1002 yield mark, event 1091 if start is ENTER and mark is EXIT: 1092 break 1093 else: 1094 yield mark, event 1003 1095 1004 1096 … … 1011 1103 :param stream: The marked event stream to filter 1012 1104 """ 1105 stream = PushBackStream(stream) 1013 1106 for mark, event in stream: 1014 1107 yield mark, event 1015 1108 if mark: 1016 while True: 1017 try: 1018 mark, event = stream.next() 1019 except StopIteration: 1020 break 1021 if not mark: 1109 start = mark 1110 for mark, event in stream: 1111 if start is not ENTER and mark != start: 1112 stream.push((mark, event)) 1022 1113 break 1023 1114 yield mark, event 1115 if start is ENTER and mark is EXIT: 1116 break 1024 1117 for subevent in self._inject(): 1025 1118 yield subevent 1026 yield mark, event1027 1119 1028 1120 … … 1037 1129 for mark, event in stream: 1038 1130 yield mark, event 1039 if mark i n (ENTER, OUTSIDE):1131 if mark is ENTER: 1040 1132 for subevent in self._inject(): 1041 1133 yield subevent … … 1053 1145 yield mark, event 1054 1146 if mark is ENTER: 1055 while True: 1056 mark, event = stream.next() 1147 for mark, event in stream: 1057 1148 if mark is EXIT: 1058 1149 break … … 1111 1202 1112 1203 def reset(self): 1113 """ Reset the buffer so that it's empty."""1204 """Empty the buffer of events.""" 1114 1205 del self.events[:] 1115 1206 … … 1134 1225 :param stream: the marked event stream to filter 1135 1226 """ 1136 stream = iter(stream) 1227 stream = PushBackStream(stream) 1228 1137 1229 for mark, event in stream: 1138 1230 if mark: 1139 1231 if not self.accumulate: 1140 1232 self.buffer.reset() 1141 events = [] 1142 while mark: 1233 events = [(mark, event)] 1234 self.buffer.append(event) 1235 start = mark 1236 for mark, event in stream: 1237 if start is not ENTER and mark != start: 1238 stream.push((mark, event)) 1239 break 1143 1240 events.append((mark, event)) 1144 1241 self.buffer.append(event) 1145 mark, event = stream.next() 1242 if start is ENTER and mark is EXIT: 1243 break 1146 1244 for i in events: 1147 1245 yield i 1148 yield mark, event 1246 else: 1247 yield mark, event 1149 1248 1150 1249 … … 1160 1259 stored 1161 1260 """ 1162 if not accumulate:1163 buffer.reset()1164 1261 self.buffer = buffer 1165 1262 self.accumulate = accumulate … … 1171 1268 :param stream: the marked event stream to filter 1172 1269 """ 1173 attributes = None 1174 stream = iter(stream) 1270 attributes = [] 1271 stream = PushBackStream(stream) 1272 broken = False 1273 if not self.accumulate: 1274 self.buffer.reset() 1175 1275 for mark, event in stream: 1176 1276 if mark: 1277 # Send a BREAK event if there was no other event sent between 1177 1278 if not self.accumulate: 1279 if not broken and self.buffer: 1280 yield BREAK, (BREAK, None, None) 1178 1281 self.buffer.reset() 1179 while mark: 1180 if mark is ATTR: 1181 attributes = [name for name, _ in data[1]] 1282 self.buffer.append(event) 1283 start = mark 1284 if mark is ATTR: 1285 attributes.extend([name for name, _ in event[1][1]]) 1286 for mark, event in stream: 1287 if start is mark is ATTR: 1288 attributes.extend([name for name, _ in event[1][1]]) 1289 # Handle non-element contiguous selection 1290 if start is not ENTER and mark != start: 1291 # Operating on the attributes of a START event 1292 if start is ATTR: 1293 kind, data, pos = event 1294 assert kind is START 1295 data = (data[0], data[1] - attributes) 1296 attributes = None 1297 stream.push((mark, (kind, data, pos))) 1298 else: 1299 stream.push((mark, event)) 1300 break 1182 1301 self.buffer.append(event) 1183 mark, event = stream.next() 1184 # If we've cut attributes, the associated element should START 1185 # immediately after. 1186 if attributes: 1187 assert kind is START 1188 data = (data[0], data[1] - attributes) 1189 attributes = None 1190 1191 yield mark, event 1302 if start is ENTER and mark is EXIT: 1303 break 1304 broken = False 1305 else: 1306 broken = True 1307 yield mark, event 1308 if not broken and self.buffer: 1309 yield BREAK, (BREAK, None, None)
Note: See TracChangeset
for help on using the changeset viewer.
