Completed
Push — master ( cb46a0...974133 )
by Marc
04:34
created

src/Inigo/Parser.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Kaloa\Renderer\Inigo;
4
5
use Kaloa\Renderer\Config;
6
use Kaloa\Renderer\Inigo\Handler\AbbrHandler;
7
use Kaloa\Renderer\Inigo\Handler\AmazonHandler;
8
use Kaloa\Renderer\Inigo\Handler\CodeHandler;
9
use Kaloa\Renderer\Inigo\Handler\FootnotesHandler;
10
use Kaloa\Renderer\Inigo\Handler\HTMLHandler;
11
use Kaloa\Renderer\Inigo\Handler\ImgHandler;
12
use Kaloa\Renderer\Inigo\Handler\ProtoHandler;
13
use Kaloa\Renderer\Inigo\Handler\QuoteHandler;
14
use Kaloa\Renderer\Inigo\Handler\SimpleHandler;
15
use Kaloa\Renderer\Inigo\Handler\UrlHandler;
16
use Kaloa\Renderer\Inigo\Handler\YouTubeHandler;
17
use Kaloa\Renderer\Inigo\Tag;
18
use SplStack;
19
20
/**
21
 * Inigo
22
 *
23
 * Do not use this renderer. The code is very (!) old. It's in here for
24
 * backwards compatibility reasons
25
 *
26
 * @author Marc Ermshaus
27
 */
