@wdprlib/parser 2.2.0 → 3.1.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
  `) {
@@ -635,6 +641,28 @@ var KNOWN_BLOCK_NAMES = new Set([
635
641
  "gallery",
636
642
  "file"
637
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
+ ]);
638
666
 
639
667
  // packages/parser/src/parser/rules/utils.ts
640
668
  var SAFE_ATTRIBUTES = new Set([
@@ -761,13 +789,14 @@ function parseBlockName(ctx, startPos) {
761
789
 
762
790
  // packages/parser/src/parser/rules/inline/utils.ts
763
791
  function isExcludedBlockToken(ctx, tokenPos) {
764
- if (!ctx.excludedBlockNames?.size)
792
+ const excluded = ctx.scope.excludedBlockNames;
793
+ if (!excluded?.size)
765
794
  return false;
766
795
  const token = ctx.tokens[tokenPos];
767
796
  if (token?.type !== "BLOCK_OPEN" && token?.type !== "BLOCK_END_OPEN")
768
797
  return false;
769
798
  const nameResult = parseBlockName(ctx, tokenPos + 1);
770
- return nameResult !== null && ctx.excludedBlockNames.has(nameResult.name);
799
+ return nameResult !== null && excluded.has(nameResult.name);
771
800
  }
772
801
  function isUnknownBlockToken(ctx, tokenPos) {
773
802
  const token = ctx.tokens[tokenPos];
@@ -782,6 +811,15 @@ function isUnknownBlockToken(ctx, tokenPos) {
782
811
  }
783
812
  return !KNOWN_BLOCK_NAMES.has(nameResult.name);
784
813
  }
814
+ function isIndentAcceptingBlock(ctx, tokenPos) {
815
+ const token = ctx.tokens[tokenPos];
816
+ if (token?.type !== "BLOCK_OPEN" && token?.type !== "BLOCK_END_OPEN")
817
+ return false;
818
+ const nameResult = parseBlockName(ctx, tokenPos + 1);
819
+ if (nameResult === null)
820
+ return false;
821
+ return INDENT_ACCEPTING_BLOCK_NAMES.has(nameResult.name);
822
+ }
785
823
  function canApplyInlineRule(rule, token) {
786
824
  if (rule.startTokens.length === 0) {
787
825
  return true;
@@ -799,9 +837,9 @@ function parseInlineUntil(ctx, endType) {
799
837
  if (!token || token.type === "EOF") {
800
838
  break;
801
839
  }
802
- if (paragraphMode && ctx.blockCloseCondition) {
840
+ if (paragraphMode && ctx.scope.blockCloseCondition) {
803
841
  const checkCtx = { ...ctx, pos };
804
- if (ctx.blockCloseCondition(checkCtx)) {
842
+ if (ctx.scope.blockCloseCondition(checkCtx)) {
805
843
  break;
806
844
  }
807
845
  }
@@ -853,7 +891,7 @@ function parseInlineUntil(ctx, endType) {
853
891
  skipWhitespace++;
854
892
  }
855
893
  const blockNameToken = ctx.tokens[afterOpen + skipWhitespace];
856
- 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) {
857
895
  isInvalidBlockOpen = true;
858
896
  }
859
897
  }
@@ -875,7 +913,8 @@ function parseInlineUntil(ctx, endType) {
875
913
  }
876
914
  const isExcludedBlock = (nextMeaningfulToken?.type === "BLOCK_OPEN" || nextMeaningfulToken?.type === "BLOCK_END_OPEN") && isExcludedBlockToken(ctx, pos + lookAhead);
877
915
  const isUnknownBlock = (nextMeaningfulToken?.type === "BLOCK_OPEN" || nextMeaningfulToken?.type === "BLOCK_END_OPEN") && isUnknownBlockToken(ctx, pos + lookAhead);
878
- const isBlockStart = nextMeaningfulToken && BLOCK_START_TOKENS.includes(nextMeaningfulToken.type) && nextMeaningfulToken.lineStart && !isOrphanCloseSpan && !isAnchorName && !isInvalidBlockOpen && !isInvalidHeading && !isExcludedBlock && !isUnknownBlock;
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;
879
918
  if (!nextMeaningfulToken || nextMeaningfulToken.type === "NEWLINE" || nextMeaningfulToken.type === "EOF" || isBlockStart) {
880
919
  if (isBlockStart && nodes.length > 0) {
881
920
  const nextPos = pos + lookAhead;
@@ -1253,8 +1292,11 @@ function parseBlocksUntil(ctx, closeCondition, options) {
1253
1292
  ...ctx,
1254
1293
  pos,
1255
1294
  blockRules,
1256
- blockCloseCondition: closeCondition,
1257
- excludedBlockNames: excluded
1295
+ scope: {
1296
+ ...ctx.scope,
1297
+ blockCloseCondition: closeCondition,
1298
+ excludedBlockNames: excluded
1299
+ }
1258
1300
  };
1259
1301
  for (const rule of blockRules) {
1260
1302
  if (canApplyBlockRule(rule, token)) {
@@ -2242,15 +2284,15 @@ var divRule = {
2242
2284
  if (ctx.tokens[pos]?.type !== "NEWLINE") {
2243
2285
  return consumeFailedDiv(ctx);
2244
2286
  }
2245
- if (ctx.divClosesBudget === 0) {
2287
+ if (ctx.scope.divClosesBudget === 0) {
2246
2288
  return { success: false };
2247
2289
  }
2248
2290
  pos++;
2249
2291
  consumed++;
2250
2292
  const openPosition = openToken.position;
2251
2293
  let bodyBudget;
2252
- if (ctx.divClosesBudget !== undefined) {
2253
- bodyBudget = ctx.divClosesBudget - 1;
2294
+ if (ctx.scope.divClosesBudget !== undefined) {
2295
+ bodyBudget = ctx.scope.divClosesBudget - 1;
2254
2296
  } else {
2255
2297
  const closesInScope = countDivCloses(ctx, pos);
2256
2298
  bodyBudget = closesInScope > 0 ? closesInScope - 1 : 0;
@@ -2265,7 +2307,11 @@ var divRule = {
2265
2307
  }
2266
2308
  return false;
2267
2309
  };
2268
- const bodyCtx = { ...ctx, pos, divClosesBudget: bodyBudget };
2310
+ const bodyCtx = {
2311
+ ...ctx,
2312
+ pos,
2313
+ scope: { ...ctx.scope, divClosesBudget: bodyBudget }
2314
+ };
2269
2315
  let children;
2270
2316
  if (paragraphStrip) {
2271
2317
  const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
@@ -3908,10 +3954,10 @@ var footnoteBlockRule = {
3908
3954
  }
3909
3955
  pos++;
3910
3956
  consumed++;
3911
- if (ctx.footnoteBlockParsed) {
3957
+ if (ctx.scope.footnoteBlockParsed) {
3912
3958
  return { success: false };
3913
3959
  }
3914
- ctx.footnoteBlockParsed = true;
3960
+ ctx.scope = { ...ctx.scope, footnoteBlockParsed: true };
3915
3961
  const title = attrs.title !== undefined ? attrs.title : null;
3916
3962
  const hide = attrs.hide === "true" || attrs.hide === "yes";
3917
3963
  return {
@@ -4667,6 +4713,24 @@ var mathBlockRule = {
4667
4713
  };
4668
4714
 
4669
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
+ }
4670
4734
  var htmlBlockRule = {
4671
4735
  name: "html",
4672
4736
  startTokens: ["BLOCK_OPEN"],
@@ -4693,20 +4757,32 @@ var htmlBlockRule = {
4693
4757
  }
4694
4758
  pos++;
4695
4759
  consumed++;
4760
+ const disabled = ctx.settings.allowHtmlBlocks === false;
4761
+ const hasCloseAhead = disabled && lookaheadHasHtmlClose(ctx, pos);
4696
4762
  let contents = "";
4697
4763
  let foundClose = false;
4698
4764
  while (pos < ctx.tokens.length) {
4699
4765
  const token = ctx.tokens[pos];
4700
4766
  if (!token || token.type === "EOF")
4701
4767
  break;
4768
+ if (disabled && !hasCloseAhead && token.type === "NEWLINE" && ctx.tokens[pos + 1]?.type === "NEWLINE") {
4769
+ break;
4770
+ }
4702
4771
  if (token.type === "BLOCK_END_OPEN") {
4703
4772
  const closeNameResult = parseBlockName(ctx, pos + 1);
4704
4773
  if (closeNameResult?.name.toLowerCase() === "html") {
4705
- foundClose = true;
4706
- 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
+ }
4707
4781
  }
4708
4782
  }
4709
- contents += token.value;
4783
+ if (!disabled) {
4784
+ contents += token.value;
4785
+ }
4710
4786
  pos++;
4711
4787
  consumed++;
4712
4788
  }
@@ -4717,7 +4793,16 @@ var htmlBlockRule = {
4717
4793
  message: "Missing closing tag [[/html]] for [[html]]",
4718
4794
  position: openToken.position
4719
4795
  });
4720
- 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 };
4721
4806
  }
4722
4807
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4723
4808
  pos++;
@@ -4727,6 +4812,10 @@ var htmlBlockRule = {
4727
4812
  pos += closeNameResult.consumed;
4728
4813
  consumed += closeNameResult.consumed;
4729
4814
  }
4815
+ while (ctx.tokens[pos]?.type === "WHITESPACE") {
4816
+ pos++;
4817
+ consumed++;
4818
+ }
4730
4819
  if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
4731
4820
  pos++;
4732
4821
  consumed++;
@@ -4736,6 +4825,15 @@ var htmlBlockRule = {
4736
4825
  consumed++;
4737
4826
  }
4738
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
+ }
4739
4837
  contents = contents.trim();
4740
4838
  ctx.htmlBlocks.push(contents);
4741
4839
  return {
@@ -6421,6 +6519,82 @@ var commentRule = {
6421
6519
  }
6422
6520
  };
6423
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
+
6424
6598
  // packages/parser/src/parser/rules/inline/raw.ts
6425
6599
  var rawRule = {
6426
6600
  name: "raw",
@@ -8131,6 +8305,7 @@ var inlineRules = [
8131
8305
  underscoreLineBreakRule,
8132
8306
  newlineLineBreakRule,
8133
8307
  commentRule,
8308
+ htmlInlineRule,
8134
8309
  rawRule,
8135
8310
  imageRule,
8136
8311
  sizeRule,
@@ -8648,83 +8823,564 @@ function buildTableOfContents(entries) {
8648
8823
  return trees.map((tree) => buildTocList(indexer, tree.list));
8649
8824
  }
8650
8825
 
8651
- // packages/parser/src/parser/parse.ts
8652
- class Parser {
8653
- ctx;
8654
- constructor(tokens, options = {}) {
8655
- this.ctx = {
8656
- tokens,
8657
- pos: 0,
8658
- version: options.version ?? "wikidot",
8659
- trackPositions: options.trackPositions ?? true,
8660
- settings: options.settings ?? DEFAULT_SETTINGS,
8661
- footnotes: [],
8662
- tocEntries: [],
8663
- codeBlocks: [],
8664
- htmlBlocks: [],
8665
- footnoteBlockParsed: false,
8666
- bibcites: [],
8667
- diagnostics: [],
8668
- blockRules,
8669
- blockFallbackRule: paragraphRule,
8670
- inlineRules
8671
- };
8672
- }
8673
- parse() {
8674
- const children = [];
8675
- while (!this.isAtEnd()) {
8676
- const blocks = this.parseBlock();
8677
- children.push(...blocks);
8678
- }
8679
- const mergedChildren = mergeSpanStripParagraphs(children);
8680
- const divProcessed = suppressDivAdjacentParagraphs(mergedChildren);
8681
- const cleanedChildren = cleanInternalFlags(divProcessed);
8682
- const hasFootnoteBlock = cleanedChildren.some((el) => el.element === "footnote-block");
8683
- if (!hasFootnoteBlock) {
8684
- cleanedChildren.push({
8685
- element: "footnote-block",
8686
- data: { title: null, hide: false }
8687
- });
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;
8688
8840
  }
8689
- const tableOfContents = buildTableOfContents(this.ctx.tocEntries);
8690
- const result = {
8691
- elements: cleanedChildren
8692
- };
8693
- if (tableOfContents.length > 0) {
8694
- 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;
8695
8849
  }
8696
- if (this.ctx.footnotes.length > 0) {
8697
- 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;
8698
8857
  }
8699
- if (this.ctx.codeBlocks.length > 0) {
8700
- 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;
8701
8864
  }
8702
- if (this.ctx.htmlBlocks.length > 0) {
8703
- 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
+ }
8704
8870
  }
8705
- return { ast: result, diagnostics: this.ctx.diagnostics };
8706
- }
8707
- isAtEnd() {
8708
- return this.ctx.pos >= this.ctx.tokens.length || this.currentToken().type === "EOF";
8709
- }
8710
- currentToken() {
8711
- return this.ctx.tokens[this.ctx.pos] ?? this.eofToken();
8712
8871
  }
8713
- 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
+ }
8714
8898
  return {
8715
- type: "EOF",
8716
- value: "",
8717
- position: {
8718
- start: { line: 0, column: 0, offset: 0 },
8719
- end: { line: 0, column: 0, offset: 0 }
8720
- },
8721
- lineStart: false
8899
+ element: "list",
8900
+ data: { ...listData, items: newItems }
8722
8901
  };
8723
8902
  }
8724
- skipWhitespace() {
8725
- while (this.currentToken().type === "WHITESPACE") {
8726
- this.ctx.pos++;
8727
- }
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/rules/block/module/iftags/condition.ts
9076
+ function parseTagCondition(condition) {
9077
+ const required = [];
9078
+ const forbidden = [];
9079
+ const optional = [];
9080
+ const parts = condition.trim().split(/\s+/);
9081
+ for (const part of parts) {
9082
+ if (!part)
9083
+ continue;
9084
+ if (part.startsWith("+")) {
9085
+ const tag = part.slice(1);
9086
+ if (tag)
9087
+ required.push(tag);
9088
+ } else if (part.startsWith("-")) {
9089
+ const tag = part.slice(1);
9090
+ if (tag)
9091
+ forbidden.push(tag);
9092
+ } else {
9093
+ optional.push(part);
9094
+ }
9095
+ }
9096
+ return { required, forbidden, optional };
9097
+ }
9098
+ function evaluateTagCondition(condition, pageTags) {
9099
+ if (condition.required.length === 0 && condition.forbidden.length === 0 && condition.optional.length === 0) {
9100
+ return false;
9101
+ }
9102
+ const tagSet = new Set(pageTags);
9103
+ for (const tag of condition.required) {
9104
+ if (!tagSet.has(tag)) {
9105
+ return false;
9106
+ }
9107
+ }
9108
+ for (const tag of condition.forbidden) {
9109
+ if (tagSet.has(tag)) {
9110
+ return false;
9111
+ }
9112
+ }
9113
+ if (condition.optional.length > 0) {
9114
+ if (!condition.optional.some((tag) => tagSet.has(tag))) {
9115
+ return false;
9116
+ }
9117
+ }
9118
+ return true;
9119
+ }
9120
+
9121
+ // packages/parser/src/parser/rules/block/module/iftags/preprocess.ts
9122
+ var BASE_PLACEHOLDER_OPEN = "";
9123
+ var BASE_PLACEHOLDER_CLOSE = "";
9124
+ var INNERMOST_IFTAGS_PATTERN = /\[\[\s*iftags\b([^\]]*)\]\]((?:(?!\[\[\s*iftags\b|\[\[\/\s*iftags\s*\]\]).)*)\[\[\/\s*iftags\s*\]\]/gis;
9125
+ var RAW_BLOCK_OPEN_PATTERN = /\[\[\s*(code|html)\b[^\]]*\]\]/iy;
9126
+ function preprocessIftags(source, pageTags) {
9127
+ if (!source.includes("[["))
9128
+ return source;
9129
+ const sentinels = makeUniqueSentinels(source);
9130
+ const { masked, placeholders } = maskRawRegions(source, sentinels);
9131
+ const reduced = reduceIftags(masked, pageTags);
9132
+ return restorePlaceholders(reduced, placeholders, sentinels);
9133
+ }
9134
+ function makeUniqueSentinels(source) {
9135
+ let open = BASE_PLACEHOLDER_OPEN;
9136
+ let close = BASE_PLACEHOLDER_CLOSE;
9137
+ while (source.includes(open) || source.includes(close)) {
9138
+ open += BASE_PLACEHOLDER_OPEN;
9139
+ close += BASE_PLACEHOLDER_CLOSE;
9140
+ }
9141
+ return { open, close };
9142
+ }
9143
+ function reduceIftags(source, pageTags) {
9144
+ let current = source;
9145
+ const maxIterations = source.length + 1;
9146
+ const tagSet = pageTags ?? [];
9147
+ for (let i = 0;i < maxIterations; i++) {
9148
+ const depths = pageTags === null ? computeBracketDepths(current) : null;
9149
+ let changed = false;
9150
+ const next = current.replace(INNERMOST_IFTAGS_PATTERN, (match, cond, body, offset) => {
9151
+ if (depths !== null && depths[offset] === 0) {
9152
+ return match;
9153
+ }
9154
+ changed = true;
9155
+ const condition = parseTagCondition(cond);
9156
+ return evaluateTagCondition(condition, tagSet) ? body : "";
9157
+ });
9158
+ if (!changed)
9159
+ return current;
9160
+ current = next;
9161
+ }
9162
+ return current;
9163
+ }
9164
+ function computeBracketDepths(masked) {
9165
+ const n = masked.length;
9166
+ const depths = new Int32Array(n + 1);
9167
+ let depth = 0;
9168
+ let i = 0;
9169
+ while (i < n) {
9170
+ depths[i] = depth;
9171
+ const c = masked.charCodeAt(i);
9172
+ const c1 = i + 1 < n ? masked.charCodeAt(i + 1) : -1;
9173
+ const c2 = i + 2 < n ? masked.charCodeAt(i + 2) : -1;
9174
+ if (depth > 0 && c === 34 && precededByEqualsAttr(masked, i)) {
9175
+ const end = findQuoteEnd(masked, i + 1);
9176
+ for (let k = i;k <= end; k++)
9177
+ depths[k] = depth;
9178
+ i = end + 1;
9179
+ continue;
9180
+ }
9181
+ if (c === 91 && c1 === 91 && c2 === 91) {
9182
+ const end = findTripleLinkEnd(masked, i + 3);
9183
+ for (let k = i;k <= end; k++)
9184
+ depths[k] = depth;
9185
+ i = end + 1;
9186
+ continue;
9187
+ }
9188
+ if (c === 91 && c1 === 91) {
9189
+ depth++;
9190
+ depths[i + 1] = depth;
9191
+ i += 2;
9192
+ continue;
9193
+ }
9194
+ if (c === 93 && c1 === 93) {
9195
+ depth = Math.max(0, depth - 1);
9196
+ depths[i + 1] = depth;
9197
+ i += 2;
9198
+ continue;
9199
+ }
9200
+ if (c === 10) {
9201
+ depth = 0;
9202
+ }
9203
+ i++;
9204
+ }
9205
+ depths[n] = depth;
9206
+ return depths;
9207
+ }
9208
+ function precededByEqualsAttr(s, i) {
9209
+ let j = i - 1;
9210
+ while (j >= 0) {
9211
+ const ch = s.charCodeAt(j);
9212
+ if (ch === 32 || ch === 9) {
9213
+ j--;
9214
+ continue;
9215
+ }
9216
+ return ch === 61;
9217
+ }
9218
+ return false;
9219
+ }
9220
+ function findQuoteEnd(s, from) {
9221
+ for (let i = from;i < s.length; i++) {
9222
+ const ch = s.charCodeAt(i);
9223
+ if (ch === 34 || ch === 10)
9224
+ return i;
9225
+ }
9226
+ return s.length - 1;
9227
+ }
9228
+ function findTripleLinkEnd(s, from) {
9229
+ for (let i = from;i < s.length; i++) {
9230
+ if (s.charCodeAt(i) === 93 && i + 2 < s.length && s.charCodeAt(i + 1) === 93 && s.charCodeAt(i + 2) === 93) {
9231
+ return i + 2;
9232
+ }
9233
+ if (s.charCodeAt(i) === 10 && i + 1 < s.length && s.charCodeAt(i + 1) === 10) {
9234
+ return i;
9235
+ }
9236
+ }
9237
+ return s.length - 1;
9238
+ }
9239
+ function maskRawRegions(source, sentinels) {
9240
+ const placeholders = [];
9241
+ let masked = "";
9242
+ let i = 0;
9243
+ while (i < source.length) {
9244
+ if (source[i] === "[" && source[i + 1] === "[") {
9245
+ RAW_BLOCK_OPEN_PATTERN.lastIndex = i;
9246
+ const openMatch = RAW_BLOCK_OPEN_PATTERN.exec(source);
9247
+ if (openMatch) {
9248
+ const name = openMatch[1].toLowerCase();
9249
+ const openLen = openMatch[0].length;
9250
+ const closePattern = new RegExp(`\\[\\[\\/\\s*${name}\\s*\\]\\]`, "ig");
9251
+ closePattern.lastIndex = i + openLen;
9252
+ const closeMatch = closePattern.exec(source);
9253
+ if (closeMatch) {
9254
+ const regionEnd = closeMatch.index + closeMatch[0].length;
9255
+ masked += pushPlaceholder(placeholders, source.slice(i, regionEnd), sentinels);
9256
+ i = regionEnd;
9257
+ continue;
9258
+ }
9259
+ if (name === "code") {
9260
+ masked += pushPlaceholder(placeholders, source.slice(i), sentinels);
9261
+ i = source.length;
9262
+ continue;
9263
+ }
9264
+ }
9265
+ }
9266
+ if (source[i] === "@" && source[i + 1] === "<") {
9267
+ const close = source.indexOf(">@", i + 2);
9268
+ const newline = source.indexOf(`
9269
+ `, i + 2);
9270
+ if (close !== -1 && (newline === -1 || close < newline)) {
9271
+ const regionEnd = close + 2;
9272
+ masked += pushPlaceholder(placeholders, source.slice(i, regionEnd), sentinels);
9273
+ i = regionEnd;
9274
+ continue;
9275
+ }
9276
+ }
9277
+ if (source[i] === "@" && source[i + 1] === "@") {
9278
+ const close = source.indexOf("@@", i + 2);
9279
+ const newline = source.indexOf(`
9280
+ `, i + 2);
9281
+ if (close !== -1 && (newline === -1 || close < newline)) {
9282
+ const regionEnd = close + 2;
9283
+ masked += pushPlaceholder(placeholders, source.slice(i, regionEnd), sentinels);
9284
+ i = regionEnd;
9285
+ continue;
9286
+ }
9287
+ }
9288
+ masked += source[i];
9289
+ i++;
9290
+ }
9291
+ return { masked, placeholders };
9292
+ }
9293
+ function pushPlaceholder(placeholders, text, sentinels) {
9294
+ const idx = placeholders.length;
9295
+ placeholders.push(text);
9296
+ return `${sentinels.open}${idx}${sentinels.close}`;
9297
+ }
9298
+ function escapeRegex(str) {
9299
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9300
+ }
9301
+ function restorePlaceholders(source, placeholders, sentinels) {
9302
+ const pattern = new RegExp(`${escapeRegex(sentinels.open)}(\\d+)${escapeRegex(sentinels.close)}`, "g");
9303
+ return source.replace(pattern, (_, idx) => placeholders[Number(idx)] ?? "");
9304
+ }
9305
+
9306
+ // packages/parser/src/parser/parse.ts
9307
+ class Parser {
9308
+ ctx;
9309
+ constructor(tokens, options = {}) {
9310
+ this.ctx = {
9311
+ tokens,
9312
+ pos: 0,
9313
+ version: options.version ?? "wikidot",
9314
+ trackPositions: options.trackPositions ?? true,
9315
+ settings: options.settings ?? DEFAULT_SETTINGS,
9316
+ footnotes: [],
9317
+ tocEntries: [],
9318
+ codeBlocks: [],
9319
+ htmlBlocks: [],
9320
+ bibcites: [],
9321
+ diagnostics: [],
9322
+ blockRules,
9323
+ blockFallbackRule: paragraphRule,
9324
+ inlineRules,
9325
+ scope: {
9326
+ footnoteBlockParsed: false
9327
+ }
9328
+ };
9329
+ }
9330
+ parse() {
9331
+ const children = [];
9332
+ while (!this.isAtEnd()) {
9333
+ const blocks = this.parseBlock();
9334
+ children.push(...blocks);
9335
+ }
9336
+ const mergedChildren = mergeSpanStripParagraphs(children);
9337
+ const divProcessed = suppressDivAdjacentParagraphs(mergedChildren);
9338
+ const cleanedChildren = cleanInternalFlags(divProcessed);
9339
+ if (!containsFootnoteBlock(cleanedChildren)) {
9340
+ cleanedChildren.push({
9341
+ element: "footnote-block",
9342
+ data: { title: null, hide: false }
9343
+ });
9344
+ }
9345
+ const tableOfContents = buildTableOfContents(this.ctx.tocEntries);
9346
+ const result = {
9347
+ elements: cleanedChildren
9348
+ };
9349
+ if (tableOfContents.length > 0) {
9350
+ result["table-of-contents"] = tableOfContents;
9351
+ }
9352
+ if (this.ctx.footnotes.length > 0) {
9353
+ result.footnotes = this.ctx.footnotes;
9354
+ }
9355
+ if (this.ctx.codeBlocks.length > 0) {
9356
+ result["code-blocks"] = this.ctx.codeBlocks;
9357
+ }
9358
+ if (this.ctx.htmlBlocks.length > 0) {
9359
+ result["html-blocks"] = this.ctx.htmlBlocks;
9360
+ }
9361
+ return { ast: result, diagnostics: this.ctx.diagnostics };
9362
+ }
9363
+ isAtEnd() {
9364
+ return this.ctx.pos >= this.ctx.tokens.length || this.currentToken().type === "EOF";
9365
+ }
9366
+ currentToken() {
9367
+ return this.ctx.tokens[this.ctx.pos] ?? this.eofToken();
9368
+ }
9369
+ eofToken() {
9370
+ return {
9371
+ type: "EOF",
9372
+ value: "",
9373
+ position: {
9374
+ start: { line: 0, column: 0, offset: 0 },
9375
+ end: { line: 0, column: 0, offset: 0 }
9376
+ },
9377
+ lineStart: false
9378
+ };
9379
+ }
9380
+ skipWhitespace() {
9381
+ while (this.currentToken().type === "WHITESPACE") {
9382
+ this.ctx.pos++;
9383
+ }
8728
9384
  }
8729
9385
  parseBlock() {
8730
9386
  this.skipWhitespace();
@@ -8755,10 +9411,19 @@ class Parser {
8755
9411
  }
8756
9412
  }
8757
9413
  function parse(source, options) {
8758
- const preprocessed = preprocess(source);
9414
+ const iftagsProcessed = options?.pageTags !== undefined ? preprocessIftags(source, options.pageTags) : source;
9415
+ const preprocessed = preprocess(iftagsProcessed);
8759
9416
  const tokens = tokenize(preprocessed, { trackPositions: options?.trackPositions });
8760
9417
  return new Parser(tokens, options).parse();
8761
9418
  }
9419
+ function containsFootnoteBlock(elements) {
9420
+ let found = false;
9421
+ walkElements(elements, (el) => {
9422
+ if (el.element === "footnote-block")
9423
+ found = true;
9424
+ });
9425
+ return found;
9426
+ }
8762
9427
  // packages/parser/src/parser/rules/block/module/listpages/compiler.ts
8763
9428
  var DEFAULT_PREVIEW_LENGTH = 200;
8764
9429
  var VARIABLE_REGEX = /%%([a-z_]+)(?:\{([^}]+)\})?(?:\((\d+)\))?(?:\|([^%]*(?:%(?!%)[^%]*)*))?%%/gi;
@@ -8980,334 +9645,85 @@ function strftime(date, format) {
8980
9645
  return MONTHS[date.getMonth()] ?? "";
8981
9646
  case "a":
8982
9647
  return DAYS_SHORT[date.getDay()] ?? "";
8983
- case "A":
8984
- return DAYS[date.getDay()] ?? "";
8985
- case "w":
8986
- return String(date.getDay());
8987
- case "j":
8988
- return pad(getDayOfYear(date), 3);
8989
- case "Z":
8990
- return "UTC";
8991
- case "z":
8992
- return "+0000";
8993
- case "%":
8994
- return "%";
8995
- default:
8996
- return `%${token}`;
8997
- }
8998
- });
8999
- }
9000
- function getDayOfYear(date) {
9001
- const start = new Date(date.getFullYear(), 0, 0);
9002
- const diff = date.getTime() - start.getTime();
9003
- const oneDay = 1000 * 60 * 60 * 24;
9004
- return Math.floor(diff / oneDay);
9005
- }
9006
-
9007
- // packages/parser/src/parser/rules/block/module/listusers/compiler.ts
9008
- var VARIABLE_REGEX2 = /%%([a-z_]+)%%/gi;
9009
- function compileListUsersTemplate(template) {
9010
- const parts = [];
9011
- let lastIndex = 0;
9012
- for (const match of template.matchAll(VARIABLE_REGEX2)) {
9013
- if (match.index !== undefined && match.index > lastIndex) {
9014
- parts.push(template.slice(lastIndex, match.index));
9015
- }
9016
- const [, varName] = match;
9017
- if (!varName)
9018
- continue;
9019
- const getter = createVariableGetter2(varName.toLowerCase());
9020
- parts.push(getter);
9021
- lastIndex = match.index !== undefined ? match.index + match[0].length : lastIndex;
9022
- }
9023
- if (lastIndex < template.length) {
9024
- parts.push(template.slice(lastIndex));
9025
- }
9026
- return (ctx) => {
9027
- let result = "";
9028
- for (const part of parts) {
9029
- result += typeof part === "string" ? part : part(ctx);
9030
- }
9031
- return result;
9032
- };
9033
- }
9034
- function createVariableGetter2(name) {
9035
- switch (name) {
9036
- case "number":
9037
- return (ctx) => String(ctx.user.number);
9038
- case "title":
9039
- return (ctx) => ctx.user.title;
9040
- case "name":
9041
- return (ctx) => ctx.user.name;
9042
- default:
9043
- return () => "";
9044
- }
9045
- }
9046
-
9047
- // packages/parser/src/parser/rules/block/module/listusers/extract.ts
9048
- var VARIABLE_REGEX3 = /%%([a-z_]+)%%/gi;
9049
- var KNOWN_VARIABLES = ["number", "title", "name"];
9050
- function extractListUsersVariables(template) {
9051
- const variables = new Set;
9052
- for (const match of template.matchAll(VARIABLE_REGEX3)) {
9053
- const [, varName] = match;
9054
- if (!varName)
9055
- continue;
9056
- const normalized = varName.toLowerCase();
9057
- if (KNOWN_VARIABLES.includes(normalized)) {
9058
- variables.add(normalized);
9059
- }
9060
- }
9061
- return Array.from(variables);
9062
- }
9063
-
9064
- // packages/parser/src/parser/rules/block/module/walk.ts
9065
- function walkElements(elements, callback) {
9066
- for (const element of elements) {
9067
- callback(element);
9068
- if (element.element === "list") {
9069
- const listData = element.data;
9070
- for (const item of listData.items) {
9071
- if (item["item-type"] === "elements") {
9072
- walkElements(item.elements, callback);
9073
- } else if (item["item-type"] === "sub-list") {
9074
- walkElements([{ element: "list", data: item.data }], callback);
9075
- }
9076
- }
9077
- continue;
9078
- }
9079
- if (element.element === "table") {
9080
- const tableData = element.data;
9081
- for (const row of tableData.rows) {
9082
- for (const cell of row.cells) {
9083
- walkElements(cell.elements, callback);
9084
- }
9085
- }
9086
- continue;
9087
- }
9088
- if (element.element === "definition-list") {
9089
- const defListData = element.data;
9090
- for (const item of defListData) {
9091
- walkElements(item.key, callback);
9092
- walkElements(item.value, callback);
9093
- }
9094
- continue;
9095
- }
9096
- if (element.element === "tab-view") {
9097
- const tabData = element.data;
9098
- for (const tab of tabData) {
9099
- walkElements(tab.elements, callback);
9100
- }
9101
- continue;
9102
- }
9103
- if ("data" in element && element.data && typeof element.data === "object") {
9104
- const data = element.data;
9105
- if ("elements" in data && Array.isArray(data.elements)) {
9106
- walkElements(data.elements, callback);
9107
- }
9108
- }
9109
- }
9110
- }
9111
- function mapElementChildren(element, transform) {
9112
- if (element.element === "list") {
9113
- const listData = element.data;
9114
- const newItems = [];
9115
- for (const item of listData.items) {
9116
- if (item["item-type"] === "elements") {
9117
- newItems.push({
9118
- "item-type": "elements",
9119
- attributes: item.attributes,
9120
- elements: transform(item.elements)
9121
- });
9122
- } else if (item["item-type"] === "sub-list") {
9123
- const subListResult = transform([{ element: "list", data: item.data }]);
9124
- const resolvedList = subListResult[0];
9125
- if (resolvedList?.element === "list") {
9126
- newItems.push({
9127
- "item-type": "sub-list",
9128
- element: "list",
9129
- data: resolvedList.data
9130
- });
9131
- } else {
9132
- newItems.push(item);
9133
- }
9134
- }
9135
- }
9136
- return {
9137
- element: "list",
9138
- data: { ...listData, items: newItems }
9139
- };
9140
- }
9141
- if (element.element === "table") {
9142
- const tableData = element.data;
9143
- const newRows = [];
9144
- for (const row of tableData.rows) {
9145
- const newCells = [];
9146
- for (const cell of row.cells) {
9147
- newCells.push({ ...cell, elements: transform(cell.elements) });
9148
- }
9149
- newRows.push({ ...row, cells: newCells });
9150
- }
9151
- return {
9152
- element: "table",
9153
- data: { ...tableData, rows: newRows }
9154
- };
9155
- }
9156
- if (element.element === "definition-list") {
9157
- const defListData = element.data;
9158
- const newItems = [];
9159
- for (const item of defListData) {
9160
- newItems.push({
9161
- key_string: item.key_string,
9162
- key: transform(item.key),
9163
- value: transform(item.value)
9164
- });
9165
- }
9166
- return {
9167
- element: "definition-list",
9168
- data: newItems
9169
- };
9170
- }
9171
- if (element.element === "tab-view") {
9172
- const tabData = element.data;
9173
- const newTabs = [];
9174
- for (const tab of tabData) {
9175
- newTabs.push({ ...tab, elements: transform(tab.elements) });
9176
- }
9177
- return {
9178
- element: "tab-view",
9179
- data: newTabs
9180
- };
9181
- }
9182
- if ("data" in element && element.data && typeof element.data === "object") {
9183
- const data = element.data;
9184
- if ("elements" in data && Array.isArray(data.elements)) {
9185
- return {
9186
- ...element,
9187
- data: {
9188
- ...data,
9189
- elements: transform(data.elements)
9190
- }
9191
- };
9648
+ case "A":
9649
+ return DAYS[date.getDay()] ?? "";
9650
+ case "w":
9651
+ return String(date.getDay());
9652
+ case "j":
9653
+ return pad(getDayOfYear(date), 3);
9654
+ case "Z":
9655
+ return "UTC";
9656
+ case "z":
9657
+ return "+0000";
9658
+ case "%":
9659
+ return "%";
9660
+ default:
9661
+ return `%${token}`;
9192
9662
  }
9193
- }
9194
- return element;
9663
+ });
9195
9664
  }
9196
- function mapElementChildrenWithState(element, state, transform) {
9197
- if (element.element === "list") {
9198
- const listData = element.data;
9199
- const newItems = [];
9200
- let currentState = state;
9201
- for (const item of listData.items) {
9202
- if (item["item-type"] === "elements") {
9203
- const result = transform(item.elements, currentState);
9204
- newItems.push({
9205
- "item-type": "elements",
9206
- attributes: item.attributes,
9207
- elements: result.elements
9208
- });
9209
- currentState = result.state;
9210
- } else if (item["item-type"] === "sub-list") {
9211
- const result = transform([{ element: "list", data: item.data }], currentState);
9212
- const resolvedList = result.elements[0];
9213
- if (resolvedList?.element === "list") {
9214
- newItems.push({
9215
- "item-type": "sub-list",
9216
- element: "list",
9217
- data: resolvedList.data
9218
- });
9219
- } else {
9220
- newItems.push(item);
9221
- }
9222
- currentState = result.state;
9223
- }
9224
- }
9225
- return {
9226
- element: {
9227
- element: "list",
9228
- data: { ...listData, items: newItems }
9229
- },
9230
- state: currentState
9231
- };
9232
- }
9233
- if (element.element === "table") {
9234
- const tableData = element.data;
9235
- const newRows = [];
9236
- let currentState = state;
9237
- for (const row of tableData.rows) {
9238
- const newCells = [];
9239
- for (const cell of row.cells) {
9240
- const result = transform(cell.elements, currentState);
9241
- newCells.push({ ...cell, elements: result.elements });
9242
- currentState = result.state;
9243
- }
9244
- newRows.push({ ...row, cells: newCells });
9665
+ function getDayOfYear(date) {
9666
+ const start = new Date(date.getFullYear(), 0, 0);
9667
+ const diff = date.getTime() - start.getTime();
9668
+ const oneDay = 1000 * 60 * 60 * 24;
9669
+ return Math.floor(diff / oneDay);
9670
+ }
9671
+
9672
+ // packages/parser/src/parser/rules/block/module/listusers/compiler.ts
9673
+ var VARIABLE_REGEX2 = /%%([a-z_]+)%%/gi;
9674
+ function compileListUsersTemplate(template) {
9675
+ const parts = [];
9676
+ let lastIndex = 0;
9677
+ for (const match of template.matchAll(VARIABLE_REGEX2)) {
9678
+ if (match.index !== undefined && match.index > lastIndex) {
9679
+ parts.push(template.slice(lastIndex, match.index));
9245
9680
  }
9246
- return {
9247
- element: {
9248
- element: "table",
9249
- data: { ...tableData, rows: newRows }
9250
- },
9251
- state: currentState
9252
- };
9681
+ const [, varName] = match;
9682
+ if (!varName)
9683
+ continue;
9684
+ const getter = createVariableGetter2(varName.toLowerCase());
9685
+ parts.push(getter);
9686
+ lastIndex = match.index !== undefined ? match.index + match[0].length : lastIndex;
9253
9687
  }
9254
- if (element.element === "definition-list") {
9255
- const defListData = element.data;
9256
- const newItems = [];
9257
- let currentState = state;
9258
- for (const item of defListData) {
9259
- const keyResult = transform(item.key, currentState);
9260
- currentState = keyResult.state;
9261
- const valueResult = transform(item.value, currentState);
9262
- currentState = valueResult.state;
9263
- newItems.push({
9264
- key_string: item.key_string,
9265
- key: keyResult.elements,
9266
- value: valueResult.elements
9267
- });
9268
- }
9269
- return {
9270
- element: {
9271
- element: "definition-list",
9272
- data: newItems
9273
- },
9274
- state: currentState
9275
- };
9688
+ if (lastIndex < template.length) {
9689
+ parts.push(template.slice(lastIndex));
9276
9690
  }
9277
- if (element.element === "tab-view") {
9278
- const tabData = element.data;
9279
- const newTabs = [];
9280
- let currentState = state;
9281
- for (const tab of tabData) {
9282
- const result = transform(tab.elements, currentState);
9283
- newTabs.push({ ...tab, elements: result.elements });
9284
- currentState = result.state;
9691
+ return (ctx) => {
9692
+ let result = "";
9693
+ for (const part of parts) {
9694
+ result += typeof part === "string" ? part : part(ctx);
9285
9695
  }
9286
- return {
9287
- element: {
9288
- element: "tab-view",
9289
- data: newTabs
9290
- },
9291
- state: currentState
9292
- };
9696
+ return result;
9697
+ };
9698
+ }
9699
+ function createVariableGetter2(name) {
9700
+ switch (name) {
9701
+ case "number":
9702
+ return (ctx) => String(ctx.user.number);
9703
+ case "title":
9704
+ return (ctx) => ctx.user.title;
9705
+ case "name":
9706
+ return (ctx) => ctx.user.name;
9707
+ default:
9708
+ return () => "";
9293
9709
  }
9294
- if ("data" in element && element.data && typeof element.data === "object") {
9295
- const data = element.data;
9296
- if ("elements" in data && Array.isArray(data.elements)) {
9297
- const result = transform(data.elements, state);
9298
- return {
9299
- element: {
9300
- ...element,
9301
- data: {
9302
- ...data,
9303
- elements: result.elements
9304
- }
9305
- },
9306
- state: result.state
9307
- };
9710
+ }
9711
+
9712
+ // packages/parser/src/parser/rules/block/module/listusers/extract.ts
9713
+ var VARIABLE_REGEX3 = /%%([a-z_]+)%%/gi;
9714
+ var KNOWN_VARIABLES = ["number", "title", "name"];
9715
+ function extractListUsersVariables(template) {
9716
+ const variables = new Set;
9717
+ for (const match of template.matchAll(VARIABLE_REGEX3)) {
9718
+ const [, varName] = match;
9719
+ if (!varName)
9720
+ continue;
9721
+ const normalized = varName.toLowerCase();
9722
+ if (KNOWN_VARIABLES.includes(normalized)) {
9723
+ variables.add(normalized);
9308
9724
  }
9309
9725
  }
9310
- return { element, state };
9726
+ return Array.from(variables);
9311
9727
  }
9312
9728
 
9313
9729
  // packages/parser/src/parser/rules/block/module/listpages/extract.ts
@@ -9909,51 +10325,6 @@ function resolveAndNormalizeQuery(requirement, urlParams) {
9909
10325
  const resolved = resolveQuery(requirement, urlParams);
9910
10326
  return normalizeQuery(resolved);
9911
10327
  }
9912
- // packages/parser/src/parser/rules/block/module/iftags/condition.ts
9913
- function parseTagCondition(condition) {
9914
- const required = [];
9915
- const forbidden = [];
9916
- const optional = [];
9917
- const parts = condition.trim().split(/\s+/);
9918
- for (const part of parts) {
9919
- if (!part)
9920
- continue;
9921
- if (part.startsWith("+")) {
9922
- const tag = part.slice(1);
9923
- if (tag)
9924
- required.push(tag);
9925
- } else if (part.startsWith("-")) {
9926
- const tag = part.slice(1);
9927
- if (tag)
9928
- forbidden.push(tag);
9929
- } else {
9930
- optional.push(part);
9931
- }
9932
- }
9933
- return { required, forbidden, optional };
9934
- }
9935
- function evaluateTagCondition(condition, pageTags) {
9936
- if (condition.required.length === 0 && condition.forbidden.length === 0 && condition.optional.length === 0) {
9937
- return false;
9938
- }
9939
- const tagSet = new Set(pageTags);
9940
- for (const tag of condition.required) {
9941
- if (!tagSet.has(tag)) {
9942
- return false;
9943
- }
9944
- }
9945
- for (const tag of condition.forbidden) {
9946
- if (tagSet.has(tag)) {
9947
- return false;
9948
- }
9949
- }
9950
- if (condition.optional.length > 0) {
9951
- if (!condition.optional.some((tag) => tagSet.has(tag))) {
9952
- return false;
9953
- }
9954
- }
9955
- return true;
9956
- }
9957
10328
  // packages/parser/src/parser/rules/block/module/iftags/resolve.ts
9958
10329
  function isIfTagsElement(element) {
9959
10330
  return element.element === "if-tags";
@@ -10011,7 +10382,67 @@ async function resolveIncludesAsync(source, fetcher, options) {
10011
10382
  };
10012
10383
  return expandIterativeAsync(source, cachedFetcher, maxIterations);
10013
10384
  }
10014
- var INCLUDE_PATTERN = /^\[\[include\s([^\]]*(?:\](?!\])[^\]]*)*)\]\]/gim;
10385
+ var INCLUDE_OPEN_PATTERN = /^\[\[include\s/gim;
10386
+ function isRestOfLineBlank(source, pos) {
10387
+ for (let i = pos;i < source.length; i++) {
10388
+ const ch = source[i];
10389
+ if (ch === `
10390
+ `)
10391
+ return true;
10392
+ if (ch !== " " && ch !== "\t" && ch !== "\r")
10393
+ return false;
10394
+ }
10395
+ return true;
10396
+ }
10397
+ function scanIncludeDirectives(source) {
10398
+ const matches = [];
10399
+ const opener = new RegExp(INCLUDE_OPEN_PATTERN.source, INCLUDE_OPEN_PATTERN.flags);
10400
+ let m;
10401
+ while ((m = opener.exec(source)) !== null) {
10402
+ const start = m.index;
10403
+ const contentStart = start + m[0].length;
10404
+ const firstNewline = source.indexOf(`
10405
+ `, start);
10406
+ let depth = 0;
10407
+ let linkDepth = 0;
10408
+ let i = start;
10409
+ let closeEnd = -1;
10410
+ while (i < source.length) {
10411
+ if (source.startsWith("[[[", i)) {
10412
+ linkDepth++;
10413
+ i += 3;
10414
+ } else if (linkDepth > 0 && source.startsWith("]]]", i)) {
10415
+ linkDepth--;
10416
+ i += 3;
10417
+ } else if (linkDepth > 0) {
10418
+ i++;
10419
+ } else if (source.startsWith("[[", i)) {
10420
+ depth++;
10421
+ i += 2;
10422
+ } else if (source.startsWith("]]", i)) {
10423
+ const closeStart = i;
10424
+ depth--;
10425
+ i += 2;
10426
+ if (depth <= 0) {
10427
+ const onOpenerLine = firstNewline === -1 || closeStart < firstNewline;
10428
+ if (onOpenerLine || isRestOfLineBlank(source, i)) {
10429
+ closeEnd = i;
10430
+ break;
10431
+ }
10432
+ }
10433
+ } else {
10434
+ i++;
10435
+ }
10436
+ }
10437
+ if (closeEnd === -1) {
10438
+ opener.lastIndex = start + 2;
10439
+ continue;
10440
+ }
10441
+ matches.push({ start, end: closeEnd, inner: source.slice(contentStart, closeEnd - 2) });
10442
+ opener.lastIndex = closeEnd;
10443
+ }
10444
+ return matches;
10445
+ }
10015
10446
  function parseIncludeDirective(inner) {
10016
10447
  const normalized = inner.replace(/\n/g, " ");
10017
10448
  const parts = normalized.split("|");
@@ -10035,14 +10466,24 @@ function parseIncludeDirective(inner) {
10035
10466
  }
10036
10467
  }
10037
10468
  const variables = {};
10469
+ const hasConcrete = new Set;
10038
10470
  for (const segment of varSegments) {
10039
10471
  const eqIndex = segment.indexOf("=");
10040
- if (eqIndex !== -1) {
10041
- const key = segment.slice(0, eqIndex).trim();
10042
- const value = segment.slice(eqIndex + 1).trim();
10043
- if (key) {
10472
+ if (eqIndex === -1)
10473
+ continue;
10474
+ const key = segment.slice(0, eqIndex).trim();
10475
+ if (!key)
10476
+ continue;
10477
+ const value = segment.slice(eqIndex + 1).trim();
10478
+ const isPlaceholder = /^\{\$[^}]*\}$/.test(value);
10479
+ const isConcrete = value !== "" && !isPlaceholder;
10480
+ if (isConcrete) {
10481
+ if (!hasConcrete.has(key)) {
10044
10482
  variables[key] = value;
10483
+ hasConcrete.add(key);
10045
10484
  }
10485
+ } else if (!Object.hasOwn(variables, key)) {
10486
+ variables[key] = value;
10046
10487
  }
10047
10488
  }
10048
10489
  let location;
@@ -10059,7 +10500,7 @@ function parseIncludeDirective(inner) {
10059
10500
  }
10060
10501
  return { location, variables };
10061
10502
  }
10062
- function replaceOneInclude(_match, inner, fetcher) {
10503
+ function replaceOneInclude(inner, fetcher) {
10063
10504
  const { location, variables } = parseIncludeDirective(inner);
10064
10505
  const content = fetcher(location);
10065
10506
  if (content === null) {
@@ -10072,25 +10513,33 @@ Page to be included "${location.page}" cannot be found!
10072
10513
  function expandIterative(source, fetcher, maxIterations) {
10073
10514
  let current = source;
10074
10515
  for (let i = 0;i < maxIterations; i++) {
10075
- const previous = current;
10076
- current = current.replace(INCLUDE_PATTERN, (_match, inner) => replaceOneInclude(_match, inner, fetcher));
10077
- if (current === previous)
10516
+ const directives = scanIncludeDirectives(current);
10517
+ if (directives.length === 0)
10518
+ break;
10519
+ let result = "";
10520
+ let lastPos = 0;
10521
+ for (const { start, end, inner } of directives) {
10522
+ result += current.slice(lastPos, start);
10523
+ result += replaceOneInclude(inner, fetcher);
10524
+ lastPos = end;
10525
+ }
10526
+ result += current.slice(lastPos);
10527
+ if (result === current)
10078
10528
  break;
10529
+ current = result;
10079
10530
  }
10080
10531
  return current;
10081
10532
  }
10082
10533
  async function expandIterativeAsync(source, fetcher, maxIterations) {
10083
10534
  let current = source;
10084
10535
  for (let i = 0;i < maxIterations; i++) {
10085
- const previous = current;
10086
- const pattern = new RegExp(INCLUDE_PATTERN.source, INCLUDE_PATTERN.flags);
10536
+ const directives = scanIncludeDirectives(current);
10537
+ if (directives.length === 0)
10538
+ break;
10087
10539
  let result = "";
10088
10540
  let lastPos = 0;
10089
- let match;
10090
- while ((match = pattern.exec(current)) !== null) {
10091
- const fullMatch = match[0];
10092
- const inner = match[1];
10093
- result += current.slice(lastPos, match.index);
10541
+ for (const { start, end, inner } of directives) {
10542
+ result += current.slice(lastPos, start);
10094
10543
  const { location, variables } = parseIncludeDirective(inner);
10095
10544
  const content = await fetcher(location);
10096
10545
  if (content === null) {
@@ -10100,12 +10549,12 @@ Page to be included "${location.page}" cannot be found!
10100
10549
  } else {
10101
10550
  result += substituteVariables(content, variables);
10102
10551
  }
10103
- lastPos = match.index + fullMatch.length;
10552
+ lastPos = end;
10104
10553
  }
10105
10554
  result += current.slice(lastPos);
10106
- current = result;
10107
- if (current === previous)
10555
+ if (result === current)
10108
10556
  break;
10557
+ current = result;
10109
10558
  }
10110
10559
  return current;
10111
10560
  }
@@ -10338,6 +10787,7 @@ export {
10338
10787
  resolveListUsers,
10339
10788
  resolveIncludesAsync,
10340
10789
  resolveIncludes,
10790
+ preprocessIftags,
10341
10791
  parseTags,
10342
10792
  parseParent,
10343
10793
  parseOrder,