104fd306cSNickeau<?php 204fd306cSNickeau 304fd306cSNickeaunamespace ComboStrap; 404fd306cSNickeau 504fd306cSNickeau 604fd306cSNickeauclass OutlineSection extends TreeNode 704fd306cSNickeau{ 804fd306cSNickeau const CANONICAL = "outline"; 904fd306cSNickeau const HEADER_DOKUWIKI_CALL = "header"; 1004fd306cSNickeau 1104fd306cSNickeau 1204fd306cSNickeau /** 1304fd306cSNickeau * Not to confound with header calls that are {@link OutlineSection::getContentCalls()} 1404fd306cSNickeau * of a section that has children 1504fd306cSNickeau * 1604fd306cSNickeau * @var Call[] $headingCalls 1704fd306cSNickeau */ 1804fd306cSNickeau private array $headingCalls = []; 1904fd306cSNickeau /** 2004fd306cSNickeau * 2104fd306cSNickeau * @var Call[] $contentCalls 2204fd306cSNickeau */ 2304fd306cSNickeau private array $contentCalls = []; 2404fd306cSNickeau 2504fd306cSNickeau 26*70bbd7f1Sgerardnico private string $headingId; 27*70bbd7f1Sgerardnico 2804fd306cSNickeau private int $startFileIndex; 2904fd306cSNickeau private ?int $endFileIndex = null; 3004fd306cSNickeau 3104fd306cSNickeau private ?Call $headingEnterCall; 3204fd306cSNickeau /** 3304fd306cSNickeau * @var array an array to make sure that the id are unique 3404fd306cSNickeau */ 3504fd306cSNickeau private array $tocUniqueId = []; 3604fd306cSNickeau 3704fd306cSNickeau /** 3804fd306cSNickeau * @var int - a best guess on the number of 3904fd306cSNickeau */ 4004fd306cSNickeau private int $lineNumber; 4104fd306cSNickeau 4204fd306cSNickeau 4304fd306cSNickeau /** 4404fd306cSNickeau * @param Call|null $headingEnterCall - null if the section is the root 4504fd306cSNickeau */ 4604fd306cSNickeau private function __construct(Call $headingEnterCall = null) 4704fd306cSNickeau { 4804fd306cSNickeau $this->headingEnterCall = $headingEnterCall; 4904fd306cSNickeau if ($headingEnterCall !== null) { 5004fd306cSNickeau $position = $headingEnterCall->getFirstMatchedCharacterPosition(); 5104fd306cSNickeau if ($position === null) { 5204fd306cSNickeau $this->startFileIndex = 0; 5304fd306cSNickeau } else { 5404fd306cSNickeau $this->startFileIndex = $position; 5504fd306cSNickeau } 5604fd306cSNickeau $this->addHeaderCall($headingEnterCall); 5704fd306cSNickeau } else { 5804fd306cSNickeau $this->startFileIndex = 0; 5904fd306cSNickeau } 6004fd306cSNickeau $this->lineNumber = 1; // the heading 6104fd306cSNickeau 6204fd306cSNickeau } 6304fd306cSNickeau 6404fd306cSNickeau 6504fd306cSNickeau public static function createOutlineRoot(): OutlineSection 6604fd306cSNickeau { 6704fd306cSNickeau return new OutlineSection(null); 6804fd306cSNickeau } 6904fd306cSNickeau 7004fd306cSNickeau 7104fd306cSNickeau /** 7204fd306cSNickeau * Return a text to an HTML Id 7304fd306cSNickeau * @param string $fragment 7404fd306cSNickeau * @return string 7504fd306cSNickeau */ 7604fd306cSNickeau public static function textToHtmlSectionId(string $fragment): string 7704fd306cSNickeau { 7804fd306cSNickeau $check = false; 7904fd306cSNickeau // for empty string, the below function returns `section` 8004fd306cSNickeau return sectionID($fragment, $check); 8104fd306cSNickeau } 8204fd306cSNickeau 8304fd306cSNickeau public static function createFromEnterHeadingCall(Call $enterHeadingCall): OutlineSection 8404fd306cSNickeau { 8504fd306cSNickeau return new OutlineSection($enterHeadingCall); 8604fd306cSNickeau } 8704fd306cSNickeau 8804fd306cSNickeau public function getFirstChild(): OutlineSection 8904fd306cSNickeau { 9004fd306cSNickeau 9104fd306cSNickeau /** @noinspection PhpIncompatibleReturnTypeInspection */ 9204fd306cSNickeau return parent::getFirstChild(); 9304fd306cSNickeau 9404fd306cSNickeau } 9504fd306cSNickeau 9604fd306cSNickeau 9704fd306cSNickeau public function addContentCall(Call $actualCall): OutlineSection 9804fd306cSNickeau { 9904fd306cSNickeau 10004fd306cSNickeau $this->contentCalls[] = $actualCall; 10104fd306cSNickeau return $this; 10204fd306cSNickeau 10304fd306cSNickeau 10404fd306cSNickeau } 10504fd306cSNickeau 10604fd306cSNickeau public function addHeaderCall(Call $actualCall): OutlineSection 10704fd306cSNickeau { 10804fd306cSNickeau 10904fd306cSNickeau $this->headingCalls[] = $actualCall; 11004fd306cSNickeau return $this; 11104fd306cSNickeau } 11204fd306cSNickeau 11304fd306cSNickeau public function getLabel(): string 11404fd306cSNickeau { 11504fd306cSNickeau $label = ""; 11604fd306cSNickeau foreach ($this->headingCalls as $call) { 11704fd306cSNickeau if ($call->isTextCall()) { 11804fd306cSNickeau // Building the text for the toc 11904fd306cSNickeau // only cdata for now 12004fd306cSNickeau // no image, ... 12104fd306cSNickeau if ($label != "") { 12204fd306cSNickeau $label .= " "; 12304fd306cSNickeau } 12404fd306cSNickeau $label .= trim($call->getCapturedContent()); 12504fd306cSNickeau } 12604fd306cSNickeau } 12704fd306cSNickeau return trim($label); 12804fd306cSNickeau } 12904fd306cSNickeau 13004fd306cSNickeau public function setStartPosition(int $startPosition): OutlineSection 13104fd306cSNickeau { 13204fd306cSNickeau $this->startFileIndex = $startPosition; 13304fd306cSNickeau return $this; 13404fd306cSNickeau } 13504fd306cSNickeau 13604fd306cSNickeau public function setEndPosition(int $endFileIndex): OutlineSection 13704fd306cSNickeau { 13804fd306cSNickeau $this->endFileIndex = $endFileIndex; 13904fd306cSNickeau return $this; 14004fd306cSNickeau } 14104fd306cSNickeau 14204fd306cSNickeau /** 14304fd306cSNickeau * @return Call[] 14404fd306cSNickeau */ 14504fd306cSNickeau public function getHeadingCalls(): array 14604fd306cSNickeau { 14704fd306cSNickeau if ( 14804fd306cSNickeau $this->headingEnterCall !== null && 14904fd306cSNickeau $this->headingEnterCall->isPluginCall() && 15004fd306cSNickeau !$this->headingEnterCall->hasAttribute("id") 15104fd306cSNickeau ) { 15204fd306cSNickeau $this->headingEnterCall->addAttribute("id", $this->getHeadingId()); 15304fd306cSNickeau } 15404fd306cSNickeau return $this->headingCalls; 15504fd306cSNickeau } 15604fd306cSNickeau 15704fd306cSNickeau 15804fd306cSNickeau public 15904fd306cSNickeau function getEnterHeadingCall(): ?Call 16004fd306cSNickeau { 16104fd306cSNickeau return $this->headingEnterCall; 16204fd306cSNickeau } 16304fd306cSNickeau 16404fd306cSNickeau 16504fd306cSNickeau public 16604fd306cSNickeau function getCalls(): array 16704fd306cSNickeau { 16804fd306cSNickeau return array_merge($this->headingCalls, $this->contentCalls); 16904fd306cSNickeau } 17004fd306cSNickeau 17104fd306cSNickeau public 17204fd306cSNickeau function getContentCalls(): array 17304fd306cSNickeau { 17404fd306cSNickeau return $this->contentCalls; 17504fd306cSNickeau } 17604fd306cSNickeau 17704fd306cSNickeau /** 17804fd306cSNickeau * @return int 17904fd306cSNickeau */ 18004fd306cSNickeau public 18104fd306cSNickeau function getLevel(): int 18204fd306cSNickeau { 18304fd306cSNickeau if ($this->headingEnterCall === null) { 18404fd306cSNickeau return 0; 18504fd306cSNickeau } 18604fd306cSNickeau switch ($this->headingEnterCall->getTagName()) { 18704fd306cSNickeau case self::HEADER_DOKUWIKI_CALL: 18804fd306cSNickeau $level = $this->headingEnterCall->getInstructionCall()[1][1]; 18904fd306cSNickeau break; 19004fd306cSNickeau default: 19104fd306cSNickeau $level = $this->headingEnterCall->getAttribute(HeadingTag::LEVEL); 19204fd306cSNickeau break; 19304fd306cSNickeau } 19404fd306cSNickeau 19504fd306cSNickeau try { 19604fd306cSNickeau return DataType::toInteger($level); 19704fd306cSNickeau } catch (ExceptionBadArgument $e) { 19804fd306cSNickeau // should not happen 19904fd306cSNickeau LogUtility::internalError("The level ($level) could not be cast to an integer", self::CANONICAL); 20004fd306cSNickeau return 0; 20104fd306cSNickeau } 20204fd306cSNickeau } 20304fd306cSNickeau 20404fd306cSNickeau public 20504fd306cSNickeau function getStartPosition(): int 20604fd306cSNickeau { 20704fd306cSNickeau return $this->startFileIndex; 20804fd306cSNickeau } 20904fd306cSNickeau 21004fd306cSNickeau public 21104fd306cSNickeau function getEndPosition(): ?int 21204fd306cSNickeau { 21304fd306cSNickeau return $this->endFileIndex; 21404fd306cSNickeau } 21504fd306cSNickeau 21604fd306cSNickeau public 21704fd306cSNickeau function hasContentCall(): bool 21804fd306cSNickeau { 21904fd306cSNickeau return sizeof($this->contentCalls) > 0; 22004fd306cSNickeau } 22104fd306cSNickeau 22204fd306cSNickeau /** 22304fd306cSNickeau */ 22404fd306cSNickeau public 22504fd306cSNickeau function getHeadingId() 22604fd306cSNickeau { 22704fd306cSNickeau 22804fd306cSNickeau if (!isset($this->headingId)) { 22904fd306cSNickeau $id = $this->headingEnterCall->getAttribute("id"); 23004fd306cSNickeau if ($id !== null) { 23104fd306cSNickeau return $id; 23204fd306cSNickeau } 23304fd306cSNickeau $label = $this->getLabel(); 23404fd306cSNickeau $this->headingId = sectionID($label, $this->tocUniqueId); 23504fd306cSNickeau } 23604fd306cSNickeau return $this->headingId; 23704fd306cSNickeau 23804fd306cSNickeau } 23904fd306cSNickeau 24004fd306cSNickeau /** 24104fd306cSNickeau * A HTML section should have a heading 24204fd306cSNickeau * but in a markup document, we may have data before the first 24304fd306cSNickeau * heading making a section without heading 24404fd306cSNickeau * @return bool 24504fd306cSNickeau */ 24604fd306cSNickeau public 24704fd306cSNickeau function hasHeading(): bool 24804fd306cSNickeau { 24904fd306cSNickeau return $this->headingEnterCall !== null; 25004fd306cSNickeau } 25104fd306cSNickeau 25204fd306cSNickeau /** 25304fd306cSNickeau * @return OutlineSection[] 25404fd306cSNickeau */ 25504fd306cSNickeau public 25604fd306cSNickeau function getChildren(): array 25704fd306cSNickeau { 25804fd306cSNickeau return parent::getChildren(); 25904fd306cSNickeau } 26004fd306cSNickeau 26104fd306cSNickeau public function setLevel(int $level): OutlineSection 26204fd306cSNickeau { 26304fd306cSNickeau switch ($this->headingEnterCall->getTagName()) { 26404fd306cSNickeau case self::HEADER_DOKUWIKI_CALL: 26504fd306cSNickeau $this->headingEnterCall->getInstructionCall()[1][1] = $level; 26604fd306cSNickeau break; 26704fd306cSNickeau default: 26804fd306cSNickeau $this->headingEnterCall->setAttribute(HeadingTag::LEVEL, $level); 26904fd306cSNickeau $headingExitCall = $this->headingCalls[count($this->headingCalls) - 1]; 27004fd306cSNickeau $headingExitCall->setAttribute(HeadingTag::LEVEL, $level); 27104fd306cSNickeau break; 27204fd306cSNickeau } 27304fd306cSNickeau 27404fd306cSNickeau /** 27504fd306cSNickeau * Update the descdenants sections 27604fd306cSNickeau * @param OutlineSection $parentSection 27704fd306cSNickeau * @return void 27804fd306cSNickeau */ 27904fd306cSNickeau $updateLevel = function (OutlineSection $parentSection) { 28004fd306cSNickeau foreach ($parentSection->getChildren() as $child) { 28104fd306cSNickeau $child->setLevel($parentSection->getLevel() + 1); 28204fd306cSNickeau } 28304fd306cSNickeau }; 28404fd306cSNickeau TreeVisit::visit($this, $updateLevel); 28504fd306cSNickeau 28604fd306cSNickeau return $this; 28704fd306cSNickeau } 28804fd306cSNickeau 28904fd306cSNickeau 29004fd306cSNickeau public function deleteContentCalls(): OutlineSection 29104fd306cSNickeau { 29204fd306cSNickeau $this->contentCalls = []; 29304fd306cSNickeau return $this; 29404fd306cSNickeau } 29504fd306cSNickeau 29604fd306cSNickeau public function incrementLineNumber(): OutlineSection 29704fd306cSNickeau { 29804fd306cSNickeau $this->lineNumber++; 29904fd306cSNickeau return $this; 30004fd306cSNickeau } 30104fd306cSNickeau 30204fd306cSNickeau public function getLineCount(): int 30304fd306cSNickeau { 30404fd306cSNickeau return $this->lineNumber; 30504fd306cSNickeau } 30604fd306cSNickeau 30704fd306cSNickeau 30804fd306cSNickeau} 309