md2gost 1.5.0 → 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;
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();
626
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
  })
@@ -939,15 +2505,18 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
939
2505
  ] : [],
940
2506
  new Table({
941
2507
  // width: { type: "pct", size: 100 },
942
- width: { type: "dxa", size: 9572 },
2508
+ width: {
2509
+ type: "dxa",
2510
+ size: convertMillimetersToTwip(section.orientation === "landscape" ? 255.8 : 168.8)
2511
+ },
943
2512
  rows: node.rows.map((row, rowI) => new TableRow({
944
- tableHeader: rowI == 0,
2513
+ tableHeader: node.header !== false && rowI == 0,
945
2514
  cantSplit: true,
946
2515
  children: row.map((item, colI) => new TableCell({
947
2516
  children: item.type != "text" ? renderNodeL2(item) : [
948
2517
  new Paragraph({
949
- children: renderText(item.text, node.normalFontSize ? void 0 : doc.table.text.size),
950
- 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",
951
2520
  indent: { firstLine: 0 },
952
2521
  spacing: { after: 0, line: 240 * doc.table.text.line_spacing }
953
2522
  })
@@ -965,13 +2534,13 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
965
2534
  new Paragraph({ children: [new PageBreak()] })
966
2535
  ];
967
2536
  case "image":
968
- const type = node.src.split(".").at(-1) || "";
2537
+ const type = (node.src.split(".").at(-1) || "").toLowerCase();
969
2538
  if (!["jpg", "png", "gif", "bmp", "svg"].includes(type))
970
2539
  throw new UserInputError(`Unsupported image format: "${type}", file: ${node.src}`);
971
2540
  const img_path = getPath(node.src);
972
- if (!fs3.existsSync(img_path))
2541
+ if (!fs4.existsSync(img_path))
973
2542
  throw new UserInputError(`File not exist: ${node.src}`);
974
- const data = fs3.readFileSync(img_path);
2543
+ const data = fs4.readFileSync(img_path);
975
2544
  const dimensions = imageSize(data);
976
2545
  const [MaxW, MaxH] = section.orientation == "landscape" ? [950, 600] : [600, 900];
977
2546
  let [width, height] = [dimensions.width, dimensions.height];
@@ -1003,7 +2572,7 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1003
2572
  }),
1004
2573
  ...node.text ? [
1005
2574
  new Paragraph({
1006
- children: renderText(node.text, doc.img.text.size),
2575
+ children: renderText(node.text, node.sourceLine, doc.img.text.size),
1007
2576
  alignment: "center",
1008
2577
  indent: { firstLine: 0 },
1009
2578
  spacing: {
@@ -1018,7 +2587,7 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1018
2587
  return [
1019
2588
  ...node.title ? [
1020
2589
  new Paragraph({
1021
- children: renderText(node.title, doc.code.title.size),
2590
+ children: renderText(node.title, node.sourceLine, doc.code.title.size),
1022
2591
  style: STYLE_code_title
1023
2592
  })
1024
2593
  ] : prevNode?.type == "code" ? [
@@ -1030,11 +2599,44 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1030
2599
  ...i == 0 && !node.title && doc.code.spacing.before && prevNode?.type != "code" ? { spacing: { before: doc.code.spacing.before * 20 } } : {}
1031
2600
  }))
1032
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
+ ];
1033
2635
  case "externalDoc":
1034
2636
  const doc_path = getPath(node.path);
1035
- if (!fs3.existsSync(doc_path))
2637
+ if (!fs4.existsSync(doc_path))
1036
2638
  throw new UserInputError(`File not exist: ${node.path}`);
1037
- const ext = path3.extname(doc_path);
2639
+ const ext = path4.extname(doc_path);
1038
2640
  if (ext != ".docx" && ext != ".pdf")
1039
2641
  throw new UserInputError(`File not .docx or .pdf: ${node.path}`);
1040
2642
  return new Paragraph({
@@ -1042,6 +2644,39 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1042
2644
  indent: { firstLine: 0 },
1043
2645
  alignment: "left"
1044
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
+ ];
1045
2680
  case "sectionBreak":
1046
2681
  return [];
1047
2682
  default:
@@ -1049,12 +2684,20 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1049
2684
  throw new Error("switch default");
1050
2685
  }
1051
2686
  }, renderNodeL2 = function(node, prevChild, prevNode) {
1052
- const r = renderNode2(node, prevChild, prevNode);
1053
- if (r instanceof Array) return r;
1054
- 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
+ }
1055
2696
  };
1056
- var renderNode = renderNode2, renderNodeL = renderNodeL2;
2697
+ var renderHeaderFooter = renderHeaderFooter2, renderHeaderFooterTable = renderHeaderFooterTable2, renderNode = renderNode2, renderNodeL = renderNodeL2;
1057
2698
  const children = [];
2699
+ const header = renderHeaderFooter2(section.header, false);
2700
+ const footer = renderHeaderFooter2(section.footer, true);
1058
2701
  sections.push({
1059
2702
  children,
1060
2703
  properties: {
@@ -1082,20 +2725,8 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1082
2725
  }
1083
2726
  }
1084
2727
  },
1085
- footers: {
1086
- default: new Footer({
1087
- children: [
1088
- new Paragraph({
1089
- alignment: AlignmentType.CENTER,
1090
- children: [new TextRun({
1091
- children: section.displayPageNum ? [PageNumber.CURRENT] : []
1092
- })],
1093
- indent: { firstLine: 0 },
1094
- spacing: { line: 240 }
1095
- })
1096
- ]
1097
- })
1098
- }
2728
+ ...header ? { headers: { default: header } } : {},
2729
+ ...footer ? { footers: { default: footer } } : {}
1099
2730
  });
1100
2731
  for (let i = 0; i < section.nodes.length; i++)
1101
2732
  children.push(...renderNodeL2(section.nodes[i], children.at(-1), section.nodes[i - 1]));
@@ -1130,8 +2761,8 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1130
2761
  })
1131
2762
  }
1132
2763
  ]);
