1

我必须将 .DBF 和 .FPT 文件从 Visual FoxPro 转换为 MySQL。现在我的脚本适用于 .DBF 文件,它使用 dbase_open() 和 dbase_get_record_with_names() 打开并读取它们,然后执行 MySQL INSERT 命令。

但是,这些 .DBF 文件的某些字段属于 MEMO 类型,因此存储在以 .FPT 结尾的单独文件中。我如何阅读这个文件?

我在MSDN中找到了此文件类型的规范,但我不知道如何使用 PHP 逐字节读取此文件(另外,我更喜欢更简单的解决方案)。

有任何想法吗?

4

7 回答 7

13

好吧,我仔细研究了 DBF 和 FPT 文件结构的 MSDN 规范,结果是一个漂亮的 PHP 类,它可以同时打开一个 DBF 和(可选)一个 FPT 备忘录文件。这个类会给你一个又一个的记录,从而从备忘录文件中获取任何备忘录——如果打开的话。

class Prodigy_DBF {
    private $Filename, $DB_Type, $DB_Update, $DB_Records, $DB_FirstData, $DB_RecordLength, $DB_Flags, $DB_CodePageMark, $DB_Fields, $FileHandle, $FileOpened;
    private $Memo_Handle, $Memo_Opened, $Memo_BlockSize;

    private function Initialize() {

        if($this->FileOpened) {
            fclose($this->FileHandle);
        }

        if($this->Memo_Opened) {
            fclose($this->Memo_Handle);
        }

        $this->FileOpened = false;
        $this->FileHandle = NULL;
        $this->Filename = NULL;
        $this->DB_Type = NULL;
        $this->DB_Update = NULL;
        $this->DB_Records = NULL;
        $this->DB_FirstData = NULL;
        $this->DB_RecordLength = NULL;
        $this->DB_CodePageMark = NULL;
        $this->DB_Flags = NULL;
        $this->DB_Fields = array();

        $this->Memo_Handle = NULL;
        $this->Memo_Opened = false;
        $this->Memo_BlockSize = NULL;
    }

    public function __construct($Filename, $MemoFilename = NULL) {
        $this->Prodigy_DBF($Filename, $MemoFilename);
    }

    public function Prodigy_DBF($Filename, $MemoFilename = NULL) {
        $this->Initialize();
        $this->OpenDatabase($Filename, $MemoFilename);
    }

    public function OpenDatabase($Filename, $MemoFilename = NULL) {
        $Return = false;
        $this->Initialize();

        $this->FileHandle = fopen($Filename, "r");
        if($this->FileHandle) {
            // DB Open, reading headers
            $this->DB_Type = dechex(ord(fread($this->FileHandle, 1)));
            $LUPD = fread($this->FileHandle, 3);
            $this->DB_Update = ord($LUPD[0])."/".ord($LUPD[1])."/".ord($LUPD[2]);
            $Rec = unpack("V", fread($this->FileHandle, 4));
            $this->DB_Records = $Rec[1];
            $Pos = fread($this->FileHandle, 2);
            $this->DB_FirstData = (ord($Pos[0]) + ord($Pos[1]) * 256);
            $Len = fread($this->FileHandle, 2);
            $this->DB_RecordLength = (ord($Len[0]) + ord($Len[1]) * 256);
            fseek($this->FileHandle, 28); // Ignoring "reserved" bytes, jumping to table flags
            $this->DB_Flags = dechex(ord(fread($this->FileHandle, 1)));
            $this->DB_CodePageMark = ord(fread($this->FileHandle, 1));
            fseek($this->FileHandle, 2, SEEK_CUR);    // Ignoring next 2 "reserved" bytes

            // Now reading field captions and attributes
            while(!feof($this->FileHandle)) {

                // Checking for end of header
                if(ord(fread($this->FileHandle, 1)) == 13) {
                    break;  // End of header!
                } else {
                    // Go back
                    fseek($this->FileHandle, -1, SEEK_CUR);
                }

                $Field["Name"] = trim(fread($this->FileHandle, 11));
                $Field["Type"] = fread($this->FileHandle, 1);
                fseek($this->FileHandle, 4, SEEK_CUR);  // Skipping attribute "displacement"
                $Field["Size"] = ord(fread($this->FileHandle, 1));
                fseek($this->FileHandle, 15, SEEK_CUR); // Skipping any remaining attributes
                $this->DB_Fields[] = $Field;
            }

            // Setting file pointer to the first record
            fseek($this->FileHandle, $this->DB_FirstData);

            $this->FileOpened = true;

            // Open memo file, if exists
            if(!empty($MemoFilename) and file_exists($MemoFilename) and preg_match("%^(.+).fpt$%i", $MemoFilename)) {
                $this->Memo_Handle = fopen($MemoFilename, "r");
                if($this->Memo_Handle) {
                    $this->Memo_Opened = true;

                    // Getting block size
                    fseek($this->Memo_Handle, 6);
                    $Data = unpack("n", fread($this->Memo_Handle, 2));
                    $this->Memo_BlockSize = $Data[1];
                }
            }
        }

        return $Return;
    }

