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