1<?php
2
3// implementation of
4// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code
5
6// must be run within Dokuwiki
7use ComboStrap\Html;
8use ComboStrap\PluginUtility;
9use ComboStrap\Prism;
10use ComboStrap\TagAttributes;
11
12require_once(__DIR__ . '/../ComboStrap/StringUtility.php');
13require_once(__DIR__ . '/../ComboStrap/Prism.php');
14
15if (!defined('DOKU_INC')) die();
16
17/**
18 *
19 * Support <a href="https://github.github.com/gfm/#fenced-code-blocks">Github code block</a>
20 *
21 * The original code markdown code block is the {@link syntax_plugin_combo_preformatted}
22 */
23class syntax_plugin_combo_codemarkdown extends DokuWiki_Syntax_Plugin
24{
25
26
27    /**
28     * The exit pattern of markdown
29     * use in the enter pattern as regexp look-ahead
30     */
31    const MARKDOWN_EXIT_PATTERN = "^\s*(?:\`|~){3}\s*$";
32
33    /**
34     * The syntax name, not a tag
35     * This must be the same that the last part name
36     * of the class (without any space !)
37     * This is the id in the callstack
38     * and gives us just a way to get the
39     * {@link \dokuwiki\Extension\SyntaxPlugin::getPluginComponent()}
40     * value (tag = plugin component)
41     */
42    const TAG = "codemarkdown";
43
44
45    function getType()
46    {
47        /**
48         * You can't write in a code block
49         */
50        return 'protected';
51    }
52
53    /**
54     * How DokuWiki will add P element
55     *
56     *  * 'normal' - The plugin can be used inside paragraphs
57     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
58     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
59     *
60     * @see DokuWiki_Syntax_Plugin::getPType()
61     */
62    function getPType()
63    {
64        return 'block';
65    }
66
67    /**
68     * @return array
69     * Allow which kind of plugin inside
70     *
71     * No one of array('baseonly','container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs')
72     * because we manage self the content and we call self the parser
73     *
74     * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php
75     */
76    function getAllowedTypes()
77    {
78        return array();
79    }
80
81    /**
82     * Order of precedence
83     * @return int
84     */
85    function getSort(): int
86    {
87
88        return 199;
89    }
90
91
92    function connectTo($mode)
93    {
94
95        //
96        // This pattern works with mgs flag
97        //
98        // Match:
99        //  * the start of a line
100        //  * any consecutive blank character
101        //  * 3 consecutive backtick characters (`) or tildes (~)
102        //  * a word (info string - ie the language)
103        //  * any consecutive blank character
104        //  * the end of a line
105        //  * a look ahead to see if we have the exit pattern
106        // https://github.github.com/gfm/#fenced-code-blocks
107        $gitHubMarkdownPattern = "^\s*(?:\`|~){3}\w*\s*$(?=.*?" . self::MARKDOWN_EXIT_PATTERN . ")";
108        $this->Lexer->addEntryPattern($gitHubMarkdownPattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent()));
109
110
111    }
112
113
114    function postConnect()
115    {
116
117
118        $this->Lexer->addExitPattern(self::MARKDOWN_EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent()));
119
120
121    }
122
123    /**
124     *
125     * The handle function goal is to parse the matched syntax through the pattern function
126     * and to return the result for use in the renderer
127     * This result is always cached until the page is modified.
128     * @param string $match
129     * @param int $state
130     * @param int $pos - byte position in the original source file
131     * @param Doku_Handler $handler
132     * @return array|bool
133     * @see DokuWiki_Syntax_Plugin::handle()
134     *
135     */
136    function handle($match, $state, $pos, Doku_Handler $handler)
137    {
138
139        switch ($state) {
140
141            case DOKU_LEXER_ENTER:
142                $trimmedMatch = trim($match);
143                $language = preg_replace("(`{3}|~{3})", "", $trimmedMatch);
144
145                $attributes = [TagAttributes::TYPE_KEY => $language];
146                return array(
147                    PluginUtility::STATE => $state,
148                    PluginUtility::ATTRIBUTES => $attributes
149                );
150
151            case DOKU_LEXER_UNMATCHED :
152
153                return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler);
154
155
156            case DOKU_LEXER_EXIT :
157
158                return array(PluginUtility::STATE => $state);
159
160
161        }
162        return array();
163
164    }
165
166    /**
167     * Render the output
168     * @param string $format
169     * @param Doku_Renderer $renderer
170     * @param array $data - what the function handle() return'ed
171     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
172     * @see DokuWiki_Syntax_Plugin::render()
173     *
174     *
175     */
176    function render($format, Doku_Renderer $renderer, $data)
177    {
178
179
180        if ($format == 'xhtml') {
181
182            /** @var $renderer Doku_Renderer_xhtml */
183            $state = $data [PluginUtility::STATE];
184            switch ($state) {
185                case DOKU_LEXER_ENTER :
186                    $attributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES], syntax_plugin_combo_code::CODE_TAG);
187                    Prism::htmlEnter($renderer, $this, $attributes);
188                    break;
189
190                case DOKU_LEXER_UNMATCHED :
191
192                    // Delete the eol at the beginning and end
193                    // otherwise we get a big block
194                    $payload = trim($data[PluginUtility::PAYLOAD], "\n\r");
195                    $renderer->doc .= Html::encode($payload);
196                    break;
197
198                case DOKU_LEXER_EXIT :
199
200                    Prism::htmlExit($renderer);
201                    break;
202
203            }
204            return true;
205        } else if ($format == 'code') {
206
207            /**
208             * The renderer to download the code
209             * @var Doku_Renderer_code $renderer
210             */
211            $state = $data [PluginUtility::STATE];
212            if ($state == DOKU_LEXER_UNMATCHED) {
213
214                $attributes = $data[PluginUtility::ATTRIBUTES];
215                $text = $data[PluginUtility::PAYLOAD];
216                $language = strtolower($attributes["type"]);
217                $filename = $language;
218                $renderer->code($text, $language, $filename);
219
220            }
221        }
222
223        // unsupported $mode
224        return false;
225
226    }
227
228
229}
230
231