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 Monospace(Inline): 457 458 "Monospaced text." 459 460 def to_string(self, out): 461 out.start_monospace() 462 self._to_string(out) 463 out.end_monospace() 464 465 class Smaller(Inline): 466 467 "Smaller text." 468 469 def to_string(self, out): 470 out.start_smaller() 471 self._to_string(out) 472 out.end_smaller() 473 474 class Strikethrough(Inline): 475 476 "Crossed-out text." 477 478 def to_string(self, out): 479 out.start_strikethrough() 480 self._to_string(out) 481 out.end_strikethrough() 482 483 class Subscript(Inline): 484 485 "Subscripted text." 486 487 def to_string(self, out): 488 out.start_subscript() 489 self._to_string(out) 490 out.end_subscript() 491 492 class Superscript(Inline): 493 494 "Superscripted text." 495 496 def to_string(self, out): 497 out.start_superscript() 498 self._to_string(out) 499 out.end_superscript() 500 501 class Underline(Inline): 502 503 "Underlined text." 504 505 def to_string(self, out): 506 out.start_underline() 507 self._to_string(out) 508 out.end_underline() 509 510 511 512 # Nodes without children. 513 514 class Node: 515 516 "A document node without children." 517 518 def empty(self): 519 return False 520 521 class Break(Node): 522 523 "A paragraph break." 524 525 def __repr__(self): 526 return "Break()" 527 528 def prettyprint(self, indent=""): 529 return "%sBreak" % indent 530 531 def to_string(self, out): 532 out.break_() 533 534 class Macro(Node): 535 536 "Macro details." 537 538 def __init__(self, name, args): 539 self.name = name 540 self.args = args 541 542 def __repr__(self): 543 return "Macro(%r, %r)" % (self.name, self.args) 544 545 def prettyprint(self, indent=""): 546 return "%sMacro: name=%r args=%r" % (indent, self.name, self.args) 547 548 def to_string(self, out): 549 out.macro(self.name, self.args) 550 551 class Rule(Node): 552 553 "A horizontal rule." 554 555 def __init__(self, length): 556 self.length = length 557 558 def __repr__(self): 559 return "Rule(%d)" % self.length 560 561 def prettyprint(self, indent=""): 562 return "%sRule: length=%d" % (indent, self.length) 563 564 def to_string(self, out): 565 out.rule(self.length) 566 567 class TableAttr(Node): 568 569 "A table attribute." 570 571 def __init__(self, name, value=None, concise=False, quote=None): 572 self.name = name 573 self.value = value 574 self.concise = concise 575 self.quote = quote 576 577 def __repr__(self): 578 return "TableAttr(%r, %r, %r, %r)" % (self.name, self.value, self.concise, self.quote) 579 580 def prettyprint(self, indent=""): 581 return "%sTableAttr: name=%r value=%r concise=%r quote=%r" % (indent, self.name, self.value, self.concise, self.quote) 582 583 def to_string(self, out): 584 out.table_attr(self.name, self.value, self.concise, self.quote) 585 586 class Text(Node): 587 588 "A text node." 589 590 def __init__(self, s): 591 self.s = s 592 593 def empty(self): 594 return not self.s 595 596 def multiline(self): 597 return "\n" in self.s 598 599 def merge(self, text): 600 self.s += text.s 601 602 def __repr__(self): 603 return "Text(%r)" % self.s 604 605 def prettyprint(self, indent=""): 606 return "%sText: %r" % (indent, self.s) 607 608 def to_string(self, out): 609 out.text(self.s) 610 611 # vim: tabstop=4 expandtab shiftwidth=4