Compare commits

...

35 Commits

Author SHA1 Message Date
6e58b99069 maj deps 2025-02-18 12:17:16 +04:00
664a713c0a setSheet peut prendre une instance de params 2025-02-01 11:02:36 +04:00
c8db7095c9 forcer wrap=false par défaut 2025-02-01 10:41:55 +04:00
702ef1cb4f bug 2025-02-01 10:35:57 +04:00
967319ff0b support merge_offset 2025-01-24 18:22:05 +04:00
1d2d8686c4 support de merge_cells dans rowParams 2025-01-24 18:05:49 +04:00
51ebba1ab7 maj du nom de la branche 2025-01-24 17:11:02 +04:00
11724eef98 implémenter la fusion des cellules 2025-01-24 17:08:57 +04:00
099cdcfc3e typo 2025-01-23 16:07:12 +04:00
65a9876d3d renommer wrap_text en wrap 2025-01-23 15:45:16 +04:00
7d699ee61e cosmetic 2025-01-23 10:09:41 +04:00
747f61a03d maj ref 2025-01-23 09:49:07 +04:00
2b4dff08e2 ajout _merge2php82 2025-01-23 07:02:12 +04:00
a9df7e7c30 typo 2025-01-22 11:43:01 +04:00
b6f35877ae modifs.mineures sans commentaires 2024-11-30 11:22:39 +04:00
fd68d0ecd5 support hauteur de ligne 2024-11-30 11:18:58 +04:00
60dfb1e177 modifs.mineures sans commentaires 2024-11-30 06:44:08 +04:00
19a3cb9b76 maj even/odd 2024-11-30 06:08:15 +04:00
71ca020b1e suite paramètres 2024-11-30 05:59:20 +04:00
4468c648da suites paramètres writer 2024-11-30 05:45:27 +04:00
2fb49a110e réorganiser le code 2024-11-30 05:17:34 +04:00
0c87ff1a7b améliorer gestion bordures 2024-11-29 19:39:14 +04:00
8b79df5a75 cosmetic 2024-11-29 19:30:26 +04:00
d2c6d51bdb gestion des bordures améliorée 2024-11-29 19:06:14 +04:00
4865e6a1fc index spécifique pour odd/even 2024-11-29 18:38:04 +04:00
23ded28680 gestion de la différence odd/even 2024-11-29 18:02:28 +04:00
f9fd6b838e maj projet 2024-11-29 16:52:27 +04:00
ed34b1e093 maj traitement des dates 2024-11-29 16:52:20 +04:00
8258dd53b5 support du formatage des lignes et colonnes 2024-11-29 16:01:17 +04:00
bda119cbed application des modifs nulib/spout pour v3.7.4 2024-11-29 13:36:36 +04:00
d416e6799b modifs.mineures sans commentaires 2024-11-28 21:36:37 +04:00
9f2bc810e4 maj projet 2024-11-28 20:39:08 +04:00
958a9c9090 maj deps 2024-11-28 20:07:48 +04:00
075374015c merge upstream 3.4.7 2024-11-27 13:50:04 +04:00
80850f0540 importation initiale 2024-11-27 13:49:42 +04:00
30 changed files with 5754 additions and 4 deletions

6
.composer.yaml Normal file
View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 mode: yaml -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
require:
nulib/php: ^7.4-dev
branch:
develop:
master:

0
.dockerignore Normal file
View File

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
.~lock*#
.*.swp
/vendor/
/.idea/shelf/
/.idea/workspace.xml
/.idea/httpRequests/
/.idea/dataSources/
/.idea/dataSources.local.xml
/.phpunit.result.cache

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,12 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="GrazieInspection" enabled="false" level="GRAMMAR_ERROR" enabled_by_default="false" />
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

17
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state>
<expanded-state>
<State />
</expanded-state>
<selected-state>
<State>
<id>Angular</id>
</State>
</selected-state>
</profile-state>
</entry>
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/nulib-spout.iml" filepath="$PROJECT_DIR$/.idea/nulib-spout.iml" />
</modules>
</component>
</project>

13
.idea/nulib-spout.iml generated Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="nulib\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="nulib\ext\" />
<sourceFolder url="file://$MODULE_DIR$/upstream-3.x/src" isTestSource="false" packagePrefix="OpenSpout\" />
<excludeFolder url="file://$MODULE_DIR$/vendor" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

23
.idea/php-docker-settings.xml generated Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpDockerContainerSettings">
<list>
<map>
<entry key="125ffb9d-fd5f-4e71-8182-94191665795a">
<value>
<DockerContainerSettings>
<option name="version" value="1" />
<option name="volumeBindings">
<list>
<DockerVolumeBindingImpl>
<option name="containerPath" value="/opt/project" />
</DockerVolumeBindingImpl>
</list>
</option>
</DockerContainerSettings>
</value>
</entry>
</map>
</list>
</component>
</project>

14
.idea/php-test-framework.xml generated Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpTestFrameworkVersionCache">
<tools_cache>
<tool tool_name="PHPUnit">
<cache>
<versions>
<info id="Local/vendor/autoload.php" version="9.6.21" />
</versions>
</cache>
</tool>
</tools_cache>
</component>
</project>

