1 #!/usr/bin/env python 2 3 """ 4 Moin wiki format document tree nodes. 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 class Container: 23 24 "A container of document nodes." 25 26 def __init__(self, nodes): 27 self.nodes = nodes 28 29 # In principle, allow blocks within containers. Some nodes may forbid 30 # them to simplify the document structure. 31 32 self.allow_blocks = True 33 34 def append(self, node): 35 self.nodes.append(node) 36 37 def append_many(self, nodes): 38 for node in nodes: 39 self.append(node) 40 41 add = append 42 43 def append_inline(self, node): 44 45 "Append 'node' inline within the appropriate container." 46 47 n = self.append_point() 48 49 # Redirect the addition if another container is to accept the node. 50 51 if n is not self: 52 n.append_inline(node) 53 54 # Otherwise, append within this container. 55 56 else: 57 n.append(node) 58 59 def append_inline_many(self, nodes): 60 for node in nodes: 61 self.append_inline(node) 62 63 def append_point(self): 64 65 "Return the container to which inline nodes are added." 66 67 return self 68 69 def empty(self): 70 return not self.nodes 71 72 def node(self, index): 73 try: 74 return self.nodes[index] 75 except IndexError: 76 return None 77 78 def normalise(self): 79 80 "Combine adjacent text nodes." 81 82 nodes = self.nodes 83 self.nodes = [] 84 text = None 85 86 for node in nodes: 87 88 # Open a text node or merge text into an open node. 89 90 if isinstance(node, Text): 91 if not text: 92 text = node 93 else: 94 text.merge(node) 95 96 # Close any open text node and append the current node. 97 98 else: 99 if text: 100 self.append(text) 101 text = None 102 self.append(node) 103 104 # Add any open text node. 105 106 if text: 107 self.append(text) 108 109 def replace(self, old, new): 110 111 "Replace 'old' with 'new' in the children." 112 113 i = self.nodes.index(old) 114 self.nodes[i] = new 115 116 def text_content(self): 117 118 """ 119 Return a string containing the content of text nodes within this 120 container. 121 """ 122 123 l = [] 124 125 for node in self.nodes: 126 if isinstance(node, Text): 127 l.append(node.s) 128 elif isinstance(node, Container): 129 l.append(node.text_content()) 130 131 return "".join(l) 132 133 def __str__(self): 134 return self.prettyprint() 135 136 def _prettyprint(self, l, indent=""): 137 for node in self.nodes: 138 l.append(node.prettyprint(indent + " ")) 139 return "\n".join(l) 140 141 def _to_string(self, out): 142 for node in self.nodes: 143 node.to_string(out) 144 145 class Region(Container): 146 147 "A region of the page." 148 149 def __init__(self, nodes, level=0, indent=0, type=None, args=None, 150 transparent=True, extra=None): 151 Container.__init__(self, nodes) 152 self.level = level 153 self.indent = indent 154 self.type = type 155 self.args = args 156 self.transparent = transparent 157 self.extra = extra 158 159 def add(self, node): 160 last = self.node(-1) 161 if last and last.empty(): 162 self.nodes[-1] = node 163 else: 164 self.append(node) 165 166 def append_point(self): 167 168 "Return the container to which inline nodes are added." 169 170 if self.transparent: 171 return self.nodes[-1] 172 else: 173 return self 174 175 def have_end(self, s): 176 return self.level and s.startswith("}") and self.level == len(s) 177 178 def __repr__(self): 179 return "Region(%r, %r, %r, %r, %r, %r, %r)" % (self.nodes, self.level, 180 self.indent, self.type, self.args, self.transparent, self.extra) 181 182 def prettyprint(self, indent=""): 183 l = ["%sRegion: level=%d indent=%d type=%s args=%r extra=%r" % (indent, 184 self.level, self.indent, self.type, self.args, self.extra)] 185 return self._prettyprint(l, indent) 186 187 def to_string(self, out): 188 out.start_region(self.level, self.indent, self.type, self.extra) 189 190 # Obtain a serialiser for the region from the same format family. 191 # Retain the same serialiser if no appropriate serialiser could be 192 # obtained. 193 194 serialiser_name = "%s.%s" % (out.format, self.type) 195 serialiser = out.get_serialiser(serialiser_name) 196 197 # Serialise the region. 198 199 self._to_string(serialiser) 200 201 # End the region with the previous serialiser. 202 203 out.end_region(self.level, self.indent, self.type, self.extra) 204 205 206 207 # Block nodes. 208 209 class Block(Container): 210 211 "A block in the page." 212 213 def __repr__(self): 214 return "Block(%r)" % self.nodes 215 216 def prettyprint(self, indent=""): 217 l = ["%sBlock" % indent] 218 return self._prettyprint(l, indent) 219 220 def to_string(self, out): 221 out.start_block() 222 self._to_string(out) 223 out.end_block() 224 225 class DefItem(Container): 226 227 "A definition item." 228 229 def __init__(self, nodes, pad, extra): 230 Container.__init__(self, nodes) 231 self.pad = pad 232 self.extra = extra 233 234 def __repr__(self): 235 return "DefItem(%r, %r, %r)" % (self.nodes, self.pad, self.extra) 236 237 def prettyprint(self, indent=""): 238 l = ["%sDefItem: pad=%r extra=%r" % (indent, self.pad, self.extra)] 239 return self._prettyprint(l, indent) 240 241 def to_string(self, out): 242 out.start_defitem(self.pad, self.extra) 243 self._to_string(out) 244 out.end_defitem(self.pad, self.extra) 245 246 class DefTerm(Container): 247 248 "A definition term." 249 250 def __init__(self, nodes, pad, extra=""): 251 Container.__init__(self, nodes) 252 self.pad = pad 253 self.extra = extra 254 255 def __repr__(self): 256 return "DefTerm(%r, %r, %r)" % (self.nodes, self.pad, self.extra) 257 258 def prettyprint(self, indent=""): 259 l = ["%sDefTerm: pad=%r extra=%r" % (indent, self.pad, self.extra)] 260 return self._prettyprint(l, indent) 261 262 def to_string(self, out): 263 out.start_defterm(self.pad, self.extra) 264 self._to_string(out) 265 out.end_defterm(self.pad, self.extra) 266 267 class FontStyle(Container): 268 269 "Emphasised and/or strong text." 270 271 def __init__(self, nodes, emphasis=False, strong=False): 272 Container.__init__(self, nodes) 273 self.emphasis = emphasis 274 self.strong = strong 275 276 def close_emphasis(self): 277 if self.strong: 278 span = FontStyle(self.nodes, emphasis=True) 279 self.nodes = [span] 280 self.emphasis = False 281 return self.strong 282 283 def close_strong(self): 284 if self.emphasis: 285 span = FontStyle(self.nodes, strong=True) 286 self.nodes = [span] 287 self.strong = False 288 return self.emphasis 289 290 def __repr__(self): 291 return "FontStyle(%r, %r, %r)" % (self.nodes, self.emphasis, self.strong) 292 293 def prettyprint(self, indent=""): 294 l = ["%sFontStyle: emphasis=%r strong=%r" % (indent, self.emphasis, self.strong)] 295 return self._prettyprint(l, indent) 296 297 def to_string(self, out): 298 if self.emphasis: 299 out.start_emphasis() 300 elif self.strong: 301 out.start_strong() 302 self._to_string(out) 303 if self.emphasis: 304 out.end_emphasis() 305 elif self.strong: 306 out.end_strong() 307 308 class Heading(Container): 309 310 "A heading." 311 312 def __init__(self, nodes, level, start_extra="", start_pad="", end_pad="", end_extra="", 313 identifier=None): 314 Container.__init__(self, nodes) 315 self.level = level 316 self.start_extra = start_extra 317 self.start_pad = start_pad 318 self.end_pad = end_pad 319 self.end_extra = end_extra 320 self.identifier = identifier 321 322 def __repr__(self): 323 return "Heading(%r, %d, %r, %r, %r, %r, %r)" % ( 324 self.nodes, self.level, self.start_extra, self.start_pad, 325 self.end_pad, self.end_extra, self.identifier) 326 327 def prettyprint(self, indent=""): 328 l = ["%sHeading: level=%d start_extra=%r start_pad=%r end_pad=%r" 329 " end_extra=%r identifier=%r" % ( 330 indent, self.level, self.start_extra, self.start_pad, self.end_pad, 331 self.end_extra, self.identifier)] 332 return self._prettyprint(l, indent) 333 334 def to_string(self, out): 335 out.start_heading(self.level, self.start_extra, self.start_pad, self.identifier) 336 self._to_string(out) 337 out.end_heading(self.level, self.end_pad, self.end_extra) 338 339 class List(Container): 340 341 "A list." 342 343 def __init__(self, nodes, indent, marker, num): 344 Container.__init__(self, nodes) 345 self.indent = indent 346 self.marker = marker 347 self.num = num 348 349 def __repr__(self): 350 return "List(%r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.num) 351 352 def prettyprint(self, indent=""): 353 l = ["%sList: indent=%d marker=%r num=%r" % (indent, self.indent, self.marker, self.num)] 354 return self._prettyprint(l, indent) 355 356 def to_string(self, out): 357 out.start_list(self.indent, self.marker, self.num) 358 self._to_string(out) 359 out.end_list(self.indent, self.marker, self.num) 360 361 class ListItem(Container): 362 363 "A list item." 364 365 def __init__(self, nodes, indent, marker, space, num): 366 Container.__init__(self, nodes) 367 self.indent = indent 368 self.marker = marker 369 self.space = space 370 self.num = num 371 372 # Forbid blocks within list items for simpler structure. 373 374 self.allow_blocks = False 375 376 def __repr__(self): 377 return "ListItem(%r, %r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.space, self.num) 378 379 def prettyprint(self, indent=""): 380 l = ["%sListItem: indent=%d marker=%r space=%r num=%r" % (indent, self.indent, self.marker, self.space, self.num)] 381 return self._prettyprint(l, indent) 382 383 def to_string(self, out): 384 out.start_listitem(self.indent, self.marker, self.space, self.num) 385 self._to_string(out) 386 out.end_listitem(self.indent, self.marker, self.space, self.num) 387 388 class TableAttrs(Container): 389 390 "A collection of table attributes." 391 392 def __repr__(self): 393 return "TableAttrs(%r)" % self.nodes 394 395 def prettyprint(self, indent=""): 396 l = ["%sTableAttrs:" % indent] 397 return self._prettyprint(l, indent) 398 399 def to_string(self, out): 400 out.start_table_attrs() 401 out.table_attrs(self.nodes) 402 out.end_table_attrs() 403 404 class Table(Container): 405 406 "A table." 407 408 def __repr__(self): 409 return "Table(%r)" % self.nodes 410 411 def prettyprint(self, indent=""): 412 l = ["%sTable:" % indent] 413 return self._prettyprint(l, indent) 414 415 def to_string(self, out): 416 out.start_table() 417 self._to_string(out) 418 out.end_table() 419 420 class TableCell(Container): 421 422 "A table cell." 423 424 def __init__(self, nodes, attrs=None): 425 Container.__init__(self, nodes) 426 self.attrs = attrs 427 428 def __repr__(self): 429 return "TableCell(%r, %r)" % (self.nodes, self.attrs) 430 431 def prettyprint(self, indent=""): 432 l = ["%sTableCell:" % indent] 433 return self._prettyprint(l, indent) 434 435 def to_string(self, out): 436 out.start_table_cell(self.attrs) 437 self._to_string(out) 438 out.end_table_cell() 439 440 class TableRow(Container): 441 442 "A table row." 443 444 def __init__(self, nodes, trailing=""): 445 Container.__init__(self, nodes) 446 self.trailing = trailing 447 448 def __repr__(self): 449 return "TableRow(%r, %r)" % (self.nodes, self.trailing) 450 451 def prettyprint(self, indent=""): 452 l = ["%sTableRow: trailing=%r" % (indent, self.trailing)] 453 return self._prettyprint(l, indent) 454 455 def to_string(self, out): 456 out.start_table_row() 457 self._to_string(out) 458 out.end_table_row(self.trailing) 459 460 461 462 # Inline nodes with children. 463 464 class Inline(Container): 465 466 "Generic inline formatting." 467 468 def __repr__(self): 469 return "%s(%r)" % (self.__class__.__name__, self.nodes) 470 471 def prettyprint(self, indent=""): 472 l = ["%s%s" % (indent, self.__class__.__name__)] 473 return self._prettyprint(l, indent) 474 475 class Larger(Inline): 476 477 "Larger text." 478 479 def to_string(self, out): 480 out.start_larger() 481 self._to_string(out) 482 out.end_larger() 483 484 class Link(Container): 485 486 "Link details." 487 488 def __init__(self, nodes, target): 489 Container.__init__(self, nodes) 490 self.target = target 491 492 def __repr__(self): 493 return "Link(%r, %r)" % (self.nodes, self.target) 494 495 def prettyprint(self, indent=""): 496 l = ["%sLink: target=%r" % (indent, self.target)] 497 return self._prettyprint(l, indent) 498 499 def to_string(self, out): 500 out.start_link(self.target, self.nodes) 501 if self.nodes: 502 out.start_linktext() 503 self._to_string(out) 504 out.end_linktext() 505 out.end_link() 506 507 class Macro(Container): 508 509 "Macro details." 510 511 def __init__(self, name, args, parent, nodes=None): 512 Container.__init__(self, nodes or []) 513 self.name = name 514 self.parent = parent 515 self.args = args 516 517 def __repr__(self): 518 return "Macro(%r, %r, %r, %r)" % (self.name, self.args, self.parent, self.nodes) 519 520 def prettyprint(self, indent=""): 521 l = ["%sMacro: name=%r args=%r" % (indent, self.name, self.args)] 522 return self._prettyprint(l, indent) 523 524 def to_string(self, out): 525 out.start_macro(self.name, self.args, self.nodes) 526 if self.nodes: 527 self._to_string(out) 528 out.end_macro() 529 530 class Monospace(Inline): 531 532 "Monospaced text." 533 534 def to_string(self, out): 535 out.start_monospace() 536 self._to_string(out) 537 out.end_monospace() 538 539 class Smaller(Inline): 540 541 "Smaller text." 542 543 def to_string(self, out): 544 out.start_smaller() 545 self._to_string(out) 546 out.end_smaller() 547 548 class Strikethrough(Inline): 549 550 "Crossed-out text." 551 552 def to_string(self, out): 553 out.start_strikethrough() 554 self._to_string(out) 555 out.end_strikethrough() 556 557 class Subscript(Inline): 558 559 "Subscripted text." 560 561 def to_string(self, out): 562 out.start_subscript() 563 self._to_string(out) 564 out.end_subscript() 565 566 class Superscript(Inline): 567 568 "Superscripted text." 569 570 def to_string(self, out): 571 out.start_superscript() 572 self._to_string(out) 573 out.end_superscript() 574 575 class Underline(Inline): 576 577 "Underlined text." 578 579 def to_string(self, out): 580 out.start_underline() 581 self._to_string(out) 582 out.end_underline() 583 584 585 586 # Nodes without children. 587 588 class Node: 589 590 "A document node without children." 591 592 def empty(self): 593 return False 594 595 class Anchor(Node): 596 597 "Anchor details." 598 599 def __init__(self, target): 600 self.target = target 601 602 def __repr__(self): 603 return "Anchor(%r)" % self.target 604 605 def prettyprint(self, indent=""): 606 return "%sAnchor: target=%r" % (indent, self.target) 607 608 def to_string(self, out): 609 out.anchor(self.target) 610 611 class Break(Node): 612 613 "A paragraph break." 614 615 def __repr__(self): 616 return "Break()" 617 618 def prettyprint(self, indent=""): 619 return "%sBreak" % indent 620 621 def to_string(self, out): 622 out.break_() 623 624 class Comment(Node): 625 626 "A comment." 627 628 def __init__(self, comment, extra): 629 self.comment = comment 630 self.extra = extra 631 632 def __repr__(self): 633 return "Comment(%r, %r)" % (self.comment, self.extra) 634 635 def prettyprint(self, indent=""): 636 return "%sComment: comment=%r extra=%r" % (indent, self.comment, self.extra) 637 638 def to_string(self, out): 639 out.comment(self.comment, self.extra) 640 641 class Directive(Node): 642 643 "A processing directive." 644 645 def __init__(self, directive, extra): 646 self.directive = directive 647 self.extra = extra 648 649 def __repr__(self): 650 return "Directive(%r, %r)" % (self.directive, self.extra) 651 652 def prettyprint(self, indent=""): 653 return "%sDirective: directive=%r extra=%r" % (indent, self.directive, self.extra) 654 655 def to_string(self, out): 656 out.directive(self.directive, self.extra) 657 658 class LineBreak(Node): 659 660 "A line break within a block." 661 662 def __repr__(self): 663 return "LineBreak()" 664 665 def prettyprint(self, indent=""): 666 return "%sLineBreak" % indent 667 668 def to_string(self, out): 669 out.linebreak() 670 671 class Rule(Node): 672 673 "A horizontal rule." 674 675 def __init__(self, length): 676 self.length = length 677 678 def __repr__(self): 679 return "Rule(%d)" % self.length 680 681 def prettyprint(self, indent=""): 682 return "%sRule: length=%d" % (indent, self.length) 683 684 def to_string(self, out): 685 out.rule(self.length) 686 687 class TableAttr(Node): 688 689 "A table attribute." 690 691 def __init__(self, name, value=None, concise=False, quote=None): 692 self.name = name 693 self.value = value 694 self.concise = concise 695 self.quote = quote 696 697 def __repr__(self): 698 return "TableAttr(%r, %r, %r, %r)" % (self.name, self.value, self.concise, self.quote) 699 700 def prettyprint(self, indent=""): 701 return "%sTableAttr: name=%r value=%r concise=%r quote=%r" % (indent, self.name, self.value, self.concise, self.quote) 702 703 def to_string(self, out): 704 out.table_attr(self.name, self.value, self.concise, self.quote) 705 706 class Text(Node): 707 708 "A text node." 709 710 def __init__(self, s): 711 self.s = s 712 713 def empty(self): 714 return not self.s 715 716 def multiline(self): 717 return "\n" in self.s 718 719 def merge(self, text): 720 self.s += text.s 721 722 def __repr__(self): 723 return "Text(%r)" % self.s 724 725 def prettyprint(self, indent=""): 726 return "%sText: %r" % (indent, self.s) 727 728 def to_string(self, out): 729 out.text(self.s) 730 731 # vim: tabstop=4 expandtab shiftwidth=4