/// <summary> /// Process a hierarchical Content record. /// </summary> private FileRecordHierarchyLinker ProcessHierarchicalContent(FileRecordHierarchyLinker linker, List <FileRecord> records, FileRecord record) { if (!CheckNotHeaderOrTrailer(record)) { return(linker); } // Make sure we know what the record is. if (!_hierarchy.ContainsKey(record.RecordIdentifier)) { record.Messages.Add(MessageType.Error, "Record identifier is unknown."); return(linker); } // Ensure that the record is valid from a hierarchy position perspective. var isValid = true; var curr = _hierarchy[record.RecordIdentifier]; var prev = _hierarchy[records.Last(x => x.Level >= 0).RecordIdentifier]; if (curr.Level == prev.Level) { if (prev.Parent.Children.ContainsKey(record.RecordIdentifier)) { linker = linker.Parent; } else { isValid = record.Messages.Add(MessageType.Error, "Record identifier is not a valid peer of the previous record.") == null; } } else if (curr.Level < prev.Level) { if (FindUpHierachy(prev, record)) { linker = MoveUpHierarchy(linker, prev, curr.Parent, record); } else { isValid = record.Messages.Add(MessageType.Error, "Record identifier is not valid within current traversed hierarchy.") == null; } } else { if (!prev.Children.ContainsKey(record.RecordIdentifier)) { isValid = record.Messages.Add(MessageType.Error, "Record identifier is not a direct descendent of the previous record.") == null; } } // Check that record is in alignment with the hierarchy attribute configuration. var parentValue = linker.Value; if (isValid) { linker = linker.AddChild(curr, record); if (linker.Index > 0 && !curr.HierarchyReflector.IsCollection) { isValid = false; curr.HierarchyReflector.CreateErrorMessage(record, "{0} does not support multiple records; too many provided."); } if (curr.HierarchyReflector.IsCollection && curr.HierarchyReflector.FileHierarchy.MaxCount > 0 && linker.Index >= curr.HierarchyReflector.FileHierarchy.MaxCount) { curr.HierarchyReflector.CreateErrorMessage(record, "{0} must not exceed {2} records(s); too many provided.", curr.HierarchyReflector.FileHierarchy.MaxCount); } record.Level = curr.Level; } else { record.Level = -1; } // Parse the record to get the value and only update the linker where valid. record.Value = _fileFormat.ReadCreateRecordValueInternal(record, curr.HierarchyReflector.FileHierarchy.ChildType ?? curr.RecordReflector.Type); curr.RecordReflector.Validate(record); if (isValid) { linker.Value = record.Value; } RecordProcess(record); return(linker); }
/// <summary> /// Move/traverse up hierarchy for the item and confirm alignment with the hierarchy attribute configuration(s) and update the values accordingly. /// </summary> private FileRecordHierarchyLinker MoveUpHierarchy(FileRecordHierarchyLinker linker, FileRecordHierarchyItem from, FileRecordHierarchyItem to, FileRecord record, bool final = false) { if (from == null) { return(null); } if (!final && from == to) { return(linker); } foreach (var child in from.Children) { var fhr = child.Value.HierarchyReflector; var fha = child.Value.HierarchyReflector.FileHierarchy; var count = linker.GetChildCount(fhr.RecordIdentifier); // Validate alignment to configuration. if (fha.IsMandatory && count == 0) { fhr.CreateErrorMessage(record, "{0} is required; no record found."); continue; } // Minimum count check only honoured where at least one record found; otherwise, use IsMandatory to catch. if (fhr.IsCollection && count > 0 && fha.MinCount > 0 && count < fha.MinCount) { fhr.CreateErrorMessage(record, "{0} must have at least {2} records(s); additional required.", fha.MinCount); } // Update the values as we move up the hierarchy. var vals = linker.GetChildValues(fhr.RecordIdentifier); fhr.SetValue(linker.Value, vals); } return(MoveUpHierarchy(linker.Parent, from.Parent, to, record, final)); }
/// <summary> /// Initializes a new instance of the <see cref="FileValidationException"/> class. /// </summary> /// <param name="fileValidation">The <see cref="FileValidation"/> rule that caused the exception.</param> /// <param name="message">The message.</param> /// <param name="record">The <see cref="FileRecord"/> where applicable.</param> internal FileValidationException(FileValidation fileValidation, string message, FileRecord record = null) : base(message) { FileValidation = fileValidation; Record = record; }
/// <summary> /// Compose the underlying children records. /// </summary> private void ComposeChildren(List <FileRecord> records, FileRecordReflector frf, FileRecord current) { foreach (var child in frf.Children) { var obj = child.GetValue(current.Value); if (child.IsCollection) { foreach (var item in (System.Collections.IEnumerable)obj) { ComposeRecord(records, child.PropertyType, current.Level + 1, child.RecordIdentifier, item, true); } } else { ComposeRecord(records, child.PropertyType, current.Level + 1, child.RecordIdentifier, obj, true); } } }
/// <summary> /// Enables post-processing when writing the line data <see cref="StringBuilder"/>. /// </summary> /// <param name="record">The related <see cref="FileRecord"/>.</param> /// <param name="sb">The line data <see cref="StringBuilder"/>.</param> internal void WritePostProcessLineDataInternal(FileRecord record, StringBuilder sb) { WritePostProcessLineData(record, sb); }
/// <summary> /// Enables post-processing when writing the line data <see cref="StringBuilder"/>. /// </summary> /// <param name="record">The related <see cref="FileRecord"/>.</param> /// <param name="sb">The line data <see cref="StringBuilder"/>.</param> protected virtual void WritePostProcessLineData(FileRecord record, StringBuilder sb) { }
/// <summary> /// Writes the indexed <paramref name="column"/> from the <paramref name="record"/> to the line data <see cref="StringBuilder"/>. /// </summary> /// <param name="fcr">The corresponding <see cref="FileColumnReflector"/> configuration.</param> /// <param name="record">The related <see cref="FileRecord"/>.</param> /// <param name="column">The column index.</param> /// <param name="sb">The line data <see cref="StringBuilder"/>.</param> /// <returns><c>true</c> indicates that the column write was successful; otherwise, <c>false</c>.</returns> /// <remarks>Implementers must use <see cref="FileColumnReflector"/> <see cref="FileColumnReflector.StringWidthCorrector(FileRecord, ref string)"/> to /// validate and correct the column string prior to updating the line data to ensure that the configured file and column formatting rules are adhered to.</remarks> internal bool WriteColumnToLineDataInternal(FileColumnReflector fcr, FileRecord record, int column, StringBuilder sb) { return(WriteColumnToLineData(fcr, record, column, sb)); }
/// <summary> /// Writes the indexed <paramref name="column"/> from the <paramref name="record"/> to the line data <see cref="StringBuilder"/>. /// </summary> /// <param name="fcr">The corresponding <see cref="FileColumnReflector"/> configuration.</param> /// <param name="record">The related <see cref="FileRecord"/>.</param> /// <param name="column">The column index.</param> /// <param name="sb">The line data <see cref="StringBuilder"/>.</param> /// <returns><c>true</c> indicates that the column write was successful; otherwise, <c>false</c>.</returns> /// <remarks>Implementers must use <see cref="FileColumnReflector"/> <see cref="FileColumnReflector.StringWidthCorrector(FileRecord, ref string)"/> to /// validate and correct the column string prior to updating the line data to ensure that the configured file and column formatting rules are adhered to.</remarks> protected abstract bool WriteColumnToLineData(FileColumnReflector fcr, FileRecord record, int column, StringBuilder sb);
/// <summary> /// Read the <see cref="FileRecord"/> creating the corresponding value (<paramref name="type"/> instance). /// </summary> /// <param name="record">The <see cref="FileRecord"/>.</param> /// <param name="type">The identified <see cref="Type"/> to be created.</param> /// <returns>The <see cref="Type"/> instance.</returns> internal object ReadCreateRecordValueInternal(FileRecord record, Type type) { return(ReadCreateRecordValue(record, type)); }
/// <summary> /// Read the <see cref="FileRecord"/> creating the corresponding value (<paramref name="type"/> instance). /// </summary> /// <param name="record">The <see cref="FileRecord"/>.</param> /// <param name="type">The identified <see cref="Type"/> to be created.</param> /// <returns>The <see cref="Type"/> instance.</returns> protected abstract object ReadCreateRecordValue(FileRecord record, Type type);
/// <summary> /// Reads the <see cref="FileRecord"/> extracting the <see cref="FileRecord.RecordIdentifier"/>. /// </summary> /// <param name="record">The <see cref="FileRecord"/>.</param> /// <returns>The record identifier where applicable; otherwise, <c>null</c>.</returns> protected abstract string ReadRecordIdentifier(FileRecord record);
/// <summary> /// Reads the <see cref="FileRecord"/> extracting the <see cref="FileRecord.RecordIdentifier"/>. /// </summary> /// <param name="record">The <see cref="FileRecord"/>.</param> /// <returns>The record identifier where applicable; otherwise, <c>null</c>.</returns> internal string ReadRecordIdentifierInternal(FileRecord record) { return(ReadRecordIdentifier(record)); }
/// <summary> /// Writes the indexed <paramref name="column"/> from the <paramref name="record"/> to the line data <see cref="StringBuilder"/>. /// </summary> /// <param name="fcr">The corresponding <see cref="FileColumnReflector"/> configuration.</param> /// <param name="record">The related <see cref="FileRecord"/>.</param> /// <param name="column">The column index.</param> /// <param name="sb">The line data <see cref="StringBuilder"/>.</param> /// <returns><c>true</c> indicates that the column write was successful; otherwise, <c>false</c>.</returns> protected override bool WriteColumnToLineData(FileColumnReflector fcr, FileRecord record, int column, StringBuilder sb) { // Delimit each column. Check.NotNull(sb, nameof(sb)); if (column > 0) { sb.Append(Delimiter); } // Get the string value and correct the width if needed. var str = column > Check.NotNull(record, nameof(record)).Columns.Count ? null : record.Columns[column]; // Override if it is the hierarchy column. if (HierarchyColumnIndex.HasValue && HierarchyColumnIndex.Value == column) { str = record.RecordIdentifier; } // Where null this means there is nothing specific to write. if (string.IsNullOrEmpty(str)) { return(true); } // Validate/correct the string value to ensure column width conformance. if (!Check.NotNull(fcr, nameof(fcr)).StringWidthCorrector(record, ref str)) { return(false); } // Check if the column content contains the delimiter and handle accordingly. var qualify = fcr.PropertyTypeCode == TypeCode.String && TextQualifier != NoCharacter && !TextQualifierOnlyWithDelimiterOnWrite; if (str.IndexOf(Delimiter) >= 0) { if (TextQualifier == NoCharacter) { record.Messages.Add(MessageType.Error, "Text delimiter character found inside column text; no text qualifier has been specified and would result in errant record."); return(false); } else { qualify = true; } } // Double qualify a qualifier inside of the text. if (TextQualifier != NoCharacter) { if (!qualify && str.IndexOf(TextQualifier) >= 0) { qualify = true; } if (qualify) { str = str.Replace(TextQualifier.ToString(System.Globalization.CultureInfo.InvariantCulture), new string(TextQualifier, 2)); } } if (qualify) { sb.Append(TextQualifier); } sb.Append(str); if (qualify) { sb.Append(TextQualifier); } return(true); }
/// <summary> /// Reads the <see cref="FileRecord"/> extracting the <see cref="FileRecord.RecordIdentifier"/> and <see cref="FileRecord.Columns"/>. /// </summary> /// <param name="record">The <see cref="FileRecord"/>.</param> /// <returns>The record identifier where applicable; otherwise, <c>null</c>.</returns> protected override string ReadRecordIdentifier(FileRecord record) { char c = Char.MinValue; var sb = new StringBuilder(256); var cols = new List <string>(); bool isQualified = false; // Iterate and split the record data into multiple columns. for (int i = 0; i < Check.NotNull(record, nameof(record)).LineData.Length; i++) { c = record.LineData[i]; if (c == Delimiter) { // Where not qualified it is the end of the column. if (!isQualified) { cols.Add(sb.ToString()); sb.Clear(); continue; } } if (c == TextQualifier) { if (isQualified) { // Where end of record data, this is terminating the column correctly. if (i >= record.LineData.Length - 1) { isQualified = false; break; } // Where next character is delimiter, then terminating column correctly. if (record.LineData[i + 1] == Delimiter) { isQualified = false; continue; } // Where next charater is same, then it is escaped as single char. if (record.LineData[i + 1] == TextQualifier) { sb.Append(TextQualifier); i++; continue; } // Handle the text qualifier issue. switch (TextQualifierHandling) { case TextQualifierHandling.LooseAllow: record.Messages.Add(MessageType.Warning, QualifierInsideQualifiedText, i + 1); break; case TextQualifierHandling.LooseSkip: record.Messages.Add(MessageType.Warning, QualifierInsideQualifiedText, i + 1); continue; default: record.Messages.Add(MessageType.Error, QualifierInsideQualifiedText, i + 1); return(null); } } else { if (sb.Length == 0) { isQualified = true; continue; } if (i < record.LineData.Length - 1 && record.LineData[i + 1] == TextQualifier) { sb.Append(TextQualifier); i++; continue; } // Handle the text qualifier issue. switch (TextQualifierHandling) { case TextQualifierHandling.LooseAllow: record.Messages.Add(MessageType.Warning, QualifierInsideUnqualifiedText, i + 1); break; case TextQualifierHandling.LooseSkip: record.Messages.Add(MessageType.Warning, QualifierInsideUnqualifiedText, i + 1); continue; default: record.Messages.Add(MessageType.Error, QualifierInsideUnqualifiedText, i + 1); return(null); } } } sb.Append(c); } // Finish up the column processing. if (isQualified) { switch (TextQualifierHandling) { case TextQualifierHandling.LooseAllow: case TextQualifierHandling.LooseSkip: record.Messages.Add(MessageType.Warning, QualifierNotClosedText); break; default: record.Messages.Add(MessageType.Error, QualifierNotClosedText); return(null); } } if (sb.Length > 0) { cols.Add(sb.ToString()); } record.Columns = cols; // Determine the record identifier. if (IsHierarchical) { if (HierarchyColumnIndex >= record.Columns.Count) { record.Messages.Add(MessageType.Error, "Unable to determine the code identitier as the hierarchy column index is outside the bounds of the number of columns found for the record."); } else { return(record.Columns[HierarchyColumnIndex.Value]); } } return(null); }
/// <summary> /// Initializes a new instance of the <see cref="FileReaderLoggerData"/> class. /// </summary> /// <param name="operationResult">The <see cref="FileOperationResult"/>.</param> /// <param name="fileRecord">The corresponding <see cref="T:FileRecord"/> to the logged message.</param> /// <param name="messageItem">The specific <see cref="T:MessageItem"/> from the <see cref="FileRecord"/> being logged.</param> public FileReaderLoggerData(FileOperationResult operationResult, FileRecord fileRecord = null, MessageItem messageItem = null) { OperationResult = operationResult ?? throw new ArgumentNullException(nameof(operationResult)); FileRecord = fileRecord; MessageItem = messageItem; }