emdp 1.0.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.
Files changed (199) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -0
  3. package/dist/cjs/cli.js +36 -0
  4. package/dist/cjs/gfm.js +26 -0
  5. package/dist/cjs/index.js +26 -0
  6. package/dist/cjs/parser/block-parser.js +644 -0
  7. package/dist/cjs/parser/blocks/blockquote.js +28 -0
  8. package/dist/cjs/parser/blocks/code-block-fenced.js +58 -0
  9. package/dist/cjs/parser/blocks/code-block-indented.js +47 -0
  10. package/dist/cjs/parser/blocks/heading-atx.js +29 -0
  11. package/dist/cjs/parser/blocks/heading-setext.js +24 -0
  12. package/dist/cjs/parser/blocks/html-block.js +83 -0
  13. package/dist/cjs/parser/blocks/link-reference.js +109 -0
  14. package/dist/cjs/parser/blocks/list.js +155 -0
  15. package/dist/cjs/parser/blocks/paragraph.js +20 -0
  16. package/dist/cjs/parser/blocks/table.js +163 -0
  17. package/dist/cjs/parser/blocks/task-list.js +66 -0
  18. package/dist/cjs/parser/blocks/thematic-break.js +17 -0
  19. package/dist/cjs/parser/entities.js +2133 -0
  20. package/dist/cjs/parser/gfm/block-parser.js +773 -0
  21. package/dist/cjs/parser/gfm/index.js +125 -0
  22. package/dist/cjs/parser/gfm/inline-parser.js +813 -0
  23. package/dist/cjs/parser/gfm/renderer.js +513 -0
  24. package/dist/cjs/parser/index.js +104 -0
  25. package/dist/cjs/parser/inline-parser.js +564 -0
  26. package/dist/cjs/parser/inlines/autolink-extended.js +364 -0
  27. package/dist/cjs/parser/inlines/autolink.js +44 -0
  28. package/dist/cjs/parser/inlines/code-span.js +48 -0
  29. package/dist/cjs/parser/inlines/emphasis.js +64 -0
  30. package/dist/cjs/parser/inlines/entity.js +25 -0
  31. package/dist/cjs/parser/inlines/escape.js +25 -0
  32. package/dist/cjs/parser/inlines/footnote.js +41 -0
  33. package/dist/cjs/parser/inlines/hard-break.js +45 -0
  34. package/dist/cjs/parser/inlines/image.js +9 -0
  35. package/dist/cjs/parser/inlines/link.js +166 -0
  36. package/dist/cjs/parser/inlines/soft-break.js +18 -0
  37. package/dist/cjs/parser/inlines/strikethrough.js +48 -0
  38. package/dist/cjs/parser/inlines/text.js +20 -0
  39. package/dist/cjs/parser/renderer.js +345 -0
  40. package/dist/cjs/parser/types.js +5 -0
  41. package/dist/cjs/parser/utils.js +277 -0
  42. package/dist/cli.d.ts +6 -0
  43. package/dist/cli.js +36 -0
  44. package/dist/esm/cli.js +34 -0
  45. package/dist/esm/gfm.js +5 -0
  46. package/dist/esm/index.js +5 -0
  47. package/dist/esm/package.json +3 -0
  48. package/dist/esm/parser/block-parser.js +640 -0
  49. package/dist/esm/parser/blocks/blockquote.js +22 -0
  50. package/dist/esm/parser/blocks/code-block-fenced.js +52 -0
  51. package/dist/esm/parser/blocks/code-block-indented.js +42 -0
  52. package/dist/esm/parser/blocks/heading-atx.js +24 -0
  53. package/dist/esm/parser/blocks/heading-setext.js +19 -0
  54. package/dist/esm/parser/blocks/html-block.js +77 -0
  55. package/dist/esm/parser/blocks/link-reference.js +105 -0
  56. package/dist/esm/parser/blocks/list.js +145 -0
  57. package/dist/esm/parser/blocks/paragraph.js +15 -0
  58. package/dist/esm/parser/blocks/table.js +152 -0
  59. package/dist/esm/parser/blocks/task-list.js +61 -0
  60. package/dist/esm/parser/blocks/thematic-break.js +13 -0
  61. package/dist/esm/parser/entities.js +2130 -0
  62. package/dist/esm/parser/gfm/block-parser.js +769 -0
  63. package/dist/esm/parser/gfm/index.js +115 -0
  64. package/dist/esm/parser/gfm/inline-parser.js +809 -0
  65. package/dist/esm/parser/gfm/renderer.js +509 -0
  66. package/dist/esm/parser/index.js +80 -0
  67. package/dist/esm/parser/inline-parser.js +560 -0
  68. package/dist/esm/parser/inlines/autolink-extended.js +357 -0
  69. package/dist/esm/parser/inlines/autolink.js +40 -0
  70. package/dist/esm/parser/inlines/code-span.js +44 -0
  71. package/dist/esm/parser/inlines/emphasis.js +59 -0
  72. package/dist/esm/parser/inlines/entity.js +21 -0
  73. package/dist/esm/parser/inlines/escape.js +21 -0
  74. package/dist/esm/parser/inlines/footnote.js +38 -0
  75. package/dist/esm/parser/inlines/hard-break.js +41 -0
  76. package/dist/esm/parser/inlines/image.js +4 -0
  77. package/dist/esm/parser/inlines/link.js +156 -0
  78. package/dist/esm/parser/inlines/soft-break.js +14 -0
  79. package/dist/esm/parser/inlines/strikethrough.js +42 -0
  80. package/dist/esm/parser/inlines/text.js +16 -0
  81. package/dist/esm/parser/renderer.js +341 -0
  82. package/dist/esm/parser/types.js +4 -0
  83. package/dist/esm/parser/utils.js +254 -0
  84. package/dist/gfm.d.ts +6 -0
  85. package/dist/gfm.js +26 -0
  86. package/dist/index.d.ts +5 -0
  87. package/dist/index.js +26 -0
  88. package/dist/parser/block-parser.d.ts +25 -0
  89. package/dist/parser/block-parser.js +644 -0
  90. package/dist/parser/blocks/blockquote.d.ts +8 -0
  91. package/dist/parser/blocks/blockquote.js +28 -0
  92. package/dist/parser/blocks/code-block-fenced.d.ts +14 -0
  93. package/dist/parser/blocks/code-block-fenced.js +58 -0
  94. package/dist/parser/blocks/code-block-indented.d.ts +7 -0
  95. package/dist/parser/blocks/code-block-indented.js +47 -0
  96. package/dist/parser/blocks/heading-atx.d.ts +10 -0
  97. package/dist/parser/blocks/heading-atx.js +29 -0
  98. package/dist/parser/blocks/heading-setext.d.ts +8 -0
  99. package/dist/parser/blocks/heading-setext.js +24 -0
  100. package/dist/parser/blocks/html-block.d.ts +9 -0
  101. package/dist/parser/blocks/html-block.js +83 -0
  102. package/dist/parser/blocks/link-reference.d.ts +11 -0
  103. package/dist/parser/blocks/link-reference.js +109 -0
  104. package/dist/parser/blocks/list.d.ts +25 -0
  105. package/dist/parser/blocks/list.js +155 -0
  106. package/dist/parser/blocks/paragraph.d.ts +7 -0
  107. package/dist/parser/blocks/paragraph.js +20 -0
  108. package/dist/parser/blocks/table.d.ts +13 -0
  109. package/dist/parser/blocks/table.js +163 -0
  110. package/dist/parser/blocks/task-list.d.ts +10 -0
  111. package/dist/parser/blocks/task-list.js +66 -0
  112. package/dist/parser/blocks/thematic-break.d.ts +6 -0
  113. package/dist/parser/blocks/thematic-break.js +17 -0
  114. package/dist/parser/entities.d.ts +4 -0
  115. package/dist/parser/entities.js +2133 -0
  116. package/dist/parser/gfm/block-parser.d.ts +32 -0
  117. package/dist/parser/gfm/block-parser.js +773 -0
  118. package/dist/parser/gfm/index.d.ts +31 -0
  119. package/dist/parser/gfm/index.js +125 -0
  120. package/dist/parser/gfm/inline-parser.d.ts +25 -0
  121. package/dist/parser/gfm/inline-parser.js +813 -0
  122. package/dist/parser/gfm/renderer.d.ts +43 -0
  123. package/dist/parser/gfm/renderer.js +513 -0
  124. package/dist/parser/index.d.ts +33 -0
  125. package/dist/parser/index.js +104 -0
  126. package/dist/parser/inline-parser.d.ts +16 -0
  127. package/dist/parser/inline-parser.js +564 -0
  128. package/dist/parser/inlines/autolink-extended.d.ts +24 -0
  129. package/dist/parser/inlines/autolink-extended.js +364 -0
  130. package/dist/parser/inlines/autolink.d.ts +9 -0
  131. package/dist/parser/inlines/autolink.js +44 -0
  132. package/dist/parser/inlines/code-span.d.ts +9 -0
  133. package/dist/parser/inlines/code-span.js +48 -0
  134. package/dist/parser/inlines/emphasis.d.ts +14 -0
  135. package/dist/parser/inlines/emphasis.js +64 -0
  136. package/dist/parser/inlines/entity.d.ts +8 -0
  137. package/dist/parser/inlines/entity.js +25 -0
  138. package/dist/parser/inlines/escape.d.ts +8 -0
  139. package/dist/parser/inlines/escape.js +25 -0
  140. package/dist/parser/inlines/footnote.d.ts +9 -0
  141. package/dist/parser/inlines/footnote.js +41 -0
  142. package/dist/parser/inlines/hard-break.d.ts +9 -0
  143. package/dist/parser/inlines/hard-break.js +45 -0
  144. package/dist/parser/inlines/image.d.ts +4 -0
  145. package/dist/parser/inlines/image.js +9 -0
  146. package/dist/parser/inlines/link.d.ts +33 -0
  147. package/dist/parser/inlines/link.js +166 -0
  148. package/dist/parser/inlines/soft-break.d.ts +9 -0
  149. package/dist/parser/inlines/soft-break.js +18 -0
  150. package/dist/parser/inlines/strikethrough.d.ts +16 -0
  151. package/dist/parser/inlines/strikethrough.js +48 -0
  152. package/dist/parser/inlines/text.d.ts +6 -0
  153. package/dist/parser/inlines/text.js +20 -0
  154. package/dist/parser/renderer.d.ts +33 -0
  155. package/dist/parser/renderer.js +345 -0
  156. package/dist/parser/types.d.ts +152 -0
  157. package/dist/parser/types.js +5 -0
  158. package/dist/parser/utils.d.ts +32 -0
  159. package/dist/parser/utils.js +277 -0
  160. package/dist/types/cli.d.ts +6 -0
  161. package/dist/types/gfm.d.ts +6 -0
  162. package/dist/types/index.d.ts +5 -0
  163. package/dist/types/parser/block-parser.d.ts +25 -0
  164. package/dist/types/parser/blocks/blockquote.d.ts +8 -0
  165. package/dist/types/parser/blocks/code-block-fenced.d.ts +14 -0
  166. package/dist/types/parser/blocks/code-block-indented.d.ts +7 -0
  167. package/dist/types/parser/blocks/heading-atx.d.ts +10 -0
  168. package/dist/types/parser/blocks/heading-setext.d.ts +8 -0
  169. package/dist/types/parser/blocks/html-block.d.ts +9 -0
  170. package/dist/types/parser/blocks/link-reference.d.ts +11 -0
  171. package/dist/types/parser/blocks/list.d.ts +25 -0
  172. package/dist/types/parser/blocks/paragraph.d.ts +7 -0
  173. package/dist/types/parser/blocks/table.d.ts +13 -0
  174. package/dist/types/parser/blocks/task-list.d.ts +10 -0
  175. package/dist/types/parser/blocks/thematic-break.d.ts +6 -0
  176. package/dist/types/parser/entities.d.ts +4 -0
  177. package/dist/types/parser/gfm/block-parser.d.ts +32 -0
  178. package/dist/types/parser/gfm/index.d.ts +31 -0
  179. package/dist/types/parser/gfm/inline-parser.d.ts +25 -0
  180. package/dist/types/parser/gfm/renderer.d.ts +43 -0
  181. package/dist/types/parser/index.d.ts +33 -0
  182. package/dist/types/parser/inline-parser.d.ts +16 -0
  183. package/dist/types/parser/inlines/autolink-extended.d.ts +24 -0
  184. package/dist/types/parser/inlines/autolink.d.ts +9 -0
  185. package/dist/types/parser/inlines/code-span.d.ts +9 -0
  186. package/dist/types/parser/inlines/emphasis.d.ts +14 -0
  187. package/dist/types/parser/inlines/entity.d.ts +8 -0
  188. package/dist/types/parser/inlines/escape.d.ts +8 -0
  189. package/dist/types/parser/inlines/footnote.d.ts +9 -0
  190. package/dist/types/parser/inlines/hard-break.d.ts +9 -0
  191. package/dist/types/parser/inlines/image.d.ts +4 -0
  192. package/dist/types/parser/inlines/link.d.ts +33 -0
  193. package/dist/types/parser/inlines/soft-break.d.ts +9 -0
  194. package/dist/types/parser/inlines/strikethrough.d.ts +16 -0
  195. package/dist/types/parser/inlines/text.d.ts +6 -0
  196. package/dist/types/parser/renderer.d.ts +33 -0
  197. package/dist/types/parser/types.d.ts +152 -0
  198. package/dist/types/parser/utils.d.ts +32 -0
  199. package/package.json +54 -0
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Parser for inline links and images, including destinations, titles, and reference labels.
3
+ */
4
+ import { normalizeLabel, unescapeString, decodeHtmlEntities } from '../utils.js';
5
+ const LINK_DESTINATION_ANGLE_REGEX = /^<([^<>\n\\]|\\.)*>/;
6
+ const LINK_DESTINATION_BARE_REGEX = /^[^\s\x00-\x1f]*?(?:\([^\s\x00-\x1f]*?\)[^\s\x00-\x1f]*?)*[^\s\x00-\x1f)]/;
7
+ const LINK_TITLE_REGEX = /^(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'|\(([^()\\]*(?:\\.[^()\\]*)*)\))/;
8
+ export function parseLinkDestination(text) {
9
+ if (text[0] === '<') {
10
+ const match = text.match(LINK_DESTINATION_ANGLE_REGEX);
11
+ if (match) {
12
+ return {
13
+ destination: decodeHtmlEntities(unescapeString(match[0].slice(1, -1))),
14
+ length: match[0].length,
15
+ };
16
+ }
17
+ return null;
18
+ }
19
+ let parenDepth = 0;
20
+ let i = 0;
21
+ while (i < text.length) {
22
+ const char = text[i];
23
+ if (char === '\\' && i + 1 < text.length) {
24
+ const nextChar = text[i + 1];
25
+ if (nextChar === ' ' || nextChar === '\t' || nextChar === '\n' || nextChar === '\r') {
26
+ return null;
27
+ }
28
+ i += 2;
29
+ continue;
30
+ }
31
+ if (char === '(') {
32
+ parenDepth++;
33
+ }
34
+ else if (char === ')') {
35
+ if (parenDepth === 0)
36
+ break;
37
+ parenDepth--;
38
+ }
39
+ else if (char === ' ' || char === '\t' || char === '\n' || char === '\r' || char.charCodeAt(0) < 0x20) {
40
+ break;
41
+ }
42
+ i++;
43
+ }
44
+ if (i === 0)
45
+ return null;
46
+ return {
47
+ destination: decodeHtmlEntities(unescapeString(text.slice(0, i))),
48
+ length: i,
49
+ };
50
+ }
51
+ export function parseLinkTitle(text) {
52
+ const match = text.match(LINK_TITLE_REGEX);
53
+ if (!match)
54
+ return null;
55
+ const title = match[1] ?? match[2] ?? match[3] ?? '';
56
+ return {
57
+ title: decodeHtmlEntities(unescapeString(title)),
58
+ length: match[0].length,
59
+ };
60
+ }
61
+ export function parseInlineLink(text, pos) {
62
+ if (text[pos] !== '(')
63
+ return null;
64
+ let i = pos + 1;
65
+ while (i < text.length && /[ \t\n]/.test(text[i]))
66
+ i++;
67
+ if (text[i] === ')') {
68
+ return { destination: '', title: '', length: i - pos + 1 };
69
+ }
70
+ const destResult = parseLinkDestination(text.slice(i));
71
+ if (!destResult)
72
+ return null;
73
+ const destination = destResult.destination;
74
+ i += destResult.length;
75
+ while (i < text.length && /[ \t]/.test(text[i]))
76
+ i++;
77
+ let title = '';
78
+ if (text[i] === '\n') {
79
+ i++;
80
+ while (i < text.length && /[ \t]/.test(text[i]))
81
+ i++;
82
+ }
83
+ if (text[i] === '"' || text[i] === "'" || text[i] === '(') {
84
+ const titleResult = parseLinkTitle(text.slice(i));
85
+ if (titleResult) {
86
+ title = titleResult.title;
87
+ i += titleResult.length;
88
+ }
89
+ }
90
+ while (i < text.length && /[ \t\n]/.test(text[i]))
91
+ i++;
92
+ if (text[i] !== ')')
93
+ return null;
94
+ return { destination, title, length: i - pos + 1 };
95
+ }
96
+ export function parseLinkLabel(text, pos) {
97
+ if (text[pos] !== '[')
98
+ return null;
99
+ let i = pos + 1;
100
+ let depth = 1;
101
+ let label = '';
102
+ while (i < text.length && depth > 0) {
103
+ const char = text[i];
104
+ if (char === '\\' && i + 1 < text.length) {
105
+ const next = text[i + 1];
106
+ if (next === '[' || next === ']' || next === '\\') {
107
+ label += next;
108
+ i += 2;
109
+ continue;
110
+ }
111
+ label += '\\';
112
+ i++;
113
+ continue;
114
+ }
115
+ if (char === '[') {
116
+ return null;
117
+ }
118
+ if (char === ']') {
119
+ depth--;
120
+ if (depth === 0)
121
+ break;
122
+ }
123
+ label += char;
124
+ i++;
125
+ }
126
+ if (depth !== 0)
127
+ return null;
128
+ if (label.length > 999)
129
+ return null;
130
+ if (label.trim() === '') {
131
+ return { label: '', length: i - pos + 1 };
132
+ }
133
+ return { label: normalizeLabel(label), length: i - pos + 1 };
134
+ }
135
+ export function createLinkNode(destination, title) {
136
+ return {
137
+ type: 'link',
138
+ destination,
139
+ title,
140
+ children: [],
141
+ };
142
+ }
143
+ export function createImageNode(destination, title, alt) {
144
+ return {
145
+ type: 'image',
146
+ destination,
147
+ title,
148
+ alt,
149
+ };
150
+ }
151
+ export function isLinkStart(text, pos) {
152
+ return text[pos] === '[';
153
+ }
154
+ export function isImageStart(text, pos) {
155
+ return text[pos] === '!' && text[pos + 1] === '[';
156
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Parser for soft line breaks created by single newlines.
3
+ */
4
+ export function parseSoftBreak(text, pos) {
5
+ if (text[pos] !== '\n')
6
+ return null;
7
+ return {
8
+ node: { type: 'softbreak' },
9
+ length: 1,
10
+ };
11
+ }
12
+ export function isSoftBreak(text, pos) {
13
+ return text[pos] === '\n';
14
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Parser for GFM strikethrough using ~ delimiters.
3
+ */
4
+ export function parseStrikethroughDelimiter(text, pos) {
5
+ if (text[pos] !== '~')
6
+ return null;
7
+ let length = 0;
8
+ let i = pos;
9
+ while (i < text.length && text[i] === '~') {
10
+ length++;
11
+ i++;
12
+ }
13
+ if (length > 2)
14
+ return null;
15
+ const charBefore = pos > 0 ? text[pos - 1] : '\n';
16
+ const charAfter = i < text.length ? text[i] : '\n';
17
+ const isWhitespace = (c) => /\s/.test(c) || c === '\n';
18
+ const beforeIsWhitespace = isWhitespace(charBefore);
19
+ const afterIsWhitespace = isWhitespace(charAfter);
20
+ const canOpen = !afterIsWhitespace;
21
+ const canClose = !beforeIsWhitespace;
22
+ return {
23
+ char: '~',
24
+ length,
25
+ canOpen,
26
+ canClose,
27
+ position: pos,
28
+ origLength: length,
29
+ };
30
+ }
31
+ export function isStrikethroughDelimiter(text, pos) {
32
+ return text[pos] === '~';
33
+ }
34
+ export function createStrikethroughNode(children = []) {
35
+ return {
36
+ type: 'strikethrough',
37
+ children,
38
+ };
39
+ }
40
+ export function canStrikethroughDelimitersMatch(opener, closer) {
41
+ return opener.length === closer.length;
42
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Text node factories for literal inline content.
3
+ */
4
+ export function createTextNode(literal, noDelim = false) {
5
+ return {
6
+ type: 'text',
7
+ literal,
8
+ ...(noDelim ? { noDelim: true } : {}),
9
+ };
10
+ }
11
+ export function mergeTextNodes(nodes) {
12
+ return {
13
+ type: 'text',
14
+ literal: nodes.map(n => n.literal).join(''),
15
+ };
16
+ }
@@ -0,0 +1,341 @@
1
+ /**
2
+ * HTML renderer that converts a Markdown AST into HTML with configurable list, URL safety,
3
+ * and soft-break handling.
4
+ */
5
+ import { escapeHtml } from './utils.js';
6
+ function encodeUrl(url) {
7
+ let result = '';
8
+ let i = 0;
9
+ while (i < url.length) {
10
+ const char = url[i];
11
+ const code = char.charCodeAt(0);
12
+ if (char === '%' && i + 2 < url.length && /^[0-9a-fA-F]{2}$/.test(url.slice(i + 1, i + 3))) {
13
+ result += url.slice(i, i + 3).toUpperCase();
14
+ i += 3;
15
+ }
16
+ else if (/[A-Za-z0-9\-._~:/?#@!$&'()*+,;=]/.test(char)) {
17
+ result += char;
18
+ i++;
19
+ }
20
+ else if (code >= 0xD800 && code <= 0xDBFF && i + 1 < url.length) {
21
+ const fullChar = url.slice(i, i + 2);
22
+ const encoded = encodeURIComponent(fullChar);
23
+ result += encoded;
24
+ i += 2;
25
+ }
26
+ else {
27
+ const encoded = encodeURIComponent(char);
28
+ result += encoded;
29
+ i++;
30
+ }
31
+ }
32
+ return result;
33
+ }
34
+ export class HtmlRenderer {
35
+ options;
36
+ constructor(options = {}) {
37
+ this.options = {
38
+ softbreak: '\n',
39
+ safe: false,
40
+ ...options,
41
+ };
42
+ }
43
+ render(document) {
44
+ const content = this.renderBlocks(document.children);
45
+ return content ? content + '\n' : '';
46
+ }
47
+ renderBlocks(blocks, tight = false) {
48
+ return blocks.map(block => this.renderBlock(block, tight)).join('\n');
49
+ }
50
+ renderBlock(block, tight = false) {
51
+ switch (block.type) {
52
+ case 'paragraph':
53
+ return this.renderParagraph(block, tight);
54
+ case 'heading':
55
+ return this.renderHeading(block);
56
+ case 'thematic_break':
57
+ return this.renderThematicBreak(block);
58
+ case 'code_block':
59
+ return this.renderCodeBlock(block);
60
+ case 'blockquote':
61
+ return this.renderBlockquote(block);
62
+ case 'list':
63
+ return this.renderList(block);
64
+ case 'list_item':
65
+ return this.renderListItem(block);
66
+ case 'html_block':
67
+ return this.renderHtmlBlock(block);
68
+ default:
69
+ return '';
70
+ }
71
+ }
72
+ renderParagraph(node, tight = false) {
73
+ const content = this.renderInlines(node.children);
74
+ if (tight) {
75
+ return content;
76
+ }
77
+ return `<p>${content}</p>`;
78
+ }
79
+ renderHeading(node) {
80
+ const content = this.renderInlines(node.children);
81
+ return `<h${node.level}>${content}</h${node.level}>`;
82
+ }
83
+ renderThematicBreak(_node) {
84
+ return '<hr />';
85
+ }
86
+ renderCodeBlock(node) {
87
+ const escaped = escapeHtml(node.literal);
88
+ if (node.info) {
89
+ const lang = escapeHtml(node.info.split(/\s+/)[0]);
90
+ return `<pre><code class="language-${lang}">${escaped}</code></pre>`;
91
+ }
92
+ return `<pre><code>${escaped}</code></pre>`;
93
+ }
94
+ renderBlockquote(node) {
95
+ const content = this.renderBlocks(node.children);
96
+ if (content) {
97
+ return `<blockquote>\n${content}\n</blockquote>`;
98
+ }
99
+ return '<blockquote>\n</blockquote>';
100
+ }
101
+ renderList(node) {
102
+ const tag = node.listType === 'bullet' ? 'ul' : 'ol';
103
+ const startAttr = node.listType === 'ordered' && node.start !== 1 ? ` start="${node.start}"` : '';
104
+ const items = node.children.map(item => this.renderListItem(item, node.tight)).join('\n');
105
+ return `<${tag}${startAttr}>\n${items}\n</${tag}>`;
106
+ }
107
+ renderListItem(node, tight = false) {
108
+ if (node.children.length === 0) {
109
+ return '<li></li>';
110
+ }
111
+ const content = this.renderBlocks(node.children, tight);
112
+ if (tight && node.children.length === 1 && node.children[0].type === 'paragraph') {
113
+ return `<li>${content}</li>`;
114
+ }
115
+ if (content) {
116
+ return `<li>\n${content}\n</li>`;
117
+ }
118
+ return '<li></li>';
119
+ }
120
+ renderHtmlBlock(node) {
121
+ if (this.options.safe) {
122
+ return '<!-- raw HTML omitted -->';
123
+ }
124
+ return node.literal.replace(/\n$/, '');
125
+ }
126
+ renderInlines(inlines) {
127
+ if (this.options.smart) {
128
+ return this.renderInlinesSmart(inlines);
129
+ }
130
+ return inlines.map(inline => this.renderInline(inline)).join('');
131
+ }
132
+ renderInline(inline) {
133
+ switch (inline.type) {
134
+ case 'text':
135
+ return this.renderText(inline);
136
+ case 'softbreak':
137
+ return this.renderSoftbreak(inline);
138
+ case 'hardbreak':
139
+ return this.renderHardbreak(inline);
140
+ case 'code_span':
141
+ return this.renderCodeSpan(inline);
142
+ case 'emphasis':
143
+ return this.renderEmphasis(inline);
144
+ case 'strong':
145
+ return this.renderStrong(inline);
146
+ case 'link':
147
+ return this.renderLink(inline);
148
+ case 'image':
149
+ return this.renderImage(inline);
150
+ case 'html_inline':
151
+ return this.renderHtmlInline(inline);
152
+ default:
153
+ return '';
154
+ }
155
+ }
156
+ renderText(node) {
157
+ return escapeHtml(node.literal);
158
+ }
159
+ renderSoftbreak(_node) {
160
+ return this.options.softbreak;
161
+ }
162
+ renderHardbreak(_node) {
163
+ return '<br />\n';
164
+ }
165
+ renderCodeSpan(node) {
166
+ return `<code>${escapeHtml(node.literal)}</code>`;
167
+ }
168
+ renderEmphasis(node) {
169
+ const content = this.renderInlines(node.children);
170
+ return `<em>${content}</em>`;
171
+ }
172
+ renderStrong(node) {
173
+ const content = this.renderInlines(node.children);
174
+ return `<strong>${content}</strong>`;
175
+ }
176
+ renderLink(node) {
177
+ if (this.options.safe && /^javascript:/i.test(node.destination)) {
178
+ return this.renderInlines(node.children);
179
+ }
180
+ const href = escapeHtml(encodeUrl(node.destination));
181
+ const title = node.title ? ` title="${escapeHtml(node.title)}"` : '';
182
+ const content = this.renderInlines(node.children);
183
+ return `<a href="${href}"${title}>${content}</a>`;
184
+ }
185
+ renderImage(node) {
186
+ if (this.options.safe && /^javascript:/i.test(node.destination)) {
187
+ return escapeHtml(node.alt);
188
+ }
189
+ const src = escapeHtml(encodeUrl(node.destination));
190
+ const alt = escapeHtml(node.alt);
191
+ const title = node.title ? ` title="${escapeHtml(node.title)}"` : '';
192
+ return `<img src="${src}" alt="${alt}"${title} />`;
193
+ }
194
+ renderHtmlInline(node) {
195
+ if (this.options.safe) {
196
+ return '<!-- raw HTML omitted -->';
197
+ }
198
+ return node.literal;
199
+ }
200
+ renderInlinesSmart(inlines) {
201
+ const tokens = [];
202
+ const nodeTokens = new Map();
203
+ const pushTextTokens = (node) => {
204
+ const indices = [];
205
+ for (let i = 0; i < node.literal.length; i++) {
206
+ const idx = tokens.length;
207
+ tokens.push({
208
+ char: node.literal[i],
209
+ node,
210
+ nodeOffset: i,
211
+ noSmart: !!node.noSmart,
212
+ });
213
+ indices.push(idx);
214
+ }
215
+ nodeTokens.set(node, indices);
216
+ };
217
+ for (const inline of inlines) {
218
+ if (inline.type === 'text') {
219
+ pushTextTokens(inline);
220
+ }
221
+ else if (inline.type === 'softbreak' || inline.type === 'hardbreak') {
222
+ tokens.push({ char: '\n', node: null, nodeOffset: 0, noSmart: false });
223
+ }
224
+ else {
225
+ tokens.push({ char: 'A', node: null, nodeOffset: 0, noSmart: false });
226
+ }
227
+ }
228
+ const replacements = new Map();
229
+ const doubleStack = [];
230
+ const singleStack = [];
231
+ const isWhitespace = (c) => /\s/.test(c);
232
+ const isPunctuation = (c) => /[\p{P}\p{S}]/u.test(c);
233
+ const isAlphaNum = (c) => /[A-Za-z0-9]/.test(c);
234
+ for (let i = 0; i < tokens.length; i++) {
235
+ const token = tokens[i];
236
+ if (token.noSmart)
237
+ continue;
238
+ if (token.char !== '"' && token.char !== "'")
239
+ continue;
240
+ const before = i > 0 ? tokens[i - 1].char : '\n';
241
+ const after = i + 1 < tokens.length ? tokens[i + 1].char : '\n';
242
+ const beforeIsWhitespace = isWhitespace(before);
243
+ const afterIsWhitespace = isWhitespace(after);
244
+ const beforeIsPunct = isPunctuation(before);
245
+ const afterIsPunct = isPunctuation(after);
246
+ let leftFlanking = !afterIsWhitespace &&
247
+ (!afterIsPunct || beforeIsWhitespace || beforeIsPunct);
248
+ let rightFlanking = !beforeIsWhitespace &&
249
+ (!beforeIsPunct || afterIsWhitespace || afterIsPunct);
250
+ if (before === ')' || before === ']') {
251
+ leftFlanking = false;
252
+ }
253
+ if (token.char === "'") {
254
+ if (isAlphaNum(before) && isAlphaNum(after)) {
255
+ replacements.set(i, '’');
256
+ continue;
257
+ }
258
+ if ((before === ')' || before === ']') && isAlphaNum(after)) {
259
+ replacements.set(i, '’');
260
+ continue;
261
+ }
262
+ if (rightFlanking && singleStack.length > 0) {
263
+ const opener = singleStack.pop();
264
+ replacements.set(opener, '‘');
265
+ replacements.set(i, '’');
266
+ continue;
267
+ }
268
+ if (leftFlanking) {
269
+ singleStack.push(i);
270
+ continue;
271
+ }
272
+ replacements.set(i, '’');
273
+ }
274
+ else {
275
+ if (rightFlanking && doubleStack.length > 0) {
276
+ const opener = doubleStack.pop();
277
+ replacements.set(opener, '“');
278
+ replacements.set(i, '”');
279
+ continue;
280
+ }
281
+ if (leftFlanking) {
282
+ doubleStack.push(i);
283
+ continue;
284
+ }
285
+ replacements.set(i, '“');
286
+ }
287
+ }
288
+ for (const idx of doubleStack) {
289
+ replacements.set(idx, '“');
290
+ }
291
+ for (const idx of singleStack) {
292
+ replacements.set(idx, '’');
293
+ }
294
+ const renderTextNodes = new Map();
295
+ for (const [node, indices] of nodeTokens.entries()) {
296
+ if (node.noSmart) {
297
+ renderTextNodes.set(node, escapeHtml(node.literal));
298
+ continue;
299
+ }
300
+ let result = '';
301
+ for (let i = 0; i < indices.length; i++) {
302
+ const tokenIndex = indices[i];
303
+ const token = tokens[tokenIndex];
304
+ const replacement = replacements.get(tokenIndex);
305
+ result += replacement ?? token.char;
306
+ }
307
+ result = this.applyDashesAndEllipses(result);
308
+ renderTextNodes.set(node, escapeHtml(result));
309
+ }
310
+ return inlines.map(inline => {
311
+ if (inline.type === 'text') {
312
+ return renderTextNodes.get(inline) ?? '';
313
+ }
314
+ return this.renderInline(inline);
315
+ }).join('');
316
+ }
317
+ applyDashesAndEllipses(text) {
318
+ let result = text.replace(/\.{3}/g, '…');
319
+ result = result.replace(/-{2,}/g, (match) => {
320
+ const count = match.length;
321
+ if (count % 3 === 0) {
322
+ return '—'.repeat(count / 3);
323
+ }
324
+ if (count % 2 === 0) {
325
+ return '–'.repeat(count / 2);
326
+ }
327
+ let emCount = Math.floor(count / 3);
328
+ const remainder = count % 3;
329
+ let enCount = 0;
330
+ if (remainder === 1) {
331
+ emCount = Math.max(0, emCount - 1);
332
+ enCount = 2;
333
+ }
334
+ else if (remainder === 2) {
335
+ enCount = 1;
336
+ }
337
+ return '—'.repeat(emCount) + '–'.repeat(enCount);
338
+ });
339
+ return result;
340
+ }
341
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Type definitions for the Markdown AST nodes and parser/render options.
3
+ */
4
+ export {};