Skip to content

Commit 558576a

Browse files
authored
Merge pull request #21 from swagfin/feature/advanced-file-map-tests
Feature/advanced file map tests
2 parents 6b1706b + 49ff17e commit 558576a

13 files changed

Lines changed: 329 additions & 36 deletions

ObjectSemantics.NET.Tests/CognitiveMapTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,5 +101,33 @@ public void Should_Escape_Xml_Char_Values_If_Option_Is_Enabled(string value, str
101101
});
102102
Assert.Equal(expected, generatedTemplate);
103103
}
104+
105+
106+
[Fact]
107+
public void Additional_Headers_And_Class_Properties_Should_Also_Be_Mapped_Combined()
108+
{
109+
Payment payment = new Payment
110+
{
111+
Id = 1,
112+
Amount = 1000,
113+
PayMethod = "CHEQUE",
114+
PayMethodId = 2,
115+
ReferenceNo = "CHEQUE0001",
116+
UserId = 242
117+
};
118+
//additional params (outside the class)
119+
Dictionary<string, object> additionalParams = new Dictionary<string, object>
120+
{
121+
{ "ReceivedBy", "John Doe"},
122+
{ "NewBalance", 1050 }
123+
};
124+
125+
string generatedTemplate = payment.Map("{{Id}}-{{ ReferenceNo }} Confirmed. ${{ Amount:N2 }} received via {{ PayMethod }}({{PayMethodId}}) from user-id {{ UserId }}. New Balance: {{ NewBalance:N2 }}, Received By: {{ReceivedBy}}.", additionalParams);
126+
127+
128+
string expectedResponse = "1-CHEQUE0001 Confirmed. $1,000.00 received via CHEQUE(2) from user-id 242. New Balance: 1,050.00, Received By: John Doe.";
129+
130+
Assert.Equal(generatedTemplate, expectedResponse);
131+
}
104132
}
105133
}

ObjectSemantics.NET.Tests/EnumerableLoopTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,5 +140,20 @@ public void Should_Map_Array_Of_String_With_Formatting()
140140
string expectedResult = " MORGAN GEORGE JANE ";
141141
Assert.Equal(expectedResult, generatedTemplate, false, true, true);
142142
}
143+
144+
[Theory]
145+
[InlineData(@"{{ #foreach(MyFriends) }}[{{ .:uppercase }}]{{ #endforeach }}")]
146+
[InlineData(@"{{# foreach(MyFriends) }}[{{ .:uppercase }}]{{# endforeach }}")]
147+
[InlineData(@"{{ #foreach(MyFriends) }}[{{ .:uppercase }}]{{ #endforeach }}")]
148+
public void Should_Evaluate_ForEach_Having_Spaces_Before_And_After_Parentheses(string template)
149+
{
150+
Person person = new Person
151+
{
152+
MyFriends = new string[] { "Morgan", "George" }
153+
};
154+
155+
string result = person.Map(template);
156+
Assert.Equal("[MORGAN][GEORGE]", result);
157+
}
143158
}
144159
}

