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