Commit 08aff3b4c61b4e3ce011c2166d4d5b39a5ebb0f5

Authored by Mihail
1 parent 95c167d6

add TableParser and redid CsvParser

common/components/parsers/config.php
@@ -11,7 +11,6 @@ @@ -11,7 +11,6 @@
11 'console' => 11 'console' =>
12 ['class' => 'common\components\parsers\CustomCsvParser', 12 ['class' => 'common\components\parsers\CustomCsvParser',
13 'auto_detect_first_line' => true, 13 'auto_detect_first_line' => true,
14 - 'hasHeaderRow' => true,  
15 'converter_conf' => [ 14 'converter_conf' => [
16 'class' => ' common\components\parsers\CustomConverter', 15 'class' => ' common\components\parsers\CustomConverter',
17 'configuration' => ["string" => 'DESCR', 16 'configuration' => ["string" => 'DESCR',
@@ -38,11 +37,9 @@ @@ -38,11 +37,9 @@
38 'crosses' => ['class' => 'common\components\parsers\CustomCsvParser', 37 'crosses' => ['class' => 'common\components\parsers\CustomCsvParser',
39 'auto_detect_first_line' => true, 38 'auto_detect_first_line' => true,
40 'min_column_quantity' => 4, 39 'min_column_quantity' => 4,
41 - 'hasHeaderRow' => true,  
42 'keys' =>['ARTICLE', 'CROSS_ARTICLE', 'BRAND', 'CROSS_BRAND'], 40 'keys' =>['ARTICLE', 'CROSS_ARTICLE', 'BRAND', 'CROSS_BRAND'],
43 'converter_conf' => [ 41 'converter_conf' => [
44 'class' => ' common\components\parsers\CustomConverter', 42 'class' => ' common\components\parsers\CustomConverter',
45 - 'hasKey' => 1,  
46 'configuration' => [ 43 'configuration' => [
47 "brand" => ['BRAND', 'CROSS_BRAND'], 44 "brand" => ['BRAND', 'CROSS_BRAND'],
48 "crosses" => [], 45 "crosses" => [],
vendor/yiisoft/multiparser/CsvParser.php
@@ -11,43 +11,11 @@ use common\components\CustomVarDamp; @@ -11,43 +11,11 @@ use common\components\CustomVarDamp;
11 * @package yii\multiparser 11 * @package yii\multiparser
12 * @todo - перевести на анг. яз. 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 /** @var string - разделитель csv */ 16 /** @var string - разделитель csv */
34 public $delimiter = ';'; 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,132 +23,53 @@ class CsvParser extends Parser
55 */ 23 */
56 public function setup() 24 public function setup()
57 { 25 {
  26 +
58 $this->file->setCsvControl($this->delimiter); 27 $this->file->setCsvControl($this->delimiter);
59 $this->file->setFlags(\SplFileObject::READ_CSV); 28 $this->file->setFlags(\SplFileObject::READ_CSV);
60 $this->file->setFlags(\SplFileObject::SKIP_EMPTY); 29 $this->file->setFlags(\SplFileObject::SKIP_EMPTY);
61 30
62 - if ($this->auto_detect_first_line) {  
63 - $this->shiftToFirstValuableLine();  
64 - }  
65 -  
66 parent::setup(); 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 public function read() 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 protected function readRow( ) 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 \ No newline at end of file 76 \ No newline at end of file
vendor/yiisoft/multiparser/Parser.php
@@ -8,27 +8,44 @@ @@ -8,27 +8,44 @@
8 8
9 namespace yii\multiparser; 9 namespace yii\multiparser;
10 10
  11 +//@todo - заменить read на parse
  12 +//@todo - xml - убрать из названий функций xml и array - это и так понятно
  13 +
  14 +
  15 +use common\components\CustomVarDamp;
  16 +
11 abstract class Parser 17 abstract class Parser
12 { 18 {
13 public $converter_conf = []; 19 public $converter_conf = [];
14 protected $converter = NULL; 20 protected $converter = NULL;
15 21
  22 + /**
  23 + * @var array - результирующий массив с отпарсенными значениями
  24 + */
  25 + protected $result = [];
  26 +
16 /** @var array - массив с заголовком, 27 /** @var array - массив с заголовком,
17 * */ 28 * */
18 public $keys = NULL; 29 public $keys = NULL;
19 - public $hasHeaderRow = false; 30 + /** @var bool
  31 + имеет ли файл заголовок который будет установлен ключами возвращемого массива*/
  32 + public $has_header_row = false;
20 33
21 /** @var экземляр SplFileObject читаемого файла */ 34 /** @var экземляр SplFileObject читаемого файла */
22 public $file; 35 public $file;
23 36
  37 +
  38 +
24 public function setup() 39 public function setup()
25 { 40 {
  41 +
26 $this->setupConverter(); 42 $this->setupConverter();
  43 +
27 } 44 }
28 45
29 protected function setupConverter() 46 protected function setupConverter()
30 { 47 {
31 - if ($this->hasHeaderRow) { 48 + if ( $this->has_header_row || $this->keys !== NULL ) {
32 // если у файла есть заголовок, то в результате имеем ассоциативный массив 49 // если у файла есть заголовок, то в результате имеем ассоциативный массив
33 $this->converter_conf['hasKey'] = 1; 50 $this->converter_conf['hasKey'] = 1;
34 } 51 }
vendor/yiisoft/multiparser/TableParser.php 0 → 100644
  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 \ No newline at end of file 172 \ No newline at end of file