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 append_text = append 33 34 def empty(self): 35 return not self.nodes 36 37 def last(self): 38 return self.nodes and self.nodes[-1] or None 39 40 def normalise(self): 41 42 "Combine adjacent text nodes." 43 44 nodes = self.nodes 45 self.nodes = [] 46 text = None 47 48 for node in nodes: 49 50 # Open a text node or merge text into an open node. 51 52 if isinstance(node, Text): 53 if not text: 54 text = node 55 else: 56 text.merge(node) 57 58 # Close any open text node and append the current node. 59 60 else: 61 if text: 62 self.append(text) 63 text = None 64 self.append(node) 65 66 # Add any open text node. 67 68 if text: 69 self.append(text) 70 71 def __str__(self): 72 return self.prettyprint() 73 74 def prettyprint(self, indent=""): 75 pass 76 77 class Region(Container): 78 79 "A region of the page." 80 81 transparent_region_types = ["wiki"] 82 83 def __init__(self, nodes, level=0, indent=0, type=None): 84 Container.__init__(self, nodes) 85 self.level = level 86 self.indent = indent 87 self.type = type 88 89 def append(self, node): 90 last = self.last() 91 if last and last.empty(): 92 self.nodes[-1] = node 93 else: 94 self.nodes.append(node) 95 96 def append_text(self, s): 97 if self.is_transparent(): 98 self.nodes[-1].append(s) 99 else: 100 self.append(s) 101 102 def have_end(self, s): 103 return self.level and s.startswith("}") and self.level == len(s) 104 105 def is_transparent(self): 106 return not self.level or self.type in self.transparent_region_types 107 108 def __repr__(self): 109 return "Region(%r, %r, %r, %r)" % (self.nodes, self.level, self.indent, self.type) 110 111 def prettyprint(self, indent=""): 112 l = ["%sRegion: level=%d indent=%d type=%s" % (indent, self.level, self.indent, self.type)] 113 for node in self.nodes: 114 l.append(node.prettyprint(indent + " ")) 115 return "\n".join(l) 116 117 def to_string(self, out): 118 out.start_region(self.level, self.indent, self.type) 119 for node in self.nodes: 120 node.to_string(out) 121 out.end_region(self.level, self.indent, self.type) 122 123 class Block(Container): 124 125 "A block in the page." 126 127 def __init__(self, nodes, final=True): 128 Container.__init__(self, nodes) 129 self.final = final 130 131 def __repr__(self): 132 return "Block(%r)" % self.nodes 133 134 def prettyprint(self, indent=""): 135 l = ["%sBlock: final=%s" % (indent, self.final)] 136 for node in self.nodes: 137 l.append(node.prettyprint(indent + " ")) 138 return "\n".join(l) 139 140 def to_string(self, out): 141 out.start_block(self.final) 142 for node in self.nodes: 143 node.to_string(out) 144 out.end_block(self.final) 145 146 class Heading(Container): 147 148 "A heading." 149 150 def __init__(self, nodes, level, start_extra="", start_pad="", end_pad="", end_extra=""): 151 Container.__init__(self, nodes) 152 self.level = level 153 self.start_extra = start_extra 154 self.start_pad = start_pad 155 self.end_pad = end_pad 156 self.end_extra = end_extra 157 158 def __repr__(self): 159 return "Heading(%r, %d, %r, %r, %r, %r)" % ( 160 self.nodes, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra) 161 162 def prettyprint(self, indent=""): 163 l = ["%sHeading: level=%d start_extra=%r start_pad=%r end_pad=%r end_extra=%r" % ( 164 indent, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)] 165 for node in self.nodes: 166 l.append(node.prettyprint(indent + " ")) 167 return "\n".join(l) 168 169 def to_string(self, out): 170 out.start_heading(self.level, self.start_extra, self.start_pad) 171 for node in self.nodes: 172 node.to_string(out) 173 out.end_heading(self.level, self.end_pad, self.end_extra) 174 175 class ListItem(Container): 176 177 "A list item." 178 179 def __init__(self, nodes, indent, marker, space): 180 Container.__init__(self, nodes) 181 self.indent = indent 182 self.marker = marker 183 self.space = space 184 185 def __repr__(self): 186 return "ListItem(%r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.space) 187 188 def prettyprint(self, indent=""): 189 l = ["%sListItem: indent=%d marker=%r space=%r" % (indent, self.indent, self.marker, self.space)] 190 for node in self.nodes: 191 l.append(node.prettyprint(indent + " ")) 192 return "\n".join(l) 193 194 def to_string(self, out): 195 out.start_listitem(self.indent, self.marker, self.space) 196 for node in self.nodes: 197 node.to_string(out) 198 out.end_listitem(self.indent, self.marker) 199 200 201 202 class Node: 203 204 "A document node without children." 205 206 def empty(self): 207 return False 208 209 class Rule(Node): 210 211 "A horizontal rule." 212 213 def __init__(self, length): 214 self.length = length 215 216 def __repr__(self): 217 return "Rule(%d)" % self.length 218 219 def prettyprint(self, indent=""): 220 return "%sRule: %d" % (indent, self.length) 221 222 def to_string(self, out): 223 out.rule(self.length) 224 225 class Text(Node): 226 227 "A text node." 228 229 def __init__(self, s): 230 self.s = s 231 232 def empty(self): 233 return not self.s 234 235 def merge(self, text): 236 self.s += text.s 237 238 def __repr__(self): 239 return "Text(%r)" % self.s 240 241 def prettyprint(self, indent=""): 242 return "%sText: %r" % (indent, self.s) 243 244 def to_string(self, out): 245 out.text(self.s) 246 247 # vim: tabstop=4 expandtab shiftwidth=4