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): 275 Container.__init__(self, nodes) 276 self.indent = indent 277 self.marker = marker 278 279 def __repr__(self): 280 return "List(%r, %r, %r)" % (self.nodes, self.indent, self.marker) 281 282 def prettyprint(self, indent=""): 283 l = ["%sList: indent=%d marker=%r" % (indent, self.indent, self.marker)] 284 return self._prettyprint(l, indent) 285 286 def to_string(self, out): 287 out.start_list() 288 self._to_string(out) 289 out.end_list() 290 291 class ListItem(Container): 292 293 "A list item." 294 295 def __init__(self, nodes, indent, marker, space): 296 Container.__init__(self, nodes) 297 self.indent = indent 298 self.marker = marker 299 self.space = space 300 301 def __repr__(self): 302 return "ListItem(%r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.space) 303 304 def prettyprint(self, indent=""): 305 l = ["%sListItem: indent=%d marker=%r space=%r" % (indent, self.indent, self.marker, self.space)] 306 return self._prettyprint(l, indent) 307 308 def to_string(self, out): 309 out.start_listitem(self.indent, self.marker, self.space) 310 self._to_string(out) 311 out.end_listitem(self.indent, self.marker) 312 313 class TableAttrs(Container): 314 315 "A collection of table attributes." 316 317 def __repr__(self): 318 return "TableAttrs(%r)" % self.nodes 319 320 def prettyprint(self, indent=""): 321 l = ["%sTableAttrs:" % indent] 322 return self._prettyprint(l, indent) 323 324 def to_string(self, out): 325 out.start_table_attrs() 326 self._to_string(out) 327 out.end_table_attrs() 328 329 class Table(Container): 330 331 "A table." 332 333 def __repr__(self): 334 return "Table(%r)" % self.nodes 335 336 def prettyprint(self, indent=""): 337 l = ["%sTable:" % indent] 338 return self._prettyprint(l, indent) 339 340 def to_string(self, out): 341 out.start_table() 342 self._to_string(out) 343 out.end_table() 344 345 class TableCell(Container): 346 347 "A table cell." 348 349 def __init__(self, nodes, attrs=None): 350 Container.__init__(self, nodes) 351 self.attrs = attrs 352 353 def __repr__(self): 354 return "TableCell(%r, %f)" % (self.nodes, self.attrs) 355 356 def prettyprint(self, indent=""): 357 l = ["%sTableCell:" % indent] 358 if self.attrs: 359 l.append(self.attrs.prettyprint(indent + " ")) 360 return self._prettyprint(l, indent) 361 362 def to_string(self, out): 363 out.start_table_cell(self.attrs) 364 self._to_string(out) 365 out.end_table_cell() 366 367 class TableRow(Container): 368 369 "A table row." 370 371 def __init__(self, nodes, trailing=""): 372 Container.__init__(self, nodes) 373 self.trailing = trailing 374 375 def __repr__(self): 376 return "TableRow(%r, %r)" % (self.nodes, self.trailing) 377 378 def prettyprint(self, indent=""): 379 l = ["%sTableRow: trailing=%r" % (indent, self.trailing)] 380 return self._prettyprint(l, indent) 381 382 def to_string(self, out): 383 out.start_table_row() 384 self._to_string(out) 385 out.end_table_row(self.trailing) 386 387 388 389 class Inline(Container): 390 391 "Generic inline formatting." 392 393 def __repr__(self): 394 return "%s(%r)" % (self.__class__.__name__, self.nodes) 395 396 def prettyprint(self, indent=""): 397 l = ["%s%s" % (indent, self.__class__.__name__)] 398 return self._prettyprint(l, indent) 399 400 class Larger(Inline): 401 402 "Larger text." 403 404 def to_string(self, out): 405 out.start_larger() 406 self._to_string(out) 407 out.end_larger() 408 409 class Link(Container): 410 411 "Link details." 412 413 def __init__(self, nodes, target): 414 Container.__init__(self, nodes) 415 self.target = target 416 417 def __repr__(self): 418 return "Link(%r, %r)" % (self.nodes, self.target) 419 420 def prettyprint(self, indent=""): 421 l = ["%sLink: target=%r" % (indent, self.target)] 422 return self._prettyprint(l, indent) 423 424 def to_string(self, out): 425 out.start_link(self.target) 426 if self.nodes: 427 out.start_linktext() 428 self._to_string(out) 429 out.end_linktext() 430 out.end_link() 431 432 class Monospace(Inline): 433 434 "Monospaced text." 435 436 def to_string(self, out): 437 out.start_monospace() 438 self._to_string(out) 439 out.end_monospace() 440 441 class Smaller(Inline): 442 443 "Smaller text." 444 445 def to_string(self, out): 446 out.start_smaller() 447 self._to_string(out) 448 out.end_smaller() 449 450 class Subscript(Inline): 451 452 "Subscripted text." 453 454 def to_string(self, out): 455 out.start_subscript() 456 self._to_string(out) 457 out.end_subscript() 458 459 class Superscript(Inline): 460 461 "Superscripted text." 462 463 def to_string(self, out): 464 out.start_superscript() 465 self._to_string(out) 466 out.end_superscript() 467 468 class Underline(Inline): 469 470 "Underlined text." 471 472 def to_string(self, out): 473 out.start_underline() 474 self._to_string(out) 475 out.end_underline() 476 477 478 479 class Node: 480 481 "A document node without children." 482 483 def empty(self): 484 return False 485 486 class Break(Node): 487 488 "A paragraph break." 489 490 def __repr__(self): 491 return "Break()" 492 493 def prettyprint(self, indent=""): 494 return "%sBreak" % indent 495 496 def to_string(self, out): 497 out.break_() 498 499 class Rule(Node): 500 501 "A horizontal rule." 502 503 def __init__(self, length): 504 self.length = length 505 506 def __repr__(self): 507 return "Rule(%d)" % self.length 508 509 def prettyprint(self, indent=""): 510 return "%sRule: length=%d" % (indent, self.length) 511 512 def to_string(self, out): 513 out.rule(self.length) 514 515 class TableAttr(Node): 516 517 "A table attribute." 518 519 def __init__(self, name, value=None, concise=False, quote=None): 520 self.name = name 521 self.value = value 522 self.concise = concise 523 self.quote = quote 524 525 def __repr__(self): 526 return "TableAttr(%r, %r, %r, %r)" % (self.name, self.value, self.concise, self.quote) 527 528 def prettyprint(self, indent=""): 529 return "%sTableAttr: name=%r value=%r concise=%r quote=%r" % (indent, self.name, self.value, self.concise, self.quote) 530 531 def to_string(self, out): 532 out.table_attr(self.name, self.value, self.concise, self.quote) 533 534 class Text(Node): 535 536 "A text node." 537 538 def __init__(self, s): 539 self.s = s 540 541 def empty(self): 542 return not self.s 543 544 def merge(self, text): 545 self.s += text.s 546 547 def __repr__(self): 548 return "Text(%r)" % self.s 549 550 def prettyprint(self, indent=""): 551 return "%sText: %r" % (indent, self.s) 552 553 def to_string(self, out): 554 out.text(self.s) 555 556 # vim: tabstop=4 expandtab shiftwidth=4