/// <summary> /// Writes the field to the CSV file. /// When all fields are written for a record, /// <see cref="IWriter.NextRecord" /> must be called /// to complete writing of the current record. /// </summary> /// <typeparam name="T">The type of the field.</typeparam> /// <param name="field">The field to write.</param> /// <param name="converter">The converter used to convert the field into a string.</param> public virtual void WriteField <T>(T field, ITypeConverter converter) { var type = field == null ? typeof(string) : field.GetType(); context.ReusableMemberMapData.TypeConverter = converter; if (!context.TypeConverterOptionsCache.TryGetValue(type, out TypeConverterOptions typeConverterOptions)) { typeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions { CultureInfo = context.WriterConfiguration.CultureInfo }, context.WriterConfiguration.TypeConverterOptionsCache.GetOptions(type)); context.TypeConverterOptionsCache.Add(type, typeConverterOptions); } context.ReusableMemberMapData.TypeConverterOptions = typeConverterOptions; var fieldString = converter.ConvertToString(field, this, context.ReusableMemberMapData); WriteConvertedField(fieldString); }
/// <summary> /// Writes out the column styles for the record /// </summary> /// <param name="properties">Properties for the record</param> private void WriteColumnStyles( ExcelPropertyMapCollection properties) { // Write the column styles for all the columns for (var col = 0; col < properties.Count; col++) { // Determine if this property is written var propertyMap = properties[col]; if (!CanWrite(propertyMap)) { continue; } // Now get the property converter and options var data = propertyMap.Data; var typeConverterOptions = TypeConverterOptions.Merge( TypeConverterOptionsFactory.GetOptions(data.Property.PropertyType, _configuration.CultureInfo), data.TypeConverterOptions); // Write the cell formatting style if defined for this type var isDate = data.TypeConverter.ConvertedType == typeof(DateTime); var format = isDate ? typeConverterOptions.DateFormat : typeConverterOptions.NumberFormat; if (format != null) { using (var xlColumn = _sheet.Column(col + 1)) { if (isDate) { xlColumn.Style.DateFormat.Format = format; } else { xlColumn.Style.NumberFormat.Format = format; } } } } }
/// <summary> /// Creates the constructor arguments used to create a type. /// </summary> /// <param name="map">The mapping to create the arguments for.</param> /// <param name="argumentExpressions">The arguments that will be added to the mapping.</param> protected virtual void CreateConstructorArgumentExpressionsForMapping(ClassMap map, List <Expression> argumentExpressions) { foreach (var parameterMap in map.ParameterMaps) { if (parameterMap.ConstructorTypeMap != null) { // Constructor paramter type. var arguments = new List <Expression>(); CreateConstructorArgumentExpressionsForMapping(parameterMap.ConstructorTypeMap, arguments); var constructorExpression = Expression.New(Reader.Configuration.GetConstructor(parameterMap.ConstructorTypeMap.ClassType), arguments); argumentExpressions.Add(constructorExpression); } else if (parameterMap.ReferenceMap != null) { // Reference type. var referenceBindings = new List <MemberBinding>(); ExpressionManager.CreateMemberBindingsForMapping(parameterMap.ReferenceMap.Data.Mapping, parameterMap.ReferenceMap.Data.Parameter.ParameterType, referenceBindings); // This is in case an IContractResolver is being used. var type = ReflectionHelper.CreateInstance(parameterMap.ReferenceMap.Data.Parameter.ParameterType).GetType(); var referenceBody = Expression.MemberInit(Expression.New(type), referenceBindings); argumentExpressions.Add(referenceBody); } else { // Value type. var index = Reader.Configuration.HasHeaderRecord ? Reader.GetFieldIndex(parameterMap.Data.Name, 0) : parameterMap.Data.Index; // Get the field using the field index. var method = typeof(IReaderRow).GetProperty("Item", typeof(string), new[] { typeof(int) }).GetGetMethod(); Expression fieldExpression = Expression.Call(Expression.Constant(Reader), method, Expression.Constant(index, typeof(int))); // Convert the field. var typeConverterExpression = Expression.Constant(parameterMap.Data.TypeConverter); parameterMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions { CultureInfo = Reader.Context.ReaderConfiguration.CultureInfo }, Reader.Context.ReaderConfiguration.TypeConverterOptionsCache.GetOptions(parameterMap.Data.Parameter.ParameterType), parameterMap.Data.TypeConverterOptions); // Create type converter expression. var memberMapData = new MemberMapData(null) { Index = parameterMap.Data.Index, TypeConverter = parameterMap.Data.TypeConverter, TypeConverterOptions = parameterMap.Data.TypeConverterOptions }; memberMapData.Names.Add(parameterMap.Data.Name); Expression typeConverterFieldExpression = Expression.Call(typeConverterExpression, nameof(ITypeConverter.ConvertFromString), null, fieldExpression, Expression.Constant(Reader), Expression.Constant(memberMapData)); typeConverterFieldExpression = Expression.Convert(typeConverterFieldExpression, parameterMap.Data.Parameter.ParameterType); fieldExpression = typeConverterFieldExpression; argumentExpressions.Add(fieldExpression); } } }
/// <summary> /// Auto maps the given map using constructor parameters. /// </summary> /// <param name="map">The map.</param> /// <param name="context">The context.</param> /// <param name="mapParents">The list of parents for the map.</param> /// <param name="indexStart">The index starting point.</param> protected virtual void AutoMapConstructorParameters(ClassMap map, CsvContext context, LinkedList <Type> mapParents, int indexStart = 0) { var type = map.GetGenericType(); var args = new GetConstructorArgs(map.ClassType); var constructor = context.Configuration.GetConstructor(args); var parameters = constructor.GetParameters(); foreach (var parameter in parameters) { var parameterMap = new ParameterMap(parameter); if (parameter.GetCustomAttributes <IgnoreAttribute>(true).Any() || parameter.GetCustomAttributes <ConstantAttribute>(true).Any()) { // If there is an IgnoreAttribute or ConstantAttribute, we still need to add a map because a constructor requires // all parameters to be present. A default value will be used later on. ApplyAttributes(parameterMap); map.ParameterMaps.Add(parameterMap); continue; } var typeConverterType = context.TypeConverterCache.GetConverter(parameter.ParameterType).GetType(); var memberTypeInfo = parameter.ParameterType.GetTypeInfo(); var isDefaultConverter = typeConverterType == typeof(DefaultTypeConverter); if (isDefaultConverter && (memberTypeInfo.HasParameterlessConstructor() || memberTypeInfo.IsUserDefinedStruct())) { // If the type is not one covered by our type converters // and it has a parameterless constructor, create a // reference map for it. if (context.Configuration.IgnoreReferences) { throw new InvalidOperationException($"Configuration '{nameof(CsvConfiguration.IgnoreReferences)}' can't be true " + "when using types without a default constructor. Constructor parameters " + "are used and all members including references must be used."); } if (CheckForCircularReference(parameter.ParameterType, mapParents)) { throw new InvalidOperationException($"A circular reference was detected in constructor paramter '{parameter.Name}'." + "Since all parameters must be supplied for a constructor, this parameter can't be skipped."); } mapParents.AddLast(type); var refMapType = typeof(DefaultClassMap <>).MakeGenericType(parameter.ParameterType); var refMap = (ClassMap)ObjectResolver.Current.Resolve(refMapType); AutoMapMembers(refMap, context, mapParents, Math.Max(map.GetMaxIndex(isParameter: true) + 1, indexStart)); mapParents.Drop(mapParents.Find(type)); var referenceMap = new ParameterReferenceMap(parameter, refMap); if (context.Configuration.ReferenceHeaderPrefix != null) { var referenceHeaderPrefix = new ReferenceHeaderPrefixArgs(memberTypeInfo.MemberType(), memberTypeInfo.Name); referenceMap.Data.Prefix = context.Configuration.ReferenceHeaderPrefix(referenceHeaderPrefix); } ApplyAttributes(referenceMap); parameterMap.ReferenceMap = referenceMap; } else if (context.Configuration.ShouldUseConstructorParameters(new ShouldUseConstructorParametersArgs(parameter.ParameterType))) { mapParents.AddLast(type); var constructorMapType = typeof(DefaultClassMap <>).MakeGenericType(parameter.ParameterType); var constructorMap = (ClassMap)ObjectResolver.Current.Resolve(constructorMapType); // Need to use Max here for nested types. AutoMapConstructorParameters(constructorMap, context, mapParents, Math.Max(map.GetMaxIndex(isParameter: true) + 1, indexStart)); mapParents.Drop(mapParents.Find(type)); parameterMap.ConstructorTypeMap = constructorMap; } else { parameterMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions(), context.TypeConverterOptionsCache.GetOptions(parameter.ParameterType), parameterMap.Data.TypeConverterOptions); parameterMap.Data.Index = map.GetMaxIndex(isParameter: true) + 1; ApplyAttributes(parameterMap); } map.ParameterMaps.Add(parameterMap); } map.ReIndex(indexStart); }
/// <summary> /// Auto maps the given map and checks for circular references as it goes. /// </summary> /// <param name="map">The map to auto map.</param> /// <param name="configuration">The configuration.</param> /// <param name="mapParents">The list of parents for the map.</param> /// <param name="indexStart">The index starting point.</param> protected virtual void AutoMapMembers(ClassMap map, Configuration configuration, LinkedList <Type> mapParents, int indexStart = 0) { var type = map.GetGenericType(); var flags = BindingFlags.Instance | BindingFlags.Public; if (configuration.IncludePrivateMembers) { flags = flags | BindingFlags.NonPublic; } var members = new List <MemberInfo>(); if ((configuration.MemberTypes & MemberTypes.Properties) == MemberTypes.Properties) { // We need to go up the declaration tree and find the actual type the property // exists on and use that PropertyInfo instead. This is so we can get the private // set method for the property. var properties = new List <PropertyInfo>(); foreach (var property in type.GetProperties(flags)) { properties.Add(ReflectionHelper.GetDeclaringProperty(type, property, flags)); } members.AddRange(properties); } if ((configuration.MemberTypes & MemberTypes.Fields) == MemberTypes.Fields) { var fields = new List <MemberInfo>(); foreach (var field in type.GetFields(flags)) { if (!field.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any()) { fields.Add(field); } } members.AddRange(fields); } foreach (var member in members) { var typeConverterType = configuration.TypeConverterFactory.GetConverter(member.MemberType()).GetType(); if (configuration.HasHeaderRecord && enumerableConverters.Contains(typeConverterType)) { // Enumerable converters can't write the header properly, so skip it. continue; } var memberTypeInfo = member.MemberType().GetTypeInfo(); var isDefaultConverter = typeConverterType == typeof(DefaultTypeConverter); if (isDefaultConverter && (memberTypeInfo.HasParameterlessConstructor() || memberTypeInfo.IsUserDefinedStruct())) { // If the type is not one covered by our type converters // and it has a parameterless constructor, create a // reference map for it. if (configuration.IgnoreReferences) { continue; } if (CheckForCircularReference(member.MemberType(), mapParents)) { continue; } mapParents.AddLast(type); var refMapType = typeof(DefaultClassMap <>).MakeGenericType(member.MemberType()); var refMap = (ClassMap)ReflectionHelper.CreateInstance(refMapType); // Need to use Max here for nested types. AutoMapMembers(refMap, configuration, mapParents, Math.Max(map.GetMaxIndex() + 1, indexStart)); mapParents.Drop(mapParents.Find(type)); if (refMap.MemberMaps.Count > 0 || refMap.ReferenceMaps.Count > 0) { var referenceMap = new MemberReferenceMap(member, refMap); if (configuration.PrefixReferenceHeaders) { referenceMap.Prefix(); } map.ReferenceMaps.Add(referenceMap); } } else { var memberMap = MemberMap.CreateGeneric(map.ClassType, member); // Use global values as the starting point. memberMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions(), configuration.TypeConverterOptionsFactory.GetOptions(member.MemberType()), memberMap.Data.TypeConverterOptions); memberMap.Data.Index = map.GetMaxIndex() + 1; if (!isDefaultConverter) { // Only add the member map if it can be converted later on. // If the member will use the default converter, don't add it because // we don't want the .ToString() value to be used when auto mapping. map.MemberMaps.Add(memberMap); } } } map.ReIndex(indexStart); }
/// <summary> /// Creates the constructor arguments used to create a type. /// </summary> /// <param name="map">The mapping to create the arguments for.</param> /// <param name="argumentExpressions">The arguments that will be added to the mapping.</param> public virtual void CreateConstructorArgumentExpressionsForMapping(ClassMap map, List <Expression> argumentExpressions) { foreach (var parameterMap in map.ParameterMaps) { if (parameterMap.ConstructorTypeMap != null) { // Constructor parameter type. var arguments = new List <Expression>(); CreateConstructorArgumentExpressionsForMapping(parameterMap.ConstructorTypeMap, arguments); var constructorExpression = Expression.New(reader.Configuration.GetConstructor(parameterMap.ConstructorTypeMap.ClassType), arguments); argumentExpressions.Add(constructorExpression); } else if (parameterMap.ReferenceMap != null) { // Reference type. var referenceAssignments = new List <MemberAssignment>(); CreateMemberAssignmentsForMapping(parameterMap.ReferenceMap.Data.Mapping, referenceAssignments); var referenceBody = CreateInstanceAndAssignMembers(parameterMap.ReferenceMap.Data.Parameter.ParameterType, referenceAssignments); argumentExpressions.Add(referenceBody); } else { // Value type. int index; if (parameterMap.Data.IsNameSet || reader.Configuration.HasHeaderRecord && !parameterMap.Data.IsIndexSet) { // Use name. index = reader.GetFieldIndex(parameterMap.Data.Name, 0); } else { // Use index. index = parameterMap.Data.Index; } // Get the field using the field index. var method = typeof(IReaderRow).GetProperty("Item", typeof(string), new[] { typeof(int) }).GetGetMethod(); Expression fieldExpression = Expression.Call(Expression.Constant(reader), method, Expression.Constant(index, typeof(int))); // Convert the field. var typeConverterExpression = Expression.Constant(parameterMap.Data.TypeConverter); parameterMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions { CultureInfo = reader.Context.ReaderConfiguration.CultureInfo }, reader.Context.ReaderConfiguration.TypeConverterOptionsCache.GetOptions(parameterMap.Data.Parameter.ParameterType), parameterMap.Data.TypeConverterOptions); // Create type converter expression. var memberMapData = new MemberMapData(null) { Index = parameterMap.Data.Index, TypeConverter = parameterMap.Data.TypeConverter, TypeConverterOptions = parameterMap.Data.TypeConverterOptions }; memberMapData.Names.Add(parameterMap.Data.Name); Expression typeConverterFieldExpression = Expression.Call(typeConverterExpression, nameof(ITypeConverter.ConvertFromString), null, fieldExpression, Expression.Constant(reader), Expression.Constant(memberMapData)); typeConverterFieldExpression = Expression.Convert(typeConverterFieldExpression, parameterMap.Data.Parameter.ParameterType); fieldExpression = typeConverterFieldExpression; argumentExpressions.Add(fieldExpression); } } }