xref: /plugin/parserfunctions/action.php (revision bca4461f991170aa87d92750205a2b7ff2e29faa)
18bd4c45fSnerun<?php
28bd4c45fSnerun/**
38bd4c45fSnerun * DokuWiki Plugin parserfunctions (Action Component)
48bd4c45fSnerun *
58bd4c45fSnerun * Captures nested functions {{#func: ... #}} before the Lexer and processes
68bd4c45fSnerun * them recursively.
78bd4c45fSnerun *
88bd4c45fSnerun * @author  ChatGPT -- Wed, 02 jul 2025 12:04:42 -0300
98bd4c45fSnerun */
108bd4c45fSnerun
118bd4c45fSnerunuse dokuwiki\Extension\ActionPlugin;
128bd4c45fSnerunuse dokuwiki\Extension\EventHandler;
138bd4c45fSnerunuse dokuwiki\Extension\Event;
148bd4c45fSnerun
158bd4c45fSnerunclass action_plugin_parserfunctions extends ActionPlugin
168bd4c45fSnerun{
178bd4c45fSnerun    /** @var helper_plugin_parserfunctions */
188bd4c45fSnerun    private $helper;
198bd4c45fSnerun
208bd4c45fSnerun    public function __construct()
218bd4c45fSnerun    {
228bd4c45fSnerun        $this->helper = plugin_load('helper', 'parserfunctions');
238bd4c45fSnerun    }
248bd4c45fSnerun
258bd4c45fSnerun    /** @inheritDoc */
268bd4c45fSnerun    public function register(EventHandler $controller)
278bd4c45fSnerun    {
288bd4c45fSnerun        $controller->register_hook(
298bd4c45fSnerun            'PARSER_WIKITEXT_PREPROCESS',
308bd4c45fSnerun            'BEFORE',
318bd4c45fSnerun            $this,
328bd4c45fSnerun            'handlePreprocess'
338bd4c45fSnerun        );
348bd4c45fSnerun    }
358bd4c45fSnerun
368bd4c45fSnerun    public function handlePreprocess(Event $event)
378bd4c45fSnerun    {
388bd4c45fSnerun        $text = $event->data;
398bd4c45fSnerun        $text = $this->processParserFunctions($text);
408bd4c45fSnerun        $event->data = $text;
418bd4c45fSnerun    }
428bd4c45fSnerun
438bd4c45fSnerun    private function processParserFunctions($text)
448bd4c45fSnerun    {
45*bca4461fSnerun        // 1. Protects escaped blocks
46*bca4461fSnerun        $protectedBlocks = [];
47*bca4461fSnerun        /* The "s" modifier makes . catch line breaks.
48*bca4461fSnerun         * The "i" modifier ignores case (if someone writes <CODE>).
49*bca4461fSnerun         */
50*bca4461fSnerun        $text = preg_replace_callback('/%%.*?%%|<(nowiki|code|file|html)[^>]*>.*?<\/\1>/si', function ($matches) use (&$protectedBlocks) {
51*bca4461fSnerun            $key = '@@ESC' . count($protectedBlocks) . '@@';
52*bca4461fSnerun            $protectedBlocks[$key] = $matches[0];
53*bca4461fSnerun            return $key;
54*bca4461fSnerun        }, $text);
55*bca4461fSnerun
56*bca4461fSnerun        // 2. Processes functions normally
578bd4c45fSnerun        $index = 0;
588bd4c45fSnerun        while (($match = $this->extractBalancedFunction($text)) !== false) {
598bd4c45fSnerun            $resolved = plugin_load('syntax', 'parserfunctions')->resolveFunction($match);
608bd4c45fSnerun            $text = str_replace($match, $resolved, $text);
618bd4c45fSnerun            $index++;
628bd4c45fSnerun        }
638bd4c45fSnerun
64*bca4461fSnerun        // 3. Restores protected blocks
65*bca4461fSnerun        foreach ($protectedBlocks as $key => $original) {
66*bca4461fSnerun            $text = str_replace($key, $original, $text);
67*bca4461fSnerun        }
68*bca4461fSnerun
698bd4c45fSnerun        return $text;
708bd4c45fSnerun    }
718bd4c45fSnerun
728bd4c45fSnerun    private function extractBalancedFunction($text)
738bd4c45fSnerun    {
748bd4c45fSnerun        $start = strpos($text, '{{#');
758bd4c45fSnerun        if ($start === false) return false;
768bd4c45fSnerun
778bd4c45fSnerun        $level = 0;
788bd4c45fSnerun        $length = strlen($text);
798bd4c45fSnerun        for ($i = $start; $i < $length - 2; $i++) {
808bd4c45fSnerun            if (substr($text, $i, 3) === '{{#') {
818bd4c45fSnerun                $level++;
828bd4c45fSnerun                $i += 2;
838bd4c45fSnerun            } elseif (substr($text, $i, 3) === '#}}') {
848bd4c45fSnerun                $level--;
858bd4c45fSnerun                $i += 2;
868bd4c45fSnerun
878bd4c45fSnerun                if ($level === 0) {
888bd4c45fSnerun                    return substr($text, $start, $i - $start + 1);
898bd4c45fSnerun                }
908bd4c45fSnerun            }
918bd4c45fSnerun        }
928bd4c45fSnerun
938bd4c45fSnerun        return false; // Malformed
948bd4c45fSnerun    }
958bd4c45fSnerun}
968bd4c45fSnerun
97