如何用PHP阅读FoxPro备忘录?

时间:2009-12-22 16:10:04

标签: php mysql foxpro dbf

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

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

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

有什么想法吗?

7 个答案:

答案 0 :(得分:12)

好的,我仔细研究了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或其他任何东西 - 所以您可能希望在使用之前稍微改进它。

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

答案 1 :(得分: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] ));

这对我有帮助

答案 2 :(得分:1)

虽然不是PHP,但VFP是基于1的引用,我认为PHP是基于零的引用,所以你必须对decypher进行相应的调整,但是这样可行,希望你能够 完成后发布此部分的版本。

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

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

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

标题位置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格式存储。该字段与FIRST字节一起存储为低位,第四字节存储为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个字节标识块SIGNATURE,后4个字节是内容的长度。对于这两个,字节首先用HIGH-BYTE存储。

从你的FSEEK(),它的上面的nMemoBlock的REVERSE与高字节。这里的“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偏移量。为了帮助简化和测试,我创建了一个简单的.DBF,包含2个字段......一个字符字段和一个备忘录字段,添加了一些记录和一些基本内容来确认所有内容,位置等。

答案 3 :(得分: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();
    }
}
?>

答案 4 :(得分:0)

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

您可能需要从头开始编码。对于按字节读取,请遇到fopen() fread() and colleagues

编辑:似乎有一个Visual FoxPro ODBC driver。您可以通过PDO和该连接器连接到FoxPro数据库。我不知道成功的机会如何,以及它将会有多少工作。

答案 5 :(得分:0)

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

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

有一个工具可以自动将Visual FoxPro数据迁移到MySQL。你可能想看看它是否可以节省一些时间:

转到http://leafe.com/dls/vfp

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

Rick Schummer VFP MVP

答案 6 :(得分:0)

您可能还想查看PHP dbase库。它们可以很好地处理DBF文件。