1133
- fs3.writeFileSync(fout, buffer);
1134
- function renderText(text, size) {
2764
+ fs4.writeFileSync(fout, buffer);
2765
+ function renderText(text, sourceLine, size) {
1135
2766
  function renderRune(rune, link = false) {
1136
2767
  if (rune.anchor) return new Bookmark({
1137
2768
  id: rune.anchor,
@@ -1141,8 +2772,12 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1141
2772
  const children2 = [renderRune({ ...rune, link: void 0 }, true)];
1142
2773
  return rune.link.startsWith("#") ? new InternalHyperlink({ children: children2, anchor: rune.link.slice(1) }) : new ExternalHyperlink({ children: children2, link: rune.link });
1143
2774
  }
2775
+ if (rune.type == "math") return renderMath(rune.text, false, sourceLine);
1144
2776
  let children = null;
1145
- 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") {
1146
2781
  rune.type = "text";
1147
2782
  children = doc.totalPagesOffset == 0 ? [PageNumber.TOTAL_PAGES] : [
1148
2783
  // Complex Formula Field: { = { NUMPAGES } + 1 }
@@ -1226,7 +2861,36 @@ async function serializeDocx(doc, fout, workdir, assets, checkFilesIsInsidePath)
1226
2861
  }
1227
2862
  }
1228
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
+ }
1229
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
+ };
1230
2894
  function addListItemLevel(levels, level, startIndex, itemCount, ordered, alternativeStyle, mark) {
1231
2895
  if (levels.find((v) => v.level == level)) return;
1232
2896
  levels.sort((a, b) => a.level - b.level);
@@ -1265,7 +2929,7 @@ function addListItemLevel(levels, level, startIndex, itemCount, ordered, alterna
1265
2929
  }
1266
2930
  });
1267
2931
  }
