@redocly/realm-plugin-asciidoc 0.1.0

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,747 @@
1
+ import path from 'node:path';
2
+ import Asciidoctor from '@asciidoctor/core';
3
+ const asciidoctor = Asciidoctor();
4
+ export function parseAsciidoc(content, relativePath, contentRoot) {
5
+ var _a;
6
+ const absolutePath = path.resolve(contentRoot, relativePath);
7
+ const doc = asciidoctor.load(content, {
8
+ safe: 'safe',
9
+ base_dir: path.dirname(absolutePath),
10
+ docfile: absolutePath,
11
+ attributes: {
12
+ showtitle: true,
13
+ 'source-highlighter': 'highlight.js',
14
+ icons: 'font',
15
+ idprefix: '',
16
+ idseparator: '-',
17
+ },
18
+ });
19
+ const title = ((_a = doc.getDocumentTitle()) === null || _a === void 0 ? void 0 : _a.toString()) || '';
20
+ const rawAttributes = doc.getAttributes();
21
+ const attributes = {};
22
+ for (const [key, value] of Object.entries(rawAttributes)) {
23
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
24
+ attributes[key] = value;
25
+ }
26
+ }
27
+ // Walk the AST to produce serializable content nodes
28
+ const topBlocks = doc.getBlocks();
29
+ const contentNodes = [];
30
+ // AsciiDoc `= Title` (level 0) is the document title — asciidoctor excludes it from
31
+ // getBlocks()/getSections(). Prepend it as an H1 heading so it renders on the page.
32
+ if (title) {
33
+ const { html: titleHtml, icons: titleIcons } = extractInlineIcons(title);
34
+ contentNodes.push({
35
+ type: 'section',
36
+ id: '',
37
+ title: stripInlineHtml(title),
38
+ titleHtml,
39
+ titleIcons,
40
+ level: 1,
41
+ children: [],
42
+ });
43
+ }
44
+ contentNodes.push(...convertBlocks(topBlocks, relativePath));
45
+ // Build section tree for TOC, search, and AI docs
46
+ const sections = buildSectionTree(doc.getSections());
47
+ // Extract links from converted HTML (used for sitemap / link checking)
48
+ const convertedHtml = doc.convert();
49
+ const links = extractLinks(convertedHtml, relativePath);
50
+ return {
51
+ title,
52
+ attributes,
53
+ sections,
54
+ contentNodes,
55
+ links,
56
+ sourceContent: content,
57
+ };
58
+ }
59
+ function convertBlocks(blocks, relativePath) {
60
+ const nodes = [];
61
+ for (let i = 0; i < blocks.length; i++) {
62
+ const block = blocks[i];
63
+ const context = block.getContext();
64
+ switch (context) {
65
+ case 'section':
66
+ nodes.push(convertSection(block, relativePath));
67
+ break;
68
+ case 'paragraph':
69
+ nodes.push(convertParagraph(block));
70
+ break;
71
+ case 'listing': {
72
+ const codeNode = convertListing(block, blocks[i + 1]);
73
+ // If we consumed a colist, skip it
74
+ if (codeNode.type === 'codeBlock' &&
75
+ codeNode.calloutDescriptions &&
76
+ codeNode.calloutDescriptions.length > 0) {
77
+ const next = blocks[i + 1];
78
+ if (next && next.getContext() === 'colist') {
79
+ i++;
80
+ }
81
+ }
82
+ nodes.push(codeNode);
83
+ break;
84
+ }
85
+ case 'literal':
86
+ nodes.push(convertLiteral(block));
87
+ break;
88
+ case 'admonition':
89
+ nodes.push(convertAdmonition(block, relativePath));
90
+ break;
91
+ case 'ulist':
92
+ nodes.push(convertList(block, false, relativePath));
93
+ break;
94
+ case 'olist':
95
+ nodes.push(convertList(block, true, relativePath));
96
+ break;
97
+ case 'dlist':
98
+ nodes.push(convertDescriptionList(block, relativePath));
99
+ break;
100
+ case 'table':
101
+ nodes.push(convertTable(block));
102
+ break;
103
+ case 'image':
104
+ nodes.push(convertImage(block, relativePath));
105
+ break;
106
+ case 'quote':
107
+ case 'verse':
108
+ nodes.push(convertQuote(block, relativePath));
109
+ break;
110
+ case 'sidebar':
111
+ nodes.push(convertSidebar(block, relativePath));
112
+ break;
113
+ case 'example':
114
+ nodes.push(convertExample(block, relativePath));
115
+ break;
116
+ case 'open':
117
+ nodes.push(convertOpen(block, relativePath));
118
+ break;
119
+ case 'thematic_break':
120
+ nodes.push({ type: 'thematicBreak' });
121
+ break;
122
+ case 'pass':
123
+ case 'stem':
124
+ nodes.push(convertPassthrough(block));
125
+ break;
126
+ case 'preamble':
127
+ // Preamble is a wrapper; recurse into its children
128
+ nodes.push(...convertBlocks(block.getBlocks(), relativePath));
129
+ break;
130
+ case 'toc':
131
+ // Skip generated TOC — we use our own
132
+ break;
133
+ case 'colist':
134
+ // Colist is consumed alongside listing blocks; if orphaned, skip
135
+ break;
136
+ default:
137
+ console.warn(`[asciidoc-plugin] Unsupported block type "${context}" — skipping.`);
138
+ break;
139
+ }
140
+ }
141
+ return nodes;
142
+ }
143
+ function convertSection(section, relativePath) {
144
+ const id = section.getId() || slugify(section.getTitle() || '');
145
+ const rawTitle = section.getTitle() || '';
146
+ const level = section.getLevel();
147
+ // Section titles can contain inline icons (e.g. `== icon:flag[] Heading`)
148
+ const { html: titleHtml, icons: titleIcons } = extractInlineIcons(rawTitle);
149
+ // Plain text title (strip HTML) for TOC and search
150
+ const title = stripInlineHtml(rawTitle);
151
+ const children = convertBlocks(section.getBlocks(), relativePath);
152
+ return {
153
+ type: 'section',
154
+ id,
155
+ title,
156
+ titleHtml,
157
+ titleIcons,
158
+ level,
159
+ children,
160
+ };
161
+ }
162
+ function convertParagraph(block) {
163
+ const html = block.getContent() || '';
164
+ const { html: processedHtml, icons } = extractInlineIcons(html);
165
+ const role = block.getRole() || undefined;
166
+ return {
167
+ type: 'paragraph',
168
+ contentHtml: processedHtml,
169
+ icons,
170
+ role,
171
+ };
172
+ }
173
+ function convertListing(block, nextBlock) {
174
+ const lang = block.getAttribute('language') || '';
175
+ const rawSource = block.getSource() || '';
176
+ const title = block.getTitle() || undefined;
177
+ // Parse callout markers from source: <1>, <2>, etc.
178
+ const { cleaned, callouts } = parseCallouts(rawSource);
179
+ // Check if next block is a colist (callout list)
180
+ let calloutDescriptions;
181
+ if (nextBlock && nextBlock.getContext() === 'colist') {
182
+ calloutDescriptions = extractColistDescriptions(nextBlock);
183
+ }
184
+ return {
185
+ type: 'codeBlock',
186
+ lang,
187
+ source: cleaned,
188
+ title,
189
+ callouts: callouts.length > 0 ? callouts : undefined,
190
+ calloutDescriptions: calloutDescriptions && calloutDescriptions.length > 0 ? calloutDescriptions : undefined,
191
+ };
192
+ }
193
+ function convertLiteral(block) {
194
+ const source = block.getSource() || '';
195
+ return {
196
+ type: 'codeBlock',
197
+ lang: '',
198
+ source,
199
+ title: block.getTitle() || undefined,
200
+ };
201
+ }
202
+ const ADMONITION_TYPE_MAP = {
203
+ note: 'info',
204
+ tip: 'success',
205
+ warning: 'warning',
206
+ caution: 'warning',
207
+ important: 'danger',
208
+ };
209
+ function convertAdmonition(block, relativePath) {
210
+ const style = (block.getStyle() || 'note').toLowerCase();
211
+ const admonitionType = ADMONITION_TYPE_MAP[style] || 'info';
212
+ const name = style.charAt(0).toUpperCase() + style.slice(1);
213
+ const innerBlocks = block.getBlocks();
214
+ const children = convertBlocks(innerBlocks, relativePath);
215
+ return {
216
+ type: 'admonition',
217
+ admonitionType,
218
+ name,
219
+ children,
220
+ };
221
+ }
222
+ function convertList(block, ordered, relativePath) {
223
+ const items = [];
224
+ for (const item of block.getItems()) {
225
+ const textHtml = item.getText() || '';
226
+ const { html: processedHtml, icons } = extractInlineIcons(textHtml);
227
+ const childBlocks = item.hasBlocks() ? item.getBlocks() : [];
228
+ const children = convertBlocks(childBlocks, relativePath);
229
+ items.push({
230
+ contentHtml: processedHtml,
231
+ icons,
232
+ children,
233
+ });
234
+ }
235
+ return {
236
+ type: 'list',
237
+ ordered,
238
+ items,
239
+ };
240
+ }
241
+ function convertDescriptionList(block, relativePath) {
242
+ const items = [];
243
+ const rawItems = block.getItems();
244
+ for (const pair of rawItems) {
245
+ if (!Array.isArray(pair) || pair.length < 2)
246
+ continue;
247
+ const [terms, definition] = pair;
248
+ const termsHtml = [];
249
+ if (Array.isArray(terms)) {
250
+ for (const term of terms) {
251
+ const text = typeof term.getText === 'function' ? term.getText() : String(term);
252
+ termsHtml.push(text);
253
+ }
254
+ }
255
+ let descriptionHtml = '';
256
+ let descriptionIcons = [];
257
+ let children = [];
258
+ if (definition && typeof definition.getText === 'function') {
259
+ const rawHtml = definition.getText() || '';
260
+ const extracted = extractInlineIcons(rawHtml);
261
+ descriptionHtml = extracted.html;
262
+ descriptionIcons = extracted.icons;
263
+ if (typeof definition.hasBlocks === 'function' && definition.hasBlocks()) {
264
+ children = convertBlocks(definition.getBlocks(), relativePath);
265
+ }
266
+ }
267
+ items.push({
268
+ termsHtml,
269
+ descriptionHtml,
270
+ descriptionIcons,
271
+ children,
272
+ });
273
+ }
274
+ return {
275
+ type: 'descriptionList',
276
+ items,
277
+ };
278
+ }
279
+ function convertTable(block) {
280
+ const rows = block.getRows();
281
+ const title = block.getTitle() || undefined;
282
+ const convertRow = (row) => row.map((cell) => {
283
+ var _a;
284
+ const colSpan = cell.getColumnSpan();
285
+ const rowSpan = cell.getRowSpan();
286
+ // getContent() returns rendered HTML for asciidoc cells; getText() for plain text cells
287
+ const style = (_a = cell.getStyle) === null || _a === void 0 ? void 0 : _a.call(cell);
288
+ const contentHtml = (style === 'asciidoc' ? cell.getContent() : cell.getText()) || '';
289
+ return Object.assign(Object.assign({ contentHtml }, (colSpan > 1 ? { colSpan } : {})), (rowSpan > 1 ? { rowSpan } : {}));
290
+ });
291
+ return {
292
+ type: 'table',
293
+ title,
294
+ head: (rows.head || []).map(convertRow),
295
+ body: (rows.body || []).map(convertRow),
296
+ foot: (rows.foot || []).map(convertRow),
297
+ };
298
+ }
299
+ function convertImage(block, relativePath) {
300
+ const target = block.getAttribute('target') || '';
301
+ const alt = block.getAttribute('alt') || '';
302
+ const width = block.getAttribute('width');
303
+ const height = block.getAttribute('height');
304
+ const title = block.getTitle() || undefined;
305
+ const src = normalizeImageSrc(target, relativePath);
306
+ return Object.assign(Object.assign({ type: 'image', src,
307
+ alt,
308
+ title }, (width ? { width: String(width) } : {})), (height ? { height: String(height) } : {}));
309
+ }
310
+ function convertQuote(block, relativePath) {
311
+ const attribution = block.getAttribute('attribution') || undefined;
312
+ const citeTitle = block.getAttribute('citetitle') || undefined;
313
+ const innerBlocks = block.getBlocks();
314
+ const children = innerBlocks.length > 0
315
+ ? convertBlocks(innerBlocks, relativePath)
316
+ : block.getContent()
317
+ ? [
318
+ {
319
+ type: 'paragraph',
320
+ contentHtml: block.getContent() || '',
321
+ icons: [],
322
+ },
323
+ ]
324
+ : [];
325
+ return {
326
+ type: 'quote',
327
+ attribution,
328
+ citeTitle,
329
+ children,
330
+ };
331
+ }
332
+ function convertSidebar(block, relativePath) {
333
+ return {
334
+ type: 'sidebar',
335
+ title: block.getTitle() || undefined,
336
+ children: convertBlocks(block.getBlocks(), relativePath),
337
+ };
338
+ }
339
+ function convertExample(block, relativePath) {
340
+ const isCollapsible = block.isOption('collapsible');
341
+ if (isCollapsible) {
342
+ return {
343
+ type: 'collapsible',
344
+ title: block.getTitle() || undefined,
345
+ children: convertBlocks(block.getBlocks(), relativePath),
346
+ };
347
+ }
348
+ return {
349
+ type: 'example',
350
+ title: block.getTitle() || undefined,
351
+ children: convertBlocks(block.getBlocks(), relativePath),
352
+ };
353
+ }
354
+ function convertOpen(block, relativePath) {
355
+ // An open block is a generic container — just recurse into children
356
+ const children = convertBlocks(block.getBlocks(), relativePath);
357
+ if (children.length === 1)
358
+ return children[0];
359
+ // Return as an example-like container if it has a role
360
+ const role = block.getRole();
361
+ if (role) {
362
+ return {
363
+ type: 'paragraph',
364
+ contentHtml: block.getContent() || '',
365
+ icons: [],
366
+ role,
367
+ };
368
+ }
369
+ // Just flatten open blocks
370
+ return {
371
+ type: 'example',
372
+ title: undefined,
373
+ children,
374
+ };
375
+ }
376
+ function convertPassthrough(block) {
377
+ return {
378
+ type: 'htmlPassthrough',
379
+ html: block.getContent() || '',
380
+ };
381
+ }
382
+ /**
383
+ * Parses raw AsciiDoc source callout markers like `<1>` `<2>` from code.
384
+ * Returns cleaned source and callout positions.
385
+ */
386
+ function parseCallouts(source) {
387
+ const callouts = [];
388
+ const calloutRegex = /\s*<(\d+)>\s*$/;
389
+ const lines = source.split('\n');
390
+ const cleanedLines = [];
391
+ for (let i = 0; i < lines.length; i++) {
392
+ let line = lines[i];
393
+ const match = line.match(calloutRegex);
394
+ if (match) {
395
+ callouts.push({ line: i + 1, number: parseInt(match[1], 10) });
396
+ line = line.replace(calloutRegex, '');
397
+ }
398
+ cleanedLines.push(line);
399
+ }
400
+ return { cleaned: cleanedLines.join('\n'), callouts };
401
+ }
402
+ /**
403
+ * Extracts callout descriptions from a colist (callout list) block.
404
+ */
405
+ function extractColistDescriptions(colist) {
406
+ const descriptions = [];
407
+ try {
408
+ const items = colist.getItems();
409
+ if (items && Array.isArray(items)) {
410
+ for (const item of items) {
411
+ if (typeof item.getText === 'function') {
412
+ descriptions.push(stripInlineHtml(item.getText()));
413
+ }
414
+ }
415
+ }
416
+ }
417
+ catch (_a) {
418
+ // Colist may not support getItems on all versions
419
+ }
420
+ return descriptions;
421
+ }
422
+ /**
423
+ * Known Font Awesome brand icon names that live under the `brands/` style.
424
+ */
425
+ const FA_BRAND_ICONS = new Set([
426
+ 'github',
427
+ 'twitter',
428
+ 'facebook',
429
+ 'linkedin',
430
+ 'youtube',
431
+ 'google',
432
+ 'apple',
433
+ 'android',
434
+ 'windows',
435
+ 'docker',
436
+ 'npm',
437
+ 'node-js',
438
+ 'node',
439
+ 'react',
440
+ 'angular',
441
+ 'vuejs',
442
+ 'python',
443
+ 'java',
444
+ 'php',
445
+ 'rust',
446
+ 'golang',
447
+ 'aws',
448
+ 'slack',
449
+ 'discord',
450
+ 'reddit',
451
+ 'stackoverflow',
452
+ 'stack-overflow',
453
+ 'gitlab',
454
+ 'bitbucket',
455
+ 'codepen',
456
+ 'jsfiddle',
457
+ 'bootstrap',
458
+ 'sass',
459
+ 'less',
460
+ 'css3',
461
+ 'html5',
462
+ 'js',
463
+ 'markdown',
464
+ 'stripe',
465
+ 'paypal',
466
+ 'shopify',
467
+ 'wordpress',
468
+ 'medium',
469
+ 'dev',
470
+ 'figma',
471
+ 'sketch',
472
+ 'dribbble',
473
+ 'behance',
474
+ 'instagram',
475
+ 'pinterest',
476
+ 'tiktok',
477
+ 'whatsapp',
478
+ 'telegram',
479
+ 'x-twitter',
480
+ ]);
481
+ /**
482
+ * Extracts `<i class="fa fa-*">` icons from inline HTML and replaces them
483
+ * with `<!--icon:N-->` placeholders. The extracted icon data is stored in
484
+ * the InlineIcon array so the template can render CDNIcon components.
485
+ */
486
+ function extractInlineIcons(html) {
487
+ const icons = [];
488
+ const processed = html.replace(/<i class="fa\s+fa-([a-z0-9-]+)([^"]*)"[^>]*><\/i>/gi, (_match, name, extraClasses) => {
489
+ let iconName = name;
490
+ let style = 'solid';
491
+ if (iconName.endsWith('-o')) {
492
+ iconName = iconName.slice(0, -2);
493
+ style = 'regular';
494
+ }
495
+ if (FA_BRAND_ICONS.has(iconName)) {
496
+ style = 'brands';
497
+ }
498
+ const size = extraClasses.includes('fa-2x') ? '2em' : undefined;
499
+ const idx = icons.length;
500
+ icons.push({ name: iconName, style, size });
501
+ return `<!--icon:${idx}-->`;
502
+ });
503
+ return { html: processed, icons };
504
+ }
505
+ function buildSectionTree(sections) {
506
+ return sections.map((section) => {
507
+ const id = section.getId() || slugify(section.getTitle() || '');
508
+ const title = section.getTitle() || '';
509
+ const level = section.getLevel();
510
+ // Extract plain text from the section's non-section blocks
511
+ const textContent = extractTextFromBlocks(section.getBlocks());
512
+ // Recurse into child sections
513
+ const childSections = section
514
+ .getBlocks()
515
+ .filter((b) => b.getContext() === 'section');
516
+ return {
517
+ id,
518
+ title,
519
+ level,
520
+ textContent,
521
+ children: buildSectionTree(childSections),
522
+ };
523
+ });
524
+ }
525
+ function extractTextFromBlocks(blocks) {
526
+ var _a;
527
+ const parts = [];
528
+ for (const block of blocks) {
529
+ const context = block.getContext();
530
+ if (context === 'section')
531
+ continue;
532
+ if (context === 'paragraph' || context === 'verse') {
533
+ const html = block.getContent();
534
+ if (html)
535
+ parts.push(stripInlineHtml(html));
536
+ }
537
+ else if (context === 'literal') {
538
+ const source = block.getSource();
539
+ if (source)
540
+ parts.push(normalizeUnicode(source));
541
+ }
542
+ else if (context === 'listing') {
543
+ const lang = block.getAttribute('language') || '';
544
+ const source = block.getSource();
545
+ if (source)
546
+ parts.push('```' + lang + '\n' + normalizeUnicode(source) + '\n```');
547
+ }
548
+ else if (context === 'ulist' || context === 'olist') {
549
+ try {
550
+ const items = block.getItems();
551
+ if (items && Array.isArray(items)) {
552
+ const isOrdered = context === 'olist';
553
+ const lines = [];
554
+ for (let idx = 0; idx < items.length; idx++) {
555
+ const item = items[idx];
556
+ if (typeof item.getText === 'function') {
557
+ const prefix = isOrdered ? `${idx + 1}. ` : '- ';
558
+ lines.push(prefix + stripInlineHtml(item.getText()));
559
+ }
560
+ }
561
+ if (lines.length)
562
+ parts.push(lines.join('\n'));
563
+ }
564
+ }
565
+ catch (_b) {
566
+ /* skip if getItems fails */
567
+ }
568
+ }
569
+ else if (context === 'dlist') {
570
+ try {
571
+ const items = block.getItems();
572
+ if (items && Array.isArray(items)) {
573
+ const lines = [];
574
+ for (const pair of items) {
575
+ if (Array.isArray(pair)) {
576
+ const [terms, definition] = pair;
577
+ if (Array.isArray(terms)) {
578
+ for (const t of terms) {
579
+ if (typeof t.getText === 'function') {
580
+ lines.push('**' + stripInlineHtml(t.getText()) + '**');
581
+ }
582
+ }
583
+ }
584
+ if (definition && typeof definition.getText === 'function') {
585
+ lines.push(': ' + stripInlineHtml(definition.getText()));
586
+ }
587
+ }
588
+ }
589
+ if (lines.length)
590
+ parts.push(lines.join('\n'));
591
+ }
592
+ }
593
+ catch (_c) {
594
+ /* skip */
595
+ }
596
+ }
597
+ else if (context === 'table') {
598
+ try {
599
+ parts.push(tableToMarkdown(block));
600
+ }
601
+ catch (_d) {
602
+ /* skip */
603
+ }
604
+ }
605
+ else if (context === 'admonition') {
606
+ const style = (((_a = block.getStyle) === null || _a === void 0 ? void 0 : _a.call(block)) || 'note').toUpperCase();
607
+ const innerBlocks = block.getBlocks();
608
+ const inner = (innerBlocks === null || innerBlocks === void 0 ? void 0 : innerBlocks.length)
609
+ ? extractTextFromBlocks(innerBlocks)
610
+ : stripInlineHtml(block.getContent() || '');
611
+ if (inner) {
612
+ parts.push(`> [!${style}]\n> ${inner.replace(/\n/g, '\n> ')}`);
613
+ }
614
+ }
615
+ else if (context === 'quote' ||
616
+ context === 'open' ||
617
+ context === 'example' ||
618
+ context === 'sidebar' ||
619
+ context === 'preamble') {
620
+ const innerBlocks = block.getBlocks();
621
+ if (innerBlocks && innerBlocks.length > 0) {
622
+ parts.push(extractTextFromBlocks(innerBlocks));
623
+ }
624
+ else {
625
+ const html = block.getContent();
626
+ if (html)
627
+ parts.push(stripInlineHtml(html));
628
+ }
629
+ }
630
+ }
631
+ return parts.filter(Boolean).join('\n\n');
632
+ }
633
+ function tableToMarkdown(block) {
634
+ const rows = block.getRows();
635
+ const headRows = [];
636
+ const bodyRows = [];
637
+ const footRows = [];
638
+ const extractRow = (row) => row.map((cell) => { var _a; return stripInlineHtml(((_a = cell.getText) === null || _a === void 0 ? void 0 : _a.call(cell)) || '').replace(/\|/g, '\\|'); });
639
+ for (const row of rows.head || []) {
640
+ if (Array.isArray(row))
641
+ headRows.push(extractRow(row));
642
+ }
643
+ for (const row of rows.body || []) {
644
+ if (Array.isArray(row))
645
+ bodyRows.push(extractRow(row));
646
+ }
647
+ for (const row of rows.foot || []) {
648
+ if (Array.isArray(row))
649
+ footRows.push(extractRow(row));
650
+ }
651
+ const allRows = [...headRows, ...bodyRows, ...footRows];
652
+ if (allRows.length === 0)
653
+ return '';
654
+ const colCount = Math.max(...allRows.map((r) => r.length));
655
+ const pad = (row) => {
656
+ while (row.length < colCount)
657
+ row.push('');
658
+ return row;
659
+ };
660
+ const lines = [];
661
+ if (headRows.length > 0) {
662
+ for (const row of headRows) {
663
+ lines.push('| ' + pad(row).join(' | ') + ' |');
664
+ }
665
+ }
666
+ else {
667
+ // Use first body row as header if no explicit header
668
+ const firstRow = bodyRows.shift();
669
+ if (firstRow) {
670
+ lines.push('| ' + pad(firstRow).join(' | ') + ' |');
671
+ }
672
+ }
673
+ // Separator row
674
+ lines.push('| ' + Array(colCount).fill('---').join(' | ') + ' |');
675
+ for (const row of bodyRows) {
676
+ lines.push('| ' + pad(row).join(' | ') + ' |');
677
+ }
678
+ for (const row of footRows) {
679
+ lines.push('| ' + pad(row).join(' | ') + ' |');
680
+ }
681
+ return lines.join('\n');
682
+ }
683
+ function extractLinks(html, _relativePath) {
684
+ const links = [];
685
+ const linkRegex = /<a\s+[^>]*href="([^"]*)"[^>]*>([^<]*)<\/a>/gi;
686
+ let match;
687
+ while ((match = linkRegex.exec(html)) !== null) {
688
+ const href = match[1];
689
+ const text = match[2];
690
+ let type = 'link';
691
+ if (href.startsWith('#')) {
692
+ type = 'xref';
693
+ }
694
+ else if (href.startsWith('http://') || href.startsWith('https://')) {
695
+ type = 'url';
696
+ }
697
+ links.push({ href, text, type });
698
+ }
699
+ return links;
700
+ }
701
+ function normalizeImageSrc(src, relativePath) {
702
+ if (!src)
703
+ return src;
704
+ if (src.startsWith('/') ||
705
+ src.startsWith('//') ||
706
+ src.startsWith('data:') ||
707
+ /^[a-z][a-z\d+\-.]*:/i.test(src)) {
708
+ return src;
709
+ }
710
+ const baseDir = path.posix.dirname(relativePath);
711
+ const resolvedPath = path.posix.normalize(path.posix.join(baseDir, src));
712
+ return `/${resolvedPath.replace(/^\/+/, '')}`;
713
+ }
714
+ function stripInlineHtml(text) {
715
+ return decodeHtmlEntities(text
716
+ .replace(/<span class="icon"><i class="fa[^"]*fa-([^"\s]+)"[^>]*><\/i><\/span>/gi, (_m, name) => `:${name}:`)
717
+ .replace(/<i class="fa[^"]*fa-([^"\s]+)"[^>]*><\/i>/gi, (_m, name) => `:${name}:`)
718
+ .replace(/<[^>]+>/g, ''));
719
+ }
720
+ function decodeHtmlEntities(text) {
721
+ return normalizeUnicode(text
722
+ .replace(/&#(\d+);/g, (_m, code) => String.fromCodePoint(Number(code)))
723
+ .replace(/&#x([0-9a-f]+);/gi, (_m, hex) => String.fromCodePoint(parseInt(hex, 16)))
724
+ .replace(/&amp;/g, '&')
725
+ .replace(/&lt;/g, '<')
726
+ .replace(/&gt;/g, '>')
727
+ .replace(/&quot;/g, '"')
728
+ .replace(/&apos;/g, "'"));
729
+ }
730
+ function normalizeUnicode(text) {
731
+ return text
732
+ .replace(/\u2014/g, '--')
733
+ .replace(/\u2013/g, '-')
734
+ .replace(/\u2018|\u2019/g, "'")
735
+ .replace(/\u201C|\u201D/g, '"')
736
+ .replace(/\u2026/g, '...')
737
+ .replace(/\u00A0/g, ' ');
738
+ }
739
+ function slugify(text) {
740
+ return text
741
+ .toLowerCase()
742
+ .replace(/[^\w\s-]/g, '')
743
+ .replace(/\s+/g, '-')
744
+ .replace(/-+/g, '-')
745
+ .replace(/^-|-$/g, '');
746
+ }
747
+ //# sourceMappingURL=asciidoc-parser.js.map