From c2150f4ff864a81d4889495dfbe78a2cec7e9175 Mon Sep 17 00:00:00 2001 From: leandromoh Date: Mon, 2 Mar 2026 10:07:04 -0300 Subject: [PATCH 1/6] done adds impl --- .../Reader/FixedLengthReaderBuilder.cs | 6 +- .../Reader/VariableLengthReaderBuilder.cs | 7 ++- RecordParser/Engines/Reader/ReaderEngine.cs | 62 ++++++++++++++++++- RecordParser/Parsers/FixedLengthReader.cs | 10 ++- RecordParser/Parsers/VariableLengthReader.cs | 19 +++++- RecordParser/Visitors/CultureInfoVisitor.cs | 2 +- RecordParser/Visitors/TryCatchVisitor.cs | 39 ++++++++++++ 7 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 RecordParser/Visitors/TryCatchVisitor.cs diff --git a/RecordParser/Builders/Reader/FixedLengthReaderBuilder.cs b/RecordParser/Builders/Reader/FixedLengthReaderBuilder.cs index 1a4514f..d8592fa 100644 --- a/RecordParser/Builders/Reader/FixedLengthReaderBuilder.cs +++ b/RecordParser/Builders/Reader/FixedLengthReaderBuilder.cs @@ -95,10 +95,12 @@ public IFixedLengthReader Build(CultureInfo cultureInfo = null, Func facto { var map = MappingReadConfiguration.Merge(list, dic); var func = ReaderEngine.RecordParserSpanFlat(map, factory); - func = CultureInfoVisitor.ReplaceCulture(func, cultureInfo); - return new FixedLengthReader(func.Compile()); + var func2 = ReaderEngine.RecordParserSpanFlatSafe(map, factory); + func2 = CultureInfoVisitor.ReplaceCulture(func2, cultureInfo); + + return new FixedLengthReader(func.Compile(), func2.Compile()); } } } diff --git a/RecordParser/Builders/Reader/VariableLengthReaderBuilder.cs b/RecordParser/Builders/Reader/VariableLengthReaderBuilder.cs index 1c6e702..3e0e0bf 100644 --- a/RecordParser/Builders/Reader/VariableLengthReaderBuilder.cs +++ b/RecordParser/Builders/Reader/VariableLengthReaderBuilder.cs @@ -101,11 +101,14 @@ public IVariableLengthReader Build(string separator, CultureInfo cultureInfo var quote = QuoteHelper.Quote.Char; var map = MappingReadConfiguration.Merge(list.Select(x => x.Value), dic); - var func = ReaderEngine.RecordParserSpanCSV(map, factory); + var func = ReaderEngine.RecordParserSpanCSV(map, factory); func = CultureInfoVisitor.ReplaceCulture(func, cultureInfo); - return new VariableLengthReader(func.Compile(), separator, quote); + var func2 = ReaderEngine.RecordParserSpanCSVSafe(map, factory); + func2 = CultureInfoVisitor.ReplaceCulture(func2, cultureInfo); + + return new VariableLengthReader(func.Compile(), func2.Compile(), separator, quote); } } } diff --git a/RecordParser/Engines/Reader/ReaderEngine.cs b/RecordParser/Engines/Reader/ReaderEngine.cs index bb69069..72bf7e8 100644 --- a/RecordParser/Engines/Reader/ReaderEngine.cs +++ b/RecordParser/Engines/Reader/ReaderEngine.cs @@ -7,9 +7,14 @@ using System.Linq.Expressions; using static RecordParser.Engines.ExpressionHelper; +// todo apagar internal delegate T FuncSpanIntT(ReadOnlySpan span, int index); + public delegate T FuncSpanT(ReadOnlySpan text); +internal delegate T FuncSpanTSafe(ReadOnlySpan text, Action exceptionHandler); + internal delegate T FuncSpanArrayT(in TextFindHelper finder); +internal delegate T FuncSpanArrayTSafe(in TextFindHelper finder, Action exceptionHandler); namespace RecordParser.Engines.Reader { @@ -34,6 +39,32 @@ public static Expression> RecordParserSpanCSV(IEnumerable> RecordParserSpanCSVSafe(IEnumerable mappedColumns, Func factory) + { + // parameters + var configParameter = Expression.Parameter(typeof(TextFindHelper).MakeByRefType(), "config"); + var exceptionHandler = Expression.Parameter(typeof(Action), "exceptionHandler"); + + // variables + var instanceVariable = Expression.Variable(typeof(T), "inst"); + + var blockThatSetProperties = MountSetProperties(instanceVariable, mappedColumns, (i, mapConfig) => + { + return Expression.Call(configParameter, nameof(TextFindHelper.GetValue), Type.EmptyTypes, Expression.Constant(mapConfig.start)); + }, + (i, assign) => + { + var visitor = new TryCatchVisitor(exceptionHandler, i); + var result = visitor.Visit(assign); + return result; + }); + + var body = MountBody(instanceVariable, blockThatSetProperties, mappedColumns, factory); + var result = Expression.Lambda>(body, configParameter, exceptionHandler); + + return result; + } + public static Expression> RecordParserSpanFlat(IEnumerable mappedColumns, Func factory) { // parameters @@ -53,6 +84,32 @@ public static Expression> RecordParserSpanFlat(IEnumerable> RecordParserSpanFlatSafe(IEnumerable mappedColumns, Func factory) + { + // parameters + var line = Expression.Parameter(typeof(ReadOnlySpan), "span"); + var exceptionHandler = Expression.Parameter(typeof(Action), "exceptionHandler"); + + // variables + var instanceVariable = Expression.Variable(typeof(T), "inst"); + + var blockThatSetProperties = MountSetProperties(instanceVariable, mappedColumns, (i, mapConfig) => + { + return Slice(line, Expression.Constant(mapConfig.start), Expression.Constant(mapConfig.length.Value)); + }, + (i, assign) => + { + var visitor = new TryCatchVisitor(exceptionHandler, i); + var result = visitor.Visit(assign); + return result; + }); + + var body = MountBody(instanceVariable, blockThatSetProperties, mappedColumns, factory); + var result = Expression.Lambda>(body, line, exceptionHandler); + + return result; + } + private static BlockExpression MountBody( ParameterExpression instanceVariable, BlockExpression blockThatSetProperties, @@ -75,7 +132,8 @@ private static BlockExpression MountBody( private static BlockExpression MountSetProperties( ParameterExpression objectParameter, IEnumerable mappedColumns, - Func getTextValue) + Func getTextValue, + Func assignHandler = null) { var replacer = new ParameterReplacerVisitor(objectParameter); var assignsExpressions = new List(); @@ -115,7 +173,7 @@ private static BlockExpression MountSetProperties( var assign = Expression.Assign(replacer.Visit(x.prop), valueToBeSetExpression); - assignsExpressions.Add(assign); + assignsExpressions.Add(assignHandler?.Invoke(i, assign) ?? assign); } assignsExpressions.Add(objectParameter); diff --git a/RecordParser/Parsers/FixedLengthReader.cs b/RecordParser/Parsers/FixedLengthReader.cs index d67e549..9631b4a 100644 --- a/RecordParser/Parsers/FixedLengthReader.cs +++ b/RecordParser/Parsers/FixedLengthReader.cs @@ -6,15 +6,18 @@ public interface IFixedLengthReader { T Parse(ReadOnlySpan line); bool TryParse(ReadOnlySpan line, out T result); + T Parse(ReadOnlySpan line, Action exceptionHandler); } internal class FixedLengthReader : IFixedLengthReader { private readonly FuncSpanT parser; + private readonly FuncSpanTSafe parserWithExceptionHandler; - internal FixedLengthReader(FuncSpanT parser) + internal FixedLengthReader(FuncSpanT parser, FuncSpanTSafe parserWithExceptionHandler) { this.parser = parser; + this.parserWithExceptionHandler = parserWithExceptionHandler; } public T Parse(ReadOnlySpan line) @@ -35,5 +38,10 @@ public bool TryParse(ReadOnlySpan line, out T result) return false; } } + + public T Parse(ReadOnlySpan line, Action exceptionHandler) + { + return parserWithExceptionHandler(line, exceptionHandler); + } } } diff --git a/RecordParser/Parsers/VariableLengthReader.cs b/RecordParser/Parsers/VariableLengthReader.cs index afc532b..fe5d1b0 100644 --- a/RecordParser/Parsers/VariableLengthReader.cs +++ b/RecordParser/Parsers/VariableLengthReader.cs @@ -7,6 +7,7 @@ public interface IVariableLengthReader { T Parse(ReadOnlySpan line); bool TryParse(ReadOnlySpan line, out T result); + T Parse(ReadOnlySpan line, Action exceptionHandler); internal string Separator { get; } } @@ -14,14 +15,16 @@ public interface IVariableLengthReader internal class VariableLengthReader : IVariableLengthReader { private readonly FuncSpanArrayT parser; + private readonly FuncSpanArrayTSafe parserWithExceptionHandler; private readonly string delimiter; private readonly (char ch, string str) quote; string IVariableLengthReader.Separator => delimiter; - internal VariableLengthReader(FuncSpanArrayT parser, string separator, char quote) + internal VariableLengthReader(FuncSpanArrayT parser, FuncSpanArrayTSafe parserWithExceptionHandler, string separator, char quote) { this.parser = parser; + this.parserWithExceptionHandler = parserWithExceptionHandler; delimiter = separator; this.quote = (quote, quote.ToString()); } @@ -53,5 +56,19 @@ public bool TryParse(ReadOnlySpan line, out T result) return false; } } + + public T Parse(ReadOnlySpan line, Action exceptionHandler) + { + var finder = new TextFindHelper(line, delimiter, quote); + + try + { + return parserWithExceptionHandler(in finder, exceptionHandler); + } + finally + { + finder.Dispose(); + } + } } } diff --git a/RecordParser/Visitors/CultureInfoVisitor.cs b/RecordParser/Visitors/CultureInfoVisitor.cs index 55b079a..10878f5 100644 --- a/RecordParser/Visitors/CultureInfoVisitor.cs +++ b/RecordParser/Visitors/CultureInfoVisitor.cs @@ -8,7 +8,7 @@ internal class CultureInfoVisitor : ExpressionVisitor { private readonly ConstantExpression cultureExpression; - public CultureInfoVisitor(CultureInfo cultureInfo) + private CultureInfoVisitor(CultureInfo cultureInfo) { cultureExpression = Expression.Constant(cultureInfo, typeof(CultureInfo)); } diff --git a/RecordParser/Visitors/TryCatchVisitor.cs b/RecordParser/Visitors/TryCatchVisitor.cs new file mode 100644 index 0000000..e75e95b --- /dev/null +++ b/RecordParser/Visitors/TryCatchVisitor.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq.Expressions; + +namespace RecordParser.Visitors +{ + internal class TryCatchVisitor : ExpressionVisitor + { + private readonly ParameterExpression _exceptionHandler; + private readonly ConstantExpression columnIndexExpression; + + public TryCatchVisitor(ParameterExpression exceptionHandler, int columnIndex) + { + _exceptionHandler = exceptionHandler; + columnIndexExpression = Expression.Constant(columnIndex); + } + + protected override Expression VisitBinary(BinaryExpression node) + { + if (node.NodeType != ExpressionType.Assign) + return node; + + var ex = Expression.Parameter(typeof(Exception)); + + var tryCatch = + Expression.TryCatch( + Expression.Block(typeof(void), node), + Expression.Catch( + ex, + Expression.Block( + expressions: new[] + { + Expression.Invoke(_exceptionHandler, ex, columnIndexExpression), + }) + )); + + return tryCatch; + } + } +} \ No newline at end of file From a705334930d6c06e4293c3ce061ba4f11db13089 Mon Sep 17 00:00:00 2001 From: leandromoh Date: Mon, 2 Mar 2026 10:07:30 -0300 Subject: [PATCH 2/6] adds tests --- .../FixedLengthReaderBuilderTest.cs | 71 +++++++++++++++++++ .../VariableLengthReaderBuilderTest.cs | 71 +++++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/RecordParser.Test/FixedLengthReaderBuilderTest.cs b/RecordParser.Test/FixedLengthReaderBuilderTest.cs index 3354192..a9bebf0 100644 --- a/RecordParser.Test/FixedLengthReaderBuilderTest.cs +++ b/RecordParser.Test/FixedLengthReaderBuilderTest.cs @@ -4,6 +4,7 @@ using RecordParser.Builders.Writer; using RecordParser.Parsers; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; @@ -134,6 +135,51 @@ public void Given_trim_is_enabled_should_remove_whitespace_from_both_sides_of_st Baz: "baz")); } + private class MyCustomException : Exception + { + public MyCustomException(string msg) : base(msg) { } + } + + [Fact] + public void Given_partial_invalid_record_called_with_parse_that_handle_exception_should_not_throw_and_parse_valid_values() + { + var reader = new FixedLengthReaderBuilder<(string Name, DateTime Birthday, decimal Money, Color Color1, Color Color2, bool? IsValid)>() + .Map(x => x.Name, 0, 5) + .Map(x => x.Birthday, 5, 10) + .Map(x => x.Money, 15, 7) + .Map(x => x.Color1, 22, 15) + .Map(x => x.Color2, 37, 15) + .Map(x => x.IsValid, 52, 5, text => bool.TryParse(text, out bool result) ? result : throw new MyCustomException($"Invalid boolean value: {text}")) + .Build(); + + var erros = new List<(Exception ex, int index)>(); + var result = reader.Parse(" foo _datehere_ 123.45_invalid_color_ LightBlue _FOO_", (ex, index) => erros.Add((ex, index))); + + result.Should().BeEquivalentTo((Name: "foo", + Birthday: default(DateTime), + Money: 123.45M, + Color1: default(Color), + Color2: Color.LightBlue, + IsValid: default(bool?))); + + erros.Should().SatisfyRespectively( + e1 => + { + e1.ex.Should().Match(ex => ex.Message.StartsWith("String '_datehere_' was not recognized as a valid DateTime.")); + e1.index.Should().Be(1); + }, + e2 => + { + e2.ex.Should().Match(ex => ex.Message.StartsWith("Requested value '_invalid_color_' was not found.")); + e2.index.Should().Be(3); + }, + e3 => + { + e3.ex.Should().Match(ex => ex.Message.StartsWith($"Invalid boolean value: _FOO_")); + e3.index.Should().Be(5); + }); + } + [Fact] public void Given_invalid_record_called_with_try_parse_should_not_throw() { @@ -148,6 +194,31 @@ public void Given_invalid_record_called_with_try_parse_should_not_throw() result.Should().Be(default); } + [Fact] + public void Given_valid_record_called_with_parse_that_handle_exception_should_not_throw_and_parse_valid_values() + { + var reader = new FixedLengthReaderBuilder<(string Name, DateTime Birthday, decimal Money, Color Color1, Color Color2, bool? IsValid)>() + .Map(x => x.Name, 0, 5) + .Map(x => x.Birthday, 5, 10) + .Map(x => x.Money, 15, 7) + .Map(x => x.Color1, 22, 15) + .Map(x => x.Color2, 37, 15) + .Map(x => x.IsValid, 52, 5, text => bool.TryParse(text, out bool result) ? result : throw new MyCustomException($"Invalid boolean value: {text}")) + .Build(); + + var erros = new List<(Exception ex, int index)>(); + var result = reader.Parse(" foo 2026.03.19 123.45 White LightBlue TRUE ", (ex, index) => erros.Add((ex, index))); + + result.Should().BeEquivalentTo((Name: "foo", + Birthday: new DateTime(2026, 03, 19), + Money: 123.45M, + Color1: Color.White, + Color2: Color.LightBlue, + IsValid: true)); + + erros.Should().BeEmpty(); + } + [Fact] public void Given_valid_record_called_with_try_parse_should_set_out_parameter_with_result() { diff --git a/RecordParser.Test/VariableLengthReaderBuilderTest.cs b/RecordParser.Test/VariableLengthReaderBuilderTest.cs index 91f2256..22fa697 100644 --- a/RecordParser.Test/VariableLengthReaderBuilderTest.cs +++ b/RecordParser.Test/VariableLengthReaderBuilderTest.cs @@ -4,6 +4,7 @@ using RecordParser.Builders.Writer; using RecordParser.Parsers; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; @@ -161,6 +162,51 @@ public void Given_trim_is_enabled_should_remove_whitespace_from_both_sides_of_st Baz: "baz")); } + private class MyCustomException : Exception + { + public MyCustomException(string msg) : base(msg) { } + } + + [Fact] + public void Given_partial_invalid_record_called_with_parse_that_handle_exception_should_not_throw_and_parse_valid_values() + { + var reader = new VariableLengthReaderBuilder<(string Name, DateTime Birthday, decimal Money, Color Color1, Color Color2, bool? IsValid)>() + .Map(x => x.Name, 0) + .Map(x => x.Birthday, 1) + .Map(x => x.Money, 2) + .Map(x => x.Color1, 3) + .Map(x => x.Color2, 4) + .Map(x => x.IsValid, 5, text => bool.TryParse(text, out bool result) ? result : throw new MyCustomException($"Invalid boolean value: {text}")) + .Build(";"); + + var erros = new List<(Exception ex, int index)>(); + var result = reader.Parse("foo;_datehere_;123.45;_invalid_color_;LightBlue;_FOO_", (ex, index) => erros.Add((ex, index))); + + result.Should().BeEquivalentTo((Name: "foo", + Birthday: default(DateTime), + Money: 123.45M, + Color1: default(Color), + Color2: Color.LightBlue, + IsValid: default(bool?))); + + erros.Should().SatisfyRespectively( + e1 => + { + e1.ex.Should().Match(ex => ex.Message.StartsWith("String '_datehere_' was not recognized as a valid DateTime.")); + e1.index.Should().Be(1); + }, + e2 => + { + e2.ex.Should().Match(ex => ex.Message.StartsWith("Requested value '_invalid_color_' was not found.")); + e2.index.Should().Be(3); + }, + e3 => + { + e3.ex.Should().Match(ex => ex.Message.StartsWith($"Invalid boolean value: _FOO_")); + e3.index.Should().Be(5); + }); + } + [Fact] public void Given_invalid_record_called_with_try_parse_should_not_throw() { @@ -175,6 +221,31 @@ public void Given_invalid_record_called_with_try_parse_should_not_throw() result.Should().Be(default); } + [Fact] + public void Given_valid_record_called_with_parse_that_handle_exception_should_not_throw_and_parse_valid_values() + { + var reader = new VariableLengthReaderBuilder<(string Name, DateTime Birthday, decimal Money, Color Color1, Color Color2, bool? IsValid)>() + .Map(x => x.Name, 0) + .Map(x => x.Birthday, 1) + .Map(x => x.Money, 2) + .Map(x => x.Color1, 3) + .Map(x => x.Color2, 4) + .Map(x => x.IsValid, 5, text => bool.TryParse(text, out bool result) ? result : throw new MyCustomException($"Invalid boolean value: {text}")) + .Build(";"); + + var erros = new List<(Exception ex, int index)>(); + var result = reader.Parse("foo bar baz;2026.03.19;123.45;White;LightBlue;TRUE", (ex, index) => erros.Add((ex, index))); + + result.Should().BeEquivalentTo((Name: "foo bar baz", + Birthday: new DateTime(2026, 03, 19), + Money: 123.45M, + Color1: Color.White, + Color2: Color.LightBlue, + IsValid: true)); + + erros.Should().BeEmpty(); + } + [Fact] public void Given_valid_record_called_with_try_parse_should_set_out_parameter_with_result() { From fd82e9baf6da36cdc85ef14c9188794bd9395b8b Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 10 Apr 2026 20:44:52 -0300 Subject: [PATCH 3/6] minor visual refactor --- RecordParser/Engines/Reader/ReaderEngine.cs | 3 -- RecordParser/Parsers/FixedLengthReader.cs | 12 ++++---- RecordParser/Parsers/VariableLengthReader.cs | 30 ++++++++++---------- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/RecordParser/Engines/Reader/ReaderEngine.cs b/RecordParser/Engines/Reader/ReaderEngine.cs index 72bf7e8..0aea4e0 100644 --- a/RecordParser/Engines/Reader/ReaderEngine.cs +++ b/RecordParser/Engines/Reader/ReaderEngine.cs @@ -7,9 +7,6 @@ using System.Linq.Expressions; using static RecordParser.Engines.ExpressionHelper; -// todo apagar -internal delegate T FuncSpanIntT(ReadOnlySpan span, int index); - public delegate T FuncSpanT(ReadOnlySpan text); internal delegate T FuncSpanTSafe(ReadOnlySpan text, Action exceptionHandler); diff --git a/RecordParser/Parsers/FixedLengthReader.cs b/RecordParser/Parsers/FixedLengthReader.cs index 9631b4a..0a2d184 100644 --- a/RecordParser/Parsers/FixedLengthReader.cs +++ b/RecordParser/Parsers/FixedLengthReader.cs @@ -4,9 +4,9 @@ namespace RecordParser.Parsers { public interface IFixedLengthReader { + T Parse(ReadOnlySpan line, Action exceptionHandler); T Parse(ReadOnlySpan line); bool TryParse(ReadOnlySpan line, out T result); - T Parse(ReadOnlySpan line, Action exceptionHandler); } internal class FixedLengthReader : IFixedLengthReader @@ -20,6 +20,11 @@ internal FixedLengthReader(FuncSpanT parser, FuncSpanTSafe parserWithExcep this.parserWithExceptionHandler = parserWithExceptionHandler; } + public T Parse(ReadOnlySpan line, Action exceptionHandler) + { + return parserWithExceptionHandler(line, exceptionHandler); + } + public T Parse(ReadOnlySpan line) { return parser(line); @@ -38,10 +43,5 @@ public bool TryParse(ReadOnlySpan line, out T result) return false; } } - - public T Parse(ReadOnlySpan line, Action exceptionHandler) - { - return parserWithExceptionHandler(line, exceptionHandler); - } } } diff --git a/RecordParser/Parsers/VariableLengthReader.cs b/RecordParser/Parsers/VariableLengthReader.cs index fe5d1b0..91bbe27 100644 --- a/RecordParser/Parsers/VariableLengthReader.cs +++ b/RecordParser/Parsers/VariableLengthReader.cs @@ -5,9 +5,9 @@ namespace RecordParser.Parsers { public interface IVariableLengthReader { + T Parse(ReadOnlySpan line, Action exceptionHandler); T Parse(ReadOnlySpan line); bool TryParse(ReadOnlySpan line, out T result); - T Parse(ReadOnlySpan line, Action exceptionHandler); internal string Separator { get; } } @@ -29,13 +29,13 @@ internal VariableLengthReader(FuncSpanArrayT parser, FuncSpanArrayTSafe pa this.quote = (quote, quote.ToString()); } - public T Parse(ReadOnlySpan line) + public T Parse(ReadOnlySpan line, Action exceptionHandler) { var finder = new TextFindHelper(line, delimiter, quote); try { - return parser(in finder); + return parserWithExceptionHandler(in finder, exceptionHandler); } finally { @@ -43,31 +43,31 @@ public T Parse(ReadOnlySpan line) } } - public bool TryParse(ReadOnlySpan line, out T result) + public T Parse(ReadOnlySpan line) { + var finder = new TextFindHelper(line, delimiter, quote); + try { - result = Parse(line); - return true; + return parser(in finder); } - catch + finally { - result = default; - return false; + finder.Dispose(); } } - public T Parse(ReadOnlySpan line, Action exceptionHandler) + public bool TryParse(ReadOnlySpan line, out T result) { - var finder = new TextFindHelper(line, delimiter, quote); - try { - return parserWithExceptionHandler(in finder, exceptionHandler); + result = Parse(line); + return true; } - finally + catch { - finder.Dispose(); + result = default; + return false; } } } From 18ad28c1dbd6bcf1e301f6aa2b8a8db742af4bf4 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 10 Apr 2026 21:50:49 -0300 Subject: [PATCH 4/6] change index to be startIndex --- .../FixedLengthReaderBuilderTest.cs | 6 ++--- .../Reader/MappingReadConfiguration.cs | 5 +++- RecordParser/Engines/Reader/ReaderEngine.cs | 27 +++++++++---------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/RecordParser.Test/FixedLengthReaderBuilderTest.cs b/RecordParser.Test/FixedLengthReaderBuilderTest.cs index a9bebf0..babdd1d 100644 --- a/RecordParser.Test/FixedLengthReaderBuilderTest.cs +++ b/RecordParser.Test/FixedLengthReaderBuilderTest.cs @@ -166,17 +166,17 @@ public void Given_partial_invalid_record_called_with_parse_that_handle_exception e1 => { e1.ex.Should().Match(ex => ex.Message.StartsWith("String '_datehere_' was not recognized as a valid DateTime.")); - e1.index.Should().Be(1); + e1.index.Should().Be(5); }, e2 => { e2.ex.Should().Match(ex => ex.Message.StartsWith("Requested value '_invalid_color_' was not found.")); - e2.index.Should().Be(3); + e2.index.Should().Be(22); }, e3 => { e3.ex.Should().Match(ex => ex.Message.StartsWith($"Invalid boolean value: _FOO_")); - e3.index.Should().Be(5); + e3.index.Should().Be(52); }); } diff --git a/RecordParser/Builders/Reader/MappingReadConfiguration.cs b/RecordParser/Builders/Reader/MappingReadConfiguration.cs index dcbbe8f..2250b87 100644 --- a/RecordParser/Builders/Reader/MappingReadConfiguration.cs +++ b/RecordParser/Builders/Reader/MappingReadConfiguration.cs @@ -42,7 +42,10 @@ public static IEnumerable Merge( }); result = result - .OrderBy(x => x.start) + .Select((x, i) => (item: x, index: i)) + .OrderBy(x => x.item.start) + .ThenBy(x => x.index) + .Select(x => x.item) .ToList(); return result; diff --git a/RecordParser/Engines/Reader/ReaderEngine.cs b/RecordParser/Engines/Reader/ReaderEngine.cs index 0aea4e0..aabb810 100644 --- a/RecordParser/Engines/Reader/ReaderEngine.cs +++ b/RecordParser/Engines/Reader/ReaderEngine.cs @@ -25,7 +25,7 @@ public static Expression> RecordParserSpanCSV(IEnumerable + var blockThatSetProperties = MountSetProperties(instanceVariable, mappedColumns, mapConfig => { return Expression.Call(configParameter, nameof(TextFindHelper.GetValue), Type.EmptyTypes, Expression.Constant(mapConfig.start)); }); @@ -45,13 +45,13 @@ public static Expression> RecordParserSpanCSVSafe(IEnum // variables var instanceVariable = Expression.Variable(typeof(T), "inst"); - var blockThatSetProperties = MountSetProperties(instanceVariable, mappedColumns, (i, mapConfig) => + var blockThatSetProperties = MountSetProperties(instanceVariable, mappedColumns, mapConfig => { return Expression.Call(configParameter, nameof(TextFindHelper.GetValue), Type.EmptyTypes, Expression.Constant(mapConfig.start)); }, - (i, assign) => + (mapConfig, assign) => { - var visitor = new TryCatchVisitor(exceptionHandler, i); + var visitor = new TryCatchVisitor(exceptionHandler, mapConfig.start); var result = visitor.Visit(assign); return result; }); @@ -70,7 +70,7 @@ public static Expression> RecordParserSpanFlat(IEnumerable + var blockThatSetProperties = MountSetProperties(instanceVariable, mappedColumns, mapConfig => { return Slice(line, Expression.Constant(mapConfig.start), Expression.Constant(mapConfig.length.Value)); }); @@ -90,13 +90,13 @@ public static Expression> RecordParserSpanFlatSafe(IEnumerab // variables var instanceVariable = Expression.Variable(typeof(T), "inst"); - var blockThatSetProperties = MountSetProperties(instanceVariable, mappedColumns, (i, mapConfig) => + var blockThatSetProperties = MountSetProperties(instanceVariable, mappedColumns, mapConfig => { return Slice(line, Expression.Constant(mapConfig.start), Expression.Constant(mapConfig.length.Value)); }, - (i, assign) => + (mapConfig, assign) => { - var visitor = new TryCatchVisitor(exceptionHandler, i); + var visitor = new TryCatchVisitor(exceptionHandler, mapConfig.start); var result = visitor.Visit(assign); return result; }); @@ -129,18 +129,15 @@ private static BlockExpression MountBody( private static BlockExpression MountSetProperties( ParameterExpression objectParameter, IEnumerable mappedColumns, - Func getTextValue, - Func assignHandler = null) + Func getTextValue, + Func assignHandler = null) { var replacer = new ParameterReplacerVisitor(objectParameter); var assignsExpressions = new List(); - var i = -1; foreach (var x in mappedColumns) { - i++; - - Expression textValue = getTextValue(i, x); + Expression textValue = getTextValue(x); if (x.ShouldTrim) textValue = Trim(textValue); @@ -170,7 +167,7 @@ private static BlockExpression MountSetProperties( var assign = Expression.Assign(replacer.Visit(x.prop), valueToBeSetExpression); - assignsExpressions.Add(assignHandler?.Invoke(i, assign) ?? assign); + assignsExpressions.Add(assignHandler?.Invoke(x, assign) ?? assign); } assignsExpressions.Add(objectParameter); From ea14e46efedce3994328915dd180119f6e9edf43 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 Apr 2026 02:30:26 -0300 Subject: [PATCH 5/6] adds documentation to methods interface --- RecordParser/Parsers/FixedLengthReader.cs | 38 ++++++++++++++++++++ RecordParser/Parsers/VariableLengthReader.cs | 38 ++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/RecordParser/Parsers/FixedLengthReader.cs b/RecordParser/Parsers/FixedLengthReader.cs index 0a2d184..9203ad0 100644 --- a/RecordParser/Parsers/FixedLengthReader.cs +++ b/RecordParser/Parsers/FixedLengthReader.cs @@ -4,8 +4,46 @@ namespace RecordParser.Parsers { public interface IFixedLengthReader { + /// + /// Converts a span of characters to object of type . + /// In case of failure in property mapping, the callback + /// is called, then parsing continues for the next property. + /// + /// The span of characters to parse. + /// + /// Callback function to be invoked when any property mapping throws an exception. + /// The first parameter is the thrown exception. + /// The second parameter is the index of the property whose mapping failed. + /// + /// + /// An object that is equivalent to the values contained in . + /// T Parse(ReadOnlySpan line, Action exceptionHandler); + + /// + /// Converts a span of characters to object of type . + /// + /// The span of characters to parse. + /// + /// An object that is equivalent to the values contained in . + /// T Parse(ReadOnlySpan line); + + /// + /// Converts a span of characters to object of type . + /// A return value indicates whether the operation succeeded. + /// + /// The span of characters to parse. + /// + /// When this method returns, contains the result of successfully parsing + /// or an undefined value on failure. + /// + /// + /// true if value was successfully parsed; otherwise, false. + /// + /// + /// The parsing is interrupted at the first property mapping failure. + /// bool TryParse(ReadOnlySpan line, out T result); } diff --git a/RecordParser/Parsers/VariableLengthReader.cs b/RecordParser/Parsers/VariableLengthReader.cs index 91bbe27..963c449 100644 --- a/RecordParser/Parsers/VariableLengthReader.cs +++ b/RecordParser/Parsers/VariableLengthReader.cs @@ -5,8 +5,46 @@ namespace RecordParser.Parsers { public interface IVariableLengthReader { + /// + /// Converts a span of characters to object of type . + /// In case of failure in property mapping, the callback + /// is called, then parsing continues for the next property. + /// + /// The span of characters to parse. + /// + /// Callback function to be invoked when any property mapping throws an exception. + /// The first parameter is the thrown exception. + /// The second parameter is the index of the property whose mapping failed. + /// + /// + /// An object that is equivalent to the values contained in . + /// T Parse(ReadOnlySpan line, Action exceptionHandler); + + /// + /// Converts a span of characters to object of type . + /// + /// The span of characters to parse. + /// + /// An object that is equivalent to the values contained in . + /// T Parse(ReadOnlySpan line); + + /// + /// Converts a span of characters to object of type . + /// A return value indicates whether the operation succeeded. + /// + /// The span of characters to parse. + /// + /// When this method returns, contains the result of successfully parsing + /// or an undefined value on failure. + /// + /// + /// true if value was successfully parsed; otherwise, false. + /// + /// + /// The parsing is interrupted at the first property mapping failure. + /// bool TryParse(ReadOnlySpan line, out T result); internal string Separator { get; } From 674e67ff49101044caf2f8a62329c87fcd614d8a Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 Apr 2026 12:48:15 -0300 Subject: [PATCH 6/6] improve doc new method --- RecordParser/Parsers/FixedLengthReader.cs | 2 +- RecordParser/Parsers/VariableLengthReader.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RecordParser/Parsers/FixedLengthReader.cs b/RecordParser/Parsers/FixedLengthReader.cs index 9203ad0..89a04a9 100644 --- a/RecordParser/Parsers/FixedLengthReader.cs +++ b/RecordParser/Parsers/FixedLengthReader.cs @@ -13,7 +13,7 @@ public interface IFixedLengthReader /// /// Callback function to be invoked when any property mapping throws an exception. /// The first parameter is the thrown exception. - /// The second parameter is the index of the property whose mapping failed. + /// The second parameter is the specified index of the property whose mapping failed. /// /// /// An object that is equivalent to the values contained in . diff --git a/RecordParser/Parsers/VariableLengthReader.cs b/RecordParser/Parsers/VariableLengthReader.cs index 963c449..20d9b44 100644 --- a/RecordParser/Parsers/VariableLengthReader.cs +++ b/RecordParser/Parsers/VariableLengthReader.cs @@ -14,7 +14,7 @@ public interface IVariableLengthReader /// /// Callback function to be invoked when any property mapping throws an exception. /// The first parameter is the thrown exception. - /// The second parameter is the index of the property whose mapping failed. + /// The second parameter is the specified index of the property whose mapping failed. /// /// /// An object that is equivalent to the values contained in .