1268
- var RawXml = class extends XmlComponent {
2932
+ var RawXml = class extends XmlComponent2 {
1269
2933
  constructor(tag, attributes = {}, text = null) {
1270
2934
  super(tag);
1271
2935
  if (Object.keys(attributes).length > 0)
@@ -1275,60 +2939,158 @@ var RawXml = class extends XmlComponent {
1275
2939
  }
1276
2940
  };
1277
2941
  function renderCodeHighlighting(code, lang) {
1278
- const grammar = Prism.languages[lang.trim().toLowerCase()];
2942
+ const language = lang.trim().toLowerCase();
2943
+ const grammar = Prism.languages[language];
1279
2944
  if (!grammar) return null;
1280
2945
  const themeColors = {
1281
2946
  // shared
1282
2947
  "plain": "000000",
1283
2948
  "comment": "008000",
2949
+ "hashbang": "008000",
2950
+ "shebang": "008000",
1284
2951
  "punctuation": "000000",
1285
2952
  "operator": "000000",
2953
+ "script": "000000",
2954
+ "script-punctuation": "000000",
2955
+ "expression": "000000",
2956
+ "interpolation": "000000",
1286
2957
  // keywords & control flow
1287
2958
  "keyword": "0000ff",
2959
+ "control-flow": "af00db",
1288
2960
  "boolean": "0000ff",
1289
- "builtin": "0000ff",
2961
+ "null": "0000ff",
2962
+ "builtin": "267f99",
1290
2963
  "important": "0000ff",
2964
+ // "instruction": "0000ff",
2965
+ // "directive": "0000ff",
2966
+ "directive-hash": "0000ff",
2967
+ "macro": "0000ff",
2968
+ "macro-name": "0000ff",
2969
+ "rule": "0000ff",
1291
2970
  // literals
1292
- "string": lang === "css" ? "0451a5" : lang === "json" ? "a31515" : "a31515",
1293
- "number": "098658",
1294
- "constant": "0000ff",
1295
- "symbol": "0000ff",
2971
+ "string": "a31515",
2972
+ "template-string": "a31515",
2973
+ "template-punctuation": "a31515",
2974
+ "string-literal": "a31515",
1296
2975
  "char": "a31515",
2976
+ "number": "098658",
2977
+ "datetime": "098658",
2978
+ "constant": "0070c1",
2979
+ "symbol": "0070c1",
2980
+ "scalar": "0000ff",
2981
+ "conversion-option": "0000ff",
1297
2982
  // identifiers
1298
- "class-name": lang === "js" || lang === "ts" ? "267f99" : "0451a5",
1299
- "function": "0451a5",
1300
- "variable": "000000",
1301
- "namespace": "000000",
1302
- "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",
1303
3004
  // markup / HTML / XML
1304
- "tag": lang === "html" || lang === "xml" ? "800000" : "0000ff",
3005
+ "tag": "800000",
1305
3006
  "selector": "800000",
1306
3007
  "attr-name": "e50000",
1307
- "attr-value": "a31515",
3008
+ "attr-value": "0000ff",
1308
3009
  "entity": "800000",
1309
3010
  "doctype": "808080",
3011
+ "doctype-tag": "808080",
3012
+ "name": "808080",
1310
3013
  "cdata": "808080",
1311
3014
  "prolog": "808080",
3015
+ "delimiter": language === "php" ? "800000" : "a31515",
1312
3016
  // CSS specific
1313
3017
  "atrule": "0000ff",
1314
3018
  "url": "0451a5",
1315
3019
  // regex
1316
- "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"
1317
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
+ ]);
1318
3078
  const tokens = Prism.tokenize(code, grammar);
1319
3079
  return tokensToParagraphs(tokens);
1320
3080
  function tokensToParagraphs(tokens2) {
1321
3081
  const paragraphs = [];
1322
3082
  let currentRuns = [];
1323
- function addText(text, type) {
3083
+ const plainStyle = { color: themeColors.plain, bold: false, italics: false, strike: false };
3084
+ function addText(text, style) {
1324
3085
  const lines = text.split("\n");
1325
3086
  lines.forEach((line, index) => {
1326
3087
  currentRuns.push(
1327
3088
  new TextRun({
1328
3089
  text: line,
1329
- color: themeColors[type] || themeColors["plain"],
1330
- bold: type === "bold",
1331
- italics: type === "italic"
3090
+ color: style.color,
3091
+ bold: style.bold,
3092
+ italics: style.italics,
3093
+ strike: style.strike
1332
3094
  })
1333
3095
  );
1334
3096
  if (index < lines.length - 1) {
@@ -1337,15 +3099,28 @@ function renderCodeHighlighting(code, lang) {
1337
3099
  }
1338
3100
  });
1339
3101
  }
1340
- function process2(token, parentType = "plain") {
3102
+ function process2(token, parentStyle = plainStyle) {
1341
3103
  if (typeof token === "string")
1342
- addText(token, parentType);
1343
- else if (Array.isArray(token.content))
1344
- token.content.forEach((t) => process2(t, token.type));
1345
- else if (typeof token.content === "string")
1346
- addText(token.content, token.type);
1347
- else
1348
- 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
+ }
1349
3124
  }
1350
3125
  tokens2.forEach((t) => process2(t));
1351
3126
  if (currentRuns.length > 0) {
@@ -1354,6 +3129,37 @@ function renderCodeHighlighting(code, lang) {
1354
3129
  return paragraphs;
1355
3130
  }
1356
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
+ }
1357
3163
  function genXml_app({ totalTime }) {
1358
3164
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1359
3165
  <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
@@ -1361,20 +3167,29 @@ function genXml_app({ totalTime }) {
1361
3167
  </Properties>
1362
3168
  `;
1363
3169
  }
3170
+ function escapeXml(value) {
3171
+ return value.replaceAll(/[<>&"']/g, (ch) => ({
3172
+ "<": "&lt;",
3173
+ ">": "&gt;",
3174
+ "&": "&amp;",
3175
+ '"': "&quot;",
3176
+ "'": "&apos;"
3177
+ })[ch]);
3178
+ }
1364
3179
  function genXml_core({ title, creator, createdAt, modifiedAt }) {
1365
3180
  if (!createdAt) createdAt = /* @__PURE__ */ new Date();
1366
3181
  if (!modifiedAt) modifiedAt = /* @__PURE__ */ new Date();
1367
3182
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1368
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">
1369
- <dc:title>${title || "Document"}</dc:title>
1370
- <dc:creator>${creator || "Student"}</dc:creator>
1371
- <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>
1372
3187
  <dcterms:created xsi:type="dcterms:W3CDTF">${createdAt.toISOString()}</dcterms:created>
1373
3188
  <dcterms:modified xsi:type="dcterms:W3CDTF">${modifiedAt.toISOString()}</dcterms:modified>
1374
3189
  </cp:coreProperties>`;
1375
3190
  }
1376
3191
  function genXml_style(assets, doc) {
1377
- let xml = fs3.readFileSync(path3.join(assets, "styles.xml"), { encoding: "utf8" });
3192
+ let xml = fs4.readFileSync(path4.join(assets, "styles.xml"), { encoding: "utf8" });
1378
3193
  xml = editStyle(`<w:style w:type="paragraph" w:default="1" w:styleId="a2">`, (xml2) => {
1379
3194
  xml2 = replaceTag(xml2, `<w:spacing w:line="${doc.text.line_spacing * 240}" w:lineRule="auto" w:after="${doc.text.spacing.after * 20}"/>`);
1380
3195
  xml2 = replaceTag(xml2, `<w:ind w:firstLine="${convertMillimetersToTwip(doc.text.indent * 10)}"/>`);
@@ -1399,7 +3214,33 @@ function genXml_style(assets, doc) {
1399
3214
  xml2 = addrPrStyle(xml2, doc.table.title.style == "bold" ? "<w:b/>" : "<w:i/>");
1400
3215
  return xml2;
1401
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
+ }
1402
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
+ }
1403
3244
  function replaceTag(xml2, tag) {
1404
3245
  const tagName = /<([^\s]+)\b/.exec(tag)?.[1];
1405
3246
  if (!tagName) throw err();
@@ -1439,13 +3280,14 @@ function genXml_style(assets, doc) {
1439
3280
 
1440
3281
  // src/enricher.ts
1441
3282
  function enrichDoc(doc, logwarn = console.warn) {
3283
+ const warnAt = (line, message) => logwarn(`Line ${doc.mapSourceLine(line)}: ${message}`);
1442
3284
  for (let i = 0; i < doc.nodes.length; i++) {
1443
3285
  const node = doc.nodes[i];
1444
- if (node.type == "title" && node.text.toUpperCase().startsWith("\u0422\u0418\u0422\u0423\u041B\u042C\u041D\u0418\u041A")) {
1445
- doc.nodes.splice(i, 1, { type: "text", text: node.text.toUpperCase() });
1446
- 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 });
1447
3289
  i++;
1448
- } 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") {
1449
3291
  node.level = 0;
1450
3292
  node.text = node.text.toUpperCase();
1451
3293
  const nextNode = doc.nodes[i + 1];
@@ -1453,81 +3295,83 @@ function enrichDoc(doc, logwarn = console.warn) {
1453
3295
  nextNode.noIndent = true;
1454
3296
  nextNode.text = trimEnd(nextNode.text, ".").toUpperCase();
1455
3297
  }
1456
- 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 });
1457
3299
  while (i + 1 < doc.nodes.length && doc.nodes[i + 1]?.type != "title") i++;
1458
- doc.nodes.splice(i + 1, 0, { type: "pageBreak" });
1459
- } 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") {
1460
3302
  node.level = 0;
1461
3303
  node.text = node.text.toUpperCase();
1462
- doc.nodes.splice(i + 1, 0, { type: "tableOfContents" });
1463
- } 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") {
1464
3306
  node.center = true;
1465
3307
  node.text = node.text.toUpperCase();
1466
- 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");
1467
3309
  const list = doc.nodes[i + 1];
1468
3310
  if (list?.type != "list") {
1469
- warn();
3311
+ warn(node.sourceLine);
1470
3312
  continue;
1471
3313
  }
1472
- 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")];
1473
3315
  const align = ["l", "l"];
1474
3316
  for (const item of list.items) {
1475
3317
  if (item.type != "listItem") {
1476
- warn();
3318
+ warn(item.sourceLine);
1477
3319
  continue;
1478
3320
  }
1479
3321
  const i2 = item.text.indexOf(":");
1480
3322
  if (i2 < 0) {
1481
- warn();
3323
+ warn(item.sourceLine);
1482
3324
  continue;
1483
3325
  }
1484
- 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()));
1485
3327
  }
1486
- 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." });
1487
- doc.nodes.splice(i + 2, 0, { type: "table", align, rows, normalFontSize: true, tags: ["definitions_table"] });
1488
- doc.nodes.splice(i + 3, 0, { type: "pageBreak" });
1489
- } 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") {
1490
3332
  node.center = true;
1491
3333
  node.text = node.text.toUpperCase();
1492
- 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");
1493
3335
  const list = doc.nodes[i + 1];
1494
3336
  if (list?.type != "list") {
1495
- warn();
3337
+ warn(node.sourceLine);
1496
3338
  continue;
1497
3339
  }
1498
3340
  const items = [];
1499
3341
  for (const item of list.items) {
1500
3342
  if (item.type != "listItem") {
1501
- warn();
3343
+ warn(item.sourceLine);
1502
3344
  continue;
1503
3345
  }
1504
- 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 });
1505
3347
  }
1506
- 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." });
1507
- 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 });
1508
3350
  doc.nodes.splice(i + 2, 0, ...items);
