Как читать FoxPro Memo с помощью PHP?

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

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

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

Любые идеи?


person Akinzekeel    schedule 22.12.2009    source источник


Ответы (7)


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

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

person Akinzekeel    schedule 23.12.2009
comment
Вот отредактированная версия вышеуказанного класса, которая также поддерживает формат DBF dBase Level 7. gist.github.com/Cleric-K/72b6d0b131f71f8e94c68a2a1832863d - person Cleric; 23.06.2017

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

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

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

person ashslv    schedule 11.05.2011

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

FILETOSTR() в VFP откроет файл и прочитает все содержимое в одну переменную памяти в виде строки символов — все управляющие клавиши, символы старшего байта и т. д. без изменений. Вам, вероятно, придется полагаться на 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 есть memo FIELD (и у вас может быть много на одну структуру записи), там будет 4 байта. В поле ЗАПИСЬ он идентифицирует «блок» в файле заметок, в котором хранится содержимое.

MemoBytes = 4 байта в указанном вами местоположении поля. Они будут храниться как ASCII от 0 до 255. Это поле хранится с ПЕРВЫМ байтом в качестве младшего и 4-м байтом в виде 256 ^ 3 = 16777216. Первый когда-либо использованный «Блок» будет начинаться со смещения позиции 512 в соответствии со спецификацией файла memo .fpt, которую занимает заголовок. позиции 0-511.

Итак, если ваше первое поле memo имеет содержимое «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-го байта (1-й фактический символ данных ПОСЛЕ 8 байтов заголовка, который вы только что прочитали для подписи и длины заметки). Это начало ваших данных.

А теперь прочтите остальную часть...

FSEEK() +9 characters to new position

cFinalMemoData = FREAD( handle, nMemoLength )

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

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

person DRapp    schedule 22.12.2009
comment
Спасибо за это подробное объяснение. Код, который я разрабатываю, примерно работает так, как вы описали. Однако, похоже, есть проблема с данными, поскольку в некоторых записях меньше полей, чем в других, а это означает, что я не могу сказать, когда запись начинается и когда запись заканчивается. Вы сказали, что DBF идентифицирует блок в FPT, у вас есть дополнительная информация об этом? Я использую dbase-функции PHP, я думаю, что они не возвращают двоичные данные... Поля memo в DBF просто NULL - person Akinzekeel; 23.12.2009
comment
если ноль, то с этой записью не будет связанного содержания заметок. Однако байты поля, выделенные ДЛЯ поля memo, всегда будут 4 байта. Если у него ЕСТЬ значение, это будет синтаксический анализ, как я описал, указывающий на блок в файле .FPT. - person DRapp; 23.12.2009

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

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

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

person Pekka    schedule 22.12.2009
comment
Спасибо за быстрый ответ. Прямо сейчас я пытаюсь прочитать файл с помощью fopen() и fread(). Единственная проблема, с которой я здесь сталкиваюсь, заключается в том, что я не знаю, как преобразовать байт в целое число (как мне все равно сохранить BYTE в переменной PHP?). - person Akinzekeel; 22.12.2009
comment
Хорошо, я провел небольшое исследование и нашел функцию unpack(), которая может преобразовывать необработанные данные в несколько типов целых чисел или других форматов. Если я смогу решить эту проблему, я дам вам знать. В противном случае - дальнейшие предложения всегда приветствуются :) - person Akinzekeel; 22.12.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

person Rick Schummer    schedule 23.12.2009

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

person mtanish    schedule 10.02.2010
comment
Да, вы можете легко прочитать их с помощью функций dbase, но я обнаружил, что эти функции не отображают данные из полей MEMO (которые, скорее всего, хранятся в отдельном файле). Вот почему я написал свой собственный класс, который может автоматически читать файлы MEMO соответственно. - person Akinzekeel; 11.02.2010

person    schedule
comment
Вы решили свою проблему? Если да, то какие строки вы изменили? - person Akinzekeel; 11.06.2010
comment
да...решил мои проблемы......изменил это $Value = trim($RawData); ниже... здесь я добавил: ›Memo_BlockSize)+8); $Value = trim(fread($this->Memo_Handle, $this->Memo_BlockSize)); } }else{ $Value = trim($RawData); } Я делаю правильные вещи? но я решил свои проблемы.. - person Chu SOon; 19.07.2010