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