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