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