@wdprlib/parser 3.1.2 → 3.2.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 (124) hide show
  1. package/dist/index.cjs +295 -118
  2. package/dist/index.js +272 -95
  3. package/package.json +5 -3
  4. package/src/index.ts +163 -0
  5. package/src/lexer/index.ts +20 -0
  6. package/src/lexer/lexer.ts +687 -0
  7. package/src/lexer/tokens.ts +141 -0
  8. package/src/parser/constants.ts +173 -0
  9. package/src/parser/depth.ts +251 -0
  10. package/src/parser/index.ts +18 -0
  11. package/src/parser/parse.ts +315 -0
  12. package/src/parser/postprocess/divAdjacentParagraph.ts +76 -0
  13. package/src/parser/postprocess/index.ts +15 -0
  14. package/src/parser/postprocess/spanStrip.ts +697 -0
  15. package/src/parser/preprocess/expr.ts +265 -0
  16. package/src/parser/preprocess/index.ts +38 -0
  17. package/src/parser/preprocess/typography.ts +67 -0
  18. package/src/parser/preprocess/utils.ts +250 -0
  19. package/src/parser/preprocess/whitespace.ts +111 -0
  20. package/src/parser/rules/block/align.ts +282 -0
  21. package/src/parser/rules/block/bibliography.ts +359 -0
  22. package/src/parser/rules/block/block-list.ts +689 -0
  23. package/src/parser/rules/block/blockquote.ts +238 -0
  24. package/src/parser/rules/block/center.ts +87 -0
  25. package/src/parser/rules/block/clear-float.ts +75 -0
  26. package/src/parser/rules/block/code.ts +187 -0
  27. package/src/parser/rules/block/collapsible.ts +337 -0
  28. package/src/parser/rules/block/comment.ts +73 -0
  29. package/src/parser/rules/block/content-separator.ts +79 -0
  30. package/src/parser/rules/block/definition-list.ts +270 -0
  31. package/src/parser/rules/block/div.ts +400 -0
  32. package/src/parser/rules/block/embed-block.ts +153 -0
  33. package/src/parser/rules/block/footnoteblock.ts +200 -0
  34. package/src/parser/rules/block/heading.ts +142 -0
  35. package/src/parser/rules/block/horizontal-rule.ts +61 -0
  36. package/src/parser/rules/block/html.ts +222 -0
  37. package/src/parser/rules/block/iframe.ts +239 -0
  38. package/src/parser/rules/block/iftags.ts +150 -0
  39. package/src/parser/rules/block/include.ts +179 -0
  40. package/src/parser/rules/block/index.ts +127 -0
  41. package/src/parser/rules/block/list.ts +244 -0
  42. package/src/parser/rules/block/math.ts +183 -0
  43. package/src/parser/rules/block/module/backlinks/index.ts +31 -0
  44. package/src/parser/rules/block/module/backlinks/types.ts +21 -0
  45. package/src/parser/rules/block/module/categories/index.ts +34 -0
  46. package/src/parser/rules/block/module/categories/types.ts +21 -0
  47. package/src/parser/rules/block/module/css/index.ts +37 -0
  48. package/src/parser/rules/block/module/iftags/condition.ts +109 -0
  49. package/src/parser/rules/block/module/iftags/index.ts +26 -0
  50. package/src/parser/rules/block/module/iftags/preprocess.ts +140 -0
  51. package/src/parser/rules/block/module/iftags/resolve.ts +73 -0
  52. package/src/parser/rules/block/module/iftags/types.ts +63 -0
  53. package/src/parser/rules/block/module/include/index.ts +20 -0
  54. package/src/parser/rules/block/module/include/resolve.ts +556 -0
  55. package/src/parser/rules/block/module/index.ts +122 -0
  56. package/src/parser/rules/block/module/join/index.ts +34 -0
  57. package/src/parser/rules/block/module/join/types.ts +23 -0
  58. package/src/parser/rules/block/module/listpages/compiler.ts +453 -0
  59. package/src/parser/rules/block/module/listpages/extract.ts +410 -0
  60. package/src/parser/rules/block/module/listpages/index.ts +83 -0
  61. package/src/parser/rules/block/module/listpages/normalize.ts +390 -0
  62. package/src/parser/rules/block/module/listpages/parser.ts +106 -0
  63. package/src/parser/rules/block/module/listpages/resolve.ts +130 -0
  64. package/src/parser/rules/block/module/listpages/types.ts +513 -0
  65. package/src/parser/rules/block/module/listpages/url-resolver.ts +186 -0
  66. package/src/parser/rules/block/module/listusers/compiler.ts +77 -0
  67. package/src/parser/rules/block/module/listusers/extract.ts +45 -0
  68. package/src/parser/rules/block/module/listusers/index.ts +36 -0
  69. package/src/parser/rules/block/module/listusers/parser.ts +54 -0
  70. package/src/parser/rules/block/module/listusers/resolve.ts +58 -0
  71. package/src/parser/rules/block/module/listusers/types.ts +93 -0
  72. package/src/parser/rules/block/module/mapping.ts +61 -0
  73. package/src/parser/rules/block/module/page-tree/index.ts +38 -0
  74. package/src/parser/rules/block/module/page-tree/types.ts +29 -0
  75. package/src/parser/rules/block/module/rate/index.ts +28 -0
  76. package/src/parser/rules/block/module/rate/types.ts +19 -0
  77. package/src/parser/rules/block/module/resolve.ts +411 -0
  78. package/src/parser/rules/block/module/types-common.ts +59 -0
  79. package/src/parser/rules/block/module/types.ts +61 -0
  80. package/src/parser/rules/block/module/utils.ts +43 -0
  81. package/src/parser/rules/block/module/walk.ts +380 -0
  82. package/src/parser/rules/block/module.ts +164 -0
  83. package/src/parser/rules/block/orphan-li.ts +177 -0
  84. package/src/parser/rules/block/paragraph.ts +157 -0
  85. package/src/parser/rules/block/table-block.ts +726 -0
  86. package/src/parser/rules/block/table.ts +441 -0
  87. package/src/parser/rules/block/tabview.ts +331 -0
  88. package/src/parser/rules/block/toc.ts +129 -0
  89. package/src/parser/rules/block/utils.ts +615 -0
  90. package/src/parser/rules/index.ts +49 -0
  91. package/src/parser/rules/inline/anchor-name.ts +154 -0
  92. package/src/parser/rules/inline/anchor.ts +327 -0
  93. package/src/parser/rules/inline/bibcite.ts +153 -0
  94. package/src/parser/rules/inline/bold.ts +86 -0
  95. package/src/parser/rules/inline/color.ts +140 -0
  96. package/src/parser/rules/inline/comment.ts +90 -0
  97. package/src/parser/rules/inline/equation-ref.ts +115 -0
  98. package/src/parser/rules/inline/expr.ts +526 -0
  99. package/src/parser/rules/inline/footnote.ts +223 -0
  100. package/src/parser/rules/inline/guillemet.ts +64 -0
  101. package/src/parser/rules/inline/html.ts +132 -0
  102. package/src/parser/rules/inline/image.ts +328 -0
  103. package/src/parser/rules/inline/index.ts +150 -0
  104. package/src/parser/rules/inline/italic.ts +74 -0
  105. package/src/parser/rules/inline/line-break.ts +326 -0
  106. package/src/parser/rules/inline/link-anchor.ts +147 -0
  107. package/src/parser/rules/inline/link-single.ts +164 -0
  108. package/src/parser/rules/inline/link-star.ts +134 -0
  109. package/src/parser/rules/inline/link-triple.ts +267 -0
  110. package/src/parser/rules/inline/math-inline.ts +126 -0
  111. package/src/parser/rules/inline/monospace.ts +78 -0
  112. package/src/parser/rules/inline/raw.ts +262 -0
  113. package/src/parser/rules/inline/size.ts +244 -0
  114. package/src/parser/rules/inline/span.ts +424 -0
  115. package/src/parser/rules/inline/strikethrough.ts +115 -0
  116. package/src/parser/rules/inline/subscript.ts +84 -0
  117. package/src/parser/rules/inline/superscript.ts +84 -0
  118. package/src/parser/rules/inline/text.ts +84 -0
  119. package/src/parser/rules/inline/underline.ts +127 -0
  120. package/src/parser/rules/inline/user.ts +147 -0
  121. package/src/parser/rules/inline/utils.ts +344 -0
  122. package/src/parser/rules/types.ts +252 -0
  123. package/src/parser/rules/utils.ts +155 -0
  124. package/src/parser/toc.ts +130 -0
