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