@wdprlib/parser 4.3.0 → 5.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.
Files changed (32) hide show
  1. package/README.md +26 -38
  2. package/dist/index.cjs +1035 -249
  3. package/dist/index.d.cts +203 -158
  4. package/dist/index.d.ts +203 -158
  5. package/dist/index.js +930 -139
  6. package/package.json +2 -2
  7. package/src/index.ts +13 -0
  8. package/src/parser/parse/context.ts +1 -0
  9. package/src/parser/parse/options.ts +6 -0
  10. package/src/parser/parse/result.ts +3 -1
  11. package/src/parser/preprocess/expr/index.ts +26 -0
  12. package/src/parser/preprocess/utils/raw-regions.ts +16 -7
  13. package/src/parser/rules/block/module/include/index.ts +6 -1
  14. package/src/parser/rules/block/module/include/resolve/index.ts +23 -3
  15. package/src/parser/rules/block/module/include/resolve/iterate.ts +71 -0
  16. package/src/parser/rules/block/module/index.ts +4 -1
  17. package/src/parser/rules/block/module/listpages/index.ts +2 -0
  18. package/src/parser/rules/block/module/listpages/resolution/items.ts +2 -2
  19. package/src/parser/rules/block/module/listpages/resolution/wrapper.ts +3 -3
  20. package/src/parser/rules/block/module/listpages/selectors.ts +64 -0
  21. package/src/parser/rules/block/module/listpages/types/external-data.ts +22 -0
  22. package/src/parser/rules/block/module/listusers/resolve.ts +2 -2
  23. package/src/parser/rules/block/module/resolution/document.ts +357 -0
  24. package/src/parser/rules/block/module/resolution/resolve-async.ts +269 -0
  25. package/src/parser/rules/block/module/resolution/styles.ts +134 -12
  26. package/src/parser/rules/block/module/resolution/walk-resolve.ts +19 -1
  27. package/src/parser/rules/block/module/resolve.ts +32 -13
  28. package/src/parser/rules/block/module/types.ts +7 -2
  29. package/src/parser/rules/contracts/parse-context.ts +1 -0
  30. package/src/pipeline/index.ts +7 -0
  31. package/src/pipeline/process.ts +172 -0
  32. package/src/pipeline/types.ts +63 -0
