Skip to content

Commit 624f62c

Browse files
committed
feat: derive clip name from FileMaker metadata on paste
1 parent 7e2e7c7 commit 624f62c

11 files changed

Lines changed: 212 additions & 0 deletions

src/SharpFM.Model/ClipTypes/IClipTypeStrategy.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,13 @@ public interface IClipTypeStrategy
2929
/// "new clip" flows in the host and by plugins.
3030
/// </summary>
3131
string DefaultXml(string clipName);
32+
33+
/// <summary>
34+
/// Pull a clip-display-name hint out of <paramref name="xml"/> when the
35+
/// wire format carries one (e.g. <c>&lt;Script name="…"&gt;</c> for
36+
/// <c>Mac-XMSC</c>, <c>&lt;BaseTable name="…"&gt;</c> for <c>Mac-XMTB</c>).
37+
/// Returns <c>null</c> for formats with no embedded name or when the
38+
/// element is absent. Must not throw on malformed input.
39+
/// </summary>
40+
string? TryGetSourceName(string xml);
3241
}

src/SharpFM.Model/ClipTypes/LayoutClipStrategy.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ public ClipParseResult Parse(string xml)
2929

3030
public string DefaultXml(string clipName) =>
3131
"<fmxmlsnippet type=\"FMObjectList\"></fmxmlsnippet>";
32+
33+
public string? TryGetSourceName(string xml) => null;
3234
}

src/SharpFM.Model/ClipTypes/OpaqueClipStrategy.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,6 @@ public ClipParseResult Parse(string xml)
4444

4545
public string DefaultXml(string clipName) =>
4646
"<fmxmlsnippet type=\"FMObjectList\"></fmxmlsnippet>";
47+
48+
public string? TryGetSourceName(string xml) => null;
4749
}

src/SharpFM.Model/ClipTypes/ScriptClipStrategy.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Xml;
5+
using System.Xml.Linq;
36
using SharpFM.Model.Parsing;
47
using SharpFM.Model.Scripting;
58

@@ -63,4 +66,17 @@ public ClipParseResult Parse(string xml)
6366

6467
public string DefaultXml(string clipName) =>
6568
"<fmxmlsnippet type=\"FMObjectList\"></fmxmlsnippet>";
69+
70+
public string? TryGetSourceName(string xml)
71+
{
72+
try
73+
{
74+
var name = XDocument.Parse(xml).Descendants("Script").FirstOrDefault()?.Attribute("name")?.Value;
75+
return string.IsNullOrEmpty(name) ? null : name;
76+
}
77+
catch (XmlException)
78+
{
79+
return null;
80+
}
81+
}
6682
}

src/SharpFM.Model/ClipTypes/TableClipStrategy.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Xml;
5+
using System.Xml.Linq;
36
using SharpFM.Model.Parsing;
47
using SharpFM.Model.Schema;
58
using SharpFM.Model.Scripting;
@@ -66,4 +69,17 @@ public string DefaultXml(string clipName) =>
6669
_wrapsBaseTable
6770
? $"<fmxmlsnippet type=\"FMObjectList\"><BaseTable name=\"{XmlHelpers.XmlEscape(clipName)}\"></BaseTable></fmxmlsnippet>"
6871
: "<fmxmlsnippet type=\"FMObjectList\"></fmxmlsnippet>";
72+
73+
public string? TryGetSourceName(string xml)
74+
{
75+
try
76+
{
77+
var name = XDocument.Parse(xml).Descendants("BaseTable").FirstOrDefault()?.Attribute("name")?.Value;
78+
return string.IsNullOrEmpty(name) ? null : name;
79+
}
80+
catch (XmlException)
81+
{
82+
return null;
83+
}
84+
}
6985
}

src/SharpFM/ViewModels/MainWindowViewModel.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,19 @@ public async Task CopyAsClass()
334334
}
335335
}
336336

337+
// Pick a clip name that doesn't collide with anything already loaded.
338+
// Returns the input when it's free, otherwise appends "(2)", "(3)", … until
339+
// a free slot is found.
340+
private string UniqueClipName(string desired)
341+
{
342+
if (FileMakerClips.All(c => c.Clip.Name != desired)) return desired;
343+
for (var n = 2; ; n++)
344+
{
345+
var candidate = $"{desired} ({n})";
346+
if (FileMakerClips.All(c => c.Clip.Name != candidate)) return candidate;
347+
}
348+
}
349+
337350
public async Task PasteFileMakerClipData()
338351
{
339352
try
@@ -355,6 +368,10 @@ public async Task PasteFileMakerClipData()
355368
// don't add duplicates
356369
if (FileMakerClips.Any(k => k.Clip.Xml == clip.Xml)) continue;
357370

371+
var sourceName = ClipTypeRegistry.For(format).TryGetSourceName(clip.Xml);
372+
var desired = string.IsNullOrWhiteSpace(sourceName) ? "new-clip" : sourceName;
373+
clip = clip.Rename(UniqueClipName(desired));
374+
358375
lastAdded = new ClipViewModel(clip);
359376
FileMakerClips.Add(lastAdded);
360377
count++;

tests/SharpFM.Tests/ClipTypes/LayoutClipStrategyTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ public void Identity_IsLayout()
1212
Assert.Equal("Layout", LayoutClipStrategy.Instance.DisplayName);
1313
}
1414

