@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.
- package/dist/index.cjs +295 -118
- package/dist/index.js +272 -95
- package/package.json +5 -3
- package/src/index.ts +163 -0
- package/src/lexer/index.ts +20 -0
- package/src/lexer/lexer.ts +687 -0
- package/src/lexer/tokens.ts +141 -0
- package/src/parser/constants.ts +173 -0
- package/src/parser/depth.ts +251 -0
- package/src/parser/index.ts +18 -0
- package/src/parser/parse.ts +315 -0
- package/src/parser/postprocess/divAdjacentParagraph.ts +76 -0
- package/src/parser/postprocess/index.ts +15 -0
- package/src/parser/postprocess/spanStrip.ts +697 -0
- package/src/parser/preprocess/expr.ts +265 -0
- package/src/parser/preprocess/index.ts +38 -0
- package/src/parser/preprocess/typography.ts +67 -0
- package/src/parser/preprocess/utils.ts +250 -0
- package/src/parser/preprocess/whitespace.ts +111 -0
- package/src/parser/rules/block/align.ts +282 -0
- package/src/parser/rules/block/bibliography.ts +359 -0
- package/src/parser/rules/block/block-list.ts +689 -0
- package/src/parser/rules/block/blockquote.ts +238 -0
- package/src/parser/rules/block/center.ts +87 -0
- package/src/parser/rules/block/clear-float.ts +75 -0
- package/src/parser/rules/block/code.ts +187 -0
- package/src/parser/rules/block/collapsible.ts +337 -0
- package/src/parser/rules/block/comment.ts +73 -0
- package/src/parser/rules/block/content-separator.ts +79 -0
- package/src/parser/rules/block/definition-list.ts +270 -0
- package/src/parser/rules/block/div.ts +400 -0
- package/src/parser/rules/block/embed-block.ts +153 -0
- package/src/parser/rules/block/footnoteblock.ts +200 -0
- package/src/parser/rules/block/heading.ts +142 -0
- package/src/parser/rules/block/horizontal-rule.ts +61 -0
- package/src/parser/rules/block/html.ts +222 -0
- package/src/parser/rules/block/iframe.ts +239 -0
- package/src/parser/rules/block/iftags.ts +150 -0
- package/src/parser/rules/block/include.ts +179 -0
- package/src/parser/rules/block/index.ts +127 -0
- package/src/parser/rules/block/list.ts +244 -0
- package/src/parser/rules/block/math.ts +183 -0
- package/src/parser/rules/block/module/backlinks/index.ts +31 -0
- package/src/parser/rules/block/module/backlinks/types.ts +21 -0
- package/src/parser/rules/block/module/categories/index.ts +34 -0
- package/src/parser/rules/block/module/categories/types.ts +21 -0
- package/src/parser/rules/block/module/css/index.ts +37 -0
- package/src/parser/rules/block/module/iftags/condition.ts +109 -0
- package/src/parser/rules/block/module/iftags/index.ts +26 -0
- package/src/parser/rules/block/module/iftags/preprocess.ts +140 -0
- package/src/parser/rules/block/module/iftags/resolve.ts +73 -0
- package/src/parser/rules/block/module/iftags/types.ts +63 -0
- package/src/parser/rules/block/module/include/index.ts +20 -0
- package/src/parser/rules/block/module/include/resolve.ts +556 -0
- package/src/parser/rules/block/module/index.ts +122 -0
- package/src/parser/rules/block/module/join/index.ts +34 -0
- package/src/parser/rules/block/module/join/types.ts +23 -0
- package/src/parser/rules/block/module/listpages/compiler.ts +453 -0
- package/src/parser/rules/block/module/listpages/extract.ts +410 -0
- package/src/parser/rules/block/module/listpages/index.ts +83 -0
- package/src/parser/rules/block/module/listpages/normalize.ts +390 -0
- package/src/parser/rules/block/module/listpages/parser.ts +106 -0
- package/src/parser/rules/block/module/listpages/resolve.ts +130 -0
- package/src/parser/rules/block/module/listpages/types.ts +513 -0
- package/src/parser/rules/block/module/listpages/url-resolver.ts +186 -0
- package/src/parser/rules/block/module/listusers/compiler.ts +77 -0
- package/src/parser/rules/block/module/listusers/extract.ts +45 -0
- package/src/parser/rules/block/module/listusers/index.ts +36 -0
- package/src/parser/rules/block/module/listusers/parser.ts +54 -0
- package/src/parser/rules/block/module/listusers/resolve.ts +58 -0
- package/src/parser/rules/block/module/listusers/types.ts +93 -0
- package/src/parser/rules/block/module/mapping.ts +61 -0
- package/src/parser/rules/block/module/page-tree/index.ts +38 -0
- package/src/parser/rules/block/module/page-tree/types.ts +29 -0
- package/src/parser/rules/block/module/rate/index.ts +28 -0
- package/src/parser/rules/block/module/rate/types.ts +19 -0
- package/src/parser/rules/block/module/resolve.ts +411 -0
- package/src/parser/rules/block/module/types-common.ts +59 -0
- package/src/parser/rules/block/module/types.ts +61 -0
- package/src/parser/rules/block/module/utils.ts +43 -0
- package/src/parser/rules/block/module/walk.ts +380 -0
- package/src/parser/rules/block/module.ts +164 -0
- package/src/parser/rules/block/orphan-li.ts +177 -0
- package/src/parser/rules/block/paragraph.ts +157 -0
- package/src/parser/rules/block/table-block.ts +726 -0
- package/src/parser/rules/block/table.ts +441 -0
- package/src/parser/rules/block/tabview.ts +331 -0
- package/src/parser/rules/block/toc.ts +129 -0
- package/src/parser/rules/block/utils.ts +615 -0
- package/src/parser/rules/index.ts +49 -0
- package/src/parser/rules/inline/anchor-name.ts +154 -0
- package/src/parser/rules/inline/anchor.ts +327 -0
- package/src/parser/rules/inline/bibcite.ts +153 -0
- package/src/parser/rules/inline/bold.ts +86 -0
- package/src/parser/rules/inline/color.ts +140 -0
- package/src/parser/rules/inline/comment.ts +90 -0
- package/src/parser/rules/inline/equation-ref.ts +115 -0
- package/src/parser/rules/inline/expr.ts +526 -0
- package/src/parser/rules/inline/footnote.ts +223 -0
- package/src/parser/rules/inline/guillemet.ts +64 -0
- package/src/parser/rules/inline/html.ts +132 -0
- package/src/parser/rules/inline/image.ts +328 -0
- package/src/parser/rules/inline/index.ts +150 -0
- package/src/parser/rules/inline/italic.ts +74 -0
- package/src/parser/rules/inline/line-break.ts +326 -0
- package/src/parser/rules/inline/link-anchor.ts +147 -0
- package/src/parser/rules/inline/link-single.ts +164 -0
- package/src/parser/rules/inline/link-star.ts +134 -0
- package/src/parser/rules/inline/link-triple.ts +267 -0
- package/src/parser/rules/inline/math-inline.ts +126 -0
- package/src/parser/rules/inline/monospace.ts +78 -0
- package/src/parser/rules/inline/raw.ts +262 -0
- package/src/parser/rules/inline/size.ts +244 -0
- package/src/parser/rules/inline/span.ts +424 -0
- package/src/parser/rules/inline/strikethrough.ts +115 -0
- package/src/parser/rules/inline/subscript.ts +84 -0
- package/src/parser/rules/inline/superscript.ts +84 -0
- package/src/parser/rules/inline/text.ts +84 -0
- package/src/parser/rules/inline/underline.ts +127 -0
- package/src/parser/rules/inline/user.ts +147 -0
- package/src/parser/rules/inline/utils.ts +344 -0
- package/src/parser/rules/types.ts +252 -0
- package/src/parser/rules/utils.ts +155 -0
- 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
|
+
};
|