xref: /plugin/combo/ComboStrap/OutlineSection.php (revision 70bbd7f1f72440223cc13f3495efdcb2b0a11514)
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