/// <summary> /// The method evaluates whether give name equals to the header name set /// in the descriptor. /// </summary> /// <remarks> /// This internal method detects its result by comparing the determined header /// name either by using lower and upper cases or by ignoring it. /// </remarks> /// <param name="header"> /// The name to be compared. /// </param> /// <param name="exactly"> /// If true, then an case-sensitive string comparison is performed. /// Otherwise as case-insensitive string comparison is performed. /// </param> /// <param name="descriptor"> /// The column descriptor to get header information from. /// </param> /// <returns> /// True, if given name equals the header name within the descriptor, /// and false if not. /// </returns> private static Boolean IsHeaderName(String header, Boolean exactly, ItemDescriptor descriptor) { String other = CsvImporter <TInstance> .GetHeaderName(descriptor); StringComparison flags = exactly ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; return(String.Compare(header, other, flags) == 0); }
/// <summary> /// This method tries to load all values from given file using given /// settings. /// </summary> /// <remarks> /// The settings parameter describes how the data within given file should be /// processed. For example, users may define the expected culture, the expected /// file encoding, which separator is used and so on. /// </remarks> /// <param name="filename"> /// The fully qualified path of the input file. /// </param> /// <param name="settings"> /// The settings to be used to process file data. /// </param> /// <returns> /// A list of classes of <typeparamref name="TInstance"/>. /// </returns> /// <exception cref="ArgumentException"> /// This exception is thrown in case of an invalid file name. /// </exception> /// <exception cref="FileNotFoundException"> /// This exception is thrown in case of a file with given name does not /// exist. /// </exception> public static IEnumerable <TInstance> Load(String filename, CsvSettings settings) { if (String.IsNullOrWhiteSpace(filename)) { throw new ArgumentException("The filename should not be empty.", nameof(filename)); } if (!File.Exists(filename)) { throw new FileNotFoundException($"File {filename} could not be found."); } using (Stream stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { return(CsvImporter <TInstance> .Load(stream, settings)); } }
/// <summary> /// This method tries to determine whether given list of cells contain /// all and only header names. /// </summary> /// <remarks> /// The order of headers does not matter in this validation because of /// it only checks whether all the header names are included. Further, /// each name comparison is done by ignoring upper and lower cases. /// </remarks> /// <param name="headers"> /// The list of cells to be verified, which should contain header names. /// </param> /// <param name="descriptors"> /// The list of descriptors representing a single line. /// </param> /// <returns> /// True is returned when all header names have occurred at least once, /// no matter at which position a header name has occurred. Otherwise, /// false is returned. /// </returns> private static Boolean IsHeaderLine(List <String> headers, List <ItemDescriptor> descriptors) { Int32 verified = 0; for (Int32 index = 0; index < headers.Count; index++) { foreach (ItemDescriptor descriptor in descriptors) { if (CsvImporter <TInstance> .IsHeaderName(headers[index], false, descriptor)) { verified++; break; } } } return(verified == headers.Count); }
/// <summary> /// This method tries to validate given headers by applying exact /// validation rules. /// </summary> /// <remarks> /// Exact validation rules means in detail that each string in given headers /// must exactly match its corresponding header name. Furthermore, exactly /// match means that each header name is compared by applying a case-sensitive /// name check. It also means that each header position must be the same /// position which is defined within given descriptors. /// </remarks> /// <param name="headers"> /// The list of header names to be validated. /// </param> /// <param name="descriptors"> /// The list of descriptors representing a single line. /// </param> /// <exception cref="FormatException"> /// This exception is thrown as soon as one of the header validation rules /// has been violated. /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// This exception might be thrown if accessing the lists fails. /// </exception> private static void ValidateHeader(List <String> headers, List <ItemDescriptor> descriptors) { // Keep in mind, the column validation has already checked the column count. And it is trusted // in that an Argument Out Of Range Exception is thrown if there was a made mistake beforehand. for (Int32 index = 0; index < headers.Count; index++) { String header = headers[index]; ItemDescriptor descriptor = descriptors[index]; if (!CsvImporter <TInstance> .IsHeaderName(header, true, descriptor)) { throw new FormatException( $"Header validation mismatch. Header name \"{header}\" at column {index} does not fit " + $"to the expected header name \"{CsvImporter<TInstance>.GetHeaderName(descriptor)}.\""); } } }
/// <summary> /// This method tries to process given line according to given set of parameters. /// </summary> /// <remarks> /// This method processes given line by splitting it into its pieces and then by /// trying to convert each element into its type-safe object expression. /// </remarks> /// <param name="line"> /// The line to be processed. /// </param> /// <param name="separator"> /// The separator to be used to split given line at. /// </param> /// <param name="exactly"> /// An exception of type 'FormatException' is throw if this parameter is 'true' and /// converting one of the values has failed. Otherwise, each variable is set to a /// default value. Which default value will be used in such a failure case depends /// on current data type. For example, strings using <null> as default value, /// 'false' is used as default for Boolean types, and the corresponding max-value is /// used for all other types. /// </param> /// <param name="culture"> /// The culture information to be used for data conversion. /// </param> /// <param name="mapping"> /// The value mappings to be used for an extended data interpretation. /// </param> /// <param name="descriptors"> /// A list of data type descriptor items that help interpreting data. /// </param> /// <returns> /// An instance of class of type <typeparamref name="TInstance"/>. /// </returns> /// <exception cref="InvalidOperationException"> /// This exception is thrown for example if <typeparamref name="TInstance"/> does not /// provide a default constructor. /// </exception> /// <exception cref="FormatException"> /// This exception is thrown for example if 'exactly' is set to 'true' and one of the /// data items could not be converted. /// </exception> /// <exception cref="NotSupportedException"> /// This exception is thrown as soon as a class of type <typeparamref name="TInstance"/> /// wants to use an unsupported data type. /// </exception> private static TInstance ProcessLine(String line, Char separator, Boolean exactly, CultureInfo culture, CsvMappings mapping, List <ItemDescriptor> descriptors) { TInstance result = CsvImporter <TInstance> .Construct(); // throws List <String> values = ProcessHelper.SplitIntoCells(line, separator); for (Int32 index = 0; index < values.Count; index++) { // A simple break is possible because of column validation has been done already. if (index >= descriptors.Count) { break; } String value = values[index]; PropertyInfo property = descriptors[index].Origin; Type type = property.PropertyType; property.SetValue(result, TypeConverter.IntoObject(value, type, exactly, culture, mapping)); // throws } return(result); }
/// <summary> /// This method tries to load all values from given stream using given /// settings. /// </summary> /// <remarks> /// The settings parameter describes how the data within given file should be /// processed. For example, users may define the expected culture, the expected /// file encoding, which separator is used and so on. /// </remarks> /// <param name="stream"> /// The stream to read data from. /// </param> /// <param name="settings"> /// The settings to be used to process stream data. /// </param> /// <returns> /// A list of classes of <typeparamref name="TInstance"/>. /// </returns> /// <exception cref="ArgumentNullException"> /// This exception is thrown either if given stream or if given settings is /// invalid. /// </exception> /// <exception cref="ArgumentException"> /// This exception is thrown in case of given stream does not have read access. /// </exception> public static IEnumerable <TInstance> Load(Stream stream, CsvSettings settings) { List <TInstance> values = new List <TInstance>(); if (stream == null) { throw new ArgumentNullException(nameof(stream), $"The stream to read the data from is invalid."); } if (!stream.CanRead) { throw new ArgumentException("No read access to given stream.", nameof(stream)); } if (settings == null) { throw new ArgumentNullException(nameof(settings), "The CSV settings are invalid."); } TypeDescriptor descriptor = TypeProcessor.LoadDescriptor <TInstance>(); // NOTE: Keep in mind! All item descriptors are confirmed and ordered already. // This means they are in the right (which means expected) order! Therefore, // something like "search for right column" is absolutely redundant. List <ItemDescriptor> items = descriptor.Settings.ToList(); List <String> lines = new List <String>(); using (StreamReader reader = new StreamReader(stream, settings.Encoding)) { while (!reader.EndOfStream) { String line = reader.ReadLine(); if (!String.IsNullOrWhiteSpace(line)) { lines.Add(line); } } } if (lines.Count > 0) { Char separator = settings.Separator; Boolean exactly = settings.Exactly; CultureInfo culture = settings.Culture; Boolean heading = settings.Heading; CsvMappings mapping = settings.Mappings; CsvImporter <TInstance> .ValidateColumns(lines, separator, exactly, items); // throws Int32 index = 0; List <String> cells = ProcessHelper.SplitIntoCells(lines[index], separator); if (CsvImporter <TInstance> .IsHeaderLine(cells, items)) { // An exact header validation is performed, but only if the first // line represents a header and Heading and Exactly are both enabled. // In such a case a Format Exception is thrown as soon as the header // does not exactly fit! In this conjunction, the validation must pass // the case-sensitive check as well as the position check. if (heading && exactly) { CsvImporter <TInstance> .ValidateHeader(cells, items); // throws } index++; } for (; index < lines.Count; index++) { values.Add(CsvImporter <TInstance> .ProcessLine(lines[index], separator, exactly, culture, mapping, items)); } } return(values); }
/// <summary> /// This method tries to load all values from given stream. /// </summary> /// <remarks> /// This method performes loading of data with default settings. Using /// default settings means that the header is processed, but only if one /// exist. Further, The information for header processing is taken from /// column attributes or from property names. /// </remarks> /// <param name="stream"> /// The stream to read data from. /// </param> /// <returns> /// A list of classes of <typeparamref name="TInstance"/>. /// </returns> /// <exception cref="ArgumentNullException"> /// This exception is thrown in case of given stream is invalid. /// </exception> /// <exception cref="ArgumentException"> /// This exception is thrown in case of given stream does not have read access. /// </exception> public static IEnumerable <TInstance> Load(Stream stream) { return(CsvImporter <TInstance> .Load(stream, new CsvSettings())); }
/// <summary> /// This method tries to load all values from given file. /// </summary> /// <remarks> /// This method performes loading of data with default settings. Using /// default settings means that the header is processed, but only if one /// exist. Further, the information for header processing is taken from /// column attributes or from property names. /// </remarks> /// <param name="filename"> /// The fully qualified path of the input file. /// </param> /// <returns> /// A list of classes of <typeparamref name="TInstance"/>. /// </returns> /// <exception cref="ArgumentException"> /// This exception is thrown in case of an invalid file name. /// </exception> /// <exception cref="FileNotFoundException"> /// This exception is thrown in case of a file with given name does not /// exist. /// </exception> public static IEnumerable <TInstance> Load(String filename) { return(CsvImporter <TInstance> .Load(filename, new CsvSettings())); }