Skip to content

Commit 85fe93d

Browse files
committed
fix #75
1 parent 2a723f7 commit 85fe93d

File tree

14 files changed

+318
-134
lines changed

14 files changed

+318
-134
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,8 @@ Troubleshooting
9797
-----
9898

9999
I'm not an expert on dBase and I don't know all the specifics of the field types and versions, so the lib may not be able to handle some situations. If you find an error, please open an issue and send me a sample table that I can reproduce your problem and I'll try to help.
100+
101+
Useful links
102+
-----
103+
104+
[Xbase File Format Description](http://www.manmrk.net/tutorials/database/xbase/)

src/XBase/Enum/TableType.php

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace XBase\Enum;
44

5+
/**
6+
* @see https://www.dbf2002.com/dbf-file-format.html
7+
*/
58
final class TableType
69
{
710
/** @var int dBase II or FoxBASE */
@@ -23,7 +26,7 @@ final class TableType
2326
/** @var int FoxBASE+/dBase III PLUS/FoxPro, with memo (*.DBT) */
2427
const DBASE_III_PLUS_MEMO = 0x83;
2528
/** @var int dBase IV/dBase 5, with memo (*.DBT) */
26-
const DBASE_IV_MEMO_PLUS_MEMO = 0x8B;
29+
const DBASE_IV_MEMO = 0x8B;
2730
/** @var int dBase 7, with memo (*.DBT) */
2831
const DBASE_7_MEMO = 0x8C;
2932
/** @var int dBase IV/dBase 5, SQL table files, with memo (*.DBT) */
@@ -32,7 +35,7 @@ final class TableType
3235
const SMT = 0xE5;
3336
/** @var int dBase IV/dBase 5, SQL system files, with memo */
3437
const DBASE_IV_SQL_SYSTEM_MEMO = 0xEB;
35-
/** @var int FoxPro 2.x ( or earlier) with memo (*.FTP) */
38+
/** @var int FoxPro 2.x ( or earlier) with memo (*.FPT) */
3639
const FOXPRO_MEMO = 0xF5;
3740
/** @var int FoxBASE */
3841
const FOXBASE = 0xFB;
@@ -42,10 +45,32 @@ public static function isFoxpro(int $version): bool
4245
return in_array($version, [
4346
self::VISUAL_FOXPRO,
4447
self::VISUAL_FOXPRO_AI,
48+
self::VISUAL_FOXPRO_VAR,
4549
self::DBASE_III_PLUS_MEMO,
4650
self::DBASE_IV_SQL_TABLE_MEMO,
4751
self::FOXPRO_MEMO,
4852
self::FOXBASE,
4953
]);
5054
}
55+
56+
public static function isVisualFoxpro(int $version): bool
57+
{
58+
return in_array($version, [
59+
self::VISUAL_FOXPRO,
60+
self::VISUAL_FOXPRO_AI,
61+
self::VISUAL_FOXPRO_VAR,
62+
]);
63+
}
64+
65+
public static function hasMemo(int $version): bool
66+
{
67+
return in_array($version, [
68+
self::DBASE_III_PLUS_MEMO,
69+
self::DBASE_IV_MEMO,
70+
self::DBASE_IV_SQL_TABLE_MEMO,
71+
self::DBASE_IV_SQL_SYSTEM_MEMO,
72+
self::DBASE_7_MEMO,
73+
self::FOXPRO_MEMO,
74+
]);
75+
}
5176
}