package/dist/index.cjs CHANGED
@@ -31,13 +31,15 @@ var __export = (target, all) => {
31
31
  var exports_src = {};
32
32
  __export(exports_src, {
33
33
  tokenize: () => tokenize,
34
- text: () => import_ast6.text,
34
+ text: () => import_ast8.text,
35
35
  resolveTagCloud: () => resolveTagCloud,
36
36
  resolveModules: () => resolveModules,
37
37
  resolveListUsers: () => resolveListUsers,
38
38
  resolveIncludesWithTrace: () => resolveIncludesWithTrace,
39
+ resolveIncludesAsyncWithTrace: () => resolveIncludesAsyncWithTrace,
39
40
  resolveIncludesAsync: () => resolveIncludesAsync,
40
41
  resolveIncludes: () => resolveIncludes,
42
+ processWikitext: () => processWikitext,
41
43
  preprocessIftags: () => preprocessIftags,
42
44
  parseTags: () => parseTags,
43
45
  parseParent: () => parseParent,
@@ -46,37 +48,39 @@ __export(exports_src, {
46
48
  parseDateSelector: () => parseDateSelector,
47
49
  parseCategory: () => parseCategory,
48
50
  parse: () => parse,
49
- paragraph: () => import_ast6.paragraph,
51
+ paragraph: () => import_ast8.paragraph,
50
52
  normalizeQuery: () => normalizeQuery,
51
- listItemSubList: () => import_ast6.listItemSubList,
52
- listItemElements: () => import_ast6.listItemElements,
53
- list: () => import_ast6.list,
54
- link: () => import_ast6.link,
55
- lineBreak: () => import_ast6.lineBreak,
56
- italics: () => import_ast6.italics,
53
+ matchesListPagesSelectors: () => matchesListPagesSelectors,
54
+ listItemSubList: () => import_ast8.listItemSubList,
55
+ listItemElements: () => import_ast8.listItemElements,
56
+ list: () => import_ast8.list,
57
+ link: () => import_ast8.link,
58
+ lineBreak: () => import_ast8.lineBreak,
59
+ italics: () => import_ast8.italics,
57
60
  isTagCloudModule: () => isTagCloudModule,
58
61
  isListUsersModule: () => isListUsersModule2,
59
- horizontalRule: () => import_ast6.horizontalRule,
60
- heading: () => import_ast6.heading,
62
+ horizontalRule: () => import_ast8.horizontalRule,
63
+ heading: () => import_ast8.heading,
61
64
  extractListUsersVariables: () => extractListUsersVariables,
62
65
  extractIncludeReferences: () => extractIncludeReferences,
63
66
  extractDataRequirements: () => extractDataRequirements,
67
+ definePageData: () => definePageData,
64
68
  createToken: () => createToken,
65
- createSettings: () => import_ast7.createSettings,
66
- createPosition: () => import_ast6.createPosition,
67
- createPoint: () => import_ast6.createPoint,
68
- container: () => import_ast6.container,
69
+ createSettings: () => import_ast9.createSettings,
70
+ createPosition: () => import_ast8.createPosition,
71
+ createPoint: () => import_ast8.createPoint,
72
+ container: () => import_ast8.container,
69
73
  compileTemplate: () => compileTemplate,
70
74
  compileListUsersTemplate: () => compileListUsersTemplate,
71
- bold: () => import_ast6.bold,
72
- STYLE_SLOT_PREFIX: () => import_ast3.STYLE_SLOT_PREFIX,
75
+ bold: () => import_ast8.bold,
76
+ STYLE_SLOT_PREFIX: () => import_ast4.STYLE_SLOT_PREFIX,
73
77
  Parser: () => Parser,
74
78
  Lexer: () => Lexer,
75
- DEFAULT_SETTINGS: () => import_ast7.DEFAULT_SETTINGS
79
+ DEFAULT_SETTINGS: () => import_ast9.DEFAULT_SETTINGS
76
80
  });
77
81
  module.exports = __toCommonJS(exports_src);
78
- var import_ast6 = require("@wdprlib/ast");
79
- var import_ast7 = require("@wdprlib/ast");
82
+ var import_ast8 = require("@wdprlib/ast");
83
+ var import_ast9 = require("@wdprlib/ast");
80
84
 
81
85
  // packages/parser/src/lexer/tokens.ts
82
86
  function createToken(type, value, position, lineStart = false) {
@@ -1443,7 +1447,7 @@ function parseNextBlock(ctx, skipWhitespace2, isAtEnd2) {
1443
1447
  }
1444
1448
 
1445
1449
  // packages/parser/src/parser/parse/context.ts
1446
- var import_ast4 = require("@wdprlib/ast");
1450
+ var import_ast5 = require("@wdprlib/ast");
1447
1451
 
1448
1452
  // packages/parser/src/parser/rules/tokens.ts
1449
1453
  function currentToken(ctx) {
@@ -5843,6 +5847,11 @@ function extractDataRequirements(ast) {
5843
5847
  });
5844
5848
  return result;
5845
5849
  }
5850
+ // packages/parser/src/parser/rules/block/module/types.ts
5851
+ function getModuleParseAst(result) {
5852
+ return "ast" in result ? result.ast : result;
5853
+ }
5854
+
5846
5855
  // packages/parser/src/parser/rules/block/module/listpages/resolution/items.ts
5847
5856
  function renderListPagesItems(module2, data, compiledTemplate, parse) {
5848
5857
  const items = [];
@@ -5857,7 +5866,7 @@ function renderListPagesItems(module2, data, compiledTemplate, parse) {
5857
5866
  limit: module2.limit,
5858
5867
  site: data.site
5859
5868
  };
5860
- const itemAst = parse(compiledTemplate(ctx));
5869
+ const itemAst = getModuleParseAst(parse(compiledTemplate(ctx)));
5861
5870
  if (module2.separate) {
5862
5871
  items.push({
5863
5872
  element: "container",
@@ -5881,12 +5890,12 @@ function wrapListPagesResult(module2, items, parse) {
5881
5890
  }
5882
5891
  const result = [];
5883
5892
  if (module2["prepend-line"] && !module2.separate) {
5884
- const prependAst = parse(module2["prepend-line"]);
5893
+ const prependAst = getModuleParseAst(parse(module2["prepend-line"]));
5885
5894
  result.push(...prependAst.elements);
5886
5895
  }
5887
5896
  result.push(...items);
5888
5897
  if (module2["append-line"] && !module2.separate) {
5889
- const appendAst = parse(module2["append-line"]);
5898
+ const appendAst = getModuleParseAst(parse(module2["append-line"]));
5890
5899
  result.push(...appendAst.elements);
5891
5900
  }
5892
5901
  if (module2.wrapper) {
@@ -5912,6 +5921,60 @@ function resolveListPages(module2, data, compiledTemplate, parse) {
5912
5921
  const items = renderListPagesItems(module2, data, compiledTemplate, parse);
5913
5922
  return wrapListPagesResult(module2, items, parse);
5914
5923
  }
5924
+ // packages/parser/src/parser/rules/block/module/listpages/types/external-data.ts
5925
+ function definePageData(input) {
5926
+ const separator = input.fullname.indexOf(":");
5927
+ const category = separator === -1 ? "_default" : input.fullname.slice(0, separator);
5928
+ const name = separator === -1 ? input.fullname : input.fullname.slice(separator + 1);
5929
+ return {
5930
+ ...input,
5931
+ name: input.name ?? name,
5932
+ category: input.category ?? category,
5933
+ hiddenTags: input.hiddenTags ?? [],
5934
+ children: input.children ?? 0,
5935
+ comments: input.comments ?? 0,
5936
+ size: input.size ?? 0,
5937
+ rating: input.rating ?? 0,
5938
+ ratingVotes: input.ratingVotes ?? 0,
5939
+ revisions: input.revisions ?? 0
5940
+ };
5941
+ }
5942
+ // packages/parser/src/parser/rules/block/module/listpages/selectors.ts
5943
+ function matchesListPagesSelectors(page, query, currentPage) {
5944
+ return matchesCategory(page.category, query.category, currentPage.category) && matchesTags(page, query.tags, currentPage);
5945
+ }
5946
+ function matchesCategory(category, selector, currentCategory) {
5947
+ if (!selector)
5948
+ return category === currentCategory;
5949
+ if (selector.exclude.includes(category))
5950
+ return false;
5951
+ const hasPositiveSelector = selector.all || selector.current || selector.include.length > 0;
5952
+ return !hasPositiveSelector || selector.all || selector.current && category === currentCategory || selector.include.includes(category);
5953
+ }
5954
+ function matchesTags(page, selector, currentPage) {
5955
+ if (!selector)
5956
+ return true;
5957
+ const allTags = new Set([...page.tags, ...page.hiddenTags]);
5958
+ if (selector.all.some((tag) => !allTags.has(tag)))
5959
+ return false;
5960
+ if (selector.any.length > 0 && !selector.any.some((tag) => allTags.has(tag)))
5961
+ return false;
5962
+ if (selector.none.some((tag) => allTags.has(tag)))
5963
+ return false;
5964
+ if (selector.special === "none")
5965
+ return allTags.size === 0;
5966
+ const currentTags = new Set(currentPage.tags.filter((tag) => !tag.startsWith("_")));
5967
+ if (selector.special === "same-visible") {
5968
+ return page.tags.some((tag) => currentTags.has(tag));
5969
+ }
5970
+ if (selector.special === "same-all") {
5971
+ return setsEqual(new Set(page.tags), currentTags);
5972
+ }
5973
+ return true;
5974
+ }
5975
+ function setsEqual(left, right) {
5976
+ return left.size === right.size && [...left].every((value) => right.has(value));
5977
+ }
5915
5978
  // packages/parser/src/parser/rules/block/module/listpages/url-resolution/fields.ts
5916
5979
  var URL_RESOLVABLE_FIELDS = [
5917
5980
  { attr: "offset", queryKey: "offset", type: "number" },
@@ -6341,13 +6404,20 @@ var BASE_PLACEHOLDER_OPEN = "";
6341
6404
  var BASE_PLACEHOLDER_CLOSE = "";
6342
6405
  var RAW_BLOCK_OPEN_PATTERN = /\[\[\s*(code|html)\b[^\]]*\]\]/iy;
6343
6406
  function makeUniqueSentinels(source) {
6344
- let open = BASE_PLACEHOLDER_OPEN;
6345
- let close = BASE_PLACEHOLDER_CLOSE;
6346
- while (source.includes(open) || source.includes(close)) {
6347
- open += BASE_PLACEHOLDER_OPEN;
6348
- close += BASE_PLACEHOLDER_CLOSE;
6407
+ let openRun = 0;
6408
+ let closeRun = 0;
6409
+ let longestOpenRun = 0;
6410
+ let longestCloseRun = 0;
6411
+ for (const char of source) {
6412
+ openRun = char === BASE_PLACEHOLDER_OPEN ? openRun + 1 : 0;
6413
+ closeRun = char === BASE_PLACEHOLDER_CLOSE ? closeRun + 1 : 0;
6414
+ longestOpenRun = Math.max(longestOpenRun, openRun);
6415
+ longestCloseRun = Math.max(longestCloseRun, closeRun);
6349
6416
  }
6350
- return { open, close };
6417
+ return {
6418
+ open: BASE_PLACEHOLDER_OPEN.repeat(longestOpenRun + 1),
6419
+ close: BASE_PLACEHOLDER_CLOSE.repeat(longestCloseRun + 1)
6420
+ };
6351
6421
  }
6352
6422
  function maskRawRegions(source, sentinels) {
6353
6423
  const placeholders = [];
@@ -6805,16 +6875,31 @@ function expandIterativeWithTrace(source, fetcher, maxIterations) {
6805
6875
  reachedMaxIterations: iterations.length === maxIterations && scanIncludeDirectives(current2).length > 0
6806
6876
  };
6807
6877
  }
6808
- async function expandIterativeAsync(source, fetcher, maxIterations) {
6878
+ async function expandIterativeAsyncWithTrace(source, fetcher, maxIterations) {
6809
6879
  let current2 = source;
6810
6880
  const replacementCache = new Map;
6881
+ const dependencies = [];
6882
+ const iterations = [];
6811
6883
  for (let i = 0;i < maxIterations; i++) {
6812
- const expanded = await expandOneIterationAsync(current2, fetcher, replacementCache);
6813
- if (expanded === null || expanded === current2)
6884
+ const expanded = await expandOneIterationAsyncWithTrace(current2, fetcher, replacementCache, i);
6885
+ if (expanded === null)
6814
6886
  break;
6815
- current2 = expanded;
6887
+ dependencies.push(...expanded.dependencies);
6888
+ iterations.push({
6889
+ iteration: i,
6890
+ directives: expanded.references,
6891
+ changed: expanded.source !== current2
6892
+ });
6893
+ if (expanded.source === current2)
6894
+ break;
6895
+ current2 = expanded.source;
6816
6896
  }
6817
- return current2;
6897
+ return {
6898
+ source: current2,
6899
+ dependencies,
6900
+ iterations,
6901
+ reachedMaxIterations: iterations.length === maxIterations && scanIncludeDirectives(current2).length > 0
6902
+ };
6818
6903
  }
6819
6904
  function expandOneIteration(source, fetcher, replacementCache) {
6820
6905
  if (!MAYBE_INCLUDE_PATTERN.test(source))
@@ -6852,12 +6937,13 @@ function expandOneIterationWithTrace(source, fetcher, replacementCache, iteratio
6852
6937
  dependencies
6853
6938
  };
6854
6939
  }
6855
- async function expandOneIterationAsync(source, fetcher, replacementCache) {
6940
+ async function expandOneIterationAsyncWithTrace(source, fetcher, replacementCache, iteration) {
6856
6941
  if (!MAYBE_INCLUDE_PATTERN.test(source))
6857
6942
  return null;
6858
6943
  const directives = scanIncludeDirectives(source);
6859
6944
  if (directives.length === 0)
6860
6945
  return null;
6946
+ const references = directives.map(createIncludeReference);
6861
6947
  const replacements = await Promise.all(directives.map(({ inner }) => replaceCachedAsync(inner, fetcher, replacementCache)));
6862
6948
  const parts = [];
6863
6949
  let lastPos = 0;
@@ -6867,7 +6953,11 @@ async function expandOneIterationAsync(source, fetcher, replacementCache) {
6867
6953
  lastPos = end;
6868
6954
  }
6869
6955
  parts.push(source.slice(lastPos));
6870
- return parts.join("");
6956
+ return {
6957
+ source: parts.join(""),
6958
+ references,
6959
+ dependencies: references.map((reference) => ({ ...reference, iteration }))
6960
+ };
6871
6961
  }
6872
6962
  function replaceCached(inner, fetcher, cache) {
6873
6963
  const cached = cache.get(inner);
@@ -6909,12 +6999,20 @@ function resolveIncludesWithTrace(source, fetcher, options) {
6909
6999
  return expandIterativeWithTrace(source, cachedFetcher, maxIterations);
6910
7000
  }
6911
7001
  async function resolveIncludesAsync(source, fetcher, options) {
7002
+ return (await resolveIncludesAsyncWithTrace(source, fetcher, options)).source;
7003
+ }
7004
+ async function resolveIncludesAsyncWithTrace(source, fetcher, options) {
6912
7005
  if (options?.settings && !options.settings.enablePageSyntax) {
6913
- return source;
7006
+ return {
7007
+ source,
7008
+ dependencies: [],
7009
+ iterations: [],
7010
+ reachedMaxIterations: false
7011
+ };
6914
7012
  }
6915
7013
  const maxIterations = options?.maxIterations ?? 10;
6916
7014
  const cachedFetcher = createCachedAsyncIncludeFetcher(fetcher, normalizePageKey);
6917
- return expandIterativeAsync(source, cachedFetcher, maxIterations);
7015
+ return expandIterativeAsyncWithTrace(source, cachedFetcher, maxIterations);
6918
7016
  }
6919
7017
  function normalizePageKey(location) {
6920
7018
  const site = location.site ?? "";
@@ -6928,7 +7026,7 @@ function isListUsersModule2(module2) {
6928
7026
  function resolveListUsers(_module, data, compiledTemplate, parse) {
6929
7027
  const ctx = { user: data.user };
6930
7028
  const substituted = compiledTemplate(ctx);
6931
- const itemAst = parse(substituted);
7029
+ const itemAst = getModuleParseAst(parse(substituted));
6932
7030
  return itemAst.elements;
6933
7031
  }
6934
7032
  // packages/parser/src/parser/rules/block/module/resolution/data-maps.ts
@@ -7081,6 +7179,117 @@ function countDynamicModules(elements) {
7081
7179
  return counts;
7082
7180
  }
7083
7181
 
7182
+ // packages/parser/src/parser/rules/block/module/resolution/styles.ts
7183
+ var import_ast2 = require("@wdprlib/ast");
7184
+ function collectStyles(elements, ignoredAnchors = new WeakSet) {
7185
+ const styles = [];
7186
+ const anchoredStyles = [];
7187
+ const anchors = [];
7188
+ const usedSlots = collectExistingStyleSlots(elements);
7189
+ const ctx = { nextSlotId: 0, usedSlots };
7190
+ const filtered = collectStylesFromElements(elements, styles, anchoredStyles, anchors, ignoredAnchors, ctx);
7191
+ return { elements: filtered, styles, anchoredStyles, anchors };
7192
+ }
7193
+ function mergeCollectedStyles(existing, collected, resolvedSlots = new Map, anchoredStyles = []) {
7194
+ const unanchoredPrevious = [...existing ?? []];
7195
+ for (const style of anchoredStyles)
7196
+ removeFirst(unanchoredPrevious, style);
7197
+ for (const style of collected) {
7198
+ if (isStyleSlotMarker(style))
7199
+ removeFirst(unanchoredPrevious, style);
7200
+ }
7201
+ const merged = unanchoredPrevious.filter((style) => !isStyleSlotMarker(style));
7202
+ for (const style of collected) {
7203
+ if (isStyleSlotMarker(style))
7204
+ appendStyleSlot(merged, style, resolvedSlots);
7205
+ else
7206
+ merged.push(style);
7207
+ }
7208
+ return merged;
7209
+ }
7210
+ function isStyleSlotMarker(style) {
7211
+ return style.startsWith(import_ast2.STYLE_SLOT_PREFIX);
7212
+ }
7213
+ function createStyleSlotMarker(slotId) {
7214
+ return `${import_ast2.STYLE_SLOT_PREFIX}${slotId}`;
7215
+ }
7216
+ function getStyleSlotId(data) {
7217
+ const slotId = data._styleSlot;
7218
+ return Number.isSafeInteger(slotId) && slotId >= 0 ? slotId : undefined;
7219
+ }
7220
+ function removeFirst(values, value) {
7221
+ const index = values.indexOf(value);
7222
+ if (index >= 0)
7223
+ values.splice(index, 1);
7224
+ }
7225
+ function appendStyleSlot(target, marker, resolvedSlots) {
7226
+ const slotId = Number(marker.slice(import_ast2.STYLE_SLOT_PREFIX.length));
7227
+ const replacement = resolvedSlots.get(slotId);
7228
+ if (replacement)
7229
+ target.push(...replacement);
7230
+ else
7231
+ target.push(marker);
7232
+ }
7233
+ function collectExistingStyleSlots(elements) {
7234
+ const slots = new Set;
7235
+ walkElements(elements, (element) => {
7236
+ if (element.element !== "if-tags")
7237
+ return;
7238
+ const slotId = getStyleSlotId(element.data);
7239
+ if (slotId !== undefined)
7240
+ slots.add(slotId);
7241
+ });
7242
+ return slots;
7243
+ }
7244
+ function allocateStyleSlot(ctx) {
7245
+ while (ctx.usedSlots.has(ctx.nextSlotId))
7246
+ ctx.nextSlotId++;
7247
+ const slotId = ctx.nextSlotId++;
7248
+ ctx.usedSlots.add(slotId);
7249
+ return slotId;
7250
+ }
7251
+ function collectStylesFromElements(elements, styles, anchoredStyles, anchors, ignoredAnchors, ctx) {
7252
+ const result = [];
7253
+ for (const element of elements) {
7254
+ if (element.element === "style") {
7255
+ const css = element.data;
7256
+ if (css.startsWith(import_ast2.STYLE_SLOT_PREFIX)) {
7257
+ styles.push(css);
7258
+ continue;
7259
+ }
7260
+ if (css.startsWith(import_ast2.STYLE_ANCHOR_PREFIX)) {
7261
+ anchors.push(element);
7262
+ if (ignoredAnchors.has(element)) {
7263
+ result.push(element);
7264
+ continue;
7265
+ }
7266
+ const anchoredCss = css.slice(import_ast2.STYLE_ANCHOR_PREFIX.length);
7267
+ styles.push(anchoredCss);
7268
+ anchoredStyles.push(anchoredCss);
7269
+ result.push(element);
7270
+ continue;
7271
+ }
7272
+ styles.push(css);
7273
+ const anchor = { element: "style", data: `${import_ast2.STYLE_ANCHOR_PREFIX}${css}` };
7274
+ anchors.push(anchor);
7275
+ result.push(anchor);
7276
+ continue;
7277
+ }
7278
+ if (element.element === "if-tags") {
7279
+ const slotId = getStyleSlotId(element.data) ?? allocateStyleSlot(ctx);
7280
+ styles.push(createStyleSlotMarker(slotId));
7281
+ const data = { ...element.data, _styleSlot: slotId };
7282
+ result.push({
7283
+ element: "if-tags",
7284
+ data
7285
+ });
7286
+ continue;
7287
+ }
7288
+ result.push(mapElementChildren(element, (children) => collectStylesFromElements(children, styles, anchoredStyles, anchors, ignoredAnchors, ctx)));
7289
+ }
7290
+ return result;
7291
+ }
7292
+
7084
7293
  // packages/parser/src/parser/rules/block/module/resolution/walk-resolve.ts
7085
7294
  function walkAndResolve(elements, ctx) {
7086
7295
  const result = [];
@@ -7103,6 +7312,7 @@ function walkAndResolve(elements, ctx) {
7103
7312
  if (isIfTagsElement(element)) {
7104
7313
  const ifTagsData = element.data;
7105
7314
  const resolveResult = resolveIfTags(ifTagsData, ctx.pageTags);
7315
+ const styleSlotId = getStyleSlotId(ifTagsData);
7106
7316
  if (resolveResult.evaluated) {
7107
7317
  if (resolveResult.matched) {
7108
7318
  const childResult = walkAndResolve(ifTagsData.elements, {
@@ -7111,11 +7321,23 @@ function walkAndResolve(elements, ctx) {
7111
7321
  listUsersIdCounter: listUsersId,
7112
7322
  tagCloudIdCounter: tagCloudId
7113
7323
  });
7114
- result.push(...childResult.elements);
7324
+ if (styleSlotId === undefined) {
7325
+ result.push(...childResult.elements);
7326
+ } else {
7327
+ const collected = collectStyles(childResult.elements);
7328
+ ctx.resolvedStyleSlots.set(styleSlotId, collected.styles);
7329
+ for (const anchor of collected.anchors)
7330
+ ctx.routedStyleAnchors.add(anchor);
7331
+ result.push({ element: "style", data: createStyleSlotMarker(styleSlotId) }, ...collected.elements);
7332
+ }
7115
7333
  listPagesId = childResult.nextListPagesId;
7116
7334
  listUsersId = childResult.nextListUsersId;
7117
7335
  tagCloudId = childResult.nextTagCloudId;
7118
7336
  } else {
7337
+ if (styleSlotId !== undefined) {
7338
+ ctx.resolvedStyleSlots.set(styleSlotId, []);
7339
+ result.push({ element: "style", data: createStyleSlotMarker(styleSlotId) });
7340
+ }
7119
7341
  const counts = countDynamicModules(ifTagsData.elements);
7120
7342
  listPagesId += counts.listPagesId;
7121
7343
  listUsersId += counts.listUsersId;
@@ -7170,143 +7392,463 @@ function walkAndResolve(elements, ctx) {
7170
7392
  };
7171
7393
  }
7172
7394
 
7173
- // packages/parser/src/parser/rules/block/module/resolution/styles.ts
7174
- var import_ast2 = require("@wdprlib/ast");
7175
- function collectStyles(elements) {
7176
- const styles = [];
7177
- const ctx = { nextSlotId: 0 };
7178
- const filtered = collectStylesFromElements(elements, styles, ctx);
7179
- return { elements: filtered, styles };
7180
- }
7181
- function collectStylesFromElements(elements, styles, ctx) {
7182
- const result = [];
7183
- for (const element of elements) {
7184
- if (element.element === "style") {
7185
- styles.push(element.data);
7186
- continue;
7187
- }
7188
- if (element.element === "if-tags") {
7189
- const slotId = ctx.nextSlotId++;
7190
- styles.push(`${import_ast2.STYLE_SLOT_PREFIX}${slotId}`);
7191
- const data = { ...element.data, _styleSlot: slotId };
7192
- result.push({
7193
- element: "if-tags",
7194
- data
7195
- });
7196
- continue;
7197
- }
7198
- result.push(mapElementChildren(element, (children) => collectStylesFromElements(children, styles, ctx)));
7395
+ // packages/parser/src/parser/rules/block/module/resolution/document.ts
7396
+ var import_ast3 = require("@wdprlib/ast");
7397
+
7398
+ // packages/parser/src/parser/toc.ts
7399
+ class TocIndexer {
7400
+ index = 0;
7401
+ next() {
7402
+ return this.index++;
7199
7403
  }
7200
- return result;
7201
7404
  }
7202
-
7203
- // packages/parser/src/parser/rules/block/module/resolve.ts
7204
- var MODULE_SECONDARY_INCLUDE_MAX_ITERATIONS = 5;
7205
- async function resolveModules(ast, dataProvider, options) {
7206
- const parse = createModuleParseFunction(options, dataProvider);
7207
- const listPagesCtx = await buildListPagesContext(dataProvider, options.requirements.listPages ?? [], options.compiledListPagesTemplates, parse, options.urlPath);
7208
- const listUsersCtx = await buildListUsersContext(dataProvider, options.requirements.listUsers ?? [], options.compiledListUsersTemplates, parse);
7209
- const tagCloudCtx = await buildTagCloudContext(dataProvider, options.requirements.tagCloud ?? []);
7210
- const pageTags = dataProvider.getPageTags?.() ?? null;
7211
- const resolvedElements = walkAndResolve(ast.elements, {
7212
- listPages: listPagesCtx,
7213
- listUsers: listUsersCtx,
7214
- tagCloud: tagCloudCtx,
7215
- fetchListPagesProvided: dataProvider.fetchListPages !== undefined,
7216
- fetchListUsersProvided: dataProvider.fetchListUsers !== undefined,
7217
- fetchTagCloudProvided: dataProvider.fetchTagCloud !== undefined,
7218
- pageTags,
7219
- listPagesIdCounter: 0,
7220
- listUsersIdCounter: 0,
7221
- tagCloudIdCounter: 0
7222
- });
7223
- const { elements: finalElements, styles } = collectStyles(resolvedElements.elements);
7224
- const result = {
7225
- ...ast,
7226
- elements: finalElements
7405
+ function buildTocList(indexer, items) {
7406
+ const listItems = items.map((item) => buildTocListItem(indexer, item));
7407
+ return {
7408
+ element: "list",
7409
+ data: {
7410
+ type: "bullet",
7411
+ attributes: {},
7412
+ items: listItems
7413
+ }
7227
7414
  };
7228
- if (styles.length > 0) {
7229
- result.styles = styles;
7230
- }
7231
- return result;
7232
7415
  }
7233
- function createModuleParseFunction(options, dataProvider) {
7234
- const transform = createModuleSourceTransform(options, dataProvider);
7235
- if (!transform) {
7236
- return options.parse;
7416
+ function buildTocListItem(indexer, item) {
7417
+ if (item.kind === "list") {
7418
+ return {
7419
+ "item-type": "sub-list",
7420
+ element: "list",
7421
+ data: {
7422
+ type: "bullet",
7423
+ attributes: {},
7424
+ items: item.children.map((child) => buildTocListItem(indexer, child))
7425
+ }
7426
+ };
7237
7427
  }
7238
- return (source) => options.parse(transform(source));
7428
+ const anchor = `#toc${indexer.next()}`;
7429
+ const linkElement = {
7430
+ element: "link",
7431
+ data: {
7432
+ type: "table-of-contents",
7433
+ link: anchor,
7434
+ extra: null,
7435
+ label: { text: item.value },
7436
+ target: null
7437
+ }
7438
+ };
7439
+ return {
7440
+ "item-type": "elements",
7441
+ attributes: {},
7442
+ elements: [linkElement]
7443
+ };
7239
7444
  }
7240
- function createModuleSourceTransform(options, dataProvider) {
7241
- if (options.transformModuleSource) {
7242
- return options.transformModuleSource;
7243
- }
7244
- if (!dataProvider.fetchInclude) {
7245
- return null;
7445
+ function buildTableOfContents(entries) {
7446
+ if (entries.length === 0) {
7447
+ return [];
7246
7448
  }
7247
- return (source) => resolveIncludes(source, dataProvider.fetchInclude, {
7248
- maxIterations: options.includeMaxIterations ?? MODULE_SECONDARY_INCLUDE_MAX_ITERATIONS
7249
- });
7449
+ const depthItems = entries.map((entry) => ({
7450
+ depth: entry.level - 1,
7451
+ ltype: null,
7452
+ value: entry.text
7453
+ }));
7454
+ const trees = processDepths(null, depthItems);
7455
+ const indexer = new TocIndexer;
7456
+ return trees.map((tree) => buildTocList(indexer, tree.list));
7250
7457
  }
7251
7458
 
7252
- // packages/parser/src/parser/rules/block/module/index.ts
7253
- var import_ast3 = require("@wdprlib/ast");
7254
-
7255
- // packages/parser/src/parser/rules/block/footnoteblock/attributes.ts
7256
- function parseFootnoteBlockAttributes(ctx, startPos) {
7257
- const attrs = {};
7258
- let pos = startPos;
7259
- let consumed = 0;
7260
- while (pos < ctx.tokens.length) {
7261
- const token5 = ctx.tokens[pos];
7262
- if (!token5 || token5.type === "BLOCK_CLOSE" || token5.type === "NEWLINE" || token5.type === "EOF") {
7263
- break;
7264
- }
7265
- if (token5.type === "WHITESPACE") {
7266
- pos++;
7267
- consumed++;
7268
- continue;
7269
- }
7270
- if (token5.type === "TEXT" || token5.type === "IDENTIFIER") {
7271
- const name = token5.value.toLowerCase();
7272
- pos++;
7273
- consumed++;
7274
- const eqToken = ctx.tokens[pos];
7275
- if (eqToken?.type === "EQUALS") {
7276
- pos++;
7277
- consumed++;
7278
- const valueToken = ctx.tokens[pos];
7279
- if (valueToken?.type === "QUOTED_STRING") {
7280
- attrs[name] = stripQuotes2(valueToken.value);
7281
- pos++;
7282
- consumed++;
7283
- } else if (valueToken?.type === "TEXT" || valueToken?.type === "IDENTIFIER") {
7284
- attrs[name] = valueToken.value;
7285
- pos++;
7286
- consumed++;
7287
- }
7288
- } else {
7289
- attrs[name] = "true";
7459
+ // packages/parser/src/parser/rules/block/module/resolution/document.ts
7460
+ class ModuleDocumentRegistry {
7461
+ footnotesByElement = new WeakMap;
7462
+ diagnostics = [];
7463
+ register(result, options = {}) {
7464
+ const parseResult = isParseResult(result) ? result : null;
7465
+ const ast = parseResult ? parseResult.ast : result;
7466
+ const elements = options.stripLegacyImplicitFootnoteBlock ? stripTrailingDefaultFootnoteBlock(ast.elements) : ast.elements;
7467
+ const registeredAst = elements === ast.elements ? ast : { ...ast, elements };
7468
+ if (parseResult) {
7469
+ this.diagnostics.push(...parseResult.diagnostics);
7470
+ }
7471
+ this.registerFootnotes(registeredAst);
7472
+ return registeredAst;
7473
+ }
7474
+ finalize(ast, elements, pageTags, ensureFootnoteBlock = true) {
7475
+ const normalizedElements = normalizeFootnoteBlocks(elements, ensureFootnoteBlock);
7476
+ const footnotes = [];
7477
+ const htmlBlocks = [];
7478
+ const codeBlocks = [];
7479
+ const tocEntries = [];
7480
+ const collectElement = (element) => {
7481
+ if (element.element === "footnote") {
7482
+ const content = this.footnotesByElement.get(element);
7483
+ if (content)
7484
+ footnotes.push(content);
7485
+ } else if (element.element === "html") {
7486
+ htmlBlocks.push(element.data.contents);
7487
+ } else if (element.element === "code") {
7488
+ codeBlocks.push(element.data);
7489
+ } else if (isTocHeading(element)) {
7490
+ tocEntries.push({
7491
+ level: element.data.type.header.level,
7492
+ text: extractHeadingText(element.data.elements)
7493
+ });
7290
7494
  }
7291
- } else {
7292
- pos++;
7293
- consumed++;
7495
+ };
7496
+ walkRenderOrder(normalizedElements, pageTags, collectElement);
7497
+ const result = { ...ast, elements: normalizedElements };
7498
+ setOptionalArray(result, "footnotes", footnotes);
7499
+ setOptionalArray(result, "html-blocks", htmlBlocks);
7500
+ setOptionalArray(result, "code-blocks", codeBlocks);
7501
+ setOptionalArray(result, "table-of-contents", buildTableOfContents(tocEntries));
7502
+ return result;
7503
+ }
7504
+ registerFootnotes(ast) {
7505
+ const footnoteElements = [];
7506
+ walkSyntaxOrder(ast.elements, (element) => {
7507
+ if (element.element === "footnote")
7508
+ footnoteElements.push(element);
7509
+ });
7510
+ for (let i = 0;i < footnoteElements.length; i++) {
7511
+ const content = ast.footnotes?.[i];
7512
+ if (content)
7513
+ this.footnotesByElement.set(footnoteElements[i], content);
7294
7514
  }
7295
7515
  }
7296
- return { attrs, consumed };
7297
7516
  }
7298
- function stripQuotes2(value) {
7299
- return value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
7517
+ function isParseResult(result) {
7518
+ return "ast" in result && "diagnostics" in result;
7300
7519
  }
7301
-
7302
- // packages/parser/src/parser/rules/block/footnoteblock/open.ts
7303
- function parseFootnoteBlockOpen(ctx, startPos) {
7304
- if (ctx.tokens[startPos]?.type !== "BLOCK_OPEN") {
7305
- return null;
7520
+ function setOptionalArray(tree, key, value) {
7521
+ if (value.length > 0) {
7522
+ tree[key] = value;
7523
+ } else {
7524
+ delete tree[key];
7306
7525
  }
7307
- let pos = startPos + 1;
7308
- let consumed = 1;
7309
- while (ctx.tokens[pos]?.type === "WHITESPACE") {
7526
+ }
7527
+ function stripTrailingDefaultFootnoteBlock(elements) {
7528
+ const last = elements.at(-1);
7529
+ if (last?.element !== "footnote-block" || last.data.title !== null || last.data.hide === true) {
7530
+ return elements;
7531
+ }
7532
+ return elements.slice(0, -1);
7533
+ }
7534
+ function normalizeFootnoteBlocks(elements, ensureFootnoteBlock) {
7535
+ const state = { found: false };
7536
+ const normalized = mapSyntaxElements(elements, (element) => {
7537
+ if (element.element !== "footnote-block")
7538
+ return element;
7539
+ if (state.found)
7540
+ return null;
7541
+ state.found = true;
7542
+ return element;
7543
+ });
7544
+ if (!state.found && ensureFootnoteBlock) {
7545
+ normalized.push({
7546
+ element: "footnote-block",
7547
+ data: { title: null, hide: false }
7548
+ });
7549
+ }
7550
+ return normalized;
7551
+ }
7552
+ function containsSyntaxFootnoteBlock(elements) {
7553
+ let found = false;
7554
+ walkSyntaxOrder(elements, (element) => {
7555
+ if (element.element === "footnote-block")
7556
+ found = true;
7557
+ });
7558
+ return found;
7559
+ }
7560
+ function mapSyntaxElements(elements, transform) {
7561
+ const result = [];
7562
+ for (const original of elements) {
7563
+ const element = transform(original);
7564
+ if (!element)
7565
+ continue;
7566
+ result.push(mapSyntaxChildren(element, (children) => mapSyntaxElements(children, transform)));
7567
+ }
7568
+ return result;
7569
+ }
7570
+ function mapSyntaxChildren(element, transform) {
7571
+ if (element.element === "if") {
7572
+ return {
7573
+ ...element,
7574
+ data: {
7575
+ ...element.data,
7576
+ then: transform(element.data.then),
7577
+ else: transform(element.data.else)
7578
+ }
7579
+ };
7580
+ }
7581
+ if (element.element === "ifexpr") {
7582
+ return {
7583
+ ...element,
7584
+ data: {
7585
+ ...element.data,
7586
+ then: transform(element.data.then),
7587
+ else: transform(element.data.else)
7588
+ }
7589
+ };
7590
+ }
7591
+ if (element.element === "bibliography-block") {
7592
+ return {
7593
+ ...element,
7594
+ data: {
7595
+ ...element.data,
7596
+ entries: element.data.entries.map((entry) => ({
7597
+ ...entry,
7598
+ key: transform(entry.key),
7599
+ value: transform(entry.value)
7600
+ }))
7601
+ }
7602
+ };
7603
+ }
7604
+ if (element.element === "list") {
7605
+ return {
7606
+ ...element,
7607
+ data: {
7608
+ ...element.data,
7609
+ items: element.data.items.map((item) => item["item-type"] === "elements" ? { ...item, elements: transform(item.elements) } : mapSubList(item, transform))
7610
+ }
7611
+ };
7612
+ }
7613
+ if (element.element === "table") {
7614
+ return {
7615
+ ...element,
7616
+ data: {
7617
+ ...element.data,
7618
+ rows: element.data.rows.map((row) => ({
7619
+ ...row,
7620
+ cells: row.cells.map((cell) => ({ ...cell, elements: transform(cell.elements) }))
7621
+ }))
7622
+ }
7623
+ };
7624
+ }
7625
+ if (element.element === "definition-list") {
7626
+ return {
7627
+ ...element,
7628
+ data: element.data.map((entry) => ({
7629
+ ...entry,
7630
+ key: transform(entry.key),
7631
+ value: transform(entry.value)
7632
+ }))
7633
+ };
7634
+ }
7635
+ if (element.element === "tab-view") {
7636
+ return {
7637
+ ...element,
7638
+ data: element.data.map((tab) => ({ ...tab, elements: transform(tab.elements) }))
7639
+ };
7640
+ }
7641
+ const children = getGenericElementChildren(element);
7642
+ return children === null ? element : withGenericElementChildren(element, transform(children));
7643
+ }
7644
+ function mapSubList(item, transform) {
7645
+ const transformed = transform([listElement(item.data)])[0];
7646
+ return transformed?.element === "list" ? { ...item, data: transformed.data } : item;
7647
+ }
7648
+ function walkSyntaxOrder(elements, callback) {
7649
+ for (const element of elements) {
7650
+ callback(element);
7651
+ walkAllSyntaxChildren(element, (children) => walkSyntaxOrder(children, callback));
7652
+ }
7653
+ }
7654
+ function walkRenderOrder(elements, pageTags, callback) {
7655
+ for (const element of elements) {
7656
+ callback(element);
7657
+ if (element.element === "if") {
7658
+ walkRenderOrder(import_ast3.isTruthy(element.data.condition) ? element.data.then : element.data.else, pageTags, callback);
7659
+ continue;
7660
+ }
7661
+ if (element.element === "ifexpr") {
7662
+ const evaluated = import_ast3.evaluateExpression(element.data.expression);
7663
+ if (evaluated.success) {
7664
+ walkRenderOrder(evaluated.value !== 0 ? element.data.then : element.data.else, pageTags, callback);
7665
+ }
7666
+ continue;
7667
+ }
7668
+ if (element.element === "if-tags") {
7669
+ const resolved = resolveIfTags(element.data, pageTags);
7670
+ if (!resolved.evaluated || resolved.matched) {
7671
+ walkRenderOrder(element.data.elements, pageTags, callback);
7672
+ }
7673
+ continue;
7674
+ }
7675
+ if (element.element === "bibliography-block") {
7676
+ if (!element.data.hide) {
7677
+ for (const entry of element.data.entries) {
7678
+ walkRenderOrder(entry.value, pageTags, callback);
7679
+ }
7680
+ }
7681
+ continue;
7682
+ }
7683
+ walkBasicChildren(element, (children) => walkRenderOrder(children, pageTags, callback));
7684
+ }
7685
+ }
7686
+ function walkAllSyntaxChildren(element, callback) {
7687
+ if (element.element === "if" || element.element === "ifexpr") {
7688
+ callback(element.data.then);
7689
+ callback(element.data.else);
7690
+ return;
7691
+ }
7692
+ if (element.element === "bibliography-block") {
7693
+ for (const entry of element.data.entries) {
7694
+ callback(entry.key);
7695
+ callback(entry.value);
7696
+ }
7697
+ return;
7698
+ }
7699
+ walkBasicChildren(element, callback);
7700
+ }
7701
+ function walkBasicChildren(element, callback) {
7702
+ if (element.element === "list") {
7703
+ for (const item of element.data.items) {
7704
+ callback(item["item-type"] === "elements" ? item.elements : [listElement(item.data)]);
7705
+ }
7706
+ return;
7707
+ }
7708
+ if (element.element === "table") {
7709
+ for (const row of element.data.rows) {
7710
+ for (const cell of row.cells)
7711
+ callback(cell.elements);
7712
+ }
7713
+ return;
7714
+ }
7715
+ if (element.element === "definition-list") {
7716
+ for (const entry of element.data) {
7717
+ callback(entry.key);
7718
+ callback(entry.value);
7719
+ }
7720
+ return;
7721
+ }
7722
+ if (element.element === "tab-view") {
7723
+ for (const tab of element.data)
7724
+ callback(tab.elements);
7725
+ return;
7726
+ }
7727
+ const children = getGenericElementChildren(element);
7728
+ if (children !== null)
7729
+ callback(children);
7730
+ }
7731
+ function isTocHeading(element) {
7732
+ return element.element === "container" && typeof element.data.type === "object" && "header" in element.data.type && element.data.type.header["has-toc"];
7733
+ }
7734
+
7735
+ // packages/parser/src/parser/rules/block/module/resolve.ts
7736
+ var MODULE_SECONDARY_INCLUDE_MAX_ITERATIONS = 5;
7737
+ async function resolveModules(ast, dataProvider, options) {
7738
+ const registry = new ModuleDocumentRegistry;
7739
+ registry.register(ast);
7740
+ const parse = createModuleParseFunction(options, dataProvider, registry);
7741
+ const listPagesCtx = await buildListPagesContext(dataProvider, options.requirements.listPages ?? [], options.compiledListPagesTemplates, parse, options.urlPath);
7742
+ const listUsersCtx = await buildListUsersContext(dataProvider, options.requirements.listUsers ?? [], options.compiledListUsersTemplates, parse);
7743
+ const tagCloudCtx = await buildTagCloudContext(dataProvider, options.requirements.tagCloud ?? []);
7744
+ const pageTags = dataProvider.getPageTags?.() ?? null;
7745
+ const resolvedStyleSlots = new Map;
7746
+ const routedStyleAnchors = new WeakSet;
7747
+ const resolvedElements = walkAndResolve(ast.elements, {
7748
+ listPages: listPagesCtx,
7749
+ listUsers: listUsersCtx,
7750
+ tagCloud: tagCloudCtx,
7751
+ fetchListPagesProvided: dataProvider.fetchListPages !== undefined,
7752
+ fetchListUsersProvided: dataProvider.fetchListUsers !== undefined,
7753
+ fetchTagCloudProvided: dataProvider.fetchTagCloud !== undefined,
7754
+ pageTags,
7755
+ listPagesIdCounter: 0,
7756
+ listUsersIdCounter: 0,
7757
+ tagCloudIdCounter: 0,
7758
+ resolvedStyleSlots,
7759
+ routedStyleAnchors
7760
+ });
7761
+ const {
7762
+ elements: finalElements,
7763
+ styles,
7764
+ anchoredStyles
7765
+ } = collectStyles(resolvedElements.elements, routedStyleAnchors);
7766
+ const result = {
7767
+ ...ast,
7768
+ elements: finalElements
7769
+ };
7770
+ const mergedStyles = mergeCollectedStyles(ast.styles, styles, resolvedStyleSlots, anchoredStyles);
7771
+ if (mergedStyles.length > 0)
7772
+ result.styles = mergedStyles;
7773
+ options.onDiagnostics?.(registry.diagnostics);
7774
+ return registry.finalize(result, finalElements, pageTags, containsSyntaxFootnoteBlock(ast.elements));
7775
+ }
7776
+ function createModuleParseFunction(options, dataProvider, registry) {
7777
+ const transform = createModuleSourceTransform(options, dataProvider);
7778
+ return (source) => registry.register(options.parse(transform ? transform(source) : source), {
7779
+ stripLegacyImplicitFootnoteBlock: true
7780
+ });
7781
+ }
7782
+ function createModuleSourceTransform(options, dataProvider) {
7783
+ if (options.transformModuleSource) {
7784
+ return options.transformModuleSource;
7785
+ }
7786
+ if (!dataProvider.fetchInclude) {
7787
+ return null;
7788
+ }
7789
+ return (source) => resolveIncludes(source, dataProvider.fetchInclude, {
7790
+ maxIterations: options.includeMaxIterations ?? MODULE_SECONDARY_INCLUDE_MAX_ITERATIONS
7791
+ });
7792
+ }
7793
+
7794
+ // packages/parser/src/parser/rules/block/module/index.ts
7795
+ var import_ast4 = require("@wdprlib/ast");
7796
+
7797
+ // packages/parser/src/parser/rules/block/footnoteblock/attributes.ts
7798
+ function parseFootnoteBlockAttributes(ctx, startPos) {
7799
+ const attrs = {};
7800
+ let pos = startPos;
7801
+ let consumed = 0;
7802
+ while (pos < ctx.tokens.length) {
7803
+ const token5 = ctx.tokens[pos];
7804
+ if (!token5 || token5.type === "BLOCK_CLOSE" || token5.type === "NEWLINE" || token5.type === "EOF") {
7805
+ break;
7806
+ }
7807
+ if (token5.type === "WHITESPACE") {
7808
+ pos++;
7809
+ consumed++;
7810
+ continue;
7811
+ }
7812
+ if (token5.type === "TEXT" || token5.type === "IDENTIFIER") {
7813
+ const name = token5.value.toLowerCase();
7814
+ pos++;
7815
+ consumed++;
7816
+ const eqToken = ctx.tokens[pos];
7817
+ if (eqToken?.type === "EQUALS") {
7818
+ pos++;
7819
+ consumed++;
7820
+ const valueToken = ctx.tokens[pos];
7821
+ if (valueToken?.type === "QUOTED_STRING") {
7822
+ attrs[name] = stripQuotes2(valueToken.value);
7823
+ pos++;
7824
+ consumed++;
7825
+ } else if (valueToken?.type === "TEXT" || valueToken?.type === "IDENTIFIER") {
7826
+ attrs[name] = valueToken.value;
7827
+ pos++;
7828
+ consumed++;
7829
+ }
7830
+ } else {
7831
+ attrs[name] = "true";
7832
+ }
7833
+ } else {
7834
+ pos++;
7835
+ consumed++;
7836
+ }
7837
+ }
7838
+ return { attrs, consumed };
7839
+ }
7840
+ function stripQuotes2(value) {
7841
+ return value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
7842
+ }
7843
+
7844
+ // packages/parser/src/parser/rules/block/footnoteblock/open.ts
7845
+ function parseFootnoteBlockOpen(ctx, startPos) {
7846
+ if (ctx.tokens[startPos]?.type !== "BLOCK_OPEN") {
7847
+ return null;
7848
+ }
7849
+ let pos = startPos + 1;
7850
+ let consumed = 1;
7851
+ while (ctx.tokens[pos]?.type === "WHITESPACE") {
7310
7852
  pos++;
7311
7853
  consumed++;
7312
7854
  }
@@ -12369,7 +12911,8 @@ function createParseContext(tokens, options = {}) {
12369
12911
  pos: 0,
12370
12912
  version: options.version ?? "wikidot",
12371
12913
  trackPositions: options.trackPositions ?? true,
12372
- settings: options.settings ?? import_ast4.DEFAULT_SETTINGS,
12914
+ settings: options.settings ?? import_ast5.DEFAULT_SETTINGS,
12915
+ appendImplicitFootnoteBlock: options.appendImplicitFootnoteBlock ?? true,
12373
12916
  footnotes: [],
12374
12917
  tocEntries: [],
12375
12918
  codeBlocks: [],
@@ -12887,67 +13430,6 @@ function suppressAtLevel(elements) {
12887
13430
  function suppressDivAdjacentParagraphs(elements) {
12888
13431
  return suppressAtLevel(elements);
12889
13432
  }
12890
- // packages/parser/src/parser/toc.ts
12891
- class TocIndexer {
12892
- index = 0;
12893
- next() {
12894
- return this.index++;
12895
- }
12896
- }
12897
- function buildTocList(indexer, items) {
12898
- const listItems = items.map((item) => buildTocListItem(indexer, item));
12899
- return {
12900
- element: "list",
12901
- data: {
12902
- type: "bullet",
12903
- attributes: {},
12904
- items: listItems
12905
- }
12906
- };
12907
- }
12908
- function buildTocListItem(indexer, item) {
12909
- if (item.kind === "list") {
12910
- return {
12911
- "item-type": "sub-list",
12912
- element: "list",
12913
- data: {
12914
- type: "bullet",
12915
- attributes: {},
12916
- items: item.children.map((child) => buildTocListItem(indexer, child))
12917
- }
12918
- };
12919
- }
12920
- const anchor = `#toc${indexer.next()}`;
12921
- const linkElement = {
12922
- element: "link",
12923
- data: {
12924
- type: "table-of-contents",
12925
- link: anchor,
12926
- extra: null,
12927
- label: { text: item.value },
12928
- target: null
12929
- }
12930
- };
12931
- return {
12932
- "item-type": "elements",
12933
- attributes: {},
12934
- elements: [linkElement]
12935
- };
12936
- }
12937
- function buildTableOfContents(entries) {
12938
- if (entries.length === 0) {
12939
- return [];
12940
- }
12941
- const depthItems = entries.map((entry) => ({
12942
- depth: entry.level - 1,
12943
- ltype: null,
12944
- value: entry.text
12945
- }));
12946
- const trees = processDepths(null, depthItems);
12947
- const indexer = new TocIndexer;
12948
- return trees.map((tree) => buildTocList(indexer, tree.list));
12949
- }
12950
-
12951
13433
  // packages/parser/src/parser/parse/footnotes.ts
12952
13434
  function containsFootnoteBlock(elements) {
12953
13435
  let found = false;
@@ -12961,7 +13443,9 @@ function containsFootnoteBlock(elements) {
12961
13443
  // packages/parser/src/parser/parse/result.ts
12962
13444
  function finalizeParseResult(ctx, children) {
12963
13445
  const cleanedChildren = postprocessChildren(children);
12964
- appendImplicitFootnoteBlock(cleanedChildren);
13446
+ if (ctx.appendImplicitFootnoteBlock) {
13447
+ appendImplicitFootnoteBlock(cleanedChildren);
13448
+ }
12965
13449
  return {
12966
13450
  ast: buildSyntaxTree(ctx, cleanedChildren),
12967
13451
  diagnostics: ctx.diagnostics
@@ -13333,30 +13817,6 @@ function preprocess(text) {
13333
13817
  return result;
13334
13818
  }
13335
13819
 
13336
- // packages/parser/src/parser/preprocess/expr/evaluate.ts
13337
- var import_ast5 = require("@wdprlib/ast");
13338
- function evaluateDirective(kind, match) {
13339
- if (kind === "expr") {
13340
- const result2 = import_ast5.evaluateExpression(match.head);
13341
- if (result2.success)
13342
- return import_ast5.formatExprValue(result2.value);
13343
- if (result2.error === "empty expression")
13344
- return "";
13345
- return "ERROR";
13346
- }
13347
- if (kind === "if") {
13348
- if (!match.hasPipe)
13349
- return "";
13350
- return import_ast5.isTruthy(match.head) ? match.thenText : match.elseText;
13351
- }
13352
- if (!match.hasPipe)
13353
- return "";
13354
- const result = import_ast5.evaluateExpression(match.head);
13355
- if (!result.success)
13356
- return "ERROR";
13357
- return result.value !== 0 && !Number.isNaN(result.value) ? match.thenText : match.elseText;
13358
- }
13359
-
13360
13820
  // packages/parser/src/parser/preprocess/expr/chars.ts
13361
13821
  function isWhitespace(ch) {
13362
13822
  return ch === " " || ch === "\t" || ch === `
@@ -13385,6 +13845,30 @@ function matchDirectiveKind(source, i) {
13385
13845
  return null;
13386
13846
  }
13387
13847
 
13848
+ // packages/parser/src/parser/preprocess/expr/evaluate.ts
13849
+ var import_ast6 = require("@wdprlib/ast");
13850
+ function evaluateDirective(kind, match) {
13851
+ if (kind === "expr") {
13852
+ const result2 = import_ast6.evaluateExpression(match.head);
13853
+ if (result2.success)
13854
+ return import_ast6.formatExprValue(result2.value);
13855
+ if (result2.error === "empty expression")
13856
+ return "";
13857
+ return "ERROR";
13858
+ }
13859
+ if (kind === "if") {
13860
+ if (!match.hasPipe)
13861
+ return "";
13862
+ return import_ast6.isTruthy(match.head) ? match.thenText : match.elseText;
13863
+ }
13864
+ if (!match.hasPipe)
13865
+ return "";
13866
+ const result = import_ast6.evaluateExpression(match.head);
13867
+ if (!result.success)
13868
+ return "ERROR";
13869
+ return result.value !== 0 && !Number.isNaN(result.value) ? match.thenText : match.elseText;
13870
+ }
13871
+
13388
13872
  // packages/parser/src/parser/preprocess/expr/parse.ts
13389
13873
  function tryParseInnermostDirective(source, start, kind) {
13390
13874
  const keywordLen = kind === "ifexpr" ? 6 : kind === "expr" ? 4 : 2;
@@ -13486,11 +13970,14 @@ function expandInnermost(source) {
13486
13970
  }
13487
13971
 
13488
13972
  // packages/parser/src/parser/preprocess/expr/index.ts
13973
+ var MAX_EXPR_NESTING = 64;
13489
13974
  function preprocessExpr(source) {
13490
13975
  if (!source.includes("[[#"))
13491
13976
  return source;
13492
13977
  const sentinels = makeUniqueSentinels(source);
13493
13978
  const { masked, placeholders } = maskRawRegions(source, sentinels);
13979
+ if (exceedsExprNestingLimit(masked))
13980
+ return source;
13494
13981
  const reduced = reduceExpr(masked);
13495
13982
  return restorePlaceholders(reduced, placeholders, sentinels);
13496
13983
  }
@@ -13505,6 +13992,26 @@ function reduceExpr(source) {
13505
13992
  }
13506
13993
  return current2;
13507
13994
  }
13995
+ function exceedsExprNestingLimit(source) {
13996
+ const expressionStack = [];
13997
+ let expressionDepth = 0;
13998
+ for (let i = 0;i < source.length; i++) {
13999
+ if (source.startsWith("[[", i)) {
14000
+ const isExpression = matchDirectiveKind(source, i) !== null;
14001
+ expressionStack.push(isExpression);
14002
+ if (isExpression && ++expressionDepth > MAX_EXPR_NESTING)
14003
+ return true;
14004
+ i++;
14005
+ continue;
14006
+ }
14007
+ if (source.startsWith("]]", i)) {
14008
+ if (expressionStack.pop())
14009
+ expressionDepth--;
14010
+ i++;
14011
+ }
14012
+ }
14013
+ return false;
14014
+ }
13508
14015
 
13509
14016
  // packages/parser/src/parser/parse/source.ts
13510
14017
  function prepareSourceForParse(source, options) {
@@ -13531,3 +14038,282 @@ function parse(source, options) {
13531
14038
  });
13532
14039
  return new Parser(tokens, options).parse();
13533
14040
  }
14041
+ // packages/parser/src/pipeline/process.ts
14042
+ var import_ast7 = require("@wdprlib/ast");
14043
+
14044
+ // packages/parser/src/parser/rules/block/module/resolution/resolve-async.ts
14045
+ async function resolveModulesWithAsyncParse(ast, dataProvider, options) {
14046
+ const registry = new ModuleDocumentRegistry;
14047
+ registry.register(ast);
14048
+ const parse2 = async (source) => registry.register(await options.parse(source));
14049
+ const [listPagesData, listUsersData, tagCloudData] = await Promise.all([
14050
+ buildListPagesDataMap(dataProvider, options.requirements.listPages ?? [], options.urlPath),
14051
+ buildListUsersDataMap(dataProvider, options.requirements.listUsers ?? []),
14052
+ buildTagCloudDataMap(dataProvider, options.requirements.tagCloud ?? [])
14053
+ ]);
14054
+ const context = {
14055
+ dataProvider,
14056
+ listPagesData,
14057
+ listUsersData,
14058
+ tagCloudData,
14059
+ options,
14060
+ parse: parse2
14061
+ };
14062
+ const state = { listPagesId: 0, listUsersId: 0, tagCloudId: 0 };
14063
+ const resolvedElements = await resolveElements(ast.elements, context, state);
14064
+ const { elements, styles, anchoredStyles } = collectStyles(resolvedElements);
14065
+ const intermediate = { ...ast, elements };
14066
+ const mergedStyles = mergeCollectedStyles(ast.styles, styles, new Map, anchoredStyles);
14067
+ if (mergedStyles.length > 0)
14068
+ intermediate.styles = mergedStyles;
14069
+ else
14070
+ delete intermediate.styles;
14071
+ return {
14072
+ ast: registry.finalize(intermediate, elements, options.pageTags),
14073
+ diagnostics: registry.diagnostics
14074
+ };
14075
+ }
14076
+ async function resolveElements(elements, context, state) {
14077
+ const result = [];
14078
+ for (const element of elements) {
14079
+ if (element.element === "module") {
14080
+ const resolved = await resolveModuleElement(element, context, state);
14081
+ if (resolved !== null) {
14082
+ result.push(...resolved);
14083
+ continue;
14084
+ }
14085
+ }
14086
+ if (element.element === "if-tags") {
14087
+ const resolution = resolveIfTags(element.data, context.options.pageTags);
14088
+ if (resolution.matched) {
14089
+ result.push(...await resolveElements(element.data.elements, context, state));
14090
+ }
14091
+ continue;
14092
+ }
14093
+ result.push(await resolveElementChildren(element, context, state));
14094
+ }
14095
+ return result;
14096
+ }
14097
+ async function resolveModuleElement(element, context, state) {
14098
+ if (isListPagesModule2(element.data)) {
14099
+ const id = state.listPagesId++;
14100
+ const data = context.listPagesData.get(id);
14101
+ const template = context.options.compiledListPagesTemplates.get(id);
14102
+ if (data && template)
14103
+ return resolveListPagesAsync(element.data, data, template, context.parse);
14104
+ return context.dataProvider.fetchListPages ? [] : [element];
14105
+ }
14106
+ if (isListUsersModule2(element.data)) {
14107
+ const id = state.listUsersId++;
14108
+ const data = context.listUsersData.get(id);
14109
+ const template = context.options.compiledListUsersTemplates?.get(id);
14110
+ if (data && template) {
14111
+ const variableContext = { user: data.user };
14112
+ return (await context.parse(template(variableContext))).elements;
14113
+ }
14114
+ return context.dataProvider.fetchListUsers ? [] : [element];
14115
+ }
14116
+ if (isTagCloudModule(element.data)) {
14117
+ const id = state.tagCloudId++;
14118
+ const data = context.tagCloudData.get(id);
14119
+ if (data)
14120
+ return resolveTagCloud(element.data, data);
14121
+ return context.dataProvider.fetchTagCloud ? [] : [element];
14122
+ }
14123
+ return null;
14124
+ }
14125
+ async function resolveListPagesAsync(module2, data, template, parse2) {
14126
+ if (data.pages.length === 0)
14127
+ return [];
14128
+ const result = [];
14129
+ if (module2["prepend-line"] && !module2.separate) {
14130
+ result.push(...(await parse2(module2["prepend-line"])).elements);
14131
+ }
14132
+ for (let i = 0;i < data.pages.length; i++) {
14133
+ const page = data.pages[i];
14134
+ if (!page)
14135
+ continue;
14136
+ const variableContext = {
14137
+ page,
14138
+ index: i + 1,
14139
+ total: data.totalCount,
14140
+ limit: module2.limit,
14141
+ site: data.site
14142
+ };
14143
+ const parsed = await parse2(template(variableContext));
14144
+ if (module2.separate) {
14145
+ result.push({
14146
+ element: "container",
14147
+ data: {
14148
+ type: "div",
14149
+ attributes: { class: "list-pages-item" },
14150
+ elements: parsed.elements
14151
+ }
14152
+ });
14153
+ } else {
14154
+ result.push(...parsed.elements);
14155
+ }
14156
+ }
14157
+ if (module2["append-line"] && !module2.separate) {
14158
+ result.push(...(await parse2(module2["append-line"])).elements);
14159
+ }
14160
+ return module2.wrapper ? [
14161
+ {
14162
+ element: "container",
14163
+ data: {
14164
+ type: "div",
14165
+ attributes: { class: "list-pages-box" },
14166
+ elements: result
14167
+ }
14168
+ }
14169
+ ] : result;
14170
+ }
14171
+ async function resolveElementChildren(element, context, state) {
14172
+ if (element.element === "list") {
14173
+ const items = [];
14174
+ for (const item of element.data.items) {
14175
+ if (item["item-type"] === "elements") {
14176
+ items.push({ ...item, elements: await resolveElements(item.elements, context, state) });
14177
+ } else {
14178
+ const resolved = (await resolveElements([listElement(item.data)], context, state))[0];
14179
+ items.push(resolved?.element === "list" ? { ...item, data: resolved.data } : item);
14180
+ }
14181
+ }
14182
+ return { ...element, data: { ...element.data, items } };
14183
+ }
14184
+ if (element.element === "table") {
14185
+ const rows = [];
14186
+ for (const row of element.data.rows) {
14187
+ const cells = [];
14188
+ for (const cell of row.cells) {
14189
+ cells.push({ ...cell, elements: await resolveElements(cell.elements, context, state) });
14190
+ }
14191
+ rows.push({ ...row, cells });
14192
+ }
14193
+ return { ...element, data: { ...element.data, rows } };
14194
+ }
14195
+ if (element.element === "definition-list") {
14196
+ const entries = [];
14197
+ for (const entry of element.data) {
14198
+ entries.push({
14199
+ ...entry,
14200
+ key: await resolveElements(entry.key, context, state),
14201
+ value: await resolveElements(entry.value, context, state)
14202
+ });
14203
+ }
14204
+ return { ...element, data: entries };
14205
+ }
14206
+ if (element.element === "tab-view") {
14207
+ const tabs = [];
14208
+ for (const tab of element.data) {
14209
+ tabs.push({ ...tab, elements: await resolveElements(tab.elements, context, state) });
14210
+ }
14211
+ return { ...element, data: tabs };
14212
+ }
14213
+ const children = getGenericElementChildren(element);
14214
+ return children === null ? element : withGenericElementChildren(element, await resolveElements(children, context, state));
14215
+ }
14216
+
14217
+ // packages/parser/src/pipeline/process.ts
14218
+ var DEFAULT_MODULE_MAX_PASSES = 5;
14219
+ var PIPELINE_DIAGNOSTIC_POSITION = {
14220
+ start: { line: 1, column: 1, offset: 0 },
14221
+ end: { line: 1, column: 1, offset: 0 }
14222
+ };
14223
+ async function processWikitext(source, options) {
14224
+ const settings = options.settings ?? import_ast7.DEFAULT_SETTINGS;
14225
+ const callbackContext = {
14226
+ page: options.page,
14227
+ settings
14228
+ };
14229
+ const dependencies = [];
14230
+ const diagnostics = [];
14231
+ const fetchInclude = createRequestIncludeFetcher(options.dataProvider?.fetchInclude ? (pageRef) => options.dataProvider.fetchInclude(pageRef, callbackContext) : undefined);
14232
+ const resolveSource = async (input) => {
14233
+ if (!fetchInclude)
14234
+ return input;
14235
+ const resolution = await resolveIncludesAsyncWithTrace(input, fetchInclude, {
14236
+ maxIterations: options.includeMaxIterations,
14237
+ settings
14238
+ });
14239
+ dependencies.push(...resolution.dependencies);
14240
+ if (resolution.reachedMaxIterations) {
14241
+ diagnostics.push(createLimitDiagnostic("include-resolution-limit", `Include expansion stopped after ${options.includeMaxIterations ?? 10} iterations.`));
14242
+ }
14243
+ return resolution.source;
14244
+ };
14245
+ const expandedSource = await resolveSource(source);
14246
+ const initial = parse(expandedSource, {
14247
+ settings,
14248
+ pageTags: options.page.tags,
14249
+ appendImplicitFootnoteBlock: false
14250
+ });
14251
+ diagnostics.push(...initial.diagnostics);
14252
+ const dataProvider = createModuleDataProvider(options, callbackContext);
14253
+ const parseFragment = async (fragmentSource) => parse(await resolveSource(fragmentSource), {
14254
+ settings,
14255
+ pageTags: options.page.tags,
14256
+ appendImplicitFootnoteBlock: false
14257
+ });
14258
+ let ast = initial.ast;
14259
+ for (let pass = 0;pass < DEFAULT_MODULE_MAX_PASSES; pass++) {
14260
+ const extraction = extractDataRequirements(ast);
14261
+ if (pass > 0 && !hasResolvableRequirements(extraction.requirements, options.dataProvider)) {
14262
+ break;
14263
+ }
14264
+ const resolved = await resolveModulesWithAsyncParse(ast, dataProvider, {
14265
+ parse: parseFragment,
14266
+ compiledListPagesTemplates: extraction.compiledListPagesTemplates,
14267
+ compiledListUsersTemplates: extraction.compiledListUsersTemplates,
14268
+ requirements: extraction.requirements,
14269
+ urlPath: options.page.urlPath,
14270
+ pageTags: options.page.tags
14271
+ });
14272
+ ast = resolved.ast;
14273
+ diagnostics.push(...resolved.diagnostics);
14274
+ }
14275
+ if (hasResolvableRequirements(extractDataRequirements(ast).requirements, options.dataProvider)) {
14276
+ diagnostics.push(createLimitDiagnostic("module-resolution-limit", `Module resolution stopped after ${DEFAULT_MODULE_MAX_PASSES} passes.`));
14277
+ }
14278
+ return {
14279
+ ast,
14280
+ page: options.page,
14281
+ settings,
14282
+ diagnostics,
14283
+ dependencies
14284
+ };
14285
+ }
14286
+ function hasResolvableRequirements(requirements, provider) {
14287
+ return Boolean(provider?.fetchListPages && requirements.listPages.length > 0 || provider?.fetchListUsers && requirements.listUsers.length > 0 || provider?.fetchTagCloud && requirements.tagCloud.length > 0);
14288
+ }
14289
+ function createLimitDiagnostic(code, message) {
14290
+ return {
14291
+ severity: "warning",
14292
+ code,
14293
+ message,
14294
+ position: PIPELINE_DIAGNOSTIC_POSITION
14295
+ };
14296
+ }
14297
+ function createModuleDataProvider(options, context) {
14298
+ const provider = options.dataProvider;
14299
+ return {
14300
+ fetchListPages: provider?.fetchListPages ? (query, requirement) => provider.fetchListPages(query, requirement, context) : undefined,
14301
+ fetchListUsers: provider?.fetchListUsers ? (requirement) => provider.fetchListUsers(requirement, context) : undefined,
14302
+ fetchTagCloud: provider?.fetchTagCloud ? (requirement) => provider.fetchTagCloud(requirement, context) : undefined,
14303
+ getPageTags: () => options.page.tags
14304
+ };
14305
+ }
14306
+ function createRequestIncludeFetcher(fetcher) {
14307
+ if (!fetcher)
14308
+ return;
14309
+ const cache = new Map;
14310
+ return (pageRef) => {
14311
+ const key = `${pageRef.site ?? ""}:${pageRef.page.toLowerCase()}`;
14312
+ const cached = cache.get(key);
14313
+ if (cached)
14314
+ return cached;
14315
+ const result = fetcher(pageRef).catch(() => null);
14316
+ cache.set(key, result);
14317
+ return result;
14318
+ };
14319
+ }