# HG changeset patch # User Paul Boddie # Date 1687961546 -7200 # Node ID 35ae3eea290f696e3ea8daea24f6c6bf11343d78 # Parent 23098f02bda7e266cc6495d4fdc022563b33bc98 Introduced support for a generic visitor interface where document nodes, when a generic visit method is invoked by a serialiser or other component, merely divert the invocation to a specific method on that visiting component. diff -r 23098f02bda7 -r 35ae3eea290f moinformat/serialisers/__init__.py --- a/moinformat/serialisers/__init__.py Tue Jun 20 18:58:47 2023 +0200 +++ b/moinformat/serialisers/__init__.py Wed Jun 28 16:12:26 2023 +0200 @@ -3,7 +3,7 @@ """ Moin wiki serialisers. -Copyright (C) 2017, 2018 Paul Boddie +Copyright (C) 2017, 2018, 2023 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -43,7 +43,7 @@ "Serialise 'doc' using the given 'serialiser' instance." serialiser.reset() - doc.to_string(serialiser) + doc.visit(serialiser) return serialiser.get_output() # vim: tabstop=4 expandtab shiftwidth=4 diff -r 23098f02bda7 -r 35ae3eea290f moinformat/serialisers/common.py --- a/moinformat/serialisers/common.py Tue Jun 20 18:58:47 2023 +0200 +++ b/moinformat/serialisers/common.py Wed Jun 28 16:12:26 2023 +0200 @@ -3,7 +3,7 @@ """ Moin serialiser support. -Copyright (C) 2017, 2018, 2019, 2021 Paul Boddie +Copyright (C) 2017, 2018, 2019, 2021, 2023 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -92,6 +92,36 @@ else: return cls(self.metadata, self.serialisers) + # Serialisation visitor methods. + + def to_string(self, node): + + "Visit the 'node' to invoke the appropriate serialisation handler." + + node.visit(self) + + def region_to_string(self, region): + + """ + Obtain a serialiser for the region from the same format family. Retain + the same serialiser if no appropriate serialiser could be obtained. + """ + + serialiser_name = self.formats and "%s.%s" % (self.formats[0], region.type) or None + serialiser = self.get_serialiser(serialiser_name) + + # Serialise the region. + + serialiser.container(region) + + def container(self, container): + + "Visit all nodes in 'container'." + + if container.nodes: + for node in container.nodes: + self.to_string(node) + def escape_attr(s): "Escape XML document attribute." diff -r 23098f02bda7 -r 35ae3eea290f moinformat/serialisers/html/graphviz.py --- a/moinformat/serialisers/html/graphviz.py Tue Jun 20 18:58:47 2023 +0200 +++ b/moinformat/serialisers/html/graphviz.py Wed Jun 28 16:12:26 2023 +0200 @@ -3,7 +3,7 @@ """ Graphviz serialiser, generating content for embedding in HTML documents. -Copyright (C) 2018, 2019, 2022 Paul Boddie +Copyright (C) 2018, 2019, 2022, 2023 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -61,13 +61,13 @@ def end_block(self): pass - def directive(self, key, value, directive): - if not self.directives.has_key(key): - self.directives[key] = [] - self.directives[key].append(value) + def directive(self, directive): + if not self.directives.has_key(directive.key): + self.directives[directive.key] = [] + self.directives[directive.key].append(directive.value) def text(self, text): - self.process_graph(text) + self.process_graph(text.s) diff -r 23098f02bda7 -r 35ae3eea290f moinformat/serialisers/html/moin.py --- a/moinformat/serialisers/html/moin.py Tue Jun 20 18:58:47 2023 +0200 +++ b/moinformat/serialisers/html/moin.py Wed Jun 28 16:12:26 2023 +0200 @@ -3,7 +3,8 @@ """ HTML serialiser. -Copyright (C) 2017, 2018, 2019, 2021, 2022 Paul Boddie +Copyright (C) 2017, 2018, 2019, 2021, 2022, + 2023 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -30,90 +31,7 @@ input_formats = ["moin", "wiki"] formats = ["html"] - def _region_tag(self, type): - - # NOTE: Need to support types in general. - - type = type and type.split()[0] - - if type == "inline": - return "tt" - elif type in (None, "python"): - return "pre" - else: - return "span" - - def start_region(self, level, indent, type, args, extra): - - # Generate attributes, joining them when preparing the tag. - - l = [] - out = l.append - - if level: - out("level-%d" % level) - - if indent: - out("indent-%d" % indent) - - # NOTE: Encode type details for CSS. - - out("type-%s" % escape_attr(type or "opaque")) - - tag = self._region_tag(type) - - # Inline regions must preserve "indent" as space in the text. - - if type == "inline" and indent: - self.out(" " * indent) - - self.out("<%s class='%s'>" % (tag, " ".join(l))) - - def end_region(self, level, indent, type, args, extra): - tag = self._region_tag(type) - self.out("" % tag) - - def start_block(self): - self.out("

") - - def end_block(self): - self.out("

