@wdprlib/parser 5.2.0 → 5.4.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 (106) hide show
  1. package/dist/index.cjs +2211 -1182
  2. package/dist/index.d.cts +15 -0
  3. package/dist/index.d.ts +15 -0
  4. package/dist/index.js +2211 -1182
  5. package/package.json +3 -2
  6. package/src/build-info.generated.ts +2 -2
  7. package/src/lexer/lexer.ts +34 -5
  8. package/src/lexer/quoted-string.ts +4 -4
  9. package/src/lexer/syntax-actions.ts +6 -1
  10. package/src/lexer/text-actions.ts +15 -1
  11. package/src/lexer/url-schemes.ts +78 -0
  12. package/src/parser/constants.ts +4 -0
  13. package/src/parser/parse/block.ts +1 -1
  14. package/src/parser/postprocess/divAdjacentParagraph.ts +23 -21
  15. package/src/parser/postprocess/spanStrip/merge.ts +8 -2
  16. package/src/parser/preprocess/typography.ts +25 -3
  17. package/src/parser/preprocess/utils/raw-regions.ts +59 -14
  18. package/src/parser/preprocess/whitespace/index.ts +8 -1
  19. package/src/parser/rules/block/bibliography/entry-content.ts +1 -1
  20. package/src/parser/rules/block/block-list/bare-content.ts +3 -1
  21. package/src/parser/rules/block/block-list/bare-paragraph.ts +7 -2
  22. package/src/parser/rules/block/block-list/item-content.ts +7 -3
  23. package/src/parser/rules/block/block-list/li-content.ts +8 -3
  24. package/src/parser/rules/block/block-list/li-item.ts +1 -1
  25. package/src/parser/rules/block/blockquote/build.ts +1 -1
  26. package/src/parser/rules/block/code/boundary.ts +76 -0
  27. package/src/parser/rules/block/code/content.ts +11 -40
  28. package/src/parser/rules/block/code/index.ts +8 -31
  29. package/src/parser/rules/block/code/open.ts +46 -0
  30. package/src/parser/rules/block/definition-list/item-key.ts +1 -1
  31. package/src/parser/rules/block/definition-list/item-value.ts +1 -1
  32. package/src/parser/rules/block/div/failed.ts +2 -0
  33. package/src/parser/rules/block/index.ts +3 -0
  34. package/src/parser/rules/block/list/line.ts +6 -3
  35. package/src/parser/rules/block/math/boundary.ts +104 -0
  36. package/src/parser/rules/block/math/index.ts +17 -57
  37. package/src/parser/rules/block/module/listpages/resolution/items.ts +2 -2
  38. package/src/parser/rules/block/module/listpages/resolution/pager.ts +43 -0
  39. package/src/parser/rules/block/module/listpages/resolution/wrapper.ts +6 -1
  40. package/src/parser/rules/block/module/listpages/resolve.ts +1 -1
  41. package/src/parser/rules/block/module/listpages/types/external-data.ts +13 -0
  42. package/src/parser/rules/block/module/listpages/url-resolution/params.ts +5 -4
  43. package/src/parser/rules/block/module/resolution/data-maps.ts +75 -3
  44. package/src/parser/rules/block/module/resolution/resolve-async.ts +12 -3
  45. package/src/parser/rules/block/note/boundary.ts +88 -0
  46. package/src/parser/rules/block/note/index.ts +37 -0
  47. package/src/parser/rules/block/paragraph/index.ts +12 -4
  48. package/src/parser/rules/block/paragraph/normalize.ts +6 -1
  49. package/src/parser/rules/block/parsing/block-item.ts +1 -1
  50. package/src/parser/rules/block/parsing/content.ts +1 -1
  51. package/src/parser/rules/block/parsing/inline-content.ts +6 -3
  52. package/src/parser/rules/block/table/pipe/cell.ts +3 -1
  53. package/src/parser/rules/block/table-block/cell-content/index.ts +1 -1
  54. package/src/parser/rules/block/table-block/cell-content/segments.ts +5 -2
  55. package/src/parser/rules/block/table-block/cell-newline.ts +2 -1
  56. package/src/parser/rules/contracts/rule.ts +4 -2
  57. package/src/parser/rules/contracts/scope.ts +2 -0
  58. package/src/parser/rules/inline/anchor/child.ts +8 -2
  59. package/src/parser/rules/inline/anchor/content.ts +3 -1
  60. package/src/parser/rules/inline/anchor/index.ts +4 -1
  61. package/src/parser/rules/inline/anchor/newline.ts +2 -1
  62. package/src/parser/rules/inline/autolink.ts +153 -0
  63. package/src/parser/rules/inline/button/attributes.ts +17 -0
  64. package/src/parser/rules/inline/button/index.ts +17 -0
  65. package/src/parser/rules/inline/button/syntax.ts +56 -0
  66. package/src/parser/rules/inline/date/index.ts +17 -0
  67. package/src/parser/rules/inline/date/syntax.ts +46 -0
  68. package/src/parser/rules/inline/email/candidates.ts +134 -0
  69. package/src/parser/rules/inline/email/index.ts +36 -0
  70. package/src/parser/rules/inline/email/scan.ts +76 -0
  71. package/src/parser/rules/inline/expr/branch.ts +3 -1
  72. package/src/parser/rules/inline/footnote/boundary.ts +56 -0
  73. package/src/parser/rules/inline/footnote/content.ts +29 -41
  74. package/src/parser/rules/inline/footnote/elements.ts +9 -34
  75. package/src/parser/rules/inline/footnote/index.ts +4 -1
  76. package/src/parser/rules/inline/formatting/close.ts +12 -0
  77. package/src/parser/rules/inline/index.ts +14 -0
  78. package/src/parser/rules/inline/line-break/newline.ts +8 -1
  79. package/src/parser/rules/inline/link-bracket/direct-url.ts +11 -3
  80. package/src/parser/rules/inline/link-bracket/parsed.ts +9 -4
  81. package/src/parser/rules/inline/link-bracket/parts.ts +14 -36
  82. package/src/parser/rules/inline/link-bracket/special-target.ts +9 -0
  83. package/src/parser/rules/inline/link-single.ts +9 -7
  84. package/src/parser/rules/inline/link-triple/index.ts +1 -0
  85. package/src/parser/rules/inline/link-triple/label.ts +7 -1
  86. package/src/parser/rules/inline/parsing/automatic-line-break.ts +35 -0
  87. package/src/parser/rules/inline/parsing/block-boundary.ts +2 -0
  88. package/src/parser/rules/inline/parsing/block-start-predicates.ts +10 -0
  89. package/src/parser/rules/inline/parsing/inline-content.ts +36 -6
  90. package/src/parser/rules/inline/parsing/plain-text.ts +7 -2
  91. package/src/parser/rules/inline/parsing/preserved-line-break.ts +13 -0
  92. package/src/parser/rules/inline/parsing/raw-tag.ts +19 -0
  93. package/src/parser/rules/inline/raw/angle.ts +2 -1
  94. package/src/parser/rules/inline/raw/end.ts +21 -1
  95. package/src/parser/rules/inline/size/content.ts +1 -1
  96. package/src/parser/rules/inline/size/value.ts +11 -0
  97. package/src/parser/rules/inline/social/index.ts +17 -0
  98. package/src/parser/rules/inline/social/syntax.ts +40 -0
  99. package/src/parser/rules/inline/span/content.ts +3 -1
  100. package/src/parser/rules/inline/span/newline.ts +2 -1
  101. package/src/parser/rules/opaque-probe.ts +58 -0
  102. package/src/pipeline/process.ts +8 -1
  103. package/src/parser/rules/block/math/content.ts +0 -54
  104. package/src/parser/rules/block/math/name.ts +0 -35
  105. package/src/parser/rules/inline/footnote/child.ts +0 -22
  106. package/src/parser/rules/inline/footnote/newline.ts +0 -27
