otomate 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/SKILL.md +368 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/otomate.js +7453 -6938
- package/dist/otomate.js.map +1 -1
- package/dist/otomate.umd.cjs +61 -43
- package/dist/otomate.umd.cjs.map +1 -1
- package/guides/html-to-docx-and-back.md +484 -0
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -295,13 +295,18 @@ When CSS rules are provided, otomate generates real Word styles with OOXML forma
|
|
|
295
295
|
| `font-weight: bold` | `w:b` | ≥700 or "bold" |
|
|
296
296
|
| `font-style: italic` | `w:i` | |
|
|
297
297
|
| `color` | `w:color` | Hex, rgb(), named colors |
|
|
298
|
-
| `background-color` | `w:shd`
|
|
298
|
+
| `background-color` | `w:shd` | Paragraph fill + run highlight |
|
|
299
299
|
| `text-decoration: underline` | `w:u` | |
|
|
300
300
|
| `text-decoration: line-through` | `w:strike` | |
|
|
301
301
|
| `text-align` | `w:jc` | left, center, right, justify→both |
|
|
302
|
-
| `margin-top/bottom` | `w:spacing before/after` | Converted to twips |
|
|
303
|
-
| `margin-left` | `w:ind left` | Converted to twips |
|
|
302
|
+
| `margin-top` / `margin-bottom` | `w:spacing before/after` | Converted to twips |
|
|
303
|
+
| `margin-left` / `margin-right` | `w:ind left/right` | Converted to twips |
|
|
304
|
+
| `padding-top` / `padding-bottom` | `w:spacing before/after` | Added on top of margin values |
|
|
305
|
+
| `padding-left` / `padding-right` | `w:ind left/right` | Added on top of margin values |
|
|
306
|
+
| `text-indent` | `w:ind firstLine` | Converted to twips |
|
|
304
307
|
| `line-height` | `w:spacing line` | |
|
|
308
|
+
| `border` | `w:pBdr` (all sides) | Shorthand; per-side overrides take priority |
|
|
309
|
+
| `border-top` / `border-bottom` / `border-left` / `border-right` | `w:pBdr` | solid→single, dashed, dotted, double |
|
|
305
310
|
|
|
306
311
|
Container CSS (on `div` elements) cascades to all child paragraphs, headings, and list items.
|
|
307
312
|
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: otomate
|
|
3
|
+
description: Integrate the otomate library to read, write, and diff Word documents (.docx) and HTML via a Universal Document Model. Use when building apps that generate Word docs from HTML or CSS, construct structured documents programmatically, apply tracked changes, round-trip .docx files, or template documents with {{placeholders}}. Triggers on phrases like "generate a Word doc", "build a docx", "convert HTML to docx", "use otomate", "tracked changes in Word", "docx template injection".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Integrating otomate
|
|
7
|
+
|
|
8
|
+
This is a condensed integration guide for AI coding assistants. Read it end-to-end before writing any code that imports from `otomate`.
|
|
9
|
+
|
|
10
|
+
## TL;DR — 30 seconds
|
|
11
|
+
|
|
12
|
+
otomate turns HTML or structured data into `.docx` files (and back), using a single JSON-serializable tree called the **UDM**. For 80% of real use cases you only need three calls:
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { readHtml, writeDocx, diff, writeDiffDocx } from "otomate";
|
|
16
|
+
|
|
17
|
+
// 1. HTML → .docx (Word file)
|
|
18
|
+
const tree = readHtml(htmlString, { css: optionalCssString });
|
|
19
|
+
const buffer: Uint8Array = await writeDocx(tree);
|
|
20
|
+
|
|
21
|
+
// 2. Tracked changes between two versions
|
|
22
|
+
const delta = diff(oldTree, newTree);
|
|
23
|
+
const trackedBuffer = await writeDiffDocx(newTree, delta, { author: "Me" });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`writeDocx` / `readDocx` / `writeDiffDocx` are **async** — always `await` them. `readHtml` / `writeHtml` are synchronous.
|
|
27
|
+
|
|
28
|
+
## Mental model
|
|
29
|
+
|
|
30
|
+
- **UDM = Universal Document Model.** A plain, JSON-serializable tree of nodes (`root` → `paragraph` → `text`, etc.). Every format (HTML, docx) converts to and from UDM; diffs operate on UDM.
|
|
31
|
+
- **Marks are flat, not nested.** Bold + italic text is one `{ type: "text", value: "x", marks: [{type:"strong"},{type:"emphasis"}] }` node, not `strong(emphasis(text("x")))`. This matters when you build trees programmatically — use marks, don't nest inline wrappers.
|
|
32
|
+
- **`classes: string[]` is first-class** on every node. HTML classes, Word paragraph styles, and Markdown attributes all map here.
|
|
33
|
+
- **Format-specific metadata lives under `data`.** `data.html` for HTML-only fields (id, aria-*, data-*, style), `data.docx` for Word-specific state, `data.css` for parsed CSS rules.
|
|
34
|
+
- **Lossless round-trips via an embedded snapshot.** When otomate writes a .docx, it embeds a JSON snapshot of the UDM inside the ZIP and binds it to the document.xml via a SHA-256 hash. `readDocx` uses the snapshot only if the hash still matches — i.e. the file hasn't been edited in Word. Otherwise it falls back to OOXML parsing (which is lossy for `div`/`figure`/`data.html.*`/custom marks).
|
|
35
|
+
|
|
36
|
+
## Package map
|
|
37
|
+
|
|
38
|
+
You only need one import in almost all cases:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { /* anything */ } from "otomate";
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The `otomate` umbrella re-exports everything from these sub-packages:
|
|
45
|
+
|
|
46
|
+
| Package | What's inside |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `@otomate/core` | UDM types (`Root`, `Paragraph`, `Heading`, …), builders (`root()`, `paragraph()`, `text()`, `strong()`, `withClasses()`, `withData()`), traversal (`visit`, `nodeAtPath`), hashing |
|
|
49
|
+
| `@otomate/diff` | Tree diff engine (`diff()`) + Myers text diff (`diffText()`) |
|
|
50
|
+
| `@otomate/html` | `readHtml`, `writeHtml`, `renderDiffHtml`, `parseCssRules` |
|
|
51
|
+
| `@otomate/docx` | `readDocx`, `writeDocx`, `writeDiffDocx`, `extractDocx`, `packDocx` |
|
|
52
|
+
| `@otomate/css-docx` | `cssToRunProps`, `cssToParaProps`, `parseColor`, `parseLengthTwips`, `buildStyleElement` |
|
|
53
|
+
| `@otomate/inject` | `inject()` for `{{placeholder}}` template filling |
|
|
54
|
+
|
|
55
|
+
Direct sub-package imports (`import { readDocx } from "@otomate/docx"`) work too and have the same shapes. Prefer the umbrella unless you need to tree-shake aggressively.
|
|
56
|
+
|
|
57
|
+
## The five entry points
|
|
58
|
+
|
|
59
|
+
### `readHtml(html, options?): Root`
|
|
60
|
+
**Sync.** Parses an HTML string into a UDM tree.
|
|
61
|
+
- `html` — any HTML fragment or full document
|
|
62
|
+
- `options.css?` — additional CSS string merged with inline `<style>` blocks found in the HTML. Both sources are parsed and stored on `tree.data.css`. Inline `<style>` blocks are extracted automatically — you don't need to strip them first.
|
|
63
|
+
- Returns: `Root` (the top of the UDM tree)
|
|
64
|
+
|
|
65
|
+
### `writeHtml(tree, options?): string`
|
|
66
|
+
**Sync.** Serializes a UDM tree back to HTML.
|
|
67
|
+
- `options.restoreAttributes?` (default `true`) — restore `id`/`data-*`/`style`/`aria-*` from `node.data.html`
|
|
68
|
+
- `options.prettyPrint?` (default `false`) — indent output
|
|
69
|
+
- Returns: HTML string
|
|
70
|
+
|
|
71
|
+
### `readDocx(buffer): Promise<Root>`
|
|
72
|
+
**Async.** Parses a `.docx` buffer (ZIP + OOXML) into a UDM tree.
|
|
73
|
+
- `buffer` — `ArrayBuffer` or `Uint8Array`. Don't pass a file path; read the file yourself first (`fs.readFileSync(path)` in Node).
|
|
74
|
+
- Returns: `Promise<Root>`. If the file was written by otomate and is unchanged, the snapshot path gives a perfect round-trip. Otherwise falls back to OOXML parsing.
|
|
75
|
+
|
|
76
|
+
### `writeDocx(tree, options?): Promise<Uint8Array>`
|
|
77
|
+
**Async.** Serializes a UDM tree into a `.docx` buffer.
|
|
78
|
+
- `options.cssClasses?` — extra CSS class → declarations map, merged with `tree.data.css.classRules`. Explicit options win over tree-embedded CSS.
|
|
79
|
+
- Returns: `Promise<Uint8Array>`. Write with `fs.writeFileSync("out.docx", buffer)`.
|
|
80
|
+
|
|
81
|
+
### `writeDiffDocx(newTree, diffResult, options?): Promise<Uint8Array>`
|
|
82
|
+
**Async.** Serializes a tree with tracked changes (`<w:ins>` / `<w:del>`) so Word's Accept/Reject-All flow works.
|
|
83
|
+
- `newTree` — the "after" tree
|
|
84
|
+
- `diffResult` — output from `diff(oldTree, newTree)`
|
|
85
|
+
- `options.author?` (default `"Otomate"`) — shown on each revision
|
|
86
|
+
- `options.date?` (default `"2024-01-01T00:00:00Z"`) — ISO 8601 string on each revision
|
|
87
|
+
- All `DocxWriteOptions` fields (e.g. `cssClasses`) are also accepted
|
|
88
|
+
- Returns: `Promise<Uint8Array>`
|
|
89
|
+
|
|
90
|
+
## Pattern recipes
|
|
91
|
+
|
|
92
|
+
### R1. HTML (from a CMS) → branded Word doc with CSS
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { readHtml, writeDocx } from "otomate";
|
|
96
|
+
import { writeFileSync } from "node:fs";
|
|
97
|
+
|
|
98
|
+
const htmlFromCms = `
|
|
99
|
+
<style>
|
|
100
|
+
.intro { font-style: italic; color: #666; }
|
|
101
|
+
h1 { color: #1e3a5f; }
|
|
102
|
+
</style>
|
|
103
|
+
<h1>Quarterly Report</h1>
|
|
104
|
+
<p class="intro">Summary of Q1 results.</p>
|
|
105
|
+
<p>Revenue is up 12%.</p>
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
const tree = readHtml(htmlFromCms); // inline <style> auto-extracted
|
|
109
|
+
const buffer = await writeDocx(tree);
|
|
110
|
+
writeFileSync("report.docx", buffer);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
To override or supplement inline styles (e.g. brand CSS applied to every doc), pass `options.css`:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const brandCss = `h1 { font-family: Georgia; font-size: 28pt; color: #003366; }`;
|
|
117
|
+
const tree = readHtml(htmlFromCms, { css: brandCss });
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### R2. Build a document programmatically from app data
|
|
121
|
+
|
|
122
|
+
No HTML round-trip needed; construct the tree directly via `@otomate/core` builders:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import {
|
|
126
|
+
root, heading, paragraph, text, strong, emphasis,
|
|
127
|
+
list, listItem, table, tableRow, tableCell, withClasses,
|
|
128
|
+
writeDocx,
|
|
129
|
+
} from "otomate";
|
|
130
|
+
|
|
131
|
+
const doc = root(
|
|
132
|
+
heading(1, text("Project Proposal")),
|
|
133
|
+
paragraph(
|
|
134
|
+
text("This quarter we will "),
|
|
135
|
+
text("ship", [strong()]),
|
|
136
|
+
text(" the "),
|
|
137
|
+
text("redesign", [emphasis()]),
|
|
138
|
+
text("."),
|
|
139
|
+
),
|
|
140
|
+
heading(2, text("Key Milestones")),
|
|
141
|
+
list(true, // ordered
|
|
142
|
+
listItem(paragraph(text("Kickoff (week 1)"))),
|
|
143
|
+
listItem(paragraph(text("Beta (week 6)"))),
|
|
144
|
+
listItem(paragraph(text("Launch (week 12)"))),
|
|
145
|
+
),
|
|
146
|
+
withClasses(
|
|
147
|
+
paragraph(text("Confidential — do not redistribute.")),
|
|
148
|
+
"footer-note",
|
|
149
|
+
),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const buffer = await writeDocx(doc, {
|
|
153
|
+
cssClasses: {
|
|
154
|
+
"footer-note": { "font-size": "9pt", color: "#888", "font-style": "italic" },
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Key things to notice:
|
|
160
|
+
- `text(value, marks)` — marks are the second argument
|
|
161
|
+
- `list(ordered, ...items)` — `true` for `<ol>`, `false` for `<ul>`
|
|
162
|
+
- `tableRow(isHeader, ...cells)` — `isHeader: true` produces `<th>` cells
|
|
163
|
+
- `withClasses(node, ...classes)` / `withData(node, data)` return the same node, so they chain inline
|
|
164
|
+
|
|
165
|
+
### R3. Round-trip a Word file (read, optionally mutate, write back)
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { readDocx, writeDocx } from "otomate";
|
|
169
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
170
|
+
|
|
171
|
+
const buf = await readFileSync("input.docx");
|
|
172
|
+
const tree = await readDocx(buf);
|
|
173
|
+
|
|
174
|
+
// Optional: mutate the tree. E.g. change the first heading's text.
|
|
175
|
+
if (tree.children[0]?.type === "heading") {
|
|
176
|
+
(tree.children[0].children[0] as any).value = "New Title";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const out = await writeDocx(tree);
|
|
180
|
+
writeFileSync("output.docx", out);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**If you don't mutate the tree, the output is byte-close to the input.** The embedded UDM snapshot + hash means the reader picked the lossless path; any mutation will invalidate the snapshot on write and the output will reflect your changes.
|
|
184
|
+
|
|
185
|
+
### R4. Tracked changes between two versions
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { readHtml, diff, writeDiffDocx } from "otomate";
|
|
189
|
+
|
|
190
|
+
const oldTree = readHtml("<h1>Product Roadmap</h1><p>Q1 2024 goals...</p>");
|
|
191
|
+
const newTree = readHtml("<h1>Product Roadmap 2024</h1><p>Revised Q1 goals...</p>");
|
|
192
|
+
|
|
193
|
+
const delta = diff(oldTree, newTree);
|
|
194
|
+
|
|
195
|
+
const trackedBuffer = await writeDiffDocx(newTree, delta, {
|
|
196
|
+
author: "Jane Editor",
|
|
197
|
+
date: "2024-04-07T10:30:00Z",
|
|
198
|
+
});
|
|
199
|
+
writeFileSync("roadmap-tracked.docx", trackedBuffer);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The resulting `.docx` opens in Word with all changes as revisions. Word's "Accept All" / "Reject All" buttons work.
|
|
203
|
+
|
|
204
|
+
> **For the full round-trip flow** — generating the diff, writing tracked-changes docx, re-importing, re-rendering HTML with revisions visible, and accepting changes — read `guides/html-to-docx-and-back.md` in this package. It walks through a 7-stage reference implementation and includes a long troubleshooting section covering every real pitfall: wrong `renderDiffHtml` argument order, nested deletes not rendering as `<w:del>`, async-forgot-await traps, stale-dist issues, CSS class-name sanitization, hyperlink rId collisions, and more.
|
|
205
|
+
|
|
206
|
+
### R5. Template injection with `{{placeholders}}`
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { readDocx, inject, writeDocx } from "otomate";
|
|
210
|
+
|
|
211
|
+
const template = await readDocx(readFileSync("offer-template.docx"));
|
|
212
|
+
|
|
213
|
+
const filled = inject(template, {
|
|
214
|
+
companyName: "Acme Corp",
|
|
215
|
+
employee: { name: "Jane Smith", title: "Engineer" },
|
|
216
|
+
benefits: [
|
|
217
|
+
{ name: "Health", coverage: "Full" },
|
|
218
|
+
{ name: "401k", coverage: "6% match" },
|
|
219
|
+
],
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
writeFileSync("jane-offer.docx", await writeDocx(filled));
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Placeholder syntax inside the Word template:
|
|
226
|
+
- `{{fieldName}}` — simple substitution
|
|
227
|
+
- `{{employee.name}}` — dot-path
|
|
228
|
+
- `{{#each benefits}}...{{name}}: {{coverage}}...{{/each}}` — loops
|
|
229
|
+
- `{{#if condition}}...{{/if}}` — conditionals
|
|
230
|
+
|
|
231
|
+
### R6. Branded CSS shared across every generated doc
|
|
232
|
+
|
|
233
|
+
Build the CSS once, attach it to every tree:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
const BRAND_CSS = `
|
|
237
|
+
h1 { font-family: Georgia; font-size: 28pt; color: #003366; }
|
|
238
|
+
h2 { font-family: Georgia; font-size: 18pt; color: #003366; }
|
|
239
|
+
p { font-family: Calibri; font-size: 11pt; color: #333; line-height: 1.5; }
|
|
240
|
+
.intro { font-style: italic; color: #666; }
|
|
241
|
+
`;
|
|
242
|
+
|
|
243
|
+
async function generate(html: string): Promise<Uint8Array> {
|
|
244
|
+
return writeDocx(readHtml(html, { css: BRAND_CSS }));
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Alternatively, pass `cssClasses` directly to `writeDocx` — it's merged on top of any `tree.data.css` the HTML reader produced.
|
|
249
|
+
|
|
250
|
+
## UDM node reference
|
|
251
|
+
|
|
252
|
+
All types live in `@otomate/core` and are re-exported from `otomate`. Required fields are **bold**.
|
|
253
|
+
|
|
254
|
+
### Block nodes
|
|
255
|
+
|
|
256
|
+
| Type | Required | Optional |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| `root` | **`type: "root"`**, **`children: BlockNode[]`** | `classes`, `data`, `id` |
|
|
259
|
+
| `paragraph` | **`type: "paragraph"`**, **`children: InlineNode[]`** | `classes`, `data`, `id` |
|
|
260
|
+
| `heading` | **`type: "heading"`**, **`depth: 1..6`**, **`children: InlineNode[]`** | `classes`, `data`, `id` |
|
|
261
|
+
| `blockquote` | **`type: "blockquote"`**, **`children: BlockNode[]`** | same |
|
|
262
|
+
| `list` | **`type: "list"`**, **`ordered: boolean`**, **`children: ListItem[]`** | `start?: number`, `classes`, `data` |
|
|
263
|
+
| `listItem` | **`type: "listItem"`**, **`children: BlockNode[]`** | `checked?: boolean` |
|
|
264
|
+
| `codeBlock` | **`type: "codeBlock"`**, **`value: string`** | `lang?: string`, `meta?: string` |
|
|
265
|
+
| `table` | **`type: "table"`**, **`children: TableRow[]`** | same |
|
|
266
|
+
| `tableRow` | **`type: "tableRow"`**, **`children: TableCell[]`** | `isHeader?: boolean` |
|
|
267
|
+
| `tableCell` | **`type: "tableCell"`**, **`children: BlockNode[]`** | `colspan?: number`, `rowspan?: number`, `align?: "left"\|"center"\|"right"` |
|
|
268
|
+
| `thematicBreak` | **`type: "thematicBreak"`** | — |
|
|
269
|
+
| `div` | **`type: "div"`**, **`children: BlockNode[]`** | same |
|
|
270
|
+
| `figure` | **`type: "figure"`**, **`children: (BlockNode \| FigCaption)[]`** | same |
|
|
271
|
+
| `figCaption` | **`type: "figCaption"`**, **`children: InlineNode[]`** | same |
|
|
272
|
+
| `html` | **`type: "html"`**, **`value: string`** | raw-HTML passthrough — use sparingly |
|
|
273
|
+
|
|
274
|
+
### Inline nodes
|
|
275
|
+
|
|
276
|
+
| Type | Required | Optional |
|
|
277
|
+
|---|---|---|
|
|
278
|
+
| `text` | **`type: "text"`**, **`value: string`** | `marks?: Mark[]` |
|
|
279
|
+
| `break` | **`type: "break"`** | (line break) |
|
|
280
|
+
| `image` | **`type: "image"`**, **`url: string`** | `alt?: string`, `title?: string` |
|
|
281
|
+
| `inlineCode` | **`type: "inlineCode"`**, **`value: string`** | — |
|
|
282
|
+
|
|
283
|
+
### Marks (applied to `text` nodes via `marks[]`)
|
|
284
|
+
|
|
285
|
+
Built-in: `strong`, `emphasis`, `strikethrough`, `underline`, `superscript`, `subscript`, `code`, `link` (needs `attrs.url`), `highlight` (optional `attrs.color`). Custom mark types are allowed — the writer preserves them via the UDM snapshot.
|
|
286
|
+
|
|
287
|
+
## CSS → OOXML cheat-sheet
|
|
288
|
+
|
|
289
|
+
These CSS properties are understood by `writeDocx` (via `@otomate/css-docx`). Anything not listed is silently ignored.
|
|
290
|
+
|
|
291
|
+
**Run (text) properties**
|
|
292
|
+
|
|
293
|
+
| CSS | OOXML |
|
|
294
|
+
|---|---|
|
|
295
|
+
| `font-family: Arial` | `w:rFonts` |
|
|
296
|
+
| `font-size: 14pt` | `w:sz` (half-points) |
|
|
297
|
+
| `font-weight: bold` / `≥700` | `w:b` |
|
|
298
|
+
| `font-style: italic` | `w:i` |
|
|
299
|
+
| `color: #333` | `w:color` |
|
|
300
|
+
| `background-color: yellow` | `w:shd` |
|
|
301
|
+
| `text-decoration: underline` | `w:u val="single"` |
|
|
302
|
+
| `text-decoration: line-through` | `w:strike` |
|
|
303
|
+
|
|
304
|
+
**Paragraph properties**
|
|
305
|
+
|
|
306
|
+
| CSS | OOXML |
|
|
307
|
+
|---|---|
|
|
308
|
+
| `text-align: left/center/right/justify` | `w:jc` |
|
|
309
|
+
| `margin-top/bottom: 12pt` | `w:spacing before/after` (twips) |
|
|
310
|
+
| `line-height: 1.5` | `w:spacing line` |
|
|
311
|
+
| `margin-left/right: 0.5in` | `w:ind left/right` (twips) |
|
|
312
|
+
| `border: 1pt solid #000` (and `border-top/right/bottom/left`) | `w:pBdr` (per side) |
|
|
313
|
+
| `background-color: #fafafa` | `w:shd` |
|
|
314
|
+
|
|
315
|
+
**Color formats accepted**: named colors, `#rgb`, `#rrggbb`, `#rrggbbaa`, `rgb()`, `rgba()`, `hsl()`, `hsla()`. Alpha is dropped (OOXML has no alpha channel).
|
|
316
|
+
|
|
317
|
+
**Length units accepted**: `px`, `pt`, `em`, `rem`, `cm`, `mm`, `in`. Percentages (`%`) are **not** supported for paragraph spacing — OOXML has no basis to resolve them against.
|
|
318
|
+
|
|
319
|
+
## Gotchas (ranked by how often they bite)
|
|
320
|
+
|
|
321
|
+
1. **`writeDocx`, `readDocx`, `writeDiffDocx` are async.** Forgetting `await` leaves you holding a `Promise<Uint8Array>` instead of a buffer — `fs.writeFileSync` will then fail or write garbage. `readHtml`/`writeHtml` are synchronous; don't await them.
|
|
322
|
+
2. **Node ≥20 only for the docx path.** `writer.ts` and `reader.ts` use `node:crypto` for the snapshot hash. The HTML-only path (`readHtml`/`writeHtml`) is browser-safe.
|
|
323
|
+
3. **Pass a buffer, not a path.** `readDocx(buffer)` expects `Uint8Array` or `ArrayBuffer`. Call `fs.readFileSync("file.docx")` first.
|
|
324
|
+
4. **Marks are flat.** Put bold on a text node via `text("x", [strong()])`, not `strong(text("x"))`. The builders enforce this — `strong()` returns a `Mark`, not a wrapper node.
|
|
325
|
+
5. **CSS class names get sanitized for OOXML.** Only `[A-Za-z0-9_\-:]` survives and the result is clamped to 31 characters. A class like `"my class!"` becomes `"myclass"` in the generated Word style. Keep class names alphanumeric if you want 1:1 fidelity.
|
|
326
|
+
6. **`readDocx` prefers the embedded snapshot only if its hash matches.** If a user opened the file in Word and saved it, the snapshot is ignored and you get the OOXML-parsed tree — which is lossy for `div`, `figure`, custom marks, `data.html.*` attributes, and the original CSS rules. If your downstream workflow involves users editing in Word, don't rely on those features surviving.
|
|
327
|
+
7. **The umbrella export is the safest import path.** `import { X } from "otomate"` re-exports the complete public API. Direct sub-package imports (`@otomate/docx`) work but mean you have to track dependencies yourself.
|
|
328
|
+
8. **`writeDocx` mutates nothing** — it always returns a fresh `Uint8Array`. Safe to call multiple times on the same tree.
|
|
329
|
+
9. **Tables need a `<w:p/>` after them** per Word's quirk. The writer handles this automatically; you don't need to think about it when building trees.
|
|
330
|
+
|
|
331
|
+
## How to verify your integration
|
|
332
|
+
|
|
333
|
+
Drop this into a scratch file and run it with `tsx` or `node --import tsx`:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { readHtml, writeDocx, readDocx } from "otomate";
|
|
337
|
+
import assert from "node:assert/strict";
|
|
338
|
+
|
|
339
|
+
const HTML = `
|
|
340
|
+
<style>.title { color: red; }</style>
|
|
341
|
+
<h1 class="title">Hello</h1>
|
|
342
|
+
<p>World <strong>bold</strong>.</p>
|
|
343
|
+
`;
|
|
344
|
+
|
|
345
|
+
const tree = readHtml(HTML);
|
|
346
|
+
assert.equal(tree.children[0]?.type, "heading");
|
|
347
|
+
assert.equal((tree.data as any)?.css?.classRules?.title?.color, "red");
|
|
348
|
+
|
|
349
|
+
const buf = await writeDocx(tree);
|
|
350
|
+
assert.ok(buf.byteLength > 1000, "docx should be at least 1kb");
|
|
351
|
+
|
|
352
|
+
const reread = await readDocx(buf);
|
|
353
|
+
// Snapshot path: structure survives exactly
|
|
354
|
+
assert.equal((reread.children[0] as any)?.children?.[0]?.value, "Hello");
|
|
355
|
+
|
|
356
|
+
console.log("otomate integration OK");
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
If any assertion fails or the script throws, **do not generate further code using the library** — report the failure to the user and investigate.
|
|
360
|
+
|
|
361
|
+
## Where to look for more detail
|
|
362
|
+
|
|
363
|
+
- **Full HTML ↔ docx walkthrough with troubleshooting**: `guides/html-to-docx-and-back.md` (next to this file in `node_modules/otomate/`). A 7-stage end-to-end implementation guide covering the tracked-changes round-trip, plus a long troubleshooting section that catalogs every real trap encountered while building the reference test. **Read this before integrating the tracked-changes flow.**
|
|
364
|
+
- **Types**: `packages/core/src/types.ts` — canonical definitions of every node
|
|
365
|
+
- **Builders**: `packages/core/src/builders.ts` — the exact signature of every factory
|
|
366
|
+
- **CSS mapping**: `packages/css-docx/src/convert.ts` — every CSS property the library understands
|
|
367
|
+
- **End-to-end examples**: `packages/docx/src/__tests__/realworld.test.ts` — the most realistic usage patterns the library is tested against
|
|
368
|
+
- **Architecture**: the repo's top-level `README.md`
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { type UdmNode, type UdmParent, type UdmLiteral, type Root, type Paragraph, type Heading, type Blockquote, type List, type ListItem, type CodeBlock, type Table, type TableRow, type TableCell, type ThematicBreak, type Div, type Figure, type FigCaption, type Math, type FootnoteDefinition, type Html, type Text, type Break, type Image, type FootnoteReference, type InlineCode, type InlineMath, type Mark, type InlineNode, type BlockNode, type AnyNode, type Position, type Point, isParent, isLiteral, isText, root, paragraph, heading, blockquote, list, listItem, codeBlock, table, tableRow, tableCell, thematicBreak, div, text, lineBreak, image, strong, emphasis, strikethrough, underline, code, link, highlight, withClasses, withData, withId, visit, nodeCount, collectNodes, pathTo, nodeAtPath, hashNode, invalidateHash, } from "@otomate/core";
|
|
2
2
|
export { diff, diffText, type DiffResult, type DiffOptions, type EditOperation, type TextChange, type NodeMapping, } from "@otomate/diff";
|
|
3
3
|
export { readHtml, writeHtml, renderDiffHtml, parseCssClasses, parseCssRules, type HtmlReadOptions, type HtmlWriteOptions, type DiffHtmlOptions, type CssClassRules, type CssParsedRules, } from "@otomate/html";
|
|
4
|
-
export { readDocx, writeDocx, type DocxReadOptions, type DocxWriteOptions, } from "@otomate/docx";
|
|
4
|
+
export { readDocx, writeDocx, writeDiffDocx, extractDocx, packDocx, type DocxReadOptions, type DocxWriteOptions, type DiffDocxOptions, } from "@otomate/docx";
|
|
5
5
|
export { inject, normalizeTextRuns, parseTokens, splitByPlaceholders, hasPlaceholders, resolveValue, valueToString, isTruthy, type InjectData, type InjectOptions, type PlaceholderToken, type PlaceholderHit, type RichContent, } from "@otomate/inject";
|
|
6
6
|
export { cssToRunProps, cssToParaProps, parseColor, parseLengthTwips, parseLengthHalfPts, runPropsToXml, paraPropsToXml, buildStyleElement, type OoxmlRunProps, type OoxmlParaProps, } from "@otomate/css-docx";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAC7C,KAAK,IAAI,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,KAAK,UAAU,EACxD,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,KAAK,SAAS,EACxC,KAAK,KAAK,EAAE,KAAK,QAAQ,EAAE,KAAK,SAAS,EACzC,KAAK,aAAa,EAAE,KAAK,GAAG,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAC1D,KAAK,IAAI,EAAE,KAAK,kBAAkB,EAAE,KAAK,IAAI,EAC7C,KAAK,IAAI,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,EACjC,KAAK,iBAAiB,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EACxD,KAAK,IAAI,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EACxD,KAAK,QAAQ,EAAE,KAAK,KAAK,EAEzB,QAAQ,EAAE,SAAS,EAAE,MAAM,EAE3B,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EACpD,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,EACzD,IAAI,EAAE,SAAS,EAAE,KAAK,EACtB,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EACjE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAE7B,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAElD,QAAQ,EAAE,cAAc,GACzB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,IAAI,EACJ,QAAQ,EACR,KAAK,UAAU,EAAE,KAAK,WAAW,EACjC,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,GACtD,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,QAAQ,EAAE,SAAS,EAAE,cAAc,EACnC,eAAe,EAAE,aAAa,EAC9B,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EACjE,KAAK,aAAa,EAAE,KAAK,cAAc,GACxC,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,QAAQ,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAC7C,KAAK,IAAI,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,KAAK,UAAU,EACxD,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,KAAK,SAAS,EACxC,KAAK,KAAK,EAAE,KAAK,QAAQ,EAAE,KAAK,SAAS,EACzC,KAAK,aAAa,EAAE,KAAK,GAAG,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAC1D,KAAK,IAAI,EAAE,KAAK,kBAAkB,EAAE,KAAK,IAAI,EAC7C,KAAK,IAAI,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,EACjC,KAAK,iBAAiB,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EACxD,KAAK,IAAI,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EACxD,KAAK,QAAQ,EAAE,KAAK,KAAK,EAEzB,QAAQ,EAAE,SAAS,EAAE,MAAM,EAE3B,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EACpD,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,EACzD,IAAI,EAAE,SAAS,EAAE,KAAK,EACtB,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EACjE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAE7B,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAElD,QAAQ,EAAE,cAAc,GACzB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,IAAI,EACJ,QAAQ,EACR,KAAK,UAAU,EAAE,KAAK,WAAW,EACjC,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,GACtD,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,QAAQ,EAAE,SAAS,EAAE,cAAc,EACnC,eAAe,EAAE,aAAa,EAC9B,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EACjE,KAAK,aAAa,EAAE,KAAK,cAAc,GACxC,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,QAAQ,EAAE,SAAS,EAAE,aAAa,EAClC,WAAW,EAAE,QAAQ,EACrB,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,GAClE,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,WAAW,EAAE,mBAAmB,EAAE,eAAe,EACjD,YAAY,EAAE,aAAa,EAAE,QAAQ,EACrC,KAAK,UAAU,EAAE,KAAK,aAAa,EACnC,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,GAC7D,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,aAAa,EAAE,cAAc,EAC7B,UAAU,EAAE,gBAAgB,EAAE,kBAAkB,EAChD,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAChD,KAAK,aAAa,EAAE,KAAK,cAAc,GACxC,MAAM,mBAAmB,CAAC"}
|