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