357 lines
12 KiB
C#
357 lines
12 KiB
C#
/*
|
|
DBFReader
|
|
Class for reading the records assuming that the given
|
|
InputStream comtains DBF data.
|
|
|
|
This file is part of DotNetDBF packege.
|
|
|
|
original author (javadbf): anil@linuxense.com 2004/03/31
|
|
|
|
License: LGPL (http://www.gnu.org/copyleft/lesser.html)
|
|
|
|
ported to C# (DotNetDBF): Jay Tuley <jay+dotnetdbf@tuley.name> 6/28/2007
|
|
*/
|
|
|
|
using System.Text;
|
|
|
|
namespace DotNetDBF
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public class DBFReader : DBFBase, IDisposable
|
|
{
|
|
private readonly BinaryReader _dataInputStream;
|
|
private readonly DBFHeader _header;
|
|
private readonly int _unusedSize;
|
|
/* Class specific variables */
|
|
private bool _isClosed = true;
|
|
|
|
|
|
/**
|
|
Initializes a DBFReader object.
|
|
|
|
When this constructor returns the object
|
|
will have completed reading the header (meta date) and
|
|
header information can be queried there on. And it will
|
|
be ready to return the first row.
|
|
|
|
@param InputStream where the data is read from.
|
|
*/
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public DBFReader(string dbfFilePath)
|
|
{
|
|
try
|
|
{
|
|
_dataInputStream = new BinaryReader(
|
|
File.Open(dbfFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
|
|
);
|
|
|
|
_isClosed = false;
|
|
_header = new DBFHeader();
|
|
_header.Read(_dataInputStream);
|
|
_unusedSize = _header.RecordLength - _header.RecordSize();
|
|
|
|
/* it might be required to leap to the start of records at times */
|
|
var t_dataStartIndex = _header.HeaderLength
|
|
- (32 + (32 * _header.FieldArray.Length))
|
|
- 1;
|
|
if (t_dataStartIndex > 0)
|
|
{
|
|
_dataInputStream.ReadBytes((t_dataStartIndex));
|
|
}
|
|
}
|
|
catch (IOException ex)
|
|
{
|
|
throw new DBFException("Failed To Read DBF", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public DBFReader(Stream stream)
|
|
{
|
|
try
|
|
{
|
|
_dataInputStream = new BinaryReader(stream);
|
|
_isClosed = false;
|
|
_header = new DBFHeader();
|
|
_header.Read(_dataInputStream);
|
|
_unusedSize = _header.RecordLength - _header.RecordSize();
|
|
|
|
/* it might be required to leap to the start of records at times */
|
|
var t_dataStartIndex = _header.HeaderLength
|
|
- (32 + (32 * _header.FieldArray.Length))
|
|
- 1;
|
|
if (t_dataStartIndex > 0)
|
|
{
|
|
_dataInputStream.ReadBytes((t_dataStartIndex));
|
|
}
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
throw new DBFException("Failed To Read DBF", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Returns the number of records in the DBF.
|
|
*/
|
|
|
|
public int RecordCount => _header.NumberOfRecords;
|
|
|
|
/**
|
|
Returns the asked Field. In case of an invalid index,
|
|
it returns a ArrayIndexOutOfBoundsException.
|
|
|
|
@param index. Index of the field. Index of the first field is zero.
|
|
*/
|
|
|
|
public DBFField[] Fields => _header.FieldArray;
|
|
|
|
#region IDisposable Members
|
|
|
|
/// <summary>Performs application-defined tasks associated with freeing, releasing,
|
|
/// or resetting unmanaged resources.</summary>
|
|
/// <filterpriority>2</filterpriority>
|
|
public void Dispose()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public delegate Stream LazyStream();
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public override string ToString()
|
|
{
|
|
var sb =
|
|
new StringBuilder(_header.Year + "/" + _header.Month + "/"
|
|
+ _header.Day + "\n"
|
|
+ "Total records: " + _header.NumberOfRecords +
|
|
"\nHeader length: " + _header.HeaderLength +
|
|
"");
|
|
|
|
for (var i = 0; i < _header.FieldArray.Length; i++)
|
|
{
|
|
sb.Append(_header.FieldArray[i].Name);
|
|
sb.Append("\n");
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public void Close()
|
|
{
|
|
_dataInputStream.Close();
|
|
_isClosed = true;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public void SkipRecord(int count)
|
|
{
|
|
if (_isClosed)
|
|
{
|
|
throw new DBFException("Source is not open");
|
|
}
|
|
|
|
try
|
|
{
|
|
_dataInputStream.BaseStream.Seek(_header.RecordLength * count, SeekOrigin.Current);
|
|
}
|
|
catch (EndOfStreamException)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the returns the next row in the DBF stream.
|
|
/// </summary>
|
|
/// <returns>返回null时不要再读取下一行数据</returns>
|
|
public string[] NextRecord()
|
|
{
|
|
if (_isClosed)
|
|
{
|
|
throw new DBFException("Source is not open");
|
|
}
|
|
|
|
try
|
|
{
|
|
int t_byte = _dataInputStream.ReadByte();
|
|
|
|
if (t_byte == DBFFieldType.EndOfData)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
//0x2a--deleted
|
|
if (t_byte == '*')
|
|
{
|
|
_dataInputStream.BaseStream.Seek(_header.RecordLength - 1, SeekOrigin.Current);
|
|
return new string[0];
|
|
}
|
|
|
|
if (t_byte != 0x20)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var fieldArray = _header.FieldArray;
|
|
var recordObjects = new string[fieldArray.Length];
|
|
|
|
for (var i = 0; i < fieldArray.Length; i++)
|
|
{
|
|
switch (fieldArray[i].DataType)
|
|
{
|
|
case NativeDbType.Char:
|
|
|
|
var b_array = new byte[fieldArray[i].FieldLength];
|
|
_dataInputStream.Read(b_array, 0, b_array.Length);
|
|
recordObjects[i] = CharEncoding.GetString(b_array).Trim();
|
|
break;
|
|
|
|
case NativeDbType.Date:
|
|
|
|
var t_byte_year = new byte[4];
|
|
_dataInputStream.Read(t_byte_year, 0, t_byte_year.Length);
|
|
|
|
var t_byte_month = new byte[2];
|
|
_dataInputStream.Read(t_byte_month, 0, t_byte_month.Length);
|
|
|
|
var t_byte_day = new byte[2];
|
|
_dataInputStream.Read(t_byte_day, 0, t_byte_day.Length);
|
|
|
|
try
|
|
{
|
|
var tYear = CharEncoding.GetString(t_byte_year);
|
|
var tMonth = CharEncoding.GetString(t_byte_month);
|
|
var tDay = CharEncoding.GetString(t_byte_day);
|
|
recordObjects[i] = string.Join("", new[] { tYear, tMonth, tDay });
|
|
}
|
|
catch (ArgumentOutOfRangeException)
|
|
{
|
|
/* this field may be empty or may have improper value set */
|
|
recordObjects[i] = null;
|
|
}
|
|
|
|
break;
|
|
|
|
case NativeDbType.Float:
|
|
|
|
try
|
|
{
|
|
var t_float = new byte[fieldArray[i].FieldLength];
|
|
_dataInputStream.Read(t_float, 0, t_float.Length);
|
|
var tParsed = CharEncoding.GetString(t_float);
|
|
var tLast = tParsed.Substring(tParsed.Length - 1);
|
|
if (tParsed.Length > 0 && tLast != " " && tLast != NullSymbol)
|
|
{
|
|
//
|
|
// A Float in FoxPro has 20 significant digits, since it is
|
|
// stored as a string with possible E-postfix notation.
|
|
// An IEEE 754 float or double can not handle this number of digits
|
|
// correctly. Therefor the only correct implementation is to use a decimal.
|
|
//
|
|
recordObjects[i] = tParsed;
|
|
}
|
|
else
|
|
{
|
|
recordObjects[i] = null;
|
|
}
|
|
}
|
|
catch (FormatException e)
|
|
{
|
|
throw new DBFException("Failed to parse Float", e);
|
|
}
|
|
|
|
break;
|
|
|
|
case NativeDbType.Numeric:
|
|
try
|
|
{
|
|
var t_numeric = new byte[fieldArray[i].FieldLength];
|
|
_dataInputStream.Read(t_numeric, 0, t_numeric.Length);
|
|
var tParsed = CharEncoding.GetString(t_numeric);
|
|
var tLast = tParsed.Substring(tParsed.Length - 1);
|
|
if (tParsed.Length > 0 && tLast != " " && tLast != NullSymbol)
|
|
{
|
|
recordObjects[i] = tParsed;
|
|
}
|
|
else
|
|
{
|
|
recordObjects[i] = null;
|
|
}
|
|
}
|
|
catch (FormatException e)
|
|
{
|
|
throw new DBFException("Failed to parse Number", e);
|
|
}
|
|
|
|
break;
|
|
|
|
case NativeDbType.Logical:
|
|
|
|
var t_logical = _dataInputStream.ReadByte();
|
|
//todo find out whats really valid
|
|
if (t_logical == 'Y' || t_logical == 't' || t_logical == 'T' || t_logical == 't')
|
|
{
|
|
recordObjects[i] = "true";
|
|
}
|
|
else if (t_logical == DBFFieldType.UnknownByte)
|
|
{
|
|
recordObjects[i] = "";
|
|
}
|
|
else
|
|
{
|
|
recordObjects[i] = "false";
|
|
}
|
|
break;
|
|
|
|
case NativeDbType.Memo:
|
|
recordObjects[i] = "#memo";
|
|
break;
|
|
default:
|
|
{
|
|
var data = _dataInputStream.ReadBytes(fieldArray[i].FieldLength);
|
|
recordObjects[i] = data != null ? Convert.ToBase64String(data) : string.Empty;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_unusedSize > 0)
|
|
{
|
|
_dataInputStream.BaseStream.Seek(_unusedSize, SeekOrigin.Current);
|
|
}
|
|
|
|
return recordObjects;
|
|
}
|
|
catch (EndOfStreamException)
|
|
{
|
|
return null;
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
throw new DBFException("Problem Reading File", e);
|
|
}
|
|
}
|
|
}
|
|
}
|