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>2000")); + } + + [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(); + for (var index = 0; index < input.ColumnSpecifications.Length; index++) + { + cancellationToken.ThrowIfCancellationRequested(); + var obj = csvReader.GetField(typeList[index], index); + innerList.Add(obj); + } + resultData.Add(innerList); + } + } + else if (options.ContainsHeaderRow && !input.ColumnSpecifications.Any()) + { + if (string.Equals(options.ReplaceHeaderWhitespaceWith, " ")) + headers = csvReader.HeaderRecord.ToList(); + else + headers = csvReader.HeaderRecord.Select(x => x.Replace(" ", options.ReplaceHeaderWhitespaceWith)).ToList(); + + while (csvReader.Read()) + { + var innerList = new List(); + for (var index = 0; index < csvReader.HeaderRecord.Length; index++) + { + cancellationToken.ThrowIfCancellationRequested(); + var obj = csvReader.GetField(index); + innerList.Add(obj); + } + resultData.Add(innerList); + } + } + else if (!options.ContainsHeaderRow && !input.ColumnSpecifications.Any()) + { + if (!csvReader.Read()) + throw new ArgumentException("CSV input can not be empty"); + + headers = csvReader.Parser.Record.Select((x, index) => index.ToString()).ToList(); + resultData.Add(new List(csvReader.Parser.Record)); + while (csvReader.Read()) + { + var innerList = new List(); + for (var index = 0; index < headers.Count; index++) + { + cancellationToken.ThrowIfCancellationRequested(); + var obj = csvReader.GetField(index); + innerList.Add(obj); + } + resultData.Add(innerList); + } + } + + var xmlResult = WriteXmlString(resultData, options, cultureInfo, headers, cancellationToken); + + return new Result(true, xmlResult); + } + + private static Type ToType(ColumnType code, bool useNullables) + { + return code switch + { + ColumnType.Boolean => useNullables ? typeof(bool?) : typeof(bool), + ColumnType.Char => useNullables ? typeof(char?) : typeof(char), + ColumnType.DateTime => useNullables ? typeof(DateTime?) : typeof(DateTime), + ColumnType.Decimal => useNullables ? typeof(decimal?) : typeof(decimal), + ColumnType.Double => useNullables ? typeof(double?) : typeof(double), + ColumnType.Int => useNullables ? typeof(int?) : typeof(int), + ColumnType.Long => useNullables ? typeof(long?) : typeof(long), + ColumnType.String => typeof(string), + _ => null, + }; + } + + private static string WriteXmlString(IEnumerable> data, Options options, CultureInfo culture, IReadOnlyList headers, CancellationToken cancellationToken) + { + using var ms = new MemoryStream(); + using (var writer = new XmlTextWriter(ms, new UTF8Encoding(false)) { Formatting = Formatting.Indented }) + { + writer.WriteStartDocument(); + if (string.IsNullOrEmpty(options.XmlRootElementName)) + writer.WriteStartElement("Root"); + else + writer.WriteStartElement(options.XmlRootElementName); + + foreach (var row in data) + { + if (string.IsNullOrEmpty(options.XmlRowElementName)) + writer.WriteStartElement("Row"); + else + writer.WriteStartElement(options.XmlRowElementName); + + for (var i = 0; i < headers.Count; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + writer.WriteElementString(headers[i], Convert.ToString(row[i], culture)); + } + + writer.WriteEndElement(); + } + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + return Encoding.UTF8.GetString(ms.ToArray()); + } +} \ No newline at end of file diff --git a/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/ColumnSpecification.cs b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/ColumnSpecification.cs new file mode 100644 index 0000000..e92ab1f --- /dev/null +++ b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/ColumnSpecification.cs @@ -0,0 +1,19 @@ +namespace Frends.CSV.ConvertToXML.Definitions; + +/// +/// ColumnSpecification values +/// +public class ColumnSpecification +{ + /// + /// Name of the resulting column + /// + /// foo + public string Name { get; set; } + + /// + /// Type for the resulting column. + /// + /// String + public ColumnType Type { get; set; } +} diff --git a/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/ColumnType.cs b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/ColumnType.cs new file mode 100644 index 0000000..3cda885 --- /dev/null +++ b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/ColumnType.cs @@ -0,0 +1,18 @@ +namespace Frends.CSV.ConvertToXML.Definitions; + +/// +/// Input types. +/// +public enum ColumnType +{ +#pragma warning disable CS1591 // self explanatory + String, + Int, + Long, + Decimal, + Double, + Boolean, + DateTime, + Char +#pragma warning restore CS1591 // self explanatory +} \ No newline at end of file diff --git a/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/Input.cs b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/Input.cs new file mode 100644 index 0000000..417c6b8 --- /dev/null +++ b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/Input.cs @@ -0,0 +1,31 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace Frends.CSV.ConvertToXML.Definitions; + +/// +/// Input parameters. +/// +public class Input +{ + /// + /// Input csv string + /// + /// 1;Foo;Bar + [DisplayFormat(DataFormatString = "Text")] + public string Csv { get; set; } + + /// + /// Delimiter. + /// + /// ; + [DefaultValue("\";\"")] + public string Delimiter { get; set; } + + /// + /// You can map columns to specific types. + /// The order of the columns are used for mapping, that means that the ColumnSpecification elements need to be created in the same order as the CSV fields. + /// + /// [ { foo, String } ] + public ColumnSpecification[] ColumnSpecifications { get; set; } +} \ No newline at end of file diff --git a/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/Options.cs b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/Options.cs new file mode 100644 index 0000000..948439e --- /dev/null +++ b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/Options.cs @@ -0,0 +1,81 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace Frends.CSV.ConvertToXML.Definitions; + +/// +/// Options parameters. +/// +public class Options +{ + /// + /// This flag tells the reader if there is a header row in the CSV string. + /// + /// true + [DefaultValue("true")] + public bool ContainsHeaderRow { get; set; } = true; + + /// + /// This flag tells the reader to trim whitespace from the beginning and ending of the field value when reading. + /// + /// true + [DefaultValue("true")] + public bool TrimOutput { get; set; } = true; + + /// + /// If the CSV string contains metadata before the header row you can set this value to ignore a specific amount of rows from the beginning of the csv string. + /// + /// 2 + [DefaultValue(0)] + public int SkipRowsFromTop { get; set; } + + /// + /// A flag to let the reader know if a record should be skipped when reading if it's empty. + /// A record is considered empty if all fields are empty. + /// + /// true + [DefaultValue("true")] + public bool SkipEmptyRows { get; set; } + + /// + /// If intended header value contains whitespaces replace it(them) with this string, default action is to do nothing. + /// + /// " " + [DisplayFormat(DataFormatString = "Text")] + [DefaultValue(" ")] + public string ReplaceHeaderWhitespaceWith { get; set; } + + /// + /// The culture info to read/write the entries with, e.g. for decimal separators. + /// InvariantCulture will be used by default. + /// See list of cultures here: https://learn.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo.name?view=net-7.0 + /// NOTE: Due to an issue with the CsvHelpers library, all CSV tasks will use the culture info setting of the first CSV task in the process; you cannot use different cultures for reading and parsing CSV files in the same process.| + /// + /// fi-FI + [DisplayFormat(DataFormatString = "Text")] + public string CultureInfo { get; set; } = ""; + + /// + /// The flag for reader to treat missing fields as nulls instead of throwing a MissingFieldException. + /// In case of providing column specifications manually the value of empty field defined as string will be empty instead of null + /// + /// true + [DefaultValue("true")] + public bool TreatMissingFieldsAsNulls { get; set; } = false; + + /// + /// Specifies the name for the XML root element. + /// + /// Root + [DisplayFormat(DataFormatString = "Text")] + [DefaultValue("Root")] + public string XmlRootElementName { get; set; } + + /// + /// Specifies the name for the XML row element. + /// + /// Row + [DisplayFormat(DataFormatString = "Text")] + [DefaultValue("Row")] + public string XmlRowElementName { get; set; } +} \ No newline at end of file diff --git a/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/Result.cs b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/Result.cs new file mode 100644 index 0000000..93f9d90 --- /dev/null +++ b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Definitions/Result.cs @@ -0,0 +1,25 @@ +namespace Frends.CSV.ConvertToXML.Definitions; + +/// +/// Task's result. +/// +public class Result +{ + /// + /// Operation complete without errors. + /// + /// true + public bool Success { get; private set; } + + /// + /// Result as XML. + /// + /// "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Root>\r\n <Row>\r\n <value>1</value>\r\n <foos>foo</foos>\r\n <bars>bar</bars>\r\n</Row>\r\n</Root>" + public string Xml { get; private set; } + + internal Result(bool success, string xml) + { + Success = success; + Xml = xml; + } +} \ No newline at end of file diff --git a/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML.csproj b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML.csproj new file mode 100644 index 0000000..0b60d16 --- /dev/null +++ b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML.csproj @@ -0,0 +1,30 @@ + + + + net6.0 + 1.0.0 + Frends + Frends + Frends + Frends + Frends + MIT + true + Frends Task to Convert CSV string content to a XML string. + https://frends.com/ + https://github.com/FrendsPlatform/Frends.CSV2 + + + + + + PreserveNewest + + + + + + + + + \ No newline at end of file diff --git a/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/FrendsTaskMetadata.json b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/FrendsTaskMetadata.json new file mode 100644 index 0000000..04e6eeb --- /dev/null +++ b/Frends.CSV.ConvertToXML/Frends.CSV.ConvertToXML/FrendsTaskMetadata.json @@ -0,0 +1,7 @@ +{ + "Tasks": [ + { + "TaskMethod": "Frends.CSV.ConvertToXML.CSV.ConvertToXML" + } + ] +} \ No newline at end of file diff --git a/Frends.CSV.ConvertToXML/README.md b/Frends.CSV.ConvertToXML/README.md new file mode 100644 index 0000000..4d4750e --- /dev/null +++ b/Frends.CSV.ConvertToXML/README.md @@ -0,0 +1,25 @@ +# Frends.CSV.ConvertToXML +Frends Task to Convert CSV string content to a XML string. + +[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) +[![Build](https://github.com/FrendsPlatform/Frends.CSV2/actions/workflows/ConvertToXML_build_and_test_on_main.yml/badge.svg)](https://github.com/FrendsPlatform/Frends.CSV2/actions) +![Coverage](https://app-github-custom-badges.azurewebsites.net/Badge?key=FrendsPlatform/Frends.CSV2/Frends.CSV.ConvertToXML|main) + +# Installing + +You can install the Task via Frends UI Task View. + +## Building + + +Rebuild the project + +`dotnet build` + +Run tests + +`dotnet test` + +Create a NuGet package + +`dotnet pack --configuration Release` diff --git a/README.md b/README.md index 2228d2f..ff286cc 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Frends Task to process CSV files. # Tasks - [Frends.CSV.ConvertToJSON](Frends.CSV.ConvertToJSON/README.md) +- [Frends.CSV.ConvertToXML](Frends.CSV.ConvertToXML/README.md) - [Frends.CSV.Create](Frends.CSV.Create/README.md) - [Frends.CSV.Parse](Frends.CSV.Parse/README.md)