xref: /plugin/combo/syntax/codemarkdown.php (revision c3437056399326d621a01da73b649707fbb0ae69)
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\PluginUtility;
8use ComboStrap\Prism;
9use ComboStrap\Tag;
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    function getSort()
82    {
83
84        return 199;
85    }
86
87
88    function connectTo($mode)
89    {
90
91        //
92        // This pattern works with mgs flag
93        //
94        // Match:
95        //  * the start of a line
96        //  * any consecutive blank character
97        //  * 3 consecutive backtick characters (`) or tildes (~)
98        //  * a word (info string - ie the language)
99        //  * any consecutive blank character
100        //  * the end of a line
101        //  * a look ahead to see if we have the exit pattern
102        // https://github.github.com/gfm/#fenced-code-blocks
103        $gitHubMarkdownPattern = "^\s*(?:\`|~){3}\w*\s*$(?=.*?" . self::MARKDOWN_EXIT_PATTERN . ")";
104        $this->Lexer->addEntryPattern($gitHubMarkdownPattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent()));
105
106
107    }
108
109
110    function postConnect()
111    {
112
113
114        $this->Lexer->addExitPattern(self::MARKDOWN_EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent()));
115
116
117    }
118
119    /**
120     *
121     * The handle function goal is to parse the matched syntax through the pattern function
122     * and to return the result for use in the renderer
123     * This result is always cached until the page is modified.
124     * @param string $match
125     * @param int $state
126     * @param int $pos - byte position in the original source file
127     * @param Doku_Handler $handler
128     * @return array|bool
129     * @see DokuWiki_Syntax_Plugin::handle()
130     *
131     */
132    function handle($match, $state, $pos, Doku_Handler $handler)
133    {
134
135        switch ($state) {
136
137            case DOKU_LEXER_ENTER:
138                $trimmedMatch = trim($match);
139                $language = preg_replace("(`{3}|~{3})", "", $trimmedMatch);
140
141                $attributes = [TagAttributes::TYPE_KEY => $language];
142                return array(
143                    PluginUtility::STATE => $state,
144                    PluginUtility::ATTRIBUTES => $attributes
145                );
146
147            case DOKU_LEXER_UNMATCHED :
148
149                return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler);
150
151
152            case DOKU_LEXER_EXIT :
153
154                return array(PluginUtility::STATE => $state);
155
156
157        }
158        return array();
159
160    }
161
162    /**
163     * Render the output
164     * @param string $format
165     * @param Doku_Renderer $renderer
166     * @param array $data - what the function handle() return'ed
167     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
168     * @see DokuWiki_Syntax_Plugin::render()
169     *
170     *
171     */
172    function render($format, Doku_Renderer $renderer, $data)
173    {
174
175
176        if ($format == 'xhtml') {
177
178            /** @var Doku_Renderer_xhtml $renderer */
179            $state = $data [PluginUtility::STATE];
180            switch ($state) {
181                case DOKU_LEXER_ENTER :
182                    $attributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES], syntax_plugin_combo_code::CODE_TAG);
183                    Prism::htmlEnter($renderer, $this, $attributes);
184                    break;
185
186                case DOKU_LEXER_UNMATCHED :
187
188                    // Delete the eol at the beginning and end
189                    // otherwise we get a big block
190                    $payload = trim($data[PluginUtility::PAYLOAD], "\n\r");
191                    $renderer->doc .= PluginUtility::htmlEncode($payload);
192                    break;
193
194                case DOKU_LEXER_EXIT :
195
196                    Prism::htmlExit($renderer);
197                    break;
198
199            }
200            return true;
201        } else if ($format == 'code') {
202
203            /**
204             * The renderer to download the code
205             * @var Doku_Renderer_code $renderer
206             */
207            $state = $data [PluginUtility::STATE];
208            if ($state == DOKU_LEXER_UNMATCHED) {
209
210                $attributes = $data[PluginUtility::ATTRIBUTES];
211                $text = $data[PluginUtility::PAYLOAD];
212                $language = strtolower($attributes["type"]);
213                $filename = $language;
214                $renderer->code($text, $language, $filename);
215
216            }
217        }
218
219        // unsupported $mode
220        return false;
221
222    }
223
224
225}
226
227