1 #!/usr/bin/env python 2 3 """ 4 Moin wiki format parser. 5 6 Copyright (C) 2017, 2018 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 # Document transformations. 23 24 from moinformat.macros import get_macro 25 26 # Parser functionality and pattern definition. 27 28 from moinformat.parsers.common import ParserBase, get_patterns, choice, \ 29 excl, expect, group, optional, recur, \ 30 repeat 31 32 # Serialisation. 33 34 from moinformat.serialisers import serialise 35 36 # Document tree nodes. 37 38 from moinformat.tree.moin import Anchor, Break, Comment, DefItem, DefTerm, \ 39 Directive, FontStyle, Heading, Larger, \ 40 LineBreak, Link, List, ListItem, Macro, \ 41 Monospace, Region, Rule, Smaller, \ 42 Strikethrough, Subscript, Superscript, Table, \ 43 TableAttr, TableAttrs, TableCell, TableRow, \ 44 Text, Underline 45 46 join = "".join 47 48 class MoinParser(ParserBase): 49 50 "A wiki region parser." 51 52 format = "moin" 53 54 def __init__(self, metadata, parsers=None, root=None): 55 56 """ 57 Initialise the parser with the given 'metadata' and optional 'parsers'. 58 An optional 'root' indicates the document-level parser. 59 """ 60 61 ParserBase.__init__(self, metadata, parsers, root) 62 63 # Record certain node occurrences for later evaluation. 64 65 self.macros = [] 66 67 # Record headings for identifier disambiguation. 68 69 self.headings = [] 70 71 # Principal parser methods. 72 73 def parse(self, s): 74 75 """ 76 Parse page text 's'. Pages consist of regions delimited by markers. 77 """ 78 79 self.items = self.get_items(s) 80 self.region = Region([], type="moin") 81 82 # Parse page header and directives. 83 84 self.parse_region_header(self.region) 85 self.parse_region_directives(self.region) 86 87 # Handle pages directly with this parser. Pages do not need to use an 88 # explicit format indicator. 89 90 if not self.region.type: 91 self.parse_region_content(self.items, self.region) 92 93 # Otherwise, test the type and find an appropriate parser. 94 95 else: 96 self.parse_region_type(self.region) 97 98 # Assign heading identifiers. 99 100 self.identify_headings() 101 102 return self.region 103 104 105 106 # Macro evaluation. 107 108 def evaluate_macros(self): 109 110 "Evaluate the macro nodes in the document." 111 112 for node in self.macros: 113 114 # Obtain a class for the named macro. 115 116 macro_cls = get_macro(node.name) 117 if not macro_cls: 118 continue 119 120 # Instantiate the class and evaluate the macro. 121 122 macro = macro_cls(node, self.region) 123 macro.evaluate() 124 125 # Heading disambiguation. 126 127 def identify_headings(self): 128 129 "Assign identifiers to headings based on their textual content." 130 131 d = {} 132 133 for heading in self.headings: 134 text = heading.text_content() 135 136 if not d.has_key(text): 137 d[text] = 0 138 heading.identifier = text 139 else: 140 d[text] += 1 141 heading.identifier = "%s-%d" % (text, d[text]) 142 143 144 145 # Conversion back to text. 146 147 def get_serialiser(self): 148 149 "Return metadata employing Moin as the output format." 150 151 metadata = self.metadata.copy() 152 metadata.set("link_format", None) 153 metadata.set("output_context", "standalone") 154 metadata.set("output_format", "moin") 155 return metadata.get_serialiser() 156 157 158 159 # Parser methods supporting different page features. 160 161 def parse_attrname(self, attrs): 162 163 "Handle an attribute name within 'attrs'." 164 165 name = self.match_group("name") 166 attr = TableAttr(name) 167 168 preceding = self.read_until(["attrvalue"], False) 169 if preceding == "": 170 attr.quote = self.match_group("quote") 171 attr.value = self.match_group("value") 172 173 attrs.append(attr) 174 175 def parse_break(self, region): 176 177 "Handle a paragraph break within 'region'." 178 179 self.add_node(region, Break()) 180 self.new_block(region) 181 182 def parse_comment(self, region): 183 184 "Handle a comment within 'region'." 185 186 comment = self.match_group("comment") 187 extra = self.match_group("extra") 188 self.add_node(region, Comment(comment, extra)) 189 self.new_block(region) 190 191 def parse_defitem(self, region, extra=""): 192 193 "Handle a definition item within 'region'." 194 195 pad = self.match_group("pad") 196 item = DefItem([], pad, extra) 197 self.parse_region_details(item, self.listitem_pattern_names) 198 self.add_node(region, item) 199 self.new_block(region) 200 201 def parse_defterm(self, region): 202 203 "Handle a definition term within 'region'." 204 205 pad = self.match_group("pad") 206 term = DefTerm([], pad) 207 self.parse_region_details(term, ["deftermend", "deftermsep"]) 208 self.add_node(region, term) 209 210 if self.matching_pattern() == "deftermsep": 211 self.parse_defitem(region) 212 213 # Add padding from the separator to the term, there being no item. 214 215 else: 216 term.extra = self.match_group("pad") 217 218 def parse_defterm_empty(self, region): 219 220 "Handle an empty definition term within 'region'." 221 222 extra = self.match_group("pad") 223 self.parse_region_details(region, ["deftermsep"]) 224 self.parse_defitem(region, extra) 225 226 def parse_directive(self, region): 227 228 "Handle a processing directive within 'region'." 229 230 directive = self.match_group("directive") 231 extra = self.match_group("extra") 232 self.add_node(region, Directive(directive, extra)) 233 self.new_block(region) 234 235 def parse_fontstyle(self, region): 236 237 "Handle emphasis and strong styles." 238 239 n = len(self.match_group("style")) 240 241 # Handle endings. 242 243 if isinstance(region, FontStyle): 244 emphasis = n in (2, 4, 5) 245 strong = n in (3, 5, 6) 246 active = True 247 248 if region.emphasis and emphasis: 249 active = region.close_emphasis() 250 n -= 2 251 if region.strong and strong: 252 active = region.close_strong() 253 n -= 3 254 255 if not active: 256 if n: 257 self.items.rewind(n) 258 raise StopIteration 259 260 elif not n: 261 return 262 263 # Handle new styles. 264 265 emphasis = n in (2, 4, 5) 266 strong = n in (3, 5, 6) 267 double = n in (4, 6) 268 269 span = FontStyle([], emphasis, strong) 270 if not double: 271 self.parse_region_details(span, self.inline_pattern_names) 272 region.append_inline(span) 273 274 def parse_halign(self, attrs): 275 276 "Handle horizontal alignment within 'attrs'." 277 278 value = self.match_group("value") 279 attr = TableAttr("halign", value == "(" and "left" or value == ")" and "right" or "center", True) 280 attrs.append(attr) 281 282 def parse_heading(self, region): 283 284 "Handle a heading." 285 286 start_extra = self.match_group("extra") 287 level = len(self.match_group("level")) 288 start_pad = self.match_group("pad") 289 heading = Heading([], level, start_extra, start_pad) 290 self.parse_region_details(heading, ["headingend"] + self.inline_pattern_names) 291 self.add_node(region, heading) 292 self.new_block(region) 293 294 # Record the heading for later processing. 295 296 self.root.headings.append(heading) 297 298 def parse_heading_end(self, heading): 299 300 "Handle the end of a heading." 301 302 level = len(self.match_group("level")) 303 if heading.level == level: 304 heading.end_pad = self.match_group("pad") 305 heading.end_extra = self.match_group("extra") 306 raise StopIteration 307 308 def parse_list(self, item): 309 310 "Create a list, starting with 'item'." 311 312 list = List([item], item.indent, item.marker, item.num) 313 self.parse_region_details(list, self.list_pattern_names, True) 314 return list 315 316 def parse_listitem(self, region): 317 318 "Handle a list item marker within 'region'." 319 320 indent = len(self.match_group("indent")) 321 marker = self.match_group("marker") 322 num = self.match_group("num") 323 space = self.match_group("pad") 324 325 last = region.node(-1) 326 327 new_list = not isinstance(last, (List, ListItem)) 328 same_indent = not new_list and indent == last.indent 329 new_marker = not new_list and last.marker != marker and same_indent 330 new_num = not new_list and num is not None and last.num != num and same_indent 331 332 # If the marker or number changes at the same indent, or if the indent 333 # is smaller, queue the item and end the list. 334 335 # Note that Moin format does not seek to support item renumbering, 336 # instead starting new lists on number changes. 337 338 if not new_list and (new_marker or new_num or indent < last.indent): 339 self.queue_match() 340 self.end_region(region) 341 342 # Obtain a list item and populate it. 343 344 item = ListItem([], indent, marker, space, num) 345 self.parse_region_details(item, self.listitem_pattern_names) 346 347 # Start a new list if not preceded by a list item, adding a trailing 348 # block for new elements. 349 350 if new_list: 351 item = self.parse_list(item) 352 self.add_node(region, item) 353 self.new_block(region) 354 355 # Add a nested list to the last item. 356 357 elif indent > last.indent: 358 item = self.parse_list(item) 359 self.add_node(last, item) 360 361 # Add the item to the current list. 362 363 else: 364 self.add_node(region, item) 365 366 def parse_rule(self, region): 367 368 "Handle a horizontal rule within 'region'." 369 370 length = len(self.match_group("rule")) 371 rule = Rule(length) 372 self.add_node(region, rule) 373 self.new_block(region) 374 375 def parse_section(self, region): 376 377 "Handle the start of a new section within 'region'." 378 379 # Parse the section and start a new block after the section. 380 381 indent = len(self.match_group("indent")) 382 level = len(self.match_group("level")) 383 384 section = self.parse_region(level, indent, "inline") 385 386 # If the section is inline, treat it like any other inline element. 387 388 if section.type == "inline": 389 region.append_inline(section) 390 391 # Otherwise, add it as a new block element. 392 393 else: 394 self.add_node(region, section) 395 if region.allow_blocks: 396 self.new_block(region) 397 398 def parse_table_attrs(self, cell): 399 400 "Handle the start of table attributes within 'cell'." 401 402 attrs = TableAttrs([]) 403 self.parse_region_details(attrs, self.table_attr_pattern_names) 404 405 # Test the validity of the attributes. 406 407 last = None 408 409 for node in attrs.nodes: 410 411 # Text separator nodes must be whitespace. 412 413 if isinstance(node, Text): 414 if node.s.strip(): 415 break 416 417 # Named attributes must be preceded by space if not the first. 418 419 elif last and not node.concise and not isinstance(last, Text): 420 break 421 422 last = node 423 424 # All nodes were valid: preserve the collection. 425 426 else: 427 # Add the attributes as a node, also recording their presence. 428 429 cell.append(attrs) 430 cell.attrs = attrs 431 return 432 433 # Invalid nodes were found: serialise the attributes as text. 434 435 cell.append_inline(Text(serialise(attrs, self.get_serialiser()))) 436 437 def parse_table_row(self, region): 438 439 "Handle the start of a table row within 'region'." 440 441 # Identify any active table. 442 443 table = region.node(-2) 444 block = region.node(-1) 445 446 if not (isinstance(table, Table) and block.empty()): 447 new_table = table = Table([]) 448 else: 449 new_table = None 450 451 row = TableRow([]) 452 453 while True: 454 cell = TableCell([]) 455 self.parse_region_details(cell, self.table_row_pattern_names) 456 457 # Handle the end of the row. 458 459 if self.matching_pattern() == "tableend": 460 trailing = self.match_group("extra") 461 462 # If the cell was started but not finished, convert the row into text. 463 464 if not row.nodes or not cell.empty(): 465 466 # Convert the nodes back to text. 467 468 serialiser = self.get_serialiser() 469 470 for node in row.nodes: 471 region.append_inline(Text(serialise(node, serialiser))) 472 473 region.append_inline(Text(serialise(cell, serialiser) + trailing)) 474 475 self.new_block(region) 476 return 477 478 # Append the final cell, if not empty. 479 480 else: 481 row.trailing = trailing 482 483 if not cell.empty(): 484 row.append(cell) 485 break 486 487 # A cell separator has been found. 488 489 row.append(cell) 490 491 # Add the row to the table and any new table to the region. 492 493 table.add(row) 494 if new_table: 495 self.add_node(region, new_table) 496 497 self.new_block(region) 498 499 def parse_valign(self, attrs): 500 501 "Handle vertical alignment within 'attrs'." 502 503 value = self.match_group("value") 504 attr = TableAttr("valign", value == "^" and "top" or "bottom", True) 505 attrs.append(attr) 506 507 508 509 def inline_patterns_for(self, name): 510 names = self.inline_pattern_names[:] 511 names[names.index(name)] = "%send" % name 512 return names 513 514 515 516 # Inline formatting handlers. 517 518 def parse_inline(self, region, cls, pattern_name): 519 520 "Handle an inline region." 521 522 span = cls([]) 523 self.parse_region_details(span, self.inline_patterns_for(pattern_name)) 524 region.append_inline(span) 525 526 def parse_larger(self, region): 527 self.parse_inline(region, Larger, "larger") 528 529 def parse_monospace(self, region): 530 span = Monospace([]) 531 self.parse_region_details(span, ["monospaceend"]) 532 region.append_inline(span) 533 534 def parse_smaller(self, region): 535 self.parse_inline(region, Smaller, "smaller") 536 537 def parse_strike(self, region): 538 self.parse_inline(region, Strikethrough, "strike") 539 540 def parse_sub(self, region): 541 self.parse_inline(region, Subscript, "sub") 542 543 def parse_super(self, region): 544 self.parse_inline(region, Superscript, "super") 545 546 def parse_underline(self, region): 547 self.parse_inline(region, Underline, "underline") 548 549 550 551 # Complete inline pattern handlers. 552 553 def parse_anchor(self, region): 554 target = self.match_group("target") 555 anchor = Anchor(target) 556 region.append_inline(anchor) 557 558 def parse_linebreak(self, region): 559 region.append_inline(LineBreak()) 560 561 def parse_link(self, region): 562 target = self.match_group("target") 563 text = self.match_group("text") 564 link = Link(text and [Text(text)] or [], target) 565 region.append_inline(link) 566 567 def parse_macro(self, region): 568 name = self.match_group("name") 569 args = self.match_group("args") 570 571 # Obtain the raw arguments. Moin usually leaves it to the macro to 572 # interpret the individual arguments. 573 574 arglist = args and args.split(",") or [] 575 macro = Macro(name, arglist, region.append_point(), region) 576 region.append_inline(macro) 577 578 # Record the macro for later processing. 579 580 self.root.macros.append(macro) 581 582 583 584 # Table attribute handlers. 585 586 def parse_table_attr(self, attrs, pattern_name): 587 588 "Handle a table attribute." 589 590 attrs.append(TableAttr(pattern_name, self.match_group("value"), True)) 591 592 def parse_colour(self, cell): 593 self.parse_table_attr(cell, "colour") 594 595 def parse_colspan(self, cell): 596 self.parse_table_attr(cell, "colspan") 597 598 def parse_rowspan(self, cell): 599 self.parse_table_attr(cell, "rowspan") 600 601 def parse_width(self, cell): 602 self.parse_table_attr(cell, "width") 603 604 605 606 # Regular expressions. 607 608 syntax = { 609 # Page regions: 610 611 "regionstart" : join((group("indent", r"\N*"), # ws... (optional) 612 group("level", repeat("[{]", 3)))), # {{{... 613 614 "regionend" : join((r"\N*", # ws... (optional) 615 group("feature", join(( 616 group("level", repeat("[}]", 3)), # }}}... 617 optional(group("extra", r"\n"))))))), # nl (optional) 618 619 # Region header and directives: 620 621 "header" : join(("#!", # #! 622 group("args", ".*?"), "\n")), # text-excl-nl 623 624 "directive" : join((r"^#", # # 625 group("directive", r".*?$"), # rest of line 626 optional(group("extra", r"\n")))), # nl (optional) 627 628 # Region contents: 629 630 # Line-oriented patterns support features which require their own 631 # separate lines. 632 633 "break" : r"^(\s*?)\n", # blank line 634 635 "comment" : join((r"^##", # ## 636 group("comment", r".*?$"), # rest of line 637 optional(group("extra", r"\n")))), # nl (optional) 638 639 "defterm" : join(("^", 640 group("pad", r"\N+"), # ws... 641 expect(".+?::"))), # text :: 642 643 "defterm_empty" : join(("^", 644 group("pad", r"\N+"), # ws... 645 expect("::\s+"))), # :: ws... 646 647 "heading" : join(("^", 648 group("extra", r"\N*"), # ws... (optional) 649 group("level", "=+"), # =... 650 group("pad", r"\s+"), # ws... 651 expect(join((r".*?\N+", # text 652 recur("level"), # =... 653 r"\N*$"))))), # ws... (optional) 654 655 "listitem" : join(("^", 656 group("indent", r"\N+"), # ws... 657 group("marker", r"\*"), # list-marker 658 group("pad", r"\s*"))), # ws... (optional) 659 660 "listitem_num" : join(("^", 661 group("indent", r"\N+"), # ws... 662 group("marker", r"\d+\."), # decimal-marker 663 optional(join(("#", group("num", r"\d+")))), # # num (optional) 664 group("pad", r"\s+"))), # ws... 665 666 "listitem_alpha": join(("^", 667 group("indent", r"\N+"), # ws... 668 group("marker", r"[aA]\."), # alpha-marker 669 optional(join(("#", group("num", r"\d+")))), # # num (optional) 670 group("pad", r"\s+"))), # ws... 671 672 "listitem_roman": join(("^", 673 group("indent", r"\N+"), # ws... 674 group("marker", r"[iI]\."), # roman-marker 675 optional(join(("#", group("num", r"\d+")))), # # num (optional) 676 group("pad", r"\s+"))), # ws... 677 678 "listitem_dot" : join(("^", 679 group("indent", r"\N+"), # ws... 680 group("marker", r"\."), # dot-marker 681 group("pad", r"\s*"))), # ws... (optional) 682 683 "tablerow" : r"^\|\|", # || 684 685 # Region contents: 686 687 # Inline patterns are for markup features that appear within blocks. 688 # The patterns below start inline spans that can contain other markup 689 # features. 690 691 "fontstyle" : group("style", repeat("'", 2, 6)), # ''... 692 "larger" : r"~\+", # ~+ 693 "monospace" : r"`", # ` 694 "rule" : group("rule", "-----*"), # ----... 695 "smaller" : r"~-", # ~- 696 "strike" : r"--\(", # --( 697 "sub" : r",,", # ,, 698 "super" : r"\^", # ^ 699 "underline" : r"__", # __ 700 701 # Complete inline patterns are for markup features that do not support 702 # arbitrary content within them: 703 704 "anchor" : join((r"\(\(", # (( 705 group("target", ".*?"), # target 706 r"\)\)")), # )) 707 708 "linebreak" : r"\\\\", # \\ 709 710 "link" : join((r"\[\[", # [[ 711 group("target", ".*?"), # target 712 optional(join((r"\|", # | 713 group("text", r"\E*?")))), # text-incl-nl (optional) 714 "]]")), # ]] 715 716 "macro" : join(("<<", # << 717 group("name", "\w+?"), # digit-letter... 718 optional(join((r"\(", # ( (optional) 719 group("args", ".*?"), # not-)... 720 r"\)"))), # ) (optional) 721 ">>")), # >> 722 723 # Ending patterns for inline features: 724 725 "largerend" : r"\+~", # +~ 726 "monospaceend" : r"`", # ` 727 "smallerend" : r"-~", # -~ 728 "strikeend" : r"\)--", # )-- 729 "subend" : r",,", # ,, 730 "superend" : r"\^", # ^ 731 "underlineend" : r"__", # __ 732 733 # Heading contents: 734 735 "headingend" : join((group("pad", r"\N+"), # ws... 736 group("level", "=+"), # =... 737 group("extra", r"\N*\n"))), # ws (optional) nl 738 739 # List contents: 740 741 "deftermend" : join(("::", group("pad", r"\s*?\n"))), # :: 742 # ws... (optional) 743 # nl 744 745 "deftermsep" : join(("::", group("pad", r"\s+"))), # :: 746 # ws... 747 748 "listitemend" : join((r"^", # next line 749 choice((expect(r"[^\s]"), # without indent 750 expect(r"\Z"), # end of string 751 expect(r"\N+\*"), # or with ws... list-marker 752 expect(r"\N+\d\."), # or with ws... decimal-marker 753 expect(r"\N+[aA]\."), # or with ws... alpha-marker 754 expect(r"\N+[iI]\."), # or with ws... roman-marker 755 expect(r"\N+\."), # or with ws... dot-marker 756 expect(r"\N+.+?::\s"), # or with ws... text :: ws (next defterm) 757 expect(r"\N+::\s"))))), # or with ws... :: ws (next defitem) 758 759 # Table contents: 760 761 "tableattrs" : join(("<", # lt 762 excl("<"))), # not-lt 763 764 "tablecell" : r"\|\|", # || 765 766 "tableend" : join((group("extra", r"\s*?"), # ws... (optional) 767 "^")), # next line 768 769 # Table attributes: 770 771 "tableattrsend" : r">", # > 772 "halign" : group("value", "[(:)]"), # halign-marker 773 "valign" : group("value", "[v^]"), # valign-marker 774 "colour" : group("value", join(("\#", # # 775 repeat("[0-9A-F]", 6, 6)))), # nnnnnn 776 777 "colspan" : join(("-", # - 778 group("value", "\d+"))), # n... 779 780 "rowspan" : join((r"\|", # | 781 group("value", "\d+"))), # n... 782 783 "width" : group("value", "\d+%"), # n... % 784 785 "attrname" : join((excl(r"[-\d]"), # not-dash-or-digit 786 group("name", r"[-\w]+"))), # dash-digit-letter... 787 788 "attrvalue" : join(("=", group("quote", r"\Q"), # quote 789 group("value", ".*?"), # non-quote... (optional) 790 recur("quote"))), # quote 791 } 792 793 patterns = get_patterns(syntax) 794 795 796 797 # Patterns available within certain markup features. 798 799 table_attr_pattern_names = [ 800 "attrname", "colour", "colspan", "halign", "rowspan", "tableattrsend", 801 "valign", "width" 802 ] 803 804 inline_pattern_names = [ 805 "anchor", "fontstyle", "larger", "linebreak", "link", "macro", 806 "monospace", "regionstart", "smaller", "strike", "sub", "super", 807 "underline", 808 ] 809 810 list_pattern_names = [ 811 "listitem", "listitem_alpha", "listitem_dot", "listitem_num", 812 "listitem_roman", 813 ] 814 815 listitem_pattern_names = inline_pattern_names + ["listitemend"] 816 817 region_without_table_pattern_names = inline_pattern_names + list_pattern_names + [ 818 "break", "comment", "heading", "defterm", "defterm_empty", 819 "regionend", "rule", 820 ] 821 822 table_row_pattern_names = inline_pattern_names + [ 823 "tableattrs", "tablecell", "tableend" 824 ] 825 826 # The region pattern names are specifically used by the common parser 827 # functionality. 828 829 region_pattern_names = region_without_table_pattern_names + ["tablerow"] 830 831 832 833 # Pattern handlers. 834 835 end_region = ParserBase.end_region 836 parse_section_end = ParserBase.parse_region_end 837 838 handlers = { 839 None : end_region, 840 "anchor" : parse_anchor, 841 "attrname" : parse_attrname, 842 "break" : parse_break, 843 "colour" : parse_colour, 844 "colspan" : parse_colspan, 845 "comment" : parse_comment, 846 "defterm" : parse_defterm, 847 "defterm_empty" : parse_defterm_empty, 848 "deftermend" : end_region, 849 "deftermsep" : end_region, 850 "directive" : parse_directive, 851 "fontstyle" : parse_fontstyle, 852 "halign" : parse_halign, 853 "heading" : parse_heading, 854 "headingend" : parse_heading_end, 855 "larger" : parse_larger, 856 "largerend" : end_region, 857 "linebreak" : parse_linebreak, 858 "link" : parse_link, 859 "macro" : parse_macro, 860 "listitemend" : end_region, 861 "listitem" : parse_listitem, 862 "listitem_alpha" : parse_listitem, 863 "listitem_dot" : parse_listitem, 864 "listitem_num" : parse_listitem, 865 "listitem_roman" : parse_listitem, 866 "monospace" : parse_monospace, 867 "monospaceend" : end_region, 868 "regionstart" : parse_section, 869 "regionend" : parse_section_end, 870 "rowspan" : parse_rowspan, 871 "rule" : parse_rule, 872 "smaller" : parse_smaller, 873 "smallerend" : end_region, 874 "strike" : parse_strike, 875 "strikeend" : end_region, 876 "sub" : parse_sub, 877 "subend" : end_region, 878 "super" : parse_super, 879 "superend" : end_region, 880 "tableattrs" : parse_table_attrs, 881 "tableattrsend" : end_region, 882 "tablerow" : parse_table_row, 883 "tablecell" : end_region, 884 "tableend" : end_region, 885 "underline" : parse_underline, 886 "underlineend" : end_region, 887 "valign" : parse_valign, 888 "width" : parse_width, 889 } 890 891 parser = MoinParser 892 893 # vim: tabstop=4 expandtab shiftwidth=4