stack = [[ "mark" => null, "pending" => null, "end" => null, "have_content" => false, ]]; $this->index = 0; } /** @var array */ protected $stack; /** @var int */ protected $index; function haveContent(): bool { return $this->stack[$this->index]["have_content"]; } function add($vs): void { if ($vs !== null) { $pending =& $this->stack[$this->index]["pending"]; if ($pending === null) $pending = []; $pending[] = $vs; } } function flushContent(bool $haveContent=false): ?array { $content = null; foreach ($this->stack as &$item) { if ($haveContent) A::append_nn($content, $item["pending"]); $item["pending"] = null; }; unset($item); $this->stack[$this->index]["have_content"] = $haveContent; return $content; } function flush(bool $haveContent=false): ?array { $currentHaveContent = $this->haveContent(); $content = $this->flushContent($currentHaveContent); if ($haveContent != $currentHaveContent) $this->flushContent($haveContent); return $content; } function push($mark, ?array $start=null, ?array $end=null): ?array { $content = $this->flush(); A::append_nn($content, $start); $this->stack[] = [ "mark" => $mark, "pending" => null, "end" => $end, "have_content" => false, ]; $this->index++; return $content; } protected function haveMark($mark): bool { $index = $this->index; while ($index > 0) { $item = $this->stack[$index--]; if ($item["mark"] === $mark) return true; } return false; } function pop($mark): ?array { if (!$this->haveMark($mark)) return null; $content = null; while ($this->index > 0) { $item = array_pop($this->stack); $this->index--; if ($item["have_content"]) A::append_nn($content, $item["pending"]); A::append_nn($content, $item["end"]); if ($item["mark"] === $mark) break; } return $content; } protected abstract function getRowTags($options): array; function startRow($options=null): ?array { $content = $this->flush(); $rowTags = $this->getRowTags($options); A::append_nn($content, $this->push([$this, "row"], ...$rowTags)); return $content; } function row($options=null): ?array { $content = $this->endRow(); A::append_nn($content, $this->startRow($options)); return $content; } protected abstract function getColTags($size, $options): array; function startCol($size, $options=null): ?array { $content = $this->flush(); $colTags = $this->getColTags($size, $options); A::append_nn($content, $this->push([$this, "col"], ...$colTags)); return $content; } function col($size, $options=null): ?array { $content = $this->endCol(); A::append_nn($content, $this->startCol($size, $options)); return $content; } protected abstract function getPanelTags($title, $options): array; function startPanel($title, $options=null): ?array { $content = $this->flush(true); $panelTags = $this->getPanelTags($title, $options); A::append_nn($content, $this->push([$this, "panel"], ...$panelTags)); return $content; } function panel($title, $options=null): ?array { $content = $this->endPanel(); A::append_nn($content, $this->startPanel($title, $options)); return $content; } function endPanel(): ?array { return $this->pop([$this, "panel"]); } function endCol(): ?array { return $this->pop([$this, "col"]); } function endRow(): ?array { return $this->pop([$this, "row"]); } function end(): ?array { $content = null; A::append_nn($content, $this->flush()); A::append_nn($content, $this->endPanel()); A::append_nn($content, $this->endCol()); A::append_nn($content, $this->endRow()); return $content; } }