11"""Utilities for generating Mermaid diagrams."""
22from collections import defaultdict
3+ import urllib .parse
4+ import urllib .request
5+ import base64
6+
7+ try :
8+ from mermaid import Mermaid
9+ from mermaid .graph import Graph
10+ MERMAID_PKG_AVAILABLE = True
11+ except ImportError :
12+ MERMAID_PKG_AVAILABLE = False
313
414# Color constants
515COLOR_BLACK = "#000"
@@ -24,6 +34,7 @@ class MermaidDiagram:
2434 def __init__ (self , name = "" , comment = None , ** kwargs ):
2535 self .name = name
2636 self .comment = comment
37+ self .format = "svg" # Default format for compatibility with graphviz
2738 self .graph_attr = {}
2839 self .node_attr = {}
2940 self .edge_attr = {}
@@ -209,18 +220,47 @@ def pipe(self, format="svg", encoding="utf-8"):
209220 """
210221 Return the diagram in the specified format.
211222
212- For Mermaid, we return the source wrapped in appropriate HTML.
213- This is meant for compatibility with the graphviz API.
223+ For SVG format, renders via mermaid.ink API.
224+ For other formats, returns the Mermaid source.
225+
226+ This maintains compatibility with the graphviz API.
214227 """
215228 source = self .source ()
229+
216230 if format == "svg" :
217- # Return raw mermaid source - rendering happens client-side
218- return source
219- elif format == "png" or format == "pdf" :
220- # For file formats, return the source as-is
221- # The management command will handle file writing
222- return source
223- return source
231+ # Render to SVG using mermaid.ink API
232+ try :
233+ svg_content = self ._render_to_svg (source )
234+ return svg_content if encoding else svg_content .encode ('utf-8' )
235+ except Exception :
236+ # Fallback to source if rendering fails
237+ return source if encoding else source .encode ('utf-8' )
238+ else :
239+ # For other formats, return the Mermaid source
240+ return source if encoding else source .encode ('utf-8' )
241+
242+ def _render_to_svg (self , mermaid_source ):
243+ """
244+ Render Mermaid source to SVG using mermaid.ink API.
245+
246+ Args:
247+ mermaid_source: Mermaid diagram source code
248+
249+ Returns:
250+ SVG content as string
251+ """
252+ # Use mermaid.ink API to render
253+ # https://mermaid.ink/svg/<base64_encoded_source>
254+ encoded = base64 .b64encode (mermaid_source .encode ('utf-8' )).decode ('ascii' )
255+ url = f"https://mermaid.ink/svg/{ encoded } "
256+
257+ try :
258+ with urllib .request .urlopen (url , timeout = 10 ) as response :
259+ svg_content = response .read ().decode ('utf-8' )
260+ return svg_content
261+ except Exception as e :
262+ # If API call fails, return a fallback SVG with error message
263+ raise Exception (f"Failed to render via mermaid.ink: { e } " )
224264
225265 def render (self , filename , directory = None , format = "svg" , cleanup = False ):
226266 """
@@ -229,17 +269,25 @@ def render(self, filename, directory=None, format="svg", cleanup=False):
229269 Args:
230270 filename: Base filename (without extension)
231271 directory: Output directory
232- format: Output format (svg, png, pdf) - for compatibility
272+ format: Output format (svg or mmd)
233273 cleanup: Cleanup intermediate files (not used for Mermaid)
234274 """
235275 import os
236276
277+ # Determine file extension and content based on format
278+ if format == "svg" :
279+ ext = "svg"
280+ content = self .pipe (format = "svg" , encoding = "utf-8" )
281+ else :
282+ ext = "mmd"
283+ content = self .source ()
284+
237285 if directory :
238- filepath = os .path .join (directory , f"{ filename } .mmd " )
286+ filepath = os .path .join (directory , f"{ filename } .{ ext } " )
239287 else :
240- filepath = f"{ filename } .mmd "
288+ filepath = f"{ filename } .{ ext } "
241289
242290 with open (filepath , "w" , encoding = "utf-8" ) as f :
243- f .write (self . source () )
291+ f .write (content )
244292
245293 return filepath
0 commit comments