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