md2gost 1.3.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/LICENSE +21 -0
- package/README.md +926 -0
- package/assets/run.ps1 +61 -0
- package/assets/styles.xml +1140 -0
- package/assets/template.dotm +0 -0
- package/dist/cli.js +92 -0
- package/dist/cli.js.map +6 -0
- package/dist/index.cjs +2165 -0
- package/dist/index.cjs.map +6 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.mjs +2130 -0
- package/dist/index.mjs.map +6 -0
- package/package.json +227 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2130 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import path4 from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
|
|
5
|
+
// src/main.ts
|
|
6
|
+
import path3 from "path";
|
|
7
|
+
|
|
8
|
+
// src/doc.ts
|
|
9
|
+
var Doc = class {
|
|
10
|
+
nodes = [];
|
|
11
|
+
rainbow = false;
|
|
12
|
+
numberingLazy = false;
|
|
13
|
+
numberingSections = false;
|
|
14
|
+
numberingAutoprefix = true;
|
|
15
|
+
backtickMono = "italic";
|
|
16
|
+
title;
|
|
17
|
+
author;
|
|
18
|
+
etime;
|
|
19
|
+
ctime;
|
|
20
|
+
mtime;
|
|
21
|
+
hyphenation = false;
|
|
22
|
+
headings = {
|
|
23
|
+
h1: { size: 14, spacing: { before: 18, after: 4 }, indent_full: false, uppercase: false },
|
|
24
|
+
h3: { size: 14, spacing: { before: 8, after: 4 }, indent_full: false, uppercase: false },
|
|
25
|
+
h2: { size: 14, spacing: { before: 8, after: 4 }, indent_full: false, uppercase: false },
|
|
26
|
+
h4: { size: 14, spacing: { before: 8, after: 4 }, indent_full: false, uppercase: false },
|
|
27
|
+
h5: { size: 14, spacing: { before: 8, after: 4 }, indent_full: false, uppercase: false },
|
|
28
|
+
h6: { size: 14, spacing: { before: 8, after: 4 }, indent_full: false, uppercase: false }
|
|
29
|
+
};
|
|
30
|
+
text = {
|
|
31
|
+
size: 14,
|
|
32
|
+
line_spacing: 1.5,
|
|
33
|
+
indent: 1.25,
|
|
34
|
+
spacing: { after: 8 }
|
|
35
|
+
};
|
|
36
|
+
table = {
|
|
37
|
+
title: {
|
|
38
|
+
style: "normal",
|
|
39
|
+
size: void 0
|
|
40
|
+
},
|
|
41
|
+
heading: {
|
|
42
|
+
style: "normal",
|
|
43
|
+
align: "center"
|
|
44
|
+
},
|
|
45
|
+
spacing: { before: void 0, after: void 0 },
|
|
46
|
+
text: {
|
|
47
|
+
size: 12,
|
|
48
|
+
line_spacing: 1.25
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
code = {
|
|
52
|
+
title: {
|
|
53
|
+
style: "normal",
|
|
54
|
+
size: void 0
|
|
55
|
+
},
|
|
56
|
+
spacing: { before: void 0, after: void 0 },
|
|
57
|
+
highlight: false,
|
|
58
|
+
text: { size: 12 }
|
|
59
|
+
};
|
|
60
|
+
list = {
|
|
61
|
+
ordered: { style: "bracket" },
|
|
62
|
+
unordered: { style: "dash" },
|
|
63
|
+
autopunctuation: true
|
|
64
|
+
};
|
|
65
|
+
img = {
|
|
66
|
+
spacing: { before: void 0, after: void 0 },
|
|
67
|
+
text: { size: void 0 }
|
|
68
|
+
};
|
|
69
|
+
appendText(text) {
|
|
70
|
+
this.nodes.push({ type: "text", text });
|
|
71
|
+
}
|
|
72
|
+
appendTitle(text, level) {
|
|
73
|
+
this.nodes.push({ type: "title", text, level });
|
|
74
|
+
}
|
|
75
|
+
appendNode(node) {
|
|
76
|
+
this.nodes.push(node);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
function tableRow(...items) {
|
|
80
|
+
return items.map((v) => ({ type: "text", text: v }));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/parser.ts
|
|
84
|
+
import fs2 from "fs/promises";
|
|
85
|
+
|
|
86
|
+
// src/utils.ts
|
|
87
|
+
import fs from "fs/promises";
|
|
88
|
+
function choice(...options) {
|
|
89
|
+
return options[randomInt(options.length)];
|
|
90
|
+
}
|
|
91
|
+
function randomInt(maxmin, max, rnd = Math.random) {
|
|
92
|
+
if (max != void 0)
|
|
93
|
+
return Math.floor(rnd() * (maxmin - max)) + max;
|
|
94
|
+
return Math.floor(rnd() * maxmin);
|
|
95
|
+
}
|
|
96
|
+
function trimEnd(str, ...chs) {
|
|
97
|
+
if (!chs || chs.length == 0) return str.trimEnd();
|
|
98
|
+
let trimmed = false;
|
|
99
|
+
do {
|
|
100
|
+
trimmed = false;
|
|
101
|
+
for (const ch of chs)
|
|
102
|
+
while (str.endsWith(ch)) {
|
|
103
|
+
str = str.slice(0, -ch.length);
|
|
104
|
+
trimmed = true;
|
|
105
|
+
}
|
|
106
|
+
} while (trimmed);
|
|
107
|
+
return str;
|
|
108
|
+
}
|
|
109
|
+
function trimStart(str, ...chs) {
|
|
110
|
+
if (!chs || chs.length == 0) return str.trimStart();
|
|
111
|
+
let trimmed = false;
|
|
112
|
+
do {
|
|
113
|
+
trimmed = false;
|
|
114
|
+
for (const ch of chs)
|
|
115
|
+
while (str.startsWith(ch)) {
|
|
116
|
+
str = str.slice(ch.length);
|
|
117
|
+
trimmed = true;
|
|
118
|
+
}
|
|
119
|
+
} while (trimmed);
|
|
120
|
+
return str;
|
|
121
|
+
}
|
|
122
|
+
function toCapitalCase(str) {
|
|
123
|
+
return str.slice(0, 1).toUpperCase() + str.slice(1);
|
|
124
|
+
}
|
|
125
|
+
async function checkIfFileIsBlocked(path5) {
|
|
126
|
+
let file;
|
|
127
|
+
try {
|
|
128
|
+
file = await fs.open(path5, "r+");
|
|
129
|
+
return false;
|
|
130
|
+
} catch (err) {
|
|
131
|
+
if (!err) return false;
|
|
132
|
+
if (err.code === "EBUSY" || err.code === "EACCES" || err.code === "EPERM") return true;
|
|
133
|
+
if (err.code === "ENOENT") return false;
|
|
134
|
+
throw err;
|
|
135
|
+
} finally {
|
|
136
|
+
if (file) await file.close();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function hslToHex(h, s, l) {
|
|
140
|
+
l /= 100;
|
|
141
|
+
const a = s * Math.min(l, 1 - l) / 100;
|
|
142
|
+
const f = (n) => {
|
|
143
|
+
const k = (n + h / 30) % 12;
|
|
144
|
+
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
145
|
+
return Math.round(255 * color).toString(16).padStart(2, "0");
|
|
146
|
+
};
|
|
147
|
+
return `${f(0)}${f(8)}${f(4)}`;
|
|
148
|
+
}
|
|
149
|
+
function repeat(n, v) {
|
|
150
|
+
if (v === void 0) v = (i) => i;
|
|
151
|
+
return new Array(n).fill(null).map((_, i) => v instanceof Function ? v(i) : v);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/parser.ts
|
|
155
|
+
import path from "path";
|
|
156
|
+
async function parseMD(file, logwarn = console.warn) {
|
|
157
|
+
const lines = (await fs2.readFile(file, { encoding: "utf8" })).split("\n");
|
|
158
|
+
const doc = new Doc();
|
|
159
|
+
try {
|
|
160
|
+
doc.title = toCapitalCase(trimEnd(path.parse(file).name, ".g"));
|
|
161
|
+
const stats = await fs2.stat(file);
|
|
162
|
+
doc.ctime = stats.birthtime;
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
function parseList(text, parts, ordered, level = 0) {
|
|
166
|
+
const startIndex = isFinite(parseInt(parts[0])) ? parseInt(parts[0]) : 1;
|
|
167
|
+
const mark = ordered ? parts[1] == "." ? "." : ")" : parts[1] == "*" ? "*" : "-";
|
|
168
|
+
const node = { type: "list", ordered, mark, startIndex, items: [{ type: "listItem", text }] };
|
|
169
|
+
const P = ordered ? "1)" : "*";
|
|
170
|
+
function skipEmptyLines() {
|
|
171
|
+
let emptyFound = false;
|
|
172
|
+
for (let i = lineI; i < lines.length; i++) {
|
|
173
|
+
let ln = parseLine(lines[i]);
|
|
174
|
+
if (ln.prefix == " ") ln = parseLine(ln.text);
|
|
175
|
+
if (ln.prefix == "" && ln.text == "") {
|
|
176
|
+
emptyFound = true;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (ln.prefix == "" && !emptyFound) {
|
|
180
|
+
lineI = i;
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
if (ln.prefix == "*" || ln.prefix == "1)") {
|
|
184
|
+
lineI = i;
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
while (lineI < lines.length) {
|
|
192
|
+
if (skipEmptyLines()) break;
|
|
193
|
+
let ln = parseLine(lines[lineI]);
|
|
194
|
+
const last = node.items.at(-1);
|
|
195
|
+
if (ln.level == level) {
|
|
196
|
+
if (ln.prefix == " ") ln = parseLine(ln.text);
|
|
197
|
+
if (ln.prefix == "" && last?.type == "listItem")
|
|
198
|
+
last.text += "\n" + ln.text;
|
|
199
|
+
else if (ln.prefix == P)
|
|
200
|
+
node.items.push({ type: "listItem", text: ln.text });
|
|
201
|
+
else break;
|
|
202
|
+
} else if (ln.level > level) {
|
|
203
|
+
if (ln.level == level + 1) ln = parseLine(ln.text);
|
|
204
|
+
if ((ln.prefix == "" || ln.prefix == " ") && last?.type == "listItem")
|
|
205
|
+
last.text += "\n" + ln.text;
|
|
206
|
+
else if (ln.prefix == "*" || ln.prefix == "1)") {
|
|
207
|
+
lineI++;
|
|
208
|
+
node.items.push(parseList(ln.text, ln.parts, ln.prefix == "1)", level + 1));
|
|
209
|
+
continue;
|
|
210
|
+
} else break;
|
|
211
|
+
} else break;
|
|
212
|
+
lineI++;
|
|
213
|
+
}
|
|
214
|
+
return node;
|
|
215
|
+
}
|
|
216
|
+
function parseImg(parts) {
|
|
217
|
+
const text = parts[0].replaceAll("\\n", "\n").trim() || void 0;
|
|
218
|
+
const size = parts[2];
|
|
219
|
+
let width = null;
|
|
220
|
+
let height = null;
|
|
221
|
+
if (size) {
|
|
222
|
+
const s = size.split("x");
|
|
223
|
+
const w = parseInt(s[0] || "");
|
|
224
|
+
const h = parseInt(s[1] || "");
|
|
225
|
+
if (isFinite(w)) width = w;
|
|
226
|
+
if (isFinite(h)) height = h;
|
|
227
|
+
}
|
|
228
|
+
const src = trimEnd(trimStart(parts[1], "<"), ">");
|
|
229
|
+
return { type: "image", text, src, width, height };
|
|
230
|
+
}
|
|
231
|
+
function parseCode(text) {
|
|
232
|
+
const header = text.split(" ");
|
|
233
|
+
const title = header.slice(1).join(" ").trim() || void 0;
|
|
234
|
+
const node = { type: "code", lang: header[0], title, code: "" };
|
|
235
|
+
text = "";
|
|
236
|
+
while (lineI < lines.length) {
|
|
237
|
+
let ln = lines[lineI++];
|
|
238
|
+
if (ln.startsWith("```")) break;
|
|
239
|
+
text += "\n" + ln;
|
|
240
|
+
}
|
|
241
|
+
node.code = text.trim();
|
|
242
|
+
return node;
|
|
243
|
+
}
|
|
244
|
+
function parseSection(text) {
|
|
245
|
+
const match = /from\s+(\d+)/.exec(text);
|
|
246
|
+
const num = match ? parseInt(match[1] || "") : NaN;
|
|
247
|
+
return { type: "sectionBreak", pageStart: isFinite(num) ? num : null };
|
|
248
|
+
}
|
|
249
|
+
class RuleError extends Error {
|
|
250
|
+
}
|
|
251
|
+
;
|
|
252
|
+
function apllyRule(text) {
|
|
253
|
+
text = text.trim().replaceAll(/\s+/g, " ");
|
|
254
|
+
const textl = text.toLowerCase();
|
|
255
|
+
const rules = {
|
|
256
|
+
"highlight code": (v) => doc.code.highlight = true,
|
|
257
|
+
// deprecated
|
|
258
|
+
"rainbow": (v) => doc.rainbow = true,
|
|
259
|
+
"title": (v) => doc.title = v,
|
|
260
|
+
"author": (v) => doc.author = v,
|
|
261
|
+
"etime": (v) => doc.etime = tryParseInt(v),
|
|
262
|
+
"ctime": (v) => doc.ctime = tryParseDate(v),
|
|
263
|
+
"mtime": (v) => doc.mtime = tryParseDate(v),
|
|
264
|
+
"numbering lazy": (v) => {
|
|
265
|
+
choices(v, "", "on", "off");
|
|
266
|
+
doc.numberingLazy = !v || v == "on";
|
|
267
|
+
},
|
|
268
|
+
"numbering sections": (v) => {
|
|
269
|
+
choices(v, "", "on", "off");
|
|
270
|
+
doc.numberingSections = !v || v == "on";
|
|
271
|
+
},
|
|
272
|
+
"numbering autoprefix": (v) => {
|
|
273
|
+
choices(v, "", "on", "off");
|
|
274
|
+
doc.numberingAutoprefix = v == "on";
|
|
275
|
+
},
|
|
276
|
+
"backtick_mono": (v) => doc.backtickMono = choices(v, "italic", "off", "on", "outline"),
|
|
277
|
+
"hyphenation": (v) => doc.hyphenation = true,
|
|
278
|
+
"table title style": (v) => doc.table.title.style = choices(v, "normal", "bold", "italic"),
|
|
279
|
+
"table title size": (v) => doc.table.title.size = tryParseInt(v),
|
|
280
|
+
"table spacing before": (v) => doc.table.spacing.before = tryParseInt(v),
|
|
281
|
+
"table spacing after": (v) => doc.table.spacing.after = tryParseInt(v),
|
|
282
|
+
"table heading style": (v) => doc.table.heading.style = choices(v, "normal", "bold", "italic"),
|
|
283
|
+
"table heading align": (v) => doc.table.heading.align = choices(v, "left", "center", "right"),
|
|
284
|
+
"table text size": (v) => doc.table.text.size = tryParseInt(v),
|
|
285
|
+
"table text line_spacing": (v) => doc.table.text.line_spacing = tryParseFloat(v),
|
|
286
|
+
"code title style": (v) => doc.code.title.style = choices(v, "normal", "bold", "italic"),
|
|
287
|
+
"code title size": (v) => doc.code.title.size = tryParseInt(v),
|
|
288
|
+
"code spacing before": (v) => doc.code.spacing.before = tryParseInt(v),
|
|
289
|
+
"code spacing after": (v) => doc.code.spacing.after = tryParseInt(v),
|
|
290
|
+
"code highlight": (v) => {
|
|
291
|
+
choices(v, "", "on", "off");
|
|
292
|
+
doc.code.highlight = !v || v == "on";
|
|
293
|
+
},
|
|
294
|
+
"code text size": (v) => doc.code.text.size = tryParseInt(v),
|
|
295
|
+
"list ordered style": (v) => doc.list.ordered.style = choices(v, "bracket", "dot", "keep"),
|
|
296
|
+
"list unordered style": (v) => doc.list.unordered.style = choices(v, "dash", "bullet", "keep"),
|
|
297
|
+
"list autopunctuation": (v) => {
|
|
298
|
+
choices(v, "", "on", "off");
|
|
299
|
+
doc.list.autopunctuation = v == "on";
|
|
300
|
+
},
|
|
301
|
+
"headings alt_style_1": (v) => {
|
|
302
|
+
for (let i = 1; i <= 6; i++) {
|
|
303
|
+
doc.headings[`h${i}`].indent_full = true;
|
|
304
|
+
doc.headings[`h${i}`].spacing.after = 10;
|
|
305
|
+
doc.headings[`h${i}`].spacing.before = 15;
|
|
306
|
+
}
|
|
307
|
+
doc.headings.h1.size = 18;
|
|
308
|
+
doc.headings.h1.uppercase = true;
|
|
309
|
+
doc.headings.h1.spacing.before = 0;
|
|
310
|
+
doc.headings.h2.size = 16;
|
|
311
|
+
},
|
|
312
|
+
"text size": (v) => doc.text.size = tryParseInt(v),
|
|
313
|
+
"text line_spacing": (v) => doc.text.line_spacing = tryParseFloat(v),
|
|
314
|
+
"text indent": (v) => doc.text.indent = tryParseFloat(v),
|
|
315
|
+
"text spacing after": (v) => doc.text.spacing.after = tryParseInt(v),
|
|
316
|
+
"img text size": (v) => doc.img.text.size = tryParseInt(v),
|
|
317
|
+
"img spacing before": (v) => doc.img.spacing.before = tryParseInt(v),
|
|
318
|
+
"img spacing after": (v) => doc.img.spacing.after = tryParseInt(v)
|
|
319
|
+
};
|
|
320
|
+
const reRules = [
|
|
321
|
+
{
|
|
322
|
+
re: /^(headings (h[1-6])(\+?) (size|spacing (before|after)|uppercase|indent))(.*)/,
|
|
323
|
+
n: (m) => ({ rule: m[1], value: (m.at(-1) || "").trim() }),
|
|
324
|
+
f(m) {
|
|
325
|
+
const level = parseInt(m[2][1]);
|
|
326
|
+
const plus = m[3] == "+";
|
|
327
|
+
const property = m[4];
|
|
328
|
+
const value = (m.at(-1) || "").trim();
|
|
329
|
+
for (let i = level; i <= (plus ? 6 : level); i++) {
|
|
330
|
+
const h = doc.headings[`h${i}`];
|
|
331
|
+
if (property == "size") h.size = tryParseInt(value);
|
|
332
|
+
else if (property == "spacing before") h.spacing.before = tryParseInt(value);
|
|
333
|
+
else if (property == "spacing after") h.spacing.after = tryParseInt(value);
|
|
334
|
+
else if (property == "uppercase") {
|
|
335
|
+
choices(value, "", "on", "off");
|
|
336
|
+
h.uppercase = !value || value == "on";
|
|
337
|
+
} else if (property == "indent") h.indent_full = choices(value, "first_line", "left") == "left";
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
];
|
|
342
|
+
const rulePrefix = Object.keys(rules).find(
|
|
343
|
+
(prefix) => textl.startsWith(prefix) && !textl.slice(prefix.length)[0]?.trim()
|
|
344
|
+
);
|
|
345
|
+
const reRule = rulePrefix ? void 0 : reRules.map(({ re, f, n }) => ({ f, n, m: re.exec(textl) })).find(({ m }) => !!m);
|
|
346
|
+
let applyRule = rulePrefix ? () => rules[rulePrefix](text.slice(rulePrefix.length).trim()) : reRule ? () => reRule.f(reRule.m) : void 0;
|
|
347
|
+
if (applyRule) {
|
|
348
|
+
try {
|
|
349
|
+
applyRule();
|
|
350
|
+
} catch (e) {
|
|
351
|
+
let rule = rulePrefix;
|
|
352
|
+
let value = rulePrefix && text.slice(rulePrefix.length);
|
|
353
|
+
if (reRule) {
|
|
354
|
+
const n = reRule.n(reRule.m);
|
|
355
|
+
rule = n.rule;
|
|
356
|
+
value = n.value;
|
|
357
|
+
}
|
|
358
|
+
if (e instanceof RuleError) logwarn(`Rule "${rule}" wrong value: "${value}". ${e.message}`);
|
|
359
|
+
else throw e;
|
|
360
|
+
}
|
|
361
|
+
} else {
|
|
362
|
+
logwarn(`Wrong rule: "${text}"`);
|
|
363
|
+
}
|
|
364
|
+
function tryParseInt(v) {
|
|
365
|
+
const num = parseInt(v);
|
|
366
|
+
if (!isFinite(num)) throw new RuleError("Only integers are allowed");
|
|
367
|
+
return num;
|
|
368
|
+
}
|
|
369
|
+
function tryParseFloat(v) {
|
|
370
|
+
const num = parseFloat(v);
|
|
371
|
+
if (!isFinite(num)) throw new RuleError("Only integers and floats are allowed");
|
|
372
|
+
return num;
|
|
373
|
+
}
|
|
374
|
+
function tryParseDate(v) {
|
|
375
|
+
const date = new Date(v);
|
|
376
|
+
if (!isFinite(date.valueOf())) throw new RuleError("Only ISO 8601 date is allowed");
|
|
377
|
+
return date;
|
|
378
|
+
}
|
|
379
|
+
function choices(v, ...vars) {
|
|
380
|
+
if (vars.includes(v)) return v;
|
|
381
|
+
throw new RuleError("Allowed: " + vars.join(", "));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function skipComment() {
|
|
385
|
+
while (lineI - 1 < lines.length && !lines[lineI - 1].includes("-->"))
|
|
386
|
+
lineI++;
|
|
387
|
+
}
|
|
388
|
+
let lineI = 0;
|
|
389
|
+
while (lineI < lines.length) {
|
|
390
|
+
const line = lines[lineI++];
|
|
391
|
+
const { prefix, text, parts } = parseLine(line);
|
|
392
|
+
switch (prefix) {
|
|
393
|
+
case "#":
|
|
394
|
+
doc.appendTitle(text, 1);
|
|
395
|
+
break;
|
|
396
|
+
case "##":
|
|
397
|
+
doc.appendTitle(text, 2);
|
|
398
|
+
break;
|
|
399
|
+
case "###":
|
|
400
|
+
doc.appendTitle(text, 3);
|
|
401
|
+
break;
|
|
402
|
+
case "####":
|
|
403
|
+
doc.appendTitle(text, 4);
|
|
404
|
+
break;
|
|
405
|
+
case "#####":
|
|
406
|
+
doc.appendTitle(text, 5);
|
|
407
|
+
break;
|
|
408
|
+
case "######":
|
|
409
|
+
doc.appendTitle(text, 6);
|
|
410
|
+
break;
|
|
411
|
+
case "*":
|
|
412
|
+
doc.appendNode(parseList(text, parts, false));
|
|
413
|
+
break;
|
|
414
|
+
case "1)":
|
|
415
|
+
doc.appendNode(parseList(text, parts, true));
|
|
416
|
+
break;
|
|
417
|
+
case "Img":
|
|
418
|
+
doc.appendNode(parseImg(parts));
|
|
419
|
+
break;
|
|
420
|
+
case "Code":
|
|
421
|
+
doc.appendNode(parseCode(text));
|
|
422
|
+
break;
|
|
423
|
+
case "Comment":
|
|
424
|
+
skipComment();
|
|
425
|
+
break;
|
|
426
|
+
case "!!section":
|
|
427
|
+
doc.appendNode(parseSection(text));
|
|
428
|
+
break;
|
|
429
|
+
case "!!rule":
|
|
430
|
+
apllyRule(text);
|
|
431
|
+
break;
|
|
432
|
+
case "---":
|
|
433
|
+
doc.appendNode({ type: "pageBreak" });
|
|
434
|
+
break;
|
|
435
|
+
case "":
|
|
436
|
+
case " ":
|
|
437
|
+
const last = doc.nodes.at(-1);
|
|
438
|
+
if (line.trim() != "" && last?.type == "text") {
|
|
439
|
+
if (last.text != "") last.text += "\n";
|
|
440
|
+
last.text += line.trim();
|
|
441
|
+
} else doc.appendText(line.trim());
|
|
442
|
+
break;
|
|
443
|
+
default:
|
|
444
|
+
prefix;
|
|
445
|
+
throw new Error("switch default");
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
doc.nodes = doc.nodes.filter((n) => n.type != "text" || n.text != "");
|
|
449
|
+
findDocs(doc.nodes, logwarn);
|
|
450
|
+
findTables(doc.nodes);
|
|
451
|
+
findPageBreaks(doc.nodes);
|
|
452
|
+
return doc;
|
|
453
|
+
}
|
|
454
|
+
function parseLine(line) {
|
|
455
|
+
let level = 0;
|
|
456
|
+
while (line.startsWith(" ") || line.startsWith(" ")) {
|
|
457
|
+
if (line.startsWith(" ")) line = line = line.slice(4);
|
|
458
|
+
else if (line.startsWith(" ")) line = line.slice(1);
|
|
459
|
+
level++;
|
|
460
|
+
}
|
|
461
|
+
if (level > 0) return { prefix: " ", text: line.trim(), level, parts: [] };
|
|
462
|
+
if (/^\s*---+\s*$/.test(line)) return { prefix: "---", text: "", level, parts: [] };
|
|
463
|
+
const splited = line.trim().split(/\s/);
|
|
464
|
+
let prefix = splited[0].toLowerCase();
|
|
465
|
+
let text = splited.slice(1).join(" ");
|
|
466
|
+
let parts = [];
|
|
467
|
+
if (prefix.startsWith("!")) {
|
|
468
|
+
const m_img = /!\[(.*)\]\((.*)\)({(.*)})?/.exec(line);
|
|
469
|
+
if (m_img) return {
|
|
470
|
+
prefix: "Img",
|
|
471
|
+
text: line.trim(),
|
|
472
|
+
level,
|
|
473
|
+
parts: [m_img[1], m_img[2], m_img[4]]
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
if (prefix == "-" || prefix == "*") {
|
|
477
|
+
parts = ["", prefix];
|
|
478
|
+
prefix = "*";
|
|
479
|
+
}
|
|
480
|
+
if (prefix.endsWith(".") || prefix.endsWith(")")) {
|
|
481
|
+
const index = parseInt(prefix.slice(0, -1));
|
|
482
|
+
if (isFinite(index)) {
|
|
483
|
+
parts = [`${index}`, prefix.at(-1) || ")"];
|
|
484
|
+
prefix = "1)";
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (prefix.startsWith("```")) return { prefix: "Code", text: line.trim().slice(3), level, parts };
|
|
488
|
+
if (prefix.startsWith("<!--")) return { prefix: "Comment", text: line.trim().slice("<!--".length).trim(), level, parts };
|
|
489
|
+
if (["#", "##", "###", "####", "#####", "######", "*", "1)", "!!section", "!!rule"].includes(prefix))
|
|
490
|
+
return { prefix, text, level: 0, parts };
|
|
491
|
+
return { prefix: "", text: line.trim(), level, parts };
|
|
492
|
+
}
|
|
493
|
+
function findDocs(nodes, logwarn = console.warn) {
|
|
494
|
+
const re_doc = /^!!\(([^{}]*)\)\s*{(.*)}$/s;
|
|
495
|
+
const re_remTrailingComma = /,(\s*[}\]])/g;
|
|
496
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
497
|
+
const node = nodes[i];
|
|
498
|
+
if (node.type != "text") continue;
|
|
499
|
+
const m_doc = re_doc.exec(node.text.trimEnd().replaceAll(re_remTrailingComma, "$1"));
|
|
500
|
+
if (!m_doc) continue;
|
|
501
|
+
let dict = {};
|
|
502
|
+
try {
|
|
503
|
+
dict = JSON.parse(`{${m_doc[2]}}`);
|
|
504
|
+
} catch (x) {
|
|
505
|
+
logwarn(`Cant parse doc dict: {${m_doc[2].replaceAll("\n", " ")}}`);
|
|
506
|
+
}
|
|
507
|
+
nodes.splice(i, 1, {
|
|
508
|
+
type: "externalDoc",
|
|
509
|
+
path: trimEnd(trimStart(m_doc[1], "<", '"'), ">", '"'),
|
|
510
|
+
dict: stringifyDict(dict)
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function stringifyDict(dict) {
|
|
515
|
+
const r = {};
|
|
516
|
+
for (const key in dict) {
|
|
517
|
+
let v = dict[key];
|
|
518
|
+
if (typeof v == "string" || typeof v == "boolean" || typeof v == "number") {
|
|
519
|
+
r[key] = `${v}`;
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
if (!v) continue;
|
|
523
|
+
if (v instanceof Array) {
|
|
524
|
+
const d2 = {};
|
|
525
|
+
v.forEach((v2, i) => d2[`${i}`] = v2);
|
|
526
|
+
v = d2;
|
|
527
|
+
}
|
|
528
|
+
const d = stringifyDict(v);
|
|
529
|
+
for (const k in d)
|
|
530
|
+
r[key + "." + k] = d[k];
|
|
531
|
+
}
|
|
532
|
+
return r;
|
|
533
|
+
}
|
|
534
|
+
function findTables(nodes) {
|
|
535
|
+
const re_sep = /^(\s*:?-+:?\s*(?<!\\)\|)+\s*:?-+:?\s*$/;
|
|
536
|
+
const re_sep_oneCol = /^\|\s*:?-+:?\s*\|$/;
|
|
537
|
+
const trim = (line) => {
|
|
538
|
+
line = line.trim();
|
|
539
|
+
if (line.at(0) == "|") line = line.slice(1);
|
|
540
|
+
if (line.at(-1) == "|") line = line.slice(0, -1);
|
|
541
|
+
return line.trim();
|
|
542
|
+
};
|
|
543
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
544
|
+
const node = nodes[i];
|
|
545
|
+
if (node.type != "text") continue;
|
|
546
|
+
const lines = node.text.split("\n");
|
|
547
|
+
const offset = lines[0].includes("|") ? 0 : 1;
|
|
548
|
+
if (lines.length < 2 + offset) continue;
|
|
549
|
+
const sep = trim(lines[1 + offset]);
|
|
550
|
+
if (!re_sep.test(sep) && !re_sep_oneCol.test(lines[1 + offset])) continue;
|
|
551
|
+
const cols = sep.split("|");
|
|
552
|
+
const header = trim(lines[offset]).split(/(?<!\\)\|/).map((v) => v.trim());
|
|
553
|
+
if (header.length != cols.length) continue;
|
|
554
|
+
const align = cols.map((v) => v.trim()).map((v) => v.startsWith(":") && v.endsWith(":") ? "c" : v.endsWith(":") ? "r" : "l");
|
|
555
|
+
const rows = [tableRow(...header)];
|
|
556
|
+
for (const line of lines.slice(2 + offset)) {
|
|
557
|
+
const row = trim(line).split(/(?<!\\)\|/).map((v) => v.trim());
|
|
558
|
+
while (row.length < cols.length) row.push("");
|
|
559
|
+
rows.push(tableRow(...row));
|
|
560
|
+
}
|
|
561
|
+
const table = { type: "table", align, rows };
|
|
562
|
+
nodes.splice(i, 1, table);
|
|
563
|
+
const prev = nodes[i - 1];
|
|
564
|
+
if (offset > 0) {
|
|
565
|
+
table.title = lines[0];
|
|
566
|
+
} else if (prev?.type == "text") {
|
|
567
|
+
nodes.splice(i - 1, 1);
|
|
568
|
+
table.title = prev.text;
|
|
569
|
+
i--;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function findPageBreaks(nodes) {
|
|
574
|
+
const re_sep = /^\s*---+\s*$/;
|
|
575
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
576
|
+
const node = nodes[i];
|
|
577
|
+
if (node.type != "text") continue;
|
|
578
|
+
if (!node.text.includes("---")) continue;
|
|
579
|
+
const lines = node.text.split("\n");
|
|
580
|
+
const newNodes = [];
|
|
581
|
+
for (const line of lines) {
|
|
582
|
+
if (re_sep.test(line)) newNodes.push({ type: "pageBreak" });
|
|
583
|
+
else {
|
|
584
|
+
const last = newNodes.at(-1);
|
|
585
|
+
if (last?.type == "text") last.text += "\n" + line;
|
|
586
|
+
else newNodes.push({ type: "text", text: line, noIndent: node.noIndent, noMargin: node.noMargin });
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
nodes.splice(i, 1, ...newNodes);
|
|
590
|
+
i += newNodes.length - 1;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function runifyDoc(doc) {
|
|
594
|
+
function runifyNode(node) {
|
|
595
|
+
if ("text" in node && node.text)
|
|
596
|
+
node.text = runifyText(node.text, doc.rainbow);
|
|
597
|
+
if ("title" in node && node.title)
|
|
598
|
+
node.title = runifyText(node.title, doc.rainbow);
|
|
599
|
+
if (node.type == "table")
|
|
600
|
+
node.rows.forEach((row) => row.forEach(runifyNode));
|
|
601
|
+
if (node.type == "list")
|
|
602
|
+
node.items.forEach(runifyNode);
|
|
603
|
+
}
|
|
604
|
+
doc.nodes.forEach(runifyNode);
|
|
605
|
+
return doc;
|
|
606
|
+
}
|
|
607
|
+
var rainbowI = 0;
|
|
608
|
+
function runifyText(text, rainbow = false) {
|
|
609
|
+
return replaceAmpCodes(text).replaceAll("\n", "	\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("*", "⋆")).split(/(\[[^\]]*\]\([^\)]*\))/g).map((p) => {
|
|
610
|
+
const m = /\[([^\]]*)\]\(([^\)]*)\)/.exec(p);
|
|
611
|
+
if (!m) return { text: p };
|
|
612
|
+
return { text: m[1], link: m[2] };
|
|
613
|
+
}).map((rune) => rune.text.split("\n").map((p, i) => ({
|
|
614
|
+
text: p.replaceAll(/\s+/g, " ").replaceAll(" ", "\xA0").replaceAll("	", " "),
|
|
615
|
+
linebreak: i > 0,
|
|
616
|
+
link: rune.link
|
|
617
|
+
}))).flat().map((rune) => rune.text.split("***").map((p, i, arr) => ({
|
|
618
|
+
text: p,
|
|
619
|
+
linebreak: i == 0 && rune.linebreak,
|
|
620
|
+
link: rune.link,
|
|
621
|
+
bold: i % 2 == 1 && i != arr.length - 1,
|
|
622
|
+
italic: i % 2 == 1 && i != arr.length - 1
|
|
623
|
+
}))).flat().map((rune) => rune.text.split("**").map((p, i, arr) => ({
|
|
624
|
+
text: p,
|
|
625
|
+
linebreak: i == 0 && rune.linebreak,
|
|
626
|
+
link: rune.link,
|
|
627
|
+
bold: i % 2 == 1 && i != arr.length - 1 || rune.bold,
|
|
628
|
+
italic: rune.italic
|
|
629
|
+
}))).flat().map((rune) => rune.text.split("*").map((p, i, arr) => ({
|
|
630
|
+
text: p,
|
|
631
|
+
linebreak: i == 0 && rune.linebreak,
|
|
632
|
+
link: rune.link,
|
|
633
|
+
bold: rune.bold,
|
|
634
|
+
italic: i % 2 == 1 && i != arr.length - 1 || rune.italic
|
|
635
|
+
}))).flat().map((rune) => rune.text.split("```").map((p, i, arr) => ({
|
|
636
|
+
...rune,
|
|
637
|
+
text: p,
|
|
638
|
+
linebreak: i == 0 && rune.linebreak,
|
|
639
|
+
mono: i % 2 == 1 && i != arr.length - 1
|
|
640
|
+
}))).flat().map((rune) => rune.mono ? rune : rune.text.split("`").map((p, i, arr) => ({
|
|
641
|
+
...rune,
|
|
642
|
+
text: p,
|
|
643
|
+
linebreak: i == 0 && rune.linebreak,
|
|
644
|
+
mono: i % 2 == 1 && i != arr.length - 1
|
|
645
|
+
}))).flat().map((rune) => rune.link ? [rune] : rune.text.split(/(\[!?[a-zA-Zа-яА-ЯёЁ_\d#]+\s*[+\-]?\s*\d*\])/g).map((p, i) => {
|
|
646
|
+
const m = /\[(!?([a-zA-Zа-яА-ЯёЁ_\d]+|#)(\s*[-\+]\s*\d+)?)\]/.exec(p);
|
|
647
|
+
const v = m && m[1];
|
|
648
|
+
const isVal = v?.at(0) == "!";
|
|
649
|
+
return {
|
|
650
|
+
...rune,
|
|
651
|
+
text: v ? isVal ? v.slice(1) : v : p,
|
|
652
|
+
type: v ? isVal ? "val" : "ref" : rune.type,
|
|
653
|
+
linebreak: i == 0 && rune.linebreak
|
|
654
|
+
};
|
|
655
|
+
})).flat().map((rune) => ({
|
|
656
|
+
...rune,
|
|
657
|
+
...!rune.type || rune.type == "text" ? {
|
|
658
|
+
text: rune.text.replaceAll("⋆", "*").replaceAll("​", "")
|
|
659
|
+
} : {}
|
|
660
|
+
})).map((rune) => !rainbow || rune.type && rune.type != "text" ? [rune] : rune.text.split("").map((p, i) => ({
|
|
661
|
+
...rune,
|
|
662
|
+
text: p,
|
|
663
|
+
linebreak: i == 0 && rune.linebreak,
|
|
664
|
+
color: hslToHex(rainbowI++ % 360, 100, 50)
|
|
665
|
+
}))).flat();
|
|
666
|
+
}
|
|
667
|
+
function replaceAmpCodes(text) {
|
|
668
|
+
const codes = {
|
|
669
|
+
"|": "|",
|
|
670
|
+
"­": "\xAD",
|
|
671
|
+
"«": "\xAB",
|
|
672
|
+
"»": "\xBB",
|
|
673
|
+
"‹": "\u2039",
|
|
674
|
+
"›": "\u203A",
|
|
675
|
+
"α": "\u03B1",
|
|
676
|
+
"β": "\u03B2",
|
|
677
|
+
"γ": "\u03B3",
|
|
678
|
+
"δ": "\u03B4",
|
|
679
|
+
"ε": "\u03B5",
|
|
680
|
+
"ζ": "\u03B6",
|
|
681
|
+
"η": "\u03B7",
|
|
682
|
+
"θ": "\u03B8",
|
|
683
|
+
"ι": "\u03B9",
|
|
684
|
+
"κ": "\u03BA",
|
|
685
|
+
"λ": "\u03BB",
|
|
686
|
+
"μ": "\u03BC",
|
|
687
|
+
"ν": "\u03BD",
|
|
688
|
+
"ξ": "\u03BE",
|
|
689
|
+
"ο": "\u03BF",
|
|
690
|
+
"π": "\u03C0",
|
|
691
|
+
"ρ": "\u03C1",
|
|
692
|
+
"σ": "\u03C3",
|
|
693
|
+
"τ": "\u03C4",
|
|
694
|
+
"υ": "\u03C5",
|
|
695
|
+
"φ": "\u03C6",
|
|
696
|
+
"χ": "\u03C7",
|
|
697
|
+
"ψ": "\u03C8",
|
|
698
|
+
"ω": "\u03C9",
|
|
699
|
+
"Δ": "\u0394",
|
|
700
|
+
"Σ": "\u03A3",
|
|
701
|
+
"Ω": "\u03A9",
|
|
702
|
+
"∞": "\u221E",
|
|
703
|
+
"∑": "\u2211",
|
|
704
|
+
"∏": "\u220F",
|
|
705
|
+
"√": "\u221A",
|
|
706
|
+
"∫": "\u222B",
|
|
707
|
+
"∂": "\u2202",
|
|
708
|
+
"≈": "\u2248",
|
|
709
|
+
"≠": "\u2260",
|
|
710
|
+
"<": "<",
|
|
711
|
+
">": ">",
|
|
712
|
+
"≤": "\u2264",
|
|
713
|
+
"≥": "\u2265",
|
|
714
|
+
"±": "\xB1",
|
|
715
|
+
"×": "\xD7",
|
|
716
|
+
"÷": "\xF7",
|
|
717
|
+
"©": "\xA9",
|
|
718
|
+
"®": "\xAE",
|
|
719
|
+
"™": "\u2122",
|
|
720
|
+
"§": "\xA7",
|
|
721
|
+
"¶": "\xB6",
|
|
722
|
+
"…": "\u2026",
|
|
723
|
+
"•": "\u2022",
|
|
724
|
+
"·": "\xB7",
|
|
725
|
+
"°": "\xB0",
|
|
726
|
+
"€": "\u20AC",
|
|
727
|
+
"£": "\xA3",
|
|
728
|
+
"¥": "\xA5",
|
|
729
|
+
"¢": "\xA2",
|
|
730
|
+
"¤": "\xA4",
|
|
731
|
+
"ƒ": "\u0192",
|
|
732
|
+
"‰": "\u2030"
|
|
733
|
+
};
|
|
734
|
+
for (const code in codes) {
|
|
735
|
+
if (!Object.hasOwn(codes, code)) continue;
|
|
736
|
+
const char = codes[code];
|
|
737
|
+
text = text.replaceAll(code, char);
|
|
738
|
+
}
|
|
739
|
+
return text;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// src/serializer.ts
|
|
743
|
+
import * as fs3 from "fs";
|
|
744
|
+
import { AlignmentType, Document, Footer, convertMillimetersToTwip, Packer, PageBreak, PageNumber, Paragraph, TableOfContents, TextRun, LevelFormat, Table, TableRow, TableCell, ImageRun, ExternalHyperlink, InternalHyperlink, Bookmark } from "docx";
|
|
745
|
+
import { imageSize } from "image-size";
|
|
746
|
+
import path2 from "path";
|
|
747
|
+
import Prism from "prismjs";
|
|
748
|
+
import "prismjs/components/prism-clike";
|
|
749
|
+
import "prismjs/components/prism-markup";
|
|
750
|
+
import "prismjs/components/prism-css";
|
|
751
|
+
import "prismjs/components/prism-javascript";
|
|
752
|
+
import "prismjs/components/prism-typescript";
|
|
753
|
+
import "prismjs/components/prism-jsx";
|
|
754
|
+
import "prismjs/components/prism-tsx";
|
|
755
|
+
import "prismjs/components/prism-json";
|
|
756
|
+
import "prismjs/components/prism-bash";
|
|
757
|
+
import "prismjs/components/prism-powershell";
|
|
758
|
+
import "prismjs/components/prism-python";
|
|
759
|
+
import "prismjs/components/prism-java";
|
|
760
|
+
import "prismjs/components/prism-c";
|
|
761
|
+
import "prismjs/components/prism-cpp";
|
|
762
|
+
import "prismjs/components/prism-csharp";
|
|
763
|
+
import "prismjs/components/prism-go";
|
|
764
|
+
import "prismjs/components/prism-rust";
|
|
765
|
+
import "prismjs/components/prism-php";
|
|
766
|
+
import "prismjs/components/prism-ruby";
|
|
767
|
+
import "prismjs/components/prism-swift";
|
|
768
|
+
import "prismjs/components/prism-kotlin";
|
|
769
|
+
import "prismjs/components/prism-sql";
|
|
770
|
+
import "prismjs/components/prism-yaml";
|
|
771
|
+
import "prismjs/components/prism-markdown";
|
|
772
|
+
import "prismjs/components/prism-docker";
|
|
773
|
+
import "prismjs/components/prism-nginx";
|
|
774
|
+
var STYLE_list = "afc";
|
|
775
|
+
var STYLE_table_title = "TableCaption";
|
|
776
|
+
var STYLE_code_title = "ListingCaption";
|
|
777
|
+
var STYLE_code = "ListingCode";
|
|
778
|
+
async function serializeDocx(doc, fout, workdir, assets) {
|
|
779
|
+
const getPath = (fname) => {
|
|
780
|
+
if (process.platform == "win32" && fname[0] == "/") fname = "." + fname;
|
|
781
|
+
return path2.isAbsolute(fname) ? fname : path2.join(workdir, fname);
|
|
782
|
+
};
|
|
783
|
+
const sections = [];
|
|
784
|
+
const numbering = [];
|
|
785
|
+
const docSections = (function splitSections() {
|
|
786
|
+
const sections2 = [{ pageStart: 1, nodes: [] }];
|
|
787
|
+
for (let j = 0; j < doc.nodes.length; j++) {
|
|
788
|
+
const node = doc.nodes[j];
|
|
789
|
+
if (node.type == "sectionBreak")
|
|
790
|
+
sections2.push({ pageStart: node.pageStart, nodes: [] });
|
|
791
|
+
else
|
|
792
|
+
sections2.at(-1)?.nodes.push(node);
|
|
793
|
+
}
|
|
794
|
+
return sections2.filter((s) => s.nodes.length > 0);
|
|
795
|
+
})();
|
|
796
|
+
for (const section of docSections) {
|
|
797
|
+
let renderNode2 = function(node, prevChild, prevNode) {
|
|
798
|
+
switch (node.type) {
|
|
799
|
+
case "text":
|
|
800
|
+
return new Paragraph({
|
|
801
|
+
children: renderText(node.text),
|
|
802
|
+
indent: node.noIndent ? { firstLine: 0 } : {},
|
|
803
|
+
...node.center ? { alignment: "center", indent: { firstLine: 0 } } : {},
|
|
804
|
+
spacing: {
|
|
805
|
+
...node.noMargin ? { after: 0 } : {},
|
|
806
|
+
...prevChild instanceof Table ? { before: (doc.table.spacing.after ?? doc.text.spacing.after) * 20 } : {}
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
case "title":
|
|
810
|
+
if (node.level < 0 || node.level > 6) throw new Error("Wrong heading level");
|
|
811
|
+
const level = node.level == 0 ? 1 : node.level;
|
|
812
|
+
const styles = doc.headings[`h${level}`];
|
|
813
|
+
if (styles.uppercase) node.text.forEach((r) => r.text = r.text.toUpperCase());
|
|
814
|
+
return new Paragraph({
|
|
815
|
+
children: renderText(node.text, styles.size),
|
|
816
|
+
style: `${node.level}`,
|
|
817
|
+
...node.center ? { alignment: "center", indent: { firstLine: 0 } } : {},
|
|
818
|
+
...!node.center && node.level != 0 && styles.indent_full ? { indent: { firstLine: 0, left: convertMillimetersToTwip(12.5) } } : {},
|
|
819
|
+
spacing: {
|
|
820
|
+
before: styles.spacing.before * 20,
|
|
821
|
+
after: styles.spacing.after * 20
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
case "pageBreak":
|
|
825
|
+
if (!(prevChild instanceof Paragraph))
|
|
826
|
+
return new Paragraph({ children: [new PageBreak()] });
|
|
827
|
+
prevChild.addChildElement(new PageBreak());
|
|
828
|
+
return [];
|
|
829
|
+
case "list":
|
|
830
|
+
let renderList2 = function(node2, level2 = 0) {
|
|
831
|
+
let mark = node2.mark;
|
|
832
|
+
if (node2.ordered) mark = doc.list.ordered.style == "keep" ? mark : doc.list.ordered.style == "dot" ? "." : ")";
|
|
833
|
+
else mark = doc.list.unordered.style == "keep" ? mark : doc.list.unordered.style == "bullet" ? "*" : "-";
|
|
834
|
+
addListItemLevel(list.levels, level2, node2.startIndex, node2.items.length, !!node2.ordered, !!node2.alternativeStyle, mark);
|
|
835
|
+
for (const item of node2.items) {
|
|
836
|
+
if (item.type == "list") renderList2({ ...item, alternativeStyle: !!node2.alternativeStyle }, level2 + 1);
|
|
837
|
+
else {
|
|
838
|
+
items.push(new Paragraph({
|
|
839
|
+
children: renderText(item.text),
|
|
840
|
+
style: STYLE_list,
|
|
841
|
+
numbering: { reference: id, level: level2 },
|
|
842
|
+
spacing: {
|
|
843
|
+
...addMargin ? { before: (doc.table.spacing.after ?? doc.text.spacing.after) * 20 } : {}
|
|
844
|
+
}
|
|
845
|
+
}));
|
|
846
|
+
addMargin = false;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
var renderList = renderList2;
|
|
851
|
+
if (node.items.length == 0) return [];
|
|
852
|
+
const id = `l${numbering.length + 1}`;
|
|
853
|
+
const list = {
|
|
854
|
+
reference: id,
|
|
855
|
+
levels: []
|
|
856
|
+
};
|
|
857
|
+
numbering.push(list);
|
|
858
|
+
const items = [];
|
|
859
|
+
let addMargin = prevChild instanceof Table;
|
|
860
|
+
renderList2(node);
|
|
861
|
+
return items;
|
|
862
|
+
case "table":
|
|
863
|
+
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);
|
|
864
|
+
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);
|
|
865
|
+
const spacingInner = Math.max(doc.table.spacing.after ?? doc.text.spacing.after, doc.table.spacing.before ?? 0);
|
|
866
|
+
return [
|
|
867
|
+
...node.title ? [
|
|
868
|
+
new Paragraph({
|
|
869
|
+
children: renderText(node.title, doc.table.title.size),
|
|
870
|
+
style: STYLE_table_title,
|
|
871
|
+
...prevChild instanceof Table ? { spacing: { before: spacingInner * 20 } } : doc.table.spacing.before ? { spacing: { before: doc.table.spacing.before * 20 } } : {}
|
|
872
|
+
})
|
|
873
|
+
] : prevChild instanceof Table ? [
|
|
874
|
+
new Paragraph({ spacing: { line: 20, before: spacingInner * 20, after: 0 } })
|
|
875
|
+
] : doc.table.spacing.before ? [
|
|
876
|
+
new Paragraph({ spacing: { line: 20, before: doc.table.spacing.before * 20, after: 0 } })
|
|
877
|
+
] : [],
|
|
878
|
+
new Table({
|
|
879
|
+
// width: { type: "pct", size: 100 },
|
|
880
|
+
width: { type: "dxa", size: 9572 },
|
|
881
|
+
rows: node.rows.map((row, rowI) => new TableRow({
|
|
882
|
+
tableHeader: rowI == 0,
|
|
883
|
+
cantSplit: true,
|
|
884
|
+
children: row.map((item, colI) => new TableCell({
|
|
885
|
+
children: item.type != "text" ? renderNodeL2(item) : [
|
|
886
|
+
new Paragraph({
|
|
887
|
+
children: renderText(item.text, node.normalFontSize ? void 0 : doc.table.text.size),
|
|
888
|
+
alignment: rowI == 0 ? doc.table.heading.align : node.align[colI] == "c" ? "center" : node.align[colI] == "r" ? "right" : "left",
|
|
889
|
+
indent: { firstLine: 0 },
|
|
890
|
+
spacing: { after: 0, line: 240 * doc.table.text.line_spacing }
|
|
891
|
+
})
|
|
892
|
+
]
|
|
893
|
+
}))
|
|
894
|
+
}))
|
|
895
|
+
})
|
|
896
|
+
];
|
|
897
|
+
case "tableOfContents":
|
|
898
|
+
return [
|
|
899
|
+
new TableOfContents("\u041E\u0433\u043B\u0430\u0432\u043B\u0435\u043D\u0438\u0435", {
|
|
900
|
+
hyperlink: true,
|
|
901
|
+
headingStyleRange: "1-3"
|
|
902
|
+
}),
|
|
903
|
+
new Paragraph({ children: [new PageBreak()] })
|
|
904
|
+
];
|
|
905
|
+
case "image":
|
|
906
|
+
const type = node.src.split(".").at(-1) || "";
|
|
907
|
+
if (!["jpg", "png", "gif", "bmp", "svg"].includes(type))
|
|
908
|
+
throw new Error(`Unsupported image format: "${type}", file: ${node.src}`);
|
|
909
|
+
const img_path = getPath(node.src);
|
|
910
|
+
if (!fs3.existsSync(img_path))
|
|
911
|
+
throw new Error(`File not exist: ${node.src}`);
|
|
912
|
+
const data = fs3.readFileSync(img_path);
|
|
913
|
+
const dimensions = imageSize(data);
|
|
914
|
+
const [MaxW, MaxH] = [600, 800];
|
|
915
|
+
let [width, height] = [dimensions.width, dimensions.height];
|
|
916
|
+
if (node.width && node.height) [width, height] = [node.width, node.height];
|
|
917
|
+
if (node.width) [width, height] = [node.width, height / width * node.width];
|
|
918
|
+
if (node.height) [width, height] = [width / height * node.height, node.height];
|
|
919
|
+
if (height > MaxH) [width, height] = [width / height * MaxH, MaxH];
|
|
920
|
+
if (width > MaxW) [width, height] = [MaxW, height / width * MaxW];
|
|
921
|
+
return [
|
|
922
|
+
new Paragraph({
|
|
923
|
+
alignment: "center",
|
|
924
|
+
indent: { firstLine: 0 },
|
|
925
|
+
spacing: {
|
|
926
|
+
line: 240,
|
|
927
|
+
...!node.text && doc.img.spacing.after !== void 0 ? { after: doc.img.spacing.after * 20 } : {},
|
|
928
|
+
...doc.img.spacing.before !== void 0 ? { before: doc.img.spacing.before * 20 } : {}
|
|
929
|
+
},
|
|
930
|
+
keepNext: !!node.text,
|
|
931
|
+
children: [
|
|
932
|
+
new ImageRun({
|
|
933
|
+
type,
|
|
934
|
+
data,
|
|
935
|
+
transformation: { width, height },
|
|
936
|
+
...type == "svg" ? { fallback: { data: "", type: "png" } } : {}
|
|
937
|
+
})
|
|
938
|
+
]
|
|
939
|
+
}),
|
|
940
|
+
...node.text ? [
|
|
941
|
+
new Paragraph({
|
|
942
|
+
children: renderText(node.text, doc.img.text.size),
|
|
943
|
+
alignment: "center",
|
|
944
|
+
indent: { firstLine: 0 },
|
|
945
|
+
spacing: {
|
|
946
|
+
line: 240,
|
|
947
|
+
...doc.img.spacing.after !== void 0 ? { after: doc.img.spacing.after * 20 } : {}
|
|
948
|
+
}
|
|
949
|
+
})
|
|
950
|
+
] : []
|
|
951
|
+
];
|
|
952
|
+
case "code":
|
|
953
|
+
const code = doc.code.highlight && renderCodeHighlighting(node.code, node.lang);
|
|
954
|
+
return [
|
|
955
|
+
...node.title ? [
|
|
956
|
+
new Paragraph({
|
|
957
|
+
children: renderText(node.title, doc.code.title.size),
|
|
958
|
+
style: STYLE_code_title
|
|
959
|
+
})
|
|
960
|
+
] : prevNode?.type == "code" ? [
|
|
961
|
+
new Paragraph({ spacing: { line: 20 } })
|
|
962
|
+
] : [],
|
|
963
|
+
...code ? code : node.code.split("\n").map((p, i) => new Paragraph({
|
|
964
|
+
children: [new TextRun(p)],
|
|
965
|
+
style: STYLE_code,
|
|
966
|
+
...i == 0 && !node.title && doc.code.spacing.before && prevNode?.type != "code" ? { spacing: { before: doc.code.spacing.before * 20 } } : {}
|
|
967
|
+
}))
|
|
968
|
+
];
|
|
969
|
+
case "externalDoc":
|
|
970
|
+
const doc_path = getPath(node.path);
|
|
971
|
+
if (!fs3.existsSync(doc_path))
|
|
972
|
+
throw new Error(`File not exist: ${node.path}`);
|
|
973
|
+
const ext = path2.extname(doc_path);
|
|
974
|
+
if (ext != ".docx" && ext != ".pdf")
|
|
975
|
+
throw new Error(`File not .docx or .pdf: ${node.path}`);
|
|
976
|
+
return new Paragraph({
|
|
977
|
+
text: `!!(${doc_path})${JSON.stringify(node.dict)}`,
|
|
978
|
+
indent: { firstLine: 0 },
|
|
979
|
+
alignment: "left"
|
|
980
|
+
});
|
|
981
|
+
case "sectionBreak":
|
|
982
|
+
return [];
|
|
983
|
+
default:
|
|
984
|
+
node;
|
|
985
|
+
throw new Error("switch default");
|
|
986
|
+
}
|
|
987
|
+
}, renderNodeL2 = function(node, prevChild, prevNode) {
|
|
988
|
+
const r = renderNode2(node, prevChild, prevNode);
|
|
989
|
+
if (r instanceof Array) return r;
|
|
990
|
+
return [r];
|
|
991
|
+
};
|
|
992
|
+
var renderNode = renderNode2, renderNodeL = renderNodeL2;
|
|
993
|
+
const children = [];
|
|
994
|
+
sections.push({
|
|
995
|
+
children,
|
|
996
|
+
properties: {
|
|
997
|
+
page: {
|
|
998
|
+
pageNumbers: !section.pageStart ? {} : {
|
|
999
|
+
start: section.pageStart
|
|
1000
|
+
},
|
|
1001
|
+
margin: {
|
|
1002
|
+
top: convertMillimetersToTwip(20),
|
|
1003
|
+
left: convertMillimetersToTwip(30),
|
|
1004
|
+
right: convertMillimetersToTwip(15),
|
|
1005
|
+
bottom: convertMillimetersToTwip(20)
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
},
|
|
1009
|
+
footers: {
|
|
1010
|
+
default: new Footer({
|
|
1011
|
+
children: [
|
|
1012
|
+
new Paragraph({
|
|
1013
|
+
alignment: AlignmentType.CENTER,
|
|
1014
|
+
children: [new TextRun({
|
|
1015
|
+
children: section.pageStart ? [PageNumber.CURRENT] : []
|
|
1016
|
+
})],
|
|
1017
|
+
indent: { firstLine: 0 },
|
|
1018
|
+
spacing: { line: 240 }
|
|
1019
|
+
})
|
|
1020
|
+
]
|
|
1021
|
+
})
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
1024
|
+
for (let i = 0; i < section.nodes.length; i++)
|
|
1025
|
+
children.push(...renderNodeL2(section.nodes[i], children.at(-1), section.nodes[i - 1]));
|
|
1026
|
+
}
|
|
1027
|
+
const docx = new Document({
|
|
1028
|
+
sections,
|
|
1029
|
+
externalStyles: genXml_style(assets, doc),
|
|
1030
|
+
// features: { updateFields: true },
|
|
1031
|
+
numbering: { config: numbering },
|
|
1032
|
+
...doc.rainbow ? { background: { color: "#000000" } } : {},
|
|
1033
|
+
...doc.hyphenation ? {
|
|
1034
|
+
hyphenation: {
|
|
1035
|
+
autoHyphenation: true,
|
|
1036
|
+
consecutiveHyphenLimit: 2,
|
|
1037
|
+
doNotHyphenateCaps: true,
|
|
1038
|
+
hyphenationZone: 360
|
|
1039
|
+
}
|
|
1040
|
+
} : {}
|
|
1041
|
+
// title: doc.title,
|
|
1042
|
+
// creator: doc.author,
|
|
1043
|
+
// lastModifiedBy: doc.author,
|
|
1044
|
+
});
|
|
1045
|
+
const buffer = await Packer.toBuffer(docx, void 0, [
|
|
1046
|
+
{ path: "docProps/app.xml", data: genXml_app({ totalTime: doc.etime }) },
|
|
1047
|
+
{
|
|
1048
|
+
path: "docProps/core.xml",
|
|
1049
|
+
data: genXml_core({
|
|
1050
|
+
title: doc.title,
|
|
1051
|
+
creator: doc.author,
|
|
1052
|
+
createdAt: doc.ctime,
|
|
1053
|
+
modifiedAt: doc.mtime
|
|
1054
|
+
})
|
|
1055
|
+
}
|
|
1056
|
+
]);
|
|
1057
|
+
fs3.writeFileSync(fout, buffer);
|
|
1058
|
+
function renderText(text, size) {
|
|
1059
|
+
function renderRune(rune, link = false) {
|
|
1060
|
+
if (rune.anchor) return new Bookmark({
|
|
1061
|
+
id: rune.anchor,
|
|
1062
|
+
children: [renderRune({ ...rune, anchor: void 0 })]
|
|
1063
|
+
});
|
|
1064
|
+
if (rune.link) {
|
|
1065
|
+
const children2 = [renderRune({ ...rune, link: void 0 }, true)];
|
|
1066
|
+
return rune.link.startsWith("#") ? new InternalHyperlink({ children: children2, anchor: rune.link.slice(1) }) : new ExternalHyperlink({ children: children2, link: rune.link });
|
|
1067
|
+
}
|
|
1068
|
+
let children = null;
|
|
1069
|
+
if (rune.type == "val" && rune.text == "pages") {
|
|
1070
|
+
rune.type = "text";
|
|
1071
|
+
children = [PageNumber.TOTAL_PAGES];
|
|
1072
|
+
}
|
|
1073
|
+
return new TextRun({
|
|
1074
|
+
...children ? { children } : { text: rune.text },
|
|
1075
|
+
language: { value: rune.lang == "en" ? "en-US" : "ru-RU" },
|
|
1076
|
+
...rune.linebreak ? { break: 1 } : {},
|
|
1077
|
+
...rune.bold ? { bold: true } : {},
|
|
1078
|
+
...rune.italic ? { italics: true } : {},
|
|
1079
|
+
...link ? { color: "0563c1", underline: { type: "single" } } : {},
|
|
1080
|
+
...rune.color ? { color: rune.color } : {},
|
|
1081
|
+
...size && size > 0 ? { size: size * 2 } : {},
|
|
1082
|
+
...rune.type && rune.type != "text" ? { highlight: "black", color: "ffffff" } : {},
|
|
1083
|
+
...rune.mono ? doc.backtickMono == "outline" ? { font: "consolas", size: (doc.text.size - 1) * 2, border: { style: "single", space: 2 } } : doc.backtickMono == "on" ? { font: "consolas", size: (doc.text.size - 1) * 2 } : doc.backtickMono == "italic" ? { italics: true } : {} : {}
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
if (typeof text == "string") text = [{ text }];
|
|
1087
|
+
if (text.at(-1)?.text == "" && text.at(-1)?.linebreak) text = text.slice(0, -1);
|
|
1088
|
+
text = splitRunesByLang(text);
|
|
1089
|
+
return text.map((r) => renderRune(r));
|
|
1090
|
+
function splitRunesByLang(runes) {
|
|
1091
|
+
const result = [];
|
|
1092
|
+
for (let r = 0; r < runes.length; r++) {
|
|
1093
|
+
const rune = runes[r];
|
|
1094
|
+
const text2 = rune.text;
|
|
1095
|
+
if (!text2 || text2.length === 0) {
|
|
1096
|
+
result.push(rune);
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
let bufferStart = 0;
|
|
1100
|
+
let lastType = 0;
|
|
1101
|
+
let segmentType = 0;
|
|
1102
|
+
for (let i = 0; i < text2.length; i++) {
|
|
1103
|
+
const code = text2.charCodeAt(i);
|
|
1104
|
+
let type = 0;
|
|
1105
|
+
if (code >= 65 && code <= 90 || code >= 97 && code <= 122)
|
|
1106
|
+
type = 1;
|
|
1107
|
+
else if (code >= 1024 && code <= 1279)
|
|
1108
|
+
type = 2;
|
|
1109
|
+
if (segmentType === 0 && type !== 0) segmentType = type;
|
|
1110
|
+
if (i > bufferStart && type !== 0 && lastType !== 0 && type !== lastType) {
|
|
1111
|
+
result.push(copyRune(rune, text2.slice(bufferStart, i), segmentType, bufferStart == 0));
|
|
1112
|
+
bufferStart = i;
|
|
1113
|
+
segmentType = type;
|
|
1114
|
+
}
|
|
1115
|
+
if (type !== 0) lastType = type;
|
|
1116
|
+
}
|
|
1117
|
+
if (bufferStart === 0)
|
|
1118
|
+
result.push(copyRune(rune, text2, segmentType || lastType, true));
|
|
1119
|
+
else
|
|
1120
|
+
result.push(copyRune(rune, text2.slice(bufferStart), segmentType, false));
|
|
1121
|
+
}
|
|
1122
|
+
return result;
|
|
1123
|
+
function copyRune(src, text2, type, first) {
|
|
1124
|
+
return {
|
|
1125
|
+
...src,
|
|
1126
|
+
text: text2,
|
|
1127
|
+
anchor: first ? src.anchor : void 0,
|
|
1128
|
+
linebreak: src.linebreak && first,
|
|
1129
|
+
lang: type == 1 ? "en" : type == 2 ? "ru" : void 0
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
function addListItemLevel(levels, level, startIndex, itemCount, ordered, alternativeStyle, mark) {
|
|
1136
|
+
if (levels.find((v) => v.level == level)) return;
|
|
1137
|
+
levels.sort((a, b) => a.level - b.level);
|
|
1138
|
+
let indent = level;
|
|
1139
|
+
const format = ordered ? LevelFormat.DECIMAL : LevelFormat.BULLET;
|
|
1140
|
+
for (let i = 1; i < levels.length + 1; i++) {
|
|
1141
|
+
const prevprevformat = levels[i - 2]?.format;
|
|
1142
|
+
const prevformat = levels[i - 1]?.format;
|
|
1143
|
+
const curformat = i < levels.length ? levels[i]?.format : format;
|
|
1144
|
+
if (prevprevformat != curformat && prevformat != curformat) indent--;
|
|
1145
|
+
}
|
|
1146
|
+
const longList = startIndex + itemCount > 10;
|
|
1147
|
+
const left = alternativeStyle ? 0 : longList ? 20.5 : 17.5;
|
|
1148
|
+
const hanging = longList && !alternativeStyle ? convertMillimetersToTwip(8) : null;
|
|
1149
|
+
const text = ordered ? alternativeStyle ? `%${level + 1}.` : mark == "." ? `%${level + 1}.` : `%${level + 1})` : (
|
|
1150
|
+
// mark == "*" ? "\u2022" : "\u2012";
|
|
1151
|
+
mark == "*" ? "\xB7" : "\u2012"
|
|
1152
|
+
);
|
|
1153
|
+
levels.push({
|
|
1154
|
+
level,
|
|
1155
|
+
format,
|
|
1156
|
+
start: startIndex,
|
|
1157
|
+
text,
|
|
1158
|
+
style: {
|
|
1159
|
+
...text == "\xB7" ? {
|
|
1160
|
+
run: {
|
|
1161
|
+
font: { name: "Symbol" }
|
|
1162
|
+
}
|
|
1163
|
+
} : {},
|
|
1164
|
+
paragraph: {
|
|
1165
|
+
indent: {
|
|
1166
|
+
left: convertMillimetersToTwip(left + 5 * indent),
|
|
1167
|
+
...alternativeStyle ? { firstLine: convertMillimetersToTwip(12.5) } : {},
|
|
1168
|
+
...hanging ? { hanging } : {}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
function renderCodeHighlighting(code, lang) {
|
|
1175
|
+
const grammar = Prism.languages[lang.trim().toLowerCase()];
|
|
1176
|
+
if (!grammar) return null;
|
|
1177
|
+
const themeColors = {
|
|
1178
|
+
// shared
|
|
1179
|
+
"plain": "000000",
|
|
1180
|
+
"comment": "008000",
|
|
1181
|
+
"punctuation": "000000",
|
|
1182
|
+
"operator": "000000",
|
|
1183
|
+
// keywords & control flow
|
|
1184
|
+
"keyword": "0000ff",
|
|
1185
|
+
"boolean": "0000ff",
|
|
1186
|
+
"builtin": "0000ff",
|
|
1187
|
+
"important": "0000ff",
|
|
1188
|
+
// literals
|
|
1189
|
+
"string": lang === "css" ? "0451a5" : lang === "json" ? "a31515" : "a31515",
|
|
1190
|
+
"number": "098658",
|
|
1191
|
+
"constant": "0000ff",
|
|
1192
|
+
"symbol": "0000ff",
|
|
1193
|
+
"char": "a31515",
|
|
1194
|
+
// identifiers
|
|
1195
|
+
"class-name": lang === "js" || lang === "ts" ? "267f99" : "0451a5",
|
|
1196
|
+
"function": "0451a5",
|
|
1197
|
+
"variable": "000000",
|
|
1198
|
+
"namespace": "000000",
|
|
1199
|
+
"property": lang === "css" ? "e50000" : "0451a5",
|
|
1200
|
+
// markup / HTML / XML
|
|
1201
|
+
"tag": lang === "html" || lang === "xml" ? "800000" : "0000ff",
|
|
1202
|
+
"selector": "800000",
|
|
1203
|
+
"attr-name": "e50000",
|
|
1204
|
+
"attr-value": "a31515",
|
|
1205
|
+
"entity": "800000",
|
|
1206
|
+
"doctype": "808080",
|
|
1207
|
+
"cdata": "808080",
|
|
1208
|
+
"prolog": "808080",
|
|
1209
|
+
// CSS specific
|
|
1210
|
+
"atrule": "0000ff",
|
|
1211
|
+
"url": "0451a5",
|
|
1212
|
+
// regex
|
|
1213
|
+
"regex": "811f3f"
|
|
1214
|
+
};
|
|
1215
|
+
const tokens = Prism.tokenize(code, grammar);
|
|
1216
|
+
return tokensToParagraphs(tokens);
|
|
1217
|
+
function tokensToParagraphs(tokens2) {
|
|
1218
|
+
const paragraphs = [];
|
|
1219
|
+
let currentRuns = [];
|
|
1220
|
+
function addText(text, type) {
|
|
1221
|
+
const lines = text.split("\n");
|
|
1222
|
+
lines.forEach((line, index) => {
|
|
1223
|
+
currentRuns.push(
|
|
1224
|
+
new TextRun({
|
|
1225
|
+
text: line,
|
|
1226
|
+
color: themeColors[type] || themeColors["plain"],
|
|
1227
|
+
bold: type === "bold",
|
|
1228
|
+
italics: type === "italic"
|
|
1229
|
+
})
|
|
1230
|
+
);
|
|
1231
|
+
if (index < lines.length - 1) {
|
|
1232
|
+
paragraphs.push(new Paragraph({ children: currentRuns, style: STYLE_code }));
|
|
1233
|
+
currentRuns = [];
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
function process2(token, parentType = "plain") {
|
|
1238
|
+
if (typeof token === "string")
|
|
1239
|
+
addText(token, parentType);
|
|
1240
|
+
else if (Array.isArray(token.content))
|
|
1241
|
+
token.content.forEach((t) => process2(t, token.type));
|
|
1242
|
+
else if (typeof token.content === "string")
|
|
1243
|
+
addText(token.content, token.type);
|
|
1244
|
+
else
|
|
1245
|
+
process2(token.content, token.type);
|
|
1246
|
+
}
|
|
1247
|
+
tokens2.forEach((t) => process2(t));
|
|
1248
|
+
if (currentRuns.length > 0) {
|
|
1249
|
+
paragraphs.push(new Paragraph({ children: currentRuns, style: STYLE_code }));
|
|
1250
|
+
}
|
|
1251
|
+
return paragraphs;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
function genXml_app({ totalTime }) {
|
|
1255
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1256
|
+
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
|
|
1257
|
+
<TotalTime>${totalTime || randomInt(30, 120)}</TotalTime>
|
|
1258
|
+
</Properties>
|
|
1259
|
+
`;
|
|
1260
|
+
}
|
|
1261
|
+
function genXml_core({ title, creator, createdAt, modifiedAt }) {
|
|
1262
|
+
if (!createdAt) createdAt = /* @__PURE__ */ new Date();
|
|
1263
|
+
if (!modifiedAt) modifiedAt = /* @__PURE__ */ new Date();
|
|
1264
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1265
|
+
<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">
|
|
1266
|
+
<dc:title>${title || "Document"}</dc:title>
|
|
1267
|
+
<dc:creator>${creator || "Student"}</dc:creator>
|
|
1268
|
+
<cp:lastModifiedBy>${creator || "Student"}</cp:lastModifiedBy>
|
|
1269
|
+
<dcterms:created xsi:type="dcterms:W3CDTF">${createdAt.toISOString()}</dcterms:created>
|
|
1270
|
+
<dcterms:modified xsi:type="dcterms:W3CDTF">${modifiedAt.toISOString()}</dcterms:modified>
|
|
1271
|
+
</cp:coreProperties>`;
|
|
1272
|
+
}
|
|
1273
|
+
function genXml_style(assets, doc) {
|
|
1274
|
+
let xml = fs3.readFileSync(path2.join(assets, "styles.xml"), { encoding: "utf8" });
|
|
1275
|
+
xml = editStyle(`<w:style w:type="paragraph" w:default="1" w:styleId="a2">`, (xml2) => {
|
|
1276
|
+
xml2 = replaceTag(xml2, `<w:spacing w:line="${doc.text.line_spacing * 240}" w:lineRule="auto" w:after="${doc.text.spacing.after * 20}"/>`);
|
|
1277
|
+
xml2 = replaceTag(xml2, `<w:ind w:firstLine="${convertMillimetersToTwip(doc.text.indent * 10)}"/>`);
|
|
1278
|
+
xml2 = replaceTag(xml2, `<w:sz w:val="${doc.text.size * 2}"/>`);
|
|
1279
|
+
return xml2;
|
|
1280
|
+
});
|
|
1281
|
+
xml = editStyle(`<w:style w:type="paragraph" w:customStyle="1" w:styleId="ListingCaption">`, (xml2) => {
|
|
1282
|
+
if (doc.code.spacing.before !== void 0)
|
|
1283
|
+
xml2 = replaceTag(xml2, `<w:spacing w:after="0" w:line="240" w:lineRule="auto" w:before="${doc.code.spacing.before * 20}"/>`);
|
|
1284
|
+
if (doc.code.title.style != "normal")
|
|
1285
|
+
xml2 = addrPrStyle(xml2, doc.code.title.style == "bold" ? "<w:b/>" : "<w:i/>");
|
|
1286
|
+
return xml2;
|
|
1287
|
+
});
|
|
1288
|
+
xml = editStyle(`<w:style w:type="paragraph" w:customStyle="1" w:styleId="ListingCode">`, (xml2) => {
|
|
1289
|
+
xml2 = replaceTag(xml2, `<w:sz w:val="${doc.code.text.size * 2}"/>`);
|
|
1290
|
+
if (doc.code.spacing.after !== void 0)
|
|
1291
|
+
xml2 = replaceTag(xml2, `<w:spacing w:line="240" w:lineRule="auto" w:after="${doc.code.spacing.after * 20}"/>`);
|
|
1292
|
+
return xml2;
|
|
1293
|
+
});
|
|
1294
|
+
xml = editStyle(`<w:style w:type="paragraph" w:customStyle="1" w:styleId="TableCaption">`, (xml2) => {
|
|
1295
|
+
if (doc.table.title.style != "normal")
|
|
1296
|
+
xml2 = addrPrStyle(xml2, doc.table.title.style == "bold" ? "<w:b/>" : "<w:i/>");
|
|
1297
|
+
return xml2;
|
|
1298
|
+
});
|
|
1299
|
+
return xml;
|
|
1300
|
+
function replaceTag(xml2, tag) {
|
|
1301
|
+
const tagName = /<([^\s]+)\b/.exec(tag)?.[1];
|
|
1302
|
+
if (!tagName) throw err();
|
|
1303
|
+
const regex = new RegExp(`(<${tagName}\\b[^>]*/>)`, "s");
|
|
1304
|
+
return xml2.replace(regex, tag);
|
|
1305
|
+
}
|
|
1306
|
+
function editStyle(tag, f) {
|
|
1307
|
+
const startI = xml.indexOf(tag);
|
|
1308
|
+
if (startI < 0) throw err();
|
|
1309
|
+
let endI = xml.indexOf(`</w:style>`, startI);
|
|
1310
|
+
if (endI < 0) throw err();
|
|
1311
|
+
endI += `</w:style>`.length;
|
|
1312
|
+
return xml.slice(0, startI) + f(xml.slice(startI, endI)) + xml.slice(endI);
|
|
1313
|
+
}
|
|
1314
|
+
function insertAfter(xml2, search, value) {
|
|
1315
|
+
const sI = xml2.indexOf(search);
|
|
1316
|
+
if (sI < 0) return { ok: false, xml: xml2 };
|
|
1317
|
+
const i = sI + search.length;
|
|
1318
|
+
return { ok: true, xml: xml2.slice(0, i) + value + xml2.slice(i) };
|
|
1319
|
+
}
|
|
1320
|
+
function insertBefore(xml2, search, value) {
|
|
1321
|
+
const i = xml2.indexOf(search);
|
|
1322
|
+
if (i < 0) return { ok: false, xml: xml2 };
|
|
1323
|
+
return { ok: true, xml: xml2.slice(0, i) + value + xml2.slice(i) };
|
|
1324
|
+
}
|
|
1325
|
+
function addrPrStyle(xml2, value) {
|
|
1326
|
+
let r = insertAfter(xml2, "<w:rPr>", value);
|
|
1327
|
+
if (r.ok) return r.xml;
|
|
1328
|
+
r = insertBefore(xml2, "</w:style>", `<w:rPr>${value}</w:rPr>`);
|
|
1329
|
+
if (r.ok) return r.xml;
|
|
1330
|
+
throw err();
|
|
1331
|
+
}
|
|
1332
|
+
function err() {
|
|
1333
|
+
return new Error(`styles.xml is corrupted (broken extension version - upgrade or downgrade)`);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// src/enricher.ts
|
|
1338
|
+
function enrichDoc(doc, logwarn = console.warn) {
|
|
1339
|
+
for (let i = 0; i < doc.nodes.length; i++) {
|
|
1340
|
+
const node = doc.nodes[i];
|
|
1341
|
+
if (node.type == "title" && node.text.toUpperCase().startsWith("\u0422\u0418\u0422\u0423\u041B\u042C\u041D\u0418\u041A")) {
|
|
1342
|
+
doc.nodes.splice(i, 1, { type: "text", text: node.text.toUpperCase() });
|
|
1343
|
+
doc.nodes.splice(i + 1, 0, { type: "pageBreak" });
|
|
1344
|
+
i++;
|
|
1345
|
+
} else if (node.type == "title" && node.text.toUpperCase() == "\u0420\u0415\u0424\u0415\u0420\u0410\u0422") {
|
|
1346
|
+
node.level = 0;
|
|
1347
|
+
node.text = node.text.toUpperCase();
|
|
1348
|
+
const nextNode = doc.nodes[i + 1];
|
|
1349
|
+
if (nextNode?.type == "text") {
|
|
1350
|
+
nextNode.noIndent = true;
|
|
1351
|
+
nextNode.text = trimEnd(nextNode.text, ".").toUpperCase();
|
|
1352
|
+
}
|
|
1353
|
+
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." });
|
|
1354
|
+
while (i + 1 < doc.nodes.length && doc.nodes[i + 1]?.type != "title") i++;
|
|
1355
|
+
doc.nodes.splice(i + 1, 0, { type: "pageBreak" });
|
|
1356
|
+
} else if (node.type == "title" && node.text.toUpperCase() == "\u041E\u0413\u041B\u0410\u0412\u041B\u0415\u041D\u0418\u0415") {
|
|
1357
|
+
node.level = 0;
|
|
1358
|
+
node.text = node.text.toUpperCase();
|
|
1359
|
+
doc.nodes.splice(i + 1, 0, { type: "tableOfContents" });
|
|
1360
|
+
} 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") {
|
|
1361
|
+
node.center = true;
|
|
1362
|
+
node.text = node.text.toUpperCase();
|
|
1363
|
+
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");
|
|
1364
|
+
const list = doc.nodes[i + 1];
|
|
1365
|
+
if (list?.type != "list") {
|
|
1366
|
+
warn();
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
const rows = [tableRow("\u0422\u0435\u0440\u043C\u0438\u043D", "\u041E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D\u0438\u0435")];
|
|
1370
|
+
const align = ["l", "l"];
|
|
1371
|
+
for (const item of list.items) {
|
|
1372
|
+
if (item.type != "listItem") {
|
|
1373
|
+
warn();
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
const i2 = item.text.indexOf(":");
|
|
1377
|
+
if (i2 < 0) {
|
|
1378
|
+
warn();
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1381
|
+
rows.push(tableRow(item.text.slice(0, i2).trim(), item.text.slice(i2 + 1).trim()));
|
|
1382
|
+
}
|
|
1383
|
+
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." });
|
|
1384
|
+
doc.nodes.splice(i + 2, 0, { type: "table", align, rows, normalFontSize: true, tags: ["definitions_table"] });
|
|
1385
|
+
doc.nodes.splice(i + 3, 0, { type: "pageBreak" });
|
|
1386
|
+
} 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") {
|
|
1387
|
+
node.center = true;
|
|
1388
|
+
node.text = node.text.toUpperCase();
|
|
1389
|
+
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");
|
|
1390
|
+
const list = doc.nodes[i + 1];
|
|
1391
|
+
if (list?.type != "list") {
|
|
1392
|
+
warn();
|
|
1393
|
+
continue;
|
|
1394
|
+
}
|
|
1395
|
+
const items = [];
|
|
1396
|
+
for (const item of list.items) {
|
|
1397
|
+
if (item.type != "listItem") {
|
|
1398
|
+
warn();
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
items.push({ type: "text", text: trimEnd(item.text, "."), noIndent: true, noMargin: true });
|
|
1402
|
+
}
|
|
1403
|
+
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." });
|
|
1404
|
+
doc.nodes.splice(i + 2, 0, { type: "pageBreak" });
|
|
1405
|
+
doc.nodes.splice(i + 2, 0, ...items);
|
|
1406
|
+
} else if (node.type == "title" && node.text.toUpperCase() == "\u0412\u0412\u0415\u0414\u0415\u041D\u0418\u0415") {
|
|
1407
|
+
node.center = true;
|
|
1408
|
+
node.text = node.text.toUpperCase();
|
|
1409
|
+
for (let j = i + 1; j < doc.nodes.length; j++) {
|
|
1410
|
+
const node2 = doc.nodes[j];
|
|
1411
|
+
if (node2?.type == "title") {
|
|
1412
|
+
doc.nodes.splice(j, 0, { type: "pageBreak" });
|
|
1413
|
+
break;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
} else if (node.type == "title" && node.text.toUpperCase() == "\u0417\u0410\u041A\u041B\u042E\u0427\u0415\u041D\u0418\u0415") {
|
|
1417
|
+
node.center = true;
|
|
1418
|
+
node.text = node.text.toUpperCase();
|
|
1419
|
+
doc.nodes.splice(i, 0, { type: "pageBreak" });
|
|
1420
|
+
i++;
|
|
1421
|
+
} 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") {
|
|
1422
|
+
node.center = true;
|
|
1423
|
+
node.text = node.text.toUpperCase();
|
|
1424
|
+
doc.nodes.splice(i++, 0, { type: "pageBreak" });
|
|
1425
|
+
const nextNode = doc.nodes[i + 1];
|
|
1426
|
+
if (nextNode?.type == "list") {
|
|
1427
|
+
nextNode.alternativeStyle = true;
|
|
1428
|
+
i++;
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1431
|
+
const list = {
|
|
1432
|
+
type: "list",
|
|
1433
|
+
mark: ".",
|
|
1434
|
+
startIndex: 1,
|
|
1435
|
+
alternativeStyle: true,
|
|
1436
|
+
ordered: true,
|
|
1437
|
+
items: []
|
|
1438
|
+
};
|
|
1439
|
+
const startI = i + 1;
|
|
1440
|
+
while (true) {
|
|
1441
|
+
const textNode = doc.nodes[i + 1];
|
|
1442
|
+
if (textNode?.type != "text") break;
|
|
1443
|
+
const items = textNode.text.split(/\n\s*(?=\[[a-zA-Zа-яА-ЯёЁ_\d]+\])/);
|
|
1444
|
+
for (const item of items)
|
|
1445
|
+
list.items.push({ type: "listItem", text: item });
|
|
1446
|
+
i++;
|
|
1447
|
+
}
|
|
1448
|
+
if (list.items.length > 0)
|
|
1449
|
+
doc.nodes.splice(startI, i - startI + 1, list);
|
|
1450
|
+
} else if (node.type == "title" && node.text.toUpperCase() == "\u041F\u0420\u0418\u041B\u041E\u0416\u0415\u041D\u0418\u042F") {
|
|
1451
|
+
node.level = 0;
|
|
1452
|
+
node.center = true;
|
|
1453
|
+
node.text = node.text.toUpperCase();
|
|
1454
|
+
doc.nodes.splice(i++, 0, { type: "pageBreak" });
|
|
1455
|
+
for (let j = i + 1; j < doc.nodes.length; j++) {
|
|
1456
|
+
const node2 = doc.nodes[j];
|
|
1457
|
+
if (node2.type == "title") {
|
|
1458
|
+
doc.nodes.splice(i++ + 1, 0, { type: "text", text: "\u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 " + node2.text.replace("]", "].") });
|
|
1459
|
+
j++;
|
|
1460
|
+
node2.level = 1;
|
|
1461
|
+
const splitI = node2.text.indexOf("]");
|
|
1462
|
+
doc.nodes.splice(j++, 0, { type: "pageBreak" });
|
|
1463
|
+
if (splitI >= 0) {
|
|
1464
|
+
doc.nodes.splice(j++ + 1, 0, { type: "text", text: node2.text.slice(splitI + 1), center: true });
|
|
1465
|
+
node2.text = "\u041F\u0420\u0418\u041B\u041E\u0416\u0415\u041D\u0418\u0415 " + node2.text.slice(0, splitI + 1);
|
|
1466
|
+
node2.center = true;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
} else if (node.type == "list") {
|
|
1471
|
+
let enrichList2 = function(list, isLastItem, isSubList = false) {
|
|
1472
|
+
const isLongList = 3 < Math.max(...list.items.filter((v) => v.type == "listItem").map((v) => v.text.split(/\s+/).length));
|
|
1473
|
+
const hasSublist = list.items.some((v) => v.type == "list");
|
|
1474
|
+
const ending = isLongList || hasSublist || isSubList ? ";" : ",";
|
|
1475
|
+
for (let i2 = 0; i2 < list.items.length; i2++) {
|
|
1476
|
+
const item = list.items[i2];
|
|
1477
|
+
const itemNext = list.items[i2 + 1];
|
|
1478
|
+
const isLast = i2 == list.items.length - 1;
|
|
1479
|
+
if (item.type == "list") enrichList2(item, isLast, true);
|
|
1480
|
+
else {
|
|
1481
|
+
const end = itemNext?.type == "list" ? ":" : isLastItem && isLast ? "." : ending;
|
|
1482
|
+
item.text = trimEnd(item.text, ".", ",", ";", ":", "!") + end;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
var enrichList = enrichList2;
|
|
1487
|
+
if (doc.list.autopunctuation) enrichList2(node, true);
|
|
1488
|
+
} else if (node.type == "image") {
|
|
1489
|
+
if (node.text)
|
|
1490
|
+
node.text = trimEnd(node.text, ".", ",", ";", ":", "!");
|
|
1491
|
+
} else if (node.type == "table") {
|
|
1492
|
+
if (node.title)
|
|
1493
|
+
node.title = trimEnd(node.title, ".", ",", ";", ":", "!");
|
|
1494
|
+
} else if (node.type == "code") {
|
|
1495
|
+
if (node.title)
|
|
1496
|
+
node.title = trimEnd(node.title, ".", ",", ";", ":", "!");
|
|
1497
|
+
} else if (node.type == "externalDoc") {
|
|
1498
|
+
const nextNode = doc.nodes[i + 1];
|
|
1499
|
+
if (nextNode && nextNode?.type != "externalDoc" && nextNode?.type != "pageBreak" && nextNode?.type != "sectionBreak") {
|
|
1500
|
+
doc.nodes.splice(i + 1, 0, { type: "pageBreak" });
|
|
1501
|
+
}
|
|
1502
|
+
const prevNode = doc.nodes[i - 1];
|
|
1503
|
+
if (prevNode && prevNode?.type != "externalDoc" && prevNode?.type != "pageBreak" && prevNode?.type != "sectionBreak") {
|
|
1504
|
+
doc.nodes.splice(i, 0, { type: "pageBreak" });
|
|
1505
|
+
i++;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
// src/main.ts
|
|
1512
|
+
import { spawn } from "child_process";
|
|
1513
|
+
import fs4 from "fs/promises";
|
|
1514
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1515
|
+
import { PDFDocument } from "pdf-lib";
|
|
1516
|
+
|
|
1517
|
+
// src/alchemist.ts
|
|
1518
|
+
var APPENDIX_NAME = "\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0418\u041A\u041B\u041C\u041D\u041F\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0428\u0429\u042D\u042E\u042F";
|
|
1519
|
+
function alchemist(doc, logwarn = console.warn) {
|
|
1520
|
+
const counter = {
|
|
1521
|
+
codesAll: 0,
|
|
1522
|
+
imgsAll: 0,
|
|
1523
|
+
tablesAll: 0,
|
|
1524
|
+
codes: 0,
|
|
1525
|
+
imgs: 0,
|
|
1526
|
+
tables: 0,
|
|
1527
|
+
titles: { l1: 0, l2: 0, l3: 0, l4: 0, l5: 0 }
|
|
1528
|
+
};
|
|
1529
|
+
const named = {};
|
|
1530
|
+
const vals = {};
|
|
1531
|
+
let lastRefI = 0;
|
|
1532
|
+
let prevNum = -1;
|
|
1533
|
+
let prevPrefix = "";
|
|
1534
|
+
let nextNum = -1;
|
|
1535
|
+
let nextPrefix = "";
|
|
1536
|
+
let prefix = "";
|
|
1537
|
+
let synopsis = null;
|
|
1538
|
+
let appendix = false;
|
|
1539
|
+
if (doc.numberingLazy) addLazyNumbering(doc);
|
|
1540
|
+
doc.nodes.forEach((node, i) => {
|
|
1541
|
+
if (nextNum < 0) {
|
|
1542
|
+
let l1 = counter.titles.l1;
|
|
1543
|
+
let codes = counter.codes;
|
|
1544
|
+
let imgs = counter.imgs;
|
|
1545
|
+
let tables = counter.tables;
|
|
1546
|
+
for (let j = i; j < doc.nodes.length && nextNum < 0; j++) {
|
|
1547
|
+
const n = doc.nodes[j];
|
|
1548
|
+
if (n.type == "code") nextNum = codes + 1;
|
|
1549
|
+
if (n.type == "image") nextNum = imgs + 1;
|
|
1550
|
+
if (n.type == "table") nextNum = tables + 1;
|
|
1551
|
+
if (n.type == "title" && n.level == 1 && doc.numberingSections) {
|
|
1552
|
+
const num = getTitleNum(n);
|
|
1553
|
+
if (num) l1 = num;
|
|
1554
|
+
else l1++;
|
|
1555
|
+
codes = 0;
|
|
1556
|
+
imgs = 0;
|
|
1557
|
+
tables = 0;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
nextPrefix = l1 > 0 && doc.numberingSections ? `${l1}.` : "";
|
|
1561
|
+
}
|
|
1562
|
+
if (node.tags && node.type == "text" && node.tags.includes("synopsis"))
|
|
1563
|
+
synopsis = node;
|
|
1564
|
+
if (node.type == "title") {
|
|
1565
|
+
for (let i2 = 1; i2 <= 5; i2++) {
|
|
1566
|
+
if (i2 == node.level) counter.titles[`l${i2}`]++;
|
|
1567
|
+
if (i2 > node.level) counter.titles[`l${i2}`] = 0;
|
|
1568
|
+
}
|
|
1569
|
+
if (node.level == 0 && node.text.map((r) => r.text).join("").trim().toUpperCase() == "\u041F\u0420\u0418\u041B\u041E\u0416\u0415\u041D\u0418\u042F") {
|
|
1570
|
+
appendix = true;
|
|
1571
|
+
counter.titles.l1 = 0;
|
|
1572
|
+
}
|
|
1573
|
+
if (node.level == 1) {
|
|
1574
|
+
const num = getTitleNum(node);
|
|
1575
|
+
if (num) counter.titles.l1 = num;
|
|
1576
|
+
if (doc.numberingSections || appendix) {
|
|
1577
|
+
counter.codes = 0;
|
|
1578
|
+
counter.imgs = 0;
|
|
1579
|
+
counter.tables = 0;
|
|
1580
|
+
}
|
|
1581
|
+
prefix = doc.numberingSections ? `${counter.titles.l1}.` : "";
|
|
1582
|
+
if (appendix) prefix = APPENDIX_NAME[counter.titles.l1 - 1] + ".";
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
materializeNode(node);
|
|
1586
|
+
if (node.type == "code" || node.type == "image" || node.type == "table") {
|
|
1587
|
+
if (node.type == "code") {
|
|
1588
|
+
counter.codes++;
|
|
1589
|
+
counter.codesAll++;
|
|
1590
|
+
}
|
|
1591
|
+
if (node.type == "image") {
|
|
1592
|
+
counter.imgs++;
|
|
1593
|
+
counter.imgsAll++;
|
|
1594
|
+
}
|
|
1595
|
+
if (node.type == "table" && !node.tags?.includes("definitions_table")) {
|
|
1596
|
+
counter.tables++;
|
|
1597
|
+
counter.tablesAll++;
|
|
1598
|
+
}
|
|
1599
|
+
prevNum = nextNum;
|
|
1600
|
+
prevPrefix = prefix;
|
|
1601
|
+
nextNum = -1;
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
const sourcesCount = crystallizeSources();
|
|
1605
|
+
vals["codes"]?.forEach((f) => f(counter.codes));
|
|
1606
|
+
vals["imgs"]?.forEach((f) => f(counter.imgs));
|
|
1607
|
+
vals["tables"]?.forEach((f) => f(counter.tables));
|
|
1608
|
+
vals["sources"]?.forEach((f) => f(sourcesCount));
|
|
1609
|
+
Object.entries(named).forEach(([k, v]) => {
|
|
1610
|
+
if (v instanceof Array) logwarn(`\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u0441\u0441\u044B\u043B\u043A\u0430 [${k}]`);
|
|
1611
|
+
});
|
|
1612
|
+
if (synopsis) {
|
|
1613
|
+
synopsis.text = [
|
|
1614
|
+
"\u041E\u0442\u0447\u0435\u0442 ",
|
|
1615
|
+
"pages",
|
|
1616
|
+
" \u0441.",
|
|
1617
|
+
counter.imgsAll > 0 && `, ${counter.imgsAll} \u0440\u0438\u0441.`,
|
|
1618
|
+
counter.tablesAll > 0 && `, ${counter.tablesAll} \u0442\u0430\u0431\u043B.`,
|
|
1619
|
+
counter.codesAll > 0 && `, ${counter.codesAll} \u043B\u0438\u0441\u0442.`,
|
|
1620
|
+
sourcesCount > 0 && `, ${sourcesCount} \u0438\u0441\u0442\u043E\u0447\u043D.`,
|
|
1621
|
+
appendix && counter.titles.l1 > 0 && `, ${counter.titles.l1} \u043F\u0440\u0438\u043B.`
|
|
1622
|
+
].filter((v) => !!v).map((text, i) => ({ text, type: i == 1 ? "val" : "text" }));
|
|
1623
|
+
}
|
|
1624
|
+
function getTitleNum(node) {
|
|
1625
|
+
if (node.text.some((r) => r.type == "ref")) return null;
|
|
1626
|
+
const text = node.text[0]?.text;
|
|
1627
|
+
if (!text) return null;
|
|
1628
|
+
const m = /^\s*(\d+)/.exec(text);
|
|
1629
|
+
const num = parseInt(m?.[1] || "");
|
|
1630
|
+
if (!isFinite(num)) return null;
|
|
1631
|
+
return num;
|
|
1632
|
+
}
|
|
1633
|
+
function materializeNode(node) {
|
|
1634
|
+
if ("text" in node && node.text)
|
|
1635
|
+
materializeRunes(node.text, node);
|
|
1636
|
+
if ("title" in node && node.title)
|
|
1637
|
+
materializeRunes(node.title, node);
|
|
1638
|
+
if (node.type == "list") materializeList(node);
|
|
1639
|
+
if (node.type == "table") materializeTable(node);
|
|
1640
|
+
}
|
|
1641
|
+
function materializeList(node) {
|
|
1642
|
+
node.items.forEach((item) => {
|
|
1643
|
+
if (item.type == "list") materializeList(item);
|
|
1644
|
+
else materializeRunes(item.text, node);
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
function materializeTable(node) {
|
|
1648
|
+
node.rows.forEach((row) => row.forEach((item) => {
|
|
1649
|
+
materializeNode(item);
|
|
1650
|
+
}));
|
|
1651
|
+
}
|
|
1652
|
+
function materializeRunes(runes, node) {
|
|
1653
|
+
runes.forEach((rune, i) => {
|
|
1654
|
+
if (rune.type == "val") {
|
|
1655
|
+
if (!(rune.text in vals)) vals[rune.text] = [];
|
|
1656
|
+
vals[rune.text].push((n) => {
|
|
1657
|
+
rune.type = "text";
|
|
1658
|
+
rune.text = `${n}`;
|
|
1659
|
+
});
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
if (rune.type != "ref") return;
|
|
1663
|
+
const type = node.type;
|
|
1664
|
+
const m = /^(.*?)([-\+]\d+)?$/.exec(rune.text.replaceAll(/\s+/g, ""));
|
|
1665
|
+
const tag = m?.[1] || "";
|
|
1666
|
+
const math = m?.[2] || "";
|
|
1667
|
+
const mathSubstarct = math.at(0) == "-";
|
|
1668
|
+
const v = named[tag];
|
|
1669
|
+
const applyMath = (n) => {
|
|
1670
|
+
if (!math) return n;
|
|
1671
|
+
const v2 = parseInt(math.slice(1));
|
|
1672
|
+
if (mathSubstarct) return n - v2;
|
|
1673
|
+
return n + v2;
|
|
1674
|
+
};
|
|
1675
|
+
if (type == "title") {
|
|
1676
|
+
const num = counter.titles[`l${node.level}`];
|
|
1677
|
+
if (appendix) {
|
|
1678
|
+
const char = APPENDIX_NAME[counter.titles.l1 - 1];
|
|
1679
|
+
if (v instanceof Array) v.forEach((fn) => fn.f(-1, char));
|
|
1680
|
+
named[tag] = { n: -1, prefix: char };
|
|
1681
|
+
rune.type = "text";
|
|
1682
|
+
rune.text = char;
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
const prefix2 = node.level <= 1 ? "" : repeat(node.level - 1, (i2) => counter.titles[`l${i2 + 1}`]).join(".") + ".";
|
|
1686
|
+
if (v instanceof Array) v.forEach((fn) => fn.f(num, prefix2));
|
|
1687
|
+
named[tag] = { n: num, prefix: prefix2 };
|
|
1688
|
+
rune.type = "text";
|
|
1689
|
+
rune.text = repeat(node.level, (i2) => counter.titles[`l${i2 + 1}`]).join(".");
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
if (type == "code" || type == "image" || type == "table") {
|
|
1693
|
+
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" } : (() => {
|
|
1694
|
+
throw new Error("switch default");
|
|
1695
|
+
})();
|
|
1696
|
+
num++;
|
|
1697
|
+
if (doc.numberingAutoprefix) {
|
|
1698
|
+
const prevRune = runes[i - 1];
|
|
1699
|
+
if (prevRune && prevRune.text.trim().toLowerCase() == text.toLowerCase())
|
|
1700
|
+
prevRune.text = "";
|
|
1701
|
+
const nextRune = runes[i + 1];
|
|
1702
|
+
if (nextRune) {
|
|
1703
|
+
nextRune.text = nextRune.text.trimStart();
|
|
1704
|
+
if (nextRune.text[0] == "-") nextRune.text = nextRune.text.slice(1);
|
|
1705
|
+
if (nextRune.text[0] == "\u2013") nextRune.text = nextRune.text.slice(1);
|
|
1706
|
+
nextRune.text = nextRune.text.trimStart();
|
|
1707
|
+
nextRune.text = toCapitalCase(nextRune.text);
|
|
1708
|
+
}
|
|
1709
|
+
text = `${text} ${prefix}${num} \u2013 `;
|
|
1710
|
+
} else text = `${prefix}${num}`;
|
|
1711
|
+
if (v instanceof Array) v.forEach((fn) => fn.f(num, prefix));
|
|
1712
|
+
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`);
|
|
1713
|
+
named[tag] = { n: num, prefix };
|
|
1714
|
+
rune.type = "text";
|
|
1715
|
+
rune.text = text;
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
if (tag == "#") {
|
|
1719
|
+
if (nextNum < 0) return;
|
|
1720
|
+
rune.type = "text";
|
|
1721
|
+
if (mathSubstarct)
|
|
1722
|
+
rune.text = `${prevPrefix}${applyMath(prevNum + 1)}`;
|
|
1723
|
+
else
|
|
1724
|
+
rune.text = `${nextPrefix}${applyMath(nextNum)}`;
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
if (!v || v instanceof Array) {
|
|
1728
|
+
const f = (n, prefix2) => {
|
|
1729
|
+
rune.type = "text";
|
|
1730
|
+
rune.text = n < 0 ? prefix2 : `${prefix2}${applyMath(n)}`;
|
|
1731
|
+
};
|
|
1732
|
+
if (v) v.push({ f, i: lastRefI++ });
|
|
1733
|
+
else named[tag] = [{ f, i: lastRefI++ }];
|
|
1734
|
+
} else {
|
|
1735
|
+
rune.type = "text";
|
|
1736
|
+
rune.text = `${v.prefix}${applyMath(v.n)}`;
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
function crystallizeSources() {
|
|
1741
|
+
const sourcesList = doc.nodes.find((node) => node.type == "list" && node.alternativeStyle);
|
|
1742
|
+
if (!sourcesList || sourcesList.type != "list") return 0;
|
|
1743
|
+
const items = sourcesList.items.filter((v) => v.type == "listItem").map((v) => v.text).map((item, i) => {
|
|
1744
|
+
const refI = item[0]?.text ? 0 : 1;
|
|
1745
|
+
const ref = item[refI];
|
|
1746
|
+
if (ref?.type != "ref") {
|
|
1747
|
+
return { text: item, ref: "", i: 99999 + i };
|
|
1748
|
+
}
|
|
1749
|
+
const refs = named[ref.text];
|
|
1750
|
+
if (refs && !(refs instanceof Array)) {
|
|
1751
|
+
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`);
|
|
1752
|
+
return { text: item.slice(refI + 1), ref: "", i: 99999 + i };
|
|
1753
|
+
}
|
|
1754
|
+
const firstOccurrenceI = Math.min(...refs.map((v) => v.i));
|
|
1755
|
+
return { text: item.slice(refI + 1), ref: ref.text, i: firstOccurrenceI };
|
|
1756
|
+
});
|
|
1757
|
+
items.sort((a, b) => a.i - b.i);
|
|
1758
|
+
const hasIdAny = items.some((it) => !!it.ref);
|
|
1759
|
+
sourcesList.items = items.map((item, i) => {
|
|
1760
|
+
const key = hasIdAny ? item.ref : i + 1;
|
|
1761
|
+
const refs = named[key];
|
|
1762
|
+
named[key] = { prefix: "", n: i + 1 };
|
|
1763
|
+
if (refs instanceof Array) {
|
|
1764
|
+
if (hasIdAny && refs.length <= 1)
|
|
1765
|
+
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}`);
|
|
1766
|
+
refs.forEach((r) => hasIdAny ? r.f(i + 1, "") : r.f(-1, `[${i + 1}]`));
|
|
1767
|
+
}
|
|
1768
|
+
return { type: "listItem", text: item.text };
|
|
1769
|
+
});
|
|
1770
|
+
return sourcesList.items.length;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
function addLazyNumbering(doc) {
|
|
1774
|
+
doc.nodes.forEach((node) => {
|
|
1775
|
+
const runes = node.type == "code" || node.type == "table" ? node.title : node.type == "image" ? node.text : null;
|
|
1776
|
+
if (!runes) return;
|
|
1777
|
+
if (runes.find((rune) => rune.type == "ref")) return;
|
|
1778
|
+
runes.splice(0, 0, { text: "#", type: "ref" });
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
// src/main.ts
|
|
1783
|
+
import PizZip from "pizzip";
|
|
1784
|
+
var DISABLE_MACRO = false;
|
|
1785
|
+
async function render({
|
|
1786
|
+
progress = (increment, message) => {
|
|
1787
|
+
},
|
|
1788
|
+
assets,
|
|
1789
|
+
file,
|
|
1790
|
+
output,
|
|
1791
|
+
renderPDF = false,
|
|
1792
|
+
removeIntermediateDocx = false,
|
|
1793
|
+
disableMacros = false,
|
|
1794
|
+
logwarn = console.warn,
|
|
1795
|
+
logPS = (msg) => console.log(`PS: ${msg}`),
|
|
1796
|
+
logPSError = (msg) => console.error(`PS: ${msg}`)
|
|
1797
|
+
}) {
|
|
1798
|
+
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...");
|
|
1799
|
+
const fin = path3.resolve(file);
|
|
1800
|
+
const doc = await parseMD(fin, logwarn);
|
|
1801
|
+
progress(10, "\u041A\u043E\u043B\u0434\u0443\u0435\u043C \u043D\u0430\u0434 \u0441\u0438\u043D\u0442\u0430\u043A\u0441\u0438\u0441\u043E\u043C...");
|
|
1802
|
+
enrichDoc(doc, logwarn);
|
|
1803
|
+
const runicDoc = runifyDoc(doc);
|
|
1804
|
+
alchemist(runicDoc, logwarn);
|
|
1805
|
+
const pout = output && path3.parse(path3.resolve(output));
|
|
1806
|
+
if (pout) await fs4.mkdir(pout.dir, { recursive: true });
|
|
1807
|
+
const p = path3.parse(fin);
|
|
1808
|
+
const fdir = p.dir;
|
|
1809
|
+
const fname = trimEnd(p.name, ".g");
|
|
1810
|
+
const ftmp = path3.join(fdir, fname + ".tmp.docx");
|
|
1811
|
+
const fout = pout ? path3.join(pout.dir, pout.name + ".docx") : path3.join(fdir, fname + ".docx");
|
|
1812
|
+
if (await checkIfFileIsBlocked(ftmp))
|
|
1813
|
+
throw new Error(`\u0417\u0430\u043A\u0440\u043E\u0439\u0442\u0435 docx \u0444\u0430\u0439\u043B. Output file is busy or locked: ${ftmp}`);
|
|
1814
|
+
if (await checkIfFileIsBlocked(fout))
|
|
1815
|
+
throw new Error(`\u0417\u0430\u043A\u0440\u043E\u0439\u0442\u0435 docx \u0444\u0430\u0439\u043B. Output file is busy or locked: ${fout}`);
|
|
1816
|
+
progress(10, phrase_renderDocx());
|
|
1817
|
+
await serializeDocx(runicDoc, ftmp, fdir, assets);
|
|
1818
|
+
let willRunMacros = !disableMacros && (renderPDF || hasReasonForRunningMacros(runicDoc));
|
|
1819
|
+
let limitedRender = false;
|
|
1820
|
+
if (process.platform != "win32" && willRunMacros) {
|
|
1821
|
+
limitedRender = true;
|
|
1822
|
+
willRunMacros = false;
|
|
1823
|
+
}
|
|
1824
|
+
if (willRunMacros) {
|
|
1825
|
+
progress(20, phrase_runMacros());
|
|
1826
|
+
const tmpfolder = path3.join(pout ? pout.dir : fdir, ".md2gost_out");
|
|
1827
|
+
await fs4.rm(tmpfolder, { recursive: true, force: true });
|
|
1828
|
+
try {
|
|
1829
|
+
const ok = await runDocxMacro(progress, logPS, logPSError, assets, fdir, ftmp, fout, renderPDF);
|
|
1830
|
+
if (!ok) {
|
|
1831
|
+
await fs4.rename(ftmp, fout);
|
|
1832
|
+
return { fout, err: "inPS" };
|
|
1833
|
+
}
|
|
1834
|
+
} catch (x) {
|
|
1835
|
+
await fs4.rename(ftmp, fout);
|
|
1836
|
+
return { fout, err: "noPS", errS: x };
|
|
1837
|
+
}
|
|
1838
|
+
await fs4.unlink(ftmp);
|
|
1839
|
+
const errorTxt = path3.join(tmpfolder, "error.txt");
|
|
1840
|
+
if (existsSync2(errorTxt)) {
|
|
1841
|
+
const err = await fs4.readFile(errorTxt);
|
|
1842
|
+
await fs4.rm(tmpfolder, { recursive: true, force: true });
|
|
1843
|
+
return { fout, err: "vba", errS: `${err}` };
|
|
1844
|
+
}
|
|
1845
|
+
await updateMetadata(fout, doc);
|
|
1846
|
+
if (renderPDF) {
|
|
1847
|
+
progress(10, phrase_combine());
|
|
1848
|
+
if (!existsSync2(tmpfolder))
|
|
1849
|
+
return { fout, err: "pdf" };
|
|
1850
|
+
try {
|
|
1851
|
+
const files = (await fs4.readdir(tmpfolder)).sort().map((f) => path3.join(tmpfolder, f));
|
|
1852
|
+
const pdf = pout ? path3.join(pout.dir, pout.base) : path3.join(fdir, fname + ".pdf");
|
|
1853
|
+
await mergePDFs(files, pdf, doc);
|
|
1854
|
+
await fs4.rm(tmpfolder, { recursive: true, force: true });
|
|
1855
|
+
if (removeIntermediateDocx && existsSync2(fout)) await fs4.unlink(fout);
|
|
1856
|
+
return { fout: pdf };
|
|
1857
|
+
} catch (err) {
|
|
1858
|
+
return { fout, err: "pdf", errS: err };
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
} else {
|
|
1862
|
+
await fs4.rename(ftmp, fout);
|
|
1863
|
+
}
|
|
1864
|
+
return { fout, err: limitedRender ? "noWin" : void 0 };
|
|
1865
|
+
}
|
|
1866
|
+
function hasReasonForRunningMacros(doc) {
|
|
1867
|
+
if (DISABLE_MACRO) return false;
|
|
1868
|
+
return doc.nodes.some((n) => n.type == "code" || n.type == "table");
|
|
1869
|
+
}
|
|
1870
|
+
function runDocxMacro(progress, log, logError, assets, cwd, fin, fout, renderPDF) {
|
|
1871
|
+
const script = path3.join(assets, "run.ps1");
|
|
1872
|
+
const templateMacro = path3.join(assets, "template.dotm");
|
|
1873
|
+
return new Promise((res, rej) => {
|
|
1874
|
+
const child = spawn("powershell", [
|
|
1875
|
+
"-NoProfile",
|
|
1876
|
+
"-ExecutionPolicy",
|
|
1877
|
+
"Bypass",
|
|
1878
|
+
"-File",
|
|
1879
|
+
script,
|
|
1880
|
+
"-InputDoc",
|
|
1881
|
+
fin,
|
|
1882
|
+
"-OutputDoc",
|
|
1883
|
+
fout,
|
|
1884
|
+
"-MacroTemplate",
|
|
1885
|
+
templateMacro,
|
|
1886
|
+
...renderPDF ? ["-RenderPDF"] : []
|
|
1887
|
+
], { cwd });
|
|
1888
|
+
child.stdout.on("data", (data) => {
|
|
1889
|
+
data = `${data}`;
|
|
1890
|
+
const m = /\[\*(\d)\]/.exec(data);
|
|
1891
|
+
if (m) progress(12, {
|
|
1892
|
+
// "1": "Fixing breaks (listings)",
|
|
1893
|
+
"1": render_fixingBreaks(),
|
|
1894
|
+
// "2": "Fixing breaks (tables)",
|
|
1895
|
+
"2": render_fixingBreaks(),
|
|
1896
|
+
// "3": "Rendering to PDF",
|
|
1897
|
+
"3": render_renderPDF()
|
|
1898
|
+
}[m[1]] || "Make some work");
|
|
1899
|
+
log(data);
|
|
1900
|
+
});
|
|
1901
|
+
let ok = true;
|
|
1902
|
+
child.stderr.on("data", (data) => {
|
|
1903
|
+
ok = false;
|
|
1904
|
+
logError(`${data}`);
|
|
1905
|
+
});
|
|
1906
|
+
child.on("close", () => res(ok));
|
|
1907
|
+
child.on("error", rej);
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
async function mergePDFs(files, fout, doc) {
|
|
1911
|
+
const mergedPdf = await PDFDocument.create();
|
|
1912
|
+
if (doc?.title) mergedPdf.setTitle(doc.title, { showInWindowTitleBar: true });
|
|
1913
|
+
if (doc?.author) mergedPdf.setAuthor(doc.author);
|
|
1914
|
+
if (doc?.ctime) mergedPdf.setCreationDate(doc.ctime);
|
|
1915
|
+
if (doc?.mtime) mergedPdf.setModificationDate(doc.mtime);
|
|
1916
|
+
for (const file of files) {
|
|
1917
|
+
const bytes = await fs4.readFile(file);
|
|
1918
|
+
const pdf = await PDFDocument.load(bytes);
|
|
1919
|
+
const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
|
|
1920
|
+
pages.forEach((page) => mergedPdf.addPage(page));
|
|
1921
|
+
}
|
|
1922
|
+
const pdfBytes = await mergedPdf.save();
|
|
1923
|
+
await fs4.writeFile(fout, pdfBytes);
|
|
1924
|
+
}
|
|
1925
|
+
async function updateMetadata(docfile, doc) {
|
|
1926
|
+
const content = await fs4.readFile(docfile);
|
|
1927
|
+
const zip = new PizZip(content);
|
|
1928
|
+
function replaceTag(xml, tag, value) {
|
|
1929
|
+
const regex = new RegExp(`(<${tag}\\b[^>]*>)(.*?)(</${tag}>)`, "gs");
|
|
1930
|
+
return xml.replace(
|
|
1931
|
+
regex,
|
|
1932
|
+
(m, otag, old, ctag) => `${otag}${escapeXml(value)}${ctag}`
|
|
1933
|
+
);
|
|
1934
|
+
}
|
|
1935
|
+
let coreXml = zip.file("docProps/core.xml").asText();
|
|
1936
|
+
coreXml = replaceTag(coreXml, "dc:title", doc.title || "Document");
|
|
1937
|
+
if (doc.ctime) coreXml = replaceTag(coreXml, "dcterms:created", doc.ctime.toISOString());
|
|
1938
|
+
if (doc.mtime) coreXml = replaceTag(coreXml, "dcterms:modified", doc.mtime.toISOString());
|
|
1939
|
+
coreXml = replaceTag(coreXml, "dc:creator", doc.author || "Student");
|
|
1940
|
+
coreXml = replaceTag(coreXml, "cp:lastModifiedBy", doc.author || "Student");
|
|
1941
|
+
zip.file("docProps/core.xml", coreXml);
|
|
1942
|
+
let appXml = zip.file("docProps/app.xml").asText();
|
|
1943
|
+
appXml = replaceTag(appXml, "TotalTime", `${doc.etime || randomInt(30, 120)}`);
|
|
1944
|
+
zip.file("docProps/app.xml", appXml);
|
|
1945
|
+
const buffer = zip.generate({ type: "nodebuffer" });
|
|
1946
|
+
await fs4.writeFile(docfile, buffer);
|
|
1947
|
+
function escapeXml(unsafe) {
|
|
1948
|
+
return unsafe.replaceAll(/[<>&"']/g, (ch) => {
|
|
1949
|
+
switch (ch) {
|
|
1950
|
+
case "<":
|
|
1951
|
+
return "<";
|
|
1952
|
+
case ">":
|
|
1953
|
+
return ">";
|
|
1954
|
+
case "&":
|
|
1955
|
+
return "&";
|
|
1956
|
+
case '"':
|
|
1957
|
+
return """;
|
|
1958
|
+
case "'":
|
|
1959
|
+
return "'";
|
|
1960
|
+
default:
|
|
1961
|
+
return ch;
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
var phrase_renderDocx = () => choice(
|
|
1967
|
+
"\u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u0438\u0437\u0443\u0435\u043C \u0431\u0443\u043C\u0430\u0436\u043D\u044B\u0439 \u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442...",
|
|
1968
|
+
"\u041A\u0443\u0435\u043C DOCX \u0432 \u0442\u0435\u043A\u0441\u0442\u043E\u0432\u043E\u0439 \u043A\u0443\u0437\u043D\u0438\u0446\u0435...",
|
|
1969
|
+
"\u041F\u043B\u0435\u0442\u0435\u043C \u043F\u043E\u043B\u043E\u0442\u043D\u043E \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430...",
|
|
1970
|
+
"\u0422\u0440\u0430\u043D\u0441\u043C\u0443\u0442\u0438\u0440\u0443\u0435\u043C \u043F\u0440\u043E\u0441\u0442\u044B\u0435 \u0441\u0438\u043C\u0432\u043E\u043B\u044B \u0432 \u0432\u0435\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0435 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B...",
|
|
1971
|
+
"\u0412\u044B\u043A\u043E\u0432\u044B\u0432\u0430\u0435\u043C \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0438\u0437 \u0440\u0443\u0434\u044B \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432...",
|
|
1972
|
+
"\u041E\u0442\u043B\u0438\u0432\u0430\u0435\u043C \u0444\u043E\u0440\u043C\u0443 \u0431\u0443\u0434\u0443\u0449\u0438\u0445 \u0441\u0442\u0440\u0430\u043D\u0438\u0446...",
|
|
1973
|
+
"\u0417\u0430\u043A\u043B\u0430\u0434\u044B\u0432\u0430\u0435\u043C \u0444\u0443\u043D\u0434\u0430\u043C\u0435\u043D\u0442 \u0432\u0435\u043B\u0438\u043A\u043E\u0433\u043E \u0442\u0440\u0430\u043A\u0442\u0430\u0442\u0430...",
|
|
1974
|
+
"\u041F\u0435\u0440\u0435\u043F\u043B\u0430\u0432\u043B\u044F\u0435\u043C \u043C\u044B\u0441\u043B\u0438 \u0432 \u0441\u0442\u0440\u043E\u0439\u043D\u044B\u0435 \u0430\u0431\u0437\u0430\u0446\u044B...",
|
|
1975
|
+
"\u0421\u043B\u0430\u0433\u0430\u0435\u043C \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0432 \u0433\u0430\u0440\u043C\u043E\u043D\u0438\u044E \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B..."
|
|
1976
|
+
);
|
|
1977
|
+
var phrase_runMacros = () => choice(
|
|
1978
|
+
"\u041F\u0440\u043E\u0431\u0443\u0436\u0434\u0430\u0435\u043C \u0434\u0440\u0435\u0432\u043D\u0438\u0445 \u0434\u0443\u0445\u043E\u0432 VBA...",
|
|
1979
|
+
"\u041F\u0440\u0438\u0437\u044B\u0432\u0430\u0435\u043C \u0434\u0443\u0445 \u0412\u043E\u0440\u0434\u0430 \u0438\u0437 \u0431\u0435\u0437\u0434\u043D\u044B \u043E\u0444\u0438\u0441\u043D\u043E\u0433\u043E \u043F\u0430\u043A\u0435\u0442\u0430...",
|
|
1980
|
+
"\u0412\u0434\u044B\u0445\u0430\u0435\u043C \u043C\u0430\u043D\u0443 \u0432 \u0441\u043F\u044F\u0449\u0438\u0435 \u0440\u0443\u043D\u044B \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438...",
|
|
1981
|
+
"\u041E\u0442\u043F\u0438\u0440\u0430\u0435\u043C \u0442\u0430\u0439\u043D\u044B\u0435 \u0444\u0443\u043D\u043A\u0446\u0438\u0438 \u043E\u0444\u0438\u0441\u043D\u043E\u0439 \u043C\u0430\u0433\u0438\u0438..."
|
|
1982
|
+
);
|
|
1983
|
+
var render_fixingBreaks = () => choice(
|
|
1984
|
+
'\u041F\u043E\u0434\u043A\u043B\u0435\u0438\u0432\u0430\u0435\u043C "\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 \u043B\u0438\u0441\u0442\u0438\u043D\u0433\u0430" \u043D\u0430 \u0440\u0430\u0437\u043E\u0440\u0432\u0430\u043D\u043D\u044B\u0435 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B...',
|
|
1985
|
+
'\u041F\u043E\u0434\u043A\u043B\u0435\u0438\u0432\u0430\u0435\u043C "\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 \u0442\u0430\u0431\u043B\u0438\u0446\u044B" \u043D\u0430 \u0440\u0430\u0437\u043E\u0440\u0432\u0430\u043D\u043D\u044B\u0435 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B...',
|
|
1986
|
+
"\u0423\u043A\u0440\u0435\u043F\u043B\u044F\u0435\u043C \u0441\u0442\u0435\u043D\u044B \u0441\u0435\u043A\u0446\u0438\u0439 \u0441\u0432\u044F\u0437\u0443\u044E\u0449\u0438\u043C\u0438 \u0441\u043B\u043E\u0432\u0430\u043C\u0438...",
|
|
1987
|
+
"\u041D\u0430\u043D\u043E\u0441\u0438\u043C \u0440\u0443\u043D\u044B \u043F\u0440\u0435\u0435\u043C\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0441\u0442\u0438 \u043D\u0430 \u0440\u0430\u0437\u0440\u044B\u0432\u044B \u0441\u0442\u0440\u0430\u043D\u0438\u0446...",
|
|
1988
|
+
"\u0427\u0438\u043D\u0438\u043C \u0431\u0430\u0433 \u0440\u0430\u0437\u0440\u044B\u0432\u0430 \u043F\u043E\u0432\u0435\u0441\u0442\u0432\u043E\u0432\u0430\u043D\u0438\u044F...",
|
|
1989
|
+
"\u0421\u043E\u0435\u0434\u0438\u043D\u044F\u0435\u043C \u043E\u0441\u043A\u043E\u043B\u043A\u0438 \u043B\u0438\u0441\u0442\u0438\u043D\u0433\u0430 \u0432 \u0435\u0434\u0438\u043D\u043E\u0435 \u043F\u043E\u043B\u043E\u0442\u043D\u043E...",
|
|
1990
|
+
"\u041D\u0430\u043A\u043B\u0430\u0434\u044B\u0432\u0430\u0435\u043C \u0447\u0430\u0440\u044B \u043D\u0435\u043F\u0440\u0435\u0440\u044B\u0432\u043D\u043E\u0441\u0442\u0438 \u043D\u0430 \u0442\u0435\u043A\u0441\u0442..."
|
|
1991
|
+
);
|
|
1992
|
+
var render_renderPDF = () => choice(
|
|
1993
|
+
"\u0421\u043E\u0431\u0438\u0440\u0430\u0435\u043C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u0438 \u043F\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u043C \u0438\u0445 \u0432 PDF-\u0441\u0432\u0438\u0442\u043A\u0438...",
|
|
1994
|
+
"\u041E\u0431\u0440\u0430\u0449\u0430\u0435\u043C \u0436\u0438\u0432\u043E\u0435 \u0441\u043B\u043E\u0432\u043E \u0432 \u043D\u0435\u0437\u044B\u0431\u043B\u0435\u043C\u044B\u0439 \u043A\u0430\u043C\u0435\u043D\u044C PDF...",
|
|
1995
|
+
"\u041D\u0430\u0432\u0435\u043A\u0438 \u0437\u0430\u043F\u0435\u0447\u0430\u0442\u043B\u0435\u0432\u0430\u0435\u043C \u0441\u0432\u0438\u0442\u043A\u0438 \u0432 \u043A\u0440\u0438\u0441\u0442\u0430\u043B\u043B\u0430\u0445 \u043F\u0430\u043C\u044F\u0442\u0438...",
|
|
1996
|
+
"\u0417\u0430\u043F\u0435\u0447\u0430\u0442\u044B\u0432\u0430\u0435\u043C \u0442\u0435\u043A\u0441\u0442 \u0432 \u044F\u043D\u0442\u0430\u0440\u0435 \u0444\u043E\u0440\u043C\u0430\u0442\u0430 PDF..."
|
|
1997
|
+
);
|
|
1998
|
+
var phrase_combine = () => choice(
|
|
1999
|
+
"\u0421\u043A\u043B\u0435\u0438\u0432\u0430\u0435\u043C \u0432\u0441\u0451 \u0441\u0438\u043D\u0435\u0439 \u0438\u0437\u043E\u043B\u0435\u043D\u0442\u043E\u0439...",
|
|
2000
|
+
"\u0421\u043A\u0440\u0435\u043F\u043B\u044F\u0435\u043C \u043F\u0435\u0447\u0430\u0442\u044F\u043C\u0438 \u0441\u0443\u0434\u044C\u0431\u044B \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u044E\u044E \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443...",
|
|
2001
|
+
"\u0421\u043F\u043B\u0430\u0432\u043B\u044F\u0435\u043C \u0444\u0438\u043D\u0430\u043B\u044C\u043D\u044B\u0439 \u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442 \u0438\u0437 \u043C\u043D\u043E\u0436\u0435\u0441\u0442\u0432\u0430 \u043E\u0441\u043A\u043E\u043B\u043A\u043E\u0432..."
|
|
2002
|
+
);
|
|
2003
|
+
|
|
2004
|
+
// src/index.ts
|
|
2005
|
+
import assert from "assert";
|
|
2006
|
+
async function renderMarkdown(config) {
|
|
2007
|
+
assert(config && typeof config == "object", "Config object is required");
|
|
2008
|
+
assert(typeof config.input == "string", "Input file path must be a string.");
|
|
2009
|
+
assert(typeof config.output == "string" || typeof config.output == "undefined", "Output path must be a string or undefined.");
|
|
2010
|
+
assert(config.format == "docx" || config.format == "pdf" || typeof config.format == "undefined", "Format must be either 'docx' or 'pdf', or undefined.");
|
|
2011
|
+
assert(typeof config.keepIntermediateDocx == "boolean" || typeof config.keepIntermediateDocx == "undefined", "keepIntermediateDocx must be a boolean or undefined.");
|
|
2012
|
+
assert(typeof config.disableMacros == "boolean" || typeof config.disableMacros == "undefined", "disableMacros must be a boolean or undefined.");
|
|
2013
|
+
assert(typeof config.progress == "function" || typeof config.progress == "undefined", "Progress callback must be a function or undefined.");
|
|
2014
|
+
{
|
|
2015
|
+
const extraProps = getExtraProperties(
|
|
2016
|
+
config,
|
|
2017
|
+
["input", "output", "format", "keepIntermediateDocx", "disableMacros", "progress"]
|
|
2018
|
+
);
|
|
2019
|
+
assert(extraProps.length == 0, `Found unknown properties in config: ${extraProps.join(", ")}`);
|
|
2020
|
+
}
|
|
2021
|
+
const renderPDF = config.format === "pdf" || !config.format && !!config.output?.endsWith(".pdf");
|
|
2022
|
+
const removeIntermediateDocx = !config.keepIntermediateDocx;
|
|
2023
|
+
const disableMacros = !!config.disableMacros;
|
|
2024
|
+
const warnings = [];
|
|
2025
|
+
const logPS = [];
|
|
2026
|
+
let totalPercent = 0;
|
|
2027
|
+
const handleProgress = config.progress ? (increment, message) => {
|
|
2028
|
+
totalPercent = Math.min(totalPercent + increment, 99);
|
|
2029
|
+
config.progress(totalPercent, message);
|
|
2030
|
+
} : void 0;
|
|
2031
|
+
try {
|
|
2032
|
+
let throwMDE2 = function(code, msg) {
|
|
2033
|
+
throw new MDRenderError(code, msg, fout, warnings, logPS, errS ? { cause: errS } : void 0);
|
|
2034
|
+
};
|
|
2035
|
+
var throwMDE = throwMDE2;
|
|
2036
|
+
const { fout, err, errS } = await render({
|
|
2037
|
+
progress: handleProgress,
|
|
2038
|
+
assets: getAssetsDir(),
|
|
2039
|
+
file: config.input,
|
|
2040
|
+
output: config.output,
|
|
2041
|
+
renderPDF,
|
|
2042
|
+
removeIntermediateDocx,
|
|
2043
|
+
disableMacros,
|
|
2044
|
+
logwarn: (msg) => warnings.push(msg),
|
|
2045
|
+
logPS: (msg) => logPS.push(msg),
|
|
2046
|
+
logPSError: (msg) => logPS.push(msg)
|
|
2047
|
+
});
|
|
2048
|
+
config.progress?.(100, "Done!");
|
|
2049
|
+
;
|
|
2050
|
+
if (err == "noPS") throwMDE2("noPS", `Failed to initialize PowerShell. Ensure PowerShell is installed and explicitly added to your system's PATH environment variable.`);
|
|
2051
|
+
if (err == "inPS") throwMDE2("inPS", `An unexpected error occurred executing commands inside PowerShell. Verify that Microsoft Word is properly installed and licensed.`);
|
|
2052
|
+
if (err == "vba") throwMDE2("vba", `Microsoft Word VBA Macro execution failed: ${errS}`);
|
|
2053
|
+
if (err == "pdf") throwMDE2("pdf", `Failed to export document to PDF.`);
|
|
2054
|
+
if (err == "noWin") throwMDE2("noWin", `Platform restriction: Extended formatting macros and PDF rendering are only supported natively on Windows systems.`);
|
|
2055
|
+
let intermediateDocxPath;
|
|
2056
|
+
if (renderPDF) {
|
|
2057
|
+
const pin = path4.parse(path4.resolve(config.input));
|
|
2058
|
+
const fname = pin.name.endsWith(".g") ? pin.name.slice(0, -2) : pin.name;
|
|
2059
|
+
const pout = config.output ? path4.parse(path4.resolve(config.output)) : void 0;
|
|
2060
|
+
intermediateDocxPath = pout ? path4.join(pout.dir, pout.name + ".docx") : path4.join(pin.dir, fname + ".docx");
|
|
2061
|
+
}
|
|
2062
|
+
return {
|
|
2063
|
+
filePath: fout,
|
|
2064
|
+
...intermediateDocxPath && { intermediateDocxPath },
|
|
2065
|
+
warnings
|
|
2066
|
+
};
|
|
2067
|
+
} catch (x) {
|
|
2068
|
+
if (x instanceof MDRenderError) throw x;
|
|
2069
|
+
const message = x instanceof Error ? x.message : String(x);
|
|
2070
|
+
throw new MDRenderError("unknown", `An unhandled internal error occurred during rendering: ${message}`, null, warnings, logPS, { cause: x });
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
var MDRenderError = class _MDRenderError extends Error {
|
|
2074
|
+
/**
|
|
2075
|
+
* File path of the partially rendered `.docx` file, if available when the error occurred.
|
|
2076
|
+
*/
|
|
2077
|
+
renderedFile;
|
|
2078
|
+
/**
|
|
2079
|
+
* Code indicating the underlying cause of the failure:
|
|
2080
|
+
* - `unknown`: An unhandled or unexpected internal runtime error.
|
|
2081
|
+
* - `noPS`: PowerShell could not be started or found in the system PATH.
|
|
2082
|
+
* - `inPS`: A failure occurred inside PowerShell; MS Word may be missing or corrupt.
|
|
2083
|
+
* - `vba`: An error occurred during the execution of internal Word VBA macros.
|
|
2084
|
+
* - `pdf`: A failure occurred specifically during the PDF export phase.
|
|
2085
|
+
* - `noWin`: The operation requires Windows capabilities that are unavailable on the host OS.
|
|
2086
|
+
*/
|
|
2087
|
+
code;
|
|
2088
|
+
/**
|
|
2089
|
+
* Non-fatal warnings captured during the process *before* the fatal error was thrown.
|
|
2090
|
+
*/
|
|
2091
|
+
warnings;
|
|
2092
|
+
/**
|
|
2093
|
+
* Standard output and error streams aggregated from the underlying PowerShell shell session.
|
|
2094
|
+
*/
|
|
2095
|
+
powershellLog;
|
|
2096
|
+
/**
|
|
2097
|
+
* @param code - Machine-readable error code.
|
|
2098
|
+
* @param message - Human-readable error details.
|
|
2099
|
+
* @param renderedFile - Path to a partial output file, if any.
|
|
2100
|
+
* @param warnings - List of non-fatal warnings gathered prior to the exception.
|
|
2101
|
+
* @param powershellLog - List of shell execution strings collected during execution.
|
|
2102
|
+
* @param options - Standard native error options (e.g., to preserve the error `cause`).
|
|
2103
|
+
*/
|
|
2104
|
+
constructor(code, message, renderedFile, warnings = [], powershellLog, options) {
|
|
2105
|
+
super(message, options);
|
|
2106
|
+
this.name = "MDRenderError";
|
|
2107
|
+
this.code = code;
|
|
2108
|
+
this.renderedFile = renderedFile;
|
|
2109
|
+
this.warnings = warnings;
|
|
2110
|
+
this.powershellLog = powershellLog;
|
|
2111
|
+
if (Error.captureStackTrace)
|
|
2112
|
+
Error.captureStackTrace(this, _MDRenderError);
|
|
2113
|
+
}
|
|
2114
|
+
};
|
|
2115
|
+
function getAssetsDir() {
|
|
2116
|
+
if (typeof __dirname !== "undefined")
|
|
2117
|
+
return path4.resolve(__dirname, "../assets");
|
|
2118
|
+
const metaUrl = new Function("return import.meta.url")();
|
|
2119
|
+
return path4.resolve(path4.dirname(fileURLToPath(metaUrl)), "../assets");
|
|
2120
|
+
}
|
|
2121
|
+
function getExtraProperties(configObj, allowedConfigKeys) {
|
|
2122
|
+
return Object.keys(configObj).filter(
|
|
2123
|
+
(key) => !allowedConfigKeys.includes(key)
|
|
2124
|
+
);
|
|
2125
|
+
}
|
|
2126
|
+
export {
|
|
2127
|
+
MDRenderError,
|
|
2128
|
+
renderMarkdown as default
|
|
2129
|
+
};
|
|
2130
|
+
//# sourceMappingURL=index.mjs.map
|