md2gost 1.5.1 → 1.6.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.mjs CHANGED
@@ -1,13 +1,18 @@
1
+ import { fileURLToPath as __md2gostFileURLToPath } from "node:url";
2
+ import { dirname as __md2gostPathDirname } from "node:path";
3
+ const __MD2GOST_DIRNAME__ = __md2gostPathDirname(__md2gostFileURLToPath(import.meta.url));
4
+
1
5
  // src/index.ts
2
- import path5 from "path";
3
- import { fileURLToPath } from "url";
6
+ import path6 from "path";
4
7
 
5
8
  // src/main.ts
6
- import path4 from "path";
9
+ import path5 from "path";
7
10
 
8
11
  // src/doc.ts
9
12
  var Doc = class {
10
13
  nodes = [];
14
+ header = { type: "none" };
15
+ footer = { type: "auto" };
11
16
  rainbow = false;
12
17
  numberingLazy = false;
13
18
  numberingSections = false;
@@ -67,36 +72,66 @@ var Doc = class {
67
72
  spacing: { before: void 0, after: void 0 },
68
73
  text: { size: void 0 }
69
74
  };
70
- appendText(text) {
71
- this.nodes.push({ type: "text", text });
75
+ formula = {
76
+ spacing: { before: 8, after: 8 }
77
+ };
78
+ admonition = {
79
+ note: { title: "**\u041F\u0440\u0438\u043C\u0435\u0447\u0430\u043D\u0438\u0435**", indent: 1.25, spacing: { before: 8, after: 8 }, padding: { top: 6, right: 10, bottom: 6, left: 10 }, background: "f5f5f5", color: "666666", bar_width: 2.25, icon: true, icon_size: 16, title_color: true },
80
+ info: { title: "**\u041F\u0440\u0438\u043C\u0435\u0447\u0430\u043D\u0438\u0435**", indent: 1.25, spacing: { before: 8, after: 8 }, padding: { top: 6, right: 10, bottom: 6, left: 10 }, background: "f0f7ff", color: "1976d2", bar_width: 2.25, icon: true, icon_size: 16, title_color: true },
81
+ tip: { title: "**\u0420\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0430\u0446\u0438\u044F**", indent: 1.25, spacing: { before: 8, after: 8 }, padding: { top: 6, right: 10, bottom: 6, left: 10 }, background: "f2f8f2", color: "2e7d32", bar_width: 2.25, icon: true, icon_size: 16, title_color: true },
82
+ warning: { title: "**\u0412\u043D\u0438\u043C\u0430\u043D\u0438\u0435**", indent: 1.25, spacing: { before: 8, after: 8 }, padding: { top: 6, right: 10, bottom: 6, left: 10 }, background: "fff7ea", color: "b26a00", bar_width: 2.25, icon: true, icon_size: 16, title_color: true },
83
+ danger: { title: "**\u041A\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u0432\u0430\u0436\u043D\u043E**", indent: 1.25, spacing: { before: 8, after: 8 }, padding: { top: 6, right: 10, bottom: 6, left: 10 }, background: "fff0f0", color: "d32f2f", bar_width: 2.25, icon: true, icon_size: 16, title_color: true }
84
+ };
85
+ appendText(text, sourceLine) {
86
+ this.nodes.push({ type: "text", text, sourceLine });
72
87
  }
73
- appendTitle(text, level) {
74
- this.nodes.push({ type: "title", text, level });
88
+ appendTitle(text, level, sourceLine) {
89
+ this.nodes.push({ type: "title", text, level, sourceLine });
75
90
  }
76
91
  appendNode(node) {
77
92
  this.nodes.push(node);
78
93
  }
94
+ lineSkips = [];
95
+ addLineSkip(line, delta) {
96
+ this.lineSkips.push({ ln: line, d: delta });
97
+ }
98
+ mapSourceLine(line) {
99
+ for (const { ln, d } of this.lineSkips)
100
+ if (ln <= line) line -= d;
101
+ return line;
102
+ }
79
103
  };
80
- function tableRow(...items) {
81
- return items.map((v) => ({ type: "text", text: v }));
104
+ function tableRow(sourceLine, ...items) {
105
+ return items.map((v) => ({ type: "text", text: v, sourceLine }));
82
106
  }
83
107
 
84
108
  // src/parser.ts
85
- import fs2 from "fs/promises";
109
+ import fs3 from "fs/promises";
86
110
 
87
111
  // src/utils.ts
88
112
  import fs from "fs/promises";
89
113
  import fss from "fs";
90
114
  import * as path from "node:path";
91
- function choice(...options) {
92
- return options[randomInt(options.length)];
115
+
116
+ // src/errors.ts
117
+ var UserInputError = class extends Error {
118
+ constructor(message) {
119
+ super(message);
120
+ }
121
+ };
122
+
123
+ // src/utils.ts
124
+ function choice(first, ...options) {
125
+ const all = [first, ...options];
126
+ return all[randomInt(all.length)];
93
127
  }
94
128
  function randomInt(maxmin, max, rnd = Math.random) {
95
129
  if (max != void 0)
96
- return Math.floor(rnd() * (maxmin - max)) + max;
130
+ return Math.floor(rnd() * (max - maxmin)) + maxmin;
97
131
  return Math.floor(rnd() * maxmin);
98
132
  }
99
133
  function trimEnd(str, ...chs) {
134
+ chs = chs.filter((ch) => ch.length > 0);
100
135
  if (!chs || chs.length == 0) return str.trimEnd();
101
136
  let trimmed = false;
102
137
  do {
@@ -110,6 +145,7 @@ function trimEnd(str, ...chs) {
110
145
  return str;
111
146
  }
112
147
  function trimStart(str, ...chs) {
148
+ chs = chs.filter((ch) => ch.length > 0);
113
149
  if (!chs || chs.length == 0) return str.trimStart();
114
150
  let trimmed = false;
115
151
  do {
@@ -125,10 +161,10 @@ function trimStart(str, ...chs) {
125
161
  function toCapitalCase(str) {
126
162
  return str.slice(0, 1).toUpperCase() + str.slice(1);
127
163
  }
128
- async function checkIfFileIsBlocked(path6) {
164
+ async function checkIfFileIsBlocked(path7) {
129
165
  let file;
130
166
  try {
131
- file = await fs.open(path6, "r+");
167
+ file = await fs.open(path7, "r+");
132
168
  return false;
133
169
  } catch (err) {
134
170
  if (!err) return false;
@@ -181,22 +217,791 @@ function realpathAllowMissing(inputPath) {
181
217
  function isMissingPathError(err) {
182
218
  return err instanceof Error && "code" in err && (err.code === "ENOENT" || err.code === "ENOTDIR");
183
219
  }
220
+ function getSafePathResolver(workdir, checkFilesIsInsidePath) {
221
+ const allowedRealPath = checkFilesIsInsidePath === false ? false : realpathAllowMissing(checkFilesIsInsidePath === "" ? workdir : checkFilesIsInsidePath);
222
+ const resolvePath = (fname, origin) => {
223
+ if (process.platform != "win32") fname = fname.replaceAll("\\", "/");
224
+ if (process.platform == "win32" && fname.startsWith("/")) fname = "." + fname;
225
+ const targetPath = path.isAbsolute(fname) ? path.resolve(fname) : path.resolve(origin || workdir, fname);
226
+ if (allowedRealPath !== false) {
227
+ const targetRealPath = realpathAllowMissing(targetPath);
228
+ const relative2 = path.relative(allowedRealPath, targetRealPath);
229
+ const isOutside = relative2 === ".." || relative2.startsWith(`..${path.sep}`) || path.isAbsolute(relative2);
230
+ if (isOutside) throw new UserInputError(`Access denied: "${fname}" resolves outside the working directory.`);
231
+ }
232
+ return targetPath;
233
+ };
234
+ const getRelative = (fname) => {
235
+ const targetPath = resolvePath(fname);
236
+ if (allowedRealPath !== false)
237
+ return path.relative(allowedRealPath, targetPath);
238
+ return path.relative(workdir, targetPath);
239
+ };
240
+ return { resolvePath, getRelative };
241
+ }
242
+ function isLocalFilePath(link) {
243
+ const value = link.trim();
244
+ if (value.startsWith("//")) return false;
245
+ if (path.win32.isAbsolute(value)) return true;
246
+ if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(value)) return false;
247
+ if (value.startsWith("#")) return false;
248
+ return true;
249
+ }
250
+ function deepOverwrite(target, source) {
251
+ const result = Object.assign(/* @__PURE__ */ Object.create(null), target);
252
+ for (const [key, sourceValue] of Object.entries(source)) {
253
+ if (key === "__proto__" || key === "prototype" || key === "constructor")
254
+ continue;
255
+ const targetValue = Object.hasOwn(result, key) ? result[key] : void 0;
256
+ result[key] = isJSONDict(targetValue) && isJSONDict(sourceValue) ? deepOverwrite(targetValue, sourceValue) : sourceValue;
257
+ }
258
+ return result;
259
+ function isJSONDict(value) {
260
+ return value !== null && typeof value === "object" && !Array.isArray(value);
261
+ }
262
+ }
184
263
 
185
264
  // src/parser.ts
265
+ import path3 from "path";
266
+
267
+ // src/preprocessor.ts
268
+ import fs2 from "fs/promises";
186
269
  import path2 from "path";
187
- async function parseMD(file, logwarn = console.warn) {
188
- const lines = (await fs2.readFile(file, { encoding: "utf8" })).split("\n");
270
+ async function preprocess(file, variables, checkFilesIsInsidePath, options, signal, logwarn = console.warn) {
271
+ const rootFile = path2.resolve(file);
272
+ const { resolvePath, getRelative } = getSafePathResolver(path2.dirname(rootFile), checkFilesIsInsidePath);
273
+ const ctx = {
274
+ options,
275
+ workdir: path2.dirname(rootFile),
276
+ includes: 0,
277
+ iterations: 0,
278
+ inputLength: 0,
279
+ generatedLength: 0,
280
+ signal: signal || void 0,
281
+ logwarn,
282
+ countAsOutput(v) {
283
+ ctx.generatedLength += v.length;
284
+ if (options.maxGeneratedLength !== void 0 && ctx.generatedLength > options.maxGeneratedLength)
285
+ throw new UserInputError(`Maximum output length exceeded (${options.maxGeneratedLength})`);
286
+ return v;
287
+ }
288
+ };
289
+ const rootNode = { type: "include", path: rootFile, dict: {}, trimEnd: false, trimStart: false };
290
+ return processInclude(rootNode, variables, resolvePath, getRelative, [], ctx);
291
+ }
292
+ async function processInclude(node, variables, resolvePath, getRelative, stack, ctx) {
293
+ ctx.signal?.throwIfAborted();
294
+ const fpath = resolvePath(node.path);
295
+ const relativePath = getRelative(fpath);
296
+ const dir = path2.dirname(fpath);
297
+ if (ctx.options.maxIncludeDepth !== void 0 && stack.length > ctx.options.maxIncludeDepth)
298
+ throw new UserInputError(`Maximum include depth exceeded while including "${relativePath}":
299
+ ${stack.join(" -> ")}`);
300
+ if (stack.length > 0) {
301
+ ctx.includes++;
302
+ const maxIncludes = ctx.options.maxIncludes === void 0 ? 1e3 : ctx.options.maxIncludes;
303
+ if (ctx.includes > maxIncludes)
304
+ throw new UserInputError(`Maximum include count exceeded (${maxIncludes})`);
305
+ }
306
+ let rawContent;
307
+ try {
308
+ if (ctx.options.maxFileLength !== void 0) {
309
+ const size = (await fs2.stat(fpath)).size;
310
+ if (size > ctx.options.maxFileLength * 3)
311
+ throw new UserInputError(`File is too large (${size} > ${ctx.options.maxFileLength * 3}) "${relativePath}"`);
312
+ }
313
+ rawContent = await fs2.readFile(fpath, { encoding: "utf8", signal: ctx.signal });
314
+ if (ctx.options.maxFileLength !== void 0 && rawContent.length > ctx.options.maxFileLength)
315
+ throw new UserInputError(`File is too large (${rawContent.length} > ${ctx.options.maxFileLength}) "${relativePath}"`);
316
+ ctx.inputLength += rawContent.length;
317
+ if (ctx.options.maxTotalInputLength !== void 0 && ctx.inputLength > ctx.options.maxTotalInputLength)
318
+ throw new UserInputError(`Maximum total input length exceeded (${ctx.inputLength} > ${ctx.options.maxTotalInputLength})`);
319
+ } catch (err) {
320
+ ctx.signal?.throwIfAborted();
321
+ if (err instanceof UserInputError) throw err;
322
+ const reason = err instanceof Error ? err.message : String(err);
323
+ throw new UserInputError(`Failed to read included file "${relativePath}": ${reason}`);
324
+ }
325
+ let content = rawContent;
326
+ if (variables) {
327
+ variables = deepOverwrite(variables, node.dict);
328
+ const nodes = parseFExpr(rawContent);
329
+ nodes.forEach((n) => n.type == "expr" && (n.source.fname = relativePath));
330
+ content = processExpressions(nodes, variables || {}, ctx);
331
+ } else {
332
+ variables = {};
333
+ ctx.countAsOutput(content);
334
+ }
335
+ content = await fixLinks(content, fixLink);
336
+ if (!ctx.options.allowIncludes) return content;
337
+ const includes = await parseIncludes(content, ctx.logwarn);
338
+ let result = "";
339
+ for (const node2 of includes) {
340
+ if (node2.type == "text") result += node2.value;
341
+ else {
342
+ let r = await processInclude(node2, variables, resolvePath, getRelative, [...stack, relativePath], ctx);
343
+ if (node2.trimStart) r = r.trimStart();
344
+ if (node2.trimEnd) r = r.trimEnd();
345
+ result += r;
346
+ }
347
+ }
348
+ if (node.sourceLineCount) {
349
+ const delta = result.split("\n").length - node.sourceLineCount + 1;
350
+ result += "\n\uE100" + delta + "\uE101";
351
+ }
352
+ return result;
353
+ function fixLink(oldpath) {
354
+ oldpath = oldpath.trim();
355
+ oldpath = trimEnd(trimStart(oldpath, "<", '"'), ">", '"');
356
+ const newpath = !isLocalFilePath(oldpath) ? oldpath : path2.relative(ctx.workdir, resolvePath(oldpath, dir)).split(path2.sep).join("/");
357
+ if (newpath.includes(" ")) return `<${newpath}>`;
358
+ return newpath;
359
+ }
360
+ }
361
+ function parseFExpr(content) {
362
+ const nodes = [];
363
+ let textStart = 0;
364
+ let ln = 1;
365
+ function countLines(start, end) {
366
+ for (let i = start; i < end; i++)
367
+ if (content[i] == "\n") ln++;
368
+ }
369
+ function pushText(start, end) {
370
+ if (start >= end) return;
371
+ const value = content.slice(start, end);
372
+ const prev = nodes.at(-1);
373
+ countLines(start, end);
374
+ if (prev?.type == "text")
375
+ prev.value += value;
376
+ else
377
+ nodes.push({ type: "text", value });
378
+ }
379
+ function isEscaped(i) {
380
+ let slashes = 0;
381
+ for (i--; i >= 0 && content[i] == "\\"; i--)
382
+ slashes++;
383
+ return slashes % 2 != 0;
384
+ }
385
+ for (let i = 0; i < content.length; i++) {
386
+ if (content.startsWith("<!--", i)) {
387
+ const end2 = content.indexOf("-->", i + 4);
388
+ if (end2 < 0) break;
389
+ i = end2 + 2;
390
+ continue;
391
+ }
392
+ if (!content.startsWith("{{", i))
393
+ continue;
394
+ if (isEscaped(i)) {
395
+ pushText(textStart, i - 1);
396
+ pushText(i, i + 2);
397
+ textStart = i + 2;
398
+ i++;
399
+ continue;
400
+ }
401
+ const trimLeft = content[i + 2] == "-" && !/\d/.test(content[i + 3] || "");
402
+ const valueStart = i + 2 + (trimLeft ? 1 : 0);
403
+ let end = -1;
404
+ let inString = false;
405
+ let escaped = false;
406
+ for (let j = valueStart; j < content.length - 1; j++) {
407
+ const ch = content[j];
408
+ if (inString) {
409
+ if (escaped) escaped = false;
410
+ else if (ch === "\\") escaped = true;
411
+ else if (ch === '"') inString = false;
412
+ continue;
413
+ }
414
+ if (ch == '"') {
415
+ inString = true;
416
+ continue;
417
+ }
418
+ if (ch === "}" && content[j + 1] === "}") {
419
+ end = j;
420
+ break;
421
+ }
422
+ }
423
+ if (end < 0) continue;
424
+ const trimRight = end > valueStart && content[end - 1] == "-";
425
+ const valueEnd = trimRight ? end - 1 : end;
426
+ const hasQuotes = i > 0 && content[i - 1] == '"' && !isEscaped(i - 1) && content[end + 2] === '"';
427
+ let textEnd = hasQuotes ? i - 1 : i;
428
+ const untrimmedTextEnd = textEnd;
429
+ if (trimLeft)
430
+ while (textEnd > textStart && /\s/.test(content[textEnd - 1])) textEnd--;
431
+ pushText(textStart, textEnd);
432
+ countLines(textEnd, untrimmedTextEnd);
433
+ const lineStart = content.lastIndexOf("\n", i + 2);
434
+ const source = { ln, col: lineStart < 0 ? i + 1 : i - lineStart };
435
+ nodes.push({ type: "expr", value: content.slice(valueStart, valueEnd), hasQuotes, trimLeft, trimRight, source });
436
+ let next = hasQuotes ? end + 3 : end + 2;
437
+ if (trimRight) {
438
+ const untrimmedNext = next;
439
+ while (next < content.length && /\s/.test(content[next])) next++;
440
+ countLines(untrimmedNext, next);
441
+ }
442
+ textStart = next;
443
+ i = next - 1;
444
+ }
445
+ pushText(textStart, content.length);
446
+ return nodes;
447
+ }
448
+ function getByPath(value, path7) {
449
+ for (const key of path7) {
450
+ if (value === null || value === void 0)
451
+ return value;
452
+ const obj = Object(value);
453
+ if (!Object.hasOwn(obj, key))
454
+ return void 0;
455
+ value = obj[key];
456
+ }
457
+ return value;
458
+ }
459
+ function processExpressions(nodes, variables, ctx) {
460
+ const expr = parseExp(nodes, ctx);
461
+ const tree = buildExpTree(expr, ctx);
462
+ const res = execExpTree(tree, variables, ctx);
463
+ return res.join("");
464
+ }
465
+ function parseExp(nodes, ctx) {
466
+ const reVar = String.raw`[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*|\.\d+|\[\d+\])*`;
467
+ const reIdent = String.raw`[A-Za-z_$][A-Za-z0-9_$]*`;
468
+ const reStr = String.raw`"(?:\\.|[^"\\])*"`;
469
+ const reNum = String.raw`-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?`;
470
+ const reVal = String.raw`(?:${reStr}|${reNum}|true|false|null)`;
471
+ const reOCompare = String.raw`(?:==|!=|<|<=|>|>=|\bin\b)`;
472
+ const reOMath = String.raw`(?:\+|-|/|//|%|\*|\*\*)`;
473
+ const reOLogical = String.raw`(?:and|or|&&|\|\||\?\?)`;
474
+ const re_var = new RegExp(String.raw`^\s*((?:${reVar})|${reVal})\s*(?:(${reOMath}|${reOLogical})\s*((?:${reVar})|${reVal}))?\s*$`);
475
+ const re_val = new RegExp(String.raw`^${reVal}$`);
476
+ const re_str = new RegExp(String.raw`^\s*(${reStr})\s*$`);
477
+ const re_else = /^:else\s*$/;
478
+ const re_for = new RegExp(String.raw`^#for\s+(${reIdent})(?:\s*,\s*(${reIdent}))?\s+in\s+(${reVar}|${reNum}|${reStr})\s*$`);
479
+ const re_forEnd = /^\/for\s*$/;
480
+ const re_if = new RegExp(String.raw`^#if\s+((?:${reVar})|${reVal})\s*(?:(${reOCompare}|${reOLogical})\s*((?:${reVar})|${reVal}))?\s*$`);
481
+ const re_ifEnd = /^\/if\s*$/;
482
+ const re_path = /[A-Za-z_$][A-Za-z0-9_$]*|\d+/g;
483
+ return nodes.map((node) => {
484
+ ctx.signal?.throwIfAborted();
485
+ if (node.type == "text") return { type: "text", value: node.value };
486
+ const enode = node;
487
+ const source = node.source;
488
+ const expression = `{{${node.trimLeft ? "-" : ""}${node.value}${node.trimRight ? "-" : ""}}}`;
489
+ const rawValue = node.hasQuotes ? `"${expression}"` : expression;
490
+ function retWarn(msg) {
491
+ ctx.logwarn(msg + pos(enode));
492
+ return { type: "text", value: rawValue };
493
+ }
494
+ const mvar = re_var.exec(node.value);
495
+ if (mvar) {
496
+ const left = parseOperand(mvar[1]);
497
+ if (!left) return retWarn("Unsupported expression: " + rawValue);
498
+ const operator = mvar[2];
499
+ if (!operator) return { type: "var", left, source, hasQuotes: node.hasQuotes };
500
+ const right = parseOperand(mvar[3]);
501
+ if (!right) return retWarn("Unsupported expression: " + rawValue);
502
+ return { type: "var", left, operator, right, source, hasQuotes: node.hasQuotes };
503
+ }
504
+ const mstr = re_str.exec(node.value);
505
+ if (mstr) {
506
+ const str = safeParse(node.value);
507
+ if (str === void 0) return retWarn("Cant parse string: " + node.value);
508
+ return { type: "text", value: node.hasQuotes ? JSON.stringify(str) : `${str}` };
509
+ }
510
+ const mfor = re_for.exec(node.value);
511
+ if (mfor) {
512
+ if (!ctx.options.allowLoops) return retWarn("Loop expressions are not allowed");
513
+ const varValue = mfor[1];
514
+ const varKey = mfor[2];
515
+ const array = parseOperand(mfor[3]);
516
+ if (!array) return retWarn("Unsupported expression: " + rawValue);
517
+ return { type: "for", varValue, varKey, array, source };
518
+ }
519
+ const mif = re_if.exec(node.value);
520
+ if (mif) {
521
+ const left = parseOperand(mif[1]);
522
+ if (!left) return retWarn("Unsupported expression: " + rawValue);
523
+ const operator = mif[2];
524
+ if (!operator) return { type: "if", left, source };
525
+ const right = parseOperand(mif[3]);
526
+ if (!right) return retWarn("Unsupported expression: " + rawValue);
527
+ return { type: "if", left, operator, right, source };
528
+ }
529
+ if (re_else.exec(node.value)) return { type: "else", source };
530
+ if (re_ifEnd.exec(node.value)) return { type: "ifEnd", source };
531
+ if (re_forEnd.exec(node.value)) {
532
+ if (!ctx.options.allowLoops) return retWarn("Loop expressions are not allowed");
533
+ return { type: "forEnd", source };
534
+ }
535
+ return retWarn("Unsupported expression: " + rawValue);
536
+ });
537
+ function pos(node) {
538
+ return ` [Ln ${node.source.ln}, Col ${node.source.col}] (${node.source.fname})`;
539
+ }
540
+ function safeParse(value) {
541
+ try {
542
+ return JSON.parse(value);
543
+ } catch {
544
+ return void 0;
545
+ }
546
+ }
547
+ function parsePath(value) {
548
+ const parts = value?.match(re_path);
549
+ if (!parts?.length) return void 0;
550
+ return parts.map((part) => /^\d+$/.test(part) ? Number(part) : part);
551
+ }
552
+ function parseOperand(value) {
553
+ if (!value) return void 0;
554
+ if (re_val.test(value)) {
555
+ const val = safeParse(value);
556
+ if (val === void 0) return null;
557
+ return { t: "val", val };
558
+ }
559
+ const path7 = parsePath(value);
560
+ if (!path7) return null;
561
+ return { t: "var", path: path7 };
562
+ }
563
+ }
564
+ function buildExpTree(nodes, ctx, depth = 0, source) {
565
+ ctx.signal?.throwIfAborted();
566
+ if (ctx.options.maxDepth !== void 0 && depth >= ctx.options.maxDepth) {
567
+ ctx.logwarn(`Maximum expression nesting depth exceeded (${ctx.options.maxDepth})${source ? pos({ source }) : ""}`);
568
+ return nodes.map((n) => {
569
+ if (n.type == "text" || n.type == "var") return n;
570
+ if (n.type == "else" || n.type == "forEnd" || n.type == "ifEnd") {
571
+ const str = n.type == "else" ? "{{:else}}" : n.type == "ifEnd" ? "{{/if}}" : "{{/for}}";
572
+ return { type: "text", value: str };
573
+ }
574
+ if (n.type == "for") {
575
+ const second = n.varKey ? `, ${n.varKey}` : "";
576
+ return { type: "text", value: `{{#for ${n.varValue}${second} in ${strOperand(n.array)}}}` };
577
+ }
578
+ if (n.type == "if") {
579
+ const right = n.right ? ` ${n.operator} ${strOperand(n.right)}` : "";
580
+ return { type: "text", value: `{{#if ${strOperand(n.left)}${right}}}` };
581
+ }
582
+ n;
583
+ return { type: "text", value: "" };
584
+ });
585
+ }
586
+ const tree = [];
587
+ for (let i = 0; i < nodes.length; i++) {
588
+ const node = nodes[i];
589
+ if (node.type == "text" || node.type == "var") {
590
+ tree.push(node);
591
+ }
592
+ if (node.type == "else" || node.type == "forEnd" || node.type == "ifEnd") {
593
+ const str = node.type == "else" ? "{{:else}}" : node.type == "ifEnd" ? "{{/if}}" : "{{/for}}";
594
+ ctx.logwarn(`Unexpected expression (${str})` + pos(node));
595
+ tree.push({ type: "text", value: str });
596
+ }
597
+ if (node.type == "for" || node.type == "if") {
598
+ const r = parseBlockWithElse(i);
599
+ if (!r) {
600
+ if (node.type == "for") {
601
+ const second = node.varKey ? `, ${node.varKey}` : "";
602
+ tree.push({ type: "text", value: `{{#for ${node.varValue}${second} in ${strOperand(node.array)}}}` });
603
+ } else if (node.type == "if") {
604
+ const right = node.right ? ` ${node.operator} ${strOperand(node.right)}` : "";
605
+ tree.push({ type: "text", value: `{{#if ${strOperand(node.left)}${right}}}` });
606
+ }
607
+ } else {
608
+ const { body, bodyElse, endI } = r;
609
+ i = endI;
610
+ tree.push({ ...node, body, bodyElse });
611
+ }
612
+ }
613
+ }
614
+ return tree;
615
+ function pos(node) {
616
+ return ` [Ln ${node.source.ln}, Col ${node.source.col}] (${node.source.fname})`;
617
+ }
618
+ function strOperand(v) {
619
+ return v.t == "val" ? JSON.stringify(v.val) : v.path.join(".");
620
+ }
621
+ function findBlockEnd(startI, type, skipElse) {
622
+ const stack = [];
623
+ for (let i = startI; i < nodes.length; i++) {
624
+ const node = nodes[i];
625
+ if (node.type == "if") stack.push("if");
626
+ else if (node.type == "for") stack.push("for");
627
+ else if (node.type == "forEnd" && stack.at(-1) == "for") stack.pop();
628
+ else if (node.type == "ifEnd" && stack.at(-1) == "if") stack.pop();
629
+ else if (stack.length == 0) {
630
+ if (node.type == "else" && !skipElse) return i;
631
+ if (node.type == "forEnd" && type == "for") return i;
632
+ if (node.type == "ifEnd" && type == "if") return i;
633
+ }
634
+ }
635
+ return -1;
636
+ }
637
+ function parseBlockWithElse(startI) {
638
+ const node = nodes[startI];
639
+ if (node.type != "for" && node.type != "if") return null;
640
+ const n = node;
641
+ const type = n.type;
642
+ const unclosed = (m) => ctx.logwarn(`Unclosed ${type} ${m}` + pos(n));
643
+ const bodyEnd = findBlockEnd(startI + 1, type, false);
644
+ if (bodyEnd < 0) {
645
+ unclosed("body");
646
+ return null;
647
+ }
648
+ const bodyNodes = nodes.slice(startI + 1, bodyEnd);
649
+ const body = buildExpTree(bodyNodes, ctx, depth + 1, n.source);
650
+ let endI = bodyEnd;
651
+ let bodyElse;
652
+ if (nodes[bodyEnd].type == "else") {
653
+ const bodyElseEnd = findBlockEnd(bodyEnd + 1, type, true);
654
+ if (bodyElseEnd < 0) {
655
+ unclosed("else block");
656
+ return null;
657
+ }
658
+ const bodyElseNodes = nodes.slice(bodyEnd + 1, bodyElseEnd);
659
+ bodyElse = buildExpTree(bodyElseNodes, ctx, depth + 1, n.source);
660
+ endI = bodyElseEnd;
661
+ }
662
+ return { body, bodyElse, endI };
663
+ }
664
+ }
665
+ function execExpTree(tree, variables, ctx) {
666
+ return tree.flatMap((node) => {
667
+ ctx.signal?.throwIfAborted();
668
+ if (node.type == "text") return ctx.countAsOutput(node.value);
669
+ if (node.type == "var") {
670
+ const n = node;
671
+ const value = execOperator(node.left, node.operator, node.right);
672
+ const rawValue = () => {
673
+ const right = n.right ? ` ${n.operator} ${strOperand(n.right)}` : "";
674
+ const value2 = `{{${strOperand(n.left)}${right}}}`;
675
+ return n.hasQuotes ? `"${value2}"` : value2;
676
+ };
677
+ if (value === void 0) ctx.logwarn("Undefined variable: " + rawValue() + pos(node));
678
+ const result = value === void 0 ? rawValue() : typeof value == "object" || node.hasQuotes ? JSON.stringify(value) : `${value}`;
679
+ return ctx.countAsOutput(result);
680
+ }
681
+ if (node.type == "if") {
682
+ const res = execOperator(node.left, node.operator, node.right);
683
+ const result = res && typeof res == "object" ? (Array.isArray(res) ? res : Object.keys(res)).length > 0 : !!res;
684
+ if (result) return execExpTree(node.body, variables, ctx);
685
+ if (node.bodyElse) return execExpTree(node.bodyElse, variables, ctx);
686
+ return "";
687
+ }
688
+ if (node.type == "for") {
689
+ if (!ctx.options.allowLoops) throw new UserInputError("Loop expressions are not allowed");
690
+ const arr = node.array.t == "val" ? node.array.val : getByPath(variables, node.array.path);
691
+ if (!arr || typeof arr == "object" && (Array.isArray(arr) ? arr : Object.keys(arr)).length == 0) {
692
+ if (node.bodyElse) return execExpTree(node.bodyElse, variables, ctx);
693
+ return "";
694
+ }
695
+ const values = (() => {
696
+ const n = node;
697
+ function checkLoopIterations(count, final = true) {
698
+ const max = ctx.options.maxLoopIterations === void 0 ? 1e3 : ctx.options.maxLoopIterations;
699
+ if (count > max)
700
+ throw new UserInputError(`Maximum loop iterations exceeded (${final ? count + " " : ""}> ${max})` + pos(n));
701
+ if (ctx.options.maxTotalIterations !== void 0 && ctx.iterations + count > ctx.options.maxTotalIterations)
702
+ throw new UserInputError(`Maximum total loop iterations exceeded (${ctx.options.maxTotalIterations})`);
703
+ }
704
+ if (typeof arr == "number" && Number.isInteger(arr)) {
705
+ if (arr < 0) {
706
+ ctx.logwarn(`For loop source is a negative integer (${arr})` + pos(node));
707
+ return [];
708
+ }
709
+ checkLoopIterations(arr);
710
+ return Array.from({ length: arr }, (_, i) => ({ k: i, v: i + 1 }));
711
+ }
712
+ if (typeof arr == "object") {
713
+ if (Array.isArray(arr)) {
714
+ checkLoopIterations(arr.length);
715
+ return arr.map((v, i) => ({ k: i, v }));
716
+ }
717
+ const keys = Object.keys(arr);
718
+ checkLoopIterations(keys.length);
719
+ return keys.map((k) => ({ k, v: arr[k] }));
720
+ }
721
+ if (typeof arr == "string") {
722
+ const values2 = [];
723
+ for (const v of arr) {
724
+ values2.push({ k: values2.length, v });
725
+ checkLoopIterations(values2.length, false);
726
+ }
727
+ return values2;
728
+ }
729
+ })();
730
+ if (!values) {
731
+ ctx.logwarn(`For loop source is not object, string or integer (${arr})` + pos(node));
732
+ return "";
733
+ }
734
+ return values.flatMap(({ k, v }) => {
735
+ ctx.signal?.throwIfAborted();
736
+ ctx.iterations++;
737
+ if (ctx.options.maxTotalIterations !== void 0 && ctx.iterations > ctx.options.maxTotalIterations)
738
+ throw new UserInputError(`Maximum total loop iterations exceeded (${ctx.options.maxTotalIterations})`);
739
+ const locals = Object.assign(/* @__PURE__ */ Object.create(null), variables);
740
+ locals[node.varValue] = v;
741
+ if (node.varKey) locals[node.varKey] = k;
742
+ return execExpTree(node.body, locals, ctx);
743
+ });
744
+ }
745
+ node;
746
+ return "";
747
+ });
748
+ function pos(node) {
749
+ return ` [Ln ${node.source.ln}, Col ${node.source.col}] (${node.source.fname})`;
750
+ }
751
+ function strOperand(v) {
752
+ return v.t == "val" ? JSON.stringify(v.val) : v.path.join(".");
753
+ }
754
+ function isEqual(obj1, obj2) {
755
+ if (obj1 === obj2) return true;
756
+ if (obj1 == null || obj2 == null || typeof obj1 !== "object" || typeof obj2 !== "object")
757
+ return obj1 == obj2;
758
+ const keys1 = Object.keys(obj1);
759
+ const keys2 = Object.keys(obj2);
760
+ if (keys1.length !== keys2.length) return false;
761
+ for (const key of keys1)
762
+ if (!Object.hasOwn(obj2, key) || !isEqual(obj1[key], obj2[key]))
763
+ return false;
764
+ return true;
765
+ }
766
+ function execOperator(left, operator, right) {
767
+ const leftV = left.t == "val" ? left.val : getByPath(variables, left.path);
768
+ if (!operator || !right) return leftV;
769
+ const rightV = right.t == "val" ? right.val : getByPath(variables, right.path);
770
+ if (operator == "==") return isEqual(leftV, rightV);
771
+ if (operator == "!=") return !isEqual(leftV, rightV);
772
+ if (operator == "<=") return leftV < rightV || isEqual(leftV, rightV);
773
+ if (operator == ">=") return leftV > rightV || isEqual(leftV, rightV);
774
+ if (operator == "<") return leftV < rightV;
775
+ if (operator == ">") return leftV > rightV;
776
+ if (operator == "in") return !!rightV && typeof rightV == "object" && (Array.isArray(rightV) ? rightV : Object.keys(rightV)).some((v) => isEqual(v, leftV));
777
+ if (operator == "and" || operator == "&&") return leftV && rightV;
778
+ if (operator == "or" || operator == "||") return leftV || rightV;
779
+ if (operator == "??") return leftV ?? rightV;
780
+ if (operator == "+") return leftV + rightV;
781
+ if (operator == "-") return leftV - rightV;
782
+ if (operator == "*") return leftV * rightV;
783
+ if (operator == "**") return Math.pow(leftV, rightV);
784
+ if (operator == "/") return rightV == 0 ? 0 : leftV / rightV;
785
+ if (operator == "//") return Math.floor(rightV == 0 ? 0 : leftV / rightV);
786
+ if (operator == "%") return rightV == 0 ? 0 : leftV % rightV;
787
+ operator;
788
+ return false;
789
+ }
790
+ }
791
+ function parseIncludePath(value) {
792
+ value = value.trim();
793
+ const trimLeft = value.startsWith("-");
794
+ if (trimLeft) value = value.slice(1).trimStart();
795
+ const trimLeftInner = value.startsWith("-");
796
+ if (trimLeftInner) value = value.slice(1).trimStart();
797
+ const trimRight = value.endsWith("-");
798
+ if (trimRight) value = value.slice(0, -1).trimEnd();
799
+ const trimRightInner = value.endsWith("-");
800
+ if (trimRightInner) value = value.slice(0, -1).trimEnd();
801
+ const path7 = trimEnd(trimStart(value, "<", '"'), ">", '"');
802
+ return { path: path7, trimLeft, trimLeftInner, trimRight, trimRightInner };
803
+ }
804
+ function findClosingBrace(content, start) {
805
+ let depth = 0;
806
+ let inString = false;
807
+ let lineComment = false;
808
+ let blockComment = false;
809
+ for (let i = start; i < content.length; i++) {
810
+ const ch = content[i];
811
+ if (lineComment) {
812
+ if (ch == "\n") lineComment = false;
813
+ continue;
814
+ }
815
+ if (blockComment) {
816
+ if (ch == "*" && content[i + 1] == "/") {
817
+ blockComment = false;
818
+ i++;
819
+ }
820
+ continue;
821
+ }
822
+ if (inString) {
823
+ if (ch == "\\") i++;
824
+ else if (ch == '"') inString = false;
825
+ continue;
826
+ }
827
+ if (ch == '"') {
828
+ inString = true;
829
+ continue;
830
+ }
831
+ if (ch == "/" && content[i + 1] == "/") {
832
+ lineComment = true;
833
+ i++;
834
+ continue;
835
+ }
836
+ if (ch == "/" && content[i + 1] == "*") {
837
+ blockComment = true;
838
+ i++;
839
+ continue;
840
+ }
841
+ if (ch == "{") depth++;
842
+ else if (ch == "}" && --depth == 0) return i;
843
+ }
844
+ return -1;
845
+ }
846
+ async function parseIncludes(content, logwarn) {
847
+ const { default: JSONC } = await import("jsonc-simple-parser");
848
+ const re_doc = /^!!\(([^{}]*)\)\s*{(.*)}$/s;
849
+ const re_codeFence = /^( {0,3})(`{3,})([^`]*)$/;
850
+ const nodes = [];
851
+ let textStart = 0;
852
+ function pushText(start, end) {
853
+ if (start >= end) return;
854
+ const value = content.slice(start, end);
855
+ const prev = nodes.at(-1);
856
+ if (prev?.type === "text")
857
+ prev.value += value;
858
+ else
859
+ nodes.push({ type: "text", value });
860
+ }
861
+ let codeFence = 0;
862
+ for (let i = 0; i < content.length; i++) {
863
+ if (i != 0 && content[i - 1] != "\n") continue;
864
+ const lineEnd = content.indexOf("\n", i);
865
+ const line = content.slice(i, lineEnd < 0 ? content.length : lineEnd).replace(/\r$/, "");
866
+ const m_fence = re_codeFence.exec(line);
867
+ if (m_fence) {
868
+ const ticks = m_fence[2].length;
869
+ const suffix = m_fence[3];
870
+ if (codeFence == 0) {
871
+ codeFence = ticks;
872
+ continue;
873
+ }
874
+ if (ticks >= codeFence && suffix.trim() == "") {
875
+ codeFence = 0;
876
+ continue;
877
+ }
878
+ }
879
+ if (codeFence != 0) continue;
880
+ if (content.slice(i, i + 2) !== "!!") continue;
881
+ const m_start = /^!!\([^{}]*\)\s*{/.exec(content.slice(i));
882
+ if (!m_start) continue;
883
+ const end = findClosingBrace(content, i + m_start[0].length - 1);
884
+ if (end < 0) continue;
885
+ const block = content.slice(i, end + 1);
886
+ const m_doc = re_doc.exec(block);
887
+ if (!m_doc) continue;
888
+ const { path: path7, trimLeft, trimLeftInner, trimRight, trimRightInner } = parseIncludePath(m_doc[1]);
889
+ if (!path7.toLowerCase().endsWith(".md")) continue;
890
+ let dict = {};
891
+ try {
892
+ dict = JSONC.parse(`{${m_doc[2]}}`);
893
+ } catch {
894
+ logwarn(`Can't parse include dict: {${m_doc[2].replaceAll("\n", " ")}}`);
895
+ continue;
896
+ }
897
+ let textEnd = i;
898
+ if (trimLeft)
899
+ while (textEnd > textStart && /\s/.test(content[textEnd - 1])) textEnd--;
900
+ pushText(textStart, textEnd);
901
+ nodes.push({ type: "include", path: path7, dict, sourceLineCount: block.split("\n").length, trimStart: trimLeftInner, trimEnd: trimRightInner });
902
+ let next = end + 1;
903
+ if (trimRight)
904
+ while (next < content.length && /\s/.test(content[next])) next++;
905
+ textStart = next;
906
+ i = next - 1;
907
+ }
908
+ pushText(textStart, content.length);
909
+ return nodes;
910
+ }
911
+ async function fixLinks(content, fixLink) {
912
+ const { default: JSONC } = await import("jsonc-simple-parser");
913
+ const re_codeFence = /^( {0,3})(`{3,})([^`]*)$/;
914
+ let result = "";
915
+ let textStart = 0;
916
+ function append(start, end) {
917
+ if (start >= end) return;
918
+ result += content.slice(start, end);
919
+ }
920
+ let codeFence = 0;
921
+ for (let i = 0; i < content.length; i++) {
922
+ if (i != 0 && content[i - 1] != "\n") continue;
923
+ const lineEnd = content.indexOf("\n", i);
924
+ const lineTextEnd = lineEnd < 0 ? content.length : lineEnd - (content[lineEnd - 1] == "\r" ? 1 : 0);
925
+ const line = content.slice(i, lineTextEnd);
926
+ const m_fence = re_codeFence.exec(line);
927
+ if (m_fence) {
928
+ const ticks = m_fence[2].length;
929
+ const suffix = m_fence[3];
930
+ if (codeFence == 0) {
931
+ codeFence = ticks;
932
+ continue;
933
+ }
934
+ if (ticks >= codeFence && suffix.trim() == "") {
935
+ codeFence = 0;
936
+ continue;
937
+ }
938
+ }
939
+ if (codeFence != 0) continue;
940
+ if (content.at(i) != "!") continue;
941
+ const m_img = /^!\[(.*)\]\((.*)\)({(.*)})?$/.exec(line);
942
+ if (m_img) {
943
+ append(textStart, i);
944
+ result += `![${m_img[1]}](${fixLink(m_img[2])})${m_img[3] || ""}`;
945
+ textStart = lineTextEnd;
946
+ i = lineEnd < 0 ? content.length : lineEnd;
947
+ continue;
948
+ }
949
+ const m_start = /^!!\([^{}]*\)\s*{/.exec(content.slice(i));
950
+ if (!m_start) continue;
951
+ const end = findClosingBrace(content, i + m_start[0].length - 1);
952
+ if (end < 0) continue;
953
+ const block = content.slice(i, end + 1);
954
+ const m_doc = /^!!\(([^{}]*)\)(\s*{(.*)})$/s.exec(block);
955
+ if (!m_doc) continue;
956
+ try {
957
+ JSONC.parse(m_doc[2].trim());
958
+ } catch {
959
+ continue;
960
+ }
961
+ const { path: path7, trimLeft, trimLeftInner, trimRight, trimRightInner } = parseIncludePath(m_doc[1]);
962
+ let fixedPath = fixLink(path7);
963
+ if (fixedPath.startsWith("-") || fixedPath.endsWith("-"))
964
+ fixedPath = `"${fixedPath}"`;
965
+ append(textStart, i);
966
+ result += `!!(${trimLeft ? "-" : ""}${trimLeftInner ? "-" : ""}${fixedPath}${trimRightInner ? "-" : ""}${trimRight ? "-" : ""})${m_doc[2]}`;
967
+ textStart = end + 1;
968
+ i = end;
969
+ }
970
+ append(textStart, content.length);
971
+ return result;
972
+ }
973
+
974
+ // src/parser.ts
975
+ async function parseMD(file, variables, templateOptions = {}, checkFilesIsInsidePath = false, signal, logwarn = console.warn) {
976
+ signal?.throwIfAborted();
977
+ const preprocessed = await preprocess(file, variables, checkFilesIsInsidePath, templateOptions, signal, logwarn);
978
+ signal?.throwIfAborted();
979
+ const lines = preprocessed.split("\n");
189
980
  const doc = new Doc();
981
+ const warnAt = (line, message) => logwarn(`Line ${doc.mapSourceLine(line)}: ${message}`);
982
+ (function processLineSkips() {
983
+ for (let i = 0; i < lines.length; i++) {
984
+ const line = lines[i];
985
+ if (line.at(0) != "\uE100") continue;
986
+ const m = /\uE100(-?\d+)\uE101/.exec(line);
987
+ if (!m) {
988
+ lines[i] = line.replaceAll("\uE100", "").replaceAll("\uE101", "");
989
+ continue;
990
+ }
991
+ lines[i] = line.slice(m?.[0].length);
992
+ doc.addLineSkip(i + 1, parseInt(m[1]));
993
+ }
994
+ })();
190
995
  try {
191
- doc.title = toCapitalCase(trimEnd(path2.parse(file).name, ".g"));
192
- const stats = await fs2.stat(file);
996
+ doc.title = toCapitalCase(trimEnd(path3.parse(file).name, ".g"));
997
+ const stats = await fs3.stat(file);
193
998
  doc.ctime = stats.birthtime;
194
999
  } catch {
195
1000
  }
196
1001
  function parseList(text, parts, ordered, level = 0) {
197
1002
  const startIndex = isFinite(parseInt(parts[0])) ? parseInt(parts[0]) : 1;
198
1003
  const mark = ordered ? parts[1] == "." ? "." : ")" : parts[1] == "*" ? "*" : "-";
199
- const node = { type: "list", ordered, mark, startIndex, items: [{ type: "listItem", text }] };
1004
+ const node = { type: "list", ordered, mark, startIndex, sourceLine: lineI, items: [{ type: "listItem", text, sourceLine: lineI }] };
200
1005
  const P = ordered ? "1)" : "*";
201
1006
  function skipEmptyLines() {
202
1007
  let emptyFound = false;
@@ -228,7 +1033,7 @@ async function parseMD(file, logwarn = console.warn) {
228
1033
  if (ln.prefix == "" && last?.type == "listItem")
229
1034
  last.text += "\n" + ln.text;
230
1035
  else if (ln.prefix == P)
231
- node.items.push({ type: "listItem", text: ln.text });
1036
+ node.items.push({ type: "listItem", text: ln.text, sourceLine: lineI + 1 });
232
1037
  else break;
233
1038
  } else if (ln.level > level) {
234
1039
  if (ln.level == level + 1) ln = parseLine(ln.text);
@@ -257,19 +1062,19 @@ async function parseMD(file, logwarn = console.warn) {
257
1062
  if (isFinite(h)) height = h;
258
1063
  }
259
1064
  const src = trimEnd(trimStart(parts[1], "<"), ">");
260
- return { type: "image", text, src, width, height };
1065
+ return { type: "image", text, src, width, height, sourceLine: lineI };
261
1066
  }
262
1067
  function parseCode(text) {
263
1068
  const header = text.split(" ");
264
1069
  const title = header.slice(1).join(" ").trim() || void 0;
265
- const node = { type: "code", lang: header[0], title, code: "" };
266
- text = "";
1070
+ const node = { type: "code", lang: header[0], title, code: "", sourceLine: lineI };
1071
+ const code = [];
267
1072
  while (lineI < lines.length) {
268
1073
  const ln = lines[lineI++];
269
1074
  if (ln.startsWith("```")) break;
270
- text += "\n" + ln;
1075
+ code.push(ln);
271
1076
  }
272
- node.code = text.trim();
1077
+ node.code = code.join("\n");
273
1078
  return node;
274
1079
  }
275
1080
  function parseSection(text) {
@@ -282,9 +1087,78 @@ async function parseMD(file, logwarn = console.warn) {
282
1087
  return {
283
1088
  type: "sectionBreak",
284
1089
  pageStart: unpaged ? -1 : isFinite(num) ? num : null,
285
- orientation: portrait ? "portrait" : landscape ? "landscape" : null
1090
+ orientation: portrait ? "portrait" : landscape ? "landscape" : null,
1091
+ sourceLine: lineI
286
1092
  };
287
1093
  }
1094
+ function parseHeaderFooter(kind, options) {
1095
+ const value = options.trim().toLowerCase();
1096
+ if (value == "none" || kind == "footer" && value == "auto") {
1097
+ setHeaderFooter(kind, { type: value });
1098
+ return;
1099
+ }
1100
+ const align = value == "" ? "left" : /^align=(left|center|right)$/.exec(value)?.[1];
1101
+ if (!align) {
1102
+ warnAt(lineI, `Wrong ${kind} options: "${options}". Expected align=left, align=center, align=right${kind == "footer" ? ", none, or auto" : ", or none"}.`);
1103
+ return;
1104
+ }
1105
+ const end = `!!end${kind}`;
1106
+ const content = [];
1107
+ const initialLineI = lineI;
1108
+ let closed = false;
1109
+ while (lineI < lines.length) {
1110
+ const line = lines[lineI++];
1111
+ if (line.trim().toLowerCase() == end) {
1112
+ closed = true;
1113
+ break;
1114
+ }
1115
+ content.push(line);
1116
+ }
1117
+ if (!closed) {
1118
+ lineI = initialLineI;
1119
+ warnAt(initialLineI, `${kind} block is not closed`);
1120
+ return;
1121
+ }
1122
+ const nodes = [{ type: "text", text: content.join("\n").trim(), sourceLine: initialLineI + 1 }];
1123
+ findTables(nodes, false);
1124
+ const supported = nodes.filter((node) => node.type == "text" || node.type == "table");
1125
+ if (supported.length != 1 || supported[0]?.type == "text" && !supported[0].text) {
1126
+ warnAt(initialLineI, `${kind} must contain text or one table`);
1127
+ return;
1128
+ }
1129
+ setHeaderFooter(kind, { type: "content", align, nodes: supported });
1130
+ function setHeaderFooter(kind2, value2) {
1131
+ let section;
1132
+ for (let i = doc.nodes.length - 1; i >= 0; i--) {
1133
+ if (doc.nodes[i]?.type == "sectionBreak") {
1134
+ section = doc.nodes[i];
1135
+ break;
1136
+ }
1137
+ }
1138
+ if (section?.type == "sectionBreak") section[kind2] = value2;
1139
+ else doc[kind2] = value2;
1140
+ }
1141
+ }
1142
+ function parseAdmonition(parts) {
1143
+ const marker = parts[0];
1144
+ const type = parts[1];
1145
+ const title = parts[2];
1146
+ const attributes = parts[3];
1147
+ const content = [];
1148
+ const close = new RegExp(`^${marker}\\s*$`);
1149
+ const initialLineI = lineI;
1150
+ let closed = false;
1151
+ while (lineI < lines.length) {
1152
+ const line = lines[lineI++];
1153
+ if (close.test(line)) {
1154
+ closed = true;
1155
+ break;
1156
+ }
1157
+ content.push(line);
1158
+ }
1159
+ if (!closed) warnAt(initialLineI, `Admonition "${type}" is not closed`);
1160
+ return { type: "admonition", admonitionType: type, title, text: content.filter((t) => !!t.trim()).join("\n").trim(), attributes, sourceLine: initialLineI };
1161
+ }
288
1162
  class RuleError extends Error {
289
1163
  }
290
1164
  ;
@@ -354,9 +1228,43 @@ async function parseMD(file, logwarn = console.warn) {
354
1228
  "text spacing after": (v) => doc.text.spacing.after = tryParseInt(v),
355
1229
  "img text size": (v) => doc.img.text.size = tryParseInt(v),
356
1230
  "img spacing before": (v) => doc.img.spacing.before = tryParseInt(v),
357
- "img spacing after": (v) => doc.img.spacing.after = tryParseInt(v)
1231
+ "img spacing after": (v) => doc.img.spacing.after = tryParseInt(v),
1232
+ "formula spacing before": (v) => doc.formula.spacing.before = tryParseInt(v),
1233
+ "formula spacing after": (v) => doc.formula.spacing.after = tryParseInt(v)
358
1234
  };
359
1235
  const reRules = [
1236
+ {
1237
+ re: /^(admonition (all|note|info|tip|warning|danger) (spacing (before|after)|padding|indent|background|color|bar_width|icon size|icon|title_color|title))(.*)/,
1238
+ n: (m) => ({ rule: m[1], value: (m.at(-1) || "").trim() }),
1239
+ f(m) {
1240
+ const type = m[2];
1241
+ const property = m[3];
1242
+ const value = (m.at(-1) || "").trim();
1243
+ const valueKeepCase = text.slice(m[1].length).trim();
1244
+ const types = type == "all" ? ["note", "info", "tip", "warning", "danger"] : [type];
1245
+ for (const type2 of types) {
1246
+ const style = doc.admonition[type2];
1247
+ if (property == "spacing before") style.spacing.before = tryParseInt(value);
1248
+ else if (property == "spacing after") style.spacing.after = tryParseInt(value);
1249
+ else if (property == "indent") style.indent = tryParseFloat(value);
1250
+ else if (property == "padding") {
1251
+ const padding = value.split(" ").map(tryParseInt);
1252
+ if (padding.length != 2) throw new RuleError("Two values are required: horizontal vertical");
1253
+ style.padding = { top: padding[1], right: padding[0], bottom: padding[1], left: padding[0] };
1254
+ } else if (property == "background") style.background = tryParseColor(value);
1255
+ else if (property == "color") style.color = tryParseColor(value);
1256
+ else if (property == "bar_width") style.bar_width = tryParseFloat(value);
1257
+ else if (property == "icon size") style.icon_size = tryParseInt(value);
1258
+ else if (property == "icon") {
1259
+ choices(value, "on", "off");
1260
+ style.icon = value == "on";
1261
+ } else if (property == "title_color") {
1262
+ choices(value, "on", "off");
1263
+ style.title_color = value == "on";
1264
+ } else if (property == "title") style.title = valueKeepCase;
1265
+ }
1266
+ }
1267
+ },
360
1268
  {
361
1269
  re: /^(headings (h[1-6])(\+?) (size|spacing (before|after)|uppercase|indent))(.*)/,
362
1270
  n: (m) => ({ rule: m[1], value: (m.at(-1) || "").trim() }),
@@ -394,11 +1302,11 @@ async function parseMD(file, logwarn = console.warn) {
394
1302
  rule = n.rule;
395
1303
  value = n.value;
396
1304
  }
397
- if (e instanceof RuleError) logwarn(`Rule "${rule}" wrong value: "${value}". ${e.message}`);
1305
+ if (e instanceof RuleError) warnAt(lineI, `Rule "${rule}" wrong value: "${value}". ${e.message}`);
398
1306
  else throw e;
399
1307
  }
400
1308
  } else {
401
- logwarn(`Wrong rule: "${text}"`);
1309
+ warnAt(lineI, `Wrong rule: "${text}"`);
402
1310
  }
403
1311
  function tryParseInt(v) {
404
1312
  const num = parseInt(v);
@@ -415,6 +1323,11 @@ async function parseMD(file, logwarn = console.warn) {
415
1323
  if (!isFinite(date.valueOf())) throw new RuleError("Only ISO 8601 date is allowed");
416
1324
  return date;
417
1325
  }
1326
+ function tryParseColor(v) {
1327
+ const color = /^#?([\da-f]{6})$/i.exec(v)?.[1];
1328
+ if (!color) throw new RuleError("Only #RRGGBB colors are allowed");
1329
+ return color;
1330
+ }
418
1331
  function choices(v, ...vars) {
419
1332
  if (vars.includes(v)) return v;
420
1333
  throw new RuleError("Allowed: " + vars.join(", "));
@@ -428,66 +1341,85 @@ async function parseMD(file, logwarn = console.warn) {
428
1341
  while (lineI < lines.length) {
429
1342
  const line = lines[lineI++];
430
1343
  const { prefix, text, parts } = parseLine(line);
431
- switch (prefix) {
432
- case "#":
433
- doc.appendTitle(text, 1);
434
- break;
435
- case "##":
436
- doc.appendTitle(text, 2);
437
- break;
438
- case "###":
439
- doc.appendTitle(text, 3);
440
- break;
441
- case "####":
442
- doc.appendTitle(text, 4);
443
- break;
444
- case "#####":
445
- doc.appendTitle(text, 5);
446
- break;
447
- case "######":
448
- doc.appendTitle(text, 6);
449
- break;
450
- case "*":
451
- doc.appendNode(parseList(text, parts, false));
452
- break;
453
- case "1)":
454
- doc.appendNode(parseList(text, parts, true));
455
- break;
456
- case "Img":
457
- doc.appendNode(parseImg(parts));
458
- break;
459
- case "Code":
460
- doc.appendNode(parseCode(text));
461
- break;
462
- case "Comment":
463
- skipComment();
464
- break;
465
- case "!!section":
466
- doc.appendNode(parseSection(text));
467
- break;
468
- case "!!rule":
469
- apllyRule(text);
470
- break;
471
- case "---":
472
- doc.appendNode({ type: "pageBreak" });
473
- break;
474
- case "":
475
- case " ":
476
- const last = doc.nodes.at(-1);
477
- if (line.trim() != "" && last?.type == "text") {
478
- if (last.text != "") last.text += "\n";
479
- last.text += line.trim();
480
- } else doc.appendText(line.trim());
481
- break;
482
- default:
483
- prefix;
484
- throw new Error("switch default");
1344
+ try {
1345
+ switch (prefix) {
1346
+ case "#":
1347
+ doc.appendTitle(text, 1, lineI);
1348
+ break;
1349
+ case "##":
1350
+ doc.appendTitle(text, 2, lineI);
1351
+ break;
1352
+ case "###":
1353
+ doc.appendTitle(text, 3, lineI);
1354
+ break;
1355
+ case "####":
1356
+ doc.appendTitle(text, 4, lineI);
1357
+ break;
1358
+ case "#####":
1359
+ doc.appendTitle(text, 5, lineI);
1360
+ break;
1361
+ case "######":
1362
+ doc.appendTitle(text, 6, lineI);
1363
+ break;
1364
+ case "*":
1365
+ doc.appendNode(parseList(text, parts, false));
1366
+ break;
1367
+ case "1)":
1368
+ doc.appendNode(parseList(text, parts, true));
1369
+ break;
1370
+ case "Img":
1371
+ doc.appendNode(parseImg(parts));
1372
+ break;
1373
+ case "Code":
1374
+ doc.appendNode(parseCode(text));
1375
+ break;
1376
+ case "Admonition":
1377
+ doc.appendNode(parseAdmonition(parts));
1378
+ break;
1379
+ case "Comment":
1380
+ skipComment();
1381
+ break;
1382
+ case "!!section":
1383
+ doc.appendNode(parseSection(text));
1384
+ break;
1385
+ case "!!header":
1386
+ parseHeaderFooter("header", text);
1387
+ break;
1388
+ case "!!footer":
1389
+ parseHeaderFooter("footer", text);
1390
+ break;
1391
+ case "!!rule":
1392
+ apllyRule(text);
1393
+ break;
1394
+ case "---":
1395
+ doc.appendNode({ type: "pageBreak", sourceLine: lineI });
1396
+ break;
1397
+ case "":
1398
+ case " ":
1399
+ const last = doc.nodes.at(-1);
1400
+ if (line.trim() != "" && last?.type == "text") {
1401
+ if (last.text == "") last.sourceLine = lineI;
1402
+ else last.text += "\n";
1403
+ last.text += line.trim();
1404
+ } else doc.appendText(line.trim(), lineI);
1405
+ break;
1406
+ default:
1407
+ prefix;
1408
+ throw new Error("switch default");
1409
+ }
1410
+ } catch (error) {
1411
+ const message = error instanceof Error ? error.message : String(error);
1412
+ throw new Error(`Line ${lineI}: ${message}`, { cause: error });
485
1413
  }
486
1414
  }
1415
+ doc.nodes.forEach((node) => {
1416
+ if (node.type == "admonition" && node.title == "\n")
1417
+ node.title = doc.admonition[node.admonitionType].title;
1418
+ });
487
1419
  doc.nodes = doc.nodes.filter((n) => n.type != "text" || n.text != "");
488
- findDocs(doc.nodes, logwarn);
1420
+ await findDocs(doc.nodes, warnAt);
1421
+ findMath(doc.nodes, warnAt);
489
1422
  findTables(doc.nodes);
490
- findPageBreaks(doc.nodes);
491
1423
  return doc;
492
1424
  }
493
1425
  function parseLine(line) {
@@ -504,7 +1436,7 @@ function parseLine(line) {
504
1436
  const text = splited.slice(1).join(" ");
505
1437
  let parts = [];
506
1438
  if (prefix.startsWith("!")) {
507
- const m_img = /!\[(.*)\]\((.*)\)({(.*)})?/.exec(line);
1439
+ const m_img = /^!\[(.*)\]\((.*)\)({(.*)})?/.exec(line);
508
1440
  if (m_img) return {
509
1441
  prefix: "Img",
510
1442
  text: line.trim(),
@@ -524,29 +1456,37 @@ function parseLine(line) {
524
1456
  }
525
1457
  }
526
1458
  if (prefix.startsWith("```")) return { prefix: "Code", text: line.trim().slice(3), level, parts };
1459
+ const m_admonition = /^(:{3,})(note|info|tip|warning|danger)(?:\[([^\]]*)\])?(?:\{([^}]*)\})?\s*$/i.exec(line.trim());
1460
+ if (m_admonition) return {
1461
+ prefix: "Admonition",
1462
+ text: "",
1463
+ level,
1464
+ parts: [m_admonition[1], m_admonition[2].toLowerCase(), m_admonition[3] ?? "\n", m_admonition[4] || ""]
1465
+ };
527
1466
  if (prefix.startsWith("<!--")) return { prefix: "Comment", text: line.trim().slice("<!--".length).trim(), level, parts };
528
- if (["#", "##", "###", "####", "#####", "######", "*", "1)", "!!section", "!!rule"].includes(prefix))
1467
+ if (["#", "##", "###", "####", "#####", "######", "*", "1)", "!!section", "!!header", "!!footer", "!!rule"].includes(prefix))
529
1468
  return { prefix, text, level: 0, parts };
530
1469
  return { prefix: "", text: line.trim(), level, parts };
531
1470
  }
532
- function findDocs(nodes, logwarn = console.warn) {
1471
+ async function findDocs(nodes, warnAt) {
1472
+ const { default: JSONC } = await import("jsonc-simple-parser");
533
1473
  const re_doc = /^!!\(([^{}]*)\)\s*{(.*)}$/s;
534
- const re_remTrailingComma = /,(\s*[}\]])/g;
535
1474
  for (let i = 0; i < nodes.length; i++) {
536
1475
  const node = nodes[i];
537
1476
  if (node.type != "text") continue;
538
- const m_doc = re_doc.exec(node.text.trimEnd().replaceAll(re_remTrailingComma, "$1"));
1477
+ const m_doc = re_doc.exec(node.text.trimEnd());
539
1478
  if (!m_doc) continue;
540
1479
  let dict = {};
541
1480
  try {
542
- dict = JSON.parse(`{${m_doc[2]}}`);
1481
+ dict = JSONC.parse(`{${m_doc[2]}}`);
543
1482
  } catch {
544
- logwarn(`Cant parse doc dict: {${m_doc[2].replaceAll("\n", " ")}}`);
1483
+ warnAt(node.sourceLine ?? -1, `Cant parse doc dict: {${m_doc[2].replaceAll("\n", " ")}}`);
545
1484
  }
546
1485
  nodes.splice(i, 1, {
547
1486
  type: "externalDoc",
548
1487
  path: trimEnd(trimStart(m_doc[1], "<", '"'), ">", '"'),
549
- dict: stringifyDict(dict)
1488
+ dict: stringifyDict(dict),
1489
+ sourceLine: node.sourceLine
550
1490
  });
551
1491
  }
552
1492
  }
@@ -570,7 +1510,7 @@ function stringifyDict(dict) {
570
1510
  }
571
1511
  return r;
572
1512
  }
573
- function findTables(nodes) {
1513
+ function findTables(nodes, hasHeader = true) {
574
1514
  const re_sep = /^(\s*:?-+:?\s*(?<!\\)\|)+\s*:?-+:?\s*$/;
575
1515
  const re_sep_oneCol = /^\|\s*:?-+:?\s*\|$/;
576
1516
  const trim = (line) => {
@@ -585,19 +1525,20 @@ function findTables(nodes) {
585
1525
  const lines = node.text.split("\n");
586
1526
  const offset = lines[0].includes("|") ? 0 : 1;
587
1527
  if (lines.length < 2 + offset) continue;
588
- const sep = trim(lines[1 + offset]);
589
- if (!re_sep.test(sep) && !re_sep_oneCol.test(lines[1 + offset])) continue;
590
- const cols = sep.split("|");
1528
+ const sep2 = trim(lines[1 + offset]);
1529
+ if (!re_sep.test(sep2) && !re_sep_oneCol.test(lines[1 + offset])) continue;
1530
+ const cols = sep2.split("|");
591
1531
  const header = trim(lines[offset]).split(/(?<!\\)\|/).map((v) => v.trim());
592
1532
  if (header.length != cols.length) continue;
593
1533
  const align = cols.map((v) => v.trim()).map((v) => v.startsWith(":") && v.endsWith(":") ? "c" : v.endsWith(":") ? "r" : "l");
594
- const rows = [tableRow(...header)];
1534
+ let sourceLine = node.sourceLine + offset;
1535
+ const rows = [tableRow(sourceLine++, ...header)];
595
1536
  for (const line of lines.slice(2 + offset)) {
596
1537
  const row = trim(line).split(/(?<!\\)\|/).map((v) => v.trim());
597
1538
  while (row.length < cols.length) row.push("");
598
- rows.push(tableRow(...row));
1539
+ rows.push(tableRow(++sourceLine, ...row));
599
1540
  }
600
- const table = { type: "table", align, rows };
1541
+ const table = { type: "table", align, rows, sourceLine: node.sourceLine, ...hasHeader ? {} : { header: false } };
601
1542
  nodes.splice(i, 1, table);
602
1543
  const prev = nodes[i - 1];
603
1544
  if (offset > 0) {
@@ -609,43 +1550,133 @@ function findTables(nodes) {
609
1550
  }
610
1551
  }
611
1552
  }
612
- function findPageBreaks(nodes) {
613
- const re_sep = /^\s*---+\s*$/;
1553
+ function findMath(nodes, warnAt) {
614
1554
  for (let i = 0; i < nodes.length; i++) {
615
1555
  const node = nodes[i];
616
- if (node.type != "text") continue;
617
- if (!node.text.includes("---")) continue;
1556
+ if (node?.type != "text") continue;
618
1557
  const lines = node.text.split("\n");
619
- const newNodes = [];
620
- for (const line of lines) {
621
- if (re_sep.test(line)) newNodes.push({ type: "pageBreak" });
622
- else {
623
- const last = newNodes.at(-1);
624
- if (last?.type == "text") last.text += "\n" + line;
625
- else newNodes.push({ type: "text", text: line, noIndent: node.noIndent, noMargin: node.noMargin });
1558
+ const replacement = [];
1559
+ let text = [];
1560
+ let textStart = 0;
1561
+ const flush = () => {
1562
+ if (text.length)
1563
+ replacement.push({ type: "text", text: text.join("\n"), sourceLine: (node.sourceLine ?? 0) + textStart });
1564
+ text = [];
1565
+ };
1566
+ for (let line = 0; line < lines.length; line++) {
1567
+ if (lines[line]?.trim() != "$$") {
1568
+ if (!text.length) textStart = line;
1569
+ text.push(lines[line]);
1570
+ continue;
626
1571
  }
1572
+ const start = line;
1573
+ let end = line + 1;
1574
+ while (end < lines.length && lines[end]?.trim() != "$$") end++;
1575
+ if (end >= lines.length) {
1576
+ warnAt((node.sourceLine ?? 0) + start, "Formula is not closed");
1577
+ if (!text.length) textStart = line;
1578
+ text.push(lines[line]);
1579
+ continue;
1580
+ }
1581
+ let id;
1582
+ let manualNumber;
1583
+ const idMatch = /^\s*(\[[a-zA-Zа-яА-ЯёЁ_\d#]+\])\s*$/.exec(text.at(-1) || "");
1584
+ const numberMatch = /^\s*(\(.*\))\s*$/.exec(text.at(-1) || "");
1585
+ if (idMatch) {
1586
+ id = `(${idMatch[1]})`;
1587
+ text.pop();
1588
+ } else if (numberMatch) {
1589
+ manualNumber = numberMatch[1];
1590
+ text.pop();
1591
+ }
1592
+ flush();
1593
+ replacement.push({
1594
+ type: "math",
1595
+ latex: lines.slice(start + 1, end).join("\n").trim(),
1596
+ title: id ?? manualNumber,
1597
+ sourceLine: (node.sourceLine ?? 0) + start
1598
+ });
1599
+ line = end;
1600
+ textStart = end + 1;
1601
+ }
1602
+ flush();
1603
+ if (replacement.some((n) => n.type == "math")) {
1604
+ nodes.splice(i, 1, ...replacement);
1605
+ i += replacement.length - 1;
627
1606
  }
628
- nodes.splice(i, 1, ...newNodes);
629
- i += newNodes.length - 1;
630
1607
  }
631
1608
  }
632
1609
  function runifyDoc(doc) {
633
1610
  function runifyNode(node) {
634
- if ("text" in node && node.text)
1611
+ if ("text" in node && node.text !== void 0)
635
1612
  node.text = runifyText(node.text, doc.rainbow);
636
- if ("title" in node && node.title)
1613
+ if ("title" in node && node.title !== void 0)
637
1614
  node.title = runifyText(node.title, doc.rainbow);
638
1615
  if (node.type == "table")
639
1616
  node.rows.forEach((row) => row.forEach(runifyNode));
640
1617
  if (node.type == "list")
641
1618
  node.items.forEach(runifyNode);
1619
+ if (node.type == "sectionBreak") {
1620
+ runifyHeaderFooter(node.header);
1621
+ runifyHeaderFooter(node.footer);
1622
+ }
642
1623
  }
643
1624
  doc.nodes.forEach(runifyNode);
1625
+ runifyHeaderFooter(doc.header);
1626
+ runifyHeaderFooter(doc.footer);
644
1627
  return doc;
1628
+ function runifyHeaderFooter(value) {
1629
+ if (value?.type != "content") return;
1630
+ value.nodes.forEach(runifyNode);
1631
+ }
645
1632
  }
646
1633
  var rainbowI = 0;
647
1634
  function runifyText(text, rainbow = false) {
648
- return replaceAmpCodes(text).replaceAll("\n", "&Tab;\n").replaceAll(/\s*<br>\s*/g, "\n").replaceAll("\u2014", "-").replaceAll(" - ", " \u2013 ").replaceAll(/(?<=[\p{L}\p{N}])-(?=[\p{L}\p{N}])/gu, "\u2011").replaceAll(/(^|[^\p{L}\d_])"([\p{L}\d_])/gu, "$1\xAB$2").replaceAll(/([\p{L}\d_])"([^\p{L}\d_]|$)/gu, "$1\xBB$2").replaceAll(/(^|\s)(\*+)($|\s)/g, (sub) => sub.replaceAll("*", "&Star;")).split(/(\[[^\]]*\]\([^)]*\))/g).map((p) => {
1635
+ const formulas = [];
1636
+ const codeSpans = [];
1637
+ function findClosingBackticks(from, count) {
1638
+ for (let i = from; i < text.length; ) {
1639
+ const start = text.indexOf("`", i);
1640
+ if (start < 0) return -1;
1641
+ let end = start + 1;
1642
+ while (text[end] == "`") end++;
1643
+ if (end - start == count) return start;
1644
+ i = end;
1645
+ }
1646
+ return -1;
1647
+ }
1648
+ ;
1649
+ let preprocessedText = "";
1650
+ for (let i = 0; i < text.length; ) {
1651
+ if (text[i] == "`") {
1652
+ let delimiterEnd = i + 1;
1653
+ while (text[delimiterEnd] == "`") delimiterEnd++;
1654
+ const delimiterLength = delimiterEnd - i;
1655
+ const end = findClosingBackticks(delimiterEnd, delimiterLength);
1656
+ if (end >= 0) {
1657
+ codeSpans.push(text.slice(delimiterEnd, end));
1658
+ preprocessedText += `\uE002${codeSpans.length - 1}\uE003`;
1659
+ i = end + delimiterLength;
1660
+ continue;
1661
+ }
1662
+ preprocessedText += text.slice(i, delimiterEnd);
1663
+ i = delimiterEnd;
1664
+ continue;
1665
+ }
1666
+ if (text[i] == "$" && text[i - 1] != "\\" && text[i + 1] != "$") {
1667
+ let end = i + 1;
1668
+ while (end < text.length && text[end] != "\n" && (text[end] != "$" || text[end - 1] == "\\")) end++;
1669
+ if (end < text.length && text[end] == "$") {
1670
+ formulas.push(text.slice(i + 1, end));
1671
+ preprocessedText += `\uE000${formulas.length - 1}\uE001`;
1672
+ i = end + 1;
1673
+ continue;
1674
+ }
1675
+ }
1676
+ preprocessedText += text[i++];
1677
+ }
1678
+ text = preprocessedText.replaceAll("\\$", "$");
1679
+ return replaceAmpCodes(text).replaceAll("\n", "&Tab;\n").replaceAll(/\s*<br>\s*/g, "\n").replaceAll("\u2014", "-").replaceAll(" - ", " \u2013 ").replaceAll(/(^|[^\p{L}\d_])"([\p{L}\d_])/gu, "$1\xAB$2").replaceAll(/([\p{L}\d_])"([^\p{L}\d_]|$)/gu, "$1\xBB$2").replaceAll(/(^|\s)(\*+)($|\s)/g, (sub) => sub.replaceAll("*", "&Star;")).split(/(\[[^\]]*\]\([^)]*\))/g).map((p) => {
649
1680
  const m = /\[([^\]]*)\]\(([^)]*)\)/.exec(p);
650
1681
  if (!m) return { text: p };
651
1682
  return { text: m[1], link: m[2] };
@@ -671,17 +1702,23 @@ function runifyText(text, rainbow = false) {
671
1702
  link: rune.link,
672
1703
  bold: rune.bold,
673
1704
  italic: i % 2 == 1 && i != arr.length - 1 || rune.italic
674
- }))).flat().map((rune) => rune.text.split("```").map((p, i, arr) => ({
675
- ...rune,
676
- text: p,
677
- linebreak: i == 0 && rune.linebreak,
678
- mono: i % 2 == 1 && i != arr.length - 1
679
- }))).flat().map((rune) => rune.mono ? rune : rune.text.split("`").map((p, i, arr) => ({
680
- ...rune,
681
- text: p,
682
- linebreak: i == 0 && rune.linebreak,
683
- mono: i % 2 == 1 && i != arr.length - 1
684
- }))).flat().map((rune) => rune.link ? [rune] : rune.text.split(/(\[!?[a-zA-Zа-яА-ЯёЁ_\d#]+\s*[+-]?\s*\d*\])/g).map((p, i) => {
1705
+ }))).flat().map((rune) => rune.text.split(/(\uE002\d+\uE003)/).map((p, i) => {
1706
+ const m = /^\uE002(\d+)\uE003$/.exec(p);
1707
+ return {
1708
+ ...rune,
1709
+ text: m ? codeSpans[parseInt(m[1])] : p,
1710
+ mono: m ? true : rune.mono,
1711
+ linebreak: i == 0 && rune.linebreak
1712
+ };
1713
+ })).flat().map((rune) => rune.mono ? [rune] : rune.text.split(/(\uE000\d+\uE001)/).map((p, i) => {
1714
+ const m = /^\uE000(\d+)\uE001$/.exec(p);
1715
+ return {
1716
+ ...rune,
1717
+ text: m ? formulas[parseInt(m[1])] : p,
1718
+ type: m ? "math" : rune.type,
1719
+ linebreak: i == 0 && rune.linebreak
1720
+ };
1721
+ })).flat().map((rune) => rune.link || rune.mono ? [rune] : rune.text.split(/(\[!?[a-zA-Zа-яА-ЯёЁ_\d#]+\s*[+-]?\s*\d*\])/g).map((p, i) => {
685
1722
  const m = /\[(!?([a-zA-Zа-яА-ЯёЁ_\d]+|#)(\s*[-+]\s*\d+)?)\]/.exec(p);
686
1723
  const v = m && m[1];
687
1724
  const isVal = v?.at(0) == "!";
@@ -691,12 +1728,14 @@ function runifyText(text, rainbow = false) {
691
1728
  type: v ? isVal ? "val" : "ref" : rune.type,
692
1729
  linebreak: i == 0 && rune.linebreak
693
1730
  };
694
- })).flat().map((rune) => ({
1731
+ })).flat().map((rune, i, arr) => ({
695
1732
  ...rune,
696
1733
  ...!rune.type || rune.type == "text" ? {
697
- text: rune.text.replaceAll("&Star;", "*").replaceAll("&#x200B;", "")
1734
+ text: formatTypography(
1735
+ arr[i + 1]?.type == "ref" || arr[i + 1]?.type == "val" ? rune.text.replace(/[ \t]+$/, NBSP) : rune.text
1736
+ ).replaceAll("&Star;", "*").replaceAll("&#x200B;", "")
698
1737
  } : {}
699
- })).map((rune) => !rainbow || rune.type && rune.type != "text" ? [rune] : rune.text.split("").map((p, i) => ({
1738
+ })).map((rune) => !rainbow || rune.mono || rune.type && rune.type != "text" ? [rune] : rune.text.split("").map((p, i) => ({
700
1739
  ...rune,
701
1740
  text: p,
702
1741
  linebreak: i == 0 && rune.linebreak,
@@ -706,6 +1745,7 @@ function runifyText(text, rainbow = false) {
706
1745
  function replaceAmpCodes(text) {
707
1746
  const codes = {
708
1747
  "&#124;": "|",
1748
+ "&amp;": "&",
709
1749
  "&shy;": "\xAD",
710
1750
  "&laquo;": "\xAB",
711
1751
  "&raquo;": "\xBB",
@@ -777,90 +1817,616 @@ function replaceAmpCodes(text) {
777
1817
  }
778
1818
  return text;
779
1819
  }
1820
+ var NBSP = "\xA0";
1821
+ var NON_BREAKING_HYPHEN = "\u2011";
1822
+ var shortWords = ["\u0430", "\u0432", "\u0432\u043E", "\u0438", "\u043A", "\u043A\u043E", "\u043E", "\u043E\u0431", "\u043E\u0442", "\u0441", "\u0441\u043E", "\u0443", "\u0431\u0435\u0437", "\u0434\u043E", "\u0437\u0430", "\u0438\u0437", "\u043D\u0430", "\u043D\u0435", "\u043F\u043E", "\u043F\u043E\u0434", "\u043F\u0440\u0438", "\u043F\u0440\u043E", "\u0434\u043B\u044F"];
1823
+ var referenceTerms = [
1824
+ "\u0440\u0438\u0441\u0443\u043D\u043E\u043A",
1825
+ "\u0440\u0438\u0441\u0443\u043D\u043A\u0430",
1826
+ "\u0440\u0438\u0441\u0443\u043D\u043A\u0435",
1827
+ "\u0440\u0438\u0441\u0443\u043D\u043A\u0443",
1828
+ "\u0440\u0438\u0441\u0443\u043D\u043A\u043E\u043C",
1829
+ "\u0440\u0438\u0441\u0443\u043D\u043A\u0438",
1830
+ "\u0440\u0438\u0441\u0443\u043D\u043A\u043E\u0432",
1831
+ "\u0440\u0438\u0441\u0443\u043D\u043A\u0430\u0445",
1832
+ "\u0442\u0430\u0431\u043B\u0438\u0446\u0430",
1833
+ "\u0442\u0430\u0431\u043B\u0438\u0446\u044B",
1834
+ "\u0442\u0430\u0431\u043B\u0438\u0446\u0435",
1835
+ "\u0442\u0430\u0431\u043B\u0438\u0446\u0443",
1836
+ "\u0442\u0430\u0431\u043B\u0438\u0446\u0435\u0439",
1837
+ "\u0442\u0430\u0431\u043B\u0438\u0446",
1838
+ "\u0442\u0430\u0431\u043B\u0438\u0446\u0430\u0445",
1839
+ "\u043B\u0438\u0441\u0442\u0438\u043D\u0433",
1840
+ "\u043B\u0438\u0441\u0442\u0438\u043D\u0433\u0430",
1841
+ "\u043B\u0438\u0441\u0442\u0438\u043D\u0433\u0435",
1842
+ "\u043B\u0438\u0441\u0442\u0438\u043D\u0433\u0443",
1843
+ "\u043B\u0438\u0441\u0442\u0438\u043D\u0433\u043E\u043C",
1844
+ "\u043B\u0438\u0441\u0442\u0438\u043D\u0433\u0438",
1845
+ "\u043B\u0438\u0441\u0442\u0438\u043D\u0433\u043E\u0432",
1846
+ "\u043B\u0438\u0441\u0442\u0438\u043D\u0433\u0430\u0445",
1847
+ "\u0444\u043E\u0440\u043C\u0443\u043B\u0430",
1848
+ "\u0444\u043E\u0440\u043C\u0443\u043B\u044B",
1849
+ "\u0444\u043E\u0440\u043C\u0443\u043B\u0435",
1850
+ "\u0444\u043E\u0440\u043C\u0443\u043B\u0443",
1851
+ "\u0444\u043E\u0440\u043C\u0443\u043B\u043E\u0439",
1852
+ "\u0444\u043E\u0440\u043C\u0443\u043B",
1853
+ "\u0444\u043E\u0440\u043C\u0443\u043B\u0430\u0445",
1854
+ "\u0443\u0440\u0430\u0432\u043D\u0435\u043D\u0438\u0435",
1855
+ "\u0443\u0440\u0430\u0432\u043D\u0435\u043D\u0438\u044F",
1856
+ "\u0443\u0440\u0430\u0432\u043D\u0435\u043D\u0438\u0438",
1857
+ "\u0443\u0440\u0430\u0432\u043D\u0435\u043D\u0438\u044E",
1858
+ "\u0443\u0440\u0430\u0432\u043D\u0435\u043D\u0438\u0435\u043C",
1859
+ "\u0443\u0440\u0430\u0432\u043D\u0435\u043D\u0438\u0439",
1860
+ "\u0443\u0440\u0430\u0432\u043D\u0435\u043D\u0438\u044F\u0445",
1861
+ "\u0440\u0430\u0437\u0434\u0435\u043B",
1862
+ "\u0440\u0430\u0437\u0434\u0435\u043B\u0430",
1863
+ "\u0440\u0430\u0437\u0434\u0435\u043B\u0435",
1864
+ "\u0440\u0430\u0437\u0434\u0435\u043B\u0443",
1865
+ "\u0440\u0430\u0437\u0434\u0435\u043B\u043E\u043C",
1866
+ "\u0440\u0430\u0437\u0434\u0435\u043B\u044B",
1867
+ "\u0440\u0430\u0437\u0434\u0435\u043B\u043E\u0432",
1868
+ "\u0440\u0430\u0437\u0434\u0435\u043B\u0430\u0445",
1869
+ "\u0433\u043B\u0430\u0432\u0430",
1870
+ "\u0433\u043B\u0430\u0432\u044B",
1871
+ "\u0433\u043B\u0430\u0432\u0435",
1872
+ "\u0433\u043B\u0430\u0432\u0443",
1873
+ "\u0433\u043B\u0430\u0432\u043E\u0439",
1874
+ "\u0433\u043B\u0430\u0432",
1875
+ "\u0433\u043B\u0430\u0432\u0430\u0445",
1876
+ "\u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435",
1877
+ "\u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F",
1878
+ "\u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0438",
1879
+ "\u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044E",
1880
+ "\u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435\u043C",
1881
+ "\u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0439",
1882
+ "\u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F\u0445",
1883
+ "\u043F\u0440\u0438\u043C\u0435\u0440",
1884
+ "\u043F\u0440\u0438\u043C\u0435\u0440\u0430",
1885
+ "\u043F\u0440\u0438\u043C\u0435\u0440\u0435",
1886
+ "\u043F\u0440\u0438\u043C\u0435\u0440\u0443",
1887
+ "\u043F\u0440\u0438\u043C\u0435\u0440\u043E\u043C",
1888
+ "\u043F\u0440\u0438\u043C\u0435\u0440\u044B",
1889
+ "\u043F\u0440\u0438\u043C\u0435\u0440\u043E\u0432",
1890
+ "\u043F\u0440\u0438\u043C\u0435\u0440\u0430\u0445",
1891
+ "\u0430\u043B\u0433\u043E\u0440\u0438\u0442\u043C",
1892
+ "\u0430\u043B\u0433\u043E\u0440\u0438\u0442\u043C\u0430",
1893
+ "\u0430\u043B\u0433\u043E\u0440\u0438\u0442\u043C\u0435",
1894
+ "\u0430\u043B\u0433\u043E\u0440\u0438\u0442\u043C\u0443",
1895
+ "\u0430\u043B\u0433\u043E\u0440\u0438\u0442\u043C\u043E\u043C",
1896
+ "\u0430\u043B\u0433\u043E\u0440\u0438\u0442\u043C\u044B",
1897
+ "\u0430\u043B\u0433\u043E\u0440\u0438\u0442\u043C\u043E\u0432",
1898
+ "\u0430\u043B\u0433\u043E\u0440\u0438\u0442\u043C\u0430\u0445",
1899
+ "\u0441\u0445\u0435\u043C\u0430",
1900
+ "\u0441\u0445\u0435\u043C\u044B",
1901
+ "\u0441\u0445\u0435\u043C\u0435",
1902
+ "\u0441\u0445\u0435\u043C\u0443",
1903
+ "\u0441\u0445\u0435\u043C\u043E\u0439",
1904
+ "\u0441\u0445\u0435\u043C",
1905
+ "\u0441\u0445\u0435\u043C\u0430\u0445"
1906
+ ];
1907
+ var referenceAbbreviations = ["\u0440\u0438\u0441", "\u0442\u0430\u0431\u043B", "\u043B\u0438\u0441\u0442", "\u0444\u043E\u0440\u043C", "\u0443\u0440", "\u0440\u0430\u0437\u0434", "\u0433\u043B", "\u043F\u0440\u0438\u043B"];
1908
+ var units = ["%", "\xB0C", "\xB0F", "\u043C\u043C", "\u0441\u043C", "\u0434\u043C", "\u043A\u043C", "\u043C\u043A\u043C", "\u043D\u043C", "\u043C", "\u043C\u0433", "\u043A\u0433", "\u0433", "\u043C\u043B", "\u043B", "\u043C\u0441", "\u043C\u043A\u0441", "\u043D\u0441", "\u0441", "\u043C\u0438\u043D", "\u0447", "\u0413\u0446", "\u043A\u0413\u0446", "\u041C\u0413\u0446", "\u0413\u0413\u0446", "\u0412\u0442", "\u043A\u0412\u0442", "\u041C\u0412\u0442", "\u0412", "\u043A\u0412", "\u0410", "\u043C\u0410", "\u041E\u043C", "\u043A\u041E\u043C", "\u041C\u041E\u043C", "\u0411", "\u041A\u0411", "\u041C\u0411", "\u0413\u0411", "\u0422\u0411", "\u0431\u0438\u0442", "\u041A\u0431\u0438\u0442", "\u041C\u0431\u0438\u0442", "\u0413\u0431\u0438\u0442", "\u0431\u0438\u0442/\u0441", "\u041A\u0431\u0438\u0442/\u0441", "\u041C\u0431\u0438\u0442/\u0441", "\u0413\u0431\u0438\u0442/\u0441"];
1909
+ function escapeRegExp(value) {
1910
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1911
+ }
1912
+ function createAlternation(values) {
1913
+ return [...values].sort((a, b) => b.length - a.length).map(escapeRegExp).join("|");
1914
+ }
1915
+ var shortWordsPattern = createAlternation(shortWords);
1916
+ var referenceTermsPattern = createAlternation(referenceTerms);
1917
+ var referenceAbbreviationsPattern = createAlternation(referenceAbbreviations);
1918
+ var unitsPattern = createAlternation(units);
1919
+ var referenceIdentifierPattern = String.raw`(?:\(\s*\d+(?:\.\d+)*\s*\)|\d+(?:\.\d+)*|[А-ЯЁA-Z](?:[^\p{L}]|$))`;
1920
+ function formatTypography(text) {
1921
+ return text.replace(
1922
+ new RegExp(`(^|[^\\p{L}\\p{N}_])(${shortWordsPattern})[ \\t]+(?=\\S)`, "giu"),
1923
+ (_, prefix, word) => `${prefix}${word}${NBSP}`
1924
+ ).replace(
1925
+ new RegExp(`(^|[^\\p{L}\\p{N}_])(${referenceTermsPattern})[ \\t]+(?=${referenceIdentifierPattern})`, "giu"),
1926
+ (_, prefix, term) => `${prefix}${term}${NBSP}`
1927
+ ).replace(
1928
+ new RegExp(`(^|[^\\p{L}\\p{N}_])(${referenceAbbreviationsPattern})\\.[ \\t]+(?=${referenceIdentifierPattern})`, "giu"),
1929
+ (_, prefix, abbreviation) => `${prefix}${abbreviation}.${NBSP}`
1930
+ ).replace(/\b(п|пп|ч|ст|с|стр|абз)\.[ \t]+(?=\d)/giu, `$1.${NBSP}`).replace(/№[ \t]+(?=\d)/gu, `\u2116${NBSP}`).replace(/(§§?)[ \t]+(?=\d)/gu, `$1${NBSP}`).replace(/\b(ГОСТ)[ \t]+(?=Р(?:[ \t]+\d)|\d)/giu, `$1${NBSP}`).replace(
1931
+ /\b(ГОСТ\u00A0Р|ГОСТ Р)[ \t]+(?=\d)/giu,
1932
+ (_, standard) => `${standard.replace(" ", NBSP)}${NBSP}`
1933
+ ).replace(/\b(ISO|IEC|IEEE|RFC|DIN|EN)[ \t]+(?=\d)/g, `$1${NBSP}`).replace(/\b(г|ул|пр|просп|пер|бул|наб|пл|д|корп|стр|кв)\.[ \t]+(?=\S)/giu, `$1.${NBSP}`).replace(/(\d{4})[ \t]+(?=гг?\.)/gu, `$1${NBSP}`).replace(/([А-ЯЁA-Z])\.[ \t]+([А-ЯЁA-Z])\.[ \t]+(?=[А-ЯЁA-Z][\p{L}-]+)/gu, `$1.${NBSP}$2.${NBSP}`).replace(/([А-ЯЁA-Z])\.[ \t]+(?=[А-ЯЁA-Z][\p{L}-]+)/gu, `$1.${NBSP}`).replace(new RegExp(`(\\d)[ \\t]+(?=(?:${unitsPattern})(?=$|[^\\p{L}]))`, "giu"), `$1${NBSP}`).replaceAll(/(?<=[\p{L}\p{N}])-(?=[\p{L}\p{N}])/gu, NON_BREAKING_HYPHEN);
1934
+ }
780
1935
 
781
1936
  // src/serializer.ts
782
- import * as fs3 from "fs";
783
- import { AlignmentType, Document, Footer, convertMillimetersToTwip, Packer, PageBreak, PageNumber, Paragraph, TableOfContents, TextRun, LevelFormat, Table, TableRow, TableCell, ImageRun, ExternalHyperlink, InternalHyperlink, Bookmark, XmlComponent, LineRuleType, PageOrientation } from "docx";
1937
+ import * as fs4 from "fs";
1938
+ import { AlignmentType, BorderStyle, Document, Footer, Header, convertMillimetersToTwip, Packer, PageBreak, PageNumber, Paragraph, TableOfContents, TextRun, LevelFormat, Table, TableRow, TableCell, ImageRun, ExternalHyperlink, InternalHyperlink, Bookmark, XmlComponent as XmlComponent2, LineRuleType, PageOrientation, VerticalAlignTable } from "docx";
784
1939
  import { imageSize } from "image-size";
785
- import path3 from "path";
786
- import Prism from "prismjs";
787
- import "prismjs/components/prism-clike";
788
- import "prismjs/components/prism-markup";
789
- import "prismjs/components/prism-css";
790
- import "prismjs/components/prism-javascript";
791
- import "prismjs/components/prism-typescript";
792
- import "prismjs/components/prism-jsx";
793
- import "prismjs/components/prism-tsx";
794
- import "prismjs/components/prism-json";
795
- import "prismjs/components/prism-bash";
796
- import "prismjs/components/prism-powershell";
797
- import "prismjs/components/prism-python";
798
- import "prismjs/components/prism-java";
799
- import "prismjs/components/prism-c";
800
- import "prismjs/components/prism-cpp";
801
- import "prismjs/components/prism-csharp";
802
- import "prismjs/components/prism-go";
803
- import "prismjs/components/prism-rust";
804
- import "prismjs/components/prism-php";
805
- import "prismjs/components/prism-ruby";
806
- import "prismjs/components/prism-swift";
807
- import "prismjs/components/prism-kotlin";
808
- import "prismjs/components/prism-sql";
809
- import "prismjs/components/prism-yaml";
810
- import "prismjs/components/prism-markdown";
811
- import "prismjs/components/prism-docker";
812
- import "prismjs/components/prism-nginx";
1940
+ import path4 from "path";
813
1941
 
814
- // src/errors.ts
815
- var UserInputError = class extends Error {
816
- constructor(message) {
817
- super(message);
1942
+ // src/math.ts
1943
+ import { XmlComponent } from "docx";
1944
+ var Omml = class extends XmlComponent {
1945
+ constructor(tag, children = [], attributes = {}) {
1946
+ super(tag);
1947
+ if (Object.keys(attributes).length) this.root.push({ _attr: attributes });
1948
+ this.root.push(...children);
1949
+ }
1950
+ };
1951
+ var symbols = {
1952
+ alpha: "\u03B1",
1953
+ beta: "\u03B2",
1954
+ gamma: "\u03B3",
1955
+ delta: "\u03B4",
1956
+ epsilon: "\u03B5",
1957
+ varepsilon: "\u03B5",
1958
+ zeta: "\u03B6",
1959
+ eta: "\u03B7",
1960
+ theta: "\u03B8",
1961
+ vartheta: "\u03D1",
1962
+ iota: "\u03B9",
1963
+ kappa: "\u03BA",
1964
+ lambda: "\u03BB",
1965
+ mu: "\u03BC",
1966
+ nu: "\u03BD",
1967
+ xi: "\u03BE",
1968
+ pi: "\u03C0",
1969
+ rho: "\u03C1",
1970
+ sigma: "\u03C3",
1971
+ tau: "\u03C4",
1972
+ upsilon: "\u03C5",
1973
+ phi: "\u03C6",
1974
+ varphi: "\u03D5",
1975
+ chi: "\u03C7",
1976
+ psi: "\u03C8",
1977
+ omega: "\u03C9",
1978
+ Gamma: "\u0393",
1979
+ Delta: "\u0394",
1980
+ Theta: "\u0398",
1981
+ Lambda: "\u039B",
1982
+ Xi: "\u039E",
1983
+ Pi: "\u03A0",
1984
+ Sigma: "\u03A3",
1985
+ Upsilon: "\u03A5",
1986
+ Phi: "\u03A6",
1987
+ Psi: "\u03A8",
1988
+ Omega: "\u03A9",
1989
+ cdot: "\xB7",
1990
+ times: "\xD7",
1991
+ div: "\xF7",
1992
+ pm: "\xB1",
1993
+ mp: "\u2213",
1994
+ ast: "\u2217",
1995
+ star: "\u22C6",
1996
+ circ: "\u2218",
1997
+ bullet: "\u2219",
1998
+ oplus: "\u2295",
1999
+ ominus: "\u2296",
2000
+ otimes: "\u2297",
2001
+ oslash: "\u2298",
2002
+ odot: "\u2299",
2003
+ setminus: "\u2216",
2004
+ wedge: "\u2227",
2005
+ vee: "\u2228",
2006
+ le: "\u2264",
2007
+ leq: "\u2264",
2008
+ ge: "\u2265",
2009
+ geq: "\u2265",
2010
+ neq: "\u2260",
2011
+ ne: "\u2260",
2012
+ approx: "\u2248",
2013
+ equiv: "\u2261",
2014
+ sim: "\u223C",
2015
+ simeq: "\u2243",
2016
+ cong: "\u2245",
2017
+ propto: "\u221D",
2018
+ ll: "\u226A",
2019
+ gg: "\u226B",
2020
+ infty: "\u221E",
2021
+ partial: "\u2202",
2022
+ nabla: "\u2207",
2023
+ in: "\u2208",
2024
+ notin: "\u2209",
2025
+ ni: "\u220B",
2026
+ subset: "\u2282",
2027
+ subseteq: "\u2286",
2028
+ superset: "\u2283",
2029
+ supseteq: "\u2287",
2030
+ emptyset: "\u2205",
2031
+ varnothing: "\u2205",
2032
+ cup: "\u222A",
2033
+ cap: "\u2229",
2034
+ perp: "\u22A5",
2035
+ parallel: "\u2225",
2036
+ mid: "\u2223",
2037
+ angle: "\u2220",
2038
+ therefore: "\u2234",
2039
+ because: "\u2235",
2040
+ to: "\u2192",
2041
+ rightarrow: "\u2192",
2042
+ leftarrow: "\u2190",
2043
+ leftrightarrow: "\u2194",
2044
+ mapsto: "\u21A6",
2045
+ Rightarrow: "\u21D2",
2046
+ Leftarrow: "\u21D0",
2047
+ Leftrightarrow: "\u21D4",
2048
+ uparrow: "\u2191",
2049
+ downarrow: "\u2193",
2050
+ updownarrow: "\u2195",
2051
+ forall: "\u2200",
2052
+ exists: "\u2203",
2053
+ neg: "\xAC",
2054
+ land: "\u2227",
2055
+ lor: "\u2228",
2056
+ degree: "\xB0",
2057
+ ell: "\u2113",
2058
+ prime: "\u2032",
2059
+ hbar: "\u210F",
2060
+ aleph: "\u2135",
2061
+ Re: "\u211C",
2062
+ Im: "\u2111",
2063
+ ldots: "\u2026",
2064
+ dots: "\u2026",
2065
+ cdots: "\u22EF",
2066
+ vdots: "\u22EE",
2067
+ ddots: "\u22F1"
2068
+ };
2069
+ var operators = { sin: "sin", cos: "cos", tan: "tan", cot: "cot", sec: "sec", csc: "csc", log: "log", ln: "ln", exp: "exp", max: "max", min: "min", gcd: "gcd", det: "det" };
2070
+ function run(text, style) {
2071
+ const value = style == "plain" ? "p" : style == "bold" ? "b" : style == "italic" ? "i" : void 0;
2072
+ return new Omml("m:r", [
2073
+ ...style == "double-struck" || style == "script" ? [new Omml("m:rPr", [new Omml("m:scr", [], { "m:val": style }), new Omml("m:sty", [], { "m:val": "p" })])] : [],
2074
+ ...value ? [new Omml("m:rPr", [new Omml("m:sty", [], { "m:val": value })])] : [],
2075
+ new Omml("m:t", [text], { "xml:space": "preserve" })
2076
+ ]);
2077
+ }
2078
+ function arg(children) {
2079
+ return new Omml("m:e", children);
2080
+ }
2081
+ function delimiter(children, begin, end) {
2082
+ return new Omml("m:d", [
2083
+ new Omml("m:dPr", [new Omml("m:begChr", [], { "m:val": begin }), new Omml("m:endChr", [], { "m:val": end })]),
2084
+ arg(children)
2085
+ ]);
2086
+ }
2087
+ function latexToOmml(latex, para = false) {
2088
+ const parser = new LatexParser(latex);
2089
+ const body = parser.sequence();
2090
+ if (!parser.done()) throw new Error(`unexpected "${parser.peek()}"`);
2091
+ const math = new Omml("m:oMath", body);
2092
+ return para ? new Omml("m:oMathPara", [math]) : math;
2093
+ }
2094
+ var LatexParser = class {
2095
+ constructor(source) {
2096
+ this.source = source;
2097
+ }
2098
+ source;
2099
+ i = 0;
2100
+ done() {
2101
+ return this.i >= this.source.length;
2102
+ }
2103
+ peek() {
2104
+ return this.source[this.i] || "";
2105
+ }
2106
+ take() {
2107
+ return this.source[this.i++] || "";
2108
+ }
2109
+ skipWhitespace() {
2110
+ while (/\s/.test(this.peek())) this.take();
2111
+ }
2112
+ command(name) {
2113
+ return this.source.slice(this.i).startsWith(`\\${name}`) && !/[A-Za-z]/.test(this.source[this.i + name.length + 1] || "");
2114
+ }
2115
+ sequence(stop, style, preserveWhitespace = false, boundary) {
2116
+ const result = [];
2117
+ while (!this.done() && this.peek() != stop && !boundary?.()) {
2118
+ if (/\s/.test(this.peek())) {
2119
+ this.take();
2120
+ if (preserveWhitespace) result.push(run(" ", style));
2121
+ continue;
2122
+ }
2123
+ if (this.command("displaystyle") || this.command("textstyle")) {
2124
+ this.take();
2125
+ while (/[A-Za-z]/.test(this.peek())) this.take();
2126
+ continue;
2127
+ }
2128
+ if (this.source.startsWith("\\!", this.i)) {
2129
+ this.i += 2;
2130
+ continue;
2131
+ }
2132
+ result.push(this.decoratedAtom(style, preserveWhitespace));
2133
+ }
2134
+ if (stop) {
2135
+ if (this.take() != stop) throw new Error(`missing "${stop}"`);
2136
+ }
2137
+ return result;
2138
+ }
2139
+ decoratedAtom(style, preserveWhitespace = false) {
2140
+ let value = this.atom(style, preserveWhitespace);
2141
+ let sub;
2142
+ let sup;
2143
+ while (true) {
2144
+ const beforeSpace = this.i;
2145
+ this.skipWhitespace();
2146
+ if (this.peek() != "_" && this.peek() != "^") {
2147
+ this.i = beforeSpace;
2148
+ break;
2149
+ }
2150
+ const marker = this.take();
2151
+ const script = this.groupOrAtom(style);
2152
+ if (marker == "_" ? sub : sup) throw new Error("duplicate script");
2153
+ if (marker == "_") sub = script;
2154
+ else sup = script;
2155
+ }
2156
+ if (sub && sup) value = new Omml("m:sSubSup", [new Omml("m:e", [value]), new Omml("m:sub", sub), new Omml("m:sup", sup)]);
2157
+ else if (sub) value = new Omml("m:sSub", [new Omml("m:e", [value]), new Omml("m:sub", sub)]);
2158
+ else if (sup) value = new Omml("m:sSup", [new Omml("m:e", [value]), new Omml("m:sup", sup)]);
2159
+ return value;
2160
+ }
2161
+ groupOrAtom(style, preserveWhitespace = false) {
2162
+ this.skipWhitespace();
2163
+ if (this.done() || ["}", "_", "^", "&"].includes(this.peek())) throw new Error("argument expected");
2164
+ if (this.peek() == "{") {
2165
+ this.take();
2166
+ return this.sequence("}", style, preserveWhitespace);
2167
+ }
2168
+ return [this.atom(style)];
2169
+ }
2170
+ atom(style, preserveWhitespace = false) {
2171
+ const ch = this.take();
2172
+ if (!ch || ["}", "_", "^", "&"].includes(ch)) throw new Error(`unexpected "${ch}"`);
2173
+ if (ch == "{") return new Omml("m:box", [arg(this.sequence("}", style, preserveWhitespace))]);
2174
+ if (ch != "\\") return run(ch, style);
2175
+ let name = "";
2176
+ while (/[A-Za-z]/.test(this.peek())) name += this.take();
2177
+ if (!name) {
2178
+ const escaped = this.take();
2179
+ if (!escaped) throw new Error("command expected");
2180
+ const spaces = { ",": "\u2009", ";": "\u2004", ":": "\u2005", " ": " " };
2181
+ if (spaces[escaped]) return run(spaces[escaped], style);
2182
+ if (escaped == "!") return run("", style);
2183
+ return run(escaped, style);
2184
+ }
2185
+ this.skipWhitespace();
2186
+ if (name == "frac" || name == "dfrac" || name == "tfrac") return new Omml("m:f", [new Omml("m:num", this.groupOrAtom(style)), new Omml("m:den", this.groupOrAtom(style))]);
2187
+ if (name == "sqrt") {
2188
+ let degree;
2189
+ if (this.peek() == "[") {
2190
+ this.take();
2191
+ degree = this.sequence("]", style);
2192
+ }
2193
+ return new Omml("m:rad", [new Omml("m:radPr", degree ? [] : [new Omml("m:degHide", [], { "m:val": "1" })]), new Omml("m:deg", degree || []), arg(this.groupOrAtom(style))]);
2194
+ }
2195
+ if (name == "text") return new Omml("m:box", [arg(this.groupOrAtom("plain", true))]);
2196
+ if (name == "mathrm" || name == "operatorname") return new Omml("m:box", [arg(this.groupOrAtom("plain"))]);
2197
+ if (name == "mathbf") return new Omml("m:box", [arg(this.groupOrAtom("bold"))]);
2198
+ if (name == "mathit") return new Omml("m:box", [arg(this.groupOrAtom("italic"))]);
2199
+ if (["mathbb", "mathcal"].includes(name)) return new Omml("m:box", [arg(this.groupOrAtom(name == "mathbb" ? "double-struck" : "script"))]);
2200
+ if (["hat", "widehat", "vec"].includes(name)) {
2201
+ const chr = name == "vec" ? "\u20D7" : "\u0302";
2202
+ return new Omml("m:acc", [new Omml("m:accPr", [new Omml("m:chr", [], { "m:val": chr })]), arg(this.groupOrAtom(style))]);
2203
+ }
2204
+ if (["overline", "bar", "underline"].includes(name)) {
2205
+ const pos = name == "underline" ? "bot" : "top";
2206
+ return new Omml("m:bar", [new Omml("m:barPr", [new Omml("m:pos", [], { "m:val": pos })]), arg(this.groupOrAtom(style))]);
2207
+ }
2208
+ if (name == "binom" || name == "dbinom") return delimiter([new Omml("m:f", [new Omml("m:fPr", [new Omml("m:type", [], { "m:val": "noBar" })]), new Omml("m:num", this.groupOrAtom(style)), new Omml("m:den", this.groupOrAtom(style))])], "(", ")");
2209
+ if (name == "lim") {
2210
+ let below = true;
2211
+ if (this.command("limits") || this.command("nolimits")) {
2212
+ below = this.command("limits");
2213
+ this.i += below ? 7 : 9;
2214
+ this.skipWhitespace();
2215
+ }
2216
+ let sub = [];
2217
+ if (this.peek() == "_") {
2218
+ this.take();
2219
+ sub = this.groupOrAtom(style);
2220
+ }
2221
+ if (!sub.length) return run("lim", "plain");
2222
+ return new Omml(below ? "m:limLow" : "m:sSub", [arg([run("lim", "plain")]), new Omml(below ? "m:lim" : "m:sub", sub)]);
2223
+ }
2224
+ if (["sum", "prod", "int", "iint", "iiint"].includes(name)) {
2225
+ const chr = name == "sum" ? "\u2211" : name == "prod" ? "\u220F" : name == "iint" ? "\u222C" : name == "iiint" ? "\u222D" : "\u222B";
2226
+ let sub = [], sup = [];
2227
+ let limitLocation;
2228
+ while (true) {
2229
+ this.skipWhitespace();
2230
+ if (this.command("limits") || this.command("nolimits")) {
2231
+ const limits = this.command("limits");
2232
+ this.i += limits ? 7 : 9;
2233
+ limitLocation = limits ? "undOvr" : "subSup";
2234
+ continue;
2235
+ }
2236
+ if (this.peek() != "_" && this.peek() != "^") break;
2237
+ const marker = this.take();
2238
+ const script = this.groupOrAtom(style);
2239
+ if (marker == "_" ? sub.length : sup.length) throw new Error("duplicate script");
2240
+ if (marker == "_") sub = script;
2241
+ else sup = script;
2242
+ this.skipWhitespace();
2243
+ }
2244
+ while (/\s/.test(this.peek())) this.take();
2245
+ const operand = this.done() || ["}", "]", ")", "&", "=", "<", ">"].includes(this.peek()) || this.command("right") || this.command("end") || this.source.startsWith("\\\\", this.i) ? [] : [this.decoratedAtom(style)];
2246
+ return new Omml("m:nary", [new Omml("m:naryPr", [
2247
+ new Omml("m:chr", [], { "m:val": chr }),
2248
+ ...limitLocation ? [new Omml("m:limLoc", [], { "m:val": limitLocation })] : [],
2249
+ new Omml("m:subHide", [], { "m:val": sub.length ? "0" : "1" }),
2250
+ new Omml("m:supHide", [], { "m:val": sup.length ? "0" : "1" })
2251
+ ]), new Omml("m:sub", sub), new Omml("m:sup", sup), arg(operand)]);
2252
+ }
2253
+ if (name == "left") {
2254
+ const begin = this.readDelimiter();
2255
+ const body = this.sequence(void 0, style, false, () => this.command("right"));
2256
+ if (!this.command("right")) throw new Error("missing \\right");
2257
+ this.i += 6;
2258
+ return delimiter(body, begin, this.readDelimiter());
2259
+ }
2260
+ if (name == "begin") return this.matrix(style);
2261
+ if (symbols[name]) return run(symbols[name], style);
2262
+ if (operators[name]) return run(operators[name], "plain");
2263
+ if (["quad", "qquad"].includes(name)) return run(name == "quad" ? "\u2003" : "\u2003\u2003", style);
2264
+ throw new Error(`unsupported command \\${name}`);
2265
+ }
2266
+ readDelimiter() {
2267
+ this.skipWhitespace();
2268
+ const ch = this.take();
2269
+ if (ch == ".") return "";
2270
+ if (ch && "()[]|{}".includes(ch)) return ch;
2271
+ if (ch == "\\") {
2272
+ let name = "";
2273
+ while (/[A-Za-z]/.test(this.peek())) name += this.take();
2274
+ if (!name) name = this.take();
2275
+ const delimiters = { "{": "{", "}": "}", "|": "\u2016", lbrace: "{", rbrace: "}", langle: "\u27E8", rangle: "\u27E9", lvert: "|", rvert: "|", vert: "|", lVert: "\u2016", rVert: "\u2016", Vert: "\u2016" };
2276
+ if (delimiters[name]) return delimiters[name];
2277
+ }
2278
+ throw new Error("delimiter expected");
2279
+ }
2280
+ matrix(style) {
2281
+ if (this.peek() != "{") throw new Error("environment name expected");
2282
+ this.take();
2283
+ let env = "";
2284
+ while (!this.done() && this.peek() != "}") env += this.take();
2285
+ this.take();
2286
+ if (!["matrix", "pmatrix", "bmatrix", "cases"].includes(env)) throw new Error(`unsupported environment ${env}`);
2287
+ const rows = [];
2288
+ let cells = [];
2289
+ while (true) {
2290
+ this.skipWhitespace();
2291
+ if (this.done()) throw new Error(`environment ${env} is not closed`);
2292
+ if (this.command("end")) {
2293
+ this.i += 4;
2294
+ this.skipWhitespace();
2295
+ const closing = `{${env}}`;
2296
+ if (!this.source.startsWith(closing, this.i)) throw new Error(`mismatched environment ${env}`);
2297
+ this.i += closing.length;
2298
+ if (cells.length) rows.push(new Omml("m:mr", cells));
2299
+ break;
2300
+ }
2301
+ cells.push(arg(this.sequence(void 0, style, false, () => this.peek() == "&" || this.source.startsWith("\\\\", this.i) || this.command("end"))));
2302
+ if (this.peek() == "&") {
2303
+ this.take();
2304
+ continue;
2305
+ }
2306
+ if (this.source.startsWith("\\\\", this.i)) {
2307
+ this.i += 2;
2308
+ rows.push(new Omml("m:mr", cells));
2309
+ cells = [];
2310
+ }
2311
+ }
2312
+ const matrix = new Omml("m:m", rows);
2313
+ if (env == "pmatrix") return delimiter([matrix], "(", ")");
2314
+ if (env == "bmatrix") return delimiter([matrix], "[", "]");
2315
+ if (env == "cases") return delimiter([matrix], "{", "");
2316
+ return matrix;
818
2317
  }
819
2318
  };
820
2319
 
821
2320
  // src/serializer.ts
2321
+ import Prism from "prismjs";
2322
+ import "prismjs/components/prism-clike.js";
2323
+ import "prismjs/components/prism-markup.js";
2324
+ import "prismjs/components/prism-css.js";
2325
+ import "prismjs/components/prism-javascript.js";
2326
+ import "prismjs/components/prism-typescript.js";
2327
+ import "prismjs/components/prism-jsx.js";
2328
+ import "prismjs/components/prism-tsx.js";
2329
+ import "prismjs/components/prism-json.js";
2330
+ import "prismjs/components/prism-bash.js";
2331
+ import "prismjs/components/prism-powershell.js";
2332
+ import "prismjs/components/prism-python.js";
2333
+ import "prismjs/components/prism-java.js";
2334
+ import "prismjs/components/prism-c.js";
2335
+ import "prismjs/components/prism-cpp.js";
2336
+ import "prismjs/components/prism-csharp.js";
2337
+ import "prismjs/components/prism-go.js";
2338
+ import "prismjs/components/prism-rust.js";
2339
+ import "prismjs/components/prism-php.js";
2340
+ import "prismjs/components/prism-ruby.js";
2341
+ import "prismjs/components/prism-swift.js";
2342
+ import "prismjs/components/prism-kotlin.js";
2343
+ import "prismjs/components/prism-sql.js";
2344
+ import "prismjs/components/prism-yaml.js";
2345
+ import "prismjs/components/prism-markdown.js";
2346
+ import "prismjs/components/prism-docker.js";
2347
+ import "prismjs/components/prism-nginx.js";
822
2348
  var STYLE_list = "afc";
823
2349
  var STYLE_table_title = "TableCaption";
824
2350
  var STYLE_code_title = "ListingCaption";
825
2351
  var STYLE_code = "ListingCode";
826
- async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath) {
827
- const allowedRealPath = checkFilesIsInsidePath === false ? false : realpathAllowMissing(checkFilesIsInsidePath === "" ? workdir : checkFilesIsInsidePath);
828
- const getPath = (fname) => {
829
- if (process.platform != "win32") fname = fname.replaceAll("\\", "/");
830
- if (process.platform == "win32" && fname.startsWith("/")) fname = "." + fname;
831
- const targetPath = path3.isAbsolute(fname) ? path3.resolve(fname) : path3.resolve(workdir, fname);
832
- if (allowedRealPath !== false) {
833
- const targetRealPath = realpathAllowMissing(targetPath);
834
- const relative = path3.relative(allowedRealPath, targetRealPath);
835
- const isOutside = relative === ".." || relative.startsWith(`..${path3.sep}`) || path3.isAbsolute(relative);
836
- if (isOutside) throw new UserInputError(`Access denied: "${fname}" resolves outside the working directory.`);
837
- }
838
- return targetPath;
839
- };
2352
+ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath, logwarn = console.warn) {
2353
+ const { resolvePath: getPath } = getSafePathResolver(workdir, checkFilesIsInsidePath);
840
2354
  const sections = [];
841
2355
  const numbering = [];
842
2356
  const docSections = (function splitSections() {
843
- const sections2 = [{ displayPageNum: true, orientation: "portrait", pageStart: 1, nodes: [] }];
2357
+ let header = doc.header;
2358
+ let footer = doc.footer;
2359
+ const sections2 = [{ displayPageNum: true, orientation: "portrait", pageStart: 1, header, footer, nodes: [] }];
844
2360
  for (let j = 0; j < doc.nodes.length; j++) {
845
2361
  const node = doc.nodes[j];
846
- if (node.type == "sectionBreak")
2362
+ if (node.type == "sectionBreak") {
2363
+ header = node.header ?? header;
2364
+ footer = node.footer ?? footer;
847
2365
  sections2.push({
848
- displayPageNum: node.pageStart == null ? sections2.at(-1)?.displayPageNum || true : node.pageStart >= 0,
2366
+ displayPageNum: node.pageStart == null ? sections2.at(-1)?.displayPageNum ?? true : node.pageStart >= 0,
849
2367
  orientation: node.orientation || sections2.at(-1)?.orientation || "portrait",
850
2368
  pageStart: node.pageStart,
2369
+ header,
2370
+ footer,
851
2371
  nodes: []
852
2372
  });
853
- else
2373
+ } else
854
2374
  sections2.at(-1)?.nodes.push(node);
855
2375
  }
856
2376
  return sections2.filter((s) => s.nodes.length > 0);
857
2377
  })();
858
2378
  for (const section of docSections) {
859
- let renderNode2 = function(node, prevChild, prevNode) {
2379
+ let renderHeaderFooter2 = function(value, isFooter) {
2380
+ if (value.type == "none") return void 0;
2381
+ if (value.type == "auto") {
2382
+ if (!isFooter) return void 0;
2383
+ return new Footer({
2384
+ children: [new Paragraph({
2385
+ alignment: AlignmentType.CENTER,
2386
+ children: [new TextRun({ children: section.displayPageNum ? [PageNumber.CURRENT] : [] })],
2387
+ indent: { firstLine: 0 },
2388
+ spacing: { after: 0, line: 240 }
2389
+ })]
2390
+ });
2391
+ }
2392
+ const contentValue = value;
2393
+ const content = contentValue.nodes.flatMap((node) => {
2394
+ if (node.type == "text") return [new Paragraph({
2395
+ children: renderText(node.text, node.sourceLine),
2396
+ alignment: contentValue.align,
2397
+ indent: { firstLine: 0 },
2398
+ spacing: isFooter ? { after: 0, line: 240 } : { line: 240 }
2399
+ })];
2400
+ return [renderHeaderFooterTable2(node)];
2401
+ });
2402
+ return isFooter ? new Footer({ children: content }) : new Header({ children: content });
2403
+ }, renderHeaderFooterTable2 = function(node) {
2404
+ return new Table({
2405
+ width: { type: "dxa", size: convertMillimetersToTwip(section.orientation == "landscape" ? 255.8 : 168.8) },
2406
+ borders: {
2407
+ top: { style: BorderStyle.SINGLE, size: 4, color: "808080" },
2408
+ bottom: { style: BorderStyle.SINGLE, size: 4, color: "808080" },
2409
+ left: { style: BorderStyle.SINGLE, size: 4, color: "808080" },
2410
+ right: { style: BorderStyle.SINGLE, size: 4, color: "808080" },
2411
+ insideHorizontal: { style: BorderStyle.SINGLE, size: 4, color: "808080" },
2412
+ insideVertical: { style: BorderStyle.SINGLE, size: 4, color: "808080" }
2413
+ },
2414
+ rows: node.rows.map((row) => new TableRow({
2415
+ children: row.map((cell, colI) => new TableCell({
2416
+ children: cell.type == "text" ? [new Paragraph({
2417
+ children: renderText(cell.text, node.sourceLine),
2418
+ alignment: node.align[colI] == "c" ? "center" : node.align[colI] == "r" ? "right" : "left",
2419
+ indent: { firstLine: 0 },
2420
+ spacing: { after: 0, line: 240 * 1.25 }
2421
+ })] : []
2422
+ }))
2423
+ }))
2424
+ });
2425
+ }, renderNode2 = function(node, prevChild, prevNode) {
860
2426
  switch (node.type) {
861
2427
  case "text":
862
2428
  return new Paragraph({
863
- children: renderText(node.text),
2429
+ children: renderText(node.text, node.sourceLine),
864
2430
  indent: node.noIndent ? { firstLine: 0 } : {},
865
2431
  ...node.center ? { alignment: "center", indent: { firstLine: 0 } } : {},
866
2432
  spacing: {
@@ -874,7 +2440,7 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
874
2440
  const styles = doc.headings[`h${level}`];
875
2441
  if (styles.uppercase) node.text.forEach((r) => r.text = r.text.toUpperCase());
876
2442
  return new Paragraph({
877
- children: renderText(node.text, styles.size),
2443
+ children: renderText(node.text, node.sourceLine, styles.size),
878
2444
  style: `${node.level}`,
879
2445
  ...node.center ? { alignment: "center", indent: { firstLine: 0 } } : {},
880
2446
  ...!node.center && node.level != 0 && styles.indent_full ? { indent: { firstLine: 0, left: convertMillimetersToTwip(12.5) } } : {},
@@ -898,7 +2464,7 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
898
2464
  if (item.type == "list") renderList2({ ...item, alternativeStyle: !!node2.alternativeStyle }, level2 + 1);
899
2465
  else {
900
2466
  items.push(new Paragraph({
901
- children: renderText(item.text),
2467
+ children: renderText(item.text, item.sourceLine ?? node2.sourceLine),
902
2468
  style: STYLE_list,
903
2469
  numbering: { reference: id, level: level2 },
904
2470
  spacing: {
@@ -922,13 +2488,13 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
922
2488
  renderList2(node);
923
2489
  return items;
924
2490
  case "table":
925
- if (node.rows[0] && doc.table.heading.style == "italic") node.rows[0].forEach((n) => n.type == "text" ? n.text.forEach((r) => r.italic = true) : 0);
926
- if (node.rows[0] && doc.table.heading.style == "bold") node.rows[0].forEach((n) => n.type == "text" ? n.text.forEach((r) => r.bold = true) : 0);
2491
+ if (node.header !== false && node.rows[0] && doc.table.heading.style == "italic") node.rows[0].forEach((n) => n.type == "text" ? n.text.forEach((r) => r.italic = true) : 0);
2492
+ if (node.header !== false && node.rows[0] && doc.table.heading.style == "bold") node.rows[0].forEach((n) => n.type == "text" ? n.text.forEach((r) => r.bold = true) : 0);
927
2493
  const spacingInner = Math.max(doc.table.spacing.after ?? doc.text.spacing.after, doc.table.spacing.before ?? 0);
928
2494
  return [
929
2495
  ...node.title ? [
930
2496
  new Paragraph({
931
- children: renderText(node.title, doc.table.title.size),
2497
+ children: renderText(node.title, node.sourceLine, doc.table.title.size),
932
2498
  style: STYLE_table_title,
933
2499
  ...prevChild instanceof Table ? { spacing: { before: spacingInner * 20 } } : doc.table.spacing.before ? { spacing: { before: doc.table.spacing.before * 20 } } : {}
934
2500
  })
@@ -944,13 +2510,13 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
944
2510
  size: convertMillimetersToTwip(section.orientation === "landscape" ? 255.8 : 168.8)
945
2511
  },
946
2512
  rows: node.rows.map((row, rowI) => new TableRow({
947
- tableHeader: rowI == 0,
2513
+ tableHeader: node.header !== false && rowI == 0,
948
2514
  cantSplit: true,
949
2515
  children: row.map((item, colI) => new TableCell({
950
2516
  children: item.type != "text" ? renderNodeL2(item) : [
951
2517
  new Paragraph({
952
- children: renderText(item.text, node.normalFontSize ? void 0 : doc.table.text.size),
953
- alignment: rowI == 0 ? doc.table.heading.align : node.align[colI] == "c" ? "center" : node.align[colI] == "r" ? "right" : "left",
2518
+ children: renderText(item.text, node.sourceLine, node.normalFontSize ? void 0 : doc.table.text.size),
2519
+ alignment: node.header !== false && rowI == 0 ? doc.table.heading.align : node.align[colI] == "c" ? "center" : node.align[colI] == "r" ? "right" : "left",
954
2520
  indent: { firstLine: 0 },
955
2521
  spacing: { after: 0, line: 240 * doc.table.text.line_spacing }
956
2522
  })
@@ -968,13 +2534,13 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
968
2534
  new Paragraph({ children: [new PageBreak()] })
969
2535
  ];
970
2536
  case "image":
971
- const type = node.src.split(".").at(-1) || "";
2537
+ const type = (node.src.split(".").at(-1) || "").toLowerCase();
972
2538
  if (!["jpg", "png", "gif", "bmp", "svg"].includes(type))
973
2539
  throw new UserInputError(`Unsupported image format: "${type}", file: ${node.src}`);
974
2540
  const img_path = getPath(node.src);
975
- if (!fs3.existsSync(img_path))
2541
+ if (!fs4.existsSync(img_path))
976
2542
  throw new UserInputError(`File not exist: ${node.src}`);
977
- const data = fs3.readFileSync(img_path);
2543
+ const data = fs4.readFileSync(img_path);
978
2544
  const dimensions = imageSize(data);
979
2545
  const [MaxW, MaxH] = section.orientation == "landscape" ? [950, 600] : [600, 900];
980
2546
  let [width, height] = [dimensions.width, dimensions.height];
@@ -1006,7 +2572,7 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1006
2572
  }),
1007
2573
  ...node.text ? [
1008
2574
  new Paragraph({
1009
- children: renderText(node.text, doc.img.text.size),
2575
+ children: renderText(node.text, node.sourceLine, doc.img.text.size),
1010
2576
  alignment: "center",
1011
2577
  indent: { firstLine: 0 },
1012
2578
  spacing: {
@@ -1021,7 +2587,7 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1021
2587
  return [
1022
2588
  ...node.title ? [
1023
2589
  new Paragraph({
1024
- children: renderText(node.title, doc.code.title.size),
2590
+ children: renderText(node.title, node.sourceLine, doc.code.title.size),
1025
2591
  style: STYLE_code_title
1026
2592
  })
1027
2593
  ] : prevNode?.type == "code" ? [
@@ -1033,11 +2599,44 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1033
2599
  ...i == 0 && !node.title && doc.code.spacing.before && prevNode?.type != "code" ? { spacing: { before: doc.code.spacing.before * 20 } } : {}
1034
2600
  }))
1035
2601
  ];
2602
+ case "math":
2603
+ const formulaWidth = convertMillimetersToTwip(section.orientation == "landscape" ? 255.8 : 168.8);
2604
+ const number = node.title?.map((rune) => rune.text).join("") || "";
2605
+ const sideWidth = Math.max(
2606
+ convertMillimetersToTwip(5),
2607
+ Math.ceil(estimateNumberWidthPt(number, doc.text.size) * 20 + convertMillimetersToTwip(3))
2608
+ );
2609
+ return [
2610
+ ...prevNode?.type != "math" ? [
2611
+ new Paragraph({ indent: { firstLine: 0 }, spacing: { line: 20, after: doc.formula.spacing.before * 20 } })
2612
+ ] : [],
2613
+ new Table({
2614
+ width: { type: "dxa", size: formulaWidth },
2615
+ borders: { top: { style: BorderStyle.NONE }, bottom: { style: BorderStyle.NONE }, left: { style: BorderStyle.NONE }, right: { style: BorderStyle.NONE }, insideHorizontal: { style: BorderStyle.NONE }, insideVertical: { style: BorderStyle.NONE } },
2616
+ rows: [new TableRow({
2617
+ children: [
2618
+ new TableCell({ width: { type: "dxa", size: sideWidth }, children: [new Paragraph({ indent: { firstLine: 0 }, spacing: { after: 0 } })] }),
2619
+ new TableCell({
2620
+ children: [
2621
+ new Paragraph({ alignment: AlignmentType.CENTER, indent: { firstLine: 0 }, spacing: { after: 0 }, children: [renderMath(node.latex, true, node.sourceLine)] })
2622
+ ]
2623
+ }),
2624
+ new TableCell({ width: { type: "dxa", size: sideWidth }, children: [new Paragraph({
2625
+ indent: { firstLine: 0 },
2626
+ spacing: { line: 240, after: 0 },
2627
+ alignment: AlignmentType.RIGHT,
2628
+ children: renderText(node.title || [], node.sourceLine)
2629
+ })], verticalAlign: VerticalAlignTable.CENTER })
2630
+ ]
2631
+ })]
2632
+ }),
2633
+ new Paragraph({ indent: { firstLine: 0 }, spacing: { line: 20, after: doc.formula.spacing.after * 20 } })
2634
+ ];
1036
2635
  case "externalDoc":
1037
2636
  const doc_path = getPath(node.path);
1038
- if (!fs3.existsSync(doc_path))
2637
+ if (!fs4.existsSync(doc_path))
1039
2638
  throw new UserInputError(`File not exist: ${node.path}`);
1040
- const ext = path3.extname(doc_path);
2639
+ const ext = path4.extname(doc_path);
1041
2640
  if (ext != ".docx" && ext != ".pdf")
1042
2641
  throw new UserInputError(`File not .docx or .pdf: ${node.path}`);
1043
2642
  return new Paragraph({
@@ -1045,6 +2644,39 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1045
2644
  indent: { firstLine: 0 },
1046
2645
  alignment: "left"
1047
2646
  });
2647
+ case "admonition":
2648
+ const style = `xAdmonition${node.admonitionType == "note" ? "" : node.admonitionType}`;
2649
+ const admonition = doc.admonition[node.admonitionType];
2650
+ const color = "#" + admonition.color;
2651
+ const icon = admonition.icon ? [new ImageRun({
2652
+ type: "svg",
2653
+ data: Buffer.from(admonitionIcons[node.admonitionType](admonition.title_color ? color : "#000000")),
2654
+ transformation: { width: admonition.icon_size, height: admonition.icon_size },
2655
+ fallback: {
2656
+ data: Buffer.from((admonition.title_color ? admonitionIconsFallback : admonitionIconsFallbackBlack)[node.admonitionType], "base64"),
2657
+ type: "png"
2658
+ }
2659
+ }), new TextRun(" ")] : [];
2660
+ const prevIsSameType = prevNode?.type == "admonition" && prevNode.admonitionType == node.admonitionType;
2661
+ const showTitle = node.title.map((r) => r.text).join("") != "";
2662
+ return [
2663
+ ...prevIsSameType ? [
2664
+ new Paragraph({ spacing: { line: 20, before: 0, after: 0 } })
2665
+ ] : [],
2666
+ ...showTitle ? [
2667
+ new Paragraph({
2668
+ style,
2669
+ children: [...icon, ...renderText(admonition.title_color ? node.title.map((r) => ({ ...r, color })) : node.title, node.sourceLine)],
2670
+ keepNext: true,
2671
+ ...prevIsSameType ? { spacing: { before: 0 } } : {}
2672
+ })
2673
+ ] : [],
2674
+ new Paragraph({
2675
+ style,
2676
+ children: renderText(node.text, node.sourceLine),
2677
+ ...prevIsSameType && !showTitle ? { spacing: { before: 0 } } : {}
2678
+ })
2679
+ ];
1048
2680
  case "sectionBreak":
1049
2681
  return [];
1050
2682
  default:
@@ -1052,12 +2684,20 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1052
2684
  throw new Error("switch default");
1053
2685
  }
1054
2686
  }, renderNodeL2 = function(node, prevChild, prevNode) {
1055
- const r = renderNode2(node, prevChild, prevNode);
1056
- if (r instanceof Array) return r;
1057
- return [r];
2687
+ try {
2688
+ const r = renderNode2(node, prevChild, prevNode);
2689
+ if (r instanceof Array) return r;
2690
+ return [r];
2691
+ } catch (error) {
2692
+ const message = error instanceof Error ? error.message : String(error);
2693
+ if (/^Line -?\d+:/.test(message)) throw error;
2694
+ throw new Error(`${node.sourceLine ? `Line ${node.sourceLine}: ` : ""}${message}`, { cause: error });
2695
+ }
1058
2696
  };
1059
- var renderNode = renderNode2, renderNodeL = renderNodeL2;
2697
+ var renderHeaderFooter = renderHeaderFooter2, renderHeaderFooterTable = renderHeaderFooterTable2, renderNode = renderNode2, renderNodeL = renderNodeL2;
1060
2698
  const children = [];
2699
+ const header = renderHeaderFooter2(section.header, false);
2700
+ const footer = renderHeaderFooter2(section.footer, true);
1061
2701
  sections.push({
1062
2702
  children,
1063
2703
  properties: {
@@ -1085,20 +2725,8 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1085
2725
  }
1086
2726
  }
1087
2727
  },
1088
- footers: {
1089
- default: new Footer({
1090
- children: [
1091
- new Paragraph({
1092
- alignment: AlignmentType.CENTER,
1093
- children: [new TextRun({
1094
- children: section.displayPageNum ? [PageNumber.CURRENT] : []
1095
- })],
1096
- indent: { firstLine: 0 },
1097
- spacing: { line: 240 }
1098
- })
1099
- ]
1100
- })
1101
- }
2728
+ ...header ? { headers: { default: header } } : {},
2729
+ ...footer ? { footers: { default: footer } } : {}
1102
2730
  });
1103
2731
  for (let i = 0; i < section.nodes.length; i++)
1104
2732
  children.push(...renderNodeL2(section.nodes[i], children.at(-1), section.nodes[i - 1]));
@@ -1133,8 +2761,8 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1133
2761
  })
1134
2762
  }
1135
2763
  ]);
1136
- fs3.writeFileSync(fout, buffer);
1137
- function renderText(text, size) {
2764
+ fs4.writeFileSync(fout, buffer);
2765
+ function renderText(text, sourceLine, size) {
1138
2766
  function renderRune(rune, link = false) {
1139
2767
  if (rune.anchor) return new Bookmark({
1140
2768
  id: rune.anchor,
@@ -1144,8 +2772,12 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1144
2772
  const children2 = [renderRune({ ...rune, link: void 0 }, true)];
1145
2773
  return rune.link.startsWith("#") ? new InternalHyperlink({ children: children2, anchor: rune.link.slice(1) }) : new ExternalHyperlink({ children: children2, link: rune.link });
1146
2774
  }
2775
+ if (rune.type == "math") return renderMath(rune.text, false, sourceLine);
1147
2776
  let children = null;
1148
- if (rune.type == "val" && rune.text == "pages") {
2777
+ if (rune.type == "val" && rune.text == "page") {
2778
+ rune.type = "text";
2779
+ children = [PageNumber.CURRENT];
2780
+ } else if (rune.type == "val" && rune.text == "pages") {
1149
2781
  rune.type = "text";
1150
2782
  children = doc.totalPagesOffset == 0 ? [PageNumber.TOTAL_PAGES] : [
1151
2783
  // Complex Formula Field: { = { NUMPAGES } + 1 }
@@ -1229,7 +2861,36 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1229
2861
  }
1230
2862
  }
1231
2863
  }
2864
+ function renderMath(latex, para, sourceLine) {
2865
+ try {
2866
+ return latexToOmml(latex, para);
2867
+ } catch (error) {
2868
+ logwarn(`${sourceLine ? `Line ${doc.mapSourceLine(sourceLine + 1)}: ` : ""}Formula was not converted: ${error instanceof Error ? error.message : String(error)}`);
2869
+ return new TextRun(latex);
2870
+ }
2871
+ }
1232
2872
  }
2873
+ var admonitionIcons = {
2874
+ note: (c) => `<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><style>.a{fill:${c}}</style><path class="a" d="m13 12q0-0.4-0.3-0.7-0.3-0.3-0.7-0.3-0.4 0-0.7 0.3-0.3 0.3-0.3 0.7v4q0 0.4 0.3 0.7 0.3 0.3 0.7 0.3 0.4 0 0.7-0.3 0.3-0.3 0.3-0.7zm-1-2.5c0.3 0 0.6-0.1 0.9-0.4 0.2-0.2 0.3-0.5 0.3-0.8q0-0.6-0.3-0.9c-0.3-0.3-0.6-0.4-0.9-0.4-0.3 0-0.6 0.1-0.9 0.4q-0.3 0.3-0.3 0.9c0 0.3 0.1 0.6 0.3 0.8 0.3 0.3 0.6 0.4 0.9 0.4z"/><path fill-rule="evenodd" class="a" d="m22 12c0 5.5-4.5 10-10 10-5.5 0-10-4.5-10-10 0-5.5 4.5-10 10-10 5.5 0 10 4.5 10 10zm-15.7 5.7c1.5 1.5 3.6 2.3 5.7 2.3 2.1 0 4.2-0.8 5.7-2.3 1.5-1.5 2.3-3.6 2.3-5.7 0-2.1-0.8-4.2-2.3-5.7-1.5-1.5-3.6-2.3-5.7-2.3-2.1 0-4.2 0.8-5.7 2.3-1.5 1.5-2.3 3.6-2.3 5.7 0 2.1 0.8 4.2 2.3 5.7z"/></svg>`,
2875
+ info: (c) => `<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><style>.a{fill:${c}}</style><path class="a" d="m13 12q0-0.4-0.3-0.7-0.3-0.3-0.7-0.3-0.4 0-0.7 0.3-0.3 0.3-0.3 0.7v4q0 0.4 0.3 0.7 0.3 0.3 0.7 0.3 0.4 0 0.7-0.3 0.3-0.3 0.3-0.7zm-1-2.5c0.3 0 0.6-0.1 0.9-0.4 0.2-0.2 0.3-0.5 0.3-0.8q0-0.6-0.3-0.9c-0.3-0.3-0.6-0.4-0.9-0.4-0.3 0-0.6 0.1-0.9 0.4-0.2 0.2-0.3 0.5-0.3 0.9 0 0.3 0.1 0.6 0.3 0.8 0.3 0.3 0.6 0.4 0.9 0.4z"/><path fill-rule="evenodd" class="a" d="m22 12c0 5.5-4.5 10-10 10-5.5 0-10-4.5-10-10 0-5.5 4.5-10 10-10 5.5 0 10 4.5 10 10zm-15.7 5.7c1.5 1.5 3.6 2.3 5.7 2.3 2.1 0 4.2-0.8 5.7-2.3 1.5-1.5 2.3-3.6 2.3-5.7 0-2.1-0.8-4.2-2.3-5.7-1.5-1.5-3.6-2.3-5.7-2.3-2.1 0-4.2 0.8-5.7 2.3-1.5 1.5-2.3 3.6-2.3 5.7 0 2.1 0.8 4.2 2.3 5.7z"/></svg>`,
2876
+ tip: (c) => `<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><style>.a{fill:${c}}</style><path class="a" d="m8.1 20c0.2 0.9 0.7 1.6 1.5 2.2 0.7 0.5 1.5 0.8 2.4 0.8 0.9 0 1.7-0.3 2.4-0.8 0.8-0.6 1.3-1.3 1.5-2.2q0.2 0 0.4-0.1 0.1-0.1 0.3-0.2 0.1-0.1 0.2-0.3 0.1-0.2 0.1-0.4v-2.5q0.8-0.6 1.5-1.3 0.7-0.8 1.1-1.7 0.5-0.9 0.8-1.9 0.2-1 0.2-2.1c0-4.7-3.8-8.5-8.5-8.5-4.7 0-8.5 3.8-8.5 8.5q0 1.1 0.2 2.1 0.3 1 0.8 1.9 0.4 0.9 1.1 1.7 0.7 0.7 1.5 1.3v2.5q0 0.2 0.1 0.4 0.1 0.2 0.2 0.3 0.2 0.1 0.3 0.2 0.2 0.1 0.4 0.1zm6.8-3v1h-5.8v-1zm-1.1 3q-0.3 0.5-0.7 0.7-0.5 0.3-1 0.3-0.6 0-1-0.3-0.5-0.2-0.8-0.7zm-1.7-17c3.6 0 6.5 2.9 6.5 6.5q0 0.8-0.2 1.6-0.2 0.8-0.6 1.5-0.4 0.8-0.9 1.4-0.5 0.6-1.2 1h-7.2q-0.6-0.4-1.2-1-0.5-0.6-0.9-1.4-0.4-0.7-0.6-1.5-0.2-0.8-0.2-1.6c0-3.6 2.9-6.5 6.5-6.5z"/></svg>`,
2877
+ warning: (c) => `<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><style>.a{fill:${c}}</style><path class="a" d="m9.4 4.3c1.2-1.9 4-1.9 5.2 0l7.4 12.1c1.2 2-0.2 4.6-2.6 4.6h-14.8c-2.4 0-3.8-2.6-2.6-4.6zm3.4 1.1c-0.4-0.7-1.3-0.7-1.7 0l-7.4 12.1c-0.4 0.7 0.1 1.6 0.8 1.6h14.9c0.8 0 1.3-0.9 0.9-1.6zm-0.9 3.7c0.6 0 1 0.5 1 1v3c0 0.6-0.4 1-1 1-0.6 0-1-0.4-1-1v-3c0-0.5 0.4-1 1-1zm-1.1 7.5c0-0.6 0.5-1.1 1.1-1.1 0.6 0 1.2 0.5 1.2 1.1 0 0.7-0.6 1.2-1.2 1.2-0.6 0-1.1-0.5-1.1-1.2z"/></svg>`,
2878
+ danger: (c) => `<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><style>.a{fill:${c}}.b{fill:none;stroke:${c};stroke-linecap:round;stroke-linejoin:round;stroke-width:2}</style><path class="a" d="m12 14.5c-0.3 0-0.6 0.1-0.9 0.4-0.2 0.2-0.3 0.5-0.3 0.8q0 0.6 0.3 0.9c0.3 0.3 0.6 0.4 0.9 0.4 0.3 0 0.6-0.1 0.9-0.4q0.3-0.3 0.3-0.9c0-0.3-0.1-0.6-0.3-0.8-0.3-0.3-0.6-0.4-0.9-0.4z"/><path fill-rule="evenodd" class="b" d="m12 8v4"/><path class="b" d="m15.3 2q0.2 0 0.4 0 0.2 0.1 0.4 0.2 0.2 0 0.3 0.1 0.2 0.1 0.3 0.3l4.7 4.7q0.2 0.1 0.3 0.3 0.1 0.1 0.1 0.3 0.1 0.2 0.2 0.4 0 0.2 0 0.4v6.6q0 0.2 0 0.4-0.1 0.2-0.2 0.4 0 0.2-0.1 0.3-0.1 0.2-0.3 0.3l-4.7 4.7q-0.1 0.2-0.3 0.3-0.1 0.1-0.3 0.1-0.2 0.1-0.4 0.2-0.2 0-0.4 0h-6.6q-0.2 0-0.4 0-0.2-0.1-0.4-0.2-0.2 0-0.3-0.1-0.2-0.1-0.3-0.3l-4.7-4.7q-0.2-0.1-0.3-0.3-0.1-0.1-0.1-0.3-0.1-0.2-0.2-0.4 0-0.2 0-0.4v-6.6q0-0.2 0-0.4 0.1-0.2 0.2-0.4 0-0.2 0.1-0.3 0.1-0.2 0.3-0.3l4.7-4.7q0.1-0.2 0.3-0.3 0.1-0.1 0.3-0.1 0.2-0.1 0.4-0.2 0.2 0 0.4 0z"/></svg>`
2879
+ };
2880
+ var admonitionIconsFallback = {
2881
+ note: `iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAilBMVEUAAABmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmbNZZ8EAAAALnRSTlMA//BXB/7YePSoRaAMKt3USM91y08CZiK8tUzjLxM82zjGblsPPSRhBkJzadIoQt+BPAAAAMBJREFUeJydUVcOwjAUi9Mm3YsOoLRlb7j/9RDJK22EEAL/OLKd5A3G/oVtBbnLs8CyTd2PQYj9se4IzKuubffVDMIZ6eBpfy44Xk4q3GhIRa6gVLiApThJ1M9bSF2BhUTxAZg+uZQ6yDxc9dVupfkCT7HUwQENpGIOauqWad6BkxFqAaBqyJCITKOhpzxsTGNCn/fl9ka5pHJtapDla0XHvkFWC343RnL+NkQ99vrEmF2YY/+8KLXajLv522p/wANc8QfHdtgFgwAAAABJRU5ErkJggg==`,
2882
+ info: `iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAilBMVEUAAAAZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtIZdtL9UeA9AAAALnRSTlMA//BXB/7YePSoRaAMKt3USM91y08CZiK8tUzjLxM82zjGblsPPSRhBkJzadIoQt+BPAAAAMBJREFUeJydUVcOwjAUi9Mm3YsOoLRlb7j/9RDJK22EEAL/OLKd5A3G/oVtBbnLs8CyTd2PQYj9se4IzKuubffVDMIZ6eBpfy44Xk4q3GhIRa6gVLiApThJ1M9bSF2BhUTxAZg+uZQ6yDxc9dVupfkCT7HUwQENpGIOauqWad6BkxFqAaBqyJCITKOhpzxsTGNCn/fl9ka5pHJtapDla0XHvkFWC343RnL+NkQ99vrEmF2YY/+8KLXajLv522p/wANc8QfHdtgFgwAAAABJRU5ErkJggg==`,
2883
+ tip: `iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAllBMVEUAAAAufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTIufTLBWjFSAAAAMnRSTlMA//AF9MXq5ssCgtub1XmmJgghfRkRC+exGm0tRBX+i2c1Tt8pMHC0RwxdtsOoo64eS7UJBqAAAADPSURBVHichVLZEoIwENtU7luUUwUB79v//zkHpKWiM+Ypk+x2szsl+ofAUhlTrWAkR7cCHQonlvWwASvrR7Sc6XBXgx67sOc9vcCYCGMmdKKjijXnE4Z6aF+g4fQJ3ZQGGtj2bIernKTESYxwZCOHJzpS2bCQ9+wANiQkUrHkqbQhIVEFTUTxoEWcm4YYQTS1YWQ99eFK71bK+4QtFL5Fh1A4SijrRAm6a2XQP3VKeMfYOHPjY9cWKfarqYP7WKcFa+vZ5sugja8l/vgv/MALHToJTMdMgakAAAAASUVORK5CYII=`,
2884
+ warning: `iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAllBMVEUAAACyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagCyagD2Sl3HAAAAMnRSTlMA/w3r5vwbWbsElS1drcsiElT5ArX2wo045GaR779hCUx02KB7BdPiPyiCaxZSMsa5W/G4KzAAAADCSURBVHictZDJEoJADES7GZBFdlABFdz39f9/zmIoCge9WeaS5HVNkmngD6F5pKd9EeYkaX3yJ+NDRJofgwQ3gEO9P2zKlQ1oOicqHxrM6hzROCrCnWvYuQekHL/zkrMQA+rAYES346FggUbAkqLb79BHK9g7Tlt+ozEEUIlH3WVk3QFIfPXG5j2AgvtKFudr81edpcwzLiQIGG9lETX73da55NQeOpeWmUx79tiWFC6i9luNPJSzx7GKYyvom/9TvACzaQfmlOkXxAAAAABJRU5ErkJggg==`,
2885
+ danger: `iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAdVBMVEUAAADTLy+/ICDSLi7SLi7GHBzPKSnSLy/SLi7MGhrTLy/TLy/SLy/RLi7SLy/SLi7TLi7KLCzRLi7SLi7KKyvTLi7TLy/EJyfTLi6/KyvTLy/TLy/SLi7TLy/MKSnMKyvTLy/SLy/TLi7OJyfSLi7TLy/TLy/z9/9QAAAAJ3RSTlMA/wi9wgklCyEK7L/UFuXA4B0c6Rj+Vg2zDMXc2LkZHre0lhqmySRpaHOvAAAAtElEQVR4nKVRRxLDIBBbGdx77yX1/0/M2EBMHE8u0QWtBCwriH6juECiKXQ9DJQOBOGuzz1YK2jLMNyU7kRghioMhmQW1Mo1ncjgiKyVTB5iTSdyObz1BdeSu0LxfbHaXXknIqCWWwFJ6o29yyPDH4Yvm38ZO/BpVNXBkOM9AUcwczNGyAHtcbEF4WiIKPUQm8dI0vMQ8y3Ek9hlK6I0wSJvMxn6bD8+DedfS5Q9lN5p+0/xArzsBn7bxA9sAAAAAElFTkSuQmCC`
2886
+ };
2887
+ var admonitionIconsFallbackBlack = {
2888
+ note: `iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAilBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATAggvAAAALnRSTlMA//AHeFjY9N2gDCpEqM3TSToi47XVR9D9Zky8L3RWPRNPxm4CUFsPJGFpKAbKjdKbTAAAAMRJREFUeJydUdkSgjAM7BbKfYjKKQp4n///e860AdpxfNC8bGY3bZINY/+GJfzE5aUvLJOPPFB4kc7bwLru+r6rN4Ct83wx5ic+K0XlhnNV6FZUFaywk5insvMZjppAIJd4BZYycSAkxrirp1mmcIuYClThFC0ciRy0VJoovICTECgCoGlIcBCaQktfxTiYwthcIDWEo0fjWrQge+0l3MYFWQP+0C3BZLBu4qCZqGxvnoxZg2k7Y8WXQ8nTltxNPk77Q7wBVQsHvaZMK8YAAAAASUVORK5CYII=`,
2889
+ info: `iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAilBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATAggvAAAALnRSTlMA//AHeFjY9N2gDCpEqM3TSToi47XVR9D9Zky8L3RWPRNPxm4CUFsPJGFpKAbKjdKbTAAAAMRJREFUeJydUdkSgjAM7BbKfYjKKQp4n///e860AdpxfNC8bGY3bZINY/+GJfzE5aUvLJOPPFB4kc7bwLru+r6rN4Ct83wx5ic+K0XlhnNV6FZUFaywk5insvMZjppAIJd4BZYycSAkxrirp1mmcIuYClThFC0ciRy0VJoovICTECgCoGlIcBCaQktfxTiYwthcIDWEo0fjWrQge+0l3MYFWQP+0C3BZLBu4qCZqGxvnoxZg2k7Y8WXQ8nTltxNPk77Q7wBVQsHvaZMK8YAAAAASUVORK5CYII=`,
2890
+ tip: `iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAh1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD3YishAAAALXRSTlMA/+gFxBrmCvAH9EXbmyd9sC+kedXxTckiEW3ryx8VZ4s1gt8rqHC1XQyDshgZvchdAAAAyUlEQVR4nIWS5w7CMAyEfR20dO/SSSdlvf/zIaokDQWJ+3XyZ8dDIfonb9BMUxu8XbiIwOQacrwZkXZZU9j+AUm7xY0EzpNZDbkqgA9H5/6W48y9aiLbyieM3GY4yA1rhMxdMMugw1W0cGVQIhYVgQzuKJmzYW4TEmmw+VTVNiFRD0WkxVAKAXLRgujooGYbHmck0ru9xW8IWHyLVSdBrJMcJ1KwvqV/HmEFTHuwcPDYAQqwtIb7eYNVYfrOT6cvQF5UKdH+L/zQCzA0CJnLbfrbAAAAAElFTkSuQmCC`,
2891
+ warning: `iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAllBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6C80qAAAAMnRSTlMA/w3mA/xUu1kb610AyyKtEi35lbXvwvZhv2aPCTnYixagayh7TDKSBeLTdII/xrk2W23LF/kAAADFSURBVHictZDJcsIwEET7WbINosDGLGELS8Kanf//OUpyucAmN4q+jOZ1aUZq6QmKMsiif4wpQHrPzxTfCbTuBjm20gDTHDZnaaXIMK/zfsyrrwnxe834YSNrMmnI+JZPGOVqY6T2C50rzx0zlYY+cdf9A7qqDPvBoeJ74r6khfv13R/4zqvL6nZheV/SjNMiHN6S8q+GSagjdgH0KI7hkJT7O1Vy9qt66DRE1mLYiMemwVg7n3ddJg+zx0UdF2mvGf5DugCExweZt6hkrgAAAABJRU5ErkJggg==`,
2892
+ danger: `iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAYFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6T+iNAAAAIHRSTlMA/wi9Cr8lwgwY7Bwhw7cW29Tl4OkdVh6z/bmWpsXXyQwvE4cAAACqSURBVHicpVFHEsIwEJPiFKeRXgjt/79ksNfEBA8XdNJKnpFXC/xGvlKw5r7edE4nu2bXl5ZKW6oV29LpVU8VuSFSHBZL09HTgahmn5rcgomnA3HG4vWDC7PYKkkizu18BUBKLkgh2rD3eGT8w0gk/MvYwU9jng+GrJeSlSxvjM0tqLeTJXHGFcAUqmQKlziaEgO1SxQwDTyJE/mHAvJ7+LRA+XB67b0P4gn4WAUqr4Oj0wAAAABJRU5ErkJggg==`
2893
+ };
1233
2894
  function addListItemLevel(levels, level, startIndex, itemCount, ordered, alternativeStyle, mark) {
1234
2895
  if (levels.find((v) => v.level == level)) return;
1235
2896
  levels.sort((a, b) => a.level - b.level);
@@ -1268,7 +2929,7 @@ function addListItemLevel(levels, level, startIndex, itemCount, ordered, alterna
1268
2929
  }
1269
2930
  });
1270
2931
  }
1271
- var RawXml = class extends XmlComponent {
2932
+ var RawXml = class extends XmlComponent2 {
1272
2933
  constructor(tag, attributes = {}, text = null) {
1273
2934
  super(tag);
1274
2935
  if (Object.keys(attributes).length > 0)
@@ -1278,60 +2939,158 @@ var RawXml = class extends XmlComponent {
1278
2939
  }
1279
2940
  };
1280
2941
  function renderCodeHighlighting(code, lang) {
1281
- const grammar = Prism.languages[lang.trim().toLowerCase()];
2942
+ const language = lang.trim().toLowerCase();
2943
+ const grammar = Prism.languages[language];
1282
2944
  if (!grammar) return null;
1283
2945
  const themeColors = {
1284
2946
  // shared
1285
2947
  "plain": "000000",
1286
2948
  "comment": "008000",
2949
+ "hashbang": "008000",
2950
+ "shebang": "008000",
1287
2951
  "punctuation": "000000",
1288
2952
  "operator": "000000",
2953
+ "script": "000000",
2954
+ "script-punctuation": "000000",
2955
+ "expression": "000000",
2956
+ "interpolation": "000000",
1289
2957
  // keywords & control flow
1290
2958
  "keyword": "0000ff",
2959
+ "control-flow": "af00db",
1291
2960
  "boolean": "0000ff",
1292
- "builtin": "0000ff",
2961
+ "null": "0000ff",
2962
+ "builtin": "267f99",
1293
2963
  "important": "0000ff",
2964
+ // "instruction": "0000ff",
2965
+ // "directive": "0000ff",
2966
+ "directive-hash": "0000ff",
2967
+ "macro": "0000ff",
2968
+ "macro-name": "0000ff",
2969
+ "rule": "0000ff",
1294
2970
  // literals
1295
- "string": lang === "css" ? "0451a5" : lang === "json" ? "a31515" : "a31515",
1296
- "number": "098658",
1297
- "constant": "0000ff",
1298
- "symbol": "0000ff",
2971
+ "string": "a31515",
2972
+ "template-string": "a31515",
2973
+ "template-punctuation": "a31515",
2974
+ "string-literal": "a31515",
1299
2975
  "char": "a31515",
2976
+ "number": "098658",
2977
+ "datetime": "098658",
2978
+ "constant": "0070c1",
2979
+ "symbol": "0070c1",
2980
+ "scalar": "0000ff",
2981
+ "conversion-option": "0000ff",
1300
2982
  // identifiers
1301
- "class-name": lang === "js" || lang === "ts" ? "267f99" : "0451a5",
1302
- "function": "0451a5",
1303
- "variable": "000000",
1304
- "namespace": "000000",
1305
- "property": lang === "css" ? "e50000" : "0451a5",
2983
+ "class-name": "267f99",
2984
+ "class-name-definition": "267f99",
2985
+ "type-definition": "267f99",
2986
+ "return-type": "267f99",
2987
+ "attribute": "267f99",
2988
+ "attribute-class-name": "267f99",
2989
+ "annotation": "267f99",
2990
+ "decorator": "267f99",
2991
+ "namespace": "267f99",
2992
+ "double-colon": "267f99",
2993
+ "function": "795e26",
2994
+ "function-definition": "795e26",
2995
+ "method-definition": "795e26",
2996
+ "variable": "001080",
2997
+ "parameter": "001080",
2998
+ "environment": "001080",
2999
+ "assign-left": "001080",
3000
+ "property": language === "css" ? "e50000" : language === "json" ? "0451a5" : "001080",
3001
+ "key": "0451a5",
3002
+ "package": "000000",
3003
+ "lifetime-annotation": "0000ff",
1306
3004
  // markup / HTML / XML
1307
- "tag": lang === "html" || lang === "xml" ? "800000" : "0000ff",
3005
+ "tag": "800000",
1308
3006
  "selector": "800000",
1309
3007
  "attr-name": "e50000",
1310
- "attr-value": "a31515",
3008
+ "attr-value": "0000ff",
1311
3009
  "entity": "800000",
1312
3010
  "doctype": "808080",
3011
+ "doctype-tag": "808080",
3012
+ "name": "808080",
1313
3013
  "cdata": "808080",
1314
3014
  "prolog": "808080",
3015
+ "delimiter": language === "php" ? "800000" : "a31515",
1315
3016
  // CSS specific
1316
3017
  "atrule": "0000ff",
1317
3018
  "url": "0451a5",
1318
3019
  // regex
1319
- "regex": "811f3f"
3020
+ "regex": "811f3f",
3021
+ "regex-delimiter": "811f3f",
3022
+ "regex-source": "811f3f",
3023
+ "regex-flags": "0000ff",
3024
+ "interpolation-punctuation": "0000ff",
3025
+ // Markdown
3026
+ "title": "800000",
3027
+ "blockquote": "0451a5",
3028
+ "list": "0451a5",
3029
+ "bold": "000080",
3030
+ "italic": "800080",
3031
+ "code": "800000",
3032
+ "code-snippet": "800000",
3033
+ "code-block": "800000",
3034
+ "code-language": "0000ff",
3035
+ "inserted": "098658",
3036
+ "deleted": "a31515",
3037
+ "strike": "000000",
3038
+ "hr": "800000"
1320
3039
  };
3040
+ const controlFlowKeywords = /* @__PURE__ */ new Set([
3041
+ "if",
3042
+ "else",
3043
+ "elif",
3044
+ "elseif",
3045
+ "unless",
3046
+ "then",
3047
+ "switch",
3048
+ "case",
3049
+ "default",
3050
+ "when",
3051
+ "match",
3052
+ "for",
3053
+ "foreach",
3054
+ "while",
3055
+ "do",
3056
+ "until",
3057
+ "loop",
3058
+ "break",
3059
+ "continue",
3060
+ "return",
3061
+ "goto",
3062
+ "throw",
3063
+ "throws",
3064
+ "try",
3065
+ "catch",
3066
+ "except",
3067
+ "finally",
3068
+ "rescue",
3069
+ "ensure",
3070
+ "await",
3071
+ "yield",
3072
+ "new",
3073
+ "delete",
3074
+ "in",
3075
+ "of",
3076
+ "instanceof"
3077
+ ]);
1321
3078
  const tokens = Prism.tokenize(code, grammar);
1322
3079
  return tokensToParagraphs(tokens);
1323
3080
  function tokensToParagraphs(tokens2) {
1324
3081
  const paragraphs = [];
1325
3082
  let currentRuns = [];
1326
- function addText(text, type) {
3083
+ const plainStyle = { color: themeColors.plain, bold: false, italics: false, strike: false };
3084
+ function addText(text, style) {
1327
3085
  const lines = text.split("\n");
1328
3086
  lines.forEach((line, index) => {
1329
3087
  currentRuns.push(
1330
3088
  new TextRun({
1331
3089
  text: line,
1332
- color: themeColors[type] || themeColors["plain"],
1333
- bold: type === "bold",
1334
- italics: type === "italic"
3090
+ color: style.color,
3091
+ bold: style.bold,
3092
+ italics: style.italics,
3093
+ strike: style.strike
1335
3094
  })
1336
3095
  );
1337
3096
  if (index < lines.length - 1) {
@@ -1340,15 +3099,28 @@ function renderCodeHighlighting(code, lang) {
1340
3099
  }
1341
3100
  });
1342
3101
  }
1343
- function process2(token, parentType = "plain") {
3102
+ function process2(token, parentStyle = plainStyle) {
1344
3103
  if (typeof token === "string")
1345
- addText(token, parentType);
1346
- else if (Array.isArray(token.content))
1347
- token.content.forEach((t) => process2(t, token.type));
1348
- else if (typeof token.content === "string")
1349
- addText(token.content, token.type);
1350
- else
1351
- process2(token.content, token.type);
3104
+ addText(token, parentStyle);
3105
+ else {
3106
+ const aliases = typeof token.alias == "string" ? [token.alias] : token.alias || [];
3107
+ const types = [token.type, ...aliases];
3108
+ const colorType = types.find((type) => themeColors[type]);
3109
+ const isControlFlow = token.type == "keyword" && typeof token.content == "string" && controlFlowKeywords.has(token.content);
3110
+ const inheritPunctuation = token.type == "punctuation" && parentStyle.color != themeColors.plain;
3111
+ const style = {
3112
+ color: isControlFlow ? themeColors["control-flow"] : inheritPunctuation ? parentStyle.color : colorType ? themeColors[colorType] : parentStyle.color,
3113
+ bold: parentStyle.bold || types.includes("bold") || types.includes("important") || types.includes("title"),
3114
+ italics: parentStyle.italics || types.includes("italic"),
3115
+ strike: parentStyle.strike || types.includes("strike")
3116
+ };
3117
+ if (Array.isArray(token.content))
3118
+ token.content.forEach((t) => process2(t, style));
3119
+ else if (typeof token.content === "string")
3120
+ addText(token.content, style);
3121
+ else
3122
+ process2(token.content, style);
3123
+ }
1352
3124
  }
1353
3125
  tokens2.forEach((t) => process2(t));
1354
3126
  if (currentRuns.length > 0) {
@@ -1357,6 +3129,37 @@ function renderCodeHighlighting(code, lang) {
1357
3129
  return paragraphs;
1358
3130
  }
1359
3131
  }
3132
+ function estimateNumberWidthPt(text, fontSizePt) {
3133
+ const charWidths = {
3134
+ ".": 0.25,
3135
+ ",": 0.25,
3136
+ "-": 0.333,
3137
+ "(": 0.333,
3138
+ ")": 0.333,
3139
+ " ": 0.25,
3140
+ "a": 0.444,
3141
+ "b": 0.5,
3142
+ "c": 0.444,
3143
+ "d": 0.5,
3144
+ "e": 0.444,
3145
+ "A": 0.722,
3146
+ "B": 0.667,
3147
+ "C": 0.667,
3148
+ "D": 0.722,
3149
+ "E": 0.611,
3150
+ "\u0430": 0.5,
3151
+ "\u0431": 0.5,
3152
+ "\u0432": 0.5,
3153
+ "\u0433": 0.5,
3154
+ "\u0434": 0.5,
3155
+ "\u0410": 0.722,
3156
+ "\u0411": 0.667,
3157
+ "\u0412": 0.667,
3158
+ "\u0413": 0.611,
3159
+ "\u0414": 0.722
3160
+ };
3161
+ return [...text].reduce((width, char) => width + (charWidths[char] ?? 0.5) * fontSizePt, 0);
3162
+ }
1360
3163
  function genXml_app({ totalTime }) {
1361
3164
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1362
3165
  <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
@@ -1364,20 +3167,29 @@ function genXml_app({ totalTime }) {
1364
3167
  </Properties>
1365
3168
  `;
1366
3169
  }
3170
+ function escapeXml(value) {
3171
+ return value.replaceAll(/[<>&"']/g, (ch) => ({
3172
+ "<": "&lt;",
3173
+ ">": "&gt;",
3174
+ "&": "&amp;",
3175
+ '"': "&quot;",
3176
+ "'": "&apos;"
3177
+ })[ch]);
3178
+ }
1367
3179
  function genXml_core({ title, creator, createdAt, modifiedAt }) {
1368
3180
  if (!createdAt) createdAt = /* @__PURE__ */ new Date();
1369
3181
  if (!modifiedAt) modifiedAt = /* @__PURE__ */ new Date();
1370
3182
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1371
3183
  <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1372
- <dc:title>${title || "Document"}</dc:title>
1373
- <dc:creator>${creator || "Student"}</dc:creator>
1374
- <cp:lastModifiedBy>${creator || "Student"}</cp:lastModifiedBy>
3184
+ <dc:title>${escapeXml(title || "Document")}</dc:title>
3185
+ <dc:creator>${escapeXml(creator || "Student")}</dc:creator>
3186
+ <cp:lastModifiedBy>${escapeXml(creator || "Student")}</cp:lastModifiedBy>
1375
3187
  <dcterms:created xsi:type="dcterms:W3CDTF">${createdAt.toISOString()}</dcterms:created>
1376
3188
  <dcterms:modified xsi:type="dcterms:W3CDTF">${modifiedAt.toISOString()}</dcterms:modified>
1377
3189
  </cp:coreProperties>`;
1378
3190
  }
1379
3191
  function genXml_style(assets, doc) {
1380
- let xml = fs3.readFileSync(path3.join(assets, "styles.xml"), { encoding: "utf8" });
3192
+ let xml = fs4.readFileSync(path4.join(assets, "styles.xml"), { encoding: "utf8" });
1381
3193
  xml = editStyle(`<w:style w:type="paragraph" w:default="1" w:styleId="a2">`, (xml2) => {
1382
3194
  xml2 = replaceTag(xml2, `<w:spacing w:line="${doc.text.line_spacing * 240}" w:lineRule="auto" w:after="${doc.text.spacing.after * 20}"/>`);
1383
3195
  xml2 = replaceTag(xml2, `<w:ind w:firstLine="${convertMillimetersToTwip(doc.text.indent * 10)}"/>`);
@@ -1402,7 +3214,33 @@ function genXml_style(assets, doc) {
1402
3214
  xml2 = addrPrStyle(xml2, doc.table.title.style == "bold" ? "<w:b/>" : "<w:i/>");
1403
3215
  return xml2;
1404
3216
  });
3217
+ const admonitionTypes = ["note", "info", "tip", "warning", "danger"];
3218
+ for (const type of admonitionTypes) {
3219
+ const suffix = type == "note" ? "" : type;
3220
+ const styleId = `xAdmonition${suffix}`;
3221
+ const styleTag = `<w:style w:type="paragraph" w:customStyle="1" w:styleId="${styleId}">`;
3222
+ const style = doc.admonition[type];
3223
+ xml = editStyle(styleTag, (xml2) => {
3224
+ xml2 = replaceTagOrInsertAfter(xml2, "<w:pPr>", `<w:spacing w:before="${style.spacing.before * 20}" w:after="${style.spacing.after * 20}"/>`);
3225
+ xml2 = replaceTagOrInsertAfter(xml2, "<w:pPr>", `<w:ind w:left="${convertMillimetersToTwip(style.indent * 10)}" w:firstLine="0"/>`);
3226
+ xml2 = replaceBorder(xml2, "top", style.padding.top, "FFFFFF", 2);
3227
+ xml2 = replaceBorder(xml2, "right", style.padding.right, "FFFFFF", 2);
3228
+ xml2 = replaceBorder(xml2, "bottom", style.padding.bottom, "FFFFFF", 2);
3229
+ xml2 = replaceBorder(xml2, "left", style.padding.left, style.color, Math.max(1, Math.round(style.bar_width * 8)));
3230
+ xml2 = replaceTagOrInsertAfter(xml2, "<w:pPr>", `<w:shd w:val="clear" w:color="auto" w:fill="${style.background}"/>`);
3231
+ return xml2;
3232
+ });
3233
+ }
1405
3234
  return xml;
3235
+ function replaceBorder(xml2, side, space, color, size) {
3236
+ return replaceTagOrInsertAfter(xml2, "<w:pBdr>", `<w:${side} w:val="single" w:sz="${size}" w:space="${space}" w:color="${color}"/>`);
3237
+ }
3238
+ function replaceTagOrInsertAfter(xml2, insertAfter2, tag) {
3239
+ const tagName = /<([^\s>]+)/.exec(tag)?.[1];
3240
+ if (!tagName) throw err();
3241
+ const regex = new RegExp(`<${tagName}\\b[^>]*/>`);
3242
+ return regex.test(xml2) ? xml2.replace(regex, tag) : xml2.replace(insertAfter2, `${insertAfter2}${tag}`);
3243
+ }
1406
3244
  function replaceTag(xml2, tag) {
1407
3245
  const tagName = /<([^\s]+)\b/.exec(tag)?.[1];
1408
3246
  if (!tagName) throw err();
@@ -1442,13 +3280,14 @@ function genXml_style(assets, doc) {
1442
3280
 
1443
3281
  // src/enricher.ts
1444
3282
  function enrichDoc(doc, logwarn = console.warn) {
3283
+ const warnAt = (line, message) => logwarn(`Line ${doc.mapSourceLine(line)}: ${message}`);
1445
3284
  for (let i = 0; i < doc.nodes.length; i++) {
1446
3285
  const node = doc.nodes[i];
1447
- if (node.type == "title" && node.text.toUpperCase().startsWith("\u0422\u0418\u0422\u0423\u041B\u042C\u041D\u0418\u041A")) {
1448
- doc.nodes.splice(i, 1, { type: "text", text: node.text.toUpperCase() });
1449
- doc.nodes.splice(i + 1, 0, { type: "pageBreak" });
3286
+ if (node.type == "title" && node.level == 1 && node.text.toUpperCase().startsWith("\u0422\u0418\u0422\u0423\u041B\u042C\u041D\u0418\u041A")) {
3287
+ doc.nodes.splice(i, 1, { type: "text", text: node.text.toUpperCase(), sourceLine: node.sourceLine });
3288
+ doc.nodes.splice(i + 1, 0, { type: "pageBreak", sourceLine: node.sourceLine });
1450
3289
  i++;
1451
- } else if (node.type == "title" && node.text.toUpperCase() == "\u0420\u0415\u0424\u0415\u0420\u0410\u0422") {
3290
+ } else if (node.type == "title" && node.level == 1 && node.text.toUpperCase() == "\u0420\u0415\u0424\u0415\u0420\u0410\u0422") {
1452
3291
  node.level = 0;
1453
3292
  node.text = node.text.toUpperCase();
1454
3293
  const nextNode = doc.nodes[i + 1];
@@ -1456,81 +3295,83 @@ function enrichDoc(doc, logwarn = console.warn) {
1456
3295
  nextNode.noIndent = true;
1457
3296
  nextNode.text = trimEnd(nextNode.text, ".").toUpperCase();
1458
3297
  }
1459
- doc.nodes.splice(i + 1, 0, { type: "text", tags: ["synopsis"], text: "\u041E\u0442\u0447\u0435\u0442 [!pages] \u0441., [!imgs] \u0440\u0438\u0441., [!tables] \u0442\u0430\u0431\u043B., [!codes] \u043B\u0438\u0441\u0442., [!sources] \u0438\u0441\u0442\u043E\u0447\u043D." });
3298
+ doc.nodes.splice(i + 1, 0, { type: "text", tags: ["synopsis"], text: "\u041E\u0442\u0447\u0435\u0442 [!pages] \u0441., [!imgs] \u0440\u0438\u0441., [!tables] \u0442\u0430\u0431\u043B., [!codes] \u043B\u0438\u0441\u0442., [!sources] \u0438\u0441\u0442\u043E\u0447\u043D.", sourceLine: node.sourceLine });
1460
3299
  while (i + 1 < doc.nodes.length && doc.nodes[i + 1]?.type != "title") i++;
1461
- doc.nodes.splice(i + 1, 0, { type: "pageBreak" });
1462
- } else if (node.type == "title" && node.text.toUpperCase() == "\u041E\u0413\u041B\u0410\u0412\u041B\u0415\u041D\u0418\u0415") {
3300
+ doc.nodes.splice(i + 1, 0, { type: "pageBreak", sourceLine: node.sourceLine });
3301
+ } else if (node.type == "title" && node.level == 1 && node.text.toUpperCase() == "\u041E\u0413\u041B\u0410\u0412\u041B\u0415\u041D\u0418\u0415") {
1463
3302
  node.level = 0;
1464
3303
  node.text = node.text.toUpperCase();
1465
- doc.nodes.splice(i + 1, 0, { type: "tableOfContents" });
1466
- } else if (node.type == "title" && node.text.toUpperCase() == "\u0422\u0415\u0420\u041C\u0418\u041D\u042B \u0418 \u041E\u041F\u0420\u0415\u0414\u0415\u041B\u0415\u041D\u0418\u042F") {
3304
+ doc.nodes.splice(i + 1, 0, { type: "tableOfContents", sourceLine: node.sourceLine });
3305
+ } else if (node.type == "title" && node.level == 1 && node.text.toUpperCase() == "\u0422\u0415\u0420\u041C\u0418\u041D\u042B \u0418 \u041E\u041F\u0420\u0415\u0414\u0415\u041B\u0415\u041D\u0418\u042F") {
1467
3306
  node.center = true;
1468
3307
  node.text = node.text.toUpperCase();
1469
- const warn = () => logwarn("Wrong format of \u0422\u0415\u0420\u041C\u0418\u041D\u042B \u0418 \u041E\u041F\u0420\u0415\u0414\u0415\u041B\u0415\u041D\u0418\u042F");
3308
+ const warn = (ln) => warnAt(ln, "Wrong format of \u0422\u0415\u0420\u041C\u0418\u041D\u042B \u0418 \u041E\u041F\u0420\u0415\u0414\u0415\u041B\u0415\u041D\u0418\u042F");
1470
3309
  const list = doc.nodes[i + 1];
1471
3310
  if (list?.type != "list") {
1472
- warn();
3311
+ warn(node.sourceLine);
1473
3312
  continue;
1474
3313
  }
1475
- const rows = [tableRow("\u0422\u0435\u0440\u043C\u0438\u043D", "\u041E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D\u0438\u0435")];
3314
+ const rows = [tableRow(list.sourceLine, "\u0422\u0435\u0440\u043C\u0438\u043D", "\u041E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D\u0438\u0435")];
1476
3315
  const align = ["l", "l"];
1477
3316
  for (const item of list.items) {
1478
3317
  if (item.type != "listItem") {
1479
- warn();
3318
+ warn(item.sourceLine);
1480
3319
  continue;
1481
3320
  }
1482
3321
  const i2 = item.text.indexOf(":");
1483
3322
  if (i2 < 0) {
1484
- warn();
3323
+ warn(item.sourceLine);
1485
3324
  continue;
1486
3325
  }
1487
- rows.push(tableRow(item.text.slice(0, i2).trim(), item.text.slice(i2 + 1).trim()));
3326
+ rows.push(tableRow(item.sourceLine, item.text.slice(0, i2).trim(), item.text.slice(i2 + 1).trim()));
1488
3327
  }
1489
- doc.nodes.splice(i + 1, 1, { type: "text", text: "\u0412 \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0435\u043C \u043E\u0442\u0447\u0435\u0442\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u044E\u0442\u0441\u044F \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0435 \u0442\u0435\u0440\u043C\u0438\u043D\u044B \u0441 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u043C\u0438 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D\u0438\u044F\u043C\u0438." });
1490
- doc.nodes.splice(i + 2, 0, { type: "table", align, rows, normalFontSize: true, tags: ["definitions_table"] });
1491
- doc.nodes.splice(i + 3, 0, { type: "pageBreak" });
1492
- } else if (node.type == "title" && node.text.toUpperCase() == "\u041F\u0415\u0420\u0415\u0427\u0415\u041D\u042C \u0421\u041E\u041A\u0420\u0410\u0429\u0415\u041D\u0418\u0419 \u0418 \u041E\u0411\u041E\u0417\u041D\u0410\u0427\u0415\u041D\u0418\u0419") {
3328
+ doc.nodes.splice(i + 1, 1, { type: "text", text: "\u0412 \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0435\u043C \u043E\u0442\u0447\u0435\u0442\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u044E\u0442\u0441\u044F \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0435 \u0442\u0435\u0440\u043C\u0438\u043D\u044B \u0441 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u043C\u0438 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D\u0438\u044F\u043C\u0438.", sourceLine: node.sourceLine });
3329
+ doc.nodes.splice(i + 2, 0, { type: "table", align, rows, normalFontSize: true, tags: ["definitions_table"], sourceLine: node.sourceLine });
3330
+ doc.nodes.splice(i + 3, 0, { type: "pageBreak", sourceLine: node.sourceLine });
3331
+ } else if (node.type == "title" && node.level == 1 && node.text.toUpperCase() == "\u041F\u0415\u0420\u0415\u0427\u0415\u041D\u042C \u0421\u041E\u041A\u0420\u0410\u0429\u0415\u041D\u0418\u0419 \u0418 \u041E\u0411\u041E\u0417\u041D\u0410\u0427\u0415\u041D\u0418\u0419") {
1493
3332
  node.center = true;
1494
3333
  node.text = node.text.toUpperCase();
1495
- const warn = () => logwarn("Wrong format of \u041F\u0415\u0420\u0415\u0427\u0415\u041D\u042C \u0421\u041E\u041A\u0420\u0410\u0429\u0415\u041D\u0418\u0419 \u0418 \u041E\u0411\u041E\u0417\u041D\u0410\u0427\u0415\u041D\u0418\u0419");
3334
+ const warn = (ln) => warnAt(ln, "Wrong format of \u041F\u0415\u0420\u0415\u0427\u0415\u041D\u042C \u0421\u041E\u041A\u0420\u0410\u0429\u0415\u041D\u0418\u0419 \u0418 \u041E\u0411\u041E\u0417\u041D\u0410\u0427\u0415\u041D\u0418\u0419");
1496
3335
  const list = doc.nodes[i + 1];
1497
3336
  if (list?.type != "list") {
1498
- warn();
3337
+ warn(node.sourceLine);
1499
3338
  continue;
1500
3339
  }
1501
3340
  const items = [];
1502
3341
  for (const item of list.items) {
1503
3342
  if (item.type != "listItem") {
1504
- warn();
3343
+ warn(item.sourceLine);
1505
3344
  continue;
1506
3345
  }
1507
- items.push({ type: "text", text: trimEnd(item.text, "."), noIndent: true, noMargin: true });
3346
+ items.push({ type: "text", text: trimEnd(item.text, "."), noIndent: true, noMargin: true, sourceLine: item.sourceLine });
1508
3347
  }
1509
- doc.nodes.splice(i + 1, 1, { type: "text", text: "\u0412 \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0435\u043C \u043E\u0442\u0447\u0435\u0442\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u044E\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0435 \u0441\u043E\u043A\u0440\u0430\u0449\u0435\u043D\u0438\u044F \u0438 \u043E\u0431\u043E\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F." });
1510
- doc.nodes.splice(i + 2, 0, { type: "pageBreak" });
3348
+ doc.nodes.splice(i + 1, 1, { type: "text", text: "\u0412 \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0435\u043C \u043E\u0442\u0447\u0435\u0442\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u044E\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0435 \u0441\u043E\u043A\u0440\u0430\u0449\u0435\u043D\u0438\u044F \u0438 \u043E\u0431\u043E\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F.", sourceLine: node.sourceLine });
3349
+ doc.nodes.splice(i + 2, 0, { type: "pageBreak", sourceLine: node.sourceLine });
1511
3350
  doc.nodes.splice(i + 2, 0, ...items);
1512
- } else if (node.type == "title" && node.text.toUpperCase() == "\u0412\u0412\u0415\u0414\u0415\u041D\u0418\u0415") {
3351
+ } else if (node.type == "title" && node.level == 1 && node.text.toUpperCase() == "\u0412\u0412\u0415\u0414\u0415\u041D\u0418\u0415") {
1513
3352
  node.center = true;
1514
3353
  node.text = node.text.toUpperCase();
1515
3354
  for (let j = i + 1; j < doc.nodes.length; j++) {
1516
3355
  const node2 = doc.nodes[j];
1517
3356
  if (node2?.type == "title") {
1518
- doc.nodes.splice(j, 0, { type: "pageBreak" });
3357
+ doc.nodes.splice(j, 0, { type: "pageBreak", sourceLine: -1 });
1519
3358
  break;
1520
3359
  }
1521
3360
  }
1522
- } else if (node.type == "title" && node.text.toUpperCase() == "\u0417\u0410\u041A\u041B\u042E\u0427\u0415\u041D\u0418\u0415") {
3361
+ } else if (node.type == "title" && node.level == 1 && node.text.toUpperCase() == "\u0417\u0410\u041A\u041B\u042E\u0427\u0415\u041D\u0418\u0415") {
1523
3362
  node.center = true;
1524
3363
  node.text = node.text.toUpperCase();
1525
- doc.nodes.splice(i, 0, { type: "pageBreak" });
3364
+ doc.nodes.splice(i, 0, { type: "pageBreak", sourceLine: node.sourceLine });
1526
3365
  i++;
1527
- } else if (node.type == "title" && node.text.toUpperCase() == "\u0421\u041F\u0418\u0421\u041E\u041A \u0418\u0421\u041F\u041E\u041B\u042C\u0417\u041E\u0412\u0410\u041D\u041D\u042B\u0425 \u0418\u0421\u0422\u041E\u0427\u041D\u0418\u041A\u041E\u0412") {
3366
+ } else if (node.type == "title" && node.level == 1 && node.text.toUpperCase() == "\u0421\u041F\u0418\u0421\u041E\u041A \u0418\u0421\u041F\u041E\u041B\u042C\u0417\u041E\u0412\u0410\u041D\u041D\u042B\u0425 \u0418\u0421\u0422\u041E\u0427\u041D\u0418\u041A\u041E\u0412") {
1528
3367
  node.center = true;
1529
3368
  node.text = node.text.toUpperCase();
1530
- doc.nodes.splice(i++, 0, { type: "pageBreak" });
3369
+ doc.nodes.splice(i++, 0, { type: "pageBreak", sourceLine: node.sourceLine });
1531
3370
  const nextNode = doc.nodes[i + 1];
1532
3371
  if (nextNode?.type == "list") {
1533
3372
  nextNode.alternativeStyle = true;
3373
+ nextNode.tags = nextNode.tags || [];
3374
+ nextNode.tags.push("sources");
1534
3375
  i++;
1535
3376
  continue;
1536
3377
  }
@@ -1539,35 +3380,38 @@ function enrichDoc(doc, logwarn = console.warn) {
1539
3380
  mark: ".",
1540
3381
  startIndex: 1,
1541
3382
  alternativeStyle: true,
3383
+ tags: ["sources"],
1542
3384
  ordered: true,
1543
- items: []
3385
+ items: [],
3386
+ sourceLine: node.sourceLine + 1
1544
3387
  };
1545
3388
  const startI = i + 1;
1546
3389
  while (true) {
1547
3390
  const textNode = doc.nodes[i + 1];
1548
3391
  if (textNode?.type != "text") break;
1549
3392
  const items = textNode.text.split(/\n\s*(?=\[[a-zA-Zа-яА-ЯёЁ_\d]+\])/);
3393
+ let sourceLine = textNode.sourceLine;
1550
3394
  for (const item of items)
1551
- list.items.push({ type: "listItem", text: item });
3395
+ list.items.push({ type: "listItem", text: item, sourceLine: sourceLine++ });
1552
3396
  i++;
1553
3397
  }
1554
3398
  if (list.items.length > 0)
1555
3399
  doc.nodes.splice(startI, i - startI + 1, list);
1556
- } else if (node.type == "title" && node.text.toUpperCase() == "\u041F\u0420\u0418\u041B\u041E\u0416\u0415\u041D\u0418\u042F") {
3400
+ } else if (node.type == "title" && node.level == 1 && node.text.toUpperCase() == "\u041F\u0420\u0418\u041B\u041E\u0416\u0415\u041D\u0418\u042F") {
1557
3401
  node.level = 0;
1558
3402
  node.center = true;
1559
3403
  node.text = node.text.toUpperCase();
1560
- doc.nodes.splice(i++, 0, { type: "pageBreak" });
3404
+ doc.nodes.splice(i++, 0, { type: "pageBreak", sourceLine: node.sourceLine });
1561
3405
  for (let j = i + 1; j < doc.nodes.length; j++) {
1562
3406
  const node2 = doc.nodes[j];
1563
3407
  if (node2.type == "title") {
1564
- doc.nodes.splice(i++ + 1, 0, { type: "text", text: "\u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 " + node2.text.replace("]", "].") });
3408
+ doc.nodes.splice(i++ + 1, 0, { type: "text", text: "\u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 " + node2.text.replace("]", "]."), sourceLine: node2.sourceLine });
1565
3409
  j++;
1566
3410
  node2.level = 1;
1567
3411
  const splitI = node2.text.indexOf("]");
1568
- doc.nodes.splice(j++, 0, { type: "pageBreak" });
3412
+ doc.nodes.splice(j++, 0, { type: "pageBreak", sourceLine: node2.sourceLine });
1569
3413
  if (splitI >= 0) {
1570
- doc.nodes.splice(j++ + 1, 0, { type: "text", text: node2.text.slice(splitI + 1), center: true });
3414
+ doc.nodes.splice(j++ + 1, 0, { type: "text", text: node2.text.slice(splitI + 1), center: true, sourceLine: node2.sourceLine });
1571
3415
  node2.text = "\u041F\u0420\u0418\u041B\u041E\u0416\u0415\u041D\u0418\u0415 " + node2.text.slice(0, splitI + 1);
1572
3416
  node2.center = true;
1573
3417
  }
@@ -1603,11 +3447,11 @@ function enrichDoc(doc, logwarn = console.warn) {
1603
3447
  } else if (node.type == "externalDoc") {
1604
3448
  const nextNode = doc.nodes[i + 1];
1605
3449
  if (nextNode && nextNode?.type != "externalDoc" && nextNode?.type != "pageBreak" && nextNode?.type != "sectionBreak") {
1606
- doc.nodes.splice(i + 1, 0, { type: "pageBreak" });
3450
+ doc.nodes.splice(i + 1, 0, { type: "pageBreak", sourceLine: node.sourceLine });
1607
3451
  }
1608
3452
  const prevNode = doc.nodes[i - 1];
1609
3453
  if (prevNode && prevNode?.type != "externalDoc" && prevNode?.type != "pageBreak" && prevNode?.type != "sectionBreak") {
1610
- doc.nodes.splice(i, 0, { type: "pageBreak" });
3454
+ doc.nodes.splice(i, 0, { type: "pageBreak", sourceLine: node.sourceLine });
1611
3455
  i++;
1612
3456
  }
1613
3457
  }
@@ -1616,7 +3460,7 @@ function enrichDoc(doc, logwarn = console.warn) {
1616
3460
 
1617
3461
  // src/main.ts
1618
3462
  import { exec, spawn } from "child_process";
1619
- import fs4 from "fs/promises";
3463
+ import fs5 from "fs/promises";
1620
3464
  import { existsSync as existsSync2 } from "fs";
1621
3465
  import { PDFDocument } from "pdf-lib";
1622
3466
 
@@ -1627,12 +3471,15 @@ function alchemist(doc, logwarn = console.warn) {
1627
3471
  codesAll: 0,
1628
3472
  imgsAll: 0,
1629
3473
  tablesAll: 0,
3474
+ formulasAll: 0,
1630
3475
  codes: 0,
1631
3476
  imgs: 0,
1632
3477
  tables: 0,
3478
+ formulas: 0,
1633
3479
  titles: { l1: 0, l2: 0, l3: 0, l4: 0, l5: 0 }
1634
3480
  };
1635
3481
  const named = {};
3482
+ const warnAt = (line, message) => logwarn(`${line ? `Line ${doc.mapSourceLine(line)}: ` : ""}${message}`);
1636
3483
  const vals = {};
1637
3484
  let lastRefI = 0;
1638
3485
  let prevNum = -1;
@@ -1649,11 +3496,13 @@ function alchemist(doc, logwarn = console.warn) {
1649
3496
  let codes = counter.codes;
1650
3497
  let imgs = counter.imgs;
1651
3498
  let tables = counter.tables;
3499
+ let formulas = counter.formulas;
1652
3500
  for (let j = i; j < doc.nodes.length && nextNum < 0; j++) {
1653
3501
  const n = doc.nodes[j];
1654
3502
  if (n.type == "code") nextNum = codes + 1;
1655
3503
  if (n.type == "image") nextNum = imgs + 1;
1656
3504
  if (n.type == "table") nextNum = tables + 1;
3505
+ if (n.type == "math") nextNum = formulas + 1;
1657
3506
  if (n.type == "title" && n.level == 1 && doc.numberingSections) {
1658
3507
  const num = getTitleNum(n);
1659
3508
  if (num) l1 = num;
@@ -1661,6 +3510,7 @@ function alchemist(doc, logwarn = console.warn) {
1661
3510
  codes = 0;
1662
3511
  imgs = 0;
1663
3512
  tables = 0;
3513
+ formulas = 0;
1664
3514
  }
1665
3515
  }
1666
3516
  nextPrefix = l1 > 0 && doc.numberingSections ? `${l1}.` : "";
@@ -1683,13 +3533,14 @@ function alchemist(doc, logwarn = console.warn) {
1683
3533
  counter.codes = 0;
1684
3534
  counter.imgs = 0;
1685
3535
  counter.tables = 0;
3536
+ counter.formulas = 0;
1686
3537
  }
1687
3538
  prefix = doc.numberingSections ? `${counter.titles.l1}.` : "";
1688
3539
  if (appendix) prefix = APPENDIX_NAME[counter.titles.l1 - 1] + ".";
1689
3540
  }
1690
3541
  }
1691
3542
  materializeNode(node);
1692
- if (node.type == "code" || node.type == "image" || node.type == "table") {
3543
+ if (node.type == "code" || node.type == "image" || node.type == "table" || node.type == "math") {
1693
3544
  if (node.type == "code") {
1694
3545
  counter.codes++;
1695
3546
  counter.codesAll++;
@@ -1702,18 +3553,27 @@ function alchemist(doc, logwarn = console.warn) {
1702
3553
  counter.tables++;
1703
3554
  counter.tablesAll++;
1704
3555
  }
3556
+ if (node.type == "math") {
3557
+ counter.formulas++;
3558
+ counter.formulasAll++;
3559
+ }
1705
3560
  prevNum = nextNum;
1706
3561
  prevPrefix = prefix;
1707
3562
  nextNum = -1;
1708
3563
  }
1709
3564
  });
3565
+ for (const value of [doc.header, doc.footer, ...doc.nodes.filter((node) => node.type == "sectionBreak").flatMap((node) => [node.header, node.footer])]) {
3566
+ if (value?.type != "content") continue;
3567
+ value.nodes.forEach(materializeNode);
3568
+ }
1710
3569
  const sourcesCount = crystallizeSources();
1711
- vals["codes"]?.forEach((f) => f(counter.codes));
1712
- vals["imgs"]?.forEach((f) => f(counter.imgs));
1713
- vals["tables"]?.forEach((f) => f(counter.tables));
3570
+ vals["codes"]?.forEach((f) => f(counter.codesAll));
3571
+ vals["imgs"]?.forEach((f) => f(counter.imgsAll));
3572
+ vals["tables"]?.forEach((f) => f(counter.tablesAll));
3573
+ vals["formulas"]?.forEach((f) => f(counter.formulasAll));
1714
3574
  vals["sources"]?.forEach((f) => f(sourcesCount));
1715
3575
  Object.entries(named).forEach(([k, v]) => {
1716
- if (v instanceof Array) logwarn(`\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u0441\u0441\u044B\u043B\u043A\u0430 [${k}]`);
3576
+ if (v instanceof Array) v.forEach((ref) => warnAt(ref.sourceLine, `\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u0441\u0441\u044B\u043B\u043A\u0430 [${k}]`));
1717
3577
  });
1718
3578
  if (synopsis) {
1719
3579
  synopsis.text = [
@@ -1747,7 +3607,7 @@ function alchemist(doc, logwarn = console.warn) {
1747
3607
  function materializeList(node) {
1748
3608
  node.items.forEach((item) => {
1749
3609
  if (item.type == "list") materializeList(item);
1750
- else materializeRunes(item.text, node);
3610
+ else materializeRunes(item.text, { ...node, sourceLine: item.sourceLine ?? node.sourceLine });
1751
3611
  });
1752
3612
  }
1753
3613
  function materializeTable(node) {
@@ -1795,8 +3655,9 @@ function alchemist(doc, logwarn = console.warn) {
1795
3655
  rune.text = repeat(node.level, (i2) => counter.titles[`l${i2 + 1}`]).join(".");
1796
3656
  return;
1797
3657
  }
1798
- if (type == "code" || type == "image" || type == "table") {
1799
- let { num, text } = type == "code" ? { num: counter.codes, text: "\u041B\u0438\u0441\u0442\u0438\u043D\u0433" } : type == "image" ? { num: counter.imgs, text: "\u0420\u0438\u0441\u0443\u043D\u043E\u043A" } : type == "table" ? { num: counter.tables, text: "\u0422\u0430\u0431\u043B\u0438\u0446\u0430" } : (() => {
3658
+ if (type == "code" || type == "image" || type == "table" || type == "math") {
3659
+ let { num, text } = type == "code" ? { num: counter.codes, text: "\u041B\u0438\u0441\u0442\u0438\u043D\u0433" } : type == "image" ? { num: counter.imgs, text: "\u0420\u0438\u0441\u0443\u043D\u043E\u043A" } : type == "table" ? { num: counter.tables, text: "\u0422\u0430\u0431\u043B\u0438\u0446\u0430" } : type == "math" ? { num: counter.formulas, text: "" } : (() => {
3660
+ type;
1800
3661
  throw new Error("switch default");
1801
3662
  })();
1802
3663
  num++;
@@ -1813,9 +3674,10 @@ function alchemist(doc, logwarn = console.warn) {
1813
3674
  nextRune.text = toCapitalCase(nextRune.text);
1814
3675
  }
1815
3676
  text = `${text} ${prefix}${num} \u2013 `;
3677
+ if (type == "math") text = `${prefix}${num}`;
1816
3678
  } else text = `${prefix}${num}`;
1817
3679
  if (v instanceof Array) v.forEach((fn) => fn.f(num, prefix));
1818
- else if (v && tag != "#") logwarn(`id [${tag}] ${type == "code" ? "\u043B\u0438\u0441\u0442\u0438\u043D\u0433\u0430" : type == "image" ? "\u0440\u0438\u0441\u0443\u043D\u043A\u0430" : type == "table" ? "\u0442\u0430\u0431\u043B\u0438\u0446\u044B" : ""} \u0443\u0436\u0435 \u0437\u0430\u043D\u044F\u0442\u043E \u0447\u0435\u043C-\u0442\u043E \u0434\u0440\u0443\u0433\u0438\u043C`);
3680
+ else if (v && tag != "#") warnAt(node.sourceLine, `id [${tag}] ${type == "code" ? "\u043B\u0438\u0441\u0442\u0438\u043D\u0433\u0430" : type == "image" ? "\u0440\u0438\u0441\u0443\u043D\u043A\u0430" : type == "table" ? "\u0442\u0430\u0431\u043B\u0438\u0446\u044B" : type == "math" ? "\u0444\u043E\u0440\u043C\u0443\u043B\u044B" : ""} \u0443\u0436\u0435 \u0437\u0430\u043D\u044F\u0442\u043E \u0447\u0435\u043C-\u0442\u043E \u0434\u0440\u0443\u0433\u0438\u043C`);
1819
3681
  named[tag] = { n: num, prefix };
1820
3682
  rune.type = "text";
1821
3683
  rune.text = text;
@@ -1835,8 +3697,8 @@ function alchemist(doc, logwarn = console.warn) {
1835
3697
  rune.type = "text";
1836
3698
  rune.text = n < 0 ? prefix2 : `${prefix2}${applyMath(n)}`;
1837
3699
  };
1838
- if (v) v.push({ f, i: lastRefI++ });
1839
- else named[tag] = [{ f, i: lastRefI++ }];
3700
+ if (v) v.push({ f, i: lastRefI++, sourceLine: node.sourceLine });
3701
+ else named[tag] = [{ f, i: lastRefI++, sourceLine: node.sourceLine }];
1840
3702
  } else {
1841
3703
  rune.type = "text";
1842
3704
  rune.text = `${v.prefix}${applyMath(v.n)}`;
@@ -1844,21 +3706,22 @@ function alchemist(doc, logwarn = console.warn) {
1844
3706
  });
1845
3707
  }
1846
3708
  function crystallizeSources() {
1847
- const sourcesList = doc.nodes.find((node) => node.type == "list" && node.alternativeStyle);
3709
+ const sourcesList = doc.nodes.find((node) => node.type == "list" && node.tags?.includes("sources"));
1848
3710
  if (!sourcesList || sourcesList.type != "list") return 0;
1849
- const items = sourcesList.items.filter((v) => v.type == "listItem").map((v) => v.text).map((item, i) => {
3711
+ const items = sourcesList.items.filter((v) => v.type == "listItem").map((v, i) => {
3712
+ const item = v.text;
1850
3713
  const refI = item[0]?.text ? 0 : 1;
1851
3714
  const ref = item[refI];
1852
3715
  if (ref?.type != "ref") {
1853
- return { text: item, ref: "", i: 99999 + i };
3716
+ return { text: item, ref: "", i: 99999 + i, srcI: v.sourceLine };
1854
3717
  }
1855
3718
  const refs = named[ref.text];
1856
3719
  if (refs && !(refs instanceof Array)) {
1857
- logwarn(`id [${ref.text}] \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0430 \u0443\u0436\u0435 \u0437\u0430\u043D\u044F\u0442\u043E \u0447\u0435\u043C-\u0442\u043E \u0434\u0440\u0443\u0433\u0438\u043C`);
1858
- return { text: item.slice(refI + 1), ref: "", i: 99999 + i };
3720
+ warnAt(sourcesList.sourceLine, `id [${ref.text}] \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0430 \u0443\u0436\u0435 \u0437\u0430\u043D\u044F\u0442\u043E \u0447\u0435\u043C-\u0442\u043E \u0434\u0440\u0443\u0433\u0438\u043C`);
3721
+ return { text: item.slice(refI + 1), ref: "", i: 99999 + i, srcI: v.sourceLine };
1859
3722
  }
1860
- const firstOccurrenceI = Math.min(...refs.map((v) => v.i));
1861
- return { text: item.slice(refI + 1), ref: ref.text, i: firstOccurrenceI };
3723
+ const firstOccurrenceI = Math.min(...refs.map((v2) => v2.i));
3724
+ return { text: item.slice(refI + 1), ref: ref.text, i: firstOccurrenceI, srcI: v.sourceLine };
1862
3725
  });
1863
3726
  items.sort((a, b) => a.i - b.i);
1864
3727
  const hasIdAny = items.some((it) => !!it.ref);
@@ -1868,16 +3731,17 @@ function alchemist(doc, logwarn = console.warn) {
1868
3731
  named[key] = { prefix: "", n: i + 1 };
1869
3732
  if (refs instanceof Array) {
1870
3733
  if (hasIdAny && refs.length <= 1)
1871
- logwarn(`\u0412 \u0442\u0435\u043A\u0441\u0442\u0435 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0442 \u0441\u0441\u044B\u043B\u043A\u0438 \u043D\u0430 \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A: ${item.ref}`);
3734
+ warnAt(item.srcI, `\u0412 \u0442\u0435\u043A\u0441\u0442\u0435 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0442 \u0441\u0441\u044B\u043B\u043A\u0438 \u043D\u0430 \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A: ${item.ref}`);
1872
3735
  refs.forEach((r) => hasIdAny ? r.f(i + 1, "") : r.f(-1, `[${i + 1}]`));
1873
3736
  }
1874
- return { type: "listItem", text: item.text };
3737
+ return { type: "listItem", text: item.text, sourceLine: item.srcI };
1875
3738
  });
1876
3739
  return sourcesList.items.length;
1877
3740
  }
1878
3741
  }
1879
3742
  function addLazyNumbering(doc) {
1880
3743
  doc.nodes.forEach((node) => {
3744
+ if (node.type == "math" && !node.title) node.title = [{ text: "(", type: "text" }, { text: "#", type: "ref" }, { text: ")", type: "text" }];
1881
3745
  const runes = node.type == "code" || node.type == "table" ? node.title : node.type == "image" ? node.text : null;
1882
3746
  if (!runes) return;
1883
3747
  if (runes.find((rune) => rune.type == "ref")) return;
@@ -1899,6 +3763,8 @@ async function render({
1899
3763
  disableMacros = false,
1900
3764
  useLibreOffice = false,
1901
3765
  checkFilesIsInsidePath = "",
3766
+ templateOptions = { allowIncludes: true, allowLoops: true },
3767
+ variables,
1902
3768
  logwarn = console.warn,
1903
3769
  loginfo = console.info,
1904
3770
  logPS = (msg) => console.log(`PS: ${msg}`),
@@ -1906,23 +3772,23 @@ async function render({
1906
3772
  signal = null
1907
3773
  }) {
1908
3774
  signal?.throwIfAborted();
1909
- const fin = path4.resolve(file);
3775
+ const fin = path5.resolve(file);
1910
3776
  progress(10, "\u041F\u044B\u0442\u0430\u0435\u043C\u0441\u044F \u043F\u043E\u043D\u044F\u0442\u044C, \u0447\u0442\u043E \u0432\u044B \u0442\u0443\u0442 \u043D\u0430\u043F\u0438\u0441\u0430\u043B\u0438...");
1911
3777
  loginfo(`[!1] Parsing file ${fin}`);
1912
- const doc = await parseMD(fin, logwarn);
3778
+ const doc = await parseMD(fin, variables, templateOptions, checkFilesIsInsidePath, signal, logwarn);
1913
3779
  signal?.throwIfAborted();
1914
3780
  progress(10, "\u041A\u043E\u043B\u0434\u0443\u0435\u043C \u043D\u0430\u0434 \u0441\u0438\u043D\u0442\u0430\u043A\u0441\u0438\u0441\u043E\u043C...");
1915
3781
  loginfo("[!2] Processing document");
1916
3782
  enrichDoc(doc, logwarn);
1917
3783
  const runicDoc = runifyDoc(doc);
1918
3784
  alchemist(runicDoc, logwarn);
1919
- const pout = output && path4.parse(path4.resolve(output));
1920
- if (pout) await fs4.mkdir(pout.dir, { recursive: true });
1921
- const p = path4.parse(fin);
3785
+ const pout = output && path5.parse(path5.resolve(output));
3786
+ if (pout) await fs5.mkdir(pout.dir, { recursive: true });
3787
+ const p = path5.parse(fin);
1922
3788
  const fdir = p.dir;
1923
3789
  const fname = trimEnd(p.name, ".g");
1924
- const ftmp = path4.join(fdir, fname + ".tmp.docx");
1925
- const fout = pout ? path4.join(pout.dir, pout.name + ".docx") : path4.join(fdir, fname + ".docx");
3790
+ const ftmp = path5.join(fdir, fname + ".tmp.docx");
3791
+ const fout = pout ? path5.join(pout.dir, pout.name + ".docx") : path5.join(fdir, fname + ".docx");
1926
3792
  if (await checkIfFileIsBlocked(ftmp))
1927
3793
  throw new Error(`\u0417\u0430\u043A\u0440\u043E\u0439\u0442\u0435 docx \u0444\u0430\u0439\u043B. Output file is busy or locked: ${ftmp}`);
1928
3794
  if (await checkIfFileIsBlocked(fout))
@@ -1930,7 +3796,7 @@ async function render({
1930
3796
  signal?.throwIfAborted();
1931
3797
  progress(10, phrase_renderDocx());
1932
3798
  loginfo("[!3] Serializing to docx");
1933
- await serializeDocx(runicDoc, ftmp, fdir, assets, checkFilesIsInsidePath);
3799
+ await serializeDocx(runicDoc, ftmp, fdir, assets, checkFilesIsInsidePath, logwarn);
1934
3800
  let willRunMacros = !disableMacros && (renderPDF || hasReasonForRunningMacros(runicDoc));
1935
3801
  let limitedRender = false;
1936
3802
  if (process.platform != "win32" && willRunMacros && !useLibreOffice) {
@@ -1941,31 +3807,31 @@ async function render({
1941
3807
  signal?.throwIfAborted();
1942
3808
  progress(20, phrase_runMacros());
1943
3809
  loginfo("[!4] Starting macros");
1944
- const tmpfolder = path4.join(pout ? pout.dir : fdir, ".md2gost_out");
1945
- await fs4.rm(tmpfolder, { recursive: true, force: true });
3810
+ const tmpfolder = path5.join(pout ? pout.dir : fdir, ".md2gost_out");
3811
+ await fs5.rm(tmpfolder, { recursive: true, force: true });
1946
3812
  try {
1947
3813
  const macroRunner = useLibreOffice ? runUnoScript : runDocxMacro;
1948
3814
  const ok = await macroRunner(progress, logPS, logPSError, assets, fdir, ftmp, fout, renderPDF, signal);
1949
3815
  if (!ok) {
1950
- await fs4.rename(ftmp, fout);
3816
+ await fs5.rename(ftmp, fout);
1951
3817
  return { fout, err: "inPS" };
1952
3818
  }
1953
3819
  } catch (x) {
1954
3820
  if (signal?.aborted) {
1955
- await fs4.unlink(ftmp).catch(() => {
3821
+ await fs5.unlink(ftmp).catch(() => {
1956
3822
  });
1957
3823
  throw x;
1958
3824
  }
1959
- await fs4.rename(ftmp, fout);
3825
+ await fs5.rename(ftmp, fout);
1960
3826
  return { fout, err: "noPS", errS: x };
1961
3827
  }
1962
3828
  if (existsSync2(ftmp))
1963
- await fs4.unlink(ftmp);
3829
+ await fs5.unlink(ftmp);
1964
3830
  signal?.throwIfAborted();
1965
- const errorTxt = path4.join(tmpfolder, "error.txt");
3831
+ const errorTxt = path5.join(tmpfolder, "error.txt");
1966
3832
  if (existsSync2(errorTxt)) {
1967
- const err = await fs4.readFile(errorTxt);
1968
- await fs4.rm(tmpfolder, { recursive: true, force: true });
3833
+ const err = await fs5.readFile(errorTxt);
3834
+ await fs5.rm(tmpfolder, { recursive: true, force: true });
1969
3835
  return { fout, err: "vba", errS: `${err}` };
1970
3836
  }
1971
3837
  loginfo("[!5] Updating metadata");
@@ -1977,11 +3843,11 @@ async function render({
1977
3843
  if (!existsSync2(tmpfolder))
1978
3844
  return { fout, err: "pdf" };
1979
3845
  try {
1980
- const files = (await fs4.readdir(tmpfolder)).sort().map((f) => path4.join(tmpfolder, f));
1981
- const pdf = pout ? path4.join(pout.dir, pout.base) : path4.join(fdir, fname + ".pdf");
3846
+ const files = (await fs5.readdir(tmpfolder)).sort().map((f) => path5.join(tmpfolder, f));
3847
+ const pdf = pout ? path5.join(pout.dir, pout.name + ".pdf") : path5.join(fdir, fname + ".pdf");
1982
3848
  await mergePDFs(files, pdf, doc, signal || void 0);
1983
- await fs4.rm(tmpfolder, { recursive: true, force: true });
1984
- if (removeIntermediateDocx && existsSync2(fout)) await fs4.unlink(fout);
3849
+ await fs5.rm(tmpfolder, { recursive: true, force: true });
3850
+ if (removeIntermediateDocx && existsSync2(fout)) await fs5.unlink(fout);
1985
3851
  return { fout: pdf };
1986
3852
  } catch (err) {
1987
3853
  if (signal?.aborted) throw err;
@@ -1989,7 +3855,7 @@ async function render({
1989
3855
  }
1990
3856
  }
1991
3857
  } else {
1992
- await fs4.rename(ftmp, fout);
3858
+ await fs5.rename(ftmp, fout);
1993
3859
  }
1994
3860
  loginfo(`[!7] Rendered to ${fout}`);
1995
3861
  return { fout, err: limitedRender ? "noWin" : void 0 };
@@ -2023,8 +3889,8 @@ function killProcessTree(child, wordPid) {
2023
3889
  }
2024
3890
  }
2025
3891
  function runDocxMacro(progress, log, logError, assets, cwd, fin, fout, renderPDF, signal) {
2026
- const script = path4.join(assets, "run.ps1");
2027
- const templateMacro = path4.join(assets, "template.dotm");
3892
+ const script = path5.join(assets, "run.ps1");
3893
+ const templateMacro = path5.join(assets, "template.dotm");
2028
3894
  return new Promise((res, rej) => {
2029
3895
  if (signal?.aborted)
2030
3896
  return rej(signal.reason ?? new Error("Aborted before execution started"));
@@ -2100,7 +3966,7 @@ function getPythonExecutable() {
2100
3966
  return process.env.UNO_PYTHON_PATH || "python3";
2101
3967
  }
2102
3968
  function runUnoScript(progress, log, logError, assets, cwd, fin, fout, renderPDF, signal) {
2103
- const script = process.env.MACRO_ENGINE_PATH || path4.join(assets, "macro_engine.py");
3969
+ const script = process.env.MACRO_ENGINE_PATH || path5.join(assets, "macro_engine.py");
2104
3970
  const pythonExec = getPythonExecutable();
2105
3971
  return new Promise((res, rej) => {
2106
3972
  if (signal?.aborted) {
@@ -2164,31 +4030,61 @@ function runUnoScript(progress, log, logError, assets, cwd, fin, fout, renderPDF
2164
4030
  });
2165
4031
  }
2166
4032
  async function mergePDFs(files, fout, doc, signal) {
2167
- const mergedPdf = await PDFDocument.create();
4033
+ const pdfFiles = files.filter((file) => file.toLowerCase().endsWith(".pdf"));
4034
+ let mainPdfIndex = -1;
4035
+ for (let i = 0; i < pdfFiles.length; i++) {
4036
+ if (pdfFiles[i].toLowerCase().endsWith("-main.pdf"))
4037
+ mainPdfIndex = i;
4038
+ }
4039
+ let mergedPdf;
4040
+ if (mainPdfIndex >= 0) {
4041
+ const bytes = await fs5.readFile(pdfFiles[mainPdfIndex], { signal });
4042
+ mergedPdf = await PDFDocument.load(bytes);
4043
+ signal?.throwIfAborted();
4044
+ let insertionIndex = 0;
4045
+ for (const file of pdfFiles.slice(0, mainPdfIndex)) {
4046
+ const sourceBytes = await fs5.readFile(file, { signal });
4047
+ const pdf = await PDFDocument.load(sourceBytes);
4048
+ signal?.throwIfAborted();
4049
+ const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
4050
+ for (const page of pages)
4051
+ mergedPdf.insertPage(insertionIndex++, page);
4052
+ signal?.throwIfAborted();
4053
+ }
4054
+ for (const file of pdfFiles.slice(mainPdfIndex + 1)) {
4055
+ const sourceBytes = await fs5.readFile(file, { signal });
4056
+ const pdf = await PDFDocument.load(sourceBytes);
4057
+ signal?.throwIfAborted();
4058
+ const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
4059
+ pages.forEach((page) => mergedPdf.addPage(page));
4060
+ signal?.throwIfAborted();
4061
+ }
4062
+ } else {
4063
+ mergedPdf = await PDFDocument.create();
4064
+ for (const file of pdfFiles) {
4065
+ const bytes = await fs5.readFile(file, { signal });
4066
+ const pdf = await PDFDocument.load(bytes);
4067
+ signal?.throwIfAborted();
4068
+ const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
4069
+ pages.forEach((page) => mergedPdf.addPage(page));
4070
+ signal?.throwIfAborted();
4071
+ }
4072
+ }
2168
4073
  if (doc?.title) mergedPdf.setTitle(doc.title, { showInWindowTitleBar: true });
2169
4074
  if (doc?.author) mergedPdf.setAuthor(doc.author);
2170
4075
  if (doc?.ctime) mergedPdf.setCreationDate(doc.ctime);
2171
4076
  if (doc?.mtime) mergedPdf.setModificationDate(doc.mtime);
2172
- for (const file of files) {
2173
- if (!file.endsWith(".pdf")) continue;
2174
- const bytes = await fs4.readFile(file, { signal });
2175
- const pdf = await PDFDocument.load(bytes);
2176
- signal?.throwIfAborted();
2177
- const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
2178
- pages.forEach((page) => mergedPdf.addPage(page));
2179
- signal?.throwIfAborted();
2180
- }
2181
4077
  const pdfBytes = await mergedPdf.save();
2182
- await fs4.writeFile(fout, pdfBytes, { signal });
4078
+ await fs5.writeFile(fout, pdfBytes, { signal });
2183
4079
  }
2184
4080
  async function updateMetadata(docfile, doc) {
2185
- const content = await fs4.readFile(docfile);
4081
+ const content = await fs5.readFile(docfile);
2186
4082
  const zip = new PizZip(content);
2187
4083
  function replaceTag(xml, tag, value) {
2188
4084
  const regex = new RegExp(`(<${tag}\\b[^>]*>)(.*?)(</${tag}>)`, "gs");
2189
4085
  return xml.replace(
2190
4086
  regex,
2191
- (m, otag, old, ctag) => `${otag}${escapeXml(value)}${ctag}`
4087
+ (m, otag, old, ctag) => `${otag}${escapeXml2(value)}${ctag}`
2192
4088
  );
2193
4089
  }
2194
4090
  let coreXml = zip.file("docProps/core.xml").asText();
@@ -2202,8 +4098,8 @@ async function updateMetadata(docfile, doc) {
2202
4098
  appXml = replaceTag(appXml, "TotalTime", `${doc.etime || randomInt(30, 120)}`);
2203
4099
  zip.file("docProps/app.xml", appXml);
2204
4100
  const buffer = zip.generate({ type: "nodebuffer" });
2205
- await fs4.writeFile(docfile, buffer);
2206
- function escapeXml(unsafe) {
4101
+ await fs5.writeFile(docfile, buffer);
4102
+ function escapeXml2(unsafe) {
2207
4103
  return unsafe.replaceAll(/[<>&"']/g, (ch) => {
2208
4104
  switch (ch) {
2209
4105
  case "<":
@@ -2274,11 +4170,26 @@ async function renderMarkdown(config) {
2274
4170
  assert(typeof config.useLibreOffice == "boolean" || typeof config.useLibreOffice == "undefined", "useLibreOffice must be a boolean or undefined.");
2275
4171
  assert(typeof config.checkFilesIsInsidePath == "string" || config.checkFilesIsInsidePath === false || typeof config.checkFilesIsInsidePath == "undefined", "checkFilesIsInsidePath must be a string, false or undefined.");
2276
4172
  assert(typeof config.progress == "function" || typeof config.progress == "undefined", "Progress callback must be a function or undefined.");
4173
+ assert(typeof config.variables == "object" && config.variables !== null && !Array.isArray(config.variables) || typeof config.variables == "undefined", "variables must be an object or undefined.");
4174
+ assert(typeof config.templateOptions == "object" && config.templateOptions !== null && !Array.isArray(config.templateOptions) || typeof config.templateOptions == "undefined", "templateOptions must be an object or undefined.");
4175
+ if (config.templateOptions) {
4176
+ const options = config.templateOptions;
4177
+ const booleanProps = ["allowLoops", "allowIncludes"];
4178
+ const limitProps = ["maxLoopIterations", "maxTotalIterations", "maxDepth", "maxIncludeDepth", "maxIncludes", "maxFileLength", "maxTotalInputLength", "maxGeneratedLength"];
4179
+ const extraProps = getExtraProperties(options, [...booleanProps, ...limitProps]);
4180
+ assert(extraProps.length == 0, `Found unknown properties in config.templateOptions: ${extraProps.join(", ")}`);
4181
+ booleanProps.forEach(
4182
+ (p) => assert(options[p] === void 0 || typeof options[p] == "boolean", `config.templateOptions.${p} must be a boolean or undefined.`)
4183
+ );
4184
+ limitProps.forEach(
4185
+ (p) => assert(options[p] === void 0 || Number.isSafeInteger(options[p]) && options[p] >= 0, `config.templateOptions.${p} must be a non-negative safe integer or undefined.`)
4186
+ );
4187
+ }
2277
4188
  assert(config.abortSignal instanceof AbortSignal || typeof config.abortSignal == "undefined", "abortSignal must be an AbortSignal or undefined.");
2278
4189
  {
2279
4190
  const extraProps = getExtraProperties(
2280
4191
  config,
2281
- ["input", "output", "format", "keepIntermediateDocx", "disableMacros", "useLibreOffice", "progress", "logger", "abortSignal", "checkFilesIsInsidePath"]
4192
+ ["input", "output", "format", "keepIntermediateDocx", "disableMacros", "useLibreOffice", "progress", "logger", "abortSignal", "checkFilesIsInsidePath", "variables", "templateOptions"]
2282
4193
  );
2283
4194
  assert(extraProps.length == 0, `Found unknown properties in config: ${extraProps.join(", ")}`);
2284
4195
  }
@@ -2292,20 +4203,25 @@ async function renderMarkdown(config) {
2292
4203
  (p) => assert(typeof logger[p] == "function", `config.logger.${p} must be a function.`)
2293
4204
  );
2294
4205
  }
2295
- const renderPDF = config.format === "pdf" || !config.format && !!config.output?.endsWith(".pdf");
4206
+ const outputExtension = config.output && path6.extname(config.output).toLowerCase();
4207
+ const renderPDF = config.format === "pdf" || !config.format && outputExtension === ".pdf";
2296
4208
  const removeIntermediateDocx = !config.keepIntermediateDocx;
2297
4209
  const disableMacros = !!config.disableMacros;
2298
4210
  const useLibreOffice = !!config.useLibreOffice;
2299
4211
  const checkFilesIsInsidePath = config.checkFilesIsInsidePath === void 0 ? "" : config.checkFilesIsInsidePath;
2300
4212
  assert(!(disableMacros && renderPDF), "Macros must be enabled to render PDF output.");
2301
- const pin = path5.parse(path5.resolve(config.input));
4213
+ if (outputExtension && outputExtension !== ".docx" && outputExtension !== ".pdf")
4214
+ throw new TypeError("Output file extension must be either '.docx' or '.pdf'.");
4215
+ if (outputExtension && outputExtension !== (renderPDF ? ".pdf" : ".docx"))
4216
+ throw new TypeError(`Output file extension must match the selected '${renderPDF ? "pdf" : "docx"}' format.`);
4217
+ const pin = path6.parse(path6.resolve(config.input));
2302
4218
  const fname = pin.name.endsWith(".g") ? pin.name.slice(0, -2) : pin.name;
2303
4219
  let resolvedOutput = config.output;
2304
4220
  if (resolvedOutput) {
2305
- const isDir = resolvedOutput.endsWith("/") || resolvedOutput.endsWith("\\") || !path5.extname(resolvedOutput);
4221
+ const isDir = resolvedOutput.endsWith("/") || resolvedOutput.endsWith("\\") || !path6.extname(resolvedOutput);
2306
4222
  if (isDir) {
2307
4223
  const ext = renderPDF ? ".pdf" : ".docx";
2308
- resolvedOutput = path5.join(resolvedOutput, fname + ext);
4224
+ resolvedOutput = path6.join(resolvedOutput, fname + ext);
2309
4225
  }
2310
4226
  }
2311
4227
  const warnings = [];
@@ -2330,6 +4246,8 @@ async function renderMarkdown(config) {
2330
4246
  disableMacros,
2331
4247
  useLibreOffice,
2332
4248
  checkFilesIsInsidePath,
4249
+ variables: config.variables,
4250
+ templateOptions: config.templateOptions,
2333
4251
  loginfo: logger?.info,
2334
4252
  logwarn: (msg) => {
2335
4253
  logger?.warn(msg);
@@ -2345,7 +4263,6 @@ async function renderMarkdown(config) {
2345
4263
  },
2346
4264
  signal: config.abortSignal
2347
4265
  });
2348
- config.progress?.(100, "Done!");
2349
4266
  if (useLibreOffice) {
2350
4267
  if (err == "noPS") throwMDE2("noPS", `Failed to run the LibreOffice binary (soffice). Ensure LibreOffice is installed and added to your system's PATH environment variable.`);
2351
4268
  if (err == "inPS") throwMDE2("inPS", `An unexpected error occurred executing the LibreOffice conversion command. Verify that LibreOffice is properly installed, up to date, and has permission to access the target files.`);
@@ -2356,10 +4273,11 @@ async function renderMarkdown(config) {
2356
4273
  if (err == "vba") throwMDE2("vba", `Microsoft Word VBA Macro execution failed: ${errS}`);
2357
4274
  if (err == "pdf") throwMDE2("pdf", `Failed to export document to PDF.`);
2358
4275
  if (err == "noWin") throwMDE2("noWin", `Platform restriction: Extended formatting macros and PDF rendering are only supported natively on Windows systems.`);
4276
+ config.progress?.(100, "Done!");
2359
4277
  let intermediateDocxPath;
2360
4278
  if (renderPDF && !removeIntermediateDocx) {
2361
- const pout = resolvedOutput ? path5.parse(path5.resolve(resolvedOutput)) : void 0;
2362
- intermediateDocxPath = pout ? path5.join(pout.dir, pout.name + ".docx") : path5.join(pin.dir, fname + ".docx");
4279
+ const pout = resolvedOutput ? path6.parse(path6.resolve(resolvedOutput)) : void 0;
4280
+ intermediateDocxPath = pout ? path6.join(pout.dir, pout.name + ".docx") : path6.join(pin.dir, fname + ".docx");
2363
4281
  }
2364
4282
  return {
2365
4283
  filePath: fout,
@@ -2417,10 +4335,7 @@ var MDRenderError = class _MDRenderError extends Error {
2417
4335
  }
2418
4336
  };
2419
4337
  function getAssetsDir() {
2420
- if (typeof __dirname !== "undefined")
2421
- return path5.resolve(__dirname, "../assets");
2422
- const metaUrl = new Function("return import.meta.url")();
2423
- return path5.resolve(path5.dirname(fileURLToPath(metaUrl)), "../assets");
4338
+ return path6.resolve(__MD2GOST_DIRNAME__, "../assets");
2424
4339
  }
2425
4340
  function getExtraProperties(configObj, allowedConfigKeys) {
2426
4341
  return Object.keys(configObj).filter(
@@ -2429,6 +4344,7 @@ function getExtraProperties(configObj, allowedConfigKeys) {
2429
4344
  }
2430
4345
  export {
2431
4346
  MDRenderError,
4347
+ UserInputError,
2432
4348
  renderMarkdown as default
2433
4349
  };
2434
4350
  //# sourceMappingURL=index.mjs.map