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