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_text = 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, indent=""): 80 pass 81 82 class Region(Container): 83 84 "A region of the page." 85 86 transparent_region_types = ["wiki"] 87 88 def __init__(self, nodes, level=0, indent=0, type=None): 89 Container.__init__(self, nodes) 90 self.level = level 91 self.indent = indent 92 self.type = type 93 94 def add(self, node): 95 last = self.node(-1) 96 if last and last.empty(): 97 self.nodes[-1] = node 98 else: 99 self.nodes.append(node) 100 101 def append_text(self, s): 102 if self.is_transparent(): 103 self.nodes[-1].append(s) 104 else: 105 self.append(s) 106 107 def have_end(self, s): 108 return self.level and s.startswith("}") and self.level == len(s) 109 110 def is_transparent(self): 111 return not self.level or self.type in self.transparent_region_types 112 113 def __repr__(self): 114 return "Region(%r, %r, %r, %r)" % (self.nodes, self.level, self.indent, self.type) 115 116 def prettyprint(self, indent=""): 117 l = ["%sRegion: level=%d indent=%d type=%s" % (indent, self.level, self.indent, self.type)] 118 for node in self.nodes: 119 l.append(node.prettyprint(indent + " ")) 120 return "\n".join(l) 121 122 def to_string(self, out): 123 out.start_region(self.level, self.indent, self.type) 124 for node in self.nodes: 125 node.to_string(out) 126 out.end_region(self.level, self.indent, self.type) 127 128 class Block(Container): 129 130 "A block in the page." 131 132 def __repr__(self): 133 return "Block(%r)" % self.nodes 134 135 def prettyprint(self, indent=""): 136 l = ["%sBlock" % indent] 137 for node in self.nodes: 138 l.append(node.prettyprint(indent + " ")) 139 return "\n".join(l) 140 141 def to_string(self, out): 142 out.start_block() 143 for node in self.nodes: 144 node.to_string(out) 145 out.end_block() 146 147 class Heading(Container): 148 149 "A heading." 150 151 def __init__(self, nodes, level, start_extra="", start_pad="", end_pad="", end_extra=""): 152 Container.__init__(self, nodes) 153 self.level = level 154 self.start_extra = start_extra 155 self.start_pad = start_pad 156 self.end_pad = end_pad 157 self.end_extra = end_extra 158 159 def __repr__(self): 160 return "Heading(%r, %d, %r, %r, %r, %r)" % ( 161 self.nodes, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra) 162 163 def prettyprint(self, indent=""): 164 l = ["%sHeading: level=%d start_extra=%r start_pad=%r end_pad=%r end_extra=%r" % ( 165 indent, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)] 166 for node in self.nodes: 167 l.append(node.prettyprint(indent + " ")) 168 return "\n".join(l) 169 170 def to_string(self, out): 171 out.start_heading(self.level, self.start_extra, self.start_pad) 172 for node in self.nodes: 173 node.to_string(out) 174 out.end_heading(self.level, self.end_pad, self.end_extra) 175 176 class ListItem(Container): 177 178 "A list item." 179 180 def __init__(self, nodes, indent, marker, space): 181 Container.__init__(self, nodes) 182 self.indent = indent 183 self.marker = marker 184 self.space = space 185 186 def __repr__(self): 187 return "ListItem(%r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.space) 188 189 def prettyprint(self, indent=""): 190 l = ["%sListItem: indent=%d marker=%r space=%r" % (indent, self.indent, self.marker, self.space)] 191 for node in self.nodes: 192 l.append(node.prettyprint(indent + " ")) 193 return "\n".join(l) 194 195 def to_string(self, out): 196 out.start_listitem(self.indent, self.marker, self.space) 197 for node in self.nodes: 198 node.to_string(out) 199 out.end_listitem(self.indent, self.marker) 200 201 202 203 class Node: 204 205 "A document node without children." 206 207 def empty(self): 208 return False 209 210 class Break(Node): 211 212 "A paragraph break." 213 214 def __repr__(self): 215 return "Break()" 216 217 def prettyprint(self, indent=""): 218 return "%sBreak" % indent 219 220 def to_string(self, out): 221 out.break_() 222 223 class Rule(Node): 224 225 "A horizontal rule." 226 227 def __init__(self, length): 228 self.length = length 229 230 def __repr__(self): 231 return "Rule(%d)" % self.length 232 233 def prettyprint(self, indent=""): 234 return "%sRule: %d" % (indent, self.length) 235 236 def to_string(self, out): 237 out.rule(self.length) 238 239 class Text(Node): 240 241 "A text node." 242 243 def __init__(self, s): 244 self.s = s 245 246 def empty(self): 247 return not self.s 248 249 def merge(self, text): 250 self.s += text.s 251 252 def __repr__(self): 253 return "Text(%r)" % self.s 254 255 def prettyprint(self, indent=""): 256 return "%sText: %r" % (indent, self.s) 257 258 def to_string(self, out): 259 out.text(self.s) 260 261 # vim: tabstop=4 expandtab shiftwidth=4