src/XBase/Memo.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Memo
1414
/**
1515
* Memo constructor.
1616
*
17-
* @param Table $table
17+
* @param Table $table
1818
* @param string $tableName
1919
*/
2020
public function __construct(Table $table, $tableName)
@@ -29,7 +29,7 @@ public function __construct(Table $table, $tableName)
2929
*/
3030
protected function open()
3131
{
32-
$fileName = str_replace(array("dbf", "DBF"), array("fpt", "FPT"), $this->tableName);
32+
$fileName = str_replace(["dbf", "DBF"], ["fpt", "FPT"], $this->tableName);
3333

3434
if (!file_exists($fileName)) {
3535
return false;

src/XBase/Record.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace XBase;
44

5+
use XBase\Enum\TableType;
56
use XBase\Exception\InvalidColumnException;
67

78
class Record
@@ -301,7 +302,11 @@ public function getBoolean($columnName)
301302
public function getMemo($columnName)
302303
{
303304
$data = $this->forceGetString($columnName);
304-
if ($data && strlen($data) == 2) {
305+
if (TableType::FOXPRO_MEMO === $this->table->version) {
306+
return $this->memoFile->get((int) $data);
307+
}
308+
309+
if ($data && 2 === strlen($data)) {
305310
$pointer = unpack('s', $data)[1];
306311
return $this->memoFile->get($pointer);
307312
} else {

src/XBase/Table.php

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ class Table
1212
/** @var int Record field length in bytes */
1313
const FIELD_LENGTH = 32;
1414

15+
/** @var int Visual FoxPro backlist length */
16+
const VFP_BACKLIST_LENGTH = 263;
17+
1518
/** @var string Table filepath. */
1619
protected $tableName;
1720
/** @var array|null */
@@ -43,7 +46,10 @@ class Table
4346
public $encrypted;
4447
/** @var string */
4548
public $mdxFlag;
46-
/** @var string */
49+
/**
50+
* @var string Language codepage.
51+
* @see https://blog.codetitans.pl/post/dbf-and-language-code-page/
52+
*/
4753
public $languageCode;
4854
/** @var Column[] */
4955
public $columns;
@@ -71,7 +77,7 @@ public function __construct($tableName, $availableColumns = null, $convertFrom =
7177
{
7278
$this->tableName = $tableName;
7379
$this->availableColumns = $availableColumns;
74-
$this->convertFrom = $convertFrom;
80+
$this->convertFrom = $convertFrom; //todo autodetect from languageCode
7581
$this->memoFile = new Memo($this, $this->tableName);
7682
$this->open();
7783
}
@@ -108,7 +114,10 @@ protected function readHeader()
108114
$this->languageCode = $this->readByte();
109115
$this->readBytes(2); //reserved
110116

111-
$fieldCount = floor(($this->headerLength - ($this->isFoxpro() ? 296 : 33)) / self::FIELD_LENGTH);
117+
$fieldCount = ($this->headerLength - ((self::HEADER_LENGTH + 1) + (TableType::isVisualFoxpro($this->version) ? self::VFP_BACKLIST_LENGTH : 0))) / self::FIELD_LENGTH;
118+
if (is_float($fieldCount)) {
119+
trigger_error('Wrong fieldCount calculation', E_USER_WARNING);
120+
}
112121

113122
/* some checking */
114123
if ($this->headerLength > filesize($this->tableName)) {
@@ -149,11 +158,15 @@ protected function readHeader()
149158
}
150159
}
151160

152-
if ($this->isFoxpro()) {
153-
$this->backlist = $this->readBytes(263);
161+
if (chr(0x0D) !== $this->readByte()) {
162+
throw new TableException('Expected header terminator not present at position ' . ftell($this->fp));
163+
}
164+
165+
if (TableType::isVisualFoxpro($this->version)) {
166+
$this->backlist = $this->readBytes(self::VFP_BACKLIST_LENGTH);
154167
}
155168

156-
$this->setFilePos($this->headerLength);
169+
// $this->setFilePos($this->headerLength);
157170
$this->recordPos = -1;
158171
$this->record = false;
159172
$this->deleteCount = 0;

tests/FoxproTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace XBase\Tests;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use XBase\Enum\FieldType;
7+
use XBase\Enum\TableFlag;
8+
use XBase\Enum\TableType;
9+
use XBase\Table;
10+
11+
class FoxproTest extends TestCase
12+
{
13+
public function testRead()
14+
{
15+
$table = new Table(__DIR__.'/Resources/foxpro/1.dbf', null, 'cp852'); //todo file to big need to reduce
16+
17+
self::assertSame(TableType::FOXPRO_MEMO, $table->version);
18+
self::assertSame(true, $table->isFoxpro());
19+
self::assertSame(417, $table->headerLength);
20+
self::assertSame(66, $table->recordByteLength);
21+
self::assertSame(false, $table->inTransaction);
22+
self::assertSame(false, $table->encrypted);
23+
self::assertSame(TableFlag::NONE, ord($table->mdxFlag));
24+
self::assertSame(0x64, ord($table->languageCode));
25+
26+
self::assertSame(12, $table->getColumnCount());
27+
// self::assertSame(10, $table->getRecordCount());
28+
29+
$columns = $table->getColumns();
30+
self::assertIsArray($columns);
31+
self::assertCount(12, $columns);
32+
33+
$c = $columns['poz'];
34+
self::assertSame(FieldType::MEMO, $c->getType());
35+
self::assertSame(7, $c->getColIndex());
36+
self::assertSame(10, $c->getLength());
37+
self::assertSame(0x20, $c->getMemAddress());
38+
self::assertSame(0x20, $c->getBytePos());
39+
unset($c);
40+
41+
//<editor-fold desc="record">
42+
$record = $table->nextRecord();
43+
self::assertSame(40777, $record->getNum('idc'));
44+
self::assertSame(1, $record->getNum('clv'));
45+
self::assertSame(57310050.0, $record->getNum('idn'));
46+
self::assertSame(51014, $record->getNum('pvz'));
47+
self::assertSame('Rozhodnutie 1/2014/ROEP Modra o schválení registra zo dňa 31.3.2014 právoplatné dňa 24.4.2014', $record->getMemo('poz'));
48+
//</editor-fold>
49+
50+
$table->close();
51+
}
52+
53+
54+
}

tests/Resources/foxpro/1.dbf

2.63 MB
Binary file not shown.

tests/Resources/foxpro/1.fpt

7.06 MB
Binary file not shown.
776 Bytes
Binary file not shown.
512 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)