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 |