Skip to content
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,6 @@ test/*
*.o
*.save
*.dbf
*.so
*.so
build
.vscode
15 changes: 15 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.28)
project(dbf)
set(CMAKE_CXX_STANDARD 17)

set(CMAKE_CXX_FLAGS -O3)

include_directories(include)

add_library(dbf SHARED src/DBaseColDef.cpp
src/DBaseField.cpp
src/DBaseFieldProperty.cpp
src/DBaseFile.cpp
src/DBaseHeader.cpp
src/DBaseRecord.cpp
)
20 changes: 18 additions & 2 deletions include/DBaseFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
struct DBaseFile
{
/**< Open file and get contents */
bool openFile(const std::string fileName);
bool openFile(const std::string fileName, bool deferRecordLoading=false);
/**< Open file and get contents */
void stat();
void stat(bool fileInformation=true, bool columnInformation=true, bool recordInformation=true);

/**< \section Member variables */
/**< Header structure */
Expand All @@ -24,6 +24,13 @@ struct DBaseFile
/**< Data records in the file */
std::vector<DBaseRecord> m_records;

/**<deferred record loading>*/
bool readRecordDeferred();

inline unsigned long long getFileSize() const {
return m_fileSize;
}

private:
/**< Read file header safely into std::string */
void readHeader(std::ifstream& iFile);
Expand All @@ -50,6 +57,15 @@ struct DBaseFile
unsigned int m_totalHeaderLength = 0;
/**< Header contents (read raw from disk)*/
std::string m_headerData = "";
/**<special block for visual Foxpro only> */
unsigned int m_dbcSize = 0;
/**<fileName> */
std::string m_fileName;
/**<header + column definition>*/
bool m_headerLoaded = false;
/**<recorded lodeded>*/
bool m_recordLoaded = false;

};

/**< \section Exceptions */
Expand Down
3 changes: 3 additions & 0 deletions include/DBaseRecord.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ struct DBaseRecord
DBaseRecord(std::string& recordStr, std::vector<DBaseColDef>& iFileColDef);
~DBaseRecord();
void stat();
std::vector<std::shared_ptr<std::string>>& getRecordData() {
return m_recordData;
}

private:
std::vector<std::shared_ptr<std::string>> m_recordData;
Expand Down
6 changes: 5 additions & 1 deletion src/DBaseColDef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ DBaseColDef::DBaseColDef(std::string& oneColumn) {
}

// Length of field in bytes
m_fieldLength = (int)oneColumn.at(16);
m_fieldLength = (uint8_t)oneColumn.at(16);
if (m_fieldType == DBaseFieldType::Character && m_fieldLength > 254) {
std::cout << m_fieldName << " field length (" << m_fieldLength << ") exceeds limit of character type(254), truncate to 254" << std::endl;
m_fieldLength = 254;
}

// Number of decimal places
m_fieldDecCount = (int)oneColumn.at(17);
Expand Down
70 changes: 53 additions & 17 deletions src/DBaseFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,19 @@

using namespace std;

constexpr unsigned int DBC_SIZE = 263;

