Skip to content

Commit a128944

Browse files
authored
Merge pull request #597 from nightroman/dev
Escape labels in `UmlDotGraphStyle`
2 parents a05ed25 + 800849e commit a128944

File tree

2 files changed

+63
-39
lines changed

2 files changed

+63
-39
lines changed

src/Stateless/Graph/UmlDotGraphStyle.cs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,20 @@ public override string GetPrefix()
2929
public override string FormatOneCluster(SuperState stateInfo)
3030
{
3131
string stateRepresentationString = "";
32-
var sourceName = stateInfo.StateName;
3332

34-
StringBuilder label = new StringBuilder($"{sourceName}");
33+
StringBuilder label = new StringBuilder($"{EscapeLabel(stateInfo.StateName)}");
3534

3635
if (stateInfo.EntryActions.Count > 0 || stateInfo.ExitActions.Count > 0)
3736
{
3837
label.Append("\\n----------");
39-
label.Append(string.Concat(stateInfo.EntryActions.Select(act => "\\nentry / " + act)));
40-
label.Append(string.Concat(stateInfo.ExitActions.Select(act => "\\nexit / " + act)));
38+
label.Append(string.Concat(stateInfo.EntryActions.Select(act => "\\nentry / " + EscapeLabel(act))));
39+
label.Append(string.Concat(stateInfo.ExitActions.Select(act => "\\nexit / " + EscapeLabel(act))));
4140
}
4241

4342
stateRepresentationString = "\n"
44-
+ $"subgraph \"cluster{stateInfo.NodeName}\"" + "\n"
43+
+ $"subgraph \"cluster{EscapeLabel(stateInfo.NodeName)}\"" + "\n"
4544
+ "\t{" + "\n"
46-
+ $"\tlabel = \"{label.ToString()}\"" + "\n";
45+
+ $"\tlabel = \"{label}\"" + "\n";
4746

4847
foreach (var subState in stateInfo.SubStates)
4948
{
@@ -62,16 +61,18 @@ public override string FormatOneCluster(SuperState stateInfo)
6261
/// <inheritdoc/>
6362
public override string FormatOneState(State state)
6463
{
64+
var escapedStateName = EscapeLabel(state.StateName);
65+
6566
if (state.EntryActions.Count == 0 && state.ExitActions.Count == 0)
66-
return $"\"{state.StateName}\" [label=\"{state.StateName}\"];\n";
67+
return $"\"{escapedStateName}\" [label=\"{escapedStateName}\"];\n";
6768

68-
string f = $"\"{state.StateName}\" [label=\"{state.StateName}|";
69+
string f = $"\"{escapedStateName}\" [label=\"{escapedStateName}|";
6970

7071
List<string> es = new List<string>();
71-
es.AddRange(state.EntryActions.Select(act => "entry / " + act));
72-
es.AddRange(state.ExitActions.Select(act => "exit / " + act));
72+
es.AddRange(state.EntryActions.Select(act => "entry / " + EscapeLabel(act)));
73+
es.AddRange(state.ExitActions.Select(act => "exit / " + EscapeLabel(act)));
7374

74-
f += String.Join("\\n", es);
75+
f += string.Join("\\n", es);
7576

7677
f += "\"];\n";
7778

@@ -110,7 +111,7 @@ public override string FormatOneTransition(string sourceNodeName, string trigger
110111
/// <inheritdoc/>
111112
public override string FormatOneDecisionNode(string nodeName, string label)
112113
{
113-
return $"\"{nodeName}\" [shape = \"diamond\", label = \"{label}\"];\n";
114+
return $"\"{EscapeLabel(nodeName)}\" [shape = \"diamond\", label = \"{EscapeLabel(label)}\"];\n";
114115
}
115116

116117
/// <summary>
@@ -121,17 +122,22 @@ public override string FormatOneDecisionNode(string nodeName, string label)
121122
public override string GetInitialTransition(StateInfo initialState)
122123
{
123124
var initialStateName = initialState.UnderlyingState.ToString();
124-
string dirgraphText = System.Environment.NewLine + $" init [label=\"\", shape=point];";
125-
dirgraphText += System.Environment.NewLine + $" init -> \"{initialStateName}\"[style = \"solid\"]";
125+
string dirgraphText = Environment.NewLine + $" init [label=\"\", shape=point];";
126+
dirgraphText += Environment.NewLine + $" init -> \"{EscapeLabel(initialStateName)}\"[style = \"solid\"]";
126127

127-
dirgraphText += System.Environment.NewLine + "}";
128+
dirgraphText += Environment.NewLine + "}";
128129

129130
return dirgraphText;
130131
}
131132

132133
internal string FormatOneLine(string fromNodeName, string toNodeName, string label)
133134
{
134-
return $"\"{fromNodeName}\" -> \"{toNodeName}\" [style=\"solid\", label=\"{label}\"];";
135+
return $"\"{EscapeLabel(fromNodeName)}\" -> \"{EscapeLabel(toNodeName)}\" [style=\"solid\", label=\"{EscapeLabel(label)}\"];";
136+
}
137+
138+
private static string EscapeLabel(string label)
139+
{
140+
return label.Replace("\\", "\\\\").Replace("\"", "\\\"");
135141
}
136142
}
137143
}

test/Stateless.Tests/DotGraphFixture.cs

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -137,19 +137,32 @@ public void SimpleTransition()
137137
}
138138

139139
[Fact]
140-
public void SimpleTransitionUML()
140+
public void SimpleTransitionWithEscaping()
141141
{
142-
var expected = Prefix(Style.UML) + Box(Style.UML, "A") + Box(Style.UML, "B") + Line("A", "B", "X") + suffix;
142+
var state1 = "\\state \"1\"";
143+
var state2 = "\\state \"2\"";
144+
var trigger1 = "\\trigger \"1\"";
143145

144-
var sm = new StateMachine<State, Trigger>(State.A);
146+
string suffix = Environment.NewLine
147+
+ $" init [label=\"\", shape=point];" + Environment.NewLine
148+
+ $" init -> \"{EscapeLabel(state1)}\"[style = \"solid\"]" + Environment.NewLine
149+
+ "}";
145150

146-
sm.Configure(State.A)
147-
.Permit(Trigger.X, State.B);
151+
var expected =
152+
Prefix(Style.UML) +
153+
Box(Style.UML, EscapeLabel(state1)) +
154+
Box(Style.UML, EscapeLabel(state2)) +
155+
Line(EscapeLabel(state1), EscapeLabel(state2), EscapeLabel(trigger1)) + suffix;
156+
157+
var sm = new StateMachine<string, string>(state1);
158+
159+
sm.Configure(state1)
160+
.Permit(trigger1, state2);
148161

149162
string dotGraph = UmlDotGraph.Format(sm.GetInfo());
150163

151164
#if WRITE_DOTS_TO_FOLDER
152-
System.IO.File.WriteAllText(DestinationFolder + "SimpleTransitionUML.dot", dotGraph);
165+
System.IO.File.WriteAllText(DestinationFolder + "SimpleTransitionWithEscaping.dot", dotGraph);
153166
#endif
154167

155168
Assert.Equal(expected, dotGraph);
@@ -445,42 +458,46 @@ public void OnEntryWithTriggerParameter()
445458
[Fact]
446459
public void SpacedUmlWithSubstate()
447460
{
448-
string StateA = "State A";
449-
string StateB = "State B";
450-
string StateC = "State C";
451-
string StateD = "State D";
452-
string TriggerX = "Trigger X";
453-
string TriggerY = "Trigger Y";
461+
string StateA = "State \"A\"";
462+
string StateB = "State \"B\"";
463+
string StateC = "State \"C\"";
464+
string StateD = "State \"D\"";
465+
string TriggerX = "Trigger \"X\"";
466+
string TriggerY = "Trigger \"Y\"";
467+
string EnterA = "Enter \"A\"";
468+
string EnterD = "Enter \"D\"";
469+
string ExitA = "Exit \"A\"";
454470

455471
var expected = Prefix(Style.UML)
456-
+ Subgraph(Style.UML, StateD, $"{StateD}\\n----------\\nentry / Enter D",
457-
Box(Style.UML, StateB)
458-
+ Box(Style.UML, StateC))
459-
+ Box(Style.UML, StateA, new List<string> { "Enter A" }, new List<string> { "Exit A" })
460-
+ Line(StateA, StateB, TriggerX) + Line(StateA, StateC, TriggerY)
472+
+ Subgraph(Style.UML, EscapeLabel(StateD), $"{EscapeLabel(StateD)}\\n----------\\nentry / {EscapeLabel(EnterD)}",
473+
Box(Style.UML, EscapeLabel(StateB))
474+
+ Box(Style.UML, EscapeLabel(StateC)))
475+
+ Box(Style.UML, EscapeLabel(StateA), new List<string> { EscapeLabel(EnterA) }, new List<string> { EscapeLabel(ExitA) })
476+
+ Line(EscapeLabel(StateA), EscapeLabel(StateB), EscapeLabel(TriggerX))
477+
+ Line(EscapeLabel(StateA), EscapeLabel(StateC), EscapeLabel(TriggerY))
461478
+ Environment.NewLine
462479
+ $" init [label=\"\", shape=point];" + Environment.NewLine
463-
+ $" init -> \"{StateA}\"[style = \"solid\"]" + Environment.NewLine
480+
+ $" init -> \"{EscapeLabel(StateA)}\"[style = \"solid\"]" + Environment.NewLine
464481
+ "}";
465482

466-
var sm = new StateMachine<string, string>("State A");
483+
var sm = new StateMachine<string, string>(StateA);
467484

468485
sm.Configure(StateA)
469486
.Permit(TriggerX, StateB)
470487
.Permit(TriggerY, StateC)
471-
.OnEntry(TestEntryAction, "Enter A")
472-
.OnExit(TestEntryAction, "Exit A");
488+
.OnEntry(TestEntryAction, EnterA)
489+
.OnExit(TestEntryAction, ExitA);
473490

474491
sm.Configure(StateB)
475492
.SubstateOf(StateD);
476493
sm.Configure(StateC)
477494
.SubstateOf(StateD);
478495
sm.Configure(StateD)
479-
.OnEntry(TestEntryAction, "Enter D");
496+
.OnEntry(TestEntryAction, EnterD);
480497

481498
string dotGraph = UmlDotGraph.Format(sm.GetInfo());
482499
#if WRITE_DOTS_TO_FOLDER
483-
System.IO.File.WriteAllText(DestinationFolder + "UmlWithSubstate.dot", dotGraph);
500+
System.IO.File.WriteAllText(DestinationFolder + "SpacedUmlWithSubstate.dot", dotGraph);
484501
#endif
485502

486503
Assert.Equal(expected, dotGraph);
@@ -716,5 +733,6 @@ public void Reentrant_Transition_Shows_Entry_Action_When_Action_Is_Configured_Wi
716733
private void TestEntryAction() { }
717734
private void TestEntryActionString(string val) { }
718735
private State DestinationSelector() { return State.A; }
736+
private static string EscapeLabel(string label) { return label.Replace("\\", "\\\\").Replace("\"", "\\\""); }
719737
}
720738
}

0 commit comments

Comments
 (0)