@intlayer/core 9.4.4 → 9.5.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.
Files changed (171) hide show
  1. package/README.md +0 -2
  2. package/dist/cjs/dictionaryManipulator/qualifiedDictionary.cjs.map +1 -1
  3. package/dist/cjs/index.cjs +5 -8
  4. package/dist/cjs/interpreter/getPlural.cjs.map +1 -1
  5. package/dist/cjs/localization/getPrefix.cjs +6 -6
  6. package/dist/cjs/localization/getPrefix.cjs.map +1 -1
  7. package/dist/cjs/markdown/compiler.cjs +437 -209
  8. package/dist/cjs/markdown/compiler.cjs.map +1 -1
  9. package/dist/cjs/markdown/constants.cjs +32 -50
  10. package/dist/cjs/markdown/constants.cjs.map +1 -1
  11. package/dist/cjs/markdown/elementScanner.cjs +191 -0
  12. package/dist/cjs/markdown/elementScanner.cjs.map +1 -0
  13. package/dist/cjs/markdown/index.cjs +5 -8
  14. package/dist/cjs/markdown/parser.cjs +90 -48
  15. package/dist/cjs/markdown/parser.cjs.map +1 -1
  16. package/dist/cjs/markdown/renderer.cjs +14 -11
  17. package/dist/cjs/markdown/renderer.cjs.map +1 -1
  18. package/dist/cjs/markdown/utils.cjs +123 -34
  19. package/dist/cjs/markdown/utils.cjs.map +1 -1
  20. package/dist/cjs/utils/getCookie.cjs +1 -1
  21. package/dist/cjs/utils/getCookie.cjs.map +1 -1
  22. package/dist/esm/dictionaryManipulator/qualifiedDictionary.mjs.map +1 -1
  23. package/dist/esm/index.mjs +3 -3
  24. package/dist/esm/interpreter/getPlural.mjs.map +1 -1
  25. package/dist/esm/localization/getPrefix.mjs +6 -6
  26. package/dist/esm/localization/getPrefix.mjs.map +1 -1
  27. package/dist/esm/markdown/compiler.mjs +438 -211
  28. package/dist/esm/markdown/compiler.mjs.map +1 -1
  29. package/dist/esm/markdown/constants.mjs +29 -43
  30. package/dist/esm/markdown/constants.mjs.map +1 -1
  31. package/dist/esm/markdown/elementScanner.mjs +183 -0
  32. package/dist/esm/markdown/elementScanner.mjs.map +1 -0
  33. package/dist/esm/markdown/index.mjs +3 -3
  34. package/dist/esm/markdown/parser.mjs +90 -48
  35. package/dist/esm/markdown/parser.mjs.map +1 -1
  36. package/dist/esm/markdown/renderer.mjs +14 -11
  37. package/dist/esm/markdown/renderer.mjs.map +1 -1
  38. package/dist/esm/markdown/utils.mjs +124 -35
  39. package/dist/esm/markdown/utils.mjs.map +1 -1
  40. package/dist/esm/utils/getCookie.mjs +1 -1
  41. package/dist/esm/utils/getCookie.mjs.map +1 -1
  42. package/dist/types/dictionaryManipulator/qualifiedDictionary.d.ts +3 -3
  43. package/dist/types/dictionaryManipulator/qualifiedDictionary.d.ts.map +1 -1
  44. package/dist/types/index.d.ts +4 -4
  45. package/dist/types/interpreter/getPlural.d.ts +3 -3
  46. package/dist/types/interpreter/getPlural.d.ts.map +1 -1
  47. package/dist/types/localization/getPrefix.d.ts.map +1 -1
  48. package/dist/types/markdown/compiler.d.ts +43 -0
  49. package/dist/types/markdown/compiler.d.ts.map +1 -1
  50. package/dist/types/markdown/constants.d.ts +26 -17
  51. package/dist/types/markdown/constants.d.ts.map +1 -1
  52. package/dist/types/markdown/elementScanner.d.ts +14 -0
  53. package/dist/types/markdown/elementScanner.d.ts.map +1 -0
  54. package/dist/types/markdown/index.d.ts +4 -4
  55. package/dist/types/markdown/parser.d.ts +0 -5
  56. package/dist/types/markdown/parser.d.ts.map +1 -1
  57. package/dist/types/markdown/renderer.d.ts +0 -4
  58. package/dist/types/markdown/renderer.d.ts.map +1 -1
  59. package/dist/types/markdown/types.d.ts +13 -0
  60. package/dist/types/markdown/types.d.ts.map +1 -1
  61. package/dist/types/markdown/utils.d.ts +7 -2
  62. package/dist/types/markdown/utils.d.ts.map +1 -1
  63. package/package.json +7 -7
  64. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/dictionaryManipulator/qualifiedDynamicLoader.d.ts +0 -1
  65. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/getDictionaryAsync.d.ts +0 -1
  66. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/getEnumeration.d.ts +0 -1
  67. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/getGender.d.ts +0 -1
  68. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/getInsertion.d.ts +0 -1
  69. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/getPlural.d.ts +0 -1
  70. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/getSelect.d.ts +0 -1
  71. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/index.d.ts +0 -7
  72. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/interpolableNode.d.ts +0 -1
  73. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/transpiler/index.d.ts +0 -2
  74. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/transpiler/nesting/index.d.ts +0 -1
  75. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/transpiler/nesting/nesting.d.ts +0 -1
  76. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/dictionaryManipulator/qualifiedDynamicLoader.d.ts +0 -1
  77. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/getDictionaryAsync.d.ts +0 -1
  78. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/getEnumeration.d.ts +0 -1
  79. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/getGender.d.ts +0 -1
  80. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/getInsertion.d.ts +0 -1
  81. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/getPlural.d.ts +0 -1
  82. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/getSelect.d.ts +0 -1
  83. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/index.d.ts +0 -7
  84. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/interpreter/interpolableNode.d.ts +0 -1
  85. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/intlayer/dist/types/index.d.ts +0 -6
  86. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/transpiler/index.d.ts +0 -2
  87. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/transpiler/nesting/index.d.ts +0 -1
  88. package/dist/types/@intlayer/core/dist/types/@intlayer/core/dist/types/transpiler/nesting/nesting.d.ts +0 -1
  89. package/dist/types/@intlayer/core/dist/types/dictionaryManipulator/plainDynamicLoader.d.ts +0 -2
  90. package/dist/types/@intlayer/core/dist/types/dictionaryManipulator/qualifiedDynamicLoader.d.ts +0 -2
  91. package/dist/types/@intlayer/core/dist/types/formatters/compact.d.ts +0 -1
  92. package/dist/types/@intlayer/core/dist/types/formatters/currency.d.ts +0 -1
  93. package/dist/types/@intlayer/core/dist/types/formatters/date.d.ts +0 -1
  94. package/dist/types/@intlayer/core/dist/types/formatters/index.d.ts +0 -9
  95. package/dist/types/@intlayer/core/dist/types/formatters/list.d.ts +0 -1
  96. package/dist/types/@intlayer/core/dist/types/formatters/number.d.ts +0 -1
  97. package/dist/types/@intlayer/core/dist/types/formatters/percentage.d.ts +0 -1
  98. package/dist/types/@intlayer/core/dist/types/formatters/relativeTime.d.ts +0 -1
  99. package/dist/types/@intlayer/core/dist/types/formatters/units.d.ts +0 -1
  100. package/dist/types/@intlayer/core/dist/types/interpreter/dictionaryTransformCache.d.ts +0 -4
  101. package/dist/types/@intlayer/core/dist/types/interpreter/getCondition.d.ts +0 -2
  102. package/dist/types/@intlayer/core/dist/types/interpreter/getContent/deepTransform.d.ts +0 -1
  103. package/dist/types/@intlayer/core/dist/types/interpreter/getContent/getContent.d.ts +0 -3
  104. package/dist/types/@intlayer/core/dist/types/interpreter/getContent/index.d.ts +0 -3
  105. package/dist/types/@intlayer/core/dist/types/interpreter/getContent/plugins.d.ts +0 -7
  106. package/dist/types/@intlayer/core/dist/types/interpreter/getDictionary.d.ts +0 -4
  107. package/dist/types/@intlayer/core/dist/types/interpreter/getDictionaryAsync.d.ts +0 -6
  108. package/dist/types/@intlayer/core/dist/types/interpreter/getEnumeration.d.ts +0 -2
  109. package/dist/types/@intlayer/core/dist/types/interpreter/getGender.d.ts +0 -2
  110. package/dist/types/@intlayer/core/dist/types/interpreter/getInsertion.d.ts +0 -2
  111. package/dist/types/@intlayer/core/dist/types/interpreter/getIntlayer.d.ts +0 -4
  112. package/dist/types/@intlayer/core/dist/types/interpreter/getIntlayerAsync.d.ts +0 -4
  113. package/dist/types/@intlayer/core/dist/types/interpreter/getNesting.d.ts +0 -6
  114. package/dist/types/@intlayer/core/dist/types/interpreter/getPlural.d.ts +0 -3
  115. package/dist/types/@intlayer/core/dist/types/interpreter/getSelect.d.ts +0 -2
  116. package/dist/types/@intlayer/core/dist/types/interpreter/getTranslation.d.ts +0 -1
  117. package/dist/types/@intlayer/core/dist/types/interpreter/index.d.ts +0 -18
  118. package/dist/types/@intlayer/core/dist/types/interpreter/interpolableNode.d.ts +0 -2
  119. package/dist/types/@intlayer/core/dist/types/intlayer/dist/types/index.d.ts +0 -6
  120. package/dist/types/@intlayer/core/dist/types/localization/comparePaths.d.ts +0 -1
  121. package/dist/types/@intlayer/core/dist/types/localization/domainUtils.d.ts +0 -2
  122. package/dist/types/@intlayer/core/dist/types/localization/generateSitemap.d.ts +0 -2
  123. package/dist/types/@intlayer/core/dist/types/localization/getBrowserLocale.d.ts +0 -1
  124. package/dist/types/@intlayer/core/dist/types/localization/getCanonicalPath.d.ts +0 -2
  125. package/dist/types/@intlayer/core/dist/types/localization/getHTMLTextDir.d.ts +0 -1
  126. package/dist/types/@intlayer/core/dist/types/localization/getLocale.d.ts +0 -1
  127. package/dist/types/@intlayer/core/dist/types/localization/getLocaleFromPath.d.ts +0 -2
  128. package/dist/types/@intlayer/core/dist/types/localization/getLocaleLang.d.ts +0 -1
  129. package/dist/types/@intlayer/core/dist/types/localization/getLocaleName.d.ts +0 -1
  130. package/dist/types/@intlayer/core/dist/types/localization/getLocalizedPath.d.ts +0 -2
  131. package/dist/types/@intlayer/core/dist/types/localization/getLocalizedUrl.d.ts +0 -2
  132. package/dist/types/@intlayer/core/dist/types/localization/getMultilingualUrls.d.ts +0 -2
  133. package/dist/types/@intlayer/core/dist/types/localization/getPathWithoutLocale.d.ts +0 -1
  134. package/dist/types/@intlayer/core/dist/types/localization/getPrefix.d.ts +0 -2
  135. package/dist/types/@intlayer/core/dist/types/localization/index.d.ts +0 -22
  136. package/dist/types/@intlayer/core/dist/types/localization/localeDetector.d.ts +0 -2
  137. package/dist/types/@intlayer/core/dist/types/localization/localeMapper.d.ts +0 -1
  138. package/dist/types/@intlayer/core/dist/types/localization/localeResolver.d.ts +0 -1
  139. package/dist/types/@intlayer/core/dist/types/localization/resolveBrowserLocale.d.ts +0 -2
  140. package/dist/types/@intlayer/core/dist/types/localization/rewriteMatch.d.ts +0 -1
  141. package/dist/types/@intlayer/core/dist/types/localization/rewriteUtils.d.ts +0 -2
  142. package/dist/types/@intlayer/core/dist/types/localization/validatePrefix.d.ts +0 -2
  143. package/dist/types/@intlayer/core/dist/types/markdown/index.d.ts +0 -2
  144. package/dist/types/@intlayer/core/dist/types/transpiler/condition/condition.d.ts +0 -1
  145. package/dist/types/@intlayer/core/dist/types/transpiler/condition/index.d.ts +0 -1
  146. package/dist/types/@intlayer/core/dist/types/transpiler/enumeration/enumeration.d.ts +0 -1
  147. package/dist/types/@intlayer/core/dist/types/transpiler/enumeration/index.d.ts +0 -1
  148. package/dist/types/@intlayer/core/dist/types/transpiler/file/file.d.ts +0 -1
  149. package/dist/types/@intlayer/core/dist/types/transpiler/file/index.d.ts +0 -1
  150. package/dist/types/@intlayer/core/dist/types/transpiler/gender/gender.d.ts +0 -1
  151. package/dist/types/@intlayer/core/dist/types/transpiler/gender/index.d.ts +0 -1
  152. package/dist/types/@intlayer/core/dist/types/transpiler/html/html.d.ts +0 -1
  153. package/dist/types/@intlayer/core/dist/types/transpiler/html/index.d.ts +0 -1
  154. package/dist/types/@intlayer/core/dist/types/transpiler/index.d.ts +0 -22
  155. package/dist/types/@intlayer/core/dist/types/transpiler/insertion/index.d.ts +0 -1
  156. package/dist/types/@intlayer/core/dist/types/transpiler/insertion/insertion.d.ts +0 -1
  157. package/dist/types/@intlayer/core/dist/types/transpiler/markdown/index.d.ts +0 -1
  158. package/dist/types/@intlayer/core/dist/types/transpiler/markdown/markdown.d.ts +0 -1
  159. package/dist/types/@intlayer/core/dist/types/transpiler/nesting/index.d.ts +0 -1
  160. package/dist/types/@intlayer/core/dist/types/transpiler/nesting/nesting.d.ts +0 -4
  161. package/dist/types/@intlayer/core/dist/types/transpiler/plural/index.d.ts +0 -1
  162. package/dist/types/@intlayer/core/dist/types/transpiler/plural/plural.d.ts +0 -1
  163. package/dist/types/@intlayer/core/dist/types/transpiler/select/index.d.ts +0 -1
  164. package/dist/types/@intlayer/core/dist/types/transpiler/select/select.d.ts +0 -1
  165. package/dist/types/@intlayer/core/dist/types/transpiler/translation/index.d.ts +0 -1
  166. package/dist/types/@intlayer/core/dist/types/transpiler/translation/translation.d.ts +0 -2
  167. package/dist/types/@intlayer/core/dist/types/utils/index.d.ts +0 -3
  168. package/dist/types/@intlayer/core/dist/types/utils/intl.d.ts +0 -1
  169. package/dist/types/@intlayer/core/dist/types/utils/isSameKeyPath.d.ts +0 -1
  170. package/dist/types/@intlayer/core/dist/types/utils/localeStorage.d.ts +0 -2
  171. package/dist/types/intlayer/dist/types/index.d.ts +0 -14
