@wdprlib/parser 5.1.6 → 5.3.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 +764 -452
- package/dist/index.d.cts +24 -4
- package/dist/index.d.ts +24 -4
- package/dist/index.js +762 -452
- package/package.json +2 -2
- package/src/build-info.generated.ts +11 -0
- package/src/index.ts +2 -0
- package/src/lexer/lexer.ts +6 -4
- package/src/lexer/punctuation.ts +2 -1
- package/src/lexer/spacing-actions.ts +15 -0
- package/src/lexer/state.ts +30 -4
- package/src/lexer/token-factory.ts +15 -2
- package/src/lexer/tokens.ts +4 -3
- package/src/parser/rules/block/blockquote/build.ts +81 -37
- package/src/parser/rules/block/blockquote/index.ts +5 -6
- package/src/parser/rules/block/blockquote/line.ts +11 -10
- package/src/parser/rules/block/blockquote/lines.ts +102 -4
- package/src/parser/rules/block/code/content.ts +0 -3
- package/src/parser/rules/block/code/index.ts +14 -10
- package/src/parser/rules/block/definition-list/index.ts +4 -10
- package/src/parser/rules/block/definition-list/item-value.ts +2 -11
- package/src/parser/rules/block/definition-list/items.ts +1 -2
- package/src/parser/rules/block/embed-block/index.ts +30 -30
- package/src/parser/rules/block/heading/index.ts +7 -0
- package/src/parser/rules/block/module/listpages/resolution/items.ts +2 -2
- package/src/parser/rules/block/module/listpages/resolution/pager.ts +43 -0
- package/src/parser/rules/block/module/listpages/resolution/wrapper.ts +6 -1
- package/src/parser/rules/block/module/listpages/resolve.ts +1 -1
- package/src/parser/rules/block/module/listpages/types/external-data.ts +13 -0
- package/src/parser/rules/block/module/listpages/url-resolution/params.ts +5 -4
- package/src/parser/rules/block/module/resolution/data-maps.ts +75 -3
- package/src/parser/rules/block/module/resolution/resolve-async.ts +12 -3
- package/src/parser/rules/block/paragraph/index.ts +41 -10
- package/src/parser/rules/block/parsing/content.ts +18 -1
- package/src/parser/rules/block/table/pipe/cell.ts +36 -1
- package/src/parser/rules/block/table/pipe/row.ts +21 -1
- package/src/parser/rules/block/toc/element.ts +2 -2
- package/src/parser/rules/block/toc/index.ts +2 -2
- package/src/parser/rules/block/toc/open.ts +5 -18
- package/src/parser/rules/contracts/scope.ts +4 -0
- package/src/parser/rules/inline/bold.ts +5 -5
- package/src/parser/rules/inline/color/syntax.ts +5 -8
- package/src/parser/rules/inline/formatting/close.ts +39 -0
- package/src/parser/rules/inline/formatting/container.ts +7 -4
- package/src/parser/rules/inline/index.ts +2 -0
- package/src/parser/rules/inline/italic.ts +5 -5
- package/src/parser/rules/inline/monospace.ts +5 -5
- package/src/parser/rules/inline/parsing/block-start-predicates.ts +2 -0
- package/src/parser/rules/inline/parsing/inline-content.ts +58 -4
- package/src/parser/rules/inline/raw/end.ts +28 -0
- package/src/parser/rules/inline/span/content.ts +32 -1
- package/src/parser/rules/inline/strikethrough/index.ts +1 -1
- package/src/parser/rules/inline/strikethrough/parse.ts +2 -9
- package/src/parser/rules/inline/strikethrough/syntax.ts +3 -20
- package/src/parser/rules/inline/subscript.ts +5 -5
- package/src/parser/rules/inline/superscript.ts +5 -5
- package/src/parser/rules/inline/underline/index.ts +5 -78
- package/src/parser/rules/tokens.ts +6 -15
- package/src/pipeline/process.ts +8 -1
- package/src/parser/rules/inline/underline/child.ts +0 -26
- package/src/parser/rules/inline/underline/content.ts +0 -29
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Alignment } from "@wdprlib/ast";
|
|
2
2
|
import type { ParseContext } from "../../types";
|
|
3
|
+
import { parseAttributes } from "../utils";
|
|
3
4
|
|
|
4
5
|
export interface TocOpenResult {
|
|
5
6
|
align: Alignment | null;
|
|
7
|
+
title: string | undefined;
|
|
6
8
|
consumed: number;
|
|
7
9
|
}
|
|
8
10
|
|
|
@@ -48,7 +50,8 @@ export function parseTocOpen(ctx: ParseContext, startPos: number): TocOpenResult
|
|
|
48
50
|
return null;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
const attributes = parseAttributes(ctx, pos);
|
|
54
|
+
pos += attributes.consumed;
|
|
52
55
|
|
|
53
56
|
if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
|
|
54
57
|
return null;
|
|
@@ -56,27 +59,11 @@ export function parseTocOpen(ctx: ParseContext, startPos: number): TocOpenResult
|
|
|
56
59
|
|
|
57
60
|
return {
|
|
58
61
|
align,
|
|
62
|
+
title: attributes.attrs.title,
|
|
59
63
|
consumed: pos + 1 - startPos,
|
|
60
64
|
};
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
function skipUntilClose(ctx: ParseContext, startPos: number): number {
|
|
64
|
-
let pos = startPos;
|
|
65
|
-
while (pos < ctx.tokens.length) {
|
|
66
|
-
const token = ctx.tokens[pos];
|
|
67
|
-
if (
|
|
68
|
-
!token ||
|
|
69
|
-
token.type === "BLOCK_CLOSE" ||
|
|
70
|
-
token.type === "NEWLINE" ||
|
|
71
|
-
token.type === "EOF"
|
|
72
|
-
) {
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
75
|
-
pos++;
|
|
76
|
-
}
|
|
77
|
-
return pos;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
67
|
function isNameToken(
|
|
81
68
|
token: ParseContext["tokens"][number] | undefined,
|
|
82
69
|
): token is ParseContext["tokens"][number] & { type: "TEXT" | "IDENTIFIER" } {
|
|
@@ -8,6 +8,10 @@ import type { ParseContext } from "./parse-context";
|
|
|
8
8
|
* expressed as a replacement: `ctx.scope = { ...ctx.scope, X: ... }`.
|
|
9
9
|
*/
|
|
10
10
|
export interface ScopeContext {
|
|
11
|
+
/** Exclusive token boundary inherited by nested inline rules. */
|
|
12
|
+
readonly inlineEnd?: number;
|
|
13
|
+
/** Closing delimiters paired across cells of the current pipe table. */
|
|
14
|
+
readonly tableFormatting?: { end: number; suppressedClosers: Set<number> };
|
|
11
15
|
/**
|
|
12
16
|
* Close condition for the current block. The paragraph parser calls
|
|
13
17
|
* it to decide when to stop collecting inline content.
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Parses the Wikidot bold formatting syntax: `**text**`.
|
|
4
4
|
*
|
|
5
5
|
* Bold text is delimited by double asterisks. The opening and closing
|
|
6
|
-
* markers must appear
|
|
7
|
-
* before a
|
|
6
|
+
* markers must appear within the same paragraph; if no closing `**` is found
|
|
7
|
+
* before a block boundary, the opening marker is emitted as literal text.
|
|
8
8
|
*
|
|
9
9
|
* Wikidot behavior for empty bold (`****`): the markers and their
|
|
10
10
|
* (empty) content are discarded entirely, producing no output.
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import type { Element } from "@wdprlib/ast";
|
|
20
20
|
import type { InlineRule, ParseContext, RuleResult } from "../types";
|
|
21
|
-
import {
|
|
21
|
+
import { parseDelimitedContainer } from "./formatting/container";
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Inline rule for parsing `**bold**` formatting.
|
|
25
25
|
*
|
|
26
26
|
* Triggered by a `BOLD_MARKER` token (`**`). The rule checks for a
|
|
27
|
-
* matching closing marker
|
|
27
|
+
* matching closing marker within the same paragraph, then recursively parses
|
|
28
28
|
* inline content between the markers.
|
|
29
29
|
*
|
|
30
30
|
* When no closing marker is found, the opening `**` is treated as
|
|
@@ -44,6 +44,6 @@ export const boldRule: InlineRule = {
|
|
|
44
44
|
* fallback for unmatched markers
|
|
45
45
|
*/
|
|
46
46
|
parse(ctx: ParseContext): RuleResult<Element> {
|
|
47
|
-
return
|
|
47
|
+
return parseDelimitedContainer(ctx, "BOLD_MARKER", "bold", { discardEmpty: true });
|
|
48
48
|
},
|
|
49
49
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Element } from "@wdprlib/ast";
|
|
2
2
|
import type { ParseContext } from "../../types";
|
|
3
|
-
import {
|
|
3
|
+
import { findFormattingClose, consumeFormattingClose } from "../formatting/close";
|
|
4
4
|
import { parseInlineUntil } from "../utils";
|
|
5
5
|
|
|
6
6
|
export interface ColorContent {
|
|
@@ -10,7 +10,8 @@ export interface ColorContent {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function parseColorContent(ctx: ParseContext): ColorContent | null {
|
|
13
|
-
|
|
13
|
+
const close = findFormattingClose(ctx, ctx.pos + 1, "COLOR_MARKER");
|
|
14
|
+
if (close === null) {
|
|
14
15
|
return null;
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -18,7 +19,7 @@ export function parseColorContent(ctx: ParseContext): ColorContent | null {
|
|
|
18
19
|
let consumed = 1;
|
|
19
20
|
let colorSpec = "";
|
|
20
21
|
|
|
21
|
-
while (pos < ctx.tokens.length) {
|
|
22
|
+
while (pos < (ctx.scope.inlineEnd ?? ctx.tokens.length)) {
|
|
22
23
|
const token = ctx.tokens[pos];
|
|
23
24
|
if (
|
|
24
25
|
!token ||
|
|
@@ -44,16 +45,12 @@ export function parseColorContent(ctx: ParseContext): ColorContent | null {
|
|
|
44
45
|
pos += contentResult.consumed;
|
|
45
46
|
consumed += contentResult.consumed;
|
|
46
47
|
|
|
47
|
-
if (ctx.tokens[pos]?.type !== "COLOR_MARKER") {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
consumed++;
|
|
51
|
-
|
|
52
48
|
const color = colorSpec.trim();
|
|
53
49
|
if (color === "" || contentResult.elements.length === 0) {
|
|
54
50
|
return null;
|
|
55
51
|
}
|
|
56
52
|
|
|
53
|
+
consumed += consumeFormattingClose(ctx, close, pos);
|
|
57
54
|
return {
|
|
58
55
|
color: hexifyColor(color),
|
|
59
56
|
elements: contentResult.elements,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { protectedInlineRegionEnd } from "../raw/end";
|
|
2
|
+
import type { TokenType } from "../../../../lexer";
|
|
3
|
+
import type { ParseContext } from "../../types";
|
|
4
|
+
import { getParagraphNewlineBoundary } from "../parsing/paragraph-boundary";
|
|
5
|
+
|
|
6
|
+
export function findFormattingClose(
|
|
7
|
+
ctx: ParseContext,
|
|
8
|
+
start: number,
|
|
9
|
+
marker: TokenType,
|
|
10
|
+
): number | null {
|
|
11
|
+
const table = ctx.scope.tableFormatting;
|
|
12
|
+
const end = table?.end ?? ctx.scope.inlineEnd ?? ctx.tokens.length;
|
|
13
|
+
for (let pos = start; pos < end; pos++) {
|
|
14
|
+
const token = ctx.tokens[pos];
|
|
15
|
+
if (!token || token.type === "EOF" || ctx.scope.blockCloseCondition?.({ ...ctx, pos }))
|
|
16
|
+
return null;
|
|
17
|
+
if (
|
|
18
|
+
!table &&
|
|
19
|
+
token.type === "NEWLINE" &&
|
|
20
|
+
getParagraphNewlineBoundary(ctx, pos, true).shouldBreak
|
|
21
|
+
)
|
|
22
|
+
return null;
|
|
23
|
+
if (token.type === marker && !table?.suppressedClosers.has(pos)) return pos;
|
|
24
|
+
const protectedEnd = protectedInlineRegionEnd(ctx.tokens, pos, end);
|
|
25
|
+
if (protectedEnd > pos) pos = protectedEnd - 1;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function consumeFormattingClose(
|
|
31
|
+
ctx: ParseContext,
|
|
32
|
+
close: number,
|
|
33
|
+
contentEnd: number,
|
|
34
|
+
): number {
|
|
35
|
+
if (close === contentEnd) return 1;
|
|
36
|
+
// Wikidot closes formatting at the cell edge and suppresses its later delimiter.
|
|
37
|
+
ctx.scope.tableFormatting?.suppressedClosers.add(close);
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Element, StringContainerType } from "@wdprlib/ast";
|
|
2
2
|
import type { TokenType } from "../../../../lexer";
|
|
3
3
|
import type { ParseContext, RuleResult } from "../../types";
|
|
4
|
-
import { currentToken
|
|
4
|
+
import { currentToken } from "../../types";
|
|
5
|
+
import { findFormattingClose, consumeFormattingClose } from "./close";
|
|
5
6
|
import { parseInlineUntil } from "../utils";
|
|
6
7
|
|
|
7
8
|
export function createInlineContainer(type: StringContainerType, elements: Element[]): Element {
|
|
@@ -15,7 +16,7 @@ export function createInlineContainer(type: StringContainerType, elements: Eleme
|
|
|
15
16
|
};
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
export function
|
|
19
|
+
export function parseDelimitedContainer(
|
|
19
20
|
ctx: ParseContext,
|
|
20
21
|
closeToken: TokenType,
|
|
21
22
|
type: StringContainerType,
|
|
@@ -23,7 +24,8 @@ export function parseSameLineDelimitedContainer(
|
|
|
23
24
|
): RuleResult<Element> {
|
|
24
25
|
const startToken = currentToken(ctx);
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
const close = findFormattingClose(ctx, ctx.pos + 1, closeToken);
|
|
28
|
+
if (close === null) {
|
|
27
29
|
return {
|
|
28
30
|
success: true,
|
|
29
31
|
elements: [{ element: "text", data: startToken.value }],
|
|
@@ -32,7 +34,8 @@ export function parseSameLineDelimitedContainer(
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
const result = parseInlineUntil({ ...ctx, pos: ctx.pos + 1 }, closeToken);
|
|
35
|
-
const consumed =
|
|
37
|
+
const consumed =
|
|
38
|
+
1 + result.consumed + consumeFormattingClose(ctx, close, ctx.pos + 1 + result.consumed);
|
|
36
39
|
|
|
37
40
|
if (options.discardEmpty === true && result.elements.length === 0) {
|
|
38
41
|
return {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { embedBlockRule } from "../block/embed-block";
|
|
1
2
|
/**
|
|
2
3
|
*
|
|
3
4
|
* Central registry and priority-ordered list of all inline parsing rules.
|
|
@@ -130,6 +131,7 @@ export const inlineRules: InlineRule[] = [
|
|
|
130
131
|
htmlInlineRule,
|
|
131
132
|
rawRule,
|
|
132
133
|
imageRule,
|
|
134
|
+
embedBlockRule,
|
|
133
135
|
sizeRule,
|
|
134
136
|
footnoteRule,
|
|
135
137
|
spanRule,
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Parses the Wikidot italic formatting syntax: `//text//`.
|
|
4
4
|
*
|
|
5
5
|
* Italic text is delimited by double forward slashes. The opening and
|
|
6
|
-
* closing markers must appear
|
|
7
|
-
* found before a
|
|
6
|
+
* closing markers must appear within the same paragraph. If no closing `//` is
|
|
7
|
+
* found before a block boundary, the opening marker is emitted as literal text.
|
|
8
8
|
*
|
|
9
9
|
* Unlike bold (which discards empty markers), italic markers with empty
|
|
10
10
|
* content (`////`) still produce an italic container, matching Wikidot's
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import type { Element } from "@wdprlib/ast";
|
|
20
20
|
import type { InlineRule, ParseContext, RuleResult } from "../types";
|
|
21
|
-
import {
|
|
21
|
+
import { parseDelimitedContainer } from "./formatting/container";
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Inline rule for parsing `//italic//` formatting.
|
|
25
25
|
*
|
|
26
26
|
* Triggered by an `ITALIC_MARKER` token (`//`). Checks for a matching
|
|
27
|
-
* closing marker
|
|
27
|
+
* closing marker within the same paragraph, then recursively parses inline content.
|
|
28
28
|
*
|
|
29
29
|
* When no closing marker is found, the opening `//` is treated as
|
|
30
30
|
* literal text.
|
|
@@ -41,6 +41,6 @@ export const italicRule: InlineRule = {
|
|
|
41
41
|
* with `type: "italics"`, or a text fallback for unmatched markers
|
|
42
42
|
*/
|
|
43
43
|
parse(ctx: ParseContext): RuleResult<Element> {
|
|
44
|
-
return
|
|
44
|
+
return parseDelimitedContainer(ctx, "ITALIC_MARKER", "italics");
|
|
45
45
|
},
|
|
46
46
|
};
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Parses the Wikidot monospace (teletype) formatting syntax: `{{text}}`.
|
|
4
4
|
*
|
|
5
5
|
* Monospace text is delimited by double curly braces. The opening and
|
|
6
|
-
* closing markers must appear
|
|
7
|
-
* is found before a
|
|
6
|
+
* closing markers must appear within the same paragraph. If no closing `}}`
|
|
7
|
+
* is found before a block boundary, the opening marker is emitted as literal text.
|
|
8
8
|
*
|
|
9
9
|
* Note: the opening marker is `MONO_MARKER` (`{{`) and the closing marker
|
|
10
10
|
* is `MONO_CLOSE` (`}}`). These are distinct token types because `{` and
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import type { Element } from "@wdprlib/ast";
|
|
23
23
|
import type { InlineRule, ParseContext, RuleResult } from "../types";
|
|
24
|
-
import {
|
|
24
|
+
import { parseDelimitedContainer } from "./formatting/container";
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Inline rule for parsing `{{monospace}}` formatting.
|
|
28
28
|
*
|
|
29
29
|
* Triggered by a `MONO_MARKER` token (`{{`). Checks for a matching
|
|
30
|
-
* `MONO_CLOSE` (`}}`)
|
|
30
|
+
* `MONO_CLOSE` (`}}`) within the same paragraph, then recursively parses
|
|
31
31
|
* inline content between the markers.
|
|
32
32
|
*
|
|
33
33
|
* When no closing marker is found, the opening `{{` is treated as
|
|
@@ -45,6 +45,6 @@ export const monospaceRule: InlineRule = {
|
|
|
45
45
|
* with `type: "monospace"`, or a text fallback for unmatched markers
|
|
46
46
|
*/
|
|
47
47
|
parse(ctx: ParseContext): RuleResult<Element> {
|
|
48
|
-
return
|
|
48
|
+
return parseDelimitedContainer(ctx, "MONO_CLOSE", "monospace");
|
|
49
49
|
},
|
|
50
50
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseImageOpen } from "../image/open";
|
|
1
2
|
import type { ParseContext } from "../../types";
|
|
2
3
|
import { BLOCK_START_TOKEN_SET } from "../../../constants";
|
|
3
4
|
import {
|
|
@@ -26,6 +27,7 @@ export function isParagraphBreakingBlockStart(
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
return (
|
|
30
|
+
!parseImageOpen({ ...ctx, pos: nextPos }) &&
|
|
29
31
|
!isOrphanCloseSpan(ctx, nextPos) &&
|
|
30
32
|
!isAnchorName(ctx, nextPos) &&
|
|
31
33
|
!isInvalidBlockOpen(ctx, nextPos) &&
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { protectedInlineRegionEnd } from "../raw/end";
|
|
1
2
|
import type { Element } from "@wdprlib/ast";
|
|
2
3
|
import type { ParseContext } from "../../types";
|
|
3
4
|
import {
|
|
@@ -32,31 +33,53 @@ export function parseInlineUntil(ctx: ParseContext, endType: InlineEndType): Inl
|
|
|
32
33
|
let pos = ctx.pos;
|
|
33
34
|
|
|
34
35
|
const paragraphMode = endType === "PARAGRAPH_BREAK";
|
|
36
|
+
const multiline = paragraphMode || FORMATTING_CLOSE_TOKENS.has(endType);
|
|
37
|
+
let inlineEnd = ctx.scope.inlineEnd ?? ctx.tokens.length;
|
|
38
|
+
if (!multiline) {
|
|
39
|
+
for (let end = ctx.pos; end < inlineEnd; end++) {
|
|
40
|
+
const protectedEnd = protectedInlineRegionEnd(ctx.tokens, end, inlineEnd);
|
|
41
|
+
if (protectedEnd > end) {
|
|
42
|
+
end = protectedEnd - 1;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (
|
|
46
|
+
ctx.tokens[end]?.type === "NEWLINE" &&
|
|
47
|
+
ctx.tokens[end - 1]?.type === "UNDERSCORE" &&
|
|
48
|
+
ctx.tokens[end - 2]?.type === "WHITESPACE"
|
|
49
|
+
)
|
|
50
|
+
continue;
|
|
51
|
+
if (ctx.tokens[end]?.type === "NEWLINE" || ctx.tokens[end]?.type === endType) {
|
|
52
|
+
inlineEnd = end;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
35
57
|
const { inlineRules } = ctx;
|
|
36
58
|
const inlineCtx: ParseContext = {
|
|
37
59
|
...ctx,
|
|
38
60
|
pos,
|
|
61
|
+
scope: { ...ctx.scope, inlineEnd },
|
|
39
62
|
};
|
|
40
63
|
const canCollectLongPlainTextRuns = ctx.tokens.length >= MIN_INLINE_TEXT_RUN_DOCUMENT_TOKENS;
|
|
41
64
|
|
|
42
|
-
while (pos <
|
|
65
|
+
while (pos < inlineEnd) {
|
|
43
66
|
const token = ctx.tokens[pos];
|
|
44
67
|
if (!token || token.type === "EOF") {
|
|
45
68
|
break;
|
|
46
69
|
}
|
|
47
70
|
|
|
48
|
-
if (
|
|
71
|
+
if (ctx.scope.blockCloseCondition) {
|
|
49
72
|
const checkCtx: ParseContext = { ...ctx, pos };
|
|
50
73
|
if (ctx.scope.blockCloseCondition(checkCtx)) {
|
|
51
74
|
break;
|
|
52
75
|
}
|
|
53
76
|
}
|
|
54
77
|
|
|
55
|
-
if (!
|
|
78
|
+
if (!multiline && token.type === "NEWLINE") {
|
|
56
79
|
break;
|
|
57
80
|
}
|
|
58
81
|
|
|
59
|
-
if (
|
|
82
|
+
if (multiline && token.type === "NEWLINE" && !ctx.scope.tableFormatting) {
|
|
60
83
|
const boundary = getParagraphNewlineBoundary(ctx, pos, nodes.length > 0);
|
|
61
84
|
if (boundary.shouldBreak) {
|
|
62
85
|
if (boundary.preservePrecedingLineBreak) {
|
|
@@ -68,6 +91,12 @@ export function parseInlineUntil(ctx: ParseContext, endType: InlineEndType): Inl
|
|
|
68
91
|
}
|
|
69
92
|
}
|
|
70
93
|
|
|
94
|
+
if (ctx.scope.tableFormatting?.suppressedClosers.has(pos)) {
|
|
95
|
+
pos++;
|
|
96
|
+
consumed++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
71
100
|
if (token.type === endType) {
|
|
72
101
|
break;
|
|
73
102
|
}
|
|
@@ -96,6 +125,20 @@ export function parseInlineUntil(ctx: ParseContext, endType: InlineEndType): Inl
|
|
|
96
125
|
for (const rule of getCandidateInlineRules(inlineRules, token.type)) {
|
|
97
126
|
const result = rule.parse(inlineCtx);
|
|
98
127
|
if (result.success) {
|
|
128
|
+
if (rule.name === "comment") {
|
|
129
|
+
let after = pos + result.consumed;
|
|
130
|
+
while (ctx.tokens[after]?.type === "WHITESPACE") after++;
|
|
131
|
+
if (ctx.tokens[after]?.type === "NEWLINE" || ctx.tokens[after]?.type === "EOF") {
|
|
132
|
+
while (nodes.at(-1)?.element === "text") {
|
|
133
|
+
const last = nodes.at(-1)!;
|
|
134
|
+
if (last.element !== "text") break;
|
|
135
|
+
last.data = last.data.trimEnd();
|
|
136
|
+
if (last.data) break;
|
|
137
|
+
nodes.pop();
|
|
138
|
+
}
|
|
139
|
+
if (nodes.at(-1)?.element === "line-break") nodes.pop();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
99
142
|
nodes.push(...result.elements);
|
|
100
143
|
consumed += result.consumed;
|
|
101
144
|
pos += result.consumed;
|
|
@@ -113,3 +156,14 @@ export function parseInlineUntil(ctx: ParseContext, endType: InlineEndType): Inl
|
|
|
113
156
|
|
|
114
157
|
return { elements: nodes, consumed };
|
|
115
158
|
}
|
|
159
|
+
|
|
160
|
+
const FORMATTING_CLOSE_TOKENS: ReadonlySet<string> = new Set([
|
|
161
|
+
"BOLD_MARKER",
|
|
162
|
+
"ITALIC_MARKER",
|
|
163
|
+
"UNDERLINE_MARKER",
|
|
164
|
+
"STRIKE_MARKER",
|
|
165
|
+
"SUPER_MARKER",
|
|
166
|
+
"SUB_MARKER",
|
|
167
|
+
"MONO_CLOSE",
|
|
168
|
+
"COLOR_MARKER",
|
|
169
|
+
]);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Token } from "../../../../lexer";
|
|
2
|
+
|
|
3
|
+
/** Exclusive end of a complete single-line raw region, or the original position. */
|
|
4
|
+
export function rawRegionEnd(tokens: readonly Token[], start: number, end: number): number {
|
|
5
|
+
const type = tokens[start]?.type;
|
|
6
|
+
const close =
|
|
7
|
+
type === "RAW_OPEN" ? "RAW_OPEN" : type === "RAW_BLOCK_OPEN" ? "RAW_BLOCK_CLOSE" : null;
|
|
8
|
+
if (!close) return start;
|
|
9
|
+
for (let pos = start + 1; pos < end; pos++) {
|
|
10
|
+
if (tokens[pos]?.type === "NEWLINE" || tokens[pos]?.type === "EOF") break;
|
|
11
|
+
if (tokens[pos]?.type === close) return pos + 1;
|
|
12
|
+
}
|
|
13
|
+
return start;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Raw text and complete comments shield delimiters from their enclosing rule. */
|
|
17
|
+
export function protectedInlineRegionEnd(
|
|
18
|
+
tokens: readonly Token[],
|
|
19
|
+
start: number,
|
|
20
|
+
end: number,
|
|
21
|
+
): number {
|
|
22
|
+
const rawEnd = rawRegionEnd(tokens, start, end);
|
|
23
|
+
if (rawEnd > start || tokens[start]?.type !== "COMMENT_OPEN") return rawEnd;
|
|
24
|
+
for (let pos = start + 1; pos < end; pos++) {
|
|
25
|
+
if (tokens[pos]?.type === "COMMENT_CLOSE") return pos + 1;
|
|
26
|
+
}
|
|
27
|
+
return start;
|
|
28
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { rawRegionEnd } from "../raw/end";
|
|
1
2
|
import type { Element } from "@wdprlib/ast";
|
|
2
3
|
import type { ParseContext } from "../../types";
|
|
3
4
|
import { inlineRules } from "../../index";
|
|
@@ -24,16 +25,21 @@ export function parseSpanContent(
|
|
|
24
25
|
const escapedChildren: Element[] = [];
|
|
25
26
|
const splitSpans: Element[][] = [];
|
|
26
27
|
let foundClose = false;
|
|
28
|
+
let forcedClose = false;
|
|
27
29
|
let afterBlankLine = false;
|
|
28
30
|
let consumed = 0;
|
|
29
31
|
let pos = startPos;
|
|
30
32
|
|
|
31
|
-
while (pos < ctx.tokens.length) {
|
|
33
|
+
while (pos < (ctx.scope.inlineEnd ?? ctx.tokens.length)) {
|
|
32
34
|
const token = ctx.tokens[pos];
|
|
33
35
|
if (!token || token.type === "EOF") {
|
|
34
36
|
break;
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
if (ctx.scope.tableFormatting?.suppressedClosers.has(pos)) {
|
|
40
|
+
forcedClose = true;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
37
43
|
const close = parseCloseSpan(ctx, pos);
|
|
38
44
|
if (close.success) {
|
|
39
45
|
pos += close.consumed;
|
|
@@ -70,6 +76,31 @@ export function parseSpanContent(
|
|
|
70
76
|
consumed += parsed.consumed;
|
|
71
77
|
}
|
|
72
78
|
|
|
79
|
+
if (!foundClose && ctx.scope.tableFormatting && (pos === ctx.scope.inlineEnd || forcedClose)) {
|
|
80
|
+
let depth = 0;
|
|
81
|
+
for (let next = pos; next < ctx.scope.tableFormatting.end; next++) {
|
|
82
|
+
const rawEnd = rawRegionEnd(ctx.tokens, next, ctx.scope.tableFormatting.end);
|
|
83
|
+
if (rawEnd > next) {
|
|
84
|
+
next = rawEnd - 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (
|
|
88
|
+
ctx.tokens[next]?.type === "BLOCK_OPEN" &&
|
|
89
|
+
/^span_?$/i.test(ctx.tokens[next + 1]?.value ?? "")
|
|
90
|
+
)
|
|
91
|
+
depth++;
|
|
92
|
+
const close = parseCloseSpan(ctx, next);
|
|
93
|
+
if (!close.success) continue;
|
|
94
|
+
if (depth > 0) {
|
|
95
|
+
depth--;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
for (let offset = 0; offset < close.consumed; offset++)
|
|
99
|
+
ctx.scope.tableFormatting.suppressedClosers.add(next + offset);
|
|
100
|
+
foundClose = true;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
73
104
|
return { children, escapedChildren, splitSpans, consumed, foundClose };
|
|
74
105
|
}
|
|
75
106
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* token has dual meaning in Wikidot: it can be either a strikethrough
|
|
7
7
|
* marker or an em-dash. The disambiguation rule is:
|
|
8
8
|
*
|
|
9
|
-
* - If a matching closing `--` is found
|
|
9
|
+
* - If a matching closing `--` is found within the same paragraph AND the closing
|
|
10
10
|
* marker is NOT preceded by whitespace, it is treated as strikethrough.
|
|
11
11
|
* - Otherwise, the `--` is converted to an em-dash character (U+2014).
|
|
12
12
|
*
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import type { Element } from "@wdprlib/ast";
|
|
2
2
|
import type { ParseContext, RuleResult } from "../../types";
|
|
3
|
-
import {
|
|
4
|
-
import { parseInlineUntil } from "../utils";
|
|
3
|
+
import { parseDelimitedContainer } from "../formatting/container";
|
|
5
4
|
|
|
6
5
|
export function parseStrikethroughContent(ctx: ParseContext): RuleResult<Element> {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return {
|
|
10
|
-
success: true,
|
|
11
|
-
elements: [createInlineContainer("strikethrough", result.elements)],
|
|
12
|
-
consumed: 1 + result.consumed + 1,
|
|
13
|
-
};
|
|
6
|
+
return parseDelimitedContainer(ctx, "STRIKE_MARKER", "strikethrough");
|
|
14
7
|
}
|
|
@@ -1,24 +1,7 @@
|
|
|
1
1
|
import type { ParseContext } from "../../types";
|
|
2
|
+
import { findFormattingClose } from "../formatting/close";
|
|
2
3
|
|
|
3
|
-
/**
|
|
4
|
-
* Returns true when the current `--` can be parsed as strikethrough.
|
|
5
|
-
*/
|
|
6
4
|
export function hasValidStrikethroughClose(ctx: ParseContext): boolean {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
while (pos < ctx.tokens.length) {
|
|
11
|
-
const token = ctx.tokens[pos];
|
|
12
|
-
if (!token || token.type === "NEWLINE" || token.type === "EOF") {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (token.type === "STRIKE_MARKER") {
|
|
17
|
-
return !prevWasWhitespace;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
prevWasWhitespace = token.type === "WHITESPACE";
|
|
21
|
-
pos++;
|
|
22
|
-
}
|
|
23
|
-
return false;
|
|
5
|
+
const close = findFormattingClose(ctx, ctx.pos + 1, "STRIKE_MARKER");
|
|
6
|
+
return close !== null && close > ctx.pos + 1 && ctx.tokens[close - 1]?.type !== "WHITESPACE";
|
|
24
7
|
}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Parses the Wikidot subscript formatting syntax: `,,text,,`.
|
|
4
4
|
*
|
|
5
5
|
* Subscript text is delimited by double commas. The opening and closing
|
|
6
|
-
* markers must appear
|
|
7
|
-
* before a
|
|
6
|
+
* markers must appear within the same paragraph. If no closing `,,` is found
|
|
7
|
+
* before a block boundary, the opening marker is emitted as literal text.
|
|
8
8
|
*
|
|
9
9
|
* Empty subscript (`,,,,`) is silently discarded by Wikidot (produces
|
|
10
10
|
* no output), matching the behavior of bold and superscript.
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import type { Element } from "@wdprlib/ast";
|
|
19
19
|
import type { InlineRule, ParseContext, RuleResult } from "../types";
|
|
20
|
-
import {
|
|
20
|
+
import { parseDelimitedContainer } from "./formatting/container";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Inline rule for parsing `,,subscript,,` formatting.
|
|
24
24
|
*
|
|
25
25
|
* Triggered by a `SUB_MARKER` token (`,,`). Checks for a matching
|
|
26
|
-
* closing marker
|
|
26
|
+
* closing marker within the same paragraph, then recursively parses inline
|
|
27
27
|
* content between the markers.
|
|
28
28
|
*
|
|
29
29
|
* When no closing marker is found, the opening `,,` is treated as
|
|
@@ -42,6 +42,6 @@ export const subscriptRule: InlineRule = {
|
|
|
42
42
|
* text fallback for unmatched markers
|
|
43
43
|
*/
|
|
44
44
|
parse(ctx: ParseContext): RuleResult<Element> {
|
|
45
|
-
return
|
|
45
|
+
return parseDelimitedContainer(ctx, "SUB_MARKER", "subscript", { discardEmpty: true });
|
|
46
46
|
},
|
|
47
47
|
};
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Parses the Wikidot superscript formatting syntax: `^^text^^`.
|
|
4
4
|
*
|
|
5
5
|
* Superscript text is delimited by double carets. The opening and
|
|
6
|
-
* closing markers must appear
|
|
7
|
-
* is found before a
|
|
6
|
+
* closing markers must appear within the same paragraph. If no closing `^^`
|
|
7
|
+
* is found before a block boundary, the opening marker is emitted as literal text.
|
|
8
8
|
*
|
|
9
9
|
* Empty superscript (`^^^^`) is silently discarded by Wikidot (produces
|
|
10
10
|
* no output), matching the behavior of bold and subscript.
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import type { Element } from "@wdprlib/ast";
|
|
19
19
|
import type { InlineRule, ParseContext, RuleResult } from "../types";
|
|
20
|
-
import {
|
|
20
|
+
import { parseDelimitedContainer } from "./formatting/container";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Inline rule for parsing `^^superscript^^` formatting.
|
|
24
24
|
*
|
|
25
25
|
* Triggered by a `SUPER_MARKER` token (`^^`). Checks for a matching
|
|
26
|
-
* closing marker
|
|
26
|
+
* closing marker within the same paragraph, then recursively parses inline
|
|
27
27
|
* content between the markers.
|
|
28
28
|
*
|
|
29
29
|
* When no closing marker is found, the opening `^^` is treated as
|
|
@@ -42,7 +42,7 @@ export const superscriptRule: InlineRule = {
|
|
|
42
42
|
* text fallback for unmatched markers
|
|
43
43
|
*/
|
|
44
44
|
parse(ctx: ParseContext): RuleResult<Element> {
|
|
45
|
-
return
|
|
45
|
+
return parseDelimitedContainer(ctx, "SUPER_MARKER", "superscript", {
|
|
46
46
|
discardEmpty: true,
|
|
47
47
|
});
|
|
48
48
|
},
|