Commit 8cb5acbf30799eac04cf8dff13eb5e92e30ac181
1 parent
8663be10
add exaples template for csv files
Showing
5 changed files
with
389 additions
and
0 deletions
Show diff stats
1 | +<?php | ||
2 | +namespace backend\controllers; | ||
3 | + | ||
4 | +use backend\models\UploadFileParsingForm; | ||
5 | +use Yii; | ||
6 | +use yii\base\ErrorException; | ||
7 | +use yii\data\ArrayDataProvider; | ||
8 | +use yii\helpers\VarDumper; | ||
9 | +use yii\multiparser\DynamicFormHelper; | ||
10 | +use yii\web\Controller; | ||
11 | +use yii\web\UploadedFile; | ||
12 | + | ||
13 | + | ||
14 | +/** | ||
15 | + * Site controller | ||
16 | + */ | ||
17 | +class ParserController extends Controller | ||
18 | +{ | ||
19 | + /** | ||
20 | + * @var - string | ||
21 | + * file parsing extension | ||
22 | + */ | ||
23 | + protected $file_extension; | ||
24 | + | ||
25 | + public function actionIndex($mode = 0) | ||
26 | + { | ||
27 | + $model = new UploadFileParsingForm(); | ||
28 | + return $this->render('index', ['model' => $model]); | ||
29 | + } | ||
30 | + | ||
31 | + public function actionRead() | ||
32 | + { | ||
33 | + $model = new UploadFileParsingForm(); | ||
34 | + $data = []; | ||
35 | + $mode = ''; | ||
36 | + if ($model->load(\Yii::$app->request->post())) { | ||
37 | + if (!$model->file_type) { | ||
38 | + $model->file = UploadedFile::getInstance($model, 'file'); | ||
39 | + } | ||
40 | + if ($model->validate()) { | ||
41 | + // get the extension of user chosen file | ||
42 | + $this->file_extension = $this->getFileExtensionFromModel($model); | ||
43 | + | ||
44 | + if ($model->file_type) { | ||
45 | + $model->file_path = dirname(dirname(__DIR__)) . '/tests/_data/template.' . $this->file_extension; | ||
46 | + $mode = 'template'; | ||
47 | + } else { | ||
48 | + $mode = 'custom'; | ||
49 | + $model->file_path = dirname(dirname(__DIR__)) . '/tests/_data/custom_template.' . $this->file_extension; | ||
50 | + $model->file->saveAs($model->file_path); | ||
51 | + } | ||
52 | + | ||
53 | + // run parsing | ||
54 | + $data = $model->readFile(['mode' => $mode]); | ||
55 | + | ||
56 | + if ($mode == 'custom' && file_exists($model->file_path)) { | ||
57 | + unlink($model->file_path); | ||
58 | + } | ||
59 | + // safe parse data to cache | ||
60 | + Yii::$app->getCache()->set('parser_data', json_encode($data), 300); | ||
61 | + | ||
62 | + } else { | ||
63 | + // handle with error validation form | ||
64 | + $errors_str = 'Error upload form'; | ||
65 | + foreach ($model->getErrors() as $error) { | ||
66 | + $errors_str .= ' ' . implode(array_values($error)); | ||
67 | + } | ||
68 | + | ||
69 | + throw new ErrorException($errors_str); | ||
70 | + } | ||
71 | + | ||
72 | + } elseif (Yii::$app->getCache()->get('parser_data')) { | ||
73 | + // it's a get request, so retrive data from cache | ||
74 | + $data = json_decode(Yii::$app->getCache()->get('parser_data'), true); | ||
75 | + } | ||
76 | + | ||
77 | + return $this->renderResultView($data, $mode); | ||
78 | + } | ||
79 | + | ||
80 | + public function getFileExtensionFromModel($model) | ||
81 | + { | ||
82 | + switch ($model->file_type) { | ||
83 | + case 0: | ||
84 | + return $model->file->extension; | ||
85 | + case 1: | ||
86 | + return 'csv'; | ||
87 | + case 2: | ||
88 | + return 'xml'; | ||
89 | + case 3: | ||
90 | + return 'xlsx'; | ||
91 | + default: | ||
92 | + return 'csv'; | ||
93 | + } | ||
94 | + | ||
95 | + } | ||
96 | + | ||
97 | + public function renderResultView($data ) | ||
98 | + { | ||
99 | + $provider = new ArrayDataProvider([ | ||
100 | + 'allModels' => $data, | ||
101 | + 'pagination' => [ | ||
102 | + 'pageSize' => 10, | ||
103 | + ], | ||
104 | + ]); | ||
105 | + // если отпарсенные данные - ассоциативный массив, то пользователю нечего выбирать | ||
106 | + $assoc_data_arr = $this->is_assoc($data[0]); | ||
107 | + | ||
108 | + if ( $assoc_data_arr ) { | ||
109 | + | ||
110 | + // $mode == 'template' or xml file | ||
111 | + // парсинг с файла по шаблону | ||
112 | + // согласно конфигурационного файла у нас колонкам назначены ключи | ||
113 | + // то есть результат - ассоциативный массив, у пользователя нечего спрашивать | ||
114 | + // данные отконвертированы согласно настройкам и готовы к записи в БД (или к дальнейшей обработке) | ||
115 | + | ||
116 | + return $this->render('results', | ||
117 | + ['model' => $data, | ||
118 | + // список колонок для выбора | ||
119 | + 'dataProvider' => $provider]); | ||
120 | + | ||
121 | + } else { | ||
122 | + // $mode == 'custom' and not xml | ||
123 | + // для произвольного файла создадим страницу предпросмотра | ||
124 | + // с возможностью выбора соответсвий колонок с отпарсенными данными | ||
125 | + //колонки для выбора возьмем из конфигурационного файла - опция - 'basic_column' | ||
126 | + | ||
127 | + // создадим динамическую модель на столько реквизитов сколько колонок в отпарсенном файле | ||
128 | + // в ней пользователь произведет свой выбор | ||
129 | + $last_index = end(array_flip($data[0])); | ||
130 | + $header_counts = $last_index + 1; // - количество колонок выбора формы предпросмотра | ||
131 | + $header_model = DynamicFormHelper::CreateDynamicModel($header_counts); | ||
132 | + | ||
133 | + // колонки для выбора возьмем из конфигурационного файла | ||
134 | + $basicColumns = Yii::$app->multiparser->getConfiguration($this->file_extension, 'basic_column');; | ||
135 | + | ||
136 | + return $this->render('results', | ||
137 | + ['model' => $data, | ||
138 | + 'header_model' => $header_model, | ||
139 | + // список колонок для выбора | ||
140 | + 'basic_column' => $basicColumns, | ||
141 | + 'dataProvider' => $provider]); | ||
142 | + } | ||
143 | + | ||
144 | + } | ||
145 | + | ||
146 | + private function is_assoc(array $array) | ||
147 | + { | ||
148 | + // Keys of the array | ||
149 | + $keys = array_keys($array); | ||
150 | + | ||
151 | + // If the array keys of the keys match the keys, then the array must | ||
152 | + // not be associative (e.g. the keys array looked like {0:0, 1:1...}). | ||
153 | + return array_keys($keys) !== $keys; | ||
154 | + } | ||
155 | + | ||
156 | +} |
1 | +<?php | ||
2 | +namespace backend\models; | ||
3 | + | ||
4 | +use yii\base\ErrorException; | ||
5 | +use yii\base\Model; | ||
6 | +use yii\web\UploadedFile; | ||
7 | +use Yii; | ||
8 | +use common\components\CustomVarDamp; | ||
9 | + | ||
10 | +/** | ||
11 | + * UploadForm is the model behind the upload form. | ||
12 | + */ | ||
13 | +class UploadFileParsingForm extends Model | ||
14 | +{ | ||
15 | + /** | ||
16 | + * @var UploadedFile file attribute | ||
17 | + */ | ||
18 | + // chosen file | ||
19 | + public $file; | ||
20 | + // file path after save | ||
21 | + public $file_path; | ||
22 | + // 0 - custom file (user should choose in $file field) | ||
23 | + // 1 - csv template file from data/template.csv | ||
24 | + // 2 - xml template file from data/template.xml | ||
25 | + // 3 - xlsx template file from data/template.xlsx | ||
26 | + public $file_type = 0; | ||
27 | + | ||
28 | + | ||
29 | + public function rules() | ||
30 | + { | ||
31 | + $client_func = <<< JS | ||
32 | + function(attribute, value) { | ||
33 | + return $('input[name=UploadFileParsingForm[file_type]]').val() == '0'; | ||
34 | + } | ||
35 | +JS; | ||
36 | + return [ | ||
37 | + ['file_type', 'in', 'range' => range( 0, 3 ) ], | ||
38 | + ['file', 'required', 'when' => function(){ | ||
39 | + return !$this->file_type; | ||
40 | + } , 'whenClient' => $client_func], | ||
41 | + [['file'], 'file', 'extensions' => ['csv', 'xlsx', 'xml'], 'checkExtensionByMimeType' => false ], | ||
42 | + ['file_path', 'safe'], | ||
43 | + | ||
44 | + ]; | ||
45 | + } | ||
46 | + | ||
47 | + public function attributeLabels() | ||
48 | + { | ||
49 | + return [ | ||
50 | + 'file' => Yii::t('app', 'Custom file'), | ||
51 | + ]; | ||
52 | + } | ||
53 | + | ||
54 | + public function readFile( $options = [] ){ | ||
55 | + | ||
56 | + $data = Yii::$app->multiparser->parse( $this->file_path, $options ); | ||
57 | + | ||
58 | + if( !is_array( $data ) || count($data) == 0 ){ | ||
59 | + throw new ErrorException("Reading error from file - {$this->file_path}"); | ||
60 | + } | ||
61 | + | ||
62 | + if( !$this->file_path && file_exists( $this->file_path ) ) | ||
63 | + unlink( $this->file_path ); | ||
64 | + | ||
65 | + return $data; | ||
66 | + } | ||
67 | + | ||
68 | + | ||
69 | + | ||
70 | + | ||
71 | +} | ||
0 | \ No newline at end of file | 72 | \ No newline at end of file |
1 | +<?php | ||
2 | +return [ | ||
3 | + 'csv' => | ||
4 | + ['custom' => | ||
5 | + ['class' => 'yii\multiparser\CsvParser', | ||
6 | + 'converter_conf' => [ | ||
7 | + 'class' => 'yii\multiparser\Converter', | ||
8 | + 'configuration' => ["encode" => []], | ||
9 | + ] | ||
10 | + ], | ||
11 | + 'template' => | ||
12 | + ['class' => 'yii\multiparser\CsvParser', | ||
13 | + 'keys' => [ | ||
14 | + 0 => 'Description', | ||
15 | + 1 => 'Article', | ||
16 | + 2 => 'Price', | ||
17 | + 3 => 'Brand', | ||
18 | + 4 => 'Count', | ||
19 | + ], | ||
20 | + 'converter_conf' => [ | ||
21 | + 'class' => 'yii\multiparser\Converter', | ||
22 | + 'configuration' => ["encode" => 'Description', | ||
23 | + "string" => ['Description', 'Brand'], | ||
24 | + "float" => 'Price', | ||
25 | + "integer" => 'Count' | ||
26 | + ] | ||
27 | + ],], | ||
28 | + | ||
29 | + 'basic_column' => [ | ||
30 | + Null => 'null', | ||
31 | + "Description" => 'Название', | ||
32 | + "Article" => 'Артикул', | ||
33 | + "Price" => 'Цена', | ||
34 | + "Brand" => 'Производитель', | ||
35 | + "Count" => 'Количество', | ||
36 | + ], | ||
37 | + ], | ||
38 | + 'xml' => | ||
39 | + ['custom' => | ||
40 | + ['class' => 'yii\multiparser\XmlParser', | ||
41 | + 'converter_conf' => [ | ||
42 | + 'class' => 'yii\multiparser\Converter', | ||
43 | + 'configuration' => ["encode" => []], | ||
44 | + ] | ||
45 | + ], | ||
46 | + 'template' => | ||
47 | + ['class' => 'yii\multiparser\XmlParser', | ||
48 | + 'node' => 'Товар', | ||
49 | + 'has_header_row' => false, | ||
50 | + 'keys' => [ | ||
51 | + "BRAND" => 'Производитель', | ||
52 | + "ARTICLE" => 'Код', | ||
53 | + "PRICE" => 'Розница', | ||
54 | + "DESCR" => 'Наименование', | ||
55 | + "BOX" => 'Колво', | ||
56 | + "ADD_BOX" => 'Ожидаемое', | ||
57 | + "GROUP" => 'Группа' | ||
58 | + ], | ||
59 | + 'converter_conf' => [ | ||
60 | + 'class' => 'yii\multiparser\Converter', | ||
61 | + 'configuration' => [ | ||
62 | + 'converter_conf' => [ | ||
63 | + 'class' => 'yii\multiparser\Converter', | ||
64 | + 'configuration' => ["encode" => 'DESCR', | ||
65 | + "string" => ['DESCR', 'BRAND'], | ||
66 | + "float" => 'PRICE', | ||
67 | + "integer" => ['BOX', 'ADD_BOX'], | ||
68 | + ], | ||
69 | + ], | ||
70 | + ], | ||
71 | + ], | ||
72 | + ], | ||
73 | + 'basic_column' => [ | ||
74 | + Null => 'null', | ||
75 | + "BRAND" => 'Производитель', | ||
76 | + "ARTICLE" => 'Код', | ||
77 | + "PRICE" => 'Розница', | ||
78 | + "DESCR" => 'Наименование', | ||
79 | + "BOX" => 'Колво', | ||
80 | + "ADD_BOX" => 'Ожидаемое', | ||
81 | + "GROUP" => 'Группа' | ||
82 | + ], | ||
83 | + ], | ||
84 | + 'xlsx' => | ||
85 | + ['web' => | ||
86 | + ['class' => 'common\components\parsers\XlsxParser', | ||
87 | + // 'path_for_extract_files' => \Yii::getAlias('@temp_upload') . '/xlsx/', | ||
88 | + //'auto_detect_first_line' => true, | ||
89 | + //'has_header_row' => true, | ||
90 | + 'active_sheet' => 1, | ||
91 | + 'converter_conf' => [ | ||
92 | + 'class' => 'common\components\parsers\CustomConverter', | ||
93 | + 'configuration' => ["string" => []], | ||
94 | + ] | ||
95 | + ], | ||
96 | + ] | ||
97 | +]; | ||
98 | + |
1 | +<?php | ||
2 | +use yii\widgets\ActiveForm; | ||
3 | +use yii\helpers\Html; | ||
4 | +use yii\helpers\ArrayHelper; | ||
5 | + | ||
6 | +?> | ||
7 | +<div class="row"> | ||
8 | + <div class="col-lg-5"> | ||
9 | + <?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data',],'action'=>['parser/read']]); | ||
10 | + ?> | ||
11 | + <h3>Choose file to parse</h3> | ||
12 | + | ||
13 | + <?= $form->field($model, 'file')->fileInput() ?> | ||
14 | + | ||
15 | + <?= $form->field($model, 'file_type')->radioList([0 => 'Custom file', 1 => 'csv template', 2 => 'xml template', 3 => 'xlsx template'])->label(false); | ||
16 | + ?> | ||
17 | + | ||
18 | + <div class="form-group"> | ||
19 | + <?= Html::submitButton('Read', ['class' => 'btn btn-primary']) ?> | ||
20 | + </div> | ||
21 | + | ||
22 | + <?php ActiveForm::end(); | ||
23 | + ?> | ||
24 | + </div> | ||
25 | +</div> | ||
26 | + |
1 | +<?php | ||
2 | + | ||
3 | +use yii\helpers\Html; | ||
4 | +use yii\multiparser\DynamicFormHelper; | ||
5 | +use yii\widgets\ActiveForm; | ||
6 | + | ||
7 | + | ||
8 | +/* @var $this yii\web\View */ | ||
9 | +/* @var $searchModel backend\models\CatalogSearch */ | ||
10 | +/* @var $dataProvider yii\data\ActiveDataProvider */ | ||
11 | + | ||
12 | +$this->title = 'Results'; | ||
13 | +$this->params['breadcrumbs'][] = $this->title; | ||
14 | +?> | ||
15 | +<div class="catalog-index"> | ||
16 | + | ||
17 | + <h1><?= Html::encode($this->title) ?></h1> | ||
18 | + <?php | ||
19 | + | ||
20 | + $form = ActiveForm::begin(['action' => 'write']); | ||
21 | + | ||
22 | + if (empty( $header_model )) { | ||
23 | + // выведем просто массив без колонок выбора | ||
24 | + echo \yii\grid\GridView::widget([ | ||
25 | + 'dataProvider' => $dataProvider, | ||
26 | + // 'layout'=>"{pager}\n{items}", | ||
27 | + | ||
28 | + ]); | ||
29 | + | ||
30 | + } else { | ||
31 | + echo DynamicFormHelper::CreateGridWithDropDownListHeader($dataProvider, $form, $header_model, $basic_column); | ||
32 | + } | ||
33 | + ?> | ||
34 | + | ||
35 | + <?php ActiveForm::end() ?> | ||
36 | + <?= Html::a('Return', ['parser/index'], ['class' => 'btn btn-primary', 'name' => 'Return',]) ?> | ||
37 | + | ||
38 | +</div> | ||
0 | \ No newline at end of file | 39 | \ No newline at end of file |