1509
- } 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") {
1510
3352
  node.center = true;
1511
3353
  node.text = node.text.toUpperCase();
1512
3354
  for (let j = i + 1; j < doc.nodes.length; j++) {
1513
3355
  const node2 = doc.nodes[j];
1514
3356
  if (node2?.type == "title") {
1515
- doc.nodes.splice(j, 0, { type: "pageBreak" });
3357
+ doc.nodes.splice(j, 0, { type: "pageBreak", sourceLine: -1 });
1516
3358
  break;
1517
3359
  }
1518
3360
  }
1519
- } 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") {
1520
3362
  node.center = true;
1521
3363
  node.text = node.text.toUpperCase();
1522
- doc.nodes.splice(i, 0, { type: "pageBreak" });
3364
+ doc.nodes.splice(i, 0, { type: "pageBreak", sourceLine: node.sourceLine });
1523
3365
  i++;
1524
- } 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") {
1525
3367
  node.center = true;
1526
3368
  node.text = node.text.toUpperCase();
1527
- doc.nodes.splice(i++, 0, { type: "pageBreak" });
3369
+ doc.nodes.splice(i++, 0, { type: "pageBreak", sourceLine: node.sourceLine });
1528
3370
  const nextNode = doc.nodes[i + 1];
1529
3371
  if (nextNode?.type == "list") {
1530
3372
  nextNode.alternativeStyle = true;
3373
+ nextNode.tags = nextNode.tags || [];
3374
+ nextNode.tags.push("sources");
1531
3375
  i++;
1532
3376
  continue;
1533
3377
  }
@@ -1536,35 +3380,38 @@ function enrichDoc(doc, logwarn = console.warn) {
1536
3380
  mark: ".",
1537
3381
  startIndex: 1,
1538
3382
  alternativeStyle: true,
3383
+ tags: ["sources"],
1539
3384
  ordered: true,
1540
- items: []
3385
+ items: [],
3386
+ sourceLine: node.sourceLine + 1
1541
3387
  };
1542
3388
  const startI = i + 1;
1543
3389
  while (true) {
1544
3390
  const textNode = doc.nodes[i + 1];
1545
3391
  if (textNode?.type != "text") break;
1546
3392
  const items = textNode.text.split(/\n\s*(?=\[[a-zA-Zа-яА-ЯёЁ_\d]+\])/);
3393
+ let sourceLine = textNode.sourceLine;
1547
3394
  for (const item of items)
1548
- list.items.push({ type: "listItem", text: item });
3395
+ list.items.push({ type: "listItem", text: item, sourceLine: sourceLine++ });
1549
3396
  i++;
1550
3397
  }
1551
3398
  if (list.items.length > 0)
1552
3399
  doc.nodes.splice(startI, i - startI + 1, list);
1553
- } 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") {
1554
3401
  node.level = 0;
1555
3402
  node.center = true;
1556
3403
  node.text = node.text.toUpperCase();
1557
- doc.nodes.splice(i++, 0, { type: "pageBreak" });
3404
+ doc.nodes.splice(i++, 0, { type: "pageBreak", sourceLine: node.sourceLine });
1558
3405
  for (let j = i + 1; j < doc.nodes.length; j++) {
1559
3406
  const node2 = doc.nodes[j];
1560
3407
  if (node2.type == "title") {
1561
- 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 });
1562
3409
  j++;
1563
3410
  node2.level = 1;
1564
3411
  const splitI = node2.text.indexOf("]");
1565
- doc.nodes.splice(j++, 0, { type: "pageBreak" });
3412
+ doc.nodes.splice(j++, 0, { type: "pageBreak", sourceLine: node2.sourceLine });
1566
3413
  if (splitI >= 0) {
1567
- 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 });
1568
3415
  node2.text = "\u041F\u0420\u0418\u041B\u041E\u0416\u0415\u041D\u0418\u0415 " + node2.text.slice(0, splitI + 1);
1569
3416
  node2.center = true;
1570
3417
  }
@@ -1600,11 +3447,11 @@ function enrichDoc(doc, logwarn = console.warn) {
1600
3447
  } else if (node.type == "externalDoc") {
1601
3448
  const nextNode = doc.nodes[i + 1];
1602
3449
  if (nextNode && nextNode?.type != "externalDoc" && nextNode?.type != "pageBreak" && nextNode?.type != "sectionBreak") {
1603
- doc.nodes.splice(i + 1, 0, { type: "pageBreak" });
3450
+ doc.nodes.splice(i + 1, 0, { type: "pageBreak", sourceLine: node.sourceLine });
1604
3451
  }
1605
3452
  const prevNode = doc.nodes[i - 1];
1606
3453
  if (prevNode && prevNode?.type != "externalDoc" && prevNode?.type != "pageBreak" && prevNode?.type != "sectionBreak") {
1607
- doc.nodes.splice(i, 0, { type: "pageBreak" });
3454
+ doc.nodes.splice(i, 0, { type: "pageBreak", sourceLine: node.sourceLine });
1608
3455
  i++;
1609
3456
  }
1610
3457
  }
