"public void Read(BinaryReader reader)" allows for a non-standard 16 bit nFieldLength as documented below.
I have run into some DBFs where the high byte is populated with a non-zero value resulting in columns record lengths that are longer than the actual record.
In my case I'm working around this by attempting to use the extended FieldLength format, checking for length errors, and re-processing without extended FieldLength format if an error is identified
/// <summary>
/// Read header data, make sure the stream is positioned at the start of the file to read the header otherwise you will get an exception.
/// When this function is done the position will be the first record.
/// </summary>
/// <param name="reader"></param>
public void Read(BinaryReader reader)
{
var readerPos = reader.BaseStream.Position;
// Attempt to use extended FieldLength format
Read(reader, true);
// Calculate the expected field length.
var calculatedDataLength = 1;
for (var i = 0; i < mFields.Count; i++)
{
calculatedDataLength += mFields[i].Length;
}
// If the exected field length does not match the calculated, re-processess the file without extended FieldLength format
if (RecordLength != calculatedDataLength)
{
reader.BaseStream.Position = readerPos;
Read(reader, false);
}
}
/// <summary>
/// Read header data, make sure the stream is positioned at the start of the file to read the header otherwise you will get an exception.
/// When this function is done the position will be the first record.
/// </summary>
/// <param name="reader"></param>
internal void Read(BinaryReader reader, bool allowExtendedFieldLength)
{
// type of reader.
int nFileType = reader.ReadByte();
if (nFileType != 0x03)
throw new NotSupportedException("Unsupported DBF reader Type " + nFileType);
// parse the update date information.
int year = (int)reader.ReadByte();
int month = (int)reader.ReadByte();
int day = (int)reader.ReadByte();
mUpdateDate = new DateTime(year + 1900, month, day);
// read the number of records.
mNumRecords = reader.ReadUInt32();
// read the length of the header structure.
mHeaderLength = reader.ReadUInt16();
// read the length of a record
mRecordLength = reader.ReadInt16();
// skip the reserved bytes in the header.
reader.ReadBytes(20);
// calculate the number of Fields in the header
int nNumFields = (mHeaderLength - FileDescriptorSize) / ColumnDescriptorSize;
//offset from start of record, start at 1 because that's the delete flag.
int nDataOffset = 1;
// read all of the header records
mFields = new List<DbfColumn>(nNumFields);
for (int i = 0; i < nNumFields; i++)
{
// read the field name
char[] buffer = new char[11];
buffer = reader.ReadChars(11);
string sFieldName = new string(buffer);
int nullPoint = sFieldName.IndexOf((char)0);
if (nullPoint != -1)
sFieldName = sFieldName.Substring(0, nullPoint);
//read the field type
char cDbaseType = (char)reader.ReadByte();
// read the field data address, offset from the start of the record.
int nFieldDataAddress = reader.ReadInt32();
//read the field length in bytes
//if field type is char, then read FieldLength and Decimal count as one number to allow char fields to be
//longer than 256 bytes (ASCII char). This is the way Clipper and FoxPro do it, and there is really no downside
//since for char fields decimal count should be zero for other versions that do not support this extended functionality.
//-----------------------------------------------------------------------------------------------------------------------
int nFieldLength = 0;
int nDecimals = 0;
if (cDbaseType == 'C' || cDbaseType == 'c')
{
if (allowExtendedFieldLength)
{
//treat decimal count as high byte
nFieldLength = (int)reader.ReadInt16();
}
else
{
//treat decimal count as high byte
nFieldLength = (int)reader.ReadByte();
reader.ReadByte();
}
}
else
{
//read field length as an unsigned byte.
nFieldLength = (int)reader.ReadByte();
//read decimal count as one byte
nDecimals = (int)reader.ReadByte();
}
//read the reserved bytes.
reader.ReadBytes(14);
//Create and add field to collection
mFields.Add(new DbfColumn(sFieldName, DbfColumn.GetDbaseType(cDbaseType), nFieldLength, nDecimals, nDataOffset));
// add up address information, you can not trust the address recorded in the DBF file...
nDataOffset += nFieldLength;
}
// Last byte is a marker for the end of the field definitions.
reader.ReadBytes(1);
//read any extra header bytes...move to first record
//equivalent to reader.BaseStream.Seek(mHeaderLength, SeekOrigin.Begin) except that we are not using the seek function since
//we need to support streams that can not seek like web connections.
int nExtraReadBytes = mHeaderLength - (FileDescriptorSize + (ColumnDescriptorSize * mFields.Count));
if (nExtraReadBytes > 0)
reader.ReadBytes(nExtraReadBytes);
//if the stream is not forward-only, calculate number of records using file size,
//sometimes the header does not contain the correct record count
//if we are reading the file from the web, we have to use ReadNext() functions anyway so
//Number of records is not so important and we can trust the DBF to have it stored correctly.
if (reader.BaseStream.CanSeek && mNumRecords == 0)
{
//notice here that we subtract file end byte which is supposed to be 0x1A,
//but some DBF files are incorrectly written without this byte, so we round off to nearest integer.
//that gives a correct result with or without ending byte.
if (mRecordLength > 0)
mNumRecords = (uint)Math.Round(((double)(reader.BaseStream.Length - mHeaderLength - 1) / mRecordLength));
}
//lock header since it was read from a file. we don't want it modified because that would corrupt the file.
//user can override this lock if really necessary by calling UnLock() method.
mLocked = true;
//clear dirty bit
mIsDirty = false;
}