    public function GetNextRecord($FieldCaptions = false) {
        $Return = NULL;
        $Record = array();

        if(!$this->FileOpened) {
            $Return = false;
        } elseif(feof($this->FileHandle)) {
            $Return = NULL;
        } else {
            // File open and not EOF
            fseek($this->FileHandle, 1, SEEK_CUR);  // Ignoring DELETE flag
            foreach($this->DB_Fields as $Field) {
                $RawData = fread($this->FileHandle, $Field["Size"]);
                // Checking for memo reference
                if($Field["Type"] == "M" and $Field["Size"] == 4 and !empty($RawData)) {
                    // Binary Memo reference
                    $Memo_BO = unpack("V", $RawData);
                    if($this->Memo_Opened and $Memo_BO != 0) {
                        fseek($this->Memo_Handle, $Memo_BO[1] * $this->Memo_BlockSize);
                        $Type = unpack("N", fread($this->Memo_Handle, 4));
                        if($Type[1] == "1") {
                            $Len = unpack("N", fread($this->Memo_Handle, 4));
                            $Value = trim(fread($this->Memo_Handle, $Len[1]));
                        } else {
                            // Pictures will not be shown
                            $Value = "{BINARY_PICTURE}";
                        }
                    } else {
                        $Value = "{NO_MEMO_FILE_OPEN}";
                    }
                } else {
                    $Value = trim($RawData);
                }

                if($FieldCaptions) {
                    $Record[$Field["Name"]] = $Value;
                } else {
                    $Record[] = $Value;
                }
            }

            $Return = $Record;
        }

        return $Return;
    }

    function __destruct() {
        // Cleanly close any open files before destruction
        $this->Initialize();
    }
}

该类可以这样使用:

    $Test = new Prodigy_DBF("customer.DBF", "customer.FPT");
    while(($Record = $Test->GetNextRecord(true)) and !empty($Record)) {
        print_r($Record);
    }

这可能不是一个完美的课程,但它对我有用。随意使用此代码,但请注意,该类非常宽容 - 它不关心 fread() 和 fseek() 是否返回 true 或其他任何内容 - 因此您可能希望在使用之前对其进行一些改进。

另请注意,目前没有使用许多私有变量,例如记录数、记录大小等。

于 2009-12-23T15:33:42.030 回答
3

我替换了这段代码:

fseek($this->Memo_Handle, (trim($RawData) * $this->Memo_BlockSize)+8);
$Value = trim(fread($this->Memo_Handle, $this->Memo_BlockSize));

使用此代码:

fseek($this->Memo_Handle, (trim($RawData) * $this->Memo_BlockSize)+4);
$Len  = unpack("N", fread($this->Memo_Handle, 4));
$Value = trim(fread($this->Memo_Handle, $Len[1] ));

这对我有帮助

于 2011-05-11T08:18:52.973 回答
1

虽然不是 PHP,但 VFP 是从 1 开始的引用,我认为 PHP 是从零开始的引用,因此您必须相应地进行解密和调整,但这很有效,希望您能够在完成后发布您的这部分版本。

VFP 中的 FILETOSTR() 将打开一个文件,并将整个内容作为字符串读取到单个内存变量中——所有转义键、高字节字符等都完好无损。您可能需要依赖 FOPEN()、FSEEK()、FCLOSE() 等。

MemoTest.FPT 是我的示例备忘录表/文件 fpt1 = FILETOSTR( "MEMOTEST.FPT" )

首先,您必须检测创建文件时使用的 MEMO BLOCK SIZE。通常这将是 64 字节,但根据您在帖子中的链接。

标头位置 6-7 标识大小(VFP,位置 7 和 8)。第一个字节是高位

nBlockSize = ASC( SUBSTR( fpt1, 7, 1 )) * 256 + ASC( SUBSTR( fpt1, 8, 1 ))

现在,在您的个人记录中。无论您的 DBF 结构中哪里有备注 FIELD(并且每个记录结构可以有多个),都会有 4 个字节。在记录字段中,它标识了备忘录文件中存储内容的“块”。

MemoBytes = 您确定的字段位置的 4 个字节。这些将存储为 0-255 的 ASCII。该字段以第一个字节作为低位存储,第 4 个字节作为 256^3 = 16777216 存储。第一个使用的“块”将从头占用的备忘录 .fpt 文件规范的 512 位置偏移开始位置 0-511。

因此,如果您的第一个备注字段的内容为“8000”,其中 8 是实际的 0x08,而不是数字“8”,即 0x38,零是 0x00。

