diff --git a/ObjectSemantics.NET.Tests/BasicMappingTests.cs b/ObjectSemantics.NET.Tests/BasicMappingTests.cs deleted file mode 100644 index 083a492..0000000 --- a/ObjectSemantics.NET.Tests/BasicMappingTests.cs +++ /dev/null @@ -1,92 +0,0 @@ -using ObjectSemantics.NET.Tests.MoqModels; -using System.Collections.Generic; -using Xunit; - -namespace ObjectSemantics.NET.Tests -{ - public class BasicMappingTests - { - [Fact] - public void Should_Map_Object_To_Template_From_TemplateObject() - { - //Create Model - Student student = new Student - { - StudentName = "George Waynne", - Balance = 2510 - }; - var template = new ObjectSemanticsTemplate - { - FileContents = @"My Name is: {{ StudentName }}" - }; - string generatedTemplate = template.Map(student); - string expectedString = "My Name is: George Waynne"; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } - - - [Fact] - public void Should_Map_Additional_Parameters() - { - //Create Model - Student student = new Student - { - StudentName = "George Waynne" - }; - //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"My Name is: {{ StudentName }} | CompanyName: {{ CompanyName }} | CompanyEmail: {{ CompanyEmail }} | Employees: {{ Employees }}" - }; - //Additional Parameters - List additionalParams = new List - { - new ObjectSemanticsKeyValue{ Key ="CompanyName", Value= "TEST INC." }, - new ObjectSemanticsKeyValue{ Key ="CompanyEmail", Value= "test.inc@test.com" }, - new ObjectSemanticsKeyValue{ Key ="Employees", Value= 1289 }, - }; - string generatedTemplate = template.Map(student, additionalParams); - string expectedString = "My Name is: George Waynne | CompanyName: TEST INC. | CompanyEmail: test.inc@test.com | Employees: 1289"; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } - - - [Fact] - public void Should_Return_Unknown_Properties_If_Not_Found_In_Object() - { - //Create Model - Student student = new Student - { - StudentName = "George Waynne" - }; - //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"Unknown Object example: {{StudentIdentityCardXyx}}" - }; - string generatedTemplate = template.Map(student); - string expectedString = "Unknown Object example: {{ StudentIdentityCardXyx }}"; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } - - - [Fact] - public void Should_Ignore_Whitespaces_Inside_CurlyBrackets() - { - //Create Model - Student student = new Student - { - StudentName = "George Waynne" - }; - //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"StudentName is: {{ StudentName }}" - }; - string generatedTemplate = template.Map(student); - string expectedString = "StudentName is: George Waynne"; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } - - } -} diff --git a/ObjectSemantics.NET.Tests/CharacterEscapingTests.cs b/ObjectSemantics.NET.Tests/CharacterEscapingTests.cs deleted file mode 100644 index e24103e..0000000 --- a/ObjectSemantics.NET.Tests/CharacterEscapingTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -using ObjectSemantics.NET.Tests.MoqModels; -using System.Collections.Generic; -using Xunit; - -namespace ObjectSemantics.NET.Tests -{ - public class CharacterEscapingTests - { - - [Fact] - public void Should_Escape_Xml_Char_If_Option_Is_Enabled() - { - //Create Model - Student student = new Student { StudentName = "I've got \"special\" < & also >" }; - var template = new ObjectSemanticsTemplate - { - FileContents = @"{{ StudentName }}" - }; - string generatedTemplate = template.Map(student, null, new TemplateMapperOptions - { - XmlCharEscaping = true - }); - string expectedString = "I've got "special" < & also >"; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } - - [Fact] - public void Should_Escape_Xml_Char_In_LOOPS_If_Option_Is_Enabled() - { - //Create Model - Student student = new Student - { - Invoices = new List - { - new Invoice{ Narration="I've got \"special\""}, - new Invoice{ Narration="I've got < & also >"} - } - }; - //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"{{ #foreach(invoices) }} [{{ Narration }}] {{ #endforeach }}" - }; - string generatedTemplate = template.Map(student, null, new TemplateMapperOptions - { - XmlCharEscaping = true - }); - string expectedResult = @" [I've got "special"] [I've got < & also >] "; - Assert.Equal(expectedResult, generatedTemplate, false, true, true); - } - } -} diff --git a/ObjectSemantics.NET.Tests/CognitiveMapTests.cs b/ObjectSemantics.NET.Tests/CognitiveMapTests.cs new file mode 100644 index 0000000..f909a0d --- /dev/null +++ b/ObjectSemantics.NET.Tests/CognitiveMapTests.cs @@ -0,0 +1,105 @@ +using ObjectSemantics.NET.Tests.MoqModels; +using System; +using System.Collections.Generic; +using Xunit; + +namespace ObjectSemantics.NET.Tests +{ + public class CognitiveMapTests + { + [Theory] + [InlineData("John Doe")] + [InlineData("Jane Doe")] + public void Library_Entry_Point_T_Extension_Should_Work(string personName) + { + Person person = new Person + { + Name = personName + }; + string generatedTemplate = person.Map("I am {{ Name }}!"); + Assert.Equal($"I am {personName}!", generatedTemplate); + } + + [Theory] + [InlineData("John Doe")] + [InlineData("Jane Doe")] + public void Library_Entry_Point_String_Extension_Should_Work(string personName) + { + Person person = new Person + { + Name = personName + }; + string generatedTemplate = "I am {{ Name }}!".Map(person); + Assert.Equal($"I am {personName}!", generatedTemplate); + } + + [Fact] + public void Additional_Headers_Should_Also_Be_Mapped() + { + Person person = new Person + { + Name = "John Doe" + }; + + //additional params (outside the class) + Dictionary additionalParams = new Dictionary + { + { "Occupation", "Developer"}, + { "DateOfBirth", new DateTime(1995, 01, 01) } + }; + + string generatedTemplate = "Name: {{ Name }} | Occupation: {{ Occupation }} | DOB: {{ DateOfBirth }}".Map(person, additionalParams); + + Assert.Equal($"Name: {person.Name} | Occupation: {additionalParams["Occupation"]} | DOB: {additionalParams["DateOfBirth"]}", generatedTemplate); + } + + + [Theory] + [InlineData("I am {{ Name }}")] + [InlineData("I am {{ Name }}")] + [InlineData("I am {{ Name }}")] + [InlineData("I am {{Name}}")] + [InlineData("I am {{ Name }}")] + public void Whitespaces_Inside_Curl_Braces_Should_Be_Ignored(string template) + { + Person person = new Person + { + Name = "John Doe" + }; + string generatedTemplate = person.Map(template); + Assert.Equal($"I am John Doe", generatedTemplate); + } + + [Theory] + [InlineData("{{ MissingPropX }}", "{{ MissingPropX }}")] + [InlineData("{{ MissingProp123 }}", "{{ MissingProp123 }}")] + [InlineData("{{ UnknownPropertyXyz }}", "{{ UnknownPropertyXyz }}")] //it trims the excess whitespaces + [InlineData("{{ Unknown3x}}", "{{ Unknown3x }}")] //it trims the excess whitespaces + public void None_Existing_Properties_Should_Be_Returned_If_Not_Mapped(string template, string expected) + { + Person person = new Person + { + Name = "John Doe" + }; + string generatedTemplate = person.Map(template); + Assert.Equal(expected, generatedTemplate); + } + + + [Theory] + [InlineData("I've got \"special\" < & also >", "I've got "special" < & also >")] + [InlineData("I've got < & also >", "I've got < & also >")] + public void Should_Escape_Xml_Char_Values_If_Option_Is_Enabled(string value, string expected) + { + Person person = new Person() + { + Name = value + }; + string generatedTemplate = person.Map("{{ Name }}", null, new TemplateMapperOptions + { + XmlCharEscaping = true + }); + Assert.Equal(expected, generatedTemplate); + } + } +} diff --git a/ObjectSemantics.NET.Tests/ComplexityTests.cs b/ObjectSemantics.NET.Tests/ComplexityTests.cs new file mode 100644 index 0000000..1c3178d --- /dev/null +++ b/ObjectSemantics.NET.Tests/ComplexityTests.cs @@ -0,0 +1,63 @@ +using ObjectSemantics.NET.Tests.MoqModels; +using System.Collections.Generic; +using Xunit; + +namespace ObjectSemantics.NET.Tests +{ + public class ComplexityTests + { + + [Fact] + public void Should_Handle_ForEach_Loop_Inside_If_Block_Inline() + { + Person person = new Person + { + MyCars = new List + { + new Car { Make = "BMW" }, + new Car { Make = "Rolls-Royce" } + } + }; + + string template = @"{{ #if(MyCars > 0) }}{{ #foreach(MyCars) }}[{{ Make }}]{{ #endforeach }}{{ #endif }}"; + + string result = person.Map(template); + + string expected = "[BMW][Rolls-Royce]"; + Assert.Equal(expected, result); + } + + + [Fact] + public void Should_Handle_ForEach_Loop_Inside_If_Block_Multiline() + { + Person person = new Person + { + MyCars = new List + { + new Car { Make = "Toyota" }, + new Car { Make = "BMW" } + } + }; + + string template = @" +{{ #if(MyCars > 0) }} + {{ #foreach(MyCars) }} + - {{ Make:uppercase }} + {{ #endforeach }} +{{ #endif }}"; + + string result = person.Map(template); + + string expected = @" + + + - TOYOTA + + - BMW + +"; + Assert.Equal(expected, result); + } + } +} diff --git a/ObjectSemantics.NET.Tests/EnumerableLoopTests.cs b/ObjectSemantics.NET.Tests/EnumerableLoopTests.cs index 415f7b9..e7702e3 100644 --- a/ObjectSemantics.NET.Tests/EnumerableLoopTests.cs +++ b/ObjectSemantics.NET.Tests/EnumerableLoopTests.cs @@ -1,5 +1,4 @@ using ObjectSemantics.NET.Tests.MoqModels; -using System; using System.Collections.Generic; using Xunit; @@ -9,213 +8,136 @@ public class EnumerableLoopTests { [Fact] - public void Should_Map_Enumerable_Collection_In_Object() + public void Should_Map_Enumerable_Collection_SingleLine() { //Create Model - Student student = new Student + Person person = new Person { - StudentName = "John Doe", - Invoices = new List + MyCars = new List { - new Invoice{ Id=2, RefNo="INV_002",Narration="Grade II Fees Invoice", Amount=2000, InvoiceDate= new DateTime(2023, 04, 01) }, - new Invoice{ Id=1, RefNo="INV_001",Narration="Grade I Fees Invoice", Amount=320, InvoiceDate= new DateTime(2022, 08, 01) } + new Car { Make = "BMW", Year = 2023 }, + new Car { Make = "Rolls-Royce", Year = 2020 } } }; //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"{{ StudentName }} Invoices -{{ #foreach(invoices) }} - - {{ Id }} - {{ RefNo }} - {{ Narration }} - {{ Amount:N0 }} - {{ InvoiceDate:yyyy-MM-dd }} - -{{ #endforeach }}" - }; - string generatedTemplate = template.Map(student); - string expectedResult = @"John Doe Invoices - - - 2 - INV_002 - Grade II Fees Invoice - 2,000 - 2023-04-01 - - - - 1 - INV_001 - Grade I Fees Invoice - 320 - 2022-08-01 -"; - Assert.Equal(expectedResult, generatedTemplate, false, true, true); - } + string template = @"{{ #foreach(MyCars) }}I own a {{ Year }} {{ Make }}.{{ #endforeach }}"; + string result = person.Map(template); - [Fact] - public void Should_Map_Enumerable_Collection_SingleLine_Test() - { - //Create Model - Student student = new Student - { - StudentName = "John Doe", - Invoices = new List - { - new Invoice{ Id=2, RefNo="INV_002",Narration="Grade II Fees Invoice", Amount=2000, InvoiceDate= new DateTime(2023, 04, 01) }, - new Invoice{ Id=1, RefNo="INV_001",Narration="Grade I Fees Invoice", Amount=320, InvoiceDate= new DateTime(2022, 08, 01) } - } - }; - //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"{{ #foreach(invoices) }} [{{RefNo}}] {{ #endforeach }}" - }; - string generatedTemplate = template.Map(student); - string expectedResult = " [INV_002] [INV_001] "; - Assert.Equal(expectedResult, generatedTemplate, false, true, true); + string expectedResult = $"I own a 2023 BMW.I own a 2020 Rolls-Royce."; + Assert.Equal(expectedResult, result); } - [Fact] - public void Should_Map_Multiple_Same_Property_Enumerable_Collection_On_Same_Template() + public void Should_Map_Enumerable_Collection_MultiLine() { //Create Model - Student student = new Student + Person person = new Person { - StudentName = "John Doe", - Invoices = new List + Name = "John Doe", + MyCars = new List { - new Invoice{ Id=2, RefNo="INV_002",Narration="Grade II Fees Invoice", Amount=2000, InvoiceDate= new DateTime(2023, 04, 01) }, - new Invoice{ Id=1, RefNo="INV_001",Narration="Grade I Fees Invoice", Amount=320, InvoiceDate= new DateTime(2022, 08, 01) } + new Car { Make = "BMW", Year = 2023 }, + new Car { Make = "Rolls-Royce", Year = 2020 } } }; //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @" -{{ StudentName }} Invoices -LOOP #1 -{{ #foreach(invoices) }} -
{{ Id }} On Loop #1
-{{ #endforeach }} -LOOP #2 -{{ #foreach(invoices) }} -
{{ Id }} On Loop #2
-{{ #endforeach }}" - }; - string generatedTemplate = template.Map(student); - string expectedResult = @" -John Doe Invoices -LOOP #1 + string template = @" +{{ Name }}'s Cars +{{ #foreach(MyCars) }} + - {{ Year }} {{ Make }} +{{ #endforeach }}"; -
2 On Loop #1
+ string result = person.Map(template); -
1 On Loop #1
-LOOP #2 + string expectedResult = @" +John Doe's Cars -
2 On Loop #2
+ - 2023 BMW -
1 On Loop #2
"; - Assert.Equal(expectedResult, generatedTemplate, false, true, true); + - 2020 Rolls-Royce +"; + Assert.Equal(expectedResult, result); } [Fact] - public void Should_Map_Multiple_Different_Property_Enumerable_Collection_On_Same_Template() + public void Should_Map_Multiple_Enumerable_Collections() { //Create Model - Student student = new Student + Person person = new Person { - StudentName = "John Doe", - Invoices = new List + MyCars = new List { - new Invoice{ Id=2, RefNo="INV_002",Narration="Grade II Fees Invoice", Amount=2000, InvoiceDate= new DateTime(2023, 04, 01) }, - new Invoice{ Id=1, RefNo="INV_001",Narration="Grade I Fees Invoice", Amount=320, InvoiceDate= new DateTime(2022, 08, 01) } + new Car { Make = "Honda" }, + new Car { Make = "Toyota" } }, - StudentClockInDetails = new List + MyDreamCars = new List { - new StudentClockInDetail{ LastClockedInDate = new DateTime(2024, 04, 01), LastClockedInPoints = 10 }, - new StudentClockInDetail{ LastClockedInDate = new DateTime(2024, 04, 02), LastClockedInPoints = 30 } + new Car { Make = "BWM" }, + new Car { Make = "Rolls-Royce" } } }; //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @" -{{ StudentName }} Invoices -LOOP #1 -{{ #foreach(invoices) }} -
{{ Id }} On Loop #1
+ string template = @" +My Cars +{{ #foreach(MyCars) }} + - {{ Make }} {{ #endforeach }} -LOOP #2 -{{ #foreach(studentClockInDetails) }} -
Got {{ LastClockedInPoints }} for {{ LastClockedInDate:yyyy-MM-dd }}
-{{ #endforeach }}" - }; - string generatedTemplate = template.Map(student); +My Dream Cars +{{ #foreach(MyDreamCars) }} + * {{ Make }} +{{ #endforeach }} +"; + + string result = person.Map(template); + string expectedResult = @" -John Doe Invoices -LOOP #1 +My Cars -
2 On Loop #1
+ - Honda -
1 On Loop #1
-LOOP #2 + - Toyota -
Got 10 for 2024-04-01
+My Dream Cars -
Got 30 for 2024-04-02
"; + * BWM - Assert.Equal(expectedResult, generatedTemplate, false, true, true); + * Rolls-Royce + +"; + Assert.Equal(expectedResult, result); } [Fact] - public void Should_Map_Array_Of_String_With_Formatting() + public void Should_Map_Array_Of_String() { //Create Model - Student student = new Student + Person person = new Person { - ArrayOfString = new string[] - { - "String 001", - "String 002" - } + MyFriends = new string[] { "Morgan", "George", "Jane" } }; //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"{{ #foreach(ArrayOfString) }} {{ . }} | {{ .:uppercase }} {{ #endforeach }}" - }; - string generatedTemplate = template.Map(student); - string expectedResult = " String 001 | STRING 001 String 002 | STRING 002 "; + string template = @"{{ #foreach(MyFriends) }} {{ . }} {{ #endforeach }}"; + + string generatedTemplate = person.Map(template); + string expectedResult = " Morgan George Jane "; Assert.Equal(expectedResult, generatedTemplate, false, true, true); } - [Fact] - public void Should_Map_Array_Of_Double_With_Formatting_Test() + public void Should_Map_Array_Of_String_With_Formatting() { //Create Model - Student student = new Student + Person person = new Person { - ArrayOfDouble = new double[] - { - 1000.15, - 2000.22 - } + MyFriends = new string[] { "Morgan", "George", "Jane" } }; //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"{{ #foreach(ArrayOfDouble) }} {{ . }} | {{ .:N0 }} {{ #endforeach }}" - }; - string generatedTemplate = template.Map(student); - string expectedResult = " 1000.15 | 1,000 2000.22 | 2,000 "; + string template = @"{{ #foreach(MyFriends) }} {{ .:uppercase }} {{ #endforeach }}"; + + string generatedTemplate = person.Map(template); + string expectedResult = " MORGAN GEORGE JANE "; Assert.Equal(expectedResult, generatedTemplate, false, true, true); } } diff --git a/ObjectSemantics.NET.Tests/IfConditionTests.cs b/ObjectSemantics.NET.Tests/IfConditionTests.cs index dc997a9..d250295 100644 --- a/ObjectSemantics.NET.Tests/IfConditionTests.cs +++ b/ObjectSemantics.NET.Tests/IfConditionTests.cs @@ -7,111 +7,110 @@ namespace ObjectSemantics.NET.Tests public class IfConditionTests { [Theory] - [InlineData(1, "Valid")] - [InlineData(0, "Invalid")] - public void Should_Render_If_Block_When_Condition_Is_True(int id, string expected) + [InlineData(40, "Adult")] + [InlineData(18, "Adult")] + [InlineData(0, "Minor")] + [InlineData(-7, "Minor")] + public void Should_Render_If_GreaterOrEqual_Condition_Block(int age, string expected) { - var model = new Invoice { Id = id }; - - var template = new ObjectSemanticsTemplate + Person person = new Person { - FileContents = @"{{ #if(Id == 1) }}Valid{{ #else }}Invalid{{ #endif }}" + Age = age }; - string result = template.Map(model); + string template = @"{{ #if(Age >= 18) }}Adult{{ #else }}Minor{{ #endif }}"; + + string result = person.Map(template); Assert.Equal(expected, result); } [Theory] - [InlineData(18, "Minor")] - [InlineData(21, "Adult")] - [InlineData(5, "Minor")] - public void Should_Handle_LessThan_Or_Equal(int age, string expected) + [InlineData(40, "Adult 40")] + [InlineData(18, "Adult 18")] + [InlineData(0, "Minor 0")] + [InlineData(-7, "Minor -7")] + public void Should_Resolve_Variables_Inside_If_Condition(int age, string expected) { - var model = new Student { Age = age }; - - var template = new ObjectSemanticsTemplate + Person person = new Person { - FileContents = @"{{ #if(Age <= 18) }}Minor{{ #else }}Adult{{ #endif }}" + Age = age }; - var result = template.Map(model); + string template = @"{{ #if( Age > 17 ) }}Adult {{Age}}{{ #else }}Minor {{Age}}{{ #endif }}"; + + var result = person.Map(template); Assert.Equal(expected, result); } [Theory] - [InlineData(1, "1")] - [InlineData(0, "Error")] - [InlineData(-1, "Error")] - [InlineData(5, "5")] - [InlineData(+2, "2")] - public void Should_Handle_Whitespace_And_Case_Insensitive_Condition(int id, string expected) + [InlineData(40, "")] + [InlineData(18, "")] + [InlineData(0, "NoDrinkingBeer")] + [InlineData(-7, "NoDrinkingBeer")] + public void Should_Render_If_Block_Without_Else_When_True(int age, string expected) { - var model = new Invoice { Id = id }; - - var template = new ObjectSemanticsTemplate + Person person = new Person { - FileContents = @"{{ #if( id > 0 ) }}{{id}}{{ #else }}Error{{ #endif }}" + Age = age }; - var result = template.Map(model); + string template = @"{{ #if(Age < 18) }}NoDrinkingBeer{{ #endif }}"; + + string result = person.Map(template); Assert.Equal(expected, result); } - [Fact] - public void Should_Render_If_Block_Without_Else_When_True() + public void Should_Evaluate_Enumerable_Count_Inside_If_Block() { - var model = new Student { IsActive = true }; - var template = new ObjectSemanticsTemplate + Person person = new Person { - FileContents = @"Student: John {{ #if(IsActive == true) }}[Is Active]{{ #endif }}" + MyCars = new List + { + new Car { Make = "BMW" }, + new Car { Make = "Rolls-Royce" } + } }; - var result = template.Map(model); - Assert.Equal("Student: John [Is Active]", result); + string template = @"{{ #if(MyCars == 2) }}Has 2 Cars{{ #endif }}"; + + string result = person.Map(template); + Assert.Equal("Has 2 Cars", result); } [Fact] - public void Should_Evaluate_If_Enumerable_Count() + public void Should_Evaluate_Empty_Enumerable_As_Zero() { - var model = new Student + Person person = new Person { - Invoices = new List - { - new Invoice{ Id = 2, RefNo = "INV_002" }, - new Invoice{ Id = 1, RefNo = "INV_001" } - } + MyCars = null }; - var template = new ObjectSemanticsTemplate - { - FileContents = @"{{ #if(Invoices == 2) }}Matched{{ #else }}Not Matched{{ #endif }}" - }; + string template = @"{{ #if(MyCars == 0) }}Zero Cars{{ #endif }}"; - var result = template.Map(model); - Assert.Equal("Matched", result); + string result = person.Map(template); + Assert.Equal("Zero Cars", result); } - [Fact] - public void Should_Evaluate_Empty_Enumerable_As_Zero() + [Theory] + [InlineData("John DoeX2", "Yes")] + [InlineData("John Doe", "No")] + [InlineData("Doe", "No")] + [InlineData("John ", "No")] + public void Should_Evaluate_String_Conditions_If_Blocks(string name, string expected) { - var model = new Student + Person person = new Person { - Invoices = new List() + Name = name }; - var template = new ObjectSemanticsTemplate - { - FileContents = @"{{ #if(Invoices == 0) }}No invoices available{{ #else }}Invoices Found{{ #endif }}" - }; + string template = @"{{ #if(Name == John DoeX2) }}Yes{{ #else }}No{{ #endif }}"; - var result = template.Map(model); - Assert.Equal("No invoices available", result); + string result = person.Map(template); + Assert.Equal(expected, result); } - } } diff --git a/ObjectSemantics.NET.Tests/MoqFiles/template-example.txt b/ObjectSemantics.NET.Tests/MoqFiles/template-example.txt deleted file mode 100644 index e9b68dc..0000000 --- a/ObjectSemantics.NET.Tests/MoqFiles/template-example.txt +++ /dev/null @@ -1 +0,0 @@ -My Name is: {{ StudentName }} and my balance is: {{ Balance }} \ No newline at end of file diff --git a/ObjectSemantics.NET.Tests/MoqModels/Car.cs b/ObjectSemantics.NET.Tests/MoqModels/Car.cs new file mode 100644 index 0000000..1df589f --- /dev/null +++ b/ObjectSemantics.NET.Tests/MoqModels/Car.cs @@ -0,0 +1,20 @@ +using System; + +namespace ObjectSemantics.NET.Tests.MoqModels +{ + public class Car + { + public int Id { get; set; } + public string Make { get; set; } + public int Year { get; set; } + public double EngineSize { get; set; } + public float FuelEfficiency { get; set; } + public decimal Price { get; set; } + public bool IsElectric { get; set; } + public bool? IsLiked { get; set; } = null; + public DateTime ManufactureDate { get; set; } + public DateTime? LastServiceDate { get; set; } + public long Mileage { get; set; } + public short NumberOfDoors { get; set; } + } +} diff --git a/ObjectSemantics.NET.Tests/MoqModels/Invoice.cs b/ObjectSemantics.NET.Tests/MoqModels/Invoice.cs deleted file mode 100644 index 7b43786..0000000 --- a/ObjectSemantics.NET.Tests/MoqModels/Invoice.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace ObjectSemantics.NET.Tests.MoqModels -{ - internal class Invoice - { - public int Id { get; set; } - public string RefNo { get; set; } - public string Narration { get; set; } - public double Amount { get; set; } - public DateTime InvoiceDate { get; set; } - } -} diff --git a/ObjectSemantics.NET.Tests/MoqModels/Person.cs b/ObjectSemantics.NET.Tests/MoqModels/Person.cs new file mode 100644 index 0000000..98235a0 --- /dev/null +++ b/ObjectSemantics.NET.Tests/MoqModels/Person.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace ObjectSemantics.NET.Tests.MoqModels +{ + public class Person + { + public string Name { get; set; } + public int Age { get; set; } + public List MyCars { get; set; } = new List(); + public List MyDreamCars { get; set; } = new List(); + public string[] MyFriends { get; set; } + } +} diff --git a/ObjectSemantics.NET.Tests/MoqModels/Student.cs b/ObjectSemantics.NET.Tests/MoqModels/Student.cs deleted file mode 100644 index 929e3de..0000000 --- a/ObjectSemantics.NET.Tests/MoqModels/Student.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace ObjectSemantics.NET.Tests.MoqModels -{ - internal class Student - { - public Guid Id { get; set; } = Guid.NewGuid(); - public string StudentName { get; set; } - public double Balance { get; set; } - public int Age { get; set; } - public DateTime RegDate { get; set; } = DateTime.Now; - public List Invoices { get; set; } = new List(); - public string[] ArrayOfString { get; set; } = new string[] { }; - public double[] ArrayOfDouble { get; set; } = new double[] { }; - public bool IsActive { get; set; } - public List StudentClockInDetails { get; set; } = new List(); - } - class StudentClockInDetail - { - public DateTime? LastClockedInDate { get; set; } = null; - public long? LastClockedInPoints { get; set; } = null; - } -} diff --git a/ObjectSemantics.NET.Tests/ObjectSemantics.NET.Tests.csproj b/ObjectSemantics.NET.Tests/ObjectSemantics.NET.Tests.csproj index 2641d6f..c804413 100644 --- a/ObjectSemantics.NET.Tests/ObjectSemantics.NET.Tests.csproj +++ b/ObjectSemantics.NET.Tests/ObjectSemantics.NET.Tests.csproj @@ -1,36 +1,30 @@ - - netcoreapp3.1 - - false - - 3.0.1.1 - - 3.0.1.1 - - - - - Always - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - + + netcoreapp3.1 + + false + + 3.0.1.1 + + 3.0.1.1 + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + diff --git a/ObjectSemantics.NET.Tests/PropertyBooleanMapTests.cs b/ObjectSemantics.NET.Tests/PropertyBooleanMapTests.cs new file mode 100644 index 0000000..0ff5e6a --- /dev/null +++ b/ObjectSemantics.NET.Tests/PropertyBooleanMapTests.cs @@ -0,0 +1,35 @@ +using ObjectSemantics.NET.Tests.MoqModels; +using Xunit; + +namespace ObjectSemantics.NET.Tests +{ + public class PropertyBooleanMapTests + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Map_From_Bool(bool isElectric) + { + Car car = new Car() + { + IsElectric = isElectric + }; + string result = car.Map("Electric: {{ IsElectric }}"); + Assert.Equal($"Electric: {isElectric}", result, false, true, true); + } + + [Theory] + [InlineData(null)] + [InlineData(true)] + [InlineData(false)] + public void Should_Map_From_Nullable_Bool(bool? isLiked) + { + Car car = new Car() + { + IsLiked = isLiked + }; + string result = car.Map("Liked: {{ IsLiked }}"); + Assert.Equal($"Liked: {isLiked}", result, false, true, true); + } + } +} diff --git a/ObjectSemantics.NET.Tests/PropertyDateTimeMapTests.cs b/ObjectSemantics.NET.Tests/PropertyDateTimeMapTests.cs new file mode 100644 index 0000000..730a68a --- /dev/null +++ b/ObjectSemantics.NET.Tests/PropertyDateTimeMapTests.cs @@ -0,0 +1,43 @@ +using ObjectSemantics.NET.Tests.MoqModels; +using System; +using Xunit; + +namespace ObjectSemantics.NET.Tests +{ + public class PropertyDateTimeMapTests + { + [Fact] + public void Should_Map_From_Date() + { + DateTime date = new DateTime(2022, 11, 27, 18, 13, 59); + Car car = new Car() + { + ManufactureDate = date + }; + string result = car.Map("{{ ManufactureDate:yyyy }}|{{ ManufactureDate:yyyy-MM-dd HH:mm tt }}"); + Assert.Equal($"{car.ManufactureDate:yyyy}|{car.ManufactureDate:yyyy-MM-dd HH:mm tt}", result, false, true, true); + } + + [Fact] + public void Should_Map_From_Null_Date() + { + Car car = new Car() + { + LastServiceDate = null + }; + string result = car.Map("Last serviced: {{ LastServiceDate }}"); + Assert.Equal($"Last serviced: {car.LastServiceDate}", result, false, true, true); + } + + [Fact] + public void Should_Map_From_Nullable_Date() + { + Car car = new Car() + { + LastServiceDate = new DateTime(2025, 1, 1) + }; + string result = car.Map("Last serviced: {{ LastServiceDate }}"); + Assert.Equal($"Last serviced: {car.LastServiceDate}", result, false, true, true); + } + } +} diff --git a/ObjectSemantics.NET.Tests/PropertyNullableTests.cs b/ObjectSemantics.NET.Tests/PropertyNullableTests.cs index 6cb7998..fc22201 100644 --- a/ObjectSemantics.NET.Tests/PropertyNullableTests.cs +++ b/ObjectSemantics.NET.Tests/PropertyNullableTests.cs @@ -1,68 +1,68 @@ -using ObjectSemantics.NET.Tests.MoqModels; -using System; -using Xunit; +//using ObjectSemantics.NET.Tests.MoqModels; +//using System; +//using Xunit; -namespace ObjectSemantics.NET.Tests -{ - public class PropertyNullableTests - { - [Fact] - public void Should_Map_Nullable_DateTime_Property_Given_NULL() - { - //Create Model - StudentClockInDetail clockInDetails = new StudentClockInDetail { LastClockedInDate = null }; - var template = new ObjectSemanticsTemplate - { - FileContents = @"Last Clocked In: {{ LastClockedInDate:yyyy-MM-dd }}" - }; - string generatedTemplate = template.Map(clockInDetails); - string expectedString = "Last Clocked In: "; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } +//namespace ObjectSemantics.NET.Tests +//{ +// public class PropertyNullableTests +// { +// [Fact] +// public void Should_Map_Nullable_DateTime_Property_Given_NULL() +// { +// //Create Model +// StudentClockInDetail clockInDetails = new StudentClockInDetail { LastClockedInDate = null }; +// var template = new ObjectSemanticsTemplate +// { +// FileContents = @"Last Clocked In: {{ LastClockedInDate:yyyy-MM-dd }}" +// }; +// string generatedTemplate = template.Map(clockInDetails); +// string expectedString = "Last Clocked In: "; +// Assert.Equal(expectedString, generatedTemplate, false, true, true); +// } - [Fact] - public void Should_Map_Nullable_DateTime_Property_Given_A_Value() - { - //Create Model - StudentClockInDetail clockInDetails = new StudentClockInDetail { LastClockedInDate = DateTime.Now }; - var template = new ObjectSemanticsTemplate - { - FileContents = @"Last Clocked In: {{ LastClockedInDate:yyyy-MM-dd }}" - }; - string generatedTemplate = template.Map(clockInDetails); - string expectedString = $"Last Clocked In: {DateTime.Now:yyyy-MM-dd}"; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } +// [Fact] +// public void Should_Map_Nullable_DateTime_Property_Given_A_Value() +// { +// //Create Model +// StudentClockInDetail clockInDetails = new StudentClockInDetail { LastClockedInDate = DateTime.Now }; +// var template = new ObjectSemanticsTemplate +// { +// FileContents = @"Last Clocked In: {{ LastClockedInDate:yyyy-MM-dd }}" +// }; +// string generatedTemplate = template.Map(clockInDetails); +// string expectedString = $"Last Clocked In: {DateTime.Now:yyyy-MM-dd}"; +// Assert.Equal(expectedString, generatedTemplate, false, true, true); +// } - [Fact] - public void Should_Map_Nullable_Number_Property_Given_NULL() - { - //Create Model - StudentClockInDetail clockInDetails = new StudentClockInDetail { LastClockedInPoints = null }; - var template = new ObjectSemanticsTemplate - { - FileContents = @"Last Clocked In Points: {{ LastClockedInPoints:N2 }}" - }; - string generatedTemplate = template.Map(clockInDetails); - string expectedString = "Last Clocked In Points: "; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } +// [Fact] +// public void Should_Map_Nullable_Number_Property_Given_NULL() +// { +// //Create Model +// StudentClockInDetail clockInDetails = new StudentClockInDetail { LastClockedInPoints = null }; +// var template = new ObjectSemanticsTemplate +// { +// FileContents = @"Last Clocked In Points: {{ LastClockedInPoints:N2 }}" +// }; +// string generatedTemplate = template.Map(clockInDetails); +// string expectedString = "Last Clocked In Points: "; +// Assert.Equal(expectedString, generatedTemplate, false, true, true); +// } - [Theory] - [InlineData(null)] - [InlineData(2500)] - [InlineData(200)] - public void Should_Map_Nullable_Number_Property_Given_A_Value(long? number) - { - //Create Model - StudentClockInDetail clockInDetails = new StudentClockInDetail { LastClockedInPoints = number }; - var template = new ObjectSemanticsTemplate - { - FileContents = @"Last Clocked In Points: {{ LastClockedInPoints:N2 }}" - }; - string generatedTemplate = template.Map(clockInDetails); - string expectedString = $"Last Clocked In Points: {number:N2}"; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } - } -} +// [Theory] +// [InlineData(null)] +// [InlineData(2500)] +// [InlineData(200)] +// public void Should_Map_Nullable_Number_Property_Given_A_Value(long? number) +// { +// //Create Model +// StudentClockInDetail clockInDetails = new StudentClockInDetail { LastClockedInPoints = number }; +// var template = new ObjectSemanticsTemplate +// { +// FileContents = @"Last Clocked In Points: {{ LastClockedInPoints:N2 }}" +// }; +// string generatedTemplate = template.Map(clockInDetails); +// string expectedString = $"Last Clocked In Points: {number:N2}"; +// Assert.Equal(expectedString, generatedTemplate, false, true, true); +// } +// } +//} diff --git a/ObjectSemantics.NET.Tests/PropertyNumberMapTests.cs b/ObjectSemantics.NET.Tests/PropertyNumberMapTests.cs new file mode 100644 index 0000000..f08fa1f --- /dev/null +++ b/ObjectSemantics.NET.Tests/PropertyNumberMapTests.cs @@ -0,0 +1,101 @@ +using ObjectSemantics.NET.Tests.MoqModels; +using Xunit; + +namespace ObjectSemantics.NET.Tests +{ + public class PropertyNumberMapTests + { + [Theory] + [InlineData(2013)] + [InlineData(2025000)] + public void Should_Map_From_Int(int year) + { + Car car = new Car() + { + Year = year + }; + string result = car.Map("Year: {{ Year }}"); + Assert.Equal($"Year: {year}", result); + } + + [Theory] + [InlineData(3.5)] + [InlineData(1.3)] + public void Should_Map_From_Double(double size) + { + Car car = new Car() + { + EngineSize = size + }; + string result = car.Map("Engine size: {{ EngineSize }}"); + Assert.Equal($"Engine size: {size}", result); + } + + [Theory] + [InlineData(14.7f)] + [InlineData(10.2f)] + public void Should_Map_From_Float(float efficiency) + { + Car car = new Car() + { + FuelEfficiency = efficiency + }; + string result = car.Map("Fuel efficiency: {{ FuelEfficiency }}"); + Assert.Equal($"Fuel efficiency: {efficiency}", result); + } + + [Theory] + [InlineData(1050000.75)] + [InlineData(800.25)] + public void Should_Map_From_Decimal(decimal price) + { + Car car = new Car() + { + Price = price + }; + string result = car.Map("Price: {{ Price }}"); + Assert.Equal($"Price: {price}", result); + } + + [Theory] + [InlineData(150000L)] + [InlineData(6000L)] + public void Should_Map_From_Long(long mileage) + { + Car car = new Car() + { + Mileage = mileage + }; + string result = car.Map("Mileage: {{ Mileage }}"); + Assert.Equal($"Mileage: {mileage}", result, false, true, true); + } + + [Theory] + [InlineData((short)4)] + [InlineData((short)2)] + public void Should_Map_From_Short(short doors) + { + Car car = new Car() + { + NumberOfDoors = doors + }; + string result = car.Map("Doors: {{ NumberOfDoors }}"); + Assert.Equal($"Doors: {doors}", result, false, true, true); + } + + + [Theory] + [InlineData(20000)] + [InlineData(50_000)] + [InlineData(100000)] + public void Should_Support_Number_To_String_Formatting(decimal price) + { + Car car = new Car + { + Price = price + }; + string generatedTemplate = car.Map("{{ Price:#,##0 }}|{{ Price:N5 }}"); + Assert.Equal($"{price:#,##0}|{price:N5}", generatedTemplate); + } + } +} diff --git a/ObjectSemantics.NET.Tests/PropertyStringMapTests.cs b/ObjectSemantics.NET.Tests/PropertyStringMapTests.cs new file mode 100644 index 0000000..90ef124 --- /dev/null +++ b/ObjectSemantics.NET.Tests/PropertyStringMapTests.cs @@ -0,0 +1,140 @@ +using ObjectSemantics.NET.Tests.MoqModels; +using Xunit; + +namespace ObjectSemantics.NET.Tests +{ + public class PropertyStringMapTests + { + [Theory] + [InlineData("BMW")] + [InlineData("Toyota")] + [InlineData("Mercedes-Benz")] + [InlineData("Land Rover")] + public void Should_Map_From_Simple_String(string make) + { + Car car = new Car + { + Make = make + }; + string generatedTemplate = car.Map("My car make is {{ Make }}."); + Assert.Equal($"My car make is {make}.", generatedTemplate); + } + + [Theory] + [InlineData("L(rover")] + [InlineData("Toy >> tA")] + [InlineData("Peugeot 308")] + [InlineData("Fiat 500X")] + public void Should_Map_From_SpecialChar_String(string make) + { + Car car = new Car + { + Make = make + }; + string generatedTemplate = car.Map("My car make is {{ Make }}."); + Assert.Equal($"My car make is {make}.", generatedTemplate); + } + + [Fact] + public void Should_Map_From_Null_String() + { + Car car = new Car + { + Make = null + }; + string generatedTemplate = car.Map("My car make is {{ Make }}."); + Assert.Equal($"My car make is {car.Make}.", generatedTemplate); + } + + [Fact] + public void Should_Format_String_To_UpperCase() + { + Car car = new Car + { + Make = "Toyota" + }; + string generatedTemplate = car.Map("{{ Make:uppercase }}"); + Assert.Equal(car.Make.ToUpper(), generatedTemplate); + } + + [Fact] + public void Should_Format_String_To_LowerCase() + { + Car car = new Car + { + Make = "ToYota" + }; + string generatedTemplate = car.Map("{{ Make:lowercase }}"); + Assert.Equal(car.Make.ToLower(), generatedTemplate); + } + + [Theory] + [InlineData("toYota", "Toyota")] + [InlineData("BMW", "Bmw")] + [InlineData("Peugeot 308", "Peugeot 308")] + public void Should_Format_String_To_TitleCase(string make, string expectedTitleCase) + { + Car car = new Car + { + Make = make + }; + string generatedTemplate = car.Map("{{ Make:titlecase }}"); + Assert.Equal(expectedTitleCase, generatedTemplate); + } + + [Theory] + [InlineData("BMW", "BCB48DDDFF8C14B5F452EE573B4DB770")] + [InlineData("Mercedes-Benz", "BE956E74642D5B18C6A4864AAD39F494")] + [InlineData("Peugeot 308", "8C617F9B2E4DE166E277FEE781D32DAF")] + public void Should_Format_String_To_MD5(string make, string expectedString) + { + Car car = new Car + { + Make = make + }; + string generatedTemplate = car.Map("{{ Make:ToMD5 }}"); + Assert.Equal(expectedString, generatedTemplate); + } + + [Theory] + [InlineData("BMW", "Qk1X")] + [InlineData("Mercedes-Benz", "TWVyY2VkZXMtQmVueg==")] + [InlineData("Peugeot 308", "UGV1Z2VvdCAzMDg=")] + public void Should_Format_String_To_Base64_Format(string make, string expectedString) + { + Car car = new Car + { + Make = make + }; + string generatedTemplate = car.Map("{{ Make:ToBase64 }}"); + Assert.Equal(expectedString, generatedTemplate); + } + + [Theory] + [InlineData("Qk1X", "BMW")] + [InlineData("TWVyY2VkZXMtQmVueg==", "Mercedes-Benz")] + [InlineData("UGV1Z2VvdCAzMDg=", "Peugeot 308")] + public void Should_Format_String_From_Base64_Format(string make, string expectedString) + { + Car car = new Car + { + Make = make + }; + string generatedTemplate = car.Map("{{ Make:FromBase64 }}"); + Assert.Equal(expectedString, generatedTemplate); + } + + [Theory] + [InlineData("BMW")] + [InlineData("Mercedes-Benz")] + public void Should_Accept_Length_String_Format(string make) + { + Car car = new Car + { + Make = make + }; + string generatedTemplate = car.Map("{{ Make:length }}"); + Assert.Equal(make.Length.ToString(), generatedTemplate); + } + } +} diff --git a/ObjectSemantics.NET.Tests/StringFormattingTests.cs b/ObjectSemantics.NET.Tests/StringFormattingTests.cs deleted file mode 100644 index 07354f4..0000000 --- a/ObjectSemantics.NET.Tests/StringFormattingTests.cs +++ /dev/null @@ -1,169 +0,0 @@ -using ObjectSemantics.NET.Tests.MoqModels; -using System; -using Xunit; - -namespace ObjectSemantics.NET.Tests -{ - public class StringFormattingTests - { - [Fact] - public void Should_Accept_String_To_UpperCase_or_LowerCase_Formatting() - { - //Create Model - Student student = new Student - { - StudentName = "WILLiaM" - }; - //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"Original StudentName: {{ StudentName }}, Uppercase StudentName: {{ StudentName:UPPERCASE }}, Lowercase StudentName: {{ StudentName:lowercase }}" - }; - string generatedTemplate = template.Map(student); - string expectedString = "Original StudentName: WILLiaM, Uppercase StudentName: WILLIAM, Lowercase StudentName: william"; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } - - [Theory] - [InlineData("john doe", "John Doe")] - [InlineData("JANE DOE", "Jane Doe")] - [InlineData("aLiCe joHNsOn", "Alice Johnson")] - [InlineData("", "")] - [InlineData(null, "")] - public void Should_Convert_StudentName_To_TitleCase(string studentName, string expectedTitleCase) - { - // Create Model - Student student = new Student - { - StudentName = studentName - }; - - // Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"{{ StudentName:titlecase }}" - }; - - string generatedTemplate = template.Map(student); - - Assert.Equal(expectedTitleCase, generatedTemplate, ignoreCase: false, ignoreLineEndingDifferences: true, ignoreWhiteSpaceDifferences: true); - } - - - [Fact] - public void Should_Accept_Number_To_String_Formatting() - { - //Create Model - Student student = new Student - { - Balance = 20000.5788 - }; - //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"Original Balance: {{ Balance }}, #,##0 Balance: {{ Balance:#,##0 }}, N5 Balance: {{ Balance:N5 }}" - }; - string generatedTemplate = template.Map(student); - string expectedString = "Original Balance: 20000.5788, #,##0 Balance: 20,001, N5 Balance: 20,000.57880"; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } - - [Fact] - public void Should_Accept_DateTime_To_String_Formatting() - { - //Lets see how it can handle multiple : {{ RegDate:yyyy-MM-dd HH:mm tt }} - Student student = new Student - { - RegDate = new DateTime(2022, 11, 27, 18, 13, 59) - }; - //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"Original RegDate: {{ RegDate }}, yyyy RegDate: {{ RegDate:yyyy }}, yyyy-MM-dd HH:mm tt RegDate: {{ RegDate:yyyy-MM-dd HH:mm tt }}" - }; - string generatedTemplate = template.Map(student); - string expectedString = "Original RegDate: 11/27/2022 6:13:59 PM, yyyy RegDate: 2022, yyyy-MM-dd HH:mm tt RegDate: 2022-11-27 18:13 PM"; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } - - - [Fact] - public void Should_Accept_String_To_MD5_Formatting() - { - //Create Model - Student student = new Student - { - StudentName = "John DOE" - }; - //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"Original String: {{ StudentName }} | To MD5 String: {{ StudentName:ToMD5 }}" - }; - string generatedTemplate = template.Map(student); - string expectedString = "Original String: John DOE | To MD5 String: 82AF64057A5F0D528CEE6F55D05823D7"; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } - - - [Fact] - public void Should_Accept_String_To_BASE64_Formatting() - { - //Create Model - Student student = new Student - { - StudentName = "John DOE" - }; - //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"Original String: {{ StudentName }} | To BASE64 String: {{ StudentName:ToBase64 }}" - }; - string generatedTemplate = template.Map(student); - string expectedString = "Original String: John DOE | To BASE64 String: Sm9obiBET0U="; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } - - [Fact] - public void Should_Accept_String_From_BASE64_Formatting() - { - //Create Model - Student student = new Student - { - StudentName = "Sm9obiBET0U=" - }; - //Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"Original String: {{ StudentName }} | From BASE64 String: {{ StudentName:FromBase64 }}" - }; - string generatedTemplate = template.Map(student); - string expectedString = "Original String: Sm9obiBET0U= | From BASE64 String: John DOE"; - Assert.Equal(expectedString, generatedTemplate, false, true, true); - } - - [Theory] - [InlineData("Alice Johnson", "13")] - [InlineData("Bob", "3")] - [InlineData("", "0")] - [InlineData(null, "")] - public void Should_Return_Correct_Length_Of_StudentName(string studentName, string expectedLength) - { - // Create Model - Student student = new Student - { - StudentName = studentName - }; - - // Template - var template = new ObjectSemanticsTemplate - { - FileContents = @"{{ StudentName:length }}" - }; - - string generatedTemplate = template.Map(student); - - Assert.Equal(expectedLength, generatedTemplate, ignoreCase: false, ignoreLineEndingDifferences: true, ignoreWhiteSpaceDifferences: true); - } - } -} diff --git a/ObjectSemantics.NET/Algorithim/ExtractedObjProperty.cs b/ObjectSemantics.NET/Algorithim/ExtractedObjProperty.cs deleted file mode 100644 index b8f48b1..0000000 --- a/ObjectSemantics.NET/Algorithim/ExtractedObjProperty.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections; - -public class ExtractedObjProperty -{ - public Type Type { get; set; } - public string Name { get; set; } - public object OriginalValue { get; set; } - public string StringFormatted { get { return string.Format("{0}", OriginalValue); } } - public bool IsEnumerableObject - { - get { return typeof(IEnumerable).IsAssignableFrom(Type) && Type != typeof(string); } - } - public bool IsClassObject - { - get { return Type.IsClass && Type != typeof(string); } - } -} \ No newline at end of file diff --git a/ObjectSemantics.NET/Algorithim/GavinsAlgorithim.cs b/ObjectSemantics.NET/Algorithim/GavinsAlgorithim.cs deleted file mode 100644 index e985311..0000000 --- a/ObjectSemantics.NET/Algorithim/GavinsAlgorithim.cs +++ /dev/null @@ -1,241 +0,0 @@ -using ObjectSemantics.NET; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; - -public static class GavinsAlgorithim -{ - - public static string GenerateFromTemplate(T record, TemplatedContent clonedTemplate, List parameterKeyValues = null, TemplateMapperOptions options = null) where T : new() - { - //Get Object's Properties - List objProperties = GetObjectProperties(record, parameterKeyValues); - - #region Replace If Conditions - foreach (ReplaceIfOperationCode ifCondition in clonedTemplate.ReplaceIfConditionCodes) - { - ExtractedObjProperty property = objProperties.FirstOrDefault(x => x.Name.ToUpper().Equals(ifCondition.IfPropertyName.ToUpper())); - if (property != null) - { - if (property.IsPropertyValueConditionPassed(ifCondition.IfOperationValue, ifCondition.IfOperationType)) - { - //Condition Passed - TemplatedContent templatedIfContent = GenerateTemplateFromFileContents(ifCondition.IfOperationTrueTemplate, options); - string templatedIfContentMapped = GenerateFromTemplate(record, templatedIfContent, parameterKeyValues, options); - clonedTemplate.Template = clonedTemplate.Template.ReplaceFirstOccurrence(ifCondition.ReplaceRef, templatedIfContentMapped); - } - else if (!string.IsNullOrEmpty(ifCondition.IfOperationFalseTemplate)) - { - //If Else Condition Block - TemplatedContent templatedIfContent = GenerateTemplateFromFileContents(ifCondition.IfOperationFalseTemplate, options); - string templatedIfElseContentMapped = GenerateFromTemplate(record, templatedIfContent, parameterKeyValues, options); - clonedTemplate.Template = clonedTemplate.Template.ReplaceFirstOccurrence(ifCondition.ReplaceRef, templatedIfElseContentMapped); - } - else - clonedTemplate.Template = clonedTemplate.Template.ReplaceFirstOccurrence(ifCondition.ReplaceRef, string.Empty); - } - else - clonedTemplate.Template = clonedTemplate.Template.ReplaceFirstOccurrence(ifCondition.ReplaceRef, $"[IF-CONDITION EXCEPTION]: unrecognized property: [{ifCondition.IfPropertyName}]"); - } - #endregion - - #region Replace Obj Loop Attributes - foreach (ReplaceObjLoopCode objLoop in clonedTemplate.ReplaceObjLoopCodes) - { - //First Loop lets look at its target property Object - ExtractedObjProperty targetObj = objProperties.Where(x => x.IsEnumerableObject).FirstOrDefault(x => x.Name.ToUpper().Equals(objLoop.TargetObjectName.ToUpper())); - //Since this Object is of Type Enumerable - if (targetObj != null && targetObj.OriginalValue != null) - { - //Loop all data records inside target Property - string rowTemplate = objLoop.ObjLoopTemplate; - StringBuilder rowContentTemplater = new StringBuilder(); - foreach (object loopRow in (IEnumerable)targetObj.OriginalValue) - { - List rowRecordValues = GetObjPropertiesFromUnknown(loopRow); - //Loop Through class template Loops - string activeRow = rowTemplate; - foreach (ReplaceCode objLoopCode in objLoop.ReplaceObjCodes) - { - ExtractedObjProperty objProperty = rowRecordValues.FirstOrDefault(x => x.Name.ToUpper().Equals(objLoopCode.TargetPropertyName.ToUpper())); - if (objProperty != null) - activeRow = activeRow.ReplaceFirstOccurrence(objLoopCode.ReplaceRef, objProperty.GetPropertyDisplayString(objLoopCode.FormattingCommand, options)); - else if (objLoopCode.TargetPropertyName.Equals(".")) - { - activeRow = activeRow.ReplaceFirstOccurrence(objLoopCode.ReplaceRef, new ExtractedObjProperty - { - Name = objLoopCode.TargetPropertyName, - Type = loopRow.GetType(), - OriginalValue = loopRow - }.GetPropertyDisplayString(objLoopCode.FormattingCommand, options)); - } - else - activeRow = activeRow.ReplaceFirstOccurrence(objLoopCode.ReplaceRef, objLoopCode.ReplaceCommand); - } - //Append Record row - rowContentTemplater.Append(activeRow); - } - objLoop.ObjLoopTemplate = rowContentTemplater.ToString().RemoveLastInstanceOfString('\r', '\n'); //Assign Auto Generated - //Replace the main Loop area - clonedTemplate.Template = clonedTemplate.Template.ReplaceFirstOccurrence(objLoop.ReplaceRef, objLoop.ObjLoopTemplate); - } - else - clonedTemplate.Template = clonedTemplate.Template.ReplaceFirstOccurrence(objLoop.ReplaceRef, string.Empty); - - } - #endregion - - #region Replace Direct Target Attributes - foreach (ReplaceCode replaceCode in clonedTemplate.ReplaceCodes) - { - ExtractedObjProperty property = objProperties.FirstOrDefault(x => x.Name.ToUpper().Equals(replaceCode.TargetPropertyName.ToUpper())); - if (property != null) - clonedTemplate.Template = clonedTemplate.Template.ReplaceFirstOccurrence(replaceCode.ReplaceRef, property.GetPropertyDisplayString(replaceCode.FormattingCommand, options)); - else - clonedTemplate.Template = clonedTemplate.Template.ReplaceFirstOccurrence(replaceCode.ReplaceRef, @"{{ ##command## }}".Replace("##command##", replaceCode.ReplaceCommand)); - } - #endregion - return clonedTemplate.Template; - } - - internal static TemplatedContent GenerateTemplateFromFileContents(string fileContent, TemplateMapperOptions options) - { - TemplatedContent templatedContent = new TemplatedContent { Template = fileContent }; - long _replaceKey = 0; - #region If Condition - //Matches ==, !=, >, >=, <, and <= - string _matchWithElseIf = @"{{\s*#if\s*\(\s*(?\w+)\s*(?==|!=|>=|<=|>|<)\s*(?[^)]+)\s*\)\s*}}(?[\s\S]*?)(?:{{\s*#else\s*}}(?[\s\S]*?))?{{\s*#endif\s*}}"; - while (Regex.IsMatch(templatedContent.Template, _matchWithElseIf, RegexOptions.IgnoreCase)) - { - templatedContent.Template = Regex.Replace(templatedContent.Template, _matchWithElseIf, match => - { - _replaceKey++; - string _replaceCode = string.Format("RIB_{0}", _replaceKey); - templatedContent.ReplaceIfConditionCodes.Add(new ReplaceIfOperationCode - { - ReplaceRef = _replaceCode, - IfPropertyName = match.Groups[1].Value?.ToString().Trim().ToLower().Replace(" ", string.Empty), - IfOperationType = match.Groups[2].Value?.ToString().Trim().ToLower().Replace(" ", string.Empty), - IfOperationValue = match.Groups[3].Value?.ToString().Trim(), - IfOperationTrueTemplate = match.Groups[4].Value?.ToString(), - IfOperationFalseTemplate = (match.Groups.Count >= 6) ? match.Groups[5].Value?.ToString() : string.Empty - }); - // Return Replacement - return _replaceCode; - }, RegexOptions.IgnoreCase); - } - #endregion - - #region Generate Obj Looop - string _matchLoopBlock = @"{{\s*#\s*foreach\s*\(\s*(\w+)\s*\)\s*\}\}([\s\S]*?)\{\{\s*#\s*endforeach\s*}}"; - while (Regex.IsMatch(templatedContent.Template, _matchLoopBlock, RegexOptions.IgnoreCase)) - { - templatedContent.Template = Regex.Replace(templatedContent.Template, _matchLoopBlock, match => - { - _replaceKey++; - string _replaceCode = string.Format("RLB_{0}", _replaceKey); - ReplaceObjLoopCode objLoopCode = new ReplaceObjLoopCode - { - ReplaceRef = _replaceCode, - TargetObjectName = match.Groups[1].Value?.ToString().Trim().ToLower().Replace(" ", string.Empty) - }; - //Determine Forloop Elements - string loopBlock = match.Groups[2].Value?.ToString() ?? string.Empty; - string loopBlockRegex = @"{{(.+?)}}"; - while (Regex.IsMatch(loopBlock, loopBlockRegex, RegexOptions.IgnoreCase)) - { - loopBlock = Regex.Replace(loopBlock, loopBlockRegex, loopBlockMatch => - { - _replaceKey++; - string _replaceLoopBlockCode = string.Format("RLBR_{0}", _replaceKey); - objLoopCode.ReplaceObjCodes.Add(new ReplaceCode - { - ReplaceCommand = loopBlockMatch.Groups[1].Value?.Trim(), - ReplaceRef = _replaceLoopBlockCode - }); - return _replaceLoopBlockCode; - }, RegexOptions.IgnoreCase); - } - //#Just before return - objLoopCode.ObjLoopTemplate = loopBlock; - templatedContent.ReplaceObjLoopCodes.Add(objLoopCode); - // Return Replacement - return _replaceCode; - }, RegexOptions.IgnoreCase); - } - - #endregion - - #region Generate direct targets - string _paramRegex = @"{{(.+?)}}"; - while (Regex.IsMatch(templatedContent.Template, _paramRegex, RegexOptions.IgnoreCase)) - { - templatedContent.Template = Regex.Replace(templatedContent.Template, _paramRegex, propertyMatch => - { - _replaceKey++; - string _replaceCode = string.Format("RP_{0}", _replaceKey); - templatedContent.ReplaceCodes.Add(new ReplaceCode - { - ReplaceCommand = propertyMatch.Groups[1].Value?.Trim(), - ReplaceRef = _replaceCode - }); - return _replaceCode; - }, RegexOptions.IgnoreCase); - } - - #endregion - - return templatedContent; - } - - - private static List GetObjectProperties(T value, List parameters = null) where T : new() - { - List extractedObjProperties = new List(); - foreach (PropertyInfo prop in typeof(T).GetProperties()) - { - try - { - extractedObjProperties.Add(new ExtractedObjProperty - { - Type = prop.PropertyType, - Name = prop.Name, - OriginalValue = value == null ? null : prop.GetValue(value) - }); - } - catch { } - } - //Append Parameters - if (parameters != null && parameters.Count != 0) - foreach (var param in parameters) - extractedObjProperties.Add(new ExtractedObjProperty { Type = param.Type, Name = param.Key, OriginalValue = param.Value }); - return extractedObjProperties; - } - private static List GetObjPropertiesFromUnknown(object value) - { - List list = new List(); - if (value == null) - return list; - Type myType = value.GetType(); - IList props = new List(myType.GetProperties()); - //loop (Skip properties that require index parameters (like Chars in string)) - foreach (PropertyInfo prop in props.Where(x => x.GetIndexParameters().Length == 0)) - { - try - { - list.Add(new ExtractedObjProperty - { - Type = prop.PropertyType, - Name = prop.Name, - OriginalValue = value == null ? null : prop.GetValue(value) - }); - } - catch { } - } - return list; - } -} \ No newline at end of file diff --git a/ObjectSemantics.NET/Algorithim/ReplaceCode.cs b/ObjectSemantics.NET/Algorithim/ReplaceCode.cs deleted file mode 100644 index 5687fb4..0000000 --- a/ObjectSemantics.NET/Algorithim/ReplaceCode.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Linq; -using System.Text.RegularExpressions; - -public class ReplaceCode -{ - public string ReplaceRef { get; set; } - public string ReplaceCommand { get; set; } - public string TargetPropertyName - { - get - { - return ReplaceCommand?.Trim().Split(new string[] { ":" }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); - } - } - public string FormattingCommand - { - get - { - Match hasFormatting = Regex.Match(ReplaceCommand, "##command##:(.+)".Replace("##command##", TargetPropertyName), RegexOptions.IgnoreCase); - if (hasFormatting.Success) - return hasFormatting.Groups[1].Value; - return string.Empty; - } - } -} diff --git a/ObjectSemantics.NET/Algorithim/ReplaceIfOperationCode.cs b/ObjectSemantics.NET/Algorithim/ReplaceIfOperationCode.cs deleted file mode 100644 index ea567bb..0000000 --- a/ObjectSemantics.NET/Algorithim/ReplaceIfOperationCode.cs +++ /dev/null @@ -1,10 +0,0 @@ -public class ReplaceIfOperationCode -{ - public string IfPropertyName { get; set; } - public string IfOperationType { get; set; } - public string IfOperationValue { get; set; } - public string ReplaceRef { get; set; } - public string IfOperationTrueTemplate { get; set; } = string.Empty; - public string IfOperationFalseTemplate { get; set; } = string.Empty; -} - diff --git a/ObjectSemantics.NET/Algorithim/ReplaceObjLoopCode.cs b/ObjectSemantics.NET/Algorithim/ReplaceObjLoopCode.cs deleted file mode 100644 index 90b58f0..0000000 --- a/ObjectSemantics.NET/Algorithim/ReplaceObjLoopCode.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -public class ReplaceObjLoopCode -{ - public string ReplaceRef { get; set; } - public string TargetObjectName { get; set; } - public string ObjLoopTemplate { get; set; } - public List ReplaceObjCodes { get; set; } = new List(); -} diff --git a/ObjectSemantics.NET/Algorithim/TemplatedContent.cs b/ObjectSemantics.NET/Algorithim/TemplatedContent.cs deleted file mode 100644 index c016247..0000000 --- a/ObjectSemantics.NET/Algorithim/TemplatedContent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -public class TemplatedContent -{ - public string Template { get; set; } - public List ReplaceObjLoopCodes { get; set; } = new List(); - public List ReplaceCodes { get; set; } = new List(); - public List ReplaceIfConditionCodes { get; set; } = new List(); -} diff --git a/ObjectSemantics.NET/Engine/EngineAlgorithim.cs b/ObjectSemantics.NET/Engine/EngineAlgorithim.cs new file mode 100644 index 0000000..0b75f4e --- /dev/null +++ b/ObjectSemantics.NET/Engine/EngineAlgorithim.cs @@ -0,0 +1,241 @@ +using ObjectSemantics.NET.Engine.Extensions; +using ObjectSemantics.NET.Engine.Models; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; + +namespace ObjectSemantics.NET.Engine +{ + 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 LoopBlockRegex = new Regex(@"{{\s*#foreach\s*\(\s*(\w+)\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() + { + List objProperties = GetObjectProperties(record, parameterKeyValues); + Dictionary propMap = objProperties.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); + + StringBuilder result = new StringBuilder(template.Template ?? string.Empty, (template.Template?.Length ?? 0) * 2); + + // ---- IF Conditions ---- + foreach (ReplaceIfOperationCode ifCondition in template.ReplaceIfConditionCodes) + { + if (!propMap.TryGetValue(ifCondition.IfPropertyName, out ExtractedObjProperty property)) + { + result.Replace(ifCondition.ReplaceRef, "[IF-CONDITION EXCEPTION]: unrecognized property: [" + ifCondition.IfPropertyName + "]"); + continue; + } + + bool conditionPassed = property.IsPropertyValueConditionPassed(ifCondition.IfOperationValue, ifCondition.IfOperationType); + string replacement; + + if (conditionPassed) + { + EngineRunnerTemplate trueContent = GenerateRunnerTemplate(ifCondition.IfOperationTrueTemplate); + replacement = GenerateFromTemplate(record, trueContent, parameterKeyValues, options); + } + else if (!string.IsNullOrEmpty(ifCondition.IfOperationFalseTemplate)) + { + EngineRunnerTemplate falseContent = GenerateRunnerTemplate(ifCondition.IfOperationFalseTemplate); + replacement = GenerateFromTemplate(record, falseContent, parameterKeyValues, options); + } + else + { + replacement = string.Empty; + } + + result.Replace(ifCondition.ReplaceRef, replacement); + } + + // ---- Object Loops ---- + foreach (ReplaceObjLoopCode objLoop in template.ReplaceObjLoopCodes) + { + if (!propMap.TryGetValue(objLoop.TargetObjectName, out ExtractedObjProperty targetObj) || !(targetObj.OriginalValue is IEnumerable enumerable)) + { + result.Replace(objLoop.ReplaceRef, string.Empty); + continue; + } + + StringBuilder loopResult = new StringBuilder(); + + foreach (object row in enumerable) + { + List rowProps = GetObjPropertiesFromUnknown(row); + Dictionary rowMap = rowProps.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); + + StringBuilder activeRow = new StringBuilder(objLoop.ObjLoopTemplate); + + foreach (ReplaceCode objLoopCode in objLoop.ReplaceObjCodes) + { + string propName = objLoopCode.GetTargetPropertyName(); + if (propName == ".") + { + ExtractedObjProperty tempProp = new ExtractedObjProperty + { + Name = ".", + Type = row.GetType(), + OriginalValue = row + }; + activeRow.Replace(objLoopCode.ReplaceRef, tempProp.GetPropertyDisplayString(objLoopCode.GetFormattingCommand(), options)); + } + else + { + if (rowMap.TryGetValue(propName, out ExtractedObjProperty p)) + activeRow.Replace(objLoopCode.ReplaceRef, p.GetPropertyDisplayString(objLoopCode.GetFormattingCommand(), options)); + else + activeRow.Replace(objLoopCode.ReplaceRef, objLoopCode.ReplaceCommand); + } + } + + loopResult.Append(activeRow); + } + + result.Replace(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)); + else + result.Replace(replaceCode.ReplaceRef, "{{ " + replaceCode.ReplaceCommand + " }}"); + } + return result.ToString(); + } + + internal static EngineRunnerTemplate GenerateRunnerTemplate(string fileContent) + { + EngineRunnerTemplate templatedContent = new EngineRunnerTemplate { Template = fileContent }; + long key = 0; + + // ---- IF Conditions ---- + templatedContent.Template = IfConditionRegex.Replace(templatedContent.Template, m => + { + key++; + string refKey = "RIB_" + key; + templatedContent.ReplaceIfConditionCodes.Add(new ReplaceIfOperationCode + { + ReplaceRef = refKey, + IfPropertyName = m.Groups["param"].Value, + IfOperationType = m.Groups["operator"].Value, + IfOperationValue = m.Groups["value"].Value, + IfOperationTrueTemplate = m.Groups["code"].Value, + IfOperationFalseTemplate = m.Groups["else"].Success ? m.Groups["else"].Value : string.Empty + }); + return refKey; + }); + + // ---- FOREACH Loops ---- + templatedContent.Template = LoopBlockRegex.Replace(templatedContent.Template, m => + { + key++; + string refKey = "RLB_" + key; + ReplaceObjLoopCode objLoop = new ReplaceObjLoopCode + { + ReplaceRef = refKey, + TargetObjectName = m.Groups[1].Value?.Trim() ?? string.Empty + }; + + string loopBlock = m.Groups[2].Value; + loopBlock = DirectParamRegex.Replace(loopBlock, pm => + { + key++; + string loopRef = "RLBR_" + key; + objLoop.ReplaceObjCodes.Add(new ReplaceCode + { + ReplaceCommand = pm.Groups[1].Value.Trim(), + ReplaceRef = loopRef + }); + return loopRef; + }); + + objLoop.ObjLoopTemplate = loopBlock; + templatedContent.ReplaceObjLoopCodes.Add(objLoop); + return refKey; + }); + + // ---- Direct Parameters ---- + templatedContent.Template = DirectParamRegex.Replace(templatedContent.Template, m => + { + key++; + string refKey = "RP_" + key; + templatedContent.ReplaceCodes.Add(new ReplaceCode + { + ReplaceCommand = m.Groups[1].Value.Trim(), + ReplaceRef = refKey + }); + return refKey; + }); + + return templatedContent; + } + + private static List GetObjectProperties(T value, Dictionary parameters) where T : new() + { + Type type = typeof(T); + if (!PropertyCache.TryGetValue(type, out PropertyInfo[] props)) + { + props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + PropertyCache[type] = props; + } + + List result = new List(props.Length + (parameters != null ? parameters.Count : 0)); + + foreach (PropertyInfo prop in props) + { + result.Add(new ExtractedObjProperty + { + Type = prop.PropertyType, + Name = prop.Name, + OriginalValue = value == null ? null : prop.GetValue(value, null) + }); + } + + if (parameters != null) + { + foreach (KeyValuePair p in parameters) + result.Add(new ExtractedObjProperty { Type = p.Value.GetType(), Name = p.Key, OriginalValue = p.Value }); + } + + return result; + } + + private static List GetObjPropertiesFromUnknown(object value) + { + if (value == null) return new List(); + + Type type = value.GetType(); + if (!PropertyCache.TryGetValue(type, out PropertyInfo[] props)) + { + props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.GetIndexParameters().Length == 0).ToArray(); + PropertyCache[type] = props; + } + + List result = new List(props.Length); + foreach (PropertyInfo prop in props) + { + result.Add(new ExtractedObjProperty + { + Type = prop.PropertyType, + Name = prop.Name, + OriginalValue = prop.GetValue(value, null) + }); + } + + return result; + } + } +} \ No newline at end of file diff --git a/ObjectSemantics.NET/Engine/Extensions/ExtractedObjPropertyExtensions.cs b/ObjectSemantics.NET/Engine/Extensions/ExtractedObjPropertyExtensions.cs new file mode 100644 index 0000000..e596c41 --- /dev/null +++ b/ObjectSemantics.NET/Engine/Extensions/ExtractedObjPropertyExtensions.cs @@ -0,0 +1,172 @@ +using ObjectSemantics.NET.Engine.Models; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Security; + +namespace ObjectSemantics.NET.Engine.Extensions +{ + internal static class ExtractedObjPropertyExtensions + { + public static string GetPropertyDisplayString(this ExtractedObjProperty p, string stringFormatting, TemplateMapperOptions options) + { + if (p == null) + return string.Empty; + + string formatted = p.GetAppliedPropertyFormatting(stringFormatting); + if (options?.XmlCharEscaping == true && !string.IsNullOrEmpty(formatted)) + formatted = SecurityElement.Escape(formatted); + + return formatted; + } + + private static string GetAppliedPropertyFormatting(this ExtractedObjProperty p, string customFormat) + { + if (string.IsNullOrEmpty(customFormat) || p.OriginalValue == null) + return p.StringFormatted; + + Type t = p.Type; + string val = p.StringFormatted; + + // avoid repeated ToLower calls + string fmt = customFormat.Trim(); + string fmtLower = fmt.ToLowerInvariant(); + + // handle numeric and datetime formats first + try + { + if (t == typeof(int) || t == typeof(int?)) + return int.Parse(val, CultureInfo.InvariantCulture).ToString(fmt, CultureInfo.InvariantCulture); + if (t == typeof(double) || t == typeof(double?)) + return double.Parse(val, CultureInfo.InvariantCulture).ToString(fmt, CultureInfo.InvariantCulture); + if (t == typeof(long) || t == typeof(long?)) + return long.Parse(val, CultureInfo.InvariantCulture).ToString(fmt, CultureInfo.InvariantCulture); + if (t == typeof(float) || t == typeof(float?)) + return float.Parse(val, CultureInfo.InvariantCulture).ToString(fmt, CultureInfo.InvariantCulture); + if (t == typeof(decimal) || t == typeof(decimal?)) + return decimal.Parse(val, CultureInfo.InvariantCulture).ToString(fmt, CultureInfo.InvariantCulture); + if (t == typeof(DateTime) || t == typeof(DateTime?)) + return DateTime.Parse(val, CultureInfo.InvariantCulture).ToString(fmt, CultureInfo.InvariantCulture); + } + catch + { + // fall through if invalid format + } + + // custom string-based formats (single switch to avoid multiple ToLower() checks) + switch (fmtLower) + { + case "uppercase": return val?.ToUpperInvariant(); + case "lowercase": return val?.ToLowerInvariant(); + case "tomd5": return val?.ToMD5String(); + case "tobase64": return val?.ToBase64String(); + case "frombase64": return val?.FromBase64String(); + case "length": return val?.Length.ToString(CultureInfo.InvariantCulture); + case "titlecase": return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(val?.ToLowerInvariant() ?? string.Empty); + default: return val; + } + } + + private static T GetConvertibleValue(string value) where T : IConvertible + { + if (string.IsNullOrWhiteSpace(value) || string.Equals(value.Trim(), "null", StringComparison.OrdinalIgnoreCase)) + return default; + + return (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture); + } + + public static bool IsPropertyValueConditionPassed(this ExtractedObjProperty property, string valueComparer, string criteria) + { + if (property == null) + return false; + + try + { + Type t = property.Type; + object original = property.OriginalValue; + string crit = criteria?.Trim() ?? string.Empty; + + if (t == typeof(string)) + { + string v1 = (original?.ToString() ?? string.Empty).Trim().ToLowerInvariant(); + string v2 = (GetConvertibleValue(valueComparer) ?? string.Empty).Trim().ToLowerInvariant(); + switch (crit) + { + case "==": + return v1 == v2; + case "!=": + return v1 != v2; + default: + return string.Equals(v1, v2, StringComparison.OrdinalIgnoreCase); + } + } + + if (t == typeof(int) || t == typeof(double) || t == typeof(long) || + t == typeof(float) || t == typeof(decimal)) + { + double v1 = Convert.ToDouble(original ?? 0, CultureInfo.InvariantCulture); + double v2 = Convert.ToDouble(GetConvertibleValue(valueComparer), CultureInfo.InvariantCulture); + + switch (crit) + { + case "==": return v1 == v2; + case "!=": return v1 != v2; + case ">": return v1 > v2; + case ">=": return v1 >= v2; + case "<": return v1 < v2; + case "<=": return v1 <= v2; + default: return false; + } + } + + if (t == typeof(DateTime)) + { + DateTime v1 = Convert.ToDateTime(original, CultureInfo.InvariantCulture); + DateTime v2 = Convert.ToDateTime(GetConvertibleValue(valueComparer), CultureInfo.InvariantCulture); + + switch (crit) + { + case "==": return v1 == v2; + case "!=": return v1 != v2; + case ">": return v1 > v2; + case ">=": return v1 >= v2; + case "<": return v1 < v2; + case "<=": return v1 <= v2; + default: return false; + } + } + + if (t == typeof(bool)) + { + bool v1 = Convert.ToBoolean(original, CultureInfo.InvariantCulture); + bool v2 = Convert.ToBoolean(GetConvertibleValue(valueComparer)); + return crit == "==" ? v1 == v2 : crit == "!=" && v1 != v2; + } + + if (property.IsEnumerableObject) + { + int v1 = original is IEnumerable enumerable ? enumerable.Count() : 0; + double v2 = Convert.ToDouble(GetConvertibleValue(valueComparer), CultureInfo.InvariantCulture); + + switch (crit) + { + case "==": return v1 == v2; + case "!=": return v1 != v2; + case ">": return v1 > v2; + case ">=": return v1 >= v2; + case "<": return v1 < v2; + case "<=": return v1 <= v2; + default: return false; + } + } + + return false; + } + catch + { + return false; + } + } + } +} diff --git a/ObjectSemantics.NET/Engine/Extensions/ReplaceCodeExtensions.cs b/ObjectSemantics.NET/Engine/Extensions/ReplaceCodeExtensions.cs new file mode 100644 index 0000000..9595e26 --- /dev/null +++ b/ObjectSemantics.NET/Engine/Extensions/ReplaceCodeExtensions.cs @@ -0,0 +1,36 @@ +using ObjectSemantics.NET.Engine.Models; + +namespace ObjectSemantics.NET.Engine.Extensions +{ + internal static class ReplaceCodeExtensions + { + public static string GetTargetPropertyName(this ReplaceCode code) + { + if (code == null) return string.Empty; + + if (string.IsNullOrEmpty(code.ReplaceCommand)) + return string.Empty; + + int colonIndex = code.ReplaceCommand.IndexOf(':'); + return colonIndex > 0 ? code.ReplaceCommand.Substring(0, colonIndex).Trim() : code.ReplaceCommand.Trim(); + } + + public static string GetFormattingCommand(this ReplaceCode code) + { + if (code == null) return string.Empty; + + if (string.IsNullOrEmpty(code.ReplaceCommand)) + return string.Empty; + + // Find the colon separating the target and the formatting command + int colonIndex = code.ReplaceCommand.IndexOf(':'); + if (colonIndex < 0 || colonIndex >= code.ReplaceCommand.Length - 1) + return string.Empty; + + // Extract everything after the first colon + string afterColon = code.ReplaceCommand.Substring(colonIndex + 1).Trim(); + + return afterColon; + } + } +} diff --git a/ObjectSemantics.NET/Extensions/StringExtensions.cs b/ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs similarity index 96% rename from ObjectSemantics.NET/Extensions/StringExtensions.cs rename to ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs index b028e36..a0d49eb 100644 --- a/ObjectSemantics.NET/Extensions/StringExtensions.cs +++ b/ObjectSemantics.NET/Engine/Extensions/StringExtensions.cs @@ -1,9 +1,9 @@ using System; using System.Text; -namespace ObjectSemantics.NET +namespace ObjectSemantics.NET.Engine.Extensions { - public static class StringExtensions + internal static class StringExtensions { public static string ReplaceFirstOccurrence(this string text, string search, string replace) { @@ -17,6 +17,7 @@ public static string RemoveLastInstanceOfString(this string value, string remove 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) diff --git a/ObjectSemantics.NET/Engine/Models/EngineRunnerTemplate.cs b/ObjectSemantics.NET/Engine/Models/EngineRunnerTemplate.cs new file mode 100644 index 0000000..6f3a1a2 --- /dev/null +++ b/ObjectSemantics.NET/Engine/Models/EngineRunnerTemplate.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace ObjectSemantics.NET.Engine.Models +{ + internal class EngineRunnerTemplate + { + public string Template { get; set; } + public List ReplaceObjLoopCodes { get; set; } = new List(); + public List ReplaceCodes { get; set; } = new List(); + public List ReplaceIfConditionCodes { get; set; } = new List(); + } +} diff --git a/ObjectSemantics.NET/Engine/Models/ExtractedObjProperty.cs b/ObjectSemantics.NET/Engine/Models/ExtractedObjProperty.cs new file mode 100644 index 0000000..126a677 --- /dev/null +++ b/ObjectSemantics.NET/Engine/Models/ExtractedObjProperty.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections; + +namespace ObjectSemantics.NET.Engine.Models +{ + internal class ExtractedObjProperty + { + public Type Type { get; set; } + public string Name { get; set; } + public object OriginalValue { get; set; } + public string StringFormatted { get { return string.Format("{0}", OriginalValue); } } + public bool IsEnumerableObject + { + get { return typeof(IEnumerable).IsAssignableFrom(Type) && Type != typeof(string); } + } + public bool IsClassObject + { + get { return Type.IsClass && Type != typeof(string); } + } + } +} \ No newline at end of file diff --git a/ObjectSemantics.NET/Engine/Models/ReplaceCode.cs b/ObjectSemantics.NET/Engine/Models/ReplaceCode.cs new file mode 100644 index 0000000..7ba59df --- /dev/null +++ b/ObjectSemantics.NET/Engine/Models/ReplaceCode.cs @@ -0,0 +1,8 @@ +namespace ObjectSemantics.NET.Engine.Models +{ + internal class ReplaceCode + { + public string ReplaceRef { get; set; } + public string ReplaceCommand { get; set; } + } +} \ No newline at end of file diff --git a/ObjectSemantics.NET/Engine/Models/ReplaceIfOperationCode.cs b/ObjectSemantics.NET/Engine/Models/ReplaceIfOperationCode.cs new file mode 100644 index 0000000..59a8949 --- /dev/null +++ b/ObjectSemantics.NET/Engine/Models/ReplaceIfOperationCode.cs @@ -0,0 +1,12 @@ +namespace ObjectSemantics.NET.Engine.Models +{ + internal class ReplaceIfOperationCode + { + public string IfPropertyName { get; set; } + public string IfOperationType { get; set; } + public string IfOperationValue { get; set; } + public string ReplaceRef { get; set; } + public string IfOperationTrueTemplate { get; set; } = string.Empty; + public string IfOperationFalseTemplate { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/ObjectSemantics.NET/Engine/Models/ReplaceObjLoopCode.cs b/ObjectSemantics.NET/Engine/Models/ReplaceObjLoopCode.cs new file mode 100644 index 0000000..15b15d4 --- /dev/null +++ b/ObjectSemantics.NET/Engine/Models/ReplaceObjLoopCode.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace ObjectSemantics.NET.Engine.Models +{ + internal class ReplaceObjLoopCode + { + public string ReplaceRef { get; set; } + public string TargetObjectName { get; set; } + public string ObjLoopTemplate { get; set; } + public List ReplaceObjCodes { get; set; } = new List(); + } +} diff --git a/ObjectSemantics.NET/Extensions/ExtractedObjPropertyExtensions.cs b/ObjectSemantics.NET/Extensions/ExtractedObjPropertyExtensions.cs deleted file mode 100644 index 4ffab68..0000000 --- a/ObjectSemantics.NET/Extensions/ExtractedObjPropertyExtensions.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Security; - -namespace ObjectSemantics.NET -{ - public static class ExtractedObjPropertyExtensions - { - public static string GetPropertyDisplayString(this ExtractedObjProperty p, string stringFormatting, TemplateMapperOptions templateMapperOptions) - { - string formattedPropertyString = GetAppliedPropertyFormatting(p, stringFormatting); - //Apply Options to Property value string - if (templateMapperOptions == null) return formattedPropertyString; - if (templateMapperOptions.XmlCharEscaping) - formattedPropertyString = SecurityElement.Escape(formattedPropertyString); - return formattedPropertyString; - } - private static string GetAppliedPropertyFormatting(this ExtractedObjProperty p, string customFormattingValue) - { - if (string.IsNullOrWhiteSpace(customFormattingValue) || p.OriginalValue == null) - return p.StringFormatted; - if (p.Type.Equals(typeof(int)) || p.Type.Equals(typeof(int?))) - return int.Parse(p.StringFormatted).ToString(customFormattingValue); - else if (p.Type.Equals(typeof(double)) || p.Type.Equals(typeof(double?))) - return double.Parse(p.StringFormatted).ToString(customFormattingValue); - else if (p.Type.Equals(typeof(long)) || p.Type.Equals(typeof(long?))) - return long.Parse(p.StringFormatted).ToString(customFormattingValue); - else if (p.Type.Equals(typeof(float)) || p.Type.Equals(typeof(float?))) - return float.Parse(p.StringFormatted).ToString(customFormattingValue); - else if (p.Type.Equals(typeof(decimal)) || p.Type.Equals(typeof(decimal?))) - return decimal.Parse(p.StringFormatted).ToString(customFormattingValue); - else if (p.Type.Equals(typeof(DateTime)) || p.Type.Equals(typeof(DateTime?))) - return DateTime.Parse(p.StringFormatted).ToString(customFormattingValue); - //Custom Formats - else if (customFormattingValue.ToLower().Equals("uppercase")) - return p.StringFormatted?.ToUpper(); - else if (customFormattingValue.ToLower().Equals("lowercase")) - return p.StringFormatted?.ToLower(); - else if (customFormattingValue.ToLower().Equals("tomd5")) - return p.StringFormatted.ToMD5String(); - else if (customFormattingValue.ToLower().Equals("tobase64")) - return p.StringFormatted.ToBase64String(); - else if (customFormattingValue.ToLower().Equals("frombase64")) - return p.StringFormatted.FromBase64String(); - else if (customFormattingValue.ToLower().Equals("length")) - return p.StringFormatted?.Length.ToString(); - else if (customFormattingValue.ToLower().Equals("titlecase")) - return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(p.StringFormatted?.ToLower() ?? string.Empty); - else - return p.StringFormatted; - } - - private static T GetConvertibleValue(string value) where T : IConvertible - { - return (string.IsNullOrEmpty(value) || value?.ToLower()?.Trim() == "null") ? default : (T)Convert.ChangeType(value, typeof(T)); - } - public static bool IsPropertyValueConditionPassed(this ExtractedObjProperty property, string valueComparer, string criteria) - { - try - { - if (property == null) return false; - else if (property.Type == typeof(string)) - { - string v1 = property.OriginalValue?.ToString()?.Trim().ToLower() ?? string.Empty; - string v2 = GetConvertibleValue(valueComparer)?.Trim().ToLower() ?? string.Empty; - switch (criteria) - { - case "==": return v1 == v2; - case "!=": return v1 != v2; - default: - return string.Compare(v1, v2, true) == 0; - } - } - else if (property.Type == typeof(int) || property.Type == typeof(double) || property.Type == typeof(long) || property.Type == typeof(float) || property.Type == typeof(decimal)) - { - double v1 = Convert.ToDouble(property.OriginalValue ?? "0"); - double v2 = Convert.ToDouble(GetConvertibleValue(valueComparer)); - switch (criteria) - { - case "==": return v1 == v2; - case "!=": return v1 != v2; - case ">": return v1 > v2; - case ">=": return v1 >= v2; - case "<": return v1 < v2; - case "<=": return v1 <= v2; - default: return false; - } - } - else if (property.Type == typeof(DateTime)) - { - DateTime v1 = Convert.ToDateTime(property.OriginalValue); - DateTime v2 = Convert.ToDateTime(GetConvertibleValue(valueComparer)); - switch (criteria) - { - case "==": return v1 == v2; - case "!=": return v1 != v2; - case ">": return v1 > v2; - case ">=": return v1 >= v2; - case "<": return v1 < v2; - case "<=": return v1 <= v2; - default: return false; - } - } - else if (property.Type == typeof(bool)) - { - bool v1 = Convert.ToBoolean(property.OriginalValue); - bool v2 = Convert.ToBoolean(GetConvertibleValue(valueComparer)); - switch (criteria) - { - case "==": return v1 == v2; - case "!=": return v1 != v2; - default: return false; - } - } - else if (property.IsEnumerableObject) - { - int v1 = (property.OriginalValue == null) ? 0 : ((IEnumerable)property.OriginalValue).Count(); - double v2 = Convert.ToDouble(GetConvertibleValue(valueComparer)); - switch (criteria) - { - case "==": return v1 == v2; - case "!=": return v1 != v2; - case ">": return v1 > v2; - case ">=": return v1 >= v2; - case "<": return v1 < v2; - case "<=": return v1 <= v2; - default: return false; - } - } - else - return false; - } - catch { return false; } - } - } -} diff --git a/ObjectSemantics.NET/ObjectSemantics.NET.csproj b/ObjectSemantics.NET/ObjectSemantics.NET.csproj index 89c6572..31642f6 100644 --- a/ObjectSemantics.NET/ObjectSemantics.NET.csproj +++ b/ObjectSemantics.NET/ObjectSemantics.NET.csproj @@ -3,7 +3,7 @@ netstandard2.0 A library that allows you to Maps properties from a source object or class to a template string and returns the result. This is useful for dynamically generating strings based on object properties. - Crudsoft Technologies @ 2023 + Crudsoft Technologies @ 2025 https://github.com/swagfin/ObjectSemantics.NET icon.jpg @@ -15,9 +15,9 @@ ToBase64 FromBase64 . Added template extension method to allow mapping directly from Template - 6.0.5 - 6.0.5 - 6.0.5 + 7.0.0 + 7.0.0 + 7.0.0 false README.md @@ -28,12 +28,6 @@ False - - - - - - True diff --git a/ObjectSemantics.NET/ObjectSemanticsKeyValue.cs b/ObjectSemantics.NET/ObjectSemanticsKeyValue.cs deleted file mode 100644 index feb8ea3..0000000 --- a/ObjectSemantics.NET/ObjectSemanticsKeyValue.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace ObjectSemantics.NET -{ - public class ObjectSemanticsKeyValue - { - public string Key { get; set; } - public object Value { get; set; } - public Type Type { get { return Value.GetType(); } } - } -} diff --git a/ObjectSemantics.NET/ObjectSemanticsTemplate.cs b/ObjectSemantics.NET/ObjectSemanticsTemplate.cs deleted file mode 100644 index a97c68d..0000000 --- a/ObjectSemantics.NET/ObjectSemanticsTemplate.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace ObjectSemantics.NET -{ - public class ObjectSemanticsTemplate - { - public string Name { get; set; } = "default.template"; - public string FileContents { get; set; } - public string Author { get; set; } - public DateTime CreatedDate { get; set; } = DateTime.Now; - } -} \ No newline at end of file diff --git a/ObjectSemantics.NET/TemplateMapper.cs b/ObjectSemantics.NET/TemplateMapper.cs index 1a6a8e9..947c636 100644 --- a/ObjectSemantics.NET/TemplateMapper.cs +++ b/ObjectSemantics.NET/TemplateMapper.cs @@ -1,41 +1,43 @@ -using System; +using ObjectSemantics.NET.Engine; +using ObjectSemantics.NET.Engine.Models; +using System; using System.Collections.Generic; namespace ObjectSemantics.NET { public static class TemplateMapper { - /// - /// Generate a Data Template From Object Properties + /// Generate a mapped string from string /// /// - /// Single Record of T that may include a Collection inside it - /// Additional Key Value parameters that you may need mapped to file - /// Custom Options and configurations for the Template Generator + /// + /// + /// + /// /// - public static string Map(this ObjectSemanticsTemplate template, T record, List additionalKeyValues = null, TemplateMapperOptions options = null) where T : new() + public static string Map(this string template, T record, Dictionary additionalKeyValues = null, TemplateMapperOptions options = null) where T : class, new() { return Map(record, template, additionalKeyValues, options); } /// - /// Generate a Data Template From Object Properties + /// Generates a mapped string from a T record and a template /// /// - /// Single Record of T that may include a Collection inside it - /// Template File containing Template Name and Template Contents - /// Additional Key Value parameters that you may need mapped to file - /// Custom Options and configurations for the Template Generator + /// + /// + /// + /// /// - public static string Map(T record, ObjectSemanticsTemplate template, List additionalKeyValues = null, TemplateMapperOptions options = null) where T : new() + /// + public static string Map(this T record, string template, Dictionary additionalKeyValues = null, TemplateMapperOptions options = null) where T : class, new() { if (record == null) return string.Empty; if (template == null) throw new Exception("Template Object can't be NULL"); if (options == null) options = new TemplateMapperOptions(); - TemplatedContent templatedContent = GavinsAlgorithim.GenerateTemplateFromFileContents(template.FileContents, options); - if (templatedContent == null) throw new Exception($"Error Generating template from specified Template Name: {template.Name}"); - return GavinsAlgorithim.GenerateFromTemplate(record, templatedContent, additionalKeyValues, options); + EngineRunnerTemplate runnerTemplate = EngineAlgorithim.GenerateRunnerTemplate(template); + return runnerTemplate == null ? throw new Exception($"Error Mapping!") : EngineAlgorithim.GenerateFromTemplate(record, runnerTemplate, additionalKeyValues, options); } } } \ No newline at end of file diff --git a/README.md b/README.md index cedd011..004c893 100644 --- a/README.md +++ b/README.md @@ -27,103 +27,95 @@ Install-Package ObjectSemantics.NET ## 🚀 Quick Start -### Example 1: Basic Object Property Mapping +### Example 1: Mapping Object Properties ```csharp -// Create model -Student student = new Student +Person person = new Person { - StudentName = "George Waynne", - Balance = 2510 + Name = "John Doe" }; -// Define template -var template = new ObjectSemanticsTemplate +// Define template and map it using the object +string result = person.Map("I am {{ Name }}!"); + +Console.WriteLine(result); +``` + +**Output:** +``` +I am John Doe! +``` +--- + +### Example 2: Mapping Using String Extension + +```csharp +Person person = new Person { - FileContents = @"My Name is: {{ StudentName }} and my balance is {{ Balance:N2 }}" + Name = "Jane Doe" }; -// Map object to template -string result = template.Map(student); +// You can also start with the string template +string result = "I am {{ Name }}!".Map(person); Console.WriteLine(result); ``` **Output:** ``` -My Name is: George Waynne and my balance is 2,510.00 +I am Jane Doe! ``` - --- -### Example 2: Mapping Enumerable Collections +### Example 3: Mapping Enumerable Collections (Looping) ```csharp -Student student = new Student +Person person = new Person { - StudentName = "John Doe", - Invoices = new List + Name = "John Doe", + MyCars = new List { - new Invoice { Id = 2, RefNo = "INV_002", Narration = "Grade II Fees", Amount = 2000, InvoiceDate = new DateTime(2023, 04, 01) }, - new Invoice { Id = 1, RefNo = "INV_001", Narration = "Grade I Fees", Amount = 320, InvoiceDate = new DateTime(2022, 08, 01) } + new Car { Make = "BMW", Year = 2023 }, + new Car { Make = "Rolls-Royce", Year = 2020 } } }; -var template = new ObjectSemanticsTemplate -{ - FileContents = @"{{ StudentName }} Invoices -{{ #foreach(Invoices) }} - - {{ Id }} - {{ RefNo }} - {{ Narration }} - {{ Amount:N0 }} - {{ InvoiceDate:yyyy-MM-dd }} - -{{ #endforeach }}" -}; +string template = @" +{{ Name }}'s Cars +{{ #foreach(MyCars) }} + - {{ Year }} {{ Make }} +{{ #endforeach }}"; -string result = template.Map(student); +string result = person.Map(template); Console.WriteLine(result); ``` **Output:** ``` -John Doe Invoices - - - 2 - INV_002 - Grade II Fees - 2,000 - 2023-04-01 - - - - 1 - INV_001 - Grade I Fees - 320 - 2022-08-01 - +John Doe's Cars + - 2023 BMW + - 2020 Rolls-Royce ``` - --- -### Example 3: String Formatters - -Use format like `uppercase`, `lowercase`, `titlecase`, `length`. +### Example 4: Number Formatting Support ```csharp -FileContents = "Uppercase: {{ StudentName:uppercase }}, Length: {{ StudentName:length }}" +Car car = new Car +{ + Price = 50000 +}; + +string result = car.Map("{{ Price:#,##0 }} | {{ Price:N2 }}"); + +Console.WriteLine(result); ``` -Outputs: +**Output:** ``` -Uppercase: ALICE, Length: 5 +50,000 | 50,000.00 ``` - --- ## 💡 More Examples & Documentation