@@ -1613,7 +3460,7 @@ function enrichDoc(doc, logwarn = console.warn) {
1613
3460
 
1614
3461
  // src/main.ts
1615
3462
  import { exec, spawn } from "child_process";
1616
- import fs4 from "fs/promises";
3463
+ import fs5 from "fs/promises";
1617
3464
  import { existsSync as existsSync2 } from "fs";
1618
3465
  import { PDFDocument } from "pdf-lib";
1619
3466
 
@@ -1624,12 +3471,15 @@ function alchemist(doc, logwarn = console.warn) {
1624
3471
  codesAll: 0,
1625
3472
  imgsAll: 0,
1626
3473
  tablesAll: 0,
3474
+ formulasAll: 0,
1627
3475
  codes: 0,
1628
3476
  imgs: 0,
1629
3477
  tables: 0,
3478
+ formulas: 0,
1630
3479
  titles: { l1: 0, l2: 0, l3: 0, l4: 0, l5: 0 }
1631
3480
  };
1632
3481
  const named = {};
3482
+ const warnAt = (line, message) => logwarn(`${line ? `Line ${doc.mapSourceLine(line)}: ` : ""}${message}`);
1633
3483
  const vals = {};
1634
3484
  let lastRefI = 0;
1635
3485
  let prevNum = -1;
@@ -1646,11 +3496,13 @@ function alchemist(doc, logwarn = console.warn) {
1646
3496
  let codes = counter.codes;
1647
3497
  let imgs = counter.imgs;
1648
3498
  let tables = counter.tables;
3499
+ let formulas = counter.formulas;
1649
3500
  for (let j = i; j < doc.nodes.length && nextNum < 0; j++) {
1650
3501
  const n = doc.nodes[j];
1651
3502
  if (n.type == "code") nextNum = codes + 1;
1652
3503
  if (n.type == "image") nextNum = imgs + 1;
1653
3504
  if (n.type == "table") nextNum = tables + 1;
3505
+ if (n.type == "math") nextNum = formulas + 1;
1654
3506
  if (n.type == "title" && n.level == 1 && doc.numberingSections) {
1655
3507
  const num = getTitleNum(n);
1656
3508
  if (num) l1 = num;
@@ -1658,6 +3510,7 @@ function alchemist(doc, logwarn = console.warn) {
1658
3510
  codes = 0;
1659
3511
  imgs = 0;
1660
3512
  tables = 0;
3513
+ formulas = 0;
1661
3514
  }
1662
3515
  }
1663
3516
  nextPrefix = l1 > 0 && doc.numberingSections ? `${l1}.` : "";
@@ -1680,13 +3533,14 @@ function alchemist(doc, logwarn = console.warn) {
1680
3533
  counter.codes = 0;
1681
3534
  counter.imgs = 0;
1682
3535
  counter.tables = 0;
3536
+ counter.formulas = 0;
1683
3537
  }
1684
3538
  prefix = doc.numberingSections ? `${counter.titles.l1}.` : "";
1685
3539
  if (appendix) prefix = APPENDIX_NAME[counter.titles.l1 - 1] + ".";
1686
3540
  }
1687
3541
  }
1688
3542
  materializeNode(node);
1689
- if (node.type == "code" || node.type == "image" || node.type == "table") {
3543
+ if (node.type == "code" || node.type == "image" || node.type == "table" || node.type == "math") {
1690
3544
  if (node.type == "code") {
1691
3545
  counter.codes++;
1692
3546
  counter.codesAll++;
@@ -1699,18 +3553,27 @@ function alchemist(doc, logwarn = console.warn) {
1699
3553
  counter.tables++;
1700
3554
  counter.tablesAll++;
1701
3555
  }
3556
+ if (node.type == "math") {
3557
+ counter.formulas++;
3558
+ counter.formulasAll++;
3559
+ }
1702
3560
  prevNum = nextNum;
1703
3561
  prevPrefix = prefix;
1704
3562
  nextNum = -1;
1705
3563
  }
1706
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
+ }
1707
3569
  const sourcesCount = crystallizeSources();
1708
- vals["codes"]?.forEach((f) => f(counter.codes));
1709
- vals["imgs"]?.forEach((f) => f(counter.imgs));
1710
- 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));
1711
3574
  vals["sources"]?.forEach((f) => f(sourcesCount));
1712
3575
  Object.entries(named).forEach(([k, v]) => {
1713
- 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}]`));
1714
3577
  });
1715
3578
  if (synopsis) {
1716
3579
  synopsis.text = [
@@ -1744,7 +3607,7 @@ function alchemist(doc, logwarn = console.warn) {
1744
3607
  function materializeList(node) {
1745
3608
  node.items.forEach((item) => {
1746
3609
  if (item.type == "list") materializeList(item);
1747
- else materializeRunes(item.text, node);
3610
+ else materializeRunes(item.text, { ...node, sourceLine: item.sourceLine ?? node.sourceLine });
1748
3611
  });
1749
3612
  }
1750
3613
  function materializeTable(node) {
@@ -1792,8 +3655,9 @@ function alchemist(doc, logwarn = console.warn) {
1792
3655
  rune.text = repeat(node.level, (i2) => counter.titles[`l${i2 + 1}`]).join(".");
1793
3656
  return;
1794
3657
  }
1795
- if (type == "code" || type == "image" || type == "table") {
1796
- 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;
1797
3661
  throw new Error("switch default");
1798
3662
  })();
1799
3663
  num++;
@@ -1810,9 +3674,10 @@ function alchemist(doc, logwarn = console.warn) {
1810
3674
  nextRune.text = toCapitalCase(nextRune.text);
1811
3675
  }
1812
3676
  text = `${text} ${prefix}${num} \u2013 `;
3677
+ if (type == "math") text = `${prefix}${num}`;
1813
3678
  } else text = `${prefix}${num}`;
1814
3679
  if (v instanceof Array) v.forEach((fn) => fn.f(num, prefix));
1815
- 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`);
1816
3681
  named[tag] = { n: num, prefix };
1817
3682
  rune.type = "text";
1818
3683
  rune.text = text;