YourMemoField = "8000" (实际上使用 ascii,但为了便于阅读,显示十六进制预期值)

First Byte is ASCII value  * 1   ( 256 ^ 0 )
Second Byte is ASCII value * 256   (256 ^ 1)
Third Byte is ASCII value * 65536   (256 ^ 2)
Fourth Byte is ASCII value * 16777216 (256 ^ 3)

nMemoBlock =  byte1 + ( byte2 * 256 ) + ( byte3 * 65536 ) + ( byte4 * 16777216 )

现在,您需要 FSEEK() 到

FSEEK( handle, nMemoBlock * nBlockSize +1 )

对于您要查找的块的第一个字节。这将指向 BLOCK 标头。在这种情况下,根据规范,前 4 个字节标识块签名,后 4 个字节是内容的长度。对于这两个,字节首先以 HIGH-BYTE 存储。

从您的 FSEEK() 中,它与上面的 nMemoBlock 的高字节相反。这里的“Byte1-4”来自您的 FSEEK() 位置

nSignature = ( byte1 * 16777216 ) + ( byte2 * 65536 ) + ( byte3 * 256 ) + byte4

nMemoLength = ( byte5 * 16777216 ) + ( byte6 * 65536 ) + ( byte7 * 256 ) + byte8

现在,FSEEK() 到第 9 个字节(在您刚刚读取的标头的 8 个字节之后的数据的第一个实际字符,用于签名和备忘录长度)。这是您数据的开始。

现在,阅读其余内容...

FSEEK() +9 characters to new position

cFinalMemoData = FREAD( handle, nMemoLength )

我知道这并不完美,PHP 脚本也不完美,但它的伪代码足以说明事物的存储方式,并希望让您顺利上路。

同样,请在逐步完成调试过程时考虑到确保 0 或 1 个偏移量基础。为了帮助简化和测试这一点,我创建了一个带有 2 个字段的简单 .DBF……一个字符字段和一个备注字段,添加了一些记录和一些基本内容以确认所有内容、位置等。

于 2009-12-22T20:02:25.467 回答
1
<?
class Prodigy_DBF {
    private $Filename, $DB_Type, $DB_Update, $DB_Records, $DB_FirstData, $DB_RecordLength, $DB_Flags, $DB_CodePageMark, $DB_Fields, $FileHandle, $FileOpened;
    private $Memo_Handle, $Memo_Opened, $Memo_BlockSize;

    private function Initialize() {

        if($this->FileOpened) {
            fclose($this->FileHandle);
        }

        if($this->Memo_Opened) {
            fclose($this->Memo_Handle);
        }

        $this->FileOpened = false;
        $this->FileHandle = NULL;
        $this->Filename = NULL;
        $this->DB_Type = NULL;
        $this->DB_Update = NULL;
        $this->DB_Records = NULL;
        $this->DB_FirstData = NULL;
        $this->DB_RecordLength = NULL;
        $this->DB_CodePageMark = NULL;
        $this->DB_Flags = NULL;
        $this->DB_Fields = array();

        $this->Memo_Handle = NULL;
        $this->Memo_Opened = false;
        $this->Memo_BlockSize = NULL;
    }

    public function __construct($Filename, $MemoFilename = NULL) {
        $this->Prodigy_DBF($Filename, $MemoFilename);
    }

    public function Prodigy_DBF($Filename, $MemoFilename = NULL) {
        $this->Initialize();
        $this->OpenDatabase($Filename, $MemoFilename);
    }

