Completed
Push — fix-empty-regex ( d83f5c )
by Colin
03:35
created

Environment::getRendererForClass()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 19
ccs 10
cts 10
cp 1
rs 9.2
cc 4
eloc 9
nc 4
nop 2
crap 4
1
<?php
2
3
/*
4
 * This file is part of the league/commonmark package.
5
 *
6
 * (c) Colin O'Dell <[email protected]>
7
 *
8
 * Original code based on the CommonMark JS reference parser (http://bitly.com/commonmark-js)
9
 *  - (c) John MacFarlane
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace League\CommonMark;
16
17
use League\CommonMark\Block\Parser\BlockParserInterface;
18
use League\CommonMark\Block\Renderer\BlockRendererInterface;
19
use League\CommonMark\Extension\CommonMarkCoreExtension;
20
use League\CommonMark\Extension\ExtensionInterface;
21
use League\CommonMark\Extension\MiscExtension;
22
use League\CommonMark\Inline\Parser\InlineParserInterface;
23
use League\CommonMark\Inline\Processor\InlineProcessorInterface;
24
use League\CommonMark\Inline\Renderer\InlineRendererInterface;
25
use League\CommonMark\Util\Configuration;
26
use League\CommonMark\Util\ConfigurationAwareInterface;
27
28
class Environment
29
{
30
    /**
31
     * @var ExtensionInterface[]
32
     */
33
    protected $extensions = [];
34
35
    /**
36
     * @var bool
37
     */
38
    protected $extensionsInitialized = false;
39
40
    /**
41
     * @var BlockParserInterface[]
42
     */
43
    protected $blockParsers = [];
44
45
    /**
46
     * @var InlineParserInterface[]
47
     */
48
    protected $inlineParsers = [];
49
50
    /**
51
     * @var array
52
     */
53
    protected $inlineParsersByCharacter = [];
54
55
    /**
56
     * @var DocumentProcessorInterface[]
57
     */
58
    protected $documentProcessors = [];
59
60
    /**
61
     * @var InlineProcessorInterface[]
62
     */
63
    protected $inlineProcessors = [];
64
65
    /**
66
     * @var BlockRendererInterface[]
67
     */
68
    protected $blockRenderersByClass = [];
69
70
    /**
71
     * @var InlineRendererInterface[]
72
     */
73
    protected $inlineRenderersByClass = [];
74
75
    /**
76
     * @var Configuration
77
     */
78
    protected $config;
79
80
    /**
81
     * @var string
82
     */
83
    protected $inlineParserCharacterRegex;
84
85 1968
    public function __construct(array $config = [])
86 3
    {
87 1968
        $this->config = new Configuration($config);
88 1968
    }
89
90
    /**
91
     * @param array $config
92
     */
93 1866
    public function mergeConfig(array $config = [])
94
    {
95 1866
        $this->assertUninitialized('Failed to modify configuration.');
96
97 1863
        $this->config->mergeConfig($config);
98 1863
    }
99
100
    /**
101
     * @param array $config
102
     */
103 9
    public function setConfig(array $config = [])
104 3
    {
105 9
        $this->assertUninitialized('Failed to modify configuration.');
106
107 3
        $this->config->setConfig($config);
108 3
    }
109
110
    /**
111
     * @param string|null $key
112
     * @param mixed       $default
113
     *
114
     * @return mixed
115
     */
116 1872
    public function getConfig($key = null, $default = null)
117
    {
118 1872
        return $this->config->getConfig($key, $default);
119
    }
120
121
    /**
122
     * @param BlockParserInterface $parser
123
     *
124
     * @return $this
125
     */
126 6
    public function addBlockParser(BlockParserInterface $parser)
127
    {
128 6
        $this->assertUninitialized('Failed to add block parser.');
129
130 3
        $this->getMiscExtension()->addBlockParser($parser);
131
132 3
        return $this;
133
    }
134
135
    /**
136
     * @param InlineParserInterface $parser
137
     *
138
     * @return $this
139
     */
140 15
    public function addInlineParser(InlineParserInterface $parser)
141
    {
142 15
        $this->assertUninitialized('Failed to add inline parser.');
143
144 12
        $this->getMiscExtension()->addInlineParser($parser);
145
146 12
        return $this;
147
    }
148
149
    /**
150
     * @param InlineProcessorInterface $processor
151
     *
152
     * @return $this
153
     */