107
.idea/php.xml generated Normal file
View File

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpCSFixer">
<phpcsfixer_settings>
<PhpCSFixerConfiguration tool_path="$PROJECT_DIR$/vendor/bin/php-cs-fixer" />
</phpcsfixer_settings>
</component>
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
<path value="$PROJECT_DIR$/vendor/sebastian/type" />
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
<path value="$PROJECT_DIR$/vendor/symfony/string" />
<path value="$PROJECT_DIR$/vendor/symfony/process" />
<path value="$PROJECT_DIR$/vendor/sebastian/resource-operations" />
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
<path value="$PROJECT_DIR$/vendor/friendsofphp/php-cs-fixer" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/vendor/phar-io/version" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpstan-phpunit" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpstan" />
<path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit" />
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/vendor/psr/log" />
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
<path value="$PROJECT_DIR$/vendor/clue/ndjson-react" />
<path value="$PROJECT_DIR$/vendor/evenement/evenement" />
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
<path value="$PROJECT_DIR$/vendor/react/promise" />
<path value="$PROJECT_DIR$/vendor/react/cache" />
<path value="$PROJECT_DIR$/vendor/fidry/cpu-core-counter" />
<path value="$PROJECT_DIR$/vendor/react/dns" />
<path value="$PROJECT_DIR$/vendor/react/child-process" />
<path value="$PROJECT_DIR$/vendor/react/event-loop" />
<path value="$PROJECT_DIR$/vendor/react/socket" />
<path value="$PROJECT_DIR$/vendor/react/stream" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<path value="$PROJECT_DIR$/vendor/symfony/console" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php81" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php73" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/finder" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php80" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
<path value="$PROJECT_DIR$/vendor/symfony/filesystem" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/options-resolver" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/symfony/stopwatch" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/nulib/tests" />
<path value="$PROJECT_DIR$/vendor/nulib/php" />
<path value="$PROJECT_DIR$/vendor/symfony/yaml" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="7.4">
<option name="suggestChangeDefaultLanguageLevel" value="false" />
</component>
<component name="PhpStan">
<PhpStan_settings>
<PhpStanConfiguration tool_path="$PROJECT_DIR$/vendor/bin/phpstan" />
</PhpStan_settings>
</component>
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" phpunit_phar_path="" />
</phpunit_settings>
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>

10
.idea/phpunit.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PHPUnit">
<option name="directories">
<list>
<option value="$PROJECT_DIR$/tests" />
</list>
</option>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

8
.runphp.conf Normal file
View File

@ -0,0 +1,8 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
# Chemin vers runphp, e.g sbin/runphp
RUNPHP=
# Si RUNPHP n'est pas défini, les variables suivantes peuvent être définies
DIST=d11
#REGISTRY=pubdocker.univ-reunion.fr/dist

25
_merge2php82 Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
mydir="$(cd "$(dirname -- "$0")"; pwd)"
myself="$mydir/$(basename -- "$0")"
cwd="$(pwd)"
if [ "$1" != --stage2 -o -z "$2" ]; then
cp "$myself" /tmp/merge2php82.sh
exec /tmp/merge2php82.sh --stage2 "$mydir"
fi
cd "$2" || die
if [ -f vendor/nulib/php/load.sh ]; then
source ./vendor/nulib/php/load.sh || exit 1
else
source /etc/nulib.sh || exit 1
fi
git checkout php82
git rebase php74 ||
die "Le rebase automatique a échoué. Après avoir résolu les conflits, faire
git checkout php74
pp -af
"
git checkout php74
pp -af

62
composer.json Normal file
View File

@ -0,0 +1,62 @@
{
"name": "nulib/spout",
"type": "library",
"description": "wrapper pour openspout/openspout",
"repositories": [
{
"type": "path",
"url": "../nulib"
},
{
"type": "composer",
"url": "https://repos.univ-reunion.fr/composer"
}
],
"extra": {
"branch-alias": {
"dev-php74": "7.4.x-dev",
"dev-php82": "8.2.x-dev"
}
},
"replace": {
"openspout/openspout": "v3.7.4"
},
"require": {
"nulib/php": "^7.4-dev",
"ext-dom": "*",
"ext-filter": "*",
"ext-libxml": "*",
"ext-xmlreader": "*",
"ext-zip": "*",
"php": "^7.4"
},
"require-dev": {
"nulib/tests": "^7.4",
"friendsofphp/php-cs-fixer": "^3.4",
"phpstan/phpstan": "^1.4",
"phpstan/phpstan-phpunit": "^1.0",
"ext-zlib": "*"
},
"autoload": {
"psr-4": {
"nulib\\": "src",
"OpenSpout\\": "upstream-3.x/src"
}
},
"autoload-dev": {
"psr-4": {
"nulib\\ext\\": "tests"
}
},
"authors": [
{
"name": "Jephte Clain",
"email": "Jephte.Clain@univ-reunion.fr"
}
],
"config": {
"allow-plugins": {
"infection/extension-installer": false
}
}
}

4583
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

77
patches/v3.7.4.patch Normal file
View File

