Comparing version 1.7.1 to 1.8.0
@@ -91,2 +91,15 @@ "use strict"; | ||
} | ||
// Locate FoxPro9 memo file, if any. Version 0x30 may or may not have a memo file. | ||
// Conventions for memo extensions: .dbf => .fpt | .pjx => .pjt | .scx => .sct | .vcx => .vct | .frx => .frt ... | ||
if (fileVersion === 0x30) { | ||
const dbExt = path_1.extname(path).toLowerCase(); | ||
const memoExt = dbExt == '.dbf' ? '.fpt' : `.${dbExt.substr(1, 2)}t`; | ||
for (const ext of [memoExt, memoExt.toUpperCase()]) { | ||
memoPath = path.slice(0, -path_1.extname(path).length) + ext; | ||
let foundMemoFile = await utils_1.stat(memoPath).catch(() => 'missing') !== 'missing'; | ||
if (foundMemoFile) | ||
break; | ||
memoPath = undefined; | ||
} | ||
} | ||
// Parse and validate all field descriptors. Skip validation if reading in 'loose' mode. | ||
@@ -235,4 +248,12 @@ let fields = []; | ||
memoFd = await utils_1.open(dbf._memoPath, 'r'); | ||
await utils_1.read(memoFd, buffer, 0, 4, 4); | ||
memoBlockSize = (dbf._version === 0x8b ? buffer.readInt32LE(0) : 0) || 512; | ||
if (dbf._version === 0x30) { | ||
// FoxPro9 | ||
await utils_1.read(memoFd, buffer, 0, 2, 6); | ||
memoBlockSize = buffer.readUInt16BE(0) || 512; | ||
} | ||
else { | ||
// dBASE | ||
await utils_1.read(memoFd, buffer, 0, 4, 4); | ||
memoBlockSize = (dbf._version === 0x8b ? buffer.readInt32LE(0) : 0) || 512; | ||
} | ||
memoBuf = Buffer.alloc(memoBlockSize); | ||
@@ -243,4 +264,5 @@ memoFileSize = (await utils_1.stat(dbf._memoPath)).size; | ||
let currentPosition = dbf._headerLength + recordLength * dbf._recordsRead; | ||
// Create a convenience function for extracting strings from the buffer. | ||
let substr = (start, len, enc) => iconv.decode(buffer.slice(start, start + len), enc); | ||
// Create convenience functions for extracting values from the buffer. | ||
let substrAt = (start, len, enc) => iconv.decode(buffer.slice(start, start + len), enc); | ||
let int32At = (start, len) => buffer.slice(start, start + len).readInt32LE(0); | ||
// Read records in chunks, until enough records have been read. | ||
@@ -281,3 +303,3 @@ let records = []; | ||
--len; | ||
value = substr(offset, len, encoding); | ||
value = substrAt(offset, len, encoding); | ||
offset += field.size; | ||
@@ -289,3 +311,3 @@ break; | ||
++offset, --len; | ||
value = len > 0 ? parseFloat(substr(offset, len, encoding)) : null; | ||
value = len > 0 ? parseFloat(substrAt(offset, len, encoding)) : null; | ||
offset += len; | ||
@@ -309,3 +331,3 @@ break; | ||
case 'D': // Date | ||
value = buffer[offset] === 0x20 ? null : utils_2.parse8CharDate(substr(offset, 8, encoding)); | ||
value = buffer[offset] === 0x20 ? null : utils_2.parse8CharDate(substrAt(offset, 8, encoding)); | ||
offset += 8; | ||
@@ -328,3 +350,5 @@ break; | ||
} | ||
let blockIndex = parseInt(substr(offset, len, encoding)); | ||
let blockIndex = dbf._version === 0x30 | ||
? int32At(offset, len) | ||
: parseInt(substrAt(offset, len, encoding)); | ||
offset += len; | ||
@@ -378,2 +402,28 @@ // If the memo file is missing and we get this far, we must be in 'loose' read mode. | ||
} | ||
// Handle first/next block of FoxPro9 memo data. | ||
else if (dbf._version === 0x30) { | ||
// Memo header | ||
// 00 - 03: Next free block | ||
// 04 - 05: Not used | ||
// 06 - 07: Block size | ||
// 08 - 511: Not used | ||
// Memo Block | ||
// 00 - 03: Type: 0 = image, 1 = text | ||
// 04 - 07: Length | ||
// 08 - N : Data | ||
let skip = 0; | ||
if (value === '') { | ||
const memoType = memoBuf.readInt32BE(0); | ||
if (memoType != 1) | ||
break; | ||
len = memoBuf.readInt32BE(4); | ||
skip = 8; | ||
} | ||
// Read the chunk of memo data, and break out of the loop when all read. | ||
let take = Math.min(len, memoBlockSize - skip); | ||
value += iconv.decode(memoBuf.slice(skip, skip + take), encoding); | ||
len -= take; | ||
if (len === 0) | ||
break; | ||
} | ||
else { | ||
@@ -380,0 +430,0 @@ throw new Error(`Reading version ${dbf._version} memo fields is not supported.`); |
@@ -19,2 +19,3 @@ "use strict"; | ||
// size | ||
const memoSize = fileVersion == 0x30 ? 4 : 10; | ||
if (typeof size !== 'number') | ||
@@ -34,4 +35,4 @@ throw new Error('Size must be a number'); | ||
throw new Error('Invalid field size (must be 8)'); | ||
if (type === 'M' && size !== 10) | ||
throw new Error('Invalid field size (must be 10)'); | ||
if (type === 'M' && size !== memoSize) | ||
throw new Error(`Invalid field size (must be ${memoSize})`); | ||
if (type === 'T' && size !== 8) | ||
@@ -38,0 +39,0 @@ throw new Error('Invalid field size (must be 8)'); |
{ | ||
"name": "dbffile", | ||
"version": "1.7.1", | ||
"version": "1.8.0", | ||
"description": "Read and write .dbf (dBase III & Visual FoxPro) files in Node.js", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -18,3 +18,3 @@ # DBFFile | ||
- read-only (can't create/write DBF files with memo fields) | ||
- dBase III (version 0x83) and dBase IV (version 0x8b) `.dbt` memo files only | ||
- can only read dBase III (version 0x83), dBase IV (version 0x8b), and VFP9 (version 0x30) | ||
- 'Loose' read mode - tries to read any kind of .dbf file without complaining. Unsupported field types are simply skipped. | ||
@@ -21,0 +21,0 @@ - Can open an existing .dbf file |
184349
920