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