@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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/dist/blocks.d.ts +30 -0
  4. package/dist/blocks.d.ts.map +1 -0
  5. package/dist/blocks.js +69 -0
  6. package/dist/blocks.js.map +1 -0
  7. package/dist/export.d.ts +41 -0
  8. package/dist/export.d.ts.map +1 -0
  9. package/dist/export.js +70 -0
  10. package/dist/export.js.map +1 -0
  11. package/dist/import.d.ts +35 -0
  12. package/dist/import.d.ts.map +1 -0
  13. package/dist/import.js +156 -0
  14. package/dist/import.js.map +1 -0
  15. package/dist/index.d.ts +4 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +4 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/inline.d.ts +15 -0
  20. package/dist/inline.d.ts.map +1 -0
  21. package/dist/inline.js +48 -0
  22. package/dist/inline.js.map +1 -0
  23. package/dist/lists.d.ts +17 -0
  24. package/dist/lists.d.ts.map +1 -0
  25. package/dist/lists.js +60 -0
  26. package/dist/lists.js.map +1 -0
  27. package/dist/parse-blocks.d.ts +33 -0
  28. package/dist/parse-blocks.d.ts.map +1 -0
  29. package/dist/parse-blocks.js +145 -0
  30. package/dist/parse-blocks.js.map +1 -0
  31. package/dist/parse-numbering.d.ts +24 -0
  32. package/dist/parse-numbering.d.ts.map +1 -0
  33. package/dist/parse-numbering.js +90 -0
  34. package/dist/parse-numbering.js.map +1 -0
  35. package/dist/parse-runs.d.ts +28 -0
  36. package/dist/parse-runs.d.ts.map +1 -0
  37. package/dist/parse-runs.js +230 -0
  38. package/dist/parse-runs.js.map +1 -0
  39. package/dist/parse-tables.d.ts +15 -0
  40. package/dist/parse-tables.d.ts.map +1 -0
  41. package/dist/parse-tables.js +200 -0
  42. package/dist/parse-tables.js.map +1 -0
  43. package/dist/parse-xml.d.ts +26 -0
  44. package/dist/parse-xml.d.ts.map +1 -0
  45. package/dist/parse-xml.js +286 -0
  46. package/dist/parse-xml.js.map +1 -0
  47. package/dist/parts.d.ts +18 -0
  48. package/dist/parts.d.ts.map +1 -0
  49. package/dist/parts.js +102 -0
  50. package/dist/parts.js.map +1 -0
  51. package/dist/runs.d.ts +40 -0
  52. package/dist/runs.d.ts.map +1 -0
  53. package/dist/runs.js +72 -0
  54. package/dist/runs.js.map +1 -0
  55. package/dist/tables.d.ts +18 -0
  56. package/dist/tables.d.ts.map +1 -0
  57. package/dist/tables.js +95 -0
  58. package/dist/tables.js.map +1 -0
  59. package/dist/unzip.d.ts +16 -0
  60. package/dist/unzip.d.ts.map +1 -0
  61. package/dist/unzip.js +108 -0
  62. package/dist/unzip.js.map +1 -0
  63. package/dist/warnings.d.ts +18 -0
  64. package/dist/warnings.d.ts.map +1 -0
  65. package/dist/warnings.js +12 -0
  66. package/dist/warnings.js.map +1 -0
  67. package/dist/xml.d.ts +23 -0
  68. package/dist/xml.d.ts.map +1 -0
  69. package/dist/xml.js +58 -0
  70. package/dist/xml.js.map +1 -0
  71. package/dist/zip.d.ts +15 -0
  72. package/dist/zip.d.ts.map +1 -0
  73. package/dist/zip.js +165 -0
  74. package/dist/zip.js.map +1 -0
  75. 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)
@@ -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":"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"}
@@ -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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"}
@@ -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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"}
@@ -0,0 +1,4 @@
1
+ export { exportDocx, type ExportDocxOptions } from './export.js';
2
+ export { importDocx, type ImportDocxOptions, type ImportDocxResult } from './import.js';
3
+ export { type ImportWarning, type ImportWarningCode } from './warnings.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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,4 @@
1
+ export { exportDocx } from './export.js';
2
+ export { importDocx } from './import.js';
3
+ export {} from './warnings.js';
4
+ //# sourceMappingURL=index.js.map
@@ -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"}
@@ -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"}
@@ -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