diff --git a/FileHelpers.FSharp.Tests/FileHelpers.FSharp.Tests.csproj b/FileHelpers.FSharp.Tests/FileHelpers.FSharp.Tests.csproj
index 1f8bc4cbb..643434aa8 100644
--- a/FileHelpers.FSharp.Tests/FileHelpers.FSharp.Tests.csproj
+++ b/FileHelpers.FSharp.Tests/FileHelpers.FSharp.Tests.csproj
@@ -65,6 +65,9 @@
3.12.0
+
+ 3.17.0
+
1.5.0
diff --git a/FileHelpers.Tests/FileHelpers.Tests.csproj b/FileHelpers.Tests/FileHelpers.Tests.csproj
index 0d8c63b41..96e37d9e0 100644
--- a/FileHelpers.Tests/FileHelpers.Tests.csproj
+++ b/FileHelpers.Tests/FileHelpers.Tests.csproj
@@ -147,6 +147,7 @@
Code
+
@@ -658,6 +659,9 @@
3.12.0
+
+ 3.17.0
+
diff --git a/FileHelpers.Tests/Tests/Common/DelimitedFieldPadding.cs b/FileHelpers.Tests/Tests/Common/DelimitedFieldPadding.cs
new file mode 100644
index 000000000..fbaf2ed5c
--- /dev/null
+++ b/FileHelpers.Tests/Tests/Common/DelimitedFieldPadding.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+
+namespace FileHelpers.Tests.CommonTests
+{
+ [TestFixture]
+ public class DelimitedFieldPadding
+ {
+
+ [DelimitedRecord(",")]
+ private class AlignCenterSample
+ {
+ public AlignCenterSample()
+ {
+
+ }
+
+ public AlignCenterSample(string name, int amount, DateTime date )
+ {
+ this.Name = name;
+ this.Amount = amount;
+ this.Date = date;
+ }
+
+
+ [DelimitedFieldPadding(6, AlignMode.Center, '+')]
+ public string Name { get; set; }
+
+ [DelimitedFieldPadding(5, AlignMode.Center, '+')]
+ [FieldConverter(ConverterKind.Int32)]
+ public int Amount { get; set; }
+
+ [DelimitedFieldPadding(12, AlignMode.Center, '+')]
+ [FieldConverter(ConverterKind.Date, "yyyy-MM-dd")]
+ public DateTime Date { get; set; }
+ }
+
+
+ [Test]
+ public void TestPaddingCenterAlign()
+ {
+
+ List records = new List();
+ records.Add(new AlignCenterSample("AA", 100, new DateTime(2020, 1, 1)));
+
+
+ var engine = new FileHelperEngine();
+ string output = engine.WriteString(records).TrimEnd();
+
+ string expected = "++AA++,+100+,+2020-01-01+";
+
+ Assert.AreEqual(expected, output);
+ }
+
+
+ [DelimitedRecord(",")]
+ private class AlignLeftSample
+ {
+ public AlignLeftSample()
+ {
+
+ }
+
+ public AlignLeftSample(string name, int amount, DateTime date)
+ {
+ this.Name = name;
+ this.Amount = amount;
+ this.Date = date;
+ }
+
+
+ [DelimitedFieldPadding(6, AlignMode.Left, ' ')]
+ public string Name { get; set; }
+
+ [DelimitedFieldPadding(5, AlignMode.Left, ' ')]
+ [FieldConverter(ConverterKind.Int32)]
+ public int Amount { get; set; }
+
+ [DelimitedFieldPadding(12, AlignMode.Left, '*')]
+ [FieldConverter(ConverterKind.Date, "yyyy-MM-dd")]
+ public DateTime Date { get; set; }
+ }
+
+
+ [Test]
+ public void TestPaddingLeftAlign()
+ {
+
+ var records = new List();
+ records.Add(new AlignLeftSample("AA", 100, new DateTime(2020, 1, 1)));
+
+
+ var engine = new FileHelperEngine();
+ string output = engine.WriteString(records).TrimEnd();
+
+ string expected = "AA ,100 ,2020-01-01**";
+
+ Assert.AreEqual(expected, output);
+ }
+
+
+ [DelimitedRecord(",")]
+ private class AlignRightSample
+ {
+ public AlignRightSample()
+ {
+
+ }
+
+ public AlignRightSample(string name, int amount, DateTime date)
+ {
+ this.Name = name;
+ this.Amount = amount;
+ this.Date = date;
+ }
+
+
+ [DelimitedFieldPadding(6, AlignMode.Right, ' ')]
+ public string Name { get; set; }
+
+ [DelimitedFieldPadding(5, AlignMode.Right, '*')]
+ [FieldConverter(ConverterKind.Int32)]
+ public int Amount { get; set; }
+
+ [DelimitedFieldPadding(12, AlignMode.Right, '+')]
+ [FieldConverter(ConverterKind.Date, "yyyy-MM-dd")]
+ public DateTime Date { get; set; }
+ }
+
+
+
+
+ [Test]
+ public void TestPaddingRightAlign()
+ {
+
+ var records = new List();
+ records.Add(new AlignRightSample("AA", 100, new DateTime(2020, 1, 1)));
+
+
+ var engine = new FileHelperEngine();
+ string output = engine.WriteString(records).TrimEnd();
+
+ string expected = " AA,**100,++2020-01-01";
+
+ Assert.AreEqual(expected, output);
+ }
+
+
+ [FixedLengthRecord]
+ private class FixedWithWithDelimitedFiledPaddingSample
+ {
+ public FixedWithWithDelimitedFiledPaddingSample()
+ {
+
+ }
+
+ public FixedWithWithDelimitedFiledPaddingSample(string name, string description)
+ {
+ this.Name = name;
+ this.Description = description;
+ }
+
+ [DelimitedFieldPadding(10, AlignMode.Left, ' ')]
+ [FieldFixedLength(10)]
+ public string Name { get; set; }
+
+ [DelimitedFieldPadding(50, AlignMode.Left, ' ')]
+ [FieldFixedLength(50)]
+ public string Description { get; set; }
+
+
+ }
+
+
+
+
+
+
+
+ [Test]
+ public void DelimitedFieldPaddingInvalidOnFixedWidthFile()
+ {
+
+ Assert.Throws(() =>
+ {
+ var engine = new FileHelperEngine();
+ });
+
+ }
+ }
+}
diff --git a/FileHelpers/Attributes/DelimitedFieldPaddingAttribute.cs b/FileHelpers/Attributes/DelimitedFieldPaddingAttribute.cs
new file mode 100644
index 000000000..85902c2bd
--- /dev/null
+++ b/FileHelpers/Attributes/DelimitedFieldPaddingAttribute.cs
@@ -0,0 +1,45 @@
+using System;
+
+
+namespace FileHelpers
+{
+ ///
+ /// Supports padding when applied to a Delimited record field or property
+ ///
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class DelimitedFieldPaddingAttribute : Attribute
+ {
+ ///
+ /// Total length of the fields in the output file.
+ ///
+ public int TotalLength { get; private set; }
+
+ ///
+ /// The position of the alignment.
+ ///
+ public AlignMode AlignMode { get; private set; }
+
+ ///
+ /// The character for padding.
+ ///
+ public char PaddingChar { get; private set; }
+
+
+ ///
+ /// Adds padding for a delimited file record field
+ ///
+ /// Total length
+ /// Alignment position
+ /// Character used for padding
+ public DelimitedFieldPaddingAttribute(int totalLength, AlignMode alignMode, char paddingChar)
+ {
+
+ this.PaddingChar = paddingChar;
+
+ this.TotalLength = totalLength;
+
+ this.AlignMode = alignMode;
+
+ }
+ }
+}
diff --git a/FileHelpers/Fields/DelimitedField.cs b/FileHelpers/Fields/DelimitedField.cs
index d8b40fb39..ac148aa24 100644
--- a/FileHelpers/Fields/DelimitedField.cs
+++ b/FileHelpers/Fields/DelimitedField.cs
@@ -1,3 +1,4 @@
+using System;
using System.Globalization;
using System.Reflection;
using System.Text;
@@ -186,6 +187,10 @@ internal override void CreateFieldString(StringBuilder sb, object fieldValue, bo
{
string field = base.CreateFieldString(fieldValue);
+
+ if (this.DelimitedFieldPaddingChar.HasValue && this.DelimitedTotalLength > 0 && field != null && field.Length < this.DelimitedTotalLength)
+ field = this.applyDelimitedFieldPadding(field);
+
bool hasNewLine = mCompare.IndexOf(field, StringHelper.NewLine, CompareOptions.Ordinal) >= 0;
// If have a new line and this is not allowed. We throw an exception
@@ -214,6 +219,54 @@ internal override void CreateFieldString(StringBuilder sb, object fieldValue, bo
sb.Append(Separator);
}
+ private string applyDelimitedFieldPadding(string baseFieldValue)
+ {
+
+ string safeFieldValue = baseFieldValue ?? String.Empty;
+
+ string formattedOutput = null;
+
+
+
+ if (baseFieldValue != null && this.DelimitedTotalLength > 0 && baseFieldValue.Length < this.DelimitedTotalLength && this.DelimitedFieldPaddingChar.HasValue)
+ {
+ switch (this.DelimitedFieldAlignMode)
+ {
+ case AlignMode.Left:
+ formattedOutput = safeFieldValue.PadRight(this.DelimitedTotalLength, this.DelimitedFieldPaddingChar.Value);
+ break;
+
+ case AlignMode.Right:
+ formattedOutput = safeFieldValue.PadLeft(this.DelimitedTotalLength, this.DelimitedFieldPaddingChar.Value);
+ break;
+
+ case AlignMode.Center:
+ formattedOutput = this.padCenter(safeFieldValue);
+ break;
+
+
+ }
+
+ }
+ else
+ formattedOutput = baseFieldValue;
+
+
+ return formattedOutput;
+ }
+
+ private string padCenter(string baseFieldValue)
+ {
+ if (baseFieldValue == null)
+ throw new NullReferenceException("baseFieldValue");
+
+ int totalPaddingCount = this.DelimitedTotalLength - baseFieldValue.Length;
+
+ int padLeftCount = totalPaddingCount / 2 + baseFieldValue.Length;
+
+ return baseFieldValue.PadLeft(padLeftCount, this.DelimitedFieldPaddingChar.GetValueOrDefault()).PadRight(this.DelimitedTotalLength, this.DelimitedFieldPaddingChar.GetValueOrDefault());
+ }
+
///
/// create a field base class and populate the delimited values
/// base class will add its own values
diff --git a/FileHelpers/Fields/FieldBase.cs b/FileHelpers/Fields/FieldBase.cs
index bcbafc8d6..d03ba76ab 100644
--- a/FileHelpers/Fields/FieldBase.cs
+++ b/FileHelpers/Fields/FieldBase.cs
@@ -158,6 +158,12 @@ internal bool NextIsOptional
///
internal string FieldCaption { get; private set; }
+ internal char? DelimitedFieldPaddingChar { get; private set; }
+
+ internal AlignMode DelimitedFieldAlignMode { get; private set; }
+
+ internal int DelimitedTotalLength { get; private set; }
+
// --------------------------------------------------------------
// WARNING !!!
// Remember to add each of these fields to the clone method !!
@@ -207,6 +213,8 @@ public static FieldBase CreateField(FieldInfo fi, TypedRecordAttribute recordAtt
#pragma warning restore 612,618
return null;
+
+
var attributes = (FieldAttribute[])mi.GetCustomAttributes(typeof(FieldAttribute), true);
// CHECK USAGE ERRORS !!!
@@ -239,6 +247,14 @@ public static FieldBase CreateField(FieldInfo fi, TypedRecordAttribute recordAtt
"' can't be marked with the FieldArrayLength attribute - it is only valid for array fields.");
}
+ if(!(recordAttribute is DelimitedRecordAttribute)
+ && mi.IsDefined(typeof(DelimitedFieldPaddingAttribute), false))
+ {
+ throw new BadUsageException(memberName +
+ "' can't be marked with the PaddingConverterAttribute attribute - it is only valid for delimited files.");
+
+ }
+
// PROCESS IN NORMAL CONDITIONS
if (attributes.Length > 0)
{
@@ -299,6 +315,8 @@ public static FieldBase CreateField(FieldInfo fi, TypedRecordAttribute recordAtt
// FieldDiscarded
res.Discarded = mi.IsDefined(typeof(FieldValueDiscardedAttribute), false);
+
+
// FieldTrim
Attributes.WorkWithFirst(mi,
(x) =>
@@ -371,6 +389,16 @@ public static FieldBase CreateField(FieldInfo fi, TypedRecordAttribute recordAtt
if (string.IsNullOrEmpty(res.FieldFriendlyName))
res.FieldFriendlyName = res.FieldName;
+ //PaddingConverter
+ Attributes.WorkWithFirst(mi,
+ (x) =>
+ {
+ res.DelimitedFieldAlignMode = x.AlignMode;
+ res.DelimitedFieldPaddingChar = x.PaddingChar;
+ res.DelimitedTotalLength = x.TotalLength;
+ });
+
+
return res;
}
@@ -420,6 +448,9 @@ internal FieldBase()
NullValue = null;
IsArray = false;
IsNotEmpty = false;
+ DelimitedFieldPaddingChar = null;
+ DelimitedFieldAlignMode = AlignMode.Left;
+ DelimitedTotalLength = 0;
}
///
@@ -889,6 +920,8 @@ public object CreateValueForField(object fieldValue)
}
}
+
+
return val;
}
@@ -978,6 +1011,9 @@ internal FieldBase Clone()
res.FieldCaption = FieldCaption;
res.Parent = Parent;
res.ParentIndex = ParentIndex;
+ res.DelimitedFieldAlignMode = DelimitedFieldAlignMode;
+ res.DelimitedFieldPaddingChar = DelimitedFieldPaddingChar;
+ res.DelimitedTotalLength = DelimitedTotalLength;
return res;
}
diff --git a/FileHelpers/FileHelpers.csproj b/FileHelpers/FileHelpers.csproj
index 2da802573..7515f899a 100644
--- a/FileHelpers/FileHelpers.csproj
+++ b/FileHelpers/FileHelpers.csproj
@@ -41,7 +41,7 @@
3.5
- false
+ false
..\Debug\Bin\
@@ -55,7 +55,8 @@
true
4096
false
-
+
+
false
false
false
@@ -104,6 +105,7 @@
Code
+
Code