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