flowchart.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. // Package renderer provides rendering functionality to convert AST back to Mermaid syntax.
  2. // Based on the rendering patterns from mermaid.js
  3. package renderer
  4. import (
  5. "fmt"
  6. "strings"
  7. "mermaid-go/pkg/ast"
  8. )
  9. // Renderer interface for converting diagrams back to mermaid syntax
  10. type Renderer interface {
  11. Render(diagram ast.Diagram) string
  12. }
  13. // FlowchartRenderer renders flowchart diagrams back to mermaid syntax
  14. type FlowchartRenderer struct{}
  15. // NewFlowchartRenderer creates a new flowchart renderer
  16. func NewFlowchartRenderer() *FlowchartRenderer {
  17. return &FlowchartRenderer{}
  18. }
  19. // Render converts a flowchart back to mermaid syntax
  20. func (r *FlowchartRenderer) Render(flowchart *ast.Flowchart) (string, error) {
  21. var builder strings.Builder
  22. // Write graph declaration with direction
  23. direction := flowchart.Direction
  24. if direction == "" {
  25. direction = "TD" // Default direction
  26. }
  27. builder.WriteString(fmt.Sprintf("graph %s\n", direction))
  28. // Collect all vertices that are referenced in edges for proper node definitions
  29. referencedVertices := make(map[string]bool)
  30. for _, edge := range flowchart.Edges {
  31. referencedVertices[edge.Start] = true
  32. referencedVertices[edge.End] = true
  33. }
  34. // Render edges (which implicitly define vertices)
  35. for _, edge := range flowchart.Edges {
  36. line := r.renderEdge(edge, flowchart.Vertices)
  37. builder.WriteString(" ")
  38. builder.WriteString(line)
  39. builder.WriteString("\n")
  40. }
  41. // Render standalone vertices (not connected to any edges)
  42. for id, vertex := range flowchart.Vertices {
  43. if !referencedVertices[id] {
  44. line := r.renderStandaloneVertex(vertex)
  45. if line != "" {
  46. builder.WriteString(" ")
  47. builder.WriteString(line)
  48. builder.WriteString("\n")
  49. }
  50. }
  51. }
  52. // Render subgraphs
  53. for _, subGraph := range flowchart.SubGraphs {
  54. r.renderSubGraph(&builder, subGraph)
  55. }
  56. // Render class definitions
  57. for _, class := range flowchart.Classes {
  58. r.renderClassDef(&builder, class)
  59. }
  60. return builder.String(), nil
  61. }
  62. // renderEdge renders an edge with its connected vertices
  63. func (r *FlowchartRenderer) renderEdge(edge *ast.FlowEdge, vertices map[string]*ast.FlowVertex) string {
  64. startPart := r.renderVertexReference(edge.Start, vertices)
  65. arrow := r.renderArrow(edge)
  66. endPart := r.renderVertexReference(edge.End, vertices)
  67. return fmt.Sprintf("%s %s %s", startPart, arrow, endPart)
  68. }
  69. // renderVertexReference renders a vertex with its shape and label
  70. func (r *FlowchartRenderer) renderVertexReference(vertexID string, vertices map[string]*ast.FlowVertex) string {
  71. vertex := vertices[vertexID]
  72. if vertex == nil {
  73. return vertexID
  74. }
  75. return r.renderVertexWithShape(vertex)
  76. }
  77. // renderVertexWithShape renders a vertex with its shape delimiters
  78. func (r *FlowchartRenderer) renderVertexWithShape(vertex *ast.FlowVertex) string {
  79. text := vertex.ID
  80. if vertex.Text != nil && *vertex.Text != "" {
  81. text = *vertex.Text
  82. }
  83. // Determine shape based on vertex type
  84. vertexType := ast.VertexTypeRect // default
  85. if vertex.Type != nil {
  86. vertexType = *vertex.Type
  87. }
  88. switch vertexType {
  89. case ast.VertexTypeRect, ast.VertexTypeSquare:
  90. return fmt.Sprintf("%s[%s]", vertex.ID, text)
  91. case ast.VertexTypeRound:
  92. return fmt.Sprintf("%s(%s)", vertex.ID, text)
  93. case ast.VertexTypeCircle:
  94. return fmt.Sprintf("%s((%s))", vertex.ID, text)
  95. case ast.VertexTypeDiamond:
  96. return fmt.Sprintf("%s{%s}", vertex.ID, text)
  97. case ast.VertexTypeStadium:
  98. return fmt.Sprintf("%s([%s])", vertex.ID, text)
  99. case ast.VertexTypeCylinder:
  100. return fmt.Sprintf("%s[(%s)]", vertex.ID, text)
  101. case ast.VertexTypeSubroutine:
  102. return fmt.Sprintf("%s[[%s]]", vertex.ID, text)
  103. case ast.VertexTypeHexagon:
  104. return fmt.Sprintf("%s{{%s}}", vertex.ID, text)
  105. case ast.VertexTypeOdd:
  106. return fmt.Sprintf("%s>%s]", vertex.ID, text)
  107. case ast.VertexTypeTrapezoid:
  108. return fmt.Sprintf("%s[/%s/]", vertex.ID, text)
  109. case ast.VertexTypeInvTrapezoid:
  110. return fmt.Sprintf("%s[\\%s\\]", vertex.ID, text)
  111. default:
  112. return fmt.Sprintf("%s[%s]", vertex.ID, text)
  113. }
  114. }
  115. // renderArrow renders the arrow part of an edge with optional label
  116. func (r *FlowchartRenderer) renderArrow(edge *ast.FlowEdge) string {
  117. // Build arrow based on stroke and type
  118. arrow := r.buildArrowString(edge)
  119. // Add label if present
  120. if edge.Text != "" {
  121. return fmt.Sprintf("%s|%s|%s", r.getArrowStart(edge), edge.Text, r.getArrowEnd(edge))
  122. }
  123. return arrow
  124. }
  125. // buildArrowString creates the arrow string based on edge properties
  126. func (r *FlowchartRenderer) buildArrowString(edge *ast.FlowEdge) string {
  127. stroke := ast.StrokeNormal
  128. if edge.Stroke != nil {
  129. stroke = *edge.Stroke
  130. }
  131. edgeType := "arrow_point"
  132. if edge.Type != nil {
  133. edgeType = *edge.Type
  134. }
  135. switch stroke {
  136. case ast.StrokeNormal:
  137. switch edgeType {
  138. case "arrow_point":
  139. return "-->"
  140. case "arrow_cross":
  141. return "--x"
  142. case "arrow_circle":
  143. return "--o"
  144. case "arrow_open":
  145. return "---"
  146. default:
  147. return "-->"
  148. }
  149. case ast.StrokeThick:
  150. return "==>"
  151. case ast.StrokeDotted:
  152. return "-.->"
  153. case ast.StrokeInvisible:
  154. return "~~~"
  155. default:
  156. return "-->"
  157. }
  158. }
  159. // getArrowStart returns the start part of arrow for labeled edges
  160. func (r *FlowchartRenderer) getArrowStart(edge *ast.FlowEdge) string {
  161. stroke := ast.StrokeNormal
  162. if edge.Stroke != nil {
  163. stroke = *edge.Stroke
  164. }
  165. switch stroke {
  166. case ast.StrokeThick:
  167. return "=="
  168. case ast.StrokeDotted:
  169. return "-."
  170. case ast.StrokeInvisible:
  171. return "~~"
  172. default:
  173. return "--"
  174. }
  175. }
  176. // getArrowEnd returns the end part of arrow for labeled edges
  177. func (r *FlowchartRenderer) getArrowEnd(edge *ast.FlowEdge) string {
  178. edgeType := "arrow_point"
  179. if edge.Type != nil {
  180. edgeType = *edge.Type
  181. }
  182. switch edgeType {
  183. case "arrow_point":
  184. return ">"
  185. case "arrow_cross":
  186. return "x"
  187. case "arrow_circle":
  188. return "o"
  189. case "arrow_open":
  190. return ""
  191. default:
  192. return ">"
  193. }
  194. }
  195. // renderStandaloneVertex renders vertices not connected to any edges
  196. func (r *FlowchartRenderer) renderStandaloneVertex(vertex *ast.FlowVertex) string {
  197. // Only render if vertex has explicit shape/text definition
  198. if vertex.Text != nil || vertex.Type != nil {
  199. return r.renderVertexWithShape(vertex)
  200. }
  201. return ""
  202. }
  203. // renderSubGraph renders a subgraph definition
  204. func (r *FlowchartRenderer) renderSubGraph(builder *strings.Builder, subGraph *ast.FlowSubGraph) {
  205. builder.WriteString(fmt.Sprintf(" subgraph %s", subGraph.ID))
  206. if subGraph.Title != "" {
  207. builder.WriteString(fmt.Sprintf("[%s]", subGraph.Title))
  208. }
  209. builder.WriteString("\n")
  210. // Render direction if specified
  211. if subGraph.Dir != nil && *subGraph.Dir != "" {
  212. builder.WriteString(fmt.Sprintf(" direction %s\n", *subGraph.Dir))
  213. }
  214. // Render subgraph nodes
  215. for _, nodeID := range subGraph.Nodes {
  216. builder.WriteString(fmt.Sprintf(" %s\n", nodeID))
  217. }
  218. builder.WriteString(" end\n")
  219. }
  220. // renderClassDef renders a class definition
  221. func (r *FlowchartRenderer) renderClassDef(builder *strings.Builder, class *ast.FlowClass) {
  222. if len(class.Styles) > 0 {
  223. styles := strings.Join(class.Styles, ",")
  224. builder.WriteString(fmt.Sprintf(" classDef %s %s\n", class.ID, styles))
  225. }
  226. }