1.1 --- a/moinformat/serialisers/html/moin.py Tue Jun 20 18:58:47 2023 +0200
1.2 +++ b/moinformat/serialisers/html/moin.py Wed Jun 28 16:12:26 2023 +0200
1.3 @@ -3,7 +3,8 @@
1.4 """
1.5 HTML serialiser.
1.6
1.7 -Copyright (C) 2017, 2018, 2019, 2021, 2022 Paul Boddie <paul@boddie.org.uk>
1.8 +Copyright (C) 2017, 2018, 2019, 2021, 2022,
1.9 + 2023 Paul Boddie <paul@boddie.org.uk>
1.10
1.11 This program is free software; you can redistribute it and/or modify it under
1.12 the terms of the GNU General Public License as published by the Free Software
1.13 @@ -30,90 +31,7 @@
1.14 input_formats = ["moin", "wiki"]
1.15 formats = ["html"]
1.16
1.17 - def _region_tag(self, type):
1.18 -
1.19 - # NOTE: Need to support types in general.
1.20 -
1.21 - type = type and type.split()[0]
1.22 -
1.23 - if type == "inline":
1.24 - return "tt"
1.25 - elif type in (None, "python"):
1.26 - return "pre"
1.27 - else:
1.28 - return "span"
1.29 -
1.30 - def start_region(self, level, indent, type, args, extra):
1.31 -
1.32 - # Generate attributes, joining them when preparing the tag.
1.33 -
1.34 - l = []
1.35 - out = l.append
1.36 -
1.37 - if level:
1.38 - out("level-%d" % level)
1.39 -
1.40 - if indent:
1.41 - out("indent-%d" % indent)
1.42 -
1.43 - # NOTE: Encode type details for CSS.
1.44 -
1.45 - out("type-%s" % escape_attr(type or "opaque"))
1.46 -
1.47 - tag = self._region_tag(type)
1.48 -
1.49 - # Inline regions must preserve "indent" as space in the text.
1.50 -
1.51 - if type == "inline" and indent:
1.52 - self.out(" " * indent)
1.53 -
1.54 - self.out("<%s class='%s'>" % (tag, " ".join(l)))
1.55 -
1.56 - def end_region(self, level, indent, type, args, extra):
1.57 - tag = self._region_tag(type)
1.58 - self.out("</%s>" % tag)
1.59 -
1.60 - def start_block(self):
1.61 - self.out("<p>")
1.62 -
1.63 - def end_block(self):
1.64 - self.out("</p>")
1.65 -
1.66 - def start_defitem(self, pad, extra):
1.67 - self.out("<dd>")
1.68 -
1.69 - def end_defitem(self, pad, extra):
1.70 - self.out("</dd>")
1.71 -
1.72 - def start_defterm(self, pad, extra):
1.73 - self.out("<dt>")
1.74 -
1.75 - def end_defterm(self, pad, extra):
1.76 - self.out("</dt>")
1.77 -
1.78 - def start_emphasis(self):
1.79 - self.out("<em>")
1.80 -
1.81 - def end_emphasis(self):
1.82 - self.out("</em>")
1.83 -
1.84 - def start_heading(self, level, extra, pad, identifier):
1.85 - self.out("<h%d id='%s'>" % (level, escape_attr(self.linker.make_id(identifier))))
1.86 -
1.87 - def end_heading(self, level, pad, extra):
1.88 - self.out("</h%d>" % level)
1.89 -
1.90 - def start_larger(self):
1.91 - self.out("<big>")
1.92 -
1.93 - def end_larger(self):
1.94 - self.out("</big>")
1.95 -
1.96 - def start_linktext(self):
1.97 - pass
1.98 -
1.99 - def end_linktext(self):
1.100 - pass
1.101 + # Support methods.
1.102
1.103 list_tags = {
1.104 "i" : "lower-roman",
1.105 @@ -132,141 +50,246 @@
1.106
1.107 return "ul", None
1.108
1.109 - def start_list(self, indent, marker, num):
1.110 - tag, style_type = self._get_list_tag(marker)
1.111 - style = style_type and ' style="list-style-type: %s"' % escape_attr(style_type) or ""
1.112 - start = style_type and num is not None and ' start="%s"' % escape_attr(num) or ""
1.113 - self.out("<%s%s%s>" % (tag, style, start))
1.114 + def _link(self, target, nodes, tag, attr):
1.115 + link = self.linker and self.linker.translate(target) or None
1.116 +
1.117 + self.out('<%s %s="%s"' % (tag, attr, escape_attr(link.get_target())))
1.118 +
1.119 + # Provide link parameters as attributes.
1.120 +
1.121 + if nodes:
1.122 + for node in nodes:
1.123 + if isinstance(node, LinkParameter):
1.124 + self.out(" ")
1.125 + node.visit(self)
1.126 +
1.127 + # Close the tag if an image.
1.128 +
1.129 + if tag == "img":
1.130 + self.out(" />")
1.131 +
1.132 + # Provide the link label if specified. Otherwise, use a generated
1.133 + # default for the label.
1.134 +
1.135 + else:
1.136 + self.out(">")
1.137 +
1.138 + for node in nodes or []:
1.139 + if isinstance(node, LinkLabel):
1.140 + node.visit(self)
1.141 + break
1.142 + else:
1.143 + self.out(escape_text(link.get_label()))
1.144 +
1.145 + self.out("</%s>" % tag)
1.146 +
1.147 + def _region_tag(self, type):
1.148 +
1.149 + # NOTE: Need to support types in general.
1.150 +
1.151 + type = type and type.split()[0]
1.152
1.153 - def end_list(self, indent, marker, num):
1.154 - tag, style = self._get_list_tag(marker)
1.155 + if type == "inline":
1.156 + return "tt"
1.157 + elif type in (None, "python"):
1.158 + return "pre"
1.159 + else:
1.160 + return "span"
1.161 +
1.162 + # Node handler methods.
1.163 +
1.164 + def region(self, region):
1.165 + tag = self._region_tag(region.type)
1.166 +
1.167 + # Generate attributes, joining them when preparing the tag.
1.168 +
1.169 + attrs = []
1.170 + attr = attrs.append
1.171 +
1.172 + if region.level:
1.173 + attr("level-%d" % region.level)
1.174 +
1.175 + if region.indent:
1.176 + attr("indent-%d" % region.indent)
1.177 +
1.178 + # NOTE: Encode type details for CSS.
1.179 +
1.180 + attr("type-%s" % escape_attr(region.type or "opaque"))
1.181 +
1.182 + # Inline regions must preserve "indent" as space in the text.
1.183 +
1.184 + if region.type == "inline" and region.indent:
1.185 + self.out(" " * region.indent)
1.186 +
1.187 + self.out("<%s class='%s'>" % (tag, " ".join(attrs)))
1.188 +
1.189 + # Serialise the region content.
1.190 +
1.191 + self.region_to_string(region)
1.192 +
1.193 + # End the region with the previous serialiser.
1.194 +
1.195 self.out("</%s>" % tag)
1.196
1.197 - def start_listitem(self, indent, marker, space, num):
1.198 + # Block node methods.
1.199 +
1.200 + def block(self, block):
1.201 + self.out("<p>")
1.202 + self.container(block)
1.203 + self.out("</p>")
1.204 +
1.205 + def defitem(self, defitem):
1.206 + self.out("<dd>")
1.207 + self.container(defitem)
1.208 + self.out("</dd>")
1.209 +
1.210 + def defterm(self, defterm):
1.211 + self.out("<dt>")
1.212 + self.container(defterm)
1.213 + self.out("</dt>")
1.214 +
1.215 + def fontstyle(self, fontstyle):
1.216 + if fontstyle.emphasis:
1.217 + self.out("<em>")
1.218 + elif fontstyle.strong:
1.219 + self.out("<strong>")
1.220 + self.container(fontstyle)
1.221 + if fontstyle.emphasis:
1.222 + self.out("</em>")
1.223 + elif fontstyle.strong:
1.224 + self.out("</strong>")
1.225 +
1.226 + def heading(self, heading):
1.227 + self.out("<h%d id='%s'>" % (
1.228 + heading.level,
1.229 + escape_attr(self.linker.make_id(heading.identifier))))
1.230 + self.container(heading)
1.231 + self.out("</h%d>" % heading.level)
1.232 +
1.233 + def larger(self, larger):
1.234 + self.out("<big>")
1.235 + self.container(larger)
1.236 + self.out("</big>")
1.237 +
1.238 + def list(self, list):
1.239 + tag, style_type = self._get_list_tag(list.marker)
1.240 + style = style_type and \
1.241 + ' style="list-style-type: %s"' % escape_attr(style_type) or ""
1.242 + start = style_type and \
1.243 + list.num is not None and ' start="%s"' % escape_attr(list.num) or ""
1.244 + self.out("<%s%s%s>" % (tag, style, start))
1.245 + self.container(list)
1.246 + self.out("</%s>" % tag)
1.247 +
1.248 + def listitem(self, listitem):
1.249 self.out("<li>")
1.250 -
1.251 - def end_listitem(self, indent, marker, space, num):
1.252 + self.container(listitem)
1.253 self.out("</li>")
1.254
1.255 - def start_macro(self, name, args, nodes, inline):
1.256 + def macro(self, macro):
1.257
1.258 # Special case of a deliberately unexpanded macro.
1.259
1.260 - if nodes is None:
1.261 + if macro.nodes is None:
1.262 return
1.263
1.264 - tag = inline and "span" or "div"
1.265 - self.out("<%s class='macro %s'>" % (tag, escape_text(name)))
1.266 + tag = macro.inline and "span" or "div"
1.267 + self.out("<%s class='macro %s'>" % (tag, escape_text(macro.name)))
1.268
1.269 # Fallback case for when macros are not replaced.
1.270
1.271 - if not nodes:
1.272 + if not macro.nodes:
1.273 self.out(escape_text("<<"))
1.274 - self.out("<span class='name'>%s</span>" % escape_text(name))
1.275 - if args:
1.276 + self.out("<span class='name'>%s</span>" % escape_text(macro.name))
1.277 + if macro.args:
1.278 self.out("(")
1.279 first = True
1.280 - for arg in args:
1.281 + for arg in macro.args:
1.282 if not first:
1.283 self.out(",")
1.284 self.out("<span class='arg'>%s</span>" % escape_text(arg))
1.285 first = False
1.286 - if args:
1.287 + if macro.args:
1.288 self.out(")")
1.289 self.out(escape_text(">>"))
1.290
1.291 - def end_macro(self, inline):
1.292 - tag = inline and "span" or "div"
1.293 + # Produce the expanded macro content.
1.294 +
1.295 + else:
1.296 + self.container(macro)
1.297 +
1.298 + tag = macro.inline and "span" or "div"
1.299 self.out("</%s>" % tag)
1.300
1.301 - def start_monospace(self):
1.302 + def monospace(self, monospace):
1.303 self.out("<tt>")
1.304 -
1.305 - def end_monospace(self):
1.306 + self.container(monospace)
1.307 self.out("</tt>")
1.308
1.309 - def start_smaller(self):
1.310 + def smaller(self, smaller):
1.311 self.out("<small>")
1.312 -
1.313 - def end_smaller(self):
1.314 + self.container(smaller)
1.315 self.out("</small>")
1.316
1.317 - def start_strikethrough(self):
1.318 + def strikethrough(self, strikethrough):
1.319 self.out("<del>")
1.320 -
1.321 - def end_strikethrough(self):
1.322 + self.container(strikethrough)
1.323 self.out("</del>")
1.324
1.325 - def start_strong(self):
1.326 - self.out("<strong>")
1.327 -
1.328 - def end_strong(self):
1.329 - self.out("</strong>")
1.330 -
1.331 - def start_subscript(self):
1.332 + def subscript(self):
1.333 self.out("<sub>")
1.334 -
1.335 - def end_subscript(self):
1.336 + self.container(subscript)
1.337 self.out("</sub>")
1.338
1.339 - def start_superscript(self):
1.340 + def superscript(self, superscript):
1.341 self.out("<sup>")
1.342 -
1.343 - def end_superscript(self):
1.344 + self.container(superscript)
1.345 self.out("</sup>")
1.346
1.347 - def start_table(self):
1.348 + def table(self, table):
1.349 self.out("<table>")
1.350 -
1.351 - def end_table(self):
1.352 + self.container(table)
1.353 self.out("</table>")
1.354
1.355 - def start_table_attrs(self):
1.356 - pass
1.357 -
1.358 - def end_table_attrs(self):
1.359 - pass
1.360 -
1.361 - def start_table_cell(self, attrs, leading, padding):
1.362 + def table_cell(self, table_cell):
1.363 self.out("<td")
1.364
1.365 # Handle the attributes separately from their container.
1.366
1.367 - if attrs and not attrs.empty():
1.368 - for attr in attrs.nodes:
1.369 - attr.to_string(self)
1.370 + if table_cell.attrs and not table_cell.attrs.empty():
1.371 + for attr in table_cell.attrs.nodes:
1.372 + attr.visit(self)
1.373
1.374 self.out(">")
1.375 -
1.376 - def end_table_cell(self):
1.377 + self.container(table_cell)
1.378 self.out("</td>")
1.379
1.380 - def start_table_row(self, leading, padding):
1.381 + def table_row(self, table_row):
1.382 self.out("<tr>")
1.383 -
1.384 - def end_table_row(self, trailing):
1.385 + self.container(table_row)
1.386 self.out("</tr>")
1.387
1.388 - def start_underline(self):
1.389 + def underline(self, underline):
1.390 self.out("<span style='text-decoration: underline'>")
1.391 -
1.392 - def end_underline(self):
1.393 + self.container(underline)
1.394 self.out("</span>")
1.395
1.396 - def anchor(self, target):
1.397 - self.out("<a name='%s' />" % escape_attr(self.linker.make_id(target)))
1.398 + # Inline node methods.
1.399
1.400 - def break_(self):
1.401 + def anchor(self, anchor):
1.402 + self.out("<a name='%s' />" % escape_attr(self.linker.make_id(anchor.target)))
1.403 +
1.404 + def break_(self, break_):
1.405 pass
1.406
1.407 - def comment(self, comment, extra):
1.408 + def comment(self, comment):
1.409 pass
1.410
1.411 - def directive(self, directive, extra):
1.412 + def directive(self, directive):
1.413
1.414 # Obtain a blank value if the value is missing.
1.415
1.416 - name, text = (directive.split(None, 1) + [""])[:2]
1.417 + name, text = (directive.directive.split(None, 1) + [""])[:2]
1.418
1.419 # Produce a readable redirect.
1.420
1.421 @@ -281,80 +304,51 @@
1.422
1.423 self.end_block()
1.424
1.425 - def linebreak(self):
1.426 + def linebreak(self, linebreak):
1.427 self.out("<br />")
1.428
1.429 - def _link(self, target, nodes, tag, attr):
1.430 - link = self.linker and self.linker.translate(target) or None
1.431 -
1.432 - self.out('<%s %s="%s"' % (tag, attr, escape_attr(link.get_target())))
1.433 -
1.434 - # Provide link parameters as attributes.
1.435 -
1.436 - if nodes:
1.437 - for node in nodes:
1.438 - if isinstance(node, LinkParameter):
1.439 - self.out(" ")
1.440 - node.to_string(self)
1.441 -
1.442 - # Close the tag if an image.
1.443 -
1.444 - if tag == "img":
1.445 - self.out(" />")
1.446 -
1.447 - # Provide the link label if specified. Otherwise, use a generated
1.448 - # default for the label.
1.449 + def link(self, link):
1.450 + self._link(link.target, link.nodes, "a", "href")
1.451
1.452 - else:
1.453 - self.out(">")
1.454 -
1.455 - for node in nodes or []:
1.456 - if isinstance(node, LinkLabel):
1.457 - node.to_string(self)
1.458 - break
1.459 - else:
1.460 - self.out(escape_text(link.get_label()))
1.461 + def link_label(self, link_label):
1.462 + self.container(link_label)
1.463
1.464 - self.out("</%s>" % tag)
1.465 -
1.466 - def link(self, target, nodes):
1.467 - self._link(target, nodes, "a", "href")
1.468 + def link_parameter(self, link_parameter):
1.469 + s = link_parameter.text_content()
1.470 + key_value = s.split("=", 1)
1.471
1.472 - def link_label(self, nodes):
1.473 - for node in nodes:
1.474 - node.to_string(self)
1.475 -
1.476 - def link_parameter(self, key_value):
1.477 if len(key_value) == 1:
1.478 self.out(key_value[0])
1.479 else:
1.480 key, value = key_value
1.481 self.out("%s='%s'" % (key, escape_attr(value)))
1.482
1.483 - def nbsp(self):
1.484 + def nbsp(self, nbsp):
1.485 self.out(" ")
1.486
1.487 - def rule(self, height):
1.488 - self.out("<hr style='height: %dpt' />" % min(height, 10))
1.489 + def rule(self, rule):
1.490 + self.out("<hr style='height: %dpt' />" % min(rule.height, 10))
1.491
1.492 - def table_attrs(self, nodes):
1.493 + def table_attrs(self, table_attrs):
1.494
1.495 # Skip the attributes in their original form.
1.496
1.497 pass
1.498
1.499 - def table_attr(self, name, value, concise, quote):
1.500 - self.out(" %s%s" % (escape_text(name), value is not None and
1.501 - "='%s'" % escape_attr(value) or ""))
1.502 + def table_attr(self, table_attr):
1.503 + self.out(" %s%s" % (
1.504 + escape_text(table_attr.name),
1.505 + table_attr.value is not None and
1.506 + "='%s'" % escape_attr(table_attr.value) or ""))
1.507
1.508 - def text(self, s):
1.509 - self.out(escape_text(s))
1.510 + def text(self, text):
1.511 + self.out(escape_text(text.s))
1.512
1.513 - def transclusion(self, target, nodes):
1.514 - self._link(target, nodes, "img", "src")
1.515 + def transclusion(self, transclusion):
1.516 + self._link(transclusion.target, transclusion.nodes, "img", "src")
1.517
1.518 - def verbatim(self, s):
1.519 - self.text(s)
1.520 + def verbatim(self, verbatim):
1.521 + self.out(escape_text(verbatim.text))
1.522
1.523 serialiser = HTMLSerialiser
1.524