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