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 TableCell(Container): 300 301 "A table cell." 302 303 def __init__(self, nodes, attrs=None): 304 Container.__init__(self, nodes) 305 self.attrs = attrs 306 307 def __repr__(self): 308 return "TableCell(%r, %f)" % (self.nodes, self.attrs) 309 310 def prettyprint(self, indent=""): 311 l = ["%sTableCell:" % indent] 312 if self.attrs: 313 l.append(self.attrs.prettyprint(indent + " ")) 314 return self._prettyprint(l, indent) 315 316 def to_string(self, out): 317 out.start_table_cell(self.attrs) 318 self._to_string(out) 319 out.end_table_cell() 320 321 class TableRow(Container): 322 323 "A table row." 324 325 def __init__(self, nodes, trailing=""): 326 Container.__init__(self, nodes) 327 self.trailing = trailing 328 329 def __repr__(self): 330 return "TableRow(%r, %r)" % (self.nodes, self.trailing) 331 332 def prettyprint(self, indent=""): 333 l = ["%sTableRow: trailing=%r" % (indent, self.trailing)] 334 return self._prettyprint(l, indent) 335 336 def to_string(self, out): 337 out.start_table_row() 338 self._to_string(out) 339 out.end_table_row(self.trailing) 340 341 342 343 class Inline(Container): 344 345 "Generic inline formatting." 346 347 def __repr__(self): 348 return "%s(%r)" % (self.__class__.__name__, self.nodes) 349 350 def prettyprint(self, indent=""): 351 l = ["%s%s" % (indent, self.__class__.__name__)] 352 return self._prettyprint(l, indent) 353 354 class Larger(Inline): 355 356 "Larger text." 357 358 def to_string(self, out): 359 out.start_larger() 360 self._to_string(out) 361 out.end_larger() 362 363 class Monospace(Inline): 364 365 "Monospaced text." 366 367 def to_string(self, out): 368 out.start_monospace() 369 self._to_string(out) 370 out.end_monospace() 371 372 class Smaller(Inline): 373 374 "Smaller text." 375 376 def to_string(self, out): 377 out.start_smaller() 378 self._to_string(out) 379 out.end_smaller() 380 381 class Subscript(Inline): 382 383 "Subscripted text." 384 385 def to_string(self, out): 386 out.start_subscript() 387 self._to_string(out) 388 out.end_subscript() 389 390 class Superscript(Inline): 391 392 "Superscripted text." 393 394 def to_string(self, out): 395 out.start_superscript() 396 self._to_string(out) 397 out.end_superscript() 398 399 class Underline(Inline): 400 401 "Underlined text." 402 403 def to_string(self, out): 404 out.start_underline() 405 self._to_string(out) 406 out.end_underline() 407 408 409 410 class Node: 411 412 "A document node without children." 413 414 def empty(self): 415 return False 416 417 class Break(Node): 418 419 "A paragraph break." 420 421 def __repr__(self): 422 return "Break()" 423 424 def prettyprint(self, indent=""): 425 return "%sBreak" % indent 426 427 def to_string(self, out): 428 out.break_() 429 430 class Rule(Node): 431 432 "A horizontal rule." 433 434 def __init__(self, length): 435 self.length = length 436 437 def __repr__(self): 438 return "Rule(%d)" % self.length 439 440 def prettyprint(self, indent=""): 441 return "%sRule: length=%d" % (indent, self.length) 442 443 def to_string(self, out): 444 out.rule(self.length) 445 446 class TableAttr(Node): 447 448 "A table attribute." 449 450 def __init__(self, name, value=None, concise=False, quote=None): 451 self.name = name 452 self.value = value 453 self.concise = concise 454 self.quote = quote 455 456 def __repr__(self): 457 return "TableAttr(%r, %r, %r, %r)" % (self.name, self.value, self.concise, self.quote) 458 459 def prettyprint(self, indent=""): 460 return "%sTableAttr: name=%r value=%r concise=%r quote=%r" % (indent, self.name, self.value, self.concise, self.quote) 461 462 def to_string(self, out): 463 out.table_attr(self.name, self.value, self.concise, self.quote) 464 465 class Text(Node): 466 467 "A text node." 468 469 def __init__(self, s): 470 self.s = s 471 472 def empty(self): 473 return not self.s 474 475 def merge(self, text): 476 self.s += text.s 477 478 def __repr__(self): 479 return "Text(%r)" % self.s 480 481 def prettyprint(self, indent=""): 482 return "%sText: %r" % (indent, self.s) 483 484 def to_string(self, out): 485 out.text(self.s) 486 487 # vim: tabstop=4 expandtab shiftwidth=4