154 6
    public function addInlineProcessor(InlineProcessorInterface $processor)
155
    {
156 6
        $this->assertUninitialized('Failed to add inline processor.');
157
158 3
        $this->getMiscExtension()->addInlineProcessor($processor);
159
160 3
        return $this;
161
    }
162
163
    /**
164
     * @param DocumentProcessorInterface $processor
165
     *
166
     * @return $this
167
     */
168 9
    public function addDocumentProcessor(DocumentProcessorInterface $processor)
169
    {
170 9
        $this->assertUninitialized('Failed to add document processor.');
171
172 6
        $this->getMiscExtension()->addDocumentProcessor($processor);
173
174 6
        return $this;
175
    }
176
177
    /**
178
     * @param string                 $blockClass
179
     * @param BlockRendererInterface $blockRenderer
180
     *
181
     * @return $this
182
     */
183 15
    public function addBlockRenderer($blockClass, BlockRendererInterface $blockRenderer)
184
    {
185 15
        $this->assertUninitialized('Failed to add block renderer.');
186
187 12
        $this->getMiscExtension()->addBlockRenderer($blockClass, $blockRenderer);
188
189 12
        return $this;
190
    }
191
192
    /**
193
     * @param string                  $inlineClass
194
     * @param InlineRendererInterface $renderer
195
     *
196
     * @return $this
197
     */
198 15
    public function addInlineRenderer($inlineClass, InlineRendererInterface $renderer)
199
    {
200 15
        $this->assertUninitialized('Failed to add inline renderer.');
201
202 12
        $this->getMiscExtension()->addInlineRenderer($inlineClass, $renderer);
203
204 12
        return $this;
205
    }
206
207
    /**
208
     * @return BlockParserInterface[]
209
     */
210 1866
    public function getBlockParsers()
211
    {
212 1866
        $this->initializeExtensions();
213
214 1866
        return $this->blockParsers;
215
    }
216
217
    /**
218
     * @param string $name
219
     *
220
     * @return InlineParserInterface
221
     */
222 3
    public function getInlineParser($name)
223
    {
224 3
        $this->initializeExtensions();
225
226 3
        return $this->inlineParsers[$name];
227
    }
228
229
    /**
230
     * @return InlineParserInterface[]
231
     */
232 6
    public function getInlineParsers()
233
    {
234 6
        $this->initializeExtensions();
235
236 6
        return $this->inlineParsers;
237
    }
238
239
    /**
240
     * @param string $character
241
     *
242
     * @return InlineParserInterface[]|null
243
     */
244 1590
    public function getInlineParsersForCharacter($character)
245
    {
246 1590
        $this->initializeExtensions();
247
248 1590
        if (!isset($this->inlineParsersByCharacter[$character])) {
249 1476
            return;
250
        }
251
252 1197
        return $this->inlineParsersByCharacter[$character];
253
    }
254
255
    /**
256
     * @return InlineProcessorInterface[]
257
     */
258 1593
    public function getInlineProcessors()
259
    {
260 1593
        $this->initializeExtensions();
261
262 1593
        return $this->inlineProcessors;
263
    }
264
265
    /**
266
     * @return DocumentProcessorInterface[]
267
     */
268 1860
    public function getDocumentProcessors()
269
    {
270 1860
        $this->initializeExtensions();
271
272 1860
        return $this->documentProcessors;
273
    }
274
275
    /**
276
     * @param string $blockClass
277
     *
278
     * @return BlockRendererInterface|null
279
     */
280 1872
    public function getBlockRendererForClass($blockClass)
281
    {
282 1872
        $this->initializeExtensions();
283
284 1872
        return $this->getRendererForClass($this->blockRenderersByClass, $blockClass);
285
    }
286
287
    /**
288
     * @param string $inlineClass
289
     *
290
     * @return InlineRendererInterface|null
291
     */
292 1605
    public function getInlineRendererForClass($inlineClass)
293
    {
294 1605
        $this->initializeExtensions();
295
296 1605
        return $this->getRendererForClass($this->inlineRenderersByClass, $inlineClass);
297
    }
298
299 6
    public function createInlineParserEngine()
300
    {
301 6
        $this->initializeExtensions();
302
303 6
        return new InlineParserEngine($this);
304
    }
