Skip to content

Commit f2491e9

Browse files
authored
Merge pull request #85 from luads/issue_83
fix #83
2 parents e6bf87a + deba186 commit f2491e9

File tree

4 files changed

+71
-16
lines changed

4 files changed

+71
-16
lines changed

src/XBase/Record/RecordFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class RecordFactory
99
{
10-
public static function create(Table $table, int $recordIndex, $rawData = false): RecordInterface
10+
public static function create(Table $table, int $recordIndex, $rawData = false): ?RecordInterface
1111
{
1212
$class = self::getClass($table->getVersion());
1313
$refClass = new \ReflectionClass($class);

src/XBase/Table.php

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public function __construct($filepath, $availableColumns = null, $convertFrom =
9494
}
9595
}
9696

97-
protected function open()
97+
protected function open(): void
9898
{
9999
if (!file_exists($this->filepath)) {
100100
throw new \Exception(sprintf('File %s cannot be found', $this->filepath));
@@ -108,12 +108,12 @@ protected function open()
108108
$this->readHeader();
109109
}
110110

111-
public function close()
111+
public function close(): void
112112
{
113113
$this->fp->close();
114114
}
115115

116-
protected function readHeader()
116+
protected function readHeader(): void
117117
{
118118
$this->version = $this->fp->readUChar();
119119
$this->foxpro = TableType::isFoxpro($this->version);
@@ -134,11 +134,9 @@ protected function readHeader()
134134
$this->fp->read(4);
135135
}
136136

137-
$this->readColumns();
138-
139-
if (chr(0x0D) !== $this->fp->read()) {
140-
throw new TableException('Expected header terminator not present at position '.$this->fp->tell());
141-
}
137+
[$columnsCount, $terminatorLength] = $this->pickColumnsCount();
138+
$this->readColumns($columnsCount);
139+
$this->checkHeaderTerminator($terminatorLength);
142140

143141
if (TableType::isVisualFoxpro($this->version)) {
144142
$this->backlist = $this->fp->read(self::VFP_BACKLIST_LENGTH);
@@ -150,13 +148,24 @@ protected function readHeader()
150148
$this->deleteCount = 0;
151149
}
152150

153-
protected function readColumns()
151+
/**
152+
* @return array [$fieldCount, $terminatorLength]
153+
*/
154+
protected function pickColumnsCount(): array
154155
{
155-
$fieldCount = $this->getLogicalFieldCount();
156-
if (is_float($fieldCount)) {
157-
trigger_error('Wrong fieldCount calculation', E_USER_WARNING);
156+
// some files has headers with 2byte-terminator 0xOD00
157+
foreach ([1, 2] as $terminatorLength) {
158+
$fieldCount = $this->getLogicalFieldCount($terminatorLength);
159+
if (is_int($fieldCount)) {
160+
return [$fieldCount, $terminatorLength];
161+
}
158162
}
159163

164+
throw new \LogicException('Wrong fieldCount calculation');
165+
}
166+
167+
protected function readColumns(int $columnsCount): void
168+
{
160169
/* some checking */
161170
clearstatcache();
162171
if ($this->headerLength > filesize($this->filepath)) {
@@ -173,17 +182,20 @@ protected function readColumns()
173182

174183
$class = ColumnFactory::getClass($this->getVersion());
175184
$index = 0;
176-
for ($i = 0; $i < $fieldCount; $i++) {
185+
for ($i = 0; $i < $columnsCount; $i++) {
177186
/** @var ColumnInterface $column */
178187
$column = $class::create($this->fp->read(call_user_func([$class, 'getHeaderLength'])), $index++, $bytePos);
179188
$bytePos += $column->getLength();
180189
$this->addColumn($column);
181190
}
182191
}
183192

184-
protected function getLogicalFieldCount()
193+
/**
194+
* @return float|int
195+
*/
196+
protected function getLogicalFieldCount(int $terminatorLength = 1)
185197
{
186-
$headerLength = self::HEADER_LENGTH + 1; // [Terminator](1)
198+
$headerLength = self::HEADER_LENGTH + $terminatorLength; // [Terminator](1)
187199
$fieldLength = self::FIELD_LENGTH;
188200
if (in_array($this->getVersion(), [TableType::DBASE_7_MEMO, TableType::DBASE_7_NOMEMO])) {
189201
$headerLength += 36; // [Language driver name](32) + [Reserved](4) +
@@ -432,4 +444,26 @@ public function isFoxpro(): bool
432444
{
433445
return TableType::isFoxpro($this->version);
434446
}
447+
448+
/**
449+
* @throws TableException
450+
*/
451+
private function checkHeaderTerminator(int $terminatorLength): void
452+
{
453+
$terminator = $this->fp->read($terminatorLength);
454+
switch ($terminatorLength) {
455+
case 1:
456+
if (chr(0x0D) !== $terminator) {
457+
throw new TableException('Expected header terminator not present at position '.$this->fp->tell());
458+
}
459+
break;
460+
461+
case 2:
462+
$unpack = unpack('n', $terminator);
463+
if (0x0D00 !== $unpack[1]) {
464+
throw new TableException('Expected header terminator not present at position '.$this->fp->tell());
465+
}
466+
break;
467+
}
468+
}
435469
}

tests/DbfTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace XBase\Tests;
4+
5+
use XBase\Enum\TableType;
6+
use XBase\Table;
7+
8+
class DbfTest extends AbstractTestCase
9+
{
10+
public function test2ByteHeaderTerminator(): void
11+
{
12+
$table = new Table(__DIR__.'/Resources/dbf/cbrf_122019N1.dbf', null, 'cp866');
13+
14+
self::assertSame(TableType::DBASE_III_PLUS_NOMEMO, $table->getVersion());
15+
self::assertSame(442, $table->getRecordCount());
16+
17+
self::assertSame([1, 'АО ЮниКредит Банк', 1, 1], array_values($table->nextRecord()->getData()));
18+
self::assertSame([1000, 'Банк ВТБ (ПАО)', 1, 1], array_values($table->nextRecord()->getData()));
19+
self::assertSame([990, 'ООО КБ "Дружба"', 1, 1], array_values($table->pickRecord(441)->getData()));
20+
}
21+
}

tests/Resources/dbf/cbrf_122019N1.dbf

42 KB
Binary file not shown.

0 commit comments

Comments
 (0)