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();
+ }
+}