Repository: AnourValar/office Branch: master Commit: 899ec9cc1ecb Files: 37 Total size: 401.4 KB Directory structure: gitextract_73d96ymn/ ├── .gitattributes ├── .gitignore ├── .php-cs-fixer.php ├── LICENSE ├── README.md ├── composer.json ├── phpcs.xml ├── phpstan.neon ├── phpunit.xml ├── psalm.xml ├── src/ │ ├── Buffer.php │ ├── DocumentService.php │ ├── Drivers/ │ │ ├── DocumentInterface.php │ │ ├── GridInterface.php │ │ ├── LoadInterface.php │ │ ├── MixInterface.php │ │ ├── MultiSheetInterface.php │ │ ├── PhpSpreadsheetDriver.php │ │ ├── SaveInterface.php │ │ ├── SheetsInterface.php │ │ └── ZipDriver.php │ ├── Facades/ │ │ ├── ExportGridInterface.php │ │ ├── ExportGridQueryInterface.php │ │ └── ExportService.php │ ├── Format.php │ ├── Generated.php │ ├── GridService.php │ ├── Mixer.php │ ├── Sheets/ │ │ ├── Parser.php │ │ └── SchemaMapper.php │ ├── SheetsService.php │ ├── Traits/ │ │ ├── Parser.php │ │ └── XFormat.php │ └── resources/ │ └── grid.xlsx └── tests/ ├── GridServiceTest.php ├── SheetsParserTest.php └── TraitsTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ /tests export-ignore .gitattributes export-ignore .gitignore export-ignore .php-cs-fixer.php export-ignore phpcs.xml export-ignore phpstan.neon export-ignore phpunit.xml export-ignore psalm.xml export-ignore ================================================ FILE: .gitignore ================================================ .phpunit.cache/ vendor/ composer.lock .php-cs-fixer.cache ================================================ FILE: .php-cs-fixer.php ================================================ exclude('vendor') ->in(__DIR__); $config = new PhpCsFixer\Config(); return $config->setRules([ '@PSR12' => true, ]) ->setFinder($finder); ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 AnourValar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Office: Documents | Reports | Grids ## Installation ### Minimal ```bash composer require anourvalar/office ``` ### Phpspreadsheet is required to work with Excel (xlsx). ```bash composer require phpoffice/phpspreadsheet "^3.10" ``` ### Zipstream-php is required to work with Word (docx). ```bash composer require maennchen/zipstream-php "^3.2" ``` ### Mpdf is required to work with PDF. ```bash composer require mpdf/mpdf: "^8.1" ``` ## Generate a document from an XLSX (Excel) template ### One-dimensional table (basic usage) **template1.xlsx:** ![Demo](https://anour.ru/resources/office-v1-10.png) ```php $data = [ // scalar 'vat' => 'No', 'total' => [ 'price' => 2004.14, 'qty' => 3, ], // one-dimensional table 'products' => [ [ 'name' => 'Product #1', 'price' => 989, 'qty' => 1, 'date' => new \DateTime('2022-03-30'), ], [ 'name' => 'Product #2', 'price' => 1015.14, 'qty' => 2, 'date' => new \DateTime('2022-03-31'), ], ], ]; // Save to the file (new \AnourValar\Office\SheetsService()) ->generate( 'template1.xlsx', // template filename $data // markers ) ->saveAs( 'generated_document.xlsx', // filename \AnourValar\Office\Format::Xlsx // save format ); // Output to the browser header('Content-type: ' . \AnourValar\Office\Format::Xlsx->contentType()); header('Content-Disposition: attachment; filename="generated_document.xlsx"'); echo (new \AnourValar\Office\SheetsService()) ->generate('template1.xlsx', $data) ->save(\AnourValar\Office\Format::Xlsx); // Available formats: // \AnourValar\Office\Format::Xlsx // \AnourValar\Office\Format::Pdf // \AnourValar\Office\Format::Html // \AnourValar\Office\Format::Ods ``` **generated_document.xlsx:** ![Demo](https://anour.ru/resources/office-v1-11.png) **The same template with empty data** ![Demo](https://anour.ru/resources/office-v1-12.png) ### Two-dimensional table **template2.xlsx:** ![Demo](https://anour.ru/resources/office-v1-20.png) ```php $data = [ 'best_manager' => 'Sveta', // two-dimensional table 'managers' => [ 'titles' => [[ 'William', 'James', 'Sveta' ]], 'values' => [ [ // additional row 'month' => 'January', 'amount' => [700, 800, 900], // additional columns ], [ 'month' => 'February', 'amount' => [7000, 8000, 9000], ], [ 'month' => 'March', 'amount' => [70000, 80000, 90000], ], ], ], ]; // Save as XLSX (Excel) (new \AnourValar\Office\SheetsService()) ->generate('template2.xlsx', $data) ->saveAs('generated_document.xlsx'); // second argument (format) is optional ``` **generated_document.xlsx:** ![Demo](https://anour.ru/resources/office-v1-21.png) ### Additional Features **template3.xlsx:** ![Demo](https://anour.ru/resources/office-v1-30.png) ```php $data = [ 'foo' => 'Hello', 'bar' => function (SheetsInterface $driver, $column, $row) { $driver->insertImage('logo.png', $cell, ['width' => 100, 'offset_y' => -45]); return 'Logo!'; // replace marker "[bar]" with "Logo!" } ]; (new \AnourValar\Office\SheetsService()) ->hookValue(function (SheetsInterface $driver, $column, $row, $value, $sheetIndex) { // Hook will be called for every cell which is changing $value .= ' world'; return $value; }) ->generate( 'template3.ods', // ods template $data, true // cells auto format instead of template setup ) ->saveAs('generated_document.xlsx'); // Available hooks: // hookLoad: Closure(SheetsInterface $driver, string $templateFile, Format $templateFormat) // hookBefore: Closure(SheetsInterface $driver, array &$data) // hookValue: Closure(SheetsInterface $driver, string $column, int $row, $value, int $sheetIndex) // hookAfter: Closure(SheetsInterface $driver) ``` **generated_document.xlsx:** ![Demo](https://anour.ru/resources/office-v1-31.png) ### Dynamic templates ```php $data = [ 'group1' => [ 'name' => 'Group 1', 'products' => [ ['name' => 'Product 1', 'stock' => 101], ['name' => 'Product 2', 'stock' => 102], ], ], 'group2' => [ 'name' => 'Group 2', 'products' => [ ['name' => 'Product 3', 'stock' => 103], ['name' => 'Product 4', 'stock' => 104], ], ], ]; (new \AnourValar\Office\SheetsService()) ->hookLoad(function ($driver, string $templateFile, $templateFormat) { // create empty document instead of using existing return $driver->create(); }) ->hookBefore(function ($driver, array &$data) { // place markers on-fly $row = 1; foreach (array_keys($data) as $group) { // group's title $driver ->setValue("A$row", "[{$group}.name]") ->mergeCells("A$row:B$row") ->setStyle("A$row", ['align' => 'center', 'bold' => true]); $row++; // group's products $driver ->setValue("A$row", "[$group.products.name]") ->setValue("B$row", "[$group.products.stock]"); $row++; } }) ->generate('', $data) ->saveAs('generated_document.xlsx'); ``` **Dynamic template overview** ![Demo](https://anour.ru/resources/office-v1-61.png) **generated_document.xlsx:** ![Demo](https://anour.ru/resources/office-v1-62.png) ### Merge (union) few documents to a single file ```php $dataA = ['foo' => 'hello']; $dataB = ['foo' => 'world']; $documentA = (new \AnourValar\Office\SheetsService())->generate('template.xlsx', $dataA); $documentB = (new \AnourValar\Office\SheetsService())->generate('template.xlsx', $dataB); $mixer = new \AnourValar\Office\Mixer(); $mixer($documentA, $documentB)->saveAs('generated_document.xlsx'); ``` ### Access the PhpSpreadsheet directly (default driver) ```php (new \AnourValar\Office\SheetsService()) ->hookBefore(function (\AnourValar\Office\Drivers\PhpSpreadsheetDriver $driver, array &$data) { $spreadsheet = $driver->spreadsheet; // @see \PhpOffice\PhpSpreadsheet\Spreadsheet $spreadsheet->createSheet()->setTitle('Foo Bar'); // adding a new Worksheet }) ->generate('template.xlsx', []) ->saveAs('generated_document.xlsx'); ``` ## Generate a document from an DOCX (Word) template ```php (new \AnourValar\Office\DocumentService) ->generate('template.docx', ['foo' => 'bar']) ->saveAs('generated_document.docx'); ``` **template.docx:** ![Demo](https://anour.ru/resources/office-v1-70.png) **generated_document.docx:** ![Demo](https://anour.ru/resources/office-v1-71.png) ## Export table (Grid) ### Simple usage ```php $data = [ ['William', 3000], ['James', 4000], ['Sveta', 5000], ]; // Save as XLSX (Excel) (new \AnourValar\Office\GridService()) ->generate( ['Name', 'Sales'], // headers $data // data ) ->saveAs('generated_grid.xlsx'); ``` **generated_grid.xlsx:** ![Demo](https://anour.ru/resources/office-v1-41.png) ### Advanced usage (generators) ```php $headers = [ ['title' => 'Name', 'width' => 30], ['title' => 'Sales'], ]; $data = function () { yield ['name' => 'William', 'sales' => 3000]; yield ['name' => 'James', 'sales' => 4000]; yield ['name' => 'Sveta', 'sales' => 5000]; }; // Save as XLSX (Excel) (new \AnourValar\Office\GridService()) ->hookHeader(function (GridInterface $driver, mixed $header, $key, $column) { if (isset($header['width'])) { $driver->setWidth($column, $header['width']); // column with fixed width } else { $driver->autoWidth($column); // column with auto width } return $header['title']; }) ->hookRow(function (GridInterface $driver, mixed $row, $key) { return [ $row['name'], $row['sales'], ]; }) ->hookAfter(function ( GridInterface $driver, string $headersRange, string $dataRange, string $totalRange, array $columns ) { $driver->setSheetTitle('Foo'); $driver->setStyle( $headersRange, // A1:B1 ['bold' => true, 'background_color' => 'EEEEEE'] ); $driver->setStyle( $totalRange, // A1:B4 ['borders' => true, 'align' => 'left'] ); }) ->generate($headers, $data) ->saveAs('generated_grid.xlsx'); ``` **generated_grid.xlsx:** ![Demo](https://anour.ru/resources/office-v1-51.png) ### Performance By default, GridService uses PhpSpreadsheetDriver which gives a lot of features and flexability. The only cons are performance and memory consumtion. ZipDriver as an alternative is simpler, but much more faster: ```bash composer require maennchen/zipstream-php "^3.2" ``` ```php $data = [ ['William', 3000], ['James', 4000], ['Sveta', 5000], ]; // Save as XLSX (Excel) (new \AnourValar\Office\GridService(new \AnourValar\Office\Drivers\ZipDriver())) ->generate(['Name', 'Sales'], $data) ->saveAs('generated_grid.xlsx'); ``` ================================================ FILE: composer.json ================================================ { "name": "anourvalar/office", "description": "Generate documents from existing Excel & Word templates | Export tables to Excel (Grids)", "keywords": [ "excel", "xls", "xlsx", "template", "view", "document", "contract", "report", "generate", "engine", "fill", "markers", "replacers", "placeholder", "templater", "pdf", "ods", "grid", "export", "generator", "anourvalar", "variables", "word", "doc", "docx", "table" ], "homepage": "https://github.com/AnourValar/office", "license": "MIT", "require": { "php": "^8.1" }, "require-dev": { "phpunit/phpunit": "^11.0", "phpstan/phpstan": "^2.0", "friendsofphp/php-cs-fixer": "^3.26", "squizlabs/php_codesniffer": "^3.7", "vimeo/psalm": "^7.0" }, "autoload": { "psr-4": {"AnourValar\\Office\\": "src/"} }, "autoload-dev": { "psr-4": {"AnourValar\\Office\\Tests\\": "tests/"} } } ================================================ FILE: phpcs.xml ================================================ PHPCS ruleset src tests src/resources tests/ tests/ ================================================ FILE: phpstan.neon ================================================ parameters: paths: - src - tests # The level 10 is the highest level level: 5 ignoreErrors: - '#has an uninitialized readonly property#' - '#Binary operation \"\-\" between non\-empty\-string#' - '#Call to an undefined method AnourValar\\Office\\Drivers\\SaveInterface\:\:getSheetCount\(\)#' - '#Call to an undefined method AnourValar\\Office\\Drivers\\SaveInterface\:\:replace\(\)#' - '#unknown class PhpOffice#' - '#Class PhpOffice\\PhpSpreadsheet\\Writer\\Csv not found#' - '#Unsafe usage of new static#' - '#Call to an undefined method AnourValar\\Office\\Drivers\\SaveInterface\:\:setGrid\(\)#' - '#Parameter \#1 \$driver of method AnourValar\\Office\\GridService\:\:getGenerator\(\) expects AnourValar\\Office\\Drivers\\GridInterface#' - '#Class AnourValar\\Office\\Tests\\SheetsParserTest has an uninitialized property \$service#' - '#has an uninitialized property \$fileSystem#' - '#\(\) on iterable\.#' - '#Instantiated class ZipStream#' - '#unknown class ZipStream#' - '#has an uninitialized property \$sourceActiveSheetIndex#' - '#\$format is assigned outside of the constructor#' - '#has invalid return type PhpOffice#' - '#\$spreadsheet is assigned outside of the constructor#' - '#Binary operation \"\+\" between non\-empty\-string#' - '#Instantiated class PhpOffice#' - '#expects string, int given#' - '#has invalid type PhpOffice#' - '#Match expression does not handle remaining value: mixed#' - '#Access to an undefined property AnourValar\\Office\\Drivers\\MixInterface\:\:\$spreadsheet#' - '#Call to an undefined method AnourValar\\Office\\Drivers\\MixInterface\:\:sheet\(\)#' - '#Call to an undefined method#' - '#Offset numeric\-string on list\ in isset\(\) does not exist\.#' - '#SaveInterface given\.#' - '#has invalid return type Illuminate#' excludePaths: checkFunctionNameCase: true checkInternalClassCaseSensitivity: true reportMaybesInMethodSignatures: true reportStaticMethodSignatures: true checkUninitializedProperties: true checkDynamicProperties: true reportAlwaysTrueInLastCondition: true reportWrongPhpDocTypeInVarTag: true checkMissingCallableSignature: true reportPossiblyNonexistentGeneralArrayOffset: true reportPossiblyNonexistentConstantArrayOffset: true reportAnyTypeWideningInVarTag: true ================================================ FILE: phpunit.xml ================================================ tests src/ ================================================ FILE: psalm.xml ================================================ ================================================ FILE: src/Buffer.php ================================================ resource = tmpfile(); fwrite($this->resource, $buffer); $this->filename = stream_get_meta_data($this->resource)['uri']; } /** * @see magic * * @return string */ public function __toString(): string { return $this->filename; } /** * @return void * @psalm-suppress InaccessibleProperty */ public function __destruct() { fclose($this->resource); // works with php-fpm, octane, queue } } ================================================ FILE: src/DocumentService.php ================================================ driver = $driver; } /** * Generate a document from the template (document) * * @param string|\Stringable $templateFile * @param mixed $data * @return \AnourValar\Office\Generated */ public function generate(string|\Stringable $templateFile, mixed $data): Generated { // Handle with input data $data = $this->canonizeData($data); // Open the template $templateFormat = Format::tryFrom(mb_strtolower(pathinfo($templateFile, PATHINFO_EXTENSION))) ?? Format::Docx; $driver = $this->driver->load($templateFile, $templateFormat); // Handle $driver->replace($data); // Return return new Generated($driver); } /** * @param mixed $data * @return array */ protected function canonizeData(mixed $data): array { $result = []; if (is_object($data) && method_exists($data, 'toArray')) { $data = $data->toArray(); } foreach ($this->dot($data) as $key => $value) { $result["[$key]"] = $value; } return $result; } } ================================================ FILE: src/Drivers/DocumentInterface.php ================================================ spreadsheet->getActiveSheet(); } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\GridInterface::create() * @psalm-suppress InaccessibleProperty */ public function create(): self { $instance = new static(); $instance->spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); $instance->sourceActiveSheetIndex = 0; $this->readConfiguration($instance); return $instance; } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\LoadInterface::load() * @psalm-suppress InaccessibleProperty */ public function load(string $file, \AnourValar\Office\Format $format): self { $instance = new static(); $instance->spreadsheet = IOFactory::createReader($instance->getFormat($format))->load($file); $instance->sourceActiveSheetIndex = $instance->spreadsheet->getActiveSheetIndex(); $this->readConfiguration($instance); return $instance; } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\SaveInterface::save() */ public function save(string $file, \AnourValar\Office\Format $format): void { $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($this->spreadsheet, $this->getFormat($format)); $this->writeConfiguration($writer); $count = $this->spreadsheet->getSheetCount(); for ($i = 0; $i < $count; $i++) { $this->spreadsheet->getSheet($i)->setSelectedCells('A1'); } $this->spreadsheet->setActiveSheetIndex($this->sourceActiveSheetIndex); if (method_exists($writer, 'writeAllSheets')) { $writer->writeAllSheets(); } $writer->save($file); } /** * Clean up * * @return void */ public function __destruct() { if (isset($this->spreadsheet)) { $this->spreadsheet->disconnectWorksheets(); gc_collect_cycles(); } } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\MultiSheetInterface::setSheet() */ public function setSheet(int $index): self { $this->spreadsheet->setActiveSheetIndex($index); return $this; } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\MultiSheetInterface::getSheetCount() */ public function getSheetCount(): int { return $this->spreadsheet->getSheetCount(); } /** * Apply value to a cell * * @param string $cell * @param mixed $value * @param bool $autoCellFormat * @return self */ public function setValue(string $cell, $value, bool $autoCellFormat = true): self { if ($value instanceof \DateTimeInterface) { $this->sheet()->setCellValue($cell, \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel($value)); if ($autoCellFormat) { $this->setCellFormat($cell, static::FORMAT_DATE); } } elseif (is_string($value) || is_null($value)) { if (is_numeric($value)) { $this->sheet()->getCell($cell)->setValueExplicit($value, DataType::TYPE_STRING); } else { $this->sheet()->setCellValue($cell, $value); } } else { if ($autoCellFormat && is_double($value)) { $this->setCellFormat($cell, static::FORMAT_DOUBLE); } elseif ($autoCellFormat && is_integer($value)) { $this->setCellFormat($cell, static::FORMAT_INT); } $this->sheet()->getCell($cell)->setValueExplicit($value, DataType::TYPE_NUMERIC); } return $this; } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\SheetsInterface::setValues() */ public function setValues(array $data, bool $autoCellFormat = true): self { foreach ($data as $row => $columns) { foreach ($columns as $column => $value) { $this->setValue($column.$row, $value, $autoCellFormat); } } return $this; } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\GridInterface::setGrid() */ public function setGrid(iterable $data): self { $row = 0; foreach ($data as $values) { $row++; $column = 'A'; foreach ($values as $value) { if ($value !== '' && $value !== null) { $this->setValue($column.$row, $value); } $column = $this->strIncrement($column); } } return $this; } /** * Get cell' value * * @param string $cell * @return mixed */ public function getValue(string $cell) { return $this->sheet()->getCell($cell)->getValue(); } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\SheetsInterface::getValues() */ public function getValues(?string $ceilRange): array { if (! $ceilRange) { $ceilRange = sprintf('A1:%s%s', $this->sheet()->getHighestColumn(), $this->sheet()->getHighestRow()); } return $this->sheet()->rangeToArray( $ceilRange, // The worksheet range that we want to retrieve null, // Value that should be returned for empty cells false, // Should formulas be calculated (the equivalent of getCalculatedValue() for each cell) false, // Should values be formatted (the equivalent of getFormattedValue() for each cell) true // Should the array be indexed by cell row and cell column ); } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\SheetsInterface::getMergeCells() */ public function getMergeCells(): array { return array_values($this->sheet()->getMergeCells()); } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\SheetsInterface::mergeCells() */ public function mergeCells(string $ceilRange): self { $this->sheet()->mergeCells($ceilRange); return $this; } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\SheetsInterface::copyStyle() */ public function copyStyle(string $cellFrom, string $rangeTo): self { $this->sheet()->duplicateStyle($this->sheet()->getStyle($cellFrom), $rangeTo); if ($conditionalStyle = $this->sheet()->getConditionalStyles($cellFrom)) { $this->sheet()->duplicateConditionalStyle($conditionalStyle, $rangeTo); } return $this; } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\SheetsInterface::copyCellFormat() */ public function copyCellFormat(string $cellFrom, string $rangeTo): self { $this->setCellFormat($rangeTo, $this->sheet()->getStyle($cellFrom)->getNumberFormat()->getFormatCode()); return $this; } /** * Set cell (data) format * * @param string $range * @param string $format * @return self */ public function setCellFormat(string $range, string $format): self { $this->sheet()->getStyle($range)->getNumberFormat()->setFormatCode($format); return $this; } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\SheetsInterface::addRow() */ public function addRow(int $rowBefore, int $qty = 1): self { $this->sheet()->insertNewRowBefore($rowBefore, $qty); return $this; } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\SheetsInterface::deleteRow() */ public function deleteRow(int $row, int $qty = 1): self { foreach ($this->getMergeCells() as $merge) { preg_match('#(\d+)#', $merge, $details); if ($details[1] == $row) { $this->sheet()->unmergeCells($merge); } } $this->sheet()->removeRow($row, $qty); return $this; } /** * Add a column * * @param string $columnBefore * @param int $qty * @return self */ public function addColumn(string $columnBefore, int $qty = 1): self { $this->sheet()->insertNewColumnBefore($columnBefore, $qty); return $this; } /** * Set auto-width for a column * * @param string $column * @return self */ public function autoWidth(string $column): self { $this->sheet()->getColumnDimension($column)->setAutoSize(true); return $this; } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\SheetsInterface::copyWidth() */ public function copyWidth(string $columnFrom, string $columnTo): self { $width = $this->sheet()->getColumnDimension($columnFrom)->getWidth(); $this->setWidth($columnTo, $width); return $this; } /** * Set fixed width for a column * * @param string $column * @param int|float $width * @return self */ public function setWidth(string $column, int|float $width): self { $this->sheet()->getColumnDimension($column)->setWidth($width); return $this; } /** * Copy row's height * * @param int $rowFrom * @param int $rowTo * @return self */ public function copyHeight(int $rowFrom, int $rowTo): self { $height = $this->sheet()->getRowDimension($rowFrom)->getRowHeight(); $this->setHeight($rowTo, $height); return $this; } /** * Set fixed height for a row * * @param string $row * @param int|float $height * @return self */ public function setHeight(string $row, int|float $height): self { $this->sheet()->getRowDimension($row)->setRowHeight($height); return $this; } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\MixInterface::setSheetTitle() */ public function setSheetTitle(string $title): self { $this->sheet()->setTitle($title); return $this; } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\MixInterface::getSheetTitle() */ public function getSheetTitle(): string { return $this->sheet()->getTitle(); } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\MixInterface::mergeDriver() */ public function mergeDriver(\AnourValar\Office\Drivers\MixInterface $driver): self { $index = $driver->spreadsheet->getActiveSheetIndex(); $this->spreadsheet->addExternalSheet($driver->sheet()); $driver->spreadsheet->createSheet($index); return $this; } /** * Apply cell`s style without format * * @param string $cellFrom * @param string $rangeTo * @param bool $copyAlignment * @return self */ public function copyStyleWithoutFormat(string $cellFrom, string $rangeTo, bool $copyAlignment = false): self { $style = $this->sheet()->getStyle($cellFrom)->exportArray(); if (! $copyAlignment) { unset($style['alignment'], $style['numberFormat'], $style['protection']); } else { unset($style['numberFormat'], $style['protection']); } // @TODO: fixed ? if ( ! isset($style['borders']['allBorders']) && isset($style['borders']['bottom'], $style['borders']['top']) && isset($style['borders']['left'], $style['borders']['right']) && $style['borders']['bottom'] == $style['borders']['top'] && $style['borders']['bottom'] == $style['borders']['left'] && $style['borders']['bottom'] == $style['borders']['right'] ) { $style['borders']['allBorders'] = $style['borders']['bottom']; unset($style['borders']['bottom'], $style['borders']['top']); unset($style['borders']['left'], $style['borders']['right']); } $this->sheet()->getStyle($rangeTo)->applyFromArray($style); return $this; } /** * Find a cell with the value * * @param mixed $value * @param bool $strict * @return array|null */ public function findCell($value, bool $strict = false): ?array { foreach ($this->getValues(null) as $row => $rowData) { foreach ($rowData as $column => $columnData) { if (($strict && $columnData === $value) || (! $strict && $columnData == $value)) { return [$column, $row]; } } } return null; } /** * Duplicate rows (with style, value) by range * * @param string $ceilRange * @param callable $value * @param int $indentRows * @param bool $addRows * @return self */ public function duplicateRows(string $ceilRange, callable $value, int $indentRows = 0, bool $addRows = true): self { $range = explode(':', $ceilRange); $range[0] = preg_split('#([A-Z]+)([\d]+)#S', $range[0], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); $range[1] = preg_split('#([A-Z]+)([\d]+)#S', $range[1], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); $shift = $range[1][1] - $range[0][1] + 1 + $indentRows; $mergeCells = $this->getMergeCells(); $values = $this->getValues($ceilRange); // Rows if ($addRows) { $this->addRow($range[1][1] + 1, $shift + $indentRows); } // Merge foreach ($mergeCells as $item) { $item = explode(':', $item); $item[0] = preg_split('#([A-Z]+)([\d]+)#S', $item[0], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); $item[1] = preg_split('#([A-Z]+)([\d]+)#S', $item[1], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); if ( $item[0][1] >= $range[0][1] && $item[0][1] <= $range[1][1] // rows && $item[1][1] >= $range[0][1] && $item[1][1] <= $range[1][1] && $this->isColumnGE($item[0][0], $range[0][0]) && $this->isColumnLE($item[0][0], $range[1][0]) // columns && $this->isColumnGE($item[1][0], $range[0][0]) && $this->isColumnLE($item[1][0], $range[1][0]) ) { $this->mergeCells($item[0][0].($item[0][1] + $shift) . ':' . $item[1][0].($item[1][1] + $shift)); } } $curr = $range[0][1]; while ($curr <= $range[1][1]) { // rows // Height $this->copyHeight($curr, $curr + $shift); // Style, CellFormat, Value $column = $range[0][0]; while ($this->isColumnLE($column, $range[1][0])) { $this->copyStyle($column . $curr, $column . ($curr + $shift)); $this->copyCellFormat($column . $curr, $column . ($curr + $shift)); if (isset($values[$curr][$column])) { $this->setValue($column . ($curr + $shift), $value($values[$curr][$column], $column, $curr), false); } $column = $this->strIncrement($column); } $curr++; } return $this; } /** * Set custom style for the range of cells * * @param string $range * @param array $style * @return self */ public function setStyle(string $range, array $style): self { if (isset($style['bold'])) { $this->sheet()->getStyle($range)->getFont()->setBold($style['bold']); } if (isset($style['italic'])) { $this->sheet()->getStyle($range)->getFont()->setItalic($style['italic']); } if (isset($style['size'])) { $this->sheet()->getStyle($range)->getFont()->setSize($style['size']); } if (isset($style['underline'])) { $this->sheet()->getStyle($range)->getFont()->setUnderline($style['underline']); } if (isset($style['color'])) { $this->sheet()->getStyle($range)->getFont()->getColor()->setRGB($style['color']); } if (isset($style['background_color'])) { $this ->sheet() ->getStyle($range) ->getFill() ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID) ->getStartColor() ->setRGB($style['background_color']); } if (isset($style['borders'])) { $this ->sheet() ->getStyle($range) ->getBorders() ->getAllBorders() ->setBorderStyle($style['borders'] ? Border::BORDER_THIN : Border::BORDER_NONE); } if (isset($style['borders_outline'])) { $this ->sheet() ->getStyle($range) ->getBorders() ->getOutline() ->setBorderStyle($style['borders_outline'] ? Border::BORDER_THIN : Border::BORDER_NONE); } if (isset($style['align'])) { $align = match ($style['align']) { \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT => 'left', \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER => 'center', \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_RIGHT => 'right', }; $this ->sheet() ->getStyle($range) ->getAlignment()->setHorizontal($align); } if (isset($style['valign'])) { $valign = match ($style['valign']) { \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_TOP => 'top', \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER => 'center', \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_BOTTOM => 'bottom', }; $this ->sheet() ->getStyle($range) ->getAlignment()->setVertical($valign); } if (isset($style['wrap'])) { $this ->sheet() ->getStyle($range) ->getAlignment()->setWrapText($style['wrap']); } return $this; } /** * Place an image * * @param string $filename * @param string $cell * @param array $options * @return self */ public function insertImage(string $filename, string $cell, array $options = []): self { $drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); if (isset($options['base64'])) { $filename = 'data:image/' . $options['base64'] . ';base64,' . base64_encode(file_get_contents($filename)); } $drawing->setPath($filename); $drawing->setCoordinates($cell); if (isset($options['coordinates2'])) { $drawing->setCoordinates2($options['coordinates2']); } if (isset($options['name'])) { $drawing->setName($options['name']); } if (isset($options['offset_x'])) { $drawing->setOffsetX($options['offset_x']); } if (isset($options['offset_x2'])) { $drawing->setOffsetX2($options['offset_x2']); } if (isset($options['offset_y'])) { $drawing->setOffsetY($options['offset_y']); } if (isset($options['offset_y2'])) { $drawing->setOffsetY2($options['offset_y2']); } if (isset($options['rotation'])) { $drawing->setRotation($options['rotation']); } if (isset($options['width']) && isset($options['height'])) { $drawing ->setResizeProportional(false) ->setWidth($options['width']) ->setHeight($options['height']); } elseif (isset($options['width'])) { $drawing->setWidth($options['width']); } elseif (isset($options['height'])) { $drawing->setHeight($options['height']); } $drawing->setWorksheet($this->sheet()); return $this; } /** * "Reader" configuration * * @param \AnourValar\Office\Drivers\PhpSpreadsheetDriver $instance * @return void */ protected function readConfiguration(PhpSpreadsheetDriver $instance): void { // } /** * "Writer" configuration * * @param \PhpOffice\PhpSpreadsheet\Writer\IWriter $writer * @return void */ protected function writeConfiguration(\PhpOffice\PhpSpreadsheet\Writer\IWriter $writer): void { if ($writer instanceof \PhpOffice\PhpSpreadsheet\Writer\Csv) { $writer->setDelimiter(';')->setUseBOM(true); } } /** * @param \AnourValar\Office\Format $format * @return string */ protected function getFormat(\AnourValar\Office\Format $format): string { return match ($format) { \AnourValar\Office\Format::Xlsx => 'Xlsx', \AnourValar\Office\Format::Pdf => 'Mpdf', \AnourValar\Office\Format::Html => 'Html', \AnourValar\Office\Format::Ods => 'Ods', \AnourValar\Office\Format::Csv => 'Csv', default => throw new \RuntimeException('Format is not supported.'), }; } } ================================================ FILE: src/Drivers/SaveInterface.php ================================================ load(__DIR__ . '/../resources/grid.xlsx', \AnourValar\Office\Format::Xlsx); } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\LoadInterface::load() * @psalm-suppress InaccessibleProperty */ public function load(string $file, \AnourValar\Office\Format $format): self { if (! in_array($format, [\AnourValar\Office\Format::Docx, \AnourValar\Office\Format::Xlsx])) { throw new \LogicException('Driver only supports Docx, Xlsx formats.'); } $instance = new static(); $fileSystem = []; $zipArchive = new \ZipArchive(); $zipArchive->open($file); try { $count = $zipArchive->numFiles; for ($i = 0; $i < $count; $i++) { $filename = $zipArchive->getNameIndex($i); $content = $zipArchive->getFromName($filename); $fileSystem[$filename] = $content; } } catch (\Throwable $e) { $zipArchive->close(); throw $e; } $zipArchive->close(); $instance->fileSystem = $fileSystem; $instance->format = $format; return $instance; } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\SaveInterface::save() */ public function save(string $file, \AnourValar\Office\Format $format): void { if ($format != $this->format) { throw new \LogicException('Driver only supports saving in the same format.'); } if (class_exists(\ZipStream\Option\Archive::class)) { $zipStream = new \ZipStream\ZipStream(); // 2.x } else { $zipStream = new \ZipStream\ZipStream(sendHttpHeaders: false, defaultEnableZeroHeader: false); // 3.x } ob_start(); try { foreach ($this->fileSystem as $filename => $content) { $zipStream->addFile($filename, $content); } } catch (\Throwable $e) { $zipStream->finish(); ob_get_clean(); throw $e; } $zipStream->finish(); file_put_contents($file, ob_get_clean()); } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\DocumentInterface::replace() */ public function replace(array $data): self { foreach ($data as &$value) { $value = $this->escape($value); } unset($value); foreach ($this->fileSystem as $filename => &$content) { if ($content && mb_strtolower(pathinfo($filename, PATHINFO_EXTENSION)) == 'xml') { $content = $this->handleReplace($content, $data); } } unset($content); return $this; } /** * {@inheritDoc} * @see \AnourValar\Office\Drivers\GridInterface::setGrid() */ public function setGrid(iterable $data): self { $sheet = ''; // matrix $cols = ''; // columns $buckets = []; // text (sharedStrings) $bucketsIndex = 0; $row = 0; // rows count $columnsCount = 0; // columns count $firstColumn = 'A'; // columns shift // Styles $styles = $this->loadGridStyles(); // Head (titles) while ($data->valid()) { $row++; $titles = $data->current(); $data->next(); $columnsCount = count($titles); if (! $titles) { continue; } $sheet .= ''; $column = 'A'; foreach ($titles as $value) { $value = (string) $value; if ($value === '') { $firstColumn = $this->strIncrement($firstColumn); $column = $this->strIncrement($column); continue; } $value = $this->escape($value); $curr = $buckets[$value] ?? null; if ($curr === null) { $curr = $bucketsIndex; $buckets[$value] = $curr; $bucketsIndex++; } $sheet .= ''.$curr.''; $column = $this->strIncrement($column); } $sheet .= ''; break; } // Custom styles foreach (($this->gridOptions['style'] ?? []) as $column => $alias) { if (isset($styles[$alias])) { $styles[$column] = $styles[$alias]; } } // Body (data) while ($data->valid()) { $values = $data->current(); $data->next(); $row++; $ht = ''; if (isset($this->gridOptions['height'][$row])) { $ht = 'ht="'.$this->gridOptions['height'][$row].'" customHeight="1" '; } $sheet .= ''; $column = 'A'; foreach ($values as $value) { if ($value instanceof \Stringable && ! $value instanceof \DateTimeInterface) { $value = (string) $value; } if ($value === null || $value === '') { if ($this->isColumnGE($column, $firstColumn)) { $sheet .= ''; } } elseif (is_string($value)) { $value = $this->escape($value); $curr = $buckets[$value] ?? null; if ($curr === null) { $curr = $bucketsIndex; $buckets[$value] = $curr; $bucketsIndex++; } $style = ($styles[$column] ?? $styles['string']); $sheet .= ''.$curr.''; } elseif (is_double($value)) { $style = ($styles[$column] ?? $styles['double']); $sheet .= ''.$value.''; } elseif (is_integer($value)) { $style = ($styles[$column] ?? $styles['integer']); $sheet .= ''.$value.''; } elseif ($value instanceof \DateTimeInterface) { $style = ($styles[$column] ?? $styles['date']); $sheet .= ''.$this->excelDate($value).''; } else { throw new \RuntimeException('Unsupported type of value.'); } $column = $this->strIncrement($column); } $sheet .= ''; } // Columns $column = 'A'; for ($index = 1; $index <= $columnsCount; $index++) { if ($this->isColumnGE($column, $firstColumn)) { $width = ($this->gridOptions['width'][$column] ?? 20); $cols .= ''; } $column = $this->strIncrement($column); } // Save buckets $this->saveGridSharedStrings($buckets); // Save columns & matrix $this->saveGridWorksheet($cols, $sheet, $row, $column); // Etc $this->saveGridEtc(); return $this; } /** * @param string $content * @param array $data * @return string */ protected function handleReplace(string $content, array &$data): string { foreach ($data as $from => $to) { $pattern = mb_str_split($from); foreach ($pattern as &$patternItem) { $patternItem = preg_quote($patternItem); $patternItem .= '(\<[^\[]*)?'; } unset($patternItem); $pattern = implode('', $pattern); $content = preg_replace_callback("#$pattern#Uu", function ($patterns) use ($from, $to) { if (strip_tags($patterns[0]) == $from) { return $to; } return $patterns[0]; }, $content); } return $content; } /** * Set styles map for the grid template [header, integer, double, double_10, string, date, percentage, ...] * * @param string $column * @param string $style * @return self */ public function setStyle(string $column, string $style): self { $this->gridOptions['style'][$column] = $style; return $this; } /** * Set column's width for the grid * * @param string $column * @param int $width * @return self */ public function setWidth(string $column, int $width): self { $this->gridOptions['width'][$column] = $width; return $this; } /** * Set row's height for the grid * * @param string $row * @param int|float $height * @return self */ public function setHeight(string $row, int|float $height): self { $this->gridOptions['height'][$row] = $height; return $this; } /** * Set sheet title for the grid * * @param string $title * @return self */ public function setSheetTitle(string $title): self { $this->fileSystem['xl/workbook.xml'] = preg_replace( '#\#uU', '', $this->fileSystem['xl/workbook.xml'] ); return $this; } /** * @return array */ protected function loadGridStyles(): array { $styles = []; // Bucket preg_match_all('#(.*)#uU', $this->fileSystem['xl/sharedStrings.xml'], $buckets); $buckets = $buckets[1]; // Matrix preg_match_all( '#(\d+)#uU', $this->fileSystem['xl/worksheets/sheet1.xml'], $matrix ); // Parse the map foreach ($matrix[2] as $key => $value) { if (isset($buckets[$value])) { $styles[mb_substr($buckets[$value], 1, -1)] = $matrix[1][$key]; } } // Presets return array_merge(['header' => 1, 'string' => 1, 'double' => 1, 'integer' => 1, 'date' => 1], $styles); } /** * @param array $buckets * @return void */ protected function saveGridSharedStrings(array &$buckets): void { $sst = ''; foreach (array_keys($buckets) as $word) { $sst .= ''.$word.''; } $count = count($buckets); $this->fileSystem['xl/sharedStrings.xml'] = << $sst HERE; } /** * @param string $cols * @param string $sheet * @param int $lastRow * @param string $lastColumn * @return void */ protected function saveGridWorksheet(string &$cols, string &$sheet, int $lastRow, string $lastColumn): void { $worksheet = 'xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ' . 'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" ' . 'xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" ' . 'mc:Ignorable="x14ac xr xr2 xr3" ' . 'xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" ' . 'xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" ' . 'xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2" ' . 'xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3" ' . 'xr:uid="{9CD2C5FF-272A-44F0-88FE-0A0D7B1A3B22}"'; if ($cols) { $cols = "$cols"; } if ($sheet) { $sheet = "$sheet"; } else { $sheet = ''; } if (! $lastRow) { $lastRow = 1; } $this->fileSystem['xl/worksheets/sheet1.xml'] = << $cols $sheet HERE; } /** * @return void */ protected function saveGridEtc(): void { // Created at timestamp $this->fileSystem['docProps/core.xml'] = preg_replace( '#(\)(.*?)(\<\/dcterms\:created\>)#', '${1}' . date('Y-m-d\TH:i:s\Z') . '$3', $this->fileSystem['docProps/core.xml'] ); // Modified at timestamp $this->fileSystem['docProps/core.xml'] = preg_replace( '#(\)(.*?)(\<\/dcterms\:modified\>)#', '${1}' . date('Y-m-d\TH:i:s\Z') . '$3', $this->fileSystem['docProps/core.xml'] ); } } ================================================ FILE: src/Facades/ExportGridInterface.php ================================================ buildBy($myGrid->query()->acl(), array_replace($this->profile, $this->profileExport)); // out of context * $request = $this->getBuildRequest()->get(); * * return response()->streamDownload( * function () use ($generatorData, $myGrid, $exportService, $format, $request) { * echo $exportService->grid($generatorData, $myGrid, $format, $request); * }, * $myGrid->fileName($format->fileExtension(), $request), * ['Access-Control-Expose-Headers' => 'Content-Disposition'] * ); */ interface ExportGridQueryInterface extends ExportGridInterface { /** * Laravel's Query builder (base query) * * @return \Illuminate\Database\Eloquent\Builder */ public function query(): \Illuminate\Database\Eloquent\Builder; } ================================================ FILE: src/Facades/ExportService.php ================================================ getDriver($format))) ->hookHeader(function (GridInterface $driver, mixed $header, string|int $key, string $column, int $rowNumber) use (&$extras) { if (isset($header['width'])) { $driver->setWidth($column, $header['width']); } if (isset($header['height'])) { $driver->setHeight($rowNumber, $header['height']); } $extras = array_merge($extras, $this->handleExtras($driver, $column, $header)); return $header['title']; }) ->hookRow(function (GridInterface $driver, mixed $row, string|int $key, int $rowNumber) use ($grid, $request) { return $grid->item($row, $driver, $rowNumber, $request); }) ->hookAfter(function ( GridInterface $driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns ) use ($grid, $request, &$extras) { $driver->setSheetTitle($grid->sheetTitle($request)); foreach ($extras as $extra) { $extra(); } }) ->generate($grid->columns($request), $dataGenerator) ->save($format); } /** * @param \AnourValar\Office\Drivers\GridInterface $driver * @param string $column * @param array $header * @return array * @throws \RuntimeException */ protected function handleExtras(GridInterface $driver, string $column, array $header): array { $extras = []; // percentage if (! empty($header[self::PERCENTAGE])) { if ($driver instanceof \AnourValar\Office\Drivers\ZipDriver) { $driver->setStyle($column, 'percentage'); } elseif ($driver instanceof \AnourValar\Office\Drivers\PhpSpreadsheetDriver) { $extras[] = fn () => $driver->setCellFormat($column, \AnourValar\Office\Drivers\PhpSpreadsheetDriver::FORMAT_PERCENTAGE); } else { throw new \RuntimeException('The driver does not support the "percentage" feature.'); } } // double_10 if (! empty($header[self::DOUBLE_10])) { if ($driver instanceof \AnourValar\Office\Drivers\ZipDriver) { $driver->setStyle($column, 'double_10'); } elseif ($driver instanceof \AnourValar\Office\Drivers\PhpSpreadsheetDriver) { $extras[] = fn () => $driver->setCellFormat($column, \AnourValar\Office\Drivers\PhpSpreadsheetDriver::FORMAT_DOUBLE_10); } else { throw new \RuntimeException('The driver does not support the "double_10" feature.'); } } // datetime if (! empty($header[self::DATETIME])) { if ($driver instanceof \AnourValar\Office\Drivers\ZipDriver) { $driver->setStyle($column, 'datetime'); } elseif ($driver instanceof \AnourValar\Office\Drivers\PhpSpreadsheetDriver) { $extras[] = fn () => $driver->setCellFormat($column, \AnourValar\Office\Drivers\PhpSpreadsheetDriver::FORMAT_DATETIME); } else { throw new \RuntimeException('The driver does not support the "datetime" feature.'); } } return $extras; } /** * @param \AnourValar\Office\Format $format * @return \AnourValar\Office\Drivers\GridInterface */ protected function getDriver(Format $format): GridInterface { if ($format == Format::Xlsx) { return new \AnourValar\Office\Drivers\ZipDriver(); } return new \AnourValar\Office\Drivers\PhpSpreadsheetDriver(); } } ================================================ FILE: src/Format.php ================================================ reader + writer case Pdf = 'pdf'; // sheets | grid => writer case Html = 'html'; // sheets | grid => reader + writer case Ods = 'ods'; // sheets | grid => reader + writer case Csv = 'csv'; // sheets | grid => reader + writer case Docx = 'docx'; // document => reader + writer /** * @return string */ public function fileExtension(): string { return match ($this) { Format::Xlsx => 'xlsx', Format::Pdf => 'pdf', Format::Html => 'html', Format::Ods => 'ods', Format::Csv => 'csv', Format::Docx => 'docx', }; } /** * MIME * * @return string */ public function contentType(): string { return match ($this) { Format::Xlsx => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', Format::Pdf => 'application/pdf', Format::Html => 'text/html', Format::Ods => 'application/vnd.oasis.opendocument.spreadsheet', Format::Csv => 'text/csv', Format::Docx => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', }; } } ================================================ FILE: src/Generated.php ================================================ driver = $driver; } /** * Save generated document to the buffer * * @param \AnourValar\Office\Format $format * @return string */ public function save(Format $format): string { ob_start(); if ($this->hookSave) { ($this->hookSave)($this->driver, $format); } else { $this->driver->save('php://output', $format); } return ob_get_clean(); } /** * Save generated document to the file * * @param string $filename * @param \AnourValar\Office\Format|null $format * @return int|null */ public function saveAs(string $filename, ?Format $format = null): ?int { if (! $format) { $format = Format::from(mb_strtolower(pathinfo($filename, PATHINFO_EXTENSION))); } return file_put_contents($filename, $this->save($format)); } /** * Set hookSave * * @param \Closure|null $closure * @return self */ public function hookSave(?\Closure $closure): self { $this->hookSave = $closure; return $this; } } ================================================ FILE: src/GridService.php ================================================ driver = $driver; } /** * Generate a document from the template (grid) * * @param array $headers * @param iterable|\Closure $data * @param string $leftTopCorner * @return \AnourValar\Office\Generated */ public function generate(array $headers, iterable|\Closure $data, string $leftTopCorner = 'A1'): Generated { // Handle with data if ($data instanceof \Closure) { $data = $data(); } // Create new document if ($this->hookLoad) { $driver = ($this->hookLoad)($this->driver); if ($driver instanceof Generated) { $driver = $driver->driver; } } else { $driver = $this->driver->create(); } // Hook: before if ($this->hookBefore) { ($this->hookBefore)($driver, $headers, $data, $leftTopCorner); } // Set data $driver->setGrid( $this->getGenerator($driver, $headers, $data, $leftTopCorner, $headersRange, $dataRange, $totalRange, $columns)() ); // Hook: after if ($this->hookAfter) { ($this->hookAfter)($driver, $headersRange, $dataRange, $totalRange, $columns); } return new Generated($driver); } /** * Set hookLoad * * @param ?\Closure $closure * @return self */ public function hookLoad(?\Closure $closure): self { $this->hookLoad = $closure; return $this; } /** * Set hookBefore * * @param ?\Closure $closure * @return self */ public function hookBefore(?\Closure $closure): self { $this->hookBefore = $closure; return $this; } /** * Set hookHeader * * @param ?\Closure $closure * @return self */ public function hookHeader(?\Closure $closure): self { $this->hookHeader = $closure; return $this; } /** * Set hookRow * * @param ?\Closure $closure * @return self */ public function hookRow(?\Closure $closure): self { $this->hookRow = $closure; return $this; } /** * Set hookAfter * * @param ?\Closure $closure * @return self */ public function hookAfter(?\Closure $closure): self { $this->hookAfter = $closure; return $this; } /** * @param \AnourValar\Office\Drivers\GridInterface $driver * @param array $headers * @param iterable $data * @param string $leftTopCorner * @param mixed $headersRange * @param mixed $dataRange * @param mixed $totalRange * @param mixed $columns * @return \Closure * @psalm-suppress UnusedForeachValue */ protected function getGenerator( \AnourValar\Office\Drivers\GridInterface $driver, array &$headers, iterable &$data, string $leftTopCorner, &$headersRange = null, &$dataRange = null, &$totalRange = null, &$columns = null ): \Closure { return function () use ($driver, &$headers, &$data, $leftTopCorner, &$headersRange, &$dataRange, &$totalRange, &$columns) { $ltc = preg_split('|([A-Z]+)|', $leftTopCorner, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); // left top corner: row $headerRow = 1; while ($ltc[1] > 1) { $ltc[1]--; $headerRow++; yield []; } // left top corner: column $firstColumn = 'A'; $indent = []; while ($this->isColumnLE($firstColumn, $ltc[0]) && $firstColumn != $ltc[0]) { $firstColumn = $this->strIncrement($firstColumn); $indent[] = ''; } // Handle with header $lastColumn = $firstColumn; $isFirst = true; $hasHeaders = false; foreach ($headers as $key => &$header) { if ($isFirst) { $isFirst = false; } else { $lastColumn = $this->strIncrement($lastColumn); } if ($this->hookHeader) { // Hook: header $header = ($this->hookHeader)($driver, $header, $key, $lastColumn, $headerRow); } if ($header) { $hasHeaders = true; } } unset($header); // First iteration with headers if ($hasHeaders) { yield array_merge($indent, $headers); } else { $headerRow--; } // Iterations with data $dataRow = $headerRow; $isFirst = ! $hasHeaders; foreach ($data as $key => $row) { // Hook: row if ($this->hookRow) { $row = ($this->hookRow)($driver, $row, $key, $dataRow + 1); } if (is_null($row)) { continue; } if ($isFirst) { foreach ($row as $item) { if ($isFirst) { $isFirst = false; } else { $lastColumn = $this->strIncrement($lastColumn); } } } yield array_merge($indent, $row); $dataRow++; } // Statistic $headersRange = null; if ($hasHeaders) { $headersRange = sprintf('%s%d:%s%d', $firstColumn, $headerRow, $lastColumn, $headerRow); } $dataRange = null; if ($dataRow != $headerRow) { $dataRange = sprintf('%s%d:%s%d', $firstColumn, ($headerRow + 1), $lastColumn, $dataRow); } $totalRange = null; if ($hasHeaders || $dataRow != $headerRow) { $totalRange = sprintf( '%s%d:%s%d', $firstColumn, ($hasHeaders ? $headerRow : ($headerRow + 1)), $lastColumn, $dataRow ); } $columns = []; if ($totalRange) { $keys = array_keys($headers); while ($this->isColumnLE($firstColumn, $lastColumn)) { if (! $keys) { $columns[] = $firstColumn; } else { $columns[array_shift($keys)] = $firstColumn; } $firstColumn = $this->strIncrement($firstColumn); } } }; } } ================================================ FILE: src/Mixer.php ================================================ driver; if (! $referenceDriver instanceof \AnourValar\Office\Drivers\MixInterface) { throw new \LogicException('Driver must implements MixInterface.'); } $titles = []; $count = $referenceDriver->getSheetCount(); for ($i = 0; $i < $count; $i++) { $referenceDriver->setSheet($i); $titles[] = $referenceDriver->getSheetTitle(); } foreach ($generated as $driver) { if (! $driver instanceof \AnourValar\Office\Generated) { throw new \LogicException('Input data must be instanceof Generated'); } $driver = $driver->driver; if (! $driver instanceof $referenceDriver) { throw new \LogicException('All drivers should be instances of the same implementation.'); } $count = $driver->getSheetCount(); for ($i = 0; $i < $count; $i++) { $driver->setSheet($i); $driver->setSheetTitle($titles[] = $this->getTitle($driver->getSheetTitle(), $titles)); $referenceDriver->mergeDriver($driver); } } return new Generated($referenceDriver); } /** * @param string $title * @param array $titles * @return string */ protected function getTitle(string $title, array $titles): string { while (in_array($title, $titles, true)) { $title = preg_replace_callback( '#\((\d+)\)$#', fn ($patterns) => '(' . ++$patterns[1] . ')', $title, -1, $count ); if (! $count) { $title .= ' (1)'; } } return $title; } } ================================================ FILE: src/Sheets/Parser.php ================================================ toArray(); } return $data; } /** * Get schema for a document * * @param array $values * @param array $data * @param array $mergeCells * @return \AnourValar\Office\Sheets\SchemaMapper */ public function schema(array $values, array $data, array $mergeCells): SchemaMapper { $schema = new SchemaMapper(); // Step 0: Parse input arguments to a canon format $values = $this->parseValues($values, $lastColumn); $data = $this->parseData($data); $mergeCells = $this->parseMergeCells($mergeCells); // Step 1: Short path -> full path $this->canonizeMarkers($values, $data); // Step 2: Calculate additional rows & columns, redundant data $dataSchema = $this->calculateDataSchema($values, $data, $mergeCells, $schema, $lastColumn); // Step 3: Shift formulas $this->shiftFormulas($dataSchema, $schema, $mergeCells); // Step 4: Replace markers with data $this->replaceMarkers($dataSchema, $data, $schema); return $schema; } /** * @param array $values * @param mixed $lastColumn * @return array */ protected function parseValues(array $values, &$lastColumn): array { $lastColumn = 'A'; foreach ($values as &$columns) { $currLastColumn = array_key_last($columns); if ($this->isColumnLE($lastColumn, $currLastColumn)) { $lastColumn = $currLastColumn; } $columns = array_filter($columns, fn ($item) => $item !== null && $item !== ''); } unset($columns); return $values; } /** * @param array $data * @return array */ protected function parseData(array &$data): array { $result = []; foreach ($this->dot($data) as $key => $value) { if (is_array($value) && ! $value) { continue; } $result[$key] = $value; } return $result; } /** * @param array $mergeCells * @return array */ protected function parseMergeCells(array $mergeCells): array { foreach ($mergeCells as &$item) { $item = explode(':', $item); $item[0] = preg_split('#([A-Z]+)([\d]+)#S', $item[0], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); $item[1] = preg_split('#([A-Z]+)([\d]+)#S', $item[1], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); } unset($item); return $mergeCells; } /** * @param array $values * @param array $data * @return void */ protected function canonizeMarkers(array &$values, array &$data): void { foreach ($values as &$columns) { foreach ($columns as &$value) { if (! is_string($value)) { continue; } $value = preg_replace_callback( '#\[(\$?\!\s*|\$?\=\s*)?([a-z\d\.\_\*]+)\]#iS', function ($patterns) use ($data) { if (array_key_exists($patterns[2], $data)) { return $patterns[0]; } $result = null; foreach (explode('.', $patterns[2]) as $pattern) { $changed = true; $prevResult = $result; while ($changed) { $changed = false; if ($this->isShortPath($result ? ($result . '.' . $pattern) : $pattern, $data)) { if ($result) { $result .= '.'; } $result .= $pattern; $changed = true; } if ($this->isShortPath($result . '.0', $data)) { $result .= '.0'; $changed = true; } } if ($result === $prevResult && ($pattern != '*' || mb_substr($result, -2) != '.0')) { return $patterns[0]; } } if ($result && array_key_exists($result, $data) && ! is_array($data[$result])) { $result = preg_replace('#\.0(\.|$)#S', '.*$1', $result); return sprintf('[%s%s]', $patterns[1], $result); } return $patterns[0]; }, $value ); } unset($value); } unset($columns); } /** * @param array $values * @param array $data * @param array $mergeCells * @param \AnourValar\Office\Sheets\SchemaMapper $schema * @param string $lastColumn * @return array * @psalm-suppress UnusedForeachValue */ protected function calculateDataSchema( array &$values, array &$data, array &$mergeCells, SchemaMapper &$schema, string $lastColumn ): array { $dataSchema = []; $shift = 0; $step = 0; $stepLeft = 0; $stepOrigin = 0; $stepRows = 0; // fill in missing rows $prevRow = 0; foreach (array_keys($values) as $row) { $diff = ($row - $prevRow); while ($diff > 1) { $values[$row - $diff + 1] = []; $diff--; } $prevRow = $row; } ksort($values); foreach ($values as $row => $columns) { $maxMergeY = 0; $additionRows = 0; $additionColumns = 0; $additionColumn = null; foreach ($columns as $column => $value) { foreach (array_keys($data) as $markerName) { if (! $this->hasMarker($markerName, $value)) { continue; } $qty = 0; $pattern = $markerName; while ($pattern = $this->increment($pattern, true)) { if (! array_key_exists($pattern, $data)) { break; } $qty++; } $additionRows = max($additionRows, $qty); $qty = 0; $pattern = $markerName; while ($pattern = $this->increment($pattern, false)) { if (! array_key_exists($pattern, $data)) { break; } $qty++; } if ($qty) { $additionColumns = max($additionColumns, $qty); $additionColumn = $column; } } if (is_string($value)) { $columns[$column] = preg_replace('#\.\*(\.|\])#S', '.0$1', $value); } } if (! $stepRows && $this->shouldBeDeleted($columns, $data)) { $this->deleteRow($schema, $mergeCells, $row + $shift); $shift--; continue; } if ($stepRows) { $additionRows = $stepRows; } $currAdditionRows = $additionRows; if (! $additionRows) { foreach ($columns as $column => $value) { if ( ! preg_match('#\[(\$?\!\s*|\$?\=\s*)?[a-z][a-z\d\_\.]+\]#iS', (string) $value) && ! preg_match('#^\=[A-Z][A-Z\.\d]#', (string) $value) ) { unset($columns[$column]); } } if (! $columns) { continue; } } $curr = $additionColumn; $additionColumnValue = isset($curr) ? ($columns[$curr] ?? null) : null; $mergeMapX = []; foreach ($mergeCells as $item) { if ($additionColumn.($row + $shift) == $item[0][0].$item[0][1] && $item[0][1] == $item[1][1]) { while ($this->isColumnLE($item[0][0], $item[1][0]) && $item[0][0] != $item[1][0]) { $item[0][0] = $this->strIncrement($item[0][0]); $mergeMapX[] = $item[0][0]; } } } foreach ($mergeMapX as $mergeItemX) { $curr = $this->strIncrement($curr); } while ($additionColumns) { $curr = $this->strIncrement($curr); $additionColumns--; $additionColumnValue = $this->increments($additionColumnValue, false); $columns[$curr] = $additionColumnValue; $schema->copyStyle($additionColumn.($row + $shift), $curr.($row + $shift)); $schema->copyWidth($additionColumn, $curr); if ($mergeMapX) { $originalCurr = $curr; foreach ($mergeMapX as $mergeItemX) { $curr = $this->strIncrement($curr); $schema->copyWidth($mergeItemX, $curr); } $schema->mergeCells(sprintf('%s%s:%s%s', $originalCurr, ($row + $shift), $curr, ($row + $shift))); $mergeCells[] = [ [$originalCurr, ($row + $shift)], [$curr, ($row + $shift)] ]; // fill in } } $dataSchema[$row + $shift] = $columns; $originalRow = ($row + $shift); if ($additionRows) { $firstColumn = 'A'; while ($this->isColumnLE($firstColumn, $lastColumn)) { if (! isset($columns[$firstColumn]) && ! $this->insideMerge($firstColumn, $originalRow, $mergeCells)) { $columns[$firstColumn] = null; } $firstColumn = $this->strIncrement($firstColumn); } uksort($columns, fn ($a, $b) => $this->isColumnLE($a, $b) ? -1 : 1); foreach ($columns as $currKey => $currValue) { $hasMarker = preg_match('#\[([a-z][a-z\d\.\_]+)\]#iS', (string) $currValue); foreach ($mergeCells as $item) { if ($currKey.$originalRow == $item[0][0].$item[0][1] && $item[0][1] != $item[1][1]) { if (! $hasMarker) { unset($columns[$currKey]); continue; } $maxMergeY = max($maxMergeY, ($item[1][1] - $item[0][1])); } } } $shift += $maxMergeY; } while ($additionRows) { $shift += $step; $shift++; $additionRows--; foreach ($columns as &$column) { if (is_string($column)) { $column = $this->increments($column, true); } } unset($column); $dataSchema[$row + $shift] = array_filter($columns, fn ($item) => $item !== null && $item !== ''); if (! $step) { $this->addRow($schema, $mergeCells, $row + $shift); } foreach (array_keys($columns) as $curr) { $schema->copyStyle($curr.$originalRow, $curr.($row + $shift)); foreach ($mergeCells as $item) { if ($curr.$originalRow == $item[0][0].$item[0][1]) { $diff = $item[1][1] - $item[0][1]; $schema->mergeCells( sprintf('%s%s:%s%s', $item[0][0], ($row + $shift), $item[1][0], ($row + $shift + $diff)) ); } } } $iterate = $maxMergeY; while ($iterate) { $shift++; $this->addRow($schema, $mergeCells, $row + $shift); $iterate--; } } if ($stepLeft) { $stepLeft--; } if (! $stepLeft) { $step = 0; $stepRows = 0; } else { $stepOrigin--; $shift -= $stepOrigin - $stepLeft; } if ($maxMergeY) { $stepRows = $currAdditionRows; $step = $maxMergeY; $stepLeft = $step; $stepOrigin = (($maxMergeY + 1) * ($currAdditionRows)) + $step; $shift -= $stepOrigin; } } unset($values); return $dataSchema; } /** * @param array $values * @param \AnourValar\Office\Sheets\SchemaMapper $schema * @param array $mergeCells * @return void */ protected function shiftFormulas(array &$values, SchemaMapper &$schema, array &$mergeCells): void { // Prepares $ranges = []; $map = []; foreach ($values as $row => $columns) { foreach ($columns as $column => $value) { if (preg_match('#^\=[A-Z][A-Z\.\d]#', (string) $value)) { $map[$row][$column] = $value; } } } // "Outside" shifts foreach ($schema->getOriginal()['rows'] as $action) { foreach ($map as $row => $columns) { foreach ($columns as $column => $value) { $map[$row][$column] = $values[$row][$column] = preg_replace_callback( '#([A-Z]+)([\d]+)#S', function ($patterns) use ($action) { if ($action['action'] == 'add') { if ($patterns[2] >= $action['row']) { return $patterns[1] . ++$patterns[2]; } } else { if ($patterns[2] > $action['row']) { return $patterns[1] . --$patterns[2]; } } return $patterns[0]; }, $value ); } } } // "Inside" shifts $prev = 0; $prevAction = null; foreach ($schema->getOriginal()['rows'] as $action) { if ($prevAction && $prevAction['row'] + 1 == $action['row'] && $action['action'] == 'add' && $prevAction['action'] == 'add') { $prev++; } else { if ($prevAction && $prevAction['action'] == 'add') { $ranges[] = ['from' => ($prevAction['row'] - $prev - 1), 'to' => ($prevAction['row'])]; } $prev = 0; } foreach ($map as $row => $columns) { foreach ($columns as $column => $value) { $map[$row][$column] = $values[$row][$column] = preg_replace_callback( '#([A-Z]+)([\d]+)#S', function ($patterns) use ($action, $row, $prev) { if ($action['action'] == 'add') { if ($action['row'] == $row && ($row - $prev) == ($patterns[2] + 1)) { return $patterns[1] . (++$patterns[2] + $prev); } } return $patterns[0]; }, $value ); } } $prevAction = $action; } if ($prev || ($prevAction && $prevAction['action'] == 'add')) { $ranges[] = ['from' => ($prevAction['row'] - $prev - 1), 'to' => ($prevAction['row'])]; } // Dynamic table ranges foreach ($ranges as $key => $value) { foreach ($mergeCells as $merge) { if ( $value['from'] >= $merge[0][1] && $value['from'] <= $merge[1][1] && $merge[0][1] != $merge[1][1] && preg_match('#\[([a-z][a-z\d\.\_]+)\]#iS', ($values[$merge[0][1]][$merge[0][0]] ?? '')) ) { unset($ranges[$key]); } } } foreach ($map as $row => $columns) { foreach ($columns as $column => $value) { $map[$row][$column] = $values[$row][$column] = preg_replace_callback( '#([A-Z]+)([\d]+)\:([A-Z]+)([\d]+)#S', function ($patterns) use ($ranges) { if ($patterns[2] == $patterns[4]) { foreach ($ranges as $range) { if ($patterns[2] == $range['from']) { return sprintf('%s%d:%s%d', $patterns[1], $patterns[2], $patterns[3], $range['to']); } } } return $patterns[0]; }, $value ); } } } /** * @param array $dataSchema * @param array $data * @param \AnourValar\Office\Sheets\SchemaMapper $schema * @return void */ protected function replaceMarkers(array &$dataSchema, array &$data, SchemaMapper &$schema): void { $canonizeKeys = ['scalar' => [], 'closure' => []]; $canonizeValues = ['scalar' => [], 'closure' => []]; foreach ($data as $from => $to) { if (! preg_match('#^[a-z][a-z\d\.\_]+$#iS', $from)) { continue; } if (is_scalar($to) || is_null($to)) { $canonizeKeys['scalar'][] = "[$from]"; $canonizeValues['scalar'][] = $to; } else { $canonizeKeys['closure'][] = "[$from]"; $canonizeValues['closure'][] = $to; } } foreach ($dataSchema as $row => $columns) { ksort($columns); foreach ($columns as $column => $value) { if (is_string($value) && $this->shouldBeDeleted([$value], $data, '$')) { $value = null; } if (is_string($value) && mb_strlen($value)) { $value = preg_replace('#\[(\$?\!\s*|\$?\=\s*)[a-z][a-z\d\_\.]+\]#iS', '', $value); $value = trim($value); if (($key = array_search($value, $canonizeKeys['scalar'])) !== false) { // type (cast) support $value = $canonizeValues['scalar'][$key]; } elseif (($key = array_search($value, $canonizeKeys['closure'])) !== false) { $value = $canonizeValues['closure'][$key]; } else { $value = str_replace($canonizeKeys['scalar'], $canonizeValues['scalar'], $value); } } if (is_string($value) && mb_strlen($value)) { $value = preg_replace('#\[[a-z][a-z\d\_\.]+\]#iS', '', $value); $value = trim($value); } if (is_string($value) && ! mb_strlen($value)) { $value = null; } $schema->addData($row, $column, $value); } } } /** * @param string $path * @param array $markers * @return bool */ private function isShortPath(string $path, array $markers): bool { if (array_key_exists($path, $markers)) { return true; } if (! str_ends_with($path, '.')) { $path .= '.'; } foreach (array_keys($markers) as $marker) { if (strpos($marker, $path) === 0) { return true; } } return false; } /** * @param string $marker * @param string $value * @return bool */ private function hasMarker(string $marker, ?string $value): bool { $value = preg_replace('#\.\*(\.|$|)#S', '.0$1', (string) $value, -1, $count); if (! $count) { return false; } if (strpos((string) $value, "[$marker]") !== false) { return true; } return false; } /** * @param array $columns * @param array $data * @param string $prefix * @return bool */ private function shouldBeDeleted(array $columns, array &$data, string $prefix = ''): bool { $prefix = preg_quote($prefix); foreach ($columns as $column) { if (is_null($column)) { continue; } preg_match_all("#\[{$prefix}\=\s*([a-z\d\.\_]+)\]#i", $column, $patterns); foreach (($patterns[1] ?? []) as $marker) { if (! empty($data[$marker])) { continue; } foreach ($data as $key => $value) { if (strpos($key, $marker.'.') === 0 && ! empty($value)) { continue 2; } } return true; } preg_match_all("#\[{$prefix}\!\s*([a-z\d\.\_]+)\]#i", $column, $patterns); foreach (($patterns[1] ?? []) as $marker) { if (! empty($data[$marker])) { return true; } foreach ($data as $key => $value) { if (strpos($key, $marker.'.') === 0 && ! empty($value)) { return true; } } } } return false; } /** * @param string $markerName * @param bool $first * @param int $shift * @return string|null */ private function increment(string $markerName, bool $first, int $shift = 1): ?string { $markerName = explode('.', $markerName); if (! $first) { $markerName = array_reverse($markerName); } if (! $first) { $qty = 0; foreach ($markerName as $item) { if (is_numeric($item)) { $qty++; } } if ($qty < 2) { return null; } } foreach ($markerName as &$item) { if (is_numeric($item)) { $item += $shift; if (! $first) { $markerName = array_reverse($markerName); } return implode('.', $markerName); } } unset($item); return null; } /** * @param string $value * @param bool $first * @param int $shift * @return string|null */ private function increments(string $value, bool $first, int $shift = 1): ?string { return preg_replace_callback( '#\[(\$?\!\s*|\$?\=\s*)?([a-z][a-z\d\.\_]+)\]#iS', function ($patterns) use ($first, $shift) { $patterns[2] = $this->increment($patterns[2], $first, $shift); if ($patterns[2]) { return '[' . $patterns[1] . $patterns[2] . ']'; } return $patterns[0]; }, $value ); } /** * @param \AnourValar\Office\Sheets\SchemaMapper $schema * @param array $mergeCells * @param int $row * @return void */ private function addRow(SchemaMapper &$schema, array &$mergeCells, int $row): void { $schema->addRow($row); foreach ($mergeCells as &$mergeCell) { if ($mergeCell[0][1] >= $row) { $mergeCell[0][1]++; } if ($mergeCell[1][1] >= $row) { $mergeCell[1][1]++; } } unset($mergeCell); } /** * @param \AnourValar\Office\Sheets\SchemaMapper $schema * @param array $mergeCells * @param int $row * @return void */ private function deleteRow(SchemaMapper &$schema, array &$mergeCells, int $row): void { $schema->deleteRow($row); foreach ($mergeCells as &$mergeCell) { if ($mergeCell[0][1] >= $row) { $mergeCell[0][1]--; } if ($mergeCell[1][1] >= $row) { $mergeCell[1][1]--; } } unset($mergeCell); } /** * @param string $column * @param int $row * @param array $mergeCells * @return bool */ private function insideMerge(string $column, int $row, array &$mergeCells): bool { foreach ($mergeCells as $item) { if ( $this->isColumnLE($item[0][0], $column) && $this->isColumnGE($item[1][0], $column) && $item[0][1] <= $row && $item[1][1] >= $row && ($item[0][0] != $column || $item[0][1] != $row) ) { return true; } } return false; } } ================================================ FILE: src/Sheets/SchemaMapper.php ================================================ [], //[ 1 => ['A' => 'foo'], '2' => ['B' => 'bar'] ] 'rows' => [], //[ ['action' => 'add', 'row' => 1, 'qty' => 1], ['action' => 'delete', 'row' => 2, 'qty' => 1] ] 'copy_style' => [], //[ ['from' => 'A1', 'to' => 'A2'] ] 'merge_cells' => [], //[ 'A1:B1', 'C1:D1'] 'copy_width' => [], //[ ['from' => 'B', 'to' => 'C'] ] ]; /** * @return array */ public function toArray(): array { ksort($this->payload['data']); $this->normalizeRows($this->payload['rows']); $this->normalizeCells($this->payload['copy_style']); sort($this->payload['copy_width']); return $this->payload; } /** * @return array */ public function getOriginal(): array { return $this->payload; } /** * @param int $row * @param string $column * @param mixed $value * @return self */ public function addData(int $row, string $column, mixed $value): self { $this->payload['data'][$row][$column] = $value; return $this; } /** * @param int $rowBefore * @return self */ public function addRow(int $rowBefore): self { $this->payload['rows'][] = ['action' => 'add', 'row' => $rowBefore, 'qty' => 1]; return $this; } /** * @param int $row * @return self */ public function deleteRow(int $row): self { $this->payload['rows'][] = ['action' => 'delete', 'row' => $row, 'qty' => 1]; return $this; } /** * @param string $from * @param string $to * @return self */ public function copyStyle(string $from, string $to): self { $this->payload['copy_style'][$from.$to] = ['from' => $from, 'to' => $to]; return $this; } /** * @param string $ceilRange * @return self */ public function mergeCells(string $ceilRange): self { $this->payload['merge_cells'][] = $ceilRange; return $this; } /** * @param string $from * @param string $to * @return self */ public function copyWidth(string $from, string $to): self { $this->payload['copy_width'][$from . $to] = ['from' => $from, 'to' => $to]; return $this; } /** * @param array $rows * @return void */ protected function normalizeRows(array &$rows): void { $optimizedRows = []; $curr = []; foreach ($rows as $item) { if ($curr) { if ($item['action'] == 'add' && $curr['action'] == $item['action'] && $item['row'] == ($curr['row'] + $curr['qty'])) { $curr['qty']++; } else { $optimizedRows[] = ['action' => $curr['action'], 'row' => $curr['row'], 'qty' => $curr['qty']]; $curr = null; } } if (! $curr) { $curr = ['action' => $item['action'], 'row' => $item['row'], 'qty' => $item['qty']]; } } if ($curr) { $optimizedRows[] = ['action' => $curr['action'], 'row' => $curr['row'], 'qty' => $curr['qty']]; } $rows = $optimizedRows; } /** * @param array $data * @return void */ protected function normalizeCells(array &$data): void { ksort($data, SORT_NATURAL); $data = array_values($data); $optimizedData = []; $curr = []; foreach ($data as $item) { $expect = preg_split('#([A-Z]+)([\d]+)#S', $item['to'], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); $expect[1]++; // @phpstan-ignore-line $expect = implode('', $expect); if ($curr) { if ($item['from'] == $curr['from'] && $item['to'] == $curr['expect']) { $curr['append'] = ':' . $curr['expect']; $curr['expect'] = $expect; } else { $optimizedData[] = ['from' => $curr['from'], 'to' => $curr['to'] . $curr['append']]; $curr = null; } } if (! $curr) { $curr = ['from' => $item['from'], 'to' => $item['to'], 'append' => '', 'expect' => $expect]; } } if ($curr) { $optimizedData[] = ['from' => $curr['from'], 'to' => $curr['to'] . $curr['append']]; } $data = $optimizedData; } } ================================================ FILE: src/SheetsService.php ================================================ driver = $driver; $this->parser = $parser; } /** * Generate a document from the template (sheets) * * @param string|\Stringable $templateFile * @param mixed $data * @param bool $autoCellFormat * @return \AnourValar\Office\Generated */ public function generate(string|\Stringable $templateFile, mixed $data, bool $autoCellFormat = false): Generated { // Handle with input data $data = $this->parser->canonizeData($data); // Open the template $templateFormat = Format::tryFrom(mb_strtolower(pathinfo($templateFile, PATHINFO_EXTENSION))) ?? Format::Xlsx; if ($this->hookLoad) { $driver = ($this->hookLoad)($this->driver, $templateFile, $templateFormat); if ($driver instanceof Generated) { $driver = $driver->driver; } } else { $driver = $this->driver->load($templateFile, $templateFormat); } // Hook: before if ($this->hookBefore) { ($this->hookBefore)($driver, $data); } // Handle sheets $count = $driver->getSheetCount(); for ($sheetIndex = 0; $sheetIndex < $count; $sheetIndex++) { $driver->setSheet($sheetIndex); $this->handleSheet($driver, $data, $sheetIndex, $autoCellFormat); } // Hook: after if ($this->hookAfter) { ($this->hookAfter)($driver); } // Return return new Generated($driver); } /** * Set hookLoad * * @param ?\Closure $closure * @return self */ public function hookLoad(?\Closure $closure): self { $this->hookLoad = $closure; return $this; } /** * Set hookBefore * * @param ?\Closure $closure * @return self */ public function hookBefore(?\Closure $closure): self { $this->hookBefore = $closure; return $this; } /** * Set hookValue * * @param ?\Closure $closure * @return self */ public function hookValue(?\Closure $closure): self { $this->hookValue = $closure; return $this; } /** * Set hookAfter * * @param ?\Closure $closure * @return self */ public function hookAfter(?\Closure $closure): self { $this->hookAfter = $closure; return $this; } /** * @param \AnourValar\Office\Drivers\SheetsInterface $driver * @param array $data * @param int $sheetIndex * @throws \LogicException * @return void */ protected function handleSheet(SheetsInterface &$driver, array &$data, int $sheetIndex, bool $autoCellFormat): void { // Get schema of the document $schema = $this->parser->schema($driver->getValues(null), $data, $driver->getMergeCells())->toArray(); // Rows foreach ($schema['rows'] as $row) { if ($row['action'] == 'add') { $driver->addRow($row['row'], $row['qty']); } elseif ($row['action'] == 'delete') { $driver->deleteRow($row['row'], $row['qty']); } else { throw new \LogicException('Incorrect usage.'); } } // Copy style & cell format foreach ($schema['copy_style'] as $item) { $driver->copyStyle($item['from'], $item['to']); if (! $autoCellFormat) { $driver->copyCellFormat($item['from'], $item['to']); } } // Merge cells foreach ($schema['merge_cells'] as $item) { $driver->mergeCells($item); } // Copy width foreach ($schema['copy_width'] as $item) { $driver->copyWidth($item['from'], $item['to']); } // Data $driver->setValues($this->handleData($schema['data'], $driver, $sheetIndex), $autoCellFormat); } /** * @param array $data * @param \AnourValar\Office\Drivers\SheetsInterface $driver * @param int $sheetIndex * @return array */ protected function handleData(array $data, SheetsInterface $driver, int $sheetIndex): array { foreach ($data as $row => &$columns) { foreach ($columns as $column => &$value) { if ($value instanceof \Closure) { // Private Closure $value = $value($driver, $column, $row); if (is_null($value)) { unset($data[$row][$column]); } } elseif ($this->hookValue) { // Hook: value $value = ($this->hookValue)($driver, $column, $row, $value, $sheetIndex); if (is_null($value)) { unset($data[$row][$column]); } } } unset($value); } unset($columns); return $data; } } ================================================ FILE: src/Traits/Parser.php ================================================ $value) { if (is_array($value)) { $result = array_replace($result, $this->dot($value, $prefix.$key.'.')); } else { $result[$prefix.$key] = $value; } } return $result; } /** * @param string $compareColumn * @param string $referenceColumn * @return bool */ protected function isColumnLE(string $compareColumn, string $referenceColumn): bool { $compareLength = strlen($compareColumn); $referenceLength = strlen($referenceColumn); if ($compareLength < $referenceLength) { return true; } if ($compareLength > $referenceLength) { return false; } return $compareColumn <= $referenceColumn; } /** * @param string $compareColumn * @param string $referenceColumn * @return bool */ protected function isColumnGE(string $compareColumn, string $referenceColumn): bool { $compareLength = strlen($compareColumn); $referenceLength = strlen($referenceColumn); if ($compareLength > $referenceLength) { return true; } if ($compareLength < $referenceLength) { return false; } return $compareColumn >= $referenceColumn; } /** * Polyfill * * @param string $value * @return string */ protected function strIncrement(string $value): string { if (PHP_VERSION_ID >= 80300) { return str_increment($value); } $value++; // @phpstan-ignore-line return $value; } } ================================================ FILE: src/Traits/XFormat.php ================================================ format('Y'); $month = (int) $date->format('m'); $day = (int) $date->format('d'); $hours = (int) $date->format('H'); $minutes = (int) $date->format('i'); $seconds = (int) $date->format('s'); $leapYear = true; if ($year == 1900 && $month <= 2) { $leapYear = false; } $baseDate = 2415020; if ($month > 2) { $month -= 3; } else { $month += 9; --$year; } $century = (int) substr($year, 0, 2); $decade = (int) substr($year, 2, 2); $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5); $excelDate += $day + 1721119 - $baseDate + $leapYear; $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400; return (float) $excelDate + $excelTime; } /** * @param string|null $value * @return string */ protected function escape(?string $value): string { return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8', true); } } ================================================ FILE: tests/GridServiceTest.php ================================================ getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('A1:A1', $headersRange); $this->assertSame(null, $dataRange); $this->assertSame('A1:A1', $totalRange); $this->assertSame(['A'], $columns); }) ->generate( ['foo'], [ ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('A1:A1', $headersRange); $this->assertSame('A2:A2', $dataRange); $this->assertSame('A1:A2', $totalRange); $this->assertSame(['A'], $columns); }) ->generate( ['foo'], [ ['111'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('A1:A1', $headersRange); $this->assertSame('A2:A3', $dataRange); $this->assertSame('A1:A3', $totalRange); $this->assertSame(['A'], $columns); }) ->generate( ['foo'], [ ['foo-1'], ['foo-2'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('A1:A1', $headersRange); $this->assertSame('A2:A4', $dataRange); $this->assertSame('A1:A4', $totalRange); $this->assertSame(['A'], $columns); }) ->generate( ['foo'], [ ['foo-1'], ['foo-2'], ['foo-3'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('A1:B1', $headersRange); $this->assertSame(null, $dataRange); $this->assertSame('A1:B1', $totalRange); $this->assertSame(['A', 'B'], $columns); }) ->generate( ['foo', 'bar'], [ ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('A1:B1', $headersRange); $this->assertSame('A2:B2', $dataRange); $this->assertSame('A1:B2', $totalRange); $this->assertSame(['A', 'B'], $columns); }) ->generate( ['foo', 'bar'], [ ['foo-1', 'bar-1'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('A1:B1', $headersRange); $this->assertSame('A2:B3', $dataRange); $this->assertSame('A1:B3', $totalRange); $this->assertSame(['A', 'B'], $columns); }) ->generate( ['foo', 'bar'], [ ['foo-1', 'bar-1'], ['foo-2', 'bar-2'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('A1:B1', $headersRange); $this->assertSame('A2:B4', $dataRange); $this->assertSame('A1:B4', $totalRange); $this->assertSame(['A', 'B'], $columns); }) ->generate( ['foo', 'bar'], [ ['foo-1', 'bar-1'], ['foo-2', 'bar-2'], ['foo-3', 'bar-3'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('A1:C1', $headersRange); $this->assertSame(null, $dataRange); $this->assertSame('A1:C1', $totalRange); $this->assertSame(['A', 'B', 'C'], $columns); }) ->generate( ['foo', 'bar', 'baz'], [ ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('A1:C1', $headersRange); $this->assertSame('A2:C2', $dataRange); $this->assertSame('A1:C2', $totalRange); $this->assertSame(['A', 'B', 'C'], $columns); }) ->generate( ['foo', 'bar', 'baz'], [ ['foo-1', 'bar-1', 'baz-1'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('A1:C1', $headersRange); $this->assertSame('A2:C3', $dataRange); $this->assertSame('A1:C3', $totalRange); $this->assertSame(['A', 'B', 'C'], $columns); }) ->generate( ['foo', 'bar', 'baz'], [ ['foo-1', 'bar-1', 'baz-1'], ['foo-2', 'bar-2', 'baz-2'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('A1:C1', $headersRange); $this->assertSame('A2:C4', $dataRange); $this->assertSame('A1:C4', $totalRange); $this->assertSame(['A', 'B', 'C'], $columns); }) ->generate( ['foo', 'bar', 'baz'], [ ['foo-1', 'bar-1', 'baz-1'], ['foo-2', 'bar-2', 'baz-2'], ['foo-3', 'bar-3', 'baz-3'] ], ); } /** * @return void */ public function test_generate_statistic_with_headers_with_shift() { (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('C5:C5', $headersRange); $this->assertSame(null, $dataRange); $this->assertSame('C5:C5', $totalRange); $this->assertSame(['one' => 'C'], $columns); }) ->generate( ['one' => 'foo'], [ ], 'C5' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('C5:C5', $headersRange); $this->assertSame('C6:C6', $dataRange); $this->assertSame('C5:C6', $totalRange); $this->assertSame(['C'], $columns); }) ->generate( ['foo'], [ ['111'] ], 'C5' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('C5:C5', $headersRange); $this->assertSame('C6:C7', $dataRange); $this->assertSame('C5:C7', $totalRange); $this->assertSame(['C'], $columns); }) ->generate( ['foo'], [ ['foo-1'], ['foo-2'] ], 'C5' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('C5:C5', $headersRange); $this->assertSame('C6:C8', $dataRange); $this->assertSame('C5:C8', $totalRange); $this->assertSame(['C'], $columns); }) ->generate( ['foo'], [ ['foo-1'], ['foo-2'], ['foo-3'] ], 'C5' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('C5:D5', $headersRange); $this->assertSame(null, $dataRange); $this->assertSame('C5:D5', $totalRange); $this->assertSame(['C', 'D'], $columns); }) ->generate( ['foo', 'bar'], [ ], 'C5' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('C5:D5', $headersRange); $this->assertSame('C6:D6', $dataRange); $this->assertSame('C5:D6', $totalRange); $this->assertSame(['C', 'D'], $columns); }) ->generate( ['foo', 'bar'], [ ['foo-1', 'bar-1'] ], 'C5' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('C5:D5', $headersRange); $this->assertSame('C6:D7', $dataRange); $this->assertSame('C5:D7', $totalRange); $this->assertSame(['C', 'D'], $columns); }) ->generate( ['foo', 'bar'], [ ['foo-1', 'bar-1'], ['foo-2', 'bar-2'] ], 'C5' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('C5:D5', $headersRange); $this->assertSame('C6:D8', $dataRange); $this->assertSame('C5:D8', $totalRange); $this->assertSame(['C', 'D'], $columns); }) ->generate( ['foo', 'bar'], [ ['foo-1', 'bar-1'], ['foo-2', 'bar-2'], ['foo-3', 'bar-3'] ], 'C5' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('C5:E5', $headersRange); $this->assertSame(null, $dataRange); $this->assertSame('C5:E5', $totalRange); $this->assertSame(['C', 'D', 'E'], $columns); }) ->generate( ['foo', 'bar', 'baz'], [ ], 'C5' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('C5:E5', $headersRange); $this->assertSame('C6:E6', $dataRange); $this->assertSame('C5:E6', $totalRange); $this->assertSame(['C', 'D', 'E'], $columns); }) ->generate( ['foo', 'bar', 'baz'], [ ['foo-1', 'bar-1', 'baz-1'] ], 'C5' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('C5:E5', $headersRange); $this->assertSame('C6:E7', $dataRange); $this->assertSame('C5:E7', $totalRange); $this->assertSame(['C', 'D', 'E'], $columns); }) ->generate( ['foo', 'bar', 'baz'], [ ['foo-1', 'bar-1', 'baz-1'], ['foo-2', 'bar-2', 'baz-2'] ], 'C5' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame('C5:E5', $headersRange); $this->assertSame('C6:E8', $dataRange); $this->assertSame('C5:E8', $totalRange); $this->assertSame(['one' => 'C', 'two' => 'D', 'three' => 'E'], $columns); }) ->generate( ['one' => 'foo', 'two' => 'bar', 'three' => 'baz'], [ ['foo-1', 'bar-1', 'baz-1'], ['foo-2', 'bar-2', 'baz-2'], ['foo-3', 'bar-3', 'baz-3'] ], 'C5' ); } /** * @return void */ public function test_generate_statistic_without_headers() { (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame(null, $dataRange); $this->assertSame(null, $totalRange); $this->assertSame([], $columns); }) ->generate( [], [ ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('A1:A1', $dataRange); $this->assertSame('A1:A1', $totalRange); $this->assertSame(['A'], $columns); }) ->generate( [], [ ['111'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('A1:A2', $dataRange); $this->assertSame('A1:A2', $totalRange); $this->assertSame(['A'], $columns); }) ->generate( [], [ ['foo-1'], ['foo-2'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('A1:A3', $dataRange); $this->assertSame('A1:A3', $totalRange); $this->assertSame(['A'], $columns); }) ->generate( [], [ ['foo-1'], ['foo-2'], ['foo-3'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('A1:B1', $dataRange); $this->assertSame('A1:B1', $totalRange); $this->assertSame(['A', 'B'], $columns); }) ->generate( [], [ ['foo-1', 'bar-1'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('A1:B2', $dataRange); $this->assertSame('A1:B2', $totalRange); $this->assertSame(['A', 'B'], $columns); }) ->generate( [], [ ['foo-1', 'bar-1'], ['foo-2', 'bar-2'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('A1:B3', $dataRange); $this->assertSame('A1:B3', $totalRange); $this->assertSame(['A', 'B'], $columns); }) ->generate( [], [ ['foo-1', 'bar-1'], ['foo-2', 'bar-2'], ['foo-3', 'bar-3'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('A1:C1', $dataRange); $this->assertSame('A1:C1', $totalRange); $this->assertSame(['A', 'B', 'C'], $columns); }) ->generate( [], [ ['foo-1', 'bar-1', 'baz-1'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('A1:C2', $dataRange); $this->assertSame('A1:C2', $totalRange); $this->assertSame(['A', 'B', 'C'], $columns); }) ->generate( [], [ ['foo-1', 'bar-1', 'baz-1'], ['foo-2', 'bar-2', 'baz-2'] ], ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('A1:C3', $dataRange); $this->assertSame('A1:C3', $totalRange); $this->assertSame(['A', 'B', 'C'], $columns); }) ->generate( [], [ ['foo-1', 'bar-1', 'baz-1'], ['foo-2', 'bar-2', 'baz-2'], ['foo-3', 'bar-3', 'baz-3'] ], ); } /** * @return void */ public function test_generate_statistic_without_headers_with_shift() { (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame(null, $dataRange); $this->assertSame(null, $totalRange); $this->assertSame([], $columns); }) ->generate( [], [ ], 'D4' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('D4:D4', $dataRange); $this->assertSame('D4:D4', $totalRange); $this->assertSame(['D'], $columns); }) ->generate( [], [ ['111'] ], 'D4' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('D4:D5', $dataRange); $this->assertSame('D4:D5', $totalRange); $this->assertSame(['D'], $columns); }) ->generate( [], [ ['foo-1'], ['foo-2'] ], 'D4' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('D4:D6', $dataRange); $this->assertSame('D4:D6', $totalRange); $this->assertSame(['D'], $columns); }) ->generate( [], [ ['foo-1'], ['foo-2'], ['foo-3'] ], 'D4' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('D4:E4', $dataRange); $this->assertSame('D4:E4', $totalRange); $this->assertSame(['D', 'E'], $columns); }) ->generate( [], [ ['foo-1', 'bar-1'] ], 'D4' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('D4:E5', $dataRange); $this->assertSame('D4:E5', $totalRange); $this->assertSame(['D', 'E'], $columns); }) ->generate( [], [ ['foo-1', 'bar-1'], ['foo-2', 'bar-2'] ], 'D4' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('D4:E6', $dataRange); $this->assertSame('D4:E6', $totalRange); $this->assertSame(['D', 'E'], $columns); }) ->generate( [], [ ['foo-1', 'bar-1'], ['foo-2', 'bar-2'], ['foo-3', 'bar-3'] ], 'D4' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('D4:F4', $dataRange); $this->assertSame('D4:F4', $totalRange); $this->assertSame(['D', 'E', 'F'], $columns); }) ->generate( [], [ ['foo-1', 'bar-1', 'baz-1'] ], 'D4' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('D4:F5', $dataRange); $this->assertSame('D4:F5', $totalRange); $this->assertSame(['D', 'E', 'F'], $columns); }) ->generate( [], [ ['foo-1', 'bar-1', 'baz-1'], ['foo-2', 'bar-2', 'baz-2'] ], 'D4' ); (new \AnourValar\Office\GridService($this->getDriver())) ->hookAfter(function ($driver, ?string $headersRange, ?string $dataRange, ?string $totalRange, array $columns) { $this->assertSame(null, $headersRange); $this->assertSame('D4:F6', $dataRange); $this->assertSame('D4:F6', $totalRange); $this->assertSame(['D', 'E', 'F'], $columns); }) ->generate( [], [ ['foo-1', 'bar-1', 'baz-1'], ['foo-2', 'bar-2', 'baz-2'], ['foo-3', 'bar-3', 'baz-3'] ], 'D4' ); } /** * @return \AnourValar\Office\Drivers\GridInterface * @psalm-suppress UnusedForeachValue */ protected function getDriver(): \AnourValar\Office\Drivers\GridInterface { return new class () implements \AnourValar\Office\Drivers\GridInterface { public function create(): self { return $this; } public function setGrid(iterable $data): self { foreach ($data as $item) { } return $this; } public function save(string $file, \AnourValar\Office\Format $format): void { } }; } } ================================================ FILE: tests/SheetsParserTest.php ================================================ service = new \AnourValar\Office\Sheets\Parser(); } /** * @return void */ public function test_collision_names() { $data = [ [ 'values' => [ 1 => [ 'A' => '[title]', ], 2 => [ 'A' => '[request.title]', ], 3 => [ 'A' => '[response.body]', ], 4 => [ 'A' => '[body]', ], 5 => [ 'A' => '[products.title.title]', ], ], 'data' => [ 'title' => 'foo', 'request' => [], 'response' => ['body' => 'bar'], 'body' => [], 'products' => [ 'title' => [111], ], ], ], [ 'values' => [ 1 => [ 'A' => '[title]', ], 2 => [ 'A' => '[request.title]', ], 3 => [ 'A' => '[response.body]', ], 4 => [ 'A' => '[body]', ], 5 => [ 'A' => '[products.title.title]', ], ], 'data' => [ 'title' => 'foo', 'request' => null, 'response' => ['body' => 'bar'], 'body' => null, 'products' => [ 'title' => [111], ], ], ], [ 'values' => [ 1 => [ 'A' => '[title]', ], 2 => [ 'A' => '[request.title]', ], 3 => [ 'A' => '[response.body]', ], 4 => [ 'A' => '[body]', ], 5 => [ 'A' => '[products.title.title]', ], ], 'data' => [ 'title' => 'foo', 'response' => ['body' => 'bar'], 'products' => [ 'title' => [111], ], ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => 'foo'], 2 => ['A' => null], 3 => ['A' => 'bar'], 4 => ['A' => null], 5 => ['A' => null], ], 'rows' => [], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_empty() { $data = [ [ 'values' => [ 1 => [ 'A' => '[foo] []', 'B' => '[]', 'C' => '[bar] []', 'D' => '[foo] [baz.]', 'E' => '[foo] [б]', 'F' => '[foo] [$]', 'G' => '[foo] [%]', 'H' => '[foo] [5]', 'K' => '[foo] [a]', ], ], 'data' => [ '' => 'NO', 'bar' => 'BAR', 'baz' => ['' => 'BAZ'], '5' => 'NO', 'б' => 'NO', '$' => 'NO', '%' => 'NO', 'a' => 'NO', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '[]', 'C' => 'BAR []', 'D' => 'BAZ', 'E' => '[б]', 'F' => '[$]', 'G' => '[%]', 'H' => '[5]', 'K' => '[a]', ], ], 'rows' => [], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_scalar() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => 'test [foo] test', 'E' => '[test10]', 'F' => '[test]', 'G' => '[test1]', 'H' => 'hello [$=foo]', 'I' => '[$= foo] world', 'J' => '[$! foo] test', 'K' => '[$! foo] test', 'L' => '[$= foo1] test', 'Q' => '=A1+B2+C3+D4+E5', 'X' => '= A1', 'W' => '=1', 'Y' => '=AB100 + [test]', ], 2 => [ 'A' => '[bar]', 'B' => 'test [bar] test', 'C' => 'bar', 'D' => '[a.b] -> [a.c] -> [a.d] -> [a.e.f]', 'H' => '[hello]', 'J' => '[world]', 'K' => '[k] [9] [9k] [k9]', 'Y' => null, ], '3' => [ 'D' => '[bar] [!bar]', 'Y' => null, ], '4' => [ 'D' => 'hello world', 'Y' => null, ], '5' => [ 'A' => 1, 'B' => 2, 'Y' => null, ], '6' => [ 'Q' => '=A1+B2+C3+D4+E5', 'Y' => null, ], ], 'data' => [ 'foo' => 'hello', 'bar' => 'world', 'a' => ['b' => '11', 'c' => '22', 'e' => ['f' => 'oops']], 'test10' => 12.5, 'test' => '3', 'test1' => 5, 'hello' => null, ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'hello', 'C' => 'test hello test', 'E' => 12.5, 'F' => '3', 'G' => 5, 'H' => 'hello', 'I' => 'world', 'J' => null, 'K' => null, 'L' => null, 'Q' => '=A1+B2+C3+D3+E4', 'Y' => '=AB99 + 3', ], 2 => [ 'A' => 'world', 'B' => 'test world test', 'D' => '11 -> 22 -> -> oops', 'H' => null, 'J' => null, 'K' => '[k] [9] [9k]', ], 5 => [ 'Q' => '=A1+B2+C3+D3+E4', ], ], 'rows' => [['action' => 'delete', 'row' => 3, 'qty' => 1]], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_not_scalar() { $data = [ [ 'values' => [ 1 => [ 'A' => '[foo]', 'B' => '[baz]', ], 2 => [ 'A' => 'hello [bar] world', 'B' => null, ], 3 => [ 'A' => '[test] [=foo]', 'B' => null, ], 4 => [ 'A' => '[test2] [=bar]', 'B' => null, ], 5 => [ 'A' => '[test2] [!foo]', 'B' => null, ], ], 'data' => [ 'foo' => function () { }, 'baz' => new \DateTime('2022-11-16'), 'test' => function () { }, 'test2' => function () { throw new \LogicException('oops'); }, ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => $item['data']['foo'], 'B' => $item['data']['baz'], ], 2 => [ 'A' => 'hello world', ], 3 => [ 'A' => $item['data']['test'], ], ], 'rows' => [ ['action' => 'delete', 'row' => 4, 'qty' => 1], ['action' => 'delete', 'row' => 4, 'qty' => 1], ], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } $this->assertSame( [ 'data' => [ 1 => [ 'A' => 'hello', ], ], 'rows' => [], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema([1 => ['A' => 'hello [world]']], ['world' => function () { }], [])->toArray() ); } /** * @return void */ public function test_schema_conditions1() { $data = [ [ 'values' => [ 1 => [ 'A' => 'AA [$= foo] [$= bar]', 'B' => 'BB [$= baz] [$= foo] [$= bar]', 'C' => 'CC [$= foo] [$= baz] [$= bar]', 'D' => 'DD [$= foo] [$= bar] [$= baz]', ], 2 => [ 'A' => 'AA [$! baz] [$! foobar]', 'B' => 'BB [$! foo] [$! baz] [$! foobar]', 'C' => 'CC [$! baz] [$! foo] [$! foobar]', 'D' => 'DD [$! baz] [$! foobar] [$! foo]', ], ], 'data' => [ 'foo' => 'foo', 'bar' => 'bar', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => 'AA', 'B' => null, 'C' => null, 'D' => null, ], 2 => [ 'A' => 'AA', 'B' => null, 'C' => null, 'D' => null, ], ], 'rows' => [], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_conditions2() { $data = [ [ 'values' => [ 1 => [ 'A' => '11 [= foo] [= bar]', ], 2 => [ 'A' => '22 [= baz] [= foo] [= bar]', ], 3 => [ 'A' => '33 [= foo] [= baz] [= bar]', ], 4 => [ 'A' => '44 [= foo] [= bar] [= baz]', ], 5 => [ 'A' => '55 [! baz] [! foobar]', ], 6 => [ 'A' => '66 [! foo] [! baz] [! foobar]', ], 7 => [ 'A' => '77 [! baz] [! foo] [! foobar]', ], 8 => [ 'A' => '88 [! baz] [! foobar] [! foo]', ], ], 'data' => [ 'foo' => 'foo', 'bar' => 'bar', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '11', ], 2 => [ 'A' => '55', ], ], 'rows' => [ ['action' => 'delete', 'row' => 2, 'qty' => 1], ['action' => 'delete', 'row' => 2, 'qty' => 1], ['action' => 'delete', 'row' => 2, 'qty' => 1], ['action' => 'delete', 'row' => 3, 'qty' => 1], ['action' => 'delete', 'row' => 3, 'qty' => 1], ['action' => 'delete', 'row' => 3, 'qty' => 1], ], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_zero_empty() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo] [=test]', 'C' => null, ], 2 => [ 'A' => 'foo', 'B' => '[foo] [= list]', 'C' => null, ], 3 => [ 'A' => 'bar [= bar]', 'B' => '[bar] 111', 'C' => null, ], 4 => [ 'A' => 'foo [= list.0]', 'B' => '[foo]', 'C' => null, ], 5 => [ 'A' => 'bar', 'B' => '[bar] 222 [=bar]', 'C' => '=A3+B5+C7', ], 6 => [ 'A' => 'foo [= list.0.c]', 'B' => '[foo] [!list]', 'C' => null, ], 7 => [ 'A' => 'bar', 'B' => '[bar] 333 [! list]', 'C' => null, ], ], 'data' => [ 'list' => [], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => 'bar', 'B' => 'test2 111', ], 2 => [ 'B' => 'test2 222', 'C' => '=A1+B2+C3', ], 3 => [ 'B' => 'test2 333', ], ], 'rows' => [ ['action' => 'delete', 'row' => 1, 'qty' => 1], ['action' => 'delete', 'row' => 1, 'qty' => 1], ['action' => 'delete', 'row' => 2, 'qty' => 1], ['action' => 'delete', 'row' => 3, 'qty' => 1], ], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "#$id" ); } } /** * @return void */ public function test_schema_list_zero() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo [= list.0.c]', 'B' => '[foo]', 'K' => null, ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list.0.c]', 'D' => '[list.0.d]', 'E' => '[foo] hello [list.0.c] -> [list.0.d] world [bar]', 'F' => 'bar', 'G' => '[bar]', 'I' => '=A1*B2*C3', 'K' => '=A2:A2', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'I' => '=A1*B2*C3', 'K' => null, ], ], 'data' => [ 'list' => [], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo] [=list_c.0]', 'K' => null, ], 2 => [ 'A' => 'foo', 'B' => '[foo] [!list_c]', 'C' => '[list_c.0]', 'D' => '[list_d.0]', 'E' => '[foo] hello [list_c.0] -> [list_d.0] world [bar]', 'F' => 'bar', 'G' => '[bar] [! list_c]', 'I' => '=A1*B2*C3', 'K' => '=A2:A2', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'I' => '=A1*B2*C3', 'K' => null, ], ], 'data' => [ 'list_c' => [], 'list_d' => [], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', 'C' => null, 'D' => null, 'E' => 'test1 hello -> world test2', 'G' => 'test2', 'I' => '=A1*B1*C2', 'K' => '=A1:A1', ], '2' => [ 'B' => 'test2', 'I' => '=A1*B1*C2', ], ], 'rows' => [ ['action' => 'delete', 'row' => 1, 'qty' => 1], ], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "#$id" ); } } /** * @return void */ public function test_schema_list_one() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list.0.c]', 'D' => '[list.0.d]', 'E' => '[foo] hello [list.0.c] -> [list.0.d] world [bar]', 'F' => 'bar', 'G' => '[bar]', 'H' => '=A1*B2*C3', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], 4 => [ 'A' => '[bar] [! list]', 'K' => null, ], ], 'data' => [ 'list' => [ ['c' => '11', 'd' => '12'] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list.c]', 'D' => '[list.d]', 'E' => '[foo] hello [list.c] -> [list.d] world [bar]', 'F' => 'bar', 'G' => '[bar]', 'H' => '=A1*B2*C3', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], 4 => [ 'A' => '[bar] [!list]', 'K' => null, ], ], 'data' => [ 'list' => [ ['c' => '11', 'd' => '12'] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list_c.0]', 'D' => '[list_d.0]', 'E' => '[foo] hello [list_c.0] -> [list_d.0] world [bar]', 'F' => 'bar', 'G' => '[bar]', 'H' => '=A1*B2*C3', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], 4 => [ 'A' => '[bar] [! list_c]', 'K' => null, ], ], 'data' => [ 'list_c' => ['11'], 'list_d' => ['12'], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list_c]', 'D' => '[list_d]', 'E' => '[foo] hello [list_c] -> [list_d] world [bar]', 'F' => 'bar', 'G' => '[bar]', 'H' => '=A1*B2*C3', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], 4 => [ 'A' => '[bar] [!list_c]', 'K' => null, ], ], 'data' => [ 'list_c' => ['11'], 'list_d' => ['12'], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', 'H' => '=A1*B2*C3', 'K' => '=A2:A2', ], 2 => [ 'B' => 'test1', 'C' => '11', 'D' => '12', 'E' => 'test1 hello 11 -> 12 world test2', 'G' => 'test2', 'H' => '=A1*B2*C3', ], 3 => [ 'B' => 'test2', 'H' => '=A1*B2*C3', 'K' => '=A2:A2', ], ], 'rows' => [['action' => 'delete', 'row' => 4, 'qty' => 1]], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_two() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list.c]', 'D' => '[list.d]', 'E' => '[foo] hello [list.c] -> [list.d] world [bar]', 'F' => 'bar', 'G' => '[bar]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], ], 'data' => [ 'list' => [ ['c' => '11', 'd' => '12'], ['c' => '21', 'd' => '22'] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list.c]', 'D' => '[list.d]', 'E' => '[foo] hello [list.*.c] -> [list.*.d] world [bar]', 'F' => 'bar', 'G' => '[bar]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], ], 'data' => [ 'list' => [ ['c' => '11', 'd' => '12'], ['c' => '21', 'd' => '22'] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list_c]', 'D' => '[list_d]', 'E' => '[foo] hello [list_c] -> [list_d] world [bar]', 'F' => 'bar', 'G' => '[bar]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], ], 'data' => [ 'list_c' => ['11', '21'], 'list_d' => ['12', '22'], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list_c.*]', 'D' => '[list_d.*]', 'E' => '[foo] hello [list_c.*] -> [list_d.*] world [bar]', 'F' => 'bar', 'G' => '[bar]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'H' => '=A1*B2*C3', 'J' => 'A1*B2*C3', 'K' => '=A2:A2', ], ], 'data' => [ 'list_c' => ['11', '21'], 'list_d' => ['12', '22'], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', 'H' => '=A1*B2*C4', 'K' => '=A2:A3', ], 2 => [ 'A' => 'foo', 'B' => 'test1', 'C' => '11', 'D' => '12', 'E' => 'test1 hello 11 -> 12 world test2', 'F' => 'bar', 'G' => 'test2', 'H' => '=A1*B2*C4', 'J' => 'A1*B2*C3', ], 3 => [ 'A' => 'foo', 'B' => 'test1', 'C' => '21', 'D' => '22', 'E' => 'test1 hello 21 -> 22 world test2', 'F' => 'bar', 'G' => 'test2', 'H' => '=A1*B3*C4', 'J' => 'A1*B2*C3', ], 4 => [ 'B' => 'test2', 'H' => '=A1*B2*C4', 'K' => '=A2:A3', ], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 1], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3'], ['from' => 'B2', 'to' => 'B3'], ['from' => 'C2', 'to' => 'C3'], ['from' => 'D2', 'to' => 'D3'], ['from' => 'E2', 'to' => 'E3'], ['from' => 'F2', 'to' => 'F3'], ['from' => 'G2', 'to' => 'G3'], ['from' => 'H2', 'to' => 'H3'], ['from' => 'I2', 'to' => 'I3'], ['from' => 'J2', 'to' => 'J3'], ['from' => 'K2', 'to' => 'K3'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_three() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list.c]', 'D' => '[list.d]', 'E' => '[foo] hello [list.c] -> [list.d] world [bar]', 'G' => 'bar', 'H' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], ], 'data' => [ 'list' => [ ['c' => 11, 'd' => 12.5], ['c' => '21', 'd' => '22'], ['c' => '31', 'd' => '32'] ], 'foo' => 'test1', 'bar' => 'test2', ], 'merge_cells' => ['E2:F2'], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list.*.c]', 'D' => '[list.*.d]', 'E' => '[foo] hello [list.*.c] -> [list.*.d] world [bar]', 'G' => 'bar', 'H' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], ], 'data' => [ 'list' => [ ['c' => 11, 'd' => 12.5], ['c' => '21', 'd' => '22'], ['c' => '31', 'd' => '32'] ], 'foo' => 'test1', 'bar' => 'test2', ], 'merge_cells' => ['E2:F2'], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list_c]', 'D' => '[list_d]', 'E' => '[foo] hello [list_c] -> [list_d] world [bar]', 'G' => 'bar', 'H' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], ], 'data' => [ 'list_c' => [11, '21', '31'], 'list_d' => [12.5, '22', '32'], 'foo' => 'test1', 'bar' => 'test2', ], 'merge_cells' => ['E2:F2'], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list_c.*]', 'D' => '[list_d.*]', 'E' => '[foo] hello [list_c.*] -> [list_d.*] world [bar]', 'G' => 'bar', 'H' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], ], 'data' => [ 'list_c' => [11, '21', '31'], 'list_d' => [12.5, '22', '32'], 'foo' => 'test1', 'bar' => 'test2', ], 'merge_cells' => ['E2:F2'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', 'I' => '=A2*C2+B1+D5+E$2', 'K' => '=A2:A4', ], 2 => [ 'A' => 'foo', 'B' => 'test1', 'C' => 11, 'D' => 12.5, 'E' => 'test1 hello 11 -> 12.5 world test2', 'G' => 'bar', 'H' => 'test2', 'I' => '=A2*C2+B1+D5+E$2', ], 3 => [ 'A' => 'foo', 'B' => 'test1', 'C' => '21', 'D' => '22', 'E' => 'test1 hello 21 -> 22 world test2', 'G' => 'bar', 'H' => 'test2', 'I' => '=A3*C3+B1+D5+E$2', ], 4 => [ 'A' => 'foo', 'B' => 'test1', 'C' => '31', 'D' => '32', 'E' => 'test1 hello 31 -> 32 world test2', 'G' => 'bar', 'H' => 'test2', 'I' => '=A4*C4+B1+D5+E$2', ], 5 => [ 'B' => 'test2', 'I' => '=A2*C2+B1+D5+E$2', 'K' => '=A2:A4', ], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3:A4'], ['from' => 'B2', 'to' => 'B3:B4'], ['from' => 'C2', 'to' => 'C3:C4'], ['from' => 'D2', 'to' => 'D3:D4'], ['from' => 'E2', 'to' => 'E3:E4'], ['from' => 'G2', 'to' => 'G3:G4'], ['from' => 'H2', 'to' => 'H3:H4'], ['from' => 'I2', 'to' => 'I3:I4'], ['from' => 'J2', 'to' => 'J3:J4'], ['from' => 'K2', 'to' => 'K3:K4'], ], 'merge_cells' => ['E3:F3', 'E4:F4'], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_three_limit() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'G' => null, ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list.0.c]', 'D' => '[list.0.d]', 'E' => '[foo] hello [list.0.c] -> [list.0.d] world [bar]', 'F' => 'bar', 'G' => '[bar]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'G' => null, ], ], 'data' => [ 'list' => [ ['c' => 11, 'd' => 12.5], ['c' => '21', 'd' => '22'], ['c' => '31', 'd' => '32'] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'G' => null, ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list_c.0]', 'D' => '[list_d.0]', 'E' => '[foo] hello [list_c.0] -> [list_d.0] world [bar]', 'F' => 'bar', 'G' => '[bar]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'G' => null, ], ], 'data' => [ 'list_c' => [11, '21', '31'], 'list_d' => [12.5, '22', '32'], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', ], 2 => [ 'B' => 'test1', 'C' => 11, 'D' => 12.5, 'E' => 'test1 hello 11 -> 12.5 world test2', 'G' => 'test2', ], 3 => [ 'B' => 'test2', ], ], 'rows' => [], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_four() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list.c]', 'D' => '[list.d]', 'E' => '[foo] hello [list.c] -> [list.d] world [bar]', 'G' => 'bar', 'H' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], ], 'data' => [ 'list' => [ ['c' => 11, 'd' => 12.5], ['c' => '21', 'd' => '22'], ['c' => '31', 'd' => '32'], ['c' => '41', 'd' => '42'] ], 'foo' => 'test1', 'bar' => 'test2', ], 'merge_cells' => ['E2:F2'], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list.*.c]', 'D' => '[list.*.d]', 'E' => '[foo] hello [list.*.c] -> [list.*.d] world [bar]', 'G' => 'bar', 'H' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], ], 'data' => [ 'list' => [ ['c' => 11, 'd' => 12.5], ['c' => '21', 'd' => '22'], ['c' => '31', 'd' => '32'], ['c' => '41', 'd' => '42'] ], 'foo' => 'test1', 'bar' => 'test2', ], 'merge_cells' => ['E2:F2'], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list_c]', 'D' => '[list_d]', 'E' => '[foo] hello [list_c] -> [list_d] world [bar]', 'G' => 'bar', 'H' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], ], 'data' => [ 'list_c' => [11, '21', '31', '41'], 'list_d' => [12.5, '22', '32', '42'], 'foo' => 'test1', 'bar' => 'test2', ], 'merge_cells' => ['E2:F2'], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[list_c.*]', 'D' => '[list_d.*]', 'E' => '[foo] hello [list_c.*] -> [list_d.*] world [bar]', 'G' => 'bar', 'H' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'I' => '=A2*C2+B1+D3+E$2', 'K' => '=A2:A2', ], ], 'data' => [ 'list_c' => [11, '21', '31', '41'], 'list_d' => [12.5, '22', '32', '42'], 'foo' => 'test1', 'bar' => 'test2', ], 'merge_cells' => ['E2:F2'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', 'I' => '=A2*C2+B1+D6+E$2', 'K' => '=A2:A5', ], 2 => [ 'A' => 'foo', 'B' => 'test1', 'C' => 11, 'D' => 12.5, 'E' => 'test1 hello 11 -> 12.5 world test2', 'G' => 'bar', 'H' => 'test2', 'I' => '=A2*C2+B1+D6+E$2', ], 3 => [ 'A' => 'foo', 'B' => 'test1', 'C' => '21', 'D' => '22', 'E' => 'test1 hello 21 -> 22 world test2', 'G' => 'bar', 'H' => 'test2', 'I' => '=A3*C3+B1+D6+E$2', ], 4 => [ 'A' => 'foo', 'B' => 'test1', 'C' => '31', 'D' => '32', 'E' => 'test1 hello 31 -> 32 world test2', 'G' => 'bar', 'H' => 'test2', 'I' => '=A4*C4+B1+D6+E$2', ], 5 => [ 'A' => 'foo', 'B' => 'test1', 'C' => '41', 'D' => '42', 'E' => 'test1 hello 41 -> 42 world test2', 'G' => 'bar', 'H' => 'test2', 'I' => '=A5*C5+B1+D6+E$2', ], 6 => [ 'B' => 'test2', 'I' => '=A2*C2+B1+D6+E$2', 'K' => '=A2:A5', ], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 3], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3:A5'], ['from' => 'B2', 'to' => 'B3:B5'], ['from' => 'C2', 'to' => 'C3:C5'], ['from' => 'D2', 'to' => 'D3:D5'], ['from' => 'E2', 'to' => 'E3:E5'], ['from' => 'G2', 'to' => 'G3:G5'], ['from' => 'H2', 'to' => 'H3:H5'], ['from' => 'I2', 'to' => 'I3:I5'], ['from' => 'J2', 'to' => 'J3:J5'], ['from' => 'K2', 'to' => 'K3:K5'], ], 'merge_cells' => ['E3:F3', 'E4:F4', 'E5:F5'], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_matrix_zero_empty() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.0.c] [= test]', ], 3 => [ 'A' => 'bar [= test]', 'B' => '[foo]', 'C' => null, ], 4 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ ['c' => []] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo [= test]', 'B' => '[foo]', 'C' => '[matrix.0]', ], 3 => [ 'A' => 'bar', 'B' => '[foo] [= test]', 'C' => null, ], 4 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ [[]] ], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', 'C' => '=SUM(A2:A2)', ], 2 => [ 'B' => 'test2', ], ], 'rows' => [ ['action' => 'delete', 'row' => 2, 'qty' => 1], ['action' => 'delete', 'row' => 2, 'qty' => 1], ], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "#$id" ); } } /** * @return void */ public function test_schema_matrix_zero() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.0.c]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ ['c' => []] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.c]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ ['c' => []] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.0]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ [[]] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ [[]] ], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', 'C' => '=SUM(A2:A2)', ], 2 => [ 'B' => 'test1', 'C' => null, ], '3' => [ 'B' => 'test2', ], ], 'rows' => [], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "#$id" ); } } /** * @return void */ public function test_schema_matrix_one() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.0.c.0]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ ['c' => ['11']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.*.c.*]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ ['c' => ['11']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.c]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ ['c' => ['11']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.*.c]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ ['c' => ['11']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.0.0.0]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ [['11']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.*.*.*]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ [['11']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ [['11']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', 'C' => '=SUM(A2:A2)', ], 2 => [ 'B' => 'test1', 'C' => '11', ], 3 => [ 'B' => 'test2', ], ], 'rows' => [], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_matrix_two() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.c]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ ['c' => ['11', '12']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.*.c.*]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ ['c' => ['11', '12']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.*.c]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ ['c' => ['11', '12']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ [['11', '12']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', 'C' => '=SUM(A2:A2)', ], 2 => [ 'B' => 'test1', 'C' => '11', 'D' => '12', ], 3 => [ 'B' => 'test2', ], ], 'rows' => [], 'copy_style' => [ ['from' => 'C2', 'to' => 'D2'], ], 'merge_cells' => [], 'copy_width' => [['from' => 'C', 'to' => 'D']], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_matrix_two_limit() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => null, ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.0.c.0]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ ['c' => ['11', '12']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => null, ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.0.0.0]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ [['11', '12']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', ], 2 => [ 'B' => 'test1', 'C' => '11', ], 3 => [ 'B' => 'test2', ], ], 'rows' => [], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_matrix_three() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.c]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ ['c' => ['11', '12', '13']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.*.c]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ ['c' => ['11', '12', '13']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix.*.c.*]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ ['c' => ['11', '12', '13']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', ], 2 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '[matrix]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => null, ], ], 'data' => [ 'matrix' => [ [['11', '12', '13']] ], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', 'C' => '=SUM(A2:A2)', ], 2 => [ 'B' => 'test1', 'C' => '11', 'D' => '12', 'E' => '13', ], 3 => [ 'B' => 'test2', ], ], 'rows' => [], 'copy_style' => [ ['from' => 'C2', 'to' => 'D2'], ['from' => 'C2', 'to' => 'E2'], ], 'merge_cells' => [], 'copy_width' => [ ['from' => 'C', 'to' => 'D'], ['from' => 'C', 'to' => 'E'], ], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_equal() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', 'D' => null, ], 2 => [ 'A' => 'foo', 'B' => '[matrix.b]', 'C' => '[matrix.c]', 'D' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => 'foo: [foo] bar: [bar]', 'D' => 'bar: [bar] foo: [foo]', ], ], 'data' => [ 'matrix' => [ ['b' => 'b1', 'c' => ['c1', 'd1', 'e1']], ['b' => 'b2', 'c' => ['c2', 'd2', 'e2']], ['b' => 'b3', 'c' => ['c3', 'd3', 'e3']], ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', 'D' => null, ], 2 => [ 'A' => 'foo', 'B' => '[matrix.*.b]', 'C' => '[matrix.*.c]', 'D' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => 'foo: [foo] bar: [bar]', 'D' => 'bar: [bar] foo: [foo]', ], ], 'data' => [ 'matrix' => [ ['b' => 'b1', 'c' => ['c1', 'd1', 'e1']], ['b' => 'b2', 'c' => ['c2', 'd2', 'e2']], ['b' => 'b3', 'c' => ['c3', 'd3', 'e3']], ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'C' => '=SUM(A2:A2)', 'D' => null, ], 2 => [ 'A' => 'foo', 'B' => '[matrix.*.b]', 'C' => '[matrix.*.c]', 'D' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => 'foo: [foo] bar: [bar]', 'D' => 'bar: [bar] foo: [foo]', ], ], 'data' => [ 'matrix' => [ ['b' => 'b1', 'c' => ['c1', 'd1', 'e1']], ['b' => 'b2', 'c' => ['c2', 'd2', 'e2']], ['b' => 'b3', 'c' => ['c3', 'd3', 'e3']], ], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', 'C' => '=SUM(A2:A4)', ], 2 => [ 'A' => 'foo', 'B' => 'b1', 'C' => 'c1', 'D' => 'd1', 'E' => 'e1', ], 3 => [ 'A' => 'foo', 'B' => 'b2', 'C' => 'c2', 'D' => 'd2', 'E' => 'e2', ], 4 => [ 'A' => 'foo', 'B' => 'b3', 'C' => 'c3', 'D' => 'd3', 'E' => 'e3', ], 5 => [ 'B' => 'test2', 'C' => 'foo: test1 bar: test2', 'D' => 'bar: test2 foo: test1', ], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3:A4'], ['from' => 'B2', 'to' => 'B3:B4'], ['from' => 'C2', 'to' => 'C3:C4'], ['from' => 'C2', 'to' => 'D2'], ['from' => 'C2', 'to' => 'E2'], ['from' => 'D2', 'to' => 'D3:D4'], ['from' => 'E2', 'to' => 'E3:E4'], ], 'merge_cells' => [], 'copy_width' => [ ['from' => 'C', 'to' => 'D'], ['from' => 'C', 'to' => 'E'], ], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_equal_limit() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'D' => null, ], 2 => [ 'A' => 'foo', 'B' => '[matrix.0.b]', 'C' => '[matrix.0.c.0]', 'D' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => 'foo: [foo] bar: [bar]', 'D' => 'bar: [bar] foo: [foo]', ], ], 'data' => [ 'matrix' => [ ['b' => 'b1', 'c' => ['c1', 'd1', 'e1']], ['b' => 'b2', 'c' => ['c2', 'd2', 'e2']], ['b' => 'b3', 'c' => ['c3', 'd3', 'e3']], ], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', ], 2 => [ 'B' => 'b1', 'C' => 'c1', ], 3 => [ 'B' => 'test2', 'C' => 'foo: test1 bar: test2', 'D' => 'bar: test2 foo: test1', ], ], 'rows' => [], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_irr() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'D' => null, ], 2 => [ 'A' => 'foo', 'B' => '[matrix.0.b] [= matrix.0.b]', 'C' => '[matrix.0.c.0] [=matrix.c]', 'D' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => 'foo: [foo] bar: [bar]', 'D' => 'bar: [bar] foo: [foo]', ], ], 'data' => [ 'matrix' => [ ['b' => 'b1', 'c' => ['c1', 'd1', 'e1']], ['b' => 'b2', 'c' => ['c2', 'd2']], ['c' => ['c3', 'd3', 'e3']], ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'D' => null, ], 2 => [ 'A' => 'foo', 'B' => '[matrix.b]', 'C' => '[matrix.c]', 'D' => null, ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'C' => 'foo: [foo] bar: [bar]', 'D' => 'bar: [bar] foo: [foo]', ], ], 'data' => [ 'matrix' => [ ['b' => 'b1', 'c' => ['c1', 'd1', 'e1']], ['b' => 'b2', 'c' => ['c2', 'd2']], ['c' => ['c3', 'd3', 'e3']], ], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', ], 2 => [ 'A' => 'foo', 'B' => 'b1', 'C' => 'c1', 'D' => 'd1', 'E' => 'e1', ], 3 => [ 'A' => 'foo', 'B' => 'b2', 'C' => 'c2', 'D' => 'd2', 'E' => null, ], 4 => [ 'A' => 'foo', 'B' => null, 'C' => 'c3', 'D' => 'd3', 'E' => 'e3', ], 5 => [ 'B' => 'test2', 'C' => 'foo: test1 bar: test2', 'D' => 'bar: test2 foo: test1', ], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3:A4'], ['from' => 'B2', 'to' => 'B3:B4'], ['from' => 'C2', 'to' => 'C3:C4'], ['from' => 'C2', 'to' => 'D2'], ['from' => 'C2', 'to' => 'E2'], ['from' => 'D2', 'to' => 'D3:D4'], ['from' => 'E2', 'to' => 'E3:E4'], ], 'merge_cells' => [], 'copy_width' => [ ['from' => 'C', 'to' => 'D'], ['from' => 'C', 'to' => 'E'], ], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_combination1() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'E' => null, ], 2 => [ 'A' => 'foo', 'B' => '[rows.b]', 'C' => '[rows.c]', 'E' => '[columns.e]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'E' => null, ], ], 'data' => [ 'rows' => [ ['b' => 'b1', 'c' => 'c1'], ['b' => 'b2', 'c' => 'c2'], ], 'columns' => [ ['e' => ['E10', 'E11', '']], ['e' => ['E20', 'E21', 'E22']], ['e' => ['E30', 'E31', 'E32']], ], 'foo' => 'test1', 'bar' => 'test2', ], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'E' => null, ], 2 => [ 'A' => 'foo', 'B' => '[rows.*.b]', 'C' => '[rows.*.c]', 'E' => '[columns.*.e.*]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'E' => null, ], ], 'data' => [ 'rows' => [ ['b' => 'b1', 'c' => 'c1'], ['b' => 'b2', 'c' => 'c2'], ], 'columns' => [ ['e' => ['E10', 'E11', '']], ['e' => ['E20', 'E21', 'E22']], ['e' => ['E30', 'E31', 'E32']], ], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', ], 2 => [ 'A' => 'foo', 'B' => 'b1', 'C' => 'c1', 'E' => 'E10', 'F' => 'E11', 'G' => null, ], 3 => [ 'A' => 'foo', 'B' => 'b2', 'C' => 'c2', 'E' => 'E20', 'F' => 'E21', 'G' => 'E22', ], 4 => [ 'A' => 'foo', 'B' => null, 'C' => null, 'E' => 'E30', 'F' => 'E31', 'G' => 'E32', ], 5 => [ 'B' => 'test2', ], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3:A4'], ['from' => 'B2', 'to' => 'B3:B4'], ['from' => 'C2', 'to' => 'C3:C4'], ['from' => 'D2', 'to' => 'D3:D4'], ['from' => 'E2', 'to' => 'E3:E4'], ['from' => 'E2', 'to' => 'F2'], ['from' => 'E2', 'to' => 'G2'], ['from' => 'F2', 'to' => 'F3:F4'], ['from' => 'G2', 'to' => 'G3:G4'], ], 'merge_cells' => [], 'copy_width' => [ ['from' => 'E', 'to' => 'F'], ['from' => 'E', 'to' => 'G'], ], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_combination1_limit() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'E' => null, ], 2 => [ 'A' => 'foo', 'B' => '[rows.0.b]', 'C' => '[rows.0.c]', 'E' => '[columns.0.e.0]', ], 3 => [ 'A' => 'bar', 'B' => '[bar]', 'E' => null, ], ], 'data' => [ 'rows' => [ ['b' => 'b1', 'c' => 'c1'], ['b' => 'b2', 'c' => 'c2'], ], 'columns' => [ ['e' => ['E10', 'E11', '']], ['e' => ['E20', 'E21', 'E22']], ['e' => ['E30', 'E31', 'E32']], ], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', ], 2 => [ 'B' => 'b1', 'C' => 'c1', 'E' => 'E10', ], 3 => [ 'B' => 'test2', ], ], 'rows' => [], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_combination2() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'E' => '[columns.one]', ], 2 => [ 'A' => 'foo', 'B' => '[rows.b]', 'C' => '[rows.c]', 'E' => '[columns.two]', ], 3 => [ 'A' => '[foo]', 'E' => null, ], ], 'data' => [ 'rows' => [ ['b' => 'b1', 'c' => 'c1'], ['b' => 'b2', 'c' => 'c2'], ], 'columns' => [ [ 'one' => ['01', '02', '03'], 'two' => [15000, 20000, 30000], ], ], 'foo' => 'test1', 'bar' => 'test2', ], 'merge_cells' => ['C2:D2'], ], [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'E' => '[columns.*.one]', ], 2 => [ 'A' => 'foo', 'B' => '[rows.b]', 'C' => '[rows.c]', 'E' => '[columns.*.two.*]', ], 3 => [ 'A' => '[foo]', 'E' => null, ], ], 'data' => [ 'rows' => [ ['b' => 'b1', 'c' => 'c1'], ['b' => 'b2', 'c' => 'c2'], ], 'columns' => [ [ 'one' => ['01', '02', '03'], 'two' => [15000, 20000, 30000], ], ], 'foo' => 'test1', 'bar' => 'test2', ], 'merge_cells' => ['C2:D2'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', 'E' => '01', 'F' => '02', 'G' => '03', ], 2 => [ 'A' => 'foo', 'B' => 'b1', 'C' => 'c1', 'E' => 15000, 'F' => 20000, 'G' => 30000, ], 3 => [ 'A' => 'foo', 'B' => 'b2', 'C' => 'c2', 'E' => null, 'F' => null, 'G' => null, ], 4 => [ 'A' => 'test1', ], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 1], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3'], ['from' => 'B2', 'to' => 'B3'], ['from' => 'C2', 'to' => 'C3'], ['from' => 'E1', 'to' => 'F1'], ['from' => 'E1', 'to' => 'G1'], ['from' => 'E2', 'to' => 'E3'], ['from' => 'E2', 'to' => 'F2'], ['from' => 'E2', 'to' => 'G2'], ['from' => 'F2', 'to' => 'F3'], ['from' => 'G2', 'to' => 'G3'], ], 'merge_cells' => ['C3:D3'], 'copy_width' => [ ['from' => 'E', 'to' => 'F'], ['from' => 'E', 'to' => 'G'], ], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_combination2_limit() { $data = [ [ 'values' => [ 1 => [ 'A' => 'foo', 'B' => '[foo]', 'E' => '[columns.*.one.*]', ], 2 => [ 'A' => 'foo', 'B' => '[rows.1.b]', 'C' => '[rows.1.c]', 'E' => '[columns.*.two.*]', ], 3 => [ 'A' => '[foo]', 'E' => null, ], ], 'data' => [ 'rows' => [ ['b' => 'b1', 'c' => 'c1'], ['b' => 'b2', 'c' => 'c2'], ], 'columns' => [ [ 'one' => ['01', '02', '03'], 'two' => [15000, 20000, 30000], ], ], 'foo' => 'test1', 'bar' => 'test2', ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'test1', 'E' => '01', 'F' => '02', 'G' => '03', ], 2 => [ 'B' => 'b2', 'C' => 'c2', 'E' => 15000, 'F' => 20000, 'G' => 30000, ], 3 => [ 'A' => 'test1', ], ], 'rows' => [], 'copy_style' => [ ['from' => 'E1', 'to' => 'F1'], ['from' => 'E1', 'to' => 'G1'], ['from' => 'E2', 'to' => 'F2'], ['from' => 'E2', 'to' => 'G2'], ], 'merge_cells' => [], 'copy_width' => [ ['from' => 'E', 'to' => 'F'], ['from' => 'E', 'to' => 'G'], ], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_combination3() { $data = [ [ 'values' => [ 1 => [ 'B' => '[title] [=column.month]', 'C' => '=D6 [oops]', 'D' => 'D6', 'Q' => null, ], 3 => [ 'G' => '[column.month] [= column.month]', 'Q' => null, ], 4 => [ 'A' => '[list.name]', 'B' => '[list.count]', 'C' => 'kg', 'D' => '[list.price]', 'E' => '[comment]', 'G' => '[column.amount]', 'Q' => '=A1+B3+C4+D6', ], 6 => [ 'B' => '[total.count]', 'Q' => null, ], 7 => [ 'C' => '=D6 [oops]', 'D' => 'D6', 'Q' => null, ], ], 'data' => [ 'title' => 'foo', 'total' => ['count' => 3], 'comment' => 'bar', 'list' => [ ['name' => 'Product 1', 'count' => 2, 'price' => 753.14], ['name' => 'Product 2', 'count' => 1, 'price' => 123], ], 'column' => [ [ 'month' => ['01', '02', '03'], 'amount' => [15000, 20000, 30000], ], ], ], 'merge_cells' => ['G4:H4'], ], [ 'values' => [ 1 => [ 'B' => '[title] [=column.*.month]', 'C' => '=D6', 'D' => 'D6', 'Q' => null, ], 3 => [ 'G' => '[column.*.month] [= column.*.month.*]', 'Q' => null, ], 4 => [ 'A' => '[list.*.name]', 'B' => '[list.*.count]', 'C' => 'kg', 'D' => '[list.price]', 'E' => '[comment]', 'G' => '[column.*.amount.*]', 'Q' => '=A1+B3+C4+D6', ], 6 => [ 'B' => '[total.count]', 'Q' => null, ], 7 => [ 'C' => '=D6 [oops]', 'D' => 'D6', 'Q' => null, ], ], 'data' => [ 'title' => 'foo', 'total' => ['count' => 3], 'comment' => 'bar', 'list' => [ ['name' => 'Product 1', 'count' => 2, 'price' => 753.14], ['name' => 'Product 2', 'count' => 1, 'price' => 123], ], 'column' => [ [ 'month' => ['01', '02', '03'], 'amount' => [15000, 20000, 30000], ], ], ], 'merge_cells' => ['G4:H4'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'foo', 'C' => '=D7', ], 3 => [ 'G' => '01', 'H' => '02', 'I' => '03', ], 4 => [ 'A' => 'Product 1', 'B' => 2, 'C' => 'kg', 'D' => 753.14, 'E' => 'bar', 'G' => 15000, 'I' => 20000, 'K' => 30000, 'Q' => '=A1+B3+C4+D7', ], 5 => [ 'A' => 'Product 2', 'B' => 1, 'C' => 'kg', 'D' => 123, 'E' => 'bar', 'G' => null, 'I' => null, 'K' => null, 'Q' => '=A1+B3+C5+D7', ], 7 => [ 'B' => 3, ], 8 => [ 'C' => '=D7', ], ], 'rows' => [ ['action' => 'add', 'row' => 5, 'qty' => 1], ], 'copy_style' => [ ['from' => 'A4', 'to' => 'A5'], ['from' => 'B4', 'to' => 'B5'], ['from' => 'C4', 'to' => 'C5'], ['from' => 'D4', 'to' => 'D5'], ['from' => 'E4', 'to' => 'E5'], ['from' => 'F4', 'to' => 'F5'], ['from' => 'G3', 'to' => 'H3'], ['from' => 'G3', 'to' => 'I3'], ['from' => 'G4', 'to' => 'G5'], ['from' => 'G4', 'to' => 'I4'], ['from' => 'G4', 'to' => 'K4'], ['from' => 'I4', 'to' => 'I5'], ['from' => 'K4', 'to' => 'K5'], ['from' => 'M4', 'to' => 'M5'], ['from' => 'N4', 'to' => 'N5'], ['from' => 'O4', 'to' => 'O5'], ['from' => 'P4', 'to' => 'P5'], ['from' => 'Q4', 'to' => 'Q5'], ], 'merge_cells' => [ 'I4:J4', 'K4:L4', 'G5:H5', 'I5:J5', 'K5:L5', ], 'copy_width' => [ ['from' => 'G', 'to' => 'H'], ['from' => 'G', 'to' => 'I'], ['from' => 'G', 'to' => 'K'], ['from' => 'H', 'to' => 'J'], ['from' => 'H', 'to' => 'L'], ], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_combination3_limit() { $data = [ [ 'values' => [ 1 => [ 'B' => '[title]', 'G' => null, ], 3 => [ 'G' => '[column.*.month.*] [= column.0.month]', ], 4 => [ 'A' => '[list.0.name] [=column.0.month.0]', 'B' => '[list.0.count]', 'C' => 'kg', 'D' => '[list.0.price]', 'E' => '[comment]', 'G' => '[column.0.amount.0]', ], 6 => [ 'B' => '[total.count]', 'G' => null, ], ], 'data' => [ 'title' => 'foo', 'total' => ['count' => 3], 'comment' => 'bar', 'list' => [ ['name' => 'Product 1', 'count' => 2, 'price' => 753.14], ['name' => 'Product 2', 'count' => 1, 'price' => 123], ], 'column' => [ [ 'month' => ['01', '02', '03'], 'amount' => [15000, 20000, 30000], ], ], ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'foo', ], 3 => [ 'G' => '01', 'H' => '02', 'I' => '03', ], 4 => [ 'A' => 'Product 1', 'B' => 2, 'D' => 753.14, 'E' => 'bar', 'G' => 15000, ], 6 => [ 'B' => 3, ], ], 'rows' => [], 'copy_style' => [ ['from' => 'G3', 'to' => 'H3'], ['from' => 'G3', 'to' => 'I3'], ], 'merge_cells' => [], 'copy_width' => [ ['from' => 'G', 'to' => 'H'], ['from' => 'G', 'to' => 'I'], ], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_table_with_formula1() { $data = [ [ 'values' => [ 1 => [ 'A' => '=A4', 'B' => '=A3', 'C' => '=A2', 'D' => '=A1', 'E' => '=A2:A2', 'F' => '=B2:G2', ], 2 => [ 'A' => '[table.price]', 'B' => '[table.count]', 'C' => '=A2*B2', 'D' => '=A1+A3+A4', 'E' => 'A1+A3+A4', 'F' => null, ], 3 => [ 'A' => '=A4', 'B' => '=A3', 'C' => '=A2', 'D' => '=A1', 'E' => '=A2:A2', 'F' => '=B2:G2', ], ], 'data' => [ 'table' => [ ['price' => 11, 'count' => 1], ['price' => 12, 'count' => 2], ['price' => 13, 'count' => 3], ['price' => 14, 'count' => 4], ], ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '=A7', 'B' => '=A6', 'C' => '=A2', 'D' => '=A1', 'E' => '=A2:A5', 'F' => '=B2:G5', ], 2 => [ 'A' => 11, 'B' => 1, 'C' => '=A2*B2', 'D' => '=A1+A6+A7', 'E' => 'A1+A3+A4', ], 3 => [ 'A' => 12, 'B' => 2, 'C' => '=A3*B3', 'D' => '=A1+A6+A7', 'E' => 'A1+A3+A4', ], 4 => [ 'A' => 13, 'B' => 3, 'C' => '=A4*B4', 'D' => '=A1+A6+A7', 'E' => 'A1+A3+A4', ], 5 => [ 'A' => 14, 'B' => 4, 'C' => '=A5*B5', 'D' => '=A1+A6+A7', 'E' => 'A1+A3+A4', ], 6 => [ 'A' => '=A7', 'B' => '=A6', 'C' => '=A2', 'D' => '=A1', 'E' => '=A2:A5', 'F' => '=B2:G5', ], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 3], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3:A5'], ['from' => 'B2', 'to' => 'B3:B5'], ['from' => 'C2', 'to' => 'C3:C5'], ['from' => 'D2', 'to' => 'D3:D5'], ['from' => 'E2', 'to' => 'E3:E5'], ['from' => 'F2', 'to' => 'F3:F5'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_table_with_formula2() { $data = [ [ 'values' => [ 1 => [ 'A' => '=A1', 'B' => '=A2', 'C' => '=A3', 'D' => '=A4', 'E' => '=A5', 'F' => '=A6', 'G' => '=A7', 'H' => '=A8', 'J' => '=A3:A3', 'K' => '=A3:C3', ], 2 => [ 'A' => '[= hello]', 'K' => null, ], 3 => [ 'A' => '[table.price]', 'B' => '[table.count]', 'C' => '=A3*B3', 'K' => null, ], 4 => [ 'A' => '[=hello]', 'K' => null, ], 5 => [ 'A' => '[!table]', 'K' => null, ], 6 => [ 'A' => '[!table.count]', 'K' => null, ], 7 => [ 'A' => '=A1', 'B' => '=A2', 'C' => '=A3', 'D' => '=A4', 'E' => '=A5', 'F' => '=A6', 'G' => '=A7', 'H' => '=A8', 'J' => '=A3:A3', 'K' => '=A3:C3', ], ], 'data' => [ 'table' => [ ['price' => 11, 'count' => 1], ['price' => 12, 'count' => 2], ['price' => 13, 'count' => 3], ['price' => 14, 'count' => 4], ['price' => 15, 'count' => 5], ], ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '=A1', 'B' => '=A2', 'C' => '=A2', 'D' => '=A7', 'E' => '=A7', 'F' => '=A7', 'G' => '=A7', 'H' => '=A8', 'J' => '=A2:A6', 'K' => '=A2:C6', ], 2 => [ 'A' => 11, 'B' => 1, 'C' => '=A2*B2', ], 3 => [ 'A' => 12, 'B' => 2, 'C' => '=A3*B3', ], 4 => [ 'A' => 13, 'B' => 3, 'C' => '=A4*B4', ], 5 => [ 'A' => 14, 'B' => 4, 'C' => '=A5*B5', ], 6 => [ 'A' => 15, 'B' => 5, 'C' => '=A6*B6', ], 7 => [ 'A' => '=A1', 'B' => '=A2', 'C' => '=A2', 'D' => '=A7', 'E' => '=A7', 'F' => '=A7', 'G' => '=A7', 'H' => '=A8', 'J' => '=A2:A6', 'K' => '=A2:C6', ], ], 'rows' => [ ['action' => 'delete', 'row' => 2, 'qty' => 1], ['action' => 'add', 'row' => 3, 'qty' => 4], ['action' => 'delete', 'row' => 7, 'qty' => 1], ['action' => 'delete', 'row' => 7, 'qty' => 1], ['action' => 'delete', 'row' => 7, 'qty' => 1], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3:A6'], ['from' => 'B2', 'to' => 'B3:B6'], ['from' => 'C2', 'to' => 'C3:C6'], ['from' => 'D2', 'to' => 'D3:D6'], ['from' => 'E2', 'to' => 'E3:E6'], ['from' => 'F2', 'to' => 'F3:F6'], ['from' => 'G2', 'to' => 'G3:G6'], ['from' => 'H2', 'to' => 'H3:H6'], ['from' => 'I2', 'to' => 'I3:I6'], ['from' => 'J2', 'to' => 'J3:J6'], ['from' => 'K2', 'to' => 'K3:K6'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_table_with_formula3() { $data = [ [ 'values' => [ 1 => [ 'A' => '=AA1', 'B' => '=BB2', 'C' => '=CC3', 'D' => '=DD4', 'E' => '=EE5', 'F' => '=FF6', 'G' => '=A3:A3', ], 2 => [ 'A' => '[= hello]', 'G' => null, ], 3 => [ 'A' => '[table.price]', 'B' => '[table.count]', 'C' => '=A3*B3+C1+D2+E4+F5', 'G' => null, ], 4 => [ 'A' => '=AA1', 'B' => '=BB2', 'C' => '=CC3', 'D' => '=DD4', 'E' => '=EE5', 'F' => '=FF6', 'G' => '=A3:A3', ], ], 'data' => [ 'table' => [ ['price' => 11, 'count' => 1], ['price' => 12, 'count' => 2], ['price' => 13, 'count' => 3], ['price' => 14, 'count' => 4], ['price' => 15, 'count' => 5], ], ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '=AA1', 'B' => '=BB2', 'C' => '=CC2', 'D' => '=DD7', 'E' => '=EE8', 'F' => '=FF9', 'G' => '=A2:A6', ], 2 => [ 'A' => 11, 'B' => 1, 'C' => '=A2*B2+C1+D2+E7+F8', ], 3 => [ 'A' => 12, 'B' => 2, 'C' => '=A3*B3+C1+D3+E7+F8', ], 4 => [ 'A' => 13, 'B' => 3, 'C' => '=A4*B4+C1+D4+E7+F8', ], 5 => [ 'A' => 14, 'B' => 4, 'C' => '=A5*B5+C1+D5+E7+F8', ], 6 => [ 'A' => 15, 'B' => 5, 'C' => '=A6*B6+C1+D6+E7+F8', ], 7 => [ 'A' => '=AA1', 'B' => '=BB2', 'C' => '=CC2', 'D' => '=DD7', 'E' => '=EE8', 'F' => '=FF9', 'G' => '=A2:A6', ], ], 'rows' => [ ['action' => 'delete', 'row' => 2, 'qty' => 1], ['action' => 'add', 'row' => 3, 'qty' => 4], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3:A6'], ['from' => 'B2', 'to' => 'B3:B6'], ['from' => 'C2', 'to' => 'C3:C6'], ['from' => 'D2', 'to' => 'D3:D6'], ['from' => 'E2', 'to' => 'E3:E6'], ['from' => 'F2', 'to' => 'F3:F6'], ['from' => 'G2', 'to' => 'G3:G6'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_matrix_merge_1x1() { $data = [ [ 'values' => [ 1 => [ 'A' => '[foo]', ], 2 => [ 'A' => '[matrix]', ], 3 => [ 'A' => '[bar]', ], ], 'data' => [ 'foo' => 'test1', 'bar' => 'test2', 'matrix' => [ [['one', 'two', 'three', 'four']], ], ], 'merge_cells' => [], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => 'test1', ], 2 => [ 'A' => 'one', 'B' => 'two', 'C' => 'three', 'D' => 'four', ], 3 => [ 'A' => 'test2', ], ], 'rows' => [], 'copy_style' => [ ['from' => 'A2', 'to' => 'B2'], ['from' => 'A2', 'to' => 'C2'], ['from' => 'A2', 'to' => 'D2'], ], 'merge_cells' => [], 'copy_width' => [ ['from' => 'A', 'to' => 'B'], ['from' => 'A', 'to' => 'C'], ['from' => 'A', 'to' => 'D'], ], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_matrix_merge_1x2() { $data = [ [ 'values' => [ 1 => [ 'A' => '[foo]', ], 2 => [ 'A' => '[matrix]', ], 3 => [ 'A' => '[bar]', ], ], 'data' => [ 'foo' => 'test1', 'bar' => 'test2', 'matrix' => [ [['one', 'two', 'three', 'four']], ], ], 'merge_cells' => ['A2:B2'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => 'test1', ], 2 => [ 'A' => 'one', 'C' => 'two', 'E' => 'three', 'G' => 'four', ], 3 => [ 'A' => 'test2', ], ], 'rows' => [], 'copy_style' => [ ['from' => 'A2', 'to' => 'C2'], ['from' => 'A2', 'to' => 'E2'], ['from' => 'A2', 'to' => 'G2'], ], 'merge_cells' => ['C2:D2', 'E2:F2', 'G2:H2'], 'copy_width' => [ ['from' => 'A', 'to' => 'C'], ['from' => 'A', 'to' => 'E'], ['from' => 'A', 'to' => 'G'], ['from' => 'B', 'to' => 'D'], ['from' => 'B', 'to' => 'F'], ['from' => 'B', 'to' => 'H'], ], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_matrix_merge_1x3() { $data = [ [ 'values' => [ 1 => [ 'A' => '[foo]', ], 2 => [ 'A' => '[matrix]', ], 3 => [ 'A' => '[bar]', ], ], 'data' => [ 'foo' => 'test1', 'bar' => 'test2', 'matrix' => [ [['one', 'two', 'three', 'four']], ], ], 'merge_cells' => ['A2:C2'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => 'test1', ], 2 => [ 'A' => 'one', 'D' => 'two', 'G' => 'three', 'J' => 'four', ], 3 => [ 'A' => 'test2', ], ], 'rows' => [], 'copy_style' => [ ['from' => 'A2', 'to' => 'D2'], ['from' => 'A2', 'to' => 'G2'], ['from' => 'A2', 'to' => 'J2'], ], 'merge_cells' => ['D2:F2', 'G2:I2', 'J2:L2'], 'copy_width' => [ ['from' => 'A', 'to' => 'D'], ['from' => 'A', 'to' => 'G'], ['from' => 'A', 'to' => 'J'], ['from' => 'B', 'to' => 'E'], ['from' => 'B', 'to' => 'H'], ['from' => 'B', 'to' => 'K'], ['from' => 'C', 'to' => 'F'], ['from' => 'C', 'to' => 'I'], ['from' => 'C', 'to' => 'L'], ], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_matrix_merge_3x1() { $data = [ [ 'values' => [ 1 => [ 'A' => '[foo]', ], 2 => [ 'A' => '[matrix]', ], 3 => [ 'A' => '[bar]', ], ], 'data' => [ 'foo' => 'test1', 'bar' => 'test2', 'matrix' => [ [['one', 'two', 'three', 'four']], [['one-b', 'two-b', 'three-b', 'four-b']], [['one-c', 'two-c', 'three-c', 'four-c']], ], ], 'merge_cells' => [], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => 'test1', ], 2 => [ 'A' => 'one', 'B' => 'two', 'C' => 'three', 'D' => 'four', ], 3 => [ 'A' => 'one-b', 'B' => 'two-b', 'C' => 'three-b', 'D' => 'four-b', ], 4 => [ 'A' => 'one-c', 'B' => 'two-c', 'C' => 'three-c', 'D' => 'four-c', ], 5 => [ 'A' => 'test2', ], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3:A4'], ['from' => 'A2', 'to' => 'B2'], ['from' => 'A2', 'to' => 'C2'], ['from' => 'A2', 'to' => 'D2'], ['from' => 'B2', 'to' => 'B3:B4'], ['from' => 'C2', 'to' => 'C3:C4'], ['from' => 'D2', 'to' => 'D3:D4'], ], 'merge_cells' => [], 'copy_width' => [ ['from' => 'A', 'to' => 'B'], ['from' => 'A', 'to' => 'C'], ['from' => 'A', 'to' => 'D'], ], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_matrix_merge_3x2() { $data = [ [ 'values' => [ 1 => [ 'A' => '[foo]', ], 2 => [ 'A' => '[matrix]', ], 3 => [ 'A' => '[bar]', ], ], 'data' => [ 'foo' => 'test1', 'bar' => 'test2', 'matrix' => [ [['one', 'two', 'three', 'four']], [['one-b', 'two-b', 'three-b', 'four-b']], [['one-c', 'two-c', 'three-c', 'four-c']], ], ], 'merge_cells' => ['A2:B2'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => 'test1', ], 2 => [ 'A' => 'one', 'C' => 'two', 'E' => 'three', 'G' => 'four', ], 3 => [ 'A' => 'one-b', 'C' => 'two-b', 'E' => 'three-b', 'G' => 'four-b', ], 4 => [ 'A' => 'one-c', 'C' => 'two-c', 'E' => 'three-c', 'G' => 'four-c', ], 5 => [ 'A' => 'test2', ], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3:A4'], ['from' => 'A2', 'to' => 'C2'], ['from' => 'A2', 'to' => 'E2'], ['from' => 'A2', 'to' => 'G2'], ['from' => 'C2', 'to' => 'C3:C4'], ['from' => 'E2', 'to' => 'E3:E4'], ['from' => 'G2', 'to' => 'G3:G4'], ], 'merge_cells' => [ 'C2:D2', 'E2:F2', 'G2:H2', 'A3:B3', 'C3:D3', 'E3:F3', 'G3:H3', 'A4:B4', 'C4:D4', 'E4:F4', 'G4:H4', ], 'copy_width' => [ ['from' => 'A', 'to' => 'C'], ['from' => 'A', 'to' => 'E'], ['from' => 'A', 'to' => 'G'], ['from' => 'B', 'to' => 'D'], ['from' => 'B', 'to' => 'F'], ['from' => 'B', 'to' => 'H'], ], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_matrix_merge_3x3() { $data = [ [ 'values' => [ 1 => [ 'A' => '[foo]', ], 2 => [ 'A' => '[matrix]', ], 3 => [ 'A' => '[bar]', ], ], 'data' => [ 'foo' => 'test1', 'bar' => 'test2', 'matrix' => [ [['one', 'two', 'three', 'four']], [['one-b', 'two-b', 'three-b', 'four-b']], [['one-c', 'two-c', 'three-c', 'four-c']], ], ], 'merge_cells' => ['A2:C2'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => 'test1', ], 2 => [ 'A' => 'one', 'D' => 'two', 'G' => 'three', 'J' => 'four', ], 3 => [ 'A' => 'one-b', 'D' => 'two-b', 'G' => 'three-b', 'J' => 'four-b', ], 4 => [ 'A' => 'one-c', 'D' => 'two-c', 'G' => 'three-c', 'J' => 'four-c', ], 5 => [ 'A' => 'test2', ], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3:A4'], ['from' => 'A2', 'to' => 'D2'], ['from' => 'A2', 'to' => 'G2'], ['from' => 'A2', 'to' => 'J2'], ['from' => 'D2', 'to' => 'D3:D4'], ['from' => 'G2', 'to' => 'G3:G4'], ['from' => 'J2', 'to' => 'J3:J4'], ], 'merge_cells' => [ 'D2:F2', 'G2:I2', 'J2:L2', 'A3:C3', 'D3:F3', 'G3:I3', 'J3:L3', 'A4:C4', 'D4:F4', 'G4:I4', 'J4:L4', ], 'copy_width' => [ ['from' => 'A', 'to' => 'D'], ['from' => 'A', 'to' => 'G'], ['from' => 'A', 'to' => 'J'], ['from' => 'B', 'to' => 'E'], ['from' => 'B', 'to' => 'H'], ['from' => 'B', 'to' => 'K'], ['from' => 'C', 'to' => 'F'], ['from' => 'C', 'to' => 'I'], ['from' => 'C', 'to' => 'L'], ], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_matrix_merge_multi1() { $data = [ [ 'values' => [ 2 => [ 'C' => '[project.months]', ], 3 => [ 'A' => '[project.name]', 'C' => '[project.amount]', ], ], 'data' => [ 'project' => [ 'name' => ['N1', 'N2', 'N3'], 'months' => [['01', '02', '03']], 'amount' => [ [101, 201, 301], [102, 202, 302], [103, 203, 303], ], ], ], 'merge_cells' => ['C2:D2', 'A3:B3', 'C3:D3'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 2 => [ 'C' => '01', 'E' => '02', 'G' => '03', ], 3 => [ 'A' => 'N1', 'C' => 101, 'E' => 201, 'G' => 301, ], 4 => [ 'A' => 'N2', 'C' => 102, 'E' => 202, 'G' => 302, ], 5 => [ 'A' => 'N3', 'C' => 103, 'E' => 203, 'G' => 303, ], ], 'rows' => [ ['action' => 'add', 'row' => 4, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A3', 'to' => 'A4:A5'], ['from' => 'C2', 'to' => 'E2'], ['from' => 'C2', 'to' => 'G2'], ['from' => 'C3', 'to' => 'C4:C5'], ['from' => 'C3', 'to' => 'E3'], ['from' => 'C3', 'to' => 'G3'], ['from' => 'E3', 'to' => 'E4:E5'], ['from' => 'G3', 'to' => 'G4:G5'], ], 'merge_cells' => [ 'E2:F2', 'G2:H2', 'E3:F3', 'G3:H3', 'A4:B4', 'C4:D4', 'E4:F4', 'G4:H4', 'A5:B5', 'C5:D5', 'E5:F5', 'G5:H5', ], 'copy_width' => [ ['from' => 'C', 'to' => 'E'], ['from' => 'C', 'to' => 'G'], ['from' => 'D', 'to' => 'F'], ['from' => 'D', 'to' => 'H'], ], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_matrix_merge_multi2() { $data = [ [ 'values' => [ 2 => [ 'D' => '[project.months]', ], 3 => [ 'A' => '[project.name]', 'D' => '[project.amount]', ], ], 'data' => [ 'project' => [ 'name' => ['N1', 'N2', 'N3'], 'months' => [['01', '02', '03']], 'amount' => [ [101, 201, 301], [102, 202, 302], [103, 203, 303], ], ], ], 'merge_cells' => ['D2:F2', 'A3:C3', 'D3:F3'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 2 => [ 'D' => '01', 'G' => '02', 'J' => '03', ], 3 => [ 'A' => 'N1', 'D' => 101, 'G' => 201, 'J' => 301, ], 4 => [ 'A' => 'N2', 'D' => 102, 'G' => 202, 'J' => 302, ], 5 => [ 'A' => 'N3', 'D' => 103, 'G' => 203, 'J' => 303, ], ], 'rows' => [ ['action' => 'add', 'row' => 4, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A3', 'to' => 'A4:A5'], ['from' => 'D2', 'to' => 'G2'], ['from' => 'D2', 'to' => 'J2'], ['from' => 'D3', 'to' => 'D4:D5'], ['from' => 'D3', 'to' => 'G3'], ['from' => 'D3', 'to' => 'J3'], ['from' => 'G3', 'to' => 'G4:G5'], ['from' => 'J3', 'to' => 'J4:J5'], ], 'merge_cells' => [ 'G2:I2', 'J2:L2', 'G3:I3', 'J3:L3', 'A4:C4', 'D4:F4', 'G4:I4', 'J4:L4', 'A5:C5', 'D5:F5', 'G5:I5', 'J5:L5', ], 'copy_width' => [ ['from' => 'D', 'to' => 'G'], ['from' => 'D', 'to' => 'J'], ['from' => 'E', 'to' => 'H'], ['from' => 'E', 'to' => 'K'], ['from' => 'F', 'to' => 'I'], ['from' => 'F', 'to' => 'L'], ], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_merge_0x2() { $data = [ [ 'values' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', 'C' => null, ], 2 => [ 'A' => '[project.id]', 'B' => '[project.name]', 'C' => null, ], 3 => [ 'B' => '[project.amount_1]', 'C' => '[project.amount_2]', ], 4 => [ 'A' => '[foo]', 'C' => null, ], ], 'data' => [ 'project' => [], 'foo' => 'test1', ], 'merge_cells' => ['A2:A3'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', ], 2 => [ 'A' => null, 'B' => null, ], 3 => [ 'B' => null, 'C' => null, ], 4 => [ 'A' => 'test1', ], ], 'rows' => [], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_merge_1x2() { $data = [ [ 'values' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', 'C' => null, ], 2 => [ 'A' => '[project.id]', 'B' => '[project.name]', 'C' => null, ], 3 => [ 'B' => '[project.amount_1]', 'C' => '[project.amount_2]', ], 4 => [ 'A' => '[foo]', 'C' => null, ], ], 'data' => [ 'project' => [ ['id' => 1, 'name' => 'project 1', 'amount_1' => 101, 'amount_2' => 102], ], 'foo' => 'test1', ], 'merge_cells' => ['A2:A3'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', ], 2 => [ 'A' => 1, 'B' => 'project 1', ], 3 => [ 'B' => 101, 'C' => 102, ], 4 => [ 'A' => 'test1', ], ], 'rows' => [], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_merge_2x2() { $data = [ [ 'values' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', 'C' => null, ], 2 => [ 'A' => '[project.id]', 'B' => '[project.name]', 'C' => null, ], 3 => [ 'B' => '[project.amount_1]', 'C' => '[project.amount_2]', ], 4 => [ 'A' => '[foo]', 'C' => null, ], ], 'data' => [ 'project' => [ ['id' => 1, 'name' => 'project 1', 'amount_1' => 101, 'amount_2' => 102], ['id' => 2, 'name' => 'project 2', 'amount_1' => 201, 'amount_2' => 202], ], 'foo' => 'test1', ], 'merge_cells' => ['A2:A3'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', ], 2 => [ 'A' => 1, 'B' => 'project 1', ], 3 => [ 'B' => 101, 'C' => 102, ], 4 => [ 'A' => 2, 'B' => 'project 2', ], 5 => [ 'B' => 201, 'C' => 202, ], 6 => [ 'A' => 'test1', ], ], 'rows' => [ ['action' => 'add', 'row' => 4, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A4'], ['from' => 'B2', 'to' => 'B4'], ['from' => 'B3', 'to' => 'B5'], ['from' => 'C2', 'to' => 'C4'], ['from' => 'C3', 'to' => 'C5'], ], 'merge_cells' => [ 'A4:A5', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_merge_2x2_skip() { $data = [ [ 'values' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B2:B2)', ], 2 => [ 'A' => 'Hello', 'B' => '[project.name]', ], 4 => [ 'A' => '[foo]', 'B' => null, ], ], 'data' => [ 'project' => [ ['name' => 'project 1'], ['name' => 'project 2'], ], 'foo' => 'test1', ], 'merge_cells' => ['A2:A3'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '=SUM(A2:A3)', 'B' => '=SUM(B2:B3)', ], 2 => [ 'A' => 'Hello', 'B' => 'project 1', ], 3 => [ 'B' => 'project 2', ], 5 => [ 'A' => 'test1', ], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 1], ], 'copy_style' => [ ['from' => 'B2', 'to' => 'B3'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_merge_3x2() { $data = [ [ 'values' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', 'C' => null, ], 2 => [ 'A' => '[project.id]', 'B' => '[project.name]', 'C' => null, ], 3 => [ 'B' => '[project.amount_1]', 'C' => '[project.amount_2]', ], 4 => [ 'A' => '[foo]', 'C' => null, ], ], 'data' => [ 'project' => [ ['id' => 1, 'name' => 'project 1', 'amount_1' => 101, 'amount_2' => 102], ['id' => 2, 'name' => 'project 2', 'amount_1' => 201, 'amount_2' => 202], ['id' => 3, 'name' => 'project 3', 'amount_1' => 301, 'amount_2' => 302], ], 'foo' => 'test1', ], 'merge_cells' => ['A2:A3'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', ], 2 => [ 'A' => 1, 'B' => 'project 1', ], 3 => [ 'B' => 101, 'C' => 102, ], 4 => [ 'A' => 2, 'B' => 'project 2', ], 5 => [ 'B' => 201, 'C' => 202, ], 6 => [ 'A' => 3, 'B' => 'project 3', ], 7 => [ 'B' => 301, 'C' => 302, ], 8 => [ 'A' => 'test1', ], ], 'rows' => [ ['action' => 'add', 'row' => 4, 'qty' => 4], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A4'], ['from' => 'A2', 'to' => 'A6'], ['from' => 'B2', 'to' => 'B4'], ['from' => 'B2', 'to' => 'B6'], ['from' => 'B3', 'to' => 'B5'], ['from' => 'B3', 'to' => 'B7'], ['from' => 'C2', 'to' => 'C4'], ['from' => 'C2', 'to' => 'C6'], ['from' => 'C3', 'to' => 'C5'], ['from' => 'C3', 'to' => 'C7'], ], 'merge_cells' => [ 'A4:A5', 'A6:A7', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_merge_4x2() { $data = [ [ 'values' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', 'C' => null, ], 2 => [ 'A' => '[project.id]', 'B' => '[project.name]', 'C' => null, ], 3 => [ 'B' => '[project.amount_1]', 'C' => '[project.amount_2]', ], 4 => [ 'A' => '[foo]', 'C' => null, ], ], 'data' => [ 'project' => [ ['id' => 1, 'name' => 'project 1', 'amount_1' => 101, 'amount_2' => 102], ['id' => 2, 'name' => 'project 2', 'amount_1' => 201, 'amount_2' => 202], ['id' => 3, 'name' => 'project 3', 'amount_1' => 301, 'amount_2' => 302], ['id' => 4, 'name' => 'project 4', 'amount_1' => 401, 'amount_2' => 402], ], 'foo' => 'test1', ], 'merge_cells' => ['A2:A3'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', ], 2 => [ 'A' => 1, 'B' => 'project 1', ], 3 => [ 'B' => 101, 'C' => 102, ], 4 => [ 'A' => 2, 'B' => 'project 2', ], 5 => [ 'B' => 201, 'C' => 202, ], 6 => [ 'A' => 3, 'B' => 'project 3', ], 7 => [ 'B' => 301, 'C' => 302, ], 8 => [ 'A' => 4, 'B' => 'project 4', ], 9 => [ 'B' => 401, 'C' => 402, ], 10 => [ 'A' => 'test1', ], ], 'rows' => [ ['action' => 'add', 'row' => 4, 'qty' => 6], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A4'], ['from' => 'A2', 'to' => 'A6'], ['from' => 'A2', 'to' => 'A8'], ['from' => 'B2', 'to' => 'B4'], ['from' => 'B2', 'to' => 'B6'], ['from' => 'B2', 'to' => 'B8'], ['from' => 'B3', 'to' => 'B5'], ['from' => 'B3', 'to' => 'B7'], ['from' => 'B3', 'to' => 'B9'], ['from' => 'C2', 'to' => 'C4'], ['from' => 'C2', 'to' => 'C6'], ['from' => 'C2', 'to' => 'C8'], ['from' => 'C3', 'to' => 'C5'], ['from' => 'C3', 'to' => 'C7'], ['from' => 'C3', 'to' => 'C9'], ], 'merge_cells' => [ 'A4:A5', 'A6:A7', 'A8:A9', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_merge_4x3() { $data = [ [ 'values' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', 'C' => '=SUM(C4:C4)', ], 2 => [ 'A' => '[project.id]', 'B' => '[project.name]', 'C' => null, ], 3 => [ 'B' => '[project.amount_1]', 'C' => null, ], 4 => [ 'C' => '[project.amount_2]', ], 6 => [ 'A' => '[foo]', 'C' => null, ], ], 'data' => [ 'project' => [ ['id' => 1, 'name' => 'project 1', 'amount_1' => 101, 'amount_2' => 102], ['id' => 2, 'name' => 'project 2', 'amount_1' => 201, 'amount_2' => 202], ['id' => 3, 'name' => 'project 3', 'amount_1' => 301, 'amount_2' => 302], ['id' => 4, 'name' => 'project 4', 'amount_1' => 401, 'amount_2' => 402], ], 'foo' => 'test1', ], 'merge_cells' => ['A2:A4'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', 'C' => '=SUM(C4:C4)', ], 2 => [ 'A' => 1, 'B' => 'project 1', ], 3 => [ 'B' => 101, ], 4 => [ 'C' => 102, ], 5 => [ 'A' => 2, 'B' => 'project 2', ], 6 => [ 'B' => 201, ], 7 => [ 'C' => 202, ], 8 => [ 'A' => 3, 'B' => 'project 3', ], 9 => [ 'B' => 301, ], 10 => [ 'C' => 302, ], 11 => [ 'A' => 4, 'B' => 'project 4', ], 12 => [ 'B' => 401, ], 13 => [ 'C' => 402, ], 15 => [ 'A' => 'test1', ], ], 'rows' => [ ['action' => 'add', 'row' => 5, 'qty' => 9], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A5'], ['from' => 'A2', 'to' => 'A8'], ['from' => 'A2', 'to' => 'A11'], ['from' => 'B2', 'to' => 'B5'], ['from' => 'B2', 'to' => 'B8'], ['from' => 'B2', 'to' => 'B11'], ['from' => 'B3', 'to' => 'B6'], ['from' => 'B3', 'to' => 'B9'], ['from' => 'B3', 'to' => 'B12'], ['from' => 'B4', 'to' => 'B7'], ['from' => 'B4', 'to' => 'B10'], ['from' => 'B4', 'to' => 'B13'], ['from' => 'C2', 'to' => 'C5'], ['from' => 'C2', 'to' => 'C8'], ['from' => 'C2', 'to' => 'C11'], ['from' => 'C3', 'to' => 'C6'], ['from' => 'C3', 'to' => 'C9'], ['from' => 'C3', 'to' => 'C12'], ['from' => 'C4', 'to' => 'C7'], ['from' => 'C4', 'to' => 'C10'], ['from' => 'C4', 'to' => 'C13'], ], 'merge_cells' => [ 'A5:A7', 'A8:A10', 'A11:A13', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_merge_4x4() { $data = [ [ 'values' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', 'C' => '=SUM(C4:C4)', 'D' => '=SUM(D5:D5)', ], 2 => [ 'A' => '[project.id]', 'B' => '[project.name]', 'D' => null, ], 3 => [ 'B' => '[project.amount_1]', 'D' => null, ], 4 => [ 'C' => '[project.amount_2]', 'D' => null, ], 6 => [ 'A' => '[foo]', 'D' => null, ], ], 'data' => [ 'project' => [ ['id' => 1, 'name' => 'project 1', 'amount_1' => 101, 'amount_2' => 102], ['id' => 2, 'name' => 'project 2', 'amount_1' => 201, 'amount_2' => 202], ['id' => 3, 'name' => 'project 3', 'amount_1' => 301, 'amount_2' => 302], ['id' => 4, 'name' => 'project 4', 'amount_1' => 401, 'amount_2' => 402], ], 'foo' => 'test1', ], 'merge_cells' => ['A2:A5'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', 'C' => '=SUM(C4:C4)', 'D' => '=SUM(D5:D5)', ], 2 => [ 'A' => 1, 'B' => 'project 1', ], 3 => [ 'B' => 101, ], 4 => [ 'C' => 102, ], 6 => [ 'A' => 2, 'B' => 'project 2', ], 7 => [ 'B' => 201, ], 8 => [ 'C' => 202, ], 10 => [ 'A' => 3, 'B' => 'project 3', ], 11 => [ 'B' => 301, ], 12 => [ 'C' => 302, ], 14 => [ 'A' => 4, 'B' => 'project 4', ], 15 => [ 'B' => 401, ], 16 => [ 'C' => 402, ], 18 => [ 'A' => 'test1', ], ], 'rows' => [ ['action' => 'add', 'row' => 6, 'qty' => 12], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A6'], ['from' => 'A2', 'to' => 'A10'], ['from' => 'A2', 'to' => 'A14'], ['from' => 'B2', 'to' => 'B6'], ['from' => 'B2', 'to' => 'B10'], ['from' => 'B2', 'to' => 'B14'], ['from' => 'B3', 'to' => 'B7'], ['from' => 'B3', 'to' => 'B11'], ['from' => 'B3', 'to' => 'B15'], ['from' => 'B4', 'to' => 'B8'], ['from' => 'B4', 'to' => 'B12'], ['from' => 'B4', 'to' => 'B16'], ['from' => 'B5', 'to' => 'B9'], ['from' => 'B5', 'to' => 'B13'], ['from' => 'B5', 'to' => 'B17'], ['from' => 'C2', 'to' => 'C6'], ['from' => 'C2', 'to' => 'C10'], ['from' => 'C2', 'to' => 'C14'], ['from' => 'C3', 'to' => 'C7'], ['from' => 'C3', 'to' => 'C11'], ['from' => 'C3', 'to' => 'C15'], ['from' => 'C4', 'to' => 'C8'], ['from' => 'C4', 'to' => 'C12'], ['from' => 'C4', 'to' => 'C16'], ['from' => 'C5', 'to' => 'C9'], ['from' => 'C5', 'to' => 'C13'], ['from' => 'C5', 'to' => 'C17'], ['from' => 'D2', 'to' => 'D6'], ['from' => 'D2', 'to' => 'D10'], ['from' => 'D2', 'to' => 'D14'], ['from' => 'D3', 'to' => 'D7'], ['from' => 'D3', 'to' => 'D11'], ['from' => 'D3', 'to' => 'D15'], ['from' => 'D4', 'to' => 'D8'], ['from' => 'D4', 'to' => 'D12'], ['from' => 'D4', 'to' => 'D16'], ['from' => 'D5', 'to' => 'D9'], ['from' => 'D5', 'to' => 'D13'], ['from' => 'D5', 'to' => 'D17'], ], 'merge_cells' => [ 'A6:A9', 'A10:A13', 'A14:A17', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_merge_4x5() { $data = [ [ 'values' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', 'C' => '=SUM(C4:C4)', 'D' => '=SUM(D5:D5)', 'E' => '=SUM(E6:E6)', ], 2 => [ 'A' => '[project.id]', 'B' => '[project.name]', 'E' => null, ], 3 => [ 'B' => '[project.amount_1]', 'E' => null, ], 5 => [ 'C' => '[project.amount_2]', 'E' => null, ], 7 => [ 'A' => '[foo]', 'E' => null, ], ], 'data' => [ 'project' => [ ['id' => 1, 'name' => 'project 1', 'amount_1' => 101, 'amount_2' => 102], ['id' => 2, 'name' => 'project 2', 'amount_1' => 201, 'amount_2' => 202], ['id' => 3, 'name' => 'project 3', 'amount_1' => 301, 'amount_2' => 302], ['id' => 4, 'name' => 'project 4', 'amount_1' => 401, 'amount_2' => 402], ], 'foo' => 'test1', ], 'merge_cells' => ['A2:A6'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(B3:B3)', 'C' => '=SUM(C4:C4)', 'D' => '=SUM(D5:D5)', 'E' => '=SUM(E6:E6)', ], 2 => [ 'A' => 1, 'B' => 'project 1', ], 3 => [ 'B' => 101, ], 5 => [ 'C' => 102, ], 7 => [ 'A' => 2, 'B' => 'project 2', ], 8 => [ 'B' => 201, ], 10 => [ 'C' => 202, ], 12 => [ 'A' => 3, 'B' => 'project 3', ], 13 => [ 'B' => 301, ], 15 => [ 'C' => 302, ], 17 => [ 'A' => 4, 'B' => 'project 4', ], 18 => [ 'B' => 401, ], 20 => [ 'C' => 402, ], 22 => [ 'A' => 'test1', ], ], 'rows' => [ ['action' => 'add', 'row' => 7, 'qty' => 15], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A7'], ['from' => 'A2', 'to' => 'A12'], ['from' => 'A2', 'to' => 'A17'], ['from' => 'B2', 'to' => 'B7'], ['from' => 'B2', 'to' => 'B12'], ['from' => 'B2', 'to' => 'B17'], ['from' => 'B3', 'to' => 'B8'], ['from' => 'B3', 'to' => 'B13'], ['from' => 'B3', 'to' => 'B18'], ['from' => 'B4', 'to' => 'B9'], ['from' => 'B4', 'to' => 'B14'], ['from' => 'B4', 'to' => 'B19'], ['from' => 'B5', 'to' => 'B10'], ['from' => 'B5', 'to' => 'B15'], ['from' => 'B5', 'to' => 'B20'], ['from' => 'B6', 'to' => 'B11'], ['from' => 'B6', 'to' => 'B16'], ['from' => 'B6', 'to' => 'B21'], ['from' => 'C2', 'to' => 'C7'], ['from' => 'C2', 'to' => 'C12'], ['from' => 'C2', 'to' => 'C17'], ['from' => 'C3', 'to' => 'C8'], ['from' => 'C3', 'to' => 'C13'], ['from' => 'C3', 'to' => 'C18'], ['from' => 'C4', 'to' => 'C9'], ['from' => 'C4', 'to' => 'C14'], ['from' => 'C4', 'to' => 'C19'], ['from' => 'C5', 'to' => 'C10'], ['from' => 'C5', 'to' => 'C15'], ['from' => 'C5', 'to' => 'C20'], ['from' => 'C6', 'to' => 'C11'], ['from' => 'C6', 'to' => 'C16'], ['from' => 'C6', 'to' => 'C21'], ['from' => 'D2', 'to' => 'D7'], ['from' => 'D2', 'to' => 'D12'], ['from' => 'D2', 'to' => 'D17'], ['from' => 'D3', 'to' => 'D8'], ['from' => 'D3', 'to' => 'D13'], ['from' => 'D3', 'to' => 'D18'], ['from' => 'D4', 'to' => 'D9'], ['from' => 'D4', 'to' => 'D14'], ['from' => 'D4', 'to' => 'D19'], ['from' => 'D5', 'to' => 'D10'], ['from' => 'D5', 'to' => 'D15'], ['from' => 'D5', 'to' => 'D20'], ['from' => 'D6', 'to' => 'D11'], ['from' => 'D6', 'to' => 'D16'], ['from' => 'D6', 'to' => 'D21'], ['from' => 'E2', 'to' => 'E7'], ['from' => 'E2', 'to' => 'E12'], ['from' => 'E2', 'to' => 'E17'], ['from' => 'E3', 'to' => 'E8'], ['from' => 'E3', 'to' => 'E13'], ['from' => 'E3', 'to' => 'E18'], ['from' => 'E4', 'to' => 'E9'], ['from' => 'E4', 'to' => 'E14'], ['from' => 'E4', 'to' => 'E19'], ['from' => 'E5', 'to' => 'E10'], ['from' => 'E5', 'to' => 'E15'], ['from' => 'E5', 'to' => 'E20'], ['from' => 'E6', 'to' => 'E11'], ['from' => 'E6', 'to' => 'E16'], ['from' => 'E6', 'to' => 'E21'], ], 'merge_cells' => [ 'A7:A11', 'A12:A16', 'A17:A21', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_merge_multi() { $data = [ [ 'values' => [ 1 => [ 'B' => '[names]', ], 2 => [ 'A' => '[months]', 'B' => '[amounts.p1]', ], 4 => [ 'B' => '[amounts.p2]', ], '5' => [ 'A' => '=A1', 'B' => null, ], ], 'data' => [ 'names' => [['One', 'Two', 'Three']], 'months' => ['01', '02', '03'], 'amounts' => [ 'p1' => [ ['One R1 P1', 'Two R1 P1', 'Three R1 P1'], ['One R2 P1', 'Two R2 P1', 'Three R2 P1'], ['One R3 P1', 'Two R3 P1', 'Three R3 P1'], ], 'p2' => [ ['One R1 P2', 'Two R1 P2', 'Three R1 P2'], ['One R2 P2', 'Two R2 P2', 'Three R2 P2'], ['One R3 P2', 'Two R3 P2', 'Three R3 P2'], ], ], ], 'merge_cells' => ['A2:A4', 'B1:C1', 'B2:C2', 'B4:C4'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'One', 'D' => 'Two', 'F' => 'Three', ], 2 => [ 'A' => '01', 'B' => 'One R1 P1', 'D' => 'Two R1 P1', 'F' => 'Three R1 P1', ], 4 => [ 'B' => 'One R1 P2', 'D' => 'Two R1 P2', 'F' => 'Three R1 P2', ], 5 => [ 'A' => '02', 'B' => 'One R2 P1', 'D' => 'Two R2 P1', 'F' => 'Three R2 P1', ], 7 => [ 'B' => 'One R2 P2', 'D' => 'Two R2 P2', 'F' => 'Three R2 P2', ], 8 => [ 'A' => '03', 'B' => 'One R3 P1', 'D' => 'Two R3 P1', 'F' => 'Three R3 P1', ], 10 => [ 'B' => 'One R3 P2', 'D' => 'Two R3 P2', 'F' => 'Three R3 P2', ], 11 => [ 'A' => '=A1', ], ], 'rows' => [ ['action' => 'add', 'row' => 5, 'qty' => 6], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A5'], ['from' => 'A2', 'to' => 'A8'], ['from' => 'B1', 'to' => 'D1'], ['from' => 'B1', 'to' => 'F1'], ['from' => 'B2', 'to' => 'B5'], ['from' => 'B2', 'to' => 'B8'], ['from' => 'B2', 'to' => 'D2'], ['from' => 'B2', 'to' => 'F2'], ['from' => 'B3', 'to' => 'B6'], ['from' => 'B3', 'to' => 'B9'], ['from' => 'B4', 'to' => 'B7'], ['from' => 'B4', 'to' => 'B10'], ['from' => 'B4', 'to' => 'D4'], ['from' => 'B4', 'to' => 'F4'], ['from' => 'D2', 'to' => 'D5'], ['from' => 'D2', 'to' => 'D8'], ['from' => 'D4', 'to' => 'D7'], ['from' => 'D4', 'to' => 'D10'], ['from' => 'F2', 'to' => 'F5'], ['from' => 'F2', 'to' => 'F8'], ['from' => 'F4', 'to' => 'F7'], ['from' => 'F4', 'to' => 'F10'], ], 'merge_cells' => [ 'D1:E1', 'F1:G1', 'D2:E2', 'F2:G2', 'A5:A7', 'B5:C5', 'D5:E5', 'F5:G5', 'A8:A10', 'B8:C8', 'D8:E8', 'F8:G8', 'D4:E4', 'F4:G4', 'B7:C7', 'D7:E7', 'F7:G7', 'B10:C10', 'D10:E10', 'F10:G10', ], 'copy_width' => [ ['from' => 'B', 'to' => 'D'], ['from' => 'B', 'to' => 'F'], ['from' => 'C', 'to' => 'E'], ['from' => 'C', 'to' => 'G'], ], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_shift1() { $data = [ [ 'values' => [ 1 => [ 'A' => '[list.qty]', 'B' => null, ], 2 => [ 'A' => '=SUM(A1:A1)', 'B' => '=SUM(A1:A10)', ], 3 => [ 'A' => '-', 'B' => '[! list]', ], ], 'data' => [ 'list' => [ ['qty' => 1], ['qty' => 2] ], ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => 1, ], 2 => [ 'A' => 2, ], 3 => [ 'A' => '=SUM(A1:A2)', 'B' => '=SUM(A1:A10)', ], ], 'rows' => [ ['action' => 'add', 'row' => 2, 'qty' => 1], ['action' => 'delete', 'row' => 4, 'qty' => 1], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A2'], ['from' => 'B1', 'to' => 'B2'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_shift2() { $data = [ [ 'values' => [ 1 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(A2:A20)', ], 2 => [ 'A' => '[list.qty]', 'B' => null, ], 3 => [ 'A' => '=SUM(A2:A2)', 'B' => '=SUM(A2:A20)', ], 4 => [ 'A' => '-', 'B' => '[! list]', ], ], 'data' => [ 'list' => [ ['qty' => 1], ['qty' => 2] ], ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '=SUM(A2:A3)', 'B' => '=SUM(A2:A20)', ], 2 => [ 'A' => 1, ], 3 => [ 'A' => 2, ], 4 => [ 'A' => '=SUM(A2:A3)', 'B' => '=SUM(A2:A20)', ], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 1], ['action' => 'delete', 'row' => 5, 'qty' => 1], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3'], ['from' => 'B2', 'to' => 'B3'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_shift3() { $data = [ [ 'values' => [ 1 => [ 'A' => '[list.qty]', ], 2 => [ 'A' => '=SUM(A1:A1)', ], ], 'data' => [ 'list' => [ ['qty' => 1], ['qty' => 2] ], ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => 1, ], 2 => [ 'A' => 2, ], 3 => [ 'A' => '=SUM(A1:A2)', ], ], 'rows' => [ ['action' => 'add', 'row' => 2, 'qty' => 1], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A2'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_shift4() { $data = [ [ 'values' => [ 1 => [ 'A' => '=A2:A2', ], 2 => [ 'A' => '[list.qty]', ], ], 'data' => [ 'list' => [ ['qty' => 1], ['qty' => 2] ], ], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => '=A2:A3', ], 2 => [ 'A' => 1, ], 3 => [ 'A' => 2, ], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 1], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A3'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], [])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_merge1() { $data = [ [ 'values' => [ 1 => [ 'A' => '[list.a]', 'B' => '[list.b]', 'C' => '[list.c]', ], 2 => [ 'C' => 'Foo', ], ], 'data' => [ 'list' => [ ['a' => 'a1', 'b' => 'b1', 'c' => 'c1'], ['a' => 'a2', 'b' => 'b2', 'c' => 'c2'], ['a' => 'a3', 'b' => 'b3', 'c' => 'c3'], ['a' => 'a4', 'b' => 'b4', 'c' => 'c4'], ], ], 'merge_cells' => ['A1:A3', 'B1:B2'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'A' => 'a1', 'B' => 'b1', 'C' => 'c1', ], 2 => [ 'C' => 'Foo', ], 4 => [ 'A' => 'a2', 'B' => 'b2', 'C' => 'c2', ], 5 => [ 'C' => 'Foo', ], 7 => [ 'A' => 'a3', 'B' => 'b3', 'C' => 'c3', ], 8 => [ 'C' => 'Foo', ], 10 => [ 'A' => 'a4', 'B' => 'b4', 'C' => 'c4', ], 11 => [ 'C' => 'Foo', ], ], 'rows' => [ ['action' => 'add', 'row' => 4, 'qty' => 9], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A4'], ['from' => 'A1', 'to' => 'A7'], ['from' => 'A1', 'to' => 'A10'], ['from' => 'B1', 'to' => 'B4'], ['from' => 'B1', 'to' => 'B7'], ['from' => 'B1', 'to' => 'B10'], ['from' => 'C1', 'to' => 'C4'], ['from' => 'C1', 'to' => 'C7'], ['from' => 'C1', 'to' => 'C10'], ['from' => 'C2', 'to' => 'C5'], ['from' => 'C2', 'to' => 'C8'], ['from' => 'C2', 'to' => 'C11'], ], 'merge_cells' => ['A4:A6', 'B4:B5', 'A7:A9', 'B7:B8', 'A10:A12', 'B10:B11'], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_multi_merge2() { $data = [ [ 'values' => [ 1 => [ 'B' => '[managers.name]', ], 2 => [ 'A' => '[managers.sales.month]', 'B' => '[managers.sales.amount]', ], 3 => [ 'B' => '[managers.sales.qty]', ], 4 => [ 'B' => 'Hello', ], ], 'data' => [ 'managers' => [ 'name' => [['Liam', 'Noah', 'Emma']], 'sales' => [ ['month' => '01', 'qty' => [[1, 2, 3]], 'amount' => [100, 101, 102]], ['month' => '02', 'qty' => [[1, 2, 3]], 'amount' => [200, 201, 202]], ['month' => '03', 'qty' => [[1, 2, 3]], 'amount' => [300, 301, 302]], ], ], ], 'merge_cells' => ['B1:C1', 'A2:A4', 'B2:C2', 'B3:C3', 'B4:C4'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => [ 'B' => 'Liam', 'D' => 'Noah', 'F' => 'Emma', ], 2 => [ 'A' => '01', 'B' => 100, 'D' => 101, 'F' => 102, ], 3 => [ 'B' => 1, 'D' => 2, 'F' => 3, ], 4 => [ 'B' => 'Hello', ], 5 => [ 'A' => '02', 'B' => 200, 'D' => 201, 'F' => 202, ], 6 => [ 'B' => 1, 'D' => 2, 'F' => 3, ], 7 => [ 'B' => 'Hello', ], 8 => [ 'A' => '03', 'B' => 300, 'D' => 301, 'F' => 302, ], 9 => [ 'B' => 1, 'D' => 2, 'F' => 3, ], 10 => [ 'B' => 'Hello', ], ], 'rows' => [ ['action' => 'add', 'row' => 5, 'qty' => 6], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A5'], ['from' => 'A2', 'to' => 'A8'], ['from' => 'B1', 'to' => 'D1'], ['from' => 'B1', 'to' => 'F1'], ['from' => 'B2', 'to' => 'B5'], ['from' => 'B2', 'to' => 'B8'], ['from' => 'B2', 'to' => 'D2'], ['from' => 'B2', 'to' => 'F2'], ['from' => 'B3', 'to' => 'B6'], ['from' => 'B3', 'to' => 'B9'], ['from' => 'B3', 'to' => 'D3'], ['from' => 'B3', 'to' => 'F3'], ['from' => 'B4', 'to' => 'B7'], ['from' => 'B4', 'to' => 'B10'], ['from' => 'D2', 'to' => 'D5'], ['from' => 'D2', 'to' => 'D8'], ['from' => 'D3', 'to' => 'D6'], ['from' => 'D3', 'to' => 'D9'], ['from' => 'F2', 'to' => 'F5'], ['from' => 'F2', 'to' => 'F8'], ['from' => 'F3', 'to' => 'F6'], ['from' => 'F3', 'to' => 'F9'], ], 'merge_cells' => [ 'D1:E1', 'F1:G1', 'D2:E2', 'F2:G2', 'A5:A7', 'B5:C5', 'D5:E5', 'F5:G5', 'A8:A10', 'B8:C8', 'D8:E8', 'F8:G8', 'D3:E3', 'F3:G3', 'B6:C6', 'D6:E6', 'F6:G6', 'B9:C9', 'D9:E9', 'F9:G9', 'B7:C7', 'B10:C10', ], 'copy_width' => [ ['from' => 'B', 'to' => 'D'], ['from' => 'B', 'to' => 'F'], ['from' => 'C', 'to' => 'E'], ['from' => 'C', 'to' => 'G'], ], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_list_long() { $data = [ [ 'values' => [ 1 => [ 'A' => '[foo1]', 'B' => '[bar1]', 'C' => 'foo1', 'D' => 'bar1', ], 10 => [ 'A' => '[foo2]', 'B' => '[bar2]', 'C' => 'foo2', 'D' => 'bar2', ], ], 'data' => [ 'foo1' => [ 'foo1-1', 'foo1-2', 'foo1-3', 'foo1-4', 'foo1-5', 'foo1-6', 'foo1-7', 'foo1-8', 'foo1-9', 'foo1-10', 'foo1-11', 'foo1-12', 'foo1-13', 'foo1-14', 'foo1-15', 'foo1-16', ], 'bar1' => [ 'bar1-1', 'bar1-2', 'bar1-3', 'bar1-4', 'bar1-5', 'bar1-6', 'bar1-7', 'bar1-8', 'bar1-9', 'bar1-10', 'bar1-11', 'bar1-12', 'bar1-13', 'bar1-14', 'bar1-15', 'bar1-16', ], 'foo2' => [ 'foo2-1', 'foo2-2', 'foo2-3', 'foo2-4', 'foo2-5', 'foo2-6', 'foo2-7', 'foo2-8', 'foo2-9', 'foo2-10', 'foo2-11', 'foo2-12', 'foo2-13', 'foo2-14', 'foo2-15', 'foo2-16', ], 'bar2' => [ 'bar2-1', 'bar2-2', 'bar2-3', 'bar2-4', 'bar2-5', 'bar2-6', 'bar2-7', 'bar2-8', 'bar2-9', 'bar2-10', 'bar2-11', 'bar2-12', 'bar2-13', 'bar2-14', 'bar2-15', 'bar2-16', ], ], 'merge_cells' => [], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => 'foo1-1', 'B' => 'bar1-1', 'C' => 'foo1', 'D' => 'bar1'], 2 => ['A' => 'foo1-2', 'B' => 'bar1-2', 'C' => 'foo1', 'D' => 'bar1'], 3 => ['A' => 'foo1-3', 'B' => 'bar1-3', 'C' => 'foo1', 'D' => 'bar1'], 4 => ['A' => 'foo1-4', 'B' => 'bar1-4', 'C' => 'foo1', 'D' => 'bar1'], 5 => ['A' => 'foo1-5', 'B' => 'bar1-5', 'C' => 'foo1', 'D' => 'bar1'], 6 => ['A' => 'foo1-6', 'B' => 'bar1-6', 'C' => 'foo1', 'D' => 'bar1'], 7 => ['A' => 'foo1-7', 'B' => 'bar1-7', 'C' => 'foo1', 'D' => 'bar1'], 8 => ['A' => 'foo1-8', 'B' => 'bar1-8', 'C' => 'foo1', 'D' => 'bar1'], 9 => ['A' => 'foo1-9', 'B' => 'bar1-9', 'C' => 'foo1', 'D' => 'bar1'], 10 => ['A' => 'foo1-10', 'B' => 'bar1-10', 'C' => 'foo1', 'D' => 'bar1'], 11 => ['A' => 'foo1-11', 'B' => 'bar1-11', 'C' => 'foo1', 'D' => 'bar1'], 12 => ['A' => 'foo1-12', 'B' => 'bar1-12', 'C' => 'foo1', 'D' => 'bar1'], 13 => ['A' => 'foo1-13', 'B' => 'bar1-13', 'C' => 'foo1', 'D' => 'bar1'], 14 => ['A' => 'foo1-14', 'B' => 'bar1-14', 'C' => 'foo1', 'D' => 'bar1'], 15 => ['A' => 'foo1-15', 'B' => 'bar1-15', 'C' => 'foo1', 'D' => 'bar1'], 16 => ['A' => 'foo1-16', 'B' => 'bar1-16', 'C' => 'foo1', 'D' => 'bar1'], 25 => ['A' => 'foo2-1', 'B' => 'bar2-1', 'C' => 'foo2', 'D' => 'bar2'], 26 => ['A' => 'foo2-2', 'B' => 'bar2-2', 'C' => 'foo2', 'D' => 'bar2'], 27 => ['A' => 'foo2-3', 'B' => 'bar2-3', 'C' => 'foo2', 'D' => 'bar2'], 28 => ['A' => 'foo2-4', 'B' => 'bar2-4', 'C' => 'foo2', 'D' => 'bar2'], 29 => ['A' => 'foo2-5', 'B' => 'bar2-5', 'C' => 'foo2', 'D' => 'bar2'], 30 => ['A' => 'foo2-6', 'B' => 'bar2-6', 'C' => 'foo2', 'D' => 'bar2'], 31 => ['A' => 'foo2-7', 'B' => 'bar2-7', 'C' => 'foo2', 'D' => 'bar2'], 32 => ['A' => 'foo2-8', 'B' => 'bar2-8', 'C' => 'foo2', 'D' => 'bar2'], 33 => ['A' => 'foo2-9', 'B' => 'bar2-9', 'C' => 'foo2', 'D' => 'bar2'], 34 => ['A' => 'foo2-10', 'B' => 'bar2-10', 'C' => 'foo2', 'D' => 'bar2'], 35 => ['A' => 'foo2-11', 'B' => 'bar2-11', 'C' => 'foo2', 'D' => 'bar2'], 36 => ['A' => 'foo2-12', 'B' => 'bar2-12', 'C' => 'foo2', 'D' => 'bar2'], 37 => ['A' => 'foo2-13', 'B' => 'bar2-13', 'C' => 'foo2', 'D' => 'bar2'], 38 => ['A' => 'foo2-14', 'B' => 'bar2-14', 'C' => 'foo2', 'D' => 'bar2'], 39 => ['A' => 'foo2-15', 'B' => 'bar2-15', 'C' => 'foo2', 'D' => 'bar2'], 40 => ['A' => 'foo2-16', 'B' => 'bar2-16', 'C' => 'foo2', 'D' => 'bar2'], ], 'rows' => [ ['action' => 'add', 'row' => 2, 'qty' => 15], ['action' => 'add', 'row' => 26, 'qty' => 15], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A2:A16'], ['from' => 'A25', 'to' => 'A26:A40'], ['from' => 'B1', 'to' => 'B2:B16'], ['from' => 'B25', 'to' => 'B26:B40'], ['from' => 'C1', 'to' => 'C2:C16'], ['from' => 'C25', 'to' => 'C26:C40'], ['from' => 'D1', 'to' => 'D2:D16'], ['from' => 'D25', 'to' => 'D26:D40'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_several_tables1() { $data = [ [ 'values' => [ 1 => [ 'A' => '[tableOne.a]', 'C' => '', 'D' => '[tableOne.d]', ], 3 => [ 'A' => '[tableTwo.a]', 'C' => '', 'D' => '[tableTwo.d]', ], 5 => [ 'A' => '[tableThree.a]', 'C' => '', 'D' => '[tableThree.d]', ], ], 'data' => [ 'tableOne' => [ 'a' => ['one-a-1', 'one-a-2', 'one-a-3'], 'd' => ['one-d-1', 'one-d-2', 'one-d-3'], ], 'tableTwo' => [ 'a' => ['two-a-1', 'two-a-2', 'two-a-3'], 'd' => ['two-d-1', 'two-d-2', 'two-d-3'], ], 'tableThree' => [ 'a' => ['three-a-1', 'three-a-2', 'three-a-3'], 'd' => ['three-d-1', 'three-d-2', 'three-d-3'], ], ], 'merge_cells' => [], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => 'one-a-1', 'D' => 'one-d-1'], 2 => ['A' => 'one-a-2', 'D' => 'one-d-2'], 3 => ['A' => 'one-a-3', 'D' => 'one-d-3'], 5 => ['A' => 'two-a-1', 'D' => 'two-d-1'], 6 => ['A' => 'two-a-2', 'D' => 'two-d-2'], 7 => ['A' => 'two-a-3', 'D' => 'two-d-3'], 9 => ['A' => 'three-a-1', 'D' => 'three-d-1'], 10 => ['A' => 'three-a-2', 'D' => 'three-d-2'], 11 => ['A' => 'three-a-3', 'D' => 'three-d-3'], ], 'rows' => [ ['action' => 'add', 'row' => 2, 'qty' => 2], ['action' => 'add', 'row' => 6, 'qty' => 2], ['action' => 'add', 'row' => 10, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A2:A3'], ['from' => 'A5', 'to' => 'A6:A7'], ['from' => 'A9', 'to' => 'A10:A11'], ['from' => 'B1', 'to' => 'B2:B3'], ['from' => 'B5', 'to' => 'B6:B7'], ['from' => 'B9', 'to' => 'B10:B11'], ['from' => 'C1', 'to' => 'C2:C3'], ['from' => 'C5', 'to' => 'C6:C7'], ['from' => 'C9', 'to' => 'C10:C11'], ['from' => 'D1', 'to' => 'D2:D3'], ['from' => 'D5', 'to' => 'D6:D7'], ['from' => 'D9', 'to' => 'D10:D11'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_several_tables2() { $data = [ [ 'values' => [ 1 => [ 'A' => '[tableOne.a]', 'C' => null, 'D' => '[tableOne.d]', ], 3 => [ 'A' => '[tableTwo.a]', 'C' => null, 'D' => '[tableTwo.d]', ], 5 => [ 'A' => '[tableThree.a]', 'C' => null, 'D' => '[tableThree.d]', ], ], 'data' => [ 'tableOne' => [ 'a' => ['one-a-1'], 'd' => ['one-d-1'], ], 'tableTwo' => [], 'tableThree' => [ 'a' => ['three-a-1', 'three-a-2', 'three-a-3'], 'd' => ['three-d-1', 'three-d-2', 'three-d-3'], ], ], 'merge_cells' => [], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => 'one-a-1', 'D' => 'one-d-1'], 3 => ['A' => null, 'D' => null], 5 => ['A' => 'three-a-1', 'D' => 'three-d-1'], 6 => ['A' => 'three-a-2', 'D' => 'three-d-2'], 7 => ['A' => 'three-a-3', 'D' => 'three-d-3'], ], 'rows' => [ ['action' => 'add', 'row' => 6, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A5', 'to' => 'A6:A7'], ['from' => 'B5', 'to' => 'B6:B7'], ['from' => 'C5', 'to' => 'C6:C7'], ['from' => 'D5', 'to' => 'D6:D7'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_several_tables3() { $data = [ [ 'values' => [ 1 => [ 'A' => '[tableOne.a]', 'B' => '[tableOne.b]', ], 3 => [ 'A' => '[tableTwo.a]', 'B' => '[tableTwo.b]', ], 5 => [ 'A' => '[tableThree.a]', 'B' => '[tableThree.b]', ], ], 'data' => [ 'tableOne' => [ 'a' => ['one-a-1', 'one-a-2', 'one-a-3'], 'b' => ['one-b-1', 'one-b-2', 'one-b-3'], ], 'tableTwo' => [ 'a' => ['two-a-1', 'two-a-2', 'two-a-3'], 'b' => ['two-b-1', 'two-b-2', 'two-b-3'], ], 'tableThree' => [ 'a' => ['three-a-1', 'three-a-2', 'three-a-3'], 'b' => ['three-b-1', 'three-b-2', 'three-b-3'], ], ], 'merge_cells' => ['A1:A2', 'B1:B2', 'A3:A4', 'B3:B4', 'A5:A6', 'B5:B6'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => 'one-a-1', 'B' => 'one-b-1'], 3 => ['A' => 'one-a-2', 'B' => 'one-b-2'], 5 => ['A' => 'one-a-3', 'B' => 'one-b-3'], 7 => ['A' => 'two-a-1', 'B' => 'two-b-1'], 9 => ['A' => 'two-a-2', 'B' => 'two-b-2'], 11 => ['A' => 'two-a-3', 'B' => 'two-b-3'], 13 => ['A' => 'three-a-1', 'B' => 'three-b-1'], 15 => ['A' => 'three-a-2', 'B' => 'three-b-2'], 17 => ['A' => 'three-a-3', 'B' => 'three-b-3'], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 4], ['action' => 'add', 'row' => 9, 'qty' => 4], ['action' => 'add', 'row' => 15, 'qty' => 4], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A3'], ['from' => 'A1', 'to' => 'A5'], ['from' => 'A7', 'to' => 'A9'], ['from' => 'A7', 'to' => 'A11'], ['from' => 'A13', 'to' => 'A15'], ['from' => 'A13', 'to' => 'A17'], ['from' => 'B1', 'to' => 'B3'], ['from' => 'B1', 'to' => 'B5'], ['from' => 'B7', 'to' => 'B9'], ['from' => 'B7', 'to' => 'B11'], ['from' => 'B13', 'to' => 'B15'], ['from' => 'B13', 'to' => 'B17'], ], 'merge_cells' => [ 'A3:A4', 'B3:B4', 'A5:A6', 'B5:B6', 'A9:A10', 'B9:B10', 'A11:A12', 'B11:B12', 'A15:A16', 'B15:B16', 'A17:A18', 'B17:B18', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_several_tables4() { $data = [ [ 'values' => [ 1 => [ 'A' => '[tableOne.a] [= tableOne]', 'B' => '[tableOne.b]', ], 3 => [ 'A' => '[tableTwo.a] [= tableTwo]', 'B' => '[tableTwo.b]', ], 4 => [ 'A' => '[tableThree.a] [= tableThree]', 'B' => '[tableThree.b]', ], ], 'data' => [ 'tableOne' => [ 'a' => ['one-a-1', 'one-a-2', 'one-a-3'], 'b' => ['one-b-1', 'one-b-2', 'one-b-3'], ], 'tableTwo' => [], 'tableThree' => [ 'a' => ['three-a-1', 'three-a-2', 'three-a-3'], 'b' => ['three-b-1', 'three-b-2', 'three-b-3'], ], ], 'merge_cells' => ['A1:A2', 'B1:B2', 'A4:A5', 'B4:B5'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => 'one-a-1', 'B' => 'one-b-1'], 3 => ['A' => 'one-a-2', 'B' => 'one-b-2'], 5 => ['A' => 'one-a-3', 'B' => 'one-b-3'], 7 => ['A' => 'three-a-1', 'B' => 'three-b-1'], 9 => ['A' => 'three-a-2', 'B' => 'three-b-2'], 11 => ['A' => 'three-a-3', 'B' => 'three-b-3'], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 4], ['action' => 'delete', 'row' => 7, 'qty' => 1], ['action' => 'add', 'row' => 9, 'qty' => 4], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A3'], ['from' => 'A1', 'to' => 'A5'], ['from' => 'A7', 'to' => 'A9'], ['from' => 'A7', 'to' => 'A11'], ['from' => 'B1', 'to' => 'B3'], ['from' => 'B1', 'to' => 'B5'], ['from' => 'B7', 'to' => 'B9'], ['from' => 'B7', 'to' => 'B11'], ], 'merge_cells' => [ 'A3:A4', 'B3:B4', 'A5:A6', 'B5:B6', 'A9:A10', 'B9:B10', 'A11:A12', 'B11:B12', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_merge1() { $data = [ [ 'values' => [ 1 => [ 'A' => '[table.a] / [table.b] [$= table.a]', 'B' => 'foo', 'D' => 'bar', ], 2 => [ 'A' => 'baz', 'D' => null, ], ], 'data' => [ 'table' => [ 'a' => ['one-a-1', null, 'one-a-3'], 'b' => ['one-b-1', 'one-b-2', 'one-b-3'], ], ], 'merge_cells' => ['B1:C1', 'D1:F1'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => 'one-a-1 / one-b-1', 'B' => 'foo', 'D' => 'bar'], 2 => ['A' => null, 'B' => 'foo', 'D' => 'bar'], 3 => ['A' => 'one-a-3 / one-b-3', 'B' => 'foo', 'D' => 'bar'], ], 'rows' => [ ['action' => 'add', 'row' => 2, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A2:A3'], ['from' => 'B1', 'to' => 'B2:B3'], ['from' => 'D1', 'to' => 'D2:D3'], ], 'merge_cells' => [ 'B2:C2', 'D2:F2', 'B3:C3', 'D3:F3', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_merge2() { $data = [ [ 'values' => [ 1 => [ 'A' => 'Hello', 'B' => '[table.a]', 'C' => 'bar 1', 'D' => 'bar 2', 'F' => 'bar 3', ], ], 'data' => [ 'table' => [ 'a' => ['one', 'two', 'three'], ], ], 'merge_cells' => ['A1:A3', 'D1:E1', 'F1:H1'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => 'Hello', 'B' => 'one', 'C' => 'bar 1', 'D' => 'bar 2', 'F' => 'bar 3'], 2 => ['B' => 'two', 'C' => 'bar 1', 'D' => 'bar 2', 'F' => 'bar 3'], 3 => ['B' => 'three', 'C' => 'bar 1', 'D' => 'bar 2', 'F' => 'bar 3'], ], 'rows' => [ ['action' => 'add', 'row' => 2, 'qty' => 2], ], 'copy_style' => [ ['from' => 'B1', 'to' => 'B2:B3'], ['from' => 'C1', 'to' => 'C2:C3'], ['from' => 'D1', 'to' => 'D2:D3'], ['from' => 'F1', 'to' => 'F2:F3'], ], 'merge_cells' => [ 'D2:E2', 'F2:H2', 'D3:E3', 'F3:H3', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_merge3() { $data = [ [ 'values' => [ 1 => [ 'A' => 'Hello', 'C' => '[table.a]', 'D' => 0, 'E' => '0', 'F' => null, 'G' => '', 'H' => '=ROW()-5', ], 2 => [ 'A' => '[foo]', 'B' => '[bar]', 'C' => 1, 'D' => '1', 'H' => null, ], ], 'data' => [ 'table' => [ 'a' => ['one', 'two', 'three'], ], 'bar' => 'hello', ], 'merge_cells' => ['A1:B3'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => 'Hello', 'C' => 'one', 'D' => 0, 'E' => '0', 'H' => '=ROW()-5'], 2 => ['C' => 'two', 'D' => 0, 'E' => '0', 'H' => '=ROW()-5'], 3 => ['C' => 'three', 'D' => 0, 'E' => '0', 'H' => '=ROW()-5'], 4 => ['A' => null, 'B' => 'hello'], ], 'rows' => [ ['action' => 'add', 'row' => 2, 'qty' => 2], ], 'copy_style' => [ ['from' => 'C1', 'to' => 'C2:C3'], ['from' => 'D1', 'to' => 'D2:D3'], ['from' => 'E1', 'to' => 'E2:E3'], ['from' => 'F1', 'to' => 'F2:F3'], ['from' => 'G1', 'to' => 'G2:G3'], ['from' => 'H1', 'to' => 'H2:H3'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_merge4() { $data = [ [ 'values' => [ 1 => [ 'A' => 'Hello', 'B' => null, ], 2 => [ 'B' => '[table.a]', ], 4 => [ 'A' => '[bar]', 'B' => null, ], ], 'data' => [ 'table' => [ 'a' => ['one', 'two', 'three'], ], 'bar' => 'hello', ], 'merge_cells' => ['A1:B3'], ], [ 'values' => [ 1 => [ 'A' => null, ], 2 => [ 'B' => '[table.a]', ], 4 => [ 'A' => '[bar]' ], ], 'data' => [ 'table' => [ 'a' => ['one', 'two', 'three'], ], 'bar' => 'hello', ], 'merge_cells' => ['A1:B3'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 2 => ['B' => 'one'], 3 => ['B' => 'two'], 4 => ['B' => 'three'], 6 => ['A' => 'hello'], ], 'rows' => [ ['action' => 'add', 'row' => 3, 'qty' => 2], ], 'copy_style' => [ ['from' => 'B2', 'to' => 'B3:B4'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_merge5() { $data = [ [ 'values' => [ 1 => [ 'A' => '=SUM(B3:B3)', 'B' => null, ], 2 => [ 'A' => 'Foo', 'B' => null, ], 3 => [ 'B' => '[product.amount]' ], ], 'data' => [ 'product' => [ 'amount' => [111, 222, 333], ], ], 'merge_cells' => ['A2:A4'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => '=SUM(B3:B5)'], 3 => ['B' => 111], 4 => ['B' => 222], 5 => ['B' => 333], ], 'rows' => [ ['action' => 'add', 'row' => 4, 'qty' => 2], ], 'copy_style' => [ ['from' => 'B3', 'to' => 'B4:B5'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_merge6() { $data = [ [ 'values' => [ 1 => [ 'A' => '=SUM(B3:B3)', 'B' => null, ], 2 => [ 'A' => '[product.id]', 'B' => null, ], 3 => [ 'B' => '[product.amount]' ], ], 'data' => [ 'product' => [ 'id' => [1, 2, 3], 'amount' => [111, 222, 333], ], ], 'merge_cells' => ['A2:A4'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => '=SUM(B3:B3)'], 2 => ['A' => 1], 3 => ['B' => 111], 5 => ['A' => 2], 6 => ['B' => 222], 8 => ['A' => 3], 9 => ['B' => 333], ], 'rows' => [ ['action' => 'add', 'row' => 5, 'qty' => 6], ], 'copy_style' => [ ['from' => 'A2', 'to' => 'A5'], ['from' => 'A2', 'to' => 'A8'], ['from' => 'B2', 'to' => 'B5'], ['from' => 'B2', 'to' => 'B8'], ['from' => 'B3', 'to' => 'B6'], ['from' => 'B3', 'to' => 'B9'], ], 'merge_cells' => ['A5:A7', 'A8:A10'], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_alias1() { $data = [ [ 'values' => [ 1 => [ 'A' => '[product.id]', ], ], 'data' => [ 'product' => [ 'id' => [1, 2, 3], ], ], 'merge_cells' => [], ], [ 'values' => [ 1 => [ 'A' => '[product.id.*]', ], ], 'data' => [ 'product' => [ 'id' => [1, 2, 3], ], ], 'merge_cells' => [], ], [ 'values' => [ 1 => [ 'A' => '[product.id]', ], ], 'data' => [ 'product' => [ ['id' => 1], ['id' => 2], ['id' => 3], ], ], 'merge_cells' => [], ], [ 'values' => [ 1 => [ 'A' => '[product.*.id]', ], ], 'data' => [ 'product' => [ ['id' => 1], ['id' => 2], ['id' => 3], ], ], 'merge_cells' => [], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => 1], 2 => ['A' => 2], 3 => ['A' => 3], ], 'rows' => [ ['action' => 'add', 'row' => 2, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A2:A3'], ], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_alias2() { $data = [ [ 'values' => [ 1 => [ 'A' => '[product.*.id]', ], ], 'data' => [ 'product' => [ 'id' => [1, 2, 3], ], ], 'merge_cells' => [], ], [ 'values' => [ 1 => [ 'A' => '[product.id.*]', ], ], 'data' => [ 'product' => [ ['id' => 1], ['id' => 2], ['id' => 3], ], ], 'merge_cells' => [], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => null], ], 'rows' => [], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_alias3() { $data = [ [ 'values' => [ 1 => [ 'A' => '[product.id.2]', ], 2 => [ 'A' => '[product.id.1]', ], 3 => [ 'A' => '[product.id.0]', ], ], 'data' => [ 'product' => [ 'id' => [1, 2, 3], ], ], 'merge_cells' => [], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => 3], 2 => ['A' => 2], 3 => ['A' => 1], ], 'rows' => [], 'copy_style' => [], 'merge_cells' => [], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_styles1() { $data = [ [ 'values' => [ 1 => [ 'A' => '[product.id]', 'E' => null, ], ], 'data' => [ 'product' => [ 'id' => [1, 2, 3], ], ], 'merge_cells' => ['C1:D1'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => 1], 2 => ['A' => 2], 3 => ['A' => 3], ], 'rows' => [ ['action' => 'add', 'row' => 2, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A2:A3'], ['from' => 'B1', 'to' => 'B2:B3'], ['from' => 'C1', 'to' => 'C2:C3'], ['from' => 'E1', 'to' => 'E2:E3'], ], 'merge_cells' => [ 'C2:D2', 'C3:D3', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_styles2() { $data = [ [ 'values' => [ 1 => [ 'A' => '[product.id]', 'D' => null, ], ], 'data' => [ 'product' => [ 'id' => [1, 2, 3], ], ], 'merge_cells' => ['C1:D1'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => 1], 2 => ['A' => 2], 3 => ['A' => 3], ], 'rows' => [ ['action' => 'add', 'row' => 2, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A2:A3'], ['from' => 'B1', 'to' => 'B2:B3'], ['from' => 'C1', 'to' => 'C2:C3'], ], 'merge_cells' => [ 'C2:D2', 'C3:D3', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_styles3() { $data = [ [ 'values' => [ 1 => [ 'B' => '[product.id]', 'E' => null, ], ], 'data' => [ 'product' => [ 'id' => [1, 2, 3], ], ], 'merge_cells' => ['C1:D1'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['B' => 1], 2 => ['B' => 2], 3 => ['B' => 3], ], 'rows' => [ ['action' => 'add', 'row' => 2, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A2:A3'], ['from' => 'B1', 'to' => 'B2:B3'], ['from' => 'C1', 'to' => 'C2:C3'], ['from' => 'E1', 'to' => 'E2:E3'], ], 'merge_cells' => [ 'C2:D2', 'C3:D3', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_styles4() { $data = [ [ 'values' => [ 1 => [ 'A' => '[product.id]', 'BA' => null, ], ], 'data' => [ 'product' => [ 'id' => [1, 2, 3], ], ], 'merge_cells' => ['AB1:BA1'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['A' => 1], 2 => ['A' => 2], 3 => ['A' => 3], ], 'rows' => [ ['action' => 'add', 'row' => 2, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A2:A3'], ['from' => 'AA1', 'to' => 'AA2:AA3'], ['from' => 'AB1', 'to' => 'AB2:AB3'], ['from' => 'B1', 'to' => 'B2:B3'], ['from' => 'C1', 'to' => 'C2:C3'], ['from' => 'D1', 'to' => 'D2:D3'], ['from' => 'E1', 'to' => 'E2:E3'], ['from' => 'F1', 'to' => 'F2:F3'], ['from' => 'G1', 'to' => 'G2:G3'], ['from' => 'H1', 'to' => 'H2:H3'], ['from' => 'I1', 'to' => 'I2:I3'], ['from' => 'J1', 'to' => 'J2:J3'], ['from' => 'K1', 'to' => 'K2:K3'], ['from' => 'L1', 'to' => 'L2:L3'], ['from' => 'M1', 'to' => 'M2:M3'], ['from' => 'N1', 'to' => 'N2:N3'], ['from' => 'O1', 'to' => 'O2:O3'], ['from' => 'P1', 'to' => 'P2:P3'], ['from' => 'Q1', 'to' => 'Q2:Q3'], ['from' => 'R1', 'to' => 'R2:R3'], ['from' => 'S1', 'to' => 'S2:S3'], ['from' => 'T1', 'to' => 'T2:T3'], ['from' => 'U1', 'to' => 'U2:U3'], ['from' => 'V1', 'to' => 'V2:V3'], ['from' => 'W1', 'to' => 'W2:W3'], ['from' => 'X1', 'to' => 'X2:X3'], ['from' => 'Y1', 'to' => 'Y2:Y3'], ['from' => 'Z1', 'to' => 'Z2:Z3'], ], 'merge_cells' => [ 'AB2:BA2', 'AB3:BA3', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_styles5() { $data = [ [ 'values' => [ 1 => [ 'C' => '[foo.id]', 'E' => null, ], 2 => [ 'A' => '[bar.id]', 'E' => null, ], ], 'data' => [ 'foo' => [ 'id' => [1, 2, 3], ], 'bar' => [ 'id' => [4, 5, 6], ], ], 'merge_cells' => ['C1:E1', 'A2:D2'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['C' => 1], 2 => ['C' => 2], 3 => ['C' => 3], 4 => ['A' => 4], 5 => ['A' => 5], 6 => ['A' => 6], ], 'rows' => [ ['action' => 'add', 'row' => 2, 'qty' => 2], ['action' => 'add', 'row' => 5, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A2:A3'], ['from' => 'A4', 'to' => 'A5:A6'], ['from' => 'B1', 'to' => 'B2:B3'], ['from' => 'C1', 'to' => 'C2:C3'], ['from' => 'E4', 'to' => 'E5:E6'], ], 'merge_cells' => [ 'C2:E2', 'C3:E3', 'A5:D5', 'A6:D6', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } /** * @return void */ public function test_schema_styles6() { $data = [ [ 'values' => [ 1 => [ 'A' => '[= baz]', 'H' => null, ], 2 => [ 'C' => '[foo.id]', 'H' => null, ], 4 => [ 'A' => '[bar.id]', 'H' => null, ], ], 'data' => [ 'foo' => [ 'id' => [1, 2, 3], ], 'bar' => [ 'id' => [4, 5, 6], ], '' => 'baz', ], 'merge_cells' => ['A2:B2', 'C2:G2', 'A4:H4'], ], ]; foreach ($data as $id => $item) { $this->assertSame( [ 'data' => [ 1 => ['C' => 1], 2 => ['C' => 2], 3 => ['C' => 3], 5 => ['A' => 4], 6 => ['A' => 5], 7 => ['A' => 6], ], 'rows' => [ ['action' => 'delete', 'row' => 1, 'qty' => 1], ['action' => 'add', 'row' => 2, 'qty' => 2], ['action' => 'add', 'row' => 6, 'qty' => 2], ], 'copy_style' => [ ['from' => 'A1', 'to' => 'A2:A3'], ['from' => 'A5', 'to' => 'A6:A7'], ['from' => 'C1', 'to' => 'C2:C3'], ['from' => 'H1', 'to' => 'H2:H3'], ], 'merge_cells' => [ 'A2:B2', 'C2:G2', 'A3:B3', 'C3:G3', 'A6:H6', 'A7:H7', ], 'copy_width' => [], ], $this->service->schema($item['values'], $item['data'], $item['merge_cells'])->toArray(), "$id" ); } } } ================================================ FILE: tests/TraitsTest.php ================================================ assertTrue($this->isColumnLE('A', 'B')); $this->assertTrue($this->isColumnLE('B', 'B')); $this->assertFalse($this->isColumnLE('C', 'B')); $this->assertTrue($this->isColumnLE('Z', 'AA')); $this->assertTrue($this->isColumnLE('AA', 'AA')); $this->assertFalse($this->isColumnLE('AB', 'AA')); $this->assertFalse($this->isColumnLE('AAA', 'ZZ')); $this->assertTrue($this->isColumnLE('AAA', 'AAA')); $this->assertTrue($this->isColumnLE('AAA', 'AAB')); } /** * @return void */ public function test_isColumnGE() { $this->assertFalse($this->isColumnGE('A', 'B')); $this->assertTrue($this->isColumnGE('B', 'B')); $this->assertTrue($this->isColumnGE('C', 'B')); $this->assertFalse($this->isColumnGE('Z', 'AA')); $this->assertTrue($this->isColumnGE('AA', 'AA')); $this->assertTrue($this->isColumnGE('AB', 'AA')); $this->assertTrue($this->isColumnGE('AAA', 'ZZ')); $this->assertTrue($this->isColumnGE('AAA', 'AAA')); $this->assertFalse($this->isColumnGE('AAA', 'AAB')); } /** * @return void */ public function test_sort() { $columns = ['BA' => 7, 'AZ' => 6, 'A' => 1, 'B' => 2, 'Z' => 3, 'AA' => 4, 'AB' => 5]; uksort($columns, fn ($a, $b) => $this->isColumnLE($a, $b) ? -1 : 1); $this->assertSame( ['A' => 1, 'B' => 2, 'Z' => 3, 'AA' => 4, 'AB' => 5, 'AZ' => 6, 'BA' => 7], $columns ); } }