原帖为英文,地址: http://stackoverflow.com/questions/1947348/how-to-read-foxpro-memo-with-php
测试可正常读取memory数据,特转帖供有需要的同学参考,并向原作者致谢!
以下仅将内容粘贴过来,并未整理。
How to read FoxPro Memo with PHP?
I have to convert .DBF and .FPT files from Visual FoxPro to MySQL. Right now my script works for .DBF files, it opens and reads them with dbase_open() and dbase_get_record_with_names() and then executes the MySQL INSERT commands. However, some fields of these .DBF files are of type MEMO and therefore stored in a separate files ending in .FPT. How do I read this file? I have found the specifications of this filetype in MSDN, but I don't know how I can read this file byte-wise with PHP (also, I would really prefer a simplier solution). Any ideas? |
Alright, I have carefully studied the MSDN specifications of DBF and FPT file structures and the outcome is a beautiful PHP class which can open a DBF and (optional) an FPT memo file at the same time. This class will give you record after record and thereby fetch any memos from the memo file - if opened.
1 class Prodigy_DBF { 2 private $Filename, $DB_Type, $DB_Update, $DB_Records, $DB_FirstData, $DB_RecordLength, $DB_Flags, $DB_CodePageMark, $DB_Fields, $FileHandle, $FileOpened; 3 private $Memo_Handle, $Memo_Opened, $Memo_BlockSize; 4 5 private function Initialize() { 6 7 if($this->FileOpened) { 8 fclose($this->FileHandle); 9 } 10 11 if($this->Memo_Opened) { 12 fclose($this->Memo_Handle); 13 } 14 15 $this->FileOpened = false; 16 $this->FileHandle = NULL; 17 $this->Filename = NULL; 18 $this->DB_Type = NULL; 19 $this->DB_Update = NULL; 20 $this->DB_Records = NULL; 21 $this->DB_FirstData = NULL; 22 $this->DB_RecordLength = NULL; 23 $this->DB_CodePageMark = NULL; 24 $this->DB_Flags = NULL; 25 $this->DB_Fields = array(); 26 27 $this->Memo_Handle = NULL; 28 $this->Memo_Opened = false; 29 $this->Memo_BlockSize = NULL; 30 } 31 32 public function __construct($Filename, $MemoFilename = NULL) { 33 $this->Prodigy_DBF($Filename, $MemoFilename); 34 } 35 36 public function Prodigy_DBF($Filename, $MemoFilename = NULL) { 37 $this->Initialize(); 38 $this->OpenDatabase($Filename, $MemoFilename); 39 } 40 41 public function OpenDatabase($Filename, $MemoFilename = NULL) { 42 $Return = false; 43 $this->Initialize(); 44 45 $this->FileHandle = fopen($Filename, "r"); 46 if($this->FileHandle) { 47 // DB Open, reading headers 48 $this->DB_Type = dechex(ord(fread($this->FileHandle, 1))); 49 $LUPD = fread($this->FileHandle, 3); 50 $this->DB_Update = ord($LUPD[0])."/".ord($LUPD[1])."/".ord($LUPD[2]); 51 $Rec = unpack("V", fread($this->FileHandle, 4)); 52 $this->DB_Records = $Rec[1]; 53 $Pos = fread($this->FileHandle, 2); 54 $this->DB_FirstData = (ord($Pos[0]) + ord($Pos[1]) * 256); 55 $Len = fread($this->FileHandle, 2); 56 $this->DB_RecordLength = (ord($Len[0]) + ord($Len[1]) * 256); 57 fseek($this->FileHandle, 28); // Ignoring "reserved" bytes, jumping to table flags 58 $this->DB_Flags = dechex(ord(fread($this->FileHandle, 1))); 59 $this->DB_CodePageMark = ord(fread($this->FileHandle, 1)); 60 fseek($this->FileHandle, 2, SEEK_CUR); // Ignoring next 2 "reserved" bytes 61 62 // Now reading field captions and attributes 63 while(!feof($this->FileHandle)) { 64 65 // Checking for end of header 66 if(ord(fread($this->FileHandle, 1)) == 13) { 67 break; // End of header! 68 } else { 69 // Go back 70 fseek($this->FileHandle, -1, SEEK_CUR); 71 } 72 73 $Field["Name"] = trim(fread($this->FileHandle, 11)); 74 $Field["Type"] = fread($this->FileHandle, 1); 75 fseek($this->FileHandle, 4, SEEK_CUR); // Skipping attribute "displacement" 76 $Field["Size"] = ord(fread($this->FileHandle, 1)); 77 fseek($this->FileHandle, 15, SEEK_CUR); // Skipping any remaining attributes 78 $this->DB_Fields[] = $Field; 79 } 80 81 // Setting file pointer to the first record 82 fseek($this->FileHandle, $this->DB_FirstData); 83 84 $this->FileOpened = true; 85 86 // Open memo file, if exists 87 if(!empty($MemoFilename) and file_exists($MemoFilename) and preg_match("%^(.+).fpt$%i", $MemoFilename)) { 88 $this->Memo_Handle = fopen($MemoFilename, "r"); 89 if($this->Memo_Handle) { 90 $this->Memo_Opened = true; 91 92 // Getting block size 93 fseek($this->Memo_Handle, 6); 94 $Data = unpack("n", fread($this->Memo_Handle, 2)); 95 $this->Memo_BlockSize = $Data[1]; 96 } 97 } 98 } 99 100 return $Return; 101 } 102 103 public function GetNextRecord($FieldCaptions = false) { 104 $Return = NULL; 105 $Record = array(); 106 107 if(!$this->FileOpened) { 108 $Return = false; 109 } elseif(feof($this->FileHandle)) { 110 $Return = NULL; 111 } else { 112 // File open and not EOF 113 fseek($this->FileHandle, 1, SEEK_CUR); // Ignoring DELETE flag 114 foreach($this->DB_Fields as $Field) { 115 $RawData = fread($this->FileHandle, $Field["Size"]); 116 // Checking for memo reference 117 if($Field["Type"] == "M" and $Field["Size"] == 4 and !empty($RawData)) { 118 // Binary Memo reference 119 $Memo_BO = unpack("V", $RawData); 120 if($this->Memo_Opened and $Memo_BO != 0) { 121 fseek($this->Memo_Handle, $Memo_BO[1] * $this->Memo_BlockSize); 122 $Type = unpack("N", fread($this->Memo_Handle, 4)); 123 if($Type[1] == "1") { 124 $Len = unpack("N", fread($this->Memo_Handle, 4)); 125 $Value = trim(fread($this->Memo_Handle, $Len[1])); 126 } else { 127 // Pictures will not be shown 128 $Value = "{BINARY_PICTURE}"; 129 } 130 } else { 131 $Value = "{NO_MEMO_FILE_OPEN}"; 132 } 133 } else { 134 $Value = trim($RawData); 135 } 136 137 if($FieldCaptions) { 138 $Record[$Field["Name"]] = $Value; 139 } else { 140 $Record[] = $Value; 141 } 142 } 143 144 $Return = $Record; 145 } 146 147 return $Return; 148 } 149 150 function __destruct() { 151 // Cleanly close any open files before destruction 152 $this->Initialize(); 153 } 154 }
The class can be used like this:
1 $Test = new Prodigy_DBF("customer.DBF", "customer.FPT"); 2 while(($Record = $Test->GetNextRecord(true)) and !empty($Record)) { 3 print_r($Record); 4 }
It might not be an almighty perfect class, but it works for me. Feel free to use this code, but note that the class is VERY tolerant - it doesn't care if fread() and fseek() return true or anything else - so you might want to improve it a bit before using.
Also note that there are many private variables like number of records, recordsize etc. which are not used at the moment.
更多内容请查看原帖:http://stackoverflow.com/questions/1947348/how-to-read-foxpro-memo-with-php