1 #!/usr/bin/env python 2 3 """ 4 HTML serialiser. 5 6 Copyright (C) 2017, 2018 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 from moinformat.serialisers.common import escape_attr, escape_text, Serialiser 23 from moinformat.tree.moin import LinkLabel, LinkParameter 24 25 class HTMLSerialiser(Serialiser): 26 27 "Serialisation of the page." 28 29 format = "html" 30 31 def _region_tag(self, type): 32 33 # NOTE: Need to support types in general. 34 35 type = type and type.split()[0] 36 37 if type == "inline": 38 return "tt" 39 elif type in (None, "python"): 40 return "pre" 41 else: 42 return "span" 43 44 def start_region(self, level, indent, type, extra): 45 46 # Generate attributes, joining them when preparing the tag. 47 48 l = [] 49 out = l.append 50 51 if level: 52 out("level-%d" % level) 53 54 if indent: 55 out("indent-%d" % indent) 56 57 # NOTE: Encode type details for CSS. 58 59 out("type-%s" % escape_attr(type or "opaque")) 60 61 tag = self._region_tag(type) 62 self.out("<%s class='%s'>" % (tag, " ".join(l))) 63 64 def end_region(self, level, indent, type, extra): 65 tag = self._region_tag(type) 66 self.out("</%s>" % tag) 67 68 def start_block(self): 69 self.out("<p>") 70 71 def end_block(self): 72 self.out("</p>") 73 74 def start_defitem(self, pad, extra): 75 self.out("<dd>") 76 77 def end_defitem(self, pad, extra): 78 self.out("</dd>") 79 80 def start_defterm(self, pad, extra): 81 self.out("<dt>") 82 83 def end_defterm(self, pad, extra): 84 self.out("</dt>") 85 86 def start_emphasis(self): 87 self.out("<em>") 88 89 def end_emphasis(self): 90 self.out("</em>") 91 92 def start_heading(self, level, extra, pad, identifier): 93 self.out("<h%d id='%s'>" % (level, escape_attr(self.linker.make_id(identifier)))) 94 95 def end_heading(self, level, pad, extra): 96 self.out("</h%d>" % level) 97 98 def start_larger(self): 99 self.out("<big>") 100 101 def end_larger(self): 102 self.out("</big>") 103 104 def start_linktext(self): 105 pass 106 107 def end_linktext(self): 108 pass 109 110 list_tags = { 111 "i" : "lower-roman", 112 "I" : "upper-roman", 113 "a" : "lower-latin", 114 "A" : "upper-latin", 115 } 116 117 def _get_list_tag(self, marker): 118 if marker: 119 if marker[0].isdigit(): 120 return "ol", "decimal" 121 style_type = self.list_tags.get(marker[0]) 122 if style_type: 123 return "ol", style_type 124 125 return "ul", None 126 127 def start_list(self, indent, marker, num): 128 tag, style_type = self._get_list_tag(marker) 129 style = style_type and ' style="list-style-type: %s"' % escape_attr(style_type) or "" 130 start = style_type and num is not None and ' start="%s"' % escape_attr(num) or "" 131 self.out("<%s%s%s>" % (tag, style, start)) 132 133 def end_list(self, indent, marker, num): 134 tag, style = self._get_list_tag(marker) 135 self.out("</%s>" % tag) 136 137 def start_listitem(self, indent, marker, space, num): 138 self.out("<li>") 139 140 def end_listitem(self, indent, marker, space, num): 141 self.out("</li>") 142 143 def start_macro(self, name, args, nodes): 144 self.out("<span class='macro'>") 145 146 # Fallback case for when macros are not replaced. 147 148 if not nodes: 149 self.out(escape_text("<<")) 150 self.out("<span class='name'>%s</span>" % escape_text(name)) 151 if args: 152 self.out("(") 153 first = True 154 for arg in args: 155 if not first: 156 self.out(",") 157 self.out("<span class='arg'>%s</span>" % escape_text(arg)) 158 first = False 159 if args: 160 self.out(")") 161 self.out(escape_text(">>")) 162 163 def end_macro(self): 164 self.out("</span>") 165 166 def start_monospace(self): 167 self.out("<tt>") 168 169 def end_monospace(self): 170 self.out("</tt>") 171 172 def start_smaller(self): 173 self.out("<small>") 174 175 def end_smaller(self): 176 self.out("</small>") 177 178 def start_strikethrough(self): 179 self.out("<del>") 180 181 def end_strikethrough(self): 182 self.out("</del>") 183 184 def start_strong(self): 185 self.out("<strong>") 186 187 def end_strong(self): 188 self.out("</strong>") 189 190 def start_subscript(self): 191 self.out("<sub>") 192 193 def end_subscript(self): 194 self.out("</sub>") 195 196 def start_superscript(self): 197 self.out("<sup>") 198 199 def end_superscript(self): 200 self.out("</sup>") 201 202 def start_table(self): 203 self.out("<table>") 204 205 def end_table(self): 206 self.out("</table>") 207 208 def start_table_attrs(self): 209 pass 210 211 def end_table_attrs(self): 212 pass 213 214 def start_table_cell(self, attrs): 215 self.out("<td") 216 217 # Handle the attributes separately from their container. 218 219 if attrs and not attrs.empty(): 220 for attr in attrs.nodes: 221 attr.to_string(self) 222 223 self.out(">") 224 225 def end_table_cell(self): 226 self.out("</td>") 227 228 def start_table_row(self): 229 self.out("<tr>") 230 231 def end_table_row(self, trailing): 232 self.out("</tr>") 233 234 def start_underline(self): 235 self.out("<span style='text-decoration: underline'>") 236 237 def end_underline(self): 238 self.out("</span>") 239 240 def anchor(self, target): 241 self.out("<a name='%s' />" % escape_attr(self.linker.make_id(target))) 242 243 def break_(self): 244 pass 245 246 def comment(self, comment, extra): 247 pass 248 249 def directive(self, directive, extra): 250 pass 251 252 def linebreak(self): 253 self.out("<br />") 254 255 def _link(self, target, nodes, tag, attr): 256 link = self.linker and self.linker.translate(target) or None 257 258 self.out('<%s %s="%s"' % (tag, attr, escape_attr(link.get_target()))) 259 260 # Provide link parameters as attributes. 261 262 if nodes: 263 for node in nodes: 264 if isinstance(node, LinkParameter): 265 self.out(" ") 266 node.to_string(self) 267 268 self.out(">") 269 270 # Provide the link label if specified. Otherwise, use a generated 271 # default for the label. 272 273 for node in nodes or []: 274 if isinstance(node, LinkLabel): 275 node.to_string(self) 276 break 277 else: 278 self.out(escape_text(link.get_label())) 279 280 self.out("</%s>" % tag) 281 282 def link(self, target, nodes): 283 self._link(target, nodes, "a", "href") 284 285 def link_label(self, nodes): 286 for node in nodes: 287 node.to_string(self) 288 289 def link_parameter(self, key_value): 290 if len(key_value) == 1: 291 self.out(key_value[0]) 292 else: 293 key, value = key_value 294 self.out("%s='%s'" % (key, escape_attr(value))) 295 296 def rule(self, length): 297 self.out("<hr style='height: %dpt' />" % min(length, 10)) 298 299 def table_attrs(self, nodes): 300 301 # Skip the attributes in their original form. 302 303 pass 304 305 def table_attr(self, name, value, concise, quote): 306 self.out(" %s%s" % (escape_text(name), value is not None and 307 "='%s'" % escape_attr(value) or "")) 308 309 def text(self, s): 310 self.out(escape_text(s)) 311 312 def transclusion(self, target, nodes): 313 self._link(target, nodes, "img", "src") 314 315 def verbatim(self, s): 316 self.text(s) 317 318 serialiser = HTMLSerialiser 319 320 # vim: tabstop=4 expandtab shiftwidth=4