Passed
Pull Request — master (#66)
by Michael
02:23
created

renderer_plugin_prosemirror::table_close()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * DokuWiki Plugin prosemirror (Renderer Component)
4
 *
5
 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6
 * @author  Andreas Gohr <[email protected]>
7
 */
8
9
// must be run within Dokuwiki
10
use dokuwiki\plugin\prosemirror\schema\Mark;
11
use dokuwiki\plugin\prosemirror\schema\Node;
12
use dokuwiki\plugin\prosemirror\schema\NodeStack;
13
14
if (!defined('DOKU_INC')) {
15
    die();
16
}
17
18
require_once DOKU_INC . 'inc/parser/renderer.php';
0 ignored issues
show
Bug introduced by
The constant DOKU_INC was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
19
20
class renderer_plugin_prosemirror extends Doku_Renderer
21
{
22
23
    /** @var  NodeStack */
24
    public $nodestack;
25
26
    /** @var NodeStack[] */
27
    protected $nodestackBackup = [];
28
29
    /** @var array list of currently active formatting marks */
30
    protected $marks = [];
31
32
    /** @var int column counter for table handling */
33
    protected $colcount = 0;
34
35
    /**
36
     * The format this renderer produces
37
     */
38
    public function getFormat()
39
    {
40
        return 'prosemirror';
41
    }
42
43
    public function addToNodestackTop(Node $node)
44
    {
45
        $this->nodestack->addTop($node);
46
    }
47
48
    public function addToNodestack(Node $node)
49
    {
50
        $this->nodestack->add($node);
51
    }
52
53
    public function dropFromNodeStack($nodeType)
54
    {
55
        $this->nodestack->drop($nodeType);
56
    }
57
58
    public function getCurrentMarks()
59
    {
60
        return $this->marks;
61
    }
62
63
    /**
64
     * If there is a block scope open, close it.
65
     */
66
    protected function clearBlock()
67
    {
68
        $parentNode = $this->nodestack->current()->getType();
69
        if (in_array($parentNode, ['paragraph'])) {
70
            $this->nodestack->drop($parentNode);
71
        }
72
    }
73
74
    // FIXME implement all methods of Doku_Renderer here
75
76
    /** @inheritDoc */
77
    public function document_start()
78
    {
79
        $this->nodestack = new NodeStack();
80
    }
81
82
    /** @inheritDoc */
83
    public function document_end()
84
    {
85
        if ($this->nodestack->isEmpty()) {
86
            $this->p_open();
87
            $this->p_close();
88
        }
89
        $this->doc = json_encode($this->nodestack->doc(), JSON_PRETTY_PRINT);
0 ignored issues
show
Bug Best Practice introduced by
The property doc does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
90
    }
91
92
    public function nocache() {
93
        $docNode = $this->nodestack->getDocNode();
94
        $docNode->attr('nocache', true);
95
    }
96
97
    public function notoc()
98
    {
99
        $docNode = $this->nodestack->getDocNode();
100
        $docNode->attr('notoc', true);
101
    }
102
103
    /** @inheritDoc */
104
    public function p_open()
105
    {
106
        $this->nodestack->addTop(new Node('paragraph'));
107
    }
108
109
    /** @inheritdoc */
110
    public function p_close()
111
    {
112
        $this->nodestack->drop('paragraph');
113
    }
114
115
    /** @inheritDoc */
116
    public function quote_open()
117
    {
118
        if ($this->nodestack->current()->getType() === 'paragraph') {
119
            $this->nodestack->drop('paragraph');
120
        }
121
        $this->nodestack->addTop(new Node('blockquote'));
122
    }
123
124
    /** @inheritDoc */
125
    public function quote_close()
126
    {
127
        if ($this->nodestack->current()->getType() === 'paragraph') {
128
            $this->nodestack->drop('paragraph');
129
        }
130
        $this->nodestack->drop('blockquote');
131
    }
132
133
    #region lists
134
135
    /** @inheritDoc */
136
    public function listu_open()
137
    {
138
        if ($this->nodestack->current()->getType() === 'paragraph') {
139
            $this->nodestack->drop('paragraph');
140
        }
141
142
        $this->nodestack->addTop(new Node('bullet_list'));
143
    }
144
145
    /** @inheritDoc */
146
    public function listu_close()
147
    {
148
        $this->nodestack->drop('bullet_list');
149
    }
150
151
    /** @inheritDoc */
152
    public function listo_open()
153
    {
154
        if ($this->nodestack->current()->getType() === 'paragraph') {
155
            $this->nodestack->drop('paragraph');
156
        }
157
158
        $this->nodestack->addTop(new Node('ordered_list'));
159
    }
160
161
    /** @inheritDoc */
162
    public function listo_close()
163
    {
164
        $this->nodestack->drop('ordered_list');
165
    }
166
167
    /** @inheritDoc */
168
    public function listitem_open($level, $node = false)
169
    {
170
        $this->nodestack->addTop(new Node('list_item'));
171
    }
172
173
    /** @inheritDoc */
174
    public function listitem_close()
175
    {
176
177
        if ($this->nodestack->current()->getType() === 'paragraph') {
178
            $this->nodestack->drop('paragraph');
179
        }
180
        $this->nodestack->drop('list_item');
181
    }
182
183
    #endregion lists
184
185
    #region table
186
187
    /** @inheritDoc */
188
    public function table_open($maxcols = null, $numrows = null, $pos = null)
189
    {
190
        $this->nodestack->addTop(new Node('table'));
191
    }
192
193
    /** @inheritDoc */
194
    public function table_close($pos = null)
195
    {
196
        $this->nodestack->drop('table');
197
    }
198
199
    /** @inheritDoc */
200
    public function tablerow_open()
201
    {
202
        $this->nodestack->addTop(new Node('table_row'));
203
        $this->colcount = 0;
204
    }
205
206
    /** @inheritDoc */
207
    public function tablerow_close()
208
    {
209
        $node = $this->nodestack->drop('table_row');
210
        $node->attr('columns', $this->colcount);
211
    }
212
213
    /** @inheritDoc */
214
    public function tablecell_open($colspan = 1, $align = null, $rowspan = 1)
215
    {
216
        $this->openTableCell('table_cell', $colspan, $align, $rowspan);
217
    }
218
219
    /** @inheritdoc */
220
    public function tablecell_close()
221
    {
222
        $this->closeTableCell('table_cell');
223
    }
224
225
    /** @inheritDoc */
226
    public function tableheader_open($colspan = 1, $align = null, $rowspan = 1)
227
    {
228
        $this->openTableCell('table_header', $colspan, $align, $rowspan);
229
    }
230
231
    /** @inheritdoc */
232
    public function tableheader_close()
233
    {
234
        $this->closeTableCell('table_header');
235
    }
236
237
    /**
238
     * Add a new table cell to the top of the stack
239
     *
240
     * @param string      $type    either table_cell or table_header
241
     * @param int         $colspan
242
     * @param string|null $align   either null/left, center or right
243
     * @param int         $rowspan
244
     */
245
    protected function openTableCell($type, $colspan, $align, $rowspan) {
246
        $this->colcount += $colspan;
247
248
        $node = new Node($type);
249
        $node->attr('colspan', $colspan);
250
        $node->attr('rowspan', $rowspan);
251
        $node->attr('align', $align);
0 ignored issues
show
Bug introduced by
It seems like $align can also be of type string; however, parameter $value of dokuwiki\plugin\prosemirror\schema\Node::attr() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

251
        $node->attr('align', /** @scrutinizer ignore-type */ $align);
Loading history...
252
        $this->nodestack->addTop($node);
253
254
        $node = new Node('paragraph');
255
        $this->nodestack->addTop($node);
256
    }
257
258
    /**
259
     * Remove a table cell from the top of the stack
260
     *
261
     * @param string $type either table_cell or table_header
262
     */
263
    protected function closeTableCell($type)
264
    {
265
        if ($this->nodestack->current()->getType() === 'paragraph') {
266
            $this->nodestack->drop('paragraph');
267
        }
268
269
        $curNode = $this->nodestack->current();
270
        $curNode->trimContentLeft();
271
        $curNode->trimContentRight();
272
        $this->nodestack->drop($type);
273
    }
274
275
    #endregion table
276
277
    /** @inheritDoc */
278
    public function header($text, $level, $pos)
279
    {
280
        $node = new Node('heading');
281
        $node->attr('level', $level);
282
283
        $tnode = new Node('text');
284
        $tnode->setText($text);
285
        $node->addChild($tnode);
286
287
        $this->nodestack->add($node);
288
    }
289
290
    /** @inheritDoc */
291
    public function cdata($text)
292
    {
293
        if ($text === '') {
294
            return;
295
        }
296
297
        $parentNode = $this->nodestack->current()->getType();
298
299
        if (in_array($parentNode, ['paragraph', 'footnote'])) {
300
            $text = str_replace("\n", ' ', $text);
301
        }
302
303
        if ($parentNode === 'list_item') {
304
            $node = new Node('paragraph');
305
            $this->nodestack->addTop($node);
306
        }
307
308
        if ($parentNode === 'blockquote') {
309
            $node = new Node('paragraph');
310
            $this->nodestack->addTop($node);
311
        }
312
313
        if ($parentNode === 'doc') {
314
            $node = new Node('paragraph');
315
            $this->nodestack->addTop($node);
316
        }
317
318
        $node = new Node('text');
319
        $node->setText($text);
320
        foreach (array_keys($this->marks) as $mark) {
321
            $node->addMark(new Mark($mark));
322
        }
323
        $this->nodestack->add($node);
324
    }
325
326
    public function preformatted($text)
327
    {
328
        $this->clearBlock();
329
        $node = new Node('preformatted');
330
        $this->nodestack->addTop($node);
331
        $this->cdata($text);
332
        $this->nodestack->drop('preformatted');
333
    }
334
335
    public function code($text, $lang = null, $file = null)
336
    {
337
        $this->clearBlock();
338
        $node = new Node('code_block');
339
        $node->attr('class', 'code ' . $lang);
340
        $node->attr('data-language', $lang);
341
        $node->attr('data-filename', $file);
342
        $this->nodestack->addTop($node);
343
        $this->cdata(trim($text, "\n"));
344
        $this->nodestack->drop('code_block');
345
    }
346
347
    public function file($text, $lang = null, $file = null)
348
    {
349
        $this->code($text, $lang, $file);
350
    }
351
352
    public function html($text)
353
    {
354
        $node = new Node('html_inline');
355
        $node->attr('class', 'html_inline');
356
        $this->nodestack->addTop($node);
357
        $this->cdata(str_replace("\n", ' ', $text));
358
        $this->nodestack->drop('html_inline');
359
    }
360
361
    public function htmlblock($text)
362
    {
363
        $this->clearBlock();
364
        $node = new Node('html_block');
365
        $node->attr('class', 'html_block');
366
        $this->nodestack->addTop($node);
367
        $this->cdata(trim($text, "\n"));
368
        $this->nodestack->drop('html_block');
369
    }
370
371
    public function php($text)
372
    {
373
        $node = new Node('php_inline');
374
        $node->attr('class', 'php_inline');
375
        $this->nodestack->addTop($node);
376
        $this->cdata(str_replace("\n", ' ', $text));
377
        $this->nodestack->drop('php_inline');
378
    }
379
380
    public function phpblock($text)
381
    {
382
        $this->clearBlock();
383
        $node = new Node('php_block');
384
        $node->attr('class', 'php_block');
385
        $this->nodestack->addTop($node);
386
        $this->cdata(trim($text, "\n"));
387
        $this->nodestack->drop('php_block');
388
    }
389
390
    /**
391
     * @inheritDoc
392
     */
393
    public function rss($url, $params)
394
    {
395
        $this->clearBlock();
396
        $node = new Node('rss');
397
        $node->attr('url', hsc($url));
398
        $node->attr('max', $params['max']);
399
        $node->attr('reverse', (bool)$params['reverse']);
400
        $node->attr('author', (bool)$params['author']);
401
        $node->attr('date', (bool)$params['date']);
402
        $node->attr('details', (bool)$params['details']);
403
404
        if ($params['refresh'] % 86400 === 0) {
405
            $refresh = $params['refresh']/86400 . 'd';
406
        } else if ($params['refresh'] % 3600 === 0) {
407
            $refresh = $params['refresh']/3600 . 'h';
408
        } else {
409
            $refresh = $params['refresh']/60 . 'm';
410
        }
411
412
        $node->attr('refresh', trim($refresh));
413
        $this->nodestack->add($node);
414
    }
415
416
417
    public function footnote_open()
418
    {
419
        $footnoteNode = new Node('footnote');
420
        $this->nodestack->addTop($footnoteNode);
421
        $this->nodestackBackup[] = $this->nodestack;
422
        $this->nodestack = new NodeStack();
423
424
    }
425
426
    public function footnote_close()
427
    {
428
        $json = json_encode($this->nodestack->doc());
429
        $this->nodestack = array_pop($this->nodestackBackup);
430
        $this->nodestack->current()->attr('contentJSON', $json);
431
        $this->nodestack->drop('footnote');
432
    }
433
434
    /**
435
     * @inheritDoc
436
     */
437
    public function internalmedia(
438
        $src,
439
        $title = null,
440
        $align = null,
441
        $width = null,
442
        $height = null,
443
        $cache = null,
444
        $linking = null
445
    ) {
446
447
        // FIXME how do we handle non-images, e.g. pdfs or audio?
448
        \dokuwiki\plugin\prosemirror\parser\ImageNode::render(
449
            $this,
450
            $src,
451
            $title,
452
            $align,
453
            $width,
454
            $height,
455
            $cache,
456
            $linking
457
        );
458
    }
459
460
    /**
461
     * @inheritDoc
462
     */
463
    public function externalmedia(
464
        $src,
465
        $title = null,
466
        $align = null,
467
        $width = null,
468
        $height = null,
469
        $cache = null,
470
        $linking = null
471
    ) {
472
        \dokuwiki\plugin\prosemirror\parser\ImageNode::render(
473
            $this,
474
            $src,
475
            $title,
476
            $align,
477
            $width,
478
            $height,
479
            $cache,
480
            $linking
481
        );
482
    }
483
484
485
    public function locallink($hash, $name = null)
486
    {
487
        \dokuwiki\plugin\prosemirror\parser\LocalLinkNode::render($this, $hash, $name);
488
    }
489
490
    /**
491
     * @inheritDoc
492
     */
493
    public function internallink($id, $name = null)
494
    {
495
        \dokuwiki\plugin\prosemirror\parser\InternalLinkNode::render($this, $id, $name);
496
    }
497
498
    public function externallink($link, $title = null)
499
    {
500
        \dokuwiki\plugin\prosemirror\parser\ExternalLinkNode::render($this, $link, $title);
501
    }
502
503
    public function interwikilink($link, $title = null, $wikiName, $wikiUri)
504
    {
505
        \dokuwiki\plugin\prosemirror\parser\InterwikiLinkNode::render($this, $title, $wikiName, $wikiUri);
506
    }
507
508
    public function emaillink($address, $name = null)
509
    {
510
        \dokuwiki\plugin\prosemirror\parser\EmailLinkNode::render($this, $address, $name);
511
    }
512
513
    public function windowssharelink($link, $title = null)
514
    {
515
        \dokuwiki\plugin\prosemirror\parser\WindowsShareLinkNode::render($this, $link, $title);
516
    }
517
518
    /** @inheritDoc */
519
    public function linebreak()
520
    {
521
        $this->nodestack->add(new Node('hard_break'));
522
    }
523
524
    /** @inheritDoc */
525
    public function hr()
526
    {
527
        $this->nodestack->add(new Node('horizontal_rule'));
528
    }
529
530
    public function plugin($name, $data, $state = '', $match = '')
531
    {
532
        if (empty($match)) {
533
            return;
534
        }
535
        $eventData = [
536
            'name' => $name,
537
            'data' => $data,
538
            'state' => $state,
539
            'match' => $match,
540
            'renderer' => $this,
541
        ];
542
        $event = new Doku_Event('PROSEMIRROR_RENDER_PLUGIN', $eventData);
543
        if ($event->advise_before()) {
544
            if ($this->nodestack->current()->getType() === 'paragraph') {
545
                $nodetype = 'dwplugin_inline';
546
            } else {
547
                $nodetype = 'dwplugin_block';
548
            }
549
            $node = new Node($nodetype);
550
            $node->attr('class', 'dwplugin');
551
            $node->attr('data-pluginname', $name);
552
            $this->nodestack->addTop($node);
553
            $this->cdata($match);
554
            $this->nodestack->drop($nodetype);
555
        }
556
    }
557
558
    public function smiley($smiley)
559
    {
560
        if(array_key_exists($smiley, $this->smileys)) {
561
            $node = new Node('smiley');
562
            $node->attr('icon', $this->smileys[$smiley]);
563
            $node->attr('syntax', $smiley);
564
            $this->nodestack->add($node);
565
        } else {
566
            $this->cdata($smiley);
567
        }
568
    }
569
570
    #region elements with no special WYSIWYG representation
571
572
    /** @inheritDoc */
573
    public function entity($entity)
574
    {
575
        $this->cdata($entity); // FIXME should we handle them special?
576
    }
577
578
    /** @inheritDoc */
579
    public function multiplyentity($x, $y)
580
    {
581
        $this->cdata($x . 'x' . $y);
582
    }
583
584
    /** @inheritDoc */
585
    public function acronym($acronym)
586
    {
587
        $this->cdata($acronym);
588
    }
589
590
    /** @inheritDoc */
591
    public function apostrophe()
592
    {
593
        $this->cdata("'");
594
    }
595
596
    /** @inheritDoc */
597
    public function singlequoteopening()
598
    {
599
        $this->cdata("'");
600
    }
601
602
    /** @inheritDoc */
603
    public function singlequoteclosing()
604
    {
605
        $this->cdata("'");
606
    }
607
608
    /** @inheritDoc */
609
    public function doublequoteopening()
610
    {
611
        $this->cdata('"');
612
    }
613
614
    /** @inheritDoc */
615
    public function doublequoteclosing()
616
    {
617
        $this->cdata('"');
618
    }
619
620
    /** @inheritDoc */
621
    public function camelcaselink($link)
622
    {
623
        $this->cdata($link); // FIXME should/could we decorate it?
624
    }
625
626
    #endregion
627
628
    #region formatter marks
629
630
    /** @inheritDoc */
631
    public function strong_open()
632
    {
633
        $this->marks['strong'] = 1;
634
    }
635
636
    /** @inheritDoc */
637
    public function strong_close()
638
    {
639
        if (isset($this->marks['strong'])) {
640
            unset($this->marks['strong']);
641
        }
642
    }
643
644
    /** @inheritDoc */
645
    public function emphasis_open()
646
    {
647
        $this->marks['em'] = 1;
648
    }
649
650
    /** @inheritDoc */
651
    public function emphasis_close()
652
    {
653
        if (isset($this->marks['em'])) {
654
            unset($this->marks['em']);
655
        }
656
    }
657
658
    /** @inheritdoc */
659
    public function subscript_open()
660
    {
661
        $this->marks['subscript'] = 1;
662
    }
663
664
    /** @inheritDoc */
665
    public function subscript_close()
666
    {
667
        if (isset($this->marks['subscript'])) {
668
            unset($this->marks['subscript']);
669
        }
670
    }
671
672
    /** @inheritdoc */
673
    public function superscript_open()
674
    {
675
        $this->marks['superscript'] = 1;
676
    }
677
678
    /** @inheritDoc */
679
    public function superscript_close()
680
    {
681
        if (isset($this->marks['superscript'])) {
682
            unset($this->marks['superscript']);
683
        }
684
    }
685
686
    /** @inheritDoc */
687
    public function monospace_open()
688
    {
689
        $this->marks['code'] = 1;
690
    }
691
692
    /** @inheritDoc */
693
    public function monospace_close()
694
    {
695
        if (isset($this->marks['code'])) {
696
            unset($this->marks['code']);
697
        }
698
    }
699
700
    /** @inheritDoc */
701
    public function deleted_open()
702
    {
703
        $this->marks['deleted'] = 1;
704
    }
705
706
    /** @inheritDoc */
707
    public function deleted_close()
708
    {
709
        if (isset($this->marks['deleted'])) {
710
            unset($this->marks['deleted']);
711
        }
712
    }
713
714
    /** @inheritDoc */
715
    public function underline_open()
716
    {
717
        $this->marks['underline'] = 1;
718
    }
719
720
    /** @inheritDoc */
721
    public function underline_close()
722
    {
723
        if (isset($this->marks['underline'])) {
724
            unset($this->marks['underline']);
725
        }
726
    }
727
728
729
    /** @inheritDoc */
730
    public function unformatted($text)
731
    {
732
        $this->marks['unformatted'] = 1;
733
        parent::unformatted($text);
734
        unset($this->marks['unformatted']);
735
    }
736
737
738
    #endregion formatter marks
739
}
740