28
final class Parser
29
{
30
    const TAG_OUTLINE          = 0x1;
31
    const TAG_INLINE           = 0x2;
32
    const TAG_PRE              = 0x4;
33
    const TAG_SINGLE           = 0x8;
34
    const TAG_CLEAR_CONTENT    = 0x10;
35
    const TAG_FORCE_PARAGRAPHS = 0x20;
36
37
    const PC_IMG_ALIGN_LEFT   = 0;
38
    const PC_IMG_ALIGN_RIGHT  = 1;
39
    const PC_IMG_ALIGN_CENTER = 2;
40
41
    /* "German" style
42
    const PC_PARSER_QUOTE_LEFT = '&#8222;';
43
    const PC_PARSER_QUOTE_RIGHT = '&#8220;');
44
    /**/
45
    /* "French" style */
46
    const PC_PARSER_QUOTE_LEFT  = '&raquo;';
47
    const PC_PARSER_QUOTE_RIGHT = '&laquo;';
48
    /**/
49
50
    private $s = '';
51
    private $m_stack;
52
    private $m_handlers;
53
    private $m_vars;
54
55
    /**
56
     *
57
     * @param Config $config
58
     */
59
    public function addDefaultHandlers(Config $config)
60
    {
61
        $this->addSetting('image-dir', $config->getResourceBasePath() . '/');
62
63
        // Example for multiple tags being displayed in the same way
64
        $this
65
        ->addHandler(new SimpleHandler('b', Parser::TAG_INLINE, '<b>', '</b>'))
66
        ->addHandler(new SimpleHandler('strong', Parser::TAG_INLINE, '<strong>', '</strong>'))
67
        ->addHandler(new SimpleHandler('i', Parser::TAG_INLINE, '<i>', '</i>'))
68
        ->addHandler(new SimpleHandler('em', Parser::TAG_INLINE, '<em>', '</em>'))
69
70
        ->addHandler(new SimpleHandler('icode', Parser::TAG_INLINE, '<code>', '</code>'))
71
72
        ->addHandler(new SimpleHandler('u', Parser::TAG_INLINE, '<u>', '</u>'))
73
        ->addHandler(new SimpleHandler('s|strike', Parser::TAG_INLINE, '<s>', '</s>'))
74
75
        // Used to display other tags. Tags with type Parser::TAG_PRE will not be parsed
76
        // This tag belongs also to two types
77
78
        ->addHandler(new SimpleHandler('off|noparse', Parser::TAG_INLINE | Parser::TAG_PRE, '', ''))
79
        ->addHandler(new SimpleHandler('var', Parser::TAG_INLINE | Parser::TAG_PRE, '<var>', '</var>'))
80
//        ->addHandler(new SimpleHandler(
81
//            'quote',
82
//            Parser::TAG_OUTLINE | Parser::TAG_FORCE_PARAGRAPHS,
83
//            '<blockquote>',
84
//            "</blockquote>\n\n"
85
//        ))
86
87
        ->addHandler(new QuoteHandler())
88
89
        /* Most replacements are rather simple */
90
        ->addHandler(new SimpleHandler('h1', Parser::TAG_OUTLINE, "<h1>", "</h1>\n\n"))
91
        ->addHandler(new SimpleHandler('h2', Parser::TAG_OUTLINE, "<h2>", "</h2>\n\n"))
92
        ->addHandler(new SimpleHandler('h3', Parser::TAG_OUTLINE, "<h3>", "</h3>\n\n"))
93
        ->addHandler(new SimpleHandler('h4', Parser::TAG_OUTLINE, "<h4>", "</h4>\n\n"))
94
        ->addHandler(new SimpleHandler('h5', Parser::TAG_OUTLINE, "<h5>", "</h5>\n\n"))
95
        ->addHandler(new SimpleHandler('h6', Parser::TAG_OUTLINE, "<h6>", "</h6>\n\n"))
96
        ->addHandler(new SimpleHandler('dl', Parser::TAG_OUTLINE, "<dl>", "\n\n</dl>\n\n"))
97
        ->addHandler(new SimpleHandler('dt', Parser::TAG_OUTLINE, "\n\n<dt>", "</dt>"))
98
        ->addHandler(new SimpleHandler('dd', Parser::TAG_OUTLINE, "\n<dd>", "</dd>"))
99
        ->addHandler(new SimpleHandler('ul', Parser::TAG_OUTLINE, "<ul>", "\n</ul>\n\n"))
100
        ->addHandler(new SimpleHandler('ol', Parser::TAG_OUTLINE, "<ol>", "\n</ol>\n\n"))
101
        ->addHandler(new SimpleHandler('li', Parser::TAG_OUTLINE, "\n<li>", "</li>"))
102
        ->addHandler(new SimpleHandler('table', Parser::TAG_OUTLINE, "<table>", "\n</table>\n\n"))
103
        ->addHandler(new SimpleHandler('tr', Parser::TAG_OUTLINE, "\n<tr>", "\n</tr>"))
104
        ->addHandler(new SimpleHandler('td', Parser::TAG_OUTLINE, "\n<td>", "</td>"))
105
        ->addHandler(new SimpleHandler('th', Parser::TAG_OUTLINE, "\n<th>", "</th>"))
106
107
        ->addHandler(new SimpleHandler('indent', Parser::TAG_OUTLINE, "<div style=\"margin-left: 30px;\">", "</div>\n\n"))
108
        ->addHandler(new SimpleHandler('center', Parser::TAG_OUTLINE, "<div style=\"text-align: center;\">", "</div>\n\n"))
109
110
        ->addHandler(new UrlHandler())
111
        ->addHandler(new ImgHandler())
112
        ->addHandler(new AmazonHandler())
113
        ->addHandler(new AbbrHandler())
114
        ->addHandler(new HTMLHandler())
115
        ->addHandler(new CodeHandler($config->getSyntaxHighlighter()))
116
        ->addHandler(new FootnotesHandler())
117
        ->addHandler(new YouTubeHandler());
118
    }
119
120
    /**
121
     *
122
     */
123
    public function addHandler(ProtoHandler $class)
124
    {
125
        $tags = explode('|', $class->name);
126
127
        $j = 0;
128
129
        foreach ($tags as $tag) {
130
            if (trim($tag) !== '') {
131
                $temp = array();
132
                $temp['name'] = $tag;
133
134
                if (is_array($class->type)) {
135
                    $temp['type'] = $class->type[$j];
136
                } else {
137
                    $temp['type'] = $class->type;
138
                }
139
                $temp['function'] = $class;
140
141
                $this->m_handlers[] = $temp;
142
            }
143
144
            $j++;
145
        }
146
147
        return $this;
148
    }
149
150
    /**
151
     *
152
     */
153
    public function addSetting($name, $value = '')
154
    {
155
        $this->m_vars[$name] = $value;
156
    }
157
158
    /**
159
     *
160
     */
161
    private function printHandlerMarkup(Tag $tag, $front = true, $tag_content = '')
162
    {
163
        $data = array();
164
165
        $data['tag']    = $tag->getName();
166
        $data['params'] = $tag->getAttributes();
167
        $data['front']  = $front;
168
        $data['vars']   = $this->m_vars;
169
170
        if ($tag_content !== '') {
171
            $data['content'] = $tag_content;
172
        }
173
174
        $i = 0;
175
176
        $tagCnt = count($this->m_handlers);
177
178
        while (($i < $tagCnt) && ($this->m_handlers[$i]['name'] !== $data['tag'])) {
179
            $i++;
180
        }
181
182
        return $this->m_handlers[$i]['function']->draw($data);
183
    }
184
185
    /**
186
     * Gets the next tag
187
     *
188
     * @param  string $s        String to parse
189
     * @param  int    $i        Offset where search begins
190
     * @param  int    $position Will be filled with next tag's offset (FALSE if
191
     *                          there are no more tags)
192
     * @return Tag
193
     */
194
    private function getNextTag(&$s, $i, &$position)
195
    {
196
        $j = mb_strpos($s, '[', $i);
197
        $k = mb_strpos($s, ']', $j + 1);
198
199
        if ($j === false || $k === false) {
200
            $position = false;
201
            return null;
202
        }
203
204
        $t = mb_substr($s, $j + 1, $k - ($j + 1));
205
        $l = mb_strrpos($t, '[');
206
207
        if ($l !== false) {
208
            $j += $l + 1;
209
        }
210
211
        $position = $j;
212
213
        $tagString = mb_substr($s, $j, $k - $j + 1);
214
215
        return new Tag($tagString, $this->getHandlers());
216
    }
217
218
    /**
219
     *
220
     * @return
221
     */
222
    public function getHandlers()
223
    {
224
        return $this->m_handlers;
225
    }
226
227
    /**
228
     *
229
     */
230
    public function parse($s)
231
    {
232
        // Cleaning the data that shall be parsed
233
234
        $s = trim($s);
235
        $s = str_replace("\r\n", "\n", $s);
236
        $s = str_replace("\r", "\n", $s);
237
        $s = preg_replace("/\n{3,}/", "\n\n", $s);
238
239
        $this->s = $s;
240
241
        // Preprocessing
242
243
        foreach ($this->m_handlers as $h) {
244
            $h['function']->initialize();
245
        }
246
247
        // Heavy lifting
248
249
        $ret = ($this->s === '') ? '' : $this->parseEx();
250
251
        // Postprocessing
252
253
        $data = array();
254
        $data['vars'] = $this->m_vars;
255
256
        foreach ($this->m_handlers as $h) {
257
            $data['tag'] = $h['name'];
258
            $ret = $h['function']->postProcess($ret, $data);
259
        }
260
261
        return trim($ret);
262
    }
263
264
    /**
265
     *
266
     * @param  Tag  $tag
267
     * @return bool
268
     */
269
    private function fitsStack(Tag $tag)
270
    {
271
        return ($tag->getName() === $this->m_stack->top()->getName());
272
    }
273
274
    /**
275
     *
276
     */
277
    private function parseEx()
278
    {
279
        $ret = '';
280
281
        $cdata = '';
282
        $last_pos = 0;
283
        $f_clear_content = false;
284
285
        $this->m_stack = new SplStack();
286
287
        $tag_content = '';
288
        $pos = 0;
289
290
        $tag = $this->getNextTag($this->s, $pos, $tag_pos);
291
292
        while ($tag !== null) {
293
            // Handle all occurences of "[...]" that are not part of the list of
294
            // registered tags (m_handlers) as CDATA
295
            $executeTag = $tag->isValid();
296
297
            // If we are parsing inside of a TAG_PRE tag, do not execute current
298
            // tag (= pretend it is CDATA) unless it is the corresponding
299
            // closing tag to the active TAG_PRE tag
300
            if ($executeTag
301
                && $this->m_stack->count() > 0
302
                && $this->m_stack->top()->isOfType(self::TAG_PRE)
303
            ) {
304
                $executeTag = ($tag->isClosingTag() && $this->fitsStack($tag));
305
            }
306
307
            if ($executeTag) {
308
                // Tag is valid and not inside of a TAG_PRE tag, execute it
309
310
                // Get CDATA
311
                $cdata .= $this->formatString(mb_substr(
312
                    $this->s,
313
                    $last_pos,
314
                    $tag_pos - $last_pos
315
                ));
316
317
                if (!$tag->isClosingTag()) {
318
                    // Opening tag
319
320
                    if (!$tag->isOfType(self::TAG_INLINE)) {
321
                        // Opening tag, outline tag
322
323 View Code Duplication
                        if ($f_clear_content) {
324
                            $tag_content .= $this->printCData($cdata, true);
325
                            $tag_content .= $this->printHandlerMarkup($tag, true);
326
                        } else {
327
                            $ret .= $this->printCData($cdata, true);
328
                            $ret .= $this->printHandlerMarkup($tag, true);
329
                        }
330
331
                        // If clear content tag, detect content and skip parsing
332
                        if ($tag->isOfType(self::TAG_CLEAR_CONTENT)) {
333
                            $f_clear_content = true;
334
                        }
335
                    } else {
336
                        // Opening tag, inline tag
337
338
                        $cdata .= $this->printHandlerMarkup($tag, true);
339
                    }
340
341
                    if (!$tag->isOfType(self::TAG_SINGLE)) {
342
                        $this->m_stack->push($tag);
343
                    }
344
                } else {
345
                    // Closing tag
346
347
                    if (!$tag->isOfType(self::TAG_INLINE)) {
348
                        // Closing tag, outline tag
349
350
                        if ($tag->isOfType(self::TAG_CLEAR_CONTENT)) {
351
                            // Closing tag, outline tag, clear content tag
352
353
                            $f_clear_content = false;
354
                            $tag_content .= $this->printCData($cdata);
355
                            $ret .= $this->printHandlerMarkup($tag, false, $tag_content);
356
                            $tag_content = '';
357 View Code Duplication
                        } else {
358
                            // Closing tag, outline tag, NOT clear content tag
359
360
                            if ($f_clear_content) {
361
                                $tag_content .= $this->printCData($cdata);
362
                                $tag_content .= $this->printHandlerMarkup($tag, false);
363
                            } else {
364
                                $ret .= $this->printCData($cdata);
365
                                $ret .= $this->printHandlerMarkup($tag, false);
366
                            }
367
                        }
368
                    } else {
369
                        // Closing tag, inline tag
370
371
                        $cdata .= $this->printHandlerMarkup($tag, false);
372
                    }
373
374
                    // Tag complete, remove from stack
375
                    if ($this->fitsStack($tag)) {
376
                        $this->m_stack->pop();
377
                    } else {
378
                        // Markup error
379
                    }
380
                }
381
            } else {
382
                // Tag is CDATA
383
384
                $cdata .= $this->formatString(mb_substr(
385
                    $this->s,
386
                    $last_pos,
387
                    $tag_pos - $last_pos
388
                ) . $tag->getRawData());
389
            }
390
391
            $pos = $tag_pos + mb_strlen($tag->getRawData());
392
            $last_pos = $pos;
393
            $tag = $this->getNextTag($this->s, $pos, $tag_pos);
394
        }
395
396
        // Add string data after last tag as CDATA
397
        $cdata .= $this->formatString(mb_substr($this->s, $last_pos));
398
        $ret   .= $this->printCData($cdata, true);
399
400
        return $ret;
401
    }
402
403
    /**
404
     * Formats small pieces of CDATA
405
     *
406
     * @param  string $s
407
     * @return string
408
     */
409
    private function formatString($s)
410
    {
411
        static $last_tag = null;
412
413
        if ($this->m_stack->count() > 0
414
            && $this->m_stack->top()->isOfType(self::TAG_PRE)
415
        ) {
416
            // Do not format text inside of TAG_PRE tags
417
418
            return $s;
419
        }
420
421
        /*
422
         * TODO Replace double-quotes alternately with nice left and right
423
         * quotes
424
         */
425
426
        if ($last_tag !== null) {
427
            #echo $last_tag->getName();
428
        }
429
430
        #echo '|' . $s . '|';
431
432
        // opening quote
433
        if ($last_tag !== null && $last_tag->isOfType(self::TAG_INLINE)) {
434
            $s = preg_replace('/([\s])&quot;/', '\1&raquo;', $s);
435
436
            #echo 'without';
437
        } else {
438
            $s = preg_replace('/([\s]|^)&quot;/', '\1&raquo;', $s);
439
        }
440
441
        // [X][X] will always be rendered as [S][S], not as [S][E]
442
        $s = preg_replace('/(&raquo;)&quot;/', '\1&raquo;', $s);
443
444
        #echo '<br />';
445
446
        // everything else is closing quote
447
        $s = str_replace('&quot;', '&laquo;', $s);
448
449
        $s = str_replace('--', '&ndash;', $s);
450
451
        if ($this->m_stack->count() > 0) {
452
            $last_tag = $this->m_stack->top();
453
        } else {
454
            $last_tag = null;
455
        }
456
457
        $e = function ($s) { return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); };
458
459
        return $e($s);
460
    }
