diff --git a/.github/workflows/ConvertToXML_build_and_test_on_main.yml b/.github/workflows/ConvertToXML_build_and_test_on_main.yml
new file mode 100644
index 0000000..bf99e20
--- /dev/null
+++ b/.github/workflows/ConvertToXML_build_and_test_on_main.yml
@@ -0,0 +1,17 @@
+name: ConvertToXML build main
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - 'Frends.CSV.ConvertToXML/**'
+ workflow_dispatch:
+
+jobs:
+ build:
+ uses: FrendsPlatform/FrendsTasks/.github/workflows/build_main.yml@main
+ with:
+ workdir: Frends.CSV.ConvertToXML
+ secrets:
+ badge_service_api_key: ${{ secrets.BADGE_SERVICE_API_KEY }}
\ No newline at end of file
diff --git a/.github/workflows/ConvertToXML_build_and_test_on_push.yml b/.github/workflows/ConvertToXML_build_and_test_on_push.yml
new file mode 100644
index 0000000..136badf
--- /dev/null
+++ b/.github/workflows/ConvertToXML_build_and_test_on_push.yml
@@ -0,0 +1,18 @@
+name: ConvertToXML build test
+
+on:
+ push:
+ branches-ignore:
+ - main
+ paths:
+ - 'Frends.CSV.ConvertToXML/**'
+ workflow_dispatch:
+
+jobs:
+ build:
+ uses: FrendsPlatform/FrendsTasks/.github/workflows/build_test.yml@main
+ with:
+ workdir: Frends.CSV.ConvertToXML
+ secrets:
+ badge_service_api_key: ${{ secrets.BADGE_SERVICE_API_KEY }}
+ test_feed_api_key: ${{ secrets.TASKS_TEST_FEED_API_KEY }}
\ No newline at end of file
diff --git a/.github/workflows/ConvertToXML_release.yml b/.github/workflows/ConvertToXML_release.yml
new file mode 100644
index 0000000..c2713c8
--- /dev/null
+++ b/.github/workflows/ConvertToXML_release.yml
@@ -0,0 +1,12 @@
+name: ConvertToXML release
+
+on:
+ workflow_dispatch:
+
+jobs:
+ build:
+ uses: FrendsPlatform/FrendsTasks/.github/workflows/release.yml@main
+ with:
+ workdir: Frends.CSV.ConvertToXML
+ secrets:
+ feed_api_key: ${{ secrets.TASKS_FEED_API_KEY }}
\ No newline at end of file
diff --git a/Frends.CSV.ConvertToXML/.editorconfig b/Frends.CSV.ConvertToXML/.editorconfig
new file mode 100644
index 0000000..af6fcf9
--- /dev/null
+++ b/Frends.CSV.ConvertToXML/.editorconfig
@@ -0,0 +1,4 @@
+[*.cs]
+
+# CS8602: Dereference of a possibly null reference.
+dotnet_diagnostic.CS8602.severity = none
diff --git a/Frends.CSV.ConvertToXML/CHANGELOG.md b/Frends.CSV.ConvertToXML/CHANGELOG.md
new file mode 100644
index 0000000..cdf48e5
--- /dev/null
+++ b/Frends.CSV.ConvertToXML/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changelog
+
+## [1.0.0] - 2023-08-24
+### Added
+- Initial implementation
diff --git a/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML.UnitTests/Frends.CSV.ConvertToXML.Tests.csproj b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML.UnitTests/Frends.CSV.ConvertToXML.Tests.csproj
new file mode 100644
index 0000000..4bfdc2c
--- /dev/null
+++ b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML.UnitTests/Frends.CSV.ConvertToXML.Tests.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML.UnitTests/UnitTests.cs b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML.UnitTests/UnitTests.cs
new file mode 100644
index 0000000..5ca0f6e
--- /dev/null
+++ b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML.UnitTests/UnitTests.cs
@@ -0,0 +1,270 @@
+using Frends.CSV.ConvertToXML.Definitions;
+using NUnit.Framework;
+
+namespace Frends.CSV.ConvertToXML.UnitTests;
+
+[TestFixture]
+public class UnitTests
+{
+ [Test]
+ public void Test_WithEmptyFields()
+ {
+ var csv = @"string,1,2023-01-01,2,200,3,true,N
+ ,,,,,,,";
+
+ var input = new Input()
+ {
+ ColumnSpecifications = Array.Empty(),
+ Delimiter = ",",
+ Csv = csv
+ };
+
+ var options = new Options()
+ {
+ ContainsHeaderRow = false,
+ SkipRowsFromTop = 0,
+ SkipEmptyRows = false
+ };
+
+ var result = CSV.ConvertToXML(input, options, default);
+ Assert.IsNotNull(result.Xml);
+ }
+
+ [Test]
+ public void ConvertToXMLTest_SkipRowsWithAutomaticHeaders()
+ {
+ var csv = @"asdasd
+Coolio
+year;car;mark;price
+1997;Ford;E350;2,34
+2000;Mercury;Cougar;2,38";
+
+ var input = new Input()
+ {
+ ColumnSpecifications = Array.Empty(),
+ Delimiter = ";",
+ Csv = csv
+ };
+
+ var options = new Options()
+ {
+ ContainsHeaderRow = true,
+ SkipRowsFromTop = 2,
+ SkipEmptyRows = false
+ };
+
+ var result = CSV.ConvertToXML(input, options, default);
+ Assert.IsNotNull(result.Xml);
+ Assert.IsTrue(result.Xml.Contains("2000"));
+ }
+
+ [Test]
+ public void ConvertToXMLTest_WithColumnSpecAndMissingHeader()
+ {
+ var csv = @"1997;Ford;E350;2,34
+2000;Mercury;Cougar;2,38";
+
+ var input = new Input()
+ {
+ ColumnSpecifications = new[]
+ {
+ new ColumnSpecification() {Name = "Year", Type = ColumnType.Int},
+ new ColumnSpecification() {Name = "Car", Type = ColumnType.String},
+ new ColumnSpecification() {Name = "Mark", Type = ColumnType.String},
+ new ColumnSpecification() {Name = "Price", Type = ColumnType.Decimal}
+ },
+ Delimiter = ";",
+ Csv = csv
+ };
+
+ var options = new Options()
+ {
+ ContainsHeaderRow = false,
+ CultureInfo = "fi-FI"
+ };
+
+ var result = CSV.ConvertToXML(input, options, default);
+ Assert.IsNotNull(result.Xml);
+ Assert.IsTrue(result.Xml.Contains("2000"));
+ }
+
+ [Test]
+ public void ConvertToXMLTest_WithNoColumnSpecAndNoHeader()
+ {
+ var csv = @"1997;Ford;E350;2,34
+2000;Mercury;Cougar;2,38";
+
+ var input = new Input()
+ {
+ ColumnSpecifications = Array.Empty(),
+ Delimiter = ";",
+ Csv = csv
+ };
+
+ var options = new Options()
+ {
+ ContainsHeaderRow = false,
+ CultureInfo = "fi-FI"
+ };
+
+ var result = CSV.ConvertToXML(input, options, default);
+ Assert.IsNotNull(result.Xml);
+ Assert.IsTrue(result.Xml.Contains("<0>20000>"));
+ }
+
+ [Test]
+ public void ConvertToXMLTest_WillAllKindOfDataTypes()
+ {
+ var csv =
+@"THIS;is;header;row;with;some;random;stuff ;yes
+1997;""Fo;rd"";2,34;true;1;4294967296;f;2008-09-15;2008-05-01 7:34:42Z
+2000;Mercury;2,38;false;0;4294967296;g;2009-09-15T06:30:41.7752486;Thu, 01 May 2008 07:34:42 GMT";
+
+ var input = new Input()
+ {
+ ColumnSpecifications = new[]
+ {
+ new ColumnSpecification() {Name = "Int", Type = ColumnType.Int},
+ new ColumnSpecification() {Name = "String", Type = ColumnType.String},
+ new ColumnSpecification() {Name = "Decimal", Type = ColumnType.Decimal},
+ new ColumnSpecification() {Name = "Bool", Type = ColumnType.Boolean},
+ new ColumnSpecification() {Name = "Bool2", Type = ColumnType.Boolean},
+ new ColumnSpecification() {Name = "Long", Type = ColumnType.Long},
+ new ColumnSpecification() {Name = "Char", Type = ColumnType.Char},
+ new ColumnSpecification() {Name = "DateTime", Type = ColumnType.DateTime},
+ new ColumnSpecification() {Name = "DateTime2", Type = ColumnType.DateTime},
+ },
+ Delimiter = ";",
+ Csv = csv
+ };
+
+ var options = new Options()
+ {
+ ContainsHeaderRow = true,
+ CultureInfo = "fi-FI"
+ };
+
+ var result = CSV.ConvertToXML(input, options, default);
+ Assert.IsNotNull(result.Xml);
+ }
+
+ [Test]
+ public void TestConvertToXMLTreatMissingFieldsAsNullSetToTrue()
+ {
+ var csv =
+ @"header1,header2,header3
+ value1,value2,value3
+ value1,value2,value3
+ value1,value2";
+
+ var input = new Input()
+ {
+ ColumnSpecifications = new ColumnSpecification[0],
+ Delimiter = ",",
+ Csv = csv
+ };
+
+ var options = new Options()
+ {
+ ContainsHeaderRow = true,
+ CultureInfo = "fi-FI",
+ TreatMissingFieldsAsNulls = true
+ };
+
+ var result = CSV.ConvertToXML(input, options, default);
+ Assert.IsTrue(condition: result.Xml.Contains(""));
+ }
+
+ [Test]
+ public void TestConvertToXMLTreatMissingFieldsAsNullSetToTrueNoHeader()
+ {
+ var csv =
+ @"value1,value2,value3
+ value1,value2,value3
+ value1,value2";
+
+ var input = new Input()
+ {
+ ColumnSpecifications = new ColumnSpecification[0],
+ Delimiter = ",",
+ Csv = csv
+ };
+
+ var options = new Options()
+ {
+ ContainsHeaderRow = false,
+ CultureInfo = "fi-FI",
+ TreatMissingFieldsAsNulls = true
+ };
+
+ var result = CSV.ConvertToXML(input, options, default);
+ Assert.IsTrue(condition: result.Xml.Contains("<2 />"));
+ }
+
+ [Test]
+ public void TestConvertToXMLTreatMissingFieldsAsNullSetToTrueNoHeaderWithColumnSpecifications()
+ {
+ var csv =
+ @"string,1,2023-01-01,2,200,3,true,N
+ ,,,,,,,";
+
+ var input = new Input()
+ {
+ ColumnSpecifications = new[]
+ {
+ new ColumnSpecification() { Name = "String", Type = ColumnType.String },
+ new ColumnSpecification() { Name = "Decimal", Type = ColumnType.Decimal},
+ new ColumnSpecification() { Name = "DateTime", Type = ColumnType.DateTime },
+ new ColumnSpecification() { Name = "Int", Type = ColumnType.Int },
+ new ColumnSpecification() { Name = "Long", Type = ColumnType.Long },
+ new ColumnSpecification() { Name = "Double", Type = ColumnType.Double },
+ new ColumnSpecification() { Name = "Boolean", Type = ColumnType.Boolean },
+ new ColumnSpecification() { Name = "Char", Type = ColumnType.Char }
+ },
+ Delimiter = ",",
+ Csv = csv
+ };
+
+ var options = new Options()
+ {
+ ContainsHeaderRow = false,
+ CultureInfo = "fi-FI",
+ TreatMissingFieldsAsNulls = true
+ };
+
+ var result = CSV.ConvertToXML(input, options, default);
+ Assert.IsTrue(result.Xml.Contains(""));
+ Assert.IsTrue(result.Xml.Contains(""));
+ Assert.IsTrue(result.Xml.Contains(""));
+ Assert.IsTrue(result.Xml.Contains(""));
+ Assert.IsTrue(result.Xml.Contains(""));
+ Assert.IsTrue(result.Xml.Contains(""));
+ Assert.IsTrue(result.Xml.Contains(""));
+ }
+
+ [Test]
+ public void TestConvertToXMLTreatMissingFieldsAsNullSetToFalse()
+ {
+ var csv =
+ @"header1,header2,header3
+ value1,value2,value3
+ value1,value2,value3
+ value1,value2";
+ var input = new Input()
+ {
+ ColumnSpecifications = new ColumnSpecification[0],
+ Delimiter = ",",
+ Csv = csv
+ };
+
+ var options = new Options()
+ {
+ ContainsHeaderRow = true,
+ CultureInfo = "fi-FI",
+ TreatMissingFieldsAsNulls = false
+ };
+
+ var ex = Assert.Throws(() => CSV.ConvertToXML(input, options, default));
+ Assert.IsTrue(ex.Message.StartsWith("Field at index '2' does not exist. You can ignore missing fields by setting MissingFieldFound to null."));
+ }
+}
\ No newline at end of file
diff --git a/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML.sln b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML.sln
new file mode 100644
index 0000000..304dce7
--- /dev/null
+++ b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML.sln
@@ -0,0 +1,41 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.1.32319.34
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Frends.CSV.ConvertToXML", "Frends.CSV.ConvertToXML\Frends.CSV.ConvertToXML.csproj", "{35C305C0-8108-4A98-BB1D-AFE5C926239E}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{78F7F22E-6E20-4BCE-8362-0C558568B729}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ CHANGELOG.md = CHANGELOG.md
+ ..\.github\workflows\ConvertToXML_build_and_test_on_main.yml = ..\.github\workflows\ConvertToXML_build_and_test_on_main.yml
+ ..\.github\workflows\ConvertToXML_build_and_test_on_push.yml = ..\.github\workflows\ConvertToXML_build_and_test_on_push.yml
+ ..\.github\workflows\ConvertToXML_release.yml = ..\.github\workflows\ConvertToXML_release.yml
+ README.md = README.md
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Frends.CSV.ConvertToXML.Tests", "Frends.CSV.ConvertToXML.UnitTests\Frends.CSV.ConvertToXML.Tests.csproj", "{01BD39F5-AAAB-47BA-A6F8-41A892532D1C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {35C305C0-8108-4A98-BB1D-AFE5C926239E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {35C305C0-8108-4A98-BB1D-AFE5C926239E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {35C305C0-8108-4A98-BB1D-AFE5C926239E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {35C305C0-8108-4A98-BB1D-AFE5C926239E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {01BD39F5-AAAB-47BA-A6F8-41A892532D1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {01BD39F5-AAAB-47BA-A6F8-41A892532D1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {01BD39F5-AAAB-47BA-A6F8-41A892532D1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {01BD39F5-AAAB-47BA-A6F8-41A892532D1C}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {8986D685-9988-4F5F-B8D9-E42A4E44BFED}
+ EndGlobalSection
+EndGlobal
diff --git a/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/ConvertToXML.cs b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/ConvertToXML.cs
new file mode 100644
index 0000000..99c851e
--- /dev/null
+++ b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/ConvertToXML.cs
@@ -0,0 +1,174 @@
+using CsvHelper;
+using CsvHelper.Configuration;
+using Frends.CSV.ConvertToXML.Definitions;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Xml;
+
+namespace Frends.CSV.ConvertToXML;
+
+///
+/// CSV Task.
+///
+public class CSV
+{
+ ///
+ /// Converts CSV string content to XML string.
+ /// [Documentation](https://tasks.frends.com/tasks/frends-tasks/Frends.CSV.ConvertToXML)
+ ///
+ /// Input parameters
+ /// Optional parameters
+ /// Token generated by Frends to stop this Task.
+ /// Object { bool Success, string Xml }
+ public static Result ConvertToXML([PropertyTab] Input input, [PropertyTab] Options options, CancellationToken cancellationToken)
+ {
+ var cultureInfo = new CultureInfo(options.CultureInfo);
+ var resultData = new List>();
+ var headers = new List();
+
+ var configuration = new CsvConfiguration(cultureInfo)
+ {
+ HasHeaderRecord = options.ContainsHeaderRow,
+ Delimiter = input.Delimiter,
+ TrimOptions = options.TrimOutput ? TrimOptions.None : TrimOptions.Trim,
+ IgnoreBlankLines = options.SkipEmptyRows
+ };
+
+ // Setting the MissingFieldFound -delegate property of configuration to null when
+ // option.TreatMissingFieldsAsNulls is set to true for returning null values for missing fields.
+ // Otherwise the default setting which throws a MissingFieldException is used
+ if (options.TreatMissingFieldsAsNulls)
+ configuration.MissingFieldFound = null;
+
+ using TextReader sr = new StringReader(input.Csv);
+ //Read rows before passing textreader to csvreader for so that header row would be in the correct place
+ for (var i = 0; i < options.SkipRowsFromTop; i++)
+ sr.ReadLine();
+
+ using var csvReader = new CsvReader(sr, configuration);
+ if (options.ContainsHeaderRow)
+ {
+ csvReader.Read();
+ csvReader.ReadHeader();
+ }
+
+ if (input.ColumnSpecifications.Any())
+ {
+ var typeList = new List();
+
+ foreach (var columnSpec in input.ColumnSpecifications)
+ {
+ typeList.Add(ToType(columnSpec.Type, options.TreatMissingFieldsAsNulls));
+ headers.Add(columnSpec.Name);
+ }
+
+ while (csvReader.Read())
+ {
+ var innerList = new List