@@ -0,0 +1,183 @@
1
+ //#region src/markdown/elementScanner.ts
2
+ /**
3
+ * Lean, non-backtracking element scanner for HTML blocks, custom components, and self-closing tags.
4
+ */
5
+ const findTagEnd = (source, from) => {
6
+ const len = source.length;
7
+ let pos = from;
8
+ while (pos < len) {
9
+ const code = source.charCodeAt(pos);
10
+ if (code === 62) return pos;
11
+ if (code === 34 || code === 39) {
12
+ let cur = pos + 1;
13
+ while (cur < len && source.charCodeAt(cur) !== code && source.charCodeAt(cur) !== 10) cur++;
14
+ pos = source.charCodeAt(cur) === code ? cur + 1 : pos + 1;
15
+ continue;
16
+ }
17
+ pos++;
18
+ }
19
+ return -1;
20
+ };
21
+ /** Fold an ASCII upper-case letter to lower case, leaving anything else alone. */
22
+ const foldAscii = (code) => code >= 65 && code <= 90 ? code + 32 : code;
23
+ /**
24
+ * Compare `needle` against `source` at `at`, ignoring ASCII case.
25
+ *
26
+ * HTML tag names are case-insensitive, and the scanner asks this question once
27
+ * per `<` it walks past — so it must not allocate. Slicing the candidate out and
28
+ * lower-casing it built two strings per question, which is what made the tag
29
+ * walk the most expensive frame in an element-heavy document.
30
+ */
31
+ const matchesFolded = (source, needle, at) => {
32
+ for (let index = 0; index < needle.length; index++) if (foldAscii(source.charCodeAt(at + index)) !== foldAscii(needle.charCodeAt(index))) return false;
33
+ return true;
34
+ };
35
+ /**
36
+ * Index of the next `needle` tag at or after `from`, or -1 when there is none
37
+ * starting before `limit`.
38
+ *
39
+ * `limit` is what keeps the nesting walk linear: an opening tag only matters
40
+ * when it precedes the closing one, so searching past the closing tag walks the
41
+ * rest of the document — once per element — for an answer that is thrown away.
42
+ */
43
+ const findTag = (source, needle, from, caseInsensitive, limit) => {
44
+ if (!caseInsensitive) {
45
+ const idx = source.indexOf(needle, from);
46
+ return idx === -1 || idx >= limit ? -1 : idx;
47
+ }
48
+ const max = Math.min(limit, source.length - needle.length + 1);
49
+ let pos = from;
50
+ while (pos < max) {
51
+ const idx = source.indexOf("<", pos);
52
+ if (idx === -1 || idx >= max) return -1;
53
+ if (matchesFolded(source, needle, idx)) return idx;
54
+ pos = idx + 1;
55
+ }
56
+ return -1;
57
+ };
58
+ const makeMatch = (source, end, tagName, attrs, content) => {
59
+ const result = [
60
+ source.slice(0, end),
61
+ tagName,
62
+ attrs,
63
+ content
64
+ ];
65
+ result.index = 0;
66
+ result.input = source;
67
+ return result;
68
+ };
69
+ /** ASCII letter or digit — the characters a tag name is built from. */
70
+ const isNameChar = (code) => code >= 97 && code <= 122 || code >= 65 && code <= 90 || code >= 48 && code <= 57;
71
+ const skipLeadingSpaces = (source) => {
72
+ let i = 0;
73
+ while (source.charCodeAt(i) === 32) i++;
74
+ return source.charCodeAt(i) === 60 ? i : -1;
75
+ };
76
+ const matchElement = (source, customComponent) => {
77
+ const bracket = skipLeadingSpaces(source);
78
+ if (bracket === -1) return null;
79
+ const nameStart = bracket + 1;
80
+ let nameEnd = nameStart;
81
+ const first = source.charCodeAt(nameStart);
82
+ if (customComponent) {
83
+ if (first < 65 || first > 90) return null;
84
+ while (nameEnd < source.length && isNameChar(source.charCodeAt(nameEnd))) nameEnd++;
85
+ } else {
86
+ if (!(first >= 65 && first <= 90 || first >= 97 && first <= 122)) return null;
87
+ while (nameEnd < source.length) {
88
+ const c = source.charCodeAt(nameEnd);
89
+ if (c === 32 || c === 9 || c === 10 || c === 13 || c === 62 || c === 47) break;
90
+ nameEnd++;
91
+ }
92
+ }
93
+ const tagName = source.slice(nameStart, nameEnd);
94
+ const openTagEnd = findTagEnd(source, nameEnd);
95
+ if (openTagEnd === -1) return null;
96
+ if (!customComponent && source.charCodeAt(openTagEnd - 1) === 47) return null;
97
+ let contentStart = openTagEnd + 1;
98
+ if (source.charCodeAt(contentStart) === 10) contentStart++;
99
+ const openTag = `<${tagName}`;
100
+ const closeTag = `</${tagName}`;
101
+ let depth = 1;
102
+ let cursor = contentStart;
103
+ const len = source.length;
104
+ let contentEnd = -1;
105
+ let end = -1;
106
+ while (cursor < len) {
107
+ const closeIdx = findTag(source, closeTag, cursor, !customComponent, len);
108
+ if (closeIdx === -1) return null;
109
+ const openIdx = findTag(source, openTag, cursor, !customComponent, closeIdx);
110
+ if (openIdx !== -1) {
111
+ const next = source.charCodeAt(openIdx + openTag.length);
112
+ if (next === 32 || next === 9 || next === 10 || next === 13 || next === 62 || next === 47) {
113
+ const nestedEnd = findTagEnd(source, openIdx + openTag.length);
114
+ if (nestedEnd === -1) return null;
115
+ depth++;
116
+ cursor = nestedEnd + 1;
117
+ } else cursor = openIdx + 1;
118
+ continue;
119
+ }
120
+ if (source.charCodeAt(closeIdx + closeTag.length) !== 62) {
121
+ cursor = closeIdx + 1;
122
+ continue;
123
+ }
124
+ const closeTagEnd = closeIdx + closeTag.length + 1;
125
+ depth--;
126
+ if (depth > 0) {
127
+ cursor = closeTagEnd;
128
+ continue;
129
+ }
130
+ if (findTag(source, closeTag, closeTagEnd, !customComponent, len) === closeTagEnd && source.charCodeAt(closeTagEnd + closeTag.length) === 62) {
131
+ depth = 1;
132
+ cursor = closeTagEnd;
133
+ continue;
134
+ }
135
+ contentEnd = closeIdx;
136
+ end = closeTagEnd;
137
+ break;
138
+ }
139
+ if (end === -1) return null;
140
+ while (source.charCodeAt(end) === 10) end++;
141
+ return makeMatch(source, end, tagName, source.slice(nameEnd, openTagEnd), source.slice(contentStart, contentEnd));
142
+ };
143
+ const matchSelfClosingElement = (source) => {
144
+ const bracket = skipLeadingSpaces(source);
145
+ if (bracket === -1) return null;
146
+ const nameStart = bracket + 1;
147
+ const first = source.charCodeAt(nameStart);
148
+ if (!(first >= 65 && first <= 90 || first >= 97 && first <= 122)) return null;
149
+ let nameEnd = nameStart + 1;
150
+ while (nameEnd < source.length && (isNameChar(source.charCodeAt(nameEnd)) || source.charCodeAt(nameEnd) === 58)) nameEnd++;
151
+ const c = source.charCodeAt(nameEnd);
152
+ const hasAttrs = c === 32 || c === 9 || c === 10 || c === 13;
153
+ let tagEnd;
154
+ if (hasAttrs) {
155
+ tagEnd = findTagEnd(source, nameEnd);
156
+ if (tagEnd === -1) return null;
157
+ } else {
158
+ tagEnd = source.charCodeAt(nameEnd) === 47 ? nameEnd + 1 : nameEnd;
159
+ if (source.charCodeAt(tagEnd) !== 62) return null;
160
+ }
161
+ const tagName = source.slice(nameStart, nameEnd);
162
+ const tailStart = tagEnd + 1;
163
+ if (source.charCodeAt(tailStart) === 60 && source.charCodeAt(tailStart + 1) === 47 && matchesFolded(source, tagName, tailStart + 2) && source.charCodeAt(tailStart + 2 + tagName.length) === 62) return null;
164
+ let end = tailStart;
165
+ let scan = tailStart;
166
+ while (scan < source.length) {
167
+ const sc = source.charCodeAt(scan);
168
+ if (sc !== 32 && sc !== 9 && sc !== 10 && sc !== 13) break;
169
+ scan++;
170
+ if (source.charCodeAt(scan - 1) === 10) end = scan;
171
+ }
172
+ return makeMatch(source, end, tagName, hasAttrs ? source.slice(nameEnd + 1, tagEnd) : void 0, end > tailStart ? source.slice(tailStart, end) : void 0);
173
+ };
174
+ const matchHtmlBlockElement = (source) => matchElement(source, false);
175
+ const matchCustomComponent = (source) => matchElement(source, true);
176
+ const startsWithHtmlBlockElement = (source) => matchHtmlBlockElement(source) !== null;
177
+ const startsWithSelfClosingElement = (source) => matchSelfClosingElement(source) !== null;
178
+ const startsWithCustomComponent = (source) => matchCustomComponent(source) !== null;
179
+ const startsWithElement = (source) => startsWithHtmlBlockElement(source) || startsWithSelfClosingElement(source);
180
+
181
+ //#endregion
182
+ export { matchCustomComponent, matchElement, matchHtmlBlockElement, matchSelfClosingElement, startsWithCustomComponent, startsWithElement, startsWithHtmlBlockElement, startsWithSelfClosingElement };
183
+ //# sourceMappingURL=elementScanner.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elementScanner.mjs","names":[],"sources":["../../../src/markdown/elementScanner.ts"],"sourcesContent":["/**\n * Lean, non-backtracking element scanner for HTML blocks, custom components, and self-closing tags.\n */\n\nconst findTagEnd = (source: string, from: number): number => {\n const len = source.length;\n let pos = from;\n while (pos < len) {\n const code = source.charCodeAt(pos);\n if (code === 62) return pos; // '>'\n if (code === 34 || code === 39) {\n // '\"' or \"'\"\n let cur = pos + 1;\n while (\n cur < len &&\n source.charCodeAt(cur) !== code &&\n source.charCodeAt(cur) !== 10\n )\n cur++;\n pos = source.charCodeAt(cur) === code ? cur + 1 : pos + 1;\n continue;\n }\n pos++;\n }\n return -1;\n};\n\n/** Fold an ASCII upper-case letter to lower case, leaving anything else alone. */\nconst foldAscii = (code: number): number =>\n code >= 65 && code <= 90 ? code + 32 : code;\n\n/**\n * Compare `needle` against `source` at `at`, ignoring ASCII case.\n *\n * HTML tag names are case-insensitive, and the scanner asks this question once\n * per `<` it walks past — so it must not allocate. Slicing the candidate out and\n * lower-casing it built two strings per question, which is what made the tag\n * walk the most expensive frame in an element-heavy document.\n */\nconst matchesFolded = (source: string, needle: string, at: number): boolean => {\n for (let index = 0; index < needle.length; index++) {\n if (\n foldAscii(source.charCodeAt(at + index)) !==\n foldAscii(needle.charCodeAt(index))\n ) {\n return false;\n }\n }\n return true;\n};\n\n/**\n * Index of the next `needle` tag at or after `from`, or -1 when there is none\n * starting before `limit`.\n *\n * `limit` is what keeps the nesting walk linear: an opening tag only matters\n * when it precedes the closing one, so searching past the closing tag walks the\n * rest of the document — once per element — for an answer that is thrown away.\n */\nconst findTag = (\n source: string,\n needle: string,\n from: number,\n caseInsensitive: boolean,\n limit: number\n): number => {\n if (!caseInsensitive) {\n const idx = source.indexOf(needle, from);\n return idx === -1 || idx >= limit ? -1 : idx;\n }\n const max = Math.min(limit, source.length - needle.length + 1);\n let pos = from;\n while (pos < max) {\n const idx = source.indexOf('<', pos);\n if (idx === -1 || idx >= max) return -1;\n if (matchesFolded(source, needle, idx)) return idx;\n pos = idx + 1;\n }\n return -1;\n};\n\nconst makeMatch = (\n source: string,\n end: number,\n tagName: string,\n attrs?: string,\n content?: string\n): RegExpMatchArray => {\n const result = [\n source.slice(0, end),\n tagName,\n attrs,\n content,\n ] as unknown as RegExpMatchArray;\n result.index = 0;\n result.input = source;\n return result;\n};\n\n/** ASCII letter or digit — the characters a tag name is built from. */\nconst isNameChar = (code: number): boolean =>\n (code >= 97 && code <= 122) ||\n (code >= 65 && code <= 90) ||\n (code >= 48 && code <= 57);\n\nconst skipLeadingSpaces = (source: string): number => {\n let i = 0;\n while (source.charCodeAt(i) === 32) i++;\n return source.charCodeAt(i) === 60 ? i : -1;\n};\n\nexport const matchElement = (\n source: string,\n customComponent: boolean\n): RegExpMatchArray | null => {\n const bracket = skipLeadingSpaces(source);\n if (bracket === -1) return null;\n\n const nameStart = bracket + 1;\n let nameEnd = nameStart;\n const first = source.charCodeAt(nameStart);\n\n if (customComponent) {\n if (first < 65 || first > 90) return null; // [A-Z]\n while (nameEnd < source.length && isNameChar(source.charCodeAt(nameEnd)))\n nameEnd++;\n } else {\n if (!((first >= 65 && first <= 90) || (first >= 97 && first <= 122)))\n return null;\n while (nameEnd < source.length) {\n const c = source.charCodeAt(nameEnd);\n if (c === 32 || c === 9 || c === 10 || c === 13 || c === 62 || c === 47)\n break;\n nameEnd++;\n }\n }\n\n const tagName = source.slice(nameStart, nameEnd);\n const openTagEnd = findTagEnd(source, nameEnd);\n if (openTagEnd === -1) return null;\n if (!customComponent && source.charCodeAt(openTagEnd - 1) === 47) return null;\n\n let contentStart = openTagEnd + 1;\n if (source.charCodeAt(contentStart) === 10) contentStart++;\n\n const openTag = `<${tagName}`;\n const closeTag = `</${tagName}`;\n\n let depth = 1;\n let cursor = contentStart;\n const len = source.length;\n let contentEnd = -1;\n let end = -1;\n\n while (cursor < len) {\n const closeIdx = findTag(source, closeTag, cursor, !customComponent, len);\n\n if (closeIdx === -1) return null;\n\n const openIdx = findTag(\n source,\n openTag,\n cursor,\n !customComponent,\n closeIdx\n );\n\n if (openIdx !== -1) {\n const next = source.charCodeAt(openIdx + openTag.length);\n if (\n next === 32 ||\n next === 9 ||\n next === 10 ||\n next === 13 ||\n next === 62 ||\n next === 47\n ) {\n const nestedEnd = findTagEnd(source, openIdx + openTag.length);\n if (nestedEnd === -1) return null;\n depth++;\n cursor = nestedEnd + 1;\n } else {\n cursor = openIdx + 1;\n }\n continue;\n }\n\n if (source.charCodeAt(closeIdx + closeTag.length) !== 62) {\n cursor = closeIdx + 1;\n continue;\n }\n\n const closeTagEnd = closeIdx + closeTag.length + 1;\n depth--;\n\n if (depth > 0) {\n cursor = closeTagEnd;\n continue;\n }\n\n // (?!</\\1>)\n if (\n findTag(source, closeTag, closeTagEnd, !customComponent, len) ===\n closeTagEnd &&\n source.charCodeAt(closeTagEnd + closeTag.length) === 62\n ) {\n depth = 1;\n cursor = closeTagEnd;\n continue;\n }\n\n contentEnd = closeIdx;\n end = closeTagEnd;\n break;\n }\n\n if (end === -1) return null;\n while (source.charCodeAt(end) === 10) end++;\n\n return makeMatch(\n source,\n end,\n tagName,\n source.slice(nameEnd, openTagEnd),\n source.slice(contentStart, contentEnd)\n );\n};\n\nexport const matchSelfClosingElement = (\n source: string\n): RegExpMatchArray | null => {\n const bracket = skipLeadingSpaces(source);\n if (bracket === -1) return null;\n\n const nameStart = bracket + 1;\n const first = source.charCodeAt(nameStart);\n if (!((first >= 65 && first <= 90) || (first >= 97 && first <= 122)))\n return null;\n\n let nameEnd = nameStart + 1;\n while (\n nameEnd < source.length &&\n (isNameChar(source.charCodeAt(nameEnd)) ||\n source.charCodeAt(nameEnd) === 58) /* : */\n )\n nameEnd++;\n\n const c = source.charCodeAt(nameEnd);\n const hasAttrs = c === 32 || c === 9 || c === 10 || c === 13;\n let tagEnd: number;\n\n if (hasAttrs) {\n tagEnd = findTagEnd(source, nameEnd);\n if (tagEnd === -1) return null;\n } else {\n tagEnd = source.charCodeAt(nameEnd) === 47 ? nameEnd + 1 : nameEnd;\n if (source.charCodeAt(tagEnd) !== 62) return null;\n }\n\n const tagName = source.slice(nameStart, nameEnd);\n const tailStart = tagEnd + 1;\n\n // `<tag/></tag>` is a paired element written oddly, not a self-closing one.\n if (\n source.charCodeAt(tailStart) === 60 /* < */ &&\n source.charCodeAt(tailStart + 1) === 47 /* / */ &&\n matchesFolded(source, tagName, tailStart + 2) &&\n source.charCodeAt(tailStart + 2 + tagName.length) === 62 /* > */\n ) {\n return null;\n }\n\n let end = tailStart;\n let scan = tailStart;\n while (scan < source.length) {\n const sc = source.charCodeAt(scan);\n if (sc !== 32 && sc !== 9 && sc !== 10 && sc !== 13) break;\n scan++;\n if (source.charCodeAt(scan - 1) === 10) end = scan;\n }\n\n return makeMatch(\n source,\n end,\n tagName,\n hasAttrs ? source.slice(nameEnd + 1, tagEnd) : undefined,\n end > tailStart ? source.slice(tailStart, end) : undefined\n );\n};\n\nexport const matchHtmlBlockElement = (source: string) =>\n matchElement(source, false);\nexport const matchCustomComponent = (source: string) =>\n matchElement(source, true);\nexport const startsWithHtmlBlockElement = (source: string) =>\n matchHtmlBlockElement(source) !== null;\nexport const startsWithSelfClosingElement = (source: string) =>\n matchSelfClosingElement(source) !== null;\nexport const startsWithCustomComponent = (source: string) =>\n matchCustomComponent(source) !== null;\nexport const startsWithElement = (source: string) =>\n startsWithHtmlBlockElement(source) || startsWithSelfClosingElement(source);\n"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  import { getMarkdownMetadata } from "../transpiler/markdown/getMarkdownMetadata.mjs";
2
2
  import { validateMarkdown } from "../transpiler/markdown/validateMarkdown.mjs";
