From f490f9add7b2ef1d2bfe10de67153be0e7dfc96a Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Wed, 29 Oct 2025 14:55:28 +0300 Subject: [PATCH 1/8] chore: additional tests --- .../CognitiveMapTests.cs | 28 +++++++++++++++++++ .../MoqModels/Payment.cs | 12 ++++++++ 2 files changed, 40 insertions(+) create mode 100644 ObjectSemantics.NET.Tests/MoqModels/Payment.cs diff --git a/ObjectSemantics.NET.Tests/CognitiveMapTests.cs b/ObjectSemantics.NET.Tests/CognitiveMapTests.cs index f909a0d..5dea7e9 100644 --- a/ObjectSemantics.NET.Tests/CognitiveMapTests.cs +++ b/ObjectSemantics.NET.Tests/CognitiveMapTests.cs @@ -101,5 +101,33 @@ public void Should_Escape_Xml_Char_Values_If_Option_Is_Enabled(string value, str }); Assert.Equal(expected, generatedTemplate); } + + + [Fact] + public void Additional_Headers_And_Class_Properties_Should_Also_Be_Mapped_Combined() + { + Payment payment = new Payment + { + Id = 1, + Amount = 1000, + PayMethod = "CHEQUE", + PayMethodId = 2, + ReferenceNo = "CHEQUE0001", + UserId = 242 + }; + //additional params (outside the class) + Dictionary additionalParams = new Dictionary + { + { "ReceivedBy", "John Doe"}, + { "NewBalance", 1050 } + }; + + 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); + + + 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."; + + Assert.Equal(generatedTemplate, expectedResponse); + } } } diff --git a/ObjectSemantics.NET.Tests/MoqModels/Payment.cs b/ObjectSemantics.NET.Tests/MoqModels/Payment.cs new file mode 100644 index 0000000..2e69d17 --- /dev/null +++ b/ObjectSemantics.NET.Tests/MoqModels/Payment.cs @@ -0,0 +1,12 @@ +namespace ObjectSemantics.NET.Tests.MoqModels +{ + internal class Payment + { + public int Id { get; set; } + public int? UserId { get; set; } + public double Amount { get; set; } = 0; + public string PayMethod { get; set; } + public int? PayMethodId { get; set; } + public string ReferenceNo { get; set; } + } +} From e78ad6f8592931b93df04a01f0b5fdafdafe37ba Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Wed, 29 Oct 2025 15:13:35 +0300 Subject: [PATCH 2/8] chore: advanced map template files --- .../MoqFiles/PaymentTemplate.result.xml | 52 ++++++++ .../MoqFiles/PaymentTemplate.xml | 52 ++++++++ .../MoqModels/CustomerPayment.cs | 69 +++++++++++ .../ObjectSemantics.NET.Tests.csproj | 14 +++ .../TemplateFileMappingTests.cs | 115 ++++++++++++++++++ 5 files changed, 302 insertions(+) create mode 100644 ObjectSemantics.NET.Tests/MoqFiles/PaymentTemplate.result.xml create mode 100644 ObjectSemantics.NET.Tests/MoqFiles/PaymentTemplate.xml create mode 100644 ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs create mode 100644 ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs diff --git a/ObjectSemantics.NET.Tests/MoqFiles/PaymentTemplate.result.xml b/ObjectSemantics.NET.Tests/MoqFiles/PaymentTemplate.result.xml new file mode 100644 index 0000000..65ca76d --- /dev/null +++ b/ObjectSemantics.NET.Tests/MoqFiles/PaymentTemplate.result.xml @@ -0,0 +1,52 @@ + \ No newline at end of file diff --git a/ObjectSemantics.NET.Tests/MoqFiles/PaymentTemplate.xml b/ObjectSemantics.NET.Tests/MoqFiles/PaymentTemplate.xml new file mode 100644 index 0000000..e7c6514 --- /dev/null +++ b/ObjectSemantics.NET.Tests/MoqFiles/PaymentTemplate.xml @@ -0,0 +1,52 @@ + \ No newline at end of file diff --git a/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs b/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs new file mode 100644 index 0000000..95d2605 --- /dev/null +++ b/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs @@ -0,0 +1,69 @@ +using System; + +namespace ObjectSemantics.NET.Tests.MoqModels +{ + public class CustomerPayment + { + public int Id { get; set; } + public int CustomerId { get; set; } + public double Amount { get; set; } + public int LedgerAccountId { get; set; } + public string ReferenceNo { get; set; } + public string PaidBy { get; set; } + public string PaidByMobile { get; set; } + public DateTime PaymentDate { get; set; } + public string PaymentStatus { get; set; } + public string RegisteredBy { get; set; } + public Customer Customer { get; set; } + public string Narration { get; set; } + public string CustomerName { get; set; } + public string LedgerAccountName { get; set; } + public LedgerAccount LedgerAccount { get; set; } + public int CompanyBranchId { get; set; } + public object CompanyBranch { get; set; } + } + + public class Customer + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string CompanyName { get; set; } + public string PrimaryMobile { get; set; } + public string SecondaryMobile { get; set; } + public string PrimaryEmail { get; set; } + public string SecondaryEmail { get; set; } + public string Address { get; set; } + public double OpeningBalance { get; set; } + public DateTime OpeningDate { get; set; } + public double CurrentBalance { get; set; } + public double CreditLimit { get; set; } + public string TaxRefNo { get; set; } + public string BankAccount { get; set; } + public string RoutingNo { get; set; } + public string MoreDetails { get; set; } + public DateTime LastModified { get; set; } + public int LastModifiedAtBranchId { get; set; } + public string ModifiedBy { get; set; } + public string DefaultPriceScheme { get; set; } + public bool IsActive { get; set; } + public string Status { get; set; } + public string FullName { get; set; } + } + + public class LedgerAccount + { + public int Id { get; set; } + public int LedgerAccountTypeId { get; set; } + public string AccountCode { get; set; } + public string AccountName { get; set; } + public string Description { get; set; } + public double OpeningBalance { get; set; } + public DateTime OpeningDate { get; set; } + public bool IsBankAccount { get; set; } + public string BankAccountName { get; set; } + public string BankAccountNumber { get; set; } + public string AcCode { get; set; } + public object LedgerAccountType { get; set; } + } +} diff --git a/ObjectSemantics.NET.Tests/ObjectSemantics.NET.Tests.csproj b/ObjectSemantics.NET.Tests/ObjectSemantics.NET.Tests.csproj index c804413..5e4177c 100644 --- a/ObjectSemantics.NET.Tests/ObjectSemantics.NET.Tests.csproj +++ b/ObjectSemantics.NET.Tests/ObjectSemantics.NET.Tests.csproj @@ -10,6 +10,20 @@ 3.0.1.1 + + + + + + + + Always + + + Always + + + diff --git a/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs b/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs new file mode 100644 index 0000000..dd71049 --- /dev/null +++ b/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs @@ -0,0 +1,115 @@ +using ObjectSemantics.NET.Tests.MoqModels; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Xunit; + +namespace ObjectSemantics.NET.Tests +{ + public class TemplateFileMappingTests + { + [Fact] + public void Should_Map_Large_File_Template() + { + string template = File.ReadAllText("MoqFiles/PaymentTemplate.xml", Encoding.UTF8); + string expectedResult = File.ReadAllText("MoqFiles/PaymentTemplate.result.xml", Encoding.UTF8); + + + var payment = new CustomerPayment + { + Id = 12719, + CustomerId = 54, + Amount = 300.0, + LedgerAccountId = 1, + ReferenceNo = "CP-20251029-14QH", + PaidBy = "JOHN DOE", + PaidByMobile = "N/A", + PaymentDate = DateTime.Parse("2025-10-29T14:03:19.4147588"), + PaymentStatus = null, + RegisteredBy = "George Waynne", + Customer = new Customer + { + Id = 54, + FirstName = "JOHN DOE", + LastName = "ENTERPRISES", + CompanyName = "John Doe Enterprises", + PrimaryMobile = "N/A", + SecondaryMobile = null, + PrimaryEmail = "", + SecondaryEmail = null, + Address = "", + OpeningBalance = 0.0, + OpeningDate = DateTime.Parse("2023-11-13T00:00:00"), + CurrentBalance = 19095.0, + CreditLimit = 0.0, + TaxRefNo = "", + BankAccount = null, + RoutingNo = null, + MoreDetails = "", + LastModified = DateTime.Parse("2024-06-18T18:11:41.7819616"), + LastModifiedAtBranchId = 1, + ModifiedBy = "Joe Maina", + DefaultPriceScheme = "SELLING PRICE", + IsActive = true, + Status = "ACTIVE", + FullName = "Afrique" + }, + Narration = null, + CustomerName = "JOHN DOE ENTERPRISES", + LedgerAccountName = "Cash A/C", + LedgerAccount = new LedgerAccount + { + Id = 1, + LedgerAccountTypeId = 4, + AccountCode = "82187", + AccountName = "Cash A/C", + Description = "Current Cash", + OpeningBalance = 0.0, + OpeningDate = DateTime.Parse("2020-03-20T10:39:12"), + IsBankAccount = false, + BankAccountName = null, + BankAccountNumber = null, + AcCode = "82187", + LedgerAccountType = null + }, + CompanyBranchId = 1, + CompanyBranch = null + }; + + + //additional headers + var additionalParams = new Dictionary + { + ["BranchId"] = "1", + ["BranchName"] = "MAIN BRANCH", + ["BranchMobile"] = "Default", + ["BranchEmail"] = "Default", + ["BranchAddress"] = "Default", + ["customer_name"] = "Afrique", + ["customer_email"] = "", + ["customer_mobile"] = "N/A", + ["customer_address"] = "", + ["customer_taxrefno"] = "", + ["customer_prevBalance"] = "19,395.00", + ["customer_currentBalance"] = "19,095.00", + ["CompanyAddress"] = "Test Address", + ["CompanyBusinessType"] = "RETAIL & WHOLESALE STORE", + ["CompanyEmail"] = "test@gmail.com", + ["CompanyMobile"] = "+2547000000001", + ["CompanyName"] = "TEST COMPANY", + ["CompanyTaxNo"] = "P000000001", + ["ReportsDeliveryEmailAddresses"] = "", + ["CompanyLogo"] = "logo.jpg", + ["footer"] = "!Thank you and Come Again!" + }; + + + //map + var result = payment.Map(template, additionalParams); + + Assert.Equal(result, expectedResult); + } + + } +} From 402a8e06141ce5b82722278a0573a4ba5b009539 Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Wed, 29 Oct 2025 15:35:32 +0300 Subject: [PATCH 3/8] chore: fixed mapping issue --- .../Engine/EngineAlgorithim.cs | 21 +++++++++---------- .../Engine/Extensions/StringExtensions.cs | 15 +++++++++++++ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/ObjectSemantics.NET/Engine/EngineAlgorithim.cs b/ObjectSemantics.NET/Engine/EngineAlgorithim.cs index 0b75f4e..c8a0d3e 100644 --- a/ObjectSemantics.NET/Engine/EngineAlgorithim.cs +++ b/ObjectSemantics.NET/Engine/EngineAlgorithim.cs @@ -17,8 +17,7 @@ internal static class EngineAlgorithim private static readonly Regex IfConditionRegex = new Regex(@"{{\s*#if\s*\(\s*(?\w+)\s*(?==|!=|>=|<=|>|<)\s*(?[^)]+)\s*\)\s*}}(?[\s\S]*?)(?:{{\s*#else\s*}}(?[\s\S]*?))?{{\s*#endif\s*}}", RegexOptions.IgnoreCase | RegexOptions.Compiled); - private static readonly Regex LoopBlockRegex = new Regex(@"{{\s*#foreach\s*\(\s*(\w+)\s*\)\s*\}\}([\s\S]*?)\{\{\s*#endforeach\s*}}", RegexOptions.IgnoreCase | RegexOptions.Compiled); - + 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); private static readonly Regex DirectParamRegex = new Regex(@"{{(.+?)}}", RegexOptions.IgnoreCase | RegexOptions.Compiled); public static string GenerateFromTemplate(T record, EngineRunnerTemplate template, Dictionary parameterKeyValues = null, TemplateMapperOptions options = null) where T : new() @@ -33,7 +32,7 @@ internal static class EngineAlgorithim { if (!propMap.TryGetValue(ifCondition.IfPropertyName, out ExtractedObjProperty property)) { - result.Replace(ifCondition.ReplaceRef, "[IF-CONDITION EXCEPTION]: unrecognized property: [" + ifCondition.IfPropertyName + "]"); + result.ReplaceFirstOccurrence(ifCondition.ReplaceRef, "[IF-CONDITION EXCEPTION]: unrecognized property: [" + ifCondition.IfPropertyName + "]"); continue; } @@ -55,7 +54,7 @@ internal static class EngineAlgorithim replacement = string.Empty; } - result.Replace(ifCondition.ReplaceRef, replacement); + result.ReplaceFirstOccurrence(ifCondition.ReplaceRef, replacement); } // ---- Object Loops ---- @@ -63,7 +62,7 @@ internal static class EngineAlgorithim { if (!propMap.TryGetValue(objLoop.TargetObjectName, out ExtractedObjProperty targetObj) || !(targetObj.OriginalValue is IEnumerable enumerable)) { - result.Replace(objLoop.ReplaceRef, string.Empty); + result.ReplaceFirstOccurrence(objLoop.ReplaceRef, string.Empty); continue; } @@ -87,30 +86,30 @@ internal static class EngineAlgorithim Type = row.GetType(), OriginalValue = row }; - activeRow.Replace(objLoopCode.ReplaceRef, tempProp.GetPropertyDisplayString(objLoopCode.GetFormattingCommand(), options)); + activeRow.ReplaceFirstOccurrence(objLoopCode.ReplaceRef, tempProp.GetPropertyDisplayString(objLoopCode.GetFormattingCommand(), options)); } else { if (rowMap.TryGetValue(propName, out ExtractedObjProperty p)) - activeRow.Replace(objLoopCode.ReplaceRef, p.GetPropertyDisplayString(objLoopCode.GetFormattingCommand(), options)); + activeRow.ReplaceFirstOccurrence(objLoopCode.ReplaceRef, p.GetPropertyDisplayString(objLoopCode.GetFormattingCommand(), options)); else - activeRow.Replace(objLoopCode.ReplaceRef, objLoopCode.ReplaceCommand); + activeRow.ReplaceFirstOccurrence(objLoopCode.ReplaceRef, objLoopCode.ReplaceCommand); } } loopResult.Append(activeRow); } - result.Replace(objLoop.ReplaceRef, loopResult.ToString()); + result.ReplaceFirstOccurrence(objLoop.ReplaceRef, loopResult.ToString()); } // ---- Direct Replacements ---- foreach (ReplaceCode replaceCode in template.ReplaceCodes) { if (propMap.TryGetValue(replaceCode.GetTargetPropertyName(), out ExtractedObjProperty property)) - result.Replace(replaceCode.ReplaceRef, property.GetPropertyDisplayString(replaceCode.GetFormattingCommand(), options)); + result.ReplaceFirstOccurrence(replaceCode.ReplaceRef, property.GetPropertyDisplayString(replaceCode.GetFormattingCommand(), options)); else - result.Replace(replaceCode.ReplaceRef, "{{ " + replaceCode.ReplaceCommand + " }}"); + result.ReplaceFirstOccurrence(replaceCode.ReplaceRef, "{{ " + replaceCode.ReplaceCommand + " }}"); } return result.ToString(); } diff --git a/ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs b/ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs index a0d49eb..52c84d4 100644 --- a/ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs +++ b/ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs @@ -5,6 +5,21 @@ namespace ObjectSemantics.NET.Engine.Extensions { internal static class StringExtensions { + public static StringBuilder ReplaceFirstOccurrence(this StringBuilder sb, string search, string replace) + { + if (sb == null || string.IsNullOrEmpty(search)) + return sb; + + int pos = sb.ToString().IndexOf(search, StringComparison.Ordinal); + if (pos < 0) + return sb; + + sb.Remove(pos, search.Length); + sb.Insert(pos, replace); + + return sb; + } + public static string ReplaceFirstOccurrence(this string text, string search, string replace) { int pos = text.IndexOf(search); From 693b3da818cab8aae2d87a1250deee8aabdc094d Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Wed, 29 Oct 2025 15:54:36 +0300 Subject: [PATCH 4/8] chore: additional unit tests and refactor --- .../EnumerableLoopTests.cs | 15 ++++++++ ObjectSemantics.NET.Tests/IfConditionTests.cs | 16 ++++++++ .../Engine/EngineAlgorithim.cs | 3 +- .../Extensions/StringBuilderExtensions.cs | 37 +++++++++++++++++++ .../Engine/Extensions/StringExtensions.cs | 15 -------- 5 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 ObjectSemantics.NET/Engine/Extensions/StringBuilderExtensions.cs diff --git a/ObjectSemantics.NET.Tests/EnumerableLoopTests.cs b/ObjectSemantics.NET.Tests/EnumerableLoopTests.cs index e7702e3..1862556 100644 --- a/ObjectSemantics.NET.Tests/EnumerableLoopTests.cs +++ b/ObjectSemantics.NET.Tests/EnumerableLoopTests.cs @@ -140,5 +140,20 @@ public void Should_Map_Array_Of_String_With_Formatting() string expectedResult = " MORGAN GEORGE JANE "; Assert.Equal(expectedResult, generatedTemplate, false, true, true); } + + [Theory] + [InlineData(@"{{ #foreach(MyFriends) }}[{{ .:uppercase }}]{{ #endforeach }}")] + [InlineData(@"{{# foreach(MyFriends) }}[{{ .:uppercase }}]{{# endforeach }}")] + [InlineData(@"{{ #foreach(MyFriends) }}[{{ .:uppercase }}]{{ #endforeach }}")] + public void Should_Evaluate_ForEach_Having_Spaces_Before_And_After_Parentheses(string template) + { + Person person = new Person + { + MyFriends = new string[] { "Morgan", "George" } + }; + + string result = person.Map(template); + Assert.Equal("[MORGAN][GEORGE]", result); + } } } diff --git a/ObjectSemantics.NET.Tests/IfConditionTests.cs b/ObjectSemantics.NET.Tests/IfConditionTests.cs index 26666e8..2111ff7 100644 --- a/ObjectSemantics.NET.Tests/IfConditionTests.cs +++ b/ObjectSemantics.NET.Tests/IfConditionTests.cs @@ -130,5 +130,21 @@ public void Should_Render_Multiple_If_Condition_Statements(int age, string expec string result = person.Map(template); Assert.Equal(expected, result); } + + + [Theory] + [InlineData(@"{{#if(MyCars==0)}}Zero Cars{{#else}}Hmmm!{{#endif}}")] + [InlineData(@"{{# if (MyCars==0)}}Zero Cars{{ # else }}Hmmm!{{ # endif}}")] + [InlineData(@"{{ #if(MyCars==0) }}Zero Cars{{ #endif }}")] + public void Should_Evaluate_If_Having_Spaces_Before_And_After_Parentheses(string template) + { + Person person = new Person + { + MyCars = null + }; + + string result = person.Map(template); + Assert.Equal("Zero Cars", result); + } } } diff --git a/ObjectSemantics.NET/Engine/EngineAlgorithim.cs b/ObjectSemantics.NET/Engine/EngineAlgorithim.cs index c8a0d3e..5ce9f78 100644 --- a/ObjectSemantics.NET/Engine/EngineAlgorithim.cs +++ b/ObjectSemantics.NET/Engine/EngineAlgorithim.cs @@ -15,8 +15,7 @@ internal static class EngineAlgorithim { private static readonly ConcurrentDictionary PropertyCache = new ConcurrentDictionary(); - private static readonly Regex IfConditionRegex = new Regex(@"{{\s*#if\s*\(\s*(?\w+)\s*(?==|!=|>=|<=|>|<)\s*(?[^)]+)\s*\)\s*}}(?[\s\S]*?)(?:{{\s*#else\s*}}(?[\s\S]*?))?{{\s*#endif\s*}}", RegexOptions.IgnoreCase | RegexOptions.Compiled); - + private static readonly Regex IfConditionRegex = new Regex(@"{{\s*#\s*if\s*\(\s*(?\w+)\s*(?==|!=|>=|<=|>|<)\s*(?[^)]+?)\s*\)\s*}}(?[\s\S]*?)(?:{{\s*#\s*else\s*}}(?[\s\S]*?))?{{\s*#\s*endif\s*}}", RegexOptions.IgnoreCase | RegexOptions.Compiled); 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); private static readonly Regex DirectParamRegex = new Regex(@"{{(.+?)}}", RegexOptions.IgnoreCase | RegexOptions.Compiled); diff --git a/ObjectSemantics.NET/Engine/Extensions/StringBuilderExtensions.cs b/ObjectSemantics.NET/Engine/Extensions/StringBuilderExtensions.cs new file mode 100644 index 0000000..2975b78 --- /dev/null +++ b/ObjectSemantics.NET/Engine/Extensions/StringBuilderExtensions.cs @@ -0,0 +1,37 @@ +using System.Text; + +namespace ObjectSemantics.NET.Engine.Extensions +{ + public static class StringBuilderExtensions + { + public static StringBuilder ReplaceFirstOccurrence(this StringBuilder sb, string search, string replace) + { + if (sb == null || string.IsNullOrEmpty(search)) + return sb; + + int len = sb.Length - search.Length + 1; + for (int i = 0; i < len; i++) + { + bool match = true; + for (int j = 0; j < search.Length; j++) + { + if (sb[i + j] != search[j]) + { + match = false; + break; + } + } + + if (match) + { + sb.Remove(i, search.Length); + sb.Insert(i, replace); + break; + } + } + + return sb; + } + + } +} diff --git a/ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs b/ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs index 52c84d4..a0d49eb 100644 --- a/ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs +++ b/ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs @@ -5,21 +5,6 @@ namespace ObjectSemantics.NET.Engine.Extensions { internal static class StringExtensions { - public static StringBuilder ReplaceFirstOccurrence(this StringBuilder sb, string search, string replace) - { - if (sb == null || string.IsNullOrEmpty(search)) - return sb; - - int pos = sb.ToString().IndexOf(search, StringComparison.Ordinal); - if (pos < 0) - return sb; - - sb.Remove(pos, search.Length); - sb.Insert(pos, replace); - - return sb; - } - public static string ReplaceFirstOccurrence(this string text, string search, string replace) { int pos = text.IndexOf(search); From 3fecfa6b3493d8ab82a06a856fed5bb8c4512acd Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Wed, 29 Oct 2025 16:54:31 +0300 Subject: [PATCH 5/8] version patch --- .../Engine/EngineAlgorithim.cs | 1 + .../Extensions/StringBuilderExtensions.cs | 1 - .../Engine/Extensions/StringExtensions.cs | 20 ------------------- .../ObjectSemantics.NET.csproj | 6 +++--- 4 files changed, 4 insertions(+), 24 deletions(-) diff --git a/ObjectSemantics.NET/Engine/EngineAlgorithim.cs b/ObjectSemantics.NET/Engine/EngineAlgorithim.cs index 5ce9f78..c3bce8a 100644 --- a/ObjectSemantics.NET/Engine/EngineAlgorithim.cs +++ b/ObjectSemantics.NET/Engine/EngineAlgorithim.cs @@ -110,6 +110,7 @@ internal static class EngineAlgorithim else result.ReplaceFirstOccurrence(replaceCode.ReplaceRef, "{{ " + replaceCode.ReplaceCommand + " }}"); } + return result.ToString(); } diff --git a/ObjectSemantics.NET/Engine/Extensions/StringBuilderExtensions.cs b/ObjectSemantics.NET/Engine/Extensions/StringBuilderExtensions.cs index 2975b78..9d500d2 100644 --- a/ObjectSemantics.NET/Engine/Extensions/StringBuilderExtensions.cs +++ b/ObjectSemantics.NET/Engine/Extensions/StringBuilderExtensions.cs @@ -32,6 +32,5 @@ public static StringBuilder ReplaceFirstOccurrence(this StringBuilder sb, string return sb; } - } } diff --git a/ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs b/ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs index a0d49eb..1014a12 100644 --- a/ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs +++ b/ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs @@ -5,26 +5,6 @@ namespace ObjectSemantics.NET.Engine.Extensions { internal static class StringExtensions { - public static string ReplaceFirstOccurrence(this string text, string search, string replace) - { - int pos = text.IndexOf(search); - if (pos < 0) - return text; - return string.Format("{0}{1}{2}", text.Substring(0, pos), replace, text.Substring(pos + search.Length)); - } - public static string RemoveLastInstanceOfString(this string value, string removeString) - { - int index = value.LastIndexOf(removeString, StringComparison.Ordinal); - return index < 0 ? value : value.Remove(index, removeString.Length); - } - - public static string RemoveLastInstanceOfString(this string value, params char[] chars) - { - foreach (char c in chars) - value = value.RemoveLastInstanceOfString(c.ToString()); - return value; - } - public static string ToMD5String(this string input) { try diff --git a/ObjectSemantics.NET/ObjectSemantics.NET.csproj b/ObjectSemantics.NET/ObjectSemantics.NET.csproj index 31642f6..3dd06c1 100644 --- a/ObjectSemantics.NET/ObjectSemantics.NET.csproj +++ b/ObjectSemantics.NET/ObjectSemantics.NET.csproj @@ -15,9 +15,9 @@ ToBase64 FromBase64 . Added template extension method to allow mapping directly from Template - 7.0.0 - 7.0.0 - 7.0.0 + 7.0.1 + 7.0.1 + 7.0.1 false README.md From 0e0b9a593764b4fb6ab57a515113f7d1f5d976ac Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Wed, 29 Oct 2025 16:59:40 +0300 Subject: [PATCH 6/8] chore: tempalat test --- .../MoqModels/CustomerPayment.cs | 45 ++----------------- .../TemplateFileMappingTests.cs | 42 +---------------- 2 files changed, 5 insertions(+), 82 deletions(-) diff --git a/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs b/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs index 95d2605..d5c4a8a 100644 --- a/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs +++ b/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs @@ -7,20 +7,17 @@ public class CustomerPayment public int Id { get; set; } public int CustomerId { get; set; } public double Amount { get; set; } - public int LedgerAccountId { get; set; } public string ReferenceNo { get; set; } public string PaidBy { get; set; } public string PaidByMobile { get; set; } - public DateTime PaymentDate { get; set; } public string PaymentStatus { get; set; } public string RegisteredBy { get; set; } - public Customer Customer { get; set; } public string Narration { get; set; } public string CustomerName { get; set; } public string LedgerAccountName { get; set; } - public LedgerAccount LedgerAccount { get; set; } - public int CompanyBranchId { get; set; } - public object CompanyBranch { get; set; } + public int LedgerAccountId { get; set; } + public DateTime PaymentDate { get; set; } + public Customer Customer { get; set; } } public class Customer @@ -29,41 +26,5 @@ public class Customer public string FirstName { get; set; } public string LastName { get; set; } public string CompanyName { get; set; } - public string PrimaryMobile { get; set; } - public string SecondaryMobile { get; set; } - public string PrimaryEmail { get; set; } - public string SecondaryEmail { get; set; } - public string Address { get; set; } - public double OpeningBalance { get; set; } - public DateTime OpeningDate { get; set; } - public double CurrentBalance { get; set; } - public double CreditLimit { get; set; } - public string TaxRefNo { get; set; } - public string BankAccount { get; set; } - public string RoutingNo { get; set; } - public string MoreDetails { get; set; } - public DateTime LastModified { get; set; } - public int LastModifiedAtBranchId { get; set; } - public string ModifiedBy { get; set; } - public string DefaultPriceScheme { get; set; } - public bool IsActive { get; set; } - public string Status { get; set; } - public string FullName { get; set; } - } - - public class LedgerAccount - { - public int Id { get; set; } - public int LedgerAccountTypeId { get; set; } - public string AccountCode { get; set; } - public string AccountName { get; set; } - public string Description { get; set; } - public double OpeningBalance { get; set; } - public DateTime OpeningDate { get; set; } - public bool IsBankAccount { get; set; } - public string BankAccountName { get; set; } - public string BankAccountNumber { get; set; } - public string AcCode { get; set; } - public object LedgerAccountType { get; set; } } } diff --git a/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs b/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs index dd71049..dc0cbc5 100644 --- a/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs +++ b/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs @@ -34,47 +34,10 @@ public void Should_Map_Large_File_Template() FirstName = "JOHN DOE", LastName = "ENTERPRISES", CompanyName = "John Doe Enterprises", - PrimaryMobile = "N/A", - SecondaryMobile = null, - PrimaryEmail = "", - SecondaryEmail = null, - Address = "", - OpeningBalance = 0.0, - OpeningDate = DateTime.Parse("2023-11-13T00:00:00"), - CurrentBalance = 19095.0, - CreditLimit = 0.0, - TaxRefNo = "", - BankAccount = null, - RoutingNo = null, - MoreDetails = "", - LastModified = DateTime.Parse("2024-06-18T18:11:41.7819616"), - LastModifiedAtBranchId = 1, - ModifiedBy = "Joe Maina", - DefaultPriceScheme = "SELLING PRICE", - IsActive = true, - Status = "ACTIVE", - FullName = "Afrique" }, Narration = null, CustomerName = "JOHN DOE ENTERPRISES", - LedgerAccountName = "Cash A/C", - LedgerAccount = new LedgerAccount - { - Id = 1, - LedgerAccountTypeId = 4, - AccountCode = "82187", - AccountName = "Cash A/C", - Description = "Current Cash", - OpeningBalance = 0.0, - OpeningDate = DateTime.Parse("2020-03-20T10:39:12"), - IsBankAccount = false, - BankAccountName = null, - BankAccountNumber = null, - AcCode = "82187", - LedgerAccountType = null - }, - CompanyBranchId = 1, - CompanyBranch = null + LedgerAccountName = "Cash A/C" }; @@ -86,7 +49,7 @@ public void Should_Map_Large_File_Template() ["BranchMobile"] = "Default", ["BranchEmail"] = "Default", ["BranchAddress"] = "Default", - ["customer_name"] = "Afrique", + ["customer_name"] = "JOHN DOE", ["customer_email"] = "", ["customer_mobile"] = "N/A", ["customer_address"] = "", @@ -104,7 +67,6 @@ public void Should_Map_Large_File_Template() ["footer"] = "!Thank you and Come Again!" }; - //map var result = payment.Map(template, additionalParams); From 55a3d228a69b411a5c1d3010abd1cd1d2d63c01e Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Wed, 29 Oct 2025 17:08:05 +0300 Subject: [PATCH 7/8] chore: minor refactoring --- .../MoqModels/CustomerPayment.cs | 2 -- .../TemplateFileMappingTests.cs | 25 ++++--------------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs b/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs index d5c4a8a..bedc7e2 100644 --- a/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs +++ b/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs @@ -10,7 +10,6 @@ public class CustomerPayment public string ReferenceNo { get; set; } public string PaidBy { get; set; } public string PaidByMobile { get; set; } - public string PaymentStatus { get; set; } public string RegisteredBy { get; set; } public string Narration { get; set; } public string CustomerName { get; set; } @@ -19,7 +18,6 @@ public class CustomerPayment public DateTime PaymentDate { get; set; } public Customer Customer { get; set; } } - public class Customer { public int Id { get; set; } diff --git a/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs b/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs index dc0cbc5..704567d 100644 --- a/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs +++ b/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs @@ -26,7 +26,6 @@ public void Should_Map_Large_File_Template() PaidBy = "JOHN DOE", PaidByMobile = "N/A", PaymentDate = DateTime.Parse("2025-10-29T14:03:19.4147588"), - PaymentStatus = null, RegisteredBy = "George Waynne", Customer = new Customer { @@ -40,35 +39,21 @@ public void Should_Map_Large_File_Template() LedgerAccountName = "Cash A/C" }; - //additional headers var additionalParams = new Dictionary { - ["BranchId"] = "1", ["BranchName"] = "MAIN BRANCH", - ["BranchMobile"] = "Default", - ["BranchEmail"] = "Default", - ["BranchAddress"] = "Default", - ["customer_name"] = "JOHN DOE", - ["customer_email"] = "", - ["customer_mobile"] = "N/A", - ["customer_address"] = "", - ["customer_taxrefno"] = "", - ["customer_prevBalance"] = "19,395.00", - ["customer_currentBalance"] = "19,095.00", - ["CompanyAddress"] = "Test Address", - ["CompanyBusinessType"] = "RETAIL & WHOLESALE STORE", + ["CompanyName"] = "TEST COMPANY", ["CompanyEmail"] = "test@gmail.com", + ["CompanyAddress"] = "Test Address", ["CompanyMobile"] = "+2547000000001", - ["CompanyName"] = "TEST COMPANY", - ["CompanyTaxNo"] = "P000000001", - ["ReportsDeliveryEmailAddresses"] = "", + ["customer_prevBalance"] = "19,395.00", + ["customer_currentBalance"] = "19,095.00", ["CompanyLogo"] = "logo.jpg", - ["footer"] = "!Thank you and Come Again!" }; //map - var result = payment.Map(template, additionalParams); + string result = payment.Map(template, additionalParams); Assert.Equal(result, expectedResult); } From 49ff17e9001d8145b4d66e244618242757aa2ffc Mon Sep 17 00:00:00 2001 From: "George Njeri (Swagfin)" Date: Wed, 29 Oct 2025 17:11:14 +0300 Subject: [PATCH 8/8] chore: minor refactoring --- ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs | 2 +- ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs b/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs index bedc7e2..1fbf860 100644 --- a/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs +++ b/ObjectSemantics.NET.Tests/MoqModels/CustomerPayment.cs @@ -9,7 +9,6 @@ public class CustomerPayment public double Amount { get; set; } public string ReferenceNo { get; set; } public string PaidBy { get; set; } - public string PaidByMobile { get; set; } public string RegisteredBy { get; set; } public string Narration { get; set; } public string CustomerName { get; set; } @@ -18,6 +17,7 @@ public class CustomerPayment public DateTime PaymentDate { get; set; } public Customer Customer { get; set; } } + public class Customer { public int Id { get; set; } diff --git a/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs b/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs index 704567d..5a392ca 100644 --- a/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs +++ b/ObjectSemantics.NET.Tests/TemplateFileMappingTests.cs @@ -24,7 +24,6 @@ public void Should_Map_Large_File_Template() LedgerAccountId = 1, ReferenceNo = "CP-20251029-14QH", PaidBy = "JOHN DOE", - PaidByMobile = "N/A", PaymentDate = DateTime.Parse("2025-10-29T14:03:19.4147588"), RegisteredBy = "George Waynne", Customer = new Customer