@@ -1832,8 +3697,8 @@ function alchemist(doc, logwarn = console.warn) {
1832
3697
  rune.type = "text";
1833
3698
  rune.text = n < 0 ? prefix2 : `${prefix2}${applyMath(n)}`;
1834
3699
  };
1835
- if (v) v.push({ f, i: lastRefI++ });
1836
- 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 }];
1837
3702
  } else {
1838
3703
  rune.type = "text";
1839
3704
  rune.text = `${v.prefix}${applyMath(v.n)}`;
@@ -1841,21 +3706,22 @@ function alchemist(doc, logwarn = console.warn) {
1841
3706
  });
1842
3707
  }
1843
3708
  function crystallizeSources() {
1844
- 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"));
1845
3710
  if (!sourcesList || sourcesList.type != "list") return 0;
1846
- 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;
1847
3713
  const refI = item[0]?.text ? 0 : 1;
1848
3714
  const ref = item[refI];
1849
3715
  if (ref?.type != "ref") {
1850
- return { text: item, ref: "", i: 99999 + i };
3716
+ return { text: item, ref: "", i: 99999 + i, srcI: v.sourceLine };
1851
3717
  }
1852
3718
  const refs = named[ref.text];
1853
3719
  if (refs && !(refs instanceof Array)) {
1854
- 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`);
1855
- 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 };
1856
3722
  }
1857
- const firstOccurrenceI = Math.min(...refs.map((v) => v.i));
1858
- 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 };
1859
3725
  });
1860
3726
  items.sort((a, b) => a.i - b.i);
1861
3727
  const hasIdAny = items.some((it) => !!it.ref);
@@ -1865,16 +3731,17 @@ function alchemist(doc, logwarn = console.warn) {
1865
3731
  named[key] = { prefix: "", n: i + 1 };
1866
3732
  if (refs instanceof Array) {
1867
3733
  if (hasIdAny && refs.length <= 1)
1868
- 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}`);
1869
3735
  refs.forEach((r) => hasIdAny ? r.f(i + 1, "") : r.f(-1, `[${i + 1}]`));
1870
3736
  }
1871
- return { type: "listItem", text: item.text };
3737
+ return { type: "listItem", text: item.text, sourceLine: item.srcI };
1872
3738
  });
1873
3739
  return sourcesList.items.length;
1874
3740
  }
1875
3741
  }
