using System;
using System.Diagnostics;
using System.IO;
namespace GisSharpBlog.NetTopologySuite.IO
{
///
/// Class for holding the information assicated with a dbase header.
///
public class DbaseFileHeader
{
// Constant for the size of a record
private int FileDescriptorSize = 32;
// type of the file, must be 03h
private int _fileType=0x03;
// Date the file was last updated.
private DateTime _updateDate;
// Number of records in the datafile
private int _numRecords = 0;
// Length of the header structure
private int _headerLength;
// Length of the records
private int _recordLength;
// Number of fields in the record.
private int _numFields;
// collection of header records.
private DbaseFieldDescriptor[] _fieldDescriptions;
///
/// Initializes a new instance of the DbaseFileHeader class.
///
public DbaseFileHeader() { }
///
/// Return the date this file was last updated.
///
///
public DateTime LastUpdateDate
{
get
{
return _updateDate;
}
}
///
/// Return the number of fields in the records.
///
///
public int NumFields
{
get { return _numFields; }
set { _numFields = value; }
}
///
/// Return the number of records in the file.
///
///
public int NumRecords
{
get { return _numRecords; }
set { _numRecords = value; }
}
///
/// Return the length of the records in bytes.
///
///
public int RecordLength
{
get
{
return _recordLength;
}
}
///
/// Return the length of the header.
///
///
public int HeaderLength
{
get
{
return _headerLength;
}
}
///
/// Add a column to this DbaseFileHeader.
///
/// The name of the field to add.
/// The type is one of (C N L or D) character, number, logical(true/false), or date.
/// The Field length is the total length in bytes reserved for this column.
/// The decimal count only applies to numbers(N), and floating point values (F), and refers to the number of characters to reserve after the decimal point.
public void AddColumn (string fieldName, char fieldType, int fieldLength, int decimalCount)
{
if (fieldLength <=0) fieldLength = 1;
if (_fieldDescriptions == null) _fieldDescriptions = new DbaseFieldDescriptor[0];
int tempLength = 1; // the length is used for the offset, and there is a * for deleted as the first byte
DbaseFieldDescriptor[] tempFieldDescriptors = new DbaseFieldDescriptor[_fieldDescriptions.Length+1];
for (int i=0; i<_fieldDescriptions.Length; i++)
{
_fieldDescriptions[i].DataAddress = tempLength;
tempLength = tempLength + _fieldDescriptions[i].Length;
tempFieldDescriptors[i] = _fieldDescriptions[i];
}
tempFieldDescriptors[_fieldDescriptions.Length] = new DbaseFieldDescriptor();
tempFieldDescriptors[_fieldDescriptions.Length].Length = fieldLength;
tempFieldDescriptors[_fieldDescriptions.Length].DecimalCount = decimalCount;
tempFieldDescriptors[_fieldDescriptions.Length].DataAddress = tempLength;
// set the field name
string tempFieldName = fieldName;
if (tempFieldName == null) tempFieldName = "NoName";
if (tempFieldName.Length > 11)
{
tempFieldName = tempFieldName.Substring(0,11);
Trace.Write("FieldName "+fieldName+" is longer than 11 characters, truncating to "+tempFieldName);
}
tempFieldDescriptors[_fieldDescriptions.Length].Name = tempFieldName;
// the field type
if ((fieldType == 'C') || (fieldType == 'c'))
{
tempFieldDescriptors[_fieldDescriptions.Length].DbaseType = 'C';
if (fieldLength > 254) Trace.WriteLine("Field Length for "+fieldName+" set to "+fieldLength+" Which is longer than 254, not consistent with dbase III");
}
else if ((fieldType == 'S') || (fieldType == 's'))
{
tempFieldDescriptors[_fieldDescriptions.Length].DbaseType = 'C';
Trace.WriteLine("Field type for "+fieldName+" set to S which is flat out wrong people!, I am setting this to C, in the hopes you meant character.");
if (fieldLength >254) Trace.WriteLine("Field Length for "+fieldName+" set to "+fieldLength+" Which is longer than 254, not consistent with dbase III");
tempFieldDescriptors[_fieldDescriptions.Length].Length = 8;
}
else if ((fieldType == 'D') || (fieldType == 'd'))
{
tempFieldDescriptors[_fieldDescriptions.Length].DbaseType = 'D';
if (fieldLength != 8) Trace.WriteLine("Field Length for "+fieldName+" set to "+fieldLength+" Setting to 8 digets YYYYMMDD");
tempFieldDescriptors[_fieldDescriptions.Length].Length = 8;
}
else if ((fieldType == 'F') || (fieldType == 'f'))
{
tempFieldDescriptors[_fieldDescriptions.Length].DbaseType = 'F';
if (fieldLength > 20) Trace.WriteLine("Field Length for "+fieldName+" set to "+fieldLength+" Preserving length, but should be set to Max of 20 not valid for dbase IV, and UP specification, not present in dbaseIII.");
}
else if ((fieldType == 'N') || (fieldType == 'n'))
{
tempFieldDescriptors[_fieldDescriptions.Length].DbaseType = 'N';
if (fieldLength > 18) Trace.WriteLine("Field Length for "+fieldName+" set to "+fieldLength+" Preserving length, but should be set to Max of 18 for dbase III specification.");
if (decimalCount < 0)
{
Trace.WriteLine("Field Decimal Position for "+fieldName+" set to "+decimalCount+" Setting to 0 no decimal data will be saved.");
tempFieldDescriptors[_fieldDescriptions.Length].DecimalCount = 0;
}
if(decimalCount>fieldLength-1)
{
Trace.WriteLine("Field Decimal Position for "+fieldName+" set to "+decimalCount+" Setting to "+(fieldLength-1)+" no non decimal data will be saved.");
tempFieldDescriptors[_fieldDescriptions.Length].DecimalCount = fieldLength-1;
}
}
else if ((fieldType == 'L') || (fieldType == 'l'))
{
tempFieldDescriptors[_fieldDescriptions.Length].DbaseType = 'L';
if (fieldLength != 1) Trace.WriteLine("Field Length for "+fieldName+" set to "+fieldLength+" Setting to length of 1 for logical fields.");
tempFieldDescriptors[_fieldDescriptions.Length].Length = 1;
}
else
{
throw new NotSupportedException("Unsupported field type "+fieldType + " For column "+fieldName);
}
// the length of a record
tempLength = tempLength + tempFieldDescriptors[_fieldDescriptions.Length].Length;
// set the new fields.
_fieldDescriptions = tempFieldDescriptors;
_headerLength = 33+32*_fieldDescriptions.Length;
_numFields = _fieldDescriptions.Length;
_recordLength=tempLength;
}
///
/// Remove a column from this DbaseFileHeader.
///
///
/// return index of the removed column, -1 if no found.
public int RemoveColumn(string fieldName)
{
int retCol = -1;
int tempLength = 1;
DbaseFieldDescriptor[] tempFieldDescriptors =
new DbaseFieldDescriptor[_fieldDescriptions.Length - 1];
for (int i = 0, j = 0; i < _fieldDescriptions.Length; i++)
{
if (fieldName.ToLower()!=(_fieldDescriptions[i].Name.Trim().ToLower()))
{
// if this is the last field and we still haven't found the
// named field
if (i == j && i == _fieldDescriptions.Length - 1)
return retCol;
tempFieldDescriptors[j] = _fieldDescriptions[i];
tempFieldDescriptors[j].DataAddress = tempLength;
tempLength += tempFieldDescriptors[j].Length;
// only increment j on non-matching fields
j++;
}
else retCol = i;
}
// set the new fields.
_fieldDescriptions = tempFieldDescriptors;
_headerLength = 33+32*_fieldDescriptions.Length;
_numFields = _fieldDescriptions.Length;
_recordLength = tempLength;
return retCol;
}
///
/// Read the header data from the DBF file.
///
/// BinaryReader containing the header.
public void ReadHeader(BinaryReader reader)
{
// type of reader.
_fileType = reader.ReadByte();
if (_fileType != 0x03)
throw new NotSupportedException("Unsupported DBF reader Type "+_fileType);
// parse the update date information.
int year = (int)reader.ReadByte();
int month = (int)reader.ReadByte();
int day = (int)reader.ReadByte();
_updateDate = new DateTime(year + 1900, month, day);
// read the number of records.
_numRecords = reader.ReadInt32();
// read the length of the header structure.
_headerLength = reader.ReadInt16();
// read the length of a record
_recordLength = reader.ReadInt16();
// skip the reserved bytes in the header.
//in.skipBytes(20);
reader.ReadBytes(20);
// calculate the number of Fields in the header
_numFields = (_headerLength - FileDescriptorSize -1)/FileDescriptorSize;
// read all of the header records
_fieldDescriptions = new DbaseFieldDescriptor[_numFields];
for (int i=0; i<_numFields; i++)
{
_fieldDescriptions[i] = new DbaseFieldDescriptor();
// read the field name
char[] buffer = new char[11];
buffer = reader.ReadChars(11);
string name = new string(buffer);
int nullPoint = name.IndexOf((char)0);
if(nullPoint != -1)
name = name.Substring(0,nullPoint);
_fieldDescriptions[i].Name = name;
// read the field type
_fieldDescriptions[i].DbaseType = (char) reader.ReadByte();
// read the field data address, offset from the start of the record.
_fieldDescriptions[i].DataAddress = reader.ReadInt32();
// read the field length in bytes
int tempLength = (int) reader.ReadByte();
if (tempLength < 0) tempLength = tempLength + 256;
_fieldDescriptions[i].Length = tempLength;
// read the field decimal count in bytes
_fieldDescriptions[i].DecimalCount = (int) reader.ReadByte();
// read the reserved bytes.
//reader.skipBytes(14);
reader.ReadBytes(14);
}
// Last byte is a marker for the end of the field definitions.
reader.ReadBytes(1);
}
///
/// Set the number of records in the file
///
///
protected void SetNumRecords(int inNumRecords)
{
_numRecords = inNumRecords;
}
///
/// Write the header data to the DBF file.
///
///
public void WriteHeader(BinaryWriter writer)
{
// write the output file type.
writer.Write((byte)_fileType);
writer.Write((byte)(_updateDate.Year - 1900));
writer.Write((byte)_updateDate.Month);
writer.Write((byte)_updateDate.Day);
// write the number of records in the datafile.
writer.Write(_numRecords);
// write the length of the header structure.
writer.Write((short)_headerLength);
// write the length of a record
writer.Write((short)_recordLength);
// write the reserved bytes in the header
for (int i=0; i<20; i++)
writer.Write((byte)0);
// write all of the header records
int tempOffset = 0;
for (int i=0; i<_fieldDescriptions.Length; i++)
{
// write the field name
for (int j=0; j<11; j++)
{
if (_fieldDescriptions[i].Name.Length > j)
writer.Write((byte) _fieldDescriptions[i].Name[j]);
else writer.Write((byte)0);
}
// write the field type
writer.Write((char)_fieldDescriptions[i].DbaseType);
// write the field data address, offset from the start of the record.
writer.Write(0);
tempOffset += _fieldDescriptions[i].Length;
// write the length of the field.
writer.Write((byte)_fieldDescriptions[i].Length);
// write the decimal count.
writer.Write((byte)_fieldDescriptions[i].DecimalCount);
// write the reserved bytes.
for (int j=0; j<14; j++) writer.Write((byte)0);
}
// write the end of the field definitions marker
writer.Write((byte)0x0D);
}
///
/// Returns the fields in the dbase file.
///
public DbaseFieldDescriptor[] Fields
{
get
{
return _fieldDescriptions;
}
}
}
}