Reading a fixed width string in c#

VG008

Senior Member
Joined
Dec 9, 2010
Messages
803
Reaction score
41
Location
Johannesburg
Hi Guys

I have fixed width string which must be broken down into predefined parts. Using substring is one method of achieving this but IMO, using substring on a 200 character string can become untidy and increase the potential of bugs.

Since I come from a c++ background, I though of using a struct. The code works, but I'm unsure if this is the correct method or what the performance impact will be. Could someone please look at it?

Thank you :)

Full .cs file can be found @: https://dl.dropboxusercontent.com/u/65420529/MainWindow.xaml.cs

Code:
        /*=======================================================
         * Structure
         =======================================================*/
        unsafe public struct Transaction
        {
            private const int _RecordIDSize = 2;
            private const int _DateSize = 8;
            private const int _TimeSize = 6;
            private const int _AmountSize = 10;

            public fixed char _RecordID[_RecordIDSize];
            public fixed char _Date[_DateSize];
            public fixed char _Time[_TimeSize];
            public fixed char _Amount[_AmountSize];

            public Transaction(string RawData)
            {
                fixed (char* ptrRecordID = _RecordID)
                {
                    char[] chars = RawData.ToCharArray();
                    Marshal.Copy(chars, 0, new IntPtr(ptrRecordID), chars.Length);
                }
            }

            public string RecordID
            {
                get
                {
                    fixed (char* c = _RecordID)
                    {
                        return CStringToString(new IntPtr(c), Encoding.ASCII, _RecordIDSize);
                    }
                }
            }

            public string Date
            {
                get
                {
                    fixed (char* c = _Date)
                    {
                        return CStringToString(new IntPtr(c), Encoding.ASCII, _DateSize);
                    }
                }
            }

            public string Time
            {
                get
                {
                    fixed (char* c = _Time)
                    {
                        return CStringToString(new IntPtr(c), Encoding.ASCII, _TimeSize);
                    }
                }
            }

            public string Amount
            {
                get
                {
                    fixed (char* c = _Amount)
                    {
                        return CStringToString(new IntPtr(c), Encoding.ASCII, _AmountSize);
                    }
                }
            }

            
        }

        /*========================================================
        * Convert from char array to string variable
        ========================================================*/
        private static unsafe string CStringToString(IntPtr ptr, Encoding encoding, int LengthofCString)
        {
            void* rawPointer = ptr.ToPointer();
            if (rawPointer == null)
                return "";

            char* unsafeCString = (char*)rawPointer;

            int LengthInBytes = encoding.GetByteCount(unsafeCString, LengthofCString);
            byte[] asByteArray = new byte[LengthInBytes];

            fixed (byte* ptrByteArray = asByteArray)
            {
                encoding.GetBytes(unsafeCString, LengthofCString, ptrByteArray, LengthInBytes);
            }

            return encoding.GetString(asByteArray);
        }

        /*=======================================================
         * Example of fixed width string: 01201402101103000000010000
         * 
         * Break Down:
         * 
         * Record ID = 01
         * Date = 20140210
         * Time = 110300
         * Amount = 0000010000       
        =======================================================*/
        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            string RawData = string.Empty;
            Transaction Trans;
            
            RawData = "01201402101103000000010000";
            Trans = new Transaction(RawData);

            unsafe
            {
                MessageBox.Show
                    (
                        Trans.RecordID + "\n" + Trans.Date + "\n" + Trans.Time + "\n" + Trans.Amount
                    );
            }

            
        }
 
that just seems overly complex to me, strings to char arrays to strings

why not something simple, like the following extremely sudo code :)

Code:
Map<String, int> indexMap;
map.put("recordId", 2);
map.put("date", 8);
map.put("time", 6);
map.put("amount", 10);

Map<String, string> dataMap;
String data = "01201402101103000000010000";
lastIndex = 0;
for i in indexMap {
   String value = data.substring(lastIndex, indexMap.get(i));
   dataMap.put(indexMap.key(i), value);
   lastIndex = indexMap.get(i);
}

Transaction transaction = TransactionFactory.mapToTransaction(dataMap); //Transaction has concrete datatypes, eg Date for dates, int for amounts.
 