1876
3742
  function addLazyNumbering(doc) {
1877
3743
  doc.nodes.forEach((node) => {
3744
+ if (node.type == "math" && !node.title) node.title = [{ text: "(", type: "text" }, { text: "#", type: "ref" }, { text: ")", type: "text" }];
1878
3745
  const runes = node.type == "code" || node.type == "table" ? node.title : node.type == "image" ? node.text : null;
1879
3746
  if (!runes) return;
1880
3747
  if (runes.find((rune) => rune.type == "ref")) return;
@@ -1896,6 +3763,8 @@ async function render({
1896
3763
  disableMacros = false,
1897
3764
  useLibreOffice = false,
1898
3765
  checkFilesIsInsidePath = "",
3766
+ templateOptions = { allowIncludes: true, allowLoops: true },
3767
+ variables,
1899
3768
  logwarn = console.warn,
1900
3769
  loginfo = console.info,
1901
3770
  logPS = (msg) => console.log(`PS: ${msg}`),
@@ -1903,23 +3772,23 @@ async function render({
1903
3772
  signal = null
1904
3773
  }) {
1905
3774
  signal?.throwIfAborted();
1906
- const fin = path4.resolve(file);
3775
+ const fin = path5.resolve(file);
1907
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...");
1908
3777
  loginfo(`[!1] Parsing file ${fin}`);
1909
- const doc = await parseMD(fin, logwarn);
3778
+ const doc = await parseMD(fin, variables, templateOptions, checkFilesIsInsidePath, signal, logwarn);
1910
3779
  signal?.throwIfAborted();
1911
3780
  progress(10, "\u041A\u043E\u043B\u0434\u0443\u0435\u043C \u043D\u0430\u0434 \u0441\u0438\u043D\u0442\u0430\u043A\u0441\u0438\u0441\u043E\u043C...");
1912
3781
  loginfo("[!2] Processing document");
1913
3782
  enrichDoc(doc, logwarn);
1914
3783
  const runicDoc = runifyDoc(doc);
1915
3784
  alchemist(runicDoc, logwarn);
1916
- const pout = output && path4.parse(path4.resolve(output));
1917
- if (pout) await fs4.mkdir(pout.dir, { recursive: true });
1918
- 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);
1919
3788
  const fdir = p.dir;
1920
3789
  const fname = trimEnd(p.name, ".g");
1921
- const ftmp = path4.join(fdir, fname + ".tmp.docx");
1922
- 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");
1923
3792
  if (await checkIfFileIsBlocked(ftmp))
1924
3793
  throw new Error(`\u0417\u0430\u043A\u0440\u043E\u0439\u0442\u0435 docx \u0444\u0430\u0439\u043B. Output file is busy or locked: ${ftmp}`);
1925
3794
  if (await checkIfFileIsBlocked(fout))
@@ -1927,7 +3796,7 @@ async function render({
1927
3796
  signal?.throwIfAborted();
1928
3797
  progress(10, phrase_renderDocx());
1929
3798
  loginfo("[!3] Serializing to docx");
1930
- await serializeDocx(runicDoc, ftmp, fdir, assets, checkFilesIsInsidePath);
3799
+ await serializeDocx(runicDoc, ftmp, fdir, assets, checkFilesIsInsidePath, logwarn);
1931
3800
  let willRunMacros = !disableMacros && (renderPDF || hasReasonForRunningMacros(runicDoc));
1932
3801
  let limitedRender = false;
1933
3802
  if (process.platform != "win32" && willRunMacros && !useLibreOffice) {
@@ -1938,31 +3807,31 @@ async function render({
1938
3807
  signal?.throwIfAborted();
1939
3808
  progress(20, phrase_runMacros());
1940
3809
  loginfo("[!4] Starting macros");
1941
- const tmpfolder = path4.join(pout ? pout.dir : fdir, ".md2gost_out");
1942
- 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 });
1943
3812
  try {
1944
3813
  const macroRunner = useLibreOffice ? runUnoScript : runDocxMacro;
1945
3814
  const ok = await macroRunner(progress, logPS, logPSError, assets, fdir, ftmp, fout, renderPDF, signal);
1946
3815
  if (!ok) {
1947
- await fs4.rename(ftmp, fout);
3816
+ await fs5.rename(ftmp, fout);
1948
3817
  return { fout, err: "inPS" };
1949
3818
  }
1950
3819
  } catch (x) {
1951
3820
  if (signal?.aborted) {
1952
- await fs4.unlink(ftmp).catch(() => {
3821
+ await fs5.unlink(ftmp).catch(() => {
1953
3822
  });
1954
3823
  throw x;
1955
3824
  }
1956
- await fs4.rename(ftmp, fout);
3825
+ await fs5.rename(ftmp, fout);
1957
3826
  return { fout, err: "noPS", errS: x };
1958
3827
  }
1959
3828
  if (existsSync2(ftmp))
1960
- await fs4.unlink(ftmp);
3829
+ await fs5.unlink(ftmp);
1961
3830
  signal?.throwIfAborted();
1962
- const errorTxt = path4.join(tmpfolder, "error.txt");
3831
+ const errorTxt = path5.join(tmpfolder, "error.txt");
1963
3832
  if (existsSync2(errorTxt)) {
1964
- const err = await fs4.readFile(errorTxt);
1965
- await fs4.rm(tmpfolder, { recursive: true, force: true });
3833
+ const err = await fs5.readFile(errorTxt);
3834
+ await fs5.rm(tmpfolder, { recursive: true, force: true });
1966
3835
  return { fout, err: "vba", errS: `${err}` };
1967
3836
  }
1968
3837
  loginfo("[!5] Updating metadata");
@@ -1974,11 +3843,11 @@ async function render({
1974
3843
  if (!existsSync2(tmpfolder))
1975
3844
  return { fout, err: "pdf" };
1976
3845
  try {
1977
- const files = (await fs4.readdir(tmpfolder)).sort().map((f) => path4.join(tmpfolder, f));
1978
- 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");
1979
3848
  await mergePDFs(files, pdf, doc, signal || void 0);
1980
- await fs4.rm(tmpfolder, { recursive: true, force: true });
1981
- 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);
1982
3851
  return { fout: pdf };
1983
3852
  } catch (err) {
1984
3853
  if (signal?.aborted) throw err;
@@ -1986,7 +3855,7 @@ async function render({
1986
3855
  }
1987
3856
  }
1988
3857
  } else {
1989
- await fs4.rename(ftmp, fout);
3858
+ await fs5.rename(ftmp, fout);
1990
3859
  }
1991
3860
  loginfo(`[!7] Rendered to ${fout}`);
1992
3861
  return { fout, err: limitedRender ? "noWin" : void 0 };
@@ -2020,8 +3889,8 @@ function killProcessTree(child, wordPid) {
2020
3889
  }
2021
3890
  }
2022
3891
  function runDocxMacro(progress, log, logError, assets, cwd, fin, fout, renderPDF, signal) {
2023
- const script = path4.join(assets, "run.ps1");
2024
- const templateMacro = path4.join(assets, "template.dotm");
3892
+ const script = path5.join(assets, "run.ps1");
3893
+ const templateMacro = path5.join(assets, "template.dotm");
2025
3894
  return new Promise((res, rej) => {
2026
3895
  if (signal?.aborted)
2027
3896
  return rej(signal.reason ?? new Error("Aborted before execution started"));
@@ -2097,7 +3966,7 @@ function getPythonExecutable() {
2097
3966
  return process.env.UNO_PYTHON_PATH || "python3";
2098
3967
  }
2099
3968
  function runUnoScript(progress, log, logError, assets, cwd, fin, fout, renderPDF, signal) {
2100
- 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");
2101
3970
  const pythonExec = getPythonExecutable();
2102
3971
  return new Promise((res, rej) => {
2103
3972
  if (signal?.aborted) {
@@ -2161,31 +4030,61 @@ function runUnoScript(progress, log, logError, assets, cwd, fin, fout, renderPDF
2161
4030
  });
2162
4031
  }
2163
4032
  async function mergePDFs(files, fout, doc, signal) {
2164
- 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
+ }
2165
4073
  if (doc?.title) mergedPdf.setTitle(doc.title, { showInWindowTitleBar: true });
2166
4074
  if (doc?.author) mergedPdf.setAuthor(doc.author);
2167
4075
  if (doc?.ctime) mergedPdf.setCreationDate(doc.ctime);
2168
4076
  if (doc?.mtime) mergedPdf.setModificationDate(doc.mtime);
2169
- for (const file of files) {
2170
- if (!file.endsWith(".pdf")) continue;
2171
- const bytes = await fs4.readFile(file, { signal });
2172
- const pdf = await PDFDocument.load(bytes);
2173
- signal?.throwIfAborted();
2174
- const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
2175
- pages.forEach((page) => mergedPdf.addPage(page));
2176
- signal?.throwIfAborted();
2177
- }
2178
4077
  const pdfBytes = await mergedPdf.save();
2179
- await fs4.writeFile(fout, pdfBytes, { signal });
4078
+ await fs5.writeFile(fout, pdfBytes, { signal });
2180
4079
  }
2181
4080
  async function updateMetadata(docfile, doc) {
2182
- const content = await fs4.readFile(docfile);
4081
+ const content = await fs5.readFile(docfile);
2183
4082
  const zip = new PizZip(content);
2184
4083
  function replaceTag(xml, tag, value) {
2185
4084
  const regex = new RegExp(`(<${tag}\\b[^>]*>)(.*?)(</${tag}>)`, "gs");
2186
4085
  return xml.replace(
2187
4086
  regex,
2188
- (m, otag, old, ctag) => `${otag}${escapeXml(value)}${ctag}`
4087
+ (m, otag, old, ctag) => `${otag}${escapeXml2(value)}${ctag}`
2189
4088
  );
2190
4089
  }
2191
4090
  let coreXml = zip.file("docProps/core.xml").asText();
@@ -2199,8 +4098,8 @@ async function updateMetadata(docfile, doc) {
2199
4098
  appXml = replaceTag(appXml, "TotalTime", `${doc.etime || randomInt(30, 120)}`);
2200
4099
  zip.file("docProps/app.xml", appXml);
2201
4100
  const buffer = zip.generate({ type: "nodebuffer" });
2202
- await fs4.writeFile(docfile, buffer);
2203
- function escapeXml(unsafe) {
4101
+ await fs5.writeFile(docfile, buffer);
4102
+ function escapeXml2(unsafe) {
2204
4103
  return unsafe.replaceAll(/[<>&"']/g, (ch) => {
2205
4104
  switch (ch) {
2206
4105
  case "<":
@@ -2271,11 +4170,26 @@ async function renderMarkdown(config) {
2271
4170
  assert(typeof config.useLibreOffice == "boolean" || typeof config.useLibreOffice == "undefined", "useLibreOffice must be a boolean or undefined.");
2272
4171
  assert(typeof config.checkFilesIsInsidePath == "string" || config.checkFilesIsInsidePath === false || typeof config.checkFilesIsInsidePath == "undefined", "checkFilesIsInsidePath must be a string, false or undefined.");
2273
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
+ }
2274
4188
  assert(config.abortSignal instanceof AbortSignal || typeof config.abortSignal == "undefined", "abortSignal must be an AbortSignal or undefined.");
2275
4189
  {
2276
4190
  const extraProps = getExtraProperties(
2277
4191
  config,
2278
- ["input", "output", "format", "keepIntermediateDocx", "disableMacros", "useLibreOffice", "progress", "logger", "abortSignal", "checkFilesIsInsidePath"]
4192
+ ["input", "output", "format", "keepIntermediateDocx", "disableMacros", "useLibreOffice", "progress", "logger", "abortSignal", "checkFilesIsInsidePath", "variables", "templateOptions"]
2279
4193
  );
2280
4194
  assert(extraProps.length == 0, `Found unknown properties in config: ${extraProps.join(", ")}`);
2281
4195
  }
@@ -2289,20 +4203,25 @@ async function renderMarkdown(config) {
2289
4203
  (p) => assert(typeof logger[p] == "function", `config.logger.${p} must be a function.`)
2290
4204
  );
2291
4205
  }
2292
- 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";
2293
4208
  const removeIntermediateDocx = !config.keepIntermediateDocx;
2294
4209
  const disableMacros = !!config.disableMacros;
2295
4210
  const useLibreOffice = !!config.useLibreOffice;
2296
4211
  const checkFilesIsInsidePath = config.checkFilesIsInsidePath === void 0 ? "" : config.checkFilesIsInsidePath;
2297
4212
  assert(!(disableMacros && renderPDF), "Macros must be enabled to render PDF output.");
2298
- 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));
2299
4218
  const fname = pin.name.endsWith(".g") ? pin.name.slice(0, -2) : pin.name;
2300
4219
  let resolvedOutput = config.output;
2301
4220
  if (resolvedOutput) {
2302
- const isDir = resolvedOutput.endsWith("/") || resolvedOutput.endsWith("\\") || !path5.extname(resolvedOutput);
4221
+ const isDir = resolvedOutput.endsWith("/") || resolvedOutput.endsWith("\\") || !path6.extname(resolvedOutput);
2303
4222
  if (isDir) {
2304
4223
  const ext = renderPDF ? ".pdf" : ".docx";
2305
- resolvedOutput = path5.join(resolvedOutput, fname + ext);
4224
+ resolvedOutput = path6.join(resolvedOutput, fname + ext);
2306
4225
  }
2307
4226
  }
2308
4227
  const warnings = [];
@@ -2327,6 +4246,8 @@ async function renderMarkdown(config) {
2327
4246
  disableMacros,
2328
4247
  useLibreOffice,
2329
4248
  checkFilesIsInsidePath,
4249
+ variables: config.variables,
4250
+ templateOptions: config.templateOptions,
2330
4251
  loginfo: logger?.info,
2331
4252
  logwarn: (msg) => {
2332
4253
  logger?.warn(msg);
@@ -2342,7 +4263,6 @@ async function renderMarkdown(config) {
2342
4263
  },
2343
4264
  signal: config.abortSignal
2344
4265
  });
2345
- config.progress?.(100, "Done!");
2346
4266
  if (useLibreOffice) {
2347
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.`);
2348
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.`);
@@ -2353,10 +4273,11 @@ async function renderMarkdown(config) {
2353
4273
  if (err == "vba") throwMDE2("vba", `Microsoft Word VBA Macro execution failed: ${errS}`);
2354
4274
  if (err == "pdf") throwMDE2("pdf", `Failed to export document to PDF.`);
2355
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!");
2356
4277
  let intermediateDocxPath;
2357
4278
  if (renderPDF && !removeIntermediateDocx) {
2358
- const pout = resolvedOutput ? path5.parse(path5.resolve(resolvedOutput)) : void 0;
2359
- 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");
2360
4281
  }
2361
4282
  return {
2362
4283
  filePath: fout,
@@ -2414,10 +4335,7 @@ var MDRenderError = class _MDRenderError extends Error {
2414
4335
  }
2415
4336
  };
2416
4337
  function getAssetsDir() {
2417
- if (typeof __dirname !== "undefined")
2418
- return path5.resolve(__dirname, "../assets");
2419
- const metaUrl = new Function("return import.meta.url")();
2420
- return path5.resolve(path5.dirname(fileURLToPath(metaUrl)), "../assets");
4338
+ return path6.resolve(__MD2GOST_DIRNAME__, "../assets");
2421
4339
  }
2422
4340
  function getExtraProperties(configObj, allowedConfigKeys) {
2423
4341
  return Object.keys(configObj).filter(
@@ -2426,6 +4344,7 @@ function getExtraProperties(configObj, allowedConfigKeys) {
2426
4344
  }
2427
4345
  export {
2428
4346
  MDRenderError,
4347
+ UserInputError,
2429
4348
  renderMarkdown as default
2430
4349
  };
2431
4350
  //# sourceMappingURL=index.mjs.map