3
3
  import { md as markdown } from "../transpiler/markdown/markdown.mjs";
4
- import { ATTRIBUTES_TO_SANITIZE, ATTRIBUTE_TO_NODE_PROP_MAP, ATTR_EXTRACTOR_R, BLOCKQUOTE_ALERT_R, BLOCKQUOTE_R, BLOCKQUOTE_TRIM_LEFT_MULTILINE_R, BLOCK_END_R, BREAK_LINE_R, BREAK_THEMATIC_R, CAPTURE_LETTER_AFTER_HYPHEN, CODE_BLOCK_FENCED_R, CODE_BLOCK_R, CODE_INLINE_R, CONSECUTIVE_NEWLINE_R, CR_NEWLINE_R, CUSTOM_COMPONENT_OPENING_TAG_R, CUSTOM_COMPONENT_R, DO_NOT_PROCESS_HTML_ELEMENTS, FENCE_DELIMITER_R, FOOTNOTE_R, FOOTNOTE_REFERENCE_R, FORMFEED_R, FRONT_MATTER_R, GFM_TASK_R, HEADING_ATX_COMPLIANT_R, HEADING_R, HEADING_SETEXT_R, HTML_BLOCK_ELEMENT_R, HTML_BLOCK_OPENING_TAG_R, HTML_CHAR_CODE_R, HTML_COMMENT_R, HTML_CUSTOM_ATTR_R, HTML_LEFT_TRIM_AMOUNT_R, HTML_SELF_CLOSING_ELEMENT_R, HTML_SELF_CLOSING_OPENING_TAG_R, INLINE_SKIP_R, INTERPOLATION_R, LINK_AUTOLINK_BARE_URL_R, LINK_AUTOLINK_R, LOOKAHEAD, NAMED_CODES_TO_UNICODE, NORMALIZE_WHITESPACE_R, NP_TABLE_R, ORDERED, ORDERED_LIST_BULLET, ORDERED_LIST_ITEM_PREFIX, ORDERED_LIST_ITEM_PREFIX_R, ORDERED_LIST_ITEM_R, ORDERED_LIST_R, PARAGRAPH_R, Priority, REFERENCE_IMAGE_OR_LINK, REFERENCE_IMAGE_R, REFERENCE_LINK_R, RuleType, SHORTCODE_R, SHOULD_RENDER_AS_BLOCK_R, TABLE_CENTER_ALIGN, TABLE_LEFT_ALIGN, TABLE_RIGHT_ALIGN, TABLE_TRIM_PIPES, TAB_R, TEXT_BOLD_R, TEXT_EMPHASIZED_R, TEXT_ESCAPED_R, TEXT_MARKED_R, TEXT_PLAIN_R, TEXT_STRIKETHROUGHED_R, TRIM_STARTING_NEWLINES, UNESCAPE_R, UNORDERED, UNORDERED_LIST_BULLET, UNORDERED_LIST_ITEM_PREFIX, UNORDERED_LIST_ITEM_PREFIX_R, UNORDERED_LIST_ITEM_R, UNORDERED_LIST_R, generateListItemPrefix, generateListItemPrefixRegex, generateListItemRegex, generateListRegex } from "./constants.mjs";
4
+ import { ATTRIBUTES_TO_SANITIZE, ATTRIBUTE_TO_NODE_PROP_MAP, ATTR_EXTRACTOR_R, BLOCKQUOTE_ALERT_R, BLOCKQUOTE_R, BLOCKQUOTE_TRIM_LEFT_MULTILINE_R, BLOCK_END_R, BREAK_LINE_R, BREAK_THEMATIC_R, CAPTURE_LETTER_AFTER_HYPHEN, CODE_BLOCK_FENCED_R, CODE_BLOCK_R, CODE_INLINE_R, CONSECUTIVE_NEWLINE_R, CR_NEWLINE_R, DO_NOT_PROCESS_HTML_ELEMENTS, FENCE_DELIMITER_R, FOOTNOTE_R, FOOTNOTE_REFERENCE_R, FORMFEED_R, FRONT_MATTER_R, GFM_TASK_R, HEADING_ATX_COMPLIANT_R, HEADING_R, HEADING_SETEXT_R, HTML_CHAR_CODE_R, HTML_COMMENT_R, HTML_CUSTOM_ATTR_R, HTML_LEFT_TRIM_AMOUNT_R, INLINE_SKIP_R, INTERPOLATION_R, LINK_AUTOLINK_BARE_URL_R, LINK_AUTOLINK_R, LOOKAHEAD, NAMED_CODES_TO_UNICODE, NP_TABLE_R, ORDERED, ORDERED_LIST_BULLET, ORDERED_LIST_ITEM_PREFIX, ORDERED_LIST_ITEM_PREFIX_R, ORDERED_LIST_ITEM_R, ORDERED_LIST_R, PARAGRAPH_R, Priority, REFERENCE_IMAGE_OR_LINK, REFERENCE_IMAGE_R, REFERENCE_LINK_R, RuleScope, RuleType, SCOPE_BLOCK, SCOPE_BOTH, SCOPE_INLINE, SHORTCODE_R, SHOULD_RENDER_AS_BLOCK_R, TABLE_CENTER_ALIGN, TABLE_LEFT_ALIGN, TABLE_RIGHT_ALIGN, TABLE_TRIM_PIPES, TAB_R, TEXT_BOLD_R, TEXT_EMPHASIZED_R, TEXT_ESCAPED_R, TEXT_MARKED_R, TEXT_STRIKETHROUGHED_R, TRIM_STARTING_NEWLINES, UNESCAPE_R, UNORDERED, UNORDERED_LIST_BULLET, UNORDERED_LIST_ITEM_PREFIX, UNORDERED_LIST_ITEM_PREFIX_R, UNORDERED_LIST_ITEM_R, UNORDERED_LIST_R, generateListItemPrefix, generateListItemPrefixRegex, generateListItemRegex, generateListRegex } from "./constants.mjs";
5
5
  import { allowInline, anyScopeRegex, attributeValueToNodePropValue, blockRegex, captureNothing, cx, get, inlineRegex, normalizeAttributeKey, normalizeWhitespace, parseBlock, parseCaptureInline, parseInline, parseSimpleInline, parseStyleAttribute, parseTableAlign, parseTableAlignCapture, parseTableCells, parseTableRow, qualifies, renderNothing, sanitizer, simpleInlineRegex, slugify, some, startsWith, trimEnd, trimLeadingWhitespaceOutsideFences, unescapeString, unquote } from "./utils.mjs";
