MoinLight

Annotated moinformat/serialisers/html/graphviz.py

301:d81233407d70
2021-10-06 Paul Boddie Introduced support for multiple identifiers when registering components, thus supporting format aliases such as "dot" for "graphviz" and "wiki" for "moin", also eliminating the superfluous wiki parser.
paul@101 1
#!/usr/bin/env python
paul@101 2
paul@101 3
"""
paul@101 4
Graphviz serialiser, generating content for embedding in HTML documents.
paul@101 5
paul@201 6
Copyright (C) 2018, 2019 Paul Boddie <paul@boddie.org.uk>
paul@101 7
paul@101 8
This program is free software; you can redistribute it and/or modify it under
paul@101 9
the terms of the GNU General Public License as published by the Free Software
paul@101 10
Foundation; either version 3 of the License, or (at your option) any later
paul@101 11
version.
paul@101 12
paul@101 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@101 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@101 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@101 16
details.
paul@101 17
paul@101 18
You should have received a copy of the GNU General Public License along with
paul@101 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@101 20
"""
paul@101 21
paul@101 22
from moinformat.serialisers.common import Serialiser, escape_attr, escape_text
paul@209 23
from moinformat.utils.graphviz import Graphviz, IMAGE_FORMATS, \
paul@101 24
                                      get_output_identifier
paul@281 25
from moinformat.utils.links import LinkTarget
paul@101 26
paul@101 27
# Utility functions.
paul@101 28
paul@101 29
def select_keys(d, keys):
paul@101 30
paul@101 31
    "Select from 'd' the given 'keys'."
paul@101 32
paul@101 33
    if not d:
paul@201 34
        return {}
paul@101 35
paul@101 36
    out = {}
paul@101 37
paul@101 38
    for key in keys:
paul@101 39
        if d.has_key(key):
paul@101 40
            out[key] = d[key]
paul@101 41
paul@101 42
    return out
paul@101 43
paul@101 44
paul@101 45
paul@101 46
# The serialiser class.
paul@101 47
paul@101 48
class HTMLGraphvizSerialiser(Serialiser):
paul@101 49
paul@101 50
    "Serialisation of Graphviz regions."
paul@101 51
paul@301 52
    input_formats = ["graphviz", "dot"]
paul@301 53
    formats = ["html"]
paul@301 54
paul@101 55
    def init(self):
paul@101 56
        self.directives = {}
paul@101 57
paul@101 58
    def start_block(self):
paul@101 59
        pass
paul@101 60
paul@101 61
    def end_block(self):
paul@101 62
        pass
paul@101 63
paul@188 64
    def directive(self, key, value, directive):
paul@101 65
        if not self.directives.has_key(key):
paul@101 66
            self.directives[key] = []
paul@101 67
        self.directives[key].append(value)
paul@101 68
paul@101 69
    def text(self, text):
paul@101 70
        self.process_graph(text)
paul@101 71
paul@101 72
paul@101 73
paul@101 74
    # Special methods for graph production.
paul@101 75
paul@144 76
    def _tag(self, tagname, attrname, target, attributes, closing):
paul@144 77
        l = ["%s='%s'" % (attrname, escape_attr(target))]
paul@101 78
        for key, value in attributes.items():
paul@101 79
            l.append("%s='%s'" % (key, value))
paul@153 80
        self.out("<%s %s%s>" % (tagname, " ".join(l), closing and " /" or ""))
paul@101 81
paul@144 82
    def image(self, target, attributes):
paul@144 83
        self._tag("img", "src", target, attributes, True)
paul@101 84
paul@144 85
    def object(self, target, attributes):
paul@144 86
        self._tag("object", "data", target, attributes, False)
paul@101 87
        self.out("</object>")
paul@101 88
paul@101 89
    def raw(self, text):
paul@101 90
        self.out(text)
paul@101 91
paul@101 92
paul@101 93
paul@101 94
    # Graph output preparation.
paul@101 95
paul@101 96
    def process_graph(self, text):
