@wdprlib/parser 2.1.0 → 3.0.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.js CHANGED
@@ -27,6 +27,7 @@ class Lexer {
27
27
  state;
28
28
  options;
29
29
  splitBlockClosePositions = new Set;
30
+ blockOpenerDepth = 0;
30
31
  constructor(source, options = {}) {
31
32
  this.options = {
32
33
  trackPositions: options.trackPositions ?? true
@@ -126,6 +127,11 @@ class Lexer {
126
127
  const position = this.options.trackPositions ? createPosition(startPos, endPos) : createPosition(createPoint(0, 0, 0), createPoint(0, 0, 0));
127
128
  const lineStart = this.state.tokens.length === 0 || this.state.tokens[this.state.tokens.length - 1]?.type === "NEWLINE";
128
129
  this.state.tokens.push(createToken(type, value, position, lineStart));
130
+ if (type === "BLOCK_OPEN" || type === "BLOCK_END_OPEN") {
131
+ this.blockOpenerDepth++;
132
+ } else if (type === "BLOCK_CLOSE" && this.blockOpenerDepth > 0) {
133
+ this.blockOpenerDepth--;
134
+ }
129
135
  }
130
136
  scanToken() {
131
137
  const char = this.current();
@@ -390,7 +396,7 @@ class Lexer {
390
396
  }
391
397
  if (char === '"') {
392
398
  const lastNonWs = this.lastNonWhitespaceTokenType();
393
- if (lastNonWs === "EQUALS") {
399
+ if (this.blockOpenerDepth > 0 && lastNonWs === "EQUALS") {
394
400
  let quoted = this.advance();
395
401
  while (!this.isAtEnd() && this.current() !== '"' && this.current() !== `
396
402
  `) {
@@ -590,6 +596,73 @@ var BLOCK_START_TOKENS = [
590
596
  "CLEAR_FLOAT_LEFT",
591
597
  "CLEAR_FLOAT_RIGHT"
592
598
  ];
599
+ var KNOWN_BLOCK_NAMES = new Set([
600
+ "collapsible",
601
+ "div",
602
+ "div_",
603
+ "code",
604
+ "ul",
605
+ "ol",
606
+ "li",
607
+ "table",
608
+ "row",
609
+ "cell",
610
+ "hcell",
611
+ "tabview",
612
+ "tabs",
613
+ "module",
614
+ "module654",
615
+ "bibliography",
616
+ "footnoteblock",
617
+ "toc",
618
+ "iframe",
619
+ "math",
620
+ "html",
621
+ "iftags",
622
+ "include",
623
+ "f",
624
+ "embed",
625
+ "embedvideo",
626
+ "embedaudio",
627
+ "<",
628
+ ">",
629
+ "=",
630
+ "==",
631
+ "span",
632
+ "span_",
633
+ "user",
634
+ "a",
635
+ "anchor",
636
+ "size",
637
+ "footnote",
638
+ "eref",
639
+ "$",
640
+ "image",
641
+ "gallery",
642
+ "file"
643
+ ]);
644
+ var INDENT_ACCEPTING_BLOCK_NAMES = new Set([
645
+ "bibliography",
646
+ "ul",
647
+ "ol",
648
+ "li",
649
+ "code",
650
+ "collapsible",
651
+ "div",
652
+ "div_",
653
+ "embed",
654
+ "embedvideo",
655
+ "embedaudio",
656
+ "html",
657
+ "iframe",
658
+ "iftags",
659
+ "math",
660
+ "module",
661
+ "module654",
662
+ "table",
663
+ "tabview",
664
+ "tabs"
665
+ ]);
593
666
 
594
667
  // packages/parser/src/parser/rules/utils.ts
595
668
  var SAFE_ATTRIBUTES = new Set([
@@ -716,13 +789,36 @@ function parseBlockName(ctx, startPos) {
716
789
 
717
790
  // packages/parser/src/parser/rules/inline/utils.ts
718
791
  function isExcludedBlockToken(ctx, tokenPos) {
719
- if (!ctx.excludedBlockNames?.size)
792
+ const excluded = ctx.scope.excludedBlockNames;
793
+ if (!excluded?.size)
794
+ return false;
795
+ const token = ctx.tokens[tokenPos];
796
+ if (token?.type !== "BLOCK_OPEN" && token?.type !== "BLOCK_END_OPEN")
720
797
  return false;
798
+ const nameResult = parseBlockName(ctx, tokenPos + 1);
799
+ return nameResult !== null && excluded.has(nameResult.name);
800
+ }
801
+ function isUnknownBlockToken(ctx, tokenPos) {
802
+ const token = ctx.tokens[tokenPos];
803
+ if (token?.type !== "BLOCK_OPEN" && token?.type !== "BLOCK_END_OPEN")
804
+ return false;
805
+ const nameResult = parseBlockName(ctx, tokenPos + 1);
806
+ if (nameResult === null) {
807
+ if (ctx.tokens[tokenPos + 1]?.type === "EQUALS") {
808
+ return false;
809
+ }
810
+ return true;
811
+ }
812
+ return !KNOWN_BLOCK_NAMES.has(nameResult.name);
813
+ }
814
+ function isIndentAcceptingBlock(ctx, tokenPos) {
721
815
  const token = ctx.tokens[tokenPos];
722
816
  if (token?.type !== "BLOCK_OPEN" && token?.type !== "BLOCK_END_OPEN")
723
817
  return false;
724
818
  const nameResult = parseBlockName(ctx, tokenPos + 1);
725
- return nameResult !== null && ctx.excludedBlockNames.has(nameResult.name);
819
+ if (nameResult === null)
820
+ return false;
821
+ return INDENT_ACCEPTING_BLOCK_NAMES.has(nameResult.name);
726
822
  }
727
823
  function canApplyInlineRule(rule, token) {
728
824
  if (rule.startTokens.length === 0) {
@@ -741,9 +837,9 @@ function parseInlineUntil(ctx, endType) {
741
837
  if (!token || token.type === "EOF") {
742
838
  break;
743
839
  }
744
- if (paragraphMode && ctx.blockCloseCondition) {
840
+ if (paragraphMode && ctx.scope.blockCloseCondition) {
745
841
  const checkCtx = { ...ctx, pos };
746
- if (ctx.blockCloseCondition(checkCtx)) {
842
+ if (ctx.scope.blockCloseCondition(checkCtx)) {
747
843
  break;
748
844
  }
749
845
  }
@@ -795,7 +891,7 @@ function parseInlineUntil(ctx, endType) {
795
891
  skipWhitespace++;
796
892
  }
797
893
  const blockNameToken = ctx.tokens[afterOpen + skipWhitespace];
798
- if (blockNameToken && (blockNameToken.type === "TEXT" || blockNameToken.type === "IDENTIFIER") && blockNameToken.value.toLowerCase() === "footnoteblock" && ctx.footnoteBlockParsed) {
894
+ if (blockNameToken && (blockNameToken.type === "TEXT" || blockNameToken.type === "IDENTIFIER") && blockNameToken.value.toLowerCase() === "footnoteblock" && ctx.scope.footnoteBlockParsed) {
799
895
  isInvalidBlockOpen = true;
800
896
  }
801
897
  }
@@ -816,7 +912,9 @@ function parseInlineUntil(ctx, endType) {
816
912
  }
817
913
  }
818
914
  const isExcludedBlock = (nextMeaningfulToken?.type === "BLOCK_OPEN" || nextMeaningfulToken?.type === "BLOCK_END_OPEN") && isExcludedBlockToken(ctx, pos + lookAhead);
819
- const isBlockStart = nextMeaningfulToken && BLOCK_START_TOKENS.includes(nextMeaningfulToken.type) && nextMeaningfulToken.lineStart && !isOrphanCloseSpan && !isAnchorName && !isInvalidBlockOpen && !isInvalidHeading && !isExcludedBlock;
915
+ const isUnknownBlock = (nextMeaningfulToken?.type === "BLOCK_OPEN" || nextMeaningfulToken?.type === "BLOCK_END_OPEN") && isUnknownBlockToken(ctx, pos + lookAhead);
916
+ const isIndentedBlockOpener = nextMeaningfulToken && (nextMeaningfulToken.type === "BLOCK_OPEN" || nextMeaningfulToken.type === "BLOCK_END_OPEN") && isIndentAcceptingBlock(ctx, pos + lookAhead);
917
+ const isBlockStart = nextMeaningfulToken && BLOCK_START_TOKENS.includes(nextMeaningfulToken.type) && (nextMeaningfulToken.lineStart || isIndentedBlockOpener) && !isOrphanCloseSpan && !isAnchorName && !isInvalidBlockOpen && !isInvalidHeading && !isExcludedBlock && !isUnknownBlock;
820
918
  if (!nextMeaningfulToken || nextMeaningfulToken.type === "NEWLINE" || nextMeaningfulToken.type === "EOF" || isBlockStart) {
821
919
  if (isBlockStart && nodes.length > 0) {
822
920
  const nextPos = pos + lookAhead;
@@ -1153,7 +1251,6 @@ function buildListData(topLtype, list) {
1153
1251
  items
1154
1252
  };
1155
1253
  }
1156
-
1157
1254
  // packages/parser/src/parser/rules/block/utils.ts
1158
1255
  function canApplyBlockRule(rule, token) {
1159
1256
  if (rule.requiresLineStart && !token.lineStart) {
@@ -1195,8 +1292,11 @@ function parseBlocksUntil(ctx, closeCondition, options) {
1195
1292
  ...ctx,
1196
1293
  pos,
1197
1294
  blockRules,
1198
- blockCloseCondition: closeCondition,
1199
- excludedBlockNames: excluded
1295
+ scope: {
1296
+ ...ctx.scope,
1297
+ blockCloseCondition: closeCondition,
1298
+ excludedBlockNames: excluded
1299
+ }
1200
1300
  };
1201
1301
  for (const rule of blockRules) {
1202
1302
  if (canApplyBlockRule(rule, token)) {
@@ -2184,15 +2284,15 @@ var divRule = {
2184
2284
  if (ctx.tokens[pos]?.type !== "NEWLINE") {
2185
2285
  return consumeFailedDiv(ctx);
2186
2286
  }
2187
- if (ctx.divClosesBudget === 0) {
2287
+ if (ctx.scope.divClosesBudget === 0) {
2188
2288
  return { success: false };
2189
2289
  }
2190
2290
  pos++;
2191
2291
  consumed++;
2192
2292
  const openPosition = openToken.position;
2193
2293
  let bodyBudget;
2194
- if (ctx.divClosesBudget !== undefined) {
2195
- bodyBudget = ctx.divClosesBudget - 1;
2294
+ if (ctx.scope.divClosesBudget !== undefined) {
2295
+ bodyBudget = ctx.scope.divClosesBudget - 1;
2196
2296
  } else {
2197
2297
  const closesInScope = countDivCloses(ctx, pos);
2198
2298
  bodyBudget = closesInScope > 0 ? closesInScope - 1 : 0;
@@ -2207,7 +2307,11 @@ var divRule = {
2207
2307
  }
2208
2308
  return false;
2209
2309
  };
2210
- const bodyCtx = { ...ctx, pos, divClosesBudget: bodyBudget };
2310
+ const bodyCtx = {
2311
+ ...ctx,
2312
+ pos,
2313
+ scope: { ...ctx.scope, divClosesBudget: bodyBudget }
2314
+ };
2211
2315
  let children;
2212
2316
  if (paragraphStrip) {
2213
2317
  const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
@@ -3850,10 +3954,10 @@ var footnoteBlockRule = {
3850
3954
  }
3851
3955
  pos++;
3852
3956
  consumed++;
3853
- if (ctx.footnoteBlockParsed) {
3957
+ if (ctx.scope.footnoteBlockParsed) {
3854
3958
  return { success: false };
3855
3959
  }
3856
- ctx.footnoteBlockParsed = true;
3960
+ ctx.scope = { ...ctx.scope, footnoteBlockParsed: true };
3857
3961
  const title = attrs.title !== undefined ? attrs.title : null;
3858
3962
  const hide = attrs.hide === "true" || attrs.hide === "yes";
3859
3963
  return {
@@ -4609,6 +4713,24 @@ var mathBlockRule = {
4609
4713
  };
4610
4714
 
4611
4715
  // packages/parser/src/parser/rules/block/html.ts
4716
+ function lookaheadHasHtmlClose(ctx, from) {
4717
+ for (let i = from;i < ctx.tokens.length; i++) {
4718
+ const t = ctx.tokens[i];
4719
+ if (!t || t.type === "EOF")
4720
+ return false;
4721
+ if (t.type !== "BLOCK_END_OPEN")
4722
+ continue;
4723
+ const closeName = parseBlockName(ctx, i + 1);
4724
+ if (closeName?.name.toLowerCase() !== "html")
4725
+ continue;
4726
+ let cp = i + 1 + closeName.consumed;
4727
+ while (ctx.tokens[cp]?.type === "WHITESPACE")
4728
+ cp++;
4729
+ if (ctx.tokens[cp]?.type === "BLOCK_CLOSE")
4730
+ return true;
4731
+ }
4732
+ return false;
4733
+ }
4612
4734
  var htmlBlockRule = {
4613
4735
  name: "html",
4614
4736
  startTokens: ["BLOCK_OPEN"],
@@ -4635,20 +4757,32 @@ var htmlBlockRule = {
4635
4757
  }
4636
4758
  pos++;
4637
4759
  consumed++;
4760
+ const disabled = ctx.settings.allowHtmlBlocks === false;
4761
+ const hasCloseAhead = disabled && lookaheadHasHtmlClose(ctx, pos);
4638
4762
  let contents = "";
4639
4763
  let foundClose = false;
4640
4764
  while (pos < ctx.tokens.length) {
4641
4765
  const token = ctx.tokens[pos];
4642
4766
  if (!token || token.type === "EOF")
4643
4767
  break;
4768
+ if (disabled && !hasCloseAhead && token.type === "NEWLINE" && ctx.tokens[pos + 1]?.type === "NEWLINE") {
4769
+ break;
4770
+ }
4644
4771
  if (token.type === "BLOCK_END_OPEN") {
4645
4772
  const closeNameResult = parseBlockName(ctx, pos + 1);
4646
4773
  if (closeNameResult?.name.toLowerCase() === "html") {
4647
- foundClose = true;
4648
- break;
4774
+ let checkPos = pos + 1 + closeNameResult.consumed;
4775
+ while (ctx.tokens[checkPos]?.type === "WHITESPACE")
4776
+ checkPos++;
4777
+ if (ctx.tokens[checkPos]?.type === "BLOCK_CLOSE") {
4778
+ foundClose = true;
4779
+ break;
4780
+ }
4649
4781
  }
4650
4782
  }
4651
- contents += token.value;
4783
+ if (!disabled) {
4784
+ contents += token.value;
4785
+ }
4652
4786
  pos++;
4653
4787
  consumed++;
4654
4788
  }
@@ -4659,7 +4793,16 @@ var htmlBlockRule = {
4659
4793
  message: "Missing closing tag [[/html]] for [[html]]",
4660
4794
  position: openToken.position
4661
4795
  });
4662
- return { success: false };
4796
+ if (!disabled) {
4797
+ return { success: false };
4798
+ }
4799
+ ctx.diagnostics.push({
4800
+ severity: "info",
4801
+ code: "html-block-disabled",
4802
+ message: "[[html]] block ignored: disabled by settings",
4803
+ position: openToken.position
4804
+ });
4805
+ return { success: true, elements: [], consumed };
4663
4806
  }
4664
4807
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4665
4808
  pos++;
@@ -4669,6 +4812,10 @@ var htmlBlockRule = {
4669
4812
  pos += closeNameResult.consumed;
4670
4813
  consumed += closeNameResult.consumed;
4671
4814
  }
4815
+ while (ctx.tokens[pos]?.type === "WHITESPACE") {
4816
+ pos++;
4817
+ consumed++;
4818
+ }
4672
4819
  if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
4673
4820
  pos++;
4674
4821
  consumed++;
@@ -4678,6 +4825,15 @@ var htmlBlockRule = {
4678
4825
  consumed++;
4679
4826
  }
4680
4827
  }
4828
+ if (disabled) {
4829
+ ctx.diagnostics.push({
4830
+ severity: "info",
4831
+ code: "html-block-disabled",
4832
+ message: "[[html]] block ignored: disabled by settings",
4833
+ position: openToken.position
4834
+ });
4835
+ return { success: true, elements: [], consumed };
4836
+ }
4681
4837
  contents = contents.trim();
4682
4838
  ctx.htmlBlocks.push(contents);
4683
4839
  return {
@@ -6363,6 +6519,82 @@ var commentRule = {
6363
6519
  }
6364
6520
  };
6365
6521
 
6522
+ // packages/parser/src/parser/rules/inline/html.ts
6523
+ var htmlInlineRule = {
6524
+ name: "html",
6525
+ startTokens: ["BLOCK_OPEN"],
6526
+ parse(ctx) {
6527
+ const openToken = currentToken(ctx);
6528
+ if (openToken.type !== "BLOCK_OPEN") {
6529
+ return { success: false };
6530
+ }
6531
+ let pos = ctx.pos + 1;
6532
+ let consumed = 1;
6533
+ const nameResult = parseBlockName(ctx, pos);
6534
+ if (!nameResult || nameResult.name.toLowerCase() !== "html") {
6535
+ return { success: false };
6536
+ }
6537
+ pos += nameResult.consumed;
6538
+ consumed += nameResult.consumed;
6539
+ const attrResult = parseAttributesRaw(ctx, pos);
6540
+ pos += attrResult.consumed;
6541
+ consumed += attrResult.consumed;
6542
+ if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
6543
+ return { success: false };
6544
+ }
6545
+ pos++;
6546
+ consumed++;
6547
+ if (ctx.settings.allowHtmlBlocks !== false) {
6548
+ return { success: false };
6549
+ }
6550
+ const hasCloseAhead = lookaheadHasHtmlClose(ctx, pos);
6551
+ let foundClose = false;
6552
+ while (pos < ctx.tokens.length) {
6553
+ const token = ctx.tokens[pos];
6554
+ if (!token || token.type === "EOF")
6555
+ break;
6556
+ if (!hasCloseAhead && token.type === "NEWLINE" && ctx.tokens[pos + 1]?.type === "NEWLINE") {
6557
+ break;
6558
+ }
6559
+ if (token.type === "BLOCK_END_OPEN") {
6560
+ const closeNameResult = parseBlockName(ctx, pos + 1);
6561
+ if (closeNameResult?.name.toLowerCase() === "html") {
6562
+ let checkPos = pos + 1 + closeNameResult.consumed;
6563
+ while (ctx.tokens[checkPos]?.type === "WHITESPACE")
6564
+ checkPos++;
6565
+ if (ctx.tokens[checkPos]?.type === "BLOCK_CLOSE") {
6566
+ foundClose = true;
6567
+ consumed += checkPos - pos + 1;
6568
+ pos = checkPos + 1;
6569
+ if (ctx.tokens[pos]?.type === "NEWLINE") {
6570
+ pos++;
6571
+ consumed++;
6572
+ }
6573
+ break;
6574
+ }
6575
+ }
6576
+ }
6577
+ pos++;
6578
+ consumed++;
6579
+ }
6580
+ if (!foundClose) {
6581
+ ctx.diagnostics.push({
6582
+ severity: "warning",
6583
+ code: "unclosed-block",
6584
+ message: "Missing closing tag [[/html]] for [[html]]",
6585
+ position: openToken.position
6586
+ });
6587
+ }
6588
+ ctx.diagnostics.push({
6589
+ severity: "info",
6590
+ code: "html-block-disabled",
6591
+ message: "[[html]] block ignored: disabled by settings",
6592
+ position: openToken.position
6593
+ });
6594
+ return { success: true, elements: [], consumed };
6595
+ }
6596
+ };
6597
+
6366
6598
  // packages/parser/src/parser/rules/inline/raw.ts
6367
6599
  var rawRule = {
6368
6600
  name: "raw",
@@ -8073,6 +8305,7 @@ var inlineRules = [
8073
8305
  underscoreLineBreakRule,
8074
8306
  newlineLineBreakRule,
8075
8307
  commentRule,
8308
+ htmlInlineRule,
8076
8309
  rawRule,
8077
8310
  imageRule,
8078
8311
  sizeRule,
@@ -8590,89 +8823,339 @@ function buildTableOfContents(entries) {
8590
8823
  return trees.map((tree) => buildTocList(indexer, tree.list));
8591
8824
  }
8592
8825
 
8593
- // packages/parser/src/parser/parse.ts
8594
- class Parser {
8595
- ctx;
8596
- constructor(tokens, options = {}) {
8597
- this.ctx = {
8598
- tokens,
8599
- pos: 0,
8600
- version: options.version ?? "wikidot",
8601
- trackPositions: options.trackPositions ?? true,
8602
- settings: options.settings ?? DEFAULT_SETTINGS,
8603
- footnotes: [],
8604
- tocEntries: [],
8605
- codeBlocks: [],
8606
- htmlBlocks: [],
8607
- footnoteBlockParsed: false,
8608
- bibcites: [],
8609
- diagnostics: [],
8610
- blockRules,
8611
- blockFallbackRule: paragraphRule,
8612
- inlineRules
8613
- };
8614
- }
8615
- parse() {
8616
- const children = [];
8617
- while (!this.isAtEnd()) {
8618
- const blocks = this.parseBlock();
8619
- children.push(...blocks);
8620
- }
8621
- const mergedChildren = mergeSpanStripParagraphs(children);
8622
- const divProcessed = suppressDivAdjacentParagraphs(mergedChildren);
8623
- const cleanedChildren = cleanInternalFlags(divProcessed);
8624
- const hasFootnoteBlock = cleanedChildren.some((el) => el.element === "footnote-block");
8625
- if (!hasFootnoteBlock) {
8626
- cleanedChildren.push({
8627
- element: "footnote-block",
8628
- data: { title: null, hide: false }
8629
- });
8826
+ // packages/parser/src/parser/rules/block/module/walk.ts
8827
+ function walkElements(elements, callback) {
8828
+ for (const element of elements) {
8829
+ callback(element);
8830
+ if (element.element === "list") {
8831
+ const listData = element.data;
8832
+ for (const item of listData.items) {
8833
+ if (item["item-type"] === "elements") {
8834
+ walkElements(item.elements, callback);
8835
+ } else if (item["item-type"] === "sub-list") {
8836
+ walkElements([{ element: "list", data: item.data }], callback);
8837
+ }
8838
+ }
8839
+ continue;
8630
8840
  }
8631
- const tableOfContents = buildTableOfContents(this.ctx.tocEntries);
8632
- const result = {
8633
- elements: cleanedChildren
8634
- };
8635
- if (tableOfContents.length > 0) {
8636
- result["table-of-contents"] = tableOfContents;
8841
+ if (element.element === "table") {
8842
+ const tableData = element.data;
8843
+ for (const row of tableData.rows) {
8844
+ for (const cell of row.cells) {
8845
+ walkElements(cell.elements, callback);
8846
+ }
8847
+ }
8848
+ continue;
8637
8849
  }
8638
- if (this.ctx.footnotes.length > 0) {
8639
- result.footnotes = this.ctx.footnotes;
8850
+ if (element.element === "definition-list") {
8851
+ const defListData = element.data;
8852
+ for (const item of defListData) {
8853
+ walkElements(item.key, callback);
8854
+ walkElements(item.value, callback);
8855
+ }
8856
+ continue;
8640
8857
  }
8641
- if (this.ctx.codeBlocks.length > 0) {
8642
- result["code-blocks"] = this.ctx.codeBlocks;
8858
+ if (element.element === "tab-view") {
8859
+ const tabData = element.data;
8860
+ for (const tab of tabData) {
8861
+ walkElements(tab.elements, callback);
8862
+ }
8863
+ continue;
8643
8864
  }
8644
- if (this.ctx.htmlBlocks.length > 0) {
8645
- result["html-blocks"] = this.ctx.htmlBlocks;
8865
+ if ("data" in element && element.data && typeof element.data === "object") {
8866
+ const data = element.data;
8867
+ if ("elements" in data && Array.isArray(data.elements)) {
8868
+ walkElements(data.elements, callback);
8869
+ }
8646
8870
  }
8647
- return { ast: result, diagnostics: this.ctx.diagnostics };
8648
- }
8649
- isAtEnd() {
8650
- return this.ctx.pos >= this.ctx.tokens.length || this.currentToken().type === "EOF";
8651
- }
8652
- currentToken() {
8653
- return this.ctx.tokens[this.ctx.pos] ?? this.eofToken();
8654
8871
  }
8655
- eofToken() {
8872
+ }
8873
+ function mapElementChildren(element, transform) {
8874
+ if (element.element === "list") {
8875
+ const listData = element.data;
8876
+ const newItems = [];
8877
+ for (const item of listData.items) {
8878
+ if (item["item-type"] === "elements") {
8879
+ newItems.push({
8880
+ "item-type": "elements",
8881
+ attributes: item.attributes,
8882
+ elements: transform(item.elements)
8883
+ });
8884
+ } else if (item["item-type"] === "sub-list") {
8885
+ const subListResult = transform([{ element: "list", data: item.data }]);
8886
+ const resolvedList = subListResult[0];
8887
+ if (resolvedList?.element === "list") {
8888
+ newItems.push({
8889
+ "item-type": "sub-list",
8890
+ element: "list",
8891
+ data: resolvedList.data
8892
+ });
8893
+ } else {
8894
+ newItems.push(item);
8895
+ }
8896
+ }
8897
+ }
8656
8898
  return {
8657
- type: "EOF",
8658
- value: "",
8659
- position: {
8660
- start: { line: 0, column: 0, offset: 0 },
8661
- end: { line: 0, column: 0, offset: 0 }
8662
- },
8663
- lineStart: false
8899
+ element: "list",
8900
+ data: { ...listData, items: newItems }
8664
8901
  };
8665
8902
  }
8666
- skipWhitespace() {
8667
- while (this.currentToken().type === "WHITESPACE") {
8668
- this.ctx.pos++;
8669
- }
8670
- }
8671
- parseBlock() {
8672
- this.skipWhitespace();
8673
- if (this.isAtEnd()) {
8674
- return [];
8675
- }
8903
+ if (element.element === "table") {
8904
+ const tableData = element.data;
8905
+ const newRows = [];
8906
+ for (const row of tableData.rows) {
8907
+ const newCells = [];
8908
+ for (const cell of row.cells) {
8909
+ newCells.push({ ...cell, elements: transform(cell.elements) });
8910
+ }
8911
+ newRows.push({ ...row, cells: newCells });
8912
+ }
8913
+ return {
8914
+ element: "table",
8915
+ data: { ...tableData, rows: newRows }
8916
+ };
8917
+ }
8918
+ if (element.element === "definition-list") {
8919
+ const defListData = element.data;
8920
+ const newItems = [];
8921
+ for (const item of defListData) {
8922
+ newItems.push({
8923
+ key_string: item.key_string,
8924
+ key: transform(item.key),
8925
+ value: transform(item.value)
8926
+ });
8927
+ }
8928
+ return {
8929
+ element: "definition-list",
8930
+ data: newItems
8931
+ };
8932
+ }
8933
+ if (element.element === "tab-view") {
8934
+ const tabData = element.data;
8935
+ const newTabs = [];
8936
+ for (const tab of tabData) {
8937
+ newTabs.push({ ...tab, elements: transform(tab.elements) });
8938
+ }
8939
+ return {
8940
+ element: "tab-view",
8941
+ data: newTabs
8942
+ };
8943
+ }
8944
+ if ("data" in element && element.data && typeof element.data === "object") {
8945
+ const data = element.data;
8946
+ if ("elements" in data && Array.isArray(data.elements)) {
8947
+ return {
8948
+ ...element,
8949
+ data: {
8950
+ ...data,
8951
+ elements: transform(data.elements)
8952
+ }
8953
+ };
8954
+ }
8955
+ }
8956
+ return element;
8957
+ }
8958
+ function mapElementChildrenWithState(element, state, transform) {
8959
+ if (element.element === "list") {
8960
+ const listData = element.data;
8961
+ const newItems = [];
8962
+ let currentState = state;
8963
+ for (const item of listData.items) {
8964
+ if (item["item-type"] === "elements") {
8965
+ const result = transform(item.elements, currentState);
8966
+ newItems.push({
8967
+ "item-type": "elements",
8968
+ attributes: item.attributes,
8969
+ elements: result.elements
8970
+ });
8971
+ currentState = result.state;
8972
+ } else if (item["item-type"] === "sub-list") {
8973
+ const result = transform([{ element: "list", data: item.data }], currentState);
8974
+ const resolvedList = result.elements[0];
8975
+ if (resolvedList?.element === "list") {
8976
+ newItems.push({
8977
+ "item-type": "sub-list",
8978
+ element: "list",
8979
+ data: resolvedList.data
8980
+ });
8981
+ } else {
8982
+ newItems.push(item);
8983
+ }
8984
+ currentState = result.state;
8985
+ }
8986
+ }
8987
+ return {
8988
+ element: {
8989
+ element: "list",
8990
+ data: { ...listData, items: newItems }
8991
+ },
8992
+ state: currentState
8993
+ };
8994
+ }
8995
+ if (element.element === "table") {
8996
+ const tableData = element.data;
8997
+ const newRows = [];
8998
+ let currentState = state;
8999
+ for (const row of tableData.rows) {
9000
+ const newCells = [];
9001
+ for (const cell of row.cells) {
9002
+ const result = transform(cell.elements, currentState);
9003
+ newCells.push({ ...cell, elements: result.elements });
9004
+ currentState = result.state;
9005
+ }
9006
+ newRows.push({ ...row, cells: newCells });
9007
+ }
9008
+ return {
9009
+ element: {
9010
+ element: "table",
9011
+ data: { ...tableData, rows: newRows }
9012
+ },
9013
+ state: currentState
9014
+ };
9015
+ }
9016
+ if (element.element === "definition-list") {
9017
+ const defListData = element.data;
9018
+ const newItems = [];
9019
+ let currentState = state;
9020
+ for (const item of defListData) {
9021
+ const keyResult = transform(item.key, currentState);
9022
+ currentState = keyResult.state;
9023
+ const valueResult = transform(item.value, currentState);
9024
+ currentState = valueResult.state;
9025
+ newItems.push({
9026
+ key_string: item.key_string,
9027
+ key: keyResult.elements,
9028
+ value: valueResult.elements
9029
+ });
9030
+ }
9031
+ return {
9032
+ element: {
9033
+ element: "definition-list",
9034
+ data: newItems
9035
+ },
9036
+ state: currentState
9037
+ };
9038
+ }
9039
+ if (element.element === "tab-view") {
9040
+ const tabData = element.data;
9041
+ const newTabs = [];
9042
+ let currentState = state;
9043
+ for (const tab of tabData) {
9044
+ const result = transform(tab.elements, currentState);
9045
+ newTabs.push({ ...tab, elements: result.elements });
9046
+ currentState = result.state;
9047
+ }
9048
+ return {
9049
+ element: {
9050
+ element: "tab-view",
9051
+ data: newTabs
9052
+ },
9053
+ state: currentState
9054
+ };
9055
+ }
9056
+ if ("data" in element && element.data && typeof element.data === "object") {
9057
+ const data = element.data;
9058
+ if ("elements" in data && Array.isArray(data.elements)) {
9059
+ const result = transform(data.elements, state);
9060
+ return {
9061
+ element: {
9062
+ ...element,
9063
+ data: {
9064
+ ...data,
9065
+ elements: result.elements
9066
+ }
9067
+ },
9068
+ state: result.state
9069
+ };
9070
+ }
9071
+ }
9072
+ return { element, state };
9073
+ }
9074
+
9075
+ // packages/parser/src/parser/parse.ts
9076
+ class Parser {
9077
+ ctx;
9078
+ constructor(tokens, options = {}) {
9079
+ this.ctx = {
9080
+ tokens,
9081
+ pos: 0,
9082
+ version: options.version ?? "wikidot",
9083
+ trackPositions: options.trackPositions ?? true,
9084
+ settings: options.settings ?? DEFAULT_SETTINGS,
9085
+ footnotes: [],
9086
+ tocEntries: [],
9087
+ codeBlocks: [],
9088
+ htmlBlocks: [],
9089
+ bibcites: [],
9090
+ diagnostics: [],
9091
+ blockRules,
9092
+ blockFallbackRule: paragraphRule,
9093
+ inlineRules,
9094
+ scope: {
9095
+ footnoteBlockParsed: false
9096
+ }
9097
+ };
9098
+ }
9099
+ parse() {
9100
+ const children = [];
9101
+ while (!this.isAtEnd()) {
9102
+ const blocks = this.parseBlock();
9103
+ children.push(...blocks);
9104
+ }
9105
+ const mergedChildren = mergeSpanStripParagraphs(children);
9106
+ const divProcessed = suppressDivAdjacentParagraphs(mergedChildren);
9107
+ const cleanedChildren = cleanInternalFlags(divProcessed);
9108
+ if (!containsFootnoteBlock(cleanedChildren)) {
9109
+ cleanedChildren.push({
9110
+ element: "footnote-block",
9111
+ data: { title: null, hide: false }
9112
+ });
9113
+ }
9114
+ const tableOfContents = buildTableOfContents(this.ctx.tocEntries);
9115
+ const result = {
9116
+ elements: cleanedChildren
9117
+ };
9118
+ if (tableOfContents.length > 0) {
9119
+ result["table-of-contents"] = tableOfContents;
9120
+ }
9121
+ if (this.ctx.footnotes.length > 0) {
9122
+ result.footnotes = this.ctx.footnotes;
9123
+ }
9124
+ if (this.ctx.codeBlocks.length > 0) {
9125
+ result["code-blocks"] = this.ctx.codeBlocks;
9126
+ }
9127
+ if (this.ctx.htmlBlocks.length > 0) {
9128
+ result["html-blocks"] = this.ctx.htmlBlocks;
9129
+ }
9130
+ return { ast: result, diagnostics: this.ctx.diagnostics };
9131
+ }
9132
+ isAtEnd() {
9133
+ return this.ctx.pos >= this.ctx.tokens.length || this.currentToken().type === "EOF";
9134
+ }
9135
+ currentToken() {
9136
+ return this.ctx.tokens[this.ctx.pos] ?? this.eofToken();
9137
+ }
9138
+ eofToken() {
9139
+ return {
9140
+ type: "EOF",
9141
+ value: "",
9142
+ position: {
9143
+ start: { line: 0, column: 0, offset: 0 },
9144
+ end: { line: 0, column: 0, offset: 0 }
9145
+ },
9146
+ lineStart: false
9147
+ };
9148
+ }
9149
+ skipWhitespace() {
9150
+ while (this.currentToken().type === "WHITESPACE") {
9151
+ this.ctx.pos++;
9152
+ }
9153
+ }
9154
+ parseBlock() {
9155
+ this.skipWhitespace();
9156
+ if (this.isAtEnd()) {
9157
+ return [];
9158
+ }
8676
9159
  const token = this.currentToken();
8677
9160
  if (token.type === "NEWLINE") {
8678
9161
  this.ctx.pos++;
@@ -8701,6 +9184,14 @@ function parse(source, options) {
8701
9184
  const tokens = tokenize(preprocessed, { trackPositions: options?.trackPositions });
8702
9185
  return new Parser(tokens, options).parse();
8703
9186
  }
9187
+ function containsFootnoteBlock(elements) {
9188
+ let found = false;
9189
+ walkElements(elements, (el) => {
9190
+ if (el.element === "footnote-block")
9191
+ found = true;
9192
+ });
9193
+ return found;
9194
+ }
8704
9195
  // packages/parser/src/parser/rules/block/module/listpages/compiler.ts
8705
9196
  var DEFAULT_PREVIEW_LENGTH = 200;
8706
9197
  var VARIABLE_REGEX = /%%([a-z_]+)(?:\{([^}]+)\})?(?:\((\d+)\))?(?:\|([^%]*(?:%(?!%)[^%]*)*))?%%/gi;
@@ -8908,348 +9399,99 @@ function strftime(date, format) {
8908
9399
  return String(date.getDate());
8909
9400
  case "H":
8910
9401
  return pad(date.getHours());
8911
- case "I":
8912
- return pad(date.getHours() % 12 || 12);
8913
- case "M":
8914
- return pad(date.getMinutes());
8915
- case "S":
8916
- return pad(date.getSeconds());
8917
- case "p":
8918
- return date.getHours() < 12 ? "AM" : "PM";
8919
- case "b":
8920
- return MONTHS_SHORT[date.getMonth()] ?? "";
8921
- case "B":
8922
- return MONTHS[date.getMonth()] ?? "";
8923
- case "a":
8924
- return DAYS_SHORT[date.getDay()] ?? "";
8925
- case "A":
8926
- return DAYS[date.getDay()] ?? "";
8927
- case "w":
8928
- return String(date.getDay());
8929
- case "j":
8930
- return pad(getDayOfYear(date), 3);
8931
- case "Z":
8932
- return "UTC";
8933
- case "z":
8934
- return "+0000";
8935
- case "%":
8936
- return "%";
8937
- default:
8938
- return `%${token}`;
8939
- }
8940
- });
8941
- }
8942
- function getDayOfYear(date) {
8943
- const start = new Date(date.getFullYear(), 0, 0);
8944
- const diff = date.getTime() - start.getTime();
8945
- const oneDay = 1000 * 60 * 60 * 24;
8946
- return Math.floor(diff / oneDay);
8947
- }
8948
-
8949
- // packages/parser/src/parser/rules/block/module/listusers/compiler.ts
8950
- var VARIABLE_REGEX2 = /%%([a-z_]+)%%/gi;
8951
- function compileListUsersTemplate(template) {
8952
- const parts = [];
8953
- let lastIndex = 0;
8954
- for (const match of template.matchAll(VARIABLE_REGEX2)) {
8955
- if (match.index !== undefined && match.index > lastIndex) {
8956
- parts.push(template.slice(lastIndex, match.index));
8957
- }
8958
- const [, varName] = match;
8959
- if (!varName)
8960
- continue;
8961
- const getter = createVariableGetter2(varName.toLowerCase());
8962
- parts.push(getter);
8963
- lastIndex = match.index !== undefined ? match.index + match[0].length : lastIndex;
8964
- }
8965
- if (lastIndex < template.length) {
8966
- parts.push(template.slice(lastIndex));
8967
- }
8968
- return (ctx) => {
8969
- let result = "";
8970
- for (const part of parts) {
8971
- result += typeof part === "string" ? part : part(ctx);
8972
- }
8973
- return result;
8974
- };
8975
- }
8976
- function createVariableGetter2(name) {
8977
- switch (name) {
8978
- case "number":
8979
- return (ctx) => String(ctx.user.number);
8980
- case "title":
8981
- return (ctx) => ctx.user.title;
8982
- case "name":
8983
- return (ctx) => ctx.user.name;
8984
- default:
8985
- return () => "";
8986
- }
8987
- }
8988
-
8989
- // packages/parser/src/parser/rules/block/module/listusers/extract.ts
8990
- var VARIABLE_REGEX3 = /%%([a-z_]+)%%/gi;
8991
- var KNOWN_VARIABLES = ["number", "title", "name"];
8992
- function extractListUsersVariables(template) {
8993
- const variables = new Set;
8994
- for (const match of template.matchAll(VARIABLE_REGEX3)) {
8995
- const [, varName] = match;
8996
- if (!varName)
8997
- continue;
8998
- const normalized = varName.toLowerCase();
8999
- if (KNOWN_VARIABLES.includes(normalized)) {
9000
- variables.add(normalized);
9001
- }
9002
- }
9003
- return Array.from(variables);
9004
- }
9005
-
9006
- // packages/parser/src/parser/rules/block/module/walk.ts
9007
- function walkElements(elements, callback) {
9008
- for (const element of elements) {
9009
- callback(element);
9010
- if (element.element === "list") {
9011
- const listData = element.data;
9012
- for (const item of listData.items) {
9013
- if (item["item-type"] === "elements") {
9014
- walkElements(item.elements, callback);
9015
- } else if (item["item-type"] === "sub-list") {
9016
- walkElements([{ element: "list", data: item.data }], callback);
9017
- }
9018
- }
9019
- continue;
9020
- }
9021
- if (element.element === "table") {
9022
- const tableData = element.data;
9023
- for (const row of tableData.rows) {
9024
- for (const cell of row.cells) {
9025
- walkElements(cell.elements, callback);
9026
- }
9027
- }
9028
- continue;
9029
- }
9030
- if (element.element === "definition-list") {
9031
- const defListData = element.data;
9032
- for (const item of defListData) {
9033
- walkElements(item.key, callback);
9034
- walkElements(item.value, callback);
9035
- }
9036
- continue;
9037
- }
9038
- if (element.element === "tab-view") {
9039
- const tabData = element.data;
9040
- for (const tab of tabData) {
9041
- walkElements(tab.elements, callback);
9042
- }
9043
- continue;
9044
- }
9045
- if ("data" in element && element.data && typeof element.data === "object") {
9046
- const data = element.data;
9047
- if ("elements" in data && Array.isArray(data.elements)) {
9048
- walkElements(data.elements, callback);
9049
- }
9050
- }
9051
- }
9052
- }
9053
- function mapElementChildren(element, transform) {
9054
- if (element.element === "list") {
9055
- const listData = element.data;
9056
- const newItems = [];
9057
- for (const item of listData.items) {
9058
- if (item["item-type"] === "elements") {
9059
- newItems.push({
9060
- "item-type": "elements",
9061
- attributes: item.attributes,
9062
- elements: transform(item.elements)
9063
- });
9064
- } else if (item["item-type"] === "sub-list") {
9065
- const subListResult = transform([{ element: "list", data: item.data }]);
9066
- const resolvedList = subListResult[0];
9067
- if (resolvedList?.element === "list") {
9068
- newItems.push({
9069
- "item-type": "sub-list",
9070
- element: "list",
9071
- data: resolvedList.data
9072
- });
9073
- } else {
9074
- newItems.push(item);
9075
- }
9076
- }
9077
- }
9078
- return {
9079
- element: "list",
9080
- data: { ...listData, items: newItems }
9081
- };
9082
- }
9083
- if (element.element === "table") {
9084
- const tableData = element.data;
9085
- const newRows = [];
9086
- for (const row of tableData.rows) {
9087
- const newCells = [];
9088
- for (const cell of row.cells) {
9089
- newCells.push({ ...cell, elements: transform(cell.elements) });
9090
- }
9091
- newRows.push({ ...row, cells: newCells });
9092
- }
9093
- return {
9094
- element: "table",
9095
- data: { ...tableData, rows: newRows }
9096
- };
9097
- }
9098
- if (element.element === "definition-list") {
9099
- const defListData = element.data;
9100
- const newItems = [];
9101
- for (const item of defListData) {
9102
- newItems.push({
9103
- key_string: item.key_string,
9104
- key: transform(item.key),
9105
- value: transform(item.value)
9106
- });
9107
- }
9108
- return {
9109
- element: "definition-list",
9110
- data: newItems
9111
- };
9112
- }
9113
- if (element.element === "tab-view") {
9114
- const tabData = element.data;
9115
- const newTabs = [];
9116
- for (const tab of tabData) {
9117
- newTabs.push({ ...tab, elements: transform(tab.elements) });
9118
- }
9119
- return {
9120
- element: "tab-view",
9121
- data: newTabs
9122
- };
9123
- }
9124
- if ("data" in element && element.data && typeof element.data === "object") {
9125
- const data = element.data;
9126
- if ("elements" in data && Array.isArray(data.elements)) {
9127
- return {
9128
- ...element,
9129
- data: {
9130
- ...data,
9131
- elements: transform(data.elements)
9132
- }
9133
- };
9402
+ case "I":
9403
+ return pad(date.getHours() % 12 || 12);
9404
+ case "M":
9405
+ return pad(date.getMinutes());
9406
+ case "S":
9407
+ return pad(date.getSeconds());
9408
+ case "p":
9409
+ return date.getHours() < 12 ? "AM" : "PM";
9410
+ case "b":
9411
+ return MONTHS_SHORT[date.getMonth()] ?? "";
9412
+ case "B":
9413
+ return MONTHS[date.getMonth()] ?? "";
9414
+ case "a":
9415
+ return DAYS_SHORT[date.getDay()] ?? "";
9416
+ case "A":
9417
+ return DAYS[date.getDay()] ?? "";
9418
+ case "w":
9419
+ return String(date.getDay());
9420
+ case "j":
9421
+ return pad(getDayOfYear(date), 3);
9422
+ case "Z":
9423
+ return "UTC";
9424
+ case "z":
9425
+ return "+0000";
9426
+ case "%":
9427
+ return "%";
9428
+ default:
9429
+ return `%${token}`;
9134
9430
  }
9135
- }
9136
- return element;
9431
+ });
9137
9432
  }
9138
- function mapElementChildrenWithState(element, state, transform) {
9139
- if (element.element === "list") {
9140
- const listData = element.data;
9141
- const newItems = [];
9142
- let currentState = state;
9143
- for (const item of listData.items) {
9144
- if (item["item-type"] === "elements") {
9145
- const result = transform(item.elements, currentState);
9146
- newItems.push({
9147
- "item-type": "elements",
9148
- attributes: item.attributes,
9149
- elements: result.elements
9150
- });
9151
- currentState = result.state;
9152
- } else if (item["item-type"] === "sub-list") {
9153
- const result = transform([{ element: "list", data: item.data }], currentState);
9154
- const resolvedList = result.elements[0];
9155
- if (resolvedList?.element === "list") {
9156
- newItems.push({
9157
- "item-type": "sub-list",
9158
- element: "list",
9159
- data: resolvedList.data
9160
- });
9161
- } else {
9162
- newItems.push(item);
9163
- }
9164
- currentState = result.state;
9165
- }
9166
- }
9167
- return {
9168
- element: {
9169
- element: "list",
9170
- data: { ...listData, items: newItems }
9171
- },
9172
- state: currentState
9173
- };
9174
- }
9175
- if (element.element === "table") {
9176
- const tableData = element.data;
9177
- const newRows = [];
9178
- let currentState = state;
9179
- for (const row of tableData.rows) {
9180
- const newCells = [];
9181
- for (const cell of row.cells) {
9182
- const result = transform(cell.elements, currentState);
9183
- newCells.push({ ...cell, elements: result.elements });
9184
- currentState = result.state;
9185
- }
9186
- newRows.push({ ...row, cells: newCells });
9433
+ function getDayOfYear(date) {
9434
+ const start = new Date(date.getFullYear(), 0, 0);
9435
+ const diff = date.getTime() - start.getTime();
9436
+ const oneDay = 1000 * 60 * 60 * 24;
9437
+ return Math.floor(diff / oneDay);
9438
+ }
9439
+
9440
+ // packages/parser/src/parser/rules/block/module/listusers/compiler.ts
9441
+ var VARIABLE_REGEX2 = /%%([a-z_]+)%%/gi;
9442
+ function compileListUsersTemplate(template) {
9443
+ const parts = [];
9444
+ let lastIndex = 0;
9445
+ for (const match of template.matchAll(VARIABLE_REGEX2)) {
9446
+ if (match.index !== undefined && match.index > lastIndex) {
9447
+ parts.push(template.slice(lastIndex, match.index));
9187
9448
  }
9188
- return {
9189
- element: {
9190
- element: "table",
9191
- data: { ...tableData, rows: newRows }
9192
- },
9193
- state: currentState
9194
- };
9449
+ const [, varName] = match;
9450
+ if (!varName)
9451
+ continue;
9452
+ const getter = createVariableGetter2(varName.toLowerCase());
9453
+ parts.push(getter);
9454
+ lastIndex = match.index !== undefined ? match.index + match[0].length : lastIndex;
9195
9455
  }
9196
- if (element.element === "definition-list") {
9197
- const defListData = element.data;
9198
- const newItems = [];
9199
- let currentState = state;
9200
- for (const item of defListData) {
9201
- const keyResult = transform(item.key, currentState);
9202
- currentState = keyResult.state;
9203
- const valueResult = transform(item.value, currentState);
9204
- currentState = valueResult.state;
9205
- newItems.push({
9206
- key_string: item.key_string,
9207
- key: keyResult.elements,
9208
- value: valueResult.elements
9209
- });
9210
- }
9211
- return {
9212
- element: {
9213
- element: "definition-list",
9214
- data: newItems
9215
- },
9216
- state: currentState
9217
- };
9456
+ if (lastIndex < template.length) {
9457
+ parts.push(template.slice(lastIndex));
9218
9458
  }
9219
- if (element.element === "tab-view") {
9220
- const tabData = element.data;
9221
- const newTabs = [];
9222
- let currentState = state;
9223
- for (const tab of tabData) {
9224
- const result = transform(tab.elements, currentState);
9225
- newTabs.push({ ...tab, elements: result.elements });
9226
- currentState = result.state;
9459
+ return (ctx) => {
9460
+ let result = "";
9461
+ for (const part of parts) {
9462
+ result += typeof part === "string" ? part : part(ctx);
9227
9463
  }
9228
- return {
9229
- element: {
9230
- element: "tab-view",
9231
- data: newTabs
9232
- },
9233
- state: currentState
9234
- };
9464
+ return result;
9465
+ };
9466
+ }
9467
+ function createVariableGetter2(name) {
9468
+ switch (name) {
9469
+ case "number":
9470
+ return (ctx) => String(ctx.user.number);
9471
+ case "title":
9472
+ return (ctx) => ctx.user.title;
9473
+ case "name":
9474
+ return (ctx) => ctx.user.name;
9475
+ default:
9476
+ return () => "";
9235
9477
  }
9236
- if ("data" in element && element.data && typeof element.data === "object") {
9237
- const data = element.data;
9238
- if ("elements" in data && Array.isArray(data.elements)) {
9239
- const result = transform(data.elements, state);
9240
- return {
9241
- element: {
9242
- ...element,
9243
- data: {
9244
- ...data,
9245
- elements: result.elements
9246
- }
9247
- },
9248
- state: result.state
9249
- };
9478
+ }
9479
+
9480
+ // packages/parser/src/parser/rules/block/module/listusers/extract.ts
9481
+ var VARIABLE_REGEX3 = /%%([a-z_]+)%%/gi;
9482
+ var KNOWN_VARIABLES = ["number", "title", "name"];
9483
+ function extractListUsersVariables(template) {
9484
+ const variables = new Set;
9485
+ for (const match of template.matchAll(VARIABLE_REGEX3)) {
9486
+ const [, varName] = match;
9487
+ if (!varName)
9488
+ continue;
9489
+ const normalized = varName.toLowerCase();
9490
+ if (KNOWN_VARIABLES.includes(normalized)) {
9491
+ variables.add(normalized);
9250
9492
  }
9251
9493
  }
9252
- return { element, state };
9494
+ return Array.from(variables);
9253
9495
  }
9254
9496
 
9255
9497
  // packages/parser/src/parser/rules/block/module/listpages/extract.ts
@@ -9908,6 +10150,110 @@ function resolveIfTags(data, pageTags) {
9908
10150
  const matched = evaluateTagCondition(condition, pageTags);
9909
10151
  return { evaluated: true, matched };
9910
10152
  }
10153
+ // packages/parser/src/parser/rules/block/module/iftags/preprocess.ts
10154
+ var BASE_PLACEHOLDER_OPEN = "";
10155
+ var BASE_PLACEHOLDER_CLOSE = "";
10156
+ var INNERMOST_IFTAGS_PATTERN = /\[\[\s*iftags\b([^\]]*)\]\]((?:(?!\[\[\s*iftags\b|\[\[\/\s*iftags\s*\]\]).)*)\[\[\/\s*iftags\s*\]\]/gis;
10157
+ var RAW_BLOCK_OPEN_PATTERN = /\[\[\s*(code|html)\b[^\]]*\]\]/iy;
10158
+ function preprocessIftags(source, pageTags) {
10159
+ if (pageTags === null)
10160
+ return source;
10161
+ if (!source.includes("[["))
10162
+ return source;
10163
+ const sentinels = makeUniqueSentinels(source);
10164
+ const { masked, placeholders } = maskRawRegions(source, sentinels);
10165
+ const reduced = reduceIftags(masked, pageTags);
10166
+ return restorePlaceholders(reduced, placeholders, sentinels);
10167
+ }
10168
+ function makeUniqueSentinels(source) {
10169
+ let open = BASE_PLACEHOLDER_OPEN;
10170
+ let close = BASE_PLACEHOLDER_CLOSE;
10171
+ while (source.includes(open) || source.includes(close)) {
10172
+ open += BASE_PLACEHOLDER_OPEN;
10173
+ close += BASE_PLACEHOLDER_CLOSE;
10174
+ }
10175
+ return { open, close };
10176
+ }
10177
+ function reduceIftags(source, pageTags) {
10178
+ let current = source;
10179
+ const maxIterations = source.length + 1;
10180
+ for (let i = 0;i < maxIterations; i++) {
10181
+ const next = current.replace(INNERMOST_IFTAGS_PATTERN, (_, cond, body) => {
10182
+ const condition = parseTagCondition(cond);
10183
+ return evaluateTagCondition(condition, pageTags) ? body : "";
10184
+ });
10185
+ if (next === current)
10186
+ return current;
10187
+ current = next;
10188
+ }
10189
+ return current;
10190
+ }
10191
+ function maskRawRegions(source, sentinels) {
10192
+ const placeholders = [];
10193
+ let masked = "";
10194
+ let i = 0;
10195
+ while (i < source.length) {
10196
+ if (source[i] === "[" && source[i + 1] === "[") {
10197
+ RAW_BLOCK_OPEN_PATTERN.lastIndex = i;
10198
+ const openMatch = RAW_BLOCK_OPEN_PATTERN.exec(source);
10199
+ if (openMatch) {
10200
+ const name = openMatch[1].toLowerCase();
10201
+ const openLen = openMatch[0].length;
10202
+ const closePattern = new RegExp(`\\[\\[\\/\\s*${name}\\s*\\]\\]`, "ig");
10203
+ closePattern.lastIndex = i + openLen;
10204
+ const closeMatch = closePattern.exec(source);
10205
+ if (closeMatch) {
10206
+ const regionEnd = closeMatch.index + closeMatch[0].length;
10207
+ masked += pushPlaceholder(placeholders, source.slice(i, regionEnd), sentinels);
10208
+ i = regionEnd;
10209
+ continue;
10210
+ }
10211
+ if (name === "code") {
10212
+ masked += pushPlaceholder(placeholders, source.slice(i), sentinels);
10213
+ i = source.length;
10214
+ continue;
10215
+ }
10216
+ }
10217
+ }
10218
+ if (source[i] === "@" && source[i + 1] === "<") {
10219
+ const close = source.indexOf(">@", i + 2);
10220
+ const newline = source.indexOf(`
10221
+ `, i + 2);
10222
+ if (close !== -1 && (newline === -1 || close < newline)) {
10223
+ const regionEnd = close + 2;
10224
+ masked += pushPlaceholder(placeholders, source.slice(i, regionEnd), sentinels);
10225
+ i = regionEnd;
10226
+ continue;
10227
+ }
10228
+ }
10229
+ if (source[i] === "@" && source[i + 1] === "@") {
10230
+ const close = source.indexOf("@@", i + 2);
10231
+ const newline = source.indexOf(`
10232
+ `, i + 2);
10233
+ if (close !== -1 && (newline === -1 || close < newline)) {
10234
+ const regionEnd = close + 2;
10235
+ masked += pushPlaceholder(placeholders, source.slice(i, regionEnd), sentinels);
10236
+ i = regionEnd;
10237
+ continue;
10238
+ }
10239
+ }
10240
+ masked += source[i];
10241
+ i++;
10242
+ }
10243
+ return { masked, placeholders };
10244
+ }
10245
+ function pushPlaceholder(placeholders, text, sentinels) {
10246
+ const idx = placeholders.length;
10247
+ placeholders.push(text);
10248
+ return `${sentinels.open}${idx}${sentinels.close}`;
10249
+ }
10250
+ function escapeRegex(str) {
10251
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10252
+ }
10253
+ function restorePlaceholders(source, placeholders, sentinels) {
10254
+ const pattern = new RegExp(`${escapeRegex(sentinels.open)}(\\d+)${escapeRegex(sentinels.close)}`, "g");
10255
+ return source.replace(pattern, (_, idx) => placeholders[Number(idx)] ?? "");
10256
+ }
9911
10257
  // packages/parser/src/parser/rules/block/module/include/resolve.ts
9912
10258
  function resolveIncludes(source, fetcher, options) {
9913
10259
  if (options?.settings && !options.settings.enablePageSyntax) {
@@ -9953,7 +10299,67 @@ async function resolveIncludesAsync(source, fetcher, options) {
9953
10299
  };
9954
10300
  return expandIterativeAsync(source, cachedFetcher, maxIterations);
9955
10301
  }
9956
- var INCLUDE_PATTERN = /^\[\[include\s([^\]]*(?:\](?!\])[^\]]*)*)\]\]/gim;
10302
+ var INCLUDE_OPEN_PATTERN = /^\[\[include\s/gim;
10303
+ function isRestOfLineBlank(source, pos) {
10304
+ for (let i = pos;i < source.length; i++) {
10305
+ const ch = source[i];
10306
+ if (ch === `
10307
+ `)
10308
+ return true;
10309
+ if (ch !== " " && ch !== "\t" && ch !== "\r")
10310
+ return false;
10311
+ }
10312
+ return true;
10313
+ }
10314
+ function scanIncludeDirectives(source) {
10315
+ const matches = [];
10316
+ const opener = new RegExp(INCLUDE_OPEN_PATTERN.source, INCLUDE_OPEN_PATTERN.flags);
10317
+ let m;
10318
+ while ((m = opener.exec(source)) !== null) {
10319
+ const start = m.index;
10320
+ const contentStart = start + m[0].length;
10321
+ const firstNewline = source.indexOf(`
10322
+ `, start);
10323
+ let depth = 0;
10324
+ let linkDepth = 0;
10325
+ let i = start;
10326
+ let closeEnd = -1;
10327
+ while (i < source.length) {
10328
+ if (source.startsWith("[[[", i)) {
10329
+ linkDepth++;
10330
+ i += 3;
10331
+ } else if (linkDepth > 0 && source.startsWith("]]]", i)) {
10332
+ linkDepth--;
10333
+ i += 3;
10334
+ } else if (linkDepth > 0) {
10335
+ i++;
10336
+ } else if (source.startsWith("[[", i)) {
10337
+ depth++;
10338
+ i += 2;
10339
+ } else if (source.startsWith("]]", i)) {
10340
+ const closeStart = i;
10341
+ depth--;
10342
+ i += 2;
10343
+ if (depth <= 0) {
10344
+ const onOpenerLine = firstNewline === -1 || closeStart < firstNewline;
10345
+ if (onOpenerLine || isRestOfLineBlank(source, i)) {
10346
+ closeEnd = i;
10347
+ break;
10348
+ }
10349
+ }
10350
+ } else {
10351
+ i++;
10352
+ }
10353
+ }
10354
+ if (closeEnd === -1) {
10355
+ opener.lastIndex = start + 2;
10356
+ continue;
10357
+ }
10358
+ matches.push({ start, end: closeEnd, inner: source.slice(contentStart, closeEnd - 2) });
10359
+ opener.lastIndex = closeEnd;
10360
+ }
10361
+ return matches;
10362
+ }
9957
10363
  function parseIncludeDirective(inner) {
9958
10364
  const normalized = inner.replace(/\n/g, " ");
9959
10365
  const parts = normalized.split("|");
@@ -9977,14 +10383,24 @@ function parseIncludeDirective(inner) {
9977
10383
  }
9978
10384
  }
9979
10385
  const variables = {};
10386
+ const hasConcrete = new Set;
9980
10387
  for (const segment of varSegments) {
9981
10388
  const eqIndex = segment.indexOf("=");
9982
- if (eqIndex !== -1) {
9983
- const key = segment.slice(0, eqIndex).trim();
9984
- const value = segment.slice(eqIndex + 1).trim();
9985
- if (key) {
10389
+ if (eqIndex === -1)
10390
+ continue;
10391
+ const key = segment.slice(0, eqIndex).trim();
10392
+ if (!key)
10393
+ continue;
10394
+ const value = segment.slice(eqIndex + 1).trim();
10395
+ const isPlaceholder = /^\{\$[^}]*\}$/.test(value);
10396
+ const isConcrete = value !== "" && !isPlaceholder;
10397
+ if (isConcrete) {
10398
+ if (!hasConcrete.has(key)) {
9986
10399
  variables[key] = value;
10400
+ hasConcrete.add(key);
9987
10401
  }
10402
+ } else if (!Object.hasOwn(variables, key)) {
10403
+ variables[key] = value;
9988
10404
  }
9989
10405
  }
9990
10406
  let location;
@@ -10001,7 +10417,7 @@ function parseIncludeDirective(inner) {
10001
10417
  }
10002
10418
  return { location, variables };
10003
10419
  }
10004
- function replaceOneInclude(_match, inner, fetcher) {
10420
+ function replaceOneInclude(inner, fetcher) {
10005
10421
  const { location, variables } = parseIncludeDirective(inner);
10006
10422
  const content = fetcher(location);
10007
10423
  if (content === null) {
@@ -10014,25 +10430,33 @@ Page to be included "${location.page}" cannot be found!
10014
10430
  function expandIterative(source, fetcher, maxIterations) {
10015
10431
  let current = source;
10016
10432
  for (let i = 0;i < maxIterations; i++) {
10017
- const previous = current;
10018
- current = current.replace(INCLUDE_PATTERN, (_match, inner) => replaceOneInclude(_match, inner, fetcher));
10019
- if (current === previous)
10433
+ const directives = scanIncludeDirectives(current);
10434
+ if (directives.length === 0)
10435
+ break;
10436
+ let result = "";
10437
+ let lastPos = 0;
10438
+ for (const { start, end, inner } of directives) {
10439
+ result += current.slice(lastPos, start);
10440
+ result += replaceOneInclude(inner, fetcher);
10441
+ lastPos = end;
10442
+ }
10443
+ result += current.slice(lastPos);
10444
+ if (result === current)
10020
10445
  break;
10446
+ current = result;
10021
10447
  }
10022
10448
  return current;
10023
10449
  }
10024
10450
  async function expandIterativeAsync(source, fetcher, maxIterations) {
10025
10451
  let current = source;
10026
10452
  for (let i = 0;i < maxIterations; i++) {
10027
- const previous = current;
10028
- const pattern = new RegExp(INCLUDE_PATTERN.source, INCLUDE_PATTERN.flags);
10453
+ const directives = scanIncludeDirectives(current);
10454
+ if (directives.length === 0)
10455
+ break;
10029
10456
  let result = "";
10030
10457
  let lastPos = 0;
10031
- let match;
10032
- while ((match = pattern.exec(current)) !== null) {
10033
- const fullMatch = match[0];
10034
- const inner = match[1];
10035
- result += current.slice(lastPos, match.index);
10458
+ for (const { start, end, inner } of directives) {
10459
+ result += current.slice(lastPos, start);
10036
10460
  const { location, variables } = parseIncludeDirective(inner);
10037
10461
  const content = await fetcher(location);
10038
10462
  if (content === null) {
@@ -10042,12 +10466,12 @@ Page to be included "${location.page}" cannot be found!
10042
10466
  } else {
10043
10467
  result += substituteVariables(content, variables);
10044
10468
  }
10045
- lastPos = match.index + fullMatch.length;
10469
+ lastPos = end;
10046
10470
  }
10047
10471
  result += current.slice(lastPos);
10048
- current = result;
10049
- if (current === previous)
10472
+ if (result === current)
10050
10473
  break;
10474
+ current = result;
10051
10475
  }
10052
10476
  return current;
10053
10477
  }
@@ -10280,6 +10704,7 @@ export {
10280
10704
  resolveListUsers,
10281
10705
  resolveIncludesAsync,
10282
10706
  resolveIncludes,
10707
+ preprocessIftags,
10283
10708
  parseTags,
10284
10709
  parseParent,
10285
10710
  parseOrder,