1<?php
2
3
4require_once(__DIR__ . "/../ComboStrap/PluginUtility.php");
5
6
7use ComboStrap\Call;
8use ComboStrap\CallStack;
9use ComboStrap\LogUtility;
10use ComboStrap\PluginUtility;
11use ComboStrap\Tag;
12use ComboStrap\TagAttributes;
13
14if (!defined('DOKU_INC')) die();
15
16/**
17 *
18 * A paragraph syntax
19 *
20 * This syntax component is used dynamically while parsing (at the {@link DOKU_LEXER_END} of {@link \dokuwiki\Extension\SyntaxPlugin::handle()}
21 * with the function {@link \syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack()}
22 *
23 *
24 * !!!!!
25 *
26 * Info: The `eol` call are temporary created with {@link \dokuwiki\Parsing\ParserMode\Eol}
27 * and transformed to `p_open` and `p_close` via {@link \dokuwiki\Parsing\Handler\Block::process()}
28 *
29 * Note: p_open call may appears when the {@link \ComboStrap\Syntax::getPType()} is set to `block` or `stack`
30 * and the next call is not a block or a stack
31 *
32 * !!!!!
33 *
34 *
35 * Note on Typography
36 * TODO: https://github.com/typekit/webfontloader
37 * https://www.dokuwiki.org/plugin:typography
38 * https://stackoverflow.design/email/base/typography/
39 * http://kyleamathews.github.io/typography.js/
40 * https://docs.gitbook.com/features/advanced-branding (Roboto, Roboto Slab, Open Sans, Source Sans Pro, Lato, Ubuntu, Raleway, Merriweather)
41 * http://themenectar.com/docs/salient/theme-options/typography/ (Doc)
42 * https://www.modularscale.com/ - see the size of font
43 *
44 * See the fonts on your computer
45 * https://wordmark.it/
46 *
47 * What's a type ? Type terminology
48 * https://www.supremo.co.uk/typeterms/
49 *
50 * https://theprotoolbox.com/browse/font-tools/
51 */
52class syntax_plugin_combo_para extends DokuWiki_Syntax_Plugin
53{
54
55    const TAG = 'para';
56    const COMPONENT = "combo_para";
57
58
59    /**
60     * The component that have a `normal` {@link \dokuwiki\Extension\SyntaxPlugin::getPType()}
61     * are wrapped in a p element by {@link Block::process()} if they are not in a component with a `block` ptype
62     *
63     * This function makes it easy for the test
64     * to do it and gives a little bit of documentation
65     * on why there is a `p` in the test
66     * @param $html
67     * @return string the html wrapped in p
68     */
69    public static function wrapInP($html)
70    {
71        return "<p>" . $html . "</p>";
72    }
73
74
75    /**
76     * Syntax Type.
77     *
78     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
79     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
80     */
81    function getType()
82    {
83        return 'paragraphs';
84    }
85
86    /**
87     * How Dokuwiki will add P element
88     *
89     *  * 'normal' - The plugin can be used inside paragraphs
90     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
91     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
92     *
93     * @see DokuWiki_Syntax_Plugin::getPType()
94     */
95    function getPType()
96    {
97        /**
98         * !important!
99         * The {@link \dokuwiki\Parsing\Handler\Block::process()}
100         * will then not create an extra paragraph after it encounters a block
101         */
102        return 'block';
103    }
104
105    /**
106     * @return array
107     * Allow which kind of plugin inside
108     *
109     * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs')
110     * because we manage self the content and we call self the parser
111     */
112    function getAllowedTypes()
113    {
114        /**
115         * Not needed as we don't have any {@link syntax_plugin_combo_para::connectTo()}
116         */
117        return array();
118    }
119
120
121    /**
122     * @see Doku_Parser_Mode::getSort()
123     * The mode with the lowest sort number will win out
124     *
125     */
126    function getSort()
127    {
128        /**
129         * Not really needed as we don't have any {@link syntax_plugin_combo_para::connectTo()}
130         *
131         * Note: if we start to use it should be less than 370
132         * Ie Less than {@link \dokuwiki\Parsing\ParserMode\Eol::getSort()}
133         */
134        return 369;
135    }
136
137
138    function connectTo($mode)
139    {
140
141        /**
142         * No need to connect
143         * This syntax plugin is added dynamically with the {@link Tag::processEolToEndStack()}
144         * function
145         */
146
147    }
148
149
150    /**
151     * The handler for an internal link
152     * based on `internallink` in {@link Doku_Handler}
153     * The handler call the good renderer in {@link Doku_Renderer_xhtml} with
154     * the parameters (ie for instance internallink)
155     * @param string $match
156     * @param int $state
157     * @param int $pos
158     * @param Doku_Handler $handler
159     * @return array|bool
160     */
161    function handle($match, $state, $pos, Doku_Handler $handler)
162    {
163
164        /**
165         * No need to handle,
166         * there is no {@link syntax_plugin_combo_para::connectTo() connection}
167         */
168        return true;
169
170
171    }
172
173    /**
174     * Render the output
175     * @param string $format
176     * @param Doku_Renderer $renderer
177     * @param array $data - what the function handle() return'ed
178     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
179     * @see DokuWiki_Syntax_Plugin::render()
180     *
181     *
182     */
183    function render($format, Doku_Renderer $renderer, $data)
184    {
185        // The data
186        switch ($format) {
187            case 'xhtml':
188
189                /** @var Doku_Renderer_xhtml $renderer */
190                $state = $data[PluginUtility::STATE];
191
192                switch ($state) {
193                    case DOKU_LEXER_ENTER:
194                        $attributes = $data[PluginUtility::ATTRIBUTES];
195                        $tagAttributes = TagAttributes::createFromCallStackArray($attributes);
196                        if ($tagAttributes->hasComponentAttribute(TagAttributes::TYPE_KEY)) {
197                            $class = $tagAttributes->getType();
198                            $tagAttributes->addClassName($class);
199                        }
200                        $renderer->doc .= $tagAttributes->toHtmlEnterTag("p");
201                        break;
202                    case DOKU_LEXER_SPECIAL:
203                        $attributes = $data[PluginUtility::ATTRIBUTES];
204                        $tagAttributes = TagAttributes::createFromCallStackArray($attributes);
205                        $renderer->doc .= $tagAttributes->toHtmlEnterTag("p");
206                        $renderer->doc .= "</p>";
207                        break;
208                    case DOKU_LEXER_EXIT:
209                        $renderer->doc .= "</p>";
210                        break;
211                }
212                return true;
213
214            case 'metadata':
215
216                /** @var Doku_Renderer_metadata $renderer */
217
218
219                return true;
220
221
222            case renderer_plugin_combo_analytics::RENDERER_FORMAT:
223
224                /**
225                 * @var renderer_plugin_combo_analytics $renderer
226                 */
227                return true;
228
229        }
230        // unsupported $mode
231        return false;
232    }
233
234    /**
235     *
236     * Transform EOL into paragraph
237     * between the {@link CallStack::getActualCall()} and the {@link CallStack::isPointerAtEnd()}
238     * of the stack
239     *
240     * Info: Basically, you get an new paragraph with a blank line or `\\` : https://www.dokuwiki.org/faq:newlines
241     *
242     * It replaces the  {@link \dokuwiki\Parsing\Handler\Block::process() eol to paragraph Dokuwiki process}
243     * that takes place at the end of parsing process on the whole stack
244     *
245     * Info: The `eol` call are temporary created with {@link \dokuwiki\Parsing\ParserMode\Eol}
246     * and transformed to `p_open` and `p_close` via {@link \dokuwiki\Parsing\Handler\Block::process()}
247     *
248     * @param CallStack $callstack
249     * @param array $attributes - the attributes passed to the paragraph
250     */
251    public static function fromEolToParagraphUntilEndOfStack(CallStack &$callstack, array $attributes)
252    {
253
254        if (!is_array($attributes)) {
255            LogUtility::msg("The passed attributes array ($attributes) for the creation of the paragraph is not an array", LogUtility::LVL_MSG_ERROR);
256            $attributes = [];
257        }
258
259        /**
260         * The syntax plugin that implements the paragraph
261         * ie {@link \syntax_plugin_combo_para}
262         * We will transform the eol with a call to this syntax plugin
263         * to create the paragraph
264         */
265        $paragraphComponent = \syntax_plugin_combo_para::COMPONENT;
266        $paragraphTag = \syntax_plugin_combo_para::TAG;
267
268        /**
269         * The running variables
270         */
271        $paragraphIsOpen = false; // A pointer to see if the paragraph is open
272        while ($actualCall = $callstack->next()) {
273
274            /**
275             * end of line is not always present
276             * because the pattern is eating it
277             * Example (list_open)
278             */
279            if ($paragraphIsOpen && $actualCall->getTagName() !== "eol") {
280                if ($actualCall->getDisplay() == Call::BlOCK_DISPLAY) {
281                    $paragraphIsOpen = false;
282                    $callstack->insertBefore(
283                        Call::createComboCall(
284                            $paragraphTag,
285                            DOKU_LEXER_EXIT,
286                            $attributes
287                        )
288                    );
289                }
290            }
291
292            if ($actualCall->getTagName() === "eol") {
293
294                /**
295                 * Next Call that is not the empty string
296                 * Because Empty string would create an empty paragraph
297                 *
298                 * Start at 1 because we may not do
299                 * a loop if we are at the end, the next call
300                 * will return false
301                 */
302                $i = 1;
303                while ($nextCall = $callstack->next()) {
304                    if (!(
305                        trim($nextCall->getCapturedContent()) == "" &&
306                        $nextCall->isTextCall()
307                    )) {
308                        break;
309                    }
310                    $i++;
311                }
312                while ($i > 0) { // go back
313                    $i--;
314                    $callstack->previous();
315                }
316                if ($nextCall === false) {
317                    $nextDisplay = "last";
318                    $nextCall = null;
319                } else {
320                    $nextDisplay = $nextCall->getDisplay();
321                }
322
323
324                /**
325                 * Processing
326                 */
327                if (!$paragraphIsOpen) {
328
329                    switch ($nextDisplay) {
330                        case Call::BlOCK_DISPLAY:
331                        case "last":
332                            $callstack->deleteActualCallAndPrevious();
333                            break;
334                        case Call::INLINE_DISPLAY:
335                            $paragraphIsOpen = true;
336                            $actualCall->updateToPluginComponent(
337                                $paragraphComponent,
338                                DOKU_LEXER_ENTER,
339                                $attributes
340                            );
341                            break;
342                        case "eol":
343                            /**
344                             * Empty line
345                             */
346                            $actualCall->updateToPluginComponent(
347                                $paragraphComponent,
348                                DOKU_LEXER_ENTER,
349                                $attributes
350                            );
351                            $nextCall->updateToPluginComponent(
352                                $paragraphComponent,
353                                DOKU_LEXER_EXIT
354                            );
355                            $callstack->next();
356                            break;
357                        default:
358                            LogUtility::msg("The eol action for the combination enter / (" . $nextDisplay . ") of the call ( $nextCall ) was not implemented", LogUtility::LVL_MSG_ERROR);
359                            break;
360                    }
361                } else {
362                    /**
363                     * Paragraph is open
364                     */
365                    switch ($nextDisplay) {
366                        case "eol":
367                            /**
368                             * Empty line
369                             */
370                            $actualCall->updateToPluginComponent(
371                                $paragraphComponent,
372                                DOKU_LEXER_EXIT
373                            );
374                            $nextCall->updateToPluginComponent(
375                                $paragraphComponent,
376                                DOKU_LEXER_ENTER,
377                                $attributes
378                            );
379                            $callstack->next();
380                            break;
381                        case Call::INLINE_DISPLAY:
382                            // A space
383                            $actualCall->updateEolToSpace();
384                            break;
385                        case Call::BlOCK_DISPLAY:
386                        case "last";
387                            $actualCall->updateToPluginComponent(
388                                $paragraphComponent,
389                                DOKU_LEXER_EXIT
390                            );
391                            $paragraphIsOpen = false;
392                            break;
393                        default:
394                            LogUtility::msg("The display for a open paragraph (" . $nextDisplay . ") is not implemented", LogUtility::LVL_MSG_ERROR);
395                            break;
396                    }
397                }
398
399            }
400        }
401
402        // if the paragraph is open close it
403        if ($paragraphIsOpen) {
404            $callstack->insertBefore(
405                Call::createComboCall(
406                    \syntax_plugin_combo_para::TAG,
407                    DOKU_LEXER_EXIT
408                )
409            );
410        }
411    }
412
413}
414
415