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, transparent=True, extra=None): 150 Container.__init__(self, nodes) 151 self.level = level 152 self.indent = indent 153 self.type = type 154 self.transparent = transparent 155 self.extra = extra 156 157 def add(self, node): 158 last = self.node(-1) 159 if last and last.empty(): 160 self.nodes[-1] = node 161 else: 162 self.append(node) 163 164 def append_point(self): 165 166 "Return the container to which inline nodes are added." 167 168 if self.transparent: 169 return self.nodes[-1] 170 else: 171 return self 172 173 def have_end(self, s): 174 return self.level and s.startswith("}") and self.level == len(s) 175 176 def __repr__(self): 177 return "Region(%r, %r, %r, %r, %r, %r)" % (self.nodes, self.level, 178 self.indent, self.type, self.transparent, self.extra) 179 180 def prettyprint(self, indent=""): 181 l = ["%sRegion: level=%d indent=%d type=%s extra=%r" % (indent, 182 self.level, self.indent, self.type, self.extra)] 183 return self._prettyprint(l, indent) 184 185 def to_string(self, out): 186 out.start_region(self.level, self.indent, self.type, self.extra) 187 188 # Obtain a serialiser for the region from the same format family. 189 # Retain the same serialiser if no appropriate serialiser could be 190 # obtained. 191 192 serialiser_name = "%s.%s" % (out.format, self.type) 193 serialiser = out.get_serialiser(serialiser_name) 194 195 # Serialise the region. 196 197 self._to_string(serialiser) 198 199 # End the region with the previous serialiser. 200 201 out.end_region(self.level, self.indent, self.type, self.extra) 202 203 204 205 # Block nodes. 206 207 class Block(Container): 208 209 "A block in the page." 210 211 def __repr__(self): 212 return "Block(%r)" % self.nodes 213 214 def prettyprint(self, indent=""): 215 l = ["%sBlock" % indent] 216 return self._prettyprint(l, indent) 217 218 def to_string(self, out): 219 out.start_block() 220 self._to_string(out) 221 out.end_block() 222 223 class DefItem(Container): 224 225 "A definition item." 226 227 def __init__(self, nodes, pad, extra): 228 Container.__init__(self, nodes) 229 self.pad = pad 230 self.extra = extra 231 232 def __repr__(self): 233 return "DefItem(%r, %r, %r)" % (self.nodes, self.pad, self.extra) 234 235 def prettyprint(self, indent=""): 236 l = ["%sDefItem: pad=%r extra=%r" % (indent, self.pad, self.extra)] 237 return self._prettyprint(l, indent) 238 239 def to_string(self, out): 240 out.start_defitem(self.pad, self.extra) 241 self._to_string(out) 242 out.end_defitem(self.pad, self.extra) 243 244 class DefTerm(Container): 245 246 "A definition term." 247 248 def __init__(self, nodes, pad): 249 Container.__init__(self, nodes) 250 self.pad = pad 251 252 def __repr__(self): 253 return "DefTerm(%r, %r)" % (self.nodes, self.pad) 254 255 def prettyprint(self, indent=""): 256 l = ["%sDefTerm: pad=%r" % (indent, self.pad)] 257 return self._prettyprint(l, indent) 258 259 def to_string(self, out): 260 out.start_defterm(self.pad) 261 self._to_string(out) 262 out.end_defterm(self.pad) 263 264 class FontStyle(Container): 265 266 "Emphasised and/or strong text." 267 268 def __init__(self, nodes, emphasis=False, strong=False): 269 Container.__init__(self, nodes) 270 self.emphasis = emphasis 271 self.strong = strong 272 273 def close_emphasis(self): 274 if self.strong: 275 span = FontStyle(self.nodes, emphasis=True) 276 self.nodes = [span] 277 self.emphasis = False 278 return self.strong 279 280 def close_strong(self): 281 if self.emphasis: 282 span = FontStyle(self.nodes, strong=True) 283 self.nodes = [span] 284 self.strong = False 285 return self.emphasis 286 287 def __repr__(self): 288 return "FontStyle(%r, %r, %r)" % (self.nodes, self.emphasis, self.strong) 289 290 def prettyprint(self, indent=""): 291 l = ["%sFontStyle: emphasis=%r strong=%r" % (indent, self.emphasis, self.strong)] 292 return self._prettyprint(l, indent) 293 294 def to_string(self, out): 295 if self.emphasis: 296 out.start_emphasis() 297 elif self.strong: 298 out.start_strong() 299 self._to_string(out) 300 if self.emphasis: 301 out.end_emphasis() 302 elif self.strong: 303 out.end_strong() 304 305 class Heading(Container): 306 307 "A heading." 308 309 def __init__(self, nodes, level, start_extra="", start_pad="", end_pad="", end_extra=""): 310 Container.__init__(self, nodes) 311 self.level = level 312 self.start_extra = start_extra 313 self.start_pad = start_pad 314 self.end_pad = end_pad 315 self.end_extra = end_extra 316 317 def __repr__(self): 318 return "Heading(%r, %d, %r, %r, %r, %r)" % ( 319 self.nodes, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra) 320 321 def prettyprint(self, indent=""): 322 l = ["%sHeading: level=%d start_extra=%r start_pad=%r end_pad=%r end_extra=%r" % ( 323 indent, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)] 324 return self._prettyprint(l, indent) 325 326 def to_string(self, out): 327 out.start_heading(self.level, self.start_extra, self.start_pad, self.text_content()) 328 self._to_string(out) 329 out.end_heading(self.level, self.end_pad, self.end_extra) 330 331 class List(Container): 332 333 "A list." 334 335 def __init__(self, nodes, indent, marker, num): 336 Container.__init__(self, nodes) 337 self.indent = indent 338 self.marker = marker 339 self.num = num 340 341 def __repr__(self): 342 return "List(%r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.num) 343 344 def prettyprint(self, indent=""): 345 l = ["%sList: indent=%d marker=%r num=%r" % (indent, self.indent, self.marker, self.num)] 346 return self._prettyprint(l, indent) 347 348 def to_string(self, out): 349 out.start_list(self.indent, self.marker, self.num) 350 self._to_string(out) 351 out.end_list(self.indent, self.marker, self.num) 352 353 class ListItem(Container): 354 355 "A list item." 356 357 def __init__(self, nodes, indent, marker, space, num): 358 Container.__init__(self, nodes) 359 self.indent = indent 360 self.marker = marker 361 self.space = space 362 self.num = num 363 364 # Forbid blocks within list items for simpler structure. 365 366 self.allow_blocks = False 367 368 def __repr__(self): 369 return "ListItem(%r, %r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.space, self.num) 370 371 def prettyprint(self, indent=""): 372 l = ["%sListItem: indent=%d marker=%r space=%r num=%r" % (indent, self.indent, self.marker, self.space, self.num)] 373 return self._prettyprint(l, indent) 374 375 def to_string(self, out): 376 out.start_listitem(self.indent, self.marker, self.space, self.num) 377 self._to_string(out) 378 out.end_listitem(self.indent, self.marker, self.space, self.num) 379 380 class TableAttrs(Container): 381 382 "A collection of table attributes." 383 384 def __repr__(self): 385 return "TableAttrs(%r)" % self.nodes 386 387 def prettyprint(self, indent=""): 388 l = ["%sTableAttrs:" % indent] 389 return self._prettyprint(l, indent) 390 391 def to_string(self, out): 392 out.start_table_attrs() 393 out.table_attrs(self.nodes) 394 out.end_table_attrs() 395 396 class Table(Container): 397 398 "A table." 399 400 def __repr__(self): 401 return "Table(%r)" % self.nodes 402 403 def prettyprint(self, indent=""): 404 l = ["%sTable:" % indent] 405 return self._prettyprint(l, indent) 406 407 def to_string(self, out): 408 out.start_table() 409 self._to_string(out) 410 out.end_table() 411 412 class TableCell(Container): 413 414 "A table cell." 415 416 def __init__(self, nodes, attrs=None): 417 Container.__init__(self, nodes) 418 self.attrs = attrs 419 420 def __repr__(self): 421 return "TableCell(%r, %r)" % (self.nodes, self.attrs) 422 423 def prettyprint(self, indent=""): 424 l = ["%sTableCell:" % indent] 425 return self._prettyprint(l, indent) 426 427 def to_string(self, out): 428 out.start_table_cell(self.attrs) 429 self._to_string(out) 430 out.end_table_cell() 431 432 class TableRow(Container): 433 434 "A table row." 435 436 def __init__(self, nodes, trailing=""): 437 Container.__init__(self, nodes) 438 self.trailing = trailing 439 440 def __repr__(self): 441 return "TableRow(%r, %r)" % (self.nodes, self.trailing) 442 443 def prettyprint(self, indent=""): 444 l = ["%sTableRow: trailing=%r" % (indent, self.trailing)] 445 return self._prettyprint(l, indent) 446 447 def to_string(self, out): 448 out.start_table_row() 449 self._to_string(out) 450 out.end_table_row(self.trailing) 451 452 453 454 # Inline nodes with children. 455 456 class Inline(Container): 457 458 "Generic inline formatting." 459 460 def __repr__(self): 461 return "%s(%r)" % (self.__class__.__name__, self.nodes) 462 463 def prettyprint(self, indent=""): 464 l = ["%s%s" % (indent, self.__class__.__name__)] 465 return self._prettyprint(l, indent) 466 467 class Larger(Inline): 468 469 "Larger text." 470 471 def to_string(self, out): 472 out.start_larger() 473 self._to_string(out) 474 out.end_larger() 475 476 class Link(Container): 477 478 "Link details." 479 480 def __init__(self, nodes, target): 481 Container.__init__(self, nodes) 482 self.target = target 483 484 def __repr__(self): 485 return "Link(%r, %r)" % (self.nodes, self.target) 486 487 def prettyprint(self, indent=""): 488 l = ["%sLink: target=%r" % (indent, self.target)] 489 return self._prettyprint(l, indent) 490 491 def to_string(self, out): 492 out.start_link(self.target) 493 if self.nodes: 494 out.start_linktext() 495 self._to_string(out) 496 out.end_linktext() 497 out.end_link() 498 499 class Macro(Container): 500 501 "Macro details." 502 503 def __init__(self, name, args, parent, nodes=None): 504 Container.__init__(self, nodes or []) 505 self.name = name 506 self.parent = parent 507 self.args = args 508 509 def __repr__(self): 510 return "Macro(%r, %r, %r, %r)" % (self.name, self.args, self.parent, self.nodes) 511 512 def prettyprint(self, indent=""): 513 l = ["%sMacro: name=%r args=%r" % (indent, self.name, self.args)] 514 return self._prettyprint(l, indent) 515 516 def to_string(self, out): 517 out.start_macro(self.name, self.args, self.nodes) 518 if self.nodes: 519 self._to_string(out) 520 out.end_macro() 521 522 class Monospace(Inline): 523 524 "Monospaced text." 525 526 def to_string(self, out): 527 out.start_monospace() 528 self._to_string(out) 529 out.end_monospace() 530 531 class Smaller(Inline): 532 533 "Smaller text." 534 535 def to_string(self, out): 536 out.start_smaller() 537 self._to_string(out) 538 out.end_smaller() 539 540 class Strikethrough(Inline): 541 542 "Crossed-out text." 543 544 def to_string(self, out): 545 out.start_strikethrough() 546 self._to_string(out) 547 out.end_strikethrough() 548 549 class Subscript(Inline): 550 551 "Subscripted text." 552 553 def to_string(self, out): 554 out.start_subscript() 555 self._to_string(out) 556 out.end_subscript() 557 558 class Superscript(Inline): 559 560 "Superscripted text." 561 562 def to_string(self, out): 563 out.start_superscript() 564 self._to_string(out) 565 out.end_superscript() 566 567 class Underline(Inline): 568 569 "Underlined text." 570 571 def to_string(self, out): 572 out.start_underline() 573 self._to_string(out) 574 out.end_underline() 575 576 577 578 # Nodes without children. 579 580 class Node: 581 582 "A document node without children." 583 584 def empty(self): 585 return False 586 587 class Break(Node): 588 589 "A paragraph break." 590 591 def __repr__(self): 592 return "Break()" 593 594 def prettyprint(self, indent=""): 595 return "%sBreak" % indent 596 597 def to_string(self, out): 598 out.break_() 599 600 class LineBreak(Node): 601 602 "A line break within a block." 603 604 def __repr__(self): 605 return "LineBreak()" 606 607 def prettyprint(self, indent=""): 608 return "%sLineBreak" % indent 609 610 def to_string(self, out): 611 out.linebreak() 612 613 class Rule(Node): 614 615 "A horizontal rule." 616 617 def __init__(self, length): 618 self.length = length 619 620 def __repr__(self): 621 return "Rule(%d)" % self.length 622 623 def prettyprint(self, indent=""): 624 return "%sRule: length=%d" % (indent, self.length) 625 626 def to_string(self, out): 627 out.rule(self.length) 628 629 class TableAttr(Node): 630 631 "A table attribute." 632 633 def __init__(self, name, value=None, concise=False, quote=None): 634 self.name = name 635 self.value = value 636 self.concise = concise 637 self.quote = quote 638 639 def __repr__(self): 640 return "TableAttr(%r, %r, %r, %r)" % (self.name, self.value, self.concise, self.quote) 641 642 def prettyprint(self, indent=""): 643 return "%sTableAttr: name=%r value=%r concise=%r quote=%r" % (indent, self.name, self.value, self.concise, self.quote) 644 645 def to_string(self, out): 646 out.table_attr(self.name, self.value, self.concise, self.quote) 647 648 class Text(Node): 649 650 "A text node." 651 652 def __init__(self, s): 653 self.s = s 654 655 def empty(self): 656 return not self.s 657 658 def multiline(self): 659 return "\n" in self.s 660 661 def merge(self, text): 662 self.s += text.s 663 664 def __repr__(self): 665 return "Text(%r)" % self.s 666 667 def prettyprint(self, indent=""): 668 return "%sText: %r" % (indent, self.s) 669 670 def to_string(self, out): 671 out.text(self.s) 672 673 # vim: tabstop=4 expandtab shiftwidth=4