-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix decimal "C" type read, add support field type "@", fix read Datet…
…ime for "T", "@" field types.
- Loading branch information
nchizhov
committed
Dec 22, 2024
1 parent
51e1561
commit d04ce66
Showing
6 changed files
with
121 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
indent_style = space | ||
indent_size = 2 | ||
insert_final_newline = true | ||
trim_trailing_whitespace = true | ||
end_of_line = lf | ||
|
||
[*.md] | ||
max_line_length = off | ||
trim_trailing_whitespace = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,15 +2,15 @@ | |
"name": "inok/dbf", | ||
"description": "Package for reading DBASE-files (FoxPro) with/without MEMO-fields", | ||
"keywords": ["dbf", "memo", "foxpro", "dbase"], | ||
"homepage": "http://blog.kgd.in", | ||
"homepage": "https://blog.kgd.in", | ||
"type": "library", | ||
"license": "MIT", | ||
"version": "1.0.7", | ||
"version": "1.0.8", | ||
"authors": [ | ||
{ | ||
"name": "Chizhov Nikolay", | ||
"email": "[email protected]", | ||
"homepage": "http://blog.kgd.in", | ||
"homepage": "https://blog.kgd.in", | ||
"role": "System Administrator" | ||
} | ||
], | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,11 +3,13 @@ | |
* DBF-file MEMO-fields Reader | ||
* | ||
* Author: Chizhov Nikolay <[email protected]> | ||
* (c) 2019 CIOB "Inok" | ||
* (c) 2019-2024 CIOB "Inok" | ||
********************************************/ | ||
|
||
namespace Inok\Dbf; | ||
|
||
use Exception; | ||
|
||
class Memo { | ||
private $headers = null; | ||
|
||
|
@@ -21,6 +23,9 @@ class Memo { | |
private $isBase4 = false; | ||
private $isBase3 = false; | ||
|
||
/** | ||
* @throws Exception | ||
*/ | ||
public function __construct($file) { | ||
$this->db = $file; | ||
|
||
|
@@ -31,9 +36,12 @@ public function __destruct() { | |
$this->close(); | ||
} | ||
|
||
/** | ||
* @throws Exception | ||
*/ | ||
private function open() { | ||
if (!file_exists($this->db)) { | ||
throw new \Exception(sprintf('Memo-file %s cannot be found', $this->db)); | ||
throw new Exception(sprintf('Memo-file %s cannot be found', $this->db)); | ||
} | ||
$this->fp = fopen($this->db, "rb"); | ||
} | ||
|
@@ -75,18 +83,20 @@ private function readHeaders() { | |
"freeblock_position" => unpack("L", substr($data, 0, 4))[1], | ||
"block_size" => 512 | ||
]; | ||
} elseif ($this->isBase4) { | ||
return; | ||
} | ||
if ($this->isBase4) { | ||
$this->headers = [ | ||
"freeblock_position" => unpack("L", substr($data, 0, 4))[1], | ||
"block_size" => unpack("S", substr($data, 20, 2))[1], | ||
"dbf-file" => $fileName | ||
]; | ||
} else { | ||
$this->headers = [ | ||
"freeblock_position" => unpack("N", substr($data, 0, 4))[1], | ||
"block_size" => unpack("n", substr($data, 6, 2))[1] | ||
]; | ||
return; | ||
} | ||
$this->headers = [ | ||
"freeblock_position" => unpack("N", substr($data, 0, 4))[1], | ||
"block_size" => unpack("n", substr($data, 6, 2))[1] | ||
]; | ||
} | ||
|
||
private function readMemo($block) { | ||
|
@@ -97,22 +107,22 @@ private function readMemo($block) { | |
$text .= fread($this->fp, 512); | ||
} | ||
$memo["text"] = $this->parseDBase3($text); | ||
} else { | ||
$data = fread($this->fp, 8); | ||
if ($this->isBase4) { | ||
$memo = [ | ||
"signature" => $this->signature[unpack("N", substr($data, 0, 4))[1]], | ||
"length" => octdec(intval(bin2hex(trim(substr($data, 4, 4))))) | ||
]; | ||
$memo["text"] = $this->parseDBase4(fread($this->fp, $memo["length"])); | ||
} else { | ||
$memo = [ | ||
"signature" => $this->signature[unpack("N", substr($data, 0, 4))[1]], | ||
"length" => unpack("N", substr($data, 4, 4))[1] | ||
]; | ||
$memo["text"] = fread($this->fp, $memo["length"]); | ||
} | ||
return $memo; | ||
} | ||
$data = fread($this->fp, 8); | ||
if ($this->isBase4) { | ||
$memo = [ | ||
"signature" => $this->signature[unpack("N", substr($data, 0, 4))[1]], | ||
"length" => octdec(intval(bin2hex(trim(substr($data, 4, 4))))) | ||
]; | ||
$memo["text"] = $this->parseDBase4(fread($this->fp, $memo["length"])); | ||
return $memo; | ||
} | ||
$memo = [ | ||
"signature" => $this->signature[unpack("N", substr($data, 0, 4))[1]], | ||
"length" => unpack("N", substr($data, 4, 4))[1] | ||
]; | ||
$memo["text"] = fread($this->fp, $memo["length"]); | ||
return $memo; | ||
} | ||
|
||
|
@@ -127,7 +137,6 @@ private function parseDBase4($text) { | |
if (preg_match('/\x0d\x0a/', $text, $matches, PREG_OFFSET_CAPTURE)) { | ||
$text = substr($text, 0, $matches[0][1]); | ||
} | ||
$text = preg_replace('/\x8d\x0a/', "\n", $text); | ||
return $text; | ||
return preg_replace('/\x8d\x0a/', "\n", $text); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,11 +3,13 @@ | |
* DBF-file records Reader | ||
* | ||
* Author: Chizhov Nikolay <[email protected]> | ||
* (c) 2019 CIOB "Inok" | ||
* (c) 2019-2024 CIOB "Inok" | ||
********************************************/ | ||
|
||
namespace Inok\Dbf; | ||
|
||
use Exception; | ||
|
||
class Records { | ||
private $fp, $headers, $columns, $memo, $encode; | ||
private $records = 0; | ||
|
@@ -18,6 +20,9 @@ class Records { | |
private $logicals = ['t', 'y', 'д']; | ||
private $notTrimTypes = ["M", "P", "G", "I", "Y", "T", "0"]; | ||
|
||
/** | ||
* @throws Exception | ||
*/ | ||
public function __construct($data, $encode = "utf-8", $headers = null, $columns = null) { | ||
if ($data instanceof Table) { | ||
$this->headers = $data->getHeaders(); | ||
|
@@ -26,7 +31,7 @@ public function __construct($data, $encode = "utf-8", $headers = null, $columns | |
} | ||
else { | ||
if (is_null($headers) || is_null($columns)) { | ||
throw new \Exception('Not correct data in Record class'); | ||
throw new Exception('Not correct data in Record class'); | ||
} | ||
$this->fp = $data; | ||
$this->headers = $headers; | ||
|
@@ -64,6 +69,7 @@ public function nextRecord() { | |
case "I": | ||
$record[$column["name"]] = unpack("l", $sub_data)[1]; | ||
break; | ||
case "@": | ||
case "T": | ||
$record[$column["name"]] = $this->getDateTime($sub_data); | ||
break; | ||
|
@@ -119,9 +125,13 @@ private function convertChar($data) { | |
} | ||
|
||
private function getDateTime($data) { | ||
if (empty(trim($data))) { | ||
$data = trim($data); | ||
if (empty($data)) { | ||
return null; | ||
} | ||
if (strlen($data) == 14) { | ||
return $data; | ||
} | ||
$dateData = unpack("L", substr($data, 0, 4))[1]; | ||
$timeData = unpack("L", substr($data, 4, 4))[1]; | ||
return gmdate("YmdHis", jdtounix($dateData) + intval($timeData / 1000)); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,11 +3,13 @@ | |
* DBF-file Structure Reader | ||
* | ||
* Author: Chizhov Nikolay <[email protected]> | ||
* (c) 2019 CIOB "Inok" | ||
* (c) 2019-2024 CIOB "Inok" | ||
********************************************/ | ||
|
||
namespace Inok\Dbf; | ||
|
||
use Exception; | ||
|
||
class Table { | ||
private $headers = null; | ||
private $columns = null; | ||
|
@@ -61,11 +63,14 @@ class Table { | |
]; | ||
private $dbase7 = false, $v_foxpro = false; | ||
|
||
/** | ||
* @throws Exception | ||
*/ | ||
public function __construct($dbPath, $charset = null){ | ||
$this->db = $dbPath; | ||
if (!is_null($charset)) { | ||
if (!is_numeric($charset)) { | ||
throw new \Exception("Set not correct charset. Allows only digits."); | ||
throw new Exception("Set not correct charset. Allows only digits."); | ||
} | ||
$this->charsets[0] = $charset; | ||
} | ||
|
@@ -76,9 +81,12 @@ public function __destruct() { | |
$this->close(); | ||
} | ||
|
||
/** | ||
* @throws Exception | ||
*/ | ||
private function open() { | ||
if (!file_exists($this->db)) { | ||
throw new \Exception(sprintf('File %s cannot be found', $this->db)); | ||
throw new Exception(sprintf('File %s cannot be found', $this->db)); | ||
} | ||
$this->fp = fopen($this->db, "rb"); | ||
} | ||
|
@@ -132,34 +140,31 @@ private function readHeaders() { | |
if ($this->headers["checks"][0] != 0) { | ||
$this->error = true; | ||
$this->error_info = "Not correct DBF file by headers"; | ||
return; | ||
} | ||
$this->headers["charset_name"] = "cp" . $this->charsets[$this->headers["charset"]]; | ||
|
||
if (in_array("dBASE 7", $this->versions[$this->headers["version"]])) { | ||
$this->dbase7 = true; | ||
$this->headers["columns"] = ($this->headers["header_length"] - 68) / 48; | ||
} elseif (in_array("Visual FoxPro", $this->versions[$this->headers["version"]])) { | ||
$this->v_foxpro = true; | ||
$this->headers["memo"] = (in_array($this->headers["mdx_flag"], [2, 3, 6, 7])); | ||
$this->headers["columns"] = ($this->headers["header_length"] - 296) / 32; | ||
} else { | ||
$this->headers["columns"] = ($this->headers["header_length"] - 33) / 32; | ||
} | ||
else { | ||
$this->headers["charset_name"] = "cp".$this->charsets[$this->headers["charset"]]; | ||
|
||
if (in_array("dBASE 7", $this->versions[$this->headers["version"]])) { | ||
$this->dbase7 = true; | ||
$this->headers["columns"] = ($this->headers["header_length"] - 68) / 48; | ||
} | ||
elseif (in_array("Visual FoxPro", $this->versions[$this->headers["version"]])) { | ||
$this->v_foxpro = true; | ||
$this->headers["memo"] = (in_array($this->headers["mdx_flag"], [2, 3, 6, 7])); | ||
$this->headers["columns"] = ($this->headers["header_length"] - 296) / 32; | ||
} | ||
else { | ||
$this->headers["columns"] = ($this->headers["header_length"] - 33) / 32; | ||
} | ||
|
||
if (!isset($this->headers["memo"])) { | ||
$this->headers["memo"] = in_array($this->headers["version"], $this->memo["versions"]); | ||
} | ||
if ($this->headers["memo"]) { | ||
$this->headers["memo_file"] = ($mfile = $this->getMemoFile($file["dirname"]."/".$file["filename"])) ? $mfile : null; | ||
} | ||
|
||
$this->headers["version_name"] = | ||
implode(", ", $this->versions[$this->headers["version"]])." ".($this->headers["memo"] ? "with" : "without")." memo-fields"; | ||
unset($this->headers["checks"], $this->headers["header_length"]); | ||
if (!isset($this->headers["memo"])) { | ||
$this->headers["memo"] = in_array($this->headers["version"], $this->memo["versions"]); | ||
} | ||
if ($this->headers["memo"]) { | ||
$this->headers["memo_file"] = ($mfile = $this->getMemoFile($file["dirname"] . "/" . $file["filename"])) ? $mfile : null; | ||
} | ||
|
||
$this->headers["version_name"] = | ||
implode(", ", $this->versions[$this->headers["version"]]) . " " . ($this->headers["memo"] ? "with" : "without") . " memo-fields"; | ||
unset($this->headers["checks"], $this->headers["header_length"]); | ||
} | ||
|
||
private function readTableHeaders() { | ||
|
@@ -184,7 +189,8 @@ private function readTableHeaders() { | |
"name" => strtolower(trim(substr($data, 0, 11))), | ||
"type" => $data[11], | ||
"length" => unpack("C", $data[16])[1], | ||
"decimal" => unpack("C", $data[17])[1] | ||
"decimal" => unpack("C", $data[17])[1], | ||
"mdx_flag" => unpack("C", $data[31])[1], | ||
]; | ||
if ($this->v_foxpro) { | ||
$this->columns[$i]["flag"] = unpack("C", $data[18])[1]; | ||
|
@@ -201,8 +207,8 @@ private function readTableHeaders() { | |
$this->columns[$i]["mdx_flag"] = unpack("C", $data[31])[1]; | ||
} | ||
} | ||
if ($this->columns[$i] == "C") { | ||
$this->columns[$i]["length"] = unpack("S", substr($data, ($this->dbase7) ? 33 : 16, 2)); | ||
if ($this->columns[$i]["type"] == "C") { | ||
$this->columns[$i]["length"] = unpack("S", substr($data, ($this->dbase7) ? 33 : 16, 2))[1]; | ||
$this->columns[$i]["decimal"] = 0; | ||
} | ||
} | ||
|
@@ -225,19 +231,17 @@ private function getMemoFile($file) { | |
foreach ($this->memo["formats"] as $format => $versions) { | ||
if (in_array($this->headers["version"], $versions)) { | ||
return $this->fileExists($file.".".$format); | ||
break; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
private function fileExists($fileName) { | ||
|
||
if(file_exists($fileName)) { | ||
if (file_exists($fileName)) { | ||
return $fileName; | ||
} | ||
|
||
// Handle case insensitive requests | ||
// Handle case-insensitive requests | ||
$directoryName = dirname($fileName); | ||
$fileArray = glob($directoryName . '/*', GLOB_NOSORT); | ||
$fileNameLowerCase = strtolower($fileName); | ||
|