Commit 08aff3b4c61b4e3ce011c2166d4d5b39a5ebb0f5
1 parent
95c167d6
add TableParser and redid CsvParser
Showing
4 changed files
with
215 additions
and
141 deletions
Show diff stats
common/components/parsers/config.php
| ... | ... | @@ -11,7 +11,6 @@ |
| 11 | 11 | 'console' => |
| 12 | 12 | ['class' => 'common\components\parsers\CustomCsvParser', |
| 13 | 13 | 'auto_detect_first_line' => true, |
| 14 | - 'hasHeaderRow' => true, | |
| 15 | 14 | 'converter_conf' => [ |
| 16 | 15 | 'class' => ' common\components\parsers\CustomConverter', |
| 17 | 16 | 'configuration' => ["string" => 'DESCR', |
| ... | ... | @@ -38,11 +37,9 @@ |
| 38 | 37 | 'crosses' => ['class' => 'common\components\parsers\CustomCsvParser', |
| 39 | 38 | 'auto_detect_first_line' => true, |
| 40 | 39 | 'min_column_quantity' => 4, |
| 41 | - 'hasHeaderRow' => true, | |
| 42 | 40 | 'keys' =>['ARTICLE', 'CROSS_ARTICLE', 'BRAND', 'CROSS_BRAND'], |
| 43 | 41 | 'converter_conf' => [ |
| 44 | 42 | 'class' => ' common\components\parsers\CustomConverter', |
| 45 | - 'hasKey' => 1, | |
| 46 | 43 | 'configuration' => [ |
| 47 | 44 | "brand" => ['BRAND', 'CROSS_BRAND'], |
| 48 | 45 | "crosses" => [], | ... | ... |
vendor/yiisoft/multiparser/CsvParser.php
| ... | ... | @@ -11,43 +11,11 @@ use common\components\CustomVarDamp; |
| 11 | 11 | * @package yii\multiparser |
| 12 | 12 | * @todo - перевести на анг. яз. |
| 13 | 13 | */ |
| 14 | -class CsvParser extends Parser | |
| 14 | +class CsvParser extends TableParser | |
| 15 | 15 | { |
| 16 | - /** @var bool | |
| 17 | - имеет ли файл заголовок который будет установлен ключами возвращемого массива*/ | |
| 18 | - public $hasHeaderRow = false; | |
| 19 | - /** @var array - массив с заголовком, | |
| 20 | - * если не указан и установлено свойство $hasHeaderRow - будет определен автоматически */ | |
| 21 | - // public $keys; - определен в родительском классе | |
| 22 | - | |
| 23 | - /** @var int - первая строка с которой начинать парсить */ | |
| 24 | - public $first_line = 0; | |
| 25 | - | |
| 26 | - /** @var int - последняя строка до которой парсить | |
| 27 | - * если не указана, то парсинг происходит до конца файла*/ | |
| 28 | - public $last_line = 0; | |
| 29 | - | |
| 30 | - /** @var int - первая колонка файла с которой начнется парсинг */ | |
| 31 | - public $first_column = 0; | |
| 32 | - | |
| 33 | 16 | /** @var string - разделитель csv */ |
| 34 | 17 | public $delimiter = ';'; |
| 35 | 18 | |
| 36 | - /** @var bool | |
| 37 | - нужно ли искать автоматически первоую значисмую строку (не пустая строка) | |
| 38 | - * иначе первая строка будет взята из аттрибута $first_line */ | |
| 39 | - public $auto_detect_first_line = false; | |
| 40 | - | |
| 41 | - /** @var int - количество значимых колонок, что бы определить первую значимую строку | |
| 42 | - * используется при автоопределении первой строки*/ | |
| 43 | - public $min_column_quantity = 5; | |
| 44 | - | |
| 45 | -// /** @var array - конфигурация конвертера значений */ | |
| 46 | -// public $converter_conf = []; | |
| 47 | -// /** @var array - конвертер созданный по конфигурации */ | |
| 48 | -// public $converter = NULL; | |
| 49 | - /** @var int - текущая строка */ | |
| 50 | - private $current_line = 0; | |
| 51 | 19 | |
| 52 | 20 | |
| 53 | 21 | /** |
| ... | ... | @@ -55,132 +23,53 @@ class CsvParser extends Parser |
| 55 | 23 | */ |
| 56 | 24 | public function setup() |
| 57 | 25 | { |
| 26 | + | |
| 58 | 27 | $this->file->setCsvControl($this->delimiter); |
| 59 | 28 | $this->file->setFlags(\SplFileObject::READ_CSV); |
| 60 | 29 | $this->file->setFlags(\SplFileObject::SKIP_EMPTY); |
| 61 | 30 | |
| 62 | - if ($this->auto_detect_first_line) { | |
| 63 | - $this->shiftToFirstValuableLine(); | |
| 64 | - } | |
| 65 | - | |
| 66 | 31 | parent::setup(); |
| 67 | 32 | |
| 68 | 33 | } |
| 69 | - /** | |
| 70 | - * определяет первую значимую строку, | |
| 71 | - * считывается файл пока в нем не встретится строка с непустыми колонками | |
| 72 | - * в количестве указанном в атрибуте min_column_quantity | |
| 73 | - * в результате выполнения курсор ресурса будет находится на последней незначимой строке | |
| 74 | - */ | |
| 75 | - protected function shiftToFirstValuableLine() | |
| 76 | - { | |
| 77 | - | |
| 78 | - $finish = false; | |
| 79 | - while (!$finish ) { | |
| 80 | - $this->current_line ++; | |
| 81 | - | |
| 82 | - $j = 0; | |
| 83 | - $row = $this->file->fgetcsv();; | |
| 84 | - if ($row === false) { | |
| 85 | - continue; | |
| 86 | - } | |
| 87 | - | |
| 88 | - for ($i = 1; $i <= count($row); $i++) { | |
| 89 | - // CustomVarDamp::dump($row[$i]); | |
| 90 | - | |
| 91 | - if ($row[$i - 1] <> '') { | |
| 92 | - $j++; | |
| 93 | - } | |
| 94 | - | |
| 95 | - if ($j >= $this->min_column_quantity) { | |
| 96 | - break 2; | |
| 97 | - } | |
| 98 | - } | |
| 99 | - } | |
| 100 | - // @todo - сделать опционально | |
| 101 | - // код для того что бы парсить первую строку, закомментировано как предполагается что первая значимая строка это заголовок | |
| 102 | - // $this->current_line --; | |
| 103 | -// $this->file->seek( $this->current_line ); | |
| 104 | - } | |
| 105 | 34 | |
| 106 | - /** | |
| 107 | - * @return array - итоговый двумерный массив с результатом парсинга | |
| 108 | - * метод считывает с открытого файла данные построчно | |
| 109 | - */ | |
| 110 | 35 | public function read() |
| 111 | 36 | { |
| 37 | + parent::read(); | |
| 112 | 38 | |
| 113 | - $return = []; | |
| 114 | - | |
| 115 | - // будем считать количество пустых строк подряд - при трех подряд - считаем что это конец файла и выходим | |
| 116 | - $empty_lines = 0; | |
| 117 | - while ( $empty_lines < 3 ) { | |
| 118 | - // прочтем строку из файла. Если там есть значения - то в ней массив, иначе - false | |
| 119 | - $row = $this->readRow( ); | |
| 120 | - | |
| 121 | - if ($row === false) { | |
| 122 | - //счетчик пустых строк | |
| 123 | - $empty_lines++; | |
| 124 | - continue; | |
| 125 | - } | |
| 126 | - // строка не пустая, имеем прочитанный массив значений | |
| 127 | - $this->current_line++; | |
| 128 | - if ($this->hasHeaderRow) { | |
| 129 | - // в файле есть заголовок, но он еще не назначен, назначим | |
| 130 | - if ($this->keys === NULL) { | |
| 131 | - $this->keys = array_values($row); | |
| 132 | - } | |
| 133 | - } | |
| 134 | - // если у нас установлен лимит, при его достижении прекращаем парсинг | |
| 135 | - if (($this->last_line) && ($this->current_line > $this->last_line)) { | |
| 136 | - break; | |
| 137 | - } | |
| 138 | - // обнуляем счетчик, так как считаюся пустые строки ПОДРЯД | |
| 139 | - $empty_lines = 0; | |
| 140 | - | |
| 141 | - $return[] = $row; | |
| 142 | - } | |
| 143 | - | |
| 144 | - return $return; | |
| 39 | + return $this->result; | |
| 145 | 40 | } |
| 146 | - | |
| 147 | 41 | |
| 148 | - /** | |
| 149 | - * @return array - одномерный массив результата парсинга строки | |
| 150 | - */ | |
| 42 | + | |
| 151 | 43 | protected function readRow( ) |
| 152 | 44 | { |
| 153 | - $row = $this->file->fgetcsv(); | |
| 45 | + $this->row = $this->file->fgetcsv(); | |
| 46 | + } | |
| 154 | 47 | |
| 155 | - if (is_array($row)) { | |
| 156 | - // уберем нулевые колонки | |
| 157 | - $row = array_filter($row, function($val){ | |
| 158 | - return $val <> ''; | |
| 159 | - }); | |
| 160 | - // если есть заголовок, то перед конвертацией его нужно назначить | |
| 161 | - if ($this->hasHeaderRow && $this->keys !== NULL) { | |
| 48 | + protected function isEmptyRow(){ | |
| 162 | 49 | |
| 163 | - if (count($this->keys) !== count($row)) { | |
| 164 | - throw new \Exception("Ошибка парсинга файла в строке # {$this->current_line}. Не соответсвие числа ключевых колонок (заголовка) - числу колонок с данными", 0, 1, $this->file->getBasename(), $this->current_line); | |
| 165 | - } | |
| 50 | + $is_empty = false; | |
| 166 | 51 | |
| 167 | - $row = array_combine($this->keys, $row); | |
| 168 | - } | |
| 169 | - // попытаемся конвертировать прочитанные значения согласно конфигурации котнвертера значений | |
| 170 | - $row = $this->convert($row); | |
| 171 | - // обрежем массив к первой значимой колонке | |
| 172 | - if ( $this->first_column ) { | |
| 52 | + if ($this->row === false || $this->row === NULL ) { | |
| 53 | + return true; | |
| 54 | + } | |
| 173 | 55 | |
| 174 | - $row = array_slice($row, $this->first_column); | |
| 56 | + $j = 0; | |
| 57 | + for ($i = 1; $i <= count( $this->row ); $i++) { | |
| 175 | 58 | |
| 59 | + if ( $this->isEmptyColumn( $this->row[$i - 1] ) ) { | |
| 60 | + $j++; | |
| 176 | 61 | } |
| 177 | - } | |
| 178 | - if (is_null($row)) | |
| 179 | - $row = false; | |
| 180 | 62 | |
| 181 | - return $row; | |
| 63 | + if ( $j >= $this->min_column_quantity ) { | |
| 64 | + $is_empty = true; | |
| 65 | + break; | |
| 66 | + } | |
| 67 | + } | |
| 182 | 68 | |
| 69 | + return $is_empty; | |
| 183 | 70 | } |
| 184 | 71 | |
| 185 | - | |
| 72 | + protected function isEmptyColumn( $val ){ | |
| 73 | + return $val == ''; | |
| 74 | + } | |
| 186 | 75 | } |
| 187 | 76 | \ No newline at end of file | ... | ... |
vendor/yiisoft/multiparser/Parser.php
| ... | ... | @@ -8,27 +8,44 @@ |
| 8 | 8 | |
| 9 | 9 | namespace yii\multiparser; |
| 10 | 10 | |
| 11 | +//@todo - заменить read на parse | |
| 12 | +//@todo - xml - убрать из названий функций xml и array - это и так понятно | |
| 13 | + | |
| 14 | + | |
| 15 | +use common\components\CustomVarDamp; | |
| 16 | + | |
| 11 | 17 | abstract class Parser |
| 12 | 18 | { |
| 13 | 19 | public $converter_conf = []; |
| 14 | 20 | protected $converter = NULL; |
| 15 | 21 | |
| 22 | + /** | |
| 23 | + * @var array - результирующий массив с отпарсенными значениями | |
| 24 | + */ | |
| 25 | + protected $result = []; | |
| 26 | + | |
| 16 | 27 | /** @var array - массив с заголовком, |
| 17 | 28 | * */ |
| 18 | 29 | public $keys = NULL; |
| 19 | - public $hasHeaderRow = false; | |
| 30 | + /** @var bool | |
| 31 | + имеет ли файл заголовок который будет установлен ключами возвращемого массива*/ | |
| 32 | + public $has_header_row = false; | |
| 20 | 33 | |
| 21 | 34 | /** @var экземляр SplFileObject читаемого файла */ |
| 22 | 35 | public $file; |
| 23 | 36 | |
| 37 | + | |
| 38 | + | |
| 24 | 39 | public function setup() |
| 25 | 40 | { |
| 41 | + | |
| 26 | 42 | $this->setupConverter(); |
| 43 | + | |
| 27 | 44 | } |
| 28 | 45 | |
| 29 | 46 | protected function setupConverter() |
| 30 | 47 | { |
| 31 | - if ($this->hasHeaderRow) { | |
| 48 | + if ( $this->has_header_row || $this->keys !== NULL ) { | |
| 32 | 49 | // если у файла есть заголовок, то в результате имеем ассоциативный массив |
| 33 | 50 | $this->converter_conf['hasKey'] = 1; |
| 34 | 51 | } | ... | ... |
| 1 | +<?php | |
| 2 | +/** | |
| 3 | + * Created by PhpStorm. | |
| 4 | + * User: Tsurkanov | |
| 5 | + * Date: 22.10.2015 | |
| 6 | + * Time: 15:53 | |
| 7 | + */ | |
| 8 | + | |
| 9 | +namespace yii\multiparser; | |
| 10 | + | |
| 11 | + | |
| 12 | +use common\components\CustomVarDamp; | |
| 13 | + | |
| 14 | +abstract class TableParser extends Parser { | |
| 15 | + | |
| 16 | + | |
| 17 | + /** | |
| 18 | + * @var array - текущий отпарсенный ряд | |
| 19 | + */ | |
| 20 | + protected $row = []; | |
| 21 | + | |
| 22 | + /** @var int - первая строка с которой начинать парсить */ | |
| 23 | + public $first_line = 0; | |
| 24 | + | |
| 25 | + /** @var int - последняя строка до которой парсить | |
| 26 | + * если не указана, то парсинг происходит до конца файла*/ | |
| 27 | + public $last_line = 0; | |
| 28 | + | |
| 29 | + /** @var int - первая колонка файла с которой начнется парсинг */ | |
| 30 | + public $first_column = 0; | |
| 31 | + | |
| 32 | + | |
| 33 | + /** @var bool | |
| 34 | + нужно ли искать автоматически первоую значисмую строку (не пустая строка) | |
| 35 | + * иначе первая строка будет взята из аттрибута $first_line */ | |
| 36 | + public $auto_detect_first_line = false; | |
| 37 | + | |
| 38 | + /** @var int - количество значимых колонок, что бы определить первую значимую строку | |
| 39 | + * используется при автоопределении первой строки*/ | |
| 40 | + public $min_column_quantity = 5; | |
| 41 | + /** @var int - количество пустых строк, что бы определить конец файла, | |
| 42 | + такое количеество подряд пустых строк считается концом файла*/ | |
| 43 | + public $empty_lines_quantity = 3; | |
| 44 | + | |
| 45 | + | |
| 46 | + /** @var int - номер текущей строки парсера */ | |
| 47 | + protected $current_row_number = 0; | |
| 48 | + | |
| 49 | + | |
| 50 | + protected abstract function isEmptyRow(); | |
| 51 | + | |
| 52 | + protected abstract function isEmptyColumn($column_value); | |
| 53 | + | |
| 54 | + protected abstract function readRow(); | |
| 55 | + | |
| 56 | + | |
| 57 | + public function read() | |
| 58 | + { | |
| 59 | + if ($this->auto_detect_first_line) { | |
| 60 | + $this->shiftToFirstValuableLine(); | |
| 61 | + } | |
| 62 | + | |
| 63 | + // будем считать количество пустых строк подряд - при достижении $empty_lines_quantity - считаем что это конец файла и выходим | |
| 64 | + $empty_lines = 0; | |
| 65 | + while ( $empty_lines < $this->empty_lines_quantity ) { | |
| 66 | + // прочтем строку из файла | |
| 67 | + $this->readRow(); | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + if ( $this->isEmptyRow() ) { | |
| 72 | + //счетчик пустых строк | |
| 73 | + $empty_lines++; | |
| 74 | + continue; | |
| 75 | + } | |
| 76 | + // уберем пустые колонки из ряда | |
| 77 | + $this->filterRow(); | |
| 78 | + | |
| 79 | + | |
| 80 | + $this->adjustRowToSettings( ); | |
| 81 | + | |
| 82 | + // строка не пустая, имеем прочитанный массив значений | |
| 83 | + $this->current_row_number++; | |
| 84 | + | |
| 85 | + // для первой строки утановим ключи из заголовка | |
| 86 | + $this->setKeysFromHeader(); | |
| 87 | + | |
| 88 | + // если у нас установлен лимит, при его достижении прекращаем парсинг | |
| 89 | + if ( $this->isLastLine() ) | |
| 90 | + break; | |
| 91 | + | |
| 92 | + // обнуляем счетчик, так как считаюся пустые строки ПОДРЯД | |
| 93 | + $empty_lines = 0; | |
| 94 | + | |
| 95 | + $this->result[] = $this->row; | |
| 96 | + } | |
| 97 | + | |
| 98 | + | |
| 99 | + } | |
| 100 | + | |
| 101 | + | |
| 102 | + /** | |
| 103 | + * определяет первую значимую строку, | |
| 104 | + * считывается файл пока в нем не встретится строка с непустыми колонками | |
| 105 | + * в количестве указанном в атрибуте min_column_quantity | |
| 106 | + * в результате выполнения $current_row_number будет находится на последней незначимой строке | |
| 107 | + */ | |
| 108 | + protected function shiftToFirstValuableLine() | |
| 109 | + { | |
| 110 | + do { | |
| 111 | + | |
| 112 | + $this->current_row_number ++; | |
| 113 | + $this->readRow(); | |
| 114 | + | |
| 115 | + } while( $this->isEmptyRow() ); | |
| 116 | + | |
| 117 | + // @todo - сделать опционально | |
| 118 | + // код для того что бы парсить первую строку, закомментировано как предполагается что первая значимая строка это заголовок | |
| 119 | + // $this->current_row_number --; | |
| 120 | +// $this->file->seek( $this->current_row_number ); | |
| 121 | + } | |
| 122 | + | |
| 123 | + /** | |
| 124 | + * @return array - одномерный массив результата парсинга строки | |
| 125 | + */ | |
| 126 | + protected function adjustRowToSettings( ) | |
| 127 | + { | |
| 128 | + // если есть заголовок, то перед конвертацией его нужно назначить | |
| 129 | + if ( $this->keys !== NULL ) { | |
| 130 | + | |
| 131 | + if (count($this->keys) !== count($this->row)) { | |
| 132 | + throw new \Exception("Ошибка парсинга файла в строке # {$this->current_row_number}. Не соответсвие числа ключевых колонок (заголовка) - числу колонок с данными", 0, 1, $this->file->getBasename(), $this->current_row_number); | |
| 133 | + } | |
| 134 | + | |
| 135 | + $this->row = array_combine($this->keys, $this->row); | |
| 136 | + } | |
| 137 | + | |
| 138 | + // попытаемся конвертировать прочитанные значения согласно конфигурации котнвертера значений | |
| 139 | + $this->row = $this->convert($this->row); | |
| 140 | + | |
| 141 | + // обрежем массив к первой значимой колонке | |
| 142 | + if ( $this->first_column ) { | |
| 143 | + | |
| 144 | + $this->row = array_slice($this->row, $this->first_column); | |
| 145 | + | |
| 146 | + } | |
| 147 | + | |
| 148 | + } | |
| 149 | + | |
| 150 | + protected function setKeysFromHeader(){ | |
| 151 | + if ( $this->has_header_row ) { | |
| 152 | + // в файле есть заголовок, но он еще не назначен - назначим | |
| 153 | + if ($this->keys === NULL) { | |
| 154 | + $this->keys = array_values( $this->row ); | |
| 155 | + } | |
| 156 | + } | |
| 157 | + } | |
| 158 | + protected function filterRow(){ | |
| 159 | + $this->row = array_filter( $this->row, function($val){ | |
| 160 | + return !$this->isEmptyColumn($val); | |
| 161 | + }); | |
| 162 | + } | |
| 163 | + protected function isLastLine(){ | |
| 164 | + | |
| 165 | + if ( ( $this->last_line ) && ( $this->current_row_number > $this->last_line ) ) { | |
| 166 | + return true; | |
| 167 | + } | |
| 168 | + return false; | |
| 169 | + } | |
| 170 | + | |
| 171 | +} | |
| 0 | 172 | \ No newline at end of file | ... | ... |