using System; using System.Collections.Generic; using System.Data; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; namespace System.IO { /// /// This class reads a dbf files /// public class DBFReader : IDisposable { private BinaryReader reader; private Encoding encoding; public DBFReader(Stream stream, Encoding encoding) { this.encoding = encoding; this.reader = new BinaryReader(stream, encoding); ReadHeader(); } public DBFReader(string filename, Encoding encoding) { if (File.Exists(filename) == false) throw new FileNotFoundException(); this.encoding = encoding; var bs = new BufferedStream(File.OpenRead(filename)); this.reader = new BinaryReader(bs, encoding); ReadHeader(); } private void ReadHeader() { byte[] buffer = reader.ReadBytes(Marshal.SizeOf(typeof(DBFHeader))); // Marshall the header into a DBFHeader structure GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); this.header = (DBFHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(DBFHeader)); handle.Free(); fields = new List(); while (reader.PeekChar() != 13) { buffer = reader.ReadBytes(Marshal.SizeOf(typeof(DBFFieldDescriptor))); handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); var fieldDescriptor = (DBFFieldDescriptor)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(DBFFieldDescriptor)); if ((fieldDescriptor.Flags & DBFFieldFlags.System) != DBFFieldFlags.System ) { fields.Add(fieldDescriptor); } handle.Free(); } byte headerTerminator = reader.ReadByte(); byte[] backlink = reader.ReadBytes(263); } private void ReadRecords() { records = new List>(); // Skip back to the end of the header. reader.BaseStream.Seek(header.HeaderLenght, SeekOrigin.Begin); for (int i = 0; i < header.NumberOfRecords; i++) { if (reader.PeekChar() == '*') // DELETED { continue; } var record = new Dictionary(); var row = reader.ReadBytes(header.RecordLenght); foreach (var field in fields) { byte[] buffer = new byte[field.FieldLength]; Array.Copy(row, field.Address, buffer, 0, field.FieldLength); string text = (encoding.GetString(buffer) ?? String.Empty).Trim(); switch ((DBFFieldType)field.FieldType) { case DBFFieldType.Character: record[field] = text; break; case DBFFieldType.Currency: if (String.IsNullOrWhiteSpace(text)) { if ((field.Flags & DBFFieldFlags.AllowNullValues) == DBFFieldFlags.AllowNullValues) { record[field] = null; } else { record[field] = 0.0m; } } else { record[field] = Convert.ToDecimal(text); } break; case DBFFieldType.Numeric: case DBFFieldType.Float: if (String.IsNullOrWhiteSpace(text)) { if ((field.Flags & DBFFieldFlags.AllowNullValues) == DBFFieldFlags.AllowNullValues) { record[field] = null; } else { record[field] = 0.0f; } } else { record[field] = Convert.ToSingle(text); } break; case DBFFieldType.Date: if (String.IsNullOrWhiteSpace(text)) { if ((field.Flags & DBFFieldFlags.AllowNullValues) == DBFFieldFlags.AllowNullValues) { record[field] = null; } else { record[field] = DateTime.MinValue; } } else { record[field] = DateTime.ParseExact(text, "yyyyMMdd", CultureInfo.InvariantCulture); } break; case DBFFieldType.DateTime: if (String.IsNullOrWhiteSpace(text) || BitConverter.ToInt64(buffer, 0) == 0) { if ((field.Flags & DBFFieldFlags.AllowNullValues) == DBFFieldFlags.AllowNullValues) { record[field] = null; } else { record[field] = DateTime.MinValue; } } else { record[field] = JulianToDateTime(BitConverter.ToInt64(buffer, 0)); } break; case DBFFieldType.Double: if (String.IsNullOrWhiteSpace(text)) { if ((field.Flags & DBFFieldFlags.AllowNullValues) == DBFFieldFlags.AllowNullValues) { record[field] = null; } else { record[field] = 0.0; } } else { record[field] = Convert.ToDouble(text); } break; case DBFFieldType.Integer: if (String.IsNullOrWhiteSpace(text)) { if ((field.Flags & DBFFieldFlags.AllowNullValues) == DBFFieldFlags.AllowNullValues) { record[field] = null; } else { record[field] = 0; } } else { record[field] = BitConverter.ToInt32(buffer, 0); } break; case DBFFieldType.Logical: if (String.IsNullOrWhiteSpace(text)) { if ((field.Flags & DBFFieldFlags.AllowNullValues) == DBFFieldFlags.AllowNullValues) { record[field] = null; } else { record[field] = false; } } else { record[field] = (buffer[0] == 'Y' || buffer[0] == 'T'); } break; case DBFFieldType.Memo: case DBFFieldType.General: case DBFFieldType.Picture: default: record[field] = buffer; break; } } records.Add(record); } } public DataTable ReadToDataTable() { ReadRecords(); var table = new DataTable(); // Columns foreach (var field in fields) { var colType = ToDbType(field.FieldType); var column = new DataColumn(field.FieldName, colType ?? typeof(String)); table.Columns.Add(column); } // Rows foreach (var record in records) { var row = table.NewRow(); foreach (var column in record.Keys) { row[column.FieldName] = record[column] ?? DBNull.Value; } table.Rows.Add(row); } return table; } public IEnumerable> ReadToDictionary() { ReadRecords(); return records.Select(record => record.ToDictionary(r => r.Key.FieldName, r => r.Value)).ToList(); } public IEnumerable ReadToObject() where T : new() { ReadRecords(); var type = typeof(T); var list = new List(); foreach (var record in records) { T item = new T(); foreach (var pair in record.Select(s => new { Key = s.Key.FieldName, Value = s.Value })) { var property = type.GetProperty(pair.Key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); if (property != null) { if (property.PropertyType == pair.Value.GetType()) { property.SetValue(item, pair.Value, null); } else { if (pair.Value != DBNull.Value) { property.SetValue(item, System.Convert.ChangeType(pair.Value, property.PropertyType), null); } } } } list.Add(item); } return list; } private DBFHeader header; private List fields = new List(); private List> records = new List>(); #region IDisposable public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected void Dispose(bool disposing) { if (disposing == false) return; if (reader != null) { reader.Close(); reader.Dispose(); reader = null; } } ~DBFReader() { Dispose(false); } #endregion /// /// Convert a Julian Date as long to a .NET DateTime structure /// Implemented from pseudo code at http://en.wikipedia.org/wiki/Julian_day /// /// Julian Date to convert (days since 01/01/4713 BC) /// DateTime private static DateTime JulianToDateTime(long julianDateAsLong) { if (julianDateAsLong == 0) return DateTime.MinValue; double p = Convert.ToDouble(julianDateAsLong); double s1 = p + 68569; double n = Math.Floor(4 * s1 / 146097); double s2 = s1 - Math.Floor(((146097 * n) + 3) / 4); double i = Math.Floor(4000 * (s2 + 1) / 1461001); double s3 = s2 - Math.Floor(1461 * i / 4) + 31; double q = Math.Floor(80 * s3 / 2447); double d = s3 - Math.Floor(2447 * q / 80); double s4 = Math.Floor(q / 11); double m = q + 2 - (12 * s4); double j = (100 * (n - 49)) + i + s4; return new DateTime(Convert.ToInt32(j), Convert.ToInt32(m), Convert.ToInt32(d)); } /// /// This is the file header for a DBF. We do this special layout with everything /// packed so we can read straight from disk into the structure to populate it /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] private struct DBFHeader { /// The version. public readonly DBFVersion Version; /// The update year. public readonly byte UpdateYear; /// The update month. public readonly byte UpdateMonth; /// The update day. public readonly byte UpdateDay; /// The number of records. public readonly int NumberOfRecords; /// The length of the header. public readonly short HeaderLenght; /// The length of the bytes records. public readonly short RecordLenght; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public readonly byte[] Reserved; /// Table Flags public readonly DBFTableFlags TableFlags; /// Code Page Mark public readonly byte CodePage; /// Reserved, contains 0x00 public readonly short EndOfHeader; } public enum DBFVersion : byte { Unknown = 0, FoxBase = 0x02, FoxBaseDBase3NoMemo = 0x03, VisualFoxPro = 0x30, VisualFoxProWithAutoIncrement = 0x31, dBase4SQLTableNoMemo = 0x43, dBase4SQLSystemNoMemo = 0x63, FoxBaseDBase3WithMemo = 0x83, dBase4WithMemo = 0x8B, dBase4SQLTableWithMemo = 0xCB, FoxPro2WithMemo = 0xF5, FoxBASE = 0xFB } [Flags] public enum DBFTableFlags : byte { None = 0x00, HasStructuralCDX = 0x01, HasMemoField = 0x02, IsDBC = 0x04 } /// /// This is the field descriptor structure. There will be one of these for each column in the table. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] private struct DBFFieldDescriptor { /// The field name. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)] public readonly string FieldName; /// The field type. public readonly char FieldType; /// The field address. public readonly int Address; /// The field length in bytes. public readonly byte FieldLength; /// The field precision. public readonly byte DecimalCount; /// Field Flags public readonly DBFFieldFlags Flags; /// AutoIncrement next value public readonly int AutoIncrementNextValue; /// AutoIncrement step value public readonly byte AutoIncrementStepValue; /// Reserved [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public readonly byte[] Reserved; public override string ToString() { return String.Format("{0} {1}", FieldName, FieldType); } } [Flags] public enum DBFFieldFlags : byte { None = 0x00, System = 0x01, AllowNullValues = 0x02, Binary = 0x04, AutoIncrementing = 0x0C } public enum DBFFieldType : int { Character = 'C', Currency = 'Y', Numeric = 'N', Float = 'F', Date = 'D', DateTime = 'T', Double = 'B', Integer = 'I', Logical = 'L', Memo = 'M', General = 'G', Picture = 'P' } public static Type ToDbType(char type) { switch ((DBFFieldType)type) { case DBFFieldType.Float: return typeof(float); case DBFFieldType.Integer: return typeof(int); case DBFFieldType.Currency: return typeof(decimal); case DBFFieldType.Character: case DBFFieldType.Memo: return typeof(string); case DBFFieldType.Date: case DBFFieldType.DateTime: return typeof(DateTime); case DBFFieldType.Logical: return typeof(bool); case DBFFieldType.General: case DBFFieldType.Picture: return typeof(byte[]); default: return null; } } } }