6
6
  import { parserFor } from "./parser.mjs";
7
7
  import { createRenderer, renderFor } from "./renderer.mjs";
8
- import { compile, compileWithOptions, createCompiler, parseMarkdown, renderMarkdownAst } from "./compiler.mjs";
8
+ import { compile, compileWithOptions, createCompiler, getRuleSet, parseMarkdown, renderMarkdownAst } from "./compiler.mjs";
9
9
 
10
- export { ATTRIBUTES_TO_SANITIZE, ATTRIBUTE_TO_NODE_PROP_MAP, ATTR_EXTRACTOR_R, BLOCKQUOTE_ALERT_R, BLOCKQUOTE_R, BLOCKQUOTE_TRIM_LEFT_MULTILINE_R, BLOCK_END_R, BREAK_LINE_R, BREAK_THEMATIC_R, CAPTURE_LETTER_AFTER_HYPHEN, CODE_BLOCK_FENCED_R, CODE_BLOCK_R, CODE_INLINE_R, CONSECUTIVE_NEWLINE_R, CR_NEWLINE_R, CUSTOM_COMPONENT_OPENING_TAG_R, CUSTOM_COMPONENT_R, DO_NOT_PROCESS_HTML_ELEMENTS, FENCE_DELIMITER_R, FOOTNOTE_R, FOOTNOTE_REFERENCE_R, FORMFEED_R, FRONT_MATTER_R, GFM_TASK_R, HEADING_ATX_COMPLIANT_R, HEADING_R, HEADING_SETEXT_R, HTML_BLOCK_ELEMENT_R, HTML_BLOCK_OPENING_TAG_R, HTML_CHAR_CODE_R, HTML_COMMENT_R, HTML_CUSTOM_ATTR_R, HTML_LEFT_TRIM_AMOUNT_R, HTML_SELF_CLOSING_ELEMENT_R, HTML_SELF_CLOSING_OPENING_TAG_R, INLINE_SKIP_R, INTERPOLATION_R, LINK_AUTOLINK_BARE_URL_R, LINK_AUTOLINK_R, LOOKAHEAD, NAMED_CODES_TO_UNICODE, NORMALIZE_WHITESPACE_R, NP_TABLE_R, ORDERED, ORDERED_LIST_BULLET, ORDERED_LIST_ITEM_PREFIX, ORDERED_LIST_ITEM_PREFIX_R, ORDERED_LIST_ITEM_R, ORDERED_LIST_R, PARAGRAPH_R, Priority, REFERENCE_IMAGE_OR_LINK, REFERENCE_IMAGE_R, REFERENCE_LINK_R, RuleType, SHORTCODE_R, SHOULD_RENDER_AS_BLOCK_R, TABLE_CENTER_ALIGN, TABLE_LEFT_ALIGN, TABLE_RIGHT_ALIGN, TABLE_TRIM_PIPES, TAB_R, TEXT_BOLD_R, TEXT_EMPHASIZED_R, TEXT_ESCAPED_R, TEXT_MARKED_R, TEXT_PLAIN_R, TEXT_STRIKETHROUGHED_R, TRIM_STARTING_NEWLINES, UNESCAPE_R, UNORDERED, UNORDERED_LIST_BULLET, UNORDERED_LIST_ITEM_PREFIX, UNORDERED_LIST_ITEM_PREFIX_R, UNORDERED_LIST_ITEM_R, UNORDERED_LIST_R, allowInline, anyScopeRegex, attributeValueToNodePropValue, blockRegex, captureNothing, compile, compileWithOptions, createCompiler, createRenderer, cx, generateListItemPrefix, generateListItemPrefixRegex, generateListItemRegex, generateListRegex, get, getMarkdownMetadata, inlineRegex, markdown as md, normalizeAttributeKey, normalizeWhitespace, parseBlock, parseCaptureInline, parseInline, parseMarkdown, parseSimpleInline, parseStyleAttribute, parseTableAlign, parseTableAlignCapture, parseTableCells, parseTableRow, parserFor, qualifies, renderFor, renderMarkdownAst, renderNothing, sanitizer, simpleInlineRegex, slugify, some, startsWith, trimEnd, trimLeadingWhitespaceOutsideFences, unescapeString, unquote, validateMarkdown };
10
+ export { ATTRIBUTES_TO_SANITIZE, ATTRIBUTE_TO_NODE_PROP_MAP, ATTR_EXTRACTOR_R, BLOCKQUOTE_ALERT_R, BLOCKQUOTE_R, BLOCKQUOTE_TRIM_LEFT_MULTILINE_R, BLOCK_END_R, BREAK_LINE_R, BREAK_THEMATIC_R, CAPTURE_LETTER_AFTER_HYPHEN, CODE_BLOCK_FENCED_R, CODE_BLOCK_R, CODE_INLINE_R, CONSECUTIVE_NEWLINE_R, CR_NEWLINE_R, DO_NOT_PROCESS_HTML_ELEMENTS, FENCE_DELIMITER_R, FOOTNOTE_R, FOOTNOTE_REFERENCE_R, FORMFEED_R, FRONT_MATTER_R, GFM_TASK_R, HEADING_ATX_COMPLIANT_R, HEADING_R, HEADING_SETEXT_R, HTML_CHAR_CODE_R, HTML_COMMENT_R, HTML_CUSTOM_ATTR_R, HTML_LEFT_TRIM_AMOUNT_R, INLINE_SKIP_R, INTERPOLATION_R, LINK_AUTOLINK_BARE_URL_R, LINK_AUTOLINK_R, LOOKAHEAD, NAMED_CODES_TO_UNICODE, NP_TABLE_R, ORDERED, ORDERED_LIST_BULLET, ORDERED_LIST_ITEM_PREFIX, ORDERED_LIST_ITEM_PREFIX_R, ORDERED_LIST_ITEM_R, ORDERED_LIST_R, PARAGRAPH_R, Priority, REFERENCE_IMAGE_OR_LINK, REFERENCE_IMAGE_R, REFERENCE_LINK_R, RuleScope, RuleType, SCOPE_BLOCK, SCOPE_BOTH, SCOPE_INLINE, SHORTCODE_R, SHOULD_RENDER_AS_BLOCK_R, TABLE_CENTER_ALIGN, TABLE_LEFT_ALIGN, TABLE_RIGHT_ALIGN, TABLE_TRIM_PIPES, TAB_R, TEXT_BOLD_R, TEXT_EMPHASIZED_R, TEXT_ESCAPED_R, TEXT_MARKED_R, TEXT_STRIKETHROUGHED_R, TRIM_STARTING_NEWLINES, UNESCAPE_R, UNORDERED, UNORDERED_LIST_BULLET, UNORDERED_LIST_ITEM_PREFIX, UNORDERED_LIST_ITEM_PREFIX_R, UNORDERED_LIST_ITEM_R, UNORDERED_LIST_R, allowInline, anyScopeRegex, attributeValueToNodePropValue, blockRegex, captureNothing, compile, compileWithOptions, createCompiler, createRenderer, cx, generateListItemPrefix, generateListItemPrefixRegex, generateListItemRegex, generateListRegex, get, getMarkdownMetadata, getRuleSet, inlineRegex, markdown as md, normalizeAttributeKey, normalizeWhitespace, parseBlock, parseCaptureInline, parseInline, parseMarkdown, parseSimpleInline, parseStyleAttribute, parseTableAlign, parseTableAlignCapture, parseTableCells, parseTableRow, parserFor, qualifies, renderFor, renderMarkdownAst, renderNothing, sanitizer, simpleInlineRegex, slugify, some, startsWith, trimEnd, trimLeadingWhitespaceOutsideFences, unescapeString, unquote, validateMarkdown };
@@ -1,29 +1,7 @@
1
+ import { RuleType, SCOPE_BLOCK, SCOPE_INLINE } from "./constants.mjs";
1
2
  import { normalizeWhitespace, qualifies } from "./utils.mjs";
