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