book-source 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1973 @@
1
+ /*
2
+ Book Source
3
+
4
+ Copyright (c) 2023 Cédric Ronvel
5
+
6
+ The MIT License (MIT)
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all
16
+ copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
25
+ */
26
+
27
+ "use strict" ;
28
+
29
+
30
+
31
+ const HtmlRenderer = require( './HtmlRenderer.js' ) ;
32
+ const Style = require( './Style.js' ) ;
33
+
34
+ const emoji = require( 'string-kit/lib/emoji.js' ) ;
35
+ const inPlaceFilter = require( 'array-kit/lib/inPlaceFilter.js' ) ;
36
+
37
+
38
+
39
+ function StructuredDocument() {
40
+ this.title = 'Document' ;
41
+ this.metadata = null ;
42
+ this.theme = null ;
43
+ this.parts = [] ;
44
+ }
45
+
46
+ module.exports = StructuredDocument ;
47
+
48
+
49
+
50
+ StructuredDocument.prototype.toHtml = function( theme , params ) {
51
+ return this.render( new HtmlRenderer( theme , params ) ) ;
52
+ } ;
53
+
54
+
55
+
56
+ StructuredDocument.prototype.render = function( renderer ) {
57
+ var meta = {
58
+ title: this.title
59
+ } ;
60
+
61
+ var str = this.renderParts( renderer , this.parts , [] ) ;
62
+
63
+ if ( renderer.document ) {
64
+ str = renderer.document( meta , str ) ;
65
+ }
66
+
67
+ return str ;
68
+ } ;
69
+
70
+
71
+
72
+ StructuredDocument.prototype.renderParts = function( renderer , parts , partStack ) {
73
+ var str = '' , index = 0 ;
74
+
75
+ for ( let part of parts ) {
76
+ let childrenStr = '' ;
77
+
78
+ if ( part.children ) {
79
+ if ( renderer.group?.[ part.type ] ) {
80
+ // Some renderer have to group children an apply things to each group,
81
+ // E.g. HTML groups 'tableHeader' to produce <thead> and 'tableRow' to produce <tbody>
82
+ let groupList = this.groupByType( part.children , renderer.group[ part.type ] ) ;
83
+ //console.error( "groupList:" , groupList ) ;
84
+
85
+ for ( let group of groupList ) {
86
+ if ( renderer.group[ part.type ][ group.type ] ) {
87
+ partStack.push( part ) ;
88
+ let groupStr = this.renderParts( renderer , group.children , partStack ) ;
89
+ partStack.pop() ;
90
+ childrenStr += renderer.group[ part.type ][ group.type ]( part , groupStr , partStack ) ;
91
+ }
92
+ }
93
+ }
94
+ else {
95
+ partStack.push( part ) ;
96
+ childrenStr = this.renderParts( renderer , part.children , [ ... partStack , part ] ) ;
97
+ partStack.pop() ;
98
+ }
99
+ }
100
+
101
+ if ( renderer[ part.type ] ) {
102
+ str += renderer[ part.type ]( part , childrenStr , partStack , index ) ;
103
+ }
104
+
105
+ index ++ ;
106
+ }
107
+
108
+ return str ;
109
+ } ;
110
+
111
+
112
+
113
+ StructuredDocument.prototype.groupByType = function( parts , rendererGroup ) {
114
+ var group , groupMap = {} , groupList = [] ;
115
+
116
+ for ( let part of parts ) {
117
+ group = groupMap[ part.type ] ;
118
+ if ( ! group ) {
119
+ group = groupMap[ part.type ] = {
120
+ type: part.type ,
121
+ children: [] ,
122
+ order: + rendererGroup[ part.type ]?.order || 0
123
+ } ;
124
+ groupList.push( group ) ;
125
+ }
126
+
127
+ group.children.push( part ) ;
128
+ }
129
+
130
+ groupList.sort( ( a , b ) => a.order - b.order ) ;
131
+
132
+ return groupList ;
133
+ } ;
134
+
135
+
136
+
137
+ StructuredDocument.prototype.autoTitle = function() {
138
+ var header , str ;
139
+
140
+ for ( let part of this.parts ) {
141
+ if ( part.type === 'header' ) { header = part ; break ; }
142
+ }
143
+
144
+ if ( ! header ) { return ; }
145
+
146
+ str = this.getText( header.children ) ;
147
+ if ( str ) { this.title = str ; }
148
+ } ;
149
+
150
+
151
+
152
+ StructuredDocument.prototype.getText = function( parts ) { //= this.parts ) {
153
+ var str = '' ;
154
+
155
+ for ( let part of parts ) {
156
+ if ( part.text ) { str += part.text ; }
157
+ else if ( part.children?.length ) { str += this.getText( part.children ) ; }
158
+ }
159
+
160
+ return str ;
161
+ } ;
162
+
163
+
164
+
165
+ // Parser
166
+
167
+ StructuredDocument.parse = function( str , options ) {
168
+ if ( ! options || typeof options !== 'object' ) { options = {} ; }
169
+
170
+ if ( typeof str !== 'string' ) {
171
+ if ( str && typeof str === 'object' ) { str = str.toString() ; }
172
+ else { throw new TypeError( "Argument #0 should be a string or an object with a .toString() method" ) ; }
173
+ }
174
+
175
+ var ctx = {
176
+ i: 0 ,
177
+ iStartOfInlineChunk: 0 ,
178
+ forceInlineChunkSpace: false ,
179
+ iEndOfBlock: str.length , // For nested things, the parent end here
180
+ lastLineWasEmpty: false ,
181
+ lastBlock: null ,
182
+ rowSpanTables: new Set() , // Set of table that needs post-processing to apply row span
183
+ structuredDocument: new StructuredDocument() ,
184
+ stack: [] ,
185
+ parts: null ,
186
+ parent: null ,
187
+ rawMetadata: null
188
+ } ;
189
+
190
+ ctx.parts = ctx.structuredDocument.parts ;
191
+
192
+ parseBlocks( str , ctx ) ;
193
+
194
+ for ( let table of ctx.rowSpanTables ) { postProcessTableRowSpan( table ) ; }
195
+ ctx.structuredDocument.autoTitle() ;
196
+
197
+ if ( ctx.rawMetadata ) {
198
+ let metadataParser = options.metadataParser || JSON.parse ;
199
+ for ( let type in ctx.rawMetadata ) {
200
+ try {
201
+ let parsed = metadataParser( ctx.rawMetadata[ type ] ) ;
202
+ if ( type === 'metadata' ) {
203
+ ctx.structuredDocument.metadata = parsed ;
204
+ }
205
+ else if ( type === 'theme' && parsed && typeof parsed === 'object' ) {
206
+ ctx.structuredDocument.theme = parsed ;
207
+ }
208
+ }
209
+ catch ( error ) {}
210
+ }
211
+ }
212
+
213
+ // Call depthManagement() one last time, because some instanceOf may still be hanging...
214
+ //ctx.depth = -1 ;
215
+ //depthManagement( ctx ) ;
216
+
217
+ return ctx.structuredDocument ;
218
+ } ;
219
+
220
+
221
+
222
+ function parseBlocks( str , ctx ) {
223
+ while ( ctx.i < str.length ) {
224
+ parseBlock( str , ctx ) ;
225
+ }
226
+ }
227
+
228
+
229
+
230
+ function parseBlock( str , ctx ) {
231
+ var { isEmptyLine , endOfEmptyLine , indentCharCount , indentSpaces , indentType } = detectIndent( str , ctx.i , ctx.parent?.indent ) ;
232
+
233
+ if ( isEmptyLine ) {
234
+ ctx.i = endOfEmptyLine + 1 ;
235
+ ctx.lastLineWasEmpty = true ;
236
+ ctx.lastBlock = null ;
237
+ return ;
238
+ }
239
+
240
+ if ( indentType === QUOTE_INDENT ) {
241
+ ctx.parts.push( { type: 'quote' , indent: indentSpaces } ) ;
242
+ stack( ctx ) ;
243
+ }
244
+ else if ( indentType <= 0 ) {
245
+ unstackToIndent( ctx , indentSpaces ) ;
246
+ }
247
+
248
+ ctx.i += indentCharCount ;
249
+ var blockType = detectBlockType( str , ctx.i ) ;
250
+ //console.error( "=== parseBlock() ===" , { blockType , isEmptyLine , endOfEmptyLine , indentCharCount , indentSpaces , indentType } ) ;
251
+
252
+
253
+ switch ( blockType ) {
254
+ case BLOCK_PARAGRAPH :
255
+ parseParagraph( str , ctx ) ;
256
+ break ;
257
+ case BLOCK_HEADER :
258
+ parseHeader( str , ctx ) ;
259
+ break ;
260
+ case BLOCK_CITE :
261
+ parseCite( str , ctx ) ;
262
+ break ;
263
+ case BLOCK_LIST_ITEM :
264
+ parseListItem( str , ctx , indentSpaces ) ;
265
+ break ;
266
+ case BLOCK_ORDERED_LIST_ITEM :
267
+ parseOrderedListItem( str , ctx , indentSpaces ) ;
268
+ break ;
269
+ case BLOCK_MEDIA :
270
+ parseMedia( str , ctx ) ;
271
+ break ;
272
+ case BLOCK_FLOAT_LEFT_MEDIA :
273
+ parseMedia( str , ctx , 'left' ) ;
274
+ break ;
275
+ case BLOCK_FLOAT_RIGHT_MEDIA :
276
+ parseMedia( str , ctx , 'right' ) ;
277
+ break ;
278
+ case BLOCK_HORIZONTAL_RULE :
279
+ parseHorizontalRule( str , ctx ) ;
280
+ break ;
281
+ case BLOCK_CLEAR_FLOAT :
282
+ parseClearFloat( str , ctx ) ;
283
+ break ;
284
+ case BLOCK_CODE :
285
+ parseCodeBlock( str , ctx ) ;
286
+ break ;
287
+ case BLOCK_TABLE_CAPTION :
288
+ parseTableCaption( str , ctx ) ;
289
+ break ;
290
+ case BLOCK_TABLE_ROW :
291
+ parseTableRow( str , ctx ) ;
292
+ break ;
293
+ case BLOCK_TABLE_ROW_SEPARATOR :
294
+ parseTableRowSeparator( str , ctx ) ;
295
+ break ;
296
+ case BLOCK_TABLE_ROW_THICK_SEPARATOR :
297
+ parseTableRowSeparator( str , ctx , true ) ;
298
+ break ;
299
+ case BLOCK_ANCHOR :
300
+ parseAnchor( str , ctx ) ;
301
+ break ;
302
+ case BLOCK_METADATA :
303
+ parseMetadata( str , ctx ) ;
304
+ break ;
305
+ default :
306
+ throw new Error( "Bad block detection: " + blockType ) ;
307
+ }
308
+
309
+ ctx.lastLineWasEmpty = false ;
310
+ ctx.lastBlock = blockType ;
311
+ if ( str[ ctx.i ] === '\n' ) { ctx.i ++ ; }
312
+ }
313
+
314
+
315
+
316
+ const UNQUOTE_INDENT = - 3 ;
317
+ const UNLIST_INDENT = - 2 ;
318
+ const DISCONTINUE_INDENT = - 1 ;
319
+ const NO_INDENT = 0 ;
320
+ const CONTINUE_INDENT = 1 ; // 2 spaces, continue the previous block
321
+ const LIST_INDENT = 2 ; // 4 spaces (sub-list)
322
+ const QUOTE_INDENT = 3 ; // 8 spaces (quote)
323
+
324
+ const DETECT_INDENT = {
325
+ isEmptyLine: false ,
326
+ endOfEmptyLine: - 1 ,
327
+ indentCharCount: 0 ,
328
+ indentSpaces: 0 ,
329
+ indentDelta: 0 ,
330
+ indentType: NO_INDENT
331
+ } ;
332
+
333
+ function detectIndent( str , i , parentIndent ) {
334
+ parentIndent = parentIndent || 0 ;
335
+
336
+ DETECT_INDENT.isEmptyLine = false ;
337
+ DETECT_INDENT.endOfEmptyLine = - 1 ;
338
+ DETECT_INDENT.indentCharCount = DETECT_INDENT.indentSpaces = DETECT_INDENT.indentDelta = 0 ;
339
+ DETECT_INDENT.indentType = NO_INDENT ;
340
+
341
+ if ( str[ i ] === '\n' ) {
342
+ DETECT_INDENT.isEmptyLine = true ;
343
+ DETECT_INDENT.endOfEmptyLine = i ;
344
+ return DETECT_INDENT ;
345
+ }
346
+
347
+
348
+ var iSearch = i ;
349
+
350
+ for ( ; iSearch < str.length ; iSearch ++ ) {
351
+ if ( str[ iSearch ] === '\n' ) {
352
+ DETECT_INDENT.isEmptyLine = true ;
353
+ DETECT_INDENT.endOfEmptyLine = iSearch ;
354
+ return DETECT_INDENT ;
355
+ }
356
+
357
+ if ( str[ iSearch ] === '\t' ) {
358
+ DETECT_INDENT.indentCharCount ++ ;
359
+ DETECT_INDENT.indentSpaces += 4 ;
360
+ }
361
+ else if ( str[ iSearch ] === ' ' ) {
362
+ DETECT_INDENT.indentCharCount ++ ;
363
+ DETECT_INDENT.indentSpaces ++ ;
364
+ }
365
+ else {
366
+ break ;
367
+ }
368
+ }
369
+
370
+ DETECT_INDENT.indentDelta = DETECT_INDENT.indentSpaces - parentIndent ;
371
+
372
+ if ( DETECT_INDENT.indentDelta >= 8 ) {
373
+ DETECT_INDENT.indentType = QUOTE_INDENT ;
374
+ }
375
+ else if ( DETECT_INDENT.indentDelta >= 4 ) {
376
+ DETECT_INDENT.indentType = LIST_INDENT ;
377
+ }
378
+ else if ( DETECT_INDENT.indentDelta >= 2 ) {
379
+ DETECT_INDENT.indentType = CONTINUE_INDENT ;
380
+ }
381
+ else if ( DETECT_INDENT.indentDelta <= - 8 ) {
382
+ DETECT_INDENT.indentType = UNQUOTE_INDENT ;
383
+ }
384
+ else if ( DETECT_INDENT.indentDelta <= - 4 ) {
385
+ DETECT_INDENT.indentType = UNLIST_INDENT ;
386
+ }
387
+ else if ( DETECT_INDENT.indentDelta <= - 2 ) {
388
+ DETECT_INDENT.indentType = DISCONTINUE_INDENT ;
389
+ }
390
+
391
+ return DETECT_INDENT ;
392
+ }
393
+
394
+
395
+
396
+ const BLOCK_PARAGRAPH = 1 ;
397
+ const BLOCK_HEADER = 2 ;
398
+ const BLOCK_LIST_ITEM = 3 ;
399
+ const BLOCK_ORDERED_LIST_ITEM = 4 ;
400
+ const BLOCK_CITE = 5 ;
401
+ const BLOCK_MEDIA = 10 ;
402
+ const BLOCK_FLOAT_LEFT_MEDIA = 11 ;
403
+ const BLOCK_FLOAT_RIGHT_MEDIA = 12 ;
404
+ const BLOCK_HORIZONTAL_RULE = 20 ;
405
+ const BLOCK_CLEAR_FLOAT = 21 ;
406
+ const BLOCK_CODE = 30 ;
407
+ const BLOCK_TABLE_ROW = 40 ;
408
+ const BLOCK_TABLE_ROW_SEPARATOR = 41 ;
409
+ const BLOCK_TABLE_ROW_THICK_SEPARATOR = 42 ;
410
+ const BLOCK_TABLE_CAPTION = 43 ;
411
+ const BLOCK_ANCHOR = 50 ;
412
+ const BLOCK_METADATA = 60 ;
413
+
414
+ function detectBlockType( str , i ) {
415
+ if ( str[ i ] === '\\' ) {
416
+ return BLOCK_PARAGRAPH ;
417
+ }
418
+
419
+ if ( str[ i ] === '#' ) {
420
+ if ( str[ i + 1 ] === '(' ) { return BLOCK_ANCHOR ; }
421
+ return BLOCK_HEADER ;
422
+ }
423
+
424
+ if ( str[ i ] === '!' && str[ i + 2 ] === '[' ) {
425
+ if ( str[ i + 1 ] === '=' ) { return BLOCK_MEDIA ; }
426
+ else if ( str[ i + 1 ] === '<' ) { return BLOCK_FLOAT_LEFT_MEDIA ; }
427
+ else if ( str[ i + 1 ] === '>' ) { return BLOCK_FLOAT_RIGHT_MEDIA ; }
428
+ return BLOCK_PARAGRAPH ;
429
+ }
430
+
431
+ if ( ( str[ i ] === '*' || str[ i ] === '-' ) && str[ i + 1 ] === ' ' ) {
432
+ return BLOCK_LIST_ITEM ;
433
+ }
434
+
435
+ if ( str[ i ] === '-' && str[ i + 1 ] === '-' ) {
436
+ if ( str[ i + 2 ] === '-' ) {
437
+ if ( str[ i + 3 ] === '[' ) {
438
+ return BLOCK_METADATA ;
439
+ }
440
+
441
+ return BLOCK_HORIZONTAL_RULE ;
442
+
443
+ }
444
+ if ( str[ i + 2 ] === ' ' && searchEndOfEmptyLine( str , i + 3 ) === - 1 ) {
445
+ return BLOCK_CITE ;
446
+ }
447
+ }
448
+
449
+ if ( str[ i ] === '<' && str[ i + 1 ] === '-' && str[ i + 2 ] === '-' && str[ i + 3 ] === '-' ) {
450
+ return BLOCK_CLEAR_FLOAT ;
451
+ }
452
+
453
+ if ( str[ i ] === '`' && str[ i + 1 ] === '`' && str[ i + 2 ] === '`' ) {
454
+ return BLOCK_CODE ;
455
+ }
456
+
457
+ if ( str[ i ] === '|' ) {
458
+ if ( str[ i + 1 ] === '[' ) { return BLOCK_TABLE_CAPTION ; }
459
+
460
+ if (
461
+ str[ i + 1 ] === '-'
462
+ || ( ( str[ i + 1 ] === '<' || str[ i + 1 ] === '>' ) && str[ i + 2 ] === '-' )
463
+ ) {
464
+ return BLOCK_TABLE_ROW_SEPARATOR ;
465
+ }
466
+
467
+ if (
468
+ str[ i + 1 ] === '='
469
+ || ( ( str[ i + 1 ] === '<' || str[ i + 1 ] === '>' ) && str[ i + 2 ] === '=' )
470
+ ) {
471
+ return BLOCK_TABLE_ROW_THICK_SEPARATOR ;
472
+ }
473
+
474
+ return BLOCK_TABLE_ROW ;
475
+ }
476
+
477
+ if ( str[ i ] >= '0' && str[ i ] <= '9' ) {
478
+ let iAfterNumber = i + 1 ;
479
+ while ( str[ iAfterNumber ] >= '0' && str[ iAfterNumber ] <= '9' ) { iAfterNumber ++ ; }
480
+
481
+ if ( str[ iAfterNumber ] === '.' && ( str[ iAfterNumber + 1 ] === ' ' || str[ iAfterNumber + 1 ] === '\t' ) ) {
482
+ return BLOCK_ORDERED_LIST_ITEM ;
483
+ }
484
+ }
485
+
486
+ return BLOCK_PARAGRAPH ;
487
+ }
488
+
489
+
490
+
491
+ const DEFAULT_BLOCK_END_PARAMS = {
492
+ acceptEmptyLine: false ,
493
+ acceptBlockType: - 1 ,
494
+ acceptContinueIndent: true ,
495
+ acceptIndent: false
496
+ } ;
497
+
498
+ /*
499
+ Params:
500
+ acceptEmptyLine: the block is not interrupted by empty line
501
+ acceptBlockType: this block is accepted as a continuation
502
+ acceptContinueIndent: continue if the block is indented 2 spaces (“continue indent”)
503
+ acceptIndent: continue if the block is indented to the next level (4 or more)
504
+ */
505
+ function detectBlockEnd( str , nextScanStart , parentIndent = 0 , params = DEFAULT_BLOCK_END_PARAMS ) {
506
+ var detectedBlockType , isEmptyLine , endOfEmptyLine , indentType ,
507
+ blockEnd = nextScanStart ;
508
+
509
+ //console.error( "=== detectBlockEnd() ===" , nextScanStart , parentIndent , params ) ;
510
+ while ( nextScanStart < str.length ) {
511
+ // First, move at the start of the next line...
512
+ let endOfLine = searchEndOfLine( str , nextScanStart ) ;
513
+ //console.error( "-> endOfLine:" , endOfLine ) ;
514
+ blockEnd = endOfLine ;
515
+ nextScanStart = endOfLine + 1 ;
516
+
517
+ if ( nextScanStart > str.length ) { break ; }
518
+
519
+ ( { isEmptyLine , endOfEmptyLine , indentType } = detectIndent( str , nextScanStart , parentIndent ) ) ;
520
+ //console.error( "-> detectIndent():" , { isEmptyLine , endOfEmptyLine , indentCharCount , indentSpaces , indentType } ) ;
521
+
522
+ if ( isEmptyLine ) {
523
+ if ( ! params.acceptEmptyLine ) { return blockEnd ; }
524
+ nextScanStart = endOfEmptyLine + 1 ;
525
+ continue ;
526
+ }
527
+
528
+ if ( indentType < 0 ) { return blockEnd ; }
529
+ if ( indentType === CONTINUE_INDENT && params.acceptContinueIndent ) { continue ; }
530
+ if ( indentType > 0 && params.acceptIndent ) { continue ; }
531
+
532
+ detectedBlockType = detectBlockType( str , nextScanStart ) ;
533
+ //console.error( "-> detectBlockType():" , detectedBlockType ) ;
534
+ if ( params.acceptBlockType !== detectedBlockType ) { return blockEnd ; }
535
+ }
536
+
537
+ return blockEnd ;
538
+ }
539
+
540
+
541
+
542
+ const PARAGRAPH_END_PARAMS = {
543
+ acceptEmptyLine: false ,
544
+ acceptBlockType: BLOCK_PARAGRAPH ,
545
+ acceptContinueIndent: false ,
546
+ acceptIndent: false
547
+ } ;
548
+
549
+ function parseParagraph( str , ctx ) {
550
+ //console.error( "parseParagraph in:" , ctx.i , str.slice( ctx.i ) ) ;
551
+ ctx.parts.push( { type: 'paragraph' } ) ;
552
+
553
+ var blockEnd = detectBlockEnd( str , ctx.i , ctx.parent?.indent , PARAGRAPH_END_PARAMS ) ;
554
+ //console.error( "blockEnd:" , blockEnd ) ;
555
+ parseInlineChildren( str , ctx , blockEnd ) ;
556
+
557
+ //console.error( "children:" , children ) ;
558
+ //console.error( "parseParagraph out:" , ctx.i , str.slice( ctx.i ) ) ;
559
+ }
560
+
561
+
562
+
563
+ const HEADER_END_PARAMS = {
564
+ acceptEmptyLine: false ,
565
+ //acceptBlockType: BLOCK_HEADER ,
566
+ acceptContinueIndent: true ,
567
+ acceptIndent: false
568
+ } ;
569
+
570
+ function parseHeader( str , ctx ) {
571
+ var streak = countStreak( str , ctx.i , '#' ) ;
572
+ //if ( str[ ctx.i + streak ] !== ' ' ) { return parseParagraph( str , ctx ) ; }
573
+
574
+ ctx.i += streak ;
575
+ if ( str[ ctx.i ] === ' ' ) { ctx.i ++ ; }
576
+ ctx.parts.push( { type: 'header' , level: streak } ) ;
577
+
578
+ var blockEnd = detectBlockEnd( str , ctx.i , ctx.parent?.indent , HEADER_END_PARAMS ) ;
579
+ parseInlineChildren( str , ctx , blockEnd ) ;
580
+ }
581
+
582
+
583
+
584
+ const CITE_END_PARAMS = {
585
+ acceptEmptyLine: false ,
586
+ //acceptBlockType: BLOCK_CITE ,
587
+ acceptContinueIndent: true ,
588
+ acceptIndent: false
589
+ } ;
590
+
591
+ function parseCite( str , ctx ) {
592
+ ctx.i += 3 ;
593
+ ctx.parts.push( { type: 'cite' } ) ;
594
+
595
+ var blockEnd = detectBlockEnd( str , ctx.i , ctx.parent?.indent , CITE_END_PARAMS ) ;
596
+ parseInlineChildren( str , ctx , blockEnd ) ;
597
+ }
598
+
599
+
600
+
601
+ // Lists themselve are auto-aggregating, accepting empty-lines, item needs continue-indent
602
+ const LIST_ITEM_END_PARAMS = {
603
+ acceptEmptyLine: false ,
604
+ //acceptBlockType: BLOCK_LIST_ITEM ,
605
+ acceptContinueIndent: true ,
606
+ acceptIndent: false
607
+ } ;
608
+
609
+ function parseListItem( str , ctx , indent ) {
610
+ ctx.i += 2 ;
611
+
612
+ var lastPart = ctx.parts[ ctx.parts.length - 1 ] ;
613
+
614
+ if ( ! lastPart || lastPart.type !== 'list' ) {
615
+ ctx.parts.push( { type: 'list' , indent } ) ;
616
+ }
617
+
618
+ stack( ctx ) ;
619
+
620
+ ctx.parts.push( { type: 'listItem' , indent } ) ;
621
+
622
+ var blockEnd = detectBlockEnd( str , ctx.i , ctx.parent?.indent , LIST_ITEM_END_PARAMS ) ;
623
+ parseInlineChildren( str , ctx , blockEnd ) ;
624
+ }
625
+
626
+
627
+
628
+ function parseOrderedListItem( str , ctx , indent ) {
629
+ var endOfNumber = ctx.i ;
630
+ while ( str[ endOfNumber ] >= '0' && str[ endOfNumber ] <= '9' ) { endOfNumber ++ ; }
631
+
632
+ var order = parseInt( str.slice( ctx.i , endOfNumber ) , 10 ) ;
633
+ ctx.i = endOfNumber + 2 ;
634
+
635
+ var lastPart = ctx.parts[ ctx.parts.length - 1 ] ;
636
+
637
+ if ( ! lastPart || lastPart.type !== 'orderedList' ) {
638
+ ctx.parts.push( { type: 'orderedList' , indent } ) ;
639
+ }
640
+
641
+ stack( ctx ) ;
642
+
643
+ ctx.parts.push( { type: 'orderedListItem' , indent , order } ) ;
644
+
645
+ var blockEnd = detectBlockEnd( str , ctx.i , ctx.parent?.indent , LIST_ITEM_END_PARAMS ) ;
646
+ parseInlineChildren( str , ctx , blockEnd ) ;
647
+ }
648
+
649
+
650
+
651
+ const MEDIA_DATA_MARK = {
652
+ text: true , href: true , style: false , extra: false
653
+ } ;
654
+
655
+ function parseMedia( str , ctx , float = null ) {
656
+ var end = searchCloser( str , ctx.i + 3 , '[' , ']' ) ;
657
+ if ( end < 0 ) { return ; }
658
+
659
+ var text = str.slice( ctx.i + 3 , end ) ;
660
+
661
+ ctx.i = end ;
662
+ ctx.iStartOfInlineChunk = ctx.i + 1 ;
663
+ var data = parseDataMark( str , ctx , MEDIA_DATA_MARK ) ;
664
+ if ( ! data ) { return ; }
665
+
666
+ if ( ! data.href?.[ 0 ] ) { return ; }
667
+
668
+ var type = 'imageBlock' ;
669
+
670
+ if ( data.href[ 1 ] ) {
671
+ switch ( data.href[ 1 ] ) {
672
+ case 'image' :
673
+ type = 'imageBlock' ;
674
+ break ;
675
+ case 'audio' :
676
+ type = 'audioBlock' ;
677
+ break ;
678
+ case 'video' :
679
+ type = 'videoBlock' ;
680
+ break ;
681
+ default :
682
+ return ;
683
+ }
684
+ }
685
+
686
+ var params = { type , altText: text , href: data.href[ 0 ] } ;
687
+
688
+ if ( float ) { params.float = float ; }
689
+
690
+ if ( data.text?.length ) {
691
+ params.caption = data.text[ 0 ] ;
692
+ if ( data.text[ 1 ] ) { params.title = data.text[ 1 ] ; }
693
+ }
694
+
695
+ //if ( data.extra?.length ) { params.title = data.extra[ 0 ] ; }
696
+
697
+ ctx.parts.push( params ) ;
698
+ ctx.i ++ ;
699
+ }
700
+
701
+
702
+
703
+ function parseHorizontalRule( str , ctx ) {
704
+ var params = { type: 'horizontalRule' } ,
705
+ streak = countStreak( str , ctx.i , '-' ) ;
706
+
707
+ if (
708
+ str[ ctx.i + streak ] === '<'
709
+ && str[ ctx.i + streak + 1 ] === '>'
710
+ && str[ ctx.i + streak + 2 ] === '-'
711
+ && str[ ctx.i + streak + 3 ] === '-'
712
+ && str[ ctx.i + streak + 4 ] === '-'
713
+ ) {
714
+ params.clearFloat = true ;
715
+ }
716
+
717
+ ctx.parts.push( params ) ;
718
+ ctx.i = searchEndOfLine( str , ctx.i ) + 1 ;
719
+ }
720
+
721
+
722
+
723
+ function parseClearFloat( str , ctx ) {
724
+ var streak = countStreak( str , ctx.i + 1 , '-' ) ;
725
+
726
+ if ( str[ ctx.i + 1 + streak ] === '>' ) {
727
+ ctx.parts.push( { type: 'clearFloat' } ) ;
728
+ ctx.i = searchEndOfLine( str , ctx.i ) + 1 ;
729
+ }
730
+ else {
731
+ parseParagraph( str , ctx ) ;
732
+ }
733
+ }
734
+
735
+
736
+
737
+ function parseCodeBlock( str , ctx ) {
738
+ var streak = countStreak( str , ctx.i , '`' ) ,
739
+ endOfLine = searchEndOfLine( str , ctx.i + streak ) ,
740
+ lang = str.slice( ctx.i + streak , endOfLine ).trim() || null ,
741
+ contentStart = endOfLine + 1 ;
742
+
743
+ var ends = searchBlockSwitchCloser( str , contentStart , '`' , 3 ) ;
744
+
745
+ if ( ! ends ) {
746
+ return parseParagraph( str , ctx ) ;
747
+ }
748
+
749
+ var [ contentEnd , blockEnd ] = ends ;
750
+
751
+ var params = { type: 'codeBlock' } ;
752
+ if ( lang ) { params.lang = lang ; }
753
+ params.text = str.slice( contentStart , contentEnd - 1 ) ; // We strip the last newline
754
+ ctx.parts.push( params ) ;
755
+ ctx.i = blockEnd ;
756
+ }
757
+
758
+
759
+
760
+ function parseMetadata( str , ctx ) {
761
+ var endOfLine = searchEndOfLine( str , ctx.i + 4 ) ,
762
+ nextBracket = searchNext( str , ctx.i + 4 , endOfLine , '[' ) ;
763
+
764
+ if ( nextBracket < 0 ) { return parseParagraph( str , ctx ) ; }
765
+
766
+ var type = str.slice( ctx.i + 4 , nextBracket ).trim() || 'metadata' ,
767
+ contentStart = endOfLine + 1 ,
768
+ ends = searchFixedBlockSwitchCloser( str , contentStart , ']]---' ) ;
769
+
770
+ if ( ! ends ) { return parseParagraph( str , ctx ) ; }
771
+
772
+ var [ contentEnd , blockEnd ] = ends ;
773
+
774
+ if ( ! ctx.rawMetadata ) { ctx.rawMetadata = {} ; }
775
+ if ( ! ctx.rawMetadata[ type ] ) { ctx.rawMetadata[ type ] = '' ; }
776
+ ctx.rawMetadata[ type ] += str.slice( contentStart , contentEnd ) ; // We DON'T strip the last newline
777
+ ctx.i = blockEnd ;
778
+ }
779
+
780
+
781
+
782
+ function parseAnchor( str , ctx ) {
783
+ //console.log( "parseAnchor()" ) ;
784
+ var end = searchCloser( str , ctx.i + 2 , '(' , ')' , true ) ;
785
+ if ( end < 0 ) { return parseParagraph( str , ctx ) ; }
786
+
787
+ ctx.parts.push( {
788
+ type: 'anchor' ,
789
+ href: str.slice( ctx.i + 2 , end )
790
+ } ) ;
791
+
792
+ ctx.i = end + 1 ;
793
+ }
794
+
795
+
796
+
797
+ function parseTableCaption( str , ctx ) {
798
+ //console.log( "parseTableCaption()" ) ;
799
+ var lastCharOfLine = searchLastCharOfLine( str , ctx.i + 1 ) ;
800
+
801
+ var end = searchCloser( str , ctx.i + 2 , '[' , ']' , true , lastCharOfLine ) ;
802
+
803
+ // Check that the syntax is correct: |[ must be followed by a space and ]| must be preceded by a space
804
+ if ( str[ ctx.i + 2 ] !== ' ' || str[ end - 1 ] !== ' ' || str[ end + 1 ] !== '|' ) {
805
+ return parseParagraph( str , ctx ) ;
806
+ }
807
+
808
+ var table = ctx.parts[ ctx.parts.length - 1 ] ;
809
+
810
+ if ( ! table || table.type !== 'table' || ctx.lastLineWasEmpty ) {
811
+ table = { type: 'table' , columns: [] } ;
812
+ ctx.parts.push( table ) ;
813
+ }
814
+
815
+ ctx.i += 3 ;
816
+ stack( ctx ) ;
817
+
818
+ var lastRow = ctx.parts[ ctx.parts.length - 1 ] || null ,
819
+ tableCaption = lastRow ;
820
+
821
+ if ( ! lastRow || lastRow.type !== 'tableCaption' ) {
822
+ tableCaption = { type: 'tableCaption' } ;
823
+ ctx.parts.push( tableCaption ) ;
824
+ }
825
+
826
+ parseInlineChildren( str , ctx , end - 1 , true ) ;
827
+
828
+ if ( str[ end + 2 ] === '<' ) {
829
+ ctx.i = end + 1 ;
830
+ let data = parseDataMark( str , ctx , CELL_DATA_MARK , lastCharOfLine + 1 , false ) ;
831
+ if ( data?.style?.[ 0 ] ) { tableCaption.style = data.style[ 0 ] ; }
832
+ }
833
+
834
+ ctx.i = lastCharOfLine + 1 ;
835
+ }
836
+
837
+
838
+
839
+ const CELL_DATA_MARK = {
840
+ text: false , href: false , style: true , extra: false
841
+ } ;
842
+
843
+ function parseTableRow( str , ctx ) {
844
+ //console.log( "parseTableRow()" ) ;
845
+ var lastCharOfLine = searchLastCharOfLine( str , ctx.i + 1 ) ,
846
+ table = ctx.parts[ ctx.parts.length - 1 ] ,
847
+ tableRow ,
848
+ mergeMode = false ;
849
+
850
+ if ( ! table || table.type !== 'table' || ctx.lastLineWasEmpty ) {
851
+ table = { type: 'table' , columns: [] , children: [] } ;
852
+ ctx.parts.push( table ) ;
853
+ }
854
+
855
+ stack( ctx ) ;
856
+
857
+ if ( table.multilineRowMode && ctx.lastBlock === BLOCK_TABLE_ROW ) {
858
+ //console.error( "???" , ctx.lastBlock , BLOCK_TABLE_ROW ) ;
859
+ let lastRow = ctx.parts[ ctx.parts.length - 1 ] || null ;
860
+ tableRow = lastRow ;
861
+
862
+ if ( ! lastRow || lastRow.type !== 'tableRow' ) {
863
+ tableRow = { type: 'tableRow' } ;
864
+ ctx.parts.push( tableRow ) ;
865
+ }
866
+ else {
867
+ mergeMode = true ;
868
+ }
869
+ }
870
+ else {
871
+ tableRow = { type: 'tableRow' } ;
872
+ ctx.parts.push( tableRow ) ;
873
+ }
874
+
875
+ stack( ctx ) ;
876
+
877
+ if ( mergeMode ) { return parseTableMultilineRow( str , ctx , lastCharOfLine , table , tableRow ) ; }
878
+
879
+
880
+ //var leftAlign , rightAlign , leftCenter , rightCenter , headColumn ;
881
+ var columnSeparator , style ,
882
+ nextBar , firstSpace , lastSpace ,
883
+ firstBar = ctx.i ,
884
+ currentBar = ctx.i ;
885
+
886
+ while ( ( nextBar = searchNext( str , currentBar + 1 , lastCharOfLine + 1 , '|' ) ) !== - 1 ) {
887
+ //leftAlign = rightAlign = leftCenter = rightCenter = headColumn = false ;
888
+ columnSeparator = false ;
889
+ style = null ;
890
+
891
+ lastSpace = searchPrevious( str , nextBar - 1 , currentBar , ' ' ) ;
892
+
893
+ if ( str[ nextBar + 1 ] === '|' ) { columnSeparator = true ; }
894
+
895
+ if ( str[ currentBar + 1 ] === '<' ) {
896
+ ctx.i = currentBar ;
897
+ let data = parseDataMark( str , ctx , CELL_DATA_MARK , nextBar , false ) ;
898
+ if ( data?.style?.[ 0 ] ) { style = data.style[ 0 ] ; }
899
+ firstSpace = searchNext( str , ctx.i , nextBar , ' ' ) ;
900
+ }
901
+ else {
902
+ firstSpace = searchNext( str , currentBar + 1 , nextBar , ' ' ) ;
903
+ }
904
+
905
+ let tableCell = { type: 'tableCell' } ;
906
+
907
+ // The '|' bar position helps for column span calculation
908
+ // sx = Start X, the x position of the left bar
909
+ tableCell.sx = currentBar - firstBar ;
910
+ // ex = End X, the x position of the right bar
911
+ tableCell.ex = nextBar - firstBar ;
912
+
913
+ if ( style ) { tableCell.style = style ; }
914
+ if ( columnSeparator ) { tableCell.columnSeparator = true ; }
915
+
916
+ ctx.parts.push( tableCell ) ;
917
+
918
+ ctx.i = firstSpace + 1 ;
919
+ parseInlineChildren( str , ctx , lastSpace , true ) ;
920
+
921
+ currentBar = columnSeparator ? nextBar + 1 : nextBar ;
922
+ }
923
+
924
+ // Compute cells indexes, columnSpan, rowSpan, column template
925
+ computeIndexColumnSpan( ctx , table , tableRow ) ;
926
+
927
+ if ( str[ currentBar + 1 ] === '<' ) {
928
+ ctx.i = currentBar ;
929
+ let data = parseDataMark( str , ctx , CELL_DATA_MARK , lastCharOfLine + 1 , false ) ;
930
+ if ( data?.style?.[ 0 ] ) { tableRow.style = data.style[ 0 ] ; }
931
+ }
932
+
933
+ ctx.i = lastCharOfLine + 1 ;
934
+ }
935
+
936
+
937
+
938
+ // Merge current row with the previous
939
+ // Should only be called by parseTableRow() which put in the correct scope
940
+ function parseTableMultilineRow( str , ctx , lastCharOfLine , table , tableRow ) {
941
+ var tableCell , columnSeparator , nextBar , firstSpace , lastSpace ,
942
+ //firstBar = ctx.i ,
943
+ currentBar = ctx.i ,
944
+ columnIndex = 0 ;
945
+
946
+ while ( ( nextBar = searchNext( str , currentBar + 1 , lastCharOfLine + 1 , '|' ) ) !== - 1 ) {
947
+ columnSeparator = false ;
948
+
949
+ firstSpace = searchNext( str , currentBar + 1 , nextBar , ' ' ) ;
950
+ lastSpace = searchPrevious( str , nextBar - 1 , currentBar , ' ' ) ;
951
+
952
+ if ( str[ nextBar + 1 ] === '|' ) { columnSeparator = true ; }
953
+
954
+ tableCell = tableRow.children[ columnIndex ] ;
955
+
956
+ if ( tableCell ) {
957
+ ctx.i = firstSpace + 1 ;
958
+ ctx.forceInlineChunkSpace = true ;
959
+ parseInlineChildrenOfParent( str , ctx , tableCell , lastSpace , true ) ;
960
+ }
961
+
962
+ currentBar = columnSeparator ? nextBar + 1 : nextBar ;
963
+ columnIndex ++ ;
964
+ }
965
+
966
+ ctx.i = lastCharOfLine + 1 ;
967
+ }
968
+
969
+
970
+
971
+ function parseTableRowSeparator( str , ctx , thick = false ) {
972
+ //console.log( "parseTableRowSeparator()" ) ;
973
+ var tableRow , columnIndex ,
974
+ lastCharOfLine = searchLastCharOfLine( str , ctx.i + 1 ) ,
975
+ table = ctx.parts[ ctx.parts.length - 1 ] ;
976
+
977
+ if ( ! table || table.type !== 'table' || ctx.lastLineWasEmpty ) {
978
+ table = { type: 'table' , columns: [] , children: [] } ;
979
+ ctx.parts.push( table ) ;
980
+ }
981
+
982
+ // Fix previous table row as table head row
983
+ if ( ! table.hasHeadSeparator ) { return parseTableHeadRowSeparator( str , ctx , thick , lastCharOfLine ) ; }
984
+
985
+
986
+ // So this is a true row separator, not a head/body separator
987
+
988
+ // If this is the first row separator, we have to merge all existing rows into one
989
+ if ( ! table.hasRowSeparator ) {
990
+ table.multilineRowMode = true ;
991
+
992
+ for ( let index = 0 ; index < table.children.length ; index ++ ) {
993
+ let child = table.children[ index ] ;
994
+
995
+ if ( child.type === 'tableRow' ) {
996
+ if ( ! tableRow ) {
997
+ tableRow = child ;
998
+ }
999
+ else {
1000
+ // All subsequent tableRows, are merged into the first tableRow
1001
+
1002
+ if ( child.children ) {
1003
+ for ( columnIndex = 0 ; columnIndex < child.children.length ; columnIndex ++ ) {
1004
+ let child2 = child.children[ columnIndex ] ;
1005
+ if ( child2.type === 'tableCell' || child2.type === 'tableHeadCell' ) {
1006
+ if ( tableRow.children[ columnIndex ] ) {
1007
+ // Merge the cells
1008
+ mergeInlineParts( tableRow.children[ columnIndex ].children , child2.children ) ;
1009
+ }
1010
+ else {
1011
+ tableRow.children[ columnIndex ] = child2 ;
1012
+ }
1013
+ }
1014
+ }
1015
+ }
1016
+
1017
+ table.children.splice( index , 1 ) ;
1018
+ index -- ;
1019
+ }
1020
+ }
1021
+ }
1022
+ }
1023
+
1024
+ table.hasRowSeparator = true ;
1025
+
1026
+ if ( ! tableRow ) {
1027
+ tableRow = searchLastChildOfType( table , 'tableRow' ) ;
1028
+
1029
+ if ( ! tableRow ) {
1030
+ ctx.i = lastCharOfLine + 1 ;
1031
+ return ;
1032
+ }
1033
+ }
1034
+
1035
+ if ( thick ) { tableRow.rowSeparator = true ; }
1036
+
1037
+
1038
+ // Store row span
1039
+ var columnSeparator , nextBar ,
1040
+ firstBar = ctx.i ,
1041
+ currentBar = ctx.i ,
1042
+ columns = table.columns ,
1043
+ separatorCellIndex = 0 ;
1044
+
1045
+ while ( ( nextBar = searchNext( str , currentBar + 1 , lastCharOfLine + 1 , '|' ) ) !== - 1 ) {
1046
+ columnSeparator = false ;
1047
+
1048
+ if ( str[ nextBar + 1 ] === '|' ) { columnSeparator = true ; }
1049
+
1050
+ if ( str[ currentBar + 1 ] === '-' && str[ currentBar + 2 ] === ' ' && str[ nextBar - 1 ] === '-' && str[ nextBar - 2 ] === ' ' ) {
1051
+ // This is a rowSpan
1052
+ ctx.rowSpanTables.add( table ) ;
1053
+ if ( ! tableRow.continueRowSpan ) { tableRow.continueRowSpan = [] ; }
1054
+
1055
+ let sx = currentBar - firstBar ;
1056
+ let closestDelta = Infinity ;
1057
+ let closestColumnIndex = separatorCellIndex ;
1058
+
1059
+ for ( columnIndex = separatorCellIndex ; columnIndex < columns.length ; columnIndex ++ ) {
1060
+ let column = columns[ columnIndex ] ;
1061
+ let delta = Math.abs( sx - column.sx ) ;
1062
+ if ( delta < closestDelta ) {
1063
+ closestDelta = delta ;
1064
+ closestColumnIndex = columnIndex ;
1065
+ }
1066
+ }
1067
+
1068
+ tableRow.continueRowSpan.push( closestColumnIndex ) ;
1069
+ }
1070
+
1071
+ currentBar = columnSeparator ? nextBar + 1 : nextBar ;
1072
+ separatorCellIndex ++ ;
1073
+ }
1074
+
1075
+ ctx.i = lastCharOfLine + 1 ;
1076
+ }
1077
+
1078
+
1079
+
1080
+ function parseTableHeadRowSeparator( str , ctx , thick , lastCharOfLine ) {
1081
+ var columnIndex , tableHeadRow ,
1082
+ table = ctx.parts[ ctx.parts.length - 1 ] ,
1083
+ columns = table.columns ;
1084
+
1085
+ table.hasHeadSeparator = true ;
1086
+ var leftAlign , rightAlign , leftCenter , rightCenter , headColumn , columnSeparator , style ,
1087
+ nextBar , firstHbar , lastHbar , hbarStreak ,
1088
+ hbarChar = thick ? '=' : '-' ,
1089
+ firstBar = ctx.i ,
1090
+ currentBar = ctx.i ;
1091
+
1092
+ columnIndex = 0 ;
1093
+
1094
+ while ( ( nextBar = searchNext( str , currentBar + 1 , lastCharOfLine + 1 , '|' ) ) !== - 1 ) {
1095
+ leftAlign = rightAlign = leftCenter = rightCenter = headColumn = columnSeparator = false ;
1096
+ style = null ;
1097
+
1098
+ firstHbar = searchNext( str , currentBar + 1 , nextBar , hbarChar ) ;
1099
+ lastHbar = searchPrevious( str , nextBar - 1 , currentBar , hbarChar ) ;
1100
+
1101
+ if ( str[ nextBar + 1 ] === '|' ) { columnSeparator = true ; }
1102
+
1103
+ if ( firstHbar !== - 1 ) {
1104
+ if ( firstHbar - currentBar >= 2 ) {
1105
+ for ( let i = currentBar + 1 ; i < firstHbar ; i ++ ) {
1106
+ if ( str[ i ] === '<' ) { leftAlign = true ; }
1107
+ else if ( str[ i ] === '>' ) { leftCenter = true ; }
1108
+ }
1109
+ }
1110
+
1111
+ if ( nextBar - lastHbar >= 2 ) {
1112
+ for ( let i = lastHbar + 1 ; i < nextBar ; i ++ ) {
1113
+ if ( str[ i ] === '<' ) { rightCenter = true ; }
1114
+ else if ( str[ i ] === '>' ) { rightAlign = true ; }
1115
+ else if ( str[ i ] === ':' ) { headColumn = true ; }
1116
+ }
1117
+ }
1118
+
1119
+ hbarStreak = countStreak( str , firstHbar , hbarChar ) ;
1120
+ if ( firstHbar + hbarStreak - 1 !== lastHbar ) {
1121
+ // Check for style mark
1122
+ if ( str[ firstHbar + hbarStreak ] === '<' ) {
1123
+ ctx.i = firstHbar + hbarStreak - 1 ;
1124
+ let data = parseDataMark( str , ctx , CELL_DATA_MARK , lastHbar , false ) ;
1125
+ if ( data?.style?.[ 0 ] ) { style = data.style[ 0 ] ; }
1126
+ }
1127
+ }
1128
+ }
1129
+
1130
+ let columnTemplate = columns[ columnIndex ] ;
1131
+ if ( ! columnTemplate ) { columnTemplate = columns[ columnIndex ] = {} ; }
1132
+
1133
+ // The '|' bar position helps for column span calculation
1134
+ // sx = Start X, the x position of the left bar
1135
+ columnTemplate.sx = currentBar - firstBar ;
1136
+ // ex = End X, the x position of the right bar
1137
+ columnTemplate.ex = nextBar - firstBar ;
1138
+
1139
+ if ( headColumn ) { columnTemplate.headColumn = true ; }
1140
+ if ( style ) { columnTemplate.style = style ; }
1141
+ if ( columnSeparator ) { columnTemplate.columnSeparator = true ; }
1142
+ if ( leftAlign || rightAlign || leftCenter || rightCenter ) {
1143
+ columnTemplate.align =
1144
+ leftCenter && rightCenter ? 'center' :
1145
+ leftAlign && rightAlign ? 'justify' :
1146
+ leftAlign ? 'left' :
1147
+ rightAlign ? 'right' :
1148
+ 'default' ;
1149
+ }
1150
+
1151
+ currentBar = columnSeparator ? nextBar + 1 : nextBar ;
1152
+ columnIndex ++ ;
1153
+ }
1154
+
1155
+
1156
+ // Should come AFTER, because it needs column info
1157
+ // Fix previous table row as table head row: turn all existing tableRow into tableHeadRow
1158
+
1159
+ for ( let index = 0 ; index < table.children.length ; index ++ ) {
1160
+ let child = table.children[ index ] ;
1161
+
1162
+ if ( child.type === 'tableRow' ) {
1163
+ if ( ! tableHeadRow ) {
1164
+ // This is the first tableRow, turn it into a a tableHeadRow
1165
+ child.type = 'tableHeadRow' ;
1166
+
1167
+ if ( child.children ) {
1168
+ for ( columnIndex = 0 ; columnIndex < child.children.length ; columnIndex ++ ) {
1169
+ let child2 = child.children[ columnIndex ] ;
1170
+ if ( child2.type === 'tableCell' || child2.type === 'tableHeadCell' ) {
1171
+ child2.type = 'tableHeadCell' ;
1172
+ child2.isColumnHead = true ;
1173
+ }
1174
+ }
1175
+ }
1176
+
1177
+ tableHeadRow = child ;
1178
+ if ( thick ) { tableHeadRow.rowSeparator = true ; }
1179
+ }
1180
+ else {
1181
+ // All subsequent tableRows, are merged into the first tableHeadRow created
1182
+
1183
+ if ( child.children ) {
1184
+ for ( columnIndex = 0 ; columnIndex < child.children.length ; columnIndex ++ ) {
1185
+ let child2 = child.children[ columnIndex ] ;
1186
+ if ( child2.type === 'tableCell' || child2.type === 'tableHeadCell' ) {
1187
+ if ( tableHeadRow.children[ columnIndex ] ) {
1188
+ // Merge the cells
1189
+ mergeInlineParts( tableHeadRow.children[ columnIndex ].children , child2.children ) ;
1190
+ }
1191
+ else {
1192
+ child2.type = 'tableHeadCell' ;
1193
+ child2.isColumnHead = true ;
1194
+ tableHeadRow.children[ columnIndex ] = child2 ;
1195
+ }
1196
+ }
1197
+ }
1198
+ }
1199
+
1200
+ table.children.splice( index , 1 ) ;
1201
+ index -- ;
1202
+ }
1203
+ }
1204
+ }
1205
+
1206
+ if ( tableHeadRow ) {
1207
+ // Compute cells indexes, columnSpan, rowSpan, column template
1208
+ computeIndexColumnSpan( ctx , table , tableHeadRow ) ;
1209
+ }
1210
+
1211
+ ctx.i = lastCharOfLine + 1 ;
1212
+ }
1213
+
1214
+
1215
+
1216
+ function computeIndexColumnSpan( ctx , table , tableRow ) {
1217
+ var tableCell , cellIndex , column , columnIndex , columnSpan ,
1218
+ columns = table.columns ,
1219
+ extraSpan = columns ? columns.length - tableRow.children.length : 0 ;
1220
+
1221
+
1222
+ for ( cellIndex = columnIndex = 0 ; cellIndex < tableRow.children.length ; cellIndex ++ , columnIndex ++ ) {
1223
+ tableCell = tableRow.children[ cellIndex ] ;
1224
+ tableCell.column = columnIndex ;
1225
+ columnSpan = 1 ;
1226
+
1227
+ if ( columns ) {
1228
+ column = columns[ columnIndex ] ;
1229
+
1230
+ if ( column ) {
1231
+ if ( column.headColumn ) {
1232
+ tableCell.type = 'tableHeadCell' ;
1233
+ tableCell.isRowHead = true ;
1234
+ }
1235
+ }
1236
+
1237
+ while ( extraSpan > 0 && Math.abs( tableCell.ex - columns[ columnIndex ].ex ) > Math.abs( tableCell.ex - columns[ columnIndex + 1 ].ex ) ) {
1238
+ columnIndex ++ ;
1239
+ extraSpan -- ;
1240
+ columnSpan ++ ;
1241
+ }
1242
+
1243
+ if ( columnSpan >= 2 ) {
1244
+ tableCell.columnSpan = columnSpan ;
1245
+ }
1246
+ }
1247
+ }
1248
+ }
1249
+
1250
+
1251
+
1252
+ function parseInlineChildren( str , ctx , blockEnd , trim = false ) {
1253
+ stack( ctx ) ;
1254
+ parseInline( str , ctx , blockEnd , trim ) ;
1255
+ unstack( ctx ) ;
1256
+ }
1257
+
1258
+
1259
+
1260
+ function parseInlineChildrenOfParent( str , ctx , parent , blockEnd , trim = false ) {
1261
+ stack( ctx , parent ) ;
1262
+ parseInline( str , ctx , blockEnd , trim ) ;
1263
+ unstack( ctx ) ;
1264
+ }
1265
+
1266
+
1267
+
1268
+ // Try to parse non-block content
1269
+ function parseInline( str , ctx , blockEnd , trim = false ) {
1270
+ //console.log( "parseInline() -- remaining:" , ctx.i , str.slice( ctx.i ) ) ;
1271
+ var isSpace , scanEnd ,
1272
+ lastWasSpace = WHITE_SPACES.has( str[ ctx.i - 1 ] ) ;
1273
+
1274
+ scanEnd = blockEnd = blockEnd ?? searchEndOfLine( str , ctx.i ) ;
1275
+
1276
+ if ( trim ) {
1277
+ let first = searchNextNotInSet( str , ctx.i , blockEnd , WHITE_SPACES ) ;
1278
+
1279
+ if ( first === - 1 ) {
1280
+ ctx.i = blockEnd ;
1281
+ if ( str[ ctx.i ] === '\n' ) { ctx.i ++ ; }
1282
+ return ;
1283
+ }
1284
+
1285
+ let last = searchPreviousNotInSet( str , blockEnd - 1 , first - 1 , WHITE_SPACES ) ;
1286
+ // The scan can't fail, 'last' can't be -1, because the forward search succeeded
1287
+ ctx.i = first ;
1288
+ scanEnd = last + 1 ;
1289
+ }
1290
+
1291
+ ctx.iStartOfInlineChunk = ctx.i ;
1292
+
1293
+ for ( ; ctx.i < scanEnd ; ctx.i ++ ) {
1294
+ let char = str[ ctx.i ] ;
1295
+
1296
+ //if ( lastWasSpace ) {}
1297
+ //console.error( "Checking: " , string.inspect( char ) ) ;
1298
+
1299
+ isSpace = WHITE_SPACES.has( char ) ;
1300
+
1301
+ if ( isSpace ) {
1302
+ addInlineTextChunk( str , ctx ) ;
1303
+ parseWhiteSpace( str , ctx ) ;
1304
+ }
1305
+ else if ( char === '\\' ) {
1306
+ addInlineTextChunk( str , ctx ) ;
1307
+ parseEscape( str , ctx ) ;
1308
+ }
1309
+ else if ( char === '*' && ! WHITE_SPACES.has( str[ ctx.i + 1 ] ) ) {
1310
+ addInlineTextChunk( str , ctx ) ;
1311
+ parseEmphasis( str , ctx , scanEnd ) ;
1312
+ }
1313
+ else if ( char === '_' && ! WHITE_SPACES.has( str[ ctx.i + 1 ] ) ) {
1314
+ addInlineTextChunk( str , ctx ) ;
1315
+ parseDecoration( str , ctx , scanEnd ) ;
1316
+ }
1317
+ else if ( char === '`' ) {
1318
+ addInlineTextChunk( str , ctx ) ;
1319
+ parseCode( str , ctx , scanEnd ) ;
1320
+ }
1321
+ else if ( char === '[' ) {
1322
+ addInlineTextChunk( str , ctx ) ;
1323
+ parseStyledText( str , ctx , scanEnd ) ;
1324
+ }
1325
+ else if ( char === '!' && str[ ctx.i + 1 ] === '[' && lastWasSpace ) {
1326
+ addInlineTextChunk( str , ctx ) ;
1327
+ parseImage( str , ctx , scanEnd ) ;
1328
+ }
1329
+
1330
+ lastWasSpace = isSpace ;
1331
+ }
1332
+
1333
+ addInlineTextChunk( str , ctx ) ;
1334
+
1335
+ ctx.i = blockEnd ;
1336
+ if ( str[ ctx.i ] === '\n' ) { ctx.i ++ ; }
1337
+ }
1338
+
1339
+
1340
+
1341
+ function addInlineTextChunk( str , ctx , forcedChunk = null ) {
1342
+ var chunk = forcedChunk ?? str.slice( ctx.iStartOfInlineChunk , ctx.i ) ;
1343
+
1344
+ if ( ctx.forceInlineChunkSpace ) {
1345
+ chunk = ' ' + chunk ;
1346
+ ctx.forceInlineChunkSpace = false ;
1347
+ }
1348
+
1349
+ if ( chunk ) {
1350
+ let lastPart = ctx.parts[ ctx.parts.length - 1 ] ;
1351
+
1352
+ if ( lastPart && lastPart.type === 'text' ) {
1353
+ lastPart.text += chunk ;
1354
+ }
1355
+ else {
1356
+ ctx.parts.push( {
1357
+ type: 'text' ,
1358
+ text: chunk
1359
+ } ) ;
1360
+ }
1361
+ }
1362
+
1363
+ if ( ! forcedChunk ) {
1364
+ ctx.iStartOfInlineChunk = ctx.i ;
1365
+ }
1366
+ }
1367
+
1368
+
1369
+
1370
+ const WHITE_SPACES = new Set( [ ' ' , '\t' , '\n' , '\r' ] ) ;
1371
+
1372
+
1373
+
1374
+ function parseWhiteSpace( str , ctx ) {
1375
+ var end = ctx.i + 1 ;
1376
+ while ( WHITE_SPACES.has( str[ end ] ) ) { end ++ ; }
1377
+
1378
+ ctx.i = end - 1 ;
1379
+ ctx.iStartOfInlineChunk = ctx.i + 1 ;
1380
+
1381
+ addInlineTextChunk( str , ctx , ' ' ) ;
1382
+ }
1383
+
1384
+
1385
+
1386
+ function parseEscape( str , ctx ) {
1387
+ if ( ctx.i + 1 >= str.length ) {
1388
+ ctx.iStartOfInlineChunk = ctx.i + 1 ;
1389
+ return ;
1390
+ }
1391
+
1392
+ if ( str[ ctx.i + 1 ] === ' ' ) {
1393
+ if ( str[ ctx.i - 1 ] === '\n' ) {
1394
+ addInlineTextChunk( str , ctx , '\n' ) ;
1395
+ }
1396
+ else if ( searchEndOfEmptyLine( str , ctx.i + 2 ) !== - 1 ) {
1397
+ // Since we are not at the begining of the line, it actually search for trailing white chars
1398
+ addInlineTextChunk( str , ctx , '\n' ) ;
1399
+ }
1400
+ else {
1401
+ addInlineTextChunk( str , ctx , ' ' ) ;
1402
+ }
1403
+ }
1404
+ else if ( str[ ctx.i + 1 ] === '\n' ) {
1405
+ addInlineTextChunk( str , ctx , '\n' ) ;
1406
+ }
1407
+ else {
1408
+ addInlineTextChunk( str , ctx , str[ ctx.i + 1 ] ) ;
1409
+ }
1410
+
1411
+ ctx.i ++ ;
1412
+ ctx.iStartOfInlineChunk = ctx.i + 1 ;
1413
+ }
1414
+
1415
+
1416
+
1417
+ function parseEmphasis( str , ctx , blockEnd ) {
1418
+ //console.error( "parseStyledText()" ) ;
1419
+ var streak = countStreak( str , ctx.i , '*' ) ;
1420
+ if ( streak > 3 ) { return ; }
1421
+ var end = searchSwitchCloser( str , ctx.i + streak , '*' , streak , true , false , blockEnd ) ;
1422
+ if ( end < 0 ) { return ; }
1423
+
1424
+ var text = str.slice( ctx.i + streak , end + 1 - streak ) ;
1425
+
1426
+ ctx.parts.push( { type: 'emphasis' , level: streak , text } ) ;
1427
+ ctx.i = end ;
1428
+ ctx.iStartOfInlineChunk = ctx.i + 1 ;
1429
+ }
1430
+
1431
+
1432
+
1433
+ function parseDecoration( str , ctx , blockEnd ) {
1434
+ //console.error( "parseStyledText()" ) ;
1435
+ var streak = countStreak( str , ctx.i , '_' ) ;
1436
+ if ( streak > 2 ) { return ; }
1437
+ var end = searchSwitchCloser( str , ctx.i + streak , '_' , streak , true , false , blockEnd ) ;
1438
+ if ( end < 0 ) { return ; }
1439
+
1440
+ var text = str.slice( ctx.i + streak , end + 1 - streak ) ;
1441
+
1442
+ ctx.parts.push( {
1443
+ type: 'decoration' , underline: true , level: streak , text
1444
+ } ) ;
1445
+ ctx.i = end ;
1446
+ ctx.iStartOfInlineChunk = ctx.i + 1 ;
1447
+ }
1448
+
1449
+
1450
+
1451
+ function parseCode( str , ctx , blockEnd ) {
1452
+ //console.error( "parseStyledText()" ) ;
1453
+ var streak = countStreak( str , ctx.i , '`' ) ;
1454
+ // Markdown supports inline code inside two pairs of backquote, to allow backquote in code, hence streak can be 2.
1455
+ if ( streak > 2 ) { return ; }
1456
+ var end = searchSwitchCloser( str , ctx.i + streak , '`' , streak , false , false , blockEnd ) ;
1457
+ if ( end < 0 ) { return ; }
1458
+
1459
+ var sliceStart = ctx.i + streak ,
1460
+ sliceEnd = end + 1 - streak ;
1461
+
1462
+ if ( str[ sliceStart ] === ' ' && str[ sliceStart + 1 ] === '`' ) { sliceStart ++ ; }
1463
+ if ( str[ sliceEnd - 1 ] === ' ' && str[ sliceEnd - 2 ] === '`' ) { sliceEnd -- ; }
1464
+
1465
+ var text = str.slice( sliceStart , sliceEnd ) ;
1466
+
1467
+ ctx.parts.push( { type: 'code' , text } ) ;
1468
+ ctx.i = end ;
1469
+ ctx.iStartOfInlineChunk = ctx.i + 1 ;
1470
+ }
1471
+
1472
+
1473
+
1474
+ const STYLE_DATA_MARK = {
1475
+ text: true , href: true , style: true , extra: false
1476
+ } ;
1477
+
1478
+ function parseStyledText( str , ctx , blockEnd ) {
1479
+ //console.error( "parseStyledText()" ) ;
1480
+ var end = searchCloser( str , ctx.i + 1 , '[' , ']' , false , blockEnd ) ;
1481
+ if ( end < 0 ) { return ; }
1482
+
1483
+ var text = str.slice( ctx.i + 1 , end ) ;
1484
+
1485
+ ctx.i = end ;
1486
+ var data = parseDataMark( str , ctx , STYLE_DATA_MARK , blockEnd ) ;
1487
+ if ( ! data ) { return ; }
1488
+
1489
+ var params = { type: '' , text } ;
1490
+ if ( data.href?.[ 0 ] ) { params.href = data.href[ 0 ] ; }
1491
+ if ( data.style?.[ 0 ] ) { params.style = data.style[ 0 ] ; }
1492
+ if ( data.text?.[ 0 ] ) { params.title = data.text[ 0 ] ; }
1493
+
1494
+ if ( params.href ) {
1495
+ params.type = 'link' ;
1496
+ ctx.parts.push( params ) ;
1497
+ }
1498
+ else if ( params.style || params.title ) {
1499
+ params.type = 'styledText' ;
1500
+ ctx.parts.push( params ) ;
1501
+ }
1502
+ }
1503
+
1504
+
1505
+
1506
+ const IMAGE_DATA_MARK = {
1507
+ text: true , href: true , style: false , extra: false
1508
+ } ;
1509
+
1510
+ function parseImage( str , ctx , blockEnd ) {
1511
+ //console.error( "parseStyledText()" ) ;
1512
+ var end = searchCloser( str , ctx.i + 2 , '[' , ']' , false , blockEnd ) ;
1513
+ if ( end < 0 ) { return ; }
1514
+
1515
+ var text = str.slice( ctx.i + 2 , end ) ;
1516
+
1517
+ ctx.i = end ;
1518
+ ctx.iStartOfInlineChunk = ctx.i + 1 ;
1519
+ var data = parseDataMark( str , ctx , IMAGE_DATA_MARK , blockEnd ) ;
1520
+ if ( ! data ) { return ; }
1521
+
1522
+ var params = { type: '' , altText: text } ;
1523
+ if ( data.href?.[ 0 ] ) { params.href = data.href[ 0 ] ; }
1524
+
1525
+ if ( params.href ) {
1526
+ params.type = 'image' ;
1527
+ params.altText = text ;
1528
+ if ( data.text?.[ 0 ] ) { params.title = data.text[ 0 ] ; }
1529
+ ctx.parts.push( params ) ;
1530
+ }
1531
+ else {
1532
+ params.type = 'pictogram' ;
1533
+ params.code = text ;
1534
+ let emojiChar = emoji.get( text ) ;
1535
+ if ( emojiChar ) { params.emoji = emojiChar ; }
1536
+
1537
+ if ( data.text?.[ 0 ] ) { params.altText = data.text[ 0 ] ; }
1538
+ if ( data.text?.[ 1 ] ) { params.title = data.text[ 1 ] ; }
1539
+
1540
+ if ( emojiChar && ! params.altText ) {
1541
+ params.altText = emoji.getCanonicalName( emojiChar ) ;
1542
+ }
1543
+
1544
+ ctx.parts.push( params ) ;
1545
+ }
1546
+ }
1547
+
1548
+
1549
+
1550
+ function parseDataMark( str , ctx , allow , blockEnd , forTextElement = true ) {
1551
+ var end ,
1552
+ data = {} ;
1553
+
1554
+ for ( ;; ) {
1555
+ if ( str[ ctx.i + 1 ] === '[' && allow.text ) {
1556
+ end = searchCloser( str , ctx.i + 2 , '[' , ']' , false , blockEnd ) ;
1557
+ if ( end < 0 ) { return ; }
1558
+ if ( ! data.text ) { data.text = [] ; }
1559
+ data.text.push( str.slice( ctx.i + 2 , end ) ) ;
1560
+ ctx.i = end ;
1561
+ ctx.iStartOfInlineChunk = ctx.i + 1 ;
1562
+ }
1563
+ else if ( str[ ctx.i + 1 ] === '(' && allow.href ) {
1564
+ end = searchCloser( str , ctx.i + 2 , '(' , ')' , true , blockEnd ) ;
1565
+ if ( end < 0 ) { return ; }
1566
+ if ( ! data.href ) { data.href = [] ; }
1567
+ data.href.push( str.slice( ctx.i + 2 , end ) ) ;
1568
+ ctx.i = end ;
1569
+ ctx.iStartOfInlineChunk = ctx.i + 1 ;
1570
+ }
1571
+ else if ( str[ ctx.i + 1 ] === '<' && allow.style ) {
1572
+ end = searchCloser( str , ctx.i + 2 , '<' , '>' , true , blockEnd ) ;
1573
+ if ( end < 0 ) { return ; }
1574
+ if ( ! data.style ) { data.style = [] ; }
1575
+ //data.style.push( str.slice( ctx.i + 2 , end ) ) ;
1576
+ let style = Style.parse( str.slice( ctx.i + 2 , end ) , forTextElement ) ;
1577
+ data.style.push( style ) ;
1578
+ //console.error( "Parsed style:" , style ) ;
1579
+ ctx.i = end ;
1580
+ ctx.iStartOfInlineChunk = ctx.i + 1 ;
1581
+ }
1582
+ /*
1583
+ else if ( str[ ctx.i + 1 ] === '{' && allow.extra ) {
1584
+ end = searchCloser( str , ctx.i + 2 , '{' , '}' , false , blockEnd ) ;
1585
+ if ( end < 0 ) { return ; }
1586
+ if ( ! data.extra ) { data.extra = [] ; }
1587
+ data.extra.push( str.slice( ctx.i + 2 , end ) ) ;
1588
+ ctx.i = end ;
1589
+ ctx.iStartOfInlineChunk = ctx.i + 1 ;
1590
+ }
1591
+ */
1592
+ else {
1593
+ break ;
1594
+ }
1595
+ }
1596
+
1597
+ return data ;
1598
+ }
1599
+
1600
+
1601
+
1602
+ function postProcessTableRowSpan( table ) {
1603
+ var tableRow , tableCell , masterCell , lastRow , lastContinueRowSpan ;
1604
+
1605
+ // First pass: merge cells
1606
+ for ( tableRow of table.children ) {
1607
+ if ( tableRow.type !== 'tableRow' ) { continue ; }
1608
+
1609
+ if ( lastContinueRowSpan ) {
1610
+ for ( let columnIndex of lastContinueRowSpan ) {
1611
+ tableCell = searchColumn( tableRow , columnIndex ) ;
1612
+ masterCell = searchColumn( lastRow , columnIndex ) ;
1613
+ if ( tableCell && masterCell ) {
1614
+ if ( masterCell.masterCell ) { masterCell = masterCell.masterCell ; }
1615
+ mergeInlineParts( masterCell.children , tableCell.children ) ;
1616
+ masterCell.rowSpan = ( masterCell.rowSpan || 1 ) + 1 ;
1617
+ tableCell.masterCell = masterCell ;
1618
+ }
1619
+ }
1620
+ }
1621
+
1622
+ lastRow = tableRow ;
1623
+ lastContinueRowSpan = tableRow.continueRowSpan ;
1624
+ }
1625
+
1626
+ // Second pass: remove dead cells
1627
+ for ( tableRow of table.children ) {
1628
+ if ( tableRow.type !== 'tableRow' ) { continue ; }
1629
+ inPlaceFilter( tableRow.children , tableCell_ => ! tableCell_.masterCell ) ;
1630
+ }
1631
+ }
1632
+
1633
+
1634
+
1635
+ // Merge two inline parts blocks, adding an extra space joint in between if necessary
1636
+ function mergeInlineParts( parts , extraParts ) {
1637
+ if ( ! extraParts.length ) { return ; }
1638
+
1639
+ if ( ! parts.length ) {
1640
+ parts.push( ... extraParts ) ;
1641
+ return ;
1642
+ }
1643
+
1644
+ var lastPart = parts[ parts.length - 1 ] ,
1645
+ firstExtraPart = extraParts[ 0 ] ;
1646
+
1647
+ var needExtraSpace = ! WHITE_SPACES.has( lastPart.text[ lastPart.text.length - 1 ] ) && ! WHITE_SPACES.has( firstExtraPart.text[ 0 ] ) ;
1648
+
1649
+ if ( lastPart.type === 'text' && firstExtraPart.type === 'text' ) {
1650
+ // Combine the last existing with the first additional part
1651
+ if ( needExtraSpace ) { lastPart.text += ' ' + firstExtraPart.text ; }
1652
+ else { lastPart.text += firstExtraPart.text ; }
1653
+ for ( let i = 1 ; i < extraParts.length ; i ++ ) { parts.push( extraParts[ i ] ) ; }
1654
+
1655
+ return ;
1656
+ }
1657
+
1658
+ if ( needExtraSpace ) {
1659
+ if ( lastPart.type === 'text' ) {
1660
+ lastPart.text += ' ' ;
1661
+ }
1662
+ if ( firstExtraPart.type === 'text' ) {
1663
+ firstExtraPart.text = ' ' + firstExtraPart.text ;
1664
+ }
1665
+ else {
1666
+ // Add an additional joint part
1667
+ parts.push( { type: 'text' , text: ' ' } ) ;
1668
+ }
1669
+ }
1670
+
1671
+ parts.push( ... extraParts ) ;
1672
+ }
1673
+
1674
+
1675
+
1676
+ function searchEndOfLine( str , i ) {
1677
+ var length = str.length ;
1678
+
1679
+ for ( ; i < length ; i ++ ) {
1680
+ if ( str[ i ] === '\n' ) { return i ; }
1681
+ }
1682
+
1683
+ return str.length ;
1684
+ }
1685
+
1686
+
1687
+
1688
+ // Like searchEndOfLine() but return -1 if it's not an empty line (a line containing characters that are not space or tabs)
1689
+ function searchEndOfEmptyLine( str , i ) {
1690
+ var length = str.length ;
1691
+
1692
+ for ( ; i < length ; i ++ ) {
1693
+ if ( str[ i ] === '\n' ) { return i ; }
1694
+ if ( str[ i ] !== '\t' && str[ i ] !== ' ' ) { return - 1 ; }
1695
+ }
1696
+
1697
+ return str.length ;
1698
+ }
1699
+
1700
+
1701
+
1702
+ function searchLastCharOfLine( str , i ) {
1703
+ var length = str.length ,
1704
+ lastCharIndex = - 1 ;
1705
+
1706
+ for ( ; i < length ; i ++ ) {
1707
+ if ( str[ i ] === '\n' ) { return lastCharIndex ; }
1708
+ if ( ! WHITE_SPACES.has( str[ i ] ) ) { lastCharIndex = i ; }
1709
+ }
1710
+
1711
+ return lastCharIndex ;
1712
+ }
1713
+
1714
+
1715
+
1716
+ function searchNext( str , start , end , nextChar ) {
1717
+ for ( let i = start ; i < end ; i ++ ) {
1718
+ if ( str[ i ] === '\n' ) { return - 1 ; }
1719
+ if ( str[ i ] === '\\' && str[ i + 1 ] !== '\n' ) { i ++ ; continue ; }
1720
+ if ( str[ i ] === nextChar ) { return i ; }
1721
+ }
1722
+
1723
+ return - 1 ;
1724
+ }
1725
+
1726
+
1727
+
1728
+ function searchPrevious( str , start , end , previousChar ) {
1729
+ for ( let i = start ; i > end ; i -- ) {
1730
+ if ( str[ i ] === '\n' ) { return - 1 ; }
1731
+ if ( str[ i - 1 ] === '\\' ) { i -- ; continue ; }
1732
+ if ( str[ i ] === previousChar ) { return i ; }
1733
+ }
1734
+
1735
+ return - 1 ;
1736
+ }
1737
+
1738
+
1739
+
1740
+ // notInSet is a Set
1741
+ function searchNextNotInSet( str , start , end , notInSet ) {
1742
+ for ( let i = start ; i < end ; i ++ ) {
1743
+ if ( str[ i ] === '\n' ) { return - 1 ; }
1744
+ if ( str[ i ] === '\\' && str[ i + 1 ] !== '\n' ) { i ++ ; continue ; }
1745
+ if ( ! notInSet.has( str[ i ] ) ) { return i ; }
1746
+ }
1747
+
1748
+ return - 1 ;
1749
+ }
1750
+
1751
+
1752
+
1753
+ // notInSet is a Set
1754
+ function searchPreviousNotInSet( str , start , end , notInSet ) {
1755
+ for ( let i = start ; i > end ; i -- ) {
1756
+ if ( str[ i ] === '\n' ) { return - 1 ; }
1757
+ if ( str[ i - 1 ] === '\\' ) { i -- ; continue ; }
1758
+ if ( ! notInSet.has( str[ i ] ) ) { return i ; }
1759
+ }
1760
+
1761
+ return - 1 ;
1762
+ }
1763
+
1764
+
1765
+
1766
+ function searchCloser( str , i , opener , closer , inline = false , end = str.length ) {
1767
+ var opened = 1 ;
1768
+
1769
+ for ( ; i < end ; i ++ ) {
1770
+ if ( str[ i ] === '\\' && str[ i + 1 ] !== '\n' ) { i ++ ; continue ; }
1771
+ if ( inline && str[ i ] === '\n' ) { break ; }
1772
+
1773
+ if ( str[ i ] === opener ) {
1774
+ opened ++ ;
1775
+ }
1776
+ else if ( str[ i ] === closer ) {
1777
+ opened -- ;
1778
+ if ( ! opened ) { return i ; }
1779
+ }
1780
+ }
1781
+
1782
+ return - 1 ;
1783
+ }
1784
+
1785
+
1786
+
1787
+ /*
1788
+ Same that searchCloser() but for things like '*' that starts and stop with the same amount of '*'.
1789
+
1790
+ closerStreak: number of times the closer char should repeat
1791
+ noSpaceBefore: no space should be present right before the closer
1792
+ inline: true if it doesn't span over multiple lines
1793
+ end: the position in the string where to stop scanning
1794
+ */
1795
+ function searchSwitchCloser( str , i , closer , closerStreak = 1 , noSpaceBefore = false , inline = false , end = str.length ) {
1796
+ var streak = 0 ;
1797
+
1798
+ for ( ; i < end ; i ++ ) {
1799
+ if ( str[ i ] === '\\' && str[ i + 1 ] !== '\n' ) { i ++ ; continue ; }
1800
+ if ( inline && str[ i ] === '\n' ) { break ; }
1801
+
1802
+ if ( str[ i ] === closer && ( ! noSpaceBefore || ! WHITE_SPACES.has( str[ i - 1 ] ) ) ) {
1803
+ streak ++ ;
1804
+ if ( streak === closerStreak && str[ i + 1 ] !== closer ) { return i ; }
1805
+ }
1806
+ else {
1807
+ streak = 0 ;
1808
+ }
1809
+ }
1810
+
1811
+ return - 1 ;
1812
+ }
1813
+
1814
+
1815
+
1816
+ /*
1817
+ Same that searchSwitchCloser() but for things like block switcher like '```'.
1818
+ So it search for new lines, and search the switcher at the begining of that line.
1819
+ It returns an array with the block end index before and after the ending markup, or null if nothing was found.
1820
+
1821
+ closerMinStreak: number of times the closer char should repeat
1822
+ end: the position in the string where to stop scanning
1823
+ */
1824
+ function searchBlockSwitchCloser( str , i , closer , closerMinStreak = 1 , end = str.length ) {
1825
+ var streak = 0 ;
1826
+
1827
+ while ( i < end ) {
1828
+ // Search next line
1829
+ while ( str[ i ] !== '\n' && i < end ) { i ++ ; }
1830
+
1831
+ i ++ ;
1832
+
1833
+ if ( str[ i ] === '\\' && str[ i + 1 ] !== '\n' ) { i ++ ; continue ; }
1834
+
1835
+ if ( str[ i ] === closer ) {
1836
+ streak = countStreak( str , i , closer ) ;
1837
+ if ( streak >= closerMinStreak ) {
1838
+ end = searchEndOfEmptyLine( str , i + streak ) ;
1839
+ if ( end >= 0 ) {
1840
+ return [ i , end ] ;
1841
+ }
1842
+ }
1843
+ }
1844
+ }
1845
+
1846
+ return null ;
1847
+ }
1848
+
1849
+
1850
+
1851
+ /*
1852
+ Same that searchBlockSwitchCloser() but with a callback function.
1853
+ */
1854
+ function searchFixedBlockSwitchCloser( str , i , fixed , end = str.length ) {
1855
+ var test , j , failed ;
1856
+
1857
+ while ( i < end ) {
1858
+ // Search next line
1859
+ while ( str[ i ] !== '\n' && i < end ) { i ++ ; }
1860
+
1861
+ i ++ ;
1862
+
1863
+ if ( str[ i ] === '\\' && str[ i + 1 ] !== '\n' ) { i ++ ; continue ; }
1864
+
1865
+ if ( str[ i ] === fixed[ 0 ] ) {
1866
+ failed = false ;
1867
+ for ( j = 1 ; j < fixed.length ; j ++ ) {
1868
+ if ( str[ i + j ] !== fixed[ j ] ) { failed = true ; break ; }
1869
+ }
1870
+ if ( ! failed ) {
1871
+ end = searchEndOfEmptyLine( str , i + fixed.length ) ;
1872
+ if ( end >= 0 ) {
1873
+ return [ i , end ] ;
1874
+ }
1875
+ }
1876
+ }
1877
+ }
1878
+
1879
+ return null ;
1880
+ }
1881
+
1882
+
1883
+
1884
+ // Count successive char
1885
+ function countStreak( str , i , streaker ) {
1886
+ var length = str.length ,
1887
+ count = 0 ;
1888
+
1889
+ while ( i < length && str[ i ] === streaker ) {
1890
+ i ++ ;
1891
+ count ++ ;
1892
+ }
1893
+
1894
+ return count ;
1895
+ }
1896
+
1897
+
1898
+
1899
+ function searchChildOfType( parent , type ) {
1900
+ var children = parent.children ;
1901
+ if ( ! children ) { return null ; }
1902
+
1903
+ for ( let i = 0 ; i < children.length ; i ++ ) {
1904
+ let child = children[ i ] ;
1905
+ if ( child.type === type ) { return child ; }
1906
+ }
1907
+
1908
+ return null ;
1909
+ }
1910
+
1911
+
1912
+
1913
+ function searchLastChildOfType( parent , type ) {
1914
+ var children = parent.children ;
1915
+ if ( ! children ) { return null ; }
1916
+
1917
+ for ( let i = children.length - 1 ; i >= 0 ; i -- ) {
1918
+ let child = children[ i ] ;
1919
+ if ( child.type === type ) { return child ; }
1920
+ }
1921
+
1922
+ return null ;
1923
+ }
1924
+
1925
+
1926
+
1927
+ function searchColumn( tableRow , column ) {
1928
+ for ( let tableCell of tableRow.children ) {
1929
+ if ( tableCell.column === column ) { return tableCell ; }
1930
+ }
1931
+
1932
+ return null ;
1933
+ }
1934
+
1935
+
1936
+
1937
+ function stack( ctx , parent = ctx.parts[ ctx.parts.length - 1 ] ) {
1938
+ ctx.stack.push( {
1939
+ parts: ctx.parts ,
1940
+ parent: ctx.parent ,
1941
+ iEndOfBlock: ctx.iEndOfBlock
1942
+ } ) ;
1943
+
1944
+ ctx.parent = parent ;
1945
+ ctx.parts = parent.children = parent.children || [] ;
1946
+ }
1947
+
1948
+
1949
+
1950
+ function unstack( ctx ) {
1951
+ if ( ! ctx.stack.length ) { return ; }
1952
+ var old = ctx.stack.pop() ;
1953
+ ctx.parts = old.parts ;
1954
+ ctx.parent = old.parent ;
1955
+ ctx.iEndOfBlock = old.iEndOfBlock ;
1956
+ }
1957
+
1958
+
1959
+
1960
+ function unstackToIndent( ctx , toIndent = 0 ) {
1961
+ var parentIndent = ctx.parent?.indent || 0 ,
1962
+ parentType = ctx.parent?.type ;
1963
+
1964
+ while ( ctx.parent && ( toIndent < parentIndent || ( toIndent === parentIndent && parentType !== 'quote' ) ) ) {
1965
+ let old = ctx.stack.pop() ;
1966
+ ctx.parts = old.parts ;
1967
+ ctx.parent = old.parent ;
1968
+ ctx.iEndOfBlock = old.iEndOfBlock ;
1969
+ parentIndent = ctx.parent?.indent || 0 ;
1970
+ parentType = ctx.parent?.type ;
1971
+ }
1972
+ }
1973
+