odf-kit 0.10.4 → 0.11.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/CHANGELOG.md +24 -0
- package/README.md +18 -2
- package/dist/markdown/emitter.d.ts +92 -0
- package/dist/markdown/emitter.d.ts.map +1 -0
- package/dist/markdown/emitter.js +279 -0
- package/dist/markdown/emitter.js.map +1 -0
- package/dist/markdown/index.d.ts +15 -0
- package/dist/markdown/index.d.ts.map +1 -0
- package/dist/markdown/index.js +14 -0
- package/dist/markdown/index.js.map +1 -0
- package/package.json +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to odf-kit will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.11.0] - 2026-04-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`odtToMarkdown()`** — Convert `.odt` files directly to Markdown. Returns a Markdown string. Available via `odf-kit/markdown`.
|
|
13
|
+
- **`modelToMarkdown()`** — Convert a pre-parsed `OdtDocumentModel` to Markdown. Use when you already have a model from `readOdt()` or want to share a single parse across multiple emitters.
|
|
14
|
+
- **`odf-kit/markdown`** sub-export added.
|
|
15
|
+
- **`MarkdownEmitOptions`** — `flavor: "gfm" | "commonmark"` (default: `"gfm"`) and `trackedChanges: "final" | "original" | "changes"` (default: `"final"`).
|
|
16
|
+
- **GFM flavor** — pipe tables with `---` separator row, `~~strikethrough~~`. Compatible with GitHub, GitLab, and most modern Markdown renderers.
|
|
17
|
+
- **CommonMark flavor** — tables emitted as plain text rows (no pipe syntax), strikethrough falls back to plain text.
|
|
18
|
+
- **Inline coverage:** bold (`**text**`), italic (`_text_`), bold+italic (`**_text_**`), strikethrough (`~~text~~`), underline (`<u>text</u>`), superscript (`<sup>text</sup>`), subscript (`<sub>text</sub>`), hyperlinks (`[text](url)`), hard line breaks (two trailing spaces + newline), images (`` placeholder).
|
|
19
|
+
- **Block coverage:** headings (levels 1–6), paragraphs (blank-line separated), unordered lists (`- `), ordered lists (`1. `), nested lists (2-space indent per level), tables (GFM pipe format), sections (body emitted directly), tracked changes.
|
|
20
|
+
- 19 new tests (1078 total, 24 test suites).
|
|
21
|
+
|
|
22
|
+
## [0.10.4] - 2026-04-14
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **ODS freeze rows/columns — `ViewId` and `ActiveTable` missing** — LibreOffice requires a `ViewId` item (`"view1"`) in the view entry and an `ActiveTable` item naming the active sheet. Without both, the entire view configuration is silently ignored and freeze panes have no effect. Both items are now emitted correctly.
|
|
27
|
+
- **`typesVersions` dropped in v0.10.3** — The `typesVersions` field added in v0.10.1 was accidentally omitted from the v0.10.3 `package.json`. Restored.
|
|
28
|
+
- 6 new freeze pane tests (1059 total).
|
|
29
|
+
|
|
8
30
|
## [0.10.3] - 2026-04-14
|
|
9
31
|
|
|
10
32
|
### Changed
|
|
@@ -254,6 +276,8 @@ Initial release. Complete ODT generation support.
|
|
|
254
276
|
[0.8.1]: https://github.com/GitHubNewbie0/odf-kit/releases/tag/v0.8.1
|
|
255
277
|
[0.8.0]: https://github.com/GitHubNewbie0/odf-kit/releases/tag/v0.8.0
|
|
256
278
|
[0.7.0]: https://github.com/GitHubNewbie0/odf-kit/releases/tag/v0.7.0
|
|
279
|
+
[0.11.0]: https://github.com/GitHubNewbie0/odf-kit/releases/tag/v0.11.0
|
|
280
|
+
[0.10.4]: https://github.com/GitHubNewbie0/odf-kit/releases/tag/v0.10.4
|
|
257
281
|
[0.10.3]: https://github.com/GitHubNewbie0/odf-kit/releases/tag/v0.10.3
|
|
258
282
|
[0.10.2]: https://github.com/GitHubNewbie0/odf-kit/releases/tag/v0.10.2
|
|
259
283
|
[0.4.0]: https://github.com/GitHubNewbie0/odf-kit/releases/tag/v0.4.0
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Generate, fill, read, and convert OpenDocument Format files (.odt, .ods) in Type
|
|
|
8
8
|
npm install odf-kit
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Twelve ways to work with ODF files
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
// 1. Build an ODT document from scratch
|
|
@@ -160,6 +160,17 @@ const { bytes: bytes2 } = await docxToOdt(readFileSync("report.docx"), {
|
|
|
160
160
|
});
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
+
```typescript
|
|
164
|
+
// 12. Convert .odt to Markdown
|
|
165
|
+
import { odtToMarkdown } from "odf-kit/markdown";
|
|
166
|
+
|
|
167
|
+
const md = odtToMarkdown(readFileSync("document.odt"));
|
|
168
|
+
writeFileSync("document.md", md);
|
|
169
|
+
|
|
170
|
+
// CommonMark flavor (no pipe tables)
|
|
171
|
+
const mdCompat = odtToMarkdown(readFileSync("document.odt"), { flavor: "commonmark" });
|
|
172
|
+
```
|
|
173
|
+
|
|
163
174
|
---
|
|
164
175
|
|
|
165
176
|
## Installation
|
|
@@ -176,6 +187,7 @@ import { readOdt, odtToHtml } from "odf-kit/odt-reader";
|
|
|
176
187
|
import { readOds, odsToHtml } from "odf-kit/ods-reader";
|
|
177
188
|
import { odtToTypst, modelToTypst } from "odf-kit/typst";
|
|
178
189
|
import { docxToOdt } from "odf-kit/docx";
|
|
190
|
+
import { odtToMarkdown, modelToMarkdown } from "odf-kit/markdown";
|
|
179
191
|
```
|
|
180
192
|
|
|
181
193
|
Works in Node.js, browsers, Deno, Bun, and Cloudflare Workers. Runtime dependencies: [fflate](https://github.com/101arrowz/fflate) for ZIP, [marked](https://marked.js.org/) for Markdown parsing.
|
|
@@ -935,7 +947,11 @@ odf-kit targets ODF 1.2 (ISO/IEC 26300). Generated files include proper ZIP pack
|
|
|
935
947
|
|
|
936
948
|
## Version history
|
|
937
949
|
|
|
938
|
-
**v0.
|
|
950
|
+
**v0.11.0** — `odtToMarkdown()` and `modelToMarkdown()` via `odf-kit/markdown`. GFM and CommonMark flavors. 1078 tests passing.
|
|
951
|
+
|
|
952
|
+
**v0.10.4** — ODS freeze fix: `ViewId` and `ActiveTable` added to `settings.xml`. `typesVersions` restored (dropped in v0.10.3). 1059 tests passing.
|
|
953
|
+
|
|
954
|
+
**v0.10.3** — `module-sync` exports condition for bundler compatibility. `typesVersions` for TypeScript `moduleResolution: node` compatibility.
|
|
939
955
|
|
|
940
956
|
**v0.10.2** — ODS freeze rows/columns fix — `ActiveSplitRange` and all split axis items now correctly emitted in `settings.xml`.
|
|
941
957
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown emitter for the ODT document model.
|
|
3
|
+
*
|
|
4
|
+
* Converts the structured document model produced by the ODT parser into
|
|
5
|
+
* a Markdown string. Zero runtime dependencies — the emitter is a pure
|
|
6
|
+
* function over OdtDocumentModel.
|
|
7
|
+
*
|
|
8
|
+
* Structural coverage:
|
|
9
|
+
* - Headings levels 1–6 (# through ######)
|
|
10
|
+
* - Paragraphs with blank-line separation
|
|
11
|
+
* - Bold, italic, bold+italic, strikethrough (GFM)
|
|
12
|
+
* - Underline → <u>text</u> (HTML passthrough — valid in GFM)
|
|
13
|
+
* - Superscript → <sup>text</sup>, subscript → <sub>text</sub>
|
|
14
|
+
* - Hyperlinks → [text](url)
|
|
15
|
+
* - Hard line breaks → two trailing spaces + newline
|
|
16
|
+
* - Unordered and ordered lists with nested sub-lists
|
|
17
|
+
* - Tables → GFM pipe table with --- separator row
|
|
18
|
+
* - Images →  placeholder (base64 data not inlined)
|
|
19
|
+
* - Sections → body content only (no Markdown equivalent)
|
|
20
|
+
* - Tracked changes: final (default), original, and changes modes
|
|
21
|
+
*
|
|
22
|
+
* Text content is escaped for Markdown. The following characters are
|
|
23
|
+
* backslash-escaped when they appear in plain text: \ ` * _ { } [ ] ( ) # + - . !
|
|
24
|
+
*/
|
|
25
|
+
import type { OdtDocumentModel } from "../reader/types.js";
|
|
26
|
+
import type { ReadOdtOptions } from "../reader/types.js";
|
|
27
|
+
/**
|
|
28
|
+
* Options for the Markdown emitter.
|
|
29
|
+
*/
|
|
30
|
+
export interface MarkdownEmitOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Markdown flavor to target.
|
|
33
|
+
*
|
|
34
|
+
* "gfm" (default): GitHub Flavored Markdown — enables pipe tables and
|
|
35
|
+
* ~~strikethrough~~. Compatible with GitHub, GitLab, and most modern
|
|
36
|
+
* Markdown renderers.
|
|
37
|
+
* "commonmark": CommonMark only — tables are omitted (cells emitted as
|
|
38
|
+
* plain paragraphs), strikethrough falls back to plain text.
|
|
39
|
+
*/
|
|
40
|
+
flavor?: "commonmark" | "gfm";
|
|
41
|
+
/**
|
|
42
|
+
* Controls how tracked changes are emitted. Mirrors ReadOdtOptions.trackedChanges.
|
|
43
|
+
*
|
|
44
|
+
* "final" (default): insertions rendered as body; deletions produce no output.
|
|
45
|
+
* "original": deletions rendered as body; insertions produce no output.
|
|
46
|
+
* "changes": insertions → <ins>body</ins>, deletions → <del>body</del>.
|
|
47
|
+
*/
|
|
48
|
+
trackedChanges?: "final" | "original" | "changes";
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Convert an OdtDocumentModel to a Markdown string.
|
|
52
|
+
*
|
|
53
|
+
* @param model - The parsed ODT document model from readOdt().
|
|
54
|
+
* @param options - Markdown emitter options.
|
|
55
|
+
* @returns Markdown string.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* import { readOdt } from "odf-kit/reader";
|
|
60
|
+
* import { modelToMarkdown } from "odf-kit/markdown";
|
|
61
|
+
* import { readFileSync, writeFileSync } from "node:fs";
|
|
62
|
+
*
|
|
63
|
+
* const bytes = new Uint8Array(readFileSync("document.odt"));
|
|
64
|
+
* const model = readOdt(bytes);
|
|
65
|
+
* const md = modelToMarkdown(model);
|
|
66
|
+
* writeFileSync("document.md", md);
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare function modelToMarkdown(model: OdtDocumentModel, options?: MarkdownEmitOptions): string;
|
|
70
|
+
/**
|
|
71
|
+
* Convert an .odt file directly to a Markdown string.
|
|
72
|
+
*
|
|
73
|
+
* Convenience wrapper around readOdt() + modelToMarkdown(). Use
|
|
74
|
+
* modelToMarkdown() directly when you need access to the document model
|
|
75
|
+
* or want to share a single readOdt() call between multiple emitters.
|
|
76
|
+
*
|
|
77
|
+
* @param bytes - The raw .odt file as a Uint8Array.
|
|
78
|
+
* @param options - Combined emitter and read options.
|
|
79
|
+
* @returns Markdown string.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* import { odtToMarkdown } from "odf-kit/markdown";
|
|
84
|
+
* import { readFileSync, writeFileSync } from "node:fs";
|
|
85
|
+
*
|
|
86
|
+
* const bytes = new Uint8Array(readFileSync("document.odt"));
|
|
87
|
+
* const md = odtToMarkdown(bytes);
|
|
88
|
+
* writeFileSync("document.md", md);
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare function odtToMarkdown(bytes: Uint8Array, options?: MarkdownEmitOptions & ReadOdtOptions): string;
|
|
92
|
+
//# sourceMappingURL=emitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.d.ts","sourceRoot":"","sources":["../../src/markdown/emitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAWjB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAMzD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,YAAY,GAAG,KAAK,CAAC;IAE9B;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;CACnD;AA0OD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAE9F;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,UAAU,EACjB,OAAO,CAAC,EAAE,mBAAmB,GAAG,cAAc,GAC7C,MAAM,CAGR"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown emitter for the ODT document model.
|
|
3
|
+
*
|
|
4
|
+
* Converts the structured document model produced by the ODT parser into
|
|
5
|
+
* a Markdown string. Zero runtime dependencies — the emitter is a pure
|
|
6
|
+
* function over OdtDocumentModel.
|
|
7
|
+
*
|
|
8
|
+
* Structural coverage:
|
|
9
|
+
* - Headings levels 1–6 (# through ######)
|
|
10
|
+
* - Paragraphs with blank-line separation
|
|
11
|
+
* - Bold, italic, bold+italic, strikethrough (GFM)
|
|
12
|
+
* - Underline → <u>text</u> (HTML passthrough — valid in GFM)
|
|
13
|
+
* - Superscript → <sup>text</sup>, subscript → <sub>text</sub>
|
|
14
|
+
* - Hyperlinks → [text](url)
|
|
15
|
+
* - Hard line breaks → two trailing spaces + newline
|
|
16
|
+
* - Unordered and ordered lists with nested sub-lists
|
|
17
|
+
* - Tables → GFM pipe table with --- separator row
|
|
18
|
+
* - Images →  placeholder (base64 data not inlined)
|
|
19
|
+
* - Sections → body content only (no Markdown equivalent)
|
|
20
|
+
* - Tracked changes: final (default), original, and changes modes
|
|
21
|
+
*
|
|
22
|
+
* Text content is escaped for Markdown. The following characters are
|
|
23
|
+
* backslash-escaped when they appear in plain text: \ ` * _ { } [ ] ( ) # + - . !
|
|
24
|
+
*/
|
|
25
|
+
import { readOdt } from "../reader/parser.js";
|
|
26
|
+
// ============================================================
|
|
27
|
+
// Markdown escaping
|
|
28
|
+
// ============================================================
|
|
29
|
+
/** Characters with special meaning in Markdown inline context. */
|
|
30
|
+
const MD_ESCAPE_RE = /[\\`*_{}[\]()#+\-.!]/g;
|
|
31
|
+
/**
|
|
32
|
+
* Escape characters that carry special meaning in Markdown plain text.
|
|
33
|
+
* Does not escape inside code spans or link targets.
|
|
34
|
+
*/
|
|
35
|
+
function escapeMd(text) {
|
|
36
|
+
return text.replace(MD_ESCAPE_RE, (ch) => `\\${ch}`);
|
|
37
|
+
}
|
|
38
|
+
// ============================================================
|
|
39
|
+
// Inline node renderers
|
|
40
|
+
// ============================================================
|
|
41
|
+
/**
|
|
42
|
+
* Emit a TextSpan to a Markdown string.
|
|
43
|
+
*
|
|
44
|
+
* Formatting nesting order (innermost first):
|
|
45
|
+
* superscript/subscript → underline → strikethrough → italic → bold
|
|
46
|
+
* → hyperlink anchor.
|
|
47
|
+
*/
|
|
48
|
+
function emitTextSpan(span, options) {
|
|
49
|
+
if (span.lineBreak)
|
|
50
|
+
return " \n";
|
|
51
|
+
if (span.hidden)
|
|
52
|
+
return "";
|
|
53
|
+
let out = escapeMd(span.text);
|
|
54
|
+
if (span.superscript)
|
|
55
|
+
out = `<sup>${out}</sup>`;
|
|
56
|
+
if (span.subscript)
|
|
57
|
+
out = `<sub>${out}</sub>`;
|
|
58
|
+
if (span.underline)
|
|
59
|
+
out = `<u>${out}</u>`;
|
|
60
|
+
if (span.strikethrough) {
|
|
61
|
+
out = options?.flavor === "commonmark" ? out : `~~${out}~~`;
|
|
62
|
+
}
|
|
63
|
+
if (span.italic && span.bold) {
|
|
64
|
+
out = `**_${out}_**`;
|
|
65
|
+
}
|
|
66
|
+
else if (span.bold) {
|
|
67
|
+
out = `**${out}**`;
|
|
68
|
+
}
|
|
69
|
+
else if (span.italic) {
|
|
70
|
+
out = `_${out}_`;
|
|
71
|
+
}
|
|
72
|
+
if (span.href !== undefined) {
|
|
73
|
+
out = `[${out}](${span.href})`;
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Emit an ImageNode as a Markdown image placeholder.
|
|
79
|
+
*
|
|
80
|
+
* Base64 image data is not inlined. The alt text and name are preserved
|
|
81
|
+
* so consumers can substitute with real paths if needed.
|
|
82
|
+
*/
|
|
83
|
+
function emitImage(node) {
|
|
84
|
+
const alt = node.title ?? node.name ?? "image";
|
|
85
|
+
const src = node.name ?? "image";
|
|
86
|
+
return ``;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Emit a NoteNode (footnote/endnote) as a Markdown inline footnote.
|
|
90
|
+
* Uses the GFM/Pandoc-style inline footnote syntax: ^[content].
|
|
91
|
+
* Falls back to a bracketed superscript number for CommonMark.
|
|
92
|
+
*/
|
|
93
|
+
function emitNote(node, options) {
|
|
94
|
+
const content = emitBodyNodes(node.body, options);
|
|
95
|
+
if (options?.flavor === "commonmark") {
|
|
96
|
+
return `<sup>[note]</sup>`;
|
|
97
|
+
}
|
|
98
|
+
return `^[${content.trim()}]`;
|
|
99
|
+
}
|
|
100
|
+
/** Emit a FieldNode — page number and page count get descriptive placeholders. */
|
|
101
|
+
function emitField(node) {
|
|
102
|
+
if (node.fieldType === "pageNumber")
|
|
103
|
+
return node.value ?? "[page]";
|
|
104
|
+
if (node.fieldType === "pageCount")
|
|
105
|
+
return node.value ?? "[pages]";
|
|
106
|
+
return node.value ?? "";
|
|
107
|
+
}
|
|
108
|
+
/** Emit a single InlineNode to a Markdown string. */
|
|
109
|
+
function emitInlineNode(node, options) {
|
|
110
|
+
if ("kind" in node) {
|
|
111
|
+
switch (node.kind) {
|
|
112
|
+
case "image":
|
|
113
|
+
return emitImage(node);
|
|
114
|
+
case "note":
|
|
115
|
+
return emitNote(node, options);
|
|
116
|
+
case "bookmark":
|
|
117
|
+
// Bookmarks have no Markdown equivalent — emit nothing
|
|
118
|
+
return "";
|
|
119
|
+
case "field":
|
|
120
|
+
return emitField(node);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return emitTextSpan(node, options);
|
|
124
|
+
}
|
|
125
|
+
/** Emit an array of InlineNodes to a Markdown string. */
|
|
126
|
+
function emitSpans(spans, options) {
|
|
127
|
+
return spans.map((s) => emitInlineNode(s, options)).join("");
|
|
128
|
+
}
|
|
129
|
+
// ============================================================
|
|
130
|
+
// Block node renderers
|
|
131
|
+
// ============================================================
|
|
132
|
+
/**
|
|
133
|
+
* Emit a ListNode to a Markdown string.
|
|
134
|
+
*
|
|
135
|
+
* Unordered items use `- `; ordered items use `1. ` (renumbered by renderers).
|
|
136
|
+
* Nested sub-lists are indented with two spaces per level.
|
|
137
|
+
*/
|
|
138
|
+
function emitList(list, options, depth = 0) {
|
|
139
|
+
const indent = " ".repeat(depth);
|
|
140
|
+
const marker = list.ordered ? "1." : "-";
|
|
141
|
+
return list.items
|
|
142
|
+
.map((item) => {
|
|
143
|
+
const content = emitSpans(item.spans, options);
|
|
144
|
+
const nested = item.children !== undefined ? "\n" + emitList(item.children, options, depth + 1) : "";
|
|
145
|
+
return `${indent}${marker} ${content}${nested}`;
|
|
146
|
+
})
|
|
147
|
+
.join("\n");
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Emit a TableNode as a GFM pipe table.
|
|
151
|
+
*
|
|
152
|
+
* The first row is treated as the header row. A separator row of `---`
|
|
153
|
+
* cells is inserted after it. Falls back to plain paragraph text per cell
|
|
154
|
+
* when flavor is "commonmark".
|
|
155
|
+
*/
|
|
156
|
+
function emitTable(table, options) {
|
|
157
|
+
if (table.rows.length === 0)
|
|
158
|
+
return "";
|
|
159
|
+
if (options?.flavor === "commonmark") {
|
|
160
|
+
// CommonMark has no table syntax — emit rows as plain text paragraphs
|
|
161
|
+
return table.rows
|
|
162
|
+
.map((row) => row.cells.map((cell) => emitSpans(cell.spans, options)).join(" | "))
|
|
163
|
+
.join("\n\n");
|
|
164
|
+
}
|
|
165
|
+
const rows = table.rows.map((row) => "| " +
|
|
166
|
+
row.cells.map((cell) => emitSpans(cell.spans, options).replace(/\|/g, "\\|")).join(" | ") +
|
|
167
|
+
" |");
|
|
168
|
+
const cols = table.rows[0].cells.length;
|
|
169
|
+
const separator = "| " + Array(cols).fill("---").join(" | ") + " |";
|
|
170
|
+
// Insert separator after first row (header)
|
|
171
|
+
rows.splice(1, 0, separator);
|
|
172
|
+
return rows.join("\n");
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Emit a SectionNode — Markdown has no section construct.
|
|
176
|
+
* Emits the body content directly.
|
|
177
|
+
*/
|
|
178
|
+
function emitSection(node, options) {
|
|
179
|
+
return emitBodyNodes(node.body, options);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Emit a TrackedChangeNode.
|
|
183
|
+
*
|
|
184
|
+
* "changes" mode: insertions → <ins>body</ins>, deletions → <del>body</del>.
|
|
185
|
+
* Other modes: body emitted transparently.
|
|
186
|
+
*/
|
|
187
|
+
function emitTrackedChange(node, options) {
|
|
188
|
+
const body = emitBodyNodes(node.body, options);
|
|
189
|
+
if (options?.trackedChanges !== "changes") {
|
|
190
|
+
return body;
|
|
191
|
+
}
|
|
192
|
+
switch (node.changeType) {
|
|
193
|
+
case "insertion":
|
|
194
|
+
return `<ins>${body}</ins>`;
|
|
195
|
+
case "deletion":
|
|
196
|
+
return `<del>${body}</del>`;
|
|
197
|
+
case "format-change":
|
|
198
|
+
return body;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** Emit a single BodyNode to a Markdown string. */
|
|
202
|
+
function emitBodyNode(node, options) {
|
|
203
|
+
switch (node.kind) {
|
|
204
|
+
case "paragraph":
|
|
205
|
+
return emitSpans(node.spans, options);
|
|
206
|
+
case "heading": {
|
|
207
|
+
const prefix = "#".repeat(node.level);
|
|
208
|
+
return `${prefix} ${emitSpans(node.spans, options)}`;
|
|
209
|
+
}
|
|
210
|
+
case "list":
|
|
211
|
+
return emitList(node, options);
|
|
212
|
+
case "table":
|
|
213
|
+
return emitTable(node, options);
|
|
214
|
+
case "section":
|
|
215
|
+
return emitSection(node, options);
|
|
216
|
+
case "tracked-change":
|
|
217
|
+
return emitTrackedChange(node, options);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Emit an array of BodyNodes separated by blank lines.
|
|
222
|
+
*/
|
|
223
|
+
function emitBodyNodes(body, options) {
|
|
224
|
+
return body
|
|
225
|
+
.map((n) => emitBodyNode(n, options))
|
|
226
|
+
.filter((s) => s.trim().length > 0)
|
|
227
|
+
.join("\n\n");
|
|
228
|
+
}
|
|
229
|
+
// ============================================================
|
|
230
|
+
// Public API
|
|
231
|
+
// ============================================================
|
|
232
|
+
/**
|
|
233
|
+
* Convert an OdtDocumentModel to a Markdown string.
|
|
234
|
+
*
|
|
235
|
+
* @param model - The parsed ODT document model from readOdt().
|
|
236
|
+
* @param options - Markdown emitter options.
|
|
237
|
+
* @returns Markdown string.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```typescript
|
|
241
|
+
* import { readOdt } from "odf-kit/reader";
|
|
242
|
+
* import { modelToMarkdown } from "odf-kit/markdown";
|
|
243
|
+
* import { readFileSync, writeFileSync } from "node:fs";
|
|
244
|
+
*
|
|
245
|
+
* const bytes = new Uint8Array(readFileSync("document.odt"));
|
|
246
|
+
* const model = readOdt(bytes);
|
|
247
|
+
* const md = modelToMarkdown(model);
|
|
248
|
+
* writeFileSync("document.md", md);
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
export function modelToMarkdown(model, options) {
|
|
252
|
+
return emitBodyNodes(model.body, options);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Convert an .odt file directly to a Markdown string.
|
|
256
|
+
*
|
|
257
|
+
* Convenience wrapper around readOdt() + modelToMarkdown(). Use
|
|
258
|
+
* modelToMarkdown() directly when you need access to the document model
|
|
259
|
+
* or want to share a single readOdt() call between multiple emitters.
|
|
260
|
+
*
|
|
261
|
+
* @param bytes - The raw .odt file as a Uint8Array.
|
|
262
|
+
* @param options - Combined emitter and read options.
|
|
263
|
+
* @returns Markdown string.
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```typescript
|
|
267
|
+
* import { odtToMarkdown } from "odf-kit/markdown";
|
|
268
|
+
* import { readFileSync, writeFileSync } from "node:fs";
|
|
269
|
+
*
|
|
270
|
+
* const bytes = new Uint8Array(readFileSync("document.odt"));
|
|
271
|
+
* const md = odtToMarkdown(bytes);
|
|
272
|
+
* writeFileSync("document.md", md);
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
export function odtToMarkdown(bytes, options) {
|
|
276
|
+
const model = readOdt(bytes, options);
|
|
277
|
+
return modelToMarkdown(model, options);
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=emitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.js","sourceRoot":"","sources":["../../src/markdown/emitter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API for the odf-kit Markdown emitter.
|
|
3
|
+
*
|
|
4
|
+
* Import from "odf-kit/markdown":
|
|
5
|
+
*
|
|
6
|
+
* ```typescript
|
|
7
|
+
* import { odtToMarkdown, modelToMarkdown } from "odf-kit/markdown";
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* odtToMarkdown() converts an .odt file directly to a Markdown string.
|
|
11
|
+
* modelToMarkdown() accepts a pre-parsed OdtDocumentModel from readOdt().
|
|
12
|
+
*/
|
|
13
|
+
export { odtToMarkdown, modelToMarkdown } from "./emitter.js";
|
|
14
|
+
export type { MarkdownEmitOptions } from "./emitter.js";
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/markdown/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC9D,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API for the odf-kit Markdown emitter.
|
|
3
|
+
*
|
|
4
|
+
* Import from "odf-kit/markdown":
|
|
5
|
+
*
|
|
6
|
+
* ```typescript
|
|
7
|
+
* import { odtToMarkdown, modelToMarkdown } from "odf-kit/markdown";
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* odtToMarkdown() converts an .odt file directly to a Markdown string.
|
|
11
|
+
* modelToMarkdown() accepts a pre-parsed OdtDocumentModel from readOdt().
|
|
12
|
+
*/
|
|
13
|
+
export { odtToMarkdown, modelToMarkdown } from "./emitter.js";
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/markdown/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odf-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Generate, fill, read, and convert OpenDocument Format (.odt) files in JavaScript and TypeScript. Works in Node.js and browsers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -55,6 +55,11 @@
|
|
|
55
55
|
"types": "./dist/docx/index.d.ts",
|
|
56
56
|
"import": "./dist/docx/index.js",
|
|
57
57
|
"module-sync": "./dist/docx/index.js"
|
|
58
|
+
},
|
|
59
|
+
"./markdown": {
|
|
60
|
+
"types": "./dist/markdown/index.d.ts",
|
|
61
|
+
"import": "./dist/markdown/index.js",
|
|
62
|
+
"module-sync": "./dist/markdown/index.js"
|
|
58
63
|
}
|
|
59
64
|
},
|
|
60
65
|
"typesVersions": {
|
|
@@ -85,6 +90,9 @@
|
|
|
85
90
|
],
|
|
86
91
|
"docx": [
|
|
87
92
|
"./dist/docx/index.d.ts"
|
|
93
|
+
],
|
|
94
|
+
"markdown": [
|
|
95
|
+
"./dist/markdown/index.d.ts"
|
|
88
96
|
]
|
|
89
97
|
}
|
|
90
98
|
},
|