Skip to content

VisualFoxPro dbf types need 263 byte added in the header for dbc data… #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
155 changes: 77 additions & 78 deletions dBASE.NET/Dbf.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
namespace dBASE.NET
{
namespace dBASE.NET {
using System;
using System.Collections.Generic;
using System.IO;
Expand All @@ -9,15 +8,17 @@
/// The Dbf class encapsulated a dBASE table (.dbf) file, allowing
/// reading from disk, writing to disk, enumerating fields and enumerating records.
/// </summary>
public class Dbf
{
public class Dbf {
private DbfHeader header;

/// Emanuele Bonin 24/03/2025
/// path of DBF file
public string DBFPath;

/// <summary>
/// Initializes a new instance of the <see cref="Dbf" />.
/// </summary>
public Dbf()
{
public Dbf() {
header = DbfHeader.CreateHeader(DbfVersion.FoxBaseDBase3NoMemo);
Fields = new List<DbfField>();
Records = new List<DbfRecord>();
Expand All @@ -28,8 +29,7 @@ public Dbf()
/// </summary>
/// <param name="encoding">Custom encoding.</param>
public Dbf(Encoding encoding)
: this()
{
: this() {
Encoding = encoding ?? throw new ArgumentNullException(nameof(encoding));
}

Expand All @@ -53,8 +53,7 @@ public Dbf(Encoding encoding)
/// Creates a new <see cref="DbfRecord" /> with the same schema as the table.
/// </summary>
/// <returns>A <see cref="DbfRecord" /> with the same schema as the <see cref="T:System.Data.DataTable" />.</returns>
public DbfRecord CreateRecord()
{
public DbfRecord CreateRecord() {
DbfRecord record = new DbfRecord(Fields);
Records.Add(record);
return record;
Expand All @@ -64,20 +63,16 @@ public DbfRecord CreateRecord()
/// Opens a DBF file, reads the contents that initialize the current instance, and then closes the file.
/// </summary>
/// <param name="path">The file to read.</param>
public void Read(string path)
{
public void Read(string path) {
// Emanuele Bonin 24/03/2025
DBFPath = path;
// Open stream for reading.
using (FileStream baseStream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (FileStream baseStream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)) {
string memoPath = GetMemoPath(path);
if (memoPath == null)
{
if (memoPath == null) {
Read(baseStream);
}
else
{
using (FileStream memoStream = File.Open(memoPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
} else {
using (FileStream memoStream = File.Open(memoPath, FileMode.Open, FileAccess.Read, FileShare.Read)) {
Read(baseStream, memoStream);
}
}
Expand All @@ -89,14 +84,11 @@ public void Read(string path)
/// </summary>
/// <param name="baseStream">Stream with a database.</param>
/// <param name="memoStream">Stream with a memo.</param>
public void Read(Stream baseStream, Stream memoStream = null)
{
if (baseStream == null)
{
public void Read(Stream baseStream, Stream memoStream = null) {
if (baseStream == null) {
throw new ArgumentNullException(nameof(baseStream));
}
if (!baseStream.CanSeek)
{
if (!baseStream.CanSeek) {
throw new InvalidOperationException("The stream must provide positioning (support Seek method).");
}

Expand All @@ -121,17 +113,31 @@ public void Read(Stream baseStream, Stream memoStream = null)
/// </summary>
/// <param name="path">The file to read.</param>
/// <param name="version">The version <see cref="DbfVersion" />. If unknown specified, use current header version.</param>
public void Write(string path, DbfVersion version = DbfVersion.Unknown)
{
if (version != DbfVersion.Unknown)
{
public void Write(string path, DbfVersion version = DbfVersion.Unknown) {
// Emanuele Bonin 24/03/2025
DBFPath = path;
if (version != DbfVersion.Unknown) {
header.Version = version;
header = DbfHeader.CreateHeader(header.Version);
}

using (FileStream stream = File.Open(path, FileMode.Create, FileAccess.Write))
{
Write(stream, false);
using (FileStream stream = File.Open(path, FileMode.Create, FileAccess.Write)) {
bool Hasmemo = false;
for(int i=0; i < Fields.Count && !Hasmemo; i++) {
Hasmemo = Fields[i].Type == DbfFieldType.Memo;
}
header.HasMemoField = Hasmemo;

if (header.HasMemoField) {
// Create Memo file header
string memoPath = Path.ChangeExtension(path, "fpt");
using (FileStream memoStream = File.Open(memoPath, FileMode.Create, FileAccess.Write)) {

VFPMemoHeader VFPMemoH = new VFPMemoHeader();
VFPMemoH.Create(memoStream);
}
}
Write(stream, false);
}
}

Expand All @@ -140,43 +146,35 @@ public void Write(string path, DbfVersion version = DbfVersion.Unknown)
/// </summary>
/// <param name="stream">The output stream.</param>
/// <param name="version">The version <see cref="DbfVersion" />. If unknown specified, use current header version.</param>
public void Write(Stream stream, DbfVersion version = DbfVersion.Unknown)
{
if (version != DbfVersion.Unknown)
{
public void Write(Stream stream, DbfVersion version = DbfVersion.Unknown) {
if (version != DbfVersion.Unknown) {
header.Version = version;
header = DbfHeader.CreateHeader(header.Version);
}

Write(stream, true);
}

private void Write(Stream stream, bool leaveOpen)
{
using (BinaryWriter writer = new BinaryWriter(stream, Encoding, leaveOpen))
{
private void Write(Stream stream, bool leaveOpen) {
using (BinaryWriter writer = new BinaryWriter(stream, Encoding, leaveOpen)) {
header.Write(writer, Fields, Records);
WriteFields(writer);
WriteRecords(writer);
}
}

private static byte[] ReadMemos(Stream stream)
{
if (stream == null)
{
private static byte[] ReadMemos(Stream stream) {
if (stream == null) {
throw new ArgumentNullException(nameof(stream));
}

using (MemoryStream ms = new MemoryStream())
{
using (MemoryStream ms = new MemoryStream()) {
stream.CopyTo(ms);
return ms.ToArray();
}
}

private void ReadHeader(BinaryReader reader)
{
private void ReadHeader(BinaryReader reader) {
// Peek at version number, then try to read correct version header.
byte versionByte = reader.ReadByte();
reader.BaseStream.Seek(0, SeekOrigin.Begin);
Expand All @@ -185,65 +183,66 @@ private void ReadHeader(BinaryReader reader)
header.Read(reader);
}

private void ReadFields(BinaryReader reader)
{
private void ReadFields(BinaryReader reader) {
Fields.Clear();

// Fields are terminated by 0x0d char.
while (reader.PeekChar() != 0x0d)
{
while (reader.PeekChar() != 0x0d) {
Fields.Add(new DbfField(reader, Encoding));
}

// Read fields terminator.
reader.ReadByte();
}

private void ReadRecords(BinaryReader reader, byte[] memoData)
{
private void ReadRecords(BinaryReader reader, byte[] memoData) {
Records.Clear();

// Records are terminated by 0x1a char (officially), or EOF (also seen).
while (reader.PeekChar() != 0x1a && reader.PeekChar() != -1)
{
try
{
while (reader.PeekChar() != 0x1a && reader.PeekChar() != -1) {
try {
Records.Add(new DbfRecord(reader, header, Fields, memoData, Encoding));
}
catch (EndOfStreamException) { }
} catch (EndOfStreamException) { }
}
}

private void WriteFields(BinaryWriter writer)
{
foreach (DbfField field in Fields)
{
field.Write(writer, Encoding);
private void WriteFields(BinaryWriter writer) {
int Displacement = 0;
foreach (DbfField field in Fields) {
field.Write(writer, Encoding, Displacement);
Displacement += field.Length;
}

// Write field descriptor array terminator.
writer.Write((byte)0x0d);
writer.Write((byte)0x0d);

// Emanuele Bonin 22/03/2025
// For visualFoxPro DBF Table there are other 263 bytes to add to the header
// that is the path to the dbc that belong the table (all 0x00 for no databases)
bool isVFP = header.Version == DbfVersion.VisualFoxPro || header.Version == DbfVersion.VisualFoxProWithAutoIncrement;
if (isVFP) {
for (int i = 0; i < 263; i++) writer.Write((byte)0);
}

}

private void WriteRecords(BinaryWriter writer)
{
foreach (DbfRecord record in Records)
{
private void WriteRecords(BinaryWriter writer) {

foreach (DbfRecord record in Records) {
// Emanuele Bonin 24/04/2025
record.ParentDbf = this;
record.Write(writer, Encoding);
}

// Write EOF character.
writer.Write((byte)0x1a);
}

private static string GetMemoPath(string basePath)
{
private static string GetMemoPath(string basePath) {
string memoPath = Path.ChangeExtension(basePath, "fpt");
if (!File.Exists(memoPath))
{
if (!File.Exists(memoPath)) {
memoPath = Path.ChangeExtension(basePath, "dbt");
if (!File.Exists(memoPath))
{
if (!File.Exists(memoPath)) {
return null;
}
}
Expand Down
46 changes: 34 additions & 12 deletions dBASE.NET/Dbf3Header.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,47 @@ internal override void Read(BinaryReader reader)
internal override void Write(BinaryWriter writer, List<DbfField> fields, List<DbfRecord> records)
{
this.LastUpdate = DateTime.Now;
// Header length = header fields (32b ytes)
// + 32 bytes for each field
// + field descriptor array terminator (1 byte)
this.HeaderLength = (ushort)(32 + fields.Count * 32 + 1);
// Header length = header fields (32b ytes)
// + 32 bytes for each field
// + field descriptor array terminator (1 byte)
// Emanuele Bonin 22/03/2025
// For visualFoxPro DBF Table there are other 263 bytes to add to the header
// that is the path to the dbc that belong the table (all 0x00 for no databases)
bool isVFP = this.Version == DbfVersion.VisualFoxPro || this.Version == DbfVersion.VisualFoxProWithAutoIncrement;
this.HeaderLength = (ushort)(32 + fields.Count * 32 + 1 + (isVFP ? 263 : 0));
this.NumRecords = (uint)records.Count;
this.RecordLength = 1;
foreach (DbfField field in fields)
{
this.RecordLength += field.Length;
}

writer.Write((byte)Version);
writer.Write((byte)(LastUpdate.Year - 1900));
writer.Write((byte)(LastUpdate.Month));
writer.Write((byte)(LastUpdate.Day));
writer.Write(NumRecords);
writer.Write(HeaderLength);
writer.Write(RecordLength);
for (int i = 0; i < 20; i++) writer.Write((byte)0);
// Emanuele Bonin 24/03/2025
// Manage Table Flag to indicate if the table has a Memo field
HasMemoField = false;
byte tableFlag = 0;
if (isVFP && fields.Any(f => f.Type == DbfFieldType.Memo)) {
HasMemoField = true;
tableFlag |= 0x02;
}

writer.Write((byte)Version); // 0x00 Version
writer.Write((byte)(LastUpdate.Year - 1900)); // 0x01 AA
writer.Write((byte)(LastUpdate.Month)); // 0x02 MM
writer.Write((byte)(LastUpdate.Day)); // 0x03 DD
writer.Write(NumRecords); // 0x04 - 0x007 Number of records
writer.Write(HeaderLength); // 0x08 - 0x09 Position of first data record
writer.Write(RecordLength); // 0x0A - 0x0B Length of one data record including delete flag
for (int i = 0; i < 16; i++) writer.Write((byte)0); // 0x0C - 0x1B Reserved
// Visual foxpro
writer.Write((byte)tableFlag); // 0x1C Table flags
// Values:
// 0x01 file has a structural .cdx
// 0x02 file has a Memo field (.fpt file)
// 0x04 file is a database (.dbc)
// This byte can contain the sum of any of the above values.
// For example, the value 0x03 indicates the table has a structural .cdx and a Memo field.
for (int i = 0; i < 3; i++) writer.Write((byte)0);
}
}
}
7 changes: 5 additions & 2 deletions dBASE.NET/DbfField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ internal DbfField(BinaryReader reader, Encoding encoding)
reader.ReadBytes(8);
}

internal void Write(BinaryWriter writer, Encoding encoding)
internal void Write(BinaryWriter writer, Encoding encoding, int Displacement = 0)
{
// Pad field name with 0-bytes, then save it.
string name = this.Name;
Expand All @@ -107,7 +107,10 @@ internal void Write(BinaryWriter writer, Encoding encoding)
}

writer.Write((char)Type);
writer.Write((uint)0); // 4 reserved bytes: Field data address in memory.

writer.Write((uint)Displacement); // 4 reserved bytes: Field data address in memory.
// Displacement of field in record

writer.Write(Length);
writer.Write(Precision);
writer.Write((ushort)0); // 2 reserved byte.
Expand Down
8 changes: 7 additions & 1 deletion dBASE.NET/DbfHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,13 @@ public abstract class DbfHeader
/// </summary>
public ushort RecordLength { get; set; }

public static DbfHeader CreateHeader(DbfVersion version)
/// <summary>
/// Emanuele Bonin 24/03/2025
/// Has a memo field ? (VisualFoxPro)
/// </summary>
public bool HasMemoField { get; set; }

public static DbfHeader CreateHeader(DbfVersion version)
{
DbfHeader header;
switch(version)
Expand Down
Loading