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