    public function OpenDatabase($Filename, $MemoFilename = NULL) {
        $Return = false;
        $this->Initialize();

        $this->FileHandle = fopen($Filename, "r");
        if($this->FileHandle) {
            // DB Open, reading headers
            $this->DB_Type = dechex(ord(fread($this->FileHandle, 1)));
            $LUPD = fread($this->FileHandle, 3);
            $this->DB_Update = ord($LUPD[0])."/".ord($LUPD[1])."/".ord($LUPD[2]);
            $Rec = unpack("V", fread($this->FileHandle, 4));
            $this->DB_Records = $Rec[1];
            $Pos = fread($this->FileHandle, 2);
            $this->DB_FirstData = (ord($Pos[0]) + ord($Pos[1]) * 256);
            $Len = fread($this->FileHandle, 2);
            $this->DB_RecordLength = (ord($Len[0]) + ord($Len[1]) * 256);
            fseek($this->FileHandle, 28); // Ignoring "reserved" bytes, jumping to table flags
            $this->DB_Flags = dechex(ord(fread($this->FileHandle, 1)));
            $this->DB_CodePageMark = ord(fread($this->FileHandle, 1));
            fseek($this->FileHandle, 2, SEEK_CUR);    // Ignoring next 2 "reserved" bytes

            // Now reading field captions and attributes
            while(!feof($this->FileHandle)) {

                // Checking for end of header
                if(ord(fread($this->FileHandle, 1)) == 13) {
                    break;  // End of header!
                } else {
                    // Go back
                    fseek($this->FileHandle, -1, SEEK_CUR);
                }

                $Field["Name"] = trim(fread($this->FileHandle, 11));
                $Field["Type"] = fread($this->FileHandle, 1);
                fseek($this->FileHandle, 4, SEEK_CUR);  // Skipping attribute "displacement"
                $Field["Size"] = ord(fread($this->FileHandle, 1));
                fseek($this->FileHandle, 15, SEEK_CUR); // Skipping any remaining attributes
                $this->DB_Fields[] = $Field;
            }

            // Setting file pointer to the first record
            fseek($this->FileHandle, $this->DB_FirstData);

            $this->FileOpened = true;

            // Open memo file, if exists
            if(!empty($MemoFilename) and file_exists($MemoFilename) and preg_match("%^(.+).fpt$%i", $MemoFilename)) {
                $this->Memo_Handle = fopen($MemoFilename, "r");
                if($this->Memo_Handle) {
                    $this->Memo_Opened = true;

                    // Getting block size
                    fseek($this->Memo_Handle, 6);
                    $Data = unpack("n", fread($this->Memo_Handle, 2));
                    $this->Memo_BlockSize = $Data[1];
                }
            }
        }

        return $Return;
    }

    public function GetNextRecord($FieldCaptions = false) {
        $Return = NULL;
        $Record = array();

        if(!$this->FileOpened) {
            $Return = false;
        } elseif(feof($this->FileHandle)) {
            $Return = NULL;
        } else {
            // File open and not EOF
            fseek($this->FileHandle, 1, SEEK_CUR);  // Ignoring DELETE flag
            foreach($this->DB_Fields as $Field) {
                $RawData = fread($this->FileHandle, $Field["Size"]);
                // Checking for memo reference
                if($Field["Type"] == "M" and $Field["Size"] == 4 and !empty($RawData)) {
                    // Binary Memo reference
                    $Memo_BO = unpack("V", $RawData);
                    if($this->Memo_Opened and $Memo_BO != 0) {
                        fseek($this->Memo_Handle, $Memo_BO[1] * $this->Memo_BlockSize);
                        $Type = unpack("N", fread($this->Memo_Handle, 4));
                        if($Type[1] == "1") {
                            $Len = unpack("N", fread($this->Memo_Handle, 4));
                            $Value = trim(fread($this->Memo_Handle, $Len[1]));
                        } else {
                            // Pictures will not be shown
                            $Value = "{BINARY_PICTURE}";
                    }
                } else {
                    $Value = "{NO_MEMO_FILE_OPEN}";
                }
            } else {
                if($Field["Type"] == "M"){
                    if(trim($RawData) > 0)   {
                        fseek($this->Memo_Handle, (trim($RawData) * $this->Memo_BlockSize)+8);
                        $Value = trim(fread($this->Memo_Handle, $this->Memo_BlockSize));
                    }
                }else{
                    $Value = trim($RawData);
                }
            }

            if($FieldCaptions) {
                $Record[$Field["Name"]] = $Value;
            } else {
                $Record[] = $Value;
            }
        }

            $Return = $Record;
        }

        return $Return;
    }

    function __destruct() {
        // Cleanly close any open files before destruction
        $this->Initialize();
    }
}
?>
于 2010-06-11T03:59:04.630 回答
0

我认为 PHP 中不太可能有 FoxPro 库。

您可能必须从头开始编写代码。对于字节读取,见fopen() fread() 和同事

编辑:似乎有一个Visual FoxPro ODBC driver。您可能能够通过 PDO 和该连接器连接到 FoxPro 数据库。我不知道成功的机会有多大,需要做多少工作。

于 2009-12-22T16:13:21.393 回答
0

FPT 文件包含备忘录数据。在 DBF 中有 memo 类型的列,该列中的信息是指向 FPT 文件中条目的指针。

如果您从表中查询数据,您只需引用备注列即可获取数据。您不需要单独解析 FPT 文件中的数据。OLE DB 驱动程序(或 ODBC 驱动程序,如果您的文件是 VFP 6 或更早版本)应该只为您提供此信息。

有一个工具可以自动将您的 Visual FoxPro 数据迁移到 MySQL。您可能想检查一下是否可以节省一些时间:

访问http://leafe.com/dls/vfp

并搜索“Stru2MySQL_2”以获取迁移数据结构的工具,并搜索“VFP2MySQL 数据上传程序”以获取帮助迁移的工具。

里克·舒默 VFP MVP

于 2009-12-23T17:07:41.973 回答
0

您可能还想检查 PHP dbase 库。它们与 DBF 文件配合得很好。

于 2010-02-10T22:19:47.293 回答