File indexing completed on 2025-01-19 13:27:14

0001 /* This file is part of the KDE project
0002    Copyright (C) 2002 by Thomas Franke and Andreas Pietzowski <andreas@pietzowski.de>
0003                          Ariya Hidayat <ariyahidayat@yahoo.de>
0004 
0005    This library is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU Library General Public
0007    License as published by the Free Software Foundation; either
0008    version 2 of the License, or (at your option) any later version.
0009 
0010    This library is distributed in the hope that it will be useful,
0011    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013    Library General Public License for more details.
0014 
0015    You should have received a copy of the GNU Library General Public License
0016    along with this library; see the file COPYING.LIB.  If not, write to
0017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018  * Boston, MA 02110-1301, USA.
0019 */
0020 
0021 #include "dbase.h"
0022 
0023 #include <QDateTime>
0024 #include <QDataStream>
0025 #include <QFile>
0026 #include <QString>
0027 #include <QStringList>
0028 
0029 DBase::DBase(): m_recordCount(0)
0030 {
0031 }
0032 
0033 DBase::~DBase()
0034 {
0035     while (!fields.isEmpty()) delete fields.takeFirst();
0036     close();
0037 }
0038 
0039 // Headerdefinition in dBASE
0040 //
0041 //  Type                            char   Content
0042 //
0043 //  unsigned char version           0      dBASE-Version (3)
0044 //  unsigned char last_update[3]    1-3    Date of last update
0045 //  unsigned long records           4-7    Number of records
0046 //  unsigned short header_length    8-9    headerlength
0047 //  unsigned short record_length    10-11  recordlength
0048 //  unsigned char reserved[20]      12-31  reserved info from dBase
0049 //
0050 
0051 bool DBase::load(const QString& filename)
0052 {
0053 
0054     m_file.setFileName(filename);
0055     if (!m_file.open(QIODevice::ReadOnly))
0056         return false;
0057 
0058     m_stream.setDevice(&m_file);
0059     m_stream.setByteOrder(QDataStream::LittleEndian);
0060 
0061     unsigned filesize = m_file.size();
0062 
0063     // read dBASE version
0064     quint8 ver;
0065     m_stream >> ver;
0066     m_version = ver & 0x7f; // bit 7: has memo ?
0067 
0068     // only dBASE V.3 is supported
0069     if (m_version != 3)
0070         return false;
0071 
0072     // date of last update
0073     quint8 y, m, d;
0074     m_stream >> y >> m >> d;
0075     // because dBASE saves 102 instead of 2002 (very Y2K-save ;-)
0076     m_lastUpdate.setYMD(y + 1900, m, d);
0077 
0078     // check for valid date
0079     if (!m_lastUpdate.isValid()) return false;
0080 
0081     // number of records
0082     quint32 norec;
0083     m_stream >> norec;
0084     m_recordCount = norec;
0085 
0086     // header-length
0087     quint16 header_length;
0088     m_stream >> header_length;
0089     m_headerLength = header_length;
0090 
0091     // record-length
0092     quint16 record_length;
0093     m_stream >> record_length;
0094     m_recordLength = record_length;
0095 
0096     // read the remaining chars
0097     quint8 dummy;
0098     for (int foo = 0; foo < 20; ++foo)
0099         m_stream >> dummy;
0100 
0101     // size of file must match
0102     if (filesize < m_headerLength + m_recordLength * m_recordCount)
0103         return false;
0104 
0105     // Now read the headers of the columns and their type
0106 
0107     // Type                              char     Content
0108     //
0109     // unsigned char field_name[11]      0-10     Fieldname
0110     // unsigned char field_type          11       Fieldtype
0111     // unsigned long field_address       12-15    Fielddataaddress
0112     // unsigned char field_length        16       Fieldlength
0113     // unsigned char field_decimals      17       decimals
0114     // unsigned char reserved[14]        18-31    reserved for internal dBASE-stuff
0115 
0116     while (!fields.isEmpty()) delete fields.takeFirst();
0117     for (unsigned i = 1; i < m_headerLength / 32; ++i) {
0118         DBaseField* field = new DBaseField;
0119 
0120         // column-name
0121         quint8 colname[12];
0122         for (int j = 0; j < 11; ++j)
0123             m_stream >> colname[j];
0124         colname[11] = '\0';
0125         field->name = QString((const char*) & colname[0]);
0126 
0127         // type of column
0128         quint8 coltype;
0129         m_stream >> coltype;
0130         switch (coltype) {
0131         case 'C': field->type = DBaseField::Character; break;
0132         case 'N': field->type = DBaseField::Numeric; break;
0133         case 'D': field->type = DBaseField::Date; break;
0134         case 'M': field->type = DBaseField::Memo; break;
0135         case 'L': field->type = DBaseField::Logical; break;
0136         default: field->type = DBaseField::Unknown; break;
0137         }
0138 
0139         // fileddataaddress
0140         quint32 addr;
0141         m_stream >> addr;
0142 
0143         // columnlength
0144         quint8 colsize;
0145         m_stream >> colsize;
0146         field->length = colsize;
0147 
0148         // decimals
0149         quint8 decimals;
0150         m_stream >> decimals;
0151         field->decimals = decimals;
0152 
0153         // read remaining chars
0154         quint8 dummy;
0155         for (int foo = 0; foo < 14; ++foo)
0156             m_stream >> dummy;
0157 
0158         // now append
0159         fields.append(field);
0160     }
0161 
0162     // set the index to the first record
0163     m_stream.device()->seek(m_headerLength);
0164 
0165     return true;
0166 }
0167 
0168 QStringList DBase::readRecord(unsigned recno)
0169 {
0170     QStringList result;
0171 
0172     // out of range ? return empty strings
0173     if (recno >= m_recordCount) {
0174         for (int i = 0; i < fields.count(); i++)
0175             result.append("");
0176         return result;
0177     }
0178 
0179     // seek to where the record is
0180     qint64 filepos = m_headerLength + recno * m_recordLength;
0181     m_stream.device()->seek(filepos);
0182 
0183     // first char == '*' means the record is deleted
0184     // so we just skip it
0185     quint8 delmarker;
0186     m_stream >> delmarker;
0187     if (delmarker == 0x2a)
0188         return result;
0189 
0190     // load it
0191     for (int i = 0; i < fields.count(); ++i)
0192         switch (fields.at(i)->type) {
0193             // Numeric or Character
0194         case DBaseField::Numeric:
0195         case DBaseField::Character: {
0196             QString str;
0197             quint8 ch;
0198             for (unsigned j = 0; j < fields.at(i)->length; ++j) {
0199                 m_stream >> ch; str += QChar(ch);
0200             }
0201             result.append(str);
0202         }
0203         break;
0204 
0205         // Logical
0206         case DBaseField::Logical: {
0207             quint8 ch;
0208             m_stream >> ch;
0209             switch (ch) {
0210             case 'Y': case 'y': case 'T': case 't': result.append("True"); break;
0211             case 'N': case 'n': case 'F': case 'f': result.append("False"); break;
0212             default: result.append(""); break;
0213             }
0214         }
0215         break;
0216 
0217         // Date, stored as YYYYMMDD
0218         // Note: convert it to YYYY-MM-DD
0219         case DBaseField::Date: {
0220             QString str;
0221             quint8 ch;
0222             for (unsigned j = 0; j < fields.at(i)->length; j++) {
0223                 m_stream >> ch; str += QChar(ch);
0224             }
0225             str.insert(6, '-');
0226             str.insert(4, '-');
0227             result.append(str);
0228         }
0229         break;
0230 
0231         // Unknown/Unimplemented
0232         case DBaseField::Unknown:
0233         case DBaseField::Memo:
0234         default:
0235             result.append("");   // unknown
0236             break;
0237         }
0238 
0239     return result;
0240 }
0241 
0242 void DBase::close()
0243 {
0244     if (m_file.isOpen()) m_file.close();
0245 }