305
306
    /**
307
     * Get all registered extensions
308
     *
309
     * @return ExtensionInterface[]
310
     */
311 15
    public function getExtensions()
312
    {
313 15
        return $this->extensions;
314
    }
315
316
    /**
317
     * Add a single extension
318
     *
319
     * @param ExtensionInterface $extension
320
     *
321
     * @return $this
322
     */
323 1914
    public function addExtension(ExtensionInterface $extension)
324
    {
325 1914
        $this->assertUninitialized('Failed to add extension.');
326
327 1911
        $this->extensions[] = $extension;
328
329 1911
        return $this;
330
    }
331
332 1938
    protected function initializeExtensions()
333
    {
334
        // Only initialize them once
335 1938
        if ($this->extensionsInitialized) {
336 1860
            return;
337
        }
338
339 1938
        $this->extensionsInitialized = true;
340
341
        // Initialize all the registered extensions
342 1938
        foreach ($this->extensions as $extension) {
343 1896
            $this->initializeExtension($extension);
344 1938
        }
345
346
        // Lastly, let's build a regex which matches non-inline characters
347
        // This will enable a huge performance boost with inline parsing
348 1938
        $this->buildInlineParserCharacterRegex();
349 1938
    }
350
351
    /**
352
     * @param ExtensionInterface $extension
353
     */
354 1896
    protected function initializeExtension(ExtensionInterface $extension)
355
    {
356 1896
        $this->initalizeBlockParsers($extension->getBlockParsers());
357 1896
        $this->initializeInlineParsers($extension->getInlineParsers());
358 1896
        $this->initializeInlineProcessors($extension->getInlineProcessors());
359 1896
        $this->initializeDocumentProcessors($extension->getDocumentProcessors());
360 1896
        $this->initializeBlockRenderers($extension->getBlockRenderers());
361 1896
        $this->initializeInlineRenderers($extension->getInlineRenderers());
362 1896
    }
363
364
    /**
365
     * @param BlockParserInterface[] $blockParsers
366
     */
367 1896
    private function initalizeBlockParsers($blockParsers)
368
    {
369 1896
        foreach ($blockParsers as $blockParser) {
370 1857
            if ($blockParser instanceof EnvironmentAwareInterface) {
371
                $blockParser->setEnvironment($this);
372
            }
373
374 1857
            if ($blockParser instanceof ConfigurationAwareInterface) {
375
                $blockParser->setConfiguration($this->config);
376
            }
377
378 1857
            $this->blockParsers[$blockParser->getName()] = $blockParser;
379 1896
        }
380 1896
    }
381
382
    /**
383
     * @param InlineParserInterface[] $inlineParsers
384
     */
385 1896
    private function initializeInlineParsers($inlineParsers)
386
    {
387 1896
        foreach ($inlineParsers as $inlineParser) {
388 1863
            if ($inlineParser instanceof EnvironmentAwareInterface) {
389 1854
                $inlineParser->setEnvironment($this);
390 1854
            }
391
392 1863
            if ($inlineParser instanceof ConfigurationAwareInterface) {
393
                $inlineParser->setConfiguration($this->config);
394
            }
395
396 1863
            $this->inlineParsers[$inlineParser->getName()] = $inlineParser;
397
398 1863
            foreach ($inlineParser->getCharacters() as $character) {
399 1863
                $this->inlineParsersByCharacter[$character][] = $inlineParser;
400 1863
            }
401 1896
        }
402 1896
    }
403
404
    /**
405
     * @param InlineProcessorInterface[] $inlineProcessors
406
     */
407 1896
    private function initializeInlineProcessors($inlineProcessors)
408
    {
409 1896
        foreach ($inlineProcessors as $inlineProcessor) {
410 1857
            $this->inlineProcessors[] = $inlineProcessor;
411
412 1857
            if ($inlineProcessor instanceof ConfigurationAwareInterface) {
413
                $inlineProcessor->setConfiguration($this->config);
414
            }
415 1896
        }
416 1896
    }
417
418
    /**
419
     * @param DocumentProcessorInterface[] $documentProcessors
420
     */
421 1896
    private function initializeDocumentProcessors($documentProcessors)
422
    {
423 1896
        foreach ($documentProcessors as $documentProcessor) {
424 3
            $this->documentProcessors[] = $documentProcessor;
425
426 3
            if ($documentProcessor instanceof ConfigurationAwareInterface) {
427
                $documentProcessor->setConfiguration($this->config);
428
            }
429 1896
        }
430 1896
    }