@ -0,0 +1,77 @@
diff --git a/src/Writer/WriterAbstract.php b/src/Writer/WriterAbstract.php
index fd7c472..a2a5e37 100644
--- a/src/Writer/WriterAbstract.php
+++ b/src/Writer/WriterAbstract.php
@@ -22,6 +22,9 @@ abstract class WriterAbstract implements WriterInterface
/** @var resource Pointer to the file/stream we will write to */
protected $filePointer;
+ /** @var bool faut-il garder ouvert le flux quand close() est appelé? */
+ protected $dontCloseFilePointer = false;
+
/** @var bool Indicates whether the writer has been opened or not */
protected $isWriterOpened = false;
@@ -57,6 +60,20 @@ abstract class WriterAbstract implements WriterInterface
return $this;
}
+ public function writeToStream($filePointer)
+ {
+ $this->outputFilePath = null;
+
+ $this->filePointer = $filePointer;
+ $this->dontCloseFilePointer = true;
+ $this->throwIfFilePointerIsNotAvailable();
+
+ $this->openWriter();
+ $this->isWriterOpened = true;
+
+ return $this;
+ }
+
/**
* {@inheritdoc}
*/
@@ -177,7 +194,7 @@ abstract class WriterAbstract implements WriterInterface
$this->closeWriter();
- if (\is_resource($this->filePointer)) {
+ if (!$this->dontCloseFilePointer && \is_resource($this->filePointer)) {
$this->globalFunctionsHelper->fclose($this->filePointer);
}
diff --git a/src/Reader/XLSX/Helper/CellValueFormatter.php b/src/Reader/XLSX/Helper/CellValueFormatter.php
index 1734fb5..08e5282 100644
--- a/src/Reader/XLSX/Helper/CellValueFormatter.php
+++ b/src/Reader/XLSX/Helper/CellValueFormatter.php
@@ -268,9 +268,13 @@ class CellValueFormatter
$dateObj->modify('+'.$secondsRemainder.'seconds');
if ($this->shouldFormatDates) {
- $styleNumberFormatCode = $this->styleManager->getNumberFormatCode($cellStyleId);
- $phpDateFormat = DateFormatHelper::toPHPDateFormat($styleNumberFormatCode);
+ //$styleNumberFormatCode = $this->styleManager->getNumberFormatCode($cellStyleId);
+ //$phpDateFormat = DateFormatHelper::toPHPDateFormat($styleNumberFormatCode);
+ // Toujours utiliser le format français complet
+ $phpDateFormat = "d/m/Y H:i:s";
$cellValue = $dateObj->format($phpDateFormat);
+ // Enlever la composante heure si elle n'existe pas
+ $cellValue = preg_replace('/ 00:00:00$/', "", $cellValue);
} else {
$cellValue = $dateObj;
}
diff --git a/src/Reader/XLSX/Manager/OptionsManager.php b/src/Reader/XLSX/Manager/OptionsManager.php
index b04b92c..5749f65 100644
--- a/src/Reader/XLSX/Manager/OptionsManager.php
+++ b/src/Reader/XLSX/Manager/OptionsManager.php
@@ -29,7 +29,7 @@ class OptionsManager extends OptionsManagerAbstract
protected function setDefaultOptions()
{
$this->setOption(Options::TEMP_FOLDER, sys_get_temp_dir());
- $this->setOption(Options::SHOULD_FORMAT_DATES, false);
+ $this->setOption(Options::SHOULD_FORMAT_DATES, true);
$this->setOption(Options::SHOULD_PRESERVE_EMPTY_ROWS, false);
$this->setOption(Options::SHOULD_USE_1904_DATES, false);
}

0
src/.gitignore vendored Normal file
View File

View File

@ -0,0 +1,465 @@
<?php
namespace nulib\ext\spout;
use nulib\cl;
use nulib\file\tab\AbstractBuilder;
use nulib\file\tab\TAbstractBuilder;
use nulib\os\path;
use nulib\php\func;
use nulib\php\nur_func;
use nulib\php\time\Date;
use nulib\php\time\DateTime;
use nulib\ref\ext\spout\ref_builder;
use nulib\ref\ext\spout\ref_builder_ods;
use nulib\ref\ext\spout\ref_builder_xlsx;
use nulib\str;
use nulib\web\http;
use OpenSpout\Common\Entity\Cell;
use OpenSpout\Common\Entity\Style\Border;
use OpenSpout\Common\Entity\Style\BorderPart;
use OpenSpout\Common\Entity\Style\Style;
use OpenSpout\Common\Helper\CellTypeHelper;
use OpenSpout\Writer\Common\Creator\WriterEntityFactory;
use OpenSpout\Writer\WriterMultiSheetsAbstract;
use OpenSpout\Writer\XLSX\Entity\SheetView;
class SpoutBuilder extends AbstractBuilder {
use TAbstractBuilder;
protected static function apply_params($object, ?array $params, array $refParams) {
foreach (array_keys($refParams) as $method) {
if (!str::starts_with("->", $method)) continue;
$func = func::with([$object, $method]);
if (($args = $params[$method] ?? null) !== null) {
$func->invoke(cl::with($args));
}
if (($argss = $params["$method*"] ?? null) !== null) {
foreach ($argss as $args) {
$func->invoke(cl::with($args));
}
}
}
return $object;
}
protected static function add_border_part(?Border &$border, string $name, ?array $params): void {
if ($params === null) return;
if ($border === null) $border = new Border();
$part = new BorderPart($name);
if (($color = $params["color"] ?? null) !== null) {
$part->setColor(cl::get(ref_builder::COLORS, $color, $color));
}
if (($width = $params["width"] ?? null) !== null) $part->setWidth($width);
if (($style = $params["style"] ?? null) !== null) $part->setStyle($style);
$border->addPart($part);
}
protected static function set_defaults(?array &$params, string $key, array $defaults): void {
if ($params !== null && array_key_exists($key, $params)) {
if ($params[$key] === false) $params[$key] = null;
else $params[$key] ??= $defaults;
} else {
$params[$key] ??= $defaults;
}
}
protected static function ensure_style(&$style): ?Style {
if ($style === null) return null;
if ($style instanceof Style) return $style;
$cell = $style;
$style = new Style();
$font = $cell["font"] ?? null;
if ($font["bold"] ?? null) $style->setFontBold();
if ($font["italic"] ?? null) $style->setFontItalic();
if ($font["underline"] ?? null) $style->setFontUnderline();
if ($font["strikethrough"] ?? null) $style->setFontStrikethrough();
if (($name = $font["name"] ?? null) !== null) $style->setFontName($name);
if (($size = $font["size"] ?? null) !== null) $style->setFontSize($size);
if (($color = $font["color"] ?? null) !== null) {
$style->setFontColor(cl::get(ref_builder::COLORS, $color, $color));
}
if (($color = $cell["bg_color"] ?? null) !== null) {
$style->setBackgroundColor(cl::get(ref_builder::COLORS, $color, $color));
}
if (($align = $cell["align"] ?? null) !== null) $style->setCellAlignment($align);
//if (($align = $cell["valign"] ?? null) !== null) $style->setCellVerticalAlignment($align);
if (($wrap = $cell["wrap"] ?? null) !== null) $style->setShouldWrapText($wrap);
if (($format = $cell["format"] ?? null) !== null) $style->setFormat($format);
if (($border = $cell["border"] ?? null) !== null) {
if (is_string($border)) {
$parts = explode(" ", $border);
$border = [];
$styleAll = null;
$widthAll = null;
$colorAll = null;
foreach ($parts as $part) {
if ($part === "all") {
$border["left"] = [];
$border["top"] = [];
$border["right"] = [];
$border["bottom"] = [];
} elseif (preg_match('/^(left|top|right|bottom)$/', $part)) {
$border[$part] = [];
} elseif (preg_match('/^(none|solid|dashed|dotted|double)$/', $part)) {
$styleAll = $part;
} elseif (preg_match('/^(thin|medium|thick)$/', $part)) {
$widthAll = $part;
} else {
$colorAll = $part;
}
}
foreach ($border as &$part) {
if ($styleAll !== null) $part["style"] = $styleAll;
if ($widthAll !== null) $part["width"] = $widthAll;
if ($colorAll !== null) $part["color"] = $colorAll;
}; unset($part);
}
$top = $border["top"] ?? null;
$right = $border["right"] ?? null;
$bottom = $border["bottom"] ?? null;
$left = $border["left"] ?? null;
$border = null;
self::add_border_part($border, "top", $top);
self::add_border_part($border, "right", $right);
self::add_border_part($border, "bottom", $bottom);
self::add_border_part($border, "left", $left);
if ($border !== null) $style->setBorder($border);
}
return $style;
}
const DATE_FORMAT = "dd/mm/yyyy";
const DATETIME_FORMAT = "dd/mm/yyyy hh:mm:ss";
/** @var bool faut-il choisir le type numérique pour une chaine numérique? */
const TYPE_NUMERIC = true;
/** @var bool faut-il choisir le type date pour une chaine au bon format? */
const TYPE_DATE = true;
/** @var array configuration du writer */
const SPOUT_PARAMS = null;
/** @var array configuration de la première feuille */
const SHEET_PARAMS = null;
/** @var string nom de la première feuille */
const SHEET_NAME = null;
/** @var array configuration de la vue de la première feuille */
const SHEET_VIEW_PARAMS = null;
function __construct(?string $output, ?array $params=null) {
parent::__construct($output, $params);
$ssType = $params["ss_type"] ?? null;
if ($ssType === null) {
switch (path::ext($this->output)) {
case ".ods":
$ssType = self::SS_TYPE_ODS;
break;
case ".xlsx":
default:
$ssType = self::SS_TYPE_XLSX;
break;
}
}
$spoutParams = $params["spout"] ?? static::SPOUT_PARAMS;
$spoutParams["default_column_width"] ??= 10.5;
self::ensure_style($spoutParams["default_row_style"]);
switch ($ssType) {
case "ods":
case self::SS_TYPE_ODS:
$ssType = self::SS_TYPE_ODS;
$ssWriter = WriterEntityFactory::createODSWriter();
self::apply_params($ssWriter, $spoutParams, ref_builder_ods::PARAMS_SPOUT);
break;
case "xlsx":
case self::SS_TYPE_XLSX:
default:
$ssType = self::SS_TYPE_XLSX;
$ssWriter = WriterEntityFactory::createXLSXWriter();
self::apply_params($ssWriter, $spoutParams, ref_builder_xlsx::PARAMS_SPOUT);
break;
}
$defaultColumnWidth = $spoutParams["default_column_width"] ?? null;
if ($defaultColumnWidth !== null) $ssWriter->setDefaultColumnWidth($defaultColumnWidth);
$defaultRowHeight = $spoutParams["default_row_height"] ?? null;
if ($defaultRowHeight !== null) $ssWriter->setDefaultRowHeight($defaultRowHeight);
$defaultRowStyle = $spoutParams["default_row_style"] ?? null;
if ($defaultRowStyle !== null) $ssWriter->setDefaultRowStyle($defaultRowStyle);
$ssWriter->writeToStream($this->getResource());
$this->ssType = $ssType;
$this->ssWriter = $ssWriter;
$this->spoutParams = $spoutParams;
$this->typeNumeric = boolval($params["type_numeric"] ?? static::TYPE_NUMERIC);
$this->typeDate = boolval($params["type_date"] ?? static::TYPE_DATE);
$sheetParams = $params["sheet"] ?? static::SHEET_PARAMS;
$sheetName = $params["sheet_name"] ?? static::SHEET_NAME;
if ($sheetName !== null) $sheetParams["->setName"] = $sheetName;
$sheetViewParams = $params["sheet_view"] ?? static::SHEET_VIEW_PARAMS;
if ($sheetViewParams !== null) $sheetParams["view"] = $sheetViewParams;
$this->firstSheet = true;
$this->sheetParams = null;
$this->setSheet(null, $sheetParams);
}
const SS_TYPE_ODS = 1, SS_TYPE_XLSX = 2;
/** @var int type de fichier généré */
protected int $ssType;
protected WriterMultiSheetsAbstract $ssWriter;
protected ?array $spoutParams;
protected bool $typeNumeric;
protected bool $typeDate;
function setParams(?array $params): self {
if ($params !== null) {
if (array_key_exists("type_numeric", $params)) {
$this->typeNumeric = boolval($params["type_numeric"] ?? static::TYPE_NUMERIC);
}
if (array_key_exists("type_date", $params)) {
$this->typeDate = boolval($params["type_date"] ?? static::TYPE_DATE);
}
}
return $this;
}
protected bool $firstSheet;
protected ?array $sheetParams;
const STYLE_ROW = 0, STYLE_HEADER = 1;
protected int $rowStyle;
protected int $currentRow;
protected ?bool $differentOddEven = null;
protected int $oddEvenIndex = 0;
function setDifferentOddEven(bool $differentOddEven, ?bool $startWithOdd=null): self {
$this->differentOddEven = $differentOddEven;
if ($differentOddEven && $startWithOdd !== null) $this->oddEvenIndex = $startWithOdd? 1: 0;
return $this;
}
/**
* @param string|int|null $sheetName
*/
function setSheet($sheetName, ?array $sheetParams=null): self {
$sheet = $params["sheet"] ?? null;
$sheetName = $sheetName ?? $sheetParams["sheet_name"] ?? null;
$sheetViewParams = $sheetParams["sheet_view"] ?? null;
if ($sheet !== null) $sheetParams = $sheet;
if ($sheetName !== null) $sheetParams["->setName"] = $sheetName;
if ($sheetViewParams !== null) $sheetParams["view"] = $sheetViewParams;
$writer = $this->ssWriter;
if ($this->firstSheet) {
$this->firstSheet = false;
$sheet = $writer->getCurrentSheet();
} else {
$sheet = $writer->addNewSheetAndMakeItCurrent();
$this->wroteHeaders = false;
$this->built = false;
}
$this->rowStyle = self::STYLE_ROW;
$this->currentRow = 1;
switch ($this->ssType) {
case self::SS_TYPE_ODS:
# appliquer les paramètres de la feuille
$this->apply_params($sheet, $sheetParams, ref_builder_ods::PARAMS_SHEET);
break;
case self::SS_TYPE_XLSX:
# appliquer les paramètres de la feuille
$this->apply_params($sheet, $sheetParams, ref_builder_xlsx::PARAMS_SHEET);
# appliquer les paramètres de la vue de la feuille
$sheetViewParams =& $sheetParams["view"];
$sheetViewParams["->setFreezeRow"] ??= 2;
$sheet->setSheetView(self::apply_params(new SheetView(), $sheetViewParams, ref_builder_xlsx::PARAMS_SHEET_VIEW));
break;
}
self::set_defaults($sheetParams, "header_style", [
"font" => ["bold" => true],
"bg_color" => "gray",
]);
self::set_defaults($sheetParams, "odd_style", [
"wrap" => false,
]);
self::set_defaults($sheetParams, "even_style", [
"bg_color" => "light_gray",
"wrap" => false,
]);
$this->ensure_style($sheetParams["header_style"]);
$this->ensure_style($sheetParams["odd_style"]);
$this->ensure_style($sheetParams["even_style"]);
$this->sheetParams = $sheetParams;
if ($sheetParams !== null) {
if (array_key_exists("schema", $sheetParams)) {
$this->schema = $sheetParams["schema"] ?? null;
}
if (array_key_exists("headers", $sheetParams)) {
$this->headers = $sheetParams["headers"] ?? null;
}
if (array_key_exists("rows", $sheetParams)) {
$rows = $sheetParams["rows"] ?? null;
if (is_callable($rows)) $rows = $rows();
$this->rows = $rows;
}
if (array_key_exists("cook_func", $sheetParams)) {
$cookFunc = $sheetParams["cook_func"] ?? null;
$cookCtx = $cookArgs = null;
if ($cookFunc !== null) {
nur_func::ensure_func($cookFunc, $this, $cookArgs);
$cookCtx = nur_func::_prepare($cookFunc);
}
$this->cookCtx = $cookCtx;
$this->cookArgs = $cookArgs;
}
}
return $this;
}
/**
* les colonnes sont indexées sur 0 (e.g A = 0, B = 1, etc.)
* Les lignes sont indexées sur 1
*/
function mergeCells(int $topLeftCol, int $topLeftRow, int $bottomRightCol, int $bottomRightRow): void {
$this->ssWriter->mergeCells([$topLeftCol, $topLeftRow], [$bottomRightCol, $bottomRightRow]);
}
protected function isNumeric($value): bool {
if ($this->typeNumeric && is_numeric($value)) return true;
if (!is_string($value) && is_numeric($value)) return true;
return false;
}
protected function isDate(&$value, &$style): bool {
if ($value instanceof Date) {
$style ??= new Style();
$style->setFormat(self::DATE_FORMAT);
return true;
} elseif ($value instanceof DateTime) {
$style ??= new Style();
$style->setFormat(self::DATETIME_FORMAT);
return true;
} elseif (CellTypeHelper::isDateTimeOrDateInterval($value)) {
$style ??= new Style();
$style->setFormat(self::DATE_FORMAT);
return true;
}
if (!is_string($value) || !$this->typeDate) return false;
if (DateTime::isa_datetime($value, true)) {
$value = new DateTime($value);
$style ??= new Style();
$style->setFormat(self::DATETIME_FORMAT);
return true;
}
if (DateTime::isa_date($value, true)) {
$value = new Date($value);
$style ??= new Style();
$style->setFormat(self::DATE_FORMAT);
return true;
}
return false;
}
function _write(array $row, ?array $colStyles=null, ?array $rowStyle=null): void {
$rowParams = null;
if ($rowStyle !== null) {
# séparer rowParams (pour configurer l'instance de $row) et $rowStyle
# (pour appliquer un style sur la ligne)
foreach (array_keys(ref_builder::ROW_PARAMS) as $method) {
$value = $rowStyle[$method] ?? null;
unset($rowStyle[$method]);
if ($value !== null) $rowParams[$method] = $value;
}
if ($rowStyle === []) $rowStyle = null;
}
$sheetParams = $this->sheetParams;
$headerStyle = $sheetParams["header_style"] ?? null;
$oddStyle = $sheetParams["odd_style"] ?? null;
$evenStyle = $sheetParams["even_style"] ?? null;
$differentOddEven = $this->differentOddEven;
$differentOddEven ??= $sheetParams["different_odd_even"] ?? true;
$cells = [];
foreach ($row as $key => $col) {
$style = $colStyles[$key] ?? null;
self::ensure_style($style);
if ($col === null || $col === "") {
$type = Cell::TYPE_EMPTY;
} elseif ($this->isNumeric($col)) {
$type = Cell::TYPE_NUMERIC;
} elseif ($this->isDate($col, $style)) {
$type = Cell::TYPE_DATE;
} else {
$type = Cell::TYPE_STRING;
}
$cell = WriterEntityFactory::createCell($col, $style);
$cell->setType($type);
$cells[] = $cell;
}
if ($this->rowStyle === self::STYLE_HEADER) {
$rowStyle ??= $headerStyle;
} elseif ($differentOddEven && $this->oddEvenIndex % 2 == 0) {
$rowStyle ??= $evenStyle;
}
$rowStyle ??= $oddStyle;
self::ensure_style($rowStyle);
$row = WriterEntityFactory::createRow($cells, $rowStyle);
self::apply_params($row, $rowParams, ref_builder::ROW_PARAMS);
$mergeCells = $rowParams["merge_cells"] ?? null;
$mergeOffset = $rowParams["merge_offset"] ?? 0;
if ($mergeCells !== null) {
$currentRow = $this->currentRow;
foreach ($mergeCells as [$leftCol, $rightCol]) {
$this->mergeCells($leftCol + $mergeOffset, $currentRow, $rightCol + $mergeOffset, $currentRow);
}
}
$this->ssWriter->addRow($row);
$this->currentRow++;
if ($differentOddEven) $this->oddEvenIndex++;
}
function writeHeaders(?array $headers=null): void {
$this->rowStyle = self::STYLE_HEADER;
parent::writeHeaders($headers);
$this->rowStyle = self::STYLE_ROW;
}
function _sendContentType(): void {
switch (path::ext($this->output)) {
case ".ods":
$contentType = "application/vnd.oasis.opendocument.spreadsheet";
break;
case ".xlsx":
default:
$contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
break;
}
http::content_type($contentType);
}
protected function _checkOk(): bool {
$this->ssWriter->close();
$this->rewind();
return true;
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace nulib\ext\spout;
use nulib\cl;
use nulib\file\tab\AbstractReader;
use OpenSpout\Reader\Common\Creator\ReaderEntityFactory;
class SpoutReader extends AbstractReader {
/** @var string|int|null nom de la feuille depuis laquelle lire */
const WSNAME = null;
function __construct($input, ?array $params=null) {
parent::__construct($input, $params);
$this->ssType = $params["ss_type"] ?? null;
$this->allSheets = $params["all_sheets"] ?? true;
$wsname = static::WSNAME;
if ($params !== null && array_key_exists("wsname", $params)) {
# spécifié par l'utilisateur: $allSheets = false
$this->setWsname($params["wsname"]);
} elseif ($wsname !== null) {
# valeur non nulle de la classe: $allSheets = false
$this->setWsname($wsname);
} else {
# pas de valeur définie dans la classe, laisser $allSheets à sa valeur
# actuelle
$this->wsname = null;
}
$this->includeWsnames = cl::withn($params["include_wsnames"] ?? null);
$this->excludeWsnames = cl::withn($params["exclude_wsnames"] ?? null);
}
protected ?string $ssType;
/** @var bool faut-il retourner les lignes de toutes les feuilles? */
protected bool $allSheets;
function setAllSheets(bool $allSheets=true): self {
$this->allSheets = $allSheets;
return $this;
}
/**
* @var array|null si non null, liste de feuilles à inclure. n'est pris en
* compte que si $allSheets===true
*/
protected ?array $includeWsnames;
/**
* @var array|null si non null, liste de feuilles à exclure. n'est pris en
* compte que si $allSheets===true
*/
protected ?array $excludeWsnames;
protected $wsname;
/**
* @param string|int|null $wsname l'unique feuille à sélectionner
*
* NB: appeler cette méthode réinitialise $allSheets à false
*/
function setWsname($wsname): self {
$this->wsname = $wsname;
$this->allSheets = true;
return $this;
}
function getIterator() {
switch ($this->ssType) {
case "ods":
$ss = ReaderEntityFactory::createODSReader();
break;
case "xlsx":
$ss = ReaderEntityFactory::createXLSXReader();
break;
default:
$ss = ReaderEntityFactory::createReaderFromFile($this->input);
break;
}
$ss->open($this->input);
try {
$allSheets = $this->allSheets;
$includeWsnames = $this->includeWsnames;
$excludeWsnames = $this->excludeWsnames;
$wsname = $this->wsname;
$first = true;
foreach ($ss->getSheetIterator() as $ws) {
if ($allSheets) {
$wsname = $ws->getName();
$found = ($includeWsnames === null || in_array($wsname, $includeWsnames))
&& ($excludeWsnames === null || !in_array($wsname, $excludeWsnames));
} else {
$found = $wsname === null || $wsname === $ws->getName();
}
if ($found) {
if ($first) {
$first = false;
} else {
yield null;
# on garde le même schéma le cas échéant, mais supprimer headers
# pour permettre son recalcul
$this->headers = null;
}
$this->isrc = $this->idest = 0;
foreach ($ws->getRowIterator() as $row) {
$row = $row->toArray();
foreach ($row as &$col) {
$this->verifixCol($col);
}; unset($col);
if ($this->cookRow($row)) {
yield $row;
$this->idest++;
}
$this->isrc++;
}
}
}
} finally {
$ss->close();
}
}
}

11
src/ext/tab/SsBuilder.php Normal file
View File

@ -0,0 +1,11 @@
<?php
namespace nulib\ext\tab;
use nulib\ext\spout\SpoutBuilder;
/**
* Class SsBuilder: construction d'une feuille de calcul, pour envoi à
* l'utilisateur
*/
class SsBuilder extends SpoutBuilder {
}

9
src/ext/tab/SsReader.php Normal file
View File

@ -0,0 +1,9 @@
<?php
namespace nulib\ext\tab;
use nulib\ext\spout\SpoutReader;
use nulib\file\tab\TAbstractReader;
class SsReader extends SpoutReader {
use TAbstractReader;
}

View File

@ -0,0 +1,93 @@
<?php
namespace nulib\ref\ext\spout;
use OpenSpout\Common\Entity\Style\Color;
class ref_builder {
const PARAMS = [
# Builder
"output" => "?string",
"schema" => "?array",
"headers" => "?array",
"use_headers" => "bool",
"rows" => "?array",
"cook_func" => "?callable",
"ss_type" => "?string",
# SpoutBuilder
"type_numeric" => "bool",
"type_date" => "bool",
"spout" => self::PARAMS_SPOUT,
"sheet" => self::PARAMS_SHEET,
"sheet_name" => "?string",
"sheet_view" => self::PARAMS_SHEET_VIEW,
# TempStream
"max_memory" => "?int",
"throw_on_error" => "?bool",
];
const PARAMS_SPOUT = [
"->setColumnWidth" => ["float", ["int", null]],
"->setColumnWidthForRange" => ["int", "int", "int"],
"default_column_width" => "float",
"default_row_height" => "float",
"default_row_style" => self::STYLE,
];
const PARAMS_SHEET = [
"view" => self::PARAMS_SHEET_VIEW,
"->setName" => ["string"],
"->setIsVisible" => ["bool"],
"header_style" => self::STYLE,
"odd_style" => self::STYLE,
"even_style" => self::STYLE,
"different_odd_even" => "bool",
];
const PARAMS_SHEET_VIEW = [];
const ROW_PARAMS = [
"->setHeight" => ["float"],
"merge_cells" => "array",
"merge_offset" => "int",
];
const COLORS = [
"black" => Color::BLACK,
"white" => Color::WHITE,
"red" => Color::RED,
"dark_red" => Color::DARK_RED,
"orange" => Color::ORANGE,
"yellow" => Color::YELLOW,
"light_green" => Color::LIGHT_GREEN,
"green" => Color::GREEN,
"light_blue" => Color::LIGHT_BLUE,
"blue" => Color::BLUE,
"dark_blue" => Color::DARK_BLUE,
"purple" => Color::PURPLE,
"light_gray" => "EEEEEE",
"gray" => "B2B2B2",
];
const STYLE = [
"font" => [
"bold" => "bool",
"italic" => "bool",
"underline" => "bool",
"strikethrough" => "bool",
"name" => "string",
"size" => "int",
"color" => "string",
],
"bg_color" => "string",
"align" => "string",
"valign" => "string",
"wrap" => "bool",
"format" => "string",
"border" => [
"top" => ["color" => "string", "width" => "string", "style" => "string"],
"right" => ["color" => "string", "width" => "string", "style" => "string"],
"bottom" => ["color" => "string", "width" => "string", "style" => "string"],
"left" => ["color" => "string", "width" => "string", "style" => "string"],
],
];
}

View File

@ -0,0 +1,5 @@
<?php
namespace nulib\ref\ext\spout;
class ref_builder_ods extends ref_builder {
}

View File

@ -0,0 +1,36 @@
<?php
namespace nulib\ref\ext\spout;
class ref_builder_xlsx extends ref_builder {
const PARAMS_SHEET = [
"view" => self::PARAMS_SHEET_VIEW,
# copie de parent::SHEET
"->setName" => ["string"],
"->setIsVisible" => ["bool"],
"header_style" => self::STYLE,
"odd_style" => self::STYLE,
"even_style" => self::STYLE,
"different_odd_even" => "bool",
];
const PARAMS_SHEET_VIEW = [
"->setFreezeRow" => ["int"],
"->setFreezeColumn" => ["string"],
"->setZoomScale" => ["int"],
"->setShowFormulas" => ["bool"],
"->setShowGridLines" => ["bool"],
"->setShowRowColHeaders" => ["bool"],
"->setShowZeroes" => ["bool"],
"->setRightToLeft" => ["bool"],
"->setTabSelected" => ["bool"],
"->setShowOutlineSymbols" => ["bool"],
"->setDefaultGridColor" => ["bool"],
"->setView" => ["string"],
"->setTopLeftCell" => ["string"],
"->setColorId" => ["int"],
"->setZoomScaleNormal" => ["int"],
"->setZoomScalePageLayoutView" => ["int"],
"->setWorkbookViewId" => ["int"],
# copie de parent::PARAMS_SHEET_VIEW
];
}

0
tests/.gitignore vendored Normal file
View File

View File

@ -268,9 +268,13 @@ class CellValueFormatter
$dateObj->modify('+'.$secondsRemainder.'seconds');
if ($this->shouldFormatDates) {
$styleNumberFormatCode = $this->styleManager->getNumberFormatCode($cellStyleId);
$phpDateFormat = DateFormatHelper::toPHPDateFormat($styleNumberFormatCode);
//$styleNumberFormatCode = $this->styleManager->getNumberFormatCode($cellStyleId);
//$phpDateFormat = DateFormatHelper::toPHPDateFormat($styleNumberFormatCode);
// Toujours utiliser le format français complet
$phpDateFormat = "d/m/Y H:i:s";
$cellValue = $dateObj->format($phpDateFormat);
// Enlever la composante heure si elle n'existe pas
$cellValue = preg_replace('/ 00:00:00$/', "", $cellValue);
} else {
$cellValue = $dateObj;
}

View File

@ -29,7 +29,7 @@ class OptionsManager extends OptionsManagerAbstract
protected function setDefaultOptions()
{
$this->setOption(Options::TEMP_FOLDER, sys_get_temp_dir());
$this->setOption(Options::SHOULD_FORMAT_DATES, false);
$this->setOption(Options::SHOULD_FORMAT_DATES, true);
$this->setOption(Options::SHOULD_PRESERVE_EMPTY_ROWS, false);
$this->setOption(Options::SHOULD_USE_1904_DATES, false);
}

View File

@ -22,6 +22,9 @@ abstract class WriterAbstract implements WriterInterface
/** @var resource Pointer to the file/stream we will write to */
protected $filePointer;
/** @var bool faut-il garder ouvert le flux quand close() est appelé? */
protected $dontCloseFilePointer = false;
/** @var bool Indicates whether the writer has been opened or not */
protected $isWriterOpened = false;
@ -57,6 +60,20 @@ abstract class WriterAbstract implements WriterInterface
return $this;
}
public function writeToStream($filePointer)
{
$this->outputFilePath = null;
$this->filePointer = $filePointer;
$this->dontCloseFilePointer = true;
$this->throwIfFilePointerIsNotAvailable();
$this->openWriter();
$this->isWriterOpened = true;
return $this;
}
/**
* {@inheritdoc}
*/
@ -177,7 +194,7 @@ abstract class WriterAbstract implements WriterInterface
$this->closeWriter();
if (\is_resource($this->filePointer)) {
if (!$this->dontCloseFilePointer && \is_resource($this->filePointer)) {
$this->globalFunctionsHelper->fclose($this->filePointer);
}