/// <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 write a particular line into given stream according /// to given parameter set. /// </summary> /// <remarks> /// This method may throw exceptions which must be handled by the caller. /// </remarks> /// <param name="writer"> /// The stream writer to be used to push data. /// </param> /// <param name="separator"> /// The delimiter to be used to separate each column. /// </param> /// <param name="textual"> /// The flag indicating how strings have to be handled. If true then all string /// are enclosed in double-quotes. If false then only the necessary strings are /// enclosed in double-quotes. /// </param> /// <param name="culture"> /// The culture to be used for data conversion. /// </param> /// <param name="mapping"> /// The mapping to be used for value transformation. /// </param> /// <param name="values"> /// A list of values representing a single line of the CSV file. /// </param> private static void WriteLine(StreamWriter writer, Char separator, Boolean textual, CultureInfo culture, CsvMappings mapping, IEnumerable <Object> values) { StringBuilder builder = new StringBuilder(1024); if (values != null && values.Any()) { foreach (Object value in values) { String current = ProcessHelper.ConvertToString(value, culture, mapping); builder.Append(ProcessHelper.ConvertToOutput(current, separator, textual)); } } writer.WriteLine(ProcessHelper.FixupOutput(builder, separator).ToString()); }
/// <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); }