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 add = append 33 34 append_inline = append 35 36 def empty(self): 37 return not self.nodes 38 39 def node(self, index): 40 try: 41 return self.nodes[index] 42 except IndexError: 43 return None 44 45 def normalise(self): 46 47 "Combine adjacent text nodes." 48 49 nodes = self.nodes 50 self.nodes = [] 51 text = None 52 53 for node in nodes: 54 55 # Open a text node or merge text into an open node. 56 57 if isinstance(node, Text): 58 if not text: 59 text = node 60 else: 61 text.merge(node) 62 63 # Close any open text node and append the current node. 64 65 else: 66 if text: 67 self.append(text) 68 text = None 69 self.append(node) 70 71 # Add any open text node. 72 73 if text: 74 self.append(text) 75 76 def __str__(self): 77 return self.prettyprint() 78 79 def _prettyprint(self, l, indent=""): 80 for node in self.nodes: 81 l.append(node.prettyprint(indent + " ")) 82 return "\n".join(l) 83 84 def _to_string(self, out): 85 for node in self.nodes: 86 node.to_string(out) 87 88 class Region(Container): 89 90 "A region of the page." 91 92 transparent_region_types = ["wiki"] 93 94 def __init__(self, nodes, level=0, indent=0, type=None): 95 Container.__init__(self, nodes) 96 self.level = level 97 self.indent = indent 98 self.type = type 99 100 def add(self, node): 101 last = self.node(-1) 102 if last and last.empty(): 103 self.nodes[-1] = node 104 else: 105 self.append(node) 106 107 def append_inline(self, s): 108 if self.is_transparent(): 109 self.nodes[-1].append(s) 110 else: 111 self.append(s) 112 113 def have_end(self, s): 114 return self.level and s.startswith("}") and self.level == len(s) 115 116 def is_transparent(self): 117 return not self.level or self.type in self.transparent_region_types 118 119 def __repr__(self): 120 return "Region(%r, %r, %r, %r)" % (self.nodes, self.level, self.indent, self.type) 121 122 def prettyprint(self, indent=""): 123 l = ["%sRegion: level=%d indent=%d type=%s" % (indent, self.level, self.indent, self.type)] 124 return self._prettyprint(l, indent) 125 126 def to_string(self, out): 127 out.start_region(self.level, self.indent, self.type) 128 self._to_string(out) 129 out.end_region(self.level, self.indent, self.type) 130 131 132 133 class Block(Container): 134 135 "A block in the page." 136 137 def __repr__(self): 138 return "Block(%r)" % self.nodes 139 140 def prettyprint(self, indent=""): 141 l = ["%sBlock" % indent] 142 return self._prettyprint(l, indent) 143 144 def to_string(self, out): 145 out.start_block() 146 self._to_string(out) 147 out.end_block() 148 149 class DefItem(Container): 150 151 "A definition item." 152 153 def __init__(self, nodes, pad, extra): 154 Container.__init__(self, nodes) 155 self.pad = pad 156 self.extra = extra 157 158 def __repr__(self): 159 return "DefItem(%r, %r, %r)" % (self.nodes, self.pad, self.extra) 160 161 def prettyprint(self, indent=""): 162 l = ["%sDefItem: pad=%r extra=%r" % (indent, self.pad, self.extra)] 163 return self._prettyprint(l, indent) 164 165 def to_string(self, out): 166 out.start_defitem(self.pad, self.extra) 167 self._to_string(out) 168 out.end_defitem(self.pad, self.extra) 169 170 class DefTerm(Container): 171 172 "A definition term." 173 174 def __init__(self, nodes, pad): 175 Container.__init__(self, nodes) 176 self.pad = pad 177 178 def __repr__(self): 179 return "DefTerm(%r, %r)" % (self.nodes, self.pad) 180 181 def prettyprint(self, indent=""): 182 l = ["%sDefTerm: pad=%r" % (indent, self.pad)] 183 return self._prettyprint(l, indent) 184 185 def to_string(self, out): 186 out.start_defterm(self.pad) 187 self._to_string(out) 188 out.end_defterm(self.pad) 189 190 class FontStyle(Container): 191 192 "Emphasised and/or strong text." 193 194 def __init__(self, nodes, emphasis=False, strong=False): 195 Container.__init__(self, nodes) 196 self.emphasis = emphasis 197 self.strong = strong 198 199 def close_emphasis(self): 200 if self.strong: 201 span = FontStyle(self.nodes, emphasis=True) 202 self.nodes = [span] 203 self.emphasis = False 204 return self.strong 205 206 def close_strong(self): 207 if self.emphasis: 208 span = FontStyle(self.nodes, strong=True) 209 self.nodes = [span] 210 self.strong = False 211 return self.emphasis 212 213 def __repr__(self): 214 return "FontStyle(%r, %r, %r)" % (self.nodes, self.emphasis, self.strong) 215 216 def prettyprint(self, indent=""): 217 l = ["%sFontStyle: emphasis=%r strong=%r" % (indent, self.emphasis, self.strong)] 218 return self._prettyprint(l, indent) 219 220 def to_string(self, out): 221 if self.emphasis: 222 out.start_emphasis() 223 elif self.strong: 224 out.start_strong() 225 self._to_string(out) 226 if self.emphasis: 227 out.end_emphasis() 228 elif self.strong: 229 out.end_strong() 230 231 class Heading(Container): 232 233 "A heading." 234 235 def __init__(self, nodes, level, start_extra="", start_pad="", end_pad="", end_extra=""): 236 Container.__init__(self, nodes) 237 self.level = level 238 self.start_extra = start_extra 239 self.start_pad = start_pad 240 self.end_pad = end_pad 241 self.end_extra = end_extra 242 243 def __repr__(self): 244 return "Heading(%r, %d, %r, %r, %r, %r)" % ( 245 self.nodes, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra) 246 247 def prettyprint(self, indent=""): 248 l = ["%sHeading: level=%d start_extra=%r start_pad=%r end_pad=%r end_extra=%r" % ( 249 indent, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)] 250 return self._prettyprint(l, indent) 251 252 def to_string(self, out): 253 out.start_heading(self.level, self.start_extra, self.start_pad) 254 self._to_string(out) 255 out.end_heading(self.level, self.end_pad, self.end_extra) 256 257 class ListItem(Container): 258 259 "A list item." 260 261 def __init__(self, nodes, indent, marker, space): 262 Container.__init__(self, nodes) 263 self.indent = indent 264 self.marker = marker 265 self.space = space 266 267 def __repr__(self): 268 return "ListItem(%r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.space) 269 270 def prettyprint(self, indent=""): 271 l = ["%sListItem: indent=%d marker=%r space=%r" % (indent, self.indent, self.marker, self.space)] 272 return self._prettyprint(l, indent) 273 274 def to_string(self, out): 275 out.start_listitem(self.indent, self.marker, self.space) 276 self._to_string(out) 277 out.end_listitem(self.indent, self.marker) 278 279 class Monospace(Container): 280 281 "Monospaced text." 282 283 def __repr__(self): 284 return "Monospace(%r)" % self.nodes 285 286 def prettyprint(self, indent=""): 287 l = ["%sMonospace" % indent] 288 return self._prettyprint(l, indent) 289 290 def to_string(self, out): 291 out.start_monospace() 292 self._to_string(out) 293 out.end_monospace() 294 295 class Subscript(Container): 296 297 "Subscripted text." 298 299 def __repr__(self): 300 return "Subscript(%r)" % self.nodes 301 302 def prettyprint(self, indent=""): 303 l = ["%sSubscript" % indent] 304 return self._prettyprint(l, indent) 305 306 def to_string(self, out): 307 out.start_subscript() 308 self._to_string(out) 309 out.end_subscript() 310 311 class Superscript(Container): 312 313 "Superscripted text." 314 315 def __repr__(self): 316 return "Superscript(%r)" % self.nodes 317 318 def prettyprint(self, indent=""): 319 l = ["%sSuperscript" % indent] 320 return self._prettyprint(l, indent) 321 322 def to_string(self, out): 323 out.start_superscript() 324 self._to_string(out) 325 out.end_superscript() 326 327 class Underline(Container): 328 329 "Underlined text." 330 331 def __repr__(self): 332 return "Underline(%r)" % self.nodes 333 334 def prettyprint(self, indent=""): 335 l = ["%sUnderline" % indent] 336 return self._prettyprint(l, indent) 337 338 def to_string(self, out): 339 out.start_underline() 340 self._to_string(out) 341 out.end_underline() 342 343 344 345 class Node: 346 347 "A document node without children." 348 349 def empty(self): 350 return False 351 352 class Break(Node): 353 354 "A paragraph break." 355 356 def __repr__(self): 357 return "Break()" 358 359 def prettyprint(self, indent=""): 360 return "%sBreak" % indent 361 362 def to_string(self, out): 363 out.break_() 364 365 class Rule(Node): 366 367 "A horizontal rule." 368 369 def __init__(self, length): 370 self.length = length 371 372 def __repr__(self): 373 return "Rule(%d)" % self.length 374 375 def prettyprint(self, indent=""): 376 return "%sRule: %d" % (indent, self.length) 377 378 def to_string(self, out): 379 out.rule(self.length) 380 381 class Text(Node): 382 383 "A text node." 384 385 def __init__(self, s): 386 self.s = s 387 388 def empty(self): 389 return not self.s 390 391 def merge(self, text): 392 self.s += text.s 393 394 def __repr__(self): 395 return "Text(%r)" % self.s 396 397 def prettyprint(self, indent=""): 398 return "%sText: %r" % (indent, self.s) 399 400 def to_string(self, out): 401 out.text(self.s) 402 403 # vim: tabstop=4 expandtab shiftwidth=4