odf-kit 0.9.6 → 0.9.8
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 +47 -0
- package/README.md +306 -272
- 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/dist/ods-reader/html-renderer.d.ts +19 -0
- package/dist/ods-reader/html-renderer.d.ts.map +1 -0
- package/dist/ods-reader/html-renderer.js +123 -0
- package/dist/ods-reader/html-renderer.js.map +1 -0
- package/dist/ods-reader/index.d.ts +19 -0
- package/dist/ods-reader/index.d.ts.map +1 -0
- package/dist/ods-reader/index.js +22 -0
- package/dist/ods-reader/index.js.map +1 -0
- package/dist/ods-reader/parser.d.ts +24 -0
- package/dist/ods-reader/parser.d.ts.map +1 -0
- package/dist/ods-reader/parser.js +544 -0
- package/dist/ods-reader/parser.js.map +1 -0
- package/dist/ods-reader/types.d.ts +139 -0
- package/dist/ods-reader/types.d.ts.map +1 -0
- package/dist/ods-reader/types.js +7 -0
- package/dist/ods-reader/types.js.map +1 -0
- package/package.json +22 -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
|
+
## Nine 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,16 @@ const html = odtToHtml(bytes); // styled HTML string
|
|
|
83
119
|
```
|
|
84
120
|
|
|
85
121
|
```typescript
|
|
86
|
-
//
|
|
122
|
+
// 8. Read an existing .ods spreadsheet
|
|
123
|
+
import { readOds, odsToHtml } from "odf-kit/ods-reader";
|
|
124
|
+
|
|
125
|
+
const bytes = readFileSync("data.ods");
|
|
126
|
+
const model = readOds(bytes); // structured model — typed values
|
|
127
|
+
const html = odsToHtml(bytes); // HTML table string
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// 9. Convert .odt to Typst for PDF generation
|
|
87
132
|
import { odtToTypst } from "odf-kit/typst";
|
|
88
133
|
import { execSync } from "child_process";
|
|
89
134
|
|
|
@@ -103,12 +148,13 @@ npm install odf-kit
|
|
|
103
148
|
Node.js 22+ required. ESM only. Sub-exports:
|
|
104
149
|
|
|
105
150
|
```typescript
|
|
106
|
-
import { OdtDocument, OdsDocument, htmlToOdt, fillTemplate } from "odf-kit";
|
|
107
|
-
import { readOdt, odtToHtml }
|
|
108
|
-
import {
|
|
151
|
+
import { OdtDocument, OdsDocument, htmlToOdt, markdownToOdt, tiptapToOdt, fillTemplate } from "odf-kit";
|
|
152
|
+
import { readOdt, odtToHtml } from "odf-kit/odt-reader";
|
|
153
|
+
import { readOds, odsToHtml } from "odf-kit/ods-reader";
|
|
154
|
+
import { odtToTypst, modelToTypst } from "odf-kit/typst";
|
|
109
155
|
```
|
|
110
156
|
|
|
111
|
-
Works in Node.js, browsers, Deno, Bun, and Cloudflare Workers.
|
|
157
|
+
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
158
|
|
|
113
159
|
---
|
|
114
160
|
|
|
@@ -248,13 +294,6 @@ doc.addParagraph((p) => {
|
|
|
248
294
|
});
|
|
249
295
|
```
|
|
250
296
|
|
|
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
297
|
### Links and bookmarks
|
|
259
298
|
|
|
260
299
|
```typescript
|
|
@@ -327,8 +366,6 @@ sheet.addRow([
|
|
|
327
366
|
|
|
328
367
|
### Row and cell formatting
|
|
329
368
|
|
|
330
|
-
Options on `addRow()` apply to all cells in the row. Per-cell options inside an `OdsCellObject` override the row defaults.
|
|
331
|
-
|
|
332
369
|
```typescript
|
|
333
370
|
// Bold header row with background
|
|
334
371
|
sheet.addRow(["Month", "Revenue", "Notes"], {
|
|
@@ -340,32 +377,23 @@ sheet.addRow(["Month", "Revenue", "Notes"], {
|
|
|
340
377
|
// Mixed: row default + per-cell override
|
|
341
378
|
sheet.addRow([
|
|
342
379
|
"January",
|
|
343
|
-
{ value: 12500, type: "float", color: "#006600" },
|
|
380
|
+
{ value: 12500, type: "float", color: "#006600" },
|
|
344
381
|
"On track",
|
|
345
382
|
], { italic: true });
|
|
346
383
|
```
|
|
347
384
|
|
|
348
|
-
Available formatting options: `bold`, `italic`, `fontSize`, `fontFamily`, `color`, `underline`, `backgroundColor`, `border`, `borderTop/Bottom/Left/Right`, `align`, `verticalAlign`, `padding`, `wrap`.
|
|
349
|
-
|
|
350
385
|
### Date formatting
|
|
351
386
|
|
|
352
387
|
```typescript
|
|
353
|
-
// Document-level default
|
|
354
388
|
doc.setDateFormat("DD/MM/YYYY"); // "YYYY-MM-DD" | "DD/MM/YYYY" | "MM/DD/YYYY"
|
|
355
|
-
|
|
356
|
-
// Per-row or per-cell override
|
|
357
389
|
sheet.addRow([{ value: new Date("2026-12-25"), type: "date", dateFormat: "MM/DD/YYYY" }]);
|
|
358
390
|
```
|
|
359
391
|
|
|
360
|
-
The `office:date-value` attribute always stores the ISO date — display format is separate.
|
|
361
|
-
|
|
362
392
|
### Column widths and row heights
|
|
363
393
|
|
|
364
394
|
```typescript
|
|
365
395
|
sheet.setColumnWidth(0, "4cm");
|
|
366
396
|
sheet.setColumnWidth(1, "8cm");
|
|
367
|
-
|
|
368
|
-
sheet.addRow(["Header"]);
|
|
369
397
|
sheet.setRowHeight(0, "1.5cm");
|
|
370
398
|
```
|
|
371
399
|
|
|
@@ -373,51 +401,90 @@ sheet.setRowHeight(0, "1.5cm");
|
|
|
373
401
|
|
|
374
402
|
```typescript
|
|
375
403
|
const doc = new OdsDocument();
|
|
376
|
-
doc.
|
|
377
|
-
|
|
378
|
-
const q1 = doc.addSheet("Q1");
|
|
404
|
+
const q1 = doc.addSheet("Q1").setTabColor("#4CAF50");
|
|
405
|
+
const q2 = doc.addSheet("Q2").setTabColor("#2196F3");
|
|
379
406
|
q1.addRow(["Month", "Revenue"], { bold: true });
|
|
380
407
|
q1.addRow(["January", 12500]);
|
|
381
|
-
q1.addRow(["March", 14800]);
|
|
382
408
|
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
q2.addRow(["April", 15300]);
|
|
409
|
+
const q2sheet = doc.addSheet("Summary");
|
|
410
|
+
q2sheet.addRow(["Total", 27700]);
|
|
386
411
|
|
|
387
412
|
const bytes = await doc.save();
|
|
388
413
|
```
|
|
389
414
|
|
|
390
|
-
|
|
415
|
+
### Number formats
|
|
391
416
|
|
|
392
|
-
|
|
417
|
+
```typescript
|
|
418
|
+
sheet.addRow([{ value: 9999, type: "float", numberFormat: "integer" }]); // 9,999
|
|
419
|
+
sheet.addRow([{ value: 1234.567, type: "float", numberFormat: "decimal:2" }]); // 1,234.57
|
|
420
|
+
sheet.addRow([{ value: 0.1234, type: "percentage", numberFormat: "percentage" }]); // 12.34%
|
|
421
|
+
sheet.addRow([{ value: 0.075, type: "percentage", numberFormat: "percentage:1" }]);// 7.5%
|
|
422
|
+
sheet.addRow([{ value: 1234.56, type: "currency", numberFormat: "currency:EUR" }]);// €1,234.56
|
|
423
|
+
sheet.addRow([{ value: 99.99, type: "currency", numberFormat: "currency:USD:0" }]);// $100
|
|
393
424
|
|
|
394
|
-
|
|
425
|
+
// Row-level number format — applies to all cells in the row
|
|
426
|
+
sheet.addRow([1000, 2000, 3000], { numberFormat: "integer" });
|
|
427
|
+
```
|
|
395
428
|
|
|
396
|
-
|
|
429
|
+
### Merged cells
|
|
397
430
|
|
|
398
|
-
|
|
431
|
+
```typescript
|
|
432
|
+
// Span across 3 columns
|
|
433
|
+
sheet.addRow([{ value: "Q1 Sales Report", type: "string", colSpan: 3, bold: true }]);
|
|
434
|
+
sheet.addRow(["Region", "Units", "Revenue"]);
|
|
435
|
+
|
|
436
|
+
// Span across 2 rows
|
|
437
|
+
sheet.addRow([{ value: "North", type: "string", rowSpan: 2 }, "Jan", 12500]);
|
|
438
|
+
sheet.addRow(["Feb", 14200]); // "North" continues from above
|
|
439
|
+
|
|
440
|
+
// Combined colSpan + rowSpan
|
|
441
|
+
sheet.addRow([{ value: "Big Cell", type: "string", colSpan: 2, rowSpan: 2 }, "C"]);
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Freeze rows and columns
|
|
399
445
|
|
|
400
446
|
```typescript
|
|
401
|
-
|
|
447
|
+
// Freeze the header row
|
|
448
|
+
sheet.addRow(["Name", "Amount", "Date"], { bold: true });
|
|
449
|
+
sheet.freezeRows(1);
|
|
402
450
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
<tr><td>Alice</td><td>Send report</td><td>Friday</td></tr>
|
|
410
|
-
</table>
|
|
411
|
-
`;
|
|
451
|
+
// Freeze first column
|
|
452
|
+
sheet.freezeColumns(1);
|
|
453
|
+
|
|
454
|
+
// Both
|
|
455
|
+
sheet.freezeRows(1).freezeColumns(1);
|
|
456
|
+
```
|
|
412
457
|
|
|
413
|
-
|
|
414
|
-
const bytes = await htmlToOdt(html);
|
|
458
|
+
### Hyperlinks in cells
|
|
415
459
|
|
|
416
|
-
|
|
417
|
-
|
|
460
|
+
```typescript
|
|
461
|
+
sheet.addRow([{
|
|
462
|
+
value: "odf-kit on GitHub",
|
|
463
|
+
type: "string",
|
|
464
|
+
href: "https://github.com/GitHubNewbie0/odf-kit",
|
|
465
|
+
}]);
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Sheet tab color
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
doc.addSheet("Q1").setTabColor("#4CAF50"); // green
|
|
472
|
+
doc.addSheet("Q2").setTabColor("#2196F3"); // blue
|
|
473
|
+
doc.addSheet("Q3").setTabColor("#F44336"); // red
|
|
418
474
|
```
|
|
419
475
|
|
|
420
|
-
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Convert: HTML to ODT
|
|
479
|
+
|
|
480
|
+
`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.
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
import { htmlToOdt } from "odf-kit";
|
|
484
|
+
|
|
485
|
+
const bytes = await htmlToOdt(html); // A4 default
|
|
486
|
+
const bytes = await htmlToOdt(html, { pageFormat: "letter" }); // US letter
|
|
487
|
+
```
|
|
421
488
|
|
|
422
489
|
### Page formats
|
|
423
490
|
|
|
@@ -429,31 +496,70 @@ Both fragment HTML (`<h1>...</h1><p>...</p>`) and full documents (`<html><body>.
|
|
|
429
496
|
| `"A3"` | 29.7 × 42 cm | 2.5 cm | Large format |
|
|
430
497
|
| `"A5"` | 14.8 × 21 cm | 2 cm | Small booklets |
|
|
431
498
|
|
|
499
|
+
### Supported HTML elements
|
|
500
|
+
|
|
501
|
+
**Block:** `<h1>`–`<h6>`, `<p>`, `<ul>`, `<ol>`, `<li>` (nested), `<table>` / `<tr>` / `<td>` / `<th>`, `<blockquote>`, `<pre>`, `<hr>`, `<figure>` / `<figcaption>`, `<div>` / `<section>` (transparent).
|
|
502
|
+
|
|
503
|
+
**Inline:** `<strong>`, `<em>`, `<u>`, `<s>`, `<sup>`, `<sub>`, `<a href>`, `<code>`, `<mark>`, `<span style="">`, `<br>`.
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## Convert: Markdown to ODT
|
|
508
|
+
|
|
509
|
+
`markdownToOdt()` converts any CommonMark Markdown string to ODT. Accepts the same options as `htmlToOdt()`.
|
|
510
|
+
|
|
432
511
|
```typescript
|
|
433
|
-
|
|
434
|
-
const bytes = await htmlToOdt(html, {
|
|
435
|
-
pageFormat: "A4",
|
|
436
|
-
orientation: "landscape",
|
|
437
|
-
marginTop: "1.5cm",
|
|
438
|
-
marginBottom: "1.5cm",
|
|
439
|
-
});
|
|
512
|
+
import { markdownToOdt } from "odf-kit";
|
|
440
513
|
|
|
441
|
-
|
|
442
|
-
const bytes = await
|
|
514
|
+
const bytes = await markdownToOdt(markdownString, { pageFormat: "A4" });
|
|
515
|
+
const bytes = await markdownToOdt(markdownString, {
|
|
443
516
|
pageFormat: "letter",
|
|
444
|
-
metadata: { title: "
|
|
517
|
+
metadata: { title: "My Document", creator: "Alice" },
|
|
445
518
|
});
|
|
446
519
|
```
|
|
447
520
|
|
|
448
|
-
|
|
521
|
+
Supports headings, paragraphs, bold, italic, lists (nested), tables, links, blockquotes, code blocks, and horizontal rules.
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## Convert: TipTap/ProseMirror JSON to ODT
|
|
526
|
+
|
|
527
|
+
`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.).
|
|
449
528
|
|
|
450
|
-
**
|
|
529
|
+
**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.
|
|
451
530
|
|
|
452
|
-
|
|
531
|
+
```typescript
|
|
532
|
+
import { tiptapToOdt } from "odf-kit";
|
|
533
|
+
|
|
534
|
+
// Basic usage
|
|
535
|
+
const bytes = await tiptapToOdt(editor.getJSON(), { pageFormat: "A4" });
|
|
536
|
+
|
|
537
|
+
// With pre-fetched images
|
|
538
|
+
const images = {
|
|
539
|
+
"https://example.com/photo.jpg": jpegBytes,
|
|
540
|
+
"ipfs://Qm...": ipfsImageBytes,
|
|
541
|
+
};
|
|
542
|
+
const bytes = await tiptapToOdt(editor.getJSON(), { images });
|
|
543
|
+
|
|
544
|
+
// With custom node handler for app-specific extensions
|
|
545
|
+
const bytes = await tiptapToOdt(editor.getJSON(), {
|
|
546
|
+
unknownNodeHandler: (node, doc) => {
|
|
547
|
+
if (node.type === "callout") {
|
|
548
|
+
doc.addParagraph(`⚠️ ${node.content?.[0]?.content?.[0]?.text ?? ""}`)
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
});
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Supported TipTap nodes
|
|
453
555
|
|
|
454
|
-
**
|
|
556
|
+
**Block:** `doc`, `paragraph`, `heading` (1–6), `bulletList`, `orderedList`, `listItem` (nested), `blockquote`, `codeBlock`, `horizontalRule`, `hardBreak`, `image`, `table`, `tableRow`, `tableCell`, `tableHeader`.
|
|
455
557
|
|
|
456
|
-
**
|
|
558
|
+
**Marks:** `bold`, `italic`, `underline`, `strike`, `code`, `link`, `textStyle` (color, fontSize, fontFamily), `highlight`, `superscript`, `subscript`.
|
|
559
|
+
|
|
560
|
+
**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.
|
|
561
|
+
|
|
562
|
+
**Unknown nodes:** Silently skipped by default. Provide `unknownNodeHandler` to handle custom extensions.
|
|
457
563
|
|
|
458
564
|
---
|
|
459
565
|
|
|
@@ -484,15 +590,6 @@ Product: {product} — Qty: {qty} — Price: {price}
|
|
|
484
590
|
{/items}
|
|
485
591
|
```
|
|
486
592
|
|
|
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
593
|
### Conditionals
|
|
497
594
|
|
|
498
595
|
```
|
|
@@ -501,13 +598,7 @@ You qualify for a {percent}% discount!
|
|
|
501
598
|
{/showDiscount}
|
|
502
599
|
```
|
|
503
600
|
|
|
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.
|
|
601
|
+
Falsy values (`false`, `null`, `undefined`, `0`, `""`, `[]`) remove the block. Truthy values include it.
|
|
511
602
|
|
|
512
603
|
---
|
|
513
604
|
|
|
@@ -516,94 +607,133 @@ Template syntax follows [Mustache](https://mustache.github.io/) conventions, est
|
|
|
516
607
|
`odf-kit/reader` parses `.odt` files into a structured model and renders to HTML.
|
|
517
608
|
|
|
518
609
|
```typescript
|
|
519
|
-
import { readOdt, odtToHtml } from "odf-kit/reader";
|
|
520
|
-
import { readFileSync } from "fs";
|
|
610
|
+
import { readOdt, odtToHtml } from "odf-kit/odt-reader";
|
|
521
611
|
|
|
522
612
|
const bytes = readFileSync("report.odt");
|
|
523
|
-
|
|
524
|
-
// Structured model
|
|
525
613
|
const model = readOdt(bytes);
|
|
526
|
-
|
|
527
|
-
console.log(model.pageLayout); // PageLayout
|
|
528
|
-
console.log(model.header); // HeaderFooterContent
|
|
529
|
-
|
|
530
|
-
// Styled HTML
|
|
531
|
-
const html = odtToHtml(bytes);
|
|
614
|
+
const html = odtToHtml(bytes);
|
|
532
615
|
|
|
533
|
-
//
|
|
616
|
+
// Tracked changes
|
|
534
617
|
const final = odtToHtml(bytes, {}, { trackedChanges: "final" });
|
|
535
618
|
const original = odtToHtml(bytes, {}, { trackedChanges: "original" });
|
|
536
619
|
const marked = odtToHtml(bytes, {}, { trackedChanges: "changes" });
|
|
537
620
|
```
|
|
538
621
|
|
|
539
|
-
|
|
622
|
+
---
|
|
540
623
|
|
|
541
|
-
|
|
624
|
+
## Read: ODS Spreadsheets
|
|
542
625
|
|
|
543
|
-
|
|
626
|
+
`odf-kit/ods-reader` parses `.ods` files into a structured model and renders to HTML.
|
|
544
627
|
|
|
545
|
-
|
|
628
|
+
```typescript
|
|
629
|
+
import { readOds, odsToHtml } from "odf-kit/ods-reader";
|
|
630
|
+
import { readFileSync } from "fs";
|
|
631
|
+
|
|
632
|
+
const bytes = readFileSync("data.ods");
|
|
633
|
+
|
|
634
|
+
// Structured model — typed JavaScript values
|
|
635
|
+
const model = readOds(bytes);
|
|
636
|
+
for (const sheet of model.sheets) {
|
|
637
|
+
console.log(sheet.name);
|
|
638
|
+
for (const row of sheet.rows) {
|
|
639
|
+
for (const cell of row.cells) {
|
|
640
|
+
console.log(cell.colIndex, cell.type, cell.value);
|
|
641
|
+
// e.g. 0 "float" 1234.56
|
|
642
|
+
// e.g. 1 "string" "Hello"
|
|
643
|
+
// e.g. 2 "date" Date { 2026-01-15 }
|
|
644
|
+
// e.g. 3 "formula" 100 (cell.formula = "=SUM(A1:A10)")
|
|
645
|
+
// e.g. 4 "covered" null (part of a merged cell)
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
546
649
|
|
|
547
|
-
|
|
650
|
+
// HTML table
|
|
651
|
+
const html = odsToHtml(bytes);
|
|
652
|
+
|
|
653
|
+
// Fast mode — values only, no formatting
|
|
654
|
+
const model2 = readOds(bytes, { includeFormatting: false });
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
### Cell types
|
|
658
|
+
|
|
659
|
+
| Type | `value` | Notes |
|
|
660
|
+
|------|---------|-------|
|
|
661
|
+
| `"string"` | `string` | |
|
|
662
|
+
| `"float"` | `number` | Includes percentage and currency cells |
|
|
663
|
+
| `"date"` | `Date` (UTC) | |
|
|
664
|
+
| `"boolean"` | `boolean` | |
|
|
665
|
+
| `"formula"` | cached result | `cell.formula` has original string e.g. `"=SUM(A1:A10)"` |
|
|
666
|
+
| `"empty"` | `null` | |
|
|
667
|
+
| `"covered"` | `null` | Covered by a merge — correct `colIndex` always maintained |
|
|
668
|
+
|
|
669
|
+
### Merged cells
|
|
670
|
+
|
|
671
|
+
Primary cells have `colSpan` and/or `rowSpan`. Covered cells have `type: "covered"`, `value: null`, and the correct physical `colIndex` — no offset confusion.
|
|
548
672
|
|
|
549
673
|
```typescript
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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";
|
|
674
|
+
// A1:C1 merged — reading row 0:
|
|
675
|
+
// cell 0: { type: "string", value: "Header", colSpan: 3 }
|
|
676
|
+
// cell 1: { type: "covered", value: null, colIndex: 1 }
|
|
677
|
+
// cell 2: { type: "covered", value: null, colIndex: 2 }
|
|
678
|
+
// cell 3: { type: "string", value: "D1", colIndex: 3 } ← always correct
|
|
566
679
|
```
|
|
567
680
|
|
|
568
681
|
---
|
|
569
682
|
|
|
570
683
|
## Typst: ODT to PDF
|
|
571
684
|
|
|
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
685
|
```typescript
|
|
575
686
|
import { odtToTypst, modelToTypst } from "odf-kit/typst";
|
|
576
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
577
|
-
import { execSync } from "child_process";
|
|
578
687
|
|
|
579
|
-
// Convenience wrapper — ODT bytes → Typst string
|
|
580
688
|
const typst = odtToTypst(readFileSync("letter.odt"));
|
|
581
689
|
writeFileSync("letter.typ", typst);
|
|
582
690
|
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
691
|
```
|
|
589
692
|
|
|
590
|
-
|
|
693
|
+
---
|
|
694
|
+
|
|
695
|
+
## API Reference
|
|
591
696
|
|
|
592
|
-
###
|
|
697
|
+
### htmlToOdt / markdownToOdt
|
|
593
698
|
|
|
594
699
|
```typescript
|
|
595
|
-
|
|
700
|
+
function htmlToOdt(html: string, options?: HtmlToOdtOptions): Promise<Uint8Array>
|
|
701
|
+
function markdownToOdt(markdown: string, options?: HtmlToOdtOptions): Promise<Uint8Array>
|
|
596
702
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
703
|
+
interface HtmlToOdtOptions {
|
|
704
|
+
pageFormat?: "A4" | "letter" | "legal" | "A3" | "A5"; // default: "A4"
|
|
705
|
+
orientation?: "portrait" | "landscape";
|
|
706
|
+
marginTop?: string;
|
|
707
|
+
marginBottom?: string;
|
|
708
|
+
marginLeft?: string;
|
|
709
|
+
marginRight?: string;
|
|
710
|
+
metadata?: { title?: string; creator?: string; description?: string };
|
|
711
|
+
}
|
|
600
712
|
```
|
|
601
713
|
|
|
602
|
-
|
|
714
|
+
### tiptapToOdt
|
|
603
715
|
|
|
604
|
-
|
|
716
|
+
```typescript
|
|
717
|
+
function tiptapToOdt(json: TiptapNode, options?: TiptapToOdtOptions): Promise<Uint8Array>
|
|
605
718
|
|
|
606
|
-
|
|
719
|
+
interface TiptapNode {
|
|
720
|
+
type: string;
|
|
721
|
+
text?: string;
|
|
722
|
+
attrs?: Record<string, unknown>;
|
|
723
|
+
content?: TiptapNode[];
|
|
724
|
+
marks?: TiptapMark[];
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
interface TiptapMark {
|
|
728
|
+
type: string;
|
|
729
|
+
attrs?: Record<string, unknown>;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
interface TiptapToOdtOptions extends HtmlToOdtOptions {
|
|
733
|
+
images?: Record<string, Uint8Array>;
|
|
734
|
+
unknownNodeHandler?: (node: TiptapNode, doc: OdtDocument) => void;
|
|
735
|
+
}
|
|
736
|
+
```
|
|
607
737
|
|
|
608
738
|
### OdtDocument
|
|
609
739
|
|
|
@@ -621,56 +751,20 @@ See the [complete ODT to PDF with Typst guide](https://githubnewbie0.github.io/o
|
|
|
621
751
|
| `addPageBreak()` | Insert page break |
|
|
622
752
|
| `save()` | Generate `.odt` as `Promise<Uint8Array>` |
|
|
623
753
|
|
|
624
|
-
### OdsDocument
|
|
754
|
+
### OdsDocument / OdsSheet
|
|
625
755
|
|
|
626
756
|
| Method | Description |
|
|
627
757
|
|--------|-------------|
|
|
628
|
-
| `setMetadata(options)` | Set title, creator, description |
|
|
629
|
-
| `setDateFormat(format)` | Set default date display format
|
|
630
|
-
| `addSheet(name)` | Add a sheet tab — returns `OdsSheet` |
|
|
631
|
-
| `save()` | Generate `.ods` as `Promise<Uint8Array>` |
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
|
636
|
-
|
|
637
|
-
| `
|
|
638
|
-
| `setColumnWidth(colIndex, width)` | Set column width (e.g. `"4cm"`) |
|
|
639
|
-
| `setRowHeight(rowIndex, height)` | Set row height (e.g. `"1cm"`) |
|
|
640
|
-
|
|
641
|
-
### OdsCellValue
|
|
642
|
-
|
|
643
|
-
```typescript
|
|
644
|
-
type OdsCellValue =
|
|
645
|
-
| string // → string cell
|
|
646
|
-
| number // → float cell
|
|
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
|
-
```
|
|
758
|
+
| `doc.setMetadata(options)` | Set title, creator, description |
|
|
759
|
+
| `doc.setDateFormat(format)` | Set default date display format |
|
|
760
|
+
| `doc.addSheet(name)` | Add a sheet tab — returns `OdsSheet` |
|
|
761
|
+
| `doc.save()` | Generate `.ods` as `Promise<Uint8Array>` |
|
|
762
|
+
| `sheet.addRow(values, options?)` | Add a row of cells |
|
|
763
|
+
| `sheet.setColumnWidth(index, width)` | Set column width |
|
|
764
|
+
| `sheet.setRowHeight(index, height)` | Set row height |
|
|
765
|
+
| `sheet.freezeRows(N?)` | Freeze top N rows (default 1) |
|
|
766
|
+
| `sheet.freezeColumns(N?)` | Freeze left N columns (default 1) |
|
|
767
|
+
| `sheet.setTabColor(color)` | Set sheet tab color |
|
|
674
768
|
|
|
675
769
|
### fillTemplate
|
|
676
770
|
|
|
@@ -678,31 +772,11 @@ interface HtmlToOdtOptions {
|
|
|
678
772
|
function fillTemplate(templateBytes: Uint8Array, data: TemplateData): Uint8Array
|
|
679
773
|
```
|
|
680
774
|
|
|
681
|
-
`TemplateData` is `Record<string, unknown>` — any JSON-serializable value.
|
|
682
|
-
|
|
683
775
|
| Syntax | Description |
|
|
684
776
|
|--------|-------------|
|
|
685
777
|
| `{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
|
-
```
|
|
778
|
+
| `{object.property}` | Dot notation |
|
|
779
|
+
| `{#tag}...{/tag}` | Loop or conditional |
|
|
706
780
|
|
|
707
781
|
### TextFormatting
|
|
708
782
|
|
|
@@ -710,9 +784,9 @@ function modelToTypst(model: OdtDocumentModel, options?: TypstEmitOptions): stri
|
|
|
710
784
|
{
|
|
711
785
|
bold?: boolean,
|
|
712
786
|
italic?: boolean,
|
|
713
|
-
fontSize?: number | string,
|
|
787
|
+
fontSize?: number | string,
|
|
714
788
|
fontFamily?: string,
|
|
715
|
-
color?: string,
|
|
789
|
+
color?: string,
|
|
716
790
|
underline?: boolean,
|
|
717
791
|
strikethrough?: boolean,
|
|
718
792
|
superscript?: boolean,
|
|
@@ -721,37 +795,6 @@ function modelToTypst(model: OdtDocumentModel, options?: TypstEmitOptions): stri
|
|
|
721
795
|
}
|
|
722
796
|
```
|
|
723
797
|
|
|
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
798
|
---
|
|
756
799
|
|
|
757
800
|
## Platform support
|
|
@@ -763,7 +806,7 @@ function modelToTypst(model: OdtDocumentModel, options?: TypstEmitOptions): stri
|
|
|
763
806
|
| Deno, Bun | ✅ Full |
|
|
764
807
|
| Cloudflare Workers | ✅ Full |
|
|
765
808
|
|
|
766
|
-
ESM only. Zero Node-specific APIs in the library source — enforced at the TypeScript level
|
|
809
|
+
ESM only. Zero Node-specific APIs in the library source — enforced at the TypeScript level.
|
|
767
810
|
|
|
768
811
|
---
|
|
769
812
|
|
|
@@ -771,11 +814,11 @@ ESM only. Zero Node-specific APIs in the library source — enforced at the Type
|
|
|
771
814
|
|
|
772
815
|
**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
816
|
|
|
774
|
-
- **
|
|
817
|
+
- **Two runtime dependencies** — fflate (ZIP) and marked (Markdown parsing). No transitive dependencies.
|
|
775
818
|
- **Spec-compliant output** — every generated file passes the OASIS ODF validator. Enforced on every commit by CI.
|
|
776
819
|
- **Multiple ODF formats** — ODT documents and ODS spreadsheets from the same library.
|
|
777
|
-
- **
|
|
778
|
-
- **
|
|
820
|
+
- **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.
|
|
821
|
+
- **TipTap/ProseMirror integration** — direct JSON→ODT conversion for any TipTap-based editor, no intermediate HTML step.
|
|
779
822
|
- **Zero-dependency Typst emitter** — the only JavaScript library with built-in ODT→Typst conversion for PDF generation.
|
|
780
823
|
- **TypeScript-first** — full types across all sub-exports.
|
|
781
824
|
- **Apache 2.0** — use freely in commercial and open source projects.
|
|
@@ -787,8 +830,10 @@ ESM only. Zero Node-specific APIs in the library source — enforced at the Type
|
|
|
787
830
|
| Feature | odf-kit | simple-odf | docxtemplater |
|
|
788
831
|
|---------|---------|------------|---------------|
|
|
789
832
|
| Generate .odt from scratch | ✅ | ⚠️ flat XML only | ❌ |
|
|
790
|
-
| Generate .ods from scratch | ✅ | ❌ | ❌ |
|
|
833
|
+
| Generate .ods from scratch | ✅ merged cells, freeze, number formats, hyperlinks | ❌ | ❌ |
|
|
791
834
|
| Convert HTML → ODT | ✅ | ❌ | ❌ |
|
|
835
|
+
| Convert Markdown → ODT | ✅ | ❌ | ❌ |
|
|
836
|
+
| Convert TipTap JSON → ODT | ✅ | ❌ | ❌ |
|
|
792
837
|
| Fill .odt templates | ✅ | ❌ | ✅ .docx only |
|
|
793
838
|
| Read .odt files | ✅ | ❌ | ❌ |
|
|
794
839
|
| Convert to HTML | ✅ | ❌ | ❌ |
|
|
@@ -801,29 +846,35 @@ ESM only. Zero Node-specific APIs in the library source — enforced at the Type
|
|
|
801
846
|
|
|
802
847
|
## Specification compliance
|
|
803
848
|
|
|
804
|
-
odf-kit targets ODF 1.2 (ISO/IEC 26300). Generated files include proper ZIP packaging
|
|
849
|
+
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
850
|
|
|
806
851
|
---
|
|
807
852
|
|
|
808
853
|
## Version history
|
|
809
854
|
|
|
810
|
-
**v0.9.
|
|
855
|
+
**v0.9.8** — ODS reader: `readOds()` and `odsToHtml()` via `odf-kit/ods-reader`. Typed values, formula strings, merged cell handling, formatting, metadata. `odf-kit/odt-reader` alias added. 889 tests passing.
|
|
811
856
|
|
|
812
|
-
**v0.9.
|
|
857
|
+
**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.
|
|
813
858
|
|
|
814
|
-
**v0.
|
|
859
|
+
**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.
|
|
815
860
|
|
|
816
|
-
**v0.
|
|
861
|
+
**v0.9.5** — `markdownToOdt()`: Markdown→ODT via marked + htmlToOdt. 786 tests passing.
|
|
817
862
|
|
|
818
|
-
**v0.
|
|
863
|
+
**v0.9.4** — ODS datetime auto-detection (nonzero UTC time → datetime format). ODS formula `xmlns:of` namespace fix (Err:510 resolved).
|
|
819
864
|
|
|
820
|
-
**v0.
|
|
865
|
+
**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.
|
|
821
866
|
|
|
822
|
-
**v0.
|
|
867
|
+
**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.
|
|
823
868
|
|
|
824
|
-
**v0.
|
|
869
|
+
**v0.8.0** — `odf-kit/typst`: `odtToTypst()` and `modelToTypst()`. Zero-dependency ODT→Typst emitter for PDF generation.
|
|
825
870
|
|
|
826
|
-
**v0.
|
|
871
|
+
**v0.7.0** — Tier 3 reader: paragraph styles, page geometry, headers/footers, sections, tracked changes (all three ODF modes).
|
|
872
|
+
|
|
873
|
+
**v0.6.0** — Tier 2 reader: span styles, image float/wrap, footnotes/endnotes, bookmarks, fields, cell/row styles.
|
|
874
|
+
|
|
875
|
+
**v0.5.0** — `odf-kit/reader`: `readOdt()`, `odtToHtml()`. Tier 1 parsing.
|
|
876
|
+
|
|
877
|
+
**v0.3.0** — Template engine: loops, conditionals, dot notation, automatic XML fragment healing.
|
|
827
878
|
|
|
828
879
|
**v0.1.0** — Programmatic ODT creation: text, tables, page layout, lists, images, links, bookmarks.
|
|
829
880
|
|
|
@@ -831,8 +882,6 @@ odf-kit targets ODF 1.2 (ISO/IEC 26300). Generated files include proper ZIP pack
|
|
|
831
882
|
|
|
832
883
|
## Guides
|
|
833
884
|
|
|
834
|
-
Full walkthroughs and real-world examples on the documentation site:
|
|
835
|
-
|
|
836
885
|
- [Generate ODT files in Node.js](https://githubnewbie0.github.io/odf-kit/guides/generate-odt-nodejs.html)
|
|
837
886
|
- [Generate ODT files in the browser](https://githubnewbie0.github.io/odf-kit/guides/generate-odt-browser.html)
|
|
838
887
|
- [Fill ODT templates in JavaScript](https://githubnewbie0.github.io/odf-kit/guides/fill-odt-template-javascript.html)
|
|
@@ -860,21 +909,6 @@ npm run build
|
|
|
860
909
|
npm test
|
|
861
910
|
```
|
|
862
911
|
|
|
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
912
|
---
|
|
879
913
|
|
|
880
914
|
## License
|