xref: /plugin/combo/syntax/preformatted.php (revision 37748cd8654635afbeca80942126742f0f4cc346)
1<?php
2
3
4use ComboStrap\CallStack;
5use ComboStrap\PluginUtility;
6use ComboStrap\Prism;
7use ComboStrap\TagAttributes;
8
9require_once(__DIR__ . '/../ComboStrap/PluginUtility.php');
10
11/**
12 * Overwrite {@link \dokuwiki\Parsing\ParserMode\Preformatted}
13 */
14if (!defined('DOKU_INC')) die();
15
16/**
17 *
18 * Preformatted shows a block of text as code via space at the beginning of the line
19 *
20 * It is the same as <a href="https://github.github.com/gfm/#indented-code-blocks">indented-code-blocks</a>
21 * but with 2 characters in place of 4
22 *
23 * This component is used to:
24 *   * showcase preformatted as {@link \ComboStrap\Prism} component
25 *   * disable preformatted mode via the function {@link syntax_plugin_combo_preformatted::disablePreformatted()}
26 * used in other HTML super set syntax component to disable this behavior
27 *
28 * It's also the original markdown specification
29 *
30 */
31class syntax_plugin_combo_preformatted extends DokuWiki_Syntax_Plugin
32{
33
34    const TAG = 'preformatted';
35
36
37    const CONF_PREFORMATTED_ENABLE = "preformattedEnable";
38    /**
39     * The content is not printed when the content of a preformatted block is empty
40     */
41    const CONF_PREFORMATTED_EMPTY_CONTENT_NOT_PRINTED_ENABLE = "preformattedEmptyContentNotPrintedEnable";
42
43    const HAS_EMPTY_CONTENT = "hasEmptyContent";
44
45    /**
46     * Syntax Type.
47     *
48     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
49     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
50     * @see DokuWiki_Syntax_Plugin::getType()
51     */
52    function getType()
53    {
54        return 'formatting';
55    }
56
57    /**
58     * How DokuWiki will add P element
59     *
60     *  * 'normal' - The plugin can be used inside paragraphs
61     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
62     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
63     *
64     * @see DokuWiki_Syntax_Plugin::getPType()
65     */
66    function getPType()
67    {
68        return 'block';
69    }
70
71
72    /**
73     * @return array
74     * Allow which kind of plugin inside
75     *
76     * No one of array('baseonly','container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs')
77     * because we manage self the content and we call self the parser
78     *
79     * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php
80     */
81    function getAllowedTypes()
82    {
83        return array();
84    }
85
86    function getSort()
87    {
88        /**
89         * Should be less than the preformatted mode
90         * which is 20
91         * From {@link \dokuwiki\Parsing\ParserMode\Preformatted::getSort()}
92         **/
93        return 19;
94    }
95
96
97    function connectTo($mode)
98    {
99
100        if ($this->getConf(self::CONF_PREFORMATTED_ENABLE, 1)) {
101
102            /**
103             * From {@link \dokuwiki\Parsing\ParserMode\Preformatted}
104             */
105            $patterns = array('\n  (?![\*\-])', '\n\t(?![\*\-])');
106            foreach ($patterns as $pattern) {
107                $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent()));
108            }
109            $this->Lexer->addPattern('\n  ', PluginUtility::getModeFromTag($this->getPluginComponent()));
110            $this->Lexer->addPattern('\n\t', PluginUtility::getModeFromTag($this->getPluginComponent()));
111
112        }
113
114    }
115
116
117    function postConnect()
118    {
119        /**
120         * From {@link \dokuwiki\Parsing\ParserMode\Preformatted}
121         */
122        $this->Lexer->addExitPattern('\n', PluginUtility::getModeFromTag($this->getPluginComponent()));
123
124    }
125
126    /**
127     *
128     * The handle function goal is to parse the matched syntax through the pattern function
129     * and to return the result for use in the renderer
130     * This result is always cached until the page is modified.
131     * @param string $match
132     * @param int $state
133     * @param int $pos - byte position in the original source file
134     * @param Doku_Handler $handler
135     * @return array|bool
136     * @see DokuWiki_Syntax_Plugin::handle()
137     *
138     */
139    function handle($match, $state, $pos, Doku_Handler $handler)
140    {
141
142        switch ($state) {
143            case DOKU_LEXER_ENTER:
144                /**
145                 * used at the {@link DOKU_LEXER_EXIT} state
146                 * to add the {@link syntax_plugin_combo_preformatted::HAS_EMPTY_CONTENT}
147                 * flag
148                 */
149                $attributes = [];
150                return array(
151                    PluginUtility::STATE => $state,
152                    PluginUtility::ATTRIBUTES => $attributes
153                );
154            case DOKU_LEXER_MATCHED:
155                return array(
156                    PluginUtility::STATE => $state
157                );
158            case DOKU_LEXER_UNMATCHED:
159                return array(
160                    PluginUtility::STATE => $state,
161                    PluginUtility::PAYLOAD => $match
162                );
163            case DOKU_LEXER_EXIT:
164                $callStack = CallStack::createFromHandler($handler);
165                $openingCall = $callStack->moveToPreviousCorrespondingOpeningCall();
166                $text = "";
167                while ($callStack->next()) {
168                    $actualCall = $callStack->getActualCall();
169                    if ($actualCall->getState() == DOKU_LEXER_UNMATCHED && $actualCall->getTagName() == self::TAG) {
170                        $text .= $actualCall->getPayload() . "\n";
171                        $callStack->deleteActualCallAndPrevious();
172                    }
173                }
174                if (trim($text) == "") {
175                    $openingCall->addAttribute(self::HAS_EMPTY_CONTENT, true);
176                }
177                return array(
178                    PluginUtility::STATE => $state,
179                    PluginUtility::PAYLOAD => $text
180                );
181        }
182        return array();
183
184    }
185
186    /**
187     * Render the output
188     * @param string $format
189     * @param Doku_Renderer $renderer
190     * @param array $data - what the function handle() return'ed
191     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
192     * @see DokuWiki_Syntax_Plugin::render()
193     *
194     *
195     */
196    function render($format, Doku_Renderer $renderer, $data)
197    {
198        if ($format == "xhtml") {
199            /**
200             * @var Doku_Renderer_xhtml $renderer
201             */
202            $state = $data[PluginUtility::STATE];
203            $emptyContentShouldBeDeleted = $this->getConf(self::CONF_PREFORMATTED_EMPTY_CONTENT_NOT_PRINTED_ENABLE, 1);
204            switch ($state) {
205                case DOKU_LEXER_ENTER:
206                    $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES], self::TAG);
207                    $hasEmptyContent = $tagAttributes->getValueAndRemove(self::HAS_EMPTY_CONTENT,0);
208                    if (!($hasEmptyContent && $emptyContentShouldBeDeleted)) {
209                        Prism::htmlEnter($renderer, $this, $tagAttributes);
210                    }
211                    break;
212                case DOKU_LEXER_EXIT:
213                    // Delete the eol at the beginning and end
214                    // otherwise we get a big block
215                    $text = trim($data[PluginUtility::PAYLOAD], "\n\r");
216                    if (!(trim($text) == "" && $emptyContentShouldBeDeleted)) {
217
218                        $renderer->doc .= PluginUtility::htmlEncode($text);
219                        Prism::htmlExit($renderer);
220                    }
221                    break;
222            }
223        }
224        return false;
225    }
226
227    /**
228     * Utility function to disable preformatted formatting
229     * in a HTML super set element
230     *
231     * @param $mode
232     * @return bool
233     */
234    public
235    static function disablePreformatted($mode)
236    {
237        /**
238         * Disable {@link \dokuwiki\Parsing\ParserMode\Preformatted}
239         * and this syntax
240         */
241        if (
242            $mode == 'preformatted'
243            ||
244            $mode == PluginUtility::getModeFromTag(syntax_plugin_combo_preformatted::TAG)
245        ) {
246            return false;
247        } else {
248            return true;
249        }
250    }
251
252}
253
254