/** \brief Constructs dBase structure from given file.
* \param Name of the dBase file
* \return True if succeded. Otherwise throws exception
*/
bool DBaseFile::openFile(const std::string fileName) {
bool DBaseFile::openFile(const std::string fileName, bool deferRecordLoading) {

//open file and get file size
m_fileName = fileName;
std::ifstream iFile;
if(!iFile && iFile.is_open()) { throw fileNotFoundEx("File is already open in another process."); }
iFile.open(fileName, std::ifstream::ate | std::ifstream::binary);
iFile.open(m_fileName, std::ifstream::ate | std::ifstream::binary);
m_fileSize = (unsigned long long)iFile.tellg();
iFile.seekg(0, iFile.beg);

Expand All @@ -39,16 +42,43 @@ bool DBaseFile::openFile(const std::string fileName) {
//Read file contents into heap memory
readHeader(iFile);

// compatable with viso
if (m_header.m_fileType.find("Visual FoxPro") != std::string::npos) {
m_dbcSize = DBC_SIZE;
}

//Check header
m_colDefLength = m_header.m_numBytesInHeader - m_totalHeaderLength -1;
m_colDefLength = m_header.m_numBytesInHeader - m_fileHeaderLength - m_dbcSize -1;
validateBlockSize(m_colDefBlockSize, m_colDefLength);
if(!(m_headerData.empty())) { m_header.parse(m_headerData);}
validateBlockSize(m_colDefBlockSize, m_colDefLength);
m_headerLoaded = true;

//Read rest of file
readColDef(iFile, m_header);
readRecords(iFile, m_header);

if (!deferRecordLoading) {
readRecords(iFile, m_header);
m_recordLoaded = true;
}

iFile.close();

return true;
}


bool DBaseFile::readRecordDeferred() {
if (!m_headerLoaded) {
return openFile(m_fileName);
}
if (m_recordLoaded) {
return true;
}
std::ifstream iFile;
if(!iFile && iFile.is_open()) { throw fileNotFoundEx("File is already open in another process."); }
iFile.open(m_fileName, std::ifstream::ate | std::ifstream::binary);
readRecords(iFile, m_header);
iFile.close();

return true;
Expand All @@ -65,7 +95,7 @@ void DBaseFile::readHeader(std::ifstream& iFile) {
///Read column definition
void DBaseFile::readColDef(std::ifstream& iFile, DBaseHeader& iFileHeader) {
//omit terminating byte at header end
unsigned int headerLengthWOTerminatingChar = iFileHeader.m_numBytesInHeader - 1;
unsigned int headerLengthWOTerminatingChar = iFileHeader.m_numBytesInHeader - 1 - (m_dbcSize + 1);
iFile.seekg((m_fileHeaderLength), iFile.beg);
std::string colDefBuf((headerLengthWOTerminatingChar - m_fileHeaderLength), ' ');
iFile.read(&(colDefBuf.at(0)), (headerLengthWOTerminatingChar - m_fileHeaderLength));
Expand Down Expand Up @@ -114,21 +144,27 @@ inline void DBaseFile::validateBlockSize(unsigned int& blockSize, unsigned int&
}

///Nice formatting for console output
void DBaseFile::stat() {
void DBaseFile::stat(bool fileInformation, bool columnInformation, bool recordInformation) {
std::cout << std::endl;
if (fileInformation) {
std::cout << "========== FILE INFORMATION ==========" << std::endl;
std::cout << std::endl;
m_header.stat();
std::cout << std::endl;
std::cout << "========= COLUMN INFORMATION =========" << std::endl;
std::cout << std::endl;
for(DBaseColDef d : m_colDef) {
d.stat();
m_header.stat();
std::cout << std::endl;
}
std::cout << std::endl;
std::cout << "========= RECORD INFORMATION =========" << std::endl;
for(DBaseRecord& r : m_records) {
r.stat();
if (columnInformation) {
std::cout << "========= COLUMN INFORMATION =========" << std::endl;
std::cout << std::endl;
for(DBaseColDef d : m_colDef) {
d.stat();
}
std::cout << std::endl;
}
if (recordInformation) {
std::cout << "========= RECORD INFORMATION =========" << std::endl;
for(DBaseRecord& r : m_records) {
r.stat();
}
std::cout << std::endl;
}
std::cout << std::endl;
}
6 changes: 1 addition & 5 deletions src/DBaseHeader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@ void DBaseHeader::parse(std::string& headerData) {
//TODO: read file into m_headerData
for(unsigned int i = 0; i < headerData.size(); i++) {
currentByte = headerData.at(i);

//Read file header bit by bit. Spec of DBF files available at:
//http://www.dbf2002.com/dbf-file-format.html
if(currentByte == 0x0D) {break;}


if(i < m_blockSize) {
switch(i) {
case 0:{
Expand Down