@@ -1,3 +1,5 @@
1
+ import type { Token } from "../../../../lexer";
2
+ import { stripAutomaticLineBreak } from "../../inline/parsing/automatic-line-break";
1
3
  import type { Element } from "@wdprlib/ast";
2
4
  import type { ParseContext } from "../../types";
3
5
  import { getCandidateInlineRules } from "../../inline/utils";
@@ -8,6 +10,7 @@ interface ListItemContentResult {
8
10
  matched: boolean;
9
11
  elements: Element[];
10
12
  consumed: number;
13
+ stripLeadingLineBreak?: Token;
11
14
  }
12
15
 
13
16
  const blockListExcludedRulesCache = new WeakMap<
@@ -26,7 +29,7 @@ export function parseListItemBlockContent(
26
29
  for (const rule of getCandidateBlockRules(filteredBlockRules, token)) {
27
30
  const result = rule.parse(blockCtx);
28
31
  if (result.success) {
29
- return { matched: true, elements: result.elements, consumed: result.consumed };
32
+ return { matched: true, ...result };
30
33
  }
31
34
  }
32
35
 
@@ -56,7 +59,7 @@ export function parseListItemInlineContent(
56
59
  for (const rule of getCandidateInlineRules(ctx.inlineRules, tokenType)) {
57
60
  const result = rule.parse(inlineCtx);
58
61
  if (result.success) {
59
- return { matched: true, elements: result.elements, consumed: result.consumed };
62
+ return { matched: true, ...result };
60
63
  }
61
64
  }
62
65
 
@@ -108,7 +111,8 @@ export function collectPostLiTrailingContent(
108
111
 
109
112
  const inlineResult = parseListItemInlineContent(ctx, pos, token.type);
110
113
  if (inlineResult.matched) {
111
- elements.push(...inlineResult.elements);
114
+ stripAutomaticLineBreak(elements, inlineResult.stripLeadingLineBreak);
115
+ for (const element of inlineResult.elements) elements.push(element);
112
116
  consumed += inlineResult.consumed;
113
117
  pos += inlineResult.consumed;
114
118
  continue;
@@ -1,3 +1,7 @@
1
+ import {
2
+ createAutomaticLineBreak,
3
+ stripAutomaticLineBreak,
4
+ } from "../../inline/parsing/automatic-line-break";
1
5
  import type { Element } from "@wdprlib/ast";
2
6
  import type { ParseContext } from "../../types";
3
7
  import { parseListItemBlockContent, parseListItemInlineContent } from "./item-content";
@@ -50,7 +54,7 @@ export function collectLiItemContent(
50
54
  if (token.type === "NEWLINE") {
51
55
  const newlineResult = consumeLiItemNewlines(ctx, pos, elements.length > 0);
52
56
  if (newlineResult.addLineBreak) {
53
- elements.push({ element: "line-break" });
57
+ elements.push(createAutomaticLineBreak(token));
54
58
  }
55
59
  pos += newlineResult.consumed;
56
60
  consumed += newlineResult.consumed;
@@ -59,7 +63,7 @@ export function collectLiItemContent(
59
63
 
60
64
  const blockResult = parseListItemBlockContent(ctx, pos, token);
61
65
  if (blockResult.matched) {
62
- elements.push(...blockResult.elements);
66
+ for (const element of blockResult.elements) elements.push(element);
63
67
  consumed += blockResult.consumed;
64
68
  pos += blockResult.consumed;
65
69
  continue;
@@ -67,7 +71,8 @@ export function collectLiItemContent(
67
71
 
68
72
  const inlineResult = parseListItemInlineContent(ctx, pos, token.type);
69
73
  if (inlineResult.matched) {
70
- elements.push(...inlineResult.elements);
74
+ stripAutomaticLineBreak(elements, inlineResult.stripLeadingLineBreak);
75
+ for (const element of inlineResult.elements) elements.push(element);
71
76
  consumed += inlineResult.consumed;
72
77
  pos += inlineResult.consumed;
73
78
  continue;
@@ -62,7 +62,7 @@ export function parseLiItem(
62
62
  pos += closeConsumed;
63
63
 
64
64
  const trailingResult = collectPostLiTrailingContent(ctx, pos, listType);
65
- contentElements.push(...trailingResult.elements);
65
+ for (const element of trailingResult.elements) contentElements.push(element);
66
66
  consumed += trailingResult.consumed;
67
67
  }
68
68
 
@@ -88,7 +88,7 @@ function sliceLineTokens(ctx: ParseContext, lines: BlockquoteLine[]): Token[] {
88
88
  for (const { start, end } of lines) {
89
89
  for (let pos = start; pos < end; pos++) {
90
90
  const token = ctx.tokens[pos];
91
- if (token) {
91
+ if (token && token.type !== "BACKSLASH_BREAK") {
92
92
  tokens.push(token);
93
93
  }
94
94
  }
@@ -0,0 +1,76 @@
1
+ import type { Token } from "../../../../lexer";
2
+ import { rawRegionEnd } from "../../inline/raw/end";
3
+ import { findCodeOpen } from "./open";
4
+
5
+ interface CodeBodyBounds {
6
+ closeStart: number;
7
+ end: number;
8
+ foundClose: boolean;
9
+ }
10
+
11
+ const caches = new WeakMap<readonly Token[], Map<number, CodeBodyBounds>>();
12
+
13
+ function codeCloseEnd(tokens: readonly Token[], start: number): number {
14
+ if (tokens[start]?.type !== "BLOCK_END_OPEN" || tokens[start + 1]?.value.toLowerCase() !== "code")
15
+ return start;
16
+ let end = start + 2;
17
+ while (tokens[end]?.type === "WHITESPACE") end++;
18
+ return tokens[end]?.type === "BLOCK_CLOSE" ? end + 1 : start;
19
+ }
20
+
21
+ function nestedCodeBodyStart(tokens: readonly Token[], start: number): number {
22
+ if (tokens[start]?.type !== "BLOCK_OPEN" || tokens[start + 1]?.value.toLowerCase() !== "code")
23
+ return start;
24
+ for (let pos = start + 2; pos < tokens.length; pos++) {
25
+ const type = tokens[pos]?.type;
26
+ if (type === "NEWLINE" || type === "EOF" || type === "BLOCK_OPEN") return start;
27
+ if (type === "BLOCK_CLOSE") {
28
+ const open = findCodeOpen(tokens, start);
29
+ return open && !open.repaired ? open.bodyStart : start;
30
+ }
31
+ }
32
+ return start;
33
+ }
34
+
35
+ export function findCodeBodyBounds(tokens: readonly Token[], start: number): CodeBodyBounds {
36
+ let cache = caches.get(tokens);
37
+ if (!cache) {
38
+ cache = new Map();
39
+ caches.set(tokens, cache);
40
+ }
41
+ const cached = cache.get(start);
42
+ if (cached) return cached;
43
+ const stack = [start];
44
+ let pos = start;
45
+ while (pos < tokens.length && tokens[pos]?.type !== "EOF") {
46
+ const rawEnd = rawRegionEnd(tokens, pos, tokens.length);
47
+ if (rawEnd > pos) {
48
+ pos = rawEnd;
49
+ continue;
50
+ }
51
+ const end = codeCloseEnd(tokens, pos);
52
+ if (end > pos) {
53
+ const result = { closeStart: pos, end, foundClose: true };
54
+ cache.set(stack.pop()!, result);
55
+ if (stack.length === 0) return result;
56
+ pos = end;
57
+ continue;
58
+ }
59
+ const bodyStart = nestedCodeBodyStart(tokens, pos);
60
+ if (bodyStart > pos) {
61
+ const child = cache.get(bodyStart);
62
+ if (child) {
63
+ pos = child.end;
64
+ if (!child.foundClose) break;
65
+ } else {
66
+ stack.push(bodyStart);
67
+ pos = bodyStart;
68
+ }
69
+ continue;
70
+ }
71
+ pos++;
72
+ }
73
+ const result = { closeStart: pos, end: pos, foundClose: false };
74
+ for (const bodyStart of stack) cache.set(bodyStart, result);
75
+ return result;
76
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ParseContext } from "../../types";
2
- import { parseBlockName } from "../utils";
2
+ import { findCodeBodyBounds } from "./boundary";
3
3
 
4
4
  export interface CodeContentResult {
5
5
  contents: string;
@@ -12,43 +12,14 @@ export function collectCodeContent(
12
12
  startPos: number,
13
13
  closingSwallowed: boolean,
14
14
  ): CodeContentResult {
15
- const contentParts: string[] = [];
16
- let pos = startPos;
17
- let consumed = 0;
18
- let foundClose = closingSwallowed;
19
-
20
- while (!closingSwallowed && pos < ctx.tokens.length) {
21
- const token = ctx.tokens[pos];
22
- if (!token || token.type === "EOF") {
23
- break;
24
- }
25
-
26
- if (token.type === "BLOCK_END_OPEN") {
27
- const closeNameResult = parseBlockName(ctx, pos + 1);
28
- if (closeNameResult?.name === "code") {
29
- foundClose = true;
30
- const closeConsumed = consumeCodeClose(ctx, pos, closeNameResult.consumed);
31
- consumed += closeConsumed;
32
- break;
33
- }
34
- }
35
-
36
- contentParts.push(token.value);
37
- pos++;
38
- consumed++;
39
- }
40
-
41
- return { contents: contentParts.join(""), consumed, foundClose };
42
- }
43
-
44
- function consumeCodeClose(ctx: ParseContext, startPos: number, closeNameConsumed: number): number {
45
- let pos = startPos + 1 + closeNameConsumed;
46
- let consumed = 1 + closeNameConsumed;
47
-
48
- if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
49
- pos++;
50
- consumed++;
51
- }
52
-
53
- return consumed;
15
+ if (closingSwallowed) return { contents: "", consumed: 0, foundClose: true };
16
+ const bounds = findCodeBodyBounds(ctx.tokens, startPos);
17
+ return {
18
+ contents: ctx.tokens
19
+ .slice(startPos, bounds.closeStart)
20
+ .map((token) => token.value)
21
+ .join(""),
22
+ consumed: bounds.end - startPos,
23
+ foundClose: bounds.foundClose,
24
+ };
54
25
  }
@@ -12,7 +12,7 @@
12
12
  import type { Element, CodeBlockData } from "@wdprlib/ast";
13
13
  import type { BlockRule, ParseContext, RuleResult } from "../../types";
14
14
  import { currentToken } from "../../types";
15
- import { parseBlockName } from "../utils";
15
+ import { findCodeOpen } from "./open";
16
16
  import { parseAttributesRaw } from "../utils";
17
17
  import { repairSwallowedCodeClose } from "./attributes";
18
18
  import { parseInlineUntil } from "../../inline/utils";
@@ -38,37 +38,14 @@ export const codeBlockRule: BlockRule = {
38
38
  return { success: false };
39
39
  }
40
40
 
41
- let pos = ctx.pos + 1;
42
- let consumed = 1;
41
+ const open = findCodeOpen(ctx.tokens, ctx.pos);
42
+ if (!open) return { success: false };
43
+ const attrResult = parseAttributesRaw(ctx, ctx.pos + 2);
44
+ if (open.repaired) repairSwallowedCodeClose(ctx, open.attributesEnd, attrResult.attrs);
45
+ let pos = open.bodyStart;
46
+ let consumed = pos - ctx.pos;
43
47
 
44
- const nameResult = parseBlockName(ctx, pos);
45
- if (!nameResult || nameResult.name !== "code") {
46
- return { success: false };
47
- }
48
-
49
- pos += nameResult.consumed;
50
- consumed += nameResult.consumed;
51
-
52
- const attrResult = parseAttributesRaw(ctx, pos);
53
- pos += attrResult.consumed;
54
- consumed += attrResult.consumed;
55
-
56
- let closingSwallowed = false;
57
- if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
58
- const repaired = repairSwallowedCodeClose(ctx, pos, attrResult.attrs);
59
- if (!repaired) return { success: false };
60
- closingSwallowed = repaired.closingSwallowed;
61
- } else {
62
- pos++;
63
- consumed++;
64
- }
65
-
66
- if (ctx.tokens[pos]?.type === "NEWLINE") {
67
- pos++;
68
- consumed++;
69
- }
70
-
71
- const contentResult = collectCodeContent(ctx, pos, closingSwallowed);
48
+ const contentResult = collectCodeContent(ctx, pos, open.closingSwallowed);
72
49
  let codeContent = contentResult.contents.replace(/\n$/, "");
73
50
  consumed += contentResult.consumed;
74
51
  pos += contentResult.consumed;
@@ -0,0 +1,46 @@
1
+ import type { Token } from "../../../../lexer";
2
+
3
+ interface CodeOpenBounds {
4
+ attributesEnd: number;
5
+ bodyStart: number;
6
+ repaired: boolean;
7
+ closingSwallowed: boolean;
8
+ }
9
+
10
+ const attributeRanges = new WeakMap<readonly Token[], { start: number; end: number }>();
11
+
12
+ function findAttributesEnd(tokens: readonly Token[], start: number): number {
13
+ const cached = attributeRanges.get(tokens);
14
+ if (cached && start >= cached.start && start <= cached.end) return cached.end;
15
+ let end = start;
16
+ while (end < tokens.length) {
17
+ const type = tokens[end]?.type;
18
+ if (type === "BLOCK_CLOSE" || type === "NEWLINE" || type === "EOF") break;
19
+ end++;
20
+ }
21
+ attributeRanges.set(tokens, { start, end });
22
+ return end;
23
+ }
24
+
25
+ export function findCodeOpen(tokens: readonly Token[], start: number): CodeOpenBounds | null {
26
+ const name = tokens[start + 1];
27
+ if (
28
+ tokens[start]?.type !== "BLOCK_OPEN" ||
29
+ (name?.type !== "IDENTIFIER" && name?.type !== "TEXT") ||
30
+ name.value.toLowerCase() !== "code" ||
31
+ tokens[start + 2]?.type === "UNDERSCORE"
32
+ )
33
+ return null;
34
+ const attributesEnd = findAttributesEnd(tokens, start + 2);
35
+ const repaired = tokens[attributesEnd]?.type !== "BLOCK_CLOSE";
36
+ const last = tokens[attributesEnd - 1];
37
+ if (repaired && (last?.type !== "QUOTED_STRING" || !last.value.includes("]]"))) return null;
38
+ let bodyStart = attributesEnd + (repaired ? 0 : 1);
39
+ if (tokens[bodyStart]?.type === "NEWLINE") bodyStart++;
40
+ return {
41
+ attributesEnd,
42
+ bodyStart,
43
+ repaired,
44
+ closingSwallowed: repaired && last!.value.includes("[[/code]]"),
45
+ };
46
+ }
@@ -51,7 +51,7 @@ export function parseDefinitionItemKey(
51
51
  const inlineCtx: ParseContext = { ...ctx, pos };
52
52
  const result = parseInlineUntil(inlineCtx, "COLON");
53
53
  if (result.elements.length > 0) {
54
- key.push(...result.elements);
54
+ for (const element of result.elements) key.push(element);
55
55
  for (let i = 0; i < result.consumed; i++) {
56
56
  const t = ctx.tokens[pos + i];
57
57
  if (t) keyTokens.push(t.value);
@@ -34,7 +34,7 @@ export function parseDefinitionItemValue(
34
34
  const inlineCtx: ParseContext = { ...ctx, pos };
35
35
  const result = parseInlineUntil(inlineCtx, "NEWLINE");
36
36
  if (result.elements.length > 0) {
37
- value.push(...result.elements);
37
+ for (const element of result.elements) value.push(element);
38
38
  pos += result.consumed;
39
39
  consumed += result.consumed;
40
40
  } else {
@@ -1,3 +1,4 @@
1
+ import { markUnparsedDivStart } from "../../../postprocess/divAdjacentParagraph";
1
2
  import type { Element } from "@wdprlib/ast";
2
3
  import type { ParseContext, RuleResult } from "../../types";
3
4
  import { parseAttributes, parseBlockName } from "../utils";
@@ -100,6 +101,7 @@ export function consumeFailedDiv(ctx: ParseContext): RuleResult<Element> {
100
101
  consumed++;
101
102
  }
102
103
 
104
+ markUnparsedDivStart(elements);
103
105
  return {
104
106
  success: true,
105
107
  elements: [
@@ -27,6 +27,7 @@ import { blockListRule } from "./block-list";
27
27
  import { blockquoteRule } from "./blockquote";
28
28
  import { definitionListRule } from "./definition-list";
29
29
  import { paragraphRule } from "./paragraph";
30
+ import { noteRule } from "./note";
30
31
  import { divRule } from "./div";
31
32
  import { codeBlockRule } from "./code";
32
33
  import { collapsibleRule } from "./collapsible";
@@ -58,6 +59,7 @@ export { blockListRule } from "./block-list";
58
59
  export { blockquoteRule } from "./blockquote";
59
60
  export { definitionListRule } from "./definition-list";
60
61
  export { paragraphRule } from "./paragraph";
62
+ export { noteRule } from "./note";
61
63
  export { divRule } from "./div";
62
64
  export { codeBlockRule } from "./code";
63
65
  export { collapsibleRule } from "./collapsible";
@@ -123,6 +125,7 @@ export const blockRules: BlockRule[] = [
123
125
  iftagsRule,
124
126
  bibliographyRule,
125
127
  galleryRule,
128
+ noteRule,
126
129
  divRule,
127
130
  // paragraphRule is not included - used as fallback
128
131
  ];
@@ -51,10 +51,13 @@ export function parseNativeListLine(ctx: ParseContext, startPos: number): ListLi
51
51
  pos++;
52
52
  consumed++;
53
53
 
54
- if (ctx.tokens[pos]?.type === "WHITESPACE") {
55
- pos++;
56
- consumed++;
54
+ // Wikidotのリスト構文はマーカー直後の空白が必須(Text_Wiki List.php: `(\*|#) `)。
55
+ // 空白なしの行(例: 生URLの `*http://...`)はリストではなくインライン内容として扱う
56
+ if (ctx.tokens[pos]?.type !== "WHITESPACE") {
57
+ return { kind: "stop" };
57
58
  }
59
+ pos++;
60
+ consumed++;
58
61
 
59
62
  const inlineCtx: ParseContext = { ...ctx, pos };
60
63
  const inlineResult = parseInlineUntil(inlineCtx, "NEWLINE");
@@ -0,0 +1,104 @@
1
+ import type { Token } from "../../../../lexer";
2
+
3
+ interface MathOpenBounds {
4
+ nameStart: number;
5
+ nameEnd: number;
6
+ bodyStart: number;
7
+ }
8
+
9
+ interface MathBodyBounds {
10
+ closeStart: number;
11
+ end: number;
12
+ foundClose: boolean;
13
+ hasContent: boolean;
14
+ }
15
+
16
+ const nameRanges = new WeakMap<readonly Token[], { start: number; end: number }>();
17
+ const bodyCaches = new WeakMap<readonly Token[], Map<number, MathBodyBounds>>();
18
+
19
+ function findNameEnd(tokens: readonly Token[], start: number): number {
20
+ const cached = nameRanges.get(tokens);
21
+ if (cached && start >= cached.start && start <= cached.end) return cached.end;
22
+ let end = start;
23
+ while (end < tokens.length) {
24
+ const type = tokens[end]?.type;
25
+ if (type === "BLOCK_CLOSE" || type === "WHITESPACE" || type === "NEWLINE" || type === "EOF")
26
+ break;
27
+ end++;
28
+ }
29
+ nameRanges.set(tokens, { start, end });
30
+ return end;
31
+ }
32
+
33
+ export function findMathOpen(tokens: readonly Token[], start: number): MathOpenBounds | null {
34
+ const name = tokens[start + 1];
35
+ if (
36
+ tokens[start]?.type !== "BLOCK_OPEN" ||
37
+ (name?.type !== "IDENTIFIER" && name?.type !== "TEXT") ||
38
+ name.value.toLowerCase() !== "math" ||
39
+ tokens[start + 2]?.type === "UNDERSCORE"
40
+ )
41
+ return null;
42
+ let pos = start + 2;
43
+ while (tokens[pos]?.type === "WHITESPACE") pos++;
44
+ const nameStart = pos;
45
+ if (tokens[pos]?.type === "IDENTIFIER" || tokens[pos]?.type === "TEXT") {
46
+ pos = findNameEnd(tokens, pos);
47
+ }
48
+ const nameEnd = pos;
49
+ while (tokens[pos]?.type === "WHITESPACE") pos++;
50
+ if (tokens[pos]?.type !== "BLOCK_CLOSE") return null;
51
+ pos++;
52
+ if (tokens[pos]?.type === "NEWLINE") pos++;
53
+ return { nameStart, nameEnd, bodyStart: pos };
54
+ }
55
+
56
+ function mathCloseEnd(tokens: readonly Token[], start: number): number {
57
+ if (
58
+ tokens[start]?.type !== "BLOCK_END_OPEN" ||
59
+ tokens[start + 1]?.value.toLowerCase() !== "math" ||
60
+ tokens[start + 2]?.type !== "BLOCK_CLOSE"
61
+ )
62
+ return start;
63
+ const end = start + 3;
64
+ return tokens[end]?.type === "NEWLINE" ? end + 1 : end;
65
+ }
66
+
67
+ export function findMathBodyBounds(tokens: readonly Token[], start: number): MathBodyBounds {
68
+ let cache = bodyCaches.get(tokens);
69
+ if (!cache) {
70
+ cache = new Map();
71
+ bodyCaches.set(tokens, cache);
72
+ }
73
+ const visited: number[] = [];
74
+ let result: MathBodyBounds = {
75
+ closeStart: tokens.length,
76
+ end: tokens.length,
77
+ foundClose: false,
78
+ hasContent: false,
79
+ };
80
+ for (let pos = start; pos < tokens.length; pos++) {
81
+ const cached = cache.get(pos);
82
+ if (cached) {
83
+ result = cached;
84
+ break;
85
+ }
86
+ const end = mathCloseEnd(tokens, pos);
87
+ if (end > pos) {
88
+ result = { closeStart: pos, end, foundClose: true, hasContent: false };
89
+ cache.set(pos, result);
90
+ break;
91
+ }
92
+ visited.push(pos);
93
+ }
94
+ // Empty-body detection must also be reusable without joining each candidate's suffix.
95
+ for (let i = visited.length - 1; i >= 0; i--) {
96
+ const pos = visited[i]!;
97
+ const token = tokens[pos]!;
98
+ if (!result.hasContent && (token.type === "BACKSLASH_BREAK" || token.value.trim() !== "")) {
99
+ result = { ...result, hasContent: true };
100
+ }
101
+ cache.set(pos, result);
102
+ }
103
+ return result;
104
+ }
@@ -11,9 +11,7 @@
11
11
  import type { Element } from "@wdprlib/ast";
12
12
  import type { BlockRule, ParseContext, RuleResult } from "../../types";
13
13
  import { currentToken } from "../../types";
14
- import { parseBlockName } from "../utils";
15
- import { collectMathContent, consumeMathClose } from "./content";
16
- import { parseMathName } from "./name";
14
+ import { findMathOpen, findMathBodyBounds } from "./boundary";
17
15
 
18
16
  /**
19
17
  * Block rule for `[[math name]]...[[/math]]`.
@@ -28,79 +26,41 @@ export const mathBlockRule: BlockRule = {
28
26
 
29
27
  parse(ctx: ParseContext): RuleResult<Element> {
30
28
  const openToken = currentToken(ctx);
31
- if (openToken.type !== "BLOCK_OPEN") {
32
- return { success: false };
33
- }
34
-
35
- let pos = ctx.pos + 1;
36
- let consumed = 1;
37
-
38
- const nameResult = parseBlockName(ctx, pos);
39
- if (!nameResult || nameResult.name !== "math") {
40
- return { success: false };
41
- }
42
- pos += nameResult.consumed;
43
- consumed += nameResult.consumed;
44
-
45
- while (ctx.tokens[pos]?.type === "WHITESPACE") {
46
- pos++;
47
- consumed++;
48
- }
49
-
50
- const mathName = parseMathName(ctx, pos);
51
- pos += mathName.consumed;
52
- consumed += mathName.consumed;
53
-
54
- while (ctx.tokens[pos]?.type === "WHITESPACE") {
55
- pos++;
56
- consumed++;
57
- }
58
-
59
- if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
60
- return { success: false };
61
- }
62
- pos++;
63
- consumed++;
64
-
65
- if (ctx.tokens[pos]?.type === "NEWLINE") {
66
- pos++;
67
- consumed++;
68
- }
69
-
70
- const contentResult = collectMathContent(ctx, pos);
71
- const latexSource = contentResult.latexSource.trim();
72
- consumed += contentResult.consumed;
73
- pos += contentResult.consumed;
74
-
75
- if (!contentResult.foundClose) {
29
+ const open = findMathOpen(ctx.tokens, ctx.pos);
30
+ if (!open) return { success: false };
31
+ const bounds = findMathBodyBounds(ctx.tokens, open.bodyStart);
32
+ if (!bounds.foundClose) {
76
33
  ctx.diagnostics.push({
77
34
  severity: "warning",
78
35
  code: "unclosed-block",
79
36
  message: "Missing closing tag [[/math]] for [[math]]",
80
37
  position: openToken.position,
81
38
  });
82
- } else {
83
- const closeConsumed = consumeMathClose(ctx, pos);
84
- pos += closeConsumed;
85
- consumed += closeConsumed;
86
39
  }
87
-
88
- if (!latexSource) {
40
+ if (!bounds.hasContent) {
89
41
  return { success: false };
90
42
  }
91
-
43
+ const latexSource = ctx.tokens
44
+ .slice(open.bodyStart, bounds.closeStart)
45
+ .map((token) => (token.type === "BACKSLASH_BREAK" ? "\\\n" : token.value))
46
+ .join("")
47
+ .trim();
48
+ const name = ctx.tokens
49
+ .slice(open.nameStart, open.nameEnd)
50
+ .map((token) => token.value)
51
+ .join("");
92
52
  return {
93
53
  success: true,
94
54
  elements: [
95
55
  {
96
56
  element: "math",
97
57
  data: {
98
- name: mathName.name,
58
+ name: name || null,
99
59
  "latex-source": latexSource,
100
60
  },
101
61
  },
102
62
  ],
103
- consumed,
63
+ consumed: bounds.end - ctx.pos,
104
64
  };
105
65
  },
106
66
  };
@@ -17,9 +17,9 @@ export function renderListPagesItems(
17
17
 
18
18
  const ctx: VariableContext = {
19
19
  page,
20
- index: i + 1,
20
+ index: (data.pagination?.offset ?? 0) + i + 1,
21
21
  total: data.totalCount,
22
- limit: module.limit,
22
+ limit: data.pagination ? data.pagination.limit : module.limit,
23
23
  site: data.site,
24
24
  };
25
25
 
@@ -0,0 +1,43 @@
1
+ import type { Element } from "@wdprlib/ast";
2
+ import type { ListPagesExternalData } from "../types";
3
+
4
+ export function createListPagesPager(data: ListPagesExternalData): Element[] {
5
+ const pagination = data.pagination;
6
+ if (!pagination || pagination.totalPages <= 1) return [];
7
+ const { currentPage, totalPages, urlPath, parameter } = pagination;
8
+ const pages = new Set([1, totalPages]);
9
+ for (
10
+ let page = Math.max(1, currentPage - 2);
11
+ page <= Math.min(totalPages, currentPage + 2);
12
+ page++
13
+ ) {
14
+ pages.add(page);
15
+ }
16
+ return [
17
+ {
18
+ element: "pager",
19
+ data: {
20
+ currentPage,
21
+ totalPages,
22
+ pages: [...pages]
23
+ .sort((a, b) => a - b)
24
+ .map((page) => ({
25
+ page,
26
+ href: pageUrl(urlPath, parameter, page),
27
+ })),
28
+ },
29
+ },
30
+ ];
31
+ }
32
+
33
+ function pageUrl(urlPath: string, parameter: string, page: number): string {
34
+ const suffixStart = urlPath.search(/[?#]/);
35
+ const path = suffixStart === -1 ? urlPath : urlPath.slice(0, suffixStart);
36
+ const suffix = suffixStart === -1 ? "" : urlPath.slice(suffixStart);
37
+ const parts = path.split("/");
38
+ const result = parts.slice(0, 2);
39
+ for (let index = 2; index < parts.length; index += 2) {
40
+ if (parts[index] !== parameter) result.push(...parts.slice(index, index + 2));
41
+ }
42
+ return `${result.join("/").replace(/\/$/, "")}/${parameter}/${page}${suffix}`;
43
+ }