@godcreator/word-report 0.4.0 → 0.5.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.
@@ -0,0 +1,1031 @@
1
+ import { a as walk, r as stableValue, s as ReportContentError, t as assembleDocument } from "./assemble-BEt9_xsB.mjs";
2
+ import remarkFrontmatter from "remark-frontmatter";
3
+ import remarkGfm from "remark-gfm";
4
+ import remarkMath from "remark-math";
5
+ import remarkMdx from "remark-mdx";
6
+ import remarkParse from "remark-parse";
7
+ import { unified } from "unified";
8
+ import { parse } from "yaml";
9
+ //#region src/core/mdx/parse.ts
10
+ const mdxParser = unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkMdx).use(remarkGfm).use(remarkMath);
11
+ /** 源位置写成「行:列」,放在 ReportContentError 的 pointer 里 */
12
+ const positionOf = (node) => node.position ? `${node.position.start.line}:${node.position.start.column}` : "";
13
+ /** 只解析、不执行 MDX:标签没闭合、正文里裸的 < 与 { 这类语法错在这里报出,补上文件与行列再抛 */
14
+ function parseMdx(text, file) {
15
+ try {
16
+ return mdxParser.parse(text);
17
+ } catch (error) {
18
+ const place = error;
19
+ const at = place.line ? `${place.line}:${place.column ?? 1}` : "";
20
+ throw new ReportContentError(`MDX 语法错误:${place.reason ?? place.message ?? String(error)}`, file, at);
21
+ }
22
+ }
23
+ //#endregion
24
+ //#region src/core/mdx/context.ts
25
+ function located(context, node, source) {
26
+ context.locations.set(node, {
27
+ file: context.file,
28
+ pointer: positionOf(source)
29
+ });
30
+ return node;
31
+ }
32
+ function failAt(context, source, message) {
33
+ throw new ReportContentError(message, context.file, positionOf(source));
34
+ }
35
+ /** 只含注释的花括号表达式(MDX 里写注释的唯一办法):解析后的程序体是空的 */
36
+ function isCommentOnlyExpression(node) {
37
+ return (node.type === "mdxFlowExpression" || node.type === "mdxTextExpression") && node.data?.estree?.body?.length === 0;
38
+ }
39
+ //#endregion
40
+ //#region src/core/mdx/ids.ts
41
+ const ID_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
42
+ /** 编译器生成的 id 都以它开头;手写的 id 不许用,两边就不会撞 */
43
+ const GENERATED_ID_PREFIX = "auto-";
44
+ /** 手写的 id:规则照 docreport,另外不许占用生成 id 的前缀 */
45
+ function explicitIdOf(raw, file, at) {
46
+ if (!ID_PATTERN.test(raw)) throw new ReportContentError(`id「${raw}」不合规则:要以字母开头,只用字母、数字、_ 与 -,最长 64 个字符`, file, at);
47
+ if (raw.startsWith("auto-")) throw new ReportContentError(`id「${raw}」以 ${GENERATED_ID_PREFIX} 开头:这个前缀留给编译器生成的 id,手写的 id 换个名字`, file, at);
48
+ return raw;
49
+ }
50
+ function fnv1a36(text) {
51
+ let hash = 2166136261;
52
+ for (let i = 0; i < text.length; i++) {
53
+ hash ^= text.charCodeAt(i);
54
+ hash = Math.imul(hash, 16777619);
55
+ }
56
+ return (hash >>> 0).toString(36);
57
+ }
58
+ function withoutIds(value) {
59
+ if (Array.isArray(value)) return value.map(withoutIds);
60
+ if (value && typeof value === "object") {
61
+ const copy = {};
62
+ for (const [key, child] of Object.entries(value)) if (key !== "id") copy[key] = withoutIds(child);
63
+ return copy;
64
+ }
65
+ return value;
66
+ }
67
+ /**
68
+ * 没写 id 的节点先登记,整份文档转完后一次分配:`auto-<种类>-<指纹>`,指纹取种类加去掉所有 id 的节点内容
69
+ * (节只取标题:节里改了字,标题的锚点与目录链接不跟着变),同一份内容每次编译得到同一个 id;内容相同的按登记先后加 -2、-3
70
+ */
71
+ var GeneratedIds = class {
72
+ pending = [];
73
+ /** signature 不给就用整个节点(去掉 id);登记时节点的子内容可以还没填完,分配时才取指纹 */
74
+ request(node, kind, signature) {
75
+ this.pending.push({
76
+ node,
77
+ kind,
78
+ signature
79
+ });
80
+ return node;
81
+ }
82
+ assignAll() {
83
+ const occurrences = /* @__PURE__ */ new Map();
84
+ for (const { node, kind, signature } of this.pending) {
85
+ const base = `${GENERATED_ID_PREFIX}${kind}-${fnv1a36(`${kind}:${stableValue(withoutIds(signature ?? node))}`)}`;
86
+ const occurrence = (occurrences.get(base) ?? 0) + 1;
87
+ occurrences.set(base, occurrence);
88
+ node.id = occurrence === 1 ? base : `${base}-${occurrence}`;
89
+ }
90
+ }
91
+ };
92
+ //#endregion
93
+ //#region src/core/mdx/jsx.ts
94
+ /** 组件属性只收字符串字面量:写成 {…} 表达式、展开、没列出的属性、缺必填的属性都报错 */
95
+ function attributesOf(context, element, spec) {
96
+ const values = {};
97
+ for (const attribute of element.attributes) {
98
+ if (attribute.type !== "mdxJsxAttribute") failAt(context, element, `<${element.name}> 上不能写 {…} 展开属性`);
99
+ const name = attribute.name;
100
+ if (!Object.hasOwn(spec, name)) {
101
+ const allowed = Object.keys(spec);
102
+ failAt(context, element, `<${element.name}> 没有属性 ${name}${allowed.length ? `;能写的只有 ${allowed.join("、")}` : ";它不收任何属性"}`);
103
+ }
104
+ if (typeof attribute.value !== "string") failAt(context, element, `<${element.name}> 的 ${name} 要写成字符串,如 ${name}="…",不写成 {…} 也不省略值`);
105
+ if (Object.hasOwn(values, name)) failAt(context, element, `<${element.name}> 的 ${name} 写了两次`);
106
+ values[name] = attribute.value;
107
+ }
108
+ for (const [name, need] of Object.entries(spec)) if (need === "required" && !Object.hasOwn(values, name)) failAt(context, element, `<${element.name}> 缺少属性 ${name}`);
109
+ return values;
110
+ }
111
+ function positiveIntegerOf(context, element, name, raw) {
112
+ if (!/^[1-9]\d*$/.test(raw)) failAt(context, element, `<${element.name}> 的 ${name} 要是正整数,这里写的是「${raw}」`);
113
+ return Number(raw);
114
+ }
115
+ const isWhitespaceText$1 = (node) => node.type === "text" && !String(node.value).trim();
116
+ /**
117
+ * 组件的子元素:标签各占一行时是块级元素,写在同一行时被包进一个段落成了行内元素;两种写法都认,
118
+ * 去掉空白与纯注释后只剩元素,别的内容报错
119
+ */
120
+ function elementChildrenOf(context, element) {
121
+ const children = [];
122
+ const collect = (nodes) => {
123
+ for (const node of nodes) {
124
+ if (isWhitespaceText$1(node) || isCommentOnlyExpression(node)) continue;
125
+ if (node.type === "paragraph") collect(node.children);
126
+ else if (node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") children.push(node);
127
+ else failAt(context, node, `<${element.name}> 里只能放组件,这里出现了 ${node.type}`);
128
+ }
129
+ };
130
+ collect(element.children);
131
+ return children;
132
+ }
133
+ const FLOW_TYPES = /* @__PURE__ */ new Set([
134
+ "paragraph",
135
+ "heading",
136
+ "list",
137
+ "code",
138
+ "math",
139
+ "table",
140
+ "blockquote",
141
+ "thematicBreak",
142
+ "html",
143
+ "mdxJsxFlowElement",
144
+ "mdxFlowExpression",
145
+ "footnoteDefinition",
146
+ "definition"
147
+ ]);
148
+ /** 组件里的一行文字(图题、表题、小标题):标签在同一行时直接是行内内容,各占一行时包在一个段落里 */
149
+ function phrasingChildrenOf(context, element) {
150
+ const children = element.children.filter((node) => !isCommentOnlyExpression(node));
151
+ if (children.length === 1 && children[0].type === "paragraph") return children[0].children;
152
+ if (children.every((node) => !FLOW_TYPES.has(node.type))) return children;
153
+ return failAt(context, element, `<${element.name}> 里只能写一段文字`);
154
+ }
155
+ //#endregion
156
+ //#region src/core/mdx/inline.ts
157
+ const MARK_ORDER = [
158
+ "strong",
159
+ "em",
160
+ "sub",
161
+ "sup",
162
+ "code"
163
+ ];
164
+ const MARK_ELEMENTS = {
165
+ strong: "strong",
166
+ em: "em",
167
+ sub: "sub",
168
+ sup: "sup"
169
+ };
170
+ /** 只能单独成段的组件:写进一段文字中间时报错 */
171
+ const BLOCK_COMPONENTS = /* @__PURE__ */ new Set([
172
+ "WordFigure",
173
+ "WordFigures",
174
+ "WordTable",
175
+ "WordAbstract",
176
+ "WordBibliography",
177
+ "WordReference",
178
+ "WordCaption",
179
+ "WordPanel",
180
+ "table",
181
+ "tr",
182
+ "th",
183
+ "td"
184
+ ]);
185
+ /** 一段行内 MDX → docreport 行内节点:相邻、标记相同的文字合并,软换行按两侧字符连上或换成空格 */
186
+ function inlinesOf(context, nodes) {
187
+ const collected = [];
188
+ collectInlines(context, nodes, [], collected);
189
+ return joinedSoftBreaks(mergedAdjacentTexts(collected));
190
+ }
191
+ function textWithMarks(text, marks) {
192
+ const sorted = MARK_ORDER.filter((mark) => marks.includes(mark));
193
+ return sorted.length ? {
194
+ type: "text",
195
+ text,
196
+ marks: sorted
197
+ } : {
198
+ type: "text",
199
+ text
200
+ };
201
+ }
202
+ function idTargetsOf(context, element, raw) {
203
+ const targets = raw.split(/\s+/).filter(Boolean);
204
+ if (!targets.length) failAt(context, element, `<${element.name}> 的 to 不能空`);
205
+ return targets.map((target) => explicitIdOf(target, context.file, positionOf(element)));
206
+ }
207
+ function rejectChildren(context, element) {
208
+ if (element.children.some((child) => !isCommentOnlyExpression(child))) failAt(context, element, `<${element.name}> 不收子内容,写成 <${element.name} … />`);
209
+ }
210
+ function collectInlines(context, nodes, marks, out) {
211
+ for (const node of nodes) switch (node.type) {
212
+ case "text":
213
+ out.push(located(context, textWithMarks(node.value, marks), node));
214
+ break;
215
+ case "emphasis":
216
+ collectInlines(context, node.children, [...marks, "em"], out);
217
+ break;
218
+ case "strong":
219
+ collectInlines(context, node.children, [...marks, "strong"], out);
220
+ break;
221
+ case "inlineCode":
222
+ out.push(located(context, textWithMarks(node.value, [...marks, "code"]), node));
223
+ break;
224
+ case "inlineMath":
225
+ out.push(located(context, context.ids.request({
226
+ type: "math",
227
+ id: "",
228
+ latex: node.value
229
+ }, "m"), node));
230
+ break;
231
+ case "break":
232
+ out.push(located(context, { type: "break" }, node));
233
+ break;
234
+ case "link":
235
+ out.push(located(context, context.ids.request({
236
+ type: "link",
237
+ id: "",
238
+ text: linkTextOf(context, node),
239
+ href: node.url
240
+ }, "link"), node));
241
+ break;
242
+ case "footnoteReference":
243
+ out.push(located(context, context.ids.request({
244
+ type: "note_ref",
245
+ id: "",
246
+ target: explicitIdOf(node.label ?? node.identifier, context.file, positionOf(node))
247
+ }, "fn"), node));
248
+ break;
249
+ case "mdxJsxTextElement":
250
+ collectElement(context, node, marks, out);
251
+ break;
252
+ case "mdxTextExpression":
253
+ if (!isCommentOnlyExpression(node)) failAt(context, node, "正文里不能写 {…} 表达式;只想写花括号就写成 \\{ \\},注释写成 {/* … */}");
254
+ break;
255
+ case "image":
256
+ case "imageReference": failAt(context, node, "Markdown 图片 ![…](…) 不收:图写成 <WordFigure src=\"assets/…\">图题</WordFigure>");
257
+ case "linkReference": failAt(context, node, "不支持引用式链接 [文字][名字]:链接写成 [文字](https://…)");
258
+ case "delete": failAt(context, node, "不支持删除线 ~~…~~");
259
+ case "html": failAt(context, node, "不支持原始 HTML");
260
+ default: failAt(context, node, `这里不能写 ${node.type}`);
261
+ }
262
+ }
263
+ function collectElement(context, element, marks, out) {
264
+ const name = element.name;
265
+ if (name !== null && Object.hasOwn(MARK_ELEMENTS, name)) {
266
+ attributesOf(context, element, {});
267
+ collectInlines(context, element.children, [...marks, MARK_ELEMENTS[name]], out);
268
+ return;
269
+ }
270
+ switch (name) {
271
+ case "WordRef": {
272
+ const attributes = attributesOf(context, element, {
273
+ to: "required",
274
+ show: "optional"
275
+ });
276
+ rejectChildren(context, element);
277
+ if (/\s/.test(attributes["to"].trim())) failAt(context, element, "<WordRef> 的 to 只能写一个 id");
278
+ const [target] = idTargetsOf(context, element, attributes["to"]);
279
+ const show = attributes["show"];
280
+ if (show !== void 0 && show !== "number" && show !== "title") failAt(context, element, `<WordRef> 的 show 只能是 number 或 title(不写就显示「图 3-2」这种全称),这里写的是「${show}」`);
281
+ const display = show ?? "label";
282
+ out.push(located(context, context.ids.request({
283
+ type: "ref",
284
+ id: "",
285
+ target,
286
+ display
287
+ }, "ref"), element));
288
+ return;
289
+ }
290
+ case "WordCite": {
291
+ const attributes = attributesOf(context, element, { to: "required" });
292
+ rejectChildren(context, element);
293
+ out.push(located(context, context.ids.request({
294
+ type: "cite",
295
+ id: "",
296
+ targets: idTargetsOf(context, element, attributes["to"])
297
+ }, "cite"), element));
298
+ return;
299
+ }
300
+ case "WordPlainMath": {
301
+ const attributes = attributesOf(context, element, { tex: "required" });
302
+ const plainText = plainMathRunsOf(context, element);
303
+ out.push(located(context, context.ids.request({
304
+ type: "math",
305
+ id: "",
306
+ latex: attributes["tex"],
307
+ plainText
308
+ }, "m"), element));
309
+ return;
310
+ }
311
+ case "br":
312
+ attributesOf(context, element, {});
313
+ rejectChildren(context, element);
314
+ out.push(located(context, { type: "break" }, element));
315
+ return;
316
+ default:
317
+ if (name !== null && BLOCK_COMPONENTS.has(name)) failAt(context, element, `<${name}> 要单独成段,前后空一行,不写在一段文字中间`);
318
+ failAt(context, element, `认不出组件 <${name ?? ""}>:行内只能用 WordRef、WordCite、WordPlainMath、sub、sup、strong、em、br`);
319
+ }
320
+ }
321
+ const PLAIN_MATH_MARKS = [
322
+ "em",
323
+ "sub",
324
+ "sup"
325
+ ];
326
+ /** <WordPlainMath> 里写给 Word 的文字:只收文字、斜体、下标、上标 */
327
+ function plainMathRunsOf(context, element) {
328
+ const usage = "<WordPlainMath tex=\"…\"> 里写 Word 用的文字,只能用文字、*斜体*、<sub>、<sup>";
329
+ const inlines = inlinesOf(context, element.children);
330
+ if (!inlines.length) failAt(context, element, `${usage},这里是空的`);
331
+ return inlines.map((inline) => {
332
+ if (inline.type !== "text") failAt(context, element, `${usage},这里出现了 ${inline.type}`);
333
+ const marks = inline.marks ?? [];
334
+ const unsupported = marks.find((mark) => !PLAIN_MATH_MARKS.includes(mark));
335
+ if (unsupported !== void 0) failAt(context, element, `${usage},这里出现了 ${unsupported}`);
336
+ if (marks.includes("sub") && marks.includes("sup")) failAt(context, element, "<WordPlainMath> 里同一段文字不能既是下标又是上标");
337
+ return marks.length ? {
338
+ text: inline.text,
339
+ marks
340
+ } : { text: inline.text };
341
+ });
342
+ }
343
+ function linkTextOf(context, link) {
344
+ const inlines = [];
345
+ collectInlines(context, link.children, [], inlines);
346
+ if (inlines.some((inline) => inline.type !== "text" || inline.marks)) failAt(context, link, "链接文字只能是纯文字");
347
+ return inlines.map((inline) => inline.text).join("");
348
+ }
349
+ const sameMarks = (a, b) => (a.marks ?? []).join() === (b.marks ?? []).join();
350
+ function mergedAdjacentTexts(inlines) {
351
+ const merged = [];
352
+ for (const inline of inlines) {
353
+ const previous = merged.at(-1);
354
+ if (inline.type === "text" && previous?.type === "text" && sameMarks(previous, inline)) previous.text += inline.text;
355
+ else merged.push(inline);
356
+ }
357
+ return merged;
358
+ }
359
+ /** 中日韩文字、全角标点与全角符号:软换行两侧都是它们时直接连上,不留空格 */
360
+ const CJK = /[⺀-⿿ -〿぀-ヿ㄀-ㇿ㐀-䶿一-鿿豈-﫿︰-﹏＀-￯]/;
361
+ function joinedSoftBreaks(inlines) {
362
+ const lastCharOf = (inline) => inline?.type === "text" ? inline.text.at(-1) : void 0;
363
+ const firstCharOf = (inline) => inline?.type === "text" ? inline.text[0] : void 0;
364
+ inlines.forEach((inline, index) => {
365
+ if (inline.type !== "text") return;
366
+ inline.text = inline.text.replace(/[ \t]*\r?\n[ \t]*/g, (match, offset, whole) => {
367
+ const before = offset > 0 ? whole[offset - 1] : lastCharOf(inlines[index - 1]);
368
+ const after = offset + match.length < whole.length ? whole[offset + match.length] : firstCharOf(inlines[index + 1]);
369
+ return before !== void 0 && after !== void 0 && CJK.test(before) && CJK.test(after) ? "" : " ";
370
+ });
371
+ });
372
+ return inlines.filter((inline) => inline.type !== "text" || inline.text !== "");
373
+ }
374
+ /** 图题这类行内内容的纯文字:图不写 alt 时拿它当 alt */
375
+ function plainTextOf(inlines) {
376
+ return inlines.map((inline) => {
377
+ switch (inline.type) {
378
+ case "text": return inline.text;
379
+ case "math": return inline.plainText ? inline.plainText.map((run) => run.text).join("") : `$${inline.latex}$`;
380
+ case "link": return inline.text;
381
+ case "break": return " ";
382
+ default: return "";
383
+ }
384
+ }).join("").trim();
385
+ }
386
+ //#endregion
387
+ //#region src/core/mdx/blocks.ts
388
+ const ALLOWED_BLOCKS = {
389
+ top: /* @__PURE__ */ new Set([
390
+ "paragraph",
391
+ "list",
392
+ "figure",
393
+ "table",
394
+ "equation",
395
+ "code",
396
+ "bibliography",
397
+ "abstract"
398
+ ]),
399
+ listItem: /* @__PURE__ */ new Set(["paragraph", "list"]),
400
+ cell: /* @__PURE__ */ new Set([
401
+ "paragraph",
402
+ "list",
403
+ "figure",
404
+ "table",
405
+ "equation",
406
+ "code"
407
+ ]),
408
+ note: /* @__PURE__ */ new Set([
409
+ "paragraph",
410
+ "list",
411
+ "code"
412
+ ]),
413
+ abstract: /* @__PURE__ */ new Set(["paragraph"])
414
+ };
415
+ const BLOCK_NAMES = {
416
+ paragraph: "段落",
417
+ list: "列表",
418
+ figure: "图",
419
+ table: "表",
420
+ equation: "公式",
421
+ code: "代码块",
422
+ bibliography: "<WordBibliography />",
423
+ abstract: "<WordAbstract>"
424
+ };
425
+ const PLACE_NAMES = {
426
+ top: "正文",
427
+ listItem: "列表项",
428
+ cell: "表格单元格",
429
+ note: "脚注",
430
+ abstract: "<WordAbstract>"
431
+ };
432
+ const explicitOrRequested = (context, node, rawId, source, kind) => {
433
+ if (rawId !== void 0) node.id = explicitIdOf(rawId, context.file, positionOf(source));
434
+ else context.ids.request(node, kind);
435
+ return located(context, node, source);
436
+ };
437
+ /** 一个文件最外层的内容:按标题建节(# 是章),标题之间的块放进最近的节 */
438
+ function sectionsOf(context, root, fileNumbering) {
439
+ const top = [];
440
+ const open = [];
441
+ for (const node of root.children) {
442
+ if (node.type === "yaml") continue;
443
+ if (node.type === "heading") {
444
+ while (open.length && open.at(-1).depth >= node.depth) open.pop();
445
+ const parent = open.at(-1);
446
+ const parentDepth = parent?.depth ?? 0;
447
+ if (node.depth !== parentDepth + 1) failAt(context, node, parentDepth === 0 ? `这里是文件里最外层的标题,要写成一级标题 #,写成了 ${node.depth} 级` : `标题跳级:上一级是 ${parentDepth} 级,这里写了 ${node.depth} 级`);
448
+ const section = sectionOf(context, node, !parent ? fileNumbering : parent.section.numbering === "none" ? "none" : "decimal");
449
+ (parent?.section.children ?? top).push(section);
450
+ open.push({
451
+ depth: node.depth,
452
+ section
453
+ });
454
+ continue;
455
+ }
456
+ (open.at(-1)?.section.children ?? top).push(...blocksOf(context, [node], "top"));
457
+ }
458
+ return top;
459
+ }
460
+ function sectionOf(context, heading, numbering) {
461
+ if (heading.children.some((child) => child.type !== "text")) failAt(context, heading, "标题里只能写文字(不能有公式、加粗、链接、组件)");
462
+ const raw = heading.children.map((child) => child.value).join("");
463
+ const anchor = /\s*\[#([^\]]*)\]\s*$/.exec(raw);
464
+ const title = (anchor ? raw.slice(0, anchor.index) : raw).trim();
465
+ if (!title) failAt(context, heading, "标题不能空");
466
+ const section = {
467
+ type: "section",
468
+ id: "",
469
+ title,
470
+ numbering,
471
+ children: []
472
+ };
473
+ if (anchor) {
474
+ section.id = explicitIdOf(anchor[1], context.file, positionOf(heading));
475
+ return located(context, section, heading);
476
+ }
477
+ context.ids.request(section, "sec", title);
478
+ return located(context, section, heading);
479
+ }
480
+ /** 一串块级 MDX → docreport 块;放在不允许的位置的块报错 */
481
+ function blocksOf(context, nodes, place) {
482
+ const blocks = [];
483
+ for (const node of nodes) for (const block of blockOf(context, node, place)) {
484
+ if (!ALLOWED_BLOCKS[place].has(block.type)) failAt(context, node, `${PLACE_NAMES[place]}里不能放${BLOCK_NAMES[block.type] ?? block.type}`);
485
+ blocks.push(block);
486
+ }
487
+ return blocks;
488
+ }
489
+ const isWhitespaceText = (node) => node.type === "text" && !String(node.value).trim();
490
+ function blockOf(context, node, place) {
491
+ switch (node.type) {
492
+ case "paragraph": {
493
+ const meaningful = node.children.filter((child) => !isCommentOnlyExpression(child) && !isWhitespaceText(child));
494
+ if (meaningful.length === 0) return [];
495
+ const only = meaningful.length === 1 ? meaningful[0] : void 0;
496
+ if (only?.type === "mdxJsxTextElement" && BLOCK_COMPONENTS.has(only.name)) return [componentOf(context, only, place)];
497
+ return [paragraphOf(context, node.children, node)];
498
+ }
499
+ case "list": return [listOf(context, node)];
500
+ case "code": return [codeOf(context, node)];
501
+ case "math":
502
+ if (node.meta) failAt(context, node, "$$ 公式不编号、不写 id;要编号或被引用就写成 ```math <id> 代码块");
503
+ return [located(context, context.ids.request({
504
+ type: "equation",
505
+ id: "",
506
+ latex: node.value,
507
+ numbered: false
508
+ }, "eq"), node)];
509
+ case "table": return [tableFromGfm(context, node, false, void 0, void 0)];
510
+ case "mdxJsxFlowElement": return [componentOf(context, node, place)];
511
+ case "mdxFlowExpression":
512
+ if (!isCommentOnlyExpression(node)) failAt(context, node, "正文里不能写 {…} 表达式;注释写成 {/* … */}");
513
+ return [];
514
+ case "footnoteDefinition": {
515
+ const id = explicitIdOf(node.label ?? node.identifier, context.file, positionOf(node));
516
+ context.notes.push(located(context, {
517
+ id,
518
+ blocks: blocksOf(context, node.children, "note")
519
+ }, node));
520
+ return [];
521
+ }
522
+ case "heading": return failAt(context, node, "标题只能写在文件的最外层,不能写进列表、组件或脚注");
523
+ case "mdxjsEsm": return failAt(context, node, "报告里不写 import / export:组件都是内置的");
524
+ case "blockquote": return failAt(context, node, "不支持引用块 >");
525
+ case "thematicBreak": return failAt(context, node, "不支持分隔线");
526
+ case "html": return failAt(context, node, "不支持原始 HTML");
527
+ case "definition": return failAt(context, node, "不支持链接定义 [名字]: 地址");
528
+ case "yaml": return failAt(context, node, "文件头只能写在文件开头");
529
+ default: return failAt(context, node, `这里不能写 ${node.type}`);
530
+ }
531
+ }
532
+ function paragraphOf(context, children, source) {
533
+ return located(context, context.ids.request({
534
+ type: "paragraph",
535
+ id: "",
536
+ content: inlinesOf(context, children)
537
+ }, "p"), source);
538
+ }
539
+ function listOf(context, node) {
540
+ const ordered = Boolean(node.ordered);
541
+ const list = {
542
+ type: "list",
543
+ id: "",
544
+ ordered,
545
+ items: []
546
+ };
547
+ if (ordered && typeof node.start === "number" && node.start !== 1) list.start = node.start;
548
+ for (const item of node.children) {
549
+ if (item.checked !== null && item.checked !== void 0) failAt(context, item, "不支持任务列表 [ ] / [x]");
550
+ list.items.push(located(context, context.ids.request({
551
+ id: "",
552
+ blocks: blocksOf(context, item.children, "listItem")
553
+ }, "li"), item));
554
+ }
555
+ return located(context, context.ids.request(list, "list"), node);
556
+ }
557
+ function codeOf(context, node) {
558
+ if (node.lang === "math") {
559
+ const meta = (node.meta ?? "").trim();
560
+ if (/\s/.test(meta)) failAt(context, node, "```math 后面只能写一个 id");
561
+ return explicitOrRequested(context, {
562
+ type: "equation",
563
+ id: "",
564
+ latex: node.value,
565
+ numbered: true
566
+ }, meta || void 0, node, "eq");
567
+ }
568
+ if (node.meta) failAt(context, node, `代码块的语言名 ${node.lang} 后面不能再写别的`);
569
+ return located(context, context.ids.request({
570
+ type: "code",
571
+ id: "",
572
+ language: node.lang ?? "text",
573
+ text: node.value
574
+ }, "code"), node);
575
+ }
576
+ function componentOf(context, element, place) {
577
+ switch (element.name) {
578
+ case "WordFigure": return figureOf(context, element);
579
+ case "WordFigures": return multiFigureOf(context, element);
580
+ case "WordTable": return numberedTableOf(context, element);
581
+ case "WordAbstract": return abstractOf(context, element, place);
582
+ case "WordBibliography":
583
+ attributesOf(context, element, {});
584
+ if (element.children.some((child) => !isCommentOnlyExpression(child))) failAt(context, element, "<WordBibliography /> 不收子内容");
585
+ return located(context, context.ids.request({
586
+ type: "bibliography",
587
+ id: ""
588
+ }, "bib"), element);
589
+ case "WordReference": return failAt(context, element, "<WordReference> 只能写在报告目录的 references.mdx 里");
590
+ case "WordCaption": return failAt(context, element, "<WordCaption> 只能写在 <WordTable> 或 <WordFigures> 里");
591
+ case "WordPanel": return failAt(context, element, "<WordPanel> 只能写在 <WordFigures> 里");
592
+ case "table":
593
+ case "tr":
594
+ case "th":
595
+ case "td": return failAt(context, element, `<${element.name}> 只能写在 <WordTable> 里:不编号的简单表格直接写 GFM 表格`);
596
+ default: return failAt(context, element, `认不出组件 <${element.name ?? ""}>:块级只能用 Figure、Figures、Table、Abstract、Bibliography`);
597
+ }
598
+ }
599
+ function captionOf(context, element, what) {
600
+ const caption = inlinesOf(context, phrasingChildrenOf(context, element));
601
+ if (!caption.length) failAt(context, element, `${what}不能空`);
602
+ return caption;
603
+ }
604
+ function figureOf(context, element) {
605
+ const attributes = attributesOf(context, element, {
606
+ id: "optional",
607
+ src: "required",
608
+ alt: "optional"
609
+ });
610
+ const caption = captionOf(context, element, "<WordFigure> 的图题");
611
+ return explicitOrRequested(context, {
612
+ type: "figure",
613
+ id: "",
614
+ src: attributes["src"],
615
+ caption,
616
+ alt: attributes["alt"] ?? plainTextOf(caption)
617
+ }, attributes["id"], element, "fig");
618
+ }
619
+ function multiFigureOf(context, element) {
620
+ const attributes = attributesOf(context, element, {
621
+ id: "optional",
622
+ rows: "required",
623
+ columns: "required"
624
+ });
625
+ const children = elementChildrenOf(context, element);
626
+ const captions = children.filter((child) => child.name === "WordCaption");
627
+ if (captions.length !== 1) failAt(context, element, "<WordFigures> 里要有且只有一个 <WordCaption> 写整个图的图题");
628
+ for (const child of children) if (child.name !== "WordCaption" && child.name !== "WordPanel") failAt(context, child, `<WordFigures> 里只能放 <WordCaption> 与 <WordPanel>,不能放 <${child.name}>`);
629
+ attributesOf(context, captions[0], {});
630
+ const caption = captionOf(context, captions[0], "<WordFigures> 的图题");
631
+ const panels = children.filter((child) => child.name === "WordPanel").map((panel) => panelOf(context, panel));
632
+ const figure = {
633
+ type: "figure",
634
+ id: "",
635
+ caption,
636
+ rows: positiveIntegerOf(context, element, "rows", attributes["rows"]),
637
+ columns: positiveIntegerOf(context, element, "columns", attributes["columns"]),
638
+ panels
639
+ };
640
+ return explicitOrRequested(context, figure, attributes["id"], element, "fig");
641
+ }
642
+ function panelOf(context, element) {
643
+ const attributes = attributesOf(context, element, {
644
+ id: "optional",
645
+ row: "required",
646
+ column: "required",
647
+ label: "required",
648
+ src: "required",
649
+ alt: "optional"
650
+ });
651
+ const caption = inlinesOf(context, phrasingChildrenOf(context, element));
652
+ const alt = attributes["alt"] ?? plainTextOf(caption);
653
+ if (!alt) failAt(context, element, "<WordPanel> 没有小标题时要写 alt");
654
+ const panel = {
655
+ id: "",
656
+ row: positiveIntegerOf(context, element, "row", attributes["row"]),
657
+ column: positiveIntegerOf(context, element, "column", attributes["column"]),
658
+ label: attributes["label"],
659
+ src: attributes["src"],
660
+ alt
661
+ };
662
+ if (caption.length) panel.caption = caption;
663
+ return explicitOrRequested(context, panel, attributes["id"], element, "panel");
664
+ }
665
+ function numberedTableOf(context, element) {
666
+ const attributes = attributesOf(context, element, { id: "optional" });
667
+ const parts = element.children.filter((child) => !isCommentOnlyExpression(child) && !isWhitespaceText(child)).flatMap((child) => child.type === "paragraph" ? child.children.filter((inner) => !isWhitespaceText(inner)) : [child]);
668
+ let caption;
669
+ if (parts[0]?.name === "WordCaption") {
670
+ attributesOf(context, parts[0], {});
671
+ caption = captionOf(context, parts.shift(), "<WordCaption> 的表题");
672
+ }
673
+ const body = parts[0];
674
+ if (parts.length !== 1 || !body || !(body.type === "table" || body.name === "table")) failAt(context, element, "<WordTable> 里先写可省的 <WordCaption>,再写一张表(GFM 表格或 <table>),别的不放");
675
+ return body.type === "table" ? tableFromGfm(context, body, true, caption, attributes["id"] ?? null, element) : tableFromJsx(context, body, caption, attributes["id"] ?? null, element);
676
+ }
677
+ function finishedTable(context, table, caption, rawId, source) {
678
+ if (caption) table.caption = caption;
679
+ return explicitOrRequested(context, table, rawId ?? void 0, source, "tbl");
680
+ }
681
+ function cellOf(context, blocks, source, spans = {}) {
682
+ return located(context, context.ids.request({
683
+ id: "",
684
+ ...spans,
685
+ blocks
686
+ }, "cell"), source);
687
+ }
688
+ const columnsOf = (context, count) => Array.from({ length: count }, () => context.ids.request({ id: "" }, "col"));
689
+ function tableFromGfm(context, node, numbered, caption, rawId, source = node) {
690
+ const rows = node.children;
691
+ const width = rows[0].children.length;
692
+ return finishedTable(context, {
693
+ type: "table",
694
+ id: "",
695
+ numbered,
696
+ headerRows: 1,
697
+ columns: columnsOf(context, width),
698
+ rows: rows.map((row) => {
699
+ if (row.children.length !== width) failAt(context, row, `这一行有 ${row.children.length} 格,表头有 ${width} 格:每行格数要和表头一样`);
700
+ return located(context, context.ids.request({
701
+ id: "",
702
+ cells: row.children.map((cell) => cellOf(context, [paragraphOf(context, cell.children, cell)], cell))
703
+ }, "row"), row);
704
+ })
705
+ }, caption, rawId, source);
706
+ }
707
+ function spanOf(context, cell, attributes, name) {
708
+ return attributes[name] === void 0 ? void 0 : positiveIntegerOf(context, cell, name, attributes[name]);
709
+ }
710
+ function jsxCellOf(context, cell) {
711
+ const attributes = attributesOf(context, cell, {
712
+ rowSpan: "optional",
713
+ colSpan: "optional"
714
+ });
715
+ const spans = {};
716
+ const rowSpan = spanOf(context, cell, attributes, "rowSpan"), colSpan = spanOf(context, cell, attributes, "colSpan");
717
+ if (rowSpan !== void 0) spans.rowSpan = rowSpan;
718
+ if (colSpan !== void 0) spans.colSpan = colSpan;
719
+ const children = cell.children.filter((child) => !isCommentOnlyExpression(child) && !isWhitespaceText(child));
720
+ return cellOf(context, children.length === 0 || cell.type === "mdxJsxTextElement" ? [paragraphOf(context, children, cell)] : blocksOf(context, children, "cell"), cell, spans);
721
+ }
722
+ function tableFromJsx(context, element, caption, rawId, source) {
723
+ attributesOf(context, element, {});
724
+ const rowElements = elementChildrenOf(context, element);
725
+ if (!rowElements.length) failAt(context, element, "<table> 里至少要有一行 <tr>");
726
+ let headerRows = 0, headerEnded = false;
727
+ const rows = rowElements.map((row) => {
728
+ if (row.name !== "tr") failAt(context, row, `<table> 里只能放 <tr>,不能放 <${row.name}>`);
729
+ attributesOf(context, row, {});
730
+ const cellElements = elementChildrenOf(context, row);
731
+ if (!cellElements.length) failAt(context, row, "<tr> 里至少要有一格");
732
+ for (const cell of cellElements) if (cell.name !== "th" && cell.name !== "td") failAt(context, cell, `<tr> 里只能放 <th> 与 <td>,不能放 <${cell.name}>`);
733
+ if (cellElements.every((cell) => cell.name === "th") && !headerEnded) headerRows++;
734
+ else {
735
+ headerEnded = true;
736
+ const header = cellElements.find((cell) => cell.name === "th");
737
+ if (header) failAt(context, header, "<th> 只能用在表格开头连续的表头行里");
738
+ }
739
+ return located(context, context.ids.request({
740
+ id: "",
741
+ cells: cellElements.map((cell) => jsxCellOf(context, cell))
742
+ }, "row"), row);
743
+ });
744
+ const width = rows[0].cells.reduce((sum, cell) => sum + (cell.colSpan ?? 1), 0);
745
+ return finishedTable(context, {
746
+ type: "table",
747
+ id: "",
748
+ numbered: true,
749
+ headerRows,
750
+ columns: columnsOf(context, width),
751
+ rows
752
+ }, caption, rawId, source);
753
+ }
754
+ function abstractOf(context, element, place) {
755
+ if (context.role !== "index" || place !== "top") failAt(context, element, "<WordAbstract> 只能写在 index.mdx 的正文最前面");
756
+ const attributes = attributesOf(context, element, {
757
+ keywords: "required",
758
+ title: "optional"
759
+ });
760
+ const keywords = attributes["keywords"].split(";").map((keyword) => keyword.trim());
761
+ if (keywords.some((keyword) => !keyword)) failAt(context, element, "<WordAbstract> 的 keywords 用全角分号「;」隔开,每个关键词都不能空");
762
+ const abstract = {
763
+ type: "abstract",
764
+ id: "",
765
+ paragraphs: blocksOf(context, element.children.filter((child) => !isWhitespaceText(child)), "abstract"),
766
+ keywords
767
+ };
768
+ if (attributes["title"] !== void 0) abstract["title"] = attributes["title"];
769
+ return located(context, context.ids.request(abstract, "abs"), element);
770
+ }
771
+ //#endregion
772
+ //#region src/core/mdx/frontmatter.ts
773
+ function frontmatterOf(root, file) {
774
+ const node = root.children.find((child) => child.type === "yaml");
775
+ if (!node) return void 0;
776
+ const at = positionOf(node);
777
+ let value;
778
+ try {
779
+ value = parse(node.value, { uniqueKeys: true });
780
+ } catch (error) {
781
+ throw new ReportContentError(`文件头不是合法的 YAML:${error.message}`, file, at);
782
+ }
783
+ if (value === null || value === void 0) return {
784
+ fields: {},
785
+ at
786
+ };
787
+ if (typeof value !== "object" || Array.isArray(value)) throw new ReportContentError("文件头要写成「键: 值」的映射", file, at);
788
+ return {
789
+ fields: value,
790
+ at
791
+ };
792
+ }
793
+ function rejectUnknownKeys(fields, allowed, file, at) {
794
+ for (const key of Object.keys(fields)) if (!allowed.includes(key)) throw new ReportContentError(`文件头认不出「${key}」:这里只能写 ${allowed.join("、")}`, file, at);
795
+ }
796
+ function stringField(fields, key, file, at) {
797
+ const value = fields[key];
798
+ if (value === void 0) return void 0;
799
+ if (typeof value !== "string") throw new ReportContentError(`文件头的 ${key} 要是字符串`, file, at);
800
+ return value;
801
+ }
802
+ function stringListField(fields, key, file, at) {
803
+ const value = fields[key];
804
+ if (value === void 0) return void 0;
805
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new ReportContentError(`文件头的 ${key} 要是字符串列表`, file, at);
806
+ return value;
807
+ }
808
+ const INDEX_KEYS = [
809
+ "title",
810
+ "shortTitle",
811
+ "language",
812
+ "authors",
813
+ "date",
814
+ "revision",
815
+ "fields",
816
+ "chapters"
817
+ ];
818
+ /** 入口 index.mdx 的文件头:报告元数据与章节列表 */
819
+ function indexFrontmatterOf(root, file) {
820
+ const frontmatter = frontmatterOf(root, file);
821
+ if (!frontmatter) throw new ReportContentError("index.mdx 要有文件头(--- 包起来的 YAML),至少写 title 与 language", file, "1:1");
822
+ const { fields, at } = frontmatter;
823
+ rejectUnknownKeys(fields, INDEX_KEYS, file, at);
824
+ const title = stringField(fields, "title", file, at);
825
+ const language = stringField(fields, "language", file, at);
826
+ if (title === void 0) throw new ReportContentError("文件头缺少 title", file, at);
827
+ if (language === void 0) throw new ReportContentError("文件头缺少 language(中文报告写 zh-CN)", file, at);
828
+ const metadata = {
829
+ title,
830
+ language
831
+ };
832
+ const shortTitle = stringField(fields, "shortTitle", file, at);
833
+ const authors = stringListField(fields, "authors", file, at);
834
+ const date = stringField(fields, "date", file, at);
835
+ const revision = stringField(fields, "revision", file, at);
836
+ if (shortTitle !== void 0) metadata.shortTitle = shortTitle;
837
+ if (authors !== void 0) metadata.authors = authors;
838
+ if (date !== void 0) metadata.date = date;
839
+ if (revision !== void 0) metadata.revision = revision;
840
+ if (fields["fields"] !== void 0) metadata.fields = metadataFieldsOf(fields["fields"], file, at);
841
+ return {
842
+ metadata,
843
+ chapters: checkedChapterNames(stringListField(fields, "chapters", file, at) ?? [], file, at)
844
+ };
845
+ }
846
+ function metadataFieldsOf(value, file, at) {
847
+ const shapeError = new ReportContentError("文件头的 fields 要是列表,每项写 id、label、value 三个字符串", file, at);
848
+ if (!Array.isArray(value)) throw shapeError;
849
+ return value.map((item) => {
850
+ if (!item || typeof item !== "object" || Array.isArray(item)) throw shapeError;
851
+ const entry = item;
852
+ if (Object.keys(entry).some((key) => ![
853
+ "id",
854
+ "label",
855
+ "value"
856
+ ].includes(key))) throw shapeError;
857
+ const [id, label, fieldValue] = [
858
+ entry["id"],
859
+ entry["label"],
860
+ entry["value"]
861
+ ];
862
+ if (typeof id !== "string" || typeof label !== "string" || typeof fieldValue !== "string") throw shapeError;
863
+ return {
864
+ id,
865
+ label,
866
+ value: fieldValue
867
+ };
868
+ });
869
+ }
870
+ const RESERVED_FILE_NAMES = ["index.mdx", "references.mdx"];
871
+ /** 章文件都放在报告目录根上:只收文件名,不收子目录与上级目录 */
872
+ function checkedChapterNames(chapters, file, at) {
873
+ const seen = /* @__PURE__ */ new Set();
874
+ for (const chapter of chapters) {
875
+ if (!/^[^\\/:*?"<>|\x00-\x1f]+\.mdx$/.test(chapter) || chapter.startsWith(".")) throw new ReportContentError(`chapters 里的「${chapter}」要是报告目录根上的 .mdx 文件名,不带目录`, file, at);
876
+ if (RESERVED_FILE_NAMES.includes(chapter)) throw new ReportContentError(`chapters 里不能写 ${chapter}`, file, at);
877
+ if (seen.has(chapter)) throw new ReportContentError(`chapters 里「${chapter}」写了两次`, file, at);
878
+ seen.add(chapter);
879
+ }
880
+ return chapters;
881
+ }
882
+ /** 章文件头只认 numbering,管这个文件里的一级标题;不写就是 decimal */
883
+ function chapterNumberingOf(root, file) {
884
+ const frontmatter = frontmatterOf(root, file);
885
+ if (!frontmatter) return "decimal";
886
+ const { fields, at } = frontmatter;
887
+ rejectUnknownKeys(fields, ["numbering"], file, at);
888
+ const numbering = stringField(fields, "numbering", file, at) ?? "decimal";
889
+ if (numbering !== "decimal" && numbering !== "appendix" && numbering !== "none") throw new ReportContentError(`numbering 只能是 decimal、appendix 或 none,这里写的是「${numbering}」`, file, at);
890
+ return numbering;
891
+ }
892
+ function rejectFrontmatter(root, file, why) {
893
+ const node = root.children.find((child) => child.type === "yaml");
894
+ if (node) throw new ReportContentError(`${why}不写文件头`, file, positionOf(node));
895
+ }
896
+ //#endregion
897
+ //#region src/core/mdx/compile.ts
898
+ /** 报告目录里固定名字的两个文件 */
899
+ const MDX_ENTRY_NAME = "index.mdx";
900
+ const MDX_REFERENCES_NAME = "references.mdx";
901
+ const directoryOf = (file) => file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
902
+ const inDirectory = (directory, name) => directory ? `${directory}/${name}` : name;
903
+ /** 读入口原文就知道还要读哪些文件:章按 chapters 的顺序,文献列表 references.mdx 有没有由调用方看磁盘 */
904
+ function mdxReportInputsOf(indexText, entry) {
905
+ const { chapters } = indexFrontmatterOf(parseMdx(indexText, entry), entry);
906
+ return {
907
+ chapters: chapters.map((chapter) => inDirectory(directoryOf(entry), chapter)),
908
+ references: inDirectory(directoryOf(entry), MDX_REFERENCES_NAME)
909
+ };
910
+ }
911
+ function referencesOf(context, root) {
912
+ rejectFrontmatter(root, context.file, "references.mdx ");
913
+ const entries = [];
914
+ for (const node of root.children) {
915
+ if (isCommentOnlyExpression(node)) continue;
916
+ const element = node.type === "paragraph" && node.children.length === 1 ? node.children[0] : node;
917
+ if (element.type !== "mdxJsxFlowElement" && element.type !== "mdxJsxTextElement" || element.name !== "WordReference") failAt(context, node, "references.mdx 里只能写 <WordReference id=\"…\" url=\"…\">著录文字</WordReference>,一条一段");
918
+ const attributes = attributesOf(context, element, {
919
+ id: "required",
920
+ url: "optional"
921
+ });
922
+ const text = inlinesOf(context, phrasingChildrenOf(context, element));
923
+ if (text.length !== 1 || text[0].type !== "text" || text[0].marks) failAt(context, element, "<WordReference> 的著录文字只能是一行纯文字");
924
+ const entry = {
925
+ id: explicitIdOf(attributes["id"], context.file, positionOf(element)),
926
+ text: text[0].text
927
+ };
928
+ if (attributes["url"] !== void 0) entry.url = attributes["url"];
929
+ entries.push(located(context, entry, element));
930
+ }
931
+ return entries;
932
+ }
933
+ function locationsByPointer(document, locations) {
934
+ const sources = Object.create(null);
935
+ const byPointer = /* @__PURE__ */ new Map();
936
+ for (const { node, pointer } of walk(document)) {
937
+ const location = locations.get(node);
938
+ if (!location) continue;
939
+ byPointer.set(pointer, location);
940
+ if (typeof node.id === "string") sources[node.id] = location;
941
+ }
942
+ const locate = (pointer) => {
943
+ for (let current = pointer;; current = current.slice(0, current.lastIndexOf("/"))) {
944
+ const location = byPointer.get(current);
945
+ if (location) return location;
946
+ if (!current) return void 0;
947
+ }
948
+ };
949
+ return {
950
+ sources,
951
+ locate
952
+ };
953
+ }
954
+ /** 整份 MDX 报告 → 一份 docreport 文档(入口正文在前,各章按 chapters 的顺序接在后面);只解析、不执行 MDX */
955
+ function compileMdxReport({ reportId, entry, files }) {
956
+ if (!/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(reportId)) throw new ReportContentError(`报告目录名「${reportId}」不能当文档 id:MDX 报告的目录名要以字母开头`, entry);
957
+ const textOf = (file) => {
958
+ if (!Object.hasOwn(files, file)) throw new ReportContentError("找不到这个文件", file);
959
+ return files[file];
960
+ };
961
+ const ids = new GeneratedIds();
962
+ const locations = /* @__PURE__ */ new WeakMap();
963
+ const notes = [];
964
+ const contextFor = (file, role) => ({
965
+ file,
966
+ role,
967
+ ids,
968
+ locations,
969
+ notes
970
+ });
971
+ const indexRoot = parseMdx(textOf(entry), entry);
972
+ const { metadata, chapters } = indexFrontmatterOf(indexRoot, entry);
973
+ const blocks = sectionsOf(contextFor(entry, "index"), indexRoot, "decimal");
974
+ const chapterFiles = chapters.map((chapter) => inDirectory(directoryOf(entry), chapter));
975
+ for (const file of chapterFiles) {
976
+ const root = parseMdx(textOf(file), file);
977
+ blocks.push(...sectionsOf(contextFor(file, "chapter"), root, chapterNumberingOf(root, file)));
978
+ }
979
+ const referencesFile = inDirectory(directoryOf(entry), MDX_REFERENCES_NAME);
980
+ const hasReferences = Object.hasOwn(files, referencesFile);
981
+ const references = hasReferences ? referencesOf(contextFor(referencesFile, "references"), parseMdx(files[referencesFile], referencesFile)) : [];
982
+ ids.assignAll();
983
+ const document = {
984
+ format: "docreport",
985
+ schemaVersion: 1,
986
+ id: reportId,
987
+ metadata,
988
+ blocks
989
+ };
990
+ if (notes.length) document.notes = notes;
991
+ if (references.length) document.references = references;
992
+ return {
993
+ document,
994
+ ...locationsByPointer(document, locations),
995
+ files: [
996
+ entry,
997
+ ...chapterFiles,
998
+ ...hasReferences ? [referencesFile] : []
999
+ ]
1000
+ };
1001
+ }
1002
+ function rawMessageOf(error) {
1003
+ const prefix = `${error.file}${error.pointer ? ":" + error.pointer : ""}${error.file || error.pointer ? ": " : ""}`;
1004
+ return error.message.startsWith(prefix) ? error.message.slice(prefix.length) : error.message;
1005
+ }
1006
+ /** 组装报的错指着编译结果里的 JSON 指针:换成 MDX 文件与行列再抛 */
1007
+ function relocated(error, compiled, entry) {
1008
+ if (!(error instanceof ReportContentError) || error.file !== entry) return error;
1009
+ const location = compiled.locate(error.pointer);
1010
+ return location ? new ReportContentError(rawMessageOf(error), location.file, location.pointer) : error;
1011
+ }
1012
+ /** 编译并组装一份 MDX 报告:结果和 JSON 报告的组装结果同形,sources 指向 MDX 的行列,dependencies 是编进来的 MDX 文件 */
1013
+ function assembleMdxReport(input, options = {}) {
1014
+ const compiled = compileMdxReport(input);
1015
+ let result;
1016
+ try {
1017
+ result = assembleDocument(input.entry, { [input.entry]: compiled.document }, options);
1018
+ } catch (error) {
1019
+ throw relocated(error, compiled, input.entry);
1020
+ }
1021
+ return {
1022
+ ...result,
1023
+ sources: {
1024
+ ...result.sources,
1025
+ ...compiled.sources
1026
+ },
1027
+ dependencies: compiled.files
1028
+ };
1029
+ }
1030
+ //#endregion
1031
+ export { mdxReportInputsOf as a, compileMdxReport as i, MDX_REFERENCES_NAME as n, GENERATED_ID_PREFIX as o, assembleMdxReport as r, MDX_ENTRY_NAME as t };