This repository was archived by the owner on Jul 28, 2025. It is now read-only.
generated from nhs-england-tools/repository-template
-
Notifications
You must be signed in to change notification settings - Fork 1
feat: FileParser #19
Merged
Merged
feat: FileParser #19
Changes from all commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
40b0016
WIP: DTOSS-8925 file transform skeleton
ianfnelson 200229c
feat: Started implementing File Transform Function
alex-clayton-1 c90818c
feat: Transform function downloads file from blob storage
alex-clayton-1 56588d7
test: Started writing tests for FileTransformFunction
alex-clayton-1 2fe6582
test: Added extra test for downloading blob
alex-clayton-1 1318ab6
refactor: Extracted some code into a separate method
alex-clayton-1 770fce3
feat: Added AppConfiguration to FileTransformFunction
alex-clayton-1 9f3a883
Merge branch 'main' into feat/DTOSS-8925-transform-skeleton
alex-clayton-1 16d2fd1
feat: csvHelper nuget added, FileParser class updated
Warren-Pitterson 5835466
Merge branch 'main' into feat/DTOSS-8753-CSV-Parser
Warren-Pitterson 92530bd
feat: refactor to FileParser, DI to file transform
Warren-Pitterson 9710809
test: add file parser tests
Warren-Pitterson d37c270
Merge branch 'main' into feat/DTOSS-8753-CSV-Parser
Warren-Pitterson ca75c8b
refactor: extract fileRecordtype to Enum class, refactor FileParser f…
Warren-Pitterson a2f6f04
test: wrong scenario removed
Warren-Pitterson e53a9eb
fix: handle empty record identifiers gracefully
Warren-Pitterson 89c2f5f
feat: FileParser, Headers removed, tests updated, SonarQube fixes, Te…
Warren-Pitterson 9066d62
Merge branch 'main' into feat/DTOSS-8753-CSV-Parser
Warren-Pitterson f10db2b
fix: file formatting fix
Warren-Pitterson 1ef28f2
fix: removed enum suffix
Warren-Pitterson c2e48fb
test: test performance
Warren-Pitterson 463acbb
fix: removed skip
Warren-Pitterson cf62758
refactor: move enum back within class, renamed const to be PascalCase…
Warren-Pitterson fec6e48
refactor: FileHeaderRecordMap and FileTrailerRecordMap extracted into…
Warren-Pitterson 0c39238
refactor: ParseDataRecord
Warren-Pitterson 623b223
test: removed fileParser tests
Warren-Pitterson b6ced87
fix: object initialiser added
Warren-Pitterson b7e77c0
refactor: FilerParserTests, extracted out of FileTransformFunction an…
Warren-Pitterson c6f0a55
test: updated to correct namespace
Warren-Pitterson 63034d2
refactor: remove unused project reference
Warren-Pitterson 36241d0
refactor: nested classmap classes into FileParser class
Warren-Pitterson 4451d5f
fix: added missing brace
Warren-Pitterson b368707
refactor: variable names updated to match
Warren-Pitterson 8359e12
refactor: extract CSV reader helper methods for better maintainability
Warren-Pitterson edd035c
test: move test class, changed namespace
Warren-Pitterson 96c68a5
chore: formatting
Warren-Pitterson 001fad1
feat: FileParser uses test files instead of string literals
Warren-Pitterson 00e46f3
test: extracted helper methods and removed null coalescing operator f…
Warren-Pitterson bf3f228
refactor: moved null check
Warren-Pitterson 0919089
fix: removed unnecessary trim extension method
Warren-Pitterson 0538c16
feat: new line ending added to .csv test files and renamed valid file
Warren-Pitterson b480425
Merge branch 'main' into feat/DTOSS-8753-CSV-Parser
Warren-Pitterson 5b6b7a2
test: move test file location and extension
Warren-Pitterson File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
135 changes: 133 additions & 2 deletions
135
src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/FileParser.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,143 @@ | ||
| using CsvHelper; | ||
| using CsvHelper.Configuration; | ||
| using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; | ||
| using System.Globalization; | ||
| using System.Text; | ||
|
|
||
| namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents; | ||
|
|
||
| public class FileParser : IFileParser | ||
| { | ||
| private const string HeaderIdentifier = "NBSSAPPT_HDR"; | ||
| private const string FieldsIdentifier = "NBSSAPPT_FLDS"; | ||
| private const string DataIdentifier = "NBSSAPPT_DATA"; | ||
| private const string TrailerIdentifier = "NBSSAPPT_END"; | ||
| private const int RecordTypeIdentifier = 0; | ||
|
|
||
| /// <summary> | ||
| /// Parse a stream of appointment data | ||
| /// </summary> | ||
| public ParsedFile Parse(Stream stream) | ||
| { | ||
| // TODO - implement this | ||
| throw new NotImplementedException(); | ||
| if (stream == null) | ||
| { | ||
| throw new ArgumentNullException(nameof(stream), "Stream cannot be null"); | ||
| } | ||
|
|
||
| var result = new ParsedFile(); | ||
|
|
||
| using var reader = CreateStreamReader(stream); | ||
| using var csv = CreateCsvReader(reader); | ||
|
|
||
| var rowNumber = 0; | ||
| var fields = new List<string>(); | ||
|
|
||
| while (csv.Read()) | ||
| { | ||
| var recordIdentifier = GetFieldValue(csv, RecordTypeIdentifier); | ||
|
|
||
| switch (recordIdentifier) | ||
Warren-Pitterson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| case HeaderIdentifier: | ||
| result.FileHeader = ParseHeader(csv); | ||
| break; | ||
|
|
||
| case FieldsIdentifier: | ||
| fields = ParseFields(csv); | ||
| break; | ||
|
|
||
| case DataIdentifier: | ||
| rowNumber++; | ||
| result.DataRecords.Add(ParseDataRecord(csv, fields, rowNumber)); | ||
| break; | ||
|
|
||
| case TrailerIdentifier: | ||
| result.FileTrailer = ParseTrailer(csv); | ||
| break; | ||
|
|
||
| default: | ||
| throw new InvalidOperationException($"Unknown record identifier: {recordIdentifier}"); | ||
| } | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| private static List<string> ParseFields(CsvReader csv) | ||
| { | ||
| return Enumerable.Range(1, csv.Parser.Count - 1) | ||
| .Select(i => GetFieldValue(csv, i)) | ||
| .Where(x => !string.IsNullOrEmpty(x)) | ||
| .ToList()!; | ||
| } | ||
|
|
||
| private static string? GetFieldValue(CsvReader csv, int index) => index < csv.Parser.Count ? csv.GetField(index) : null; | ||
| private static StreamReader CreateStreamReader(Stream stream) => new(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true); | ||
| private static FileHeaderRecord ParseHeader(CsvReader csv) => csv.GetRecord<FileHeaderRecord>(); | ||
| private static FileTrailerRecord ParseTrailer(CsvReader csv) => csv.GetRecord<FileTrailerRecord>(); | ||
| private static CsvReader CreateCsvReader(StreamReader reader) | ||
| { | ||
| var config = new CsvConfiguration(CultureInfo.InvariantCulture) | ||
| { | ||
| Delimiter = "|", | ||
| Quote = '"', | ||
| Escape = '\\', | ||
| HasHeaderRecord = false, | ||
| Mode = CsvMode.RFC4180, | ||
| BadDataFound = null | ||
| }; | ||
|
|
||
| var csv = new CsvReader(reader, config); | ||
| csv.Context.RegisterClassMap<FileHeaderRecordMap>(); | ||
| csv.Context.RegisterClassMap<FileTrailerRecordMap>(); | ||
|
|
||
| return csv; | ||
| } | ||
|
|
||
| private static FileDataRecord ParseDataRecord(CsvReader csv, List<string> columnHeadings, int rowNumber) | ||
| { | ||
| if (columnHeadings.Count == 0) | ||
| { | ||
| throw new InvalidOperationException("Field headers (NBSSAPPT_FLDS) must appear before data records."); | ||
| } | ||
|
|
||
| const int dataFieldStartIndex = 1; | ||
|
|
||
| var record = new FileDataRecord { RowNumber = rowNumber }; | ||
|
|
||
| foreach (var (heading, index) in columnHeadings.Select((header, index) => (header, index + dataFieldStartIndex))) | ||
Warren-Pitterson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| if (index < csv.Parser.Count) | ||
| { | ||
| record.Fields[heading] = GetFieldValue(csv, index) ?? string.Empty; | ||
| } | ||
| } | ||
|
|
||
| return record; | ||
| } | ||
|
|
||
| public sealed class FileTrailerRecordMap : ClassMap<FileTrailerRecord> | ||
| { | ||
| public FileTrailerRecordMap() | ||
| { | ||
| Map(m => m.RecordTypeIdentifier).Index(0); | ||
| Map(m => m.ExtractId).Index(1); | ||
| Map(m => m.TransferEndDate).Index(2); | ||
| Map(m => m.TransferEndTime).Index(3); | ||
| Map(m => m.RecordCount).Index(4); | ||
| } | ||
| } | ||
|
|
||
| public sealed class FileHeaderRecordMap : ClassMap<FileHeaderRecord> | ||
| { | ||
| public FileHeaderRecordMap() | ||
| { | ||
| Map(m => m.RecordTypeIdentifier).Index(0); | ||
| Map(m => m.ExtractId).Index(1); | ||
| Map(m => m.TransferStartDate).Index(2); | ||
| Map(m => m.TransferStartTime).Index(3); | ||
| Map(m => m.RecordCount).Index(4); | ||
| } | ||
| } | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.