@@ -0,0 +1,328 @@
1
+ /**
2
+ *
3
+ * Parses the Wikidot image block syntax: `[[image source attributes]]`.
4
+ *
5
+ * Images support several alignment/float prefixes that modify how the
6
+ * image is positioned on the page:
7
+ * - `[[image src]]` -- default (no alignment)
8
+ * - `[[=image src]]` -- centered
9
+ * - `[[<image src]]` -- left-aligned
10
+ * - `[[>image src]]` -- right-aligned
11
+ * - `[[f<image src]]` -- float left
12
+ * - `[[f>image src]]` -- float right
13
+ * - `[[f=image src]]` -- float center
14
+ *
15
+ * Image sources can be:
16
+ * - Full URLs (`http://...`, `https://...`, `/path`)
17
+ * - Local file references in three formats:
18
+ * - `file.ext` (file on current page, type `file1`)
19
+ * - `page/file.ext` (file on another page, type `file2`)
20
+ * - `site:page/file.ext` or `site/page/file.ext` (cross-site file, type `file3`)
21
+ *
22
+ * Optional attributes follow the source (e.g. `alt`, `title`, `width`,
23
+ * `height`, `style`, `class`, `link`). The `link` attribute is treated
24
+ * specially: it wraps the image in a hyperlink rather than being applied
25
+ * as an HTML attribute. Unsafe attributes are filtered out.
26
+ *
27
+ * Produces an `"image"` AST element with source, alignment, link, and
28
+ * attribute data.
29
+ *
30
+ * @module
31
+ */
32
+ import type { Element, ImageSource, FloatAlignment, Alignment, AttributeMap } from "@wdprlib/ast";
33
+ import type { InlineRule, ParseContext, RuleResult } from "../types";
34
+ import { currentToken } from "../types";
35
+ import { filterUnsafeAttributes } from "../utils";
36
+ import { parseAttributesRaw } from "../block/utils";
37
+
38
+ /**
39
+ * Parses the block name portion of an image tag, including alignment
40
+ * prefix characters.
41
+ *
42
+ * The alignment prefix may consist of `=`, `<`, `>`, `f<`, `f>`, or `f=`,
43
+ * each tokenized differently depending on the lexer's context (e.g. `>`
44
+ * may appear as either a `TEXT` token or a `BLOCKQUOTE_MARKER`).
45
+ *
46
+ * @param ctx - The current parse context
47
+ * @param startPos - Token index at which to begin scanning
48
+ * @returns An object with the combined lowercased name (prefix + "image")
49
+ * and the number of tokens consumed, or `null` if no valid image
50
+ * block name was found
51
+ */
52
+ function parseImageBlockName(
53
+ ctx: ParseContext,
54
+ startPos: number,
55
+ ): { name: string; consumed: number } | null {
56
+ let pos = startPos;
57
+ let consumed = 0;
58
+
59
+ while (ctx.tokens[pos]?.type === "WHITESPACE") {
60
+ pos++;
61
+ consumed++;
62
+ }
63
+
64
+ let prefix = "";
65
+ const token = ctx.tokens[pos];
66
+
67
+ // Handle prefix characters for image variants: =image, <image, >image, f<image, f>image
68
+ // These are tokenized as separate tokens: EQUALS/TEXT + IDENTIFIER
69
+ if (token?.type === "EQUALS") {
70
+ // =image (center)
71
+ prefix = "=";
72
+ pos++;
73
+ consumed++;
74
+ } else if (token?.type === "TEXT" && token.value === "<") {
75
+ // <image (left align)
76
+ prefix = "<";
77
+ pos++;
78
+ consumed++;
79
+ } else if (token?.type === "TEXT" && token.value === ">") {
80
+ // >image (right align)
81
+ prefix = ">";
82
+ pos++;
83
+ consumed++;
84
+ } else if (token?.type === "BLOCKQUOTE_MARKER" && token.value === ">") {
85
+ // >image (right align) - may also be tokenized as BLOCKQUOTE_MARKER
86
+ prefix = ">";
87
+ pos++;
88
+ consumed++;
89
+ } else if (token?.type === "IDENTIFIER" && token.value.toLowerCase() === "f") {
90
+ // Check for f<, f>, or f=
91
+ const nextToken = ctx.tokens[pos + 1];
92
+ if (nextToken?.type === "TEXT" && nextToken.value === "<") {
93
+ prefix = "f<";
94
+ pos += 2;
95
+ consumed += 2;
96
+ } else if (nextToken?.type === "TEXT" && nextToken.value === ">") {
97
+ prefix = "f>";
98
+ pos += 2;
99
+ consumed += 2;
100
+ } else if (nextToken?.type === "BLOCKQUOTE_MARKER" && nextToken.value === ">") {
101
+ prefix = "f>";
102
+ pos += 2;
103
+ consumed += 2;
104
+ } else if (nextToken?.type === "EQUALS") {
105
+ // f=image (float center)
106
+ prefix = "f=";
107
+ pos += 2;
108
+ consumed += 2;
109
+ }
110
+ }
111
+
112
+ const nameToken = ctx.tokens[pos];
113
+ if (!nameToken || (nameToken.type !== "TEXT" && nameToken.type !== "IDENTIFIER")) {
114
+ return null;
115
+ }
116
+
117
+ return { name: prefix + nameToken.value.toLowerCase(), consumed: consumed + 1 };
118
+ }
119
+
120
+ /**
121
+ * Determines the {@link ImageSource} type and data from a raw source string.
122
+ *
123
+ * Classification logic:
124
+ * - Strings starting with `http://`, `https://`, or `/` are URL sources.
125
+ * - Strings containing a colon before a slash (e.g. `site:page/file`) are
126
+ * `file3` (cross-site) references.
127
+ * - Strings with 2+ slashes (e.g. `site/page/file`) are also `file3`.
128
+ * - Strings with exactly 1 slash (e.g. `page/file`) are `file2` references.
129
+ * - Strings with no slashes are `file1` (current-page file) references.
130
+ *
131
+ * @param src - The raw image source string from the markup
132
+ * @returns An {@link ImageSource} object describing the source type and data
133
+ */
134
+ function parseImageSource(src: string): ImageSource {
135
+ // URL sources
136
+ if (src.startsWith("http://") || src.startsWith("https://") || src.startsWith("/")) {
137
+ return { type: "url", data: src };
138
+ }
139
+
140
+ // File references - determine type based on format
141
+ // file3: site:page/file or site/page/file (2+ slashes)
142
+ // file2: page/file (1 slash)
143
+ // file1: file (no slash)
144
+ const colonIdx = src.indexOf(":");
145
+ const slashIdx = src.indexOf("/");
146
+
147
+ if (colonIdx > 0 && slashIdx > colonIdx) {
148
+ // site:page/file format (colon-based)
149
+ const site = src.substring(0, colonIdx);
150
+ const rest = src.substring(colonIdx + 1);
151
+ const lastSlash = rest.lastIndexOf("/");
152
+ const page = rest.substring(0, lastSlash);
153
+ const file = rest.substring(lastSlash + 1);
154
+ return { type: "file3", data: { site, page, file } };
155
+ }
156
+
157
+ // Count slashes to determine format
158
+ const slashes = src.split("/").length - 1;
159
+ if (slashes >= 2) {
160
+ // site/page/file format (2+ slashes = file3)
161
+ const firstSlash = src.indexOf("/");
162
+ const lastSlash = src.lastIndexOf("/");
163
+ const site = src.substring(0, firstSlash);
164
+ const page = src.substring(firstSlash + 1, lastSlash);
165
+ const file = src.substring(lastSlash + 1);
166
+ return { type: "file3", data: { site, page, file } };
167
+ }
168
+ if (slashIdx > 0) {
169
+ // page/file format (1 slash = file2)
170
+ const page = src.substring(0, slashIdx);
171
+ const file = src.substring(slashIdx + 1);
172
+ return { type: "file2", data: { page, file } };
173
+ }
174
+
175
+ // Just file
176
+ return { type: "file1", data: { file: src } };
177
+ }
178
+
179
+ /**
180
+ * Converts the image block name (including its alignment prefix) into a
181
+ * {@link FloatAlignment} descriptor.
182
+ *
183
+ * The prefix portion of the block name determines both the alignment
184
+ * direction and whether the image should float. A plain `"image"` name
185
+ * (no prefix) returns `null`, indicating no explicit alignment.
186
+ *
187
+ * @param blockName - The lowercased block name (e.g. `"f>image"`, `"=image"`, `"image"`)
188
+ * @returns A {@link FloatAlignment} object with `align` and `float` fields,
189
+ * or `null` for the unprefixed `"image"` form
190
+ */
191
+ function parseAlignment(blockName: string): FloatAlignment | null {
192
+ let align: Alignment = "left";
193
+ let float = false;
194
+
195
+ if (blockName === "=image") {
196
+ align = "center";
197
+ } else if (blockName === "<image") {
198
+ align = "left";
199
+ } else if (blockName === ">image") {
200
+ align = "right";
201
+ } else if (blockName === "f<image") {
202
+ align = "left";
203
+ float = true;
204
+ } else if (blockName === "f>image") {
205
+ align = "right";
206
+ float = true;
207
+ } else if (blockName === "f=image") {
208
+ align = "center";
209
+ float = true;
210
+ } else if (blockName === "image") {
211
+ return null;
212
+ }
213
+
214
+ return { align, float };
215
+ }
216
+
217
+ /**
218
+ * Inline rule for parsing `[[image source attributes]]` and its alignment variants.
219
+ *
220
+ * Triggered by a `BLOCK_OPEN` (`[[`) token. The rule identifies the image
221
+ * block name (with optional alignment prefix), extracts the image source,
222
+ * parses remaining attributes, filters unsafe attributes, and extracts the
223
+ * `link` attribute for special handling.
224
+ *
225
+ * Fails if the block name is not an image variant, if no source is provided,
226
+ * or if `]]` is not found.
227
+ */
228
+ export const imageRule: InlineRule = {
229
+ name: "image",
230
+ startTokens: ["BLOCK_OPEN"],
231
+
232
+ /**
233
+ * Attempts to parse an image block at the current position.
234
+ *
235
+ * @param ctx - Parse context with token stream and current position
236
+ * @returns A successful result with an `"image"` element, or `{ success: false }`
237
+ */
238
+ parse(ctx: ParseContext): RuleResult<Element> {
239
+ const openToken = currentToken(ctx);
240
+ if (openToken.type !== "BLOCK_OPEN") {
241
+ return { success: false };
242
+ }
243
+
244
+ let pos = ctx.pos + 1;
245
+ let consumed = 1;
246
+
247
+ const nameResult = parseImageBlockName(ctx, pos);
248
+ if (!nameResult) {
249
+ return { success: false };
250
+ }
251
+
252
+ // Check for image, =image, <image, >image, f<image, f>image, f=image
253
+ const blockName = nameResult.name;
254
+ const imageNames = ["image", "=image", "<image", ">image", "f<image", "f>image", "f=image"];
255
+ if (!imageNames.includes(blockName)) {
256
+ return { success: false };
257
+ }
258
+
259
+ pos += nameResult.consumed;
260
+ consumed += nameResult.consumed;
261
+
262
+ // Skip whitespace
263
+ while (ctx.tokens[pos]?.type === "WHITESPACE") {
264
+ pos++;
265
+ consumed++;
266
+ }
267
+
268
+ // Get image source (collect tokens until whitespace or ]])
269
+ let src = "";
270
+ while (pos < ctx.tokens.length) {
271
+ const srcToken = ctx.tokens[pos];
272
+ if (
273
+ !srcToken ||
274
+ srcToken.type === "WHITESPACE" ||
275
+ srcToken.type === "BLOCK_CLOSE" ||
276
+ srcToken.type === "NEWLINE" ||
277
+ srcToken.type === "EOF"
278
+ ) {
279
+ break;
280
+ }
281
+ src += srcToken.value;
282
+ pos++;
283
+ consumed++;
284
+ }
285
+
286
+ // Parse remaining attributes (raw, before safety filtering)
287
+ const attrResultRaw = parseAttributesRaw(ctx, pos, false);
288
+ pos += attrResultRaw.consumed;
289
+ consumed += attrResultRaw.consumed;
290
+
291
+ // Expect ]]
292
+ if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
293
+ return { success: false };
294
+ }
295
+ pos++;
296
+ consumed++;
297
+
298
+ // Wikidot requires image source - [[image]] without source fails
299
+ if (!src) {
300
+ return { success: false };
301
+ }
302
+
303
+ // Parse source and alignment
304
+ const source = parseImageSource(src);
305
+ const alignment = parseAlignment(blockName);
306
+
307
+ // Extract link before filtering (link is image-specific, not an HTML attribute)
308
+ const linkUrl = attrResultRaw.attrs.link;
309
+ const { link: _link, ...restAttrs } = attrResultRaw.attrs;
310
+ const cleanAttrs = filterUnsafeAttributes(restAttrs);
311
+
312
+ return {
313
+ success: true,
314
+ elements: [
315
+ {
316
+ element: "image",
317
+ data: {
318
+ source,
319
+ link: linkUrl ?? null,
320
+ alignment,
321
+ attributes: cleanAttrs as AttributeMap,
322
+ },
323
+ },
324
+ ],
325
+ consumed,
326
+ };
327
+ },
328
+ };
@@ -0,0 +1,150 @@
1
+ /**
2
+ *
3
+ * Central registry and priority-ordered list of all inline parsing rules.
4
+ *
5
+ * This module imports every inline rule, re-exports them for individual use,
6
+ * and assembles them into the {@link inlineRules} array, which defines the
7
+ * order in which rules are attempted during inline parsing.
8
+ *
9
+ * Rule ordering matters: earlier rules take priority when multiple rules
10
+ * could match the same token. For example, formatting rules (bold, italic,
11
+ * etc.) are tried before link rules, and the text/fallback rules are
12
+ * placed last as catch-alls.
13
+ *
14
+ * The `fallbackRule` is exported separately as `inlineFallbackRule` because
15
+ * it matches any token type and is used as a last resort when no other rule
16
+ * succeeds. It is NOT included in the `inlineRules` array to prevent it
17
+ * from short-circuiting more specific rules.
18
+ *
19
+ * @module
20
+ */
21
+ import type { InlineRule } from "../types";
22
+ import { boldRule } from "./bold";
23
+ import { italicRule } from "./italic";
24
+ import { underlineRule } from "./underline";
25
+ import { strikethroughRule } from "./strikethrough";
26
+ import { superscriptRule } from "./superscript";
27
+ import { subscriptRule } from "./subscript";
28
+ import { monospaceRule } from "./monospace";
29
+ import { linkTripleRule } from "./link-triple";
30
+ import { linkSingleRule } from "./link-single";
31
+ import { linkAnchorRule } from "./link-anchor";
32
+ import { linkStarRule } from "./link-star";
33
+ import { colorRule } from "./color";
34
+ import {
35
+ backslashLineBreakRule,
36
+ newlineLineBreakRule,
37
+ underscoreLineBreakRule,
38
+ } from "./line-break";
39
+ import { commentRule } from "./comment";
40
+ import { htmlInlineRule } from "./html";
41
+ import { rawRule } from "./raw";
42
+ import { spanRule, closeSpanRule } from "./span";
43
+ import { sizeRule } from "./size";
44
+ import { footnoteRule } from "./footnote";
45
+ import { imageRule } from "./image";
46
+ import { guillemetRule } from "./guillemet";
47
+ import { userRule } from "./user";
48
+ import { anchorNameRule } from "./anchor-name";
49
+ import { anchorRule } from "./anchor";
50
+ import { mathInlineRule } from "./math-inline";
51
+ import { equationRefRule } from "./equation-ref";
52
+ import { exprRule, ifRule, ifExprRule } from "./expr";
53
+ import { bibciteRule } from "./bibcite";
54
+ import { textRule, fallbackRule } from "./text";
55
+
56
+ export { boldRule } from "./bold";
57
+ export { italicRule } from "./italic";
58
+ export { underlineRule } from "./underline";
59
+ export { strikethroughRule } from "./strikethrough";
60
+ export { superscriptRule } from "./superscript";
61
+ export { subscriptRule } from "./subscript";
62
+ export { monospaceRule } from "./monospace";
63
+ export { linkTripleRule } from "./link-triple";
64
+ export { linkSingleRule } from "./link-single";
65
+ export { linkAnchorRule } from "./link-anchor";
66
+ export { linkStarRule } from "./link-star";
67
+ export { colorRule } from "./color";
68
+ export {
69
+ backslashLineBreakRule,
70
+ newlineLineBreakRule,
71
+ underscoreLineBreakRule,
72
+ } from "./line-break";
73
+ export { commentRule } from "./comment";
74
+ export { htmlInlineRule } from "./html";
75
+ export { rawRule } from "./raw";
76
+ export { spanRule, closeSpanRule } from "./span";
77
+ export { sizeRule } from "./size";
78
+ export { footnoteRule } from "./footnote";
79
+ export { imageRule } from "./image";
80
+ export { guillemetRule } from "./guillemet";
81
+ export { userRule } from "./user";
82
+ export { anchorNameRule } from "./anchor-name";
83
+ export { anchorRule } from "./anchor";
84
+ export { mathInlineRule } from "./math-inline";
85
+ export { equationRefRule } from "./equation-ref";
86
+ export { exprRule, ifRule, ifExprRule } from "./expr";
87
+ export { bibciteRule } from "./bibcite";
88
+ export { textRule, fallbackRule } from "./text";
89
+
90
+ /**
91
+ * All inline rules in priority order.
92
+ *
93
+ * Rules are tried top-to-bottom against the current token. The first
94
+ * rule whose `startTokens` match the token type and whose `parse()`
95
+ * returns `{ success: true }` wins.
96
+ *
97
+ * Ordering rationale:
98
+ * 1. Paired formatting markers (bold, italic, underline, strikethrough,
99
+ * superscript, subscript, monospace) -- most common inline syntax
100
+ * 2. Link rules (triple, single, anchor, star) -- order matters because
101
+ * `[[[` must be tried before `[`
102
+ * 3. Color, line-break, and comment rules
103
+ * 4. Raw (verbatim) text
104
+ * 5. Block-open-triggered rules (image, size, footnote, span, user,
105
+ * expr/if/ifexpr, anchor-name, anchor, math-inline, equation-ref)
106
+ * 6. Bibcite (double-parenthesis syntax)
107
+ * 7. Guillemet (typographic angle quotes)
108
+ * 8. Text rule (catch-all for TEXT and WHITESPACE tokens)
109
+ *
110
+ * The `fallbackRule` is intentionally excluded; it is used as a
111
+ * separate last-resort handler.
112
+ */
113
+ export const inlineRules: InlineRule[] = [
114
+ boldRule,
115
+ italicRule,
116
+ underlineRule,
117
+ strikethroughRule,
118
+ superscriptRule,
119
+ subscriptRule,
120
+ monospaceRule,
121
+ linkTripleRule,
122
+ linkSingleRule,
123
+ linkAnchorRule,
124
+ linkStarRule,
125
+ colorRule,
126
+ backslashLineBreakRule,
127
+ underscoreLineBreakRule,
128
+ newlineLineBreakRule,
129
+ commentRule,
130
+ htmlInlineRule,
131
+ rawRule,
132
+ imageRule,
133
+ sizeRule,
134
+ footnoteRule,
135
+ spanRule,
136
+ closeSpanRule,
137
+ userRule,
138
+ exprRule,
139
+ ifRule,
140
+ ifExprRule,
141
+ anchorNameRule,
142
+ anchorRule,
143
+ mathInlineRule,
144
+ equationRefRule,
145
+ bibciteRule,
146
+ guillemetRule,
147
+ textRule,
148
+ ];
149
+
150
+ export { fallbackRule as inlineFallbackRule };
@@ -0,0 +1,74 @@
1
+ /**
2
+ *
3
+ * Parses the Wikidot italic formatting syntax: `//text//`.
4
+ *
5
+ * Italic text is delimited by double forward slashes. The opening and
6
+ * closing markers must appear on the same line. If no closing `//` is
7
+ * found before a newline, the opening marker is emitted as literal text.
8
+ *
9
+ * Unlike bold (which discards empty markers), italic markers with empty
10
+ * content (`////`) still produce an italic container, matching Wikidot's
11
+ * behavior.
12
+ *
13
+ * Italic may nest other inline formatting within its body.
14
+ *
15
+ * Produces a `"container"` AST element with `type: "italics"`.
16
+ *
17
+ * @module
18
+ */
19
+ import type { Element } from "@wdprlib/ast";
20
+ import type { InlineRule, ParseContext, RuleResult } from "../types";
21
+ import { currentToken, hasClosingMarkerBeforeNewline } from "../types";
22
+ import { parseInlineUntil } from "./utils";
23
+
24
+ /**
25
+ * Inline rule for parsing `//italic//` formatting.
26
+ *
27
+ * Triggered by an `ITALIC_MARKER` token (`//`). Checks for a matching
28
+ * closing marker on the same line, then recursively parses inline content.
29
+ *
30
+ * When no closing marker is found, the opening `//` is treated as
31
+ * literal text.
32
+ */
33
+ export const italicRule: InlineRule = {
34
+ name: "italic",
35
+ startTokens: ["ITALIC_MARKER"],
36
+
37
+ /**
38
+ * Attempts to parse italic formatting at the current position.
39
+ *
40
+ * @param ctx - Parse context with token stream and current position
41
+ * @returns A successful result containing either a `"container"` element
42
+ * with `type: "italics"`, or a text fallback for unmatched markers
43
+ */
44
+ parse(ctx: ParseContext): RuleResult<Element> {
45
+ const startToken = currentToken(ctx);
46
+
47
+ // Check if closing marker exists
48
+ if (!hasClosingMarkerBeforeNewline({ ...ctx, pos: ctx.pos + 1 }, "ITALIC_MARKER")) {
49
+ return {
50
+ success: true,
51
+ elements: [{ element: "text", data: startToken.value }],
52
+ consumed: 1,
53
+ };
54
+ }
55
+
56
+ // Parse content between markers
57
+ const result = parseInlineUntil({ ...ctx, pos: ctx.pos + 1 }, "ITALIC_MARKER");
58
+
59
+ return {
60
+ success: true,
61
+ elements: [
62
+ {
63
+ element: "container",
64
+ data: {
65
+ type: "italics",
66
+ attributes: {},
67
+ elements: result.elements,
68
+ },
69
+ },
70
+ ],
71
+ consumed: 1 + result.consumed + 1, // open + content + close
72
+ };
73
+ },
74
+ };