@agentshouse/mdmodel 0.1.0-alpha.17 → 0.1.0-alpha.18

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.
package/dist/links.js CHANGED
@@ -1,11 +1,49 @@
1
1
  const TAB_STOP = 4;
2
2
  const CLOSING_FENCE_INDENT = 3;
3
+ const CODE_INDENT = 4;
3
4
  const HEADING = /^#{1,6}(?:[ \t]|$)/;
4
5
  const BREAK = /^(?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$/;
5
6
  const SETEXT = /^(?:=+|-+)[ \t]*$/;
6
- const LABEL = /^\[((?:[^\\\]\n]|\\.)+)\]:[ \t]*/;
7
- const DESTINATION = /^(?:(<[^<>\n\r]*>)|([^\s<][^\s]*))/d;
8
- const TITLE = /^(?:"[^"\n]*"|'[^'\n]*'|\([^()\n]*\))[ \t]*$/;
7
+ const BLANK = /^[ \t\v\f]*\r?$/;
8
+ const HTML_CLOSED = [
9
+ [
10
+ /^<(?:script|pre|style)(?:[ \t\v\f>]|$)/i,
11
+ /<\/(?:script|pre|style)>/i
12
+ ],
13
+ [
14
+ /^<!--/,
15
+ /-->/
16
+ ],
17
+ [
18
+ /^<\?/,
19
+ /\?>/
20
+ ],
21
+ [
22
+ /^<![A-Z]/,
23
+ />/
24
+ ],
25
+ [
26
+ /^<!\[CDATA\[/,
27
+ /\]\]>/
28
+ ]
29
+ ];
30
+ const HTML_BLOCK_TAG = /^<\/?(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?:[ \t\v\f>]|\/>|$)/i;
31
+ const HTML_TAG_LINE = /^(?:<[A-Za-z][A-Za-z0-9-]*(?:[ \t\v\f]+[A-Za-z_:][A-Za-z0-9_.:-]*(?:[ \t\v\f]*=[ \t\v\f]*(?:[^ \t\v\f\r\n"'=<>`]+|'[^']*'|"[^"]*"))?)*[ \t\v\f]*\/?>|<\/[A-Za-z][A-Za-z0-9-]*[ \t\v\f]*>)[ \t\v\f]*$/;
32
+ const ORDERED = /^(\d{1,9})[.)]/;
33
+ const LABEL_LENGTH = 999;
34
+ const PUNCTUATION = /^[!-/:-@[-`{-~]$/;
35
+ const INLINE_RAW = new RegExp([
36
+ '<[A-Za-z][A-Za-z0-9-]*(?:[ \\t\\n\\v\\f\\r]+[A-Za-z_:][A-Za-z0-9_.:-]*(?:[ \\t\\n\\v\\f\\r]*=[ \\t\\n\\v\\f\\r]*(?:[^ \\t\\n\\v\\f\\r"\'=<>`]+|\'[^\']*\'|"[^"]*"))?)*[ \\t\\n\\v\\f\\r]*\\/?>',
37
+ '<\\/[A-Za-z][A-Za-z0-9-]*[ \\t\\n\\v\\f\\r]*>',
38
+ '<!--(?!-?>)(?:-?[^-])*-->',
39
+ '<\\?[\\s\\S]*?\\?>',
40
+ '<![A-Z]+[ \\t\\n\\v\\f\\r]+[^>]*>',
41
+ '<!\\[CDATA\\[[\\s\\S]*?\\]\\]>',
42
+ '<[A-Za-z][A-Za-z0-9+.-]{1,31}:[^\\x00-\\x20<>]*>',
43
+ "<[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*>"
44
+ ].join('|'), 'y');
45
+ const DELIMITER_ROW = /^\|?[ \t]*:?-+:?[ \t]*(?:\|[ \t]*:?-+:?[ \t]*)*\|?[ \t]*$/;
46
+ const GAP = /^[ \t\v\f]+/;
9
47
  function endOfLine(text, from) {
10
48
  const nl = text.indexOf('\n', from);
11
49
  return nl === -1 ? text.length : nl;
@@ -123,159 +161,435 @@ function fenceSpan(text, line, open) {
123
161
  }
124
162
  return text.length;
125
163
  }
