Отголоски прошлого — получение данных из framework database 3

в 18:32, , рубрики: data mining, dos, framework, php, базы данных, Песочница, метки: , , ,

Отголоски прошлого — получение данных из framework database 3В данной статье я хочу рассказать вам об недавно приключившейся со мной ситуации и принятому пути ее решения. Я не профессиональный программист, однако небольшой опыт мне помог решить данную задачу.

Сразу хочется обратить ваше внимание, что речь в данной статье не пойдет о «популярном фреймворке» для какого-либо языка программирования, речь пойдет о стареньком «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) показало определенную закономерность:
Отголоски прошлого — получение данных из framework database 3
теперь мы смело могли найти начало и конец столбцов для парсинга.

Аналогичная ситуация повторялась и с хранилищем строк:
Отголоски прошлого — получение данных из framework database 3
однако здесь разделитель указывал не только начало и конец таблицы а и на начало и конец строки.

Даже не опытному программисту, наличие подобных закономерностей явно указывает на то, что данные можно программно преобразовать в любой необходимый формат. В моем случае, как хранилище данных был выбран 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');

Результаты
В результате я успешно получил необходимые мне для анализа наблюдения в удобном для меня формате, отстранив себя от лишней траты времени на ручное занесение.

Отголоски прошлого — получение данных из framework database 3

Исходные коды опубликовал на github с примером.

П.с. — за качество кода не ругайте сильно, он был написан лишь для выполнения одной задачи (в моем случае).

П.п.с — trachurus не имеет никакого отношения к возможному ходу мыслей некоторых пользователей. Уточнить на Wiki

Автор: zenn

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js