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