diff --git a/dBASE.NET/Dbf.cs b/dBASE.NET/Dbf.cs
index d6479f6..7931ccf 100644
--- a/dBASE.NET/Dbf.cs
+++ b/dBASE.NET/Dbf.cs
@@ -1,5 +1,4 @@
-namespace dBASE.NET
-{
+namespace dBASE.NET {
using System;
using System.Collections.Generic;
using System.IO;
@@ -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.
///
- public class Dbf
- {
+ public class Dbf {
private DbfHeader header;
+ /// Emanuele Bonin 24/03/2025
+ /// path of DBF file
+ public string DBFPath;
+
///
/// Initializes a new instance of the .
///
- public Dbf()
- {
+ public Dbf() {
header = DbfHeader.CreateHeader(DbfVersion.FoxBaseDBase3NoMemo);
Fields = new List();
Records = new List();
@@ -28,8 +29,7 @@ public Dbf()
///
/// Custom encoding.
public Dbf(Encoding encoding)
- : this()
- {
+ : this() {
Encoding = encoding ?? throw new ArgumentNullException(nameof(encoding));
}
@@ -53,8 +53,7 @@ public Dbf(Encoding encoding)
/// Creates a new with the same schema as the table.
///
/// A with the same schema as the .
- public DbfRecord CreateRecord()
- {
+ public DbfRecord CreateRecord() {
DbfRecord record = new DbfRecord(Fields);
Records.Add(record);
return record;
@@ -64,20 +63,16 @@ public DbfRecord CreateRecord()
/// Opens a DBF file, reads the contents that initialize the current instance, and then closes the file.
///
/// The file to read.
- 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);
}
}
@@ -89,14 +84,11 @@ public void Read(string path)
///
/// Stream with a database.
/// Stream with a memo.
- 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).");
}
@@ -121,17 +113,31 @@ public void Read(Stream baseStream, Stream memoStream = null)
///
/// The file to read.
/// The version . If unknown specified, use current header version.
- 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);
}
}
@@ -140,10 +146,8 @@ public void Write(string path, DbfVersion version = DbfVersion.Unknown)
///
/// The output stream.
/// The version . If unknown specified, use current header version.
- 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);
}
@@ -151,32 +155,26 @@ public void Write(Stream stream, DbfVersion version = DbfVersion.Unknown)
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);
@@ -185,13 +183,11 @@ 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));
}
@@ -199,36 +195,42 @@ private void ReadFields(BinaryReader reader)
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);
}
@@ -236,14 +238,11 @@ private void WriteRecords(BinaryWriter writer)
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;
}
}
diff --git a/dBASE.NET/Dbf3Header.cs b/dBASE.NET/Dbf3Header.cs
index b9e8e44..87e4406 100644
--- a/dBASE.NET/Dbf3Header.cs
+++ b/dBASE.NET/Dbf3Header.cs
@@ -26,10 +26,14 @@ internal override void Read(BinaryReader reader)
internal override void Write(BinaryWriter writer, List fields, List 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)
@@ -37,14 +41,32 @@ internal override void Write(BinaryWriter writer, List fields, List 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);
}
}
}
diff --git a/dBASE.NET/DbfField.cs b/dBASE.NET/DbfField.cs
index 25c59fc..8d334b2 100644
--- a/dBASE.NET/DbfField.cs
+++ b/dBASE.NET/DbfField.cs
@@ -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;
@@ -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.
diff --git a/dBASE.NET/DbfHeader.cs b/dBASE.NET/DbfHeader.cs
index 191502d..c0ec4e8 100644
--- a/dBASE.NET/DbfHeader.cs
+++ b/dBASE.NET/DbfHeader.cs
@@ -38,7 +38,13 @@ public abstract class DbfHeader
///
public ushort RecordLength { get; set; }
- public static DbfHeader CreateHeader(DbfVersion version)
+ ///
+ /// Emanuele Bonin 24/03/2025
+ /// Has a memo field ? (VisualFoxPro)
+ ///
+ public bool HasMemoField { get; set; }
+
+ public static DbfHeader CreateHeader(DbfVersion version)
{
DbfHeader header;
switch(version)
diff --git a/dBASE.NET/DbfRecord.cs b/dBASE.NET/DbfRecord.cs
index 551a0ab..f5ba947 100644
--- a/dBASE.NET/DbfRecord.cs
+++ b/dBASE.NET/DbfRecord.cs
@@ -1,8 +1,8 @@
-namespace dBASE.NET
-{
+namespace dBASE.NET {
using dBASE.NET.Encoders;
using System;
using System.Collections.Generic;
+ using System.Dynamic;
using System.IO;
using System.Linq;
using System.Text;
@@ -11,15 +11,15 @@
/// DbfRecord encapsulates a record in a .dbf file. It contains an array with
/// data (as an Object) for each field.
///
- public class DbfRecord
- {
+ public class DbfRecord {
private const string defaultSeparator = ",";
private const string defaultMask = "{name}={value}";
+ public Dbf ParentDbf;
+
private List fields;
- internal DbfRecord(BinaryReader reader, DbfHeader header, List fields, byte[] memoData, Encoding encoding)
- {
+ internal DbfRecord(BinaryReader reader, DbfHeader header, List fields, byte[] memoData, Encoding encoding) {
this.fields = fields;
Data = new List