From 2638efad6feeb0f62e41ca1f8424371ff25d70b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Christophe=20Chalt=C3=A9?= Date: Tue, 11 Sep 2018 14:34:41 +0200 Subject: [PATCH 1/3] Specific culture info name can now be specified as the first argument of the FieldConvert attribute, instead of an hardcoded decimal separator --- .gitignore | 3 +- .../Data/Good/NumberFormatFrench.txt | 3 + FileHelpers.Tests/FileHelpers.Tests.csproj | 1 + .../Tests/Converters/DecimalNumbers.cs | 42 +++ FileHelpers/Converters/ConvertHelpers.cs | 286 ++++++++++-------- FileHelpers/Enums/ConverterKind.cs | 24 +- 6 files changed, 223 insertions(+), 136 deletions(-) create mode 100644 FileHelpers.Tests/Data/Good/NumberFormatFrench.txt diff --git a/.gitignore b/.gitignore index 18cfa6839..d5e30466d 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ FileHelpers.userprefs /FileHelpers.Analyzer/FileHelpers.Analyzer/FileHelpers.Analyzer.Vsix/bin /FileHelpers.Analyzer/FileHelpers.Analyzer/FileHelpers.Analyzer.Vsix/obj /FileHelpers.Examples/obj -/FileHelpers.Examples/Release \ No newline at end of file +/FileHelpers.Examples/Release +/_ReSharper.Caches \ No newline at end of file diff --git a/FileHelpers.Tests/Data/Good/NumberFormatFrench.txt b/FileHelpers.Tests/Data/Good/NumberFormatFrench.txt new file mode 100644 index 000000000..afa7b7c8e --- /dev/null +++ b/FileHelpers.Tests/Data/Good/NumberFormatFrench.txt @@ -0,0 +1,3 @@ +10248|32,38|32,38|32,38 +10249|1011,61|1011,61|1011,61 +10250|1 165,83|1 165,83|1 165,83 \ No newline at end of file diff --git a/FileHelpers.Tests/FileHelpers.Tests.csproj b/FileHelpers.Tests/FileHelpers.Tests.csproj index 2005f0d42..50f42c0a9 100644 --- a/FileHelpers.Tests/FileHelpers.Tests.csproj +++ b/FileHelpers.Tests/FileHelpers.Tests.csproj @@ -509,6 +509,7 @@ + diff --git a/FileHelpers.Tests/Tests/Converters/DecimalNumbers.cs b/FileHelpers.Tests/Tests/Converters/DecimalNumbers.cs index e0e6b4b8b..f5336a836 100644 --- a/FileHelpers.Tests/Tests/Converters/DecimalNumbers.cs +++ b/FileHelpers.Tests/Tests/Converters/DecimalNumbers.cs @@ -26,6 +26,38 @@ public void Decimals1() CheckDecimal((decimal) 81.91, res[9]); } + [DelimitedRecord("|")] + public class DecimalTypeWithFrenchConversion + { + [FieldConverter(ConverterKind.Int32,"fr-FR")] + public int IntField; + [FieldConverter(ConverterKind.Single,"fr-FR")] + public float FloatField; + [FieldConverter(ConverterKind.Double,"fr-FR")] + public double DoubleField; + [FieldConverter(ConverterKind.Decimal,"fr-FR")] + public decimal DecimalField; + } + + [Test] + public void DecimalsWithFrenchCulture() + { + var engine = new FileHelperEngine(); + var res = TestCommon.ReadTest(engine, "Good", "NumberFormatFrench.txt"); + + Assert.AreEqual(3, res.Length); + + Assert.AreEqual(10248, res[0].IntField); + CheckDecimal((decimal) 32.38, res[0]); + + Assert.AreEqual(10249, res[1].IntField); + CheckDecimal((decimal) 1011.61, res[1]); + + Assert.AreEqual(10250, res[2].IntField); + CheckDecimal((decimal) 1165.83, res[2]); + } + + private static void CheckDecimal(decimal dec, DecimalType res) { Assert.AreEqual((decimal) dec, res.DecimalField); @@ -33,6 +65,13 @@ private static void CheckDecimal(decimal dec, DecimalType res) Assert.AreEqual((float) dec, res.FloatField); } + private static void CheckDecimal(decimal dec, DecimalTypeWithFrenchConversion res) + { + Assert.AreEqual((decimal) dec, res.DecimalField); + Assert.AreEqual((double) dec, res.DoubleField); + Assert.AreEqual((float) dec, res.FloatField); + } + [Test] public void NegativeNumbers() @@ -65,10 +104,13 @@ public class DecimalType public decimal DecimalField; } + + [Test] public void DecimalsWithExponents() { var engine = new FileHelperEngine(); + DecimalType[] res; res = TestCommon.ReadTest(engine, "Good", "NumberFormat2.txt"); diff --git a/FileHelpers/Converters/ConvertHelpers.cs b/FileHelpers/Converters/ConvertHelpers.cs index 0c2b4cc9c..934043a91 100644 --- a/FileHelpers/Converters/ConvertHelpers.cs +++ b/FileHelpers/Converters/ConvertHelpers.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Linq; using System.Text; using FileHelpers.Helpers; @@ -16,27 +17,43 @@ internal static class ConvertHelpers { private const string DefaultDecimalSep = "."; + /// + /// Array of all allowed decimal separators + /// + private static readonly string[] mAllowedDecimalSeparators = { ".", "," }; + #region " CreateCulture " /// /// Return culture information for with comma decimal separator or comma decimal separator /// - /// Decimal separator string + /// Decimal separator string or culture name /// Cultural information based on separator - private static CultureInfo CreateCulture(string decimalSep) + private static CultureInfo CreateCulture(string decimalSepOrCultureName) { - var ci = new CultureInfo(CultureInfo.CurrentCulture.LCID); + CultureInfo ci; - if (decimalSep == ".") { - ci.NumberFormat.NumberDecimalSeparator = "."; - ci.NumberFormat.NumberGroupSeparator = ","; - } - else if (decimalSep == ",") { - ci.NumberFormat.NumberDecimalSeparator = ","; - ci.NumberFormat.NumberGroupSeparator = "."; + if (mAllowedDecimalSeparators.Contains(decimalSepOrCultureName)) + { + ci = new CultureInfo(CultureInfo.CurrentCulture.LCID); + + if (decimalSepOrCultureName == ".") + { + ci.NumberFormat.NumberDecimalSeparator = "."; + ci.NumberFormat.NumberGroupSeparator = ","; + } + else if (decimalSepOrCultureName == ",") + { + ci.NumberFormat.NumberDecimalSeparator = ","; + ci.NumberFormat.NumberGroupSeparator = "."; + } + else + throw new BadUsageException("You can only use '.' or ',' as decimal or group separators"); } else - throw new BadUsageException("You can only use '.' or ',' as decimal or group separators"); + { + ci = CultureInfo.GetCultureInfo(decimalSepOrCultureName); + } return ci; } @@ -55,15 +72,17 @@ internal static ConverterBase GetDefaultConverter(string fieldName, Type fieldTy { if (fieldType.IsArray) { - - if (fieldType.GetArrayRank() != 1) { + + if (fieldType.GetArrayRank() != 1) + { throw new BadUsageException("The array field: '" + fieldName + "' has more than one dimension and is not supported by the library."); } fieldType = fieldType.GetElementType(); - if (fieldType.IsArray) { + if (fieldType.IsArray) + { throw new BadUsageException("The array field: '" + fieldName + "' is a jagged array and is not supported by the library."); } @@ -71,56 +90,56 @@ internal static ConverterBase GetDefaultConverter(string fieldName, Type fieldTy if (fieldType.IsValueType && fieldType.IsGenericType && - fieldType.GetGenericTypeDefinition() == typeof (Nullable<>)) + fieldType.GetGenericTypeDefinition() == typeof(Nullable<>)) fieldType = fieldType.GetGenericArguments()[0]; // Try to assign a default Converter - if (fieldType == typeof (string)) + if (fieldType == typeof(string)) return null; - if (fieldType == typeof (Int16)) + if (fieldType == typeof(Int16)) return new Int16Converter(); - if (fieldType == typeof (Int32)) + if (fieldType == typeof(Int32)) return new Int32Converter(); - if (fieldType == typeof (Int64)) + if (fieldType == typeof(Int64)) return new Int64Converter(); - if (fieldType == typeof (SByte)) + if (fieldType == typeof(SByte)) return new SByteConverter(); - if (fieldType == typeof (UInt16)) + if (fieldType == typeof(UInt16)) return new UInt16Converter(); - if (fieldType == typeof (UInt32)) + if (fieldType == typeof(UInt32)) return new UInt32Converter(); - if (fieldType == typeof (UInt64)) + if (fieldType == typeof(UInt64)) return new UInt64Converter(); - if (fieldType == typeof (byte)) + if (fieldType == typeof(byte)) return new ByteConverter(); - if (fieldType == typeof (decimal)) + if (fieldType == typeof(decimal)) return new DecimalConverter(); - if (fieldType == typeof (double)) + if (fieldType == typeof(double)) return new DoubleConverter(); - if (fieldType == typeof (Single)) + if (fieldType == typeof(Single)) return new SingleConverter(); - if (fieldType == typeof (DateTime)) + if (fieldType == typeof(DateTime)) return new DateTimeConverter(); - if (fieldType == typeof (bool)) + if (fieldType == typeof(bool)) return new BooleanConverter(); // Added by Alexander Obolonkov 2007.11.08 (the next three) - if (fieldType == typeof (char)) + if (fieldType == typeof(char)) return new CharConverter(); - if (fieldType == typeof (Guid)) + if (fieldType == typeof(Guid)) return new GuidConverter(); if (fieldType.IsEnum) return new EnumConverter(fieldType); @@ -151,10 +170,10 @@ internal abstract class CultureConverter /// Convert to a type given a decimal separator /// /// type we are converting - /// Separator - protected CultureConverter(Type T, string decimalSep) + /// Separator or culture name (eg. 'en-US', 'fr-FR'...) + protected CultureConverter(Type T, string decimalSepOrCultureName) { - mCulture = CreateCulture(decimalSep); + mCulture = CreateCulture(decimalSepOrCultureName); mType = T; } @@ -168,7 +187,7 @@ public override sealed string FieldToString(object from) if (from == null) return string.Empty; - return ((IConvertible) from).ToString(mCulture); + return ((IConvertible)from).ToString(mCulture); } /// @@ -198,14 +217,14 @@ internal sealed class ByteConverter : CultureConverter /// Convert a string to a byte value using the default decimal separator /// public ByteConverter() - : this(DefaultDecimalSep) {} + : this(DefaultDecimalSep) { } /// /// Convert a string to a byte /// - /// decimal separator to use '.' or ',' - public ByteConverter(string decimalSep) - : base(typeof (Byte), decimalSep) {} + /// decimal separator to use '.' or ',' + public ByteConverter(string decimalSepOrCultureName) + : base(typeof(Byte), decimalSepOrCultureName) { } /// /// Convert a string to a byte value @@ -230,14 +249,14 @@ internal sealed class UInt16Converter : CultureConverter /// Convert a number to a short integer /// public UInt16Converter() - : this(DefaultDecimalSep) {} + : this(DefaultDecimalSep) { } /// /// Convert a number to a short integer /// - /// Decimal separator - public UInt16Converter(string decimalSep) - : base(typeof (UInt16), decimalSep) {} + /// Decimal separator + public UInt16Converter(string decimalSepOrCultureName) + : base(typeof(UInt16), decimalSepOrCultureName) { } /// /// Parse a string to a short integer @@ -266,14 +285,14 @@ internal sealed class UInt32Converter : CultureConverter /// Unsigned integer converter /// public UInt32Converter() - : this(DefaultDecimalSep) {} + : this(DefaultDecimalSep) { } /// /// Unsigned integer converter with a decimal separator /// - /// dot or comma for to separate decimal - public UInt32Converter(string decimalSep) - : base(typeof (UInt32), decimalSep) {} + /// dot or comma for to separate decimal + public UInt32Converter(string decimalSepOrCultureName) + : base(typeof(UInt32), decimalSepOrCultureName) { } /// /// Convert a string to a unsigned integer value @@ -302,14 +321,14 @@ internal sealed class UInt64Converter : CultureConverter /// Unsigned long converter /// public UInt64Converter() - : this(DefaultDecimalSep) {} + : this(DefaultDecimalSep) { } /// /// Unsigned long with decimal separator /// - /// dot or comma for separator - public UInt64Converter(string decimalSep) - : base(typeof (UInt64), decimalSep) {} + /// dot or comma for separator + public UInt64Converter(string decimalSepOrCultureName) + : base(typeof(UInt64), decimalSepOrCultureName) { } /// /// Convert a string to an unsigned integer long @@ -342,14 +361,14 @@ internal sealed class SByteConverter : CultureConverter /// Signed byte converter (8 bit signed integer) /// public SByteConverter() - : this(DefaultDecimalSep) {} + : this(DefaultDecimalSep) { } /// /// Signed byte converter (8 bit signed integer) /// - /// dot or comma for separator - public SByteConverter(string decimalSep) - : base(typeof (SByte), decimalSep) {} + /// dot or comma for separator + public SByteConverter(string decimalSepOrCultureName) + : base(typeof(SByte), decimalSepOrCultureName) { } /// /// Convert a string to an signed byte @@ -374,14 +393,14 @@ internal sealed class Int16Converter : CultureConverter /// Convert a value to a short integer /// public Int16Converter() - : this(DefaultDecimalSep) {} + : this(DefaultDecimalSep) { } /// /// Convert a value to a short integer /// - /// dot or comma for separator - public Int16Converter(string decimalSep) - : base(typeof (short), decimalSep) {} + /// dot or comma for separator + public Int16Converter(string decimalSepOrCultureName) + : base(typeof(short), decimalSepOrCultureName) { } /// /// Convert a string to an short integer @@ -410,14 +429,14 @@ internal sealed class Int32Converter : CultureConverter /// Convert a value to a integer /// public Int32Converter() - : this(DefaultDecimalSep) {} + : this(DefaultDecimalSep) { } /// /// Convert a value to a integer /// - /// dot or comma for separator - public Int32Converter(string decimalSep) - : base(typeof (int), decimalSep) {} + /// dot or comma for separator + public Int32Converter(string decimalSepOrCultureName) + : base(typeof(int), decimalSepOrCultureName) { } /// /// Convert a string to an integer @@ -447,14 +466,14 @@ internal sealed class Int64Converter : CultureConverter /// Convert a value to a long integer /// public Int64Converter() - : this(DefaultDecimalSep) {} + : this(DefaultDecimalSep) { } /// /// Convert a value to a long integer /// - /// dot or comma for separator - public Int64Converter(string decimalSep) - : base(typeof (long), decimalSep) {} + /// dot or comma for separator + public Int64Converter(string decimalSepOrCultureName) + : base(typeof(long), decimalSepOrCultureName) { } /// /// Convert a string to an integer long @@ -491,14 +510,14 @@ internal sealed class DecimalConverter : CultureConverter /// Convert a value to a decimal value /// public DecimalConverter() - : this(DefaultDecimalSep) {} + : this(DefaultDecimalSep) { } /// /// Convert a value to a decimal value /// - /// dot or comma for separator - public DecimalConverter(string decimalSep) - : base(typeof (decimal), decimalSep) {} + /// dot or comma for separator + public DecimalConverter(string decimalSepOrCultureName) + : base(typeof(decimal), decimalSepOrCultureName) { } /// /// Convert a string to a decimal @@ -527,14 +546,14 @@ internal sealed class SingleConverter : CultureConverter /// Convert a value to a single floating point /// public SingleConverter() - : this(DefaultDecimalSep) {} + : this(DefaultDecimalSep) { } /// /// Convert a value to a single floating point /// - /// dot or comma for separator - public SingleConverter(string decimalSep) - : base(typeof (Single), decimalSep) {} + /// dot or comma for separator + public SingleConverter(string decimalSepOrCultureName) + : base(typeof(Single), decimalSepOrCultureName) { } /// /// Convert a string to an single precision floating point @@ -563,14 +582,14 @@ internal sealed class DoubleConverter : CultureConverter /// Convert a value to a floating point /// public DoubleConverter() - : this(DefaultDecimalSep) {} + : this(DefaultDecimalSep) { } /// /// Convert a value to a floating point /// - /// dot or comma for separator - public DoubleConverter(string decimalSep) - : base(typeof (Double), decimalSep) {} + /// dot or comma for separator + public DoubleConverter(string decimalSepOrCultureName) + : base(typeof(Double), decimalSepOrCultureName) { } /// /// Convert a string to an floating point @@ -602,14 +621,14 @@ internal sealed class PercentDoubleConverter : CultureConverter /// Convert a value to a floating point from a percentage /// public PercentDoubleConverter() - : this(DefaultDecimalSep) {} + : this(DefaultDecimalSep) { } /// /// Convert a value to a floating point from a percentage /// - /// dot or comma for separator - public PercentDoubleConverter(string decimalSep) - : base(typeof (Double), decimalSep) {} + /// dot or comma for separator + public PercentDoubleConverter(string decimalSepOrCultureName) + : base(typeof(Double), decimalSepOrCultureName) { } /// /// Convert a string to an floating point from percentage @@ -620,16 +639,18 @@ protected override object ParseString(string from) { double res; var blanksRemoved = StringHelper.RemoveBlanks(from); - if (blanksRemoved.EndsWith("%")) { + if (blanksRemoved.EndsWith("%")) + { if ( !Double.TryParse(blanksRemoved, NumberStyles.Number | NumberStyles.AllowExponent, mCulture, out res)) throw new ConvertException(from, mType); - return res/100.0; + return res / 100.0; } - else { + else + { if ( !Double.TryParse(blanksRemoved, NumberStyles.Number | NumberStyles.AllowExponent, @@ -661,14 +682,14 @@ internal sealed class DateTimeConverter : ConverterBase /// Convert a value to a date time value /// public DateTimeConverter() - : this(DefaultDateTimeFormat) {} + : this(DefaultDateTimeFormat) { } /// /// Convert a value to a date time value /// /// date format see .Net documentation public DateTimeConverter(string format) - :this(format, null) + : this(format, null) { } @@ -706,7 +727,8 @@ public override object StringToField(string from) from = string.Empty; DateTime val; - if (!DateTime.TryParseExact(from.Trim(), mFormat, mCulture, DateTimeStyles.None, out val)) { + if (!DateTime.TryParseExact(from.Trim(), mFormat, mCulture, DateTimeStyles.None, out val)) + { string extra; if (from.Length > mFormat.Length) @@ -717,7 +739,7 @@ public override object StringToField(string from) extra = " Using the format: '" + mFormat + "'"; - throw new ConvertException(from, typeof (DateTime), extra); + throw new ConvertException(from, typeof(DateTime), extra); } return val; } @@ -752,13 +774,13 @@ internal sealed class DateTimeMultiFormatConverter : ConverterBase /// Convert a value to a date time value using multiple formats /// public DateTimeMultiFormatConverter(string format1, string format2) - : this(new[] {format1, format2}) {} + : this(new[] { format1, format2 }) { } /// /// Convert a value to a date time value using multiple formats /// public DateTimeMultiFormatConverter(string format1, string format2, string format3) - : this(new[] {format1, format2, format3}) {} + : this(new[] { format1, format2, format3 }) { } /// /// Convert a date time value to a string @@ -766,15 +788,18 @@ public DateTimeMultiFormatConverter(string format1, string format2, string forma /// list of formats to try private DateTimeMultiFormatConverter(string[] formats) { - for (int i = 0; i < formats.Length; i++) { + for (int i = 0; i < formats.Length; i++) + { if (formats[i] == null || formats[i] == String.Empty) throw new BadUsageException("The format of the DateTime Converter can be null or empty."); - try { + try + { DateTime.Now.ToString(formats[i]); } - catch { + catch + { throw new BadUsageException("The format: '" + formats[i] + " is invalid for the DateTime Converter."); } @@ -794,9 +819,10 @@ public override object StringToField(string from) from = string.Empty; DateTime val; - if (!DateTime.TryParseExact(from.Trim(), mFormats, null, DateTimeStyles.None, out val)) { + if (!DateTime.TryParseExact(from.Trim(), mFormats, null, DateTimeStyles.None, out val)) + { string extra = " does not match any of the given formats: " + CreateFormats(); - throw new ConvertException(from, typeof (DateTime), extra); + throw new ConvertException(from, typeof(DateTime), extra); } return val; } @@ -809,7 +835,8 @@ private string CreateFormats() { var sb = new StringBuilder(); - for (int i = 0; i < mFormats.Length; i++) { + for (int i = 0; i < mFormats.Length; i++) + { if (i == 0) sb.Append("'" + mFormats[i] + "'"); else @@ -854,7 +881,7 @@ internal sealed class BooleanConverter : ConverterBase /// /// Simple boolean converter /// - public BooleanConverter() {} + public BooleanConverter() { } /// /// Boolean converter with true false values @@ -879,9 +906,11 @@ public override object StringToField(string from) object val; string testTo = from.ToLower(); - if (mTrueString == null) { + if (mTrueString == null) + { testTo = testTo.Trim(); - switch (testTo) { + switch (testTo) + { case "true": case "1": case "y": @@ -894,35 +923,38 @@ public override object StringToField(string from) case "n": case "f": - // I don't think that this case is possible without overriding the CustomNullHandling - // and it is possible that defaulting empty fields to be false is not correct + // I don't think that this case is possible without overriding the CustomNullHandling + // and it is possible that defaulting empty fields to be false is not correct case "": val = false; break; default: throw new ConvertException(from, - typeof (bool), + typeof(bool), "The string: " + from + " can't be recognized as boolean using default true/false values."); } } - else { + else + { // Most of the time the strings should match exactly. To improve performance // we skip the trim if the exact match is true if (testTo == mTrueStringLower) val = true; else if (testTo == mFalseStringLower) val = false; - else { + else + { testTo = testTo.Trim(); if (testTo == mTrueStringLower) val = true; else if (testTo == mFalseStringLower) val = false; - else { + else + { throw new ConvertException(from, - typeof (bool), + typeof(bool), "The string: " + from + " can't be recognized as boolean using the true/false values: " + mTrueString + "/" + mFalseString); @@ -941,7 +973,8 @@ public override object StringToField(string from) public override string FieldToString(object from) { bool b = Convert.ToBoolean(from); - if (b) { + if (b) + { if (mTrueString == null) return "True"; else @@ -999,7 +1032,7 @@ private enum CharFormat /// public CharConverter() : this("") // default, no upper or lower case conversion - {} + { } /// /// Single character converter that optionally makes it upper (X) or lower case (x) @@ -1007,7 +1040,8 @@ public CharConverter() /// empty string for no upper or lower, x for lower case, X for Upper case public CharConverter(string format) { - switch (format.Trim()) { + switch (format.Trim()) + { case "x": case "lower": mFormat = CharFormat.Lower; @@ -1038,8 +1072,10 @@ public override object StringToField(string from) if (string.IsNullOrEmpty(from)) return Char.MinValue; - try { - switch (mFormat) { + try + { + switch (mFormat) + { case CharFormat.NoChange: return from[0]; @@ -1051,12 +1087,13 @@ public override object StringToField(string from) default: throw new ConvertException(from, - typeof (Char), + typeof(Char), "Unknown char convert flag " + mFormat.ToString()); } } - catch { - throw new ConvertException(from, typeof (Char), "Upper or lower case of input string failed"); + catch + { + throw new ConvertException(from, typeof(Char), "Upper or lower case of input string failed"); } } @@ -1067,7 +1104,8 @@ public override object StringToField(string from) /// String containing the character public override string FieldToString(object from) { - switch (mFormat) { + switch (mFormat) + { case CharFormat.NoChange: return Convert.ToChar(from).ToString(); @@ -1078,7 +1116,7 @@ public override string FieldToString(object from) return char.ToUpper(Convert.ToChar(from)).ToString(); default: - throw new ConvertException("", typeof (Char), "Unknown char convert flag " + mFormat.ToString()); + throw new ConvertException("", typeof(Char), "Unknown char convert flag " + mFormat.ToString()); } } } @@ -1098,7 +1136,7 @@ internal sealed class GuidConverter : ConverterBase /// public GuidConverter() : this("D") // D or N or B or P (default is D: see Guid.ToString(string format)) - {} + { } /// /// Create a GUID converter with formats as defined for GUID @@ -1128,11 +1166,13 @@ public override object StringToField(string from) if (String.IsNullOrEmpty(from)) return Guid.Empty; - try { + try + { return new Guid(from); } - catch { - throw new ConvertException(from, typeof (Guid)); + catch + { + throw new ConvertException(from, typeof(Guid)); } } @@ -1145,7 +1185,7 @@ public override string FieldToString(object from) { if (from == null) return String.Empty; - return ((Guid) from).ToString(mFormat); + return ((Guid)from).ToString(mFormat); } } diff --git a/FileHelpers/Enums/ConverterKind.cs b/FileHelpers/Enums/ConverterKind.cs index fd7516a70..c3dc77e89 100644 --- a/FileHelpers/Enums/ConverterKind.cs +++ b/FileHelpers/Enums/ConverterKind.cs @@ -23,74 +23,74 @@ public enum ConverterKind /// /// Convert from or to Byte values. - /// Params: arg1 is the decimal separator, by default '.' + /// Params: arg1 is either a decimal separator, by default '.', or a culture name (eg. "en-US", "fr-FR") /// Byte, /// /// Convert from or to Int16 or short values. - /// Params: arg1 is the decimal separator, by default '.' + /// Params: arg1 is either a decimal separator, by default '.', or a culture name (eg. "en-US", "fr-FR") /// Int16, /// /// Convert from or to Int32 or int values. - /// Params: arg1 is the decimal separator, by default '.' + /// Params: arg1 is either a decimal separator, by default '.', or a culture name (eg. "en-US", "fr-FR") /// Int32, /// /// Convert from or to Int64 or long values. - /// Params: arg1 is the decimal separator, by default '.' + /// Params: arg1 is either a decimal separator, by default '.', or a culture name (eg. "en-US", "fr-FR") /// Int64, /// /// Convert from or to Decimal values. - /// Params: arg1 is the decimal separator, by default '.' + /// Params: arg1 is either a decimal separator, by default '.', or a culture name (eg. "en-US", "fr-FR") /// Decimal, /// /// Convert from or to Double values. - /// Params: arg1 is the decimal separator, by default '.' + /// Params: arg1 is either a decimal separator, by default '.', or a culture name (eg. "en-US", "fr-FR") /// Double, //Added by Shreyas Narasimhan (17 March 2010) /// /// Convert from or to Double values. Understands Percent '%' symbol /// and if present returns number /100 only while reading - /// Params: arg1 is the decimal separator, by default '.' + /// Params: arg1 is either a decimal separator, by default '.', or a culture name (eg. "en-US", "fr-FR") /// PercentDouble, /// /// Convert from or to Single values. - /// Params: arg1 is the decimal separator, by default '.' + /// Params: arg1 is either a decimal separator, by default '.', or a culture name (eg. "en-US", "fr-FR") /// Single, /// /// Convert from or to Byte values. - /// Params: arg1 is the decimal separator, by default '.' + /// Params: arg1 is either a decimal separator, by default '.', or a culture name (eg. "en-US", "fr-FR") /// SByte, /// /// Convert from or to UInt16 or unsigned short values. - /// Params: arg1 is the decimal separator, by default '.' + /// Params: arg1 is either a decimal separator, by default '.', or a culture name (eg. "en-US", "fr-FR") /// UInt16, /// /// Convert from or to UInt32 or unsigned int values. - /// Params: arg1 is the decimal separator, by default '.' + /// Params: arg1 is either a decimal separator, by default '.', or a culture name (eg. "en-US", "fr-FR") /// UInt32, /// /// Convert from or to UInt64 or unsigned long values. - /// Params: arg1 is the decimal separator, by default '.' + /// Params: arg1 is either a decimal separator, by default '.', or a culture name (eg. "en-US", "fr-FR") /// UInt64, From 75008e8043b8ce682a6cebc70cd6e1412d93742c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Christophe=20Chalt=C3=A9?= Date: Tue, 11 Sep 2018 16:10:05 +0200 Subject: [PATCH 2/3] Default culture can now be set in the record attribute, and customized field by field --- FileHelpers.Tests/FileHelpers.Tests.csproj | 1 + .../Tests/Converters/DecimalNumbers.cs | 38 +-- .../Tests/Converters/DefaultCultureInfo.cs | 158 +++++++++++ .../Attributes/DelimitedRecordAttribute.cs | 3 +- .../Attributes/FixedLengthRecordAttribute.cs | 3 +- .../Attributes/TypedRecordAttribute.cs | 11 +- FileHelpers/Converters/ConvertHelpers.cs | 27 +- FileHelpers/Fields/DelimitedField.cs | 4 +- FileHelpers/Fields/FieldBase.cs | 268 ++++++++++++------ FileHelpers/Fields/FixedLengthField.cs | 4 +- 10 files changed, 367 insertions(+), 150 deletions(-) create mode 100644 FileHelpers.Tests/Tests/Converters/DefaultCultureInfo.cs diff --git a/FileHelpers.Tests/FileHelpers.Tests.csproj b/FileHelpers.Tests/FileHelpers.Tests.csproj index 50f42c0a9..e5331f0b9 100644 --- a/FileHelpers.Tests/FileHelpers.Tests.csproj +++ b/FileHelpers.Tests/FileHelpers.Tests.csproj @@ -165,6 +165,7 @@ + Code diff --git a/FileHelpers.Tests/Tests/Converters/DecimalNumbers.cs b/FileHelpers.Tests/Tests/Converters/DecimalNumbers.cs index f5336a836..a46228dbd 100644 --- a/FileHelpers.Tests/Tests/Converters/DecimalNumbers.cs +++ b/FileHelpers.Tests/Tests/Converters/DecimalNumbers.cs @@ -26,36 +26,8 @@ public void Decimals1() CheckDecimal((decimal) 81.91, res[9]); } - [DelimitedRecord("|")] - public class DecimalTypeWithFrenchConversion - { - [FieldConverter(ConverterKind.Int32,"fr-FR")] - public int IntField; - [FieldConverter(ConverterKind.Single,"fr-FR")] - public float FloatField; - [FieldConverter(ConverterKind.Double,"fr-FR")] - public double DoubleField; - [FieldConverter(ConverterKind.Decimal,"fr-FR")] - public decimal DecimalField; - } - - [Test] - public void DecimalsWithFrenchCulture() - { - var engine = new FileHelperEngine(); - var res = TestCommon.ReadTest(engine, "Good", "NumberFormatFrench.txt"); - - Assert.AreEqual(3, res.Length); - - Assert.AreEqual(10248, res[0].IntField); - CheckDecimal((decimal) 32.38, res[0]); + - Assert.AreEqual(10249, res[1].IntField); - CheckDecimal((decimal) 1011.61, res[1]); - - Assert.AreEqual(10250, res[2].IntField); - CheckDecimal((decimal) 1165.83, res[2]); - } private static void CheckDecimal(decimal dec, DecimalType res) @@ -65,13 +37,7 @@ private static void CheckDecimal(decimal dec, DecimalType res) Assert.AreEqual((float) dec, res.FloatField); } - private static void CheckDecimal(decimal dec, DecimalTypeWithFrenchConversion res) - { - Assert.AreEqual((decimal) dec, res.DecimalField); - Assert.AreEqual((double) dec, res.DoubleField); - Assert.AreEqual((float) dec, res.FloatField); - } - + [Test] public void NegativeNumbers() diff --git a/FileHelpers.Tests/Tests/Converters/DefaultCultureInfo.cs b/FileHelpers.Tests/Tests/Converters/DefaultCultureInfo.cs new file mode 100644 index 000000000..2fcce02aa --- /dev/null +++ b/FileHelpers.Tests/Tests/Converters/DefaultCultureInfo.cs @@ -0,0 +1,158 @@ +using NUnit.Framework; + +namespace FileHelpers.Tests.Converters +{ + [TestFixture] + public class DefaultCultureInfo + { + [DelimitedRecord("|")] + public class RecordWithoutSpecifiedCulture + { + public decimal DecimalField; + } + + [DelimitedRecord("|", "fr-FR")] + public class RecordWithDefaultCulture + { + public decimal DecimalFieldWithoutCulture; + + + [FieldConverter(ConverterKind.Decimal, "en-US")] + public decimal DecimalFieldWithEnglishCulture; + } + + [DelimitedRecord("|")] + public class RecordWithFieldCulture + { + [FieldConverter(ConverterKind.Decimal, "fr-FR")] + public decimal DecimalFieldWithFrenchCulture; + + public decimal DecimalFieldWithoutCulture; + } + + + [Test] + public void RecordWithoutCultureHasDefaultSeparator() + { + var engine = new FileHelperEngine(); + Assert.AreEqual(1, engine.Options.Fields.Count); + var decimalConverter = engine.Options.Fields[0].Converter; + AssertCanConvertEnglishNumbers(decimalConverter); + } + + [Test] + public void RecordWithSpecifiedCultureInRecordAttributeUsesThatCultureByDefault() + { + var engine = new FileHelperEngine(); + Assert.AreEqual(2, engine.Options.Fields.Count); + var decimalConverterWithoutCulture = engine.Options.Fields[0].Converter; + AssertCanConvertFrenchNumbers(decimalConverterWithoutCulture); + + var decimalConverterWithEnglishCulture = engine.Options.Fields[1].Converter; + AssertCanConvertEnglishNumbers(decimalConverterWithEnglishCulture); + } + + [Test] + public void FieldWithSpecifiedCultureInFieldConverterAttributeUsesThatCulture() + { + var engine = new FileHelperEngine(); + Assert.AreEqual(2, engine.Options.Fields.Count); + var decimalConverterWithFrenchCulture = engine.Options.Fields[0].Converter; + AssertCanConvertFrenchNumbers(decimalConverterWithFrenchCulture); + + var decimalConverterWithoutCulture = engine.Options.Fields[1].Converter; + AssertCanConvertEnglishNumbers(decimalConverterWithoutCulture); + } + + #region Helpers + private static void AssertCanConvertEnglishNumbers(ConverterBase decimalConverter) + { + Assert.AreEqual(123.12, + decimalConverter.StringToField("123.12"), + "If no culture is specified, the decimal separator should be a dot"); + Assert.AreEqual(1234.12, + decimalConverter.StringToField("1,234.12"), + "If no culture is specified, the group separator should be a comma"); + } + + private static void AssertCanConvertFrenchNumbers(ConverterBase decimalConverterWithoutCulture) + { + Assert.AreEqual(1.23, decimalConverterWithoutCulture.StringToField("1,23"), "If a culture is specified, the decimal separator should be the specified culture decimal separator"); + Assert.AreEqual(1234.12, decimalConverterWithoutCulture.StringToField("1 234,12"), "If a culture is specified, the group separator should be the specified culture group separator"); + Assert.Catch(() => { decimalConverterWithoutCulture.StringToField("1.23"); }, "The dot is not a valid french separator"); + } + #endregion + + [DelimitedRecord("|", defaultCultureName: "fr-FR")] + public class DecimalTypeWithFrenchConversionAsAWhole + { + public int IntField; + public float FloatField; + public double DoubleField; + public decimal DecimalField; + } + + [DelimitedRecord("|")] + public class DecimalTypeWithFrenchConversion + { + [FieldConverter(ConverterKind.Int32, "fr-FR")] + public int IntField; + [FieldConverter(ConverterKind.Single, "fr-FR")] + public float FloatField; + [FieldConverter(ConverterKind.Double, "fr-FR")] + public double DoubleField; + [FieldConverter(ConverterKind.Decimal, "fr-FR")] + public decimal DecimalField; + } + + [Test] + public void DecimalsWithFrenchCulture() + { + var engine = new FileHelperEngine(); + var res = TestCommon.ReadTest(engine, "Good", "NumberFormatFrench.txt"); + + Assert.AreEqual(3, res.Length); + + Assert.AreEqual(10248, res[0].IntField); + CheckDecimal((decimal)32.38, res[0]); + + Assert.AreEqual(10249, res[1].IntField); + CheckDecimal((decimal)1011.61, res[1]); + + Assert.AreEqual(10250, res[2].IntField); + CheckDecimal((decimal)1165.83, res[2]); + } + + [Test] + public void DecimalsWithFrenchCulture2() + { + var engine = new FileHelperEngine(); + var res = TestCommon.ReadTest(engine, "Good", "NumberFormatFrench.txt"); + + Assert.AreEqual(3, res.Length); + + Assert.AreEqual(10248, res[0].IntField); + CheckDecimal((decimal)32.38, res[0]); + + Assert.AreEqual(10249, res[1].IntField); + CheckDecimal((decimal)1011.61, res[1]); + + Assert.AreEqual(10250, res[2].IntField); + CheckDecimal((decimal)1165.83, res[2]); + } + + private static void CheckDecimal(decimal dec, DecimalTypeWithFrenchConversion res) + { + Assert.AreEqual((decimal)dec, res.DecimalField); + Assert.AreEqual((double)dec, res.DoubleField); + Assert.AreEqual((float)dec, res.FloatField); + } + + private static void CheckDecimal(decimal dec, DecimalTypeWithFrenchConversionAsAWhole res) + { + Assert.AreEqual((decimal)dec, res.DecimalField); + Assert.AreEqual((double)dec, res.DoubleField); + Assert.AreEqual((float)dec, res.FloatField); + } + } +} \ No newline at end of file diff --git a/FileHelpers/Attributes/DelimitedRecordAttribute.cs b/FileHelpers/Attributes/DelimitedRecordAttribute.cs index 66721c974..f9fd54b99 100644 --- a/FileHelpers/Attributes/DelimitedRecordAttribute.cs +++ b/FileHelpers/Attributes/DelimitedRecordAttribute.cs @@ -13,7 +13,8 @@ public sealed class DelimitedRecordAttribute : TypedRecordAttribute /// Indicates that this class represents a delimited record. /// The separator string used to split the fields of the record. - public DelimitedRecordAttribute(string delimiter) + /// Default culture name used for each properties if no converter is specified otherwise. If null, the default decimal separator (".") will be used. + public DelimitedRecordAttribute(string delimiter, string defaultCultureName = null) : base(defaultCultureName: defaultCultureName) { if (Separator != String.Empty) this.Separator = delimiter; diff --git a/FileHelpers/Attributes/FixedLengthRecordAttribute.cs b/FileHelpers/Attributes/FixedLengthRecordAttribute.cs index b955f1c0c..f14784d5d 100644 --- a/FileHelpers/Attributes/FixedLengthRecordAttribute.cs +++ b/FileHelpers/Attributes/FixedLengthRecordAttribute.cs @@ -23,7 +23,8 @@ public FixedLengthRecordAttribute() /// specified variable length record behavior. /// /// The used for variable length records. By Default is FixedMode.ExactLength - public FixedLengthRecordAttribute(FixedMode fixedMode) + /// Default culture name used for each properties if no converter is specified otherwise. If null, the default decimal separator (".") will be used. + public FixedLengthRecordAttribute(FixedMode fixedMode, string defaultCultureName = null) : base(defaultCultureName: defaultCultureName) { FixedMode = fixedMode; } diff --git a/FileHelpers/Attributes/TypedRecordAttribute.cs b/FileHelpers/Attributes/TypedRecordAttribute.cs index f42660cf5..e43f6d638 100644 --- a/FileHelpers/Attributes/TypedRecordAttribute.cs +++ b/FileHelpers/Attributes/TypedRecordAttribute.cs @@ -10,10 +10,19 @@ namespace FileHelpers public abstract class TypedRecordAttribute : Attribute { + /// + /// Default culture name used for each properties if no converter is specified otherwise. If null, the default decimal separator (".") will be used. + /// + public string DefaultCultureName { get; private set; } + + #region " Constructors " /// Abstract class, see inheritors. - protected TypedRecordAttribute() {} + protected TypedRecordAttribute(string defaultCultureName) + { + this.DefaultCultureName = defaultCultureName; + } #endregion } diff --git a/FileHelpers/Converters/ConvertHelpers.cs b/FileHelpers/Converters/ConvertHelpers.cs index 934043a91..e36b3b4ca 100644 --- a/FileHelpers/Converters/ConvertHelpers.cs +++ b/FileHelpers/Converters/ConvertHelpers.cs @@ -67,8 +67,9 @@ private static CultureInfo CreateCulture(string decimalSepOrCultureName) /// /// Field name to check /// Type of the field to check + /// Default culture name used for each properties if no converter is specified otherwise. If null, the default decimal separator (".") will be used. /// Converter for this particular field - internal static ConverterBase GetDefaultConverter(string fieldName, Type fieldType) + internal static ConverterBase GetDefaultConverter(string fieldName, Type fieldType, string defaultCultureName=null) { if (fieldType.IsArray) { @@ -98,40 +99,40 @@ internal static ConverterBase GetDefaultConverter(string fieldName, Type fieldTy if (fieldType == typeof(string)) return null; if (fieldType == typeof(Int16)) - return new Int16Converter(); + return new Int16Converter(defaultCultureName ?? DefaultDecimalSep); if (fieldType == typeof(Int32)) - return new Int32Converter(); + return new Int32Converter(defaultCultureName ?? DefaultDecimalSep); if (fieldType == typeof(Int64)) - return new Int64Converter(); + return new Int64Converter(defaultCultureName ?? DefaultDecimalSep); if (fieldType == typeof(SByte)) - return new SByteConverter(); + return new SByteConverter(defaultCultureName ?? DefaultDecimalSep); if (fieldType == typeof(UInt16)) - return new UInt16Converter(); + return new UInt16Converter(defaultCultureName ?? DefaultDecimalSep); if (fieldType == typeof(UInt32)) - return new UInt32Converter(); + return new UInt32Converter(defaultCultureName ?? DefaultDecimalSep); if (fieldType == typeof(UInt64)) - return new UInt64Converter(); + return new UInt64Converter(defaultCultureName ?? DefaultDecimalSep); if (fieldType == typeof(byte)) - return new ByteConverter(); + return new ByteConverter(defaultCultureName ?? DefaultDecimalSep); if (fieldType == typeof(decimal)) - return new DecimalConverter(); + return new DecimalConverter(defaultCultureName ?? DefaultDecimalSep); if (fieldType == typeof(double)) - return new DoubleConverter(); + return new DoubleConverter(defaultCultureName ?? DefaultDecimalSep); if (fieldType == typeof(Single)) - return new SingleConverter(); + return new SingleConverter(defaultCultureName ?? DefaultDecimalSep); if (fieldType == typeof(DateTime)) - return new DateTimeConverter(); + return new DateTimeConverter(ConverterBase.DefaultDateTimeFormat, defaultCultureName); if (fieldType == typeof(bool)) return new BooleanConverter(); diff --git a/FileHelpers/Fields/DelimitedField.cs b/FileHelpers/Fields/DelimitedField.cs index c94918fff..9ffcf25db 100644 --- a/FileHelpers/Fields/DelimitedField.cs +++ b/FileHelpers/Fields/DelimitedField.cs @@ -25,8 +25,8 @@ private DelimitedField() {} /// /// field info structure /// field separator - internal DelimitedField(FieldInfo fi, string sep) - : base(fi) + internal DelimitedField(FieldInfo fi, string sep, string defaultCultureName=null) + : base(fi,defaultCultureName) { QuoteChar = '\0'; QuoteMultiline = MultilineMode.AllowForBoth; diff --git a/FileHelpers/Fields/FieldBase.cs b/FileHelpers/Fields/FieldBase.cs index ec357735d..e071aaa4f 100644 --- a/FileHelpers/Fields/FieldBase.cs +++ b/FileHelpers/Fields/FieldBase.cs @@ -185,8 +185,8 @@ public static FieldBase CreateField(FieldInfo fi, TypedRecordAttribute recordAtt var memberName = "The field: '" + fi.Name; Type fieldType = fi.FieldType; string fieldFriendlyName = AutoPropertyName(fi); - if (string.IsNullOrEmpty(fieldFriendlyName)==false) - { + if (string.IsNullOrEmpty(fieldFriendlyName) == false) + { var prop = fi.DeclaringType.GetProperty(fieldFriendlyName); if (prop != null) { @@ -200,67 +200,82 @@ public static FieldBase CreateField(FieldInfo fi, TypedRecordAttribute recordAtt } // If ignored, return null #pragma warning disable 612,618 // disable obsolete warning - if (mi.IsDefined(typeof (FieldNotInFileAttribute), true) || - mi.IsDefined(typeof (FieldIgnoredAttribute), true) || - mi.IsDefined(typeof (FieldHiddenAttribute), true)) + if (mi.IsDefined(typeof(FieldNotInFileAttribute), true) || + mi.IsDefined(typeof(FieldIgnoredAttribute), true) || + mi.IsDefined(typeof(FieldHiddenAttribute), true)) #pragma warning restore 612,618 return null; - var attributes = (FieldAttribute[]) mi.GetCustomAttributes(typeof (FieldAttribute), true); + var attributes = (FieldAttribute[])mi.GetCustomAttributes(typeof(FieldAttribute), true); // CHECK USAGE ERRORS !!! // Fixed length record and no attributes at all if (recordAttribute is FixedLengthRecordAttribute && - attributes.Length == 0) { + attributes.Length == 0) + { throw new BadUsageException(memberName + "' must be marked the FieldFixedLength attribute because the record class is marked with FixedLengthRecord."); } - if (attributes.Length > 1) { + if (attributes.Length > 1) + { throw new BadUsageException(memberName + "' has a FieldFixedLength and a FieldDelimiter attribute."); } if (recordAttribute is DelimitedRecordAttribute && - mi.IsDefined(typeof (FieldAlignAttribute), false)) { + mi.IsDefined(typeof(FieldAlignAttribute), false)) + { throw new BadUsageException(memberName + "' can't be marked with FieldAlign attribute, it is only valid for fixed length records and are used only for write purpose."); } if (fieldType.IsArray == false && - mi.IsDefined(typeof (FieldArrayLengthAttribute), false)) { + mi.IsDefined(typeof(FieldArrayLengthAttribute), false)) + { throw new BadUsageException(memberName + "' can't be marked with FieldArrayLength attribute is only valid for array fields."); } // PROCESS IN NORMAL CONDITIONS - if (attributes.Length > 0) { + if (attributes.Length > 0) + { FieldAttribute fieldAttb = attributes[0]; - if (fieldAttb is FieldFixedLengthAttribute) { + if (fieldAttb is FieldFixedLengthAttribute) + { // Fixed Field - if (recordAttribute is DelimitedRecordAttribute) { + if (recordAttribute is DelimitedRecordAttribute) + { throw new BadUsageException(memberName + "' can't be marked with FieldFixedLength attribute, it is only for the FixedLengthRecords not for delimited ones."); } - var attbFixedLength = (FieldFixedLengthAttribute) fieldAttb; + var attbFixedLength = (FieldFixedLengthAttribute)fieldAttb; var attbAlign = Attributes.GetFirst(mi); - res = new FixedLengthField(fi, attbFixedLength.Length, attbAlign); - ((FixedLengthField) res).FixedMode = ((FixedLengthRecordAttribute) recordAttribute).FixedMode; + res = new FixedLengthField(fi, + attbFixedLength.Length, + attbAlign, + recordAttribute.DefaultCultureName); + ((FixedLengthField)res).FixedMode = ((FixedLengthRecordAttribute)recordAttribute).FixedMode; } - else if (fieldAttb is FieldDelimiterAttribute) { + else if (fieldAttb is FieldDelimiterAttribute) + { // Delimited Field - if (recordAttribute is FixedLengthRecordAttribute) { + if (recordAttribute is FixedLengthRecordAttribute) + { throw new BadUsageException(memberName + "' can't be marked with FieldDelimiter attribute, it is only for DelimitedRecords not for fixed ones."); } - res = new DelimitedField(fi, ((FieldDelimiterAttribute) fieldAttb).Delimiter); + res = new DelimitedField(fi, + ((FieldDelimiterAttribute)fieldAttb).Delimiter, + recordAttribute.DefaultCultureName); } - else { + else + { throw new BadUsageException( "Custom field attributes are not currently supported. Unknown attribute: " + fieldAttb.GetType().Name + " on field: " + fi.Name); @@ -272,35 +287,41 @@ public static FieldBase CreateField(FieldInfo fi, TypedRecordAttribute recordAtt if (delimitedRecordAttribute != null) { - res = new DelimitedField(fi, delimitedRecordAttribute.Separator); + res = new DelimitedField(fi, + delimitedRecordAttribute.Separator, + recordAttribute.DefaultCultureName); } } - if (res != null) { + if (res != null) + { // FieldDiscarded - res.Discarded = mi.IsDefined(typeof (FieldValueDiscardedAttribute), false); + res.Discarded = mi.IsDefined(typeof(FieldValueDiscardedAttribute), false); // FieldTrim Attributes.WorkWithFirst(mi, - (x) => { + (x) => + { res.TrimMode = x.TrimMode; res.TrimChars = x.TrimChars; }); // FieldQuoted Attributes.WorkWithFirst(mi, - (x) => { - if (res is FixedLengthField) { + (x) => + { + if (res is FixedLengthField) + { throw new BadUsageException( memberName + "' can't be marked with FieldQuoted attribute, it is only for the delimited records."); } - ((DelimitedField) res).QuoteChar = + ((DelimitedField)res).QuoteChar = x.QuoteChar; - ((DelimitedField) res).QuoteMode = + ((DelimitedField)res).QuoteMode = x.QuoteMode; - ((DelimitedField) res).QuoteMultiline = + ((DelimitedField)res).QuoteMultiline = x.QuoteMultiline; }); @@ -320,7 +341,8 @@ public static FieldBase CreateField(FieldInfo fi, TypedRecordAttribute recordAtt res.IsNotEmpty = mi.IsDefined(typeof(FieldNotEmptyAttribute), false); // FieldArrayLength - if (fieldType.IsArray) { + if (fieldType.IsArray) + { res.IsArray = true; res.ArrayType = fieldType.GetElementType(); @@ -329,13 +351,15 @@ public static FieldBase CreateField(FieldInfo fi, TypedRecordAttribute recordAtt res.ArrayMaxLength = int.MaxValue; Attributes.WorkWithFirst(mi, - (x) => { + (x) => + { res.ArrayMinLength = x.MinLength; res.ArrayMaxLength = x.MaxLength; if (res.ArrayMaxLength < res.ArrayMinLength || res.ArrayMinLength < 0 || - res.ArrayMaxLength <= 0) { + res.ArrayMaxLength <= 0) + { throw new BadUsageException(memberName + " has invalid length values in the [FieldArrayLength] attribute."); } @@ -360,8 +384,9 @@ internal static string AutoPropertyName(FieldInfo fi) fi.Name.StartsWith("<") && fi.Name.Contains(">")) return fi.Name.Substring(1, fi.Name.IndexOf(">") - 1); - + } + return ""; } @@ -390,10 +415,10 @@ internal FieldBase() /// Verify the settings against the actual field to ensure it will work. /// /// Field Info Object - internal FieldBase(FieldInfo fi) + internal FieldBase(FieldInfo fi, string defaultCultureName = null) : this() { - + FieldInfo = fi; FieldType = FieldInfo.FieldType; MemberInfo attibuteTarget = fi; @@ -416,28 +441,34 @@ internal FieldBase(FieldInfo fi) else FieldTypeInternal = FieldType; - IsStringField = FieldTypeInternal == typeof (string); + IsStringField = FieldTypeInternal == typeof(string); - object[] attribs = attibuteTarget.GetCustomAttributes(typeof (FieldConverterAttribute), true); + object[] attribs = attibuteTarget.GetCustomAttributes(typeof(FieldConverterAttribute), true); - if (attribs.Length > 0) { - var conv = (FieldConverterAttribute) attribs[0]; + if (attribs.Length > 0) + { + var conv = (FieldConverterAttribute)attribs[0]; Converter = conv.Converter; conv.ValidateTypes(FieldInfo); } else - Converter = ConvertHelpers.GetDefaultConverter(FieldFriendlyName ?? fi.Name, FieldType); + Converter = ConvertHelpers.GetDefaultConverter(FieldFriendlyName ?? fi.Name, + FieldType, + defaultCultureName: defaultCultureName); if (Converter != null) Converter.mDestinationType = FieldTypeInternal; - attribs = attibuteTarget.GetCustomAttributes(typeof (FieldNullValueAttribute), true); + attribs = attibuteTarget.GetCustomAttributes(typeof(FieldNullValueAttribute), true); - if (attribs.Length > 0) { - NullValue = ((FieldNullValueAttribute) attribs[0]).NullValue; + if (attribs.Length > 0) + { + NullValue = ((FieldNullValueAttribute)attribs[0]).NullValue; - if (NullValue != null) { - if (!FieldTypeInternal.IsAssignableFrom(NullValue.GetType())) { + if (NullValue != null) + { + if (!FieldTypeInternal.IsAssignableFrom(NullValue.GetType())) + { throw new BadUsageException("The NullValue is of type: " + NullValue.GetType().Name + " that is not asignable to the field " + FieldInfo.Name + " of type: " + @@ -448,7 +479,7 @@ internal FieldBase(FieldInfo fi) IsNullableType = FieldTypeInternal.IsValueType && FieldTypeInternal.IsGenericType && - FieldTypeInternal.GetGenericTypeDefinition() == typeof (Nullable<>); + FieldTypeInternal.GetGenericTypeDefinition() == typeof(Nullable<>); } #endregion @@ -478,7 +509,8 @@ internal FieldBase(FieldInfo fi) /// String representation of field internal string CreateFieldString(object fieldValue) { - if (Converter == null) { + if (Converter == null) + { if (fieldValue == null) return string.Empty; else @@ -501,9 +533,11 @@ internal object ExtractFieldValue(LineInfo line) { //-> extract only what I need - if (InNewLine) { + if (InNewLine) + { // Any trailing characters, terminate - if (line.EmptyFromPos() == false) { + if (line.EmptyFromPos() == false) + { throw new BadUsageException(line, "Text '" + line.CurrentString + "' found before the new line of the field: " + FieldInfo.Name + @@ -512,14 +546,16 @@ internal object ExtractFieldValue(LineInfo line) line.ReLoad(line.mReader.ReadNextLine()); - if (line.mLineStr == null) { + if (line.mLineStr == null) + { throw new BadUsageException(line, "End of stream found parsing the field " + FieldInfo.Name + ". Please check the class record."); } } - if (IsArray == false) { + if (IsArray == false) + { ExtractedInfo info = ExtractFieldString(line); if (info.mCustomExtractedString == null) line.mCurrentPos = info.ExtractedTo + 1; @@ -531,7 +567,8 @@ internal object ExtractFieldValue(LineInfo line) else return AssignFromString(info, line).Value; } - else { + else + { if (ArrayMinLength <= 0) ArrayMinLength = 0; @@ -540,14 +577,16 @@ internal object ExtractFieldValue(LineInfo line) var res = new ArrayList(Math.Max(ArrayMinLength, 10)); while (line.mCurrentPos - CharsToDiscard < line.mLineStr.Length && - i < ArrayMaxLength) { + i < ArrayMaxLength) + { ExtractedInfo info = ExtractFieldString(line); if (info.mCustomExtractedString == null) line.mCurrentPos = info.ExtractedTo + 1; line.mCurrentPos += CharsToDiscard; - try { + try + { var value = AssignFromString(info, line); if (value.NullValueUsed && @@ -557,16 +596,19 @@ internal object ExtractFieldValue(LineInfo line) res.Add(value.Value); } - catch (NullValueNotFoundException) { + catch (NullValueNotFoundException) + { if (i == 0) break; else throw; } + i++; } - if (res.Count < ArrayMinLength) { + if (res.Count < ArrayMinLength) + { throw new InvalidOperationException( string.Format( "Line: {0} Column: {1} Field: {2}. The array has only {3} values, less than the minimum length of {4}", @@ -576,7 +618,8 @@ internal object ExtractFieldValue(LineInfo line) res.Count, ArrayMinLength)); } - else if (IsLast && line.IsEOL() == false) { + else if (IsLast && line.IsEOL() == false) + { throw new InvalidOperationException( string.Format( "Line: {0} Column: {1} Field: {2}. The array has more values than the maximum length of {3}", @@ -613,18 +656,25 @@ private AssignResult AssignFromString(ExtractedInfo fieldString, LineInfo line) { var extractedString = fieldString.ExtractedString(); - try { + try + { object val; - if (IsNotEmpty && String.IsNullOrEmpty(extractedString)) { + if (IsNotEmpty && String.IsNullOrEmpty(extractedString)) + { throw new InvalidOperationException("The value is empty and must be populated."); - } else if (Converter == null) { + } + else if (Converter == null) + { if (IsStringField) val = TrimString(extractedString); - else { + else + { extractedString = extractedString.Trim(); - if (extractedString.Length == 0) { - return new AssignResult { + if (extractedString.Length == 0) + { + return new AssignResult + { Value = GetNullValue(line), NullValueUsed = true }; @@ -633,24 +683,30 @@ private AssignResult AssignFromString(ExtractedInfo fieldString, LineInfo line) val = Convert.ChangeType(extractedString, FieldTypeInternal, null); } } - else { + else + { var trimmedString = extractedString.Trim(); if (Converter.CustomNullHandling == false && - trimmedString.Length == 0) { - return new AssignResult { + trimmedString.Length == 0) + { + return new AssignResult + { Value = GetNullValue(line), NullValueUsed = true }; } - else { + else + { if (TrimMode == TrimMode.Both) val = Converter.StringToField(trimmedString); else val = Converter.StringToField(TrimString(extractedString)); - if (val == null) { - return new AssignResult { + if (val == null) + { + return new AssignResult + { Value = GetNullValue(line), NullValueUsed = true }; @@ -658,22 +714,27 @@ private AssignResult AssignFromString(ExtractedInfo fieldString, LineInfo line) } } - return new AssignResult { + return new AssignResult + { Value = val }; } - catch (ConvertException ex) { + catch (ConvertException ex) + { ex.FieldName = FieldInfo.Name; ex.LineNumber = line.mReader.LineNumber; ex.ColumnNumber = fieldString.ExtractedFrom + 1; throw; } - catch (BadUsageException) { + catch (BadUsageException) + { throw; } - catch (Exception ex) { + catch (Exception ex) + { if (Converter == null || - Converter.GetType().Assembly == typeof (FieldBase).Assembly) { + Converter.GetType().Assembly == typeof(FieldBase).Assembly) + { throw new ConvertException(extractedString, FieldTypeInternal, FieldInfo.Name, @@ -682,7 +743,8 @@ private AssignResult AssignFromString(ExtractedInfo fieldString, LineInfo line) ex.Message, ex); } - else { + else + { throw new ConvertException(extractedString, FieldTypeInternal, FieldInfo.Name, @@ -697,7 +759,8 @@ private AssignResult AssignFromString(ExtractedInfo fieldString, LineInfo line) private String TrimString(string extractedString) { - switch (TrimMode) { + switch (TrimMode) + { case TrimMode.None: return extractedString; @@ -723,8 +786,10 @@ private String TrimString(string extractedString) /// Null value for object private object GetNullValue(LineInfo line) { - if (NullValue == null) { - if (FieldTypeInternal.IsValueType) { + if (NullValue == null) + { + if (FieldTypeInternal.IsValueType) + { if (IsNullableType) return null; @@ -748,8 +813,10 @@ private object GetNullValue(LineInfo line) /// null value of discard? private object GetDiscardedNullValue() { - if (NullValue == null) { - if (FieldTypeInternal.IsValueType) { + if (NullValue == null) + { + if (FieldTypeInternal.IsValueType) + { if (IsNullableType) return null; @@ -780,10 +847,13 @@ public object CreateValueForField(object fieldValue) { object val; - if (fieldValue == null) { - if (NullValue == null) { + if (fieldValue == null) + { + if (NullValue == null) + { if (FieldTypeInternal.IsValueType && - Nullable.GetUnderlyingType(FieldTypeInternal) == null) { + Nullable.GetUnderlyingType(FieldTypeInternal) == null) + { throw new BadUsageException( "Null Value found. You must specify a FieldNullValueAttribute in the " + FieldInfo.Name + " field of type " + FieldTypeInternal.Name + ", because this is a ValueType."); @@ -796,18 +866,22 @@ public object CreateValueForField(object fieldValue) } else if (FieldTypeInternal == fieldValue.GetType()) val = fieldValue; - else { + else + { if (Converter == null) val = Convert.ChangeType(fieldValue, FieldTypeInternal, null); - else { - try { + else + { + try + { if (Nullable.GetUnderlyingType(FieldTypeInternal) != null && Nullable.GetUnderlyingType(FieldTypeInternal) == fieldValue.GetType()) val = fieldValue; else val = Convert.ChangeType(fieldValue, FieldTypeInternal, null); } - catch { + catch + { val = Converter.StringToField(fieldValue.ToString()); } } @@ -833,9 +907,12 @@ internal void AssignToString(StringBuilder sb, object fieldValue) if (InNewLine) sb.Append(StringHelper.NewLine); - if (IsArray) { - if (fieldValue == null) { - if (0 < ArrayMinLength) { + if (IsArray) + { + if (fieldValue == null) + { + if (0 < ArrayMinLength) + { throw new InvalidOperationException( string.Format("Field: {0}. The array is null, but the minimum length is {1}", FieldInfo.Name, @@ -845,9 +922,10 @@ internal void AssignToString(StringBuilder sb, object fieldValue) return; } - var array = (IList) fieldValue; + var array = (IList)fieldValue; - if (array.Count < ArrayMinLength) { + if (array.Count < ArrayMinLength) + { throw new InvalidOperationException( string.Format("Field: {0}. The array has {1} values, but the minimum length is {2}", FieldInfo.Name, @@ -855,7 +933,8 @@ internal void AssignToString(StringBuilder sb, object fieldValue) ArrayMinLength)); } - if (array.Count > ArrayMaxLength) { + if (array.Count > ArrayMaxLength) + { throw new InvalidOperationException( string.Format("Field: {0}. The array has {1} values, but the maximum length is {2}", FieldInfo.Name, @@ -863,7 +942,8 @@ internal void AssignToString(StringBuilder sb, object fieldValue) ArrayMaxLength)); } - for (int i = 0; i < array.Count; i++) { + for (int i = 0; i < array.Count; i++) + { object val = array[i]; CreateFieldString(sb, val, IsLast && i == array.Count - 1); } diff --git a/FileHelpers/Fields/FixedLengthField.cs b/FileHelpers/Fields/FixedLengthField.cs index 9312924fc..690740be9 100644 --- a/FileHelpers/Fields/FixedLengthField.cs +++ b/FileHelpers/Fields/FixedLengthField.cs @@ -42,8 +42,8 @@ private FixedLengthField() {} /// Field definitions /// Length of this field /// Alignment, left or right - internal FixedLengthField(FieldInfo fi, int length, FieldAlignAttribute align) - : base(fi) + internal FixedLengthField(FieldInfo fi, int length, FieldAlignAttribute align, string defaultCultureName=null) + : base(fi, defaultCultureName) { FixedMode = FixedMode.ExactLength; Align = new FieldAlignAttribute(AlignMode.Left, ' '); From 8bebd4132a56ce722cb9349bdada20fd16e353bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Christophe=20Chalt=C3=A9?= Date: Tue, 11 Sep 2018 16:32:58 +0200 Subject: [PATCH 3/3] Missing comments --- FileHelpers/Fields/DelimitedField.cs | 1 + FileHelpers/Fields/FieldBase.cs | 1 + FileHelpers/Fields/FixedLengthField.cs | 1 + 3 files changed, 3 insertions(+) diff --git a/FileHelpers/Fields/DelimitedField.cs b/FileHelpers/Fields/DelimitedField.cs index 9ffcf25db..93142e5e5 100644 --- a/FileHelpers/Fields/DelimitedField.cs +++ b/FileHelpers/Fields/DelimitedField.cs @@ -25,6 +25,7 @@ private DelimitedField() {} /// /// field info structure /// field separator + /// Default culture name used for each properties if no converter is specified otherwise. If null, the default decimal separator (".") will be used. internal DelimitedField(FieldInfo fi, string sep, string defaultCultureName=null) : base(fi,defaultCultureName) { diff --git a/FileHelpers/Fields/FieldBase.cs b/FileHelpers/Fields/FieldBase.cs index e071aaa4f..6faf7ebd6 100644 --- a/FileHelpers/Fields/FieldBase.cs +++ b/FileHelpers/Fields/FieldBase.cs @@ -415,6 +415,7 @@ internal FieldBase() /// Verify the settings against the actual field to ensure it will work. /// /// Field Info Object + /// Default culture name used for each properties if no converter is specified otherwise. If null, the default decimal separator (".") will be used. internal FieldBase(FieldInfo fi, string defaultCultureName = null) : this() { diff --git a/FileHelpers/Fields/FixedLengthField.cs b/FileHelpers/Fields/FixedLengthField.cs index 690740be9..5b6b73470 100644 --- a/FileHelpers/Fields/FixedLengthField.cs +++ b/FileHelpers/Fields/FixedLengthField.cs @@ -42,6 +42,7 @@ private FixedLengthField() {} /// Field definitions /// Length of this field /// Alignment, left or right + /// Default culture name used for each properties if no converter is specified otherwise. If null, the default decimal separator (".") will be used. internal FixedLengthField(FieldInfo fi, int length, FieldAlignAttribute align, string defaultCultureName=null) : base(fi, defaultCultureName) {