431
432
    /**
433
     * @param BlockRendererInterface[] $blockRenderers
434
     */
435 1896
    private function initializeBlockRenderers($blockRenderers)
436
    {
437 1896
        foreach ($blockRenderers as $class => $blockRenderer) {
438 1866
            $this->blockRenderersByClass[$class] = $blockRenderer;
439
440 1866
            if ($blockRenderer instanceof ConfigurationAwareInterface) {
441 1854
                $blockRenderer->setConfiguration($this->config);
442 1854
            }
443 1896
        }
444 1896
    }
445
446
    /**
447
     * @param InlineRendererInterface[] $inlineRenderers
448
     */
449 1896
    private function initializeInlineRenderers($inlineRenderers)
450
    {
451 1896
        foreach ($inlineRenderers as $class => $inlineRenderer) {
452 1866
            $this->inlineRenderersByClass[$class] = $inlineRenderer;
453
454 1866
            if ($inlineRenderer instanceof ConfigurationAwareInterface) {
455 1854
                $inlineRenderer->setConfiguration($this->config);
456 1854
            }
457 1896
        }
458 1896
    }
459
460
    /**
461
     * @return Environment
462
     */
463 1860
    public static function createCommonMarkEnvironment()
464
    {
465 1860
        $environment = new static();
466 1860
        $environment->addExtension(new CommonMarkCoreExtension());
467 1860
        $environment->mergeConfig([
468
            'renderer' => [
469 1860
                'block_separator' => "\n",
470 1860
                'inner_separator' => "\n",
471 1860
                'soft_break'      => "\n",
472 1860
            ],
473 1860
            'safe' => false,
474 1860
        ]);
475
476 1860
        return $environment;
477
    }
478
479
    /**
480
     * Regex which matches any character which doesn't indicate an inline element
481
     *
482
     * This allows us to parse multiple non-special characters at once
483
     *
484
     * @return string
485
     */
486 1476
    public function getInlineParserCharacterRegex()
487
    {
488 1476
        return $this->inlineParserCharacterRegex;
489
    }
490
491 1938
    private function buildInlineParserCharacterRegex()
492
    {
493 1938
        $chars = array_keys($this->inlineParsersByCharacter);
494
495 1938
        if (empty($chars)) {
496
            // If no special inline characters exist then parse the whole line
497 75
            $this->inlineParserCharacterRegex = '/^.+$/u';
498 75
        } else {
499
            // Match any character which inline parsers are not interested in
500 1863
            $this->inlineParserCharacterRegex = '/^[^' . preg_quote(implode('', $chars)) . ']+/u';
501
        }
502 1938
    }
503
504
    /**
505
     * @param string $message
506
     *
507
     * @throws \RuntimeException
508
     */
509 1944
    private function assertUninitialized($message)
510
    {
511 1944
        if ($this->extensionsInitialized) {
512 27
            throw new \RuntimeException($message . ' Extensions have already been initialized.');
513
        }
514 1917
    }
515
516
    /**
517
     * @return MiscExtension
518
     */
519 45
    private function getMiscExtension()
520
    {
521 45
        $lastExtension = end($this->extensions);
522 45
        if ($lastExtension !== false && $lastExtension instanceof MiscExtension) {
523 6
            return $lastExtension;
524
        }
525
526 45
        $miscExtension = new MiscExtension();
527 45
        $this->addExtension($miscExtension);
528
529 45
        return $miscExtension;
530
    }
531
532
    /**
533
     * @param array  $renderers
534
     * @param string $class
535
     *
536
     * @return object|null
537
     */
538 1893
    private function getRendererForClass(array &$renderers, $class)
539
    {
540 1893
        if (isset($renderers[$class])) {
541 1866
            return $renderers[$class];
542
        }
543
544 27
        for ($parent = $class; $parent; $parent = get_parent_class($parent)) {
545 27
            if (!isset($renderers[$parent])) {
546 27
                continue;
547
            }
548
549 6
            $renderer = $renderers[$parent];
550
551
            // "Cache" this result to avoid future loops
552 6
            $renderers[$class] = $renderer;
553
554 6
            return $renderer;
555
        }
556 21
    }
557
}
558