Shame, you poor C++ coders, always overengineering things! Substring is designed for this sort of thing. Your code is almost unreadable to most C# developers who are unfamiliar with unmanaged code. No need to reinvent the wheel.
 
Wot's sudo? Do you mean pseudo?

Sudowoodo: sudowoodo.jpg

Jokes aside. That solution is overly complex and it is true that for someone that is used to managed code, that piece of unmanaged code is difficult to understand.
 
We wrote an attribute based system for our files.

Structure:
Code:
public class A
  {
    [DefaultValue("000")]
    [OffSet(1, 3)]
    [Order(1)]
    [Padding('0')]
    [PaddingDirection(PaddingDirectionAttribute.PaddingDirection.Right)]
    public string A{ get; set; }

    [DefaultValue("T")]
    [OffSet(4, 1)]
    [Order(2)]
    [Padding(' ')]
    [PaddingDirection(PaddingDirectionAttribute.PaddingDirection.Left)]
    public string B{ get; set; }

    [OffSet(5, 8)]
    [Order(3)]
    [Padding('0')]
    [PaddingDirection(PaddingDirectionAttribute.PaddingDirection.Right)]
    public string C{ get; set; }

    [OffSet(13, 5)]
    [Order(4)]
    [Padding(' ')]
    [PaddingDirection(PaddingDirectionAttribute.PaddingDirection.Left)]
    public string ElectronicBankingSuiteUserCode { get; set; }

    [OffSet(18, 30)]
    [Order(5)]
    [Padding(' ')]
    [PaddingDirection(PaddingDirectionAttribute.PaddingDirection.Right)]
    public string D{ get; set; }

  }

Parser:
Code:
  public static class Converters
  {   
    public static List<PropertyInfo> GetProperties(object obj)
    {
      return obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                  .Select(_ => new
                                  {
                                    Property = _,
                                    Attribute = (OrderAttribute)Attribute.GetCustomAttribute(_, typeof(OrderAttribute), true)
                                  })
                                         .OrderBy(_ => _.Attribute.Order)
                                         .Select(_ => _.Property).ToList<PropertyInfo>();
    }


    public static Tuple<int, int, char, string, PaddingDirectionAttribute.PaddingDirection> GetAttributeProperties(PropertyInfo property, Type type)
    {
      var offSet = CustomAttributeHelper.GetAttributeValue(type, property.Name, typeof(OffSetAttribute), "Offset");
      var length = CustomAttributeHelper.GetAttributeValue(type, property.Name, typeof(OffSetAttribute), "Length");
      var padding = CustomAttributeHelper.GetAttributeValue(type, property.Name, typeof(PaddingAttribute), "PaddingChar");
      var paddingDirection = CustomAttributeHelper.GetAttributeValue(type, property.Name, typeof(PaddingDirectionAttribute), "Direction");
      var defaultValue = CustomAttributeHelper.GetAttributeValue(type, property.Name, typeof(DefaultValue), "Value");

      return new Tuple<int, int, char, string, Attributes.PaddingDirectionAttribute.PaddingDirection>((int)offSet, (int)length, (char)padding,
        defaultValue == null ? string.Empty : defaultValue.ToString(), (Attributes.PaddingDirectionAttribute.PaddingDirection)paddingDirection);
    }
   
    public static string RecordLine(object trans, PropertyInfo property, int offSet, int length, char padding, string defaultValue, PaddingDirectionAttribute.PaddingDirection direction, ref StringBuilder record, ref int? prevOffSet)
    {
      var value = trans.GetType().GetProperty(property.Name).GetValue(trans, null);

      if (prevOffSet == null)
        prevOffSet = ((int)offSet - 1);
      else
        prevOffSet += ((int)offSet - 1);
            
      if (value != null)
      {
        if (value.ToString().Length < (int)length)
        {
          if (direction == PaddingDirectionAttribute.PaddingDirection.Right)
            value = value.ToString().PadRight((int)length, (char)padding);
          else
            value = value.ToString().PadLeft((int)length, (char)padding);
        }
        else if (value.ToString().Length > (int)length)
        {
          value = value.ToString().Substring(0, (int)length);
        }
      }
      else
      {
        if (string.IsNullOrEmpty(defaultValue))
          value = string.Empty;
        else
          value = defaultValue;

        if (direction == PaddingDirectionAttribute.PaddingDirection.Right)
          value = value.ToString().PadRight((int)length, (char)padding);
        else
          value = value.ToString().PadLeft((int)length, (char)padding);
      }
      if (record.Length == 0)
        record.Append(value.ToString().Substring((int)prevOffSet, (int)length));
      else
        record.Append(value.ToString());

      return record.ToString();
    }
    public static dynamic ToObject(dynamic obj, string line)
    {
      int? prevOffset = null;

      foreach (var propInfo in GetProperties(obj) as List<PropertyInfo>)
      {
        var propAttrib = GetAttributeProperties(propInfo, obj.GetType());
        string value = string.Empty;
        if (prevOffset == null)
          value = line.Substring((propAttrib.Item1 - 1), propAttrib.Item2);
        else
          value = line.Substring(((int)prevOffset - 1), propAttrib.Item2);

        prevOffset = ((propAttrib.Item1) + propAttrib.Item2);
        propInfo.SetValue(obj, Convert.ChangeType(value, propInfo.PropertyType), null);
      }

      return obj;
    }
  }