ObjectSemantics.NET.Tests/IfConditionTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,5 +130,21 @@ public void Should_Render_Multiple_If_Condition_Statements(int age, string expec
130130
string result = person.Map(template);
131131
Assert.Equal(expected, result);
132132
}
133+
134+
135+
[Theory]
136+
[InlineData(@"{{#if(MyCars==0)}}Zero Cars{{#else}}Hmmm!{{#endif}}")]
137+
[InlineData(@"{{# if (MyCars==0)}}Zero Cars{{ # else }}Hmmm!{{ # endif}}")]
138+
[InlineData(@"{{ #if(MyCars==0) }}Zero Cars{{ #endif }}")]
139+
public void Should_Evaluate_If_Having_Spaces_Before_And_After_Parentheses(string template)
140+
{
141+
Person person = new Person
142+
{
143+
MyCars = null
144+
};
145+
146+
string result = person.Map(template);
147+
Assert.Equal("Zero Cars", result);
148+
}
133149
}
134150
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<Template font="Calibri" FontSize="10" MarginTop="10">
2+
3+
<Data FontStyle="Bold" FontSize="11" TextWrap ="True" Align="Center">TEST COMPANY</Data>
4+
<Data TextWrap ="True" Align="Center">test@gmail.com</Data>
5+
<Data TextWrap ="True" Align="Center">Test Address</Data>
6+
<Data TextWrap ="True" Align="Center">+2547000000001</Data>
7+
8+
<LineBreak/>
9+
<Data FontStyle="Bold" FontSize="12" Align="Center">CUSTOMER PAYMENT RECEIPT</Data>
10+
11+
<LineBreak/>
12+
13+
<Data>Payment No #: 12719</Data>
14+
<Data>Ref No #: CP-20251029-14QH</Data>
15+
<Data>Payment Date: 29-Oct-2025 02:03 PM</Data>
16+
17+
<Line Style="Dash" />
18+
<Data FontStyle="Bold" Align="Right">PAYMENT TO</Data>
19+
<Data TextWrap ="True" Align="Right">MAIN BRANCH</Data>
20+
<Data TextWrap ="True" Align="Right">Account: Cash A/C</Data>
21+
<Data TextWrap ="True" Align="Right">Account Code: 1</Data>
22+
<LineBreak/>
23+
24+
<Line Style="Dash" />
25+
<Data FontStyle="Bold">PAYMENT FROM</Data>
26+
<Data TextWrap ="True">JOHN DOE ENTERPRISES</Data>
27+
<Data TextWrap ="True">Account Code: 54</Data>
28+
<Data TextWrap ="True">Paid By: JOHN DOE</Data>
29+
<LineBreak/>
30+
31+
<Line Style="Dash" />
32+
33+
<Grid ColumnWidths="2*1">
34+
<GridRow>
35+
<Data Grid.Column="0" FontStyle="Bold">AMOUNT</Data>
36+
<Data Grid.Column="1" FontStyle="Bold" FontSize="11" Align="Right">300.00</Data>
37+
</GridRow>
38+
</Grid>
39+
40+
<Data Align="Center" TextWrap="True"></Data>
41+
<Line Style="Dash" />
42+
43+
<LineBreak/>
44+
45+
<Data>Payment received by: George Waynne</Data>
46+
<LineBreak/>
47+
<Data Align="Center" TextWrap ="True">Customer Prev. Bal: 19,395.00, Current Bal: 19,095.00</Data>
48+
<LineBreak/>
49+
<LineBreak/>
50+
<Data Align="Center" FontSize="8">Powered By Semantic POS | www.semanticpos.com</Data>
51+
52+
</Template>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<Template font="Calibri" FontSize="10" MarginTop="10">
2+
3+
<Data FontStyle="Bold" FontSize="11" TextWrap ="True" Align="Center">{{ CompanyName }}</Data>
4+
<Data TextWrap ="True" Align="Center">{{ CompanyEmail }}</Data>
5+
<Data TextWrap ="True" Align="Center">{{ CompanyAddress }}</Data>
6+
<Data TextWrap ="True" Align="Center">{{ CompanyMobile }}</Data>
7+
8+
<LineBreak/>
9+
<Data FontStyle="Bold" FontSize="12" Align="Center">CUSTOMER PAYMENT RECEIPT</Data>
10+
11+
<LineBreak/>
12+
13+
<Data>Payment No #: {{ Id }}</Data>
14+
<Data>Ref No #: {{ ReferenceNo }}</Data>
15+
<Data>Payment Date: {{ PaymentDate:dd-MMM-yyyy hh:mm tt }}</Data>
16+
17+
<Line Style="Dash" />
18+
<Data FontStyle="Bold" Align="Right">PAYMENT TO</Data>
19+
<Data TextWrap ="True" Align="Right">{{ BranchName }}</Data>
20+
<Data TextWrap ="True" Align="Right">Account: {{ LedgerAccountName }}</Data>
21+
<Data TextWrap ="True" Align="Right">Account Code: {{ LedgerAccountId }}</Data>
22+
<LineBreak/>
23+
24+
<Line Style="Dash" />
25+
<Data FontStyle="Bold">PAYMENT FROM</Data>
26+
<Data TextWrap ="True">{{ CustomerName }}</Data>
27+
<Data TextWrap ="True">Account Code: {{ CustomerId }}</Data>
28+
<Data TextWrap ="True">Paid By: {{ PaidBy }}</Data>
29+
<LineBreak/>
30+
31+
<Line Style="Dash" />
32+
33+
<Grid ColumnWidths="2*1">
34+
<GridRow>
35+
<Data Grid.Column="0" FontStyle="Bold">AMOUNT</Data>
36+
<Data Grid.Column="1" FontStyle="Bold" FontSize="11" Align="Right">{{ Amount:N2 }}</Data>
37+
</GridRow>
38+
</Grid>
39+
40+
<Data Align="Center" TextWrap="True">{{ Narration }}</Data>
41+
<Line Style="Dash" />
42+
43+
<LineBreak/>
44+
45+
<Data>Payment received by: {{ RegisteredBy }}</Data>
46+
<LineBreak/>
47+
<Data Align="Center" TextWrap ="True">Customer Prev. Bal: {{ customer_prevBalance }}, Current Bal: {{ customer_currentBalance }}</Data>
48+
<LineBreak/>
49+
<LineBreak/>
50+
<Data Align="Center" FontSize="8">Powered By Semantic POS | www.semanticpos.com</Data>
51+
52+
</Template>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
3+
namespace ObjectSemantics.NET.Tests.MoqModels
4+
{
5+
public class CustomerPayment
6+
{
7+
public int Id { get; set; }
8+
public int CustomerId { get; set; }
9+
public double Amount { get; set; }
10+
public string ReferenceNo { get; set; }
11+
public string PaidBy { get; set; }
12+
public string RegisteredBy { get; set; }
13+
public string Narration { get; set; }
14+
public string CustomerName { get; set; }
15+
public string LedgerAccountName { get; set; }
16+
public int LedgerAccountId { get; set; }
17+
public DateTime PaymentDate { get; set; }
18+
public Customer Customer { get; set; }
19+
}
20+
21+
public class Customer
22+
{
23+
public int Id { get; set; }
24+
public string FirstName { get; set; }
25+
public string LastName { get; set; }
26+
public string CompanyName { get; set; }
27+
}
28+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace ObjectSemantics.NET.Tests.MoqModels
2+
{
3+
internal class Payment
4+
{
5+
public int Id { get; set; }
6+
public int? UserId { get; set; }
7+
public double Amount { get; set; } = 0;
8+
public string PayMethod { get; set; }
9+
public int? PayMethodId { get; set; }
10+
public string ReferenceNo { get; set; }
11+
}
12+
}

ObjectSemantics.NET.Tests/ObjectSemantics.NET.Tests.csproj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@
1010
<FileVersion>3.0.1.1</FileVersion>
1111
</PropertyGroup>
1212

13+
<ItemGroup>
14+
<None Remove="MoqFiles\PaymentTemplate.result.xml" />
15+
<None Remove="MoqFiles\PaymentTemplate.xml" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<Content Include="MoqFiles\PaymentTemplate.result.xml">
20+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
21+
</Content>
22+
<Content Include="MoqFiles\PaymentTemplate.xml">
23+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
24+
</Content>
25+
</ItemGroup>
26+
1327
<ItemGroup>
1428
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
1529
<PackageReference Include="xunit" Version="2.4.1" />
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using ObjectSemantics.NET.Tests.MoqModels;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Text;
6+
using Xunit;
7+
8+
namespace ObjectSemantics.NET.Tests
9+
{
10+
public class TemplateFileMappingTests
11+
{
12+
[Fact]
13+
public void Should_Map_Large_File_Template()
14+
{
15+
string template = File.ReadAllText("MoqFiles/PaymentTemplate.xml", Encoding.UTF8);
16+
string expectedResult = File.ReadAllText("MoqFiles/PaymentTemplate.result.xml", Encoding.UTF8);
17+
18+
19+
var payment = new CustomerPayment
20+
{
21+
Id = 12719,
22+
CustomerId = 54,
23+
Amount = 300.0,
24+
LedgerAccountId = 1,
25+
ReferenceNo = "CP-20251029-14QH",
26+
PaidBy = "JOHN DOE",
27+
PaymentDate = DateTime.Parse("2025-10-29T14:03:19.4147588"),
28+
RegisteredBy = "George Waynne",
29+
Customer = new Customer
30+
{
31+
Id = 54,
32+
FirstName = "JOHN DOE",
33+
LastName = "ENTERPRISES",
34+
CompanyName = "John Doe Enterprises",
35+
},
36+
Narration = null,
37+
CustomerName = "JOHN DOE ENTERPRISES",
38+
LedgerAccountName = "Cash A/C"
39+
};
40+
41+
//additional headers
42+
var additionalParams = new Dictionary<string, object>
43+
{
44+
["BranchName"] = "MAIN BRANCH",
45+
["CompanyName"] = "TEST COMPANY",
46+
["CompanyEmail"] = "test@gmail.com",
47+
["CompanyAddress"] = "Test Address",
48+
["CompanyMobile"] = "+2547000000001",
49+
["customer_prevBalance"] = "19,395.00",
50+
["customer_currentBalance"] = "19,095.00",
51+
["CompanyLogo"] = "logo.jpg",
52+
};
53+
54+
//map
55+
string result = payment.Map(template, additionalParams);
56+
57+
Assert.Equal(result, expectedResult);
58+
}
59+
60+
}
61+
}

ObjectSemantics.NET/Engine/EngineAlgorithim.cs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@ internal static class EngineAlgorithim
1515
{
1616
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> PropertyCache = new ConcurrentDictionary<Type, PropertyInfo[]>();
1717

18-
private static readonly Regex IfConditionRegex = new Regex(@"{{\s*#if\s*\(\s*(?<param>\w+)\s*(?<operator>==|!=|>=|<=|>|<)\s*(?<value>[^)]+)\s*\)\s*}}(?<code>[\s\S]*?)(?:{{\s*#else\s*}}(?<else>[\s\S]*?))?{{\s*#endif\s*}}", RegexOptions.IgnoreCase | RegexOptions.Compiled);
19-
20-
private static readonly Regex LoopBlockRegex = new Regex(@"{{\s*#foreach\s*\(\s*(\w+)\s*\)\s*\}\}([\s\S]*?)\{\{\s*#endforeach\s*}}", RegexOptions.IgnoreCase | RegexOptions.Compiled);
21-
18+
private static readonly Regex IfConditionRegex = new Regex(@"{{\s*#\s*if\s*\(\s*(?<param>\w+)\s*(?<operator>==|!=|>=|<=|>|<)\s*(?<value>[^)]+?)\s*\)\s*}}(?<code>[\s\S]*?)(?:{{\s*#\s*else\s*}}(?<else>[\s\S]*?))?{{\s*#\s*endif\s*}}", RegexOptions.IgnoreCase | RegexOptions.Compiled);
19+
private static readonly Regex LoopBlockRegex = new Regex(@"{{\s*#\s*foreach\s*\(\s*(\w+)\s*\)\s*\}\}([\s\S]*?)\{\{\s*#\s*endforeach\s*}}", RegexOptions.IgnoreCase | RegexOptions.Compiled);
2220
private static readonly Regex DirectParamRegex = new Regex(@"{{(.+?)}}", RegexOptions.IgnoreCase | RegexOptions.Compiled);
2321

2422
public static string GenerateFromTemplate<T>(T record, EngineRunnerTemplate template, Dictionary<string, object> parameterKeyValues = null, TemplateMapperOptions options = null) where T : new()
@@ -33,7 +31,7 @@ internal static class EngineAlgorithim
3331
{
3432
if (!propMap.TryGetValue(ifCondition.IfPropertyName, out ExtractedObjProperty property))
3533
{
36-
result.Replace(ifCondition.ReplaceRef, "[IF-CONDITION EXCEPTION]: unrecognized property: [" + ifCondition.IfPropertyName + "]");
34+
result.ReplaceFirstOccurrence(ifCondition.ReplaceRef, "[IF-CONDITION EXCEPTION]: unrecognized property: [" + ifCondition.IfPropertyName + "]");
3735
continue;
3836
}
3937

@@ -55,15 +53,15 @@ internal static class EngineAlgorithim
5553
replacement = string.Empty;
5654
}
5755

58-
result.Replace(ifCondition.ReplaceRef, replacement);
56+
result.ReplaceFirstOccurrence(ifCondition.ReplaceRef, replacement);
5957
}
6058

6159
// ---- Object Loops ----
6260
foreach (ReplaceObjLoopCode objLoop in template.ReplaceObjLoopCodes)
6361
{
6462
if (!propMap.TryGetValue(objLoop.TargetObjectName, out ExtractedObjProperty targetObj) || !(targetObj.OriginalValue is IEnumerable enumerable))
6563
{
66-
result.Replace(objLoop.ReplaceRef, string.Empty);
64+
result.ReplaceFirstOccurrence(objLoop.ReplaceRef, string.Empty);
6765
continue;
6866
}
6967

@@ -87,31 +85,32 @@ internal static class EngineAlgorithim
8785
Type = row.GetType(),
8886
OriginalValue = row
8987
};
90-
activeRow.Replace(objLoopCode.ReplaceRef, tempProp.GetPropertyDisplayString(objLoopCode.GetFormattingCommand(), options));
88+
activeRow.ReplaceFirstOccurrence(objLoopCode.ReplaceRef, tempProp.GetPropertyDisplayString(objLoopCode.GetFormattingCommand(), options));
9189
}
9290
else
9391
{
9492
if (rowMap.TryGetValue(propName, out ExtractedObjProperty p))
95-
activeRow.Replace(objLoopCode.ReplaceRef, p.GetPropertyDisplayString(objLoopCode.GetFormattingCommand(), options));
93+
activeRow.ReplaceFirstOccurrence(objLoopCode.ReplaceRef, p.GetPropertyDisplayString(objLoopCode.GetFormattingCommand(), options));
9694
else
97-
activeRow.Replace(objLoopCode.ReplaceRef, objLoopCode.ReplaceCommand);
95+
activeRow.ReplaceFirstOccurrence(objLoopCode.ReplaceRef, objLoopCode.ReplaceCommand);
9896
}
9997
}
10098

10199
loopResult.Append(activeRow);
102100
}
103101

104-
result.Replace(objLoop.ReplaceRef, loopResult.ToString());
102+
result.ReplaceFirstOccurrence(objLoop.ReplaceRef, loopResult.ToString());
105103
}
106104

107105
// ---- Direct Replacements ----
108106
foreach (ReplaceCode replaceCode in template.ReplaceCodes)
109107
{
110108
if (propMap.TryGetValue(replaceCode.GetTargetPropertyName(), out ExtractedObjProperty property))
111-
result.Replace(replaceCode.ReplaceRef, property.GetPropertyDisplayString(replaceCode.GetFormattingCommand(), options));
109+
result.ReplaceFirstOccurrence(replaceCode.ReplaceRef, property.GetPropertyDisplayString(replaceCode.GetFormattingCommand(), options));
112110
else
113-
result.Replace(replaceCode.ReplaceRef, "{{ " + replaceCode.ReplaceCommand + " }}");
111+
result.ReplaceFirstOccurrence(replaceCode.ReplaceRef, "{{ " + replaceCode.ReplaceCommand + " }}");
114112
}
113+
115114
return result.ToString();
116115
}
117116

0 commit comments

Comments
 (0)