357 lines
10 KiB
PHP
357 lines
10 KiB
PHP
<?php
|
||
|
||
/**
|
||
* Клетка таблицы
|
||
*/
|
||
namespace ctiso\Excel;
|
||
use XMLWriter,
|
||
ctiso\Excel\DateTime as Excel_DateTime,
|
||
ctiso\Excel\Number as Excel_Number;
|
||
|
||
class TableCell
|
||
{
|
||
/** @var string|false */
|
||
public $style = false;
|
||
/** @var string */
|
||
public $value;
|
||
/** @var bool */
|
||
public $merge = false;
|
||
|
||
/**
|
||
* @param string $value Значение клетки
|
||
*/
|
||
function __construct ($value)
|
||
{
|
||
$this->value = $value;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Ряд таблицы
|
||
*/
|
||
class TableRow
|
||
{
|
||
/** @var string|false */
|
||
public $style = false;
|
||
/** @var TableCell[] */
|
||
public $cells = [];
|
||
/** @var int|false */
|
||
public $height = false;
|
||
|
||
/**
|
||
* Устанавливает значение для клетки
|
||
* @param int $y Номер столбца
|
||
* @param string $value Значение клетки
|
||
*/
|
||
function setCell($y, $value): void
|
||
{
|
||
$this->cells[$y] = new TableCell($value);
|
||
}
|
||
|
||
/**
|
||
* Устанавливает стиль для клетки
|
||
* @param int $y Номер столбца
|
||
* @param string $name Имя стиля
|
||
*/
|
||
function setCellStyle($y, $name): void
|
||
{
|
||
$this->cells[$y]->style = $name;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Таблица
|
||
*/
|
||
class Table
|
||
{
|
||
/** @var int */
|
||
static $index;
|
||
/** @var string */
|
||
private $name;
|
||
/** @var TableRow[] */
|
||
protected $rows = [];
|
||
|
||
/** @var int|false */
|
||
protected $_splitVertical = false;
|
||
/** @var int|false */
|
||
protected $_splitHorizontal = false;
|
||
|
||
function __construct()
|
||
{
|
||
$this->name = "Page " . ((int)self::$index ++);
|
||
}
|
||
|
||
/**
|
||
* Записать значение в клетку с заданными координатами
|
||
* @param int $x Номер ряда
|
||
* @param int $y Номер столбца
|
||
* @param string $value Значение клетки
|
||
*/
|
||
function setCell(int $x, int $y, $value): void
|
||
{
|
||
assert($x > 0);
|
||
assert($y > 0);
|
||
|
||
if(! isset($this->rows[$x])) {
|
||
$this->rows[$x] = new TableRow();
|
||
}
|
||
|
||
$row = $this->rows[$x];
|
||
$row->setCell($y, $value);
|
||
}
|
||
|
||
/**
|
||
* Заполняет ряд начиная с указанного столбца значениями из массива
|
||
*/
|
||
function setRow(int $row, int $index, array $data): void
|
||
{
|
||
assert($index > 0);
|
||
assert($row > 0);
|
||
|
||
reset($data);
|
||
for ($i = $index; $i < $index + count($data); $i++) {
|
||
$this->setCell($row, $i, current($data));
|
||
next($data);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Устанавливает высоту ряда
|
||
* @param int $row Номер ряда
|
||
* @param int $value Высота ряда
|
||
*/
|
||
function setRowHeight (int $row, $value): void
|
||
{
|
||
assert($row > 0);
|
||
|
||
$this->rows[$row]->height = $value;
|
||
}
|
||
|
||
/**
|
||
* Устанавливает стиль ряда
|
||
* @param int $row Номер ряда
|
||
* @param string $name Имя стиля
|
||
*/
|
||
function setRowStyle(int $row, $name): void
|
||
{
|
||
assert($row > 0);
|
||
|
||
$this->rows[$row]->style = $name;
|
||
}
|
||
|
||
/**
|
||
* Обьединяет клетки в строке
|
||
* @param int $x Номер ряда
|
||
* @param int $cell Номер столбца
|
||
* @param bool $merge Количество клеток для обьединения
|
||
*/
|
||
function setCellMerge(int $x, int $cell, $merge): void
|
||
{
|
||
assert($x > 0);
|
||
assert($cell > 0);
|
||
|
||
$row = $this->rows[$x];
|
||
$row->cells[$cell]->merge = $merge;
|
||
}
|
||
|
||
/**
|
||
* Устанавливает стиль для клеток ряда
|
||
* @param int $row Номер ряда
|
||
* @param int $y Номер столбца
|
||
* @param string $name Имя стиля
|
||
*/
|
||
function setCellStyle ($row, $y, $name): void
|
||
{
|
||
if (isset($this->rows[$row])) {
|
||
$this->rows[$row]->setCellStyle($y, $name);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Добавляет строку к таблице
|
||
* @return int Номер добавленной строки
|
||
*/
|
||
function addRow(int $index = 1, array $data = [""])
|
||
{
|
||
assert($index > 0);
|
||
$offset = $this->getRows() + 1;
|
||
|
||
$this->setRow($offset, $index, $data);
|
||
return $offset;
|
||
}
|
||
|
||
/**
|
||
* Количество строк в таблице
|
||
*
|
||
* @return int
|
||
*/
|
||
function getRows()
|
||
{
|
||
// Высчитываем максимальный индекс, массив может быть разрежен поэтому используем array_keys
|
||
$keys = array_keys($this->rows);
|
||
return $keys === [] ? 0 : max($keys);
|
||
}
|
||
|
||
/**
|
||
* Количество столбцов в строке
|
||
*
|
||
* @return int
|
||
*/
|
||
function getRowCells(TableRow $row)
|
||
{
|
||
$keys = array_keys($row->cells);
|
||
return $keys === [] ? 0 :max($keys);
|
||
}
|
||
|
||
/**
|
||
* Разделяет таблицу на две части по вертикали
|
||
* @param int $n Количество столбцов слева
|
||
*/
|
||
function splitVertical($n): void {
|
||
$this->_splitVertical = $n;
|
||
}
|
||
|
||
/**
|
||
* Разделяет таблицу на две части по горизонтали
|
||
* @param int $n Количество столбцов сверху
|
||
*/
|
||
function splitHorizontal($n): void {
|
||
$this->_splitHorizontal = $n;
|
||
}
|
||
|
||
|
||
/**
|
||
* Количество столбцов в таблице
|
||
*
|
||
* @return int
|
||
*/
|
||
function getColumns() {
|
||
$columns = array_map($this->getRowCells(...), $this->rows);
|
||
return $columns === [] ? 0 : max($columns);
|
||
}
|
||
|
||
/**
|
||
* Кодирование строки
|
||
* @deprecated Можно заменить на значение
|
||
* @param string $s Строка
|
||
* @return string
|
||
*/
|
||
function encode($s)
|
||
{
|
||
return $s;
|
||
}
|
||
|
||
/**
|
||
* Генерация клетки таблицы (Переработать)
|
||
* @param TableCell $ncell Клетка таблицы
|
||
* @param XMLWriter $doc XMLWriter
|
||
* @param int $j Индекс клетки
|
||
* @param mixed $value Значение клетки
|
||
* @param bool $setIndex Устанавливать индекс клетки в атрибут ss:Index
|
||
*/
|
||
function createCell (TableCell $ncell, XMLWriter $doc, $j, mixed $value, $setIndex): void {
|
||
$doc->startElement("Cell");
|
||
|
||
if ($ncell->style) {
|
||
$doc->writeAttribute('ss:StyleID', $ncell->style);
|
||
}
|
||
|
||
if ($ncell->merge) {
|
||
$doc->writeAttribute('ss:MergeAcross', (string)$ncell->merge);
|
||
}
|
||
|
||
if ($setIndex) {
|
||
$doc->writeAttribute('ss:Index', (string)$j);
|
||
}
|
||
|
||
$doc->startElement("Data");
|
||
if ($value instanceof Excel_DateTime) {
|
||
$doc->writeAttribute('ss:Type', "DateTime");
|
||
$doc->text($value->getString());
|
||
} else if ($value instanceof Excel_Number) {
|
||
$doc->writeAttribute('ss:Type', "Number");
|
||
$doc->text($value->getString());
|
||
} else {
|
||
if (is_string($value)) {
|
||
$doc->writeAttribute('ss:Type', "String");
|
||
} else {
|
||
$doc->writeAttribute('ss:Type', "Number");
|
||
}
|
||
$doc->writeCdata($value);
|
||
}
|
||
$doc->endElement();
|
||
$doc->endElement();
|
||
}
|
||
|
||
/**
|
||
* Генерация таблицы
|
||
*/
|
||
public function createTable (XMLWriter $doc): void {
|
||
$doc->startElement('Worksheet');
|
||
$doc->writeAttribute('ss:Name', $this->name);
|
||
|
||
$columns = $this->getColumns();
|
||
$rows = $this->getRows();
|
||
|
||
$doc->startElement('Table');
|
||
$doc->writeAttribute('ss:ExpandedColumnCount', (string)$columns);
|
||
$doc->writeAttribute('ss:ExpandedRowCount', (string)$rows);
|
||
|
||
// Переписать цыкл !!!!!!!
|
||
for ($i = 1; $i <= $rows; $i++) {
|
||
$doc->startElement('Row');
|
||
if (isset($this->rows[$i])) {
|
||
if ($this->rows[$i]->style) {
|
||
$doc->writeAttribute('ss:StyleID', $this->rows[$i]->style);
|
||
}
|
||
|
||
if ($this->rows[$i]->height) {
|
||
$doc->writeAttribute('ss:Height', (string)$this->rows[$i]->height);
|
||
}
|
||
/** @var TableRow $nrow */
|
||
$nrow = $this->rows[$i];
|
||
// Флаг индикатор подстановки номера столбца
|
||
$setIndex = false;
|
||
for ($j = 1; $j <= $columns; $j++) {
|
||
|
||
$value = null;
|
||
if (isset($nrow->cells[$j])) {
|
||
$value = $nrow->cells[$j]->value;
|
||
}
|
||
|
||
if (!empty($value)) {
|
||
$this->createCell($nrow->cells[$j], $doc, $j, $value, $setIndex);
|
||
$setIndex = false;
|
||
} else {
|
||
$setIndex = true;
|
||
}
|
||
}
|
||
}
|
||
$doc->endElement();
|
||
}
|
||
$doc->endElement();
|
||
$this->splitPane ($doc);
|
||
$doc->endElement();
|
||
}
|
||
|
||
protected function splitPane (XMLWriter $doc): void {
|
||
$doc->startElement('WorksheetOptions');
|
||
$doc->writeAttribute('xmlns', 'urn:schemas-microsoft-com:office:excel');
|
||
|
||
$doc->writeElement('FrozenNoSplit');
|
||
if ($this->_splitVertical) {
|
||
$doc->writeElement('SplitVertical', (string) $this->_splitVertical);
|
||
$doc->writeElement('LeftColumnRightPane', (string) $this->_splitVertical);
|
||
}
|
||
if ($this->_splitHorizontal) {
|
||
$doc->writeElement('SplitHorizontal', (string) $this->_splitHorizontal);
|
||
$doc->writeElement('TopRowBottomPane', (string) $this->_splitHorizontal);
|
||
}
|
||
if ($this->_splitHorizontal && $this->_splitVertical) {
|
||
$doc->writeElement('ActivePane', (string) 0);
|
||
} else if($this->_splitHorizontal) {
|
||
$doc->writeElement('ActivePane', (string) 2);
|
||
}
|
||
$doc->endElement();
|
||
}
|
||
}
|