You can then basically just create structures for your files and then just parse them.
 
Semaphore, I like your solution for parsing fixed width files. I was wondering if you could fill in a few things for me. Do you create a class for each attribute? Would you have an example of the CustomAttributeHelper class? I've been trying to back into your code but having a bit of trouble. Any help would be appreciated.
 
Thanks semaphore.

Code:
 public static object GetAttributeValue(Type objectType, string propertyName, Type attributeType, string attributePropertyName)
    {
      var propertyInfo = objectType.GetProperty(propertyName);
      if (propertyInfo != null)
      {
        if (Attribute.IsDefined(propertyInfo, attributeType))
        {
          var attributeInstance = Attribute.GetCustomAttribute(propertyInfo, attributeType);
          if (attributeInstance != null)
          {
            foreach (PropertyInfo info in attributeType.GetProperties())
            {
              if (info.CanRead &&
                String.Compare(info.Name, attributePropertyName,
                StringComparison.InvariantCultureIgnoreCase) == 0)
              {
                return info.GetValue(attributeInstance, null);
              }
            }
          }
        }
      }
      return null;
    }

And yes those classes are mostly attributes.

Code:
 [System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = true)]
  public class PaddingDirectionAttribute : Attribute
  {
    public enum PaddingDirection
    {
      Right,
      Left
    }

    public PaddingDirection Direction { get; private set; }
    public PaddingDirectionAttribute(PaddingDirection direction)
    {
      this.Direction = direction;
    }
  }
 
Semaphore, great design and a good use of reflection. Topnotch work, thank you for your time.
 
Code:
 public static object GetAttributeValue(Type objectType, string propertyName, Type attributeType, string attributePropertyName)
    {
      var propertyInfo = objectType.GetProperty(propertyName);
      if (propertyInfo != null)
      {
        if (Attribute.IsDefined(propertyInfo, attributeType))
        {
          var attributeInstance = Attribute.GetCustomAttribute(propertyInfo, attributeType);
          if (attributeInstance != null)
          {
            foreach (PropertyInfo info in attributeType.GetProperties())
            {
              if (info.CanRead &&
                String.Compare(info.Name, attributePropertyName,
                StringComparison.InvariantCultureIgnoreCase) == 0)
              {
                return info.GetValue(attributeInstance, null);
              }
            }
          }
        }
      }
      return null;
    }

And yes those classes are mostly attributes.

Code:
 [System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = true)]
  public class PaddingDirectionAttribute : Attribute
  {
    public enum PaddingDirection
    {
      Right,
      Left
    }

    public PaddingDirection Direction { get; private set; }
    public PaddingDirectionAttribute(PaddingDirection direction)
    {
      this.Direction = direction;
    }
  }

Reflection. :love:

Saved for future reference. Nice solution.
 
Top
Sign up to the MyBroadband newsletter
X