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 def append(self, node): 30 self.nodes.append(node) 31 32 def append_many(self, nodes): 33 for node in nodes: 34 self.append(node) 35 36 add = append 37 38 append_inline = append 39 40 def append_inline_many(self, nodes): 41 for node in nodes: 42 self.append_inline(node) 43 44 def empty(self): 45 return not self.nodes 46 47 def node(self, index): 48 try: 49 return self.nodes[index] 50 except IndexError: 51 return None 52 53 def normalise(self): 54 55 "Combine adjacent text nodes." 56 57 nodes = self.nodes 58 self.nodes = [] 59 text = None 60 61 for node in nodes: 62 63 # Open a text node or merge text into an open node. 64 65 if isinstance(node, Text): 66 if not text: 67 text = node 68 else: 69 text.merge(node) 70 71 # Close any open text node and append the current node. 72 73 else: 74 if text: 75 self.append(text) 76 text = None 77 self.append(node) 78 79 # Add any open text node. 80 81 if text: 82 self.append(text) 83 84 def __str__(self): 85 return self.prettyprint() 86 87 def _prettyprint(self, l, indent=""): 88 for node in self.nodes: 89 l.append(node.prettyprint(indent + " ")) 90 return "\n".join(l) 91 92 def _to_string(self, out): 93 for node in self.nodes: 94 node.to_string(out) 95 96 class Region(Container): 97 98 "A region of the page." 99 100 def __init__(self, nodes, level=0, indent=0, type=None, transparent=True): 101 Container.__init__(self, nodes) 102 self.level = level 103 self.indent = indent 104 self.type = type 105 self.transparent = transparent 106 107 def add(self, node): 108 last = self.node(-1) 109 if last and last.empty(): 110 self.nodes[-1] = node 111 else: 112 self.append(node) 113 114 def append_inline(self, node): 115 if self.transparent: 116 self.nodes[-1].append(node) 117 else: 118 self.append(node) 119 120 def have_end(self, s): 121 return self.level and s.startswith("}") and self.level == len(s) 122 123 def __repr__(self): 124 return "Region(%r, %r, %r, %r)" % (self.nodes, self.level, self.indent, self.type) 125 126 def prettyprint(self, indent=""): 127 l = ["%sRegion: level=%d indent=%d type=%s" % (indent, self.level, self.indent, self.type)] 128 return self._prettyprint(l, indent) 129 130 def to_string(self, out): 131 out.start_region(self.level, self.indent, self.type) 132 133 # Obtain a serialiser for the region, if appropriate. 134 135 serialiser = out.formats and out.formats.get(self.type) 136 region_out = serialiser and serialiser(out.out) or out 137 138 # Serialise the region. 139 140 self._to_string(region_out) 141 142 out.end_region(self.level, self.indent, self.type) 143 144 145 146 class Block(Container): 147 148 "A block in the page." 149 150 def __repr__(self): 151 return "Block(%r)" % self.nodes 152 153 def prettyprint(self, indent=""): 154 l = ["%sBlock" % indent] 155 return self._prettyprint(l, indent) 156 157 def to_string(self, out): 158 out.start_block() 159 self._to_string(out) 160 out.end_block() 161 162 class DefItem(Container): 163 164 "A definition item." 165 166 def __init__(self, nodes, pad, extra): 167 Container.__init__(self, nodes) 168 self.pad = pad 169 self.extra = extra 170 171 def __repr__(self): 172 return "DefItem(%r, %r, %r)" % (self.nodes, self.pad, self.extra) 173 174 def prettyprint(self, indent=""): 175 l = ["%sDefItem: pad=%r extra=%r" % (indent, self.pad, self.extra)] 176 return self._prettyprint(l, indent) 177 178 def to_string(self, out): 179 out.start_defitem(self.pad, self.extra) 180 self._to_string(out) 181 out.end_defitem(self.pad, self.extra) 182 183 class DefTerm(Container): 184 185 "A definition term." 186 187 def __init__(self, nodes, pad): 188 Container.__init__(self, nodes) 189 self.pad = pad 190 191 def __repr__(self): 192 return "DefTerm(%r, %r)" % (self.nodes, self.pad) 193 194 def prettyprint(self, indent=""): 195 l = ["%sDefTerm: pad=%r" % (indent, self.pad)] 196 return self._prettyprint(l, indent) 197 198 def to_string(self, out): 199 out.start_defterm(self.pad) 200 self._to_string(out) 201 out.end_defterm(self.pad) 202 203 class FontStyle(Container): 204 205 "Emphasised and/or strong text." 206 207 def __init__(self, nodes, emphasis=False, strong=False): 208 Container.__init__(self, nodes) 209 self.emphasis = emphasis 210 self.strong = strong 211 212 def close_emphasis(self): 213 if self.strong: 214 span = FontStyle(self.nodes, emphasis=True) 215 self.nodes = [span] 216 self.emphasis = False 217 return self.strong 218 219 def close_strong(self): 220 if self.emphasis: 221 span = FontStyle(self.nodes, strong=True) 222 self.nodes = [span] 223 self.strong = False 224 return self.emphasis 225 226 def __repr__(self): 227 return "FontStyle(%r, %r, %r)" % (self.nodes, self.emphasis, self.strong) 228 229 def prettyprint(self, indent=""): 230 l = ["%sFontStyle: emphasis=%r strong=%r" % (indent, self.emphasis, self.strong)] 231 return self._prettyprint(l, indent) 232 233 def to_string(self, out): 234 if self.emphasis: 235 out.start_emphasis() 236 elif self.strong: 237 out.start_strong() 238 self._to_string(out) 239 if self.emphasis: 240 out.end_emphasis() 241 elif self.strong: 242 out.end_strong() 243 244 class Heading(Container): 245 246 "A heading." 247 248 def __init__(self, nodes, level, start_extra="", start_pad="", end_pad="", end_extra=""): 249 Container.__init__(self, nodes) 250 self.level = level 251 self.start_extra = start_extra 252 self.start_pad = start_pad 253 self.end_pad = end_pad 254 self.end_extra = end_extra 255 256 def __repr__(self): 257 return "Heading(%r, %d, %r, %r, %r, %r)" % ( 258 self.nodes, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra) 259 260 def prettyprint(self, indent=""): 261 l = ["%sHeading: level=%d start_extra=%r start_pad=%r end_pad=%r end_extra=%r" % ( 262 indent, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)] 263 return self._prettyprint(l, indent) 264 265 def to_string(self, out): 266 out.start_heading(self.level, self.start_extra, self.start_pad) 267 self._to_string(out) 268 out.end_heading(self.level, self.end_pad, self.end_extra) 269 270 class List(Container): 271 272 "A list." 273 274 def __init__(self, nodes, indent, marker, num): 275 Container.__init__(self, nodes) 276 self.indent = indent 277 self.marker = marker 278 self.num = num 279 280 def __repr__(self): 281 return "List(%r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.num) 282 283 def prettyprint(self, indent=""): 284 l = ["%sList: indent=%d marker=%r num=%r" % (indent, self.indent, self.marker, self.num)] 285 return self._prettyprint(l, indent) 286 287 def to_string(self, out): 288 out.start_list(self.indent, self.marker, self.num) 289 self._to_string(out) 290 out.end_list(self.indent, self.marker, self.num) 291 292 class ListItem(Container): 293 294 "A list item." 295 296 def __init__(self, nodes, indent, marker, space, num): 297 Container.__init__(self, nodes) 298 self.indent = indent 299 self.marker = marker 300 self.space = space 301 self.num = num 302 303 def __repr__(self): 304 return "ListItem(%r, %r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.space, self.num) 305 306 def prettyprint(self, indent=""): 307 l = ["%sListItem: indent=%d marker=%r space=%r num=%r" % (indent, self.indent, self.marker, self.space, self.num)] 308 return self._prettyprint(l, indent) 309 310 def to_string(self, out): 311 out.start_listitem(self.indent, self.marker, self.space, self.num) 312 self._to_string(out) 313 out.end_listitem(self.indent, self.marker, self.space, self.num) 314 315 class TableAttrs(Container): 316 317 "A collection of table attributes." 318 319 def __repr__(self): 320 return "TableAttrs(%r)" % self.nodes 321 322 def prettyprint(self, indent=""): 323 l = ["%sTableAttrs:" % indent] 324 return self._prettyprint(l, indent) 325 326 def to_string(self, out): 327 out.start_table_attrs() 328 self._to_string(out) 329 out.end_table_attrs() 330 331 class Table(Container): 332 333 "A table." 334 335 def __repr__(self): 336 return "Table(%r)" % self.nodes 337 338 def prettyprint(self, indent=""): 339 l = ["%sTable:" % indent] 340 return self._prettyprint(l, indent) 341 342 def to_string(self, out): 343 out.start_table() 344 self._to_string(out) 345 out.end_table() 346 347 class TableCell(Container): 348 349 "A table cell." 350 351 def __init__(self, nodes, attrs=None): 352 Container.__init__(self, nodes) 353 self.attrs = attrs 354 355 def __repr__(self): 356 return "TableCell(%r, %f)" % (self.nodes, self.attrs) 357 358 def prettyprint(self, indent=""): 359 l = ["%sTableCell:" % indent] 360 if self.attrs: 361 l.append(self.attrs.prettyprint(indent + " ")) 362 return self._prettyprint(l, indent) 363 364 def to_string(self, out): 365 out.start_table_cell(self.attrs) 366 self._to_string(out) 367 out.end_table_cell() 368 369 class TableRow(Container): 370 371 "A table row." 372 373 def __init__(self, nodes, trailing=""): 374 Container.__init__(self, nodes) 375 self.trailing = trailing 376 377 def __repr__(self): 378 return "TableRow(%r, %r)" % (self.nodes, self.trailing) 379 380 def prettyprint(self, indent=""): 381 l = ["%sTableRow: trailing=%r" % (indent, self.trailing)] 382 return self._prettyprint(l, indent) 383 384 def to_string(self, out): 385 out.start_table_row() 386 self._to_string(out) 387 out.end_table_row(self.trailing) 388 389 390 391 class Inline(Container): 392 393 "Generic inline formatting." 394 395 def __repr__(self): 396 return "%s(%r)" % (self.__class__.__name__, self.nodes) 397 398 def prettyprint(self, indent=""): 399 l = ["%s%s" % (indent, self.__class__.__name__)] 400 return self._prettyprint(l, indent) 401 402 class Larger(Inline): 403 404 "Larger text." 405 406 def to_string(self, out): 407 out.start_larger() 408 self._to_string(out) 409 out.end_larger() 410 411 class Link(Container): 412 413 "Link details." 414 415 def __init__(self, nodes, target): 416 Container.__init__(self, nodes) 417 self.target = target 418 419 def __repr__(self): 420 return "Link(%r, %r)" % (self.nodes, self.target) 421 422 def prettyprint(self, indent=""): 423 l = ["%sLink: target=%r" % (indent, self.target)] 424 return self._prettyprint(l, indent) 425 426 def to_string(self, out): 427 out.start_link(self.target) 428 if self.nodes: 429 out.start_linktext() 430 self._to_string(out) 431 out.end_linktext() 432 out.end_link() 433 434 class Monospace(Inline): 435 436 "Monospaced text." 437 438 def to_string(self, out): 439 out.start_monospace() 440 self._to_string(out) 441 out.end_monospace() 442 443 class Smaller(Inline): 444 445 "Smaller text." 446 447 def to_string(self, out): 448 out.start_smaller() 449 self._to_string(out) 450 out.end_smaller() 451 452 class Strikethrough(Inline): 453 454 "Crossed-out text." 455 456 def to_string(self, out): 457 out.start_strikethrough() 458 self._to_string(out) 459 out.end_strikethrough() 460 461 class Subscript(Inline): 462 463 "Subscripted text." 464 465 def to_string(self, out): 466 out.start_subscript() 467 self._to_string(out) 468 out.end_subscript() 469 470 class Superscript(Inline): 471 472 "Superscripted text." 473 474 def to_string(self, out): 475 out.start_superscript() 476 self._to_string(out) 477 out.end_superscript() 478 479 class Underline(Inline): 480 481 "Underlined text." 482 483 def to_string(self, out): 484 out.start_underline() 485 self._to_string(out) 486 out.end_underline() 487 488 489 490 class Node: 491 492 "A document node without children." 493 494 def empty(self): 495 return False 496 497 class Break(Node): 498 499 "A paragraph break." 500 501 def __repr__(self): 502 return "Break()" 503 504 def prettyprint(self, indent=""): 505 return "%sBreak" % indent 506 507 def to_string(self, out): 508 out.break_() 509 510 class Rule(Node): 511 512 "A horizontal rule." 513 514 def __init__(self, length): 515 self.length = length 516 517 def __repr__(self): 518 return "Rule(%d)" % self.length 519 520 def prettyprint(self, indent=""): 521 return "%sRule: length=%d" % (indent, self.length) 522 523 def to_string(self, out): 524 out.rule(self.length) 525 526 class TableAttr(Node): 527 528 "A table attribute." 529 530 def __init__(self, name, value=None, concise=False, quote=None): 531 self.name = name 532 self.value = value 533 self.concise = concise 534 self.quote = quote 535 536 def __repr__(self): 537 return "TableAttr(%r, %r, %r, %r)" % (self.name, self.value, self.concise, self.quote) 538 539 def prettyprint(self, indent=""): 540 return "%sTableAttr: name=%r value=%r concise=%r quote=%r" % (indent, self.name, self.value, self.concise, self.quote) 541 542 def to_string(self, out): 543 out.table_attr(self.name, self.value, self.concise, self.quote) 544 545 class Text(Node): 546 547 "A text node." 548 549 def __init__(self, s): 550 self.s = s 551 552 def empty(self): 553 return not self.s 554 555 def merge(self, text): 556 self.s += text.s 557 558 def __repr__(self): 559 return "Text(%r)" % self.s 560 561 def prettyprint(self, indent=""): 562 return "%sText: %r" % (indent, self.s) 563 564 def to_string(self, out): 565 out.text(self.s) 566 567 # vim: tabstop=4 expandtab shiftwidth=4