461
462
    /**
463
     * Formats whole blocks of CDATA
464
     *
465
     * @param  &string $cdata
0 ignored issues
show
The doc-type &string could not be parsed: Unknown type name "&string" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
466
     * @param  boolean $outline
467
     * @return string
468
     */
469
    private function printCData(&$cdata, $outline = false)
470
    {
471
        $cdata = trim($cdata);
472
        $ret = '';
473
474
        /*$t = '';
475
476
        if ($outline) {
477
            //echo $tag->getName();
478
            $t = ' yes';
479
        }*/
480
481
        if ($cdata === '') {
482
            return $ret;
483
        }
484
485
        //$e = function ($s) { return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); };
486
487
        if (
488
            // All top-level blocks of CDATA have to be surrounded with <p>
489
            //($this->m_stack->size() == 0)
490
491
            // An outline tag starts after this CDATA block
492
            //|| ($tag != null)
493
            /*||*/ $outline
494
495
            /*
496
             * Obvious. Should make a difference between \n and \n\n though
497
             * TODO
498
             */
499
            || (mb_strpos($cdata, "\n"))
500
501
            /* TODO Add FORCE_PARAGRAPHS parameter to tags (li?, blockquote, ...) */
502
            || ($this->m_stack->count() > 0
503
                    && $this->m_stack->top()->isOfType(self::TAG_FORCE_PARAGRAPHS))
504
        ) {
505
            if ($this->m_stack->count() > 0
506
                && $this->m_stack->top()->isOfType(self::TAG_PRE)
507
            ) {
508
                /*
509
                 * We are inside of a TAG_PRE tag and do not want the CDATA to
510
                 * be reformatted
511
                 */
512
513
                //$ret .= '[CDATA' . $t . ']' . $cdata . '[/CDATA]';
514
                $ret .= $cdata;
515
            } else {
516
                //$cdata = $e($cdata);
517
518
                $cdata = str_replace("\n\n", '</p><p>', $cdata);
519
                $cdata = str_replace("\n", "<br />\n", $cdata);
520
                $cdata = str_replace('</p><p>', "</p>\n\n<p>", $cdata);
521
                //$ret .= '<p>' . '[CDATA' . $t . ']' . $cdata . '[/CDATA]' . "</p>\n";
522
                $ret .= '<p>' . $cdata . "</p>\n";
523
            }
524
        } else {
525
            /* No need to add paragraph markup (something like [li]CDATA[/li]) */
526
527
            //$ret .= '[CDATA' . $t . ']' . $cdata . '[/CDATA]';
528
            $ret .= $cdata;
529
        }
530
531
        $cdata = '';
532
533
        return $ret;
534
    }
535
}
536