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@101 | 25 | |
paul@101 | 26 | # Utility functions. |
paul@101 | 27 | |
paul@101 | 28 | def select_keys(d, keys): |
paul@101 | 29 | |
paul@101 | 30 | "Select from 'd' the given 'keys'." |
paul@101 | 31 | |
paul@101 | 32 | if not d: |
paul@201 | 33 | return {} |
paul@101 | 34 | |
paul@101 | 35 | out = {} |
paul@101 | 36 | |
paul@101 | 37 | for key in keys: |
paul@101 | 38 | if d.has_key(key): |
paul@101 | 39 | out[key] = d[key] |
paul@101 | 40 | |
paul@101 | 41 | return out |
paul@101 | 42 | |
paul@101 | 43 | |
paul@101 | 44 | |
paul@101 | 45 | # The serialiser class. |
paul@101 | 46 | |
paul@101 | 47 | class HTMLGraphvizSerialiser(Serialiser): |
paul@101 | 48 | |
paul@101 | 49 | "Serialisation of Graphviz regions." |
paul@101 | 50 | |
paul@101 | 51 | def init(self): |
paul@101 | 52 | self.directives = {} |
paul@101 | 53 | |
paul@101 | 54 | def start_block(self): |
paul@101 | 55 | pass |
paul@101 | 56 | |
paul@101 | 57 | def end_block(self): |
paul@101 | 58 | pass |
paul@101 | 59 | |
paul@188 | 60 | def directive(self, key, value, directive): |
paul@101 | 61 | if not self.directives.has_key(key): |
paul@101 | 62 | self.directives[key] = [] |
paul@101 | 63 | self.directives[key].append(value) |
paul@101 | 64 | |
paul@101 | 65 | def text(self, text): |
paul@101 | 66 | self.process_graph(text) |
paul@101 | 67 | |
paul@101 | 68 | |
paul@101 | 69 | |
paul@101 | 70 | # Special methods for graph production. |
paul@101 | 71 | |
paul@144 | 72 | def _tag(self, tagname, attrname, target, attributes, closing): |
paul@144 | 73 | l = ["%s='%s'" % (attrname, escape_attr(target))] |
paul@101 | 74 | for key, value in attributes.items(): |
paul@101 | 75 | l.append("%s='%s'" % (key, value)) |
paul@153 | 76 | self.out("<%s %s%s>" % (tagname, " ".join(l), closing and " /" or "")) |
paul@101 | 77 | |
paul@144 | 78 | def image(self, target, attributes): |
paul@144 | 79 | self._tag("img", "src", target, attributes, True) |
paul@101 | 80 | |
paul@144 | 81 | def object(self, target, attributes): |
paul@144 | 82 | self._tag("object", "data", target, attributes, False) |
paul@101 | 83 | self.out("</object>") |
paul@101 | 84 | |
paul@101 | 85 | def raw(self, text): |
paul@101 | 86 | self.out(text) |
paul@101 | 87 | |
paul@101 | 88 | |
paul@101 | 89 | |
paul@101 | 90 | # Graph output preparation. |
paul@101 | 91 | |
paul@101 | 92 | def process_graph(self, text): |
paul@101 | 93 | |
paul@101 | 94 | "Process the graph 'text' using the known directives." |
paul@101 | 95 | |
paul@101 | 96 | filter = self.directives.get("filter", ["dot"])[0] |
paul@101 | 97 | format = self.directives.get("format", ["svg"])[0] |
paul@101 | 98 | transforms = self.directives.get("transform", []) |
paul@101 | 99 | |
paul@189 | 100 | inline = format == "svg" |
paul@189 | 101 | |
paul@189 | 102 | # Non-inline graph output is stored for a known page only. |
paul@144 | 103 | |
paul@165 | 104 | pagename = self.metadata.get("pagename") |
paul@189 | 105 | |
paul@189 | 106 | if not inline: |
paul@189 | 107 | if not pagename: |
paul@189 | 108 | return |
paul@144 | 109 | |
paul@189 | 110 | # Get an identifier and usable filename to store the output. |
paul@101 | 111 | |
paul@189 | 112 | identifier = get_output_identifier(text) |
paul@189 | 113 | attachment = "%s.%s" % (identifier, format) |
paul@189 | 114 | filename = self.output.get_attachment_filename(pagename, attachment) |
paul@189 | 115 | |
paul@189 | 116 | # Handle situations where no independent output is permitted. |
paul@101 | 117 | |
paul@189 | 118 | if not filename: |
paul@189 | 119 | return |
paul@104 | 120 | |
paul@189 | 121 | # Make sure that page attachments can be stored. |
paul@104 | 122 | |
paul@189 | 123 | self.output.ensure_attachments(pagename) |
paul@189 | 124 | target, _label = self.linker.translate("attachment:%s" % attachment) |
paul@144 | 125 | |
paul@189 | 126 | # No filename is defined for inline output. |
paul@189 | 127 | |
paul@189 | 128 | else: |
paul@189 | 129 | filename = None |
paul@144 | 130 | |
paul@101 | 131 | # Permit imagemaps only for image formats. |
paul@101 | 132 | |
paul@101 | 133 | if format in IMAGE_FORMATS: |
paul@101 | 134 | cmapx = self.directives.has_key("cmapx") |
paul@101 | 135 | |
paul@101 | 136 | # Configure Graphviz and invoke it. |
paul@101 | 137 | |
paul@189 | 138 | graphviz = Graphviz(filter, text) |
paul@101 | 139 | graphviz.call(format, transforms, filename) |
paul@101 | 140 | |
paul@101 | 141 | # Obtain any metadata. |
paul@101 | 142 | |
paul@101 | 143 | attributes = select_keys(graphviz.get_metadata(), ["width", "height"]) |
paul@101 | 144 | |
paul@101 | 145 | # For image output, create a file directly and reference it. |
paul@101 | 146 | |
paul@101 | 147 | if format in IMAGE_FORMATS: |
paul@101 | 148 | |
paul@101 | 149 | # Produce, embed and reference an imagemap if requested. |
paul@101 | 150 | |
paul@101 | 151 | if cmapx: |
paul@101 | 152 | graphviz.call("cmapx") |
paul@101 | 153 | mapid = graphviz.get_metadata().get("id") |
paul@101 | 154 | |
paul@101 | 155 | if mapid: |
paul@101 | 156 | self.raw(graphviz.get_output()) |
paul@101 | 157 | attributes["usemap"] = "#%s" % im_attributes["id"] |
paul@101 | 158 | |
paul@144 | 159 | self.image(target, attributes) |
paul@101 | 160 | |
paul@101 | 161 | # For other output, create a file and embed the object. |
paul@101 | 162 | |
paul@189 | 163 | elif not inline: |
paul@189 | 164 | self.object(target, attributes) |
paul@189 | 165 | |
paul@189 | 166 | # Or for inline output, emit it in the document itself. |
paul@189 | 167 | |
paul@101 | 168 | else: |
paul@189 | 169 | self.out(graphviz.get_inline_output()) |
paul@101 | 170 | |
paul@101 | 171 | serialiser = HTMLGraphvizSerialiser |
paul@101 | 172 | |
paul@101 | 173 | # vim: tabstop=4 expandtab shiftwidth=4 |