diff --git a/src/ext/spreadsheet/SpoutBuilder.php b/src/ext/spreadsheet/SpoutBuilder.php index d632353..6624796 100644 --- a/src/ext/spreadsheet/SpoutBuilder.php +++ b/src/ext/spreadsheet/SpoutBuilder.php @@ -4,8 +4,12 @@ namespace nur\sery\ext\spreadsheet; use nur\sery\file\csv\AbstractBuilder; use nur\sery\file\csv\TAbstractBuilder; use nur\sery\os\path; +use nur\sery\php\time\Date; +use nur\sery\php\time\DateTime; use nur\sery\web\http; +use OpenSpout\Common\Entity\Cell; 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; @@ -13,28 +17,51 @@ use OpenSpout\Writer\XLSX\Entity\SheetView; class SpoutBuilder extends AbstractBuilder { use TAbstractBuilder; + const DATE_FORMAT = "mm/dd/yyyy"; + + const DATETIME_FORMAT = "mm/dd/yyyy hh:mm:ss"; + /** @var string|int|null nom de la feuille dans laquelle écrire */ const WSNAME = null; function __construct(?string $output, ?array $params=null) { parent::__construct($output, $params); - switch (path::ext($this->output)) { - case ".ods": + $ssType = $params["ss_type"] ?? null; + if ($ssType === null) { + switch (path::ext($this->output)) { + case ".ods": + $ssType = "ods"; + break; + case ".xlsx": + default: + $ssType = "xlsx"; + break; + } + } + switch ($ssType) { + case "ods": $ss = WriterEntityFactory::createODSWriter(); break; - case ".xlsx": + case "xlsx": default: $ss = WriterEntityFactory::createXLSXWriter(); break; } + $ss->setDefaultColumnWidth(10.5); $ss->writeToStream($this->getResource()); $this->ss = $ss; + $this->parseNumeric = boolval($params["parse_numeric"] ?? false); + $this->parseDate = boolval($params["parse_date"] ?? true); $this->firstSheet = true; $this->setWsname($params["wsname"] ?? static::WSNAME); } protected WriterMultiSheetsAbstract $ss; + protected bool $parseNumeric; + + protected bool $parseDate; + const STYLE_ROW = 0, STYLE_HEADER = 1; protected int $rowStyle; @@ -60,14 +87,53 @@ class SpoutBuilder extends AbstractBuilder { return $this; } - function _write(array $row): void { - $row = WriterEntityFactory::createRowFromArray($row); - if ($this->rowStyle === self::STYLE_HEADER) { - $row->setStyle((new Style()) - ->setFontBold() - ); + protected function isNumeric($value): bool { + if ($this->parseNumeric && is_numeric($value)) return true; + if (!is_string($value) && is_numeric($value)) return true; + return false; + } + + protected function isDate(&$value, &$style): bool { + if (CellTypeHelper::isDateTimeOrDateInterval($value)) { + $style = (new Style())->setFormat(self::DATE_FORMAT); + return true; } - $this->ss->addRow($row); + if (!is_string($value)) return false; + if (DateTime::isa_datetime($value, true)) { + $value = new DateTime($value); + $style = (new Style())->setFormat(self::DATETIME_FORMAT); + return true; + } + if (DateTime::isa_date($value, true)) { + $value = new Date($value); + $style = (new Style())->setFormat(self::DATE_FORMAT); + return true; + } + return false; + } + + function _write(array $row): void { + $cells = []; + $rowStyle = null; + foreach ($row as $col) { + $style = null; + 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 = (new Style())->setFontBold(); + } + $this->ss->addRow(WriterEntityFactory::createRow($cells, $rowStyle)); } function writeHeaders(?array $headers=null): void { @@ -90,6 +156,7 @@ class SpoutBuilder extends AbstractBuilder { } function _sendFile(): int { + $this->ss->close(); $this->rewind(); $this->sendHeaders(); return $this->fpassthru(); diff --git a/src/ext/spreadsheet/SpoutReader.php b/src/ext/spreadsheet/SpoutReader.php index c67fb0b..c44a32b 100644 --- a/src/ext/spreadsheet/SpoutReader.php +++ b/src/ext/spreadsheet/SpoutReader.php @@ -11,9 +11,12 @@ class SpoutReader extends AbstractReader { function __construct($input, ?array $params=null) { parent::__construct($input, $params); + $this->ssType = $params["ss_type"] ?? null; $this->wsname = $params["wsname"] ?? static::WSNAME; } + protected ?string $ssType; + protected $wsname; /** @@ -25,7 +28,18 @@ class SpoutReader extends AbstractReader { } function getIterator() { - $ss = ReaderEntityFactory::createReaderFromFile($this->input); + 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 { $found = false; foreach ($ss->getSheetIterator() as $ws) { diff --git a/src/file/csv/TAbstractBuilder.php b/src/file/csv/TAbstractBuilder.php index fc15ea6..a102570 100644 --- a/src/file/csv/TAbstractBuilder.php +++ b/src/file/csv/TAbstractBuilder.php @@ -1,30 +1,57 @@ isExt(".csv")) $class = CsvBuilder::class; - else $class = static::class; - return new $class($builder->name); + if ($builder->isExt(".csv")) { + $class = CsvBuilder::class; + } else { + $class = static::class; + if ($builder->isExt(".ods")) { + $params["ss_type"] = "ods"; + } else { + $params["ss_type"] = "xlsx"; + } + } + return new $class($builder->name, $params); } - if (is_string($builder)) $builder = ["output" => $builder]; - if (!is_array($builder)) { + + if (is_string($builder)) { + $params["output"] = $builder; + } elseif (is_array($builder)) { + $params = cl::merge($builder, $params); + } elseif ($builder !== null) { throw ValueException::invalid_type($builder, self::class); } - $input = $builder["output"] ?? null; - if (is_string($input) && path::ext($input) === ".csv") { - $class = CsvBuilder::class; + + $output = $params["output"] ?? null; + $ssType = null; + if (is_string($output)) { + switch (path::ext($output)) { + case ".csv": + $class = CsvBuilder::class; + break; + case ".ods": + $ssType = "ods"; + break; + case ".xlsx": + default: + $ssType = "xlsx"; + break; + } } + $params["ss_type"] = $ssType; if ($class === null) $class = static::class; - return new $class(null, $builder); + return new $class(null, $params); } } diff --git a/src/file/csv/TAbstractReader.php b/src/file/csv/TAbstractReader.php index b82b70a..b2f56db 100644 --- a/src/file/csv/TAbstractReader.php +++ b/src/file/csv/TAbstractReader.php @@ -12,8 +12,16 @@ trait TAbstractReader { if ($reader instanceof self) return $reader; $class = null; if ($reader instanceof Upload) { - if ($reader->isExt(".csv")) $class = CsvReader::class; - else $class = static::class; + if ($reader->isExt(".csv")) { + $class = CsvReader::class; + } else { + $class = static::class; + if ($reader->isExt(".ods")) { + $params["ss_type"] = "ods"; + } else { + $params["ss_type"] = "xlsx"; + } + } return new $class($reader->tmpName, $params); } @@ -26,9 +34,22 @@ trait TAbstractReader { } $input = $params["input"] ?? null; - if (is_string($input) && path::ext($input) === ".csv") { - $class = CsvReader::class; + $ssType = null; + if (is_string($input)) { + switch (path::ext($input)) { + case ".csv": + $class = CsvReader::class; + break; + case ".ods": + $ssType = "ods"; + break; + case ".xlsx": + default: + $ssType = "xlsx"; + break; + } } + $params["ss_type"] = $ssType; if ($class === null) $class = static::class; return new $class(null, $params); }