В данной статье я хочу рассказать вам об недавно приключившейся со мной ситуации и принятому пути ее решения. Я не профессиональный программист, однако небольшой опыт мне помог решить данную задачу.
Сразу хочется обратить ваше внимание, что речь в данной статье не пойдет о «популярном фреймворке» для какого-либо языка программирования, речь пойдет о стареньком «framework» для ms dos и разборе формата, в котором оной хранит данные с дальнейшим преобразованием этих данных к одному из современных форматов баз данных.
Пред история
Я являюсь студентом одного из городских университетов, обучающийся по специальности «Водные биоресурсы и марикультура» и как уже говорил, не являюсь профессиональным программистом, однако приступая к практике, результатом которой должна стать выпускная работа — навык программирования не остался в стороне.
Одной из задач моей работы был анализ определенного количества морфометрических параметров рыбы. Однако, как вы уже наверное догадались — исходные данные были в рукописном виде. Для образного представления размера данных — это 40 параметров для каждого экземпляра (рыбы, ну а для техника — 40 колонок в базе) и размер выборки — 188 рыб (а как следствие — 188 строк с 40 заполненными колонками).
Вносить эти данные в какую-либо базу вручную — не было бы самым рациональным решением. Поинтересовавшись у наставника практики, который анализировал эти данные в 1994 году вопросом «имеется ли электронная версия этих данных» — был получен положительный ответ. После чего они были успешно скопированы с 5-дюймовой дискеты и увезены домой для разбора. Казалось, успех был близок и большой объем кнопкодавной работы меня обошел стороной. Так и было, но не совсем так.
Анализ полученных данных
Открыв скопированные данные с дискеты, первым, что привлекло мой взгляд — было расширение файлов(.FW3). С таким форматом я до сих пор не встречался, и естественно обратился в первую очередь к любимым поисковым системам за советом. Почти сразу выяснилось, что это формат framework 3ей версии для о.с. dos, последняя версия сего программного продукта — 10. Почитав сайт творца сего продукта, выяснилось что заполучить его будет не просто(является платным). Поиск по торрент-трекерам и другим источникам данного программного продукта тоже ничего не дал, после чего было принято решение заняться анализом формата полученных файлов.
Для каждой таблицы в данном формате имелось 3 файла(припустим, что имя таблицы — table):
- table_I.FW3 — предоставлял название колонок таблицы базы и по всей видимости ссылки на данные в _S.FW3 файле
- table_S.FW3 — мне так и не стал понятным
- table_T.FW3 — содержал в себе строки таблицы
Вскрытие нескольких файлов индекса (table_I.FW3) показало определенную закономерность:
теперь мы смело могли найти начало и конец столбцов для парсинга.
Аналогичная ситуация повторялась и с хранилищем строк:
однако здесь разделитель указывал не только начало и конец таблицы а и на начало и конец строки.
Даже не опытному программисту, наличие подобных закономерностей явно указывает на то, что данные можно программно преобразовать в любой необходимый формат. В моем случае, как хранилище данных был выбран mysql, так как с ним я имел достаточный опыт работы.
Преобразование данных
Под руками имелась связка wamp, а следовательно для добычи данных был выбран php(возможно, это не лучшее решение однако функциональности языка вполне достаточно для данной задачи).
Описывать реализацию класса в 200строчек для реализации данной задачи я думаю нет необходимости на данном ресурсе. Исходный код привожу ниже, возможно кому то он поможет при преобразовании подобных файлов.
$config = array(
'url' => 'http://localhost',
'db_host' => 'localhost',
'db_user' => 'mysql',
'db_pass' => 'mysql',
'db_name' => 'fish'
);
class dataMiner
{
private $database = null;
private $file_byte_column = null;
private $file_byte_table = null;
private $filename = null;
// массив обработанных данных
private $string_column_array = array();
private $string_table_array = array();
// байты для разрезки данных
private $column_byte_point = array(0, 129, 1, 255, 129, 5, 0);
private $row_start_point = array(0, 129, 1, 255, 129, 8, 0);
private $row_string_point = null;
// подключение к бд
private function db()
{
global $config;
if($this->database == null)
{
try
{
$this->database = new PDO("mysql:host={$config['db_host']};dbname={$config['db_name']}", $config['db_user'], $config['db_pass']);
$this->database->setAttribute(PDO::MYSQL_ATTR_INIT_COMMAND, "SET NAMES utf8");
}
catch(PDOException $e)
{
exit("Database connection error ".$e);
}
}
return $this->database;
}
/**
** Открытие файлов
*/
public function open($file_fw3_name)
{
$this->filename = $file_fw3_name;
$this->file_byte_column = file_get_contents('db/'.$file_fw3_name.'_I.FW3');
$this->file_byte_table = file_get_contents('db/'.$file_fw3_name.'_T.FW3');
return $this;
}
// приводит содержимое мнимой таблицы бд к человеческому представлению
// выбирается диапазон заголовков
// и собирается массив $this->string_column_array
// так же собираются массивы строк для данной таблицы
public function makeReadable()
{
// ищем начало и конец названия колонок и запоминаем их
$point_string = $this->byteToString($this->column_byte_point);
$start_point = strpos($this->file_byte_column, $point_string);
$end_point = strpos($this->file_byte_column, $point_string, $start_point+1);
$length = $end_point - $start_point;
$column_string = substr($this->file_byte_column, $start_point+strlen($point_string), $length-strlen($point_string));
$column_array = explode(" ", $column_string);
foreach($column_array as $value)
{
if(strlen($value) > 0)
{
$this->string_column_array[] = $value;
}
}
// ищем начало и конец для строк таблицы и запоминаем их
$this->row_string_point = $this->byteToString($this->row_start_point);
$table_start = strpos($this->file_byte_table, $this->row_string_point);
$table_end = $this->findEndPoint();
$table_length = $table_end-$table_start;
$row_string = substr($this->file_byte_table, $table_start, $table_length);
$line_row = explode($this->row_string_point, $row_string);
$i_j = 1;
// можно юзать и for($i=1;$i<=sizeof($line_row);$i++) - на вкус и цвет
foreach($line_row as $single_row)
{
if(strlen($single_row) > 0)
{
$item_array = explode(" ", $single_row);
foreach($item_array as $field)
{
if(strlen($field) > 0)
$this->string_table_array[$i_j][] = $field;
}
$i_j++;
}
}
}
/**
** Ищем точку где заканчиваются строки таблицы
*/
private function findEndPoint($start = 0, $before_starter = 0)
{
// это первая интерация ?
if($start == 0)
{
$start = strpos($this->file_byte_table, $this->row_string_point);
}
if(FALSE === ($end = strpos($this->file_byte_table, $this->row_string_point, $start+1)))
{
return $before_starter;
}
return $this->findEndPoint($end+1, $start);
}
/**
** Конвертируем байтовый массив в строчный вид
*/
public function byteToString($data)
{
return call_user_func_array("pack", array_merge(array("C*"), $data));
}
public function storeDb($table)
{
// т.к. мы парсим определенного типа файл смысла использовать prepared statement - нет, входящие данные безопасные
// если вы эстет - не читайте код ниже ))
$column_size = sizeof($this->string_column_array);
$create_query = "CREATE TABLE IF NOT EXISTS `{$table}` (
`table` VARCHAR( 24 ) NOT NULL ,";
$insert_query = "INSERT INTO `{$table}` (`table`, ";
for($i=0;$i<sizeof($this->string_column_array);$i++)
{
if($i==(sizeof($this->string_column_array)-1))
{
$create_query .= "`{$this->string_column_array[$i]}` decimal(24,2) NOT NULL DEFAULT '0.00'";
$insert_query .= "`{$this->string_column_array[$i]}`";
}
else
{
$create_query .= "`{$this->string_column_array[$i]}` decimal(24,2) NOT NULL DEFAULT '0.00' ,
";
$insert_query .= "`{$this->string_column_array[$i]}`, ";
}
}
$create_query .= "
) ENGINE = MYISAM ;";
$insert_query .= ") VALUES ";
$s = 1;
foreach($this->string_table_array as $row_array)
{
$field_size = sizeof($row_array);
$insert_query .= "( '{$this->filename}', ";
foreach($row_array as $field)
{
$insert_query .= "'{$field}', ";
}
$null_diff = $column_size-$field_size;
for($i=1;$i<=$null_diff;$i++)
{
if($i==$null_diff)
{
$insert_query .= "'0.00'";
}
else
{
$insert_query .= "'0.00', ";
}
}
if($s == sizeof($this->string_table_array))
{
$insert_query .= " );";
}
else
{
$insert_query .= " ), ";
}
$s++;
}
$this->db()->query($create_query);
$this->db()->query($insert_query);
}
public function clean()
{
$this->file_byte_column = null;
$this->file_byte_table = null;
$this->string_column_array = array();
$this->string_table_array = array();
}
}
Пример использования класса для парсинга данных в базу mysql из FW3 (исходная таблица FW3 M1_*.FW3, целевая таблица в mysql bio_trachurus):
// инициируем майнер
$miner = new dataMiner;
// задаем имя файла базы fw3 и приводим его к читабельному виду
$miner->open('M1')->makeReadable();
// заносим значения в базу данных указывая имя таблицы в которую необходимо поместить данные
$miner->storeDb('bio_trachurus');
Результаты
В результате я успешно получил необходимые мне для анализа наблюдения в удобном для меня формате, отстранив себя от лишней траты времени на ручное занесение.
Исходные коды опубликовал на github с примером.
П.с. — за качество кода не ругайте сильно, он был написан лишь для выполнения одной задачи (в моем случае).
П.п.с — trachurus не имеет никакого отношения к возможному ходу мыслей некоторых пользователей. Уточнить на Wiki
Автор: zenn