Как читать FoxPro Memo с PHP? - PullRequest
       47

Как читать FoxPro Memo с PHP?

1 голос
/ 22 декабря 2009

Мне нужно конвертировать файлы .DBF и .FPT из Visual FoxPro в MySQL. Прямо сейчас мой скрипт работает с файлами .DBF, он открывает и читает их с помощью dbase_open () и dbase_get_record_with_names (), а затем выполняет команды MySQL INSERT.

Однако некоторые поля этих файлов .DBF имеют тип MEMO и поэтому хранятся в отдельных файлах, заканчивающихся на .FPT. Как мне прочитать этот файл?

Я нашел спецификации этого типа файла в MSDN , но я не знаю, как я могу читать этот файл побайтно с помощью PHP (также, я бы действительно предпочел более простое решение).

Есть идеи?

Ответы [ 7 ]

12 голосов
/ 23 декабря 2009

Хорошо, я внимательно изучил спецификации MSDN файловых структур DBF и FPT, и в результате получился прекрасный класс 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 или что-то еще - поэтому вы можете немного улучшить его перед использованием.

Также обратите внимание, что существует много личных переменных, таких как количество записей, размер записей и т. Д., Которые в данный момент не используются.

3 голосов
/ 11 мая 2011

Я заменил этот код:

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] ));

это помогло мне

1 голос
/ 11 июня 2010
<?
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();
    }
}
?>
1 голос
/ 22 декабря 2009

Хотя это не PHP, VFP - это ссылки на основе 1, и я думаю, что PHP - ссылки на основе нуля, поэтому вам придется расшифровывать и корректировать их соответствующим образом, но это работает и, надеюсь, вы сможете опубликовать свою версию этой части, когда закончите.

FILETOSTR () в VFP откроет файл и прочитает весь контент в единственная переменная памяти в виде символьной строки - все управляющие ключи, старшие байтовые символы и т. д. не повреждены. Вам, вероятно, нужно полагаться на FOPEN (), FSEEK (), FCLOSE () и т. Д.

MemoTest.FPT был моим примером таблицы / файла заметок fpt1 = FILETOSTR ("MEMOTEST.FPT")

Во-первых, вам необходимо определить размер МЕМОБЛОКА, использованный при создании файла. Обычно это 64 байта, но по ссылке, которую вы указали в своем сообщении.

Позиции заголовков 6-7 определяют размер (VFP, позиции 7 и 8). Первый байт старшего разряда

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

Теперь по вашим индивидуальным записям. Везде, где в вашей структуре DBF есть памятка FIELD (и у вас может быть много на одну структуру записи), будет 4 байта. В поле RECORD указывается «блок» в файле заметки, в котором хранится содержимое.

MemoBytes = 4 байта в указанном вами месте поля. Они будут сохранены как ASCII от 0 до 255. Это поле сохраняется с младшим байтом FIRST, а 4-й байт - 256 ^ 3 = 16777216. Первый из когда-либо использованных «блоков» будет начинаться со смещением позиции 512 в соответствии со спецификацией файла memo .fpt, которую занимает заголовок позиции 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 выше с старшим байтом. "Byte1-4" здесь из вашей позиции FSEEK ()

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

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

Теперь, FSEEK () до 9-го байта (1-й действительный символ данных ПОСЛЕ 8 байтов заголовка, который вы только что прочитали для подписи и длины заметки). Это начало ваших данных.

Теперь прочитайте остальную часть контента ...

FSEEK() +9 characters to new position

cFinalMemoData = FREAD( handle, nMemoLength )

Я знаю, что это не идеальный сценарий или сценарий PHP, но достаточно псевдокода о том, как все хранится, и мы надеемся, что вы ХОРОШО на своем пути.

Опять же, ПОЖАЛУЙСТА, примите во внимание, что вы проходите через процесс отладки, чтобы обеспечить 0 или 1 смещение. Чтобы упростить и проверить это, я создал простой .DBF с 2 полями ... символьным полем и памятным полем, добавил несколько записей и некоторый базовый контент для подтверждения всего контента, позиций и т. Д.

0 голосов
/ 11 февраля 2010

Вы также можете проверить библиотеки PHP dbase. Они хорошо работают с файлами DBF.

0 голосов
/ 23 декабря 2009

Файл FPT содержит данные памятки. В DBF у вас есть столбцы типа memo, и информация в этом столбце является указателем на запись в файле FPT.

Если вы запрашиваете данные из таблицы, вам нужно только сослаться на столбец memo, чтобы получить данные. Вам не нужно отдельно анализировать данные из файла FPT. Драйвер OLE DB (или драйвер ODBC, если ваши файлы VFP 6 или более ранние) должны просто предоставить вам эту информацию.

Существует инструмент, который автоматически перенесет ваши данные Visual FoxPro в MySQL. Возможно, вы захотите проверить это, чтобы узнать, сможете ли вы сэкономить время:

Перейти к http://leafe.com/dls/vfp

и найдите «Stru2MySQL_2» для инструмента для миграции структур данных и «программу загрузки данных VFP2MySQL» для инструментов, помогающих с миграцией.

Рик Шуммер, VFP MVP

0 голосов
/ 22 декабря 2009

Я думаю, что в PHP нет библиотек FoxPro.

Возможно, вам придется кодировать его с нуля. Для побайтного чтения познакомьтесь с fopen () fread () и коллегами .

Редактировать: Кажется, Visual FoxPro ODBC драйвер . Возможно, вы сможете подключиться к базе данных FoxPro через PDO и этот соединитель. Каковы шансы на успех и сколько будет работы, я не знаю.

...