paul@101 97
paul@101 98
        "Process the graph 'text' using the known directives."
paul@101 99
paul@101 100
        filter = self.directives.get("filter", ["dot"])[0]
paul@101 101
        format = self.directives.get("format", ["svg"])[0]
paul@101 102
        transforms = self.directives.get("transform", [])
paul@101 103
paul@264 104
        # Add transforms if appropriate.
paul@264 105
        # NOTE: Maybe move this to the Graphviz class itself.
paul@264 106
paul@264 107
        document_index = self.metadata.get("document_index")
paul@264 108
paul@264 109
        if document_index and "fixlinks" not in transforms:
paul@264 110
            transforms.insert(0, "fixlinks")
paul@264 111
paul@264 112
        # Determine the inline status, with only SVG being appropriate.
paul@264 113
paul@282 114
        inline = format == "svg" and not self.metadata.get("no_inline")
paul@189 115
paul@189 116
        # Non-inline graph output is stored for a known page only.
paul@144 117
paul@165 118
        pagename = self.metadata.get("pagename")
paul@189 119
paul@189 120
        if not inline:
paul@189 121
            if not pagename:
paul@189 122
                return
paul@144 123
paul@189 124
            # Get an identifier and usable filename to store the output.
paul@101 125
paul@189 126
            identifier = get_output_identifier(text)
paul@189 127
            attachment = "%s.%s" % (identifier, format)
paul@189 128
            filename = self.output.get_attachment_filename(pagename, attachment)
paul@189 129
paul@189 130
            # Handle situations where no independent output is permitted.
paul@101 131
paul@189 132
            if not filename:
paul@189 133
                return
paul@104 134
paul@189 135
            # Make sure that page attachments can be stored.
paul@104 136
paul@189 137
            self.output.ensure_attachments(pagename)
paul@281 138
            link = self.linker.translate(LinkTarget("attachment", attachment))
paul@144 139
paul@189 140
        # No filename is defined for inline output.
paul@189 141
paul@189 142
        else:
paul@189 143
            filename = None
paul@144 144
paul@101 145
        # Permit imagemaps only for image formats.
paul@101 146
paul@101 147
        if format in IMAGE_FORMATS:
paul@101 148
            cmapx = self.directives.has_key("cmapx")
paul@101 149
paul@101 150
        # Configure Graphviz and invoke it.
paul@101 151
paul@189 152
        graphviz = Graphviz(filter, text)
paul@264 153
        graphviz.call(format, transforms, filename, self.metadata)
paul@101 154
paul@101 155
        # Obtain any metadata.
paul@101 156
paul@101 157
        attributes = select_keys(graphviz.get_metadata(), ["width", "height"])
paul@101 158
paul@101 159
        # For image output, create a file directly and reference it.
paul@101 160
paul@101 161
        if format in IMAGE_FORMATS:
paul@101 162
paul@101 163
            # Produce, embed and reference an imagemap if requested.
paul@101 164
paul@101 165
            if cmapx:
paul@101 166
                graphviz.call("cmapx")
paul@101 167
                mapid = graphviz.get_metadata().get("id")
paul@101 168
paul@101 169
                if mapid:
paul@101 170
                    self.raw(graphviz.get_output())
paul@101 171
                    attributes["usemap"] = "#%s" % im_attributes["id"]
paul@101 172
paul@214 173
            self.image(link.get_target(), attributes)
paul@101 174
paul@101 175
        # For other output, create a file and embed the object.
paul@101 176
paul@189 177
        elif not inline:
paul@214 178
            self.object(link.get_target(), attributes)
paul@189 179
paul@189 180
        # Or for inline output, emit it in the document itself.
paul@189 181
paul@101 182
        else:
paul@189 183
            self.out(graphviz.get_inline_output())
paul@101 184
paul@101 185
serialiser = HTMLGraphvizSerialiser
paul@101 186
paul@101 187
# vim: tabstop=4 expandtab shiftwidth=4