126
- function skipInlineCode(text, index) {
164
+ function skipInlineCode(text, index, joins) {
127
165
  let ticks = 0;
128
166
  while(text[index + ticks] === '`')ticks += 1;
129
- const close = text.indexOf('`'.repeat(ticks), index + ticks);
130
- if (close === -1) return text.length;
131
- return close + ticks;
167
+ let i = index + ticks;
168
+ while(i < text.length){
169
+ const ch = text[i];
170
+ if (ch === '`') {
171
+ let run = 0;
172
+ while(text[i + run] === '`')run += 1;
173
+ if (run === ticks) return i + run;
174
+ i += run;
175
+ continue;
176
+ }
177
+ if (ch === '\r' && text[i + 1] !== '\n') break;
178
+ if (ch === '\n' && !joins.has(i)) break;
179
+ i += 1;
180
+ }
181
+ return index + ticks;
132
182
  }
133
183
  function skipLinkSpace(text, start) {
134
184
  let i = start;
135
185
  while(text[i] === ' ' || text[i] === '\t')i += 1;
136
186
  return i;
137
187
  }
138
- function skipTitle(text, start) {
139
- const open = text[start];
140
- if (open !== '"' && open !== "'" && open !== '(') return start;
188
+ function skipInlineSpace(text, start, joins) {
189
+ let i = skipLinkSpace(text, start);
190
+ if (text[i] === '\r' && text[i + 1] === '\n') i += 1;
191
+ if (text[i] !== '\n' || !joins.has(i)) return i;
192
+ return afterIndent(text, readLine(text, i + 1, endOfLine(text, i + 1)));
193
+ }
194
+ function closeEnd(text, start, open, joins) {
141
195
  const close = open === '(' ? ')' : open;
142
- let i = start + 1;
196
+ let i = start;
143
197
  while(i < text.length){
144
198
  const ch = text[i];
199
+ if (escapes(text, i)) {
200
+ i += 2;
201
+ continue;
202
+ }
145
203
  if (ch === close) return i + 1;
146
- if (ch === '\n' || ch === '\r') return null;
204
+ if (open === '(' && ch === '(') return null;
205
+ if (ch === '\r' && text[i + 1] !== '\n') return null;
206
+ if (ch === '\n' && !joins.has(i)) return null;
147
207
  i += 1;
148
208
  }
149
209
  return null;
150
210
  }
151
- function destination(text, start) {
152
- let i = skipLinkSpace(text, start + 1);
153
- let occurrence;
154
- if (text[i] === '<') {
155
- const close = text.indexOf('>', i + 1);
156
- if (close === -1) return null;
157
- occurrence = {
158
- href: text.slice(i + 1, close),
159
- start: i,
211
+ function skipTitle(text, start, joins) {
212
+ const open = text[start];
213
+ if (open !== '"' && open !== "'" && open !== '(') return start;
214
+ return closeEnd(text, start + 1, open, joins);
215
+ }
216
+ function destinationAt(text, at, end) {
217
+ if (text[at] === '<') {
218
+ let close = at + 1;
219
+ while(close < end && text[close] !== '>'){
220
+ if (text[close] === '<') return null;
221
+ close += escapes(text, close) ? 2 : 1;
222
+ }
223
+ if (close >= end) return null;
224
+ return {
225
+ href: text.slice(at + 1, close),
226
+ start: at,
160
227
  end: close + 1,
161
228
  bracketed: true
162
229
  };
163
- i = close + 1;
164
- } else {
165
- const from = i;
166
- let depth = 0;
167
- while(i < text.length){
168
- const ch = text[i];
169
- if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') break;
170
- if (ch === '(') depth += 1;
171
- else if (ch === ')') {
172
- if (depth === 0) break;
173
- depth -= 1;
174
- }
175
- i += 1;
230
+ }
231
+ let i = at;
232
+ let depth = 0;
233
+ while(i < end){
234
+ if (escapes(text, i)) {
235
+ i += 2;
236
+ continue;
176
237
  }
177
- occurrence = {
178
- href: text.slice(from, i),
179
- start: from,
180
- end: i,
181
- bracketed: false
182
- };
238
+ const ch = text[i];
239
+ if (ch <= ' ' || ch === '\x7f') break;
240
+ if (ch === '(') depth += 1;
241
+ else if (ch === ')') {
242
+ if (depth === 0) break;
243
+ depth -= 1;
244
+ }
245
+ i += 1;
183
246
  }
184
- i = skipLinkSpace(text, i);
185
- const afterTitle = skipTitle(text, i);
247
+ if (depth !== 0) return null;
248
+ return {
249
+ href: text.slice(at, i),
250
+ start: at,
251
+ end: i,
252
+ bracketed: false
253
+ };
254
+ }
255
+ function destination(text, start, joins) {
256
+ let i = skipInlineSpace(text, start + 1, joins);
257
+ const occurrence = destinationAt(text, i, endOfLine(text, i));
258
+ if (occurrence === null) return null;
259
+ i = occurrence.end;
260
+ const spaced = skipInlineSpace(text, i, joins);
261
+ if (spaced === i && text[i] !== ')') return null;
262
+ i = spaced;
263
+ const afterTitle = skipTitle(text, i, joins);
186
264
  if (afterTitle === null) return null;
187
- i = skipLinkSpace(text, afterTitle);
265
+ i = skipInlineSpace(text, afterTitle, joins);
188
266
  if (text[i] !== ')') return null;
189
267
  return {
190
268
  occurrence,
191
269
  end: i + 1
192
270
  };
193
271
  }
194
- function labelEnd(text, start) {
272
+ function cellCount(row) {
273
+ return row.trim().replace(/^\|/, '').replace(/(?<!\\)\|$/, '').split(/(?<!\\)\|/).length;
274
+ }
275
+ function headsTable(text, header, end) {
276
+ if (end >= text.length) return false;
277
+ const row = lineText(text, readLine(text, end + 1, endOfLine(text, end + 1)));
278
+ return row.includes('|') && DELIMITER_ROW.test(row) && cellCount(row) === cellCount(header);
279
+ }
280
+ function blockText(text, line) {
281
+ if (!line.item) return lineText(text, line);
282
+ const raw = text.slice(afterIndent(text, line), line.end);
283
+ return raw.endsWith('\r') ? raw.slice(0, -1) : raw;
284
+ }
285
+ function interruptsWithHtml(head) {
286
+ return HTML_CLOSED.some(([open])=>open.test(head)) || HTML_BLOCK_TAG.test(head);
287
+ }
288
+ function markerColumn(text, line) {
289
+ return widthOf(text, line.inner, afterIndent(text, line));
290
+ }
291
+ function markerEnd(text, at, end) {
292
+ const after = itemMarker(text, at, end);
293
+ if (after === null) return null;
294
+ return after > at && (text[after - 1] === ' ' || text[after - 1] === '\t') ? after - 1 : after;
295
+ }
296
+ function itemContent(text, line) {
297
+ let marker = markerEnd(text, afterIndent(text, line), line.end);
298
+ for(;;){
299
+ let next = marker + 1;
300
+ let spaces = 0;
301
+ while(spaces < 3 && text[next] === ' '){
302
+ next += 1;
303
+ spaces += 1;
304
+ }
305
+ const nested = next < line.end ? markerEnd(text, next, line.end) : null;
306
+ if (nested === null) break;
307
+ marker = nested;
308
+ }
309
+ const at = skipLinkSpace(text, marker);
310
+ const rest = text.slice(at, line.end).trim();
311
+ const column = widthOf(text, line.inner, at);
312
+ const start = widthOf(text, line.inner, marker);
313
+ if (rest !== '' && column - start <= CODE_INDENT) return {
314
+ column,
315
+ code: null
316
+ };
317
+ return {
318
+ column: start + 1,
319
+ code: rest === '' ? null : marker + 1
320
+ };
321
+ }
322
+ function joinsParagraph(text, line, depth, item) {
323
+ if (line.depth > depth) return false;
324
+ const head = blockText(text, line);
325
+ if (head.trim() === '' || endsBlock(head) || interruptsWithHtml(head)) return false;
326
+ if (fenceAt(text, line) !== null) return false;
327
+ if (!line.item) return true;
328
+ if (item !== undefined && markerColumn(text, line) < item) return false;
329
+ if (lineText(text, line).trim() === '') return true;
330
+ const ordered = ORDERED.exec(head);
331
+ return ordered !== null && Number(ordered[1]) !== 1;
332
+ }
333
+ function startsParagraph(text, line) {
334
+ return lineText(text, line).trim() !== '' && !endsBlock(blockText(text, line));
335
+ }
336
+ function lineColumn(text, line) {
337
+ return line.item ? markerColumn(text, line) : indentWidth(text, line);
338
+ }
339
+ function endsTable(text, line, depth) {
340
+ const head = blockText(text, line);
341
+ return head.trim() === '' || line.depth !== depth || line.item || endsBlock(head) || interruptsWithHtml(head) || fenceAt(text, line) !== null;
342
+ }
343
+ function quoteInner(text, start, end, depth) {
195
344
  let i = start;
196
- let depth = 1;
197
- while(i < text.length && depth > 0){
345
+ for(let level = 0; level < depth; level += 1){
346
+ let spaces = 0;
347
+ while(spaces < 3 && i < end && text[i] === ' '){
348
+ i += 1;
349
+ spaces += 1;
350
+ }
351
+ if (text[i] !== '>') return null;
352
+ i += 1;
353
+ if (text[i] === ' ') i += 1;
354
+ }
355
+ return i;
356
+ }
357
+ function htmlEnd(text, line, item) {
358
+ const head = lineText(text, line);
359
+ const closed = HTML_CLOSED.find(([open])=>open.test(head));
360
+ if (closed === undefined && !HTML_BLOCK_TAG.test(head) && !HTML_TAG_LINE.test(head)) return null;
361
+ if (closed !== undefined && closed[1].test(head)) return afterLine(text, line.end);
362
+ let cursor = afterLine(text, line.end);
363
+ while(cursor < text.length){
364
+ const end = endOfLine(text, cursor);
365
+ const inner = quoteInner(text, cursor, end, line.depth);
366
+ if (inner === null) return cursor;
367
+ const at = skipLinkSpace(text, inner);
368
+ const body = text.slice(inner, end);
369
+ const blank = BLANK.test(text.slice(at, end));
370
+ if (item !== undefined && !blank && widthOf(text, inner, at) < item) return cursor;
371
+ if (closed === undefined && blank) return cursor;
372
+ if (closed !== undefined && closed[1].test(body)) return afterLine(text, end);
373
+ cursor = afterLine(text, end);
374
+ if (cursor === end) break;
375
+ }
376
+ return text.length;
377
+ }
378
+ function codeEnd(text, line, base) {
379
+ let stop = afterLine(text, line.end);
380
+ let cursor = stop;
381
+ while(cursor < text.length){
382
+ const end = endOfLine(text, cursor);
383
+ const current = readLine(text, cursor, end);
384
+ if (current.depth !== line.depth) break;
385
+ const blank = lineText(text, current).trim() === '' && !current.item;
386
+ if (!blank && (current.item || indentWidth(text, current) < base + CODE_INDENT)) break;
387
+ cursor = afterLine(text, end);
388
+ if (!blank) stop = cursor;
389
+ if (cursor === end) break;
390
+ }
391
+ return stop;
392
+ }
393
+ function structure(text) {
394
+ const joins = new Set();
395
+ const raw = [];
396
+ const items = [];
397
+ let paragraph = null;
398
+ let table = null;
399
+ let depth = 0;
400
+ let cursor = 0;
401
+ while(cursor < text.length){
402
+ const end = endOfLine(text, cursor);
403
+ const line = readLine(text, cursor, end);
404
+ const content = lineText(text, line);
405
+ const blank = content.trim() === '' && !line.item;
406
+ if (table !== null && endsTable(text, line, table)) table = null;
407
+ if (table !== null) {
408
+ cursor = afterLine(text, end);
409
+ if (cursor === end) break;
410
+ continue;
411
+ }
412
+ if (paragraph !== null && joinsParagraph(text, line, paragraph, items.at(-1))) {
413
+ if (headsTable(text, content, end)) {
414
+ table = line.depth;
415
+ paragraph = null;
416
+ } else joins.add(cursor - 1);
417
+ cursor = afterLine(text, end);
418
+ if (cursor === end) break;
419
+ continue;
420
+ }
421
+ paragraph = null;
422
+ if (line.depth !== depth) items.length = 0;
423
+ depth = line.depth;
424
+ if (!blank) {
425
+ while(items.length > 0 && items.at(-1) > lineColumn(text, line))items.pop();
426
+ }
427
+ if (line.item && !BREAK.test(blockText(text, line))) {
428
+ const opened = itemContent(text, line);
429
+ items.push(opened.column);
430
+ if (opened.code !== null) {
431
+ const stop = codeEnd(text, line, opened.column);
432
+ raw.push([
433
+ opened.code,
434
+ stop
435
+ ]);
436
+ cursor = stop;
437
+ continue;
438
+ }
439
+ }
440
+ const open = fenceAt(text, line);
441
+ if (open !== null) {
442
+ const stop = fenceSpan(text, line, open);
443
+ raw.push([
444
+ cursor,
445
+ stop
446
+ ]);
447
+ cursor = stop;
448
+ continue;
449
+ }
450
+ const base = items.at(-1) ?? 0;
451
+ if (!blank && !line.item && indentWidth(text, line) >= base + CODE_INDENT) {
452
+ const stop = codeEnd(text, line, base);
453
+ raw.push([
454
+ cursor,
455
+ stop
456
+ ]);
457
+ cursor = stop;
458
+ continue;
459
+ }
460
+ const html = htmlEnd(text, line, items.at(-1));
461
+ if (html !== null) {
462
+ raw.push([
463
+ cursor,
464
+ html
465
+ ]);
466
+ cursor = html;
467
+ continue;
468
+ }
469
+ if (startsParagraph(text, line)) {
470
+ if (headsTable(text, content, end)) table = line.depth;
471
+ else paragraph = line.depth;
472
+ }
473
+ cursor = afterLine(text, end);
474
+ if (cursor === end) break;
475
+ }
476
+ return {
477
+ joins,
478
+ raw
479
+ };
480
+ }
481
+ function escapes(text, index) {
482
+ return text[index] === '\\' && PUNCTUATION.test(text[index + 1] ?? '');
483
+ }
484
+ function referenceEnd(text, start, joins) {
485
+ let i = start;
486
+ while(i < text.length){
198
487
  const ch = text[i];
199
- if (ch === '\\') {
488
+ if (escapes(text, i)) {
200
489
  i += 2;
201
490
  continue;
202
491
  }
203
- if (ch === '[') depth += 1;
204
- else if (ch === ']') depth -= 1;
205
- else if (ch === '\n' || ch === '\r') return -1;
492
+ if (ch === ']') return i + 1;
493
+ if (ch === '[') return -1;
494
+ if (ch === '\r' && text[i + 1] !== '\n') return -1;
495
+ if (ch === '\n' && !joins.has(i)) return -1;
206
496
  i += 1;
207
497
  }
208
- return depth === 0 ? i : -1;
209
- }
210
- function takeLink(text, index, start, found, defs) {
211
- const end = labelEnd(text, index + 1);
212
- if (end === -1) return index + 1;
213
- const label = text.slice(index + 1, end - 1);
214
- let i = end;
215
- if (text[i] === '(') {
216
- const dest = destination(text, i);
217
- if (dest === null) return index + 1;
218
- found.push({
219
- start,
220
- end: dest.end,
221
- labelStart: index + 1,
222
- labelEnd: end - 1,
223
- destination: dest.occurrence
498
+ return -1;
499
+ }
500
+ function labelText(text, from, to) {
501
+ let label = '';
502
+ let cursor = from;
503
+ let newline = text.indexOf('\n', cursor);
504
+ while(newline !== -1 && newline < to){
505
+ label += `${text.slice(cursor, newline)}\n`;
506
+ cursor = afterIndent(text, readLine(text, newline + 1, endOfLine(text, newline + 1)));
507
+ newline = text.indexOf('\n', cursor);
508
+ }
509
+ return label + text.slice(cursor, to);
510
+ }
511
+ function labelKey(label) {
512
+ return label.replace(/[ \t\n\v\f\r]+/g, ' ').replace(/^ | $/g, '').toLowerCase().split('\u0131').map((part)=>part.toUpperCase()).join('\u0131');
513
+ }
514
+ function labelKeyAt(text, from, to) {
515
+ const label = labelText(text, from, to);
516
+ if (label.length > LABEL_LENGTH && [
517
+ ...label
518
+ ].length > LABEL_LENGTH) return '';
519
+ return labelKey(label);
520
+ }
521
+ function closeLink(text, opener, close, defs, joins) {
522
+ const end = close + 1;
523
+ const use = (destination, to)=>({
524
+ start: opener.start,
525
+ end: to,
526
+ labelStart: opener.at + 1,
527
+ labelEnd: close,
528
+ destination
224
529
  });
225
- return dest.end;
530
+ if (text[end] === '(') {
531
+ const dest = destination(text, end, joins);
532
+ if (dest !== null) return use(dest.occurrence, dest.end);
226
533
  }
227
- if (text[i] === '[') {
228
- const close = labelEnd(text, i + 1);
229
- if (close === -1) return index + 1;
230
- const id = (text.slice(i + 1, close - 1) || label).trim().toLowerCase();
231
- const defined = defs.get(id);
232
- if (defined !== undefined) {
233
- found.push({
234
- start,
235
- end: close,
236
- labelStart: index + 1,
237
- labelEnd: end - 1,
238
- destination: defined
239
- });
534
+ if (text[end] === '[') {
535
+ const second = referenceEnd(text, end + 1, joins);
536
+ const collapsed = second === end + 2;
537
+ const named = collapsed ? opener.bracketAfter ? '' : labelKeyAt(text, opener.at + 1, close) : labelKeyAt(text, end + 1, second - 1);
538
+ if (second !== -1 && (named !== '' || collapsed)) {
539
+ const defined = defs.get(named);
540
+ return defined === undefined ? null : use(defined, second);
240
541
  }
241
- return close;
242
542
  }
243
- return i;
543
+ if (opener.bracketAfter) return null;
544
+ const shortcut = defs.get(labelKeyAt(text, opener.at + 1, close));
545
+ return shortcut === undefined ? null : use(shortcut, end);
244
546
  }
245
547
  function definitionDestination(text, from, to) {
246
- let i = from;
247
- while(i < to && (text[i] === ' ' || text[i] === '\t'))i += 1;
248
- const match = DESTINATION.exec(text.slice(i, to));
249
- if (match === null) return null;
250
- const bracketed = match[1];
251
- const span = match.indices?.[1] ?? match.indices?.[2];
252
- const href = bracketed === undefined ? match[2] : bracketed.slice(1, -1);
253
- if (href === undefined || span === undefined) return null;
254
- return {
255
- href,
256
- start: i + span[0],
257
- end: i + span[1],
258
- bracketed: bracketed !== undefined
259
- };
548
+ const occurrence = destinationAt(text, skipLinkSpace(text, from), to);
549
+ return occurrence === null || occurrence.href === '' && !occurrence.bracketed ? null : occurrence;
550
+ }
551
+ function titleEnd(text, at, joins) {
552
+ const open = text[at];
553
+ if (open !== '"' && open !== "'" && open !== '(') return null;
554
+ const closed = closeEnd(text, at + 1, open, joins);
555
+ if (closed === null) return null;
556
+ const end = endOfLine(text, closed);
557
+ return BLANK.test(text.slice(closed, end)) ? afterLine(text, end) : null;
260
558
  }
261
- function definitionAt(text, line) {
262
- const head = LABEL.exec(lineText(text, line));
263
- if (head === null) return null;
264
- const id = head[1]?.trim().toLowerCase();
265
- if (id === undefined || id === '') return null;
266
- const from = line.content + head[0].length;
267
- let occurrence = definitionDestination(text, from, line.end);
268
- let next = afterLine(text, line.end);
559
+ function definitionAt(text, line, joins) {
560
+ if (text[line.content] !== '[') return null;
561
+ const close = referenceEnd(text, line.content + 1, joins);
562
+ if (close === -1 || text[close] !== ':') return null;
563
+ const id = labelKeyAt(text, line.content + 1, close - 1);
564
+ if (id === '') return null;
565
+ const from = close + 1;
566
+ let end = endOfLine(text, from);
567
+ let occurrence = definitionDestination(text, from, end);
269
568
  if (occurrence === null) {
270
- if (text.slice(from, line.end).trim() !== '' || next >= text.length) return null;
271
- const end = endOfLine(text, next);
272
- occurrence = definitionDestination(text, readLine(text, next, end).content, end);
569
+ if (text.slice(from, end).trim() !== '' || !joins.has(end)) return null;
570
+ const start = end + 1;
571
+ end = endOfLine(text, start);
572
+ occurrence = definitionDestination(text, afterIndent(text, readLine(text, start, end)), end);
273
573
  if (occurrence === null) return null;
274
- next = afterLine(text, end);
275
574
  }
276
- if (next < text.length) {
277
- const end = endOfLine(text, next);
278
- if (TITLE.test(lineText(text, readLine(text, next, end)))) next = afterLine(text, end);
575
+ const rest = text.slice(occurrence.end, end);
576
+ if (!BLANK.test(rest)) {
577
+ const gap = GAP.exec(rest);
578
+ const titled = gap === null ? null : titleEnd(text, occurrence.end + gap[0].length, joins);
579
+ return titled === null ? null : {
580
+ id,
581
+ occurrence,
582
+ next: titled
583
+ };
584
+ }
585
+ const next = afterLine(text, end);
586
+ if (joins.has(end)) {
587
+ const titled = titleEnd(text, afterIndent(text, readLine(text, next, endOfLine(text, next))), joins);
588
+ if (titled !== null) return {
589
+ id,
590
+ occurrence,
591
+ next: titled
592
+ };
279
593
  }
280
594
  return {
281
595
  id,
@@ -287,34 +601,19 @@ function endsBlock(content) {
287
601
  return HEADING.test(content) || BREAK.test(content) || SETEXT.test(content);
288
602
  }
289
603
  function scanBlocks(text) {
290
- const fences = [];
604
+ const { joins, raw } = structure(text);
291
605
  const definitions = new Map();
292
606
  const blocks = [];
293
- let paragraph = false;
294
- let depth = 0;
607
+ let fresh = 0;
295
608
  let i = 0;
296
609
  while(i < text.length){
297
- const line = readLine(text, i, endOfLine(text, i));
298
- if (line.depth !== depth || line.item) paragraph = false;
299
- depth = line.depth;
300
- const open = fenceAt(text, line);
301
- if (open !== null) {
302
- const stop = fenceSpan(text, line, open);
303
- fences.push([
304
- i,
305
- stop
306
- ]);
307
- paragraph = false;
308
- i = stop;
309
- continue;
310
- }
311
- const content = lineText(text, line);
312
- if (content.trim() === '') {
313
- paragraph = false;
314
- i = afterLine(text, line.end);
610
+ const skip = spanEndAt(raw, i);
611
+ if (skip !== undefined) {
612
+ i = skip;
315
613
  continue;
316
614
  }
317
- const definition = paragraph ? null : definitionAt(text, line);
615
+ const line = readLine(text, i, endOfLine(text, i));
616
+ const definition = lineText(text, line).trim() === '' || i !== fresh && joins.has(i - 1) ? null : definitionAt(text, line, joins);
318
617
  if (definition !== null) {
319
618
  if (!definitions.has(definition.id)) definitions.set(definition.id, definition.occurrence);
320
619
  blocks.push({
@@ -323,46 +622,128 @@ function scanBlocks(text) {
323
622
  destination: definition.occurrence
324
623
  });
325
624
  i = definition.next;
625
+ fresh = i;
326
626
  continue;
327
627
  }
328
- paragraph = !endsBlock(content);
329
628
  i = afterLine(text, line.end);
330
629
  }
331
630
  return {
332
- fences,
631
+ raw,
333
632
  definitions,
334
- blocks
633
+ blocks,
634
+ joins
335
635
  };
336
636
  }
337
- function fenceEndAt(spans, index) {
637
+ function spanEndAt(spans, index) {
338
638
  for (const [start, end] of spans){
339
639
  if (index >= start && index < end) return end;
340
640
  }
341
641
  return undefined;
342
642
  }
343
643
  export function fenceSpans(text) {
344
- return scanBlocks(text).fences;
644
+ const fences = [];
645
+ let i = 0;
646
+ while(i < text.length){
647
+ const line = readLine(text, i, endOfLine(text, i));
648
+ const open = fenceAt(text, line);
649
+ if (open === null) {
650
+ i = afterLine(text, line.end);
651
+ continue;
652
+ }
653
+ const stop = fenceSpan(text, line, open);
654
+ fences.push([
655
+ i,
656
+ stop
657
+ ]);
658
+ i = stop;
659
+ }
660
+ return fences;
661
+ }
662
+ function paragraphEnd(text, from, joins) {
663
+ let i = from;
664
+ while(i < text.length){
665
+ if (text[i] === '\n' && !joins.has(i)) return i;
666
+ if (text[i] === '\r' && text[i + 1] !== '\n') return i;
667
+ i += 1;
668
+ }
669
+ return i;
345
670
  }
346
671
  export function linkInventory(body) {
347
- const { fences, definitions, blocks } = scanBlocks(body);
672
+ const { raw, definitions, blocks, joins } = scanBlocks(body);
673
+ const skipped = [
674
+ ...raw,
675
+ ...blocks.map((block)=>[
676
+ block.start,
677
+ block.end
678
+ ])
679
+ ];
348
680
  const uses = [];
681
+ const openers = [];
682
+ let paragraph = {
683
+ start: 0,
684
+ end: -1,
685
+ text: ''
686
+ };
349
687
  let i = 0;
350
688
  while(i < body.length){
351
- const fenceEnd = fenceEndAt(fences, i);
352
- if (fenceEnd !== undefined) {
353
- i = fenceEnd;
689
+ const skipEnd = spanEndAt(skipped, i);
690
+ if (skipEnd !== undefined) {
691
+ openers.length = 0;
692
+ i = skipEnd;
354
693
  continue;
355
694
  }
356
- if (body[i] === '`') {
357
- i = skipInlineCode(body, i);
695
+ const ch = body[i];
696
+ if (escapes(body, i)) {
697
+ i += 2;
358
698
  continue;
359
699
  }
360
- if (body[i] === '!' && body[i + 1] === '[') {
361
- i = takeLink(body, i + 1, i, uses, definitions);
700
+ if (ch === '`') {
701
+ i = skipInlineCode(body, i, joins);
702
+ continue;
703
+ }
704
+ if (ch === '<') {
705
+ if (i >= paragraph.end) {
706
+ const end = paragraphEnd(body, i, joins);
707
+ paragraph = {
708
+ start: i,
709
+ end,
710
+ text: body.slice(i, end)
711
+ };
712
+ }
713
+ INLINE_RAW.lastIndex = i - paragraph.start;
714
+ const raw = INLINE_RAW.exec(paragraph.text);
715
+ if (raw !== null) {
716
+ i += raw[0].length;
717
+ continue;
718
+ }
719
+ }
720
+ if (ch === '\n' && !joins.has(i) || ch === '\r' && body[i + 1] !== '\n') openers.length = 0;
721
+ if (ch === '[' || ch === '!' && body[i + 1] === '[') {
722
+ const previous = openers.at(-1);
723
+ if (previous !== undefined) previous.bracketAfter = true;
724
+ const image = ch === '!';
725
+ openers.push({
726
+ at: image ? i + 1 : i,
727
+ start: i,
728
+ image,
729
+ active: true,
730
+ bracketAfter: false
731
+ });
732
+ i += image ? 2 : 1;
362
733
  continue;
363
734
  }
364
- if (body[i] === '[') {
365
- i = takeLink(body, i, i, uses, definitions);
735
+ if (ch === ']') {
736
+ const opener = openers.pop();
737
+ const use = opener?.active ? closeLink(body, opener, i, definitions, joins) : null;
738
+ if (opener === undefined || use === null) {
739
+ i += 1;
740
+ continue;
741
+ }
742
+ uses.push(use);
743
+ if (!opener.image) {
744
+ for (const earlier of openers)if (!earlier.image) earlier.active = false;
745
+ }
746
+ i = use.end;
366
747
  continue;
367
748
  }
368
749
  i += 1;