Skip to content

Commit d9fcaf7

Browse files
committed
refactor: build clip XML via XElement instead of string interpolation
1 parent a0bee31 commit d9fcaf7

7 files changed

Lines changed: 34 additions & 22 deletions

File tree

src/SharpFM.Model/ClipTypes/IClipTypeStrategy.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ public interface IClipTypeStrategy
2626

2727
/// <summary>
2828
/// Produce a starter XML body for a fresh clip with the given name. Used by
29-
/// "new clip" flows in the host and by plugins.
29+
/// "new clip" flows in the host and by plugins. Build with
30+
/// <see cref="System.Xml.Linq.XElement"/> rather than string concatenation so
31+
/// XML metacharacters in <paramref name="clipName"/> are escaped by the framework.
3032
/// </summary>
3133
string DefaultXml(string clipName);
3234

src/SharpFM.Model/ClipTypes/TableClipStrategy.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Xml.Linq;
66
using SharpFM.Model.Parsing;
77
using SharpFM.Model.Schema;
8-
using SharpFM.Model.Scripting;
98

109
namespace SharpFM.Model.ClipTypes;
1110

@@ -65,10 +64,15 @@ public ClipParseResult Parse(string xml)
6564
return new ParseSuccess(new TableClipModel(table), report);
6665
}
6766

68-
public string DefaultXml(string clipName) =>
69-
_wrapsBaseTable
70-
? $"<fmxmlsnippet type=\"FMObjectList\"><BaseTable name=\"{XmlHelpers.XmlEscape(clipName)}\"></BaseTable></fmxmlsnippet>"
71-
: "<fmxmlsnippet type=\"FMObjectList\"></fmxmlsnippet>";
67+
public string DefaultXml(string clipName)
68+
{
69+
var snippet = new XElement("fmxmlsnippet", new XAttribute("type", "FMObjectList"));
70+
if (_wrapsBaseTable)
71+
{
72+
snippet.Add(new XElement("BaseTable", new XAttribute("name", clipName)));
73+
}
74+
return snippet.ToString(SaveOptions.DisableFormatting);
75+
}
7276

7377
public string? TryGetSourceName(string xml)
7478
{

src/SharpFM.Model/Schema/FmField.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public XElement ToXml()
189189
// Calculation
190190
if (!string.IsNullOrEmpty(Calculation))
191191
{
192-
var calcEl = XElement.Parse($"<Calculation><![CDATA[{Calculation}]]></Calculation>");
192+
var calcEl = new XElement("Calculation", new XCData(Calculation));
193193
if (AlwaysEvaluate)
194194
calcEl.Add(new XAttribute("alwaysEvaluate", "True"));
195195
if (!string.IsNullOrEmpty(CalculationContext))
@@ -222,10 +222,10 @@ public XElement ToXml()
222222
new XAttribute("nextSerialNumber", AutoEnterValue ?? "1")));
223223
break;
224224
case AutoEnterType.ConstantData:
225-
autoEl.Add(XElement.Parse($"<ConstantData><![CDATA[{AutoEnterValue ?? ""}]]></ConstantData>"));
225+
autoEl.Add(new XElement("ConstantData", new XCData(AutoEnterValue ?? "")));
226226
break;
227227
case AutoEnterType.Calculation:
228-
autoEl.Add(XElement.Parse($"<Calculation><![CDATA[{AutoEnterValue ?? ""}]]></Calculation>"));
228+
autoEl.Add(new XElement("Calculation", new XCData(AutoEnterValue ?? "")));
229229
break;
230230
}
231231

@@ -249,16 +249,16 @@ public XElement ToXml()
249249
{
250250
var rangeEl = new XElement("Range");
251251
if (RangeMin != null)
252-
rangeEl.Add(XElement.Parse($"<MinimumValue><![CDATA[{RangeMin}]]></MinimumValue>"));
252+
rangeEl.Add(new XElement("MinimumValue", new XCData(RangeMin)));
253253
if (RangeMax != null)
254-
rangeEl.Add(XElement.Parse($"<MaximumValue><![CDATA[{RangeMax}]]></MaximumValue>"));
254+
rangeEl.Add(new XElement("MaximumValue", new XCData(RangeMax)));
255255
valEl.Add(rangeEl);
256256
}
257257

258258
if (ValidationCalculation != null)
259-
valEl.Add(XElement.Parse($"<StrictValidation><![CDATA[{ValidationCalculation}]]></StrictValidation>"));
259+
valEl.Add(new XElement("StrictValidation", new XCData(ValidationCalculation)));
260260
if (ErrorMessage != null)
261-
valEl.Add(XElement.Parse($"<ErrorMessage><![CDATA[{ErrorMessage}]]></ErrorMessage>"));
261+
valEl.Add(new XElement("ErrorMessage", new XCData(ErrorMessage)));
262262

263263
el.Add(valEl);
264264
}

src/SharpFM.Model/Scripting/Values/Calculation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public sealed record Calculation(string Text)
1616
/// common case in FileMaker script XML.
1717
/// </summary>
1818
public XElement ToXml(string elementName = "Calculation") =>
19-
XElement.Parse($"<{elementName}><![CDATA[{Text}]]></{elementName}>");
19+
new(elementName, new XCData(Text));
2020

2121
/// <summary>
2222
/// Parse the text body of the element (CDATA is transparent to

src/SharpFM.Model/Scripting/XmlHelpers.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,6 @@ namespace SharpFM.Model.Scripting;
66

77
public static class XmlHelpers
88
{
9-
public static string XmlEscape(string s)
10-
{
11-
return s.Replace("&", "&amp;")
12-
.Replace("<", "&lt;")
13-
.Replace(">", "&gt;")
14-
.Replace("\"", "&quot;");
15-
}
16-
179
public static string Unquote(string s)
1810
{
1911
if (s.Length >= 2 && s[0] == '"' && s[^1] == '"')

tests/SharpFM.Tests/ClipTypes/TableClipStrategyTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public void DefaultXml_ProducesParseableSnippet()
100100
[InlineData("My \"favorite\" stuff")]
101101
[InlineData("A & B")]
102102
[InlineData("<Angle>")]
103+
[InlineData("O'Brien")]
103104
public void Table_DefaultXml_EscapesPunctuationInName(string clipName)
104105
{
105106
var seed = TableClipStrategy.Table.DefaultXml(clipName);

tests/SharpFM.Tests/Scripting/Values/CalculationTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,17 @@ public void RoundTrip_PreservesComplexExpression()
5555

5656
Assert.Equal(expr, roundTripped.Value);
5757
}
58+
59+
[Theory]
60+
[InlineData("a < b & c > d")]
61+
[InlineData("Quote(\"hello\")")]
62+
[InlineData("If ( name = \"O'Brien\" ; 1 ; 0 )")]
63+
public void RoundTrip_PreservesXmlMetacharacters(string expr)
64+
{
65+
var emitted = new Calculation(expr).ToXml();
66+
var serialized = emitted.ToString();
67+
var reparsed = Calculation.FromXml(XElement.Parse(serialized));
68+
69+
Assert.Equal(expr, reparsed.Text);
70+
}
5871
}

0 commit comments

Comments
 (0)