xref: /template/strap/ComboStrap/OutlineSection.php (revision 912a1845972505af58f7588360db360adeeea192)
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