diff --git a/.idea/nur-sery.iml b/.idea/nur-sery.iml index 93a8f92..8f5e5e3 100644 --- a/.idea/nur-sery.iml +++ b/.idea/nur-sery.iml @@ -6,6 +6,7 @@ + diff --git a/.idea/php.xml b/.idea/php.xml index 136d7e5..a6c4286 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -45,6 +45,17 @@ + + + + + + + + + + + diff --git a/.idea/phpspec.xml b/.idea/phpspec.xml index 6827d4d..ec7e1d4 100644 --- a/.idea/phpspec.xml +++ b/.idea/phpspec.xml @@ -5,6 +5,9 @@ + + - + \ No newline at end of file diff --git a/composer.json b/composer.json index f286346..9363e80 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ }, "require-dev": { "nulib/tests": "7.4", + "phpoffice/phpspreadsheet": "^1.0", "ext-posix": "*", "ext-pcntl": "*", "ext-fileinfo": "*", diff --git a/composer.lock b/composer.lock index adeb2c2..b73565a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d2fbaee267973746e8e9db107bf8eeb5", + "content-hash": "c951b0cf9bd2f2bab34a46242331eb88", "packages": [ { "name": "symfony/deprecation-contracts", @@ -154,16 +154,16 @@ }, { "name": "symfony/yaml", - "version": "v5.4.35", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "e78db7f5c70a21f0417a31f414c4a95fe76c07e4" + "reference": "81cad0ceab3d61fe14fe941ff18a230ac9c80f83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/e78db7f5c70a21f0417a31f414c4a95fe76c07e4", - "reference": "e78db7f5c70a21f0417a31f414c4a95fe76c07e4", + "url": "https://api.github.com/repos/symfony/yaml/zipball/81cad0ceab3d61fe14fe941ff18a230ac9c80f83", + "reference": "81cad0ceab3d61fe14fe941ff18a230ac9c80f83", "shasum": "" }, "require": { @@ -209,7 +209,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.35" + "source": "https://github.com/symfony/yaml/tree/v5.4.40" }, "funding": [ { @@ -225,7 +225,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T13:51:25+00:00" + "time": "2024-05-31T14:33:22+00:00" } ], "packages-dev": [ @@ -299,6 +299,252 @@ ], "time": "2022-12-30T00:15:36+00:00" }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.17.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c", + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c", + "shasum": "" + }, + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" + }, + "type": "library", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.17.0" + }, + "time": "2023-11-17T15:01:25+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "2.2.6", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f", + "reference": "30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f", + "shasum": "" + }, + "require": { + "myclabs/php-enum": "^1.5", + "php": "^7.4 || ^8.0", + "psr/http-message": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.9", + "guzzlehttp/guzzle": "^6.5.3 || ^7.2.0", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.4", + "phpunit/phpunit": "^8.5.8 || ^9.4.2", + "vimeo/psalm": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.2.6" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + }, + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "time": "2022-11-25T18:57:19+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.11.1", @@ -358,6 +604,69 @@ ], "time": "2023-03-08T13:26:56+00:00" }, + { + "name": "myclabs/php-enum", + "version": "1.8.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/a867478eae49c9f59ece437ae7f9506bfaa27483", + "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "http://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.4" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2022-08-04T09:53:51+00:00" + }, { "name": "nikic/php-parser", "version": "v5.0.2", @@ -566,6 +875,111 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.29.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fde2ccf55eaef7e86021ff1acce26479160a0fa0", + "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.15", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^7.4 || ^8.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^1.0 || ^2.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.3", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0 || ^10.0", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.0" + }, + "time": "2023-06-14T22:48:31+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "9.2.31", @@ -988,6 +1402,217 @@ ], "time": "2024-04-05T04:35:58+00:00" }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/1.1" + }, + "time": "2023-04-04T09:50:52+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, { "name": "sebastian/cli-parser", "version": "1.0.2", @@ -1951,6 +2576,86 @@ ], "time": "2020-09-28T06:39:44+00:00" }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, { "name": "theseer/tokenizer", "version": "1.2.3", diff --git a/src/ext/spreadsheet/ssutils.php b/src/ext/spreadsheet/ssutils.php new file mode 100644 index 0000000..8755049 --- /dev/null +++ b/src/ext/spreadsheet/ssutils.php @@ -0,0 +1,14 @@ +getAllSheets() as $ws) { + $max_coords[$ws->getTitle()] = wsutils::compute_max_coords($ws); + } + return $max_coords; + } +} diff --git a/src/ext/spreadsheet/wsutils.php b/src/ext/spreadsheet/wsutils.php new file mode 100644 index 0000000..dad2dd9 --- /dev/null +++ b/src/ext/spreadsheet/wsutils.php @@ -0,0 +1,59 @@ +getHighestRow(); + $highestColumnA = $ws->getHighestColumn(); + $highestCol = Coordinate::columnIndexFromString($highestColumnA); + return [$highestRow, $highestCol]; + } + + /** + * @var int nombre de colonnes/lignes au bout desquels on arrête de chercher + * si on n'a trouvé que des cellules vides. + * + * c'est nécessaire à cause de certains fichiers provenant d'Excel que j'ai + * reçus qui ont jusqu'à 10000 colonne vides et/ou 1048576 lignes vides. un + * algorithme "bête" perd énormément de temps à chercher dans le vide, donnant + * l'impression que le processus a planté. + */ + const MAX_EMPTY_THRESHOLD = 150; + + static function compute_max_coords(Worksheet $ws): array { + [$highestRow, $highestCol] = self::get_highest_coords($ws); + + $maxRow = 1; + $maxCol = 1; + $maxEmptyRows = self::MAX_EMPTY_THRESHOLD; + for ($row = 1; $row <= $highestRow; $row++) { + $emptyRow = true; + $maxEmptyCols = self::MAX_EMPTY_THRESHOLD; + for ($col = 1; $col <= $highestCol; $col++) { + $value = null; + if ($ws->cellExists([$col, $row])) { + $value = $ws->getCell([$col, $row])->getValue(); + } + if ($value === null) { + $maxEmptyCols--; + if ($maxEmptyCols == 0) break; + } else { + $maxEmptyCols = self::MAX_EMPTY_THRESHOLD; + if ($row > $maxRow) $maxRow = $row; + if ($col > $maxCol) $maxCol = $col; + $emptyRow = false; + } + } + if ($emptyRow) { + $maxEmptyRows--; + if ($maxEmptyRows == 0) break; + } else { + $maxEmptyRows = self::MAX_EMPTY_THRESHOLD; + } + } + return [$maxRow, $maxCol]; + } +} diff --git a/src/file/csv/AbstractBuilder.php b/src/file/csv/AbstractBuilder.php new file mode 100644 index 0000000..847312d --- /dev/null +++ b/src/file/csv/AbstractBuilder.php @@ -0,0 +1,132 @@ +schema = $params["schema"] ?? static::SCHEMA; + $this->headers = $params["headers"] ?? static::HEADERS; + $rows = $params["rows"] ?? null; + if (is_callable($rows)) $rows = $rows(); + $this->rows = $rows; + $cookFunc = $params["cook_func"] ?? null; + $cookCtx = $cookArgs = null; + if ($cookFunc !== null) { + func::ensure_func($cookFunc, $this, $cookArgs); + $cookCtx = func::_prepare($cookFunc); + } + $this->cookCtx = $cookCtx; + $this->cookArgs = $cookArgs; + $this->output = $params["output"] ?? static::OUTPUT; + $maxMemory = $params["max_memory"] ?? null; + $throwOnError = $params["throw_on_error"] ?? null; + parent::__construct($maxMemory, $throwOnError); + } + + protected ?array $schema; + + protected ?array $headers; + + protected ?iterable $rows; + + protected ?string $output; + + protected ?array $cookCtx; + + protected ?array $cookArgs; + + protected function ensureHeaders(?array $row=null): void { + if ($this->headers !== null) return; + if ($this->schema === null) $headers = null; + else $headers = array_keys($this->schema); + if ($headers === null && $row !== null) $headers = array_keys($row); + $this->headers = $headers; + } + + protected abstract function _write(array $row): void; + + protected bool $wroteHeaders = false; + + function writeHeaders(?array $headers=null): void { + if ($this->wroteHeaders) return; + if ($headers !== null) $this->headers = $headers; + else $this->ensureHeaders(); + if ($this->headers !== null) $this->_write($this->headers); + $this->wroteHeaders = true; + } + + protected function cookRow(?array $row): ?array { + if ($this->cookCtx !== null) { + $args = cl::merge([$row], $this->cookArgs); + $row = func::_call($this->cookCtx, $args); + } + if ($row !== null) { + foreach ($row as &$value) { + # formatter les dates + if ($value instanceof DateTime) { + $value = $value->format(); + } elseif ($value instanceof DateTimeInterface) { + $value = DateTime::with($value)->format(); + } + }; unset($value); + } + return $row; + } + + function write(?array $row): void { + $row = $this->cookRow($row); + if ($row === null) return; + $this->writeHeaders(array_keys($row)); + $this->_write($row); + } + + function writeAll(?iterable $rows=null): void { + $unsetRows = false; + if ($rows === null) { + $rows = $this->rows; + $unsetRows = true; + } + if ($rows !== null) { + foreach ($rows as $row) { + $this->write($row); + } + } + if ($unsetRows) $this->rows = null; + } + + abstract protected function _sendContentType(): void; + + protected bool $sentHeaders = false; + + function sendHeaders(): void { + if ($this->sentHeaders) return; + $this->_sendContentType(); + $output = $this->output; + if ($output !== null) http::download_as($output); + $this->sentHeaders = true; + } + + abstract protected function _sendFile(): int; + + function sendFile(?iterable $rows=null): int { + $this->writeAll($rows); + $this->writeHeaders(); + return $this->_sendFile(); + } +} diff --git a/src/file/csv/CsvBuilder.php b/src/file/csv/CsvBuilder.php index 5d4efe9..cef42bd 100644 --- a/src/file/csv/CsvBuilder.php +++ b/src/file/csv/CsvBuilder.php @@ -1,18 +1,13 @@ csvFlavour = csv_flavours::verifix($csvFlavour); - $this->schema = $params["schema"] ?? static::SCHEMA; - $this->headers = $params["headers"] ?? static::HEADERS; - $rows = $params["rows"] ?? null; - if (is_callable($rows)) $rows = $rows(); - $this->rows = $rows; - $cookFunc = $params["cook_func"] ?? null; - $cookCtx = $cookArgs = null; - if ($cookFunc !== null) { - func::ensure_func($cookFunc, $this, $cookArgs); - $cookCtx = func::_prepare($cookFunc); - } - $this->cookCtx = $cookCtx; - $this->cookArgs = $cookArgs; - $this->output = $params["output"] ?? static::OUTPUT; - $maxMemory = $params["max_memory"] ?? null; - $throwOnError = $params["throw_on_error"] ?? null; - parent::__construct($maxMemory, $throwOnError); + parent::__construct($output, $params); } - protected ?array $schema; - - protected ?array $headers; - - protected ?iterable $rows; - - protected ?string $output; - - protected ?array $cookCtx; - - protected ?array $cookArgs; - - protected function ensureHeaders(?array $row=null): void { - if ($this->headers !== null) return; - if ($this->schema === null) $headers = null; - else $headers = array_keys($this->schema); - if ($headers === null && $row !== null) $headers = array_keys($row); - $this->headers = $headers; - } - - protected bool $wroteHeaders = false; - - function writeHeaders(?array $headers=null): void { - if ($this->wroteHeaders) return; - if ($headers !== null) $this->headers = $headers; - else $this->ensureHeaders(); - if ($this->headers !== null) $this->fputcsv($this->headers); - $this->wroteHeaders = true; - } - - protected function cookRow(?array $row): ?array { - if ($this->cookCtx !== null) { - $args = cl::merge([$row], $this->cookArgs); - $row = func::_call($this->cookCtx, $args); - } - if ($row !== null) { - foreach ($row as &$value) { - # formatter les dates - if ($value instanceof DateTime) { - $value = $value->format(); - } elseif ($value instanceof DateTimeInterface) { - $value = DateTime::with($value)->format(); - } - }; unset($value); - } - return $row; - } - - function write(?array $row): void { - $row = $this->cookRow($row); - if ($row === null) return; - $this->writeHeaders(array_keys($row)); + protected function _write(array $row): void { $this->fputcsv($row); } - function writeAll(?iterable $rows=null): void { - $unsetRows = false; - if ($rows === null) { - $rows = $this->rows; - $unsetRows = true; - } - if ($rows !== null) { - foreach ($rows as $row) { - $this->write($row); - } - } - if ($unsetRows) $this->rows = null; - } - - protected bool $sentHeaders = false; - - function sendHeaders(): void { - if ($this->sentHeaders) return; + function _sendContentType(): void { http::content_type("text/csv"); - $output = $this->output; - if ($output !== null) http::download_as($output); - $this->sentHeaders = true; } - function sendFile(?iterable $rows=null): int { - $this->writeAll($rows); - $this->writeHeaders(); + protected function _sendFile(): int { $size = $this->ftell(); if ($size === 0) return 0; $this->rewind(); diff --git a/src/file/csv/SpreadsheetBuilder.php b/src/file/csv/SpreadsheetBuilder.php new file mode 100644 index 0000000..5cffa1b --- /dev/null +++ b/src/file/csv/SpreadsheetBuilder.php @@ -0,0 +1,123 @@ +ss = new Spreadsheet(); + $this->valueBinder = new StringValueBinder(); + $this->wsname = $params["wsname"] ?? null; + parent::__construct($output, $params); + } + + protected Spreadsheet $ss; + + protected IValueBinder $valueBinder; + + protected ?string $wsname; + + protected ?Worksheet $ws = null; + + protected function ws(): Worksheet { + $ws = $this->ws; + if ($ws !== null) return $ws; + $ss = $this->ss; + $wsname = $this->wsname; + if ($wsname === null) { + $ws = $ss->getActiveSheet(); + } else { + $ws = $ss->getSheetByName($wsname); + if ($ws === null) { + $ws = $ss->createSheet()->setTitle($wsname); + } + } + return $this->ws = $ws; + } + + protected int $nrow = 0; + + const STYLE_HEADER = 0, STYLE_ROW = 1; + + protected int $style = self::STYLE_ROW; + + function _write(array $row): void { + $ws = $this->ws(); + $nrow = ++$this->nrow; + $ncol = 1; + foreach ($row as $col) { + //$cell = $ws->getCell([$ncol++, $nrow]); + $cell = $ws->getCellByColumnAndRow($ncol++, $nrow); + $cell->setValue($col, $this->valueBinder); + switch ($this->style) { + case self::STYLE_HEADER: + $cell->getStyle()->getFont()->setBold(true); + break; + case self::STYLE_ROW: + break; + } + } + } + + function writeHeaders(?array $headers=null): void { + $this->style = self::STYLE_HEADER; + parent::writeHeaders($headers); + $this->style = self::STYLE_ROW; + } + + function _sendContentType(): void { + switch (path::ext($this->output)) { + case ".ods": + $contentType = "application/vnd.oasis.opendocument.spreadsheet"; + break; + case ".xls": + $contentType = "application/vnd.ms-excel"; + break; + case ".xlsx": + default: + $contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + break; + } + http::content_type($contentType); + } + + function _sendFile(): int { + switch (path::ext($this->output)) { + case ".ods": + $writer = new Ods($this->ss); + break; + case ".xls": + $writer = new Xls($this->ss); + break; + case ".xlsx": + default: + $writer = new Xlsx($this->ss); + break; + } + $this->rewind(); + $writer->save($this->getResource()); + $this->rewind(); + $this->sendHeaders(); + return $this->fpassthru(); + } +}