@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
|
@@ -25,46 +25,60 @@ const text_utils_1 = require("../../core/text-utils");
|
|
|
25
25
|
/**
|
|
26
26
|
* Convert a Markdown string into a complete DocxDocument.
|
|
27
27
|
*
|
|
28
|
+
* Supports the full GFM feature set including inline images (embedded via the
|
|
29
|
+
* `resolveImage` callback) and footnotes (`[^id]` references with `[^id]: …`
|
|
30
|
+
* definitions). Because image resolution and document packaging are inherently
|
|
31
|
+
* asynchronous, this function is async.
|
|
32
|
+
*
|
|
28
33
|
* @param markdown - The GFM Markdown string.
|
|
29
34
|
* @param options - Optional conversion settings.
|
|
30
|
-
* @returns A DocxDocument ready to be packaged.
|
|
35
|
+
* @returns A Promise resolving to a DocxDocument ready to be packaged.
|
|
31
36
|
*/
|
|
32
|
-
function markdownToDocx(markdown, options) {
|
|
33
|
-
const { body, state } = markdownToDocxBodyInternal(markdown, options);
|
|
37
|
+
async function markdownToDocx(markdown, options) {
|
|
38
|
+
const { body, state } = await markdownToDocxBodyInternal(markdown, options);
|
|
34
39
|
return {
|
|
35
40
|
body,
|
|
36
41
|
styles: defaultMarkdownStyles(),
|
|
37
42
|
abstractNumberings: state.abstractNumberings,
|
|
38
|
-
numberingInstances: state.numberingInstances
|
|
43
|
+
numberingInstances: state.numberingInstances,
|
|
44
|
+
...(state.footnotes.length > 0 ? { footnotes: state.footnotes } : {}),
|
|
45
|
+
...(state.images.length > 0 ? { images: state.images } : {})
|
|
39
46
|
};
|
|
40
47
|
}
|
|
41
48
|
/**
|
|
42
|
-
* Convert a Markdown string into
|
|
49
|
+
* Convert a Markdown string into DOCX body content plus the supporting
|
|
50
|
+
* document-level definitions it references.
|
|
43
51
|
*
|
|
44
|
-
* **Caveat — body content is not self-contained.**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* - **
|
|
49
|
-
* - **
|
|
52
|
+
* **Caveat — body content is not self-contained.** The returned `body` may
|
|
53
|
+
* reference:
|
|
54
|
+
* - **Numbering** (`abstractNumberings` / `numberingInstances`) — used by
|
|
55
|
+
* bullet / numbered / task lists.
|
|
56
|
+
* - **Footnotes** (`footnotes`) — referenced by footnote-reference runs.
|
|
57
|
+
* - **Images** (`images`) — referenced by inline image runs.
|
|
58
|
+
* - The named `Quote` / `CodeBlock` styles (for block quotes / code blocks).
|
|
50
59
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* splicing, or use the higher-level {@link markdownToDocx} which returns a
|
|
55
|
-
* complete `DocxDocument` with the supporting definitions populated.
|
|
60
|
+
* Splice the relevant arrays into your host document alongside the body, or
|
|
61
|
+
* use the higher-level {@link markdownToDocx} which returns a complete
|
|
62
|
+
* `DocxDocument` with everything populated.
|
|
56
63
|
*
|
|
57
64
|
* @param markdown - The GFM Markdown string.
|
|
58
65
|
* @param options - Optional conversion settings.
|
|
59
|
-
* @returns
|
|
66
|
+
* @returns A Promise resolving to the body and its supporting definitions.
|
|
60
67
|
*/
|
|
61
|
-
function markdownToDocxBody(markdown, options) {
|
|
62
|
-
|
|
68
|
+
async function markdownToDocxBody(markdown, options) {
|
|
69
|
+
const { body, state } = await markdownToDocxBodyInternal(markdown, options);
|
|
70
|
+
return {
|
|
71
|
+
body,
|
|
72
|
+
abstractNumberings: state.abstractNumberings,
|
|
73
|
+
numberingInstances: state.numberingInstances,
|
|
74
|
+
footnotes: state.footnotes,
|
|
75
|
+
images: state.images
|
|
76
|
+
};
|
|
63
77
|
}
|
|
64
78
|
/**
|
|
65
79
|
* Internal implementation: converts markdown and returns both body and state.
|
|
66
80
|
*/
|
|
67
|
-
function markdownToDocxBodyInternal(markdown, options) {
|
|
81
|
+
async function markdownToDocxBodyInternal(markdown, options) {
|
|
68
82
|
const state = createState();
|
|
69
83
|
const opts = {
|
|
70
84
|
codeFont: "Courier New",
|
|
@@ -72,8 +86,14 @@ function markdownToDocxBodyInternal(markdown, options) {
|
|
|
72
86
|
...options
|
|
73
87
|
};
|
|
74
88
|
const lines = markdown.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
75
|
-
|
|
76
|
-
|
|
89
|
+
// First pass: extract footnote definitions (`[^id]: …`) so references can
|
|
90
|
+
// resolve regardless of definition order, then parse the remaining blocks.
|
|
91
|
+
const { lines: contentLines, definitions } = extractFootnoteDefinitions(lines);
|
|
92
|
+
state.footnoteDefinitions = definitions;
|
|
93
|
+
const blocks = parseMarkdownBlocks(contentLines, 0, contentLines.length);
|
|
94
|
+
const body = await convertBlocks(blocks, opts, state);
|
|
95
|
+
// Emit footnote definitions that were actually referenced, in reference order.
|
|
96
|
+
await finalizeFootnotes(state, opts);
|
|
77
97
|
return { body, state };
|
|
78
98
|
}
|
|
79
99
|
function createState() {
|
|
@@ -82,10 +102,106 @@ function createState() {
|
|
|
82
102
|
numberingInstances: [],
|
|
83
103
|
nextNumId: 1,
|
|
84
104
|
bulletNumId: undefined,
|
|
85
|
-
orderedNumId: undefined
|
|
105
|
+
orderedNumId: undefined,
|
|
106
|
+
footnotes: [],
|
|
107
|
+
images: [],
|
|
108
|
+
footnoteDefinitions: new Map(),
|
|
109
|
+
footnoteIds: new Map(),
|
|
110
|
+
footnoteOrder: [],
|
|
111
|
+
nextFootnoteId: 1,
|
|
112
|
+
nextImageId: 1,
|
|
113
|
+
nextDrawingId: 1
|
|
86
114
|
};
|
|
87
115
|
}
|
|
88
116
|
// =============================================================================
|
|
117
|
+
// Footnote definition extraction (first pass)
|
|
118
|
+
// =============================================================================
|
|
119
|
+
/**
|
|
120
|
+
* Matches a footnote definition line: `[^label]: content`.
|
|
121
|
+
* Continuation lines (indented under a definition) are folded in.
|
|
122
|
+
*/
|
|
123
|
+
const FOOTNOTE_DEF_RE = /^\[\^([^\]]+)\]:\s?(.*)$/;
|
|
124
|
+
/**
|
|
125
|
+
* Strip footnote definitions (`[^id]: …`) out of the line stream before block
|
|
126
|
+
* parsing, returning the remaining content lines plus a label→text map. A
|
|
127
|
+
* definition may span continuation lines that are indented by at least one
|
|
128
|
+
* space; those are joined with a single space.
|
|
129
|
+
*/
|
|
130
|
+
function extractFootnoteDefinitions(lines) {
|
|
131
|
+
const definitions = new Map();
|
|
132
|
+
const contentLines = [];
|
|
133
|
+
let i = 0;
|
|
134
|
+
while (i < lines.length) {
|
|
135
|
+
// Preserve fenced code blocks verbatim — a `[^id]:`-looking line inside a
|
|
136
|
+
// code fence is code, not a footnote definition.
|
|
137
|
+
const fenceMatch = lines[i].match(/^(`{3,}|~{3,})/);
|
|
138
|
+
if (fenceMatch) {
|
|
139
|
+
const fence = fenceMatch[1];
|
|
140
|
+
const closeRe = new RegExp(`^${fence[0]}{${fence.length},}$`);
|
|
141
|
+
contentLines.push(lines[i]);
|
|
142
|
+
i++;
|
|
143
|
+
while (i < lines.length) {
|
|
144
|
+
contentLines.push(lines[i]);
|
|
145
|
+
const isClose = closeRe.test(lines[i].trim());
|
|
146
|
+
i++;
|
|
147
|
+
if (isClose) {
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const match = lines[i].match(FOOTNOTE_DEF_RE);
|
|
154
|
+
if (match) {
|
|
155
|
+
const label = match[1];
|
|
156
|
+
const parts = [match[2]];
|
|
157
|
+
i++;
|
|
158
|
+
// Fold indented continuation lines into the definition.
|
|
159
|
+
while (i < lines.length && /^\s+\S/.test(lines[i])) {
|
|
160
|
+
parts.push(lines[i].trim());
|
|
161
|
+
i++;
|
|
162
|
+
}
|
|
163
|
+
definitions.set(label, parts.join(" ").trim());
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
contentLines.push(lines[i]);
|
|
167
|
+
i++;
|
|
168
|
+
}
|
|
169
|
+
return { lines: contentLines, definitions };
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* After conversion, emit a `FootnoteDef` for every footnote that was actually
|
|
173
|
+
* referenced, in reference order. References to undefined labels still get a
|
|
174
|
+
* footnote (with empty content) so the reference mark remains valid OOXML.
|
|
175
|
+
*/
|
|
176
|
+
function finalizeFootnotes(state, opts) {
|
|
177
|
+
return (async () => {
|
|
178
|
+
for (const label of state.footnoteOrder) {
|
|
179
|
+
const id = state.footnoteIds.get(label);
|
|
180
|
+
if (id === undefined) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const text = state.footnoteDefinitions.get(label) ?? "";
|
|
184
|
+
const inlines = parseInlines(text);
|
|
185
|
+
const para = await convertParagraph(inlines, opts, state, { style: "FootnoteText" });
|
|
186
|
+
state.footnotes.push({ id, content: [para] });
|
|
187
|
+
}
|
|
188
|
+
})();
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Resolve (or assign) the numeric footnote id for a markdown label, recording
|
|
192
|
+
* reference order on first use.
|
|
193
|
+
*/
|
|
194
|
+
function resolveFootnoteId(label, state) {
|
|
195
|
+
const existing = state.footnoteIds.get(label);
|
|
196
|
+
if (existing !== undefined) {
|
|
197
|
+
return existing;
|
|
198
|
+
}
|
|
199
|
+
const id = state.nextFootnoteId++;
|
|
200
|
+
state.footnoteIds.set(label, id);
|
|
201
|
+
state.footnoteOrder.push(label);
|
|
202
|
+
return id;
|
|
203
|
+
}
|
|
204
|
+
// =============================================================================
|
|
89
205
|
// Block Parser
|
|
90
206
|
// =============================================================================
|
|
91
207
|
function parseMarkdownBlocks(lines, start, end) {
|
|
@@ -436,6 +552,15 @@ function parseInlines(text) {
|
|
|
436
552
|
continue;
|
|
437
553
|
}
|
|
438
554
|
}
|
|
555
|
+
// Footnote reference [^label]
|
|
556
|
+
if (text[i] === "[" && text[i + 1] === "^") {
|
|
557
|
+
const result = parseFootnoteRef(text, i);
|
|
558
|
+
if (result) {
|
|
559
|
+
nodes.push(result.node);
|
|
560
|
+
i = result.end;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
439
564
|
// Link [text](url "title")
|
|
440
565
|
if (text[i] === "[") {
|
|
441
566
|
const result = parseLink(text, i);
|
|
@@ -541,6 +666,17 @@ function parseInlineCode(text, start) {
|
|
|
541
666
|
}
|
|
542
667
|
return { node: { type: "code", text: code }, end: afterClose };
|
|
543
668
|
}
|
|
669
|
+
function parseFootnoteRef(text, start) {
|
|
670
|
+
// [^label] — label may not contain ']' or whitespace.
|
|
671
|
+
const match = text.slice(start).match(/^\[\^([^\]\s]+)\]/);
|
|
672
|
+
if (!match) {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
return {
|
|
676
|
+
node: { type: "footnoteRef", label: match[1] },
|
|
677
|
+
end: start + match[0].length
|
|
678
|
+
};
|
|
679
|
+
}
|
|
544
680
|
function parseImage(text, start) {
|
|
545
681
|
// 
|
|
546
682
|
const altStart = start + 2; // skip "!["
|
|
@@ -763,20 +899,20 @@ function parseStrikethrough(text, start) {
|
|
|
763
899
|
end: closeIdx + 2
|
|
764
900
|
};
|
|
765
901
|
}
|
|
766
|
-
function convertBlocks(blocks, opts, state) {
|
|
902
|
+
async function convertBlocks(blocks, opts, state) {
|
|
767
903
|
const result = [];
|
|
768
904
|
for (const block of blocks) {
|
|
769
|
-
const converted = convertBlock(block, opts, 0, state);
|
|
905
|
+
const converted = await convertBlock(block, opts, 0, state);
|
|
770
906
|
result.push(...converted);
|
|
771
907
|
}
|
|
772
908
|
return result;
|
|
773
909
|
}
|
|
774
|
-
function convertBlock(block, opts, listLevel, state) {
|
|
910
|
+
async function convertBlock(block, opts, listLevel, state) {
|
|
775
911
|
switch (block.type) {
|
|
776
912
|
case "heading":
|
|
777
|
-
return [convertHeading(block)];
|
|
913
|
+
return [await convertHeading(block, opts, state)];
|
|
778
914
|
case "paragraph":
|
|
779
|
-
return [convertParagraph(block.inlines, opts)];
|
|
915
|
+
return [await convertParagraph(block.inlines, opts, state)];
|
|
780
916
|
case "blockquote":
|
|
781
917
|
return convertBlockquote(block, opts, state);
|
|
782
918
|
case "fencedCode":
|
|
@@ -786,7 +922,7 @@ function convertBlock(block, opts, listLevel, state) {
|
|
|
786
922
|
case "list":
|
|
787
923
|
return convertList(block, opts, listLevel, state);
|
|
788
924
|
case "table":
|
|
789
|
-
return [convertTable(block, opts)];
|
|
925
|
+
return [await convertTable(block, opts, state)];
|
|
790
926
|
case "html":
|
|
791
927
|
// Pass through as plain text paragraph
|
|
792
928
|
return [
|
|
@@ -796,8 +932,8 @@ function convertBlock(block, opts, listLevel, state) {
|
|
|
796
932
|
return [];
|
|
797
933
|
}
|
|
798
934
|
}
|
|
799
|
-
function convertHeading(block) {
|
|
800
|
-
const children = inlinesToRuns(block.inlines,
|
|
935
|
+
async function convertHeading(block, opts, state) {
|
|
936
|
+
const children = await inlinesToRuns(block.inlines, opts, state, true);
|
|
801
937
|
return {
|
|
802
938
|
type: "paragraph",
|
|
803
939
|
properties: {
|
|
@@ -807,19 +943,19 @@ function convertHeading(block) {
|
|
|
807
943
|
children
|
|
808
944
|
};
|
|
809
945
|
}
|
|
810
|
-
function convertParagraph(inlines, opts, props) {
|
|
811
|
-
const children = inlinesToRuns(inlines, opts);
|
|
946
|
+
async function convertParagraph(inlines, opts, state, props) {
|
|
947
|
+
const children = await inlinesToRuns(inlines, opts, state, false);
|
|
812
948
|
return {
|
|
813
949
|
type: "paragraph",
|
|
814
950
|
properties: props,
|
|
815
951
|
children
|
|
816
952
|
};
|
|
817
953
|
}
|
|
818
|
-
function convertBlockquote(block, opts, state) {
|
|
954
|
+
async function convertBlockquote(block, opts, state) {
|
|
819
955
|
// Convert blockquote children with "Quote" style and left indent
|
|
820
956
|
const result = [];
|
|
821
957
|
for (const child of block.children) {
|
|
822
|
-
const converted = convertBlock(child, opts, 0, state);
|
|
958
|
+
const converted = await convertBlock(child, opts, 0, state);
|
|
823
959
|
for (const item of converted) {
|
|
824
960
|
if (item.type === "paragraph") {
|
|
825
961
|
result.push({
|
|
@@ -879,7 +1015,7 @@ function convertThematicBreak() {
|
|
|
879
1015
|
children: []
|
|
880
1016
|
};
|
|
881
1017
|
}
|
|
882
|
-
function convertList(block, opts, parentLevel, state) {
|
|
1018
|
+
async function convertList(block, opts, parentLevel, state) {
|
|
883
1019
|
// Ordered lists with a non-default `start` need their own numbering
|
|
884
1020
|
// instance so the override actually takes effect — sharing one numId
|
|
885
1021
|
// across all lists would force every list to start at the same number.
|
|
@@ -892,7 +1028,7 @@ function convertList(block, opts, parentLevel, state) {
|
|
|
892
1028
|
for (const child of item.children) {
|
|
893
1029
|
if (child.type === "list") {
|
|
894
1030
|
// Nested list — increase level
|
|
895
|
-
const nested = convertList(child, opts, parentLevel + 1, state);
|
|
1031
|
+
const nested = await convertList(child, opts, parentLevel + 1, state);
|
|
896
1032
|
result.push(...nested);
|
|
897
1033
|
}
|
|
898
1034
|
else if (child.type === "paragraph") {
|
|
@@ -901,7 +1037,7 @@ function convertList(block, opts, parentLevel, state) {
|
|
|
901
1037
|
const props = {
|
|
902
1038
|
numbering: { numId, level: parentLevel }
|
|
903
1039
|
};
|
|
904
|
-
const para = convertParagraph(child.inlines, opts, props);
|
|
1040
|
+
const para = await convertParagraph(child.inlines, opts, state, props);
|
|
905
1041
|
// Handle task list checkbox prefix
|
|
906
1042
|
if (item.checked !== undefined) {
|
|
907
1043
|
const checkbox = item.checked ? "☑ " : "☐ ";
|
|
@@ -922,11 +1058,11 @@ function convertList(block, opts, parentLevel, state) {
|
|
|
922
1058
|
const props = {
|
|
923
1059
|
indent: { left: 720 * (parentLevel + 1) }
|
|
924
1060
|
};
|
|
925
|
-
result.push(convertParagraph(child.inlines, opts, props));
|
|
1061
|
+
result.push(await convertParagraph(child.inlines, opts, state, props));
|
|
926
1062
|
}
|
|
927
1063
|
}
|
|
928
1064
|
else {
|
|
929
|
-
const converted = convertBlock(child, opts, parentLevel, state);
|
|
1065
|
+
const converted = await convertBlock(child, opts, parentLevel, state);
|
|
930
1066
|
result.push(...converted);
|
|
931
1067
|
firstBlock = false;
|
|
932
1068
|
}
|
|
@@ -934,10 +1070,12 @@ function convertList(block, opts, parentLevel, state) {
|
|
|
934
1070
|
}
|
|
935
1071
|
return result;
|
|
936
1072
|
}
|
|
937
|
-
function convertTable(block, opts) {
|
|
1073
|
+
async function convertTable(block, opts, state) {
|
|
938
1074
|
const colCount = block.headers.length;
|
|
939
1075
|
// Header row
|
|
940
|
-
const headerCells =
|
|
1076
|
+
const headerCells = [];
|
|
1077
|
+
for (let ci = 0; ci < block.headers.length; ci++) {
|
|
1078
|
+
const cell = block.headers[ci];
|
|
941
1079
|
const cellProps = {
|
|
942
1080
|
shading: { fill: "F0F0F0" },
|
|
943
1081
|
verticalAlign: "center"
|
|
@@ -945,7 +1083,7 @@ function convertTable(block, opts) {
|
|
|
945
1083
|
const paraProps = {
|
|
946
1084
|
alignment: block.alignments[ci]
|
|
947
1085
|
};
|
|
948
|
-
const para = convertParagraph(cell, opts, paraProps);
|
|
1086
|
+
const para = await convertParagraph(cell, opts, state, paraProps);
|
|
949
1087
|
// Bold header text
|
|
950
1088
|
const boldPara = {
|
|
951
1089
|
...para,
|
|
@@ -956,21 +1094,22 @@ function convertTable(block, opts) {
|
|
|
956
1094
|
return child;
|
|
957
1095
|
})
|
|
958
1096
|
};
|
|
959
|
-
|
|
960
|
-
}
|
|
1097
|
+
headerCells.push({ properties: cellProps, content: [boldPara] });
|
|
1098
|
+
}
|
|
961
1099
|
// Data rows
|
|
962
|
-
const dataRows =
|
|
1100
|
+
const dataRows = [];
|
|
1101
|
+
for (const rowCells of block.rows) {
|
|
963
1102
|
const cells = [];
|
|
964
1103
|
for (let ci = 0; ci < colCount; ci++) {
|
|
965
1104
|
const cellInlines = ci < rowCells.length ? rowCells[ci] : [];
|
|
966
1105
|
const paraProps = {
|
|
967
1106
|
alignment: block.alignments[ci]
|
|
968
1107
|
};
|
|
969
|
-
const para = convertParagraph(cellInlines, opts, paraProps);
|
|
1108
|
+
const para = await convertParagraph(cellInlines, opts, state, paraProps);
|
|
970
1109
|
cells.push({ content: [para] });
|
|
971
1110
|
}
|
|
972
|
-
|
|
973
|
-
}
|
|
1111
|
+
dataRows.push({ cells });
|
|
1112
|
+
}
|
|
974
1113
|
const allRows = [
|
|
975
1114
|
{ properties: { tableHeader: true }, cells: headerCells },
|
|
976
1115
|
...dataRows
|
|
@@ -995,37 +1134,41 @@ function convertTable(block, opts) {
|
|
|
995
1134
|
// =============================================================================
|
|
996
1135
|
// Inline to Run Conversion
|
|
997
1136
|
// =============================================================================
|
|
998
|
-
function inlinesToRuns(inlines, opts) {
|
|
1137
|
+
async function inlinesToRuns(inlines, opts, state, isHeading) {
|
|
999
1138
|
const result = [];
|
|
1000
1139
|
for (const node of inlines) {
|
|
1001
|
-
inlineToRuns(node, result, {}, opts);
|
|
1140
|
+
await inlineToRuns(node, result, {}, opts, state, isHeading);
|
|
1002
1141
|
}
|
|
1003
1142
|
return result;
|
|
1004
1143
|
}
|
|
1005
|
-
function inlineToRuns(node, output, inheritedProps, opts) {
|
|
1144
|
+
async function inlineToRuns(node, output, inheritedProps, opts, state, isHeading) {
|
|
1006
1145
|
switch (node.type) {
|
|
1007
1146
|
case "text": {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1147
|
+
// Headings derive their font/size from the named Heading style; only
|
|
1148
|
+
// body text picks up the document default font/size.
|
|
1149
|
+
const textProps = isHeading
|
|
1150
|
+
? inheritedProps
|
|
1151
|
+
: {
|
|
1152
|
+
...inheritedProps,
|
|
1153
|
+
...(opts.defaultFont && !inheritedProps.font ? { font: opts.defaultFont } : {}),
|
|
1154
|
+
...(opts.defaultFontSize && !inheritedProps.size ? { size: opts.defaultFontSize } : {})
|
|
1155
|
+
};
|
|
1013
1156
|
output.push(makeRun(node.text, textProps));
|
|
1014
1157
|
break;
|
|
1015
1158
|
}
|
|
1016
1159
|
case "bold":
|
|
1017
1160
|
for (const child of node.children) {
|
|
1018
|
-
inlineToRuns(child, output, { ...inheritedProps, bold: true }, opts);
|
|
1161
|
+
await inlineToRuns(child, output, { ...inheritedProps, bold: true }, opts, state, isHeading);
|
|
1019
1162
|
}
|
|
1020
1163
|
break;
|
|
1021
1164
|
case "italic":
|
|
1022
1165
|
for (const child of node.children) {
|
|
1023
|
-
inlineToRuns(child, output, { ...inheritedProps, italic: true }, opts);
|
|
1166
|
+
await inlineToRuns(child, output, { ...inheritedProps, italic: true }, opts, state, isHeading);
|
|
1024
1167
|
}
|
|
1025
1168
|
break;
|
|
1026
1169
|
case "strikethrough":
|
|
1027
1170
|
for (const child of node.children) {
|
|
1028
|
-
inlineToRuns(child, output, { ...inheritedProps, strike: true }, opts);
|
|
1171
|
+
await inlineToRuns(child, output, { ...inheritedProps, strike: true }, opts, state, isHeading);
|
|
1029
1172
|
}
|
|
1030
1173
|
break;
|
|
1031
1174
|
case "code":
|
|
@@ -1040,7 +1183,7 @@ function inlineToRuns(node, output, inheritedProps, opts) {
|
|
|
1040
1183
|
const linkChildren = [];
|
|
1041
1184
|
for (const child of node.children) {
|
|
1042
1185
|
const tempOutput = [];
|
|
1043
|
-
inlineToRuns(child, tempOutput, { ...inheritedProps, color: "0563C1", underline: "single" }, opts);
|
|
1186
|
+
await inlineToRuns(child, tempOutput, { ...inheritedProps, color: "0563C1", underline: "single" }, opts, state, isHeading);
|
|
1044
1187
|
for (const run of tempOutput) {
|
|
1045
1188
|
if ("content" in run) {
|
|
1046
1189
|
linkChildren.push(run);
|
|
@@ -1057,20 +1200,71 @@ function inlineToRuns(node, output, inheritedProps, opts) {
|
|
|
1057
1200
|
output.push(link);
|
|
1058
1201
|
break;
|
|
1059
1202
|
}
|
|
1060
|
-
case "image":
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
output.push(makeRun(`[Image: ${node.alt || node.url}]`, {
|
|
1064
|
-
...inheritedProps,
|
|
1065
|
-
italic: true,
|
|
1066
|
-
color: "666666"
|
|
1067
|
-
}));
|
|
1203
|
+
case "image": {
|
|
1204
|
+
const run = await resolveImageRun(node, inheritedProps, opts, state);
|
|
1205
|
+
output.push(run);
|
|
1068
1206
|
break;
|
|
1207
|
+
}
|
|
1208
|
+
case "footnoteRef": {
|
|
1209
|
+
const id = resolveFootnoteId(node.label, state);
|
|
1210
|
+
output.push({
|
|
1211
|
+
properties: { ...inheritedProps, style: "FootnoteReference" },
|
|
1212
|
+
content: [{ type: "footnoteRef", id }]
|
|
1213
|
+
});
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1069
1216
|
case "lineBreak":
|
|
1070
1217
|
output.push(makeRun("", undefined, [{ type: "break" }]));
|
|
1071
1218
|
break;
|
|
1072
1219
|
}
|
|
1073
1220
|
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Resolve an inline image. If `resolveImage` returns image data, embed it as a
|
|
1223
|
+
* real `InlineImageContent` run and register the media in `state.images`;
|
|
1224
|
+
* otherwise fall back to an italic `[Image: alt]` placeholder so the output
|
|
1225
|
+
* remains valid.
|
|
1226
|
+
*/
|
|
1227
|
+
async function resolveImageRun(node, inheritedProps, opts, state) {
|
|
1228
|
+
const data = opts.resolveImage ? await opts.resolveImage(node.url, node.alt) : undefined;
|
|
1229
|
+
if (!data) {
|
|
1230
|
+
return makeRun(`[Image: ${node.alt || node.url}]`, {
|
|
1231
|
+
...inheritedProps,
|
|
1232
|
+
italic: true,
|
|
1233
|
+
color: "666666"
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
const n = state.nextImageId++;
|
|
1237
|
+
const fileName = `image${n}.${imageExtension(data.mediaType)}`;
|
|
1238
|
+
const rId = `mdimg${n}`;
|
|
1239
|
+
// For SVG, the packager auto-splits the PNG fallback into its own media
|
|
1240
|
+
// part, assigns a second relationship, and back-fills `svgRId` on the inline
|
|
1241
|
+
// image — so we only ever push a single ImageDef and a single rId here.
|
|
1242
|
+
state.images.push({
|
|
1243
|
+
data: data.data,
|
|
1244
|
+
mediaType: data.mediaType,
|
|
1245
|
+
fileName,
|
|
1246
|
+
rId,
|
|
1247
|
+
...(data.fallbackData ? { fallbackData: data.fallbackData } : {})
|
|
1248
|
+
});
|
|
1249
|
+
const DEFAULT_DIM = 914400; // 1 inch fallback when no size given
|
|
1250
|
+
return {
|
|
1251
|
+
content: [
|
|
1252
|
+
{
|
|
1253
|
+
type: "image",
|
|
1254
|
+
rId,
|
|
1255
|
+
width: (data.width ?? DEFAULT_DIM),
|
|
1256
|
+
height: (data.height ?? DEFAULT_DIM),
|
|
1257
|
+
altText: node.alt || undefined,
|
|
1258
|
+
name: node.alt || fileName,
|
|
1259
|
+
drawingId: state.nextDrawingId++
|
|
1260
|
+
}
|
|
1261
|
+
]
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
/** Map an image media type to its word/media/ file extension. */
|
|
1265
|
+
function imageExtension(mediaType) {
|
|
1266
|
+
return mediaType === "jpeg" ? "jpg" : mediaType;
|
|
1267
|
+
}
|
|
1074
1268
|
// =============================================================================
|
|
1075
1269
|
// Numbering Helpers
|
|
1076
1270
|
// =============================================================================
|
|
@@ -1324,6 +1518,22 @@ function defaultMarkdownStyles() {
|
|
|
1324
1518
|
name: "Code Block",
|
|
1325
1519
|
basedOn: "Normal",
|
|
1326
1520
|
runProperties: { font: "Courier New", size: 20 }
|
|
1521
|
+
},
|
|
1522
|
+
{
|
|
1523
|
+
// Character style applied to the in-text footnote reference mark so it
|
|
1524
|
+
// renders as a superscript number, matching Word's built-in style.
|
|
1525
|
+
type: "character",
|
|
1526
|
+
styleId: "FootnoteReference",
|
|
1527
|
+
name: "footnote reference",
|
|
1528
|
+
runProperties: { vertAlign: "superscript" }
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
// Paragraph style for footnote body text (smaller font, like Word).
|
|
1532
|
+
type: "paragraph",
|
|
1533
|
+
styleId: "FootnoteText",
|
|
1534
|
+
name: "footnote text",
|
|
1535
|
+
basedOn: "Normal",
|
|
1536
|
+
runProperties: { size: 20 }
|
|
1327
1537
|
}
|
|
1328
1538
|
];
|
|
1329
1539
|
}
|