15+
[Fact]
16+
public void TryGetSourceName_ReturnsNull()
17+
{
18+
var name = LayoutClipStrategy.Instance.TryGetSourceName(
19+
"<fmxmlsnippet type=\"FMObjectList\"><Layout/></fmxmlsnippet>");
20+
21+
Assert.Null(name);
22+
}
23+
1524
[Fact]
1625
public void Parse_ValidLayoutSnippet_ReturnsSuccess()
1726
{

tests/SharpFM.Tests/ClipTypes/OpaqueClipStrategyTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,12 @@ public void DefaultXml_ProducesParseableSnippet()
5353
var result = OpaqueClipStrategy.Instance.Parse(seed);
5454
Assert.IsType<ParseSuccess>(result);
5555
}
56+
57+
[Fact]
58+
public void TryGetSourceName_ReturnsNull()
59+
{
60+
var name = OpaqueClipStrategy.Instance.TryGetSourceName("<root name=\"Whatever\"/>");
61+
62+
Assert.Null(name);
63+
}
5664
}

tests/SharpFM.Tests/ClipTypes/ScriptClipStrategyTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,41 @@ public void StepsAndScript_HaveDistinctIdentities()
1414
Assert.Equal("Script", ScriptClipStrategy.Script.DisplayName);
1515
}
1616

17+
[Fact]
18+
public void TryGetSourceName_ReturnsScriptNameAttribute()
19+
{
20+
var name = ScriptClipStrategy.Script.TryGetSourceName(
21+
"<fmxmlsnippet type=\"FMObjectList\"><Script name=\"FooBar\"></Script></fmxmlsnippet>");
22+
23+
Assert.Equal("FooBar", name);
24+
}
25+
26+
[Fact]
27+
public void TryGetSourceName_StepsClipWithoutScriptWrapper_ReturnsNull()
28+
{
29+
var name = ScriptClipStrategy.Steps.TryGetSourceName(
30+
"<fmxmlsnippet type=\"FMObjectList\"><Step enable=\"True\" id=\"141\" name=\"Set Variable\"/></fmxmlsnippet>");
31+
32+
Assert.Null(name);
33+
}
34+
35+
[Fact]
36+
public void TryGetSourceName_MalformedXml_ReturnsNull()
37+
{
38+
var name = ScriptClipStrategy.Script.TryGetSourceName("<oops>");
39+
40+
Assert.Null(name);
41+
}
42+
43+
[Fact]
44+
public void TryGetSourceName_PreservesPunctuationInName()
45+
{
46+
var name = ScriptClipStrategy.Script.TryGetSourceName(
47+
"<fmxmlsnippet type=\"FMObjectList\"><Script name=\"My &quot;favorite&quot; script\"></Script></fmxmlsnippet>");
48+
49+
Assert.Equal("My \"favorite\" script", name);
50+
}
51+
1752
[Fact]
1853
public void Parse_EmptyScriptSnippet_ReturnsLosslessSuccess()
1954
{

tests/SharpFM.Tests/ClipTypes/TableClipStrategyTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,32 @@ public void TableAndField_HaveDistinctIdentities()
1212
Assert.Equal("Mac-XMFD", TableClipStrategy.Field.FormatId);
1313
}
1414

15+
[Fact]
16+
public void TryGetSourceName_ReturnsBaseTableNameAttribute()
17+
{
18+
var name = TableClipStrategy.Table.TryGetSourceName(
19+
"<fmxmlsnippet type=\"FMObjectList\"><BaseTable name=\"Customers\" id=\"1\"></BaseTable></fmxmlsnippet>");
20+
21+
Assert.Equal("Customers", name);
22+
}
23+
24+
[Fact]
25+
public void TryGetSourceName_FieldClipWithoutBaseTable_ReturnsNull()
26+
{
27+
var name = TableClipStrategy.Field.TryGetSourceName(
28+
"<fmxmlsnippet type=\"FMObjectList\"><Field id=\"1\" name=\"ID\"/></fmxmlsnippet>");
29+
30+
Assert.Null(name);
31+
}
32+
33+
[Fact]
34+
public void TryGetSourceName_MalformedXml_ReturnsNull()
35+
{
36+
var name = TableClipStrategy.Table.TryGetSourceName("<oops>");
37+
38+
Assert.Null(name);
39+
}
40+
1541
[Fact]
1642
public void Parse_ValidTable_ReturnsSuccess()
1743
{

0 commit comments

Comments
 (0)