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