diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index 4001a853bd..c31c1c530f 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -96,6 +96,15 @@ type RenderOpts struct { OmitVersion *bool } +// escapeClassNames escapes HTML special characters in class names to produce valid SVG attributes. +func escapeClassNames(classes []string) []string { + escaped := make([]string, len(classes)) + for i, class := range classes { + escaped[i] = html.EscapeString(class) + } + return escaped +} + func dimensions(diagram *d2target.Diagram, pad int) (left, top, width, height int) { tl, br := diagram.BoundingBox() left = tl.X - pad @@ -1010,7 +1019,7 @@ func drawConnection(writer io.Writer, diagramHash string, connection d2target.Co } classes := []string{base64.URLEncoding.EncodeToString([]byte(svg.EscapeText(connection.ID)))} - classes = append(classes, connection.Classes...) + classes = append(classes, escapeClassNames(connection.Classes)...) classStr := fmt.Sprintf(` class="%s"`, strings.Join(classes, " ")) fmt.Fprintf(writer, ``, classStr, opacityStyle) @@ -1628,7 +1637,7 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape if targetShape.Animated { classes = append(classes, "animated-shape") } - classes = append(classes, targetShape.Classes...) + classes = append(classes, escapeClassNames(targetShape.Classes)...) classStr := fmt.Sprintf(` class="%s"`, strings.Join(classes, " ")) fmt.Fprintf(writer, ``, classStr, opacityStyle) tl := geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y)) diff --git a/d2renderers/d2svg/d2svg_test.go b/d2renderers/d2svg/d2svg_test.go index 2d99dfe321..1093075ef2 100644 --- a/d2renderers/d2svg/d2svg_test.go +++ b/d2renderers/d2svg/d2svg_test.go @@ -81,3 +81,56 @@ func TestSortObjects(t *testing.T) { } } } + +func TestEscapeClassNames(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + { + name: "empty slice", + input: []string{}, + expected: []string{}, + }, + { + name: "no special characters", + input: []string{"foo", "bar", "baz"}, + expected: []string{"foo", "bar", "baz"}, + }, + { + name: "with double quotes", + input: []string{"test label: \"Hello World\""}, + expected: []string{"test label: "Hello World""}, + }, + { + name: "with single quotes", + input: []string{"test's value"}, + expected: []string{"test's value"}, + }, + { + name: "with angle brackets", + input: []string{"