TableParser.php 8.42 KB
<?php
/**
 * Created by PhpStorm.
 * User: Tsurkanov
 * Date: 22.10.2015
 * Time: 15:53
 */

namespace common\components\parsers;


use common\components\CustomVarDamp;

abstract class TableParser extends Parser
{
    /**
     * @var array - текущий отпарсенный ряд
     *если есть ключи, то колонки с пустыми значениями будут пропускаться (из ряда такие значения будут удаляться),
     * например если в файле вторая колонка пустая то она будет удалена
     * в остальных случаях парсятся все колонки (не проверяется - пустая ли колонка) и попадёт в итоговый массив
     */
    protected $row = [];

    /** @var int - первая строка с которой начинать парсить
     * эта строка будет считаться первой значимой строкой
     * если установлен аттрибут $has_header_row,
     * тогда следующая строка будет считаться заголовком и будет пропущена
     */
    public $first_line = 0;

    /** @var int - последняя строка до которой  парсить
     * если не указана, то парсинг происходит до конца файла*/
    public $last_line = 0;

    /** @var int - первая колонка файла с которой начнется парсинг */
    public $first_column = 0;


    /** @var bool
     * имеет ли файл заголовок в первой значимой строке
     * true - первая значимая строка будет пропущена
     */
    public $has_header_row = true;


    /** @var int - количество значимых колонок, что бы определить первую значимую строку
     * используется при автоопределении первой строки*/
    public $min_column_quantity = 5;
    /** @var int - количество пустых строк, что бы определить конец файла,
     * такое количеество подряд пустых строк считается концом файла*/
    public $empty_lines_quantity = 3;


    /** @var int - номер текущей строки парсера */
    protected $current_row_number = 0;


    protected abstract function isEmptyColumn($column_value);

    protected abstract function readRow();

    protected abstract function setResult();


    public function read()
    {
        // получим первую значимую строку
        $this->shiftToFirstValuableLine();

        // первый проход, строка прочитана в shiftToFirstValuableLine
        $first_circle = true;

        // будем считать количество пустых строк подряд - при достижении $empty_lines_quantity - считаем что это конец файла и выходим
        $empty_lines = 0;
        while ($empty_lines < $this->empty_lines_quantity) {

            // прочтем строку из файла, если это не первый проход
            if (!$first_circle){
                $this->readRow();
            }

            $first_circle = false;

            // уберем пустые колонки из ряда
            if ($this->keys === NULL) {
                $this->filterRow();
            }

            if ($this->isEmptyRow()) {
                //счетчик пустых строк
                $empty_lines++;
                $this->current_row_number++;
                continue;
            }

            // запустим конвертирование
            $this->adjustRowToSettings();

            // установим отпарсенную строку в итоговый массив результата
            $this->setResult();
            // строка не пустая, имеем прочитанный массив значений
            $this->current_row_number++;

            // если у нас установлен лимит, при  его достижении прекращаем парсинг
            if ($this->isLastLine())
                break;

            // обнуляем счетчик, так как считаюся пустые строки ПОДРЯД
            $empty_lines = 0;
        }
    }

    /**
     * определяет первую значимую строку,
     * считывается файл пока в нем не встретится строка с непустыми колонками
     * или пока не дойдет до first_line
     * пропускает заголовок если он указан
     */
    protected function shiftToFirstValuableLine()
    {
        // читаем пока не встретим значимую строку, или пока не дойдем до first_line
        do {
            $this->current_row_number++;
            $this->readRow();
        } while ( $this->isEmptyRow() && ( $this->first_line < $this->current_row_number ) );

        // если указан заголовок, то его мы тоже пропускаем (читаем далее)
        if( $this->has_header_row ) {
            $this->current_row_number++;
            $this->readRow();
        }
    }

    /**
     * @return array - одномерный массив результата парсинга строки
     */
    protected function adjustRowToSettings()
    {
        // если есть заголовок, то перед конвертацией его нужно назначить
        if ($this->keys !== NULL) {
            // adjust row to keys
            $this->adjustRowToKeys();
            // назначим заголовок
            $this->row = array_combine($this->keys, $this->row);
        }

        // попытаемся конвертировать прочитанные значения согласно конфигурации котнвертера значений
        $this->row = $this->convert($this->row);

        // обрежем массив к первой значимой колонке
        if ($this->first_column) {

            $this->row = array_slice($this->row, $this->first_column);

        }

    }

    protected  function isEmptyRow(){

        $is_empty = false;

        if ( empty( $this->row ) ) {
            return true;
        }
        if (  count( $this->row ) < $this->min_column_quantity ) {
            return true;
        }

        $j = 0;
        for ($i = 1; $i <= count( $this->row ); $i++) {

            if ( !isset( $this->row[ $i - 1 ] ) ) {
                continue;
            }

            if ( $this->isEmptyColumn( $this->row[$i - 1] ) ) {
                $j++;
            }

            if ( $j >= $this->min_column_quantity ) {
                $is_empty = true;
                break;
            }
        }

        return $is_empty;
    }



    protected function filterRow()
    {
        // нет строки - нет фильтрации
        if ( empty( $this->row ) ) {
            return;
        }
        $this->row = array_filter($this->row, function ($val) {
            return !$this->isEmptyColumn($val);
        });
    }

    protected function isLastLine()
    {

        if (($this->last_line) && ($this->current_row_number > $this->last_line)) {
            return true;
        }
        return false;
    }

    protected function adjustRowToKeys()
    {
        //уберем из ряда те колонки которых нет в ключах
        $this->row = array_intersect_key($this->row, $this->keys);

        $keys_count = count($this->keys);
        $column_count = count($this->row);
        if ($keys_count != $column_count) {
            // найдем колонки которых нет в ряде но есть ключах
            $arr_diff = array_diff_key($this->keys, $this->row);
            foreach ($arr_diff as $key => $value) {
                // колонки которых нет в ряде но есть ключах, добавим их с пустым значением
                $this->row[$key] = '';
            }
        }
    }

}