odf-kit 0.9.6 → 0.9.7
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 +29 -0
- package/README.md +242 -279
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/ods/content.d.ts +0 -9
- package/dist/ods/content.d.ts.map +1 -1
- package/dist/ods/content.js +274 -97
- package/dist/ods/content.js.map +1 -1
- package/dist/ods/document.d.ts +2 -42
- package/dist/ods/document.d.ts.map +1 -1
- package/dist/ods/document.js +7 -42
- package/dist/ods/document.js.map +1 -1
- package/dist/ods/index.d.ts +1 -1
- package/dist/ods/index.d.ts.map +1 -1
- package/dist/ods/settings.d.ts +13 -0
- package/dist/ods/settings.d.ts.map +1 -0
- package/dist/ods/settings.js +67 -0
- package/dist/ods/settings.js.map +1 -0
- package/dist/ods/sheet-builder.d.ts +42 -28
- package/dist/ods/sheet-builder.d.ts.map +1 -1
- package/dist/ods/sheet-builder.js +57 -42
- package/dist/ods/sheet-builder.js.map +1 -1
- package/dist/ods/types.d.ts +68 -30
- package/dist/ods/types.d.ts.map +1 -1
- package/package.json +14 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# odf-kit
|
|
2
2
|
|
|
3
|
-
Generate, fill, read, and convert OpenDocument Format files (.odt, .ods) in TypeScript and JavaScript. Convert HTML to ODT. Works in Node.js and browsers. No LibreOffice dependency — pure spec-compliant ODF.
|
|
3
|
+
Generate, fill, read, and convert OpenDocument Format files (.odt, .ods) in TypeScript and JavaScript. Convert HTML, Markdown, and TipTap JSON to ODT. Works in Node.js and browsers. No LibreOffice dependency — pure spec-compliant ODF.
|
|
4
4
|
|
|
5
5
|
**[Documentation & examples →](https://githubnewbie0.github.io/odf-kit/)**
|
|
6
6
|
|
|
@@ -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
|
+
## Eight ways to work with ODF files
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
// 1. Build an ODT document from scratch
|
|
@@ -41,7 +41,43 @@ const bytes = await htmlToOdt(html, { pageFormat: "A4" });
|
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
```typescript
|
|
44
|
-
// 3.
|
|
44
|
+
// 3. Convert Markdown to ODT
|
|
45
|
+
import { markdownToOdt } from "odf-kit";
|
|
46
|
+
|
|
47
|
+
const markdown = `
|
|
48
|
+
# Meeting Notes
|
|
49
|
+
|
|
50
|
+
Attendees: **Alice**, Bob, Carol
|
|
51
|
+
|
|
52
|
+
## Action Items
|
|
53
|
+
|
|
54
|
+
- Send report by Friday
|
|
55
|
+
- Review budget on Monday
|
|
56
|
+
`;
|
|
57
|
+
const bytes = await markdownToOdt(markdown, { pageFormat: "A4" });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// 4. Convert TipTap/ProseMirror JSON to ODT
|
|
62
|
+
import { tiptapToOdt } from "odf-kit";
|
|
63
|
+
|
|
64
|
+
// editor.getJSON() returns TipTap JSONContent
|
|
65
|
+
const bytes = await tiptapToOdt(editor.getJSON(), { pageFormat: "A4" });
|
|
66
|
+
|
|
67
|
+
// With pre-fetched images (e.g. from IPFS or S3)
|
|
68
|
+
const images = { [imageUrl]: await fetchImageBytes(imageUrl) };
|
|
69
|
+
const bytes2 = await tiptapToOdt(editor.getJSON(), { images });
|
|
70
|
+
|
|
71
|
+
// With custom node handler for app-specific extensions
|
|
72
|
+
const bytes3 = await tiptapToOdt(editor.getJSON(), {
|
|
73
|
+
unknownNodeHandler: (node, doc) => {
|
|
74
|
+
if (node.type === "callout") doc.addParagraph(`⚠️ ${extractText(node)}`);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// 5. Build an ODS spreadsheet from scratch
|
|
45
81
|
import { OdsDocument } from "odf-kit";
|
|
46
82
|
|
|
47
83
|
const doc = new OdsDocument();
|
|
@@ -56,7 +92,7 @@ const bytes = await doc.save();
|
|
|
56
92
|
```
|
|
57
93
|
|
|
58
94
|
```typescript
|
|
59
|
-
//
|
|
95
|
+
// 6. Fill an existing .odt template with data
|
|
60
96
|
import { fillTemplate } from "odf-kit";
|
|
61
97
|
|
|
62
98
|
const template = readFileSync("invoice-template.odt");
|
|
@@ -74,7 +110,7 @@ writeFileSync("invoice.odt", result);
|
|
|
74
110
|
```
|
|
75
111
|
|
|
76
112
|
```typescript
|
|
77
|
-
//
|
|
113
|
+
// 7. Read an existing .odt file
|
|
78
114
|
import { readOdt, odtToHtml } from "odf-kit/reader";
|
|
79
115
|
|
|
80
116
|
const bytes = readFileSync("report.odt");
|
|
@@ -83,7 +119,7 @@ const html = odtToHtml(bytes); // styled HTML string
|
|
|
83
119
|
```
|
|
84
120
|
|
|
85
121
|
```typescript
|
|
86
|
-
//
|
|
122
|
+
// 8. Convert .odt to Typst for PDF generation
|
|
87
123
|
import { odtToTypst } from "odf-kit/typst";
|
|
88
124
|
import { execSync } from "child_process";
|
|
89
125
|
|
|
@@ -103,12 +139,12 @@ npm install odf-kit
|
|
|
103
139
|
Node.js 22+ required. ESM only. Sub-exports:
|
|
104
140
|
|
|
105
141
|
```typescript
|
|
106
|
-
import { OdtDocument, OdsDocument, htmlToOdt, fillTemplate } from "odf-kit";
|
|
107
|
-
import { readOdt, odtToHtml }
|
|
108
|
-
import { odtToTypst, modelToTypst }
|
|
142
|
+
import { OdtDocument, OdsDocument, htmlToOdt, markdownToOdt, tiptapToOdt, fillTemplate } from "odf-kit";
|
|
143
|
+
import { readOdt, odtToHtml } from "odf-kit/reader";
|
|
144
|
+
import { odtToTypst, modelToTypst } from "odf-kit/typst";
|
|
109
145
|
```
|
|
110
146
|
|
|
111
|
-
Works in Node.js, browsers, Deno, Bun, and Cloudflare Workers.
|
|
147
|
+
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.
|
|
112
148
|
|
|
113
149
|
---
|
|
114
150
|
|
|
@@ -248,13 +284,6 @@ doc.addParagraph((p) => {
|
|
|
248
284
|
});
|
|
249
285
|
```
|
|
250
286
|
|
|
251
|
-
In a browser, use `fetch()` or a file input instead of `readFile()`:
|
|
252
|
-
|
|
253
|
-
```javascript
|
|
254
|
-
const response = await fetch("logo.png");
|
|
255
|
-
const logo = new Uint8Array(await response.arrayBuffer());
|
|
256
|
-
```
|
|
257
|
-
|
|
258
287
|
### Links and bookmarks
|
|
259
288
|
|
|
260
289
|
```typescript
|
|
@@ -327,8 +356,6 @@ sheet.addRow([
|
|
|
327
356
|
|
|
328
357
|
### Row and cell formatting
|
|
329
358
|
|
|
330
|
-
Options on `addRow()` apply to all cells in the row. Per-cell options inside an `OdsCellObject` override the row defaults.
|
|
331
|
-
|
|
332
359
|
```typescript
|
|
333
360
|
// Bold header row with background
|
|
334
361
|
sheet.addRow(["Month", "Revenue", "Notes"], {
|
|
@@ -340,32 +367,23 @@ sheet.addRow(["Month", "Revenue", "Notes"], {
|
|
|
340
367
|
// Mixed: row default + per-cell override
|
|
341
368
|
sheet.addRow([
|
|
342
369
|
"January",
|
|
343
|
-
{ value: 12500, type: "float", color: "#006600" },
|
|
370
|
+
{ value: 12500, type: "float", color: "#006600" },
|
|
344
371
|
"On track",
|
|
345
372
|
], { italic: true });
|
|
346
373
|
```
|
|
347
374
|
|
|
348
|
-
Available formatting options: `bold`, `italic`, `fontSize`, `fontFamily`, `color`, `underline`, `backgroundColor`, `border`, `borderTop/Bottom/Left/Right`, `align`, `verticalAlign`, `padding`, `wrap`.
|
|
349
|
-
|
|
350
375
|
### Date formatting
|
|
351
376
|
|
|
352
377
|
```typescript
|
|
353
|
-
// Document-level default
|
|
354
378
|
doc.setDateFormat("DD/MM/YYYY"); // "YYYY-MM-DD" | "DD/MM/YYYY" | "MM/DD/YYYY"
|
|
355
|
-
|
|
356
|
-
// Per-row or per-cell override
|
|
357
379
|
sheet.addRow([{ value: new Date("2026-12-25"), type: "date", dateFormat: "MM/DD/YYYY" }]);
|
|
358
380
|
```
|
|
359
381
|
|
|
360
|
-
The `office:date-value` attribute always stores the ISO date — display format is separate.
|
|
361
|
-
|
|
362
382
|
### Column widths and row heights
|
|
363
383
|
|
|
364
384
|
```typescript
|
|
365
385
|
sheet.setColumnWidth(0, "4cm");
|
|
366
386
|
sheet.setColumnWidth(1, "8cm");
|
|
367
|
-
|
|
368
|
-
sheet.addRow(["Header"]);
|
|
369
387
|
sheet.setRowHeight(0, "1.5cm");
|
|
370
388
|
```
|
|
371
389
|
|
|
@@ -373,51 +391,90 @@ sheet.setRowHeight(0, "1.5cm");
|
|
|
373
391
|
|
|
374
392
|
```typescript
|
|
375
393
|
const doc = new OdsDocument();
|
|
376
|
-
doc.
|
|
377
|
-
|
|
378
|
-
const q1 = doc.addSheet("Q1");
|
|
394
|
+
const q1 = doc.addSheet("Q1").setTabColor("#4CAF50");
|
|
395
|
+
const q2 = doc.addSheet("Q2").setTabColor("#2196F3");
|
|
379
396
|
q1.addRow(["Month", "Revenue"], { bold: true });
|
|
380
397
|
q1.addRow(["January", 12500]);
|
|
381
|
-
q1.addRow(["March", 14800]);
|
|
382
398
|
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
q2.addRow(["April", 15300]);
|
|
399
|
+
const q2sheet = doc.addSheet("Summary");
|
|
400
|
+
q2sheet.addRow(["Total", 27700]);
|
|
386
401
|
|
|
387
402
|
const bytes = await doc.save();
|
|
388
403
|
```
|
|
389
404
|
|
|
390
|
-
|
|
405
|
+
### Number formats
|
|
391
406
|
|
|
392
|
-
|
|
407
|
+
```typescript
|
|
408
|
+
sheet.addRow([{ value: 9999, type: "float", numberFormat: "integer" }]); // 9,999
|
|
409
|
+
sheet.addRow([{ value: 1234.567, type: "float", numberFormat: "decimal:2" }]); // 1,234.57
|
|
410
|
+
sheet.addRow([{ value: 0.1234, type: "percentage", numberFormat: "percentage" }]); // 12.34%
|
|
411
|
+
sheet.addRow([{ value: 0.075, type: "percentage", numberFormat: "percentage:1" }]);// 7.5%
|
|
412
|
+
sheet.addRow([{ value: 1234.56, type: "currency", numberFormat: "currency:EUR" }]);// €1,234.56
|
|
413
|
+
sheet.addRow([{ value: 99.99, type: "currency", numberFormat: "currency:USD:0" }]);// $100
|
|
414
|
+
|
|
415
|
+
// Row-level number format — applies to all cells in the row
|
|
416
|
+
sheet.addRow([1000, 2000, 3000], { numberFormat: "integer" });
|
|
417
|
+
```
|
|
393
418
|
|
|
394
|
-
|
|
419
|
+
### Merged cells
|
|
395
420
|
|
|
396
|
-
|
|
421
|
+
```typescript
|
|
422
|
+
// Span across 3 columns
|
|
423
|
+
sheet.addRow([{ value: "Q1 Sales Report", type: "string", colSpan: 3, bold: true }]);
|
|
424
|
+
sheet.addRow(["Region", "Units", "Revenue"]);
|
|
425
|
+
|
|
426
|
+
// Span across 2 rows
|
|
427
|
+
sheet.addRow([{ value: "North", type: "string", rowSpan: 2 }, "Jan", 12500]);
|
|
428
|
+
sheet.addRow(["Feb", 14200]); // "North" continues from above
|
|
429
|
+
|
|
430
|
+
// Combined colSpan + rowSpan
|
|
431
|
+
sheet.addRow([{ value: "Big Cell", type: "string", colSpan: 2, rowSpan: 2 }, "C"]);
|
|
432
|
+
```
|
|
397
433
|
|
|
398
|
-
###
|
|
434
|
+
### Freeze rows and columns
|
|
399
435
|
|
|
400
436
|
```typescript
|
|
401
|
-
|
|
437
|
+
// Freeze the header row
|
|
438
|
+
sheet.addRow(["Name", "Amount", "Date"], { bold: true });
|
|
439
|
+
sheet.freezeRows(1);
|
|
402
440
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
`;
|
|
441
|
+
// Freeze first column
|
|
442
|
+
sheet.freezeColumns(1);
|
|
443
|
+
|
|
444
|
+
// Both
|
|
445
|
+
sheet.freezeRows(1).freezeColumns(1);
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Hyperlinks in cells
|
|
412
449
|
|
|
413
|
-
|
|
414
|
-
|
|
450
|
+
```typescript
|
|
451
|
+
sheet.addRow([{
|
|
452
|
+
value: "odf-kit on GitHub",
|
|
453
|
+
type: "string",
|
|
454
|
+
href: "https://github.com/GitHubNewbie0/odf-kit",
|
|
455
|
+
}]);
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Sheet tab color
|
|
415
459
|
|
|
416
|
-
|
|
417
|
-
|
|
460
|
+
```typescript
|
|
461
|
+
doc.addSheet("Q1").setTabColor("#4CAF50"); // green
|
|
462
|
+
doc.addSheet("Q2").setTabColor("#2196F3"); // blue
|
|
463
|
+
doc.addSheet("Q3").setTabColor("#F44336"); // red
|
|
418
464
|
```
|
|
419
465
|
|
|
420
|
-
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Convert: HTML to ODT
|
|
469
|
+
|
|
470
|
+
`htmlToOdt()` converts an HTML string to a `.odt` file. The primary use case is Nextcloud Text ODT export and any web-based editor that stores content as HTML.
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
import { htmlToOdt } from "odf-kit";
|
|
474
|
+
|
|
475
|
+
const bytes = await htmlToOdt(html); // A4 default
|
|
476
|
+
const bytes = await htmlToOdt(html, { pageFormat: "letter" }); // US letter
|
|
477
|
+
```
|
|
421
478
|
|
|
422
479
|
### Page formats
|
|
423
480
|
|
|
@@ -429,31 +486,70 @@ Both fragment HTML (`<h1>...</h1><p>...</p>`) and full documents (`<html><body>.
|
|
|
429
486
|
| `"A3"` | 29.7 × 42 cm | 2.5 cm | Large format |
|
|
430
487
|
| `"A5"` | 14.8 × 21 cm | 2 cm | Small booklets |
|
|
431
488
|
|
|
489
|
+
### Supported HTML elements
|
|
490
|
+
|
|
491
|
+
**Block:** `<h1>`–`<h6>`, `<p>`, `<ul>`, `<ol>`, `<li>` (nested), `<table>` / `<tr>` / `<td>` / `<th>`, `<blockquote>`, `<pre>`, `<hr>`, `<figure>` / `<figcaption>`, `<div>` / `<section>` (transparent).
|
|
492
|
+
|
|
493
|
+
**Inline:** `<strong>`, `<em>`, `<u>`, `<s>`, `<sup>`, `<sub>`, `<a href>`, `<code>`, `<mark>`, `<span style="">`, `<br>`.
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## Convert: Markdown to ODT
|
|
498
|
+
|
|
499
|
+
`markdownToOdt()` converts any CommonMark Markdown string to ODT. Accepts the same options as `htmlToOdt()`.
|
|
500
|
+
|
|
432
501
|
```typescript
|
|
433
|
-
|
|
434
|
-
const bytes = await htmlToOdt(html, {
|
|
435
|
-
pageFormat: "A4",
|
|
436
|
-
orientation: "landscape",
|
|
437
|
-
marginTop: "1.5cm",
|
|
438
|
-
marginBottom: "1.5cm",
|
|
439
|
-
});
|
|
502
|
+
import { markdownToOdt } from "odf-kit";
|
|
440
503
|
|
|
441
|
-
|
|
442
|
-
const bytes = await
|
|
504
|
+
const bytes = await markdownToOdt(markdownString, { pageFormat: "A4" });
|
|
505
|
+
const bytes = await markdownToOdt(markdownString, {
|
|
443
506
|
pageFormat: "letter",
|
|
444
|
-
metadata: { title: "
|
|
507
|
+
metadata: { title: "My Document", creator: "Alice" },
|
|
445
508
|
});
|
|
446
509
|
```
|
|
447
510
|
|
|
448
|
-
|
|
511
|
+
Supports headings, paragraphs, bold, italic, lists (nested), tables, links, blockquotes, code blocks, and horizontal rules.
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## Convert: TipTap/ProseMirror JSON to ODT
|
|
449
516
|
|
|
450
|
-
|
|
517
|
+
`tiptapToOdt()` converts TipTap/ProseMirror `JSONContent` directly to ODT. No dependency on `@tiptap/core` — walks the JSON tree as a plain object. This is the most direct integration path for any TipTap-based editor (dDocs, Outline, Novel, BlockNote, etc.).
|
|
451
518
|
|
|
452
|
-
**
|
|
519
|
+
**Conversion happens entirely in your environment.** No document content is sent to external services — unlike cloud-based ODT conversion APIs. Suitable for sensitive documents, air-gapped environments, and applications with GDPR or data sovereignty requirements.
|
|
453
520
|
|
|
454
|
-
|
|
521
|
+
```typescript
|
|
522
|
+
import { tiptapToOdt } from "odf-kit";
|
|
455
523
|
|
|
456
|
-
|
|
524
|
+
// Basic usage
|
|
525
|
+
const bytes = await tiptapToOdt(editor.getJSON(), { pageFormat: "A4" });
|
|
526
|
+
|
|
527
|
+
// With pre-fetched images
|
|
528
|
+
const images = {
|
|
529
|
+
"https://example.com/photo.jpg": jpegBytes,
|
|
530
|
+
"ipfs://Qm...": ipfsImageBytes,
|
|
531
|
+
};
|
|
532
|
+
const bytes = await tiptapToOdt(editor.getJSON(), { images });
|
|
533
|
+
|
|
534
|
+
// With custom node handler for app-specific extensions
|
|
535
|
+
const bytes = await tiptapToOdt(editor.getJSON(), {
|
|
536
|
+
unknownNodeHandler: (node, doc) => {
|
|
537
|
+
if (node.type === "callout") {
|
|
538
|
+
doc.addParagraph(`⚠️ ${node.content?.[0]?.content?.[0]?.text ?? ""}`)
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Supported TipTap nodes
|
|
545
|
+
|
|
546
|
+
**Block:** `doc`, `paragraph`, `heading` (1–6), `bulletList`, `orderedList`, `listItem` (nested), `blockquote`, `codeBlock`, `horizontalRule`, `hardBreak`, `image`, `table`, `tableRow`, `tableCell`, `tableHeader`.
|
|
547
|
+
|
|
548
|
+
**Marks:** `bold`, `italic`, `underline`, `strike`, `code`, `link`, `textStyle` (color, fontSize, fontFamily), `highlight`, `superscript`, `subscript`.
|
|
549
|
+
|
|
550
|
+
**Images:** Data URIs are decoded and embedded directly. Other URLs are looked up in the `images` option. Unknown URLs emit a `[Image: alt]` placeholder paragraph.
|
|
551
|
+
|
|
552
|
+
**Unknown nodes:** Silently skipped by default. Provide `unknownNodeHandler` to handle custom extensions.
|
|
457
553
|
|
|
458
554
|
---
|
|
459
555
|
|
|
@@ -484,15 +580,6 @@ Product: {product} — Qty: {qty} — Price: {price}
|
|
|
484
580
|
{/items}
|
|
485
581
|
```
|
|
486
582
|
|
|
487
|
-
```typescript
|
|
488
|
-
fillTemplate(template, {
|
|
489
|
-
items: [
|
|
490
|
-
{ product: "Widget", qty: 5, price: "$125" },
|
|
491
|
-
{ product: "Gadget", qty: 3, price: "$120" },
|
|
492
|
-
],
|
|
493
|
-
});
|
|
494
|
-
```
|
|
495
|
-
|
|
496
583
|
### Conditionals
|
|
497
584
|
|
|
498
585
|
```
|
|
@@ -501,13 +588,7 @@ You qualify for a {percent}% discount!
|
|
|
501
588
|
{/showDiscount}
|
|
502
589
|
```
|
|
503
590
|
|
|
504
|
-
Falsy values (`false`, `null`, `undefined`, `0`, `""`, `[]`) remove the block. Truthy values include it.
|
|
505
|
-
|
|
506
|
-
### How it works
|
|
507
|
-
|
|
508
|
-
LibreOffice often fragments typed text like `{name}` across multiple XML elements due to editing history or spell check. odf-kit handles this automatically with a two-pass pipeline: first it reassembles fragmented placeholders, then replaces them with data. Headers and footers in `styles.xml` are processed alongside the document body.
|
|
509
|
-
|
|
510
|
-
Template syntax follows [Mustache](https://mustache.github.io/) conventions, established for document templating by [docxtemplater](https://docxtemplater.com/). odf-kit's engine is a clean-room implementation built for ODF — no code from either project was used.
|
|
591
|
+
Falsy values (`false`, `null`, `undefined`, `0`, `""`, `[]`) remove the block. Truthy values include it.
|
|
511
592
|
|
|
512
593
|
---
|
|
513
594
|
|
|
@@ -517,93 +598,73 @@ Template syntax follows [Mustache](https://mustache.github.io/) conventions, est
|
|
|
517
598
|
|
|
518
599
|
```typescript
|
|
519
600
|
import { readOdt, odtToHtml } from "odf-kit/reader";
|
|
520
|
-
import { readFileSync } from "fs";
|
|
521
601
|
|
|
522
602
|
const bytes = readFileSync("report.odt");
|
|
523
|
-
|
|
524
|
-
// Structured model
|
|
525
603
|
const model = readOdt(bytes);
|
|
526
|
-
|
|
527
|
-
console.log(model.pageLayout); // PageLayout
|
|
528
|
-
console.log(model.header); // HeaderFooterContent
|
|
604
|
+
const html = odtToHtml(bytes);
|
|
529
605
|
|
|
530
|
-
//
|
|
531
|
-
const html = odtToHtml(bytes);
|
|
532
|
-
|
|
533
|
-
// With tracked changes mode
|
|
606
|
+
// Tracked changes
|
|
534
607
|
const final = odtToHtml(bytes, {}, { trackedChanges: "final" });
|
|
535
608
|
const original = odtToHtml(bytes, {}, { trackedChanges: "original" });
|
|
536
609
|
const marked = odtToHtml(bytes, {}, { trackedChanges: "changes" });
|
|
537
610
|
```
|
|
538
611
|
|
|
539
|
-
### What the reader extracts
|
|
540
|
-
|
|
541
|
-
**Tier 1 — Structure:** paragraphs, headings, tables, lists, images, notes, bookmarks, fields, hyperlinks, tracked changes (all three ODF-defined modes: final/original/changes).
|
|
542
|
-
|
|
543
|
-
**Tier 2 — Styling:** span styles (bold, italic, font, color, highlight, underline, strikethrough, superscript, subscript), image float/wrap mode, footnotes/endnotes, cell and row background colors, style inheritance and resolution.
|
|
544
|
-
|
|
545
|
-
**Tier 3 — Layout:** paragraph styles (alignment, margins, padding, line height), table column widths, page geometry (size, margins, orientation), headers and footers (all four zones: default, first page, left/right), sections, tracked change metadata (author, date).
|
|
546
|
-
|
|
547
|
-
### Document model types
|
|
548
|
-
|
|
549
|
-
```typescript
|
|
550
|
-
import type {
|
|
551
|
-
OdtDocumentModel,
|
|
552
|
-
BodyNode, // ParagraphNode | HeadingNode | TableNode | ListNode |
|
|
553
|
-
// ImageNode | SectionNode | TrackedChangeNode
|
|
554
|
-
ParagraphNode,
|
|
555
|
-
HeadingNode,
|
|
556
|
-
TableNode,
|
|
557
|
-
ListNode,
|
|
558
|
-
ImageNode,
|
|
559
|
-
SectionNode,
|
|
560
|
-
TrackedChangeNode,
|
|
561
|
-
InlineNode, // TextNode | SpanNode | ImageNode | NoteNode |
|
|
562
|
-
// BookmarkNode | FieldNode | LinkNode
|
|
563
|
-
PageLayout,
|
|
564
|
-
ReadOdtOptions,
|
|
565
|
-
} from "odf-kit/reader";
|
|
566
|
-
```
|
|
567
|
-
|
|
568
612
|
---
|
|
569
613
|
|
|
570
614
|
## Typst: ODT to PDF
|
|
571
615
|
|
|
572
|
-
`odf-kit/typst` converts `.odt` files to [Typst](https://typst.app/) markup for PDF generation. No LibreOffice, no headless browser — just the Typst CLI.
|
|
573
|
-
|
|
574
616
|
```typescript
|
|
575
617
|
import { odtToTypst, modelToTypst } from "odf-kit/typst";
|
|
576
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
577
|
-
import { execSync } from "child_process";
|
|
578
618
|
|
|
579
|
-
// Convenience wrapper — ODT bytes → Typst string
|
|
580
619
|
const typst = odtToTypst(readFileSync("letter.odt"));
|
|
581
620
|
writeFileSync("letter.typ", typst);
|
|
582
621
|
execSync("typst compile letter.typ letter.pdf");
|
|
583
|
-
|
|
584
|
-
// From a model (if you already have one from readOdt)
|
|
585
|
-
import { readOdt } from "odf-kit/reader";
|
|
586
|
-
const model = readOdt(readFileSync("letter.odt"));
|
|
587
|
-
const typst2 = modelToTypst(model);
|
|
588
622
|
```
|
|
589
623
|
|
|
590
|
-
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
## API Reference
|
|
591
627
|
|
|
592
|
-
###
|
|
628
|
+
### htmlToOdt / markdownToOdt
|
|
593
629
|
|
|
594
630
|
```typescript
|
|
595
|
-
|
|
631
|
+
function htmlToOdt(html: string, options?: HtmlToOdtOptions): Promise<Uint8Array>
|
|
632
|
+
function markdownToOdt(markdown: string, options?: HtmlToOdtOptions): Promise<Uint8Array>
|
|
596
633
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
634
|
+
interface HtmlToOdtOptions {
|
|
635
|
+
pageFormat?: "A4" | "letter" | "legal" | "A3" | "A5"; // default: "A4"
|
|
636
|
+
orientation?: "portrait" | "landscape";
|
|
637
|
+
marginTop?: string;
|
|
638
|
+
marginBottom?: string;
|
|
639
|
+
marginLeft?: string;
|
|
640
|
+
marginRight?: string;
|
|
641
|
+
metadata?: { title?: string; creator?: string; description?: string };
|
|
642
|
+
}
|
|
600
643
|
```
|
|
601
644
|
|
|
602
|
-
|
|
645
|
+
### tiptapToOdt
|
|
603
646
|
|
|
604
|
-
|
|
647
|
+
```typescript
|
|
648
|
+
function tiptapToOdt(json: TiptapNode, options?: TiptapToOdtOptions): Promise<Uint8Array>
|
|
605
649
|
|
|
606
|
-
|
|
650
|
+
interface TiptapNode {
|
|
651
|
+
type: string;
|
|
652
|
+
text?: string;
|
|
653
|
+
attrs?: Record<string, unknown>;
|
|
654
|
+
content?: TiptapNode[];
|
|
655
|
+
marks?: TiptapMark[];
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
interface TiptapMark {
|
|
659
|
+
type: string;
|
|
660
|
+
attrs?: Record<string, unknown>;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
interface TiptapToOdtOptions extends HtmlToOdtOptions {
|
|
664
|
+
images?: Record<string, Uint8Array>;
|
|
665
|
+
unknownNodeHandler?: (node: TiptapNode, doc: OdtDocument) => void;
|
|
666
|
+
}
|
|
667
|
+
```
|
|
607
668
|
|
|
608
669
|
### OdtDocument
|
|
609
670
|
|
|
@@ -621,56 +682,20 @@ See the [complete ODT to PDF with Typst guide](https://githubnewbie0.github.io/o
|
|
|
621
682
|
| `addPageBreak()` | Insert page break |
|
|
622
683
|
| `save()` | Generate `.odt` as `Promise<Uint8Array>` |
|
|
623
684
|
|
|
624
|
-
### OdsDocument
|
|
625
|
-
|
|
626
|
-
| Method | Description |
|
|
627
|
-
|--------|-------------|
|
|
628
|
-
| `setMetadata(options)` | Set title, creator, description |
|
|
629
|
-
| `setDateFormat(format)` | Set default date display format (`"YYYY-MM-DD"` \| `"DD/MM/YYYY"` \| `"MM/DD/YYYY"`) |
|
|
630
|
-
| `addSheet(name)` | Add a sheet tab — returns `OdsSheet` |
|
|
631
|
-
| `save()` | Generate `.ods` as `Promise<Uint8Array>` |
|
|
632
|
-
|
|
633
|
-
### OdsSheet
|
|
685
|
+
### OdsDocument / OdsSheet
|
|
634
686
|
|
|
635
687
|
| Method | Description |
|
|
636
688
|
|--------|-------------|
|
|
637
|
-
| `
|
|
638
|
-
| `
|
|
639
|
-
| `
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
| boolean // → boolean cell
|
|
648
|
-
| Date // → date cell
|
|
649
|
-
| null // → empty cell
|
|
650
|
-
| undefined // → empty cell
|
|
651
|
-
| OdsCellObject; // → explicit type (required for formulas)
|
|
652
|
-
|
|
653
|
-
interface OdsCellObject extends OdsCellOptions {
|
|
654
|
-
value: string | number | boolean | Date | null;
|
|
655
|
-
type: "string" | "float" | "date" | "boolean" | "formula";
|
|
656
|
-
}
|
|
657
|
-
```
|
|
658
|
-
|
|
659
|
-
### htmlToOdt
|
|
660
|
-
|
|
661
|
-
```typescript
|
|
662
|
-
function htmlToOdt(html: string, options?: HtmlToOdtOptions): Promise<Uint8Array>
|
|
663
|
-
|
|
664
|
-
interface HtmlToOdtOptions {
|
|
665
|
-
pageFormat?: "A4" | "letter" | "legal" | "A3" | "A5"; // default: "A4"
|
|
666
|
-
orientation?: "portrait" | "landscape";
|
|
667
|
-
marginTop?: string;
|
|
668
|
-
marginBottom?: string;
|
|
669
|
-
marginLeft?: string;
|
|
670
|
-
marginRight?: string;
|
|
671
|
-
metadata?: { title?: string; creator?: string; description?: string };
|
|
672
|
-
}
|
|
673
|
-
```
|
|
689
|
+
| `doc.setMetadata(options)` | Set title, creator, description |
|
|
690
|
+
| `doc.setDateFormat(format)` | Set default date display format |
|
|
691
|
+
| `doc.addSheet(name)` | Add a sheet tab — returns `OdsSheet` |
|
|
692
|
+
| `doc.save()` | Generate `.ods` as `Promise<Uint8Array>` |
|
|
693
|
+
| `sheet.addRow(values, options?)` | Add a row of cells |
|
|
694
|
+
| `sheet.setColumnWidth(index, width)` | Set column width |
|
|
695
|
+
| `sheet.setRowHeight(index, height)` | Set row height |
|
|
696
|
+
| `sheet.freezeRows(N?)` | Freeze top N rows (default 1) |
|
|
697
|
+
| `sheet.freezeColumns(N?)` | Freeze left N columns (default 1) |
|
|
698
|
+
| `sheet.setTabColor(color)` | Set sheet tab color |
|
|
674
699
|
|
|
675
700
|
### fillTemplate
|
|
676
701
|
|
|
@@ -678,31 +703,11 @@ interface HtmlToOdtOptions {
|
|
|
678
703
|
function fillTemplate(templateBytes: Uint8Array, data: TemplateData): Uint8Array
|
|
679
704
|
```
|
|
680
705
|
|
|
681
|
-
`TemplateData` is `Record<string, unknown>` — any JSON-serializable value.
|
|
682
|
-
|
|
683
706
|
| Syntax | Description |
|
|
684
707
|
|--------|-------------|
|
|
685
708
|
| `{tag}` | Replace with value |
|
|
686
|
-
| `{object.property}` | Dot notation
|
|
687
|
-
| `{#tag}...{/tag}` | Loop
|
|
688
|
-
|
|
689
|
-
### readOdt / odtToHtml
|
|
690
|
-
|
|
691
|
-
```typescript
|
|
692
|
-
function readOdt(bytes: Uint8Array, options?: ReadOdtOptions): OdtDocumentModel
|
|
693
|
-
function odtToHtml(
|
|
694
|
-
bytes: Uint8Array,
|
|
695
|
-
htmlOptions?: HtmlOptions,
|
|
696
|
-
readOptions?: ReadOdtOptions
|
|
697
|
-
): string
|
|
698
|
-
```
|
|
699
|
-
|
|
700
|
-
### odtToTypst / modelToTypst
|
|
701
|
-
|
|
702
|
-
```typescript
|
|
703
|
-
function odtToTypst(bytes: Uint8Array, options?: TypstEmitOptions): string
|
|
704
|
-
function modelToTypst(model: OdtDocumentModel, options?: TypstEmitOptions): string
|
|
705
|
-
```
|
|
709
|
+
| `{object.property}` | Dot notation |
|
|
710
|
+
| `{#tag}...{/tag}` | Loop or conditional |
|
|
706
711
|
|
|
707
712
|
### TextFormatting
|
|
708
713
|
|
|
@@ -710,9 +715,9 @@ function modelToTypst(model: OdtDocumentModel, options?: TypstEmitOptions): stri
|
|
|
710
715
|
{
|
|
711
716
|
bold?: boolean,
|
|
712
717
|
italic?: boolean,
|
|
713
|
-
fontSize?: number | string,
|
|
718
|
+
fontSize?: number | string,
|
|
714
719
|
fontFamily?: string,
|
|
715
|
-
color?: string,
|
|
720
|
+
color?: string,
|
|
716
721
|
underline?: boolean,
|
|
717
722
|
strikethrough?: boolean,
|
|
718
723
|
superscript?: boolean,
|
|
@@ -721,37 +726,6 @@ function modelToTypst(model: OdtDocumentModel, options?: TypstEmitOptions): stri
|
|
|
721
726
|
}
|
|
722
727
|
```
|
|
723
728
|
|
|
724
|
-
### TableOptions / CellOptions
|
|
725
|
-
|
|
726
|
-
```typescript
|
|
727
|
-
// TableOptions
|
|
728
|
-
{ columnWidths?: string[], border?: string }
|
|
729
|
-
|
|
730
|
-
// CellOptions (extends TextFormatting)
|
|
731
|
-
{
|
|
732
|
-
backgroundColor?: string,
|
|
733
|
-
border?: string,
|
|
734
|
-
borderTop?: string, borderBottom?: string,
|
|
735
|
-
borderLeft?: string, borderRight?: string,
|
|
736
|
-
colSpan?: number,
|
|
737
|
-
rowSpan?: number,
|
|
738
|
-
}
|
|
739
|
-
```
|
|
740
|
-
|
|
741
|
-
### PageLayout
|
|
742
|
-
|
|
743
|
-
```typescript
|
|
744
|
-
{
|
|
745
|
-
width?: string, // "21cm" (A4 default)
|
|
746
|
-
height?: string, // "29.7cm"
|
|
747
|
-
orientation?: "portrait" | "landscape",
|
|
748
|
-
marginTop?: string, // "2cm" default
|
|
749
|
-
marginBottom?: string,
|
|
750
|
-
marginLeft?: string,
|
|
751
|
-
marginRight?: string,
|
|
752
|
-
}
|
|
753
|
-
```
|
|
754
|
-
|
|
755
729
|
---
|
|
756
730
|
|
|
757
731
|
## Platform support
|
|
@@ -763,7 +737,7 @@ function modelToTypst(model: OdtDocumentModel, options?: TypstEmitOptions): stri
|
|
|
763
737
|
| Deno, Bun | ✅ Full |
|
|
764
738
|
| Cloudflare Workers | ✅ Full |
|
|
765
739
|
|
|
766
|
-
ESM only. Zero Node-specific APIs in the library source — enforced at the TypeScript level
|
|
740
|
+
ESM only. Zero Node-specific APIs in the library source — enforced at the TypeScript level.
|
|
767
741
|
|
|
768
742
|
---
|
|
769
743
|
|
|
@@ -771,11 +745,11 @@ ESM only. Zero Node-specific APIs in the library source — enforced at the Type
|
|
|
771
745
|
|
|
772
746
|
**ODF is the ISO standard (ISO/IEC 26300) for documents.** It's the default format for LibreOffice, mandatory for many governments and public sector organisations, and the best choice for long-term document preservation.
|
|
773
747
|
|
|
774
|
-
- **
|
|
748
|
+
- **Two runtime dependencies** — fflate (ZIP) and marked (Markdown parsing). No transitive dependencies.
|
|
775
749
|
- **Spec-compliant output** — every generated file passes the OASIS ODF validator. Enforced on every commit by CI.
|
|
776
750
|
- **Multiple ODF formats** — ODT documents and ODS spreadsheets from the same library.
|
|
777
|
-
- **
|
|
778
|
-
- **
|
|
751
|
+
- **Eight complete capability modes** — build ODT, build ODS, convert HTML→ODT, convert Markdown→ODT, convert TipTap JSON→ODT, fill templates, read, convert to Typst/PDF.
|
|
752
|
+
- **TipTap/ProseMirror integration** — direct JSON→ODT conversion for any TipTap-based editor, no intermediate HTML step.
|
|
779
753
|
- **Zero-dependency Typst emitter** — the only JavaScript library with built-in ODT→Typst conversion for PDF generation.
|
|
780
754
|
- **TypeScript-first** — full types across all sub-exports.
|
|
781
755
|
- **Apache 2.0** — use freely in commercial and open source projects.
|
|
@@ -787,8 +761,10 @@ ESM only. Zero Node-specific APIs in the library source — enforced at the Type
|
|
|
787
761
|
| Feature | odf-kit | simple-odf | docxtemplater |
|
|
788
762
|
|---------|---------|------------|---------------|
|
|
789
763
|
| Generate .odt from scratch | ✅ | ⚠️ flat XML only | ❌ |
|
|
790
|
-
| Generate .ods from scratch | ✅ | ❌ | ❌ |
|
|
764
|
+
| Generate .ods from scratch | ✅ merged cells, freeze, number formats, hyperlinks | ❌ | ❌ |
|
|
791
765
|
| Convert HTML → ODT | ✅ | ❌ | ❌ |
|
|
766
|
+
| Convert Markdown → ODT | ✅ | ❌ | ❌ |
|
|
767
|
+
| Convert TipTap JSON → ODT | ✅ | ❌ | ❌ |
|
|
792
768
|
| Fill .odt templates | ✅ | ❌ | ✅ .docx only |
|
|
793
769
|
| Read .odt files | ✅ | ❌ | ❌ |
|
|
794
770
|
| Convert to HTML | ✅ | ❌ | ❌ |
|
|
@@ -801,29 +777,33 @@ ESM only. Zero Node-specific APIs in the library source — enforced at the Type
|
|
|
801
777
|
|
|
802
778
|
## Specification compliance
|
|
803
779
|
|
|
804
|
-
odf-kit targets ODF 1.2 (ISO/IEC 26300). Generated files include proper ZIP packaging
|
|
780
|
+
odf-kit targets ODF 1.2 (ISO/IEC 26300). Generated files include proper ZIP packaging, manifest, metadata, and all required namespace declarations. The OASIS ODF validator runs on every push via GitHub Actions.
|
|
805
781
|
|
|
806
782
|
---
|
|
807
783
|
|
|
808
784
|
## Version history
|
|
809
785
|
|
|
810
|
-
**v0.9.
|
|
786
|
+
**v0.9.7** — ODS enhancements: number formats (integer, decimal:N, percentage, currency), merged cells (colSpan/rowSpan), freeze rows/columns, hyperlinks in cells, sheet tab color. 849 tests passing.
|
|
811
787
|
|
|
812
|
-
**v0.9.
|
|
788
|
+
**v0.9.6** — `tiptapToOdt()`: TipTap/ProseMirror JSON→ODT conversion. `TiptapNode`, `TiptapMark`, `TiptapToOdtOptions` types. `unknownNodeHandler` for custom extensions. Image support via pre-fetched bytes map. 817 tests passing.
|
|
813
789
|
|
|
814
|
-
**v0.
|
|
790
|
+
**v0.9.5** — `markdownToOdt()`: Markdown→ODT via marked + htmlToOdt. 786 tests passing.
|
|
815
791
|
|
|
816
|
-
**v0.
|
|
792
|
+
**v0.9.4** — ODS datetime auto-detection (nonzero UTC time → datetime format). ODS formula `xmlns:of` namespace fix (Err:510 resolved).
|
|
817
793
|
|
|
818
|
-
**v0.
|
|
794
|
+
**v0.9.2** — `htmlToOdt()`: HTML→ODT conversion with page format presets, full inline formatting, lists, tables, blockquote, pre, hr, and inline CSS. 769 tests passing.
|
|
819
795
|
|
|
820
|
-
**v0.
|
|
796
|
+
**v0.9.0** — ODS spreadsheet generation: `OdsDocument`, multiple sheets, auto-typed cells, formulas, date formatting, row and cell formatting, column widths, row heights. 707 tests passing.
|
|
821
797
|
|
|
822
|
-
**v0.
|
|
798
|
+
**v0.8.0** — `odf-kit/typst`: `odtToTypst()` and `modelToTypst()`. Zero-dependency ODT→Typst emitter for PDF generation.
|
|
823
799
|
|
|
824
|
-
**v0.
|
|
800
|
+
**v0.7.0** — Tier 3 reader: paragraph styles, page geometry, headers/footers, sections, tracked changes (all three ODF modes).
|
|
825
801
|
|
|
826
|
-
**v0.
|
|
802
|
+
**v0.6.0** — Tier 2 reader: span styles, image float/wrap, footnotes/endnotes, bookmarks, fields, cell/row styles.
|
|
803
|
+
|
|
804
|
+
**v0.5.0** — `odf-kit/reader`: `readOdt()`, `odtToHtml()`. Tier 1 parsing.
|
|
805
|
+
|
|
806
|
+
**v0.3.0** — Template engine: loops, conditionals, dot notation, automatic XML fragment healing.
|
|
827
807
|
|
|
828
808
|
**v0.1.0** — Programmatic ODT creation: text, tables, page layout, lists, images, links, bookmarks.
|
|
829
809
|
|
|
@@ -831,8 +811,6 @@ odf-kit targets ODF 1.2 (ISO/IEC 26300). Generated files include proper ZIP pack
|
|
|
831
811
|
|
|
832
812
|
## Guides
|
|
833
813
|
|
|
834
|
-
Full walkthroughs and real-world examples on the documentation site:
|
|
835
|
-
|
|
836
814
|
- [Generate ODT files in Node.js](https://githubnewbie0.github.io/odf-kit/guides/generate-odt-nodejs.html)
|
|
837
815
|
- [Generate ODT files in the browser](https://githubnewbie0.github.io/odf-kit/guides/generate-odt-browser.html)
|
|
838
816
|
- [Fill ODT templates in JavaScript](https://githubnewbie0.github.io/odf-kit/guides/fill-odt-template-javascript.html)
|
|
@@ -860,21 +838,6 @@ npm run build
|
|
|
860
838
|
npm test
|
|
861
839
|
```
|
|
862
840
|
|
|
863
|
-
Full pipeline before submitting a PR:
|
|
864
|
-
|
|
865
|
-
```bash
|
|
866
|
-
npm run format:check
|
|
867
|
-
npm run lint
|
|
868
|
-
npm run build
|
|
869
|
-
npm test
|
|
870
|
-
```
|
|
871
|
-
|
|
872
|
-
---
|
|
873
|
-
|
|
874
|
-
## Acknowledgments
|
|
875
|
-
|
|
876
|
-
Template syntax follows [Mustache](https://mustache.github.io/) conventions, established for document templating by [docxtemplater](https://docxtemplater.com/). odf-kit's engine is a clean-room implementation purpose-built for ODF — no code from either project was used.
|
|
877
|
-
|
|
878
841
|
---
|
|
879
842
|
|
|
880
843
|
## License
|