2
3
 
3
4
  //#region src/markdown/parser.ts
4
- const ruleOrderCache = /* @__PURE__ */ new Map();
5
- const sameNames = (a, b) => {
6
- for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
7
- return true;
8
- };
9
- /**
10
- * Resolves `Object.keys(rules)` into precedence order, sorting only the first
11
- * time a given rule set is seen.
12
- */
13
- const orderRules = (names, rules) => {
14
- const candidates = ruleOrderCache.get(names.length);
15
- if (candidates) {
16
- for (let i = 0; i < candidates.length; i++) if (sameNames(names, candidates[i].names)) return candidates[i].order;
17
- }
18
- const order = names.slice().sort((a, b) => rules[a]._order - rules[b]._order || +a - +b);
19
- const entry = {
20
- names: names.slice(),
21
- order
22
- };
23
- if (candidates) candidates.push(entry);
24
- else ruleOrderCache.set(names.length, [entry]);
25
- return order;
26
- };
27
5
  /**
28
6
  * Advance the incremental lookbehind carried on the parse state.
29
7
  *
@@ -34,10 +12,14 @@ const orderRules = (names, rules) => {
34
12
  * rescanning — the whole document, which made parsing quadratic.
35
13
  */
36
14
  const advanceLookbehind = (state, capture) => {
15
+ const lastCode = capture.charCodeAt(capture.length - 1);
16
+ if (state.prevCaptureHasBlankLine !== true) state.prevCaptureHasBlankLine = capture.indexOf("\n\n") !== -1 || state.prevCaptureHasBlankLine !== void 0 && capture.charCodeAt(0) === 10 && state.prevCaptureIndent === "";
17
+ if (lastCode !== 32 && lastCode !== 10) {
18
+ state.prevCaptureIndent = void 0;
19
+ return;
20
+ }
37
21
  const newlineIndex = capture.lastIndexOf("\n");
38
- const hasPrevCapture = state.prevCaptureHasBlankLine !== void 0;
39
- if (state.prevCaptureHasBlankLine !== true) state.prevCaptureHasBlankLine = capture.indexOf("\n\n") !== -1 || hasPrevCapture && capture.charCodeAt(0) === 10 && state.prevCaptureIndent === "";
40
- const tail = newlineIndex === -1 ? state.prevCaptureIndent === void 0 ? void 0 : state.prevCaptureIndent + capture : capture.slice(newlineIndex + 1);
22
+ const tail = newlineIndex === -1 ? state.prevCaptureIndent === void 0 ? void 0 : state.prevCaptureIndent === "" ? capture : state.prevCaptureIndent + capture : capture.slice(newlineIndex + 1);
41
23
  if (tail === void 0 || tail === "") {
42
24
  state.prevCaptureIndent = tail;
43
25
  return;
@@ -51,45 +33,105 @@ const advanceLookbehind = (state, capture) => {
51
33
  /**
52
34
  * Creates a parser for a given set of rules, with the precedence
53
35
  * specified as a list of rules.
54
- *
55
- * @param rules - An object containing rule type -> {match, order, parse} objects
56
- * (lower order is higher precedence)
57
- *
58
- * @returns The resulting parse function
59
36
  */
60
37
  const parserFor = (rules) => {
61
- const ruleList = Object.keys(rules);
62
- ruleList.forEach((type) => {
63
- const order = rules[type]?._order;
64
- if (typeof order !== "number" || !Number.isFinite(order)) console.warn(`intlayer: Invalid order for rule \`${type}\`: ${order}`);
65
- });
66
- const order = orderRules(ruleList, rules);
38
+ const order = Object.keys(rules).sort((a, b) => rules[a]._order - rules[b]._order || +a - +b);
67
39
  const ruleCount = order.length;
68
40
  const qualifiers = new Array(ruleCount);
69
41
  const matchers = new Array(ruleCount);
70
42
  const parsers = new Array(ruleCount);
43
+ const ruleFirstChars = new Array(ruleCount);
44
+ const ruleIsBlock = new Array(ruleCount);
45
+ const ruleIsInline = new Array(ruleCount);
46
+ const blockDefaultCandidates = [];
47
+ const inlineDefaultCandidates = [];
71
48
  for (let i = 0; i < ruleCount; i++) {
72
49
  const rule = rules[order[i]];
73
- qualifiers[i] = rule._qualify;
50
+ let q = rule._qualify;
74
51
  matchers[i] = rule._match;
75
52
  parsers[i] = rule._parse;
53
+ const rawScope = rule._scope;
54
+ const isBlock = rawScope !== SCOPE_INLINE;
55
+ const isInline = rawScope !== SCOPE_BLOCK;
56
+ ruleIsBlock[i] = isBlock;
57
+ ruleIsInline[i] = isInline;
58
+ if (rule._firstChars && rule._firstChars.length > 0) ruleFirstChars[i] = rule._firstChars;
59
+ else if (Array.isArray(q) && q.length > 0 && q.every((p) => p.length > 0)) {
60
+ ruleFirstChars[i] = q.map((p) => p.charCodeAt(0));
61
+ if (q.every((p) => p.length === 1)) q = void 0;
62
+ } else {
63
+ ruleFirstChars[i] = null;
64
+ if (isBlock) blockDefaultCandidates.push(i);
65
+ if (isInline) inlineDefaultCandidates.push(i);
66
+ }
67
+ qualifiers[i] = q;
68
+ }
69
+ const blockAsciiCandidates = new Array(128);
70
+ const inlineAsciiCandidates = new Array(128);
71
+ for (let code = 0; code < 128; code++) {
72
+ const bList = [];
73
+ const iList = [];
74
+ for (let i = 0; i < ruleCount; i++) {
75
+ const isBlock = ruleIsBlock[i];
76
+ const isInline = ruleIsInline[i];
77
+ const chars = ruleFirstChars[i];
78
+ if (isBlock && (!chars || chars.includes(code))) bList.push(i);
79
+ if (isInline && (!chars || chars.includes(code))) iList.push(i);
80
+ }
81
+ blockAsciiCandidates[code] = bList;
82
+ inlineAsciiCandidates[code] = iList;
76
83
  }
77
84
  const nestedParse = (source, state = {}) => {
78
85
  const result = [];
86
+ const isInline = Boolean(state.inline || state.simple);
79
87
  if (state.prevCaptureHasBlankLine === void 0) state.prevCaptureIndent = "";
80
- if (source.trim()) while (source) for (let i = 0; i < ruleCount; i++) {
81
- const qualify = qualifiers[i];
82
- if (qualify && !qualifies(source, state, qualify)) continue;
83
- const capture = matchers[i](source, state);
84
- if (capture?.[0]) {
85
- source = source.substring(capture[0].length);
86
- const parsed = parsers[i](capture, nestedParse, state);
87
- advanceLookbehind(state, capture[0]);
88
- if (!parsed.type) parsed.type = order[i];
89
- result.push(parsed);
88
+ let hasContent = false;
89
+ for (let j = 0; j < source.length; j++) {
90
+ const c = source.charCodeAt(j);
91
+ if (c !== 32 && c !== 9 && c !== 10 && c !== 13) {
92
+ hasContent = true;
90
93
  break;
91
94
  }
92
95
  }
96
+ if (hasContent) {
97
+ const asciiCandidates = isInline ? inlineAsciiCandidates : blockAsciiCandidates;
98
+ const defaultCandidates = isInline ? inlineDefaultCandidates : blockDefaultCandidates;
99
+ while (source) {
100
+ const code = source.charCodeAt(0);
101
+ const candidates = code < 128 ? asciiCandidates[code] : defaultCandidates;
102
+ const candidateCount = candidates.length;
103
+ let matched = false;
104
+ for (let c = 0; c < candidateCount; c++) {
105
+ const i = candidates[c];
106
+ const qualify = qualifiers[i];
107
+ if (qualify && !qualifies(source, state, qualify)) continue;
108
+ const capture = matchers[i](source, state);
109
+ if (capture?.[0]) {
110
+ source = source.slice(capture[0].length);
111
+ const parsed = parsers[i](capture, nestedParse, state);
112
+ advanceLookbehind(state, capture[0]);
113
+ const nodeType = parsed.type || order[i];
114
+ parsed.type = nodeType;
115
+ const len = result.length;
116
+ if (nodeType === RuleType.text) {
117
+ if (!parsed.text) {
118
+ matched = true;
119
+ break;
120
+ }
121
+ if (len > 0 && result[len - 1].type === RuleType.text) {
122
+ result[len - 1].text += parsed.text;
123
+ matched = true;
124
+ break;
125
+ }
126
+ }
127
+ result.push(parsed);
128
+ matched = true;
129
+ break;
130
+ }
131
+ }
132
+ if (!matched) break;
133
+ }
134
+ }
93
135
  return result;
94
136
  };
95
137
  return (source, state) => nestedParse(normalizeWhitespace(source), state);
@@ -1 +1 @@
1
- {"version":3,"file":"parser.mjs","names":[],"sources":["../../../src/markdown/parser.ts"],"sourcesContent":["import type {\n NestedParser,\n ParserResult,\n ParseState,\n Rule,\n Rules,\n} from './types';\nimport { normalizeWhitespace, qualifies } from './utils';\n\n/**\n * Cached rule precedence, grouped by rule count.\n *\n * A rule's `_order` never varies, so the precedence a rule set sorts into is a\n * pure function of which rules it holds. Rules themselves are rebuilt per\n * document, so the cache is keyed by rule names rather than by object identity,\n * and grouped by count to keep the comparison short.\n */\ntype RuleOrderEntry = { names: string[]; order: string[] };\n\nconst ruleOrderCache = new Map<number, RuleOrderEntry[]>();\n\nconst sameNames = (a: string[], b: string[]): boolean => {\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n\n return true;\n};\n\n/**\n * Resolves `Object.keys(rules)` into precedence order, sorting only the first\n * time a given rule set is seen.\n */\nconst orderRules = (names: string[], rules: Rules): string[] => {\n const candidates = ruleOrderCache.get(names.length);\n\n if (candidates) {\n for (let i = 0; i < candidates.length; i++) {\n if (sameNames(names, candidates[i]!.names)) return candidates[i]!.order;\n }\n }\n\n // Sorts rules in order of increasing order, then\n // ascending rule name (numeric) in case of ties.\n // RuleType keys are string numbers — use numeric comparison to preserve\n // intended ordering (e.g. codeFenced '4' must precede headingSetext '10').\n const order = names\n .slice()\n .sort((a, b) => rules[a]!._order - rules[b]!._order || +a - +b);\n\n const entry: RuleOrderEntry = { names: names.slice(), order };\n\n if (candidates) candidates.push(entry);\n else ruleOrderCache.set(names.length, [entry]);\n\n return order;\n};\n\n/**\n * Advance the incremental lookbehind carried on the parse state.\n *\n * Rules only ever ask two questions about the source already consumed: how far\n * the current line is indented (`prevCaptureIndent`) and whether a blank line\n * has been seen (`prevCaptureHasBlankLine`). Both are maintained in time\n * proportional to the capture rather than by concatenating — and later\n * rescanning — the whole document, which made parsing quadratic.\n */\nconst advanceLookbehind = (state: ParseState, capture: string): void => {\n const newlineIndex = capture.lastIndexOf('\\n');\n // `undefined` marks a lookbehind nothing has been appended to yet, which is\n // the only way to tell an empty lookbehind from one ending on a line break.\n const hasPrevCapture = state.prevCaptureHasBlankLine !== undefined;\n\n if (state.prevCaptureHasBlankLine !== true) {\n state.prevCaptureHasBlankLine =\n capture.indexOf('\\n\\n') !== -1 ||\n // A capture opening on a line break closes a blank line when the\n // lookbehind already ended on one.\n (hasPrevCapture &&\n capture.charCodeAt(0) === 10 /* \\n */ &&\n state.prevCaptureIndent === '');\n }\n\n // Everything after the final newline forms the current line. With no newline\n // in the capture, the current line simply grew by `capture`.\n const tail =\n newlineIndex === -1\n ? state.prevCaptureIndent === undefined\n ? undefined\n : state.prevCaptureIndent + capture\n : capture.slice(newlineIndex + 1);\n\n if (tail === undefined || tail === '') {\n state.prevCaptureIndent = tail;\n return;\n }\n\n for (let i = 0; i < tail.length; i++) {\n if (tail.charCodeAt(i) !== 32 /* space */) {\n state.prevCaptureIndent = undefined;\n return;\n }\n }\n\n state.prevCaptureIndent = tail;\n};\n\n/**\n * Creates a parser for a given set of rules, with the precedence\n * specified as a list of rules.\n *\n * @param rules - An object containing rule type -> {match, order, parse} objects\n * (lower order is higher precedence)\n *\n * @returns The resulting parse function\n */\nexport const parserFor = (\n rules: Rules\n): ((source: string, state: ParseState) => ParserResult[]) => {\n const ruleList = Object.keys(rules);\n\n if (process.env.NODE_ENV !== 'production') {\n ruleList.forEach((type) => {\n const order = rules[type]?._order;\n if (typeof order !== 'number' || !Number.isFinite(order)) {\n console.warn(`intlayer: Invalid order for rule \\`${type}\\`: ${order}`);\n }\n });\n }\n\n const order = orderRules(ruleList, rules);\n const ruleCount = order.length;\n\n // Rule objects have heterogeneous shapes (`_qualify` and `_render` are both\n // optional), so their members are hoisted into parallel arrays: the hot loop\n // then reads monomorphic slots instead of polymorphic properties, and the\n // whole set costs three allocations rather than one wrapper per rule.\n const qualifiers: (Rule<any>['_qualify'] | undefined)[] = new Array(\n ruleCount\n );\n const matchers: Rule<any>['_match'][] = new Array(ruleCount);\n const parsers: Rule<any>['_parse'][] = new Array(ruleCount);\n\n for (let i = 0; i < ruleCount; i++) {\n const rule = rules[order[i]!]!;\n\n qualifiers[i] = rule._qualify;\n matchers[i] = rule._match;\n parsers[i] = rule._parse;\n }\n\n const nestedParse: NestedParser = (\n source: string,\n state: ParseState = {}\n ): ParserResult[] => {\n const result: ParserResult[] = [];\n\n if (state.prevCaptureHasBlankLine === undefined) {\n // Nothing consumed yet: an empty lookbehind sits at the start of a line.\n state.prevCaptureIndent = '';\n }\n\n if (source.trim()) {\n while (source) {\n for (let i = 0; i < ruleCount; i++) {\n const qualify = qualifiers[i];\n\n if (qualify && !qualifies(source, state, qualify)) continue;\n\n const capture = matchers[i]!(source, state);\n\n if (capture?.[0]) {\n source = source.substring(capture[0].length);\n\n const parsed: any = parsers[i]!(capture, nestedParse, state);\n\n advanceLookbehind(state, capture[0]);\n\n if (!parsed.type) {\n parsed.type = order[i];\n }\n\n result.push(parsed as ParserResult);\n break;\n }\n }\n }\n }\n\n return result;\n };\n\n return (source: string, state: ParseState) =>\n nestedParse(normalizeWhitespace(source), state);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"parser.mjs","names":[],"sources":["../../../src/markdown/parser.ts"],"sourcesContent":["import { RuleType, SCOPE_BLOCK, SCOPE_INLINE } from './constants';\nimport type {\n NestedParser,\n ParserResult,\n ParseState,\n Rule,\n Rules,\n} from './types';\nimport { normalizeWhitespace, qualifies } from './utils';\n\n/**\n * Advance the incremental lookbehind carried on the parse state.\n *\n * Rules only ever ask two questions about the source already consumed: how far\n * the current line is indented (`prevCaptureIndent`) and whether a blank line\n * has been seen (`prevCaptureHasBlankLine`). Both are maintained in time\n * proportional to the capture rather than by concatenating — and later\n * rescanning — the whole document, which made parsing quadratic.\n */\nconst advanceLookbehind = (state: ParseState, capture: string): void => {\n const lastCode = capture.charCodeAt(capture.length - 1);\n\n if (state.prevCaptureHasBlankLine !== true) {\n state.prevCaptureHasBlankLine =\n capture.indexOf('\\n\\n') !== -1 ||\n (state.prevCaptureHasBlankLine !== undefined &&\n capture.charCodeAt(0) === 10 &&\n state.prevCaptureIndent === '');\n }\n\n // A capture whose last character is neither a space nor a newline cannot\n // leave an all-space tail behind it, whatever it contains — so the common\n // case never pays for the backwards `lastIndexOf` scan.\n if (lastCode !== 32 /* space */ && lastCode !== 10 /* \\n */) {\n state.prevCaptureIndent = undefined;\n return;\n }\n\n const newlineIndex = capture.lastIndexOf('\\n');\n\n const tail =\n newlineIndex === -1\n ? state.prevCaptureIndent === undefined\n ? undefined\n : state.prevCaptureIndent === ''\n ? capture\n : state.prevCaptureIndent + capture\n : capture.slice(newlineIndex + 1);\n\n if (tail === undefined || tail === '') {\n state.prevCaptureIndent = tail;\n return;\n }\n\n for (let i = 0; i < tail.length; i++) {\n if (tail.charCodeAt(i) !== 32) {\n state.prevCaptureIndent = undefined;\n return;\n }\n }\n\n state.prevCaptureIndent = tail;\n};\n\n/**\n * Creates a parser for a given set of rules, with the precedence\n * specified as a list of rules.\n */\nexport const parserFor = (\n rules: Rules\n): ((source: string, state: ParseState) => ParserResult[]) => {\n const order = Object.keys(rules).sort(\n (a, b) => rules[a]!._order - rules[b]!._order || +a - +b\n );\n const ruleCount = order.length;\n\n const qualifiers: (Rule<any>['_qualify'] | undefined)[] = new Array(\n ruleCount\n );\n const matchers: Rule<any>['_match'][] = new Array(ruleCount);\n const parsers: Rule<any>['_parse'][] = new Array(ruleCount);\n const ruleFirstChars: (number[] | null)[] = new Array(ruleCount);\n const ruleIsBlock: boolean[] = new Array(ruleCount);\n const ruleIsInline: boolean[] = new Array(ruleCount);\n const blockDefaultCandidates: number[] = [];\n const inlineDefaultCandidates: number[] = [];\n\n for (let i = 0; i < ruleCount; i++) {\n const rule = rules[order[i]!]!;\n let q = rule._qualify;\n matchers[i] = rule._match;\n parsers[i] = rule._parse;\n\n const rawScope = rule._scope;\n const isBlock = rawScope !== SCOPE_INLINE;\n const isInline = rawScope !== SCOPE_BLOCK;\n\n ruleIsBlock[i] = isBlock;\n ruleIsInline[i] = isInline;\n\n if (rule._firstChars && rule._firstChars.length > 0) {\n ruleFirstChars[i] = rule._firstChars;\n } else if (\n Array.isArray(q) &&\n q.length > 0 &&\n q.every((p) => p.length > 0)\n ) {\n ruleFirstChars[i] = q.map((p) => p.charCodeAt(0));\n if (q.every((p) => p.length === 1)) q = undefined;\n } else {\n ruleFirstChars[i] = null;\n if (isBlock) blockDefaultCandidates.push(i);\n if (isInline) inlineDefaultCandidates.push(i);\n }\n qualifiers[i] = q;\n }\n\n const blockAsciiCandidates: number[][] = new Array(128);\n const inlineAsciiCandidates: number[][] = new Array(128);\n for (let code = 0; code < 128; code++) {\n const bList: number[] = [];\n const iList: number[] = [];\n for (let i = 0; i < ruleCount; i++) {\n const isBlock = ruleIsBlock[i]!;\n const isInline = ruleIsInline[i]!;\n const chars = ruleFirstChars[i];\n\n if (isBlock && (!chars || chars.includes(code))) {\n bList.push(i);\n }\n if (isInline && (!chars || chars.includes(code))) {\n iList.push(i);\n }\n }\n blockAsciiCandidates[code] = bList;\n inlineAsciiCandidates[code] = iList;\n }\n\n const nestedParse: NestedParser = (\n source: string,\n state: ParseState = {}\n ): ParserResult[] => {\n const result: ParserResult[] = [];\n const isInline = Boolean(state.inline || state.simple);\n\n if (state.prevCaptureHasBlankLine === undefined) {\n state.prevCaptureIndent = '';\n }\n\n // Quick non-allocating whitespace check\n let hasContent = false;\n for (let j = 0; j < source.length; j++) {\n const c = source.charCodeAt(j);\n if (c !== 32 && c !== 9 && c !== 10 && c !== 13) {\n hasContent = true;\n break;\n }\n }\n\n if (hasContent) {\n const asciiCandidates = isInline\n ? inlineAsciiCandidates\n : blockAsciiCandidates;\n const defaultCandidates = isInline\n ? inlineDefaultCandidates\n : blockDefaultCandidates;\n\n while (source) {\n const code = source.charCodeAt(0);\n const candidates =\n code < 128 ? asciiCandidates[code]! : defaultCandidates;\n const candidateCount = candidates.length;\n let matched = false;\n\n for (let c = 0; c < candidateCount; c++) {\n const i = candidates[c]!;\n const qualify = qualifiers[i];\n\n if (qualify && !qualifies(source, state, qualify)) continue;\n\n const capture = matchers[i]!(source, state);\n\n if (capture?.[0]) {\n source = source.slice(capture[0].length);\n\n const parsed: any = parsers[i]!(capture, nestedParse, state);\n\n advanceLookbehind(state, capture[0]);\n\n const nodeType = parsed.type || order[i];\n parsed.type = nodeType;\n\n const len = result.length;\n if (nodeType === RuleType.text) {\n if (!parsed.text) {\n matched = true;\n break;\n }\n if (len > 0 && result[len - 1]!.type === RuleType.text) {\n (result[len - 1] as any).text += parsed.text;\n matched = true;\n break;\n }\n }\n\n result.push(parsed as ParserResult);\n matched = true;\n break;\n }\n }\n\n if (!matched) break;\n }\n }\n\n return result;\n };\n\n return (source: string, state: ParseState) =>\n nestedParse(normalizeWhitespace(source), state);\n};\n"],"mappings":"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"}
@@ -14,10 +14,9 @@ const renderFor = (render) => {
14
14
  let lastWasString = false;
15
15
  let renderedIndex = 0;
16
16
  for (let i = 0; i < ast.length; i++) {
17
- const nodeOut = emit(ast[i], {
18
- ...state,
19
- key: renderedIndex
20
- });
17
+ state.key = renderedIndex;
18
+ const node = ast[i];
19
+ const nodeOut = Array.isArray(node) ? emit(node, state) : render(node, emit, state);
21
20
  const isString = typeof nodeOut === "string";
22
21
  if (isString && lastWasString) result[result.length - 1] = result[result.length - 1] + nodeOut;
23
22
  else if (nodeOut !== null) {
@@ -33,14 +32,18 @@ const renderFor = (render) => {
33
32
  };
34
33
  /**
35
34
  * Creates a renderer from rules with optional custom render hook.
36
- *
37
- * @param rules - The rules object containing _render functions
38
- * @param userRender - Optional custom render hook for full control
39
- * @returns A render function for AST nodes
40
35
  */
41
- const createRenderer = (rules, userRender) => (ast, render, state) => {
42
- const renderer = rules[ast.type]?._render;
43
- return userRender ? userRender(() => renderer?.(ast, render, state), ast, render, state) : renderer?.(ast, render, state);
36
+ const createRenderer = (rules, userRender) => {
37
+ const renderers = {};
38
+ for (const type in rules) renderers[type] = rules[type]?._render;
39
+ if (!userRender) return (ast, render, state) => {
40
+ const r = renderers[ast.type];
41
+ return r ? r(ast, render, state) : void 0;
42
+ };
43
+ return (ast, render, state) => {
44
+ const renderer = renderers[ast.type];
45
+ return userRender(() => renderer?.(ast, render, state), ast, render, state);
46
+ };
44
47
  };
45
48
 
46
49
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.mjs","names":[],"sources":["../../../src/markdown/renderer.ts"],"sourcesContent":["import type {\n ParserResult,\n ParseState,\n RenderRuleHook,\n RuleOutput,\n Rules,\n} from './types';\n\n/**\n * Creates a renderer for AST nodes.\n * Renamed from `reactFor` to be framework-agnostic.\n *\n * @param render - The render function to call for each node\n * @returns A function that renders AST to output\n */\nexport const renderFor = (\n render: (ast: ParserResult, render: RuleOutput, state: ParseState) => unknown\n) => {\n // Bound once instead of per node: rebuilding the recursive renderer on every\n // visit allocated a closure pair for each AST node.\n const emit = (\n ast: ParserResult | ParserResult[],\n state: ParseState = {}\n ): any => {\n if (!Array.isArray(ast)) {\n return render(ast, emit as RuleOutput, state);\n }\n\n const oldKey = state.key;\n const result: any[] = [];\n\n // map nestedOutput over the ast, except group any text\n // nodes together into a single string output.\n let lastWasString = false;\n let renderedIndex = 0;\n\n for (let i = 0; i < ast.length; i++) {\n // We clone the state to avoid side effects on other nodes in the same level\n // while ensuring each non-null rendered node gets a unique, sequential key.\n const nodeOut = emit(ast[i], { ...state, key: renderedIndex });\n const isString = typeof nodeOut === 'string';\n\n if (isString && lastWasString) {\n result[result.length - 1] =\n (result[result.length - 1] as string) + nodeOut;\n } else if (nodeOut !== null) {\n result.push(nodeOut);\n renderedIndex++;\n }\n\n lastWasString = isString;\n }\n\n state.key = oldKey;\n\n return result;\n };\n\n return emit;\n};\n\n/**\n * Creates a renderer from rules with optional custom render hook.\n *\n * @param rules - The rules object containing _render functions\n * @param userRender - Optional custom render hook for full control\n * @returns A render function for AST nodes\n */\nexport const createRenderer =\n (rules: Rules, userRender?: RenderRuleHook) =>\n (ast: ParserResult, render: RuleOutput, state: ParseState): unknown => {\n const renderer = rules[ast.type]?._render;\n\n return userRender\n ? userRender(() => renderer?.(ast, render, state), ast, render, state)\n : renderer?.(ast, render, state);\n };\n"],"mappings":";;;;;;;;AAeA,MAAa,aACX,WACG;CAGH,MAAM,QACJ,KACA,QAAoB,CAAC,MACb;EACR,IAAI,CAAC,MAAM,QAAQ,GAAG,GACpB,OAAO,OAAO,KAAK,MAAoB,KAAK;EAG9C,MAAM,SAAS,MAAM;EACrB,MAAM,SAAgB,CAAC;EAIvB,IAAI,gBAAgB;EACpB,IAAI,gBAAgB;EAEpB,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;GAGnC,MAAM,UAAU,KAAK,IAAI,IAAI;IAAE,GAAG;IAAO,KAAK;GAAc,CAAC;GAC7D,MAAM,WAAW,OAAO,YAAY;GAEpC,IAAI,YAAY,eACd,OAAO,OAAO,SAAS,KACpB,OAAO,OAAO,SAAS,KAAgB;QACrC,IAAI,YAAY,MAAM;IAC3B,OAAO,KAAK,OAAO;IACnB;GACF;GAEA,gBAAgB;EAClB;EAEA,MAAM,MAAM;EAEZ,OAAO;CACT;CAEA,OAAO;AACT;;;;;;;;AASA,MAAa,kBACV,OAAc,gBACd,KAAmB,QAAoB,UAA+B;CACrE,MAAM,WAAW,MAAM,IAAI,KAAK,EAAE;CAElC,OAAO,aACH,iBAAiB,WAAW,KAAK,QAAQ,KAAK,GAAG,KAAK,QAAQ,KAAK,IACnE,WAAW,KAAK,QAAQ,KAAK;AACnC"}
1
+ {"version":3,"file":"renderer.mjs","names":[],"sources":["../../../src/markdown/renderer.ts"],"sourcesContent":["import type {\n ParserResult,\n ParseState,\n RenderRuleHook,\n Rule,\n RuleOutput,\n Rules,\n} from './types';\n\n/**\n * Creates a renderer for AST nodes.\n * Renamed from `reactFor` to be framework-agnostic.\n *\n * @param render - The render function to call for each node\n * @returns A function that renders AST to output\n */\n\nexport const renderFor = (\n render: (ast: ParserResult, render: RuleOutput, state: ParseState) => unknown\n) => {\n const emit = (\n ast: ParserResult | ParserResult[],\n state: ParseState = {}\n ): any => {\n if (!Array.isArray(ast)) {\n return render(ast, emit as RuleOutput, state);\n }\n\n const oldKey = state.key;\n const result: any[] = [];\n\n let lastWasString = false;\n let renderedIndex = 0;\n\n for (let i = 0; i < ast.length; i++) {\n state.key = renderedIndex;\n const node = ast[i]!;\n const nodeOut = Array.isArray(node)\n ? emit(node, state)\n : render(node, emit as RuleOutput, state);\n const isString = typeof nodeOut === 'string';\n\n if (isString && lastWasString) {\n result[result.length - 1] =\n (result[result.length - 1] as string) + nodeOut;\n } else if (nodeOut !== null) {\n result.push(nodeOut);\n renderedIndex++;\n }\n\n lastWasString = isString;\n }\n\n state.key = oldKey;\n\n return result;\n };\n\n return emit;\n};\n\n/**\n * Creates a renderer from rules with optional custom render hook.\n */\nexport const createRenderer = (rules: Rules, userRender?: RenderRuleHook) => {\n const renderers: Record<string, Rule<any>['_render']> = {};\n for (const type in rules) renderers[type] = rules[type]?._render;\n\n if (!userRender) {\n return (\n ast: ParserResult,\n render: RuleOutput,\n state: ParseState\n ): unknown => {\n const r = renderers[ast.type];\n return r ? r(ast, render, state) : undefined;\n };\n }\n\n return (\n ast: ParserResult,\n render: RuleOutput,\n state: ParseState\n ): unknown => {\n const renderer = renderers[ast.type];\n\n return userRender(() => renderer?.(ast, render, state), ast, render, state);\n };\n};\n"],"mappings":";;;;;;;;AAiBA,MAAa,aACX,WACG;CACH,MAAM,QACJ,KACA,QAAoB,CAAC,MACb;EACR,IAAI,CAAC,MAAM,QAAQ,GAAG,GACpB,OAAO,OAAO,KAAK,MAAoB,KAAK;EAG9C,MAAM,SAAS,MAAM;EACrB,MAAM,SAAgB,CAAC;EAEvB,IAAI,gBAAgB;EACpB,IAAI,gBAAgB;EAEpB,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;GACnC,MAAM,MAAM;GACZ,MAAM,OAAO,IAAI;GACjB,MAAM,UAAU,MAAM,QAAQ,IAAI,IAC9B,KAAK,MAAM,KAAK,IAChB,OAAO,MAAM,MAAoB,KAAK;GAC1C,MAAM,WAAW,OAAO,YAAY;GAEpC,IAAI,YAAY,eACd,OAAO,OAAO,SAAS,KACpB,OAAO,OAAO,SAAS,KAAgB;QACrC,IAAI,YAAY,MAAM;IAC3B,OAAO,KAAK,OAAO;IACnB;GACF;GAEA,gBAAgB;EAClB;EAEA,MAAM,MAAM;EAEZ,OAAO;CACT;CAEA,OAAO;AACT;;;;AAKA,MAAa,kBAAkB,OAAc,eAAgC;CAC3E,MAAM,YAAkD,CAAC;CACzD,KAAK,MAAM,QAAQ,OAAO,UAAU,QAAQ,MAAM,KAAK,EAAE;CAEzD,IAAI,CAAC,YACH,QACE,KACA,QACA,UACY;EACZ,MAAM,IAAI,UAAU,IAAI;EACxB,OAAO,IAAI,EAAE,KAAK,QAAQ,KAAK,IAAI;CACrC;CAGF,QACE,KACA,QACA,UACY;EACZ,MAAM,WAAW,UAAU,IAAI;EAE/B,OAAO,iBAAiB,WAAW,KAAK,QAAQ,KAAK,GAAG,KAAK,QAAQ,KAAK;CAC5E;AACF"}