@cj-tech-master/excelts 9.6.1 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -3
- package/README_zh.md +18 -3
- package/dist/browser/modules/excel/cell.d.ts +4 -0
- package/dist/browser/modules/excel/note.js +5 -1
- package/dist/browser/modules/excel/row.js +35 -2
- package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
- package/dist/browser/modules/excel/stream/workbook-writer.browser.js +22 -2
- package/dist/browser/modules/excel/types.d.ts +81 -0
- package/dist/browser/modules/excel/utils/drawing-utils.d.ts +8 -0
- package/dist/browser/modules/excel/utils/drawing-utils.js +19 -2
- package/dist/browser/modules/excel/workbook.browser.d.ts +16 -0
- package/dist/browser/modules/excel/workbook.browser.js +32 -2
- package/dist/browser/modules/excel/worksheet.d.ts +31 -1
- package/dist/browser/modules/excel/worksheet.js +83 -0
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
- package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
- package/dist/browser/modules/pdf/builder/document-builder.js +22 -49
- package/dist/browser/modules/pdf/builder/pdf-editor.js +1 -1
- package/dist/browser/modules/pdf/core/pdf-stream.d.ts +28 -1
- package/dist/browser/modules/pdf/core/pdf-stream.js +38 -2
- package/dist/browser/modules/pdf/font/font-manager.d.ts +26 -0
- package/dist/browser/modules/pdf/font/font-manager.js +35 -18
- package/dist/browser/modules/pdf/render/page-renderer.d.ts +51 -3
- package/dist/browser/modules/pdf/render/page-renderer.js +111 -18
- package/dist/browser/modules/word/advanced/field-engine.js +45 -20
- package/dist/browser/modules/word/advanced/glossary.d.ts +10 -36
- package/dist/browser/modules/word/advanced/glossary.js +8 -9
- package/dist/browser/modules/word/advanced/math-convert.js +94 -12
- package/dist/browser/modules/word/advanced/ole-objects.d.ts +28 -0
- package/dist/browser/modules/word/advanced/ole-objects.js +122 -19
- package/dist/browser/modules/word/advanced/style-map.js +31 -10
- package/dist/browser/modules/word/builder/run-builders.d.ts +7 -1
- package/dist/browser/modules/word/builder/run-builders.js +7 -1
- package/dist/browser/modules/word/constants.d.ts +4 -0
- package/dist/browser/modules/word/constants.js +5 -1
- package/dist/browser/modules/word/convert/docx-to-semantic.d.ts +2 -1
- package/dist/browser/modules/word/convert/docx-to-semantic.js +135 -1
- package/dist/browser/modules/word/convert/html/html-import.d.ts +32 -1
- package/dist/browser/modules/word/convert/html/html-import.js +167 -14
- package/dist/browser/modules/word/convert/html/html.d.ts +2 -2
- package/dist/browser/modules/word/convert/html/html.js +1 -1
- package/dist/browser/modules/word/convert/markdown/markdown-import.d.ts +48 -18
- package/dist/browser/modules/word/convert/markdown/markdown-import.js +279 -69
- package/dist/browser/modules/word/convert/markdown/markdown.d.ts +1 -1
- package/dist/browser/modules/word/convert/odt/odt.js +407 -56
- package/dist/browser/modules/word/html.d.ts +2 -2
- package/dist/browser/modules/word/html.js +1 -1
- package/dist/browser/modules/word/index.base.d.ts +3 -3
- package/dist/browser/modules/word/index.base.js +1 -1
- package/dist/browser/modules/word/layout/layout-full.js +326 -19
- package/dist/browser/modules/word/layout/render-page.js +35 -8
- package/dist/browser/modules/word/markdown.d.ts +1 -1
- package/dist/browser/modules/word/query/compat.d.ts +10 -2
- package/dist/browser/modules/word/query/compat.js +29 -21
- package/dist/browser/modules/word/reader/docx-reader.js +105 -2
- package/dist/browser/modules/word/reader/math-parser.js +8 -2
- package/dist/browser/modules/word/security/cfb-reader.js +5 -5
- package/dist/browser/modules/word/types.d.ts +96 -1
- package/dist/browser/modules/word/writer/docx-packager.js +108 -2
- package/dist/browser/modules/word/writer/glossary-writer.d.ts +28 -0
- package/dist/browser/modules/word/writer/glossary-writer.js +121 -0
- package/dist/browser/modules/word/writer/header-footer-writer.js +105 -20
- package/dist/browser/modules/word/writer/math-writer.js +7 -2
- package/dist/browser/utils/font-metrics.d.ts +8 -0
- package/dist/browser/utils/font-metrics.js +43 -0
- package/dist/browser/utils/theme-colors.js +4 -1
- package/dist/cjs/modules/excel/note.js +5 -1
- package/dist/cjs/modules/excel/row.js +35 -2
- package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +22 -2
- package/dist/cjs/modules/excel/utils/drawing-utils.js +19 -2
- package/dist/cjs/modules/excel/workbook.browser.js +31 -1
- package/dist/cjs/modules/excel/worksheet.js +83 -0
- package/dist/cjs/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
- package/dist/cjs/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/shape-xform.js +112 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
- package/dist/cjs/modules/pdf/builder/document-builder.js +21 -48
- package/dist/cjs/modules/pdf/builder/pdf-editor.js +1 -1
- package/dist/cjs/modules/pdf/core/pdf-stream.js +38 -2
- package/dist/cjs/modules/pdf/font/font-manager.js +35 -18
- package/dist/cjs/modules/pdf/render/page-renderer.js +112 -18
- package/dist/cjs/modules/word/advanced/field-engine.js +45 -20
- package/dist/cjs/modules/word/advanced/glossary.js +8 -9
- package/dist/cjs/modules/word/advanced/math-convert.js +94 -12
- package/dist/cjs/modules/word/advanced/ole-objects.js +123 -19
- package/dist/cjs/modules/word/advanced/style-map.js +31 -10
- package/dist/cjs/modules/word/builder/run-builders.js +7 -1
- package/dist/cjs/modules/word/constants.js +5 -1
- package/dist/cjs/modules/word/convert/docx-to-semantic.js +135 -1
- package/dist/cjs/modules/word/convert/html/html-import.js +168 -14
- package/dist/cjs/modules/word/convert/html/html.js +2 -1
- package/dist/cjs/modules/word/convert/markdown/markdown-import.js +279 -69
- package/dist/cjs/modules/word/convert/odt/odt.js +407 -56
- package/dist/cjs/modules/word/html.js +2 -1
- package/dist/cjs/modules/word/index.base.js +4 -3
- package/dist/cjs/modules/word/layout/layout-full.js +325 -18
- package/dist/cjs/modules/word/layout/render-page.js +35 -8
- package/dist/cjs/modules/word/query/compat.js +29 -21
- package/dist/cjs/modules/word/reader/docx-reader.js +104 -1
- package/dist/cjs/modules/word/reader/math-parser.js +8 -2
- package/dist/cjs/modules/word/security/cfb-reader.js +5 -5
- package/dist/cjs/modules/word/writer/docx-packager.js +108 -2
- package/dist/cjs/modules/word/writer/glossary-writer.js +124 -0
- package/dist/cjs/modules/word/writer/header-footer-writer.js +105 -20
- package/dist/cjs/modules/word/writer/math-writer.js +7 -2
- package/dist/cjs/utils/font-metrics.js +44 -0
- package/dist/cjs/utils/theme-colors.js +4 -1
- package/dist/esm/modules/excel/note.js +5 -1
- package/dist/esm/modules/excel/row.js +35 -2
- package/dist/esm/modules/excel/stream/workbook-writer.browser.js +22 -2
- package/dist/esm/modules/excel/utils/drawing-utils.js +19 -2
- package/dist/esm/modules/excel/workbook.browser.js +32 -2
- package/dist/esm/modules/excel/worksheet.js +83 -0
- package/dist/esm/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
- package/dist/esm/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
- package/dist/esm/modules/pdf/builder/document-builder.js +22 -49
- package/dist/esm/modules/pdf/builder/pdf-editor.js +1 -1
- package/dist/esm/modules/pdf/core/pdf-stream.js +38 -2
- package/dist/esm/modules/pdf/font/font-manager.js +35 -18
- package/dist/esm/modules/pdf/render/page-renderer.js +111 -18
- package/dist/esm/modules/word/advanced/field-engine.js +45 -20
- package/dist/esm/modules/word/advanced/glossary.js +8 -9
- package/dist/esm/modules/word/advanced/math-convert.js +94 -12
- package/dist/esm/modules/word/advanced/ole-objects.js +122 -19
- package/dist/esm/modules/word/advanced/style-map.js +31 -10
- package/dist/esm/modules/word/builder/run-builders.js +7 -1
- package/dist/esm/modules/word/constants.js +5 -1
- package/dist/esm/modules/word/convert/docx-to-semantic.js +135 -1
- package/dist/esm/modules/word/convert/html/html-import.js +167 -14
- package/dist/esm/modules/word/convert/html/html.js +1 -1
- package/dist/esm/modules/word/convert/markdown/markdown-import.js +279 -69
- package/dist/esm/modules/word/convert/odt/odt.js +407 -56
- package/dist/esm/modules/word/html.js +1 -1
- package/dist/esm/modules/word/index.base.js +1 -1
- package/dist/esm/modules/word/layout/layout-full.js +326 -19
- package/dist/esm/modules/word/layout/render-page.js +35 -8
- package/dist/esm/modules/word/query/compat.js +29 -21
- package/dist/esm/modules/word/reader/docx-reader.js +105 -2
- package/dist/esm/modules/word/reader/math-parser.js +8 -2
- package/dist/esm/modules/word/security/cfb-reader.js +5 -5
- package/dist/esm/modules/word/writer/docx-packager.js +108 -2
- package/dist/esm/modules/word/writer/glossary-writer.js +121 -0
- package/dist/esm/modules/word/writer/header-footer-writer.js +105 -20
- package/dist/esm/modules/word/writer/math-writer.js +7 -2
- package/dist/esm/utils/font-metrics.js +43 -0
- package/dist/esm/utils/theme-colors.js +4 -1
- package/dist/iife/excelts.iife.js +496 -59
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +39 -39
- package/dist/types/modules/excel/cell.d.ts +4 -0
- package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
- package/dist/types/modules/excel/types.d.ts +81 -0
- package/dist/types/modules/excel/utils/drawing-utils.d.ts +8 -0
- package/dist/types/modules/excel/workbook.browser.d.ts +16 -0
- package/dist/types/modules/excel/worksheet.d.ts +31 -1
- package/dist/types/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
- package/dist/types/modules/pdf/core/pdf-stream.d.ts +28 -1
- package/dist/types/modules/pdf/font/font-manager.d.ts +26 -0
- package/dist/types/modules/pdf/render/page-renderer.d.ts +51 -3
- package/dist/types/modules/word/advanced/glossary.d.ts +10 -36
- package/dist/types/modules/word/advanced/ole-objects.d.ts +28 -0
- package/dist/types/modules/word/builder/run-builders.d.ts +7 -1
- package/dist/types/modules/word/constants.d.ts +4 -0
- package/dist/types/modules/word/convert/docx-to-semantic.d.ts +2 -1
- package/dist/types/modules/word/convert/html/html-import.d.ts +32 -1
- package/dist/types/modules/word/convert/html/html.d.ts +2 -2
- package/dist/types/modules/word/convert/markdown/markdown-import.d.ts +48 -18
- package/dist/types/modules/word/convert/markdown/markdown.d.ts +1 -1
- package/dist/types/modules/word/html.d.ts +2 -2
- package/dist/types/modules/word/index.base.d.ts +3 -3
- package/dist/types/modules/word/markdown.d.ts +1 -1
- package/dist/types/modules/word/query/compat.d.ts +10 -2
- package/dist/types/modules/word/types.d.ts +96 -1
- package/dist/types/modules/word/writer/glossary-writer.d.ts +28 -0
- package/dist/types/utils/font-metrics.d.ts +8 -0
- package/package.json +3 -1
|
@@ -21,46 +21,60 @@ import { isRun } from "../../core/text-utils.js";
|
|
|
21
21
|
/**
|
|
22
22
|
* Convert a Markdown string into a complete DocxDocument.
|
|
23
23
|
*
|
|
24
|
+
* Supports the full GFM feature set including inline images (embedded via the
|
|
25
|
+
* `resolveImage` callback) and footnotes (`[^id]` references with `[^id]: …`
|
|
26
|
+
* definitions). Because image resolution and document packaging are inherently
|
|
27
|
+
* asynchronous, this function is async.
|
|
28
|
+
*
|
|
24
29
|
* @param markdown - The GFM Markdown string.
|
|
25
30
|
* @param options - Optional conversion settings.
|
|
26
|
-
* @returns A DocxDocument ready to be packaged.
|
|
31
|
+
* @returns A Promise resolving to a DocxDocument ready to be packaged.
|
|
27
32
|
*/
|
|
28
|
-
export function markdownToDocx(markdown, options) {
|
|
29
|
-
const { body, state } = markdownToDocxBodyInternal(markdown, options);
|
|
33
|
+
export async function markdownToDocx(markdown, options) {
|
|
34
|
+
const { body, state } = await markdownToDocxBodyInternal(markdown, options);
|
|
30
35
|
return {
|
|
31
36
|
body,
|
|
32
37
|
styles: defaultMarkdownStyles(),
|
|
33
38
|
abstractNumberings: state.abstractNumberings,
|
|
34
|
-
numberingInstances: state.numberingInstances
|
|
39
|
+
numberingInstances: state.numberingInstances,
|
|
40
|
+
...(state.footnotes.length > 0 ? { footnotes: state.footnotes } : {}),
|
|
41
|
+
...(state.images.length > 0 ? { images: state.images } : {})
|
|
35
42
|
};
|
|
36
43
|
}
|
|
37
44
|
/**
|
|
38
|
-
* Convert a Markdown string into
|
|
45
|
+
* Convert a Markdown string into DOCX body content plus the supporting
|
|
46
|
+
* document-level definitions it references.
|
|
39
47
|
*
|
|
40
|
-
* **Caveat — body content is not self-contained.**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* - **
|
|
45
|
-
* - **
|
|
48
|
+
* **Caveat — body content is not self-contained.** The returned `body` may
|
|
49
|
+
* reference:
|
|
50
|
+
* - **Numbering** (`abstractNumberings` / `numberingInstances`) — used by
|
|
51
|
+
* bullet / numbered / task lists.
|
|
52
|
+
* - **Footnotes** (`footnotes`) — referenced by footnote-reference runs.
|
|
53
|
+
* - **Images** (`images`) — referenced by inline image runs.
|
|
54
|
+
* - The named `Quote` / `CodeBlock` styles (for block quotes / code blocks).
|
|
46
55
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* splicing, or use the higher-level {@link markdownToDocx} which returns a
|
|
51
|
-
* complete `DocxDocument` with the supporting definitions populated.
|
|
56
|
+
* Splice the relevant arrays into your host document alongside the body, or
|
|
57
|
+
* use the higher-level {@link markdownToDocx} which returns a complete
|
|
58
|
+
* `DocxDocument` with everything populated.
|
|
52
59
|
*
|
|
53
60
|
* @param markdown - The GFM Markdown string.
|
|
54
61
|
* @param options - Optional conversion settings.
|
|
55
|
-
* @returns
|
|
62
|
+
* @returns A Promise resolving to the body and its supporting definitions.
|
|
56
63
|
*/
|
|
57
|
-
export function markdownToDocxBody(markdown, options) {
|
|
58
|
-
|
|
64
|
+
export async function markdownToDocxBody(markdown, options) {
|
|
65
|
+
const { body, state } = await markdownToDocxBodyInternal(markdown, options);
|
|
66
|
+
return {
|
|
67
|
+
body,
|
|
68
|
+
abstractNumberings: state.abstractNumberings,
|
|
69
|
+
numberingInstances: state.numberingInstances,
|
|
70
|
+
footnotes: state.footnotes,
|
|
71
|
+
images: state.images
|
|
72
|
+
};
|
|
59
73
|
}
|
|
60
74
|
/**
|
|
61
75
|
* Internal implementation: converts markdown and returns both body and state.
|
|
62
76
|
*/
|
|
63
|
-
function markdownToDocxBodyInternal(markdown, options) {
|
|
77
|
+
async function markdownToDocxBodyInternal(markdown, options) {
|
|
64
78
|
const state = createState();
|
|
65
79
|
const opts = {
|
|
66
80
|
codeFont: "Courier New",
|
|
@@ -68,8 +82,14 @@ function markdownToDocxBodyInternal(markdown, options) {
|
|
|
68
82
|
...options
|
|
69
83
|
};
|
|
70
84
|
const lines = markdown.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
71
|
-
|
|
72
|
-
|
|
85
|
+
// First pass: extract footnote definitions (`[^id]: …`) so references can
|
|
86
|
+
// resolve regardless of definition order, then parse the remaining blocks.
|
|
87
|
+
const { lines: contentLines, definitions } = extractFootnoteDefinitions(lines);
|
|
88
|
+
state.footnoteDefinitions = definitions;
|
|
89
|
+
const blocks = parseMarkdownBlocks(contentLines, 0, contentLines.length);
|
|
90
|
+
const body = await convertBlocks(blocks, opts, state);
|
|
91
|
+
// Emit footnote definitions that were actually referenced, in reference order.
|
|
92
|
+
await finalizeFootnotes(state, opts);
|
|
73
93
|
return { body, state };
|
|
74
94
|
}
|
|
75
95
|
function createState() {
|
|
@@ -78,10 +98,106 @@ function createState() {
|
|
|
78
98
|
numberingInstances: [],
|
|
79
99
|
nextNumId: 1,
|
|
80
100
|
bulletNumId: undefined,
|
|
81
|
-
orderedNumId: undefined
|
|
101
|
+
orderedNumId: undefined,
|
|
102
|
+
footnotes: [],
|
|
103
|
+
images: [],
|
|
104
|
+
footnoteDefinitions: new Map(),
|
|
105
|
+
footnoteIds: new Map(),
|
|
106
|
+
footnoteOrder: [],
|
|
107
|
+
nextFootnoteId: 1,
|
|
108
|
+
nextImageId: 1,
|
|
109
|
+
nextDrawingId: 1
|
|
82
110
|
};
|
|
83
111
|
}
|
|
84
112
|
// =============================================================================
|
|
113
|
+
// Footnote definition extraction (first pass)
|
|
114
|
+
// =============================================================================
|
|
115
|
+
/**
|
|
116
|
+
* Matches a footnote definition line: `[^label]: content`.
|
|
117
|
+
* Continuation lines (indented under a definition) are folded in.
|
|
118
|
+
*/
|
|
119
|
+
const FOOTNOTE_DEF_RE = /^\[\^([^\]]+)\]:\s?(.*)$/;
|
|
120
|
+
/**
|
|
121
|
+
* Strip footnote definitions (`[^id]: …`) out of the line stream before block
|
|
122
|
+
* parsing, returning the remaining content lines plus a label→text map. A
|
|
123
|
+
* definition may span continuation lines that are indented by at least one
|
|
124
|
+
* space; those are joined with a single space.
|
|
125
|
+
*/
|
|
126
|
+
function extractFootnoteDefinitions(lines) {
|
|
127
|
+
const definitions = new Map();
|
|
128
|
+
const contentLines = [];
|
|
129
|
+
let i = 0;
|
|
130
|
+
while (i < lines.length) {
|
|
131
|
+
// Preserve fenced code blocks verbatim — a `[^id]:`-looking line inside a
|
|
132
|
+
// code fence is code, not a footnote definition.
|
|
133
|
+
const fenceMatch = lines[i].match(/^(`{3,}|~{3,})/);
|
|
134
|
+
if (fenceMatch) {
|
|
135
|
+
const fence = fenceMatch[1];
|
|
136
|
+
const closeRe = new RegExp(`^${fence[0]}{${fence.length},}$`);
|
|
137
|
+
contentLines.push(lines[i]);
|
|
138
|
+
i++;
|
|
139
|
+
while (i < lines.length) {
|
|
140
|
+
contentLines.push(lines[i]);
|
|
141
|
+
const isClose = closeRe.test(lines[i].trim());
|
|
142
|
+
i++;
|
|
143
|
+
if (isClose) {
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const match = lines[i].match(FOOTNOTE_DEF_RE);
|
|
150
|
+
if (match) {
|
|
151
|
+
const label = match[1];
|
|
152
|
+
const parts = [match[2]];
|
|
153
|
+
i++;
|
|
154
|
+
// Fold indented continuation lines into the definition.
|
|
155
|
+
while (i < lines.length && /^\s+\S/.test(lines[i])) {
|
|
156
|
+
parts.push(lines[i].trim());
|
|
157
|
+
i++;
|
|
158
|
+
}
|
|
159
|
+
definitions.set(label, parts.join(" ").trim());
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
contentLines.push(lines[i]);
|
|
163
|
+
i++;
|
|
164
|
+
}
|
|
165
|
+
return { lines: contentLines, definitions };
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* After conversion, emit a `FootnoteDef` for every footnote that was actually
|
|
169
|
+
* referenced, in reference order. References to undefined labels still get a
|
|
170
|
+
* footnote (with empty content) so the reference mark remains valid OOXML.
|
|
171
|
+
*/
|
|
172
|
+
function finalizeFootnotes(state, opts) {
|
|
173
|
+
return (async () => {
|
|
174
|
+
for (const label of state.footnoteOrder) {
|
|
175
|
+
const id = state.footnoteIds.get(label);
|
|
176
|
+
if (id === undefined) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const text = state.footnoteDefinitions.get(label) ?? "";
|
|
180
|
+
const inlines = parseInlines(text);
|
|
181
|
+
const para = await convertParagraph(inlines, opts, state, { style: "FootnoteText" });
|
|
182
|
+
state.footnotes.push({ id, content: [para] });
|
|
183
|
+
}
|
|
184
|
+
})();
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Resolve (or assign) the numeric footnote id for a markdown label, recording
|
|
188
|
+
* reference order on first use.
|
|
189
|
+
*/
|
|
190
|
+
function resolveFootnoteId(label, state) {
|
|
191
|
+
const existing = state.footnoteIds.get(label);
|
|
192
|
+
if (existing !== undefined) {
|
|
193
|
+
return existing;
|
|
194
|
+
}
|
|
195
|
+
const id = state.nextFootnoteId++;
|
|
196
|
+
state.footnoteIds.set(label, id);
|
|
197
|
+
state.footnoteOrder.push(label);
|
|
198
|
+
return id;
|
|
199
|
+
}
|
|
200
|
+
// =============================================================================
|
|
85
201
|
// Block Parser
|
|
86
202
|
// =============================================================================
|
|
87
203
|
function parseMarkdownBlocks(lines, start, end) {
|
|
@@ -432,6 +548,15 @@ function parseInlines(text) {
|
|
|
432
548
|
continue;
|
|
433
549
|
}
|
|
434
550
|
}
|
|
551
|
+
// Footnote reference [^label]
|
|
552
|
+
if (text[i] === "[" && text[i + 1] === "^") {
|
|
553
|
+
const result = parseFootnoteRef(text, i);
|
|
554
|
+
if (result) {
|
|
555
|
+
nodes.push(result.node);
|
|
556
|
+
i = result.end;
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
435
560
|
// Link [text](url "title")
|
|
436
561
|
if (text[i] === "[") {
|
|
437
562
|
const result = parseLink(text, i);
|
|
@@ -537,6 +662,17 @@ function parseInlineCode(text, start) {
|
|
|
537
662
|
}
|
|
538
663
|
return { node: { type: "code", text: code }, end: afterClose };
|
|
539
664
|
}
|
|
665
|
+
function parseFootnoteRef(text, start) {
|
|
666
|
+
// [^label] — label may not contain ']' or whitespace.
|
|
667
|
+
const match = text.slice(start).match(/^\[\^([^\]\s]+)\]/);
|
|
668
|
+
if (!match) {
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
node: { type: "footnoteRef", label: match[1] },
|
|
673
|
+
end: start + match[0].length
|
|
674
|
+
};
|
|
675
|
+
}
|
|
540
676
|
function parseImage(text, start) {
|
|
541
677
|
// 
|
|
542
678
|
const altStart = start + 2; // skip "!["
|
|
@@ -759,20 +895,20 @@ function parseStrikethrough(text, start) {
|
|
|
759
895
|
end: closeIdx + 2
|
|
760
896
|
};
|
|
761
897
|
}
|
|
762
|
-
function convertBlocks(blocks, opts, state) {
|
|
898
|
+
async function convertBlocks(blocks, opts, state) {
|
|
763
899
|
const result = [];
|
|
764
900
|
for (const block of blocks) {
|
|
765
|
-
const converted = convertBlock(block, opts, 0, state);
|
|
901
|
+
const converted = await convertBlock(block, opts, 0, state);
|
|
766
902
|
result.push(...converted);
|
|
767
903
|
}
|
|
768
904
|
return result;
|
|
769
905
|
}
|
|
770
|
-
function convertBlock(block, opts, listLevel, state) {
|
|
906
|
+
async function convertBlock(block, opts, listLevel, state) {
|
|
771
907
|
switch (block.type) {
|
|
772
908
|
case "heading":
|
|
773
|
-
return [convertHeading(block)];
|
|
909
|
+
return [await convertHeading(block, opts, state)];
|
|
774
910
|
case "paragraph":
|
|
775
|
-
return [convertParagraph(block.inlines, opts)];
|
|
911
|
+
return [await convertParagraph(block.inlines, opts, state)];
|
|
776
912
|
case "blockquote":
|
|
777
913
|
return convertBlockquote(block, opts, state);
|
|
778
914
|
case "fencedCode":
|
|
@@ -782,7 +918,7 @@ function convertBlock(block, opts, listLevel, state) {
|
|
|
782
918
|
case "list":
|
|
783
919
|
return convertList(block, opts, listLevel, state);
|
|
784
920
|
case "table":
|
|
785
|
-
return [convertTable(block, opts)];
|
|
921
|
+
return [await convertTable(block, opts, state)];
|
|
786
922
|
case "html":
|
|
787
923
|
// Pass through as plain text paragraph
|
|
788
924
|
return [
|
|
@@ -792,8 +928,8 @@ function convertBlock(block, opts, listLevel, state) {
|
|
|
792
928
|
return [];
|
|
793
929
|
}
|
|
794
930
|
}
|
|
795
|
-
function convertHeading(block) {
|
|
796
|
-
const children = inlinesToRuns(block.inlines,
|
|
931
|
+
async function convertHeading(block, opts, state) {
|
|
932
|
+
const children = await inlinesToRuns(block.inlines, opts, state, true);
|
|
797
933
|
return {
|
|
798
934
|
type: "paragraph",
|
|
799
935
|
properties: {
|
|
@@ -803,19 +939,19 @@ function convertHeading(block) {
|
|
|
803
939
|
children
|
|
804
940
|
};
|
|
805
941
|
}
|
|
806
|
-
function convertParagraph(inlines, opts, props) {
|
|
807
|
-
const children = inlinesToRuns(inlines, opts);
|
|
942
|
+
async function convertParagraph(inlines, opts, state, props) {
|
|
943
|
+
const children = await inlinesToRuns(inlines, opts, state, false);
|
|
808
944
|
return {
|
|
809
945
|
type: "paragraph",
|
|
810
946
|
properties: props,
|
|
811
947
|
children
|
|
812
948
|
};
|
|
813
949
|
}
|
|
814
|
-
function convertBlockquote(block, opts, state) {
|
|
950
|
+
async function convertBlockquote(block, opts, state) {
|
|
815
951
|
// Convert blockquote children with "Quote" style and left indent
|
|
816
952
|
const result = [];
|
|
817
953
|
for (const child of block.children) {
|
|
818
|
-
const converted = convertBlock(child, opts, 0, state);
|
|
954
|
+
const converted = await convertBlock(child, opts, 0, state);
|
|
819
955
|
for (const item of converted) {
|
|
820
956
|
if (item.type === "paragraph") {
|
|
821
957
|
result.push({
|
|
@@ -875,7 +1011,7 @@ function convertThematicBreak() {
|
|
|
875
1011
|
children: []
|
|
876
1012
|
};
|
|
877
1013
|
}
|
|
878
|
-
function convertList(block, opts, parentLevel, state) {
|
|
1014
|
+
async function convertList(block, opts, parentLevel, state) {
|
|
879
1015
|
// Ordered lists with a non-default `start` need their own numbering
|
|
880
1016
|
// instance so the override actually takes effect — sharing one numId
|
|
881
1017
|
// across all lists would force every list to start at the same number.
|
|
@@ -888,7 +1024,7 @@ function convertList(block, opts, parentLevel, state) {
|
|
|
888
1024
|
for (const child of item.children) {
|
|
889
1025
|
if (child.type === "list") {
|
|
890
1026
|
// Nested list — increase level
|
|
891
|
-
const nested = convertList(child, opts, parentLevel + 1, state);
|
|
1027
|
+
const nested = await convertList(child, opts, parentLevel + 1, state);
|
|
892
1028
|
result.push(...nested);
|
|
893
1029
|
}
|
|
894
1030
|
else if (child.type === "paragraph") {
|
|
@@ -897,7 +1033,7 @@ function convertList(block, opts, parentLevel, state) {
|
|
|
897
1033
|
const props = {
|
|
898
1034
|
numbering: { numId, level: parentLevel }
|
|
899
1035
|
};
|
|
900
|
-
const para = convertParagraph(child.inlines, opts, props);
|
|
1036
|
+
const para = await convertParagraph(child.inlines, opts, state, props);
|
|
901
1037
|
// Handle task list checkbox prefix
|
|
902
1038
|
if (item.checked !== undefined) {
|
|
903
1039
|
const checkbox = item.checked ? "☑ " : "☐ ";
|
|
@@ -918,11 +1054,11 @@ function convertList(block, opts, parentLevel, state) {
|
|
|
918
1054
|
const props = {
|
|
919
1055
|
indent: { left: 720 * (parentLevel + 1) }
|
|
920
1056
|
};
|
|
921
|
-
result.push(convertParagraph(child.inlines, opts, props));
|
|
1057
|
+
result.push(await convertParagraph(child.inlines, opts, state, props));
|
|
922
1058
|
}
|
|
923
1059
|
}
|
|
924
1060
|
else {
|
|
925
|
-
const converted = convertBlock(child, opts, parentLevel, state);
|
|
1061
|
+
const converted = await convertBlock(child, opts, parentLevel, state);
|
|
926
1062
|
result.push(...converted);
|
|
927
1063
|
firstBlock = false;
|
|
928
1064
|
}
|
|
@@ -930,10 +1066,12 @@ function convertList(block, opts, parentLevel, state) {
|
|
|
930
1066
|
}
|
|
931
1067
|
return result;
|
|
932
1068
|
}
|
|
933
|
-
function convertTable(block, opts) {
|
|
1069
|
+
async function convertTable(block, opts, state) {
|
|
934
1070
|
const colCount = block.headers.length;
|
|
935
1071
|
// Header row
|
|
936
|
-
const headerCells =
|
|
1072
|
+
const headerCells = [];
|
|
1073
|
+
for (let ci = 0; ci < block.headers.length; ci++) {
|
|
1074
|
+
const cell = block.headers[ci];
|
|
937
1075
|
const cellProps = {
|
|
938
1076
|
shading: { fill: "F0F0F0" },
|
|
939
1077
|
verticalAlign: "center"
|
|
@@ -941,7 +1079,7 @@ function convertTable(block, opts) {
|
|
|
941
1079
|
const paraProps = {
|
|
942
1080
|
alignment: block.alignments[ci]
|
|
943
1081
|
};
|
|
944
|
-
const para = convertParagraph(cell, opts, paraProps);
|
|
1082
|
+
const para = await convertParagraph(cell, opts, state, paraProps);
|
|
945
1083
|
// Bold header text
|
|
946
1084
|
const boldPara = {
|
|
947
1085
|
...para,
|
|
@@ -952,21 +1090,22 @@ function convertTable(block, opts) {
|
|
|
952
1090
|
return child;
|
|
953
1091
|
})
|
|
954
1092
|
};
|
|
955
|
-
|
|
956
|
-
}
|
|
1093
|
+
headerCells.push({ properties: cellProps, content: [boldPara] });
|
|
1094
|
+
}
|
|
957
1095
|
// Data rows
|
|
958
|
-
const dataRows =
|
|
1096
|
+
const dataRows = [];
|
|
1097
|
+
for (const rowCells of block.rows) {
|
|
959
1098
|
const cells = [];
|
|
960
1099
|
for (let ci = 0; ci < colCount; ci++) {
|
|
961
1100
|
const cellInlines = ci < rowCells.length ? rowCells[ci] : [];
|
|
962
1101
|
const paraProps = {
|
|
963
1102
|
alignment: block.alignments[ci]
|
|
964
1103
|
};
|
|
965
|
-
const para = convertParagraph(cellInlines, opts, paraProps);
|
|
1104
|
+
const para = await convertParagraph(cellInlines, opts, state, paraProps);
|
|
966
1105
|
cells.push({ content: [para] });
|
|
967
1106
|
}
|
|
968
|
-
|
|
969
|
-
}
|
|
1107
|
+
dataRows.push({ cells });
|
|
1108
|
+
}
|
|
970
1109
|
const allRows = [
|
|
971
1110
|
{ properties: { tableHeader: true }, cells: headerCells },
|
|
972
1111
|
...dataRows
|
|
@@ -991,37 +1130,41 @@ function convertTable(block, opts) {
|
|
|
991
1130
|
// =============================================================================
|
|
992
1131
|
// Inline to Run Conversion
|
|
993
1132
|
// =============================================================================
|
|
994
|
-
function inlinesToRuns(inlines, opts) {
|
|
1133
|
+
async function inlinesToRuns(inlines, opts, state, isHeading) {
|
|
995
1134
|
const result = [];
|
|
996
1135
|
for (const node of inlines) {
|
|
997
|
-
inlineToRuns(node, result, {}, opts);
|
|
1136
|
+
await inlineToRuns(node, result, {}, opts, state, isHeading);
|
|
998
1137
|
}
|
|
999
1138
|
return result;
|
|
1000
1139
|
}
|
|
1001
|
-
function inlineToRuns(node, output, inheritedProps, opts) {
|
|
1140
|
+
async function inlineToRuns(node, output, inheritedProps, opts, state, isHeading) {
|
|
1002
1141
|
switch (node.type) {
|
|
1003
1142
|
case "text": {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1143
|
+
// Headings derive their font/size from the named Heading style; only
|
|
1144
|
+
// body text picks up the document default font/size.
|
|
1145
|
+
const textProps = isHeading
|
|
1146
|
+
? inheritedProps
|
|
1147
|
+
: {
|
|
1148
|
+
...inheritedProps,
|
|
1149
|
+
...(opts.defaultFont && !inheritedProps.font ? { font: opts.defaultFont } : {}),
|
|
1150
|
+
...(opts.defaultFontSize && !inheritedProps.size ? { size: opts.defaultFontSize } : {})
|
|
1151
|
+
};
|
|
1009
1152
|
output.push(makeRun(node.text, textProps));
|
|
1010
1153
|
break;
|
|
1011
1154
|
}
|
|
1012
1155
|
case "bold":
|
|
1013
1156
|
for (const child of node.children) {
|
|
1014
|
-
inlineToRuns(child, output, { ...inheritedProps, bold: true }, opts);
|
|
1157
|
+
await inlineToRuns(child, output, { ...inheritedProps, bold: true }, opts, state, isHeading);
|
|
1015
1158
|
}
|
|
1016
1159
|
break;
|
|
1017
1160
|
case "italic":
|
|
1018
1161
|
for (const child of node.children) {
|
|
1019
|
-
inlineToRuns(child, output, { ...inheritedProps, italic: true }, opts);
|
|
1162
|
+
await inlineToRuns(child, output, { ...inheritedProps, italic: true }, opts, state, isHeading);
|
|
1020
1163
|
}
|
|
1021
1164
|
break;
|
|
1022
1165
|
case "strikethrough":
|
|
1023
1166
|
for (const child of node.children) {
|
|
1024
|
-
inlineToRuns(child, output, { ...inheritedProps, strike: true }, opts);
|
|
1167
|
+
await inlineToRuns(child, output, { ...inheritedProps, strike: true }, opts, state, isHeading);
|
|
1025
1168
|
}
|
|
1026
1169
|
break;
|
|
1027
1170
|
case "code":
|
|
@@ -1036,7 +1179,7 @@ function inlineToRuns(node, output, inheritedProps, opts) {
|
|
|
1036
1179
|
const linkChildren = [];
|
|
1037
1180
|
for (const child of node.children) {
|
|
1038
1181
|
const tempOutput = [];
|
|
1039
|
-
inlineToRuns(child, tempOutput, { ...inheritedProps, color: "0563C1", underline: "single" }, opts);
|
|
1182
|
+
await inlineToRuns(child, tempOutput, { ...inheritedProps, color: "0563C1", underline: "single" }, opts, state, isHeading);
|
|
1040
1183
|
for (const run of tempOutput) {
|
|
1041
1184
|
if ("content" in run) {
|
|
1042
1185
|
linkChildren.push(run);
|
|
@@ -1053,20 +1196,71 @@ function inlineToRuns(node, output, inheritedProps, opts) {
|
|
|
1053
1196
|
output.push(link);
|
|
1054
1197
|
break;
|
|
1055
1198
|
}
|
|
1056
|
-
case "image":
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
output.push(makeRun(`[Image: ${node.alt || node.url}]`, {
|
|
1060
|
-
...inheritedProps,
|
|
1061
|
-
italic: true,
|
|
1062
|
-
color: "666666"
|
|
1063
|
-
}));
|
|
1199
|
+
case "image": {
|
|
1200
|
+
const run = await resolveImageRun(node, inheritedProps, opts, state);
|
|
1201
|
+
output.push(run);
|
|
1064
1202
|
break;
|
|
1203
|
+
}
|
|
1204
|
+
case "footnoteRef": {
|
|
1205
|
+
const id = resolveFootnoteId(node.label, state);
|
|
1206
|
+
output.push({
|
|
1207
|
+
properties: { ...inheritedProps, style: "FootnoteReference" },
|
|
1208
|
+
content: [{ type: "footnoteRef", id }]
|
|
1209
|
+
});
|
|
1210
|
+
break;
|
|
1211
|
+
}
|
|
1065
1212
|
case "lineBreak":
|
|
1066
1213
|
output.push(makeRun("", undefined, [{ type: "break" }]));
|
|
1067
1214
|
break;
|
|
1068
1215
|
}
|
|
1069
1216
|
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Resolve an inline image. If `resolveImage` returns image data, embed it as a
|
|
1219
|
+
* real `InlineImageContent` run and register the media in `state.images`;
|
|
1220
|
+
* otherwise fall back to an italic `[Image: alt]` placeholder so the output
|
|
1221
|
+
* remains valid.
|
|
1222
|
+
*/
|
|
1223
|
+
async function resolveImageRun(node, inheritedProps, opts, state) {
|
|
1224
|
+
const data = opts.resolveImage ? await opts.resolveImage(node.url, node.alt) : undefined;
|
|
1225
|
+
if (!data) {
|
|
1226
|
+
return makeRun(`[Image: ${node.alt || node.url}]`, {
|
|
1227
|
+
...inheritedProps,
|
|
1228
|
+
italic: true,
|
|
1229
|
+
color: "666666"
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
const n = state.nextImageId++;
|
|
1233
|
+
const fileName = `image${n}.${imageExtension(data.mediaType)}`;
|
|
1234
|
+
const rId = `mdimg${n}`;
|
|
1235
|
+
// For SVG, the packager auto-splits the PNG fallback into its own media
|
|
1236
|
+
// part, assigns a second relationship, and back-fills `svgRId` on the inline
|
|
1237
|
+
// image — so we only ever push a single ImageDef and a single rId here.
|
|
1238
|
+
state.images.push({
|
|
1239
|
+
data: data.data,
|
|
1240
|
+
mediaType: data.mediaType,
|
|
1241
|
+
fileName,
|
|
1242
|
+
rId,
|
|
1243
|
+
...(data.fallbackData ? { fallbackData: data.fallbackData } : {})
|
|
1244
|
+
});
|
|
1245
|
+
const DEFAULT_DIM = 914400; // 1 inch fallback when no size given
|
|
1246
|
+
return {
|
|
1247
|
+
content: [
|
|
1248
|
+
{
|
|
1249
|
+
type: "image",
|
|
1250
|
+
rId,
|
|
1251
|
+
width: (data.width ?? DEFAULT_DIM),
|
|
1252
|
+
height: (data.height ?? DEFAULT_DIM),
|
|
1253
|
+
altText: node.alt || undefined,
|
|
1254
|
+
name: node.alt || fileName,
|
|
1255
|
+
drawingId: state.nextDrawingId++
|
|
1256
|
+
}
|
|
1257
|
+
]
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
/** Map an image media type to its word/media/ file extension. */
|
|
1261
|
+
function imageExtension(mediaType) {
|
|
1262
|
+
return mediaType === "jpeg" ? "jpg" : mediaType;
|
|
1263
|
+
}
|
|
1070
1264
|
// =============================================================================
|
|
1071
1265
|
// Numbering Helpers
|
|
1072
1266
|
// =============================================================================
|
|
@@ -1320,6 +1514,22 @@ function defaultMarkdownStyles() {
|
|
|
1320
1514
|
name: "Code Block",
|
|
1321
1515
|
basedOn: "Normal",
|
|
1322
1516
|
runProperties: { font: "Courier New", size: 20 }
|
|
1517
|
+
},
|
|
1518
|
+
{
|
|
1519
|
+
// Character style applied to the in-text footnote reference mark so it
|
|
1520
|
+
// renders as a superscript number, matching Word's built-in style.
|
|
1521
|
+
type: "character",
|
|
1522
|
+
styleId: "FootnoteReference",
|
|
1523
|
+
name: "footnote reference",
|
|
1524
|
+
runProperties: { vertAlign: "superscript" }
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
// Paragraph style for footnote body text (smaller font, like Word).
|
|
1528
|
+
type: "paragraph",
|
|
1529
|
+
styleId: "FootnoteText",
|
|
1530
|
+
name: "footnote text",
|
|
1531
|
+
basedOn: "Normal",
|
|
1532
|
+
runProperties: { size: 20 }
|
|
1323
1533
|
}
|
|
1324
1534
|
];
|
|
1325
1535
|
}
|