nchizhov /
inok-dbf
| 1 | <?php |
||
| 2 | /******************************************** |
||
| 3 | * DBF-file MEMO-fields Reader |
||
| 4 | * |
||
| 5 | * Author: Chizhov Nikolay <[email protected]> |
||
| 6 | * (c) 2019-2024 CIOB "Inok" |
||
| 7 | ********************************************/ |
||
| 8 | |||
| 9 | namespace Inok\Dbf; |
||
| 10 | |||
| 11 | use Exception; |
||
| 12 | |||
| 13 | class Memo { |
||
| 14 | private $headers = null; |
||
| 15 | |||
| 16 | private $db, $fp; |
||
| 17 | private $signature = [ |
||
| 18 | 0 => "template|picture", |
||
| 19 | 1 => "text", |
||
| 20 | 2 => "object", |
||
| 21 | 4294903808 => "dbaseIV" |
||
| 22 | ]; |
||
| 23 | private $isBase4 = false; |
||
| 24 | private $isBase3 = false; |
||
| 25 | |||
| 26 | /** |
||
| 27 | * @throws Exception |
||
| 28 | */ |
||
| 29 | public function __construct($file) { |
||
| 30 | $this->db = $file; |
||
| 31 | |||
| 32 | $this->open(); |
||
| 33 | } |
||
| 34 | |||
| 35 | public function __destruct() { |
||
| 36 | $this->close(); |
||
| 37 | } |
||
| 38 | |||
| 39 | /** |
||
| 40 | * @throws Exception |
||
| 41 | */ |
||
| 42 | private function open() { |
||
| 43 | if (!file_exists($this->db)) { |
||
| 44 | throw new Exception(sprintf('Memo-file %s cannot be found', $this->db)); |
||
| 45 | } |
||
| 46 | $this->fp = fopen($this->db, "rb"); |
||
| 47 | } |
||
| 48 | |||
| 49 | public function getHeaders() { |
||
| 50 | if (is_null($this->headers)) { |
||
| 51 | $this->readHeaders(); |
||
| 52 | } |
||
| 53 | return $this->headers; |
||
| 54 | } |
||
| 55 | |||
| 56 | public function getMemo($block) { |
||
| 57 | if (is_null($this->headers)) { |
||
| 58 | $this->readHeaders(); |
||
| 59 | } |
||
| 60 | return $this->readMemo($block); |
||
| 61 | } |
||
| 62 | |||
| 63 | public function close() { |
||
| 64 | if (get_resource_type($this->fp) === "file") { |
||
| 65 | fclose($this->fp); |
||
| 66 | } |
||
| 67 | } |
||
| 68 | |||
| 69 | private function readHeaders() { |
||
| 70 | $data = fread($this->fp, 512); |
||
| 71 | $fileExt = strtolower(pathinfo($this->db, PATHINFO_EXTENSION)); |
||
| 72 | $fileName = ""; |
||
| 73 | if ($fileExt == "dbt") { |
||
| 74 | $fileName = trim(substr($data, 8, 8)); |
||
| 75 | if (empty($fileName)) { |
||
| 76 | $this->isBase3 = true; |
||
| 77 | } else { |
||
| 78 | $this->isBase4 = true; |
||
| 79 | } |
||
| 80 | } |
||
| 81 | if ($this->isBase3) { |
||
| 82 | $this->headers = [ |
||
| 83 | "freeblock_position" => unpack("L", substr($data, 0, 4))[1], |
||
| 84 | "block_size" => 512 |
||
| 85 | ]; |
||
| 86 | return; |
||
| 87 | } |
||
| 88 | if ($this->isBase4) { |
||
| 89 | $this->headers = [ |
||
| 90 | "freeblock_position" => unpack("L", substr($data, 0, 4))[1], |
||
| 91 | "block_size" => unpack("S", substr($data, 20, 2))[1], |
||
| 92 | "dbf-file" => $fileName |
||
| 93 | ]; |
||
| 94 | return; |
||
| 95 | } |
||
| 96 | $this->headers = [ |
||
| 97 | "freeblock_position" => unpack("N", substr($data, 0, 4))[1], |
||
| 98 | "block_size" => unpack("n", substr($data, 6, 2))[1] |
||
| 99 | ]; |
||
| 100 | } |
||
| 101 | |||
| 102 | private function readMemo($block) { |
||
| 103 | fseek($this->fp, $this->headers["block_size"] * $block); |
||
| 104 | if ($this->isBase3) { |
||
| 105 | $text = ""; |
||
| 106 | while (!preg_match('/\x1a\x1a/', $text)) { |
||
| 107 | $text .= fread($this->fp, 512); |
||
| 108 | } |
||
| 109 | $memo["text"] = $this->parseDBase3($text); |
||
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
Loading history...
|
|||
| 110 | return $memo; |
||
| 111 | } |
||
| 112 | $data = fread($this->fp, 8); |
||
| 113 | if ($this->isBase4) { |
||
| 114 | $memo = [ |
||
| 115 | "signature" => $this->signature[unpack("N", substr($data, 0, 4))[1]], |
||
| 116 | "length" => octdec(intval(bin2hex(trim(substr($data, 4, 4))))) |
||
| 117 | ]; |
||
| 118 | $memo["text"] = $this->parseDBase4(fread($this->fp, $memo["length"])); |
||
| 119 | return $memo; |
||
| 120 | } |
||
| 121 | $memo = [ |
||
| 122 | "signature" => $this->signature[unpack("N", substr($data, 0, 4))[1]], |
||
| 123 | "length" => unpack("N", substr($data, 4, 4))[1] |
||
| 124 | ]; |
||
| 125 | $memo["text"] = fread($this->fp, $memo["length"]); |
||
| 126 | return $memo; |
||
| 127 | } |
||
| 128 | |||
| 129 | private function parseDBase3($text) { |
||
| 130 | if (preg_match('/\x1a\x1a/', $text, $matches, PREG_OFFSET_CAPTURE)) { |
||
| 131 | $text = substr($text, 0, $matches[0][1]); |
||
| 132 | } |
||
| 133 | return $text; |
||
| 134 | } |
||
| 135 | |||
| 136 | private function parseDBase4($text) { |
||
| 137 | if (preg_match('/\x0d\x0a/', $text, $matches, PREG_OFFSET_CAPTURE)) { |
||
| 138 | $text = substr($text, 0, $matches[0][1]); |
||
| 139 | } |
||
| 140 | return preg_replace('/\x8d\x0a/', "\n", $text); |
||
| 141 | } |
||
| 142 | } |
||
| 143 |