") - - def start_defitem(self, pad, extra): - self.out("
") - - def end_defitem(self, pad, extra): - self.out("
") - - def start_defterm(self, pad, extra): - self.out("
") - - def end_defterm(self, pad, extra): - self.out("
") - - def start_emphasis(self): - self.out("") - - def end_emphasis(self): - self.out("") - - def start_heading(self, level, extra, pad, identifier): - self.out("" % (level, escape_attr(self.linker.make_id(identifier)))) - - def end_heading(self, level, pad, extra): - self.out("" % level) - - def start_larger(self): - self.out("") - - def end_larger(self): - self.out("") - - def start_linktext(self): - pass - - def end_linktext(self): - pass + # Support methods. list_tags = { "i" : "lower-roman", @@ -132,141 +50,246 @@ return "ul", None - def start_list(self, indent, marker, num): - tag, style_type = self._get_list_tag(marker) - style = style_type and ' style="list-style-type: %s"' % escape_attr(style_type) or "" - start = style_type and num is not None and ' start="%s"' % escape_attr(num) or "" - self.out("<%s%s%s>" % (tag, style, start)) + def _link(self, target, nodes, tag, attr): + link = self.linker and self.linker.translate(target) or None + + self.out('<%s %s="%s"' % (tag, attr, escape_attr(link.get_target()))) + + # Provide link parameters as attributes. + + if nodes: + for node in nodes: + if isinstance(node, LinkParameter): + self.out(" ") + node.visit(self) + + # Close the tag if an image. + + if tag == "img": + self.out(" />") + + # Provide the link label if specified. Otherwise, use a generated + # default for the label. + + else: + self.out(">") + + for node in nodes or []: + if isinstance(node, LinkLabel): + node.visit(self) + break + else: + self.out(escape_text(link.get_label())) + + self.out("" % tag) + + def _region_tag(self, type): + + # NOTE: Need to support types in general. + + type = type and type.split()[0] - def end_list(self, indent, marker, num): - tag, style = self._get_list_tag(marker) + if type == "inline": + return "tt" + elif type in (None, "python"): + return "pre" + else: + return "span" + + # Node handler methods. + + def region(self, region): + tag = self._region_tag(region.type) + + # Generate attributes, joining them when preparing the tag. + + attrs = [] + attr = attrs.append + + if region.level: + attr("level-%d" % region.level) + + if region.indent: + attr("indent-%d" % region.indent) + + # NOTE: Encode type details for CSS. + + attr("type-%s" % escape_attr(region.type or "opaque")) + + # Inline regions must preserve "indent" as space in the text. + + if region.type == "inline" and region.indent: + self.out(" " * region.indent) + + self.out("<%s class='%s'>" % (tag, " ".join(attrs))) + + # Serialise the region content. + + self.region_to_string(region) + + # End the region with the previous serialiser. + self.out("" % tag) - def start_listitem(self, indent, marker, space, num): + # Block node methods. + + def block(self, block): + self.out("

") + self.container(block) + self.out("

