@doxi/docx 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/blocks.d.ts +30 -0
- package/dist/blocks.d.ts.map +1 -0
- package/dist/blocks.js +69 -0
- package/dist/blocks.js.map +1 -0
- package/dist/export.d.ts +41 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +70 -0
- package/dist/export.js.map +1 -0
- package/dist/import.d.ts +35 -0
- package/dist/import.d.ts.map +1 -0
- package/dist/import.js +156 -0
- package/dist/import.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/inline.d.ts +15 -0
- package/dist/inline.d.ts.map +1 -0
- package/dist/inline.js +48 -0
- package/dist/inline.js.map +1 -0
- package/dist/lists.d.ts +17 -0
- package/dist/lists.d.ts.map +1 -0
- package/dist/lists.js +60 -0
- package/dist/lists.js.map +1 -0
- package/dist/parse-blocks.d.ts +33 -0
- package/dist/parse-blocks.d.ts.map +1 -0
- package/dist/parse-blocks.js +145 -0
- package/dist/parse-blocks.js.map +1 -0
- package/dist/parse-numbering.d.ts +24 -0
- package/dist/parse-numbering.d.ts.map +1 -0
- package/dist/parse-numbering.js +90 -0
- package/dist/parse-numbering.js.map +1 -0
- package/dist/parse-runs.d.ts +28 -0
- package/dist/parse-runs.d.ts.map +1 -0
- package/dist/parse-runs.js +230 -0
- package/dist/parse-runs.js.map +1 -0
- package/dist/parse-tables.d.ts +15 -0
- package/dist/parse-tables.d.ts.map +1 -0
- package/dist/parse-tables.js +200 -0
- package/dist/parse-tables.js.map +1 -0
- package/dist/parse-xml.d.ts +26 -0
- package/dist/parse-xml.d.ts.map +1 -0
- package/dist/parse-xml.js +286 -0
- package/dist/parse-xml.js.map +1 -0
- package/dist/parts.d.ts +18 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +102 -0
- package/dist/parts.js.map +1 -0
- package/dist/runs.d.ts +40 -0
- package/dist/runs.d.ts.map +1 -0
- package/dist/runs.js +72 -0
- package/dist/runs.js.map +1 -0
- package/dist/tables.d.ts +18 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +95 -0
- package/dist/tables.js.map +1 -0
- package/dist/unzip.d.ts +16 -0
- package/dist/unzip.d.ts.map +1 -0
- package/dist/unzip.js +108 -0
- package/dist/unzip.js.map +1 -0
- package/dist/warnings.d.ts +18 -0
- package/dist/warnings.d.ts.map +1 -0
- package/dist/warnings.js +12 -0
- package/dist/warnings.js.map +1 -0
- package/dist/xml.d.ts +23 -0
- package/dist/xml.d.ts.map +1 -0
- package/dist/xml.js +58 -0
- package/dist/xml.js.map +1 -0
- package/dist/zip.d.ts +15 -0
- package/dist/zip.d.ts.map +1 -0
- package/dist/zip.js +165 -0
- package/dist/zip.js.map +1 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mahbub Hasan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# @doxi/docx
|
|
2
|
+
|
|
3
|
+
DOCX (OOXML) export and import for Doxiva. **Zero runtime dependencies** — hand-written ZIP reader/writer, hand-written OOXML builder, hand-rolled XML parser with a `DOMParser` fast path.
|
|
4
|
+
|
|
5
|
+
> **Status:** v0.10.x preview. Round-trips Doxiva-authored DOCX losslessly within the supported schema subset; foreign docs (Word / LibreOffice / Google Docs export) come in best-effort with explicit warnings.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @doxi/docx @doxi/core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Export
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { exportDocx } from '@doxi/docx'
|
|
17
|
+
|
|
18
|
+
const bytes: Uint8Array = exportDocx(doc) // pure function. Deterministic output.
|
|
19
|
+
// Save to disk, send to server, trigger a browser download — your call.
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Pure function: no DOM, no view state, no IO. Deterministic byte output, suitable for golden-file snapshots.
|
|
23
|
+
|
|
24
|
+
## Import
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { importDocx } from '@doxi/docx'
|
|
28
|
+
|
|
29
|
+
const { doc, warnings } = await importDocx(bytes)
|
|
30
|
+
// warnings: ImportWarning[] — surface in your UI
|
|
31
|
+
view.dispatch(view.state.tr.replace(0, view.state.doc.content.size, doc))
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Async because ZIP decompression goes through the platform `DecompressionStream` (Web Streams). Returns a Doxiva document tree plus structured warnings for anything the importer couldn't handle.
|
|
35
|
+
|
|
36
|
+
## Supported schema subset
|
|
37
|
+
|
|
38
|
+
Paragraphs (alignment + line height), headings, lists (bullet + numbered, multiple instances), tables (incl. colspan/rowspan + header rows), text with bold/italic/underline/color/font-size/font-family marks, hyperlinks, horizontal rule, forced page break, blockquote.
|
|
39
|
+
|
|
40
|
+
## Self-round-trip guarantee
|
|
41
|
+
|
|
42
|
+
`@doxi/docx` ships a property-test harness (`fast-check`) with 200 runs per arbitrary across 5 arbitraries (1000 runs total). Every generator round-trips through `importDocx(exportDocx(doc))` and asserts deep equality against the input.
|
|
43
|
+
|
|
44
|
+
## Honest deferrals
|
|
45
|
+
|
|
46
|
+
- **Images** — `word/media/` binaries and `image*.xml` rels are read past silently; no image nodes materialize yet.
|
|
47
|
+
- **`page_var` field codes** (`PAGE`, `NUMPAGES`) — exported as literal placeholder text; imported as literal text.
|
|
48
|
+
- **Multi-section documents** — only the body's first `<w:sectPr>` is considered.
|
|
49
|
+
- **Headers / footers** from `header*.xml` / `footer*.xml` parts are not yet wired into the imported `pageMeta`.
|
|
50
|
+
- **Track changes / comments / footnotes** — ignored.
|
|
51
|
+
- **DEFLATE compression** — exports are ~2-3× the size of typical `.docx`. Word reads them without complaint.
|
|
52
|
+
|
|
53
|
+
## Security
|
|
54
|
+
|
|
55
|
+
`importDocx` rejects `<w:hyperlink>` targets matching `javascript:`, `vbscript:`, and non-image `data:` URLs. An `'unsafe-content'` `ImportWarning` is emitted so the host UI can surface "1 unsafe link was removed". See [`docs/security-review.md`](../../docs/security-review.md).
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
[MIT](LICENSE)
|
package/dist/blocks.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { DxNode } from '@doxi/core';
|
|
2
|
+
import { type XmlElement } from './xml.js';
|
|
3
|
+
import type { RenderCtx } from './runs.js';
|
|
4
|
+
/** Optional paragraph render hints — set by list renderer / heading wrapper. */
|
|
5
|
+
export interface ParagraphOpts {
|
|
6
|
+
/** Apply a paragraph style (e.g. 'Heading1', 'Quote') via `<w:pStyle>`. */
|
|
7
|
+
readonly style?: string;
|
|
8
|
+
/** Emit a `<w:numPr>` with the given numbering instance + indent level. */
|
|
9
|
+
readonly listNumId?: number;
|
|
10
|
+
readonly listIlvl?: number;
|
|
11
|
+
}
|
|
12
|
+
/** Render a `paragraph` node as `<w:p>`. */
|
|
13
|
+
export declare function renderParagraph(node: DxNode, ctx: RenderCtx, opts?: ParagraphOpts): XmlElement;
|
|
14
|
+
/**
|
|
15
|
+
* Render a `heading` node as `<w:p>` with a Heading{level} style. Levels are
|
|
16
|
+
* clamped to 1..6 to match the styles defined in `parts.ts:stylesXml`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function renderHeading(node: DxNode, ctx: RenderCtx): XmlElement;
|
|
19
|
+
/**
|
|
20
|
+
* Render a `blockquote` by flattening its inner blocks. Every inner block
|
|
21
|
+
* becomes a `Quote`-styled paragraph; that loses headings-inside-blockquote
|
|
22
|
+
* styling, but blockquote-around-heading is not idiomatic enough to justify
|
|
23
|
+
* a richer mapping in v0.7.
|
|
24
|
+
*/
|
|
25
|
+
export declare function renderBlockquote(node: DxNode, ctx: RenderCtx): XmlElement[];
|
|
26
|
+
/** Horizontal rule → empty paragraph with a bottom border. */
|
|
27
|
+
export declare function renderHr(_node: DxNode): XmlElement;
|
|
28
|
+
/** Forced page break → paragraph wrapping a `<w:br w:type="page"/>`. */
|
|
29
|
+
export declare function renderPageBreak(_node: DxNode): XmlElement;
|
|
30
|
+
//# sourceMappingURL=blocks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../src/blocks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAM,KAAK,UAAU,EAAE,MAAM,UAAU,CAAA;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAE1C,gFAAgF;AAChF,MAAM,WAAW,aAAa;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,4CAA4C;AAC5C,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,GAAE,aAAkB,GAAG,UAAU,CAMlG;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,GAAG,UAAU,CAItE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,GAAG,UAAU,EAAE,CAO3E;AAED,8DAA8D;AAC9D,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAQlD;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAMzD"}
|
package/dist/blocks.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { el } from './xml.js';
|
|
2
|
+
import { renderInline } from './inline.js';
|
|
3
|
+
/** Render a `paragraph` node as `<w:p>`. */
|
|
4
|
+
export function renderParagraph(node, ctx, opts = {}) {
|
|
5
|
+
const pPr = pprForParagraph(node.attrs, opts);
|
|
6
|
+
const children = [];
|
|
7
|
+
if (pPr)
|
|
8
|
+
children.push(pPr);
|
|
9
|
+
for (const r of renderInline(node.content, ctx))
|
|
10
|
+
children.push(r);
|
|
11
|
+
return el('w:p', null, ...children);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Render a `heading` node as `<w:p>` with a Heading{level} style. Levels are
|
|
15
|
+
* clamped to 1..6 to match the styles defined in `parts.ts:stylesXml`.
|
|
16
|
+
*/
|
|
17
|
+
export function renderHeading(node, ctx) {
|
|
18
|
+
const raw = node.attrs.level ?? 1;
|
|
19
|
+
const level = Math.min(6, Math.max(1, raw));
|
|
20
|
+
return renderParagraph(node, ctx, { style: `Heading${level}` });
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Render a `blockquote` by flattening its inner blocks. Every inner block
|
|
24
|
+
* becomes a `Quote`-styled paragraph; that loses headings-inside-blockquote
|
|
25
|
+
* styling, but blockquote-around-heading is not idiomatic enough to justify
|
|
26
|
+
* a richer mapping in v0.7.
|
|
27
|
+
*/
|
|
28
|
+
export function renderBlockquote(node, ctx) {
|
|
29
|
+
const out = [];
|
|
30
|
+
for (let i = 0; i < node.content.childCount; i++) {
|
|
31
|
+
const child = node.content.child(i);
|
|
32
|
+
out.push(renderParagraph(child, ctx, { style: 'Quote' }));
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
/** Horizontal rule → empty paragraph with a bottom border. */
|
|
37
|
+
export function renderHr(_node) {
|
|
38
|
+
return el('w:p', null, el('w:pPr', null, el('w:pBdr', null, el('w:bottom', { 'w:val': 'single', 'w:sz': '6', 'w:space': '1', 'w:color': 'auto' }))));
|
|
39
|
+
}
|
|
40
|
+
/** Forced page break → paragraph wrapping a `<w:br w:type="page"/>`. */
|
|
41
|
+
export function renderPageBreak(_node) {
|
|
42
|
+
return el('w:p', null, el('w:r', null, el('w:br', { 'w:type': 'page' })));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build a `<w:pPr>` from a paragraph's attrs + opts. Returns null when no
|
|
46
|
+
* paragraph-level properties are needed.
|
|
47
|
+
*/
|
|
48
|
+
function pprForParagraph(attrs, opts) {
|
|
49
|
+
const children = [];
|
|
50
|
+
if (opts.style)
|
|
51
|
+
children.push(el('w:pStyle', { 'w:val': opts.style }));
|
|
52
|
+
if (opts.listNumId !== undefined) {
|
|
53
|
+
const ilvl = opts.listIlvl ?? 0;
|
|
54
|
+
children.push(el('w:numPr', null, el('w:ilvl', { 'w:val': String(ilvl) }), el('w:numId', { 'w:val': String(opts.listNumId) })));
|
|
55
|
+
}
|
|
56
|
+
const align = attrs.align;
|
|
57
|
+
if (align === 'left' || align === 'center' || align === 'right' || align === 'justify') {
|
|
58
|
+
const map = { left: 'left', center: 'center', right: 'right', justify: 'both' };
|
|
59
|
+
children.push(el('w:jc', { 'w:val': map[align] }));
|
|
60
|
+
}
|
|
61
|
+
const lineHeight = attrs.lineHeight;
|
|
62
|
+
if (typeof lineHeight === 'number' && lineHeight > 0) {
|
|
63
|
+
// Word's single-line is 240 (twentieths of a point). Multiplier × 240 with
|
|
64
|
+
// `w:lineRule="auto"` reproduces a CSS line-height multiplier.
|
|
65
|
+
children.push(el('w:spacing', { 'w:line': String(Math.round(240 * lineHeight)), 'w:lineRule': 'auto' }));
|
|
66
|
+
}
|
|
67
|
+
return children.length ? el('w:pPr', null, ...children) : null;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=blocks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocks.js","sourceRoot":"","sources":["../src/blocks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAmB,MAAM,UAAU,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAY1C,4CAA4C;AAC5C,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,GAAc,EAAE,OAAsB,EAAE;IACpF,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IAC7C,MAAM,QAAQ,GAAiB,EAAE,CAAA;IACjC,IAAI,GAAG;QAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC3B,KAAK,MAAM,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjE,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC,CAAA;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,GAAc;IACxD,MAAM,GAAG,GAAI,IAAI,CAAC,KAAK,CAAC,KAA4B,IAAI,CAAC,CAAA;IACzD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;IAC3C,OAAO,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,UAAU,KAAK,EAAE,EAAE,CAAC,CAAA;AACjE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,GAAc;IAC3D,MAAM,GAAG,GAAiB,EAAE,CAAA;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAW,CAAA;QAC7C,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAA;IAC3D,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,EACnB,EAAE,CAAC,OAAO,EAAE,IAAI,EACd,EAAE,CAAC,QAAQ,EAAE,IAAI,EACf,EAAE,CAAC,UAAU,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CACtF,CACF,CACF,CAAA;AACH,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,EACnB,EAAE,CAAC,KAAK,EAAE,IAAI,EACZ,EAAE,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CACjC,CACF,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CACtB,KAAwC,EACxC,IAAmB;IAEnB,MAAM,QAAQ,GAAiB,EAAE,CAAA;IACjC,IAAI,IAAI,CAAC,KAAK;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;IACtE,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAA;QAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,EAC9B,EAAE,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,EACvC,EAAE,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CACnD,CAAC,CAAA;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAkC,CAAA;IACtD,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACvF,MAAM,GAAG,GAA2B,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAA;QACvG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,KAAK,CAAE,EAAE,CAAC,CAAC,CAAA;IACrD,CAAC;IACD,MAAM,UAAU,GAAG,KAAK,CAAC,UAAuC,CAAA;IAChE,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;QACrD,2EAA2E;QAC3E,+DAA+D;QAC/D,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,UAAU,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;IAC1G,CAAC;IACD,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AAChE,CAAC"}
|
package/dist/export.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { DxNode } from '@doxi/core';
|
|
2
|
+
export interface ExportDocxOptions {
|
|
3
|
+
/** Page size in twips. Default = US Letter (12240 × 15840). */
|
|
4
|
+
readonly pageSize?: {
|
|
5
|
+
widthTwips: number;
|
|
6
|
+
heightTwips: number;
|
|
7
|
+
};
|
|
8
|
+
/** Page margins in twips. Default = 1in on all sides (1440 twips). */
|
|
9
|
+
readonly marginsTwips?: {
|
|
10
|
+
top: number;
|
|
11
|
+
right: number;
|
|
12
|
+
bottom: number;
|
|
13
|
+
left: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface RenderDocumentResult {
|
|
17
|
+
readonly xml: string;
|
|
18
|
+
/** Hyperlinks discovered during render — used to emit document.xml.rels. */
|
|
19
|
+
readonly hyperlinks: ReadonlyArray<{
|
|
20
|
+
id: string;
|
|
21
|
+
target: string;
|
|
22
|
+
}>;
|
|
23
|
+
/** Per-list numbering instances to be emitted in numbering.xml. */
|
|
24
|
+
readonly numberingPlan: ReadonlyArray<{
|
|
25
|
+
numId: number;
|
|
26
|
+
ordered: boolean;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Walk the Doxiva doc tree and emit `word/document.xml`. Side data
|
|
31
|
+
* (hyperlinks, numbering plan) is returned so the caller can wire it into
|
|
32
|
+
* the corresponding parts.
|
|
33
|
+
*/
|
|
34
|
+
export declare function renderDocumentXml(doc: DxNode, opts: ExportDocxOptions): RenderDocumentResult;
|
|
35
|
+
/**
|
|
36
|
+
* Serialize a Doxiva doc tree into a `.docx` (OOXML zip). Pure function —
|
|
37
|
+
* no DOM, no view state, no IO. Output is deterministic so snapshot tests
|
|
38
|
+
* stay stable across runs.
|
|
39
|
+
*/
|
|
40
|
+
export declare function exportDocx(doc: DxNode, opts?: ExportDocxOptions): Uint8Array;
|
|
41
|
+
//# sourceMappingURL=export.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAexC,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAA;IAC/D,sEAAsE;IACtE,QAAQ,CAAC,YAAY,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACrF;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAClE,mEAAmE;IACnE,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CAC3E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,oBAAoB,CA4B5F;AAgBD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,iBAAsB,GAAG,UAAU,CAYhF"}
|
package/dist/export.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { buildZip } from './zip.js';
|
|
2
|
+
import { contentTypesXml, rootRelsXml, stylesXml, numberingXml, documentRelsXml } from './parts.js';
|
|
3
|
+
import { el, renderXml } from './xml.js';
|
|
4
|
+
import { newRenderCtx } from './runs.js';
|
|
5
|
+
import { renderParagraph, renderHeading, renderBlockquote, renderHr, renderPageBreak, } from './blocks.js';
|
|
6
|
+
import { renderList } from './lists.js';
|
|
7
|
+
import { renderTable } from './tables.js';
|
|
8
|
+
/**
|
|
9
|
+
* Walk the Doxiva doc tree and emit `word/document.xml`. Side data
|
|
10
|
+
* (hyperlinks, numbering plan) is returned so the caller can wire it into
|
|
11
|
+
* the corresponding parts.
|
|
12
|
+
*/
|
|
13
|
+
export function renderDocumentXml(doc, opts) {
|
|
14
|
+
const ctx = newRenderCtx();
|
|
15
|
+
const bodyChildren = [];
|
|
16
|
+
for (let i = 0; i < doc.content.childCount; i++) {
|
|
17
|
+
bodyChildren.push(...renderBlock(doc.content.child(i), ctx));
|
|
18
|
+
}
|
|
19
|
+
// Section properties at end of body (page size + margins).
|
|
20
|
+
const pageW = opts.pageSize?.widthTwips ?? 12240;
|
|
21
|
+
const pageH = opts.pageSize?.heightTwips ?? 15840;
|
|
22
|
+
const m = opts.marginsTwips ?? { top: 1440, right: 1440, bottom: 1440, left: 1440 };
|
|
23
|
+
bodyChildren.push(el('w:sectPr', null, el('w:pgSz', { 'w:w': String(pageW), 'w:h': String(pageH) }), el('w:pgMar', {
|
|
24
|
+
'w:top': String(m.top),
|
|
25
|
+
'w:right': String(m.right),
|
|
26
|
+
'w:bottom': String(m.bottom),
|
|
27
|
+
'w:left': String(m.left),
|
|
28
|
+
})));
|
|
29
|
+
const root = el('w:document', {
|
|
30
|
+
'xmlns:w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
|
|
31
|
+
'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
|
|
32
|
+
}, el('w:body', null, ...bodyChildren));
|
|
33
|
+
return {
|
|
34
|
+
xml: renderXml(root, { declaration: true }),
|
|
35
|
+
hyperlinks: ctx.hyperlinks,
|
|
36
|
+
numberingPlan: ctx.numberingPlan,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Top-level block dispatch. Unsupported types are silently dropped. */
|
|
40
|
+
function renderBlock(node, ctx) {
|
|
41
|
+
switch (node.type.name) {
|
|
42
|
+
case 'paragraph': return [renderParagraph(node, ctx)];
|
|
43
|
+
case 'heading': return [renderHeading(node, ctx)];
|
|
44
|
+
case 'blockquote': return renderBlockquote(node, ctx);
|
|
45
|
+
case 'hr': return [renderHr(node)];
|
|
46
|
+
case 'page_break': return [renderPageBreak(node)];
|
|
47
|
+
case 'list': return renderList(node, ctx, 0);
|
|
48
|
+
case 'table': return [renderTable(node, ctx)];
|
|
49
|
+
default: return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Serialize a Doxiva doc tree into a `.docx` (OOXML zip). Pure function —
|
|
54
|
+
* no DOM, no view state, no IO. Output is deterministic so snapshot tests
|
|
55
|
+
* stay stable across runs.
|
|
56
|
+
*/
|
|
57
|
+
export function exportDocx(doc, opts = {}) {
|
|
58
|
+
const { xml, hyperlinks, numberingPlan } = renderDocumentXml(doc, opts);
|
|
59
|
+
const enc = new TextEncoder();
|
|
60
|
+
const entries = [
|
|
61
|
+
{ name: '[Content_Types].xml', data: enc.encode(contentTypesXml()) },
|
|
62
|
+
{ name: '_rels/.rels', data: enc.encode(rootRelsXml()) },
|
|
63
|
+
{ name: 'word/document.xml', data: enc.encode(xml) },
|
|
64
|
+
{ name: 'word/_rels/document.xml.rels', data: enc.encode(documentRelsXml(hyperlinks)) },
|
|
65
|
+
{ name: 'word/styles.xml', data: enc.encode(stylesXml()) },
|
|
66
|
+
{ name: 'word/numbering.xml', data: enc.encode(numberingXml(numberingPlan)) },
|
|
67
|
+
];
|
|
68
|
+
return buildZip(entries);
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"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"}
|
package/dist/import.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `importDocx` — read a `.docx` (OOXML zip) and produce a Doxiva doc tree.
|
|
3
|
+
*
|
|
4
|
+
* Body dispatch walks `w:body` children and routes each to the matching
|
|
5
|
+
* parser:
|
|
6
|
+
* - `w:p` → parseParagraph (Track B). Returns either a plain block or a
|
|
7
|
+
* `list-paragraph` that we accumulate and convert into a `list` node
|
|
8
|
+
* once we see a paragraph with a different numId (Track D).
|
|
9
|
+
* - `w:tbl` → parseTable (Track C).
|
|
10
|
+
* - `w:sectPr` → ignored (single-section v0.8).
|
|
11
|
+
*
|
|
12
|
+
* Best-effort by design: malformed input, unknown elements, and missing
|
|
13
|
+
* parts are reported via `ImportWarning` rather than thrown.
|
|
14
|
+
*/
|
|
15
|
+
import type { DxNode, Schema } from '@doxi/core';
|
|
16
|
+
import { type ImportWarning } from './warnings.js';
|
|
17
|
+
export interface ImportDocxOptions {
|
|
18
|
+
readonly schema?: Schema;
|
|
19
|
+
}
|
|
20
|
+
export interface ImportDocxResult {
|
|
21
|
+
readonly doc: DxNode;
|
|
22
|
+
readonly warnings: ReadonlyArray<ImportWarning>;
|
|
23
|
+
}
|
|
24
|
+
export declare function importDocx(bytes: Uint8Array, opts?: ImportDocxOptions): Promise<ImportDocxResult>;
|
|
25
|
+
/**
|
|
26
|
+
* Parse `word/_rels/document.xml.rels` into a relationship-id → target map.
|
|
27
|
+
* Returns an empty map on malformed input rather than throwing — relationship
|
|
28
|
+
* loss is reported by individual element parsers via `unknown-relationship`
|
|
29
|
+
* warnings instead.
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseRels(xml: string): Map<string, {
|
|
32
|
+
target: string;
|
|
33
|
+
type?: string;
|
|
34
|
+
}>;
|
|
35
|
+
//# sourceMappingURL=import.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../src/import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAIhD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAKpE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;CAChD;AAED,wBAAsB,UAAU,CAC9B,KAAK,EAAE,UAAU,EACjB,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAkG3B;AAcD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAmBrF"}
|
package/dist/import.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `importDocx` — read a `.docx` (OOXML zip) and produce a Doxiva doc tree.
|
|
3
|
+
*
|
|
4
|
+
* Body dispatch walks `w:body` children and routes each to the matching
|
|
5
|
+
* parser:
|
|
6
|
+
* - `w:p` → parseParagraph (Track B). Returns either a plain block or a
|
|
7
|
+
* `list-paragraph` that we accumulate and convert into a `list` node
|
|
8
|
+
* once we see a paragraph with a different numId (Track D).
|
|
9
|
+
* - `w:tbl` → parseTable (Track C).
|
|
10
|
+
* - `w:sectPr` → ignored (single-section v0.8).
|
|
11
|
+
*
|
|
12
|
+
* Best-effort by design: malformed input, unknown elements, and missing
|
|
13
|
+
* parts are reported via `ImportWarning` rather than thrown.
|
|
14
|
+
*/
|
|
15
|
+
import { defaultSchema } from '@doxi/core';
|
|
16
|
+
import { unzip } from './unzip.js';
|
|
17
|
+
import { parseXml } from './parse-xml.js';
|
|
18
|
+
import { WarningCollector } from './warnings.js';
|
|
19
|
+
import { parseParagraph } from './parse-blocks.js';
|
|
20
|
+
import { parseTable } from './parse-tables.js';
|
|
21
|
+
import { parseNumbering } from './parse-numbering.js';
|
|
22
|
+
export async function importDocx(bytes, opts = {}) {
|
|
23
|
+
const schema = (opts.schema ?? defaultSchema);
|
|
24
|
+
const warnings = new WarningCollector();
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = await unzip(bytes);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
warnings.add({ code: 'malformed-xml', message: `Failed to unzip archive: ${err.message}` });
|
|
31
|
+
return { doc: emptyDoc(schema), warnings: warnings.warnings };
|
|
32
|
+
}
|
|
33
|
+
const docEntry = entries.find((e) => e.name === 'word/document.xml');
|
|
34
|
+
if (!docEntry) {
|
|
35
|
+
warnings.add({ code: 'malformed-xml', message: 'No word/document.xml found in archive' });
|
|
36
|
+
return { doc: emptyDoc(schema), warnings: warnings.warnings };
|
|
37
|
+
}
|
|
38
|
+
const relsEntry = entries.find((e) => e.name === 'word/_rels/document.xml.rels');
|
|
39
|
+
const rels = relsEntry
|
|
40
|
+
? parseRels(new TextDecoder().decode(relsEntry.data))
|
|
41
|
+
: new Map();
|
|
42
|
+
const numberingEntry = entries.find((e) => e.name === 'word/numbering.xml');
|
|
43
|
+
const numbering = parseNumbering(numberingEntry ? new TextDecoder().decode(numberingEntry.data) : undefined, warnings);
|
|
44
|
+
let root;
|
|
45
|
+
try {
|
|
46
|
+
root = parseXml(new TextDecoder().decode(docEntry.data)).root;
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
warnings.add({ code: 'malformed-xml', message: err.message, path: 'word/document.xml' });
|
|
50
|
+
return { doc: emptyDoc(schema), warnings: warnings.warnings };
|
|
51
|
+
}
|
|
52
|
+
const ctx = { schema, warnings, rels };
|
|
53
|
+
const body = findChild(root, 'w:body');
|
|
54
|
+
const blocks = [];
|
|
55
|
+
if (body) {
|
|
56
|
+
let listAcc = [];
|
|
57
|
+
const flushList = () => {
|
|
58
|
+
if (listAcc.length === 0)
|
|
59
|
+
return;
|
|
60
|
+
// Group consecutive entries with the same numId into one `list` node.
|
|
61
|
+
let i = 0;
|
|
62
|
+
while (i < listAcc.length) {
|
|
63
|
+
const startNumId = listAcc[i].numId;
|
|
64
|
+
let j = i + 1;
|
|
65
|
+
while (j < listAcc.length && listAcc[j].numId === startNumId)
|
|
66
|
+
j++;
|
|
67
|
+
const items = listAcc.slice(i, j);
|
|
68
|
+
const ordered = numbering.instances.get(startNumId)?.ordered ?? false;
|
|
69
|
+
const listItems = items.map((it) => schema.node('list_item', null, [it.node]));
|
|
70
|
+
blocks.push(schema.node('list', { ordered }, listItems));
|
|
71
|
+
i = j;
|
|
72
|
+
}
|
|
73
|
+
listAcc = [];
|
|
74
|
+
};
|
|
75
|
+
for (const child of body.children) {
|
|
76
|
+
if (typeof child === 'string')
|
|
77
|
+
continue;
|
|
78
|
+
const el = child;
|
|
79
|
+
switch (el.name) {
|
|
80
|
+
case 'w:p': {
|
|
81
|
+
const parsed = parseParagraph(el, ctx);
|
|
82
|
+
if (parsed.kind === 'list-paragraph') {
|
|
83
|
+
listAcc.push({ numId: parsed.numId, ilvl: parsed.ilvl, node: parsed.node });
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
flushList();
|
|
87
|
+
blocks.push(parsed.node);
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case 'w:tbl':
|
|
92
|
+
flushList();
|
|
93
|
+
blocks.push(parseTable(el, ctx));
|
|
94
|
+
break;
|
|
95
|
+
case 'w:sectPr':
|
|
96
|
+
// Section properties carried at the body tail — ignored for import.
|
|
97
|
+
break;
|
|
98
|
+
default:
|
|
99
|
+
flushList();
|
|
100
|
+
warnings.add({
|
|
101
|
+
code: 'unknown-element',
|
|
102
|
+
message: `Unknown body child: ${el.name}`,
|
|
103
|
+
path: 'word/document.xml',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
flushList();
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
warnings.add({ code: 'malformed-xml', message: 'document.xml has no w:body', path: 'word/document.xml' });
|
|
111
|
+
}
|
|
112
|
+
if (blocks.length === 0)
|
|
113
|
+
blocks.push(schema.node('paragraph', null));
|
|
114
|
+
return { doc: schema.node('doc', null, blocks), warnings: warnings.warnings };
|
|
115
|
+
}
|
|
116
|
+
function emptyDoc(schema) {
|
|
117
|
+
return schema.node('doc', null, [schema.node('paragraph', null)]);
|
|
118
|
+
}
|
|
119
|
+
function findChild(el, name) {
|
|
120
|
+
for (const c of el.children) {
|
|
121
|
+
if (typeof c === 'string')
|
|
122
|
+
continue;
|
|
123
|
+
if (c.name === name)
|
|
124
|
+
return c;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Parse `word/_rels/document.xml.rels` into a relationship-id → target map.
|
|
130
|
+
* Returns an empty map on malformed input rather than throwing — relationship
|
|
131
|
+
* loss is reported by individual element parsers via `unknown-relationship`
|
|
132
|
+
* warnings instead.
|
|
133
|
+
*/
|
|
134
|
+
export function parseRels(xml) {
|
|
135
|
+
const result = new Map();
|
|
136
|
+
try {
|
|
137
|
+
const root = parseXml(xml).root;
|
|
138
|
+
for (const c of root.children) {
|
|
139
|
+
if (typeof c === 'string')
|
|
140
|
+
continue;
|
|
141
|
+
if (c.name === 'Relationship') {
|
|
142
|
+
const id = c.attrs.Id;
|
|
143
|
+
const target = c.attrs.Target;
|
|
144
|
+
const type = c.attrs.Type;
|
|
145
|
+
if (id && target) {
|
|
146
|
+
result.set(id, type !== undefined ? { target, type } : { target });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
/* malformed rels: return whatever we got */
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=import.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import.js","sourceRoot":"","sources":["../src/import.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACvF,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA0B,MAAM,aAAa,CAAA;AAChE,OAAO,EAAE,UAAU,EAAiD,MAAM,aAAa,CAAA;AACvF,OAAO,EAA8C,MAAM,eAAe,CAAA"}
|
package/dist/inline.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Fragment } from '@doxi/core';
|
|
2
|
+
import { type XmlElement } from './xml.js';
|
|
3
|
+
import { type RenderCtx } from './runs.js';
|
|
4
|
+
/**
|
|
5
|
+
* Render a Fragment of inline content into a flat list of `<w:r>` or
|
|
6
|
+
* `<w:hyperlink>` elements.
|
|
7
|
+
*
|
|
8
|
+
* - text with a `link` mark → wrapped in `<w:hyperlink r:id="…">`; a
|
|
9
|
+
* relationship is recorded on `ctx.hyperlinks` so documentRelsXml can
|
|
10
|
+
* later emit the matching `<Relationship Type="…/hyperlink">`.
|
|
11
|
+
* - image and page_var nodes are NOT in the v0.7 scope — we emit a literal
|
|
12
|
+
* placeholder run so the doc tree is still observable in the export.
|
|
13
|
+
*/
|
|
14
|
+
export declare function renderInline(content: Fragment, ctx: RenderCtx): XmlElement[];
|
|
15
|
+
//# sourceMappingURL=inline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline.d.ts","sourceRoot":"","sources":["../src/inline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,QAAQ,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,EAAM,KAAK,UAAU,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,WAAW,CAAA;AAEzD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,GAAG,UAAU,EAAE,CA+B5E"}
|
package/dist/inline.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { el } from './xml.js';
|
|
2
|
+
import { renderTextRun } from './runs.js';
|
|
3
|
+
/**
|
|
4
|
+
* Render a Fragment of inline content into a flat list of `<w:r>` or
|
|
5
|
+
* `<w:hyperlink>` elements.
|
|
6
|
+
*
|
|
7
|
+
* - text with a `link` mark → wrapped in `<w:hyperlink r:id="…">`; a
|
|
8
|
+
* relationship is recorded on `ctx.hyperlinks` so documentRelsXml can
|
|
9
|
+
* later emit the matching `<Relationship Type="…/hyperlink">`.
|
|
10
|
+
* - image and page_var nodes are NOT in the v0.7 scope — we emit a literal
|
|
11
|
+
* placeholder run so the doc tree is still observable in the export.
|
|
12
|
+
*/
|
|
13
|
+
export function renderInline(content, ctx) {
|
|
14
|
+
const out = [];
|
|
15
|
+
for (let i = 0; i < content.childCount; i++) {
|
|
16
|
+
const child = content.child(i);
|
|
17
|
+
const name = child.type.name;
|
|
18
|
+
if (child.isText) {
|
|
19
|
+
const linkMark = child.marks.find((m) => m.type.name === 'link');
|
|
20
|
+
if (linkMark) {
|
|
21
|
+
const href = linkMark.attrs.href ?? '';
|
|
22
|
+
const rid = `rIdLink${ctx.ridCounter.value++}`;
|
|
23
|
+
ctx.hyperlinks.push({ id: rid, target: href });
|
|
24
|
+
// `xmlns:r` is declared on the `<w:document>` root in export.ts —
|
|
25
|
+
// no need to re-declare it per element.
|
|
26
|
+
out.push(el('w:hyperlink', { 'r:id': rid }, renderTextRun(child)));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
out.push(renderTextRun(child));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (name === 'image') {
|
|
33
|
+
// v0.7 doesn't ship image embedding (needs word/media + binary rels).
|
|
34
|
+
// Emit a literal placeholder so the source intent is preserved.
|
|
35
|
+
const src = child.attrs.src ?? '';
|
|
36
|
+
out.push(el('w:r', null, el('w:t', { 'xml:space': 'preserve' }, `[image:${src}]`)));
|
|
37
|
+
}
|
|
38
|
+
else if (name === 'page_var') {
|
|
39
|
+
// v0.7 doesn't ship Word field codes (PAGE / NUMPAGES). Emit a
|
|
40
|
+
// literal token so the doc remains visually readable.
|
|
41
|
+
const kind = child.attrs.kind ?? '';
|
|
42
|
+
out.push(el('w:r', null, el('w:t', { 'xml:space': 'preserve' }, `{${kind}}`)));
|
|
43
|
+
}
|
|
44
|
+
// Other inline atoms are not in v0.7 scope — silently skip.
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=inline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline.js","sourceRoot":"","sources":["../src/inline.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAmB,MAAM,UAAU,CAAA;AAC9C,OAAO,EAAE,aAAa,EAAkB,MAAM,WAAW,CAAA;AAEzD;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,OAAiB,EAAE,GAAc;IAC5D,MAAM,GAAG,GAAiB,EAAE,CAAA;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAW,CAAA;QACxC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAA;QAC5B,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAA;YAChE,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,IAAI,GAAI,QAAQ,CAAC,KAAK,CAAC,IAA2B,IAAI,EAAE,CAAA;gBAC9D,MAAM,GAAG,GAAG,UAAU,GAAG,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,CAAA;gBAC9C,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;gBAC9C,kEAAkE;gBAClE,wCAAwC;gBACxC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YACpE,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAA;YAChC,CAAC;QACH,CAAC;aAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YAC5B,sEAAsE;YACtE,gEAAgE;YAChE,MAAM,GAAG,GAAI,KAAK,CAAC,KAAK,CAAC,GAA0B,IAAI,EAAE,CAAA;YACzD,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE,UAAU,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;QACrF,CAAC;aAAM,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;YAC/B,+DAA+D;YAC/D,sDAAsD;YACtD,MAAM,IAAI,GAAI,KAAK,CAAC,KAAK,CAAC,IAA2B,IAAI,EAAE,CAAA;YAC3D,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAA;QAChF,CAAC;QACD,4DAA4D;IAC9D,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
|
package/dist/lists.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DxNode } from '@doxi/core';
|
|
2
|
+
import type { XmlElement } from './xml.js';
|
|
3
|
+
import type { RenderCtx } from './runs.js';
|
|
4
|
+
/**
|
|
5
|
+
* Render a Doxiva `list` node as a flat sequence of `<w:p>` elements.
|
|
6
|
+
*
|
|
7
|
+
* Numbering model:
|
|
8
|
+
* - Each Doxiva `list` node gets its OWN numId so consecutive lists
|
|
9
|
+
* restart numbering. The numId is allocated on first sight via
|
|
10
|
+
* `ctx.numIdCounter` (starts at 3 to avoid the predefined 1/2 in
|
|
11
|
+
* numbering.xml) and recorded on `ctx.numberingPlan` so the numbering
|
|
12
|
+
* part can emit a matching `<w:num>` instance.
|
|
13
|
+
* - Nested lists increase `ilvl` and ALSO get a fresh numId — the
|
|
14
|
+
* numbering.xml abstractNum definitions provide ilvl 0 and 1 levels.
|
|
15
|
+
*/
|
|
16
|
+
export declare function renderList(list: DxNode, ctx: RenderCtx, ilvl: number): XmlElement[];
|
|
17
|
+
//# sourceMappingURL=lists.d.ts.map
|