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 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 self._to_string(out) 133 out.end_region(self.level, self.indent, self.type) 134 135 136 137 class Block(Container): 138 139 "A block in the page." 140 141 def __repr__(self): 142 return "Block(%r)" % self.nodes 143 144 def prettyprint(self, indent=""): 145 l = ["%sBlock" % indent] 146 return self._prettyprint(l, indent) 147 148 def to_string(self, out): 149 out.start_block() 150 self._to_string(out) 151 out.end_block() 152 153 class DefItem(Container): 154 155 "A definition item." 156 157 def __init__(self, nodes, pad, extra): 158 Container.__init__(self, nodes) 159 self.pad = pad 160 self.extra = extra 161 162 def __repr__(self): 163 return "DefItem(%r, %r, %r)" % (self.nodes, self.pad, self.extra) 164 165 def prettyprint(self, indent=""): 166 l = ["%sDefItem: pad=%r extra=%r" % (indent, self.pad, self.extra)] 167 return self._prettyprint(l, indent) 168 169 def to_string(self, out): 170 out.start_defitem(self.pad, self.extra) 171 self._to_string(out) 172 out.end_defitem(self.pad, self.extra) 173 174 class DefTerm(Container): 175 176 "A definition term." 177 178 def __init__(self, nodes, pad): 179 Container.__init__(self, nodes) 180 self.pad = pad 181 182 def __repr__(self): 183 return "DefTerm(%r, %r)" % (self.nodes, self.pad) 184 185 def prettyprint(self, indent=""): 186 l = ["%sDefTerm: pad=%r" % (indent, self.pad)] 187 return self._prettyprint(l, indent) 188 189 def to_string(self, out): 190 out.start_defterm(self.pad) 191 self._to_string(out) 192 out.end_defterm(self.pad) 193 194 class FontStyle(Container): 195 196 "Emphasised and/or strong text." 197 198 def __init__(self, nodes, emphasis=False, strong=False): 199 Container.__init__(self, nodes) 200 self.emphasis = emphasis 201 self.strong = strong 202 203 def close_emphasis(self): 204 if self.strong: 205 span = FontStyle(self.nodes, emphasis=True) 206 self.nodes = [span] 207 self.emphasis = False 208 return self.strong 209 210 def close_strong(self): 211 if self.emphasis: 212 span = FontStyle(self.nodes, strong=True) 213 self.nodes = [span] 214 self.strong = False 215 return self.emphasis 216 217 def __repr__(self): 218 return "FontStyle(%r, %r, %r)" % (self.nodes, self.emphasis, self.strong) 219 220 def prettyprint(self, indent=""): 221 l = ["%sFontStyle: emphasis=%r strong=%r" % (indent, self.emphasis, self.strong)] 222 return self._prettyprint(l, indent) 223 224 def to_string(self, out): 225 if self.emphasis: 226 out.start_emphasis() 227 elif self.strong: 228 out.start_strong() 229 self._to_string(out) 230 if self.emphasis: 231 out.end_emphasis() 232 elif self.strong: 233 out.end_strong() 234 235 class Heading(Container): 236 237 "A heading." 238 239 def __init__(self, nodes, level, start_extra="", start_pad="", end_pad="", end_extra=""): 240 Container.__init__(self, nodes) 241 self.level = level 242 self.start_extra = start_extra 243 self.start_pad = start_pad 244 self.end_pad = end_pad 245 self.end_extra = end_extra 246 247 def __repr__(self): 248 return "Heading(%r, %d, %r, %r, %r, %r)" % ( 249 self.nodes, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra) 250 251 def prettyprint(self, indent=""): 252 l = ["%sHeading: level=%d start_extra=%r start_pad=%r end_pad=%r end_extra=%r" % ( 253 indent, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)] 254 return self._prettyprint(l, indent) 255 256 def to_string(self, out): 257 out.start_heading(self.level, self.start_extra, self.start_pad) 258 self._to_string(out) 259 out.end_heading(self.level, self.end_pad, self.end_extra) 260 261 class ListItem(Container): 262 263 "A list item." 264 265 def __init__(self, nodes, indent, marker, space): 266 Container.__init__(self, nodes) 267 self.indent = indent 268 self.marker = marker 269 self.space = space 270 271 def __repr__(self): 272 return "ListItem(%r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.space) 273 274 def prettyprint(self, indent=""): 275 l = ["%sListItem: indent=%d marker=%r space=%r" % (indent, self.indent, self.marker, self.space)] 276 return self._prettyprint(l, indent) 277 278 def to_string(self, out): 279 out.start_listitem(self.indent, self.marker, self.space) 280 self._to_string(out) 281 out.end_listitem(self.indent, self.marker) 282 283 class TableAttrs(Container): 284 285 "A collection of table attributes." 286 287 def __repr__(self): 288 return "TableAttrs(%r)" % self.nodes 289 290 def prettyprint(self, indent=""): 291 l = ["%sTableAttrs:" % indent] 292 return self._prettyprint(l, indent) 293 294 def to_string(self, out): 295 out.start_table_attrs() 296 self._to_string(out) 297 out.end_table_attrs() 298 299 class Table(Container): 300 301 "A table." 302 303 def __repr__(self): 304 return "Table(%r)" % self.nodes 305 306 def prettyprint(self, indent=""): 307 l = ["%sTable:" % indent] 308 return self._prettyprint(l, indent) 309 310 def to_string(self, out): 311 out.start_table() 312 self._to_string(out) 313 out.end_table() 314 315 class TableCell(Container): 316 317 "A table cell." 318 319 def __init__(self, nodes, attrs=None): 320 Container.__init__(self, nodes) 321 self.attrs = attrs 322 323 def __repr__(self): 324 return "TableCell(%r, %f)" % (self.nodes, self.attrs) 325 326 def prettyprint(self, indent=""): 327 l = ["%sTableCell:" % indent] 328 if self.attrs: 329 l.append(self.attrs.prettyprint(indent + " ")) 330 return self._prettyprint(l, indent) 331 332 def to_string(self, out): 333 out.start_table_cell(self.attrs) 334 self._to_string(out) 335 out.end_table_cell() 336 337 class TableRow(Container): 338 339 "A table row." 340 341 def __init__(self, nodes, trailing=""): 342 Container.__init__(self, nodes) 343 self.trailing = trailing 344 345 def __repr__(self): 346 return "TableRow(%r, %r)" % (self.nodes, self.trailing) 347 348 def prettyprint(self, indent=""): 349 l = ["%sTableRow: trailing=%r" % (indent, self.trailing)] 350 return self._prettyprint(l, indent) 351 352 def to_string(self, out): 353 out.start_table_row() 354 self._to_string(out) 355 out.end_table_row(self.trailing) 356 357 358 359 class Inline(Container): 360 361 "Generic inline formatting." 362 363 def __repr__(self): 364 return "%s(%r)" % (self.__class__.__name__, self.nodes) 365 366 def prettyprint(self, indent=""): 367 l = ["%s%s" % (indent, self.__class__.__name__)] 368 return self._prettyprint(l, indent) 369 370 class Larger(Inline): 371 372 "Larger text." 373 374 def to_string(self, out): 375 out.start_larger() 376 self._to_string(out) 377 out.end_larger() 378 379 class Monospace(Inline): 380 381 "Monospaced text." 382 383 def to_string(self, out): 384 out.start_monospace() 385 self._to_string(out) 386 out.end_monospace() 387 388 class Smaller(Inline): 389 390 "Smaller text." 391 392 def to_string(self, out): 393 out.start_smaller() 394 self._to_string(out) 395 out.end_smaller() 396 397 class Subscript(Inline): 398 399 "Subscripted text." 400 401 def to_string(self, out): 402 out.start_subscript() 403 self._to_string(out) 404 out.end_subscript() 405 406 class Superscript(Inline): 407 408 "Superscripted text." 409 410 def to_string(self, out): 411 out.start_superscript() 412 self._to_string(out) 413 out.end_superscript() 414 415 class Underline(Inline): 416 417 "Underlined text." 418 419 def to_string(self, out): 420 out.start_underline() 421 self._to_string(out) 422 out.end_underline() 423 424 425 426 class Node: 427 428 "A document node without children." 429 430 def empty(self): 431 return False 432 433 class Break(Node): 434 435 "A paragraph break." 436 437 def __repr__(self): 438 return "Break()" 439 440 def prettyprint(self, indent=""): 441 return "%sBreak" % indent 442 443 def to_string(self, out): 444 out.break_() 445 446 class Rule(Node): 447 448 "A horizontal rule." 449 450 def __init__(self, length): 451 self.length = length 452 453 def __repr__(self): 454 return "Rule(%d)" % self.length 455 456 def prettyprint(self, indent=""): 457 return "%sRule: length=%d" % (indent, self.length) 458 459 def to_string(self, out): 460 out.rule(self.length) 461 462 class TableAttr(Node): 463 464 "A table attribute." 465 466 def __init__(self, name, value=None, concise=False, quote=None): 467 self.name = name 468 self.value = value 469 self.concise = concise 470 self.quote = quote 471 472 def __repr__(self): 473 return "TableAttr(%r, %r, %r, %r)" % (self.name, self.value, self.concise, self.quote) 474 475 def prettyprint(self, indent=""): 476 return "%sTableAttr: name=%r value=%r concise=%r quote=%r" % (indent, self.name, self.value, self.concise, self.quote) 477 478 def to_string(self, out): 479 out.table_attr(self.name, self.value, self.concise, self.quote) 480 481 class Text(Node): 482 483 "A text node." 484 485 def __init__(self, s): 486 self.s = s 487 488 def empty(self): 489 return not self.s 490 491 def merge(self, text): 492 self.s += text.s 493 494 def __repr__(self): 495 return "Text(%r)" % self.s 496 497 def prettyprint(self, indent=""): 498 return "%sText: %r" % (indent, self.s) 499 500 def to_string(self, out): 501 out.text(self.s) 502 503 # vim: tabstop=4 expandtab shiftwidth=4