") + + def defitem(self, defitem): + self.out("
") + self.container(defitem) + self.out("
") + + def defterm(self, defterm): + self.out("
") + self.container(defterm) + self.out("
") + + def fontstyle(self, fontstyle): + if fontstyle.emphasis: + self.out("") + elif fontstyle.strong: + self.out("") + self.container(fontstyle) + if fontstyle.emphasis: + self.out("") + elif fontstyle.strong: + self.out("") + + def heading(self, heading): + self.out("" % ( + heading.level, + escape_attr(self.linker.make_id(heading.identifier)))) + self.container(heading) + self.out("" % heading.level) + + def larger(self, larger): + self.out("") + self.container(larger) + self.out("") + + def list(self, list): + tag, style_type = self._get_list_tag(list.marker) + style = style_type and \ + ' style="list-style-type: %s"' % escape_attr(style_type) or "" + start = style_type and \ + list.num is not None and ' start="%s"' % escape_attr(list.num) or "" + self.out("<%s%s%s>" % (tag, style, start)) + self.container(list) + self.out("" % tag) + + def listitem(self, listitem): self.out("
  • ") - - def end_listitem(self, indent, marker, space, num): + self.container(listitem) self.out("
  • ") - def start_macro(self, name, args, nodes, inline): + def macro(self, macro): # Special case of a deliberately unexpanded macro. - if nodes is None: + if macro.nodes is None: return - tag = inline and "span" or "div" - self.out("<%s class='macro %s'>" % (tag, escape_text(name))) + tag = macro.inline and "span" or "div" + self.out("<%s class='macro %s'>" % (tag, escape_text(macro.name))) # Fallback case for when macros are not replaced. - if not nodes: + if not macro.nodes: self.out(escape_text("<<")) - self.out("%s" % escape_text(name)) - if args: + self.out("%s" % escape_text(macro.name)) + if macro.args: self.out("(") first = True - for arg in args: + for arg in macro.args: if not first: self.out(",") self.out("%s" % escape_text(arg)) first = False - if args: + if macro.args: self.out(")") self.out(escape_text(">>")) - def end_macro(self, inline): - tag = inline and "span" or "div" + # Produce the expanded macro content. + + else: + self.container(macro) + + tag = macro.inline and "span" or "div" self.out("" % tag) - def start_monospace(self): + def monospace(self, monospace): self.out("") - - def end_monospace(self): + self.container(monospace) self.out("") - def start_smaller(self): + def smaller(self, smaller): self.out("") - - def end_smaller(self): + self.container(smaller) self.out("") - def start_strikethrough(self): + def strikethrough(self, strikethrough): self.out("") - - def end_strikethrough(self): + self.container(strikethrough) self.out("") - def start_strong(self): - self.out("") - - def end_strong(self): - self.out("") - - def start_subscript(self): + def subscript(self): self.out("") - - def end_subscript(self): + self.container(subscript) self.out("") - def start_superscript(self): + def superscript(self, superscript): self.out("") - - def end_superscript(self): + self.container(superscript) self.out("") - def start_table(self): + def table(self, table): self.out("") - - def end_table(self): + self.container(table) self.out("
    ") - def start_table_attrs(self): - pass - - def end_table_attrs(self): - pass - - def start_table_cell(self, attrs, leading, padding): + def table_cell(self, table_cell): self.out("") - - def end_table_cell(self): + self.container(table_cell) self.out("") - def start_table_row(self, leading, padding): + def table_row(self, table_row): self.out("") - - def end_table_row(self, trailing): + self.container(table_row) self.out("") - def start_underline(self): + def underline(self, underline): self.out("") - - def end_underline(self): + self.container(underline) self.out("") - def anchor(self, target): - self.out("" % escape_attr(self.linker.make_id(target))) + # Inline node methods. - def break_(self): + def anchor(self, anchor): + self.out("" % escape_attr(self.linker.make_id(anchor.target))) + + def break_(self, break_): pass - def comment(self, comment, extra): + def comment(self, comment): pass - def directive(self, directive, extra): + def directive(self, directive): # Obtain a blank value if the value is missing. - name, text = (directive.split(None, 1) + [""])[:2] + name, text = (directive.directive.split(None, 1) + [""])[:2] # Produce a readable redirect. @@ -281,80 +304,51 @@ self.end_block() - def linebreak(self): + def linebreak(self, linebreak): self.out("
    ") - def _link(self, target, nodes, tag, attr): - link = self.linker and self.linker.translate(target) or None - - self.out('<%s %s="%s"' % (tag, attr, escape_attr(link.get_target()))) - - # Provide link parameters as attributes. - - if nodes: - for node in nodes: - if isinstance(node, LinkParameter): - self.out(" ") - node.to_string(self) - - # Close the tag if an image. - - if tag == "img": - self.out(" />") - - # Provide the link label if specified. Otherwise, use a generated - # default for the label. + def link(self, link): + self._link(link.target, link.nodes, "a", "href") - else: - self.out(">") - - for node in nodes or []: - if isinstance(node, LinkLabel): - node.to_string(self) - break - else: - self.out(escape_text(link.get_label())) + def link_label(self, link_label): + self.container(link_label) - self.out("" % tag) - - def link(self, target, nodes): - self._link(target, nodes, "a", "href") + def link_parameter(self, link_parameter): + s = link_parameter.text_content() + key_value = s.split("=", 1) - def link_label(self, nodes): - for node in nodes: - node.to_string(self) - - def link_parameter(self, key_value): if len(key_value) == 1: self.out(key_value[0]) else: key, value = key_value self.out("%s='%s'" % (key, escape_attr(value))) - def nbsp(self): + def nbsp(self, nbsp): self.out(" ") - def rule(self, height): - self.out("
    " % min(height, 10)) + def rule(self, rule): + self.out("
    " % min(rule.height, 10)) - def table_attrs(self, nodes): + def table_attrs(self, table_attrs): # Skip the attributes in their original form. pass - def table_attr(self, name, value, concise, quote): - self.out(" %s%s" % (escape_text(name), value is not None and - "='%s'" % escape_attr(value) or "")) + def table_attr(self, table_attr): + self.out(" %s%s" % ( + escape_text(table_attr.name), + table_attr.value is not None and + "='%s'" % escape_attr(table_attr.value) or "")) - def text(self, s): - self.out(escape_text(s)) + def text(self, text): + self.out(escape_text(text.s)) - def transclusion(self, target, nodes): - self._link(target, nodes, "img", "src") + def transclusion(self, transclusion): + self._link(transclusion.target, transclusion.nodes, "img", "src") - def verbatim(self, s): - self.text(s) + def verbatim(self, verbatim): + self.out(escape_text(verbatim.text)) serialiser = HTMLSerialiser diff -r 23098f02bda7 -r 35ae3eea290f moinformat/serialisers/html/table.py --- a/moinformat/serialisers/html/table.py Tue Jun 20 18:58:47 2023 +0200 +++ b/moinformat/serialisers/html/table.py Wed Jun 28 16:12:26 2023 +0200 @@ -28,7 +28,7 @@ input_formats = ["table"] - def continuation(self, text): + def continuation(self, continuation): self.out(" ") serialiser = HTMLTableSerialiser diff -r 23098f02bda7 -r 35ae3eea290f moinformat/serialisers/moin/graphviz.py --- a/moinformat/serialisers/moin/graphviz.py Tue Jun 20 18:58:47 2023 +0200 +++ b/moinformat/serialisers/moin/graphviz.py Wed Jun 28 16:12:26 2023 +0200 @@ -3,7 +3,7 @@ """ Moin Graphviz region serialiser. -Copyright (C) 2018, 2021 Paul Boddie +Copyright (C) 2018, 2021, 2023 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -28,20 +28,19 @@ input_formats = ["graphviz", "dot"] formats = ["moin", "wiki"] - def start_block(self): - pass - - def end_block(self): - pass + def block(self, block): + self.container(block) - def directive(self, key, value, directive): - if directive: - self.out("#%s\n" % directive) + def directive(self, directive): + if directive.directive: + self.out("#%s\n" % directive.directive) else: - self.out("//%s%s\n" % (value and "%s=" % key or key, value or "")) + self.out("//%s%s\n" % ( + directive.value and "%s=" % directive.key or directive.key, + directive.value or "")) def text(self, text): - self.out(text) + self.out(text.s) serialiser = MoinGraphvizSerialiser diff -r 23098f02bda7 -r 35ae3eea290f moinformat/serialisers/moin/moin.py --- a/moinformat/serialisers/moin/moin.py Tue Jun 20 18:58:47 2023 +0200 +++ b/moinformat/serialisers/moin/moin.py Wed Jun 28 16:12:26 2023 +0200 @@ -3,7 +3,7 @@ """ Moin wiki text serialiser. -Copyright (C) 2017, 2018, 2021, 2022 Paul Boddie +Copyright (C) 2017, 2018, 2021, 2022, 2023 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -28,224 +28,216 @@ input_formats = ["moin", "wiki"] formats = ["moin", "wiki"] - def start_region(self, level, indent, type, args, extra): + # Node handler methods. + + def region(self, region): out = self.out - if level: - out(" " * indent + "{" * level) - # Produce a header for regions within a top-level region. + if region.level: + out(" " * region.indent + "{" * region.level) - if type and type != "inline" and level: + # Produce a header for regions within a top-level region. - # Obtain individual arguments, excluding the region type. + if region.type and region.type != "inline": + + # Obtain individual arguments, excluding the region type. - args = args.split(" ")[1:] - args_str = args and (" %s" % " ".join(args)) or "" + args = region.args.split(" ")[1:] + args_str = args and (" %s" % " ".join(args)) or "" - out("#!%s%s\n" % (type, args_str)) + out("#!%s%s\n" % (region.type, args_str)) + + # Serialise the region content. - def end_region(self, level, indent, type, args, extra): - out = self.out - if level: - out("%s%s" % ("}" * level, extra or "")) + self.region_to_string(region) - def start_block(self): - pass + if region.level: + out("%s%s" % ("}" * region.level, region.extra or "")) - def end_block(self): - pass + # Block node methods. - def start_defitem(self, pad, extra): - self.out((extra and extra + "::" or "") + pad) - - def end_defitem(self, pad, extra): - pass + def block(self, block): + self.container(block) - def start_defterm(self, pad, extra): - self.out(pad) + def defitem(self, defitem): + self.out((defitem.extra and defitem.extra + "::" or "") + defitem.pad) + self.container(defitem) - def end_defterm(self, pad, extra): - self.out("::" + extra) + def defterm(self, defterm): + self.out(defterm.pad) + self.container(defterm) + self.out("::" + defterm.extra) - def start_emphasis(self): - self.out("''") - - def end_emphasis(self): - self.out("''") + def fontstyle(self, fontstyle): + if fontstyle.emphasis: + self.out("''") + elif fontstyle.strong: + self.out("'''") + self.container(fontstyle) + if fontstyle.emphasis: + self.out("''") + elif fontstyle.strong: + self.out("'''") - def start_heading(self, level, extra, pad, identifier): - self.out(extra + "=" * level + pad) + def heading(self, heading): + self.out(heading.start_extra + "=" * heading.level + heading.start_pad) + self.container(heading) + self.out(heading.end_pad + "=" * heading.level + heading.end_extra) - def end_heading(self, level, pad, extra): - self.out(pad + "=" * level + extra) - - def start_larger(self): + def larger(self, larger): self.out("~+") - - def end_larger(self): + self.container(larger) self.out("+~") - def start_list(self, indent, marker, num): - pass - - def end_list(self, indent, marker, num): - pass + def list(self, list): + self.container(list) - def start_listitem(self, indent, marker, space, num): - self.out("%s%s%s%s" % (indent * " ", marker, num and "#%s" % num or "", space)) + def listitem(self, listitem): + self.out("%s%s%s%s" % ( + listitem.indent * " ", + listitem.marker, + listitem.num and "#%s" % listitem.num or "", + listitem.space)) + self.container(listitem) - def end_listitem(self, indent, marker, space, num): - pass - - def start_macro(self, name, args, nodes, inline): + def macro(self, macro): # Special case of a deliberately unexpanded macro. - if nodes is None: + if macro.nodes is None: return # Fallback case for when macros are not replaced. - if not nodes: - self.out("<<%s%s>>" % (name, args and "(%s)" % ",".join(args) or "")) + if not macro.nodes: + self.out("<<%s%s>>" % (macro.name, macro.args and "(%s)" % ",".join(macro.args) or "")) - def end_macro(self, inline): - pass - - def start_monospace(self): + def monospace(self, monospace): + self.out("`") + self.container(monospace) self.out("`") - def end_monospace(self): - self.out("`") - - def start_smaller(self): + def smaller(self, smaller): self.out("~-") - - def end_smaller(self): + self.container(smaller) self.out("-~") - def start_strong(self): - self.out("'''") - - def end_strong(self): - self.out("'''") - - def start_strikethrough(self): + def strikethrough(self, strikethrough): self.out("--(") - - def end_strikethrough(self): + self.container(strikethrough) self.out(")--") - def start_subscript(self): + def subscript(self, subscript): + self.out(",,") + self.container(subscript) self.out(",,") - def end_subscript(self): - self.out(",,") - - def start_superscript(self): + def superscript(self, superscript): self.out("^") - - def end_superscript(self): + self.container(superscript) self.out("^") - def start_table(self): - pass - - def end_table(self): - pass + def table(self, table): + self.container(table) - def start_table_attrs(self): - self.out("<") - - def end_table_attrs(self): - self.out(">") + def table_cell(self, table_cell): + self.out("||") + self.container(table_cell) - def start_table_cell(self, attrs, leading, padding): + def table_row(self, table_row): + self.container(table_row) self.out("||") - - def end_table_cell(self): - pass + self.out(table_row.trailing) - def start_table_row(self, leading, padding): - pass - - def end_table_row(self, trailing): - self.out("||") - self.out(trailing) - - def start_underline(self): + def underline(self, underline): + self.out("__") + self.container(underline) self.out("__") - def end_underline(self): - self.out("__") + # Inline node methods. - def anchor(self, target): - self.out("((%s))" % target) + def anchor(self, anchor): + self.out("((%s))" % anchor.target) - def break_(self): + def break_(self, break_): self.out("\n") - def comment(self, comment, extra): - self.out("##%s%s" % (comment, extra)) + def comment(self, comment): + self.out("##%s%s" % (comment.comment, comment.extra)) - def directive(self, directive, extra): - self.out("#%s%s" % (directive, extra)) + def directive(self, directive): + self.out("#%s%s" % (directive.directive, directive.extra)) - def linebreak(self): + def linebreak(self, linebreak): self.out(r"\\") - def link(self, target, nodes): - self.out("[[%s" % target) - for node in nodes: + def link(self, link): + self.out("[[%s" % link.target) + for node in link.nodes: self.out("|") - node.to_string(self) + node.visit(self) self.out("]]") - def link_label(self, nodes): - for node in nodes: - node.to_string(self) + def link_label(self, link_label): + self.container(link_label) - def link_parameter(self, key_value): + def link_parameter(self, link_parameter): + s = link_parameter.text_content() + key_value = s.split("=", 1) + if len(key_value) == 1: self.out(key_value[0]) else: self.out("=".join(key_value)) - def nbsp(self): + def nbsp(self, nbsp): self.out(r"\_") - def rule(self, height): - self.out("-" * (height + 4)) + def rule(self, rule): + self.out("-" * (rule.height + 4)) - def table_attrs(self, nodes): - for node in nodes: - node.to_string(self) + def table_attrs(self, table_attrs): + self.out("<") + self.container(table_attrs) + if not table_attrs.incomplete: + self.out(">") - def table_attr(self, name, value, concise, quote): - if concise: - if name == "bgcolor": self.out(value) - elif name == "colspan": self.out("-%s" % value) - elif name == "align" : self.out(value == "left" and "(" or value == "right" and ")" or ":") - elif name == "rowspan": self.out("|%s" % value) - elif name == "valign" : self.out(value == "top" and "^" or "v") - elif name == "width" : self.out(value) + def table_attr(self, table_attr): + if table_attr.concise: + if table_attr.name == "bgcolor": + self.out(table_attr.value) + elif table_attr.name == "colspan": + self.out("-%s" % table_attr.value) + elif table_attr.name == "align": + self.out(table_attr.value == "left" and "(" or table_attr.value == "right" and ")" or ":") + elif table_attr.name == "rowspan": + self.out("|%s" % table_attr.value) + elif table_attr.name == "valign": + self.out(table_attr.value == "top" and "^" or "v") + elif table_attr.name == "width": + self.out(table_attr.value) else: - self.out("%s%s" % (escape_text(name), value is not None and - "=%s%s%s" % (quote or '"', escape_attr(value), quote or '"') or "")) - - def text(self, s): - self.out(s) + self.out("%s%s" % ( + escape_text(table_attr.name), + table_attr.value is not None and "=%s%s%s" % ( + table_attr.quote or '"', + escape_attr(table_attr.value), + table_attr.quote or '"') + or "")) - def transclusion(self, target, nodes): - self.out("{{%s" % target) - for node in nodes: + def text(self, text): + self.out(text.s) + + def transclusion(self, transclusion): + self.out("{{%s" % transclusion.target) + for node in transclusion.nodes: self.out("|") - node.to_string(self) + node.visit(self) self.out("}}") - def verbatim(self, text): + def verbatim(self, verbatim): self.out("<<<") - self.out(text) + self.out(verbatim.text) self.out(">>>") serialiser = MoinSerialiser diff -r 23098f02bda7 -r 35ae3eea290f moinformat/serialisers/moin/table.py --- a/moinformat/serialisers/moin/table.py Tue Jun 20 18:58:47 2023 +0200 +++ b/moinformat/serialisers/moin/table.py Wed Jun 28 16:12:26 2023 +0200 @@ -3,7 +3,7 @@ """ Moin wiki table serialiser. -Copyright (C) 2017, 2018, 2021 Paul Boddie +Copyright (C) 2017, 2018, 2021, 2023 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -31,31 +31,35 @@ self.first_cell = False self.first_row = False - def start_table(self): + def table(self, table): self.first_row = True + self.container(table) - def start_table_cell(self, attrs, leading, padding): + def table_cell(self, table_cell): if not self.first_cell: - self.out(leading) + self.out(table_cell.leading) self.out("||") else: self.first_cell = False - self.out(padding) - def start_table_row(self, leading, padding): + self.out(table_cell.padding) + self.container(table_cell) + + def table_row(self, table_row): self.first_cell = True + if not self.first_row: - self.out(leading) + self.out(table_row.leading) self.out("==") - self.out(padding) + self.out(table_row.padding) else: self.first_row = False - def end_table_row(self, trailing): - self.out(trailing) + self.container(table_row) + self.out(table_row.trailing) - def continuation(self, text): - self.out(text) + def continuation(self, continuation): + self.out(continuation.text) serialiser = MoinTableSerialiser diff -r 23098f02bda7 -r 35ae3eea290f moinformat/tree/graphviz.py --- a/moinformat/tree/graphviz.py Tue Jun 20 18:58:47 2023 +0200 +++ b/moinformat/tree/graphviz.py Wed Jun 28 16:12:26 2023 +0200 @@ -3,7 +3,7 @@ """ Graphviz document tree nodes. -Copyright (C) 2018 Paul Boddie +Copyright (C) 2018, 2023 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -42,7 +42,7 @@ def prettyprint(self, indent=""): return "%sDirective: key=%r value=%r directive=%r" % (indent, self.key, self.value, self.directive) - def to_string(self, out): - out.directive(self.key, self.value, self.directive) + def visit(self, visitor): + visitor.directive(self) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 23098f02bda7 -r 35ae3eea290f moinformat/tree/moin.py --- a/moinformat/tree/moin.py Tue Jun 20 18:58:47 2023 +0200 +++ b/moinformat/tree/moin.py Wed Jun 28 16:12:26 2023 +0200 @@ -3,7 +3,8 @@ """ Moin wiki format document tree nodes. -Copyright (C) 2017, 2018, 2019, 2020, 2021, 2022 Paul Boddie +Copyright (C) 2017, 2018, 2019, 2020, 2021, 2022, + 2023 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -182,10 +183,6 @@ l.append(node.prettyprint(indent + " ")) return "\n".join(l) - def _to_string(self, out): - for node in self.nodes: - node.to_string(out) - class Region(Container): "A region of the page." @@ -221,23 +218,8 @@ self.level, self.indent, self.type, self.args, self.extra)] return self._prettyprint(l, indent) - def to_string(self, out): - out.start_region(self.level, self.indent, self.type, self.args, self.extra) - - # Obtain a serialiser for the region from the same format family. - # Retain the same serialiser if no appropriate serialiser could be - # obtained. - - serialiser_name = "%s.%s" % (out.formats[0], self.type) - serialiser = out.get_serialiser(serialiser_name) - - # Serialise the region. - - self._to_string(serialiser) - - # End the region with the previous serialiser. - - out.end_region(self.level, self.indent, self.type, self.args, self.extra) + def visit(self, visitor): + visitor.region(self) @@ -254,10 +236,8 @@ l = ["%sBlock" % indent] return self._prettyprint(l, indent) - def to_string(self, out): - out.start_block() - self._to_string(out) - out.end_block() + def visit(self, visitor): + visitor.block(self) class DefItem(Container): @@ -275,10 +255,8 @@ l = ["%sDefItem: pad=%r extra=%r" % (indent, self.pad, self.extra)] return self._prettyprint(l, indent) - def to_string(self, out): - out.start_defitem(self.pad, self.extra) - self._to_string(out) - out.end_defitem(self.pad, self.extra) + def visit(self, visitor): + visitor.defitem(self) class DefTerm(Container): @@ -296,10 +274,8 @@ l = ["%sDefTerm: pad=%r extra=%r" % (indent, self.pad, self.extra)] return self._prettyprint(l, indent) - def to_string(self, out): - out.start_defterm(self.pad, self.extra) - self._to_string(out) - out.end_defterm(self.pad, self.extra) + def visit(self, visitor): + visitor.defterm(self) class FontStyle(Container): @@ -331,16 +307,8 @@ l = ["%sFontStyle: emphasis=%r strong=%r" % (indent, self.emphasis, self.strong)] return self._prettyprint(l, indent) - def to_string(self, out): - if self.emphasis: - out.start_emphasis() - elif self.strong: - out.start_strong() - self._to_string(out) - if self.emphasis: - out.end_emphasis() - elif self.strong: - out.end_strong() + def visit(self, visitor): + visitor.fontstyle(self) class Heading(Container): @@ -368,10 +336,8 @@ self.end_extra, self.identifier)] return self._prettyprint(l, indent) - def to_string(self, out): - out.start_heading(self.level, self.start_extra, self.start_pad, self.identifier) - self._to_string(out) - out.end_heading(self.level, self.end_pad, self.end_extra) + def visit(self, visitor): + visitor.heading(self) class LinkLabel(Container): @@ -384,8 +350,8 @@ l = ["%sLinkLabel" % indent] return self._prettyprint(l, indent) - def to_string(self, out): - out.link_label(self.nodes) + def visit(self, visitor): + visitor.link_label(self) class LinkParameter(Container): @@ -398,10 +364,8 @@ l = ["%sLinkParameter" % indent] return self._prettyprint(l, indent) - def to_string(self, out): - s = self.text_content() - t = s.split("=", 1) - out.link_parameter(t) + def visit(self, visitor): + visitor.link_parameter(self) class List(Container): @@ -426,12 +390,10 @@ l = ["%sList: indent=%r marker=%r num=%r" % (indent, self.indent, self.marker, self.num)] return self._prettyprint(l, indent) - def to_string(self, out): + def visit(self, visitor): if not self.first: self.init() - out.start_list(self.indent, self.marker, self.num) - self._to_string(out) - out.end_list(self.indent, self.marker, self.num) + visitor.list(self) class ListItem(Container): @@ -455,10 +417,8 @@ l = ["%sListItem: indent=%d marker=%r space=%r num=%r" % (indent, self.indent, self.marker, self.space, self.num)] return self._prettyprint(l, indent) - def to_string(self, out): - out.start_listitem(self.indent, self.marker, self.space, self.num) - self._to_string(out) - out.end_listitem(self.indent, self.marker, self.space, self.num) + def visit(self, visitor): + visitor.listitem(self) class TableAttrs(Container): @@ -480,11 +440,8 @@ l = ["%sTableAttrs:" % indent] return self._prettyprint(l, indent) - def to_string(self, out): - out.start_table_attrs() - out.table_attrs(self.nodes) - if not self.incomplete: - out.end_table_attrs() + def visit(self, visitor): + visitor.table_attrs(self) class Table(Container): @@ -497,10 +454,8 @@ l = ["%sTable:" % indent] return self._prettyprint(l, indent) - def to_string(self, out): - out.start_table() - self._to_string(out) - out.end_table() + def visit(self, visitor): + visitor.table(self) class TableCell(Container): @@ -521,10 +476,8 @@ self.padding)] return self._prettyprint(l, indent) - def to_string(self, out): - out.start_table_cell(self.attrs, self.leading, self.padding) - self._to_string(out) - out.end_table_cell() + def visit(self, visitor): + visitor.table_cell(self) class TableRow(Container): @@ -545,10 +498,8 @@ indent, self.trailing, self.leading, self.padding)] return self._prettyprint(l, indent) - def to_string(self, out): - out.start_table_row(self.leading, self.padding) - self._to_string(out) - out.end_table_row(self.trailing) + def visit(self, visitor): + visitor.table_row(self) @@ -569,10 +520,8 @@ "Larger text." - def to_string(self, out): - out.start_larger() - self._to_string(out) - out.end_larger() + def visit(self, visitor): + visitor.larger(self) class Link(Container): @@ -589,8 +538,8 @@ l = ["%sLink: target=%r" % (indent, self.target)] return self._prettyprint(l, indent) - def to_string(self, out): - out.link(self.target, self.nodes) + def visit(self, visitor): + visitor.link(self) class Macro(Container): @@ -613,56 +562,43 @@ l = ["%sMacro: name=%r args=%r" % (indent, self.name, self.args)] return self._prettyprint(l, indent) - def to_string(self, out): - out.start_macro(self.name, self.args, self.nodes, self.inline) - if self.nodes: - self._to_string(out) - out.end_macro(self.inline) + def visit(self, visitor): + visitor.macro(self) class Monospace(Inline): "Monospaced text." - def to_string(self, out): - out.start_monospace() - self._to_string(out) - out.end_monospace() + def visit(self, visitor): + visitor.monospace(self) class Smaller(Inline): "Smaller text." - def to_string(self, out): - out.start_smaller() - self._to_string(out) - out.end_smaller() + def visit(self, visitor): + visitor.smaller(self) class Strikethrough(Inline): - "Crossed-out text." + "Crossed-visitor text." - def to_string(self, out): - out.start_strikethrough() - self._to_string(out) - out.end_strikethrough() + def visit(self, visitor): + visitor.strikethrough(self) class Subscript(Inline): "Subscripted text." - def to_string(self, out): - out.start_subscript() - self._to_string(out) - out.end_subscript() + def visit(self, visitor): + visitor.subscript(self) class Superscript(Inline): "Superscripted text." - def to_string(self, out): - out.start_superscript() - self._to_string(out) - out.end_superscript() + def visit(self, visitor): + visitor.superscript(self) class Transclusion(Container): @@ -679,17 +615,15 @@ l = ["%sTransclusion: target=%r" % (indent, self.target)] return self._prettyprint(l, indent) - def to_string(self, out): - out.transclusion(self.target, self.nodes) + def visit(self, visitor): + visitor.transclusion(self) class Underline(Inline): "Underlined text." - def to_string(self, out): - out.start_underline() - self._to_string(out) - out.end_underline() + def visit(self, visitor): + visitor.underline(self) @@ -715,8 +649,8 @@ def prettyprint(self, indent=""): return "%sAnchor: target=%r" % (indent, self.target) - def to_string(self, out): - out.anchor(self.target) + def visit(self, visitor): + visitor.anchor(self) class Break(Node): @@ -728,8 +662,8 @@ def prettyprint(self, indent=""): return "%sBreak" % indent - def to_string(self, out): - out.break_() + def visit(self, visitor): + visitor.break_(self) class Comment(Node): @@ -745,8 +679,8 @@ def prettyprint(self, indent=""): return "%sComment: comment=%r extra=%r" % (indent, self.comment, self.extra) - def to_string(self, out): - out.comment(self.comment, self.extra) + def visit(self, visitor): + visitor.comment(self) class Directive(Node): @@ -762,8 +696,8 @@ def prettyprint(self, indent=""): return "%sDirective: directive=%r extra=%r" % (indent, self.directive, self.extra) - def to_string(self, out): - out.directive(self.directive, self.extra) + def visit(self, visitor): + visitor.directive(self) class LineBreak(Node): @@ -775,8 +709,8 @@ def prettyprint(self, indent=""): return "%sLineBreak" % indent - def to_string(self, out): - out.linebreak() + def visit(self, visitor): + visitor.linebreak(self) class NonBreakingSpace(Node): @@ -788,8 +722,8 @@ def prettyprint(self, indent=""): return "%sNonBreakingSpace" % indent - def to_string(self, out): - out.nbsp() + def visit(self, visitor): + visitor.nbsp(self) class Rule(Node): @@ -804,8 +738,8 @@ def prettyprint(self, indent=""): return "%sRule: height=%d" % (indent, self.height) - def to_string(self, out): - out.rule(self.height) + def visit(self, visitor): + visitor.rule(self) class TableAttr(Node): @@ -823,8 +757,8 @@ def prettyprint(self, indent=""): return "%sTableAttr: name=%r value=%r concise=%r quote=%r" % (indent, self.name, self.value, self.concise, self.quote) - def to_string(self, out): - out.table_attr(self.name, self.value, self.concise, self.quote) + def visit(self, visitor): + visitor.table_attr(self) class Text(Node): @@ -848,8 +782,8 @@ def prettyprint(self, indent=""): return "%sText: %r" % (indent, self.s) - def to_string(self, out): - out.text(self.s) + def visit(self, visitor): + visitor.text(self) class Verbatim(Node): @@ -864,7 +798,7 @@ def prettyprint(self, indent=""): return "%sVerbatim: text=%r" % (indent, self.text) - def to_string(self, out): - out.verbatim(self.text) + def visit(self, visitor): + visitor.verbatim(self) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 23098f02bda7 -r 35ae3eea290f moinformat/tree/table.py --- a/moinformat/tree/table.py Tue Jun 20 18:58:47 2023 +0200 +++ b/moinformat/tree/table.py Wed Jun 28 16:12:26 2023 +0200 @@ -3,7 +3,7 @@ """ Extended table syntax document tree nodes. -Copyright (C) 2018 Paul Boddie +Copyright (C) 2018, 2023 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -34,7 +34,7 @@ def prettyprint(self, indent=""): return "%sContinuation: %r" % (indent, self.text) - def to_string(self, out): - out.continuation(self.text) + def visit(self, visitor): + visitor.continuation(self) # vim: tabstop=4 expandtab shiftwidth=4