@cj-tech-master/excelts 9.5.4 → 9.5.5
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/dist/browser/modules/archive/compression/streaming-compress.browser.js +29 -0
- package/dist/browser/modules/archive/compression/streaming-compress.js +9 -0
- package/dist/browser/modules/archive/compression/worker-pool/pool.browser.js +26 -1
- package/dist/browser/modules/archive/fs/archive-file.d.ts +8 -5
- package/dist/browser/modules/archive/fs/archive-file.js +78 -16
- package/dist/browser/modules/archive/unzip/stream.browser.js +43 -2
- package/dist/browser/modules/excel/chart/chart-ex-builder.js +7 -2
- package/dist/browser/modules/excel/chart/chart-ex-renderer.js +4 -9
- package/dist/browser/modules/excel/chart/chart-ex-types.d.ts +0 -12
- package/dist/browser/modules/excel/chart/chart.d.ts +1 -5
- package/dist/browser/modules/excel/chart/chart.js +1 -7
- package/dist/browser/modules/excel/chart/types.d.ts +0 -6
- package/dist/browser/modules/excel/stream/workbook-reader.browser.js +25 -1
- package/dist/browser/modules/excel/stream/workbook-reader.js +9 -0
- package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +40 -0
- package/dist/browser/modules/excel/stream/workbook-writer.browser.js +228 -13
- package/dist/browser/modules/excel/utils/string-buf.d.ts +5 -26
- package/dist/browser/modules/excel/utils/string-buf.js +4 -81
- package/dist/browser/modules/excel/workbook.browser.js +135 -25
- package/dist/browser/modules/excel/xlsx/xform/chart/chart-space-xform.js +6 -20
- package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +19 -9
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +32 -8
- package/dist/browser/modules/excel/xlsx/xlsx.d.ts +10 -2
- package/dist/browser/modules/excel/xlsx/xlsx.js +9 -1
- package/dist/browser/modules/pdf/excel-bridge.d.ts +30 -1
- package/dist/browser/modules/pdf/excel-bridge.js +32 -0
- package/dist/browser/modules/pdf/font/metrics.d.ts +3 -52
- package/dist/browser/modules/pdf/font/metrics.js +3 -237
- package/dist/browser/modules/pdf/index.d.ts +1 -1
- package/dist/browser/modules/pdf/index.js +1 -1
- package/dist/browser/modules/pdf/render-layout-to-pdf.d.ts +66 -0
- package/dist/browser/modules/pdf/render-layout-to-pdf.js +647 -0
- package/dist/browser/modules/pdf/word-bridge.d.ts +80 -12
- package/dist/browser/modules/pdf/word-bridge.js +122 -274
- package/dist/browser/modules/stream/index.base.d.ts +2 -0
- package/dist/browser/modules/stream/index.base.js +2 -1
- package/dist/browser/modules/stream/internal/sink-adapter.d.ts +65 -0
- package/dist/browser/modules/stream/internal/sink-adapter.js +198 -0
- package/dist/browser/modules/stream/pull-stream.d.ts +19 -2
- package/dist/browser/modules/stream/pull-stream.js +51 -5
- package/dist/browser/modules/stream/types.d.ts +13 -1
- package/dist/browser/modules/word/advanced/diff.d.ts +61 -0
- package/dist/browser/modules/word/advanced/diff.js +167 -0
- package/dist/browser/modules/word/advanced/drawing-shapes.d.ts +269 -0
- package/dist/browser/modules/word/advanced/drawing-shapes.js +268 -0
- package/dist/browser/modules/word/advanced/field-engine.d.ts +43 -0
- package/dist/browser/modules/word/advanced/field-engine.js +1225 -0
- package/dist/browser/modules/word/advanced/glossary.d.ts +86 -0
- package/dist/browser/modules/word/advanced/glossary.js +79 -0
- package/dist/browser/modules/word/advanced/math-convert.d.ts +30 -0
- package/dist/browser/modules/word/advanced/math-convert.js +595 -0
- package/dist/browser/modules/word/advanced/ole-objects.d.ts +115 -0
- package/dist/browser/modules/word/advanced/ole-objects.js +271 -0
- package/dist/browser/modules/word/advanced/style-map.d.ts +105 -0
- package/dist/browser/modules/word/advanced/style-map.js +322 -0
- package/dist/browser/modules/word/advanced/validation.d.ts +56 -0
- package/dist/browser/modules/word/advanced/validation.js +1065 -0
- package/dist/browser/modules/word/advanced/vba-project.d.ts +91 -0
- package/dist/browser/modules/word/advanced/vba-project.js +265 -0
- package/dist/browser/modules/word/bridge/excel-bridge.d.ts +127 -0
- package/dist/browser/modules/word/bridge/excel-bridge.js +980 -0
- package/dist/browser/modules/word/builder/document-handle.d.ts +151 -0
- package/dist/browser/modules/word/builder/document-handle.js +664 -0
- package/dist/browser/modules/word/builder/paragraph-builders.d.ts +61 -0
- package/dist/browser/modules/word/builder/paragraph-builders.js +90 -0
- package/dist/browser/modules/word/builder/run-builders.d.ts +374 -0
- package/dist/browser/modules/word/builder/run-builders.js +600 -0
- package/dist/browser/modules/word/builder/table-builders.d.ts +23 -0
- package/dist/browser/modules/word/builder/table-builders.js +45 -0
- package/dist/browser/modules/word/constants.d.ts +39 -1
- package/dist/browser/modules/word/constants.js +109 -1
- package/dist/browser/modules/word/convert/conversion-ir.d.ts +210 -0
- package/dist/browser/modules/word/convert/conversion-ir.js +31 -0
- package/dist/browser/modules/word/convert/docx-to-semantic.d.ts +39 -0
- package/dist/browser/modules/word/convert/docx-to-semantic.js +499 -0
- package/dist/browser/modules/word/convert/flat-opc.d.ts +44 -0
- package/dist/browser/modules/word/convert/flat-opc.js +385 -0
- package/dist/browser/modules/word/convert/html/html-import.d.ts +50 -0
- package/dist/browser/modules/word/convert/html/html-import.js +1907 -0
- package/dist/{types/modules/word → browser/modules/word/convert/html}/html-renderer.d.ts +14 -1
- package/dist/{esm/modules/word → browser/modules/word/convert/html}/html-renderer.js +420 -69
- package/dist/browser/modules/word/convert/html/html.d.ts +15 -0
- package/dist/browser/modules/word/convert/html/html.js +15 -0
- package/dist/browser/modules/word/convert/markdown/markdown-import.d.ts +68 -0
- package/dist/browser/modules/word/convert/markdown/markdown-import.js +1325 -0
- package/dist/browser/modules/word/convert/markdown/markdown-renderer.d.ts +25 -0
- package/dist/browser/modules/word/convert/markdown/markdown-renderer.js +634 -0
- package/dist/browser/modules/word/convert/markdown/markdown.d.ts +15 -0
- package/dist/browser/modules/word/convert/markdown/markdown.js +15 -0
- package/dist/browser/modules/word/convert/odt/odt.d.ts +41 -0
- package/dist/browser/modules/word/convert/odt/odt.js +1932 -0
- package/dist/browser/modules/word/{color-utils.d.ts → core/color-utils.d.ts} +8 -1
- package/dist/browser/modules/word/core/color-utils.js +43 -0
- package/dist/browser/modules/word/core/internal-utils.d.ts +90 -0
- package/dist/browser/modules/word/core/internal-utils.js +209 -0
- package/dist/browser/modules/word/core/mapper.d.ts +44 -0
- package/dist/browser/modules/word/core/mapper.js +427 -0
- package/dist/browser/modules/word/core/opc-paths.d.ts +33 -0
- package/dist/browser/modules/word/core/opc-paths.js +48 -0
- package/dist/browser/modules/word/core/text-utils.d.ts +38 -0
- package/dist/browser/modules/word/core/text-utils.js +202 -0
- package/dist/browser/modules/word/core/walker.d.ts +119 -0
- package/dist/browser/modules/word/core/walker.js +570 -0
- package/dist/browser/modules/word/crypto.d.ts +14 -9
- package/dist/browser/modules/word/crypto.js +13 -7
- package/dist/browser/modules/word/document-io.d.ts +59 -27
- package/dist/browser/modules/word/document-io.js +80 -197
- package/dist/browser/modules/word/errors.d.ts +44 -1
- package/dist/browser/modules/word/errors.js +54 -2
- package/dist/browser/modules/word/excel.d.ts +14 -0
- package/dist/browser/modules/word/excel.js +13 -0
- package/dist/browser/modules/word/font/font-embed.d.ts +112 -0
- package/dist/browser/modules/word/font/font-embed.js +646 -0
- package/dist/{esm/modules/word → browser/modules/word/font}/font-obfuscation.js +4 -9
- package/dist/browser/modules/word/font/hyphenation.d.ts +65 -0
- package/dist/browser/modules/word/font/hyphenation.js +4210 -0
- package/dist/browser/modules/word/font/text-shaping.d.ts +58 -0
- package/dist/browser/modules/word/font/text-shaping.js +635 -0
- package/dist/browser/modules/word/html.d.ts +7 -6
- package/dist/browser/modules/word/html.js +6 -5
- package/dist/browser/modules/word/incremental-edit.d.ts +123 -0
- package/dist/browser/modules/word/incremental-edit.js +361 -0
- package/dist/browser/modules/word/index.base.d.ts +194 -10
- package/dist/browser/modules/word/index.base.js +138 -29
- package/dist/browser/modules/word/layout/layout-constants.d.ts +17 -0
- package/dist/browser/modules/word/layout/layout-constants.js +17 -0
- package/dist/browser/modules/word/layout/layout-full.d.ts +53 -0
- package/dist/browser/modules/word/layout/layout-full.js +1696 -0
- package/dist/browser/modules/word/layout/layout-model.d.ts +344 -0
- package/dist/browser/modules/word/layout/layout-model.js +16 -0
- package/dist/browser/modules/word/layout/layout.d.ts +63 -0
- package/dist/browser/modules/word/layout/layout.js +1167 -0
- package/dist/browser/modules/word/layout/render-page.d.ts +57 -0
- package/dist/browser/modules/word/layout/render-page.js +1238 -0
- package/dist/browser/modules/word/markdown.d.ts +14 -0
- package/dist/browser/modules/word/markdown.js +13 -0
- package/dist/browser/modules/word/patcher.d.ts +62 -0
- package/dist/browser/modules/word/patcher.js +537 -0
- package/dist/browser/modules/word/query/compat.d.ts +25 -0
- package/dist/browser/modules/word/query/compat.js +58 -0
- package/dist/browser/modules/word/query/data-binding.d.ts +22 -0
- package/dist/browser/modules/word/query/data-binding.js +392 -0
- package/dist/browser/modules/word/query/form-fields.d.ts +41 -0
- package/dist/browser/modules/word/query/form-fields.js +268 -0
- package/dist/browser/modules/word/query/format-search.d.ts +99 -0
- package/dist/browser/modules/word/query/format-search.js +329 -0
- package/dist/browser/modules/word/query/mail-merge.d.ts +25 -0
- package/dist/browser/modules/word/query/mail-merge.js +111 -0
- package/dist/browser/modules/word/query/merge.d.ts +50 -0
- package/dist/browser/modules/word/query/merge.js +617 -0
- package/dist/browser/modules/word/query/replace.d.ts +47 -0
- package/dist/browser/modules/word/query/replace.js +301 -0
- package/dist/browser/modules/word/query/revisions.d.ts +67 -0
- package/dist/browser/modules/word/query/revisions.js +879 -0
- package/dist/browser/modules/word/query/search.d.ts +129 -0
- package/dist/browser/modules/word/query/search.js +346 -0
- package/dist/browser/modules/word/query/split.d.ts +44 -0
- package/dist/browser/modules/word/query/split.js +135 -0
- package/dist/browser/modules/word/query/style-resolve.d.ts +104 -0
- package/dist/browser/modules/word/query/style-resolve.js +368 -0
- package/dist/browser/modules/word/reader/chart-parser.d.ts +20 -0
- package/dist/browser/modules/word/reader/chart-parser.js +810 -0
- package/dist/browser/modules/word/reader/comments-parser.d.ts +26 -0
- package/dist/browser/modules/word/reader/comments-parser.js +92 -0
- package/dist/browser/modules/word/reader/doc-props-parsers.d.ts +15 -0
- package/dist/browser/modules/word/reader/doc-props-parsers.js +190 -0
- package/dist/browser/modules/word/reader/docx-reader.d.ts +27 -0
- package/dist/browser/modules/word/reader/docx-reader.js +2557 -0
- package/dist/browser/modules/word/reader/drawing-helpers.d.ts +27 -0
- package/dist/browser/modules/word/reader/drawing-helpers.js +84 -0
- package/dist/browser/modules/word/reader/form-field-parser.d.ts +21 -0
- package/dist/browser/modules/word/reader/form-field-parser.js +82 -0
- package/dist/browser/modules/word/reader/image-parsers.d.ts +11 -0
- package/dist/browser/modules/word/reader/image-parsers.js +291 -0
- package/dist/browser/modules/word/reader/math-parser.d.ts +12 -0
- package/dist/browser/modules/word/reader/math-parser.js +422 -0
- package/dist/browser/modules/word/reader/metadata-parsers.d.ts +17 -0
- package/dist/browser/modules/word/reader/metadata-parsers.js +87 -0
- package/dist/browser/modules/word/reader/numbering-parser.d.ts +13 -0
- package/dist/browser/modules/word/reader/numbering-parser.js +166 -0
- package/dist/browser/modules/word/reader/paragraph-section-parsers.d.ts +12 -0
- package/dist/browser/modules/word/reader/paragraph-section-parsers.js +503 -0
- package/dist/browser/modules/word/reader/parse-utils.d.ts +91 -0
- package/dist/browser/modules/word/reader/parse-utils.js +249 -0
- package/dist/browser/modules/word/reader/properties-parsers.d.ts +21 -0
- package/dist/browser/modules/word/reader/properties-parsers.js +332 -0
- package/dist/browser/modules/word/reader/reader-context.d.ts +69 -0
- package/dist/browser/modules/word/reader/reader-context.js +61 -0
- package/dist/browser/modules/word/reader/sdt-helpers.d.ts +29 -0
- package/dist/browser/modules/word/reader/sdt-helpers.js +111 -0
- package/dist/browser/modules/word/reader/settings-parser.d.ts +8 -0
- package/dist/browser/modules/word/reader/settings-parser.js +263 -0
- package/dist/browser/modules/word/reader/styles-parser.d.ts +12 -0
- package/dist/browser/modules/word/reader/styles-parser.js +147 -0
- package/dist/browser/modules/word/reader/table-properties-parsers.d.ts +12 -0
- package/dist/browser/modules/word/reader/table-properties-parsers.js +234 -0
- package/dist/browser/modules/word/reader/theme-parser.d.ts +8 -0
- package/dist/browser/modules/word/reader/theme-parser.js +167 -0
- package/dist/browser/modules/word/reader/watermark-parser.d.ts +15 -0
- package/dist/browser/modules/word/reader/watermark-parser.js +110 -0
- package/dist/browser/modules/word/security/cfb-reader.d.ts +37 -0
- package/dist/browser/modules/word/security/cfb-reader.js +410 -0
- package/dist/browser/modules/word/{digital-signatures.d.ts → security/digital-signatures.d.ts} +19 -11
- package/dist/browser/modules/word/{digital-signatures.js → security/digital-signatures.js} +34 -34
- package/dist/browser/modules/word/security/document-protection.d.ts +93 -0
- package/dist/browser/modules/word/security/document-protection.js +201 -0
- package/dist/{types/modules/word → browser/modules/word/security}/encryption.d.ts +51 -4
- package/dist/browser/modules/word/security/encryption.js +602 -0
- package/dist/browser/modules/word/security/policy.d.ts +80 -0
- package/dist/browser/modules/word/security/policy.js +102 -0
- package/dist/browser/modules/word/template/template-chart.d.ts +56 -0
- package/dist/browser/modules/word/template/template-chart.js +167 -0
- package/dist/browser/modules/word/template/template-datasource.d.ts +154 -0
- package/dist/browser/modules/word/template/template-datasource.js +541 -0
- package/dist/browser/modules/word/template/template-engine.d.ts +121 -0
- package/dist/browser/modules/word/template/template-engine.js +1435 -0
- package/dist/browser/modules/word/types.d.ts +224 -25
- package/dist/browser/modules/word/units.d.ts +26 -0
- package/dist/browser/modules/word/units.js +43 -14
- package/dist/browser/modules/word/{writers → writer}/chart-writer.js +164 -23
- package/dist/browser/modules/word/writer/checkbox-writer.d.ts +17 -0
- package/dist/browser/modules/word/writer/checkbox-writer.js +79 -0
- package/dist/{types/modules/word/writers → browser/modules/word/writer}/comment-writer.d.ts +2 -1
- package/dist/browser/modules/word/{writers → writer}/comment-writer.js +8 -6
- package/dist/browser/modules/word/writer/common-parts.d.ts +57 -0
- package/dist/browser/modules/word/writer/common-parts.js +101 -0
- package/dist/{types/modules/word → browser/modules/word/writer}/content-types.d.ts +2 -2
- package/dist/{esm/modules/word → browser/modules/word/writer}/content-types.js +14 -6
- package/dist/browser/modules/word/writer/document-writer.d.ts +24 -0
- package/dist/browser/modules/word/writer/document-writer.js +473 -0
- package/dist/browser/modules/word/writer/docx-packager.d.ts +35 -0
- package/dist/browser/modules/word/writer/docx-packager.js +1515 -0
- package/dist/{types/modules/word/writers → browser/modules/word/writer}/footnote-writer.d.ts +3 -2
- package/dist/{esm/modules/word/writers → browser/modules/word/writer}/footnote-writer.js +13 -10
- package/dist/{types/modules/word/writers → browser/modules/word/writer}/header-footer-writer.d.ts +3 -2
- package/dist/{esm/modules/word/writers → browser/modules/word/writer}/header-footer-writer.js +39 -21
- package/dist/{types/modules/word/writers → browser/modules/word/writer}/image-writer.d.ts +1 -1
- package/dist/browser/modules/word/{writers → writer}/image-writer.js +11 -7
- package/dist/browser/modules/word/writer/math-writer.d.ts +20 -0
- package/dist/{esm/modules/word/writers → browser/modules/word/writer}/math-writer.js +21 -1
- package/dist/browser/modules/word/{writers → writer}/numbering-writer.d.ts +1 -1
- package/dist/{esm/modules/word/writers → browser/modules/word/writer}/numbering-writer.js +11 -4
- package/dist/browser/modules/word/{writers → writer}/paragraph-writer.d.ts +2 -1
- package/dist/browser/modules/word/{writers → writer}/paragraph-writer.js +73 -38
- package/dist/browser/modules/word/{writers → writer}/parts-writer.d.ts +3 -3
- package/dist/{esm/modules/word/writers → browser/modules/word/writer}/parts-writer.js +91 -12
- package/dist/browser/modules/word/writer/reference-scanners.d.ts +42 -0
- package/dist/browser/modules/word/writer/reference-scanners.js +111 -0
- package/dist/browser/modules/word/writer/relationships.d.ts +52 -0
- package/dist/browser/modules/word/writer/relationships.js +117 -0
- package/dist/browser/modules/word/writer/render-context.d.ts +124 -0
- package/dist/browser/modules/word/writer/render-context.js +46 -0
- package/dist/browser/modules/word/{writers → writer}/run-writer.d.ts +10 -1
- package/dist/{esm/modules/word/writers → browser/modules/word/writer}/run-writer.js +126 -24
- package/dist/browser/modules/word/writer/sdt-writer.d.ts +25 -0
- package/dist/browser/modules/word/writer/sdt-writer.js +189 -0
- package/dist/browser/modules/word/writer/stream-buf.d.ts +37 -0
- package/dist/browser/modules/word/writer/stream-buf.js +73 -0
- package/dist/browser/modules/word/writer/streaming-writer.d.ts +344 -0
- package/dist/browser/modules/word/writer/streaming-writer.js +1382 -0
- package/dist/browser/modules/word/writer/string-buf.d.ts +8 -0
- package/dist/browser/modules/word/writer/string-buf.js +7 -0
- package/dist/browser/modules/word/{writers → writer}/styles-writer.js +32 -1
- package/dist/browser/modules/word/{writers → writer}/table-writer.d.ts +2 -1
- package/dist/browser/modules/word/{writers → writer}/table-writer.js +94 -11
- package/dist/browser/modules/xml/types.d.ts +22 -0
- package/dist/browser/utils/crypto.browser.d.ts +3 -1
- package/dist/browser/utils/crypto.browser.js +3 -1
- package/dist/browser/utils/crypto.d.ts +4 -1
- package/dist/browser/utils/crypto.js +4 -1
- package/dist/browser/utils/font-metrics.d.ts +63 -0
- package/dist/browser/utils/font-metrics.js +293 -0
- package/dist/browser/utils/string-buf.d.ts +42 -0
- package/dist/browser/utils/string-buf.js +89 -0
- package/dist/browser/utils/theme-colors.d.ts +55 -0
- package/dist/browser/utils/theme-colors.js +120 -0
- package/dist/cjs/modules/archive/compression/streaming-compress.browser.js +29 -0
- package/dist/cjs/modules/archive/compression/streaming-compress.js +9 -0
- package/dist/cjs/modules/archive/compression/worker-pool/pool.browser.js +26 -1
- package/dist/cjs/modules/archive/fs/archive-file.js +78 -16
- package/dist/cjs/modules/archive/unzip/stream.browser.js +43 -2
- package/dist/cjs/modules/excel/chart/chart-ex-builder.js +7 -2
- package/dist/cjs/modules/excel/chart/chart-ex-renderer.js +4 -9
- package/dist/cjs/modules/excel/chart/chart.js +1 -7
- package/dist/cjs/modules/excel/stream/workbook-reader.browser.js +25 -1
- package/dist/cjs/modules/excel/stream/workbook-reader.js +9 -0
- package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +228 -13
- package/dist/cjs/modules/excel/utils/string-buf.js +5 -81
- package/dist/cjs/modules/excel/workbook.browser.js +135 -25
- package/dist/cjs/modules/excel/xlsx/xform/chart/chart-space-xform.js +6 -20
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +32 -8
- package/dist/cjs/modules/excel/xlsx/xlsx.js +9 -1
- package/dist/cjs/modules/pdf/excel-bridge.js +33 -0
- package/dist/cjs/modules/pdf/font/metrics.js +11 -244
- package/dist/cjs/modules/pdf/index.js +2 -1
- package/dist/cjs/modules/pdf/render-layout-to-pdf.js +651 -0
- package/dist/cjs/modules/pdf/word-bridge.js +155 -274
- package/dist/cjs/modules/stream/index.base.js +4 -2
- package/dist/cjs/modules/stream/internal/sink-adapter.js +202 -0
- package/dist/cjs/modules/stream/pull-stream.js +51 -5
- package/dist/cjs/modules/word/advanced/diff.js +170 -0
- package/dist/cjs/modules/word/advanced/drawing-shapes.js +279 -0
- package/dist/cjs/modules/word/advanced/field-engine.js +1229 -0
- package/dist/cjs/modules/word/advanced/glossary.js +87 -0
- package/dist/cjs/modules/word/advanced/math-convert.js +599 -0
- package/dist/cjs/modules/word/advanced/ole-objects.js +277 -0
- package/dist/cjs/modules/word/advanced/style-map.js +329 -0
- package/dist/cjs/modules/word/advanced/validation.js +1068 -0
- package/dist/cjs/modules/word/advanced/vba-project.js +274 -0
- package/dist/cjs/modules/word/bridge/excel-bridge.js +1020 -0
- package/dist/cjs/modules/word/builder/document-handle.js +667 -0
- package/dist/cjs/modules/word/builder/paragraph-builders.js +109 -0
- package/dist/cjs/modules/word/builder/run-builders.js +676 -0
- package/dist/cjs/modules/word/builder/table-builders.js +53 -0
- package/dist/cjs/modules/word/constants.js +111 -2
- package/dist/cjs/modules/word/convert/conversion-ir.js +34 -0
- package/dist/cjs/modules/word/convert/docx-to-semantic.js +502 -0
- package/dist/cjs/modules/word/convert/flat-opc.js +390 -0
- package/dist/cjs/modules/word/convert/html/html-import.js +1910 -0
- package/dist/cjs/modules/word/{html-renderer.js → convert/html/html-renderer.js} +420 -69
- package/dist/cjs/modules/word/convert/html/html.js +20 -0
- package/dist/cjs/modules/word/convert/markdown/markdown-import.js +1329 -0
- package/dist/cjs/modules/word/convert/markdown/markdown-renderer.js +637 -0
- package/dist/cjs/modules/word/convert/markdown/markdown.js +21 -0
- package/dist/cjs/modules/word/convert/odt/odt.js +1936 -0
- package/dist/cjs/modules/word/core/color-utils.js +47 -0
- package/dist/cjs/modules/word/core/internal-utils.js +219 -0
- package/dist/cjs/modules/word/core/mapper.js +430 -0
- package/dist/cjs/modules/word/core/opc-paths.js +53 -0
- package/dist/cjs/modules/word/core/text-utils.js +210 -0
- package/dist/cjs/modules/word/core/walker.js +577 -0
- package/dist/cjs/modules/word/crypto.js +19 -8
- package/dist/cjs/modules/word/document-io.js +117 -197
- package/dist/cjs/modules/word/errors.js +59 -13
- package/dist/cjs/modules/word/excel.js +22 -0
- package/dist/cjs/modules/word/font/font-embed.js +652 -0
- package/dist/cjs/modules/word/{font-obfuscation.js → font/font-obfuscation.js} +4 -9
- package/dist/cjs/modules/word/font/hyphenation.js +4216 -0
- package/dist/cjs/modules/word/font/text-shaping.js +640 -0
- package/dist/cjs/modules/word/html.js +9 -7
- package/dist/cjs/modules/word/incremental-edit.js +366 -0
- package/dist/cjs/modules/word/index.base.js +370 -137
- package/dist/cjs/modules/word/layout/layout-constants.js +20 -0
- package/dist/cjs/modules/word/layout/layout-full.js +1699 -0
- package/dist/cjs/modules/word/layout/layout-model.js +17 -0
- package/dist/cjs/modules/word/layout/layout.js +1170 -0
- package/dist/cjs/modules/word/layout/render-page.js +1243 -0
- package/dist/cjs/modules/word/markdown.js +19 -0
- package/dist/cjs/modules/word/patcher.js +539 -0
- package/dist/cjs/modules/word/query/compat.js +61 -0
- package/dist/cjs/modules/word/query/data-binding.js +395 -0
- package/dist/cjs/modules/word/query/form-fields.js +272 -0
- package/dist/cjs/modules/word/query/format-search.js +334 -0
- package/dist/cjs/modules/word/query/mail-merge.js +114 -0
- package/dist/cjs/modules/word/query/merge.js +620 -0
- package/dist/cjs/modules/word/query/replace.js +304 -0
- package/dist/cjs/modules/word/query/revisions.js +885 -0
- package/dist/cjs/modules/word/query/search.js +361 -0
- package/dist/cjs/modules/word/query/split.js +138 -0
- package/dist/cjs/modules/word/query/style-resolve.js +374 -0
- package/dist/cjs/modules/word/reader/chart-parser.js +814 -0
- package/dist/cjs/modules/word/reader/comments-parser.js +96 -0
- package/dist/cjs/modules/word/reader/doc-props-parsers.js +194 -0
- package/dist/cjs/modules/word/reader/docx-reader.js +2560 -0
- package/dist/cjs/modules/word/reader/drawing-helpers.js +90 -0
- package/dist/cjs/modules/word/reader/form-field-parser.js +85 -0
- package/dist/cjs/modules/word/reader/image-parsers.js +293 -0
- package/dist/cjs/modules/word/reader/math-parser.js +424 -0
- package/dist/cjs/modules/word/reader/metadata-parsers.js +93 -0
- package/dist/cjs/modules/word/reader/numbering-parser.js +168 -0
- package/dist/cjs/modules/word/reader/paragraph-section-parsers.js +505 -0
- package/dist/cjs/modules/word/reader/parse-utils.js +271 -0
- package/dist/cjs/modules/word/reader/properties-parsers.js +338 -0
- package/dist/cjs/modules/word/reader/reader-context.js +66 -0
- package/dist/cjs/modules/word/reader/sdt-helpers.js +114 -0
- package/dist/cjs/modules/word/reader/settings-parser.js +265 -0
- package/dist/cjs/modules/word/reader/styles-parser.js +149 -0
- package/dist/cjs/modules/word/reader/table-properties-parsers.js +237 -0
- package/dist/cjs/modules/word/reader/theme-parser.js +169 -0
- package/dist/cjs/modules/word/reader/watermark-parser.js +113 -0
- package/dist/cjs/modules/word/security/cfb-reader.js +414 -0
- package/dist/cjs/modules/word/{digital-signatures.js → security/digital-signatures.js} +34 -34
- package/dist/cjs/modules/word/security/document-protection.js +208 -0
- package/dist/cjs/modules/word/security/encryption.js +612 -0
- package/dist/cjs/modules/word/security/policy.js +106 -0
- package/dist/cjs/modules/word/template/template-chart.js +170 -0
- package/dist/cjs/modules/word/template/template-datasource.js +549 -0
- package/dist/cjs/modules/word/template/template-engine.js +1430 -0
- package/dist/cjs/modules/word/units.js +44 -14
- package/dist/cjs/modules/word/{writers → writer}/chart-writer.js +163 -22
- package/dist/cjs/modules/word/writer/checkbox-writer.js +82 -0
- package/dist/cjs/modules/word/{writers → writer}/comment-writer.js +8 -6
- package/dist/cjs/modules/word/writer/common-parts.js +104 -0
- package/dist/cjs/modules/word/{content-types.js → writer/content-types.js} +14 -6
- package/dist/cjs/modules/word/writer/document-writer.js +478 -0
- package/dist/cjs/modules/word/writer/docx-packager.js +1551 -0
- package/dist/cjs/modules/word/{writers → writer}/footnote-writer.js +13 -10
- package/dist/cjs/modules/word/{writers → writer}/header-footer-writer.js +38 -20
- package/dist/cjs/modules/word/{writers → writer}/image-writer.js +11 -7
- package/dist/cjs/modules/word/{writers → writer}/math-writer.js +21 -1
- package/dist/cjs/modules/word/{writers → writer}/numbering-writer.js +11 -4
- package/dist/cjs/modules/word/{writers → writer}/paragraph-writer.js +72 -37
- package/dist/cjs/modules/word/{writers → writer}/parts-writer.js +91 -12
- package/dist/cjs/modules/word/writer/reference-scanners.js +120 -0
- package/dist/cjs/modules/word/writer/relationships.js +124 -0
- package/dist/cjs/modules/word/writer/render-context.js +51 -0
- package/dist/cjs/modules/word/{writers → writer}/run-writer.js +127 -24
- package/dist/cjs/modules/word/writer/sdt-writer.js +192 -0
- package/dist/cjs/modules/word/writer/stream-buf.js +76 -0
- package/dist/cjs/modules/word/writer/streaming-writer.js +1387 -0
- package/dist/cjs/modules/word/writer/string-buf.js +11 -0
- package/dist/cjs/modules/word/{writers → writer}/styles-writer.js +32 -1
- package/dist/cjs/modules/word/{writers → writer}/table-writer.js +94 -11
- package/dist/cjs/utils/crypto.browser.js +3 -1
- package/dist/cjs/utils/crypto.js +4 -1
- package/dist/cjs/utils/font-metrics.js +303 -0
- package/dist/cjs/utils/string-buf.js +92 -0
- package/dist/cjs/utils/theme-colors.js +126 -0
- package/dist/esm/modules/archive/compression/streaming-compress.browser.js +29 -0
- package/dist/esm/modules/archive/compression/streaming-compress.js +9 -0
- package/dist/esm/modules/archive/compression/worker-pool/pool.browser.js +26 -1
- package/dist/esm/modules/archive/fs/archive-file.js +78 -16
- package/dist/esm/modules/archive/unzip/stream.browser.js +43 -2
- package/dist/esm/modules/excel/chart/chart-ex-builder.js +7 -2
- package/dist/esm/modules/excel/chart/chart-ex-renderer.js +4 -9
- package/dist/esm/modules/excel/chart/chart.js +1 -7
- package/dist/esm/modules/excel/stream/workbook-reader.browser.js +25 -1
- package/dist/esm/modules/excel/stream/workbook-reader.js +9 -0
- package/dist/esm/modules/excel/stream/workbook-writer.browser.js +228 -13
- package/dist/esm/modules/excel/utils/string-buf.js +4 -81
- package/dist/esm/modules/excel/workbook.browser.js +135 -25
- package/dist/esm/modules/excel/xlsx/xform/chart/chart-space-xform.js +6 -20
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +32 -8
- package/dist/esm/modules/excel/xlsx/xlsx.js +9 -1
- package/dist/esm/modules/pdf/excel-bridge.js +32 -0
- package/dist/esm/modules/pdf/font/metrics.js +3 -237
- package/dist/esm/modules/pdf/index.js +1 -1
- package/dist/esm/modules/pdf/render-layout-to-pdf.js +647 -0
- package/dist/esm/modules/pdf/word-bridge.js +122 -274
- package/dist/esm/modules/stream/index.base.js +2 -1
- package/dist/esm/modules/stream/internal/sink-adapter.js +198 -0
- package/dist/esm/modules/stream/pull-stream.js +51 -5
- package/dist/esm/modules/word/advanced/diff.js +167 -0
- package/dist/esm/modules/word/advanced/drawing-shapes.js +268 -0
- package/dist/esm/modules/word/advanced/field-engine.js +1225 -0
- package/dist/esm/modules/word/advanced/glossary.js +79 -0
- package/dist/esm/modules/word/advanced/math-convert.js +595 -0
- package/dist/esm/modules/word/advanced/ole-objects.js +271 -0
- package/dist/esm/modules/word/advanced/style-map.js +322 -0
- package/dist/esm/modules/word/advanced/validation.js +1065 -0
- package/dist/esm/modules/word/advanced/vba-project.js +265 -0
- package/dist/esm/modules/word/bridge/excel-bridge.js +980 -0
- package/dist/esm/modules/word/builder/document-handle.js +664 -0
- package/dist/esm/modules/word/builder/paragraph-builders.js +90 -0
- package/dist/esm/modules/word/builder/run-builders.js +600 -0
- package/dist/esm/modules/word/builder/table-builders.js +45 -0
- package/dist/esm/modules/word/constants.js +109 -1
- package/dist/esm/modules/word/convert/conversion-ir.js +31 -0
- package/dist/esm/modules/word/convert/docx-to-semantic.js +499 -0
- package/dist/esm/modules/word/convert/flat-opc.js +385 -0
- package/dist/esm/modules/word/convert/html/html-import.js +1907 -0
- package/dist/{browser/modules/word → esm/modules/word/convert/html}/html-renderer.js +420 -69
- package/dist/esm/modules/word/convert/html/html.js +15 -0
- package/dist/esm/modules/word/convert/markdown/markdown-import.js +1325 -0
- package/dist/esm/modules/word/convert/markdown/markdown-renderer.js +634 -0
- package/dist/esm/modules/word/convert/markdown/markdown.js +15 -0
- package/dist/esm/modules/word/convert/odt/odt.js +1932 -0
- package/dist/esm/modules/word/core/color-utils.js +43 -0
- package/dist/esm/modules/word/core/internal-utils.js +209 -0
- package/dist/esm/modules/word/core/mapper.js +427 -0
- package/dist/esm/modules/word/core/opc-paths.js +48 -0
- package/dist/esm/modules/word/core/text-utils.js +202 -0
- package/dist/esm/modules/word/core/walker.js +570 -0
- package/dist/esm/modules/word/crypto.js +13 -7
- package/dist/esm/modules/word/document-io.js +80 -197
- package/dist/esm/modules/word/errors.js +54 -2
- package/dist/esm/modules/word/excel.js +13 -0
- package/dist/esm/modules/word/font/font-embed.js +646 -0
- package/dist/{browser/modules/word → esm/modules/word/font}/font-obfuscation.js +4 -9
- package/dist/esm/modules/word/font/hyphenation.js +4210 -0
- package/dist/esm/modules/word/font/text-shaping.js +635 -0
- package/dist/esm/modules/word/html.js +6 -5
- package/dist/esm/modules/word/incremental-edit.js +361 -0
- package/dist/esm/modules/word/index.base.js +138 -29
- package/dist/esm/modules/word/layout/layout-constants.js +17 -0
- package/dist/esm/modules/word/layout/layout-full.js +1696 -0
- package/dist/esm/modules/word/layout/layout-model.js +16 -0
- package/dist/esm/modules/word/layout/layout.js +1167 -0
- package/dist/esm/modules/word/layout/render-page.js +1238 -0
- package/dist/esm/modules/word/markdown.js +13 -0
- package/dist/esm/modules/word/patcher.js +537 -0
- package/dist/esm/modules/word/query/compat.js +58 -0
- package/dist/esm/modules/word/query/data-binding.js +392 -0
- package/dist/esm/modules/word/query/form-fields.js +268 -0
- package/dist/esm/modules/word/query/format-search.js +329 -0
- package/dist/esm/modules/word/query/mail-merge.js +111 -0
- package/dist/esm/modules/word/query/merge.js +617 -0
- package/dist/esm/modules/word/query/replace.js +301 -0
- package/dist/esm/modules/word/query/revisions.js +879 -0
- package/dist/esm/modules/word/query/search.js +346 -0
- package/dist/esm/modules/word/query/split.js +135 -0
- package/dist/esm/modules/word/query/style-resolve.js +368 -0
- package/dist/esm/modules/word/reader/chart-parser.js +810 -0
- package/dist/esm/modules/word/reader/comments-parser.js +92 -0
- package/dist/esm/modules/word/reader/doc-props-parsers.js +190 -0
- package/dist/esm/modules/word/reader/docx-reader.js +2557 -0
- package/dist/esm/modules/word/reader/drawing-helpers.js +84 -0
- package/dist/esm/modules/word/reader/form-field-parser.js +82 -0
- package/dist/esm/modules/word/reader/image-parsers.js +291 -0
- package/dist/esm/modules/word/reader/math-parser.js +422 -0
- package/dist/esm/modules/word/reader/metadata-parsers.js +87 -0
- package/dist/esm/modules/word/reader/numbering-parser.js +166 -0
- package/dist/esm/modules/word/reader/paragraph-section-parsers.js +503 -0
- package/dist/esm/modules/word/reader/parse-utils.js +249 -0
- package/dist/esm/modules/word/reader/properties-parsers.js +332 -0
- package/dist/esm/modules/word/reader/reader-context.js +61 -0
- package/dist/esm/modules/word/reader/sdt-helpers.js +111 -0
- package/dist/esm/modules/word/reader/settings-parser.js +263 -0
- package/dist/esm/modules/word/reader/styles-parser.js +147 -0
- package/dist/esm/modules/word/reader/table-properties-parsers.js +234 -0
- package/dist/esm/modules/word/reader/theme-parser.js +167 -0
- package/dist/esm/modules/word/reader/watermark-parser.js +110 -0
- package/dist/esm/modules/word/security/cfb-reader.js +410 -0
- package/dist/esm/modules/word/{digital-signatures.js → security/digital-signatures.js} +34 -34
- package/dist/esm/modules/word/security/document-protection.js +201 -0
- package/dist/esm/modules/word/security/encryption.js +602 -0
- package/dist/esm/modules/word/security/policy.js +102 -0
- package/dist/esm/modules/word/template/template-chart.js +167 -0
- package/dist/esm/modules/word/template/template-datasource.js +541 -0
- package/dist/esm/modules/word/template/template-engine.js +1435 -0
- package/dist/esm/modules/word/units.js +43 -14
- package/dist/esm/modules/word/{writers → writer}/chart-writer.js +164 -23
- package/dist/esm/modules/word/writer/checkbox-writer.js +79 -0
- package/dist/esm/modules/word/{writers → writer}/comment-writer.js +8 -6
- package/dist/esm/modules/word/writer/common-parts.js +101 -0
- package/dist/{browser/modules/word → esm/modules/word/writer}/content-types.js +14 -6
- package/dist/esm/modules/word/writer/document-writer.js +473 -0
- package/dist/esm/modules/word/writer/docx-packager.js +1515 -0
- package/dist/{browser/modules/word/writers → esm/modules/word/writer}/footnote-writer.js +13 -10
- package/dist/{browser/modules/word/writers → esm/modules/word/writer}/header-footer-writer.js +39 -21
- package/dist/esm/modules/word/{writers → writer}/image-writer.js +11 -7
- package/dist/{browser/modules/word/writers → esm/modules/word/writer}/math-writer.js +21 -1
- package/dist/{browser/modules/word/writers → esm/modules/word/writer}/numbering-writer.js +11 -4
- package/dist/esm/modules/word/{writers → writer}/paragraph-writer.js +73 -38
- package/dist/{browser/modules/word/writers → esm/modules/word/writer}/parts-writer.js +91 -12
- package/dist/esm/modules/word/writer/reference-scanners.js +111 -0
- package/dist/esm/modules/word/writer/relationships.js +117 -0
- package/dist/esm/modules/word/writer/render-context.js +46 -0
- package/dist/{browser/modules/word/writers → esm/modules/word/writer}/run-writer.js +126 -24
- package/dist/esm/modules/word/writer/sdt-writer.js +189 -0
- package/dist/esm/modules/word/writer/stream-buf.js +73 -0
- package/dist/esm/modules/word/writer/streaming-writer.js +1382 -0
- package/dist/esm/modules/word/writer/string-buf.js +7 -0
- package/dist/esm/modules/word/{writers → writer}/styles-writer.js +32 -1
- package/dist/esm/modules/word/{writers → writer}/table-writer.js +94 -11
- package/dist/esm/utils/crypto.browser.js +3 -1
- package/dist/esm/utils/crypto.js +4 -1
- package/dist/esm/utils/font-metrics.js +293 -0
- package/dist/esm/utils/string-buf.js +89 -0
- package/dist/esm/utils/theme-colors.js +120 -0
- package/dist/iife/excelts.iife.js +70692 -70337
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +57 -57
- package/dist/types/modules/archive/fs/archive-file.d.ts +8 -5
- package/dist/types/modules/excel/chart/chart-ex-types.d.ts +0 -12
- package/dist/types/modules/excel/chart/chart.d.ts +1 -5
- package/dist/types/modules/excel/chart/types.d.ts +0 -6
- package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +40 -0
- package/dist/types/modules/excel/utils/string-buf.d.ts +5 -26
- package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +19 -9
- package/dist/types/modules/excel/xlsx/xlsx.d.ts +10 -2
- package/dist/types/modules/pdf/excel-bridge.d.ts +30 -1
- package/dist/types/modules/pdf/font/metrics.d.ts +3 -52
- package/dist/types/modules/pdf/index.d.ts +1 -1
- package/dist/types/modules/pdf/render-layout-to-pdf.d.ts +66 -0
- package/dist/types/modules/pdf/word-bridge.d.ts +80 -12
- package/dist/types/modules/stream/index.base.d.ts +2 -0
- package/dist/types/modules/stream/internal/sink-adapter.d.ts +65 -0
- package/dist/types/modules/stream/pull-stream.d.ts +19 -2
- package/dist/types/modules/stream/types.d.ts +13 -1
- package/dist/types/modules/word/advanced/diff.d.ts +61 -0
- package/dist/types/modules/word/advanced/drawing-shapes.d.ts +269 -0
- package/dist/types/modules/word/advanced/field-engine.d.ts +43 -0
- package/dist/types/modules/word/advanced/glossary.d.ts +86 -0
- package/dist/types/modules/word/advanced/math-convert.d.ts +30 -0
- package/dist/types/modules/word/advanced/ole-objects.d.ts +115 -0
- package/dist/types/modules/word/advanced/style-map.d.ts +105 -0
- package/dist/types/modules/word/advanced/validation.d.ts +56 -0
- package/dist/types/modules/word/advanced/vba-project.d.ts +91 -0
- package/dist/types/modules/word/bridge/excel-bridge.d.ts +127 -0
- package/dist/types/modules/word/builder/document-handle.d.ts +151 -0
- package/dist/types/modules/word/builder/paragraph-builders.d.ts +61 -0
- package/dist/types/modules/word/builder/run-builders.d.ts +374 -0
- package/dist/types/modules/word/builder/table-builders.d.ts +23 -0
- package/dist/types/modules/word/constants.d.ts +39 -1
- package/dist/types/modules/word/convert/conversion-ir.d.ts +210 -0
- package/dist/types/modules/word/convert/docx-to-semantic.d.ts +39 -0
- package/dist/types/modules/word/convert/flat-opc.d.ts +44 -0
- package/dist/types/modules/word/convert/html/html-import.d.ts +50 -0
- package/dist/{browser/modules/word → types/modules/word/convert/html}/html-renderer.d.ts +14 -1
- package/dist/types/modules/word/convert/html/html.d.ts +15 -0
- package/dist/types/modules/word/convert/markdown/markdown-import.d.ts +68 -0
- package/dist/types/modules/word/convert/markdown/markdown-renderer.d.ts +25 -0
- package/dist/types/modules/word/convert/markdown/markdown.d.ts +15 -0
- package/dist/types/modules/word/convert/odt/odt.d.ts +41 -0
- package/dist/types/modules/word/{color-utils.d.ts → core/color-utils.d.ts} +8 -1
- package/dist/types/modules/word/core/internal-utils.d.ts +90 -0
- package/dist/types/modules/word/core/mapper.d.ts +44 -0
- package/dist/types/modules/word/core/opc-paths.d.ts +33 -0
- package/dist/types/modules/word/core/text-utils.d.ts +38 -0
- package/dist/types/modules/word/core/walker.d.ts +119 -0
- package/dist/types/modules/word/crypto.d.ts +14 -9
- package/dist/types/modules/word/document-io.d.ts +59 -27
- package/dist/types/modules/word/errors.d.ts +44 -1
- package/dist/types/modules/word/excel.d.ts +14 -0
- package/dist/types/modules/word/font/font-embed.d.ts +112 -0
- package/dist/types/modules/word/font/hyphenation.d.ts +65 -0
- package/dist/types/modules/word/font/text-shaping.d.ts +58 -0
- package/dist/types/modules/word/html.d.ts +7 -6
- package/dist/types/modules/word/incremental-edit.d.ts +123 -0
- package/dist/types/modules/word/index.base.d.ts +194 -10
- package/dist/types/modules/word/layout/layout-constants.d.ts +17 -0
- package/dist/types/modules/word/layout/layout-full.d.ts +53 -0
- package/dist/types/modules/word/layout/layout-model.d.ts +344 -0
- package/dist/types/modules/word/layout/layout.d.ts +63 -0
- package/dist/types/modules/word/layout/render-page.d.ts +57 -0
- package/dist/types/modules/word/markdown.d.ts +14 -0
- package/dist/types/modules/word/patcher.d.ts +62 -0
- package/dist/types/modules/word/query/compat.d.ts +25 -0
- package/dist/types/modules/word/query/data-binding.d.ts +22 -0
- package/dist/types/modules/word/query/form-fields.d.ts +41 -0
- package/dist/types/modules/word/query/format-search.d.ts +99 -0
- package/dist/types/modules/word/query/mail-merge.d.ts +25 -0
- package/dist/types/modules/word/query/merge.d.ts +50 -0
- package/dist/types/modules/word/query/replace.d.ts +47 -0
- package/dist/types/modules/word/query/revisions.d.ts +67 -0
- package/dist/types/modules/word/query/search.d.ts +129 -0
- package/dist/types/modules/word/query/split.d.ts +44 -0
- package/dist/types/modules/word/query/style-resolve.d.ts +104 -0
- package/dist/types/modules/word/reader/chart-parser.d.ts +20 -0
- package/dist/types/modules/word/reader/comments-parser.d.ts +26 -0
- package/dist/types/modules/word/reader/doc-props-parsers.d.ts +15 -0
- package/dist/types/modules/word/reader/docx-reader.d.ts +27 -0
- package/dist/types/modules/word/reader/drawing-helpers.d.ts +27 -0
- package/dist/types/modules/word/reader/form-field-parser.d.ts +21 -0
- package/dist/types/modules/word/reader/image-parsers.d.ts +11 -0
- package/dist/types/modules/word/reader/math-parser.d.ts +12 -0
- package/dist/types/modules/word/reader/metadata-parsers.d.ts +17 -0
- package/dist/types/modules/word/reader/numbering-parser.d.ts +13 -0
- package/dist/types/modules/word/reader/paragraph-section-parsers.d.ts +12 -0
- package/dist/types/modules/word/reader/parse-utils.d.ts +91 -0
- package/dist/types/modules/word/reader/properties-parsers.d.ts +21 -0
- package/dist/types/modules/word/reader/reader-context.d.ts +69 -0
- package/dist/types/modules/word/reader/sdt-helpers.d.ts +29 -0
- package/dist/types/modules/word/reader/settings-parser.d.ts +8 -0
- package/dist/types/modules/word/reader/styles-parser.d.ts +12 -0
- package/dist/types/modules/word/reader/table-properties-parsers.d.ts +12 -0
- package/dist/types/modules/word/reader/theme-parser.d.ts +8 -0
- package/dist/types/modules/word/reader/watermark-parser.d.ts +15 -0
- package/dist/types/modules/word/security/cfb-reader.d.ts +37 -0
- package/dist/types/modules/word/{digital-signatures.d.ts → security/digital-signatures.d.ts} +19 -11
- package/dist/types/modules/word/security/document-protection.d.ts +93 -0
- package/dist/{browser/modules/word → types/modules/word/security}/encryption.d.ts +51 -4
- package/dist/types/modules/word/security/policy.d.ts +80 -0
- package/dist/types/modules/word/template/template-chart.d.ts +56 -0
- package/dist/types/modules/word/template/template-datasource.d.ts +154 -0
- package/dist/types/modules/word/template/template-engine.d.ts +121 -0
- package/dist/types/modules/word/types.d.ts +224 -25
- package/dist/types/modules/word/units.d.ts +26 -0
- package/dist/types/modules/word/writer/checkbox-writer.d.ts +17 -0
- package/dist/{browser/modules/word/writers → types/modules/word/writer}/comment-writer.d.ts +2 -1
- package/dist/types/modules/word/writer/common-parts.d.ts +57 -0
- package/dist/{browser/modules/word → types/modules/word/writer}/content-types.d.ts +2 -2
- package/dist/types/modules/word/writer/document-writer.d.ts +24 -0
- package/dist/types/modules/word/writer/docx-packager.d.ts +35 -0
- package/dist/{browser/modules/word/writers → types/modules/word/writer}/footnote-writer.d.ts +3 -2
- package/dist/{browser/modules/word/writers → types/modules/word/writer}/header-footer-writer.d.ts +3 -2
- package/dist/{browser/modules/word/writers → types/modules/word/writer}/image-writer.d.ts +1 -1
- package/dist/types/modules/word/writer/math-writer.d.ts +20 -0
- package/dist/types/modules/word/{writers → writer}/numbering-writer.d.ts +1 -1
- package/dist/types/modules/word/{writers → writer}/paragraph-writer.d.ts +2 -1
- package/dist/types/modules/word/{writers → writer}/parts-writer.d.ts +3 -3
- package/dist/types/modules/word/writer/reference-scanners.d.ts +42 -0
- package/dist/types/modules/word/writer/relationships.d.ts +52 -0
- package/dist/types/modules/word/writer/render-context.d.ts +124 -0
- package/dist/types/modules/word/{writers → writer}/run-writer.d.ts +10 -1
- package/dist/types/modules/word/writer/sdt-writer.d.ts +25 -0
- package/dist/types/modules/word/writer/stream-buf.d.ts +37 -0
- package/dist/types/modules/word/writer/streaming-writer.d.ts +344 -0
- package/dist/types/modules/word/writer/string-buf.d.ts +8 -0
- package/dist/types/modules/word/{writers → writer}/table-writer.d.ts +2 -1
- package/dist/types/modules/xml/types.d.ts +22 -0
- package/dist/types/utils/crypto.browser.d.ts +3 -1
- package/dist/types/utils/crypto.d.ts +4 -1
- package/dist/types/utils/font-metrics.d.ts +63 -0
- package/dist/types/utils/string-buf.d.ts +42 -0
- package/dist/types/utils/theme-colors.d.ts +55 -0
- package/package.json +121 -39
- package/dist/browser/modules/word/color-utils.js +0 -94
- package/dist/browser/modules/word/document.d.ts +0 -657
- package/dist/browser/modules/word/document.js +0 -1533
- package/dist/browser/modules/word/docx-packager.d.ts +0 -14
- package/dist/browser/modules/word/docx-packager.js +0 -822
- package/dist/browser/modules/word/docx-reader.d.ts +0 -11
- package/dist/browser/modules/word/docx-reader.js +0 -4929
- package/dist/browser/modules/word/encryption.js +0 -274
- package/dist/browser/modules/word/internal-utils.d.ts +0 -23
- package/dist/browser/modules/word/internal-utils.js +0 -54
- package/dist/browser/modules/word/namespaces.d.ts +0 -159
- package/dist/browser/modules/word/namespaces.js +0 -189
- package/dist/browser/modules/word/relationships.d.ts +0 -30
- package/dist/browser/modules/word/relationships.js +0 -48
- package/dist/browser/modules/word/writers/checkbox-writer.d.ts +0 -9
- package/dist/browser/modules/word/writers/checkbox-writer.js +0 -42
- package/dist/browser/modules/word/writers/document-writer.d.ts +0 -16
- package/dist/browser/modules/word/writers/document-writer.js +0 -461
- package/dist/browser/modules/word/writers/math-writer.d.ts +0 -9
- package/dist/cjs/modules/word/color-utils.js +0 -97
- package/dist/cjs/modules/word/document.js +0 -1645
- package/dist/cjs/modules/word/docx-packager.js +0 -825
- package/dist/cjs/modules/word/docx-reader.js +0 -4932
- package/dist/cjs/modules/word/encryption.js +0 -282
- package/dist/cjs/modules/word/internal-utils.js +0 -59
- package/dist/cjs/modules/word/namespaces.js +0 -192
- package/dist/cjs/modules/word/relationships.js +0 -55
- package/dist/cjs/modules/word/writers/checkbox-writer.js +0 -45
- package/dist/cjs/modules/word/writers/document-writer.js +0 -465
- package/dist/esm/modules/word/color-utils.js +0 -94
- package/dist/esm/modules/word/document.js +0 -1533
- package/dist/esm/modules/word/docx-packager.js +0 -822
- package/dist/esm/modules/word/docx-reader.js +0 -4929
- package/dist/esm/modules/word/encryption.js +0 -274
- package/dist/esm/modules/word/internal-utils.js +0 -54
- package/dist/esm/modules/word/namespaces.js +0 -189
- package/dist/esm/modules/word/relationships.js +0 -48
- package/dist/esm/modules/word/writers/checkbox-writer.js +0 -42
- package/dist/esm/modules/word/writers/document-writer.js +0 -461
- package/dist/types/modules/word/document.d.ts +0 -657
- package/dist/types/modules/word/docx-packager.d.ts +0 -14
- package/dist/types/modules/word/docx-reader.d.ts +0 -11
- package/dist/types/modules/word/internal-utils.d.ts +0 -23
- package/dist/types/modules/word/namespaces.d.ts +0 -159
- package/dist/types/modules/word/relationships.d.ts +0 -30
- package/dist/types/modules/word/writers/checkbox-writer.d.ts +0 -9
- package/dist/types/modules/word/writers/document-writer.d.ts +0 -16
- package/dist/types/modules/word/writers/math-writer.d.ts +0 -9
- /package/dist/browser/modules/word/{font-obfuscation.d.ts → font/font-obfuscation.d.ts} +0 -0
- /package/dist/browser/modules/word/{writers → writer}/chart-writer.d.ts +0 -0
- /package/dist/browser/modules/word/{writers → writer}/section-writer.d.ts +0 -0
- /package/dist/browser/modules/word/{writers → writer}/section-writer.js +0 -0
- /package/dist/browser/modules/word/{writers → writer}/styles-writer.d.ts +0 -0
- /package/dist/browser/modules/word/{writers → writer}/textbox-writer.d.ts +0 -0
- /package/dist/browser/modules/word/{writers → writer}/textbox-writer.js +0 -0
- /package/dist/browser/modules/word/{writers → writer}/toc-writer.d.ts +0 -0
- /package/dist/browser/modules/word/{writers → writer}/toc-writer.js +0 -0
- /package/dist/cjs/modules/word/{writers → writer}/section-writer.js +0 -0
- /package/dist/cjs/modules/word/{writers → writer}/textbox-writer.js +0 -0
- /package/dist/cjs/modules/word/{writers → writer}/toc-writer.js +0 -0
- /package/dist/esm/modules/word/{writers → writer}/section-writer.js +0 -0
- /package/dist/esm/modules/word/{writers → writer}/textbox-writer.js +0 -0
- /package/dist/esm/modules/word/{writers → writer}/toc-writer.js +0 -0
- /package/dist/types/modules/word/{font-obfuscation.d.ts → font/font-obfuscation.d.ts} +0 -0
- /package/dist/types/modules/word/{writers → writer}/chart-writer.d.ts +0 -0
- /package/dist/types/modules/word/{writers → writer}/section-writer.d.ts +0 -0
- /package/dist/types/modules/word/{writers → writer}/styles-writer.d.ts +0 -0
- /package/dist/types/modules/word/{writers → writer}/textbox-writer.d.ts +0 -0
- /package/dist/types/modules/word/{writers → writer}/toc-writer.d.ts +0 -0
|
@@ -0,0 +1,1932 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOCX Module - OpenDocument Text (ODT) Format Support
|
|
3
|
+
*
|
|
4
|
+
* Implements reading and writing of ODT (OpenDocument Text) files.
|
|
5
|
+
* ODT files are ZIP archives containing XML content in ODF namespaces.
|
|
6
|
+
*
|
|
7
|
+
* Main archive structure:
|
|
8
|
+
* - content.xml — document body and automatic styles
|
|
9
|
+
* - styles.xml — named styles, page layout, master pages
|
|
10
|
+
* - meta.xml — document metadata
|
|
11
|
+
* - META-INF/manifest.xml — manifest of all archive entries
|
|
12
|
+
* - Pictures/ — embedded images
|
|
13
|
+
*
|
|
14
|
+
* @stability experimental
|
|
15
|
+
*/
|
|
16
|
+
import { zip } from "../../../archive/create-archive.js";
|
|
17
|
+
import { unzip } from "../../../archive/read-archive.js";
|
|
18
|
+
import { parseXml, findChild, findChildren, textContent } from "../../../xml/dom.js";
|
|
19
|
+
import { XmlWriter } from "../../../xml/writer.js";
|
|
20
|
+
import { sanitizeUrl, utf8Decoder, utf8Encoder } from "../../core/internal-utils.js";
|
|
21
|
+
import { isRun } from "../../core/text-utils.js";
|
|
22
|
+
import { DocxParseError } from "../../errors.js";
|
|
23
|
+
import { EMU_PER_CM, EMU_PER_INCH, EMU_PER_POINT, EMU_PER_PX } from "../../units.js";
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// ODF Namespace Constants
|
|
26
|
+
// =============================================================================
|
|
27
|
+
/** ODF namespace URIs. */
|
|
28
|
+
const NS = {
|
|
29
|
+
office: "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
|
|
30
|
+
style: "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
|
|
31
|
+
text: "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
|
|
32
|
+
table: "urn:oasis:names:tc:opendocument:xmlns:table:1.0",
|
|
33
|
+
draw: "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
|
|
34
|
+
fo: "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
|
|
35
|
+
xlink: "http://www.w3.org/1999/xlink",
|
|
36
|
+
dc: "http://purl.org/dc/elements/1.1/",
|
|
37
|
+
meta: "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
|
|
38
|
+
svg: "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
|
|
39
|
+
manifest: "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"
|
|
40
|
+
};
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// Unit Conversion Helpers
|
|
43
|
+
// =============================================================================
|
|
44
|
+
/** Parse an ODF length value (e.g. "1.27cm", "0.5in", "12pt") to twips. */
|
|
45
|
+
function odfLengthToTwips(value) {
|
|
46
|
+
if (!value) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const match = value.match(/^(-?\d+(?:\.\d+)?)\s*(cm|mm|in|pt|pc|px|em)$/);
|
|
50
|
+
if (!match) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const num = parseFloat(match[1]);
|
|
54
|
+
const unit = match[2];
|
|
55
|
+
switch (unit) {
|
|
56
|
+
case "cm":
|
|
57
|
+
return Math.round(num * 567);
|
|
58
|
+
case "mm":
|
|
59
|
+
return Math.round(num * 56.7);
|
|
60
|
+
case "in":
|
|
61
|
+
return Math.round(num * 1440);
|
|
62
|
+
case "pt":
|
|
63
|
+
return Math.round(num * 20);
|
|
64
|
+
case "pc":
|
|
65
|
+
return Math.round(num * 240);
|
|
66
|
+
case "px":
|
|
67
|
+
// Approximate: 1px ≈ 0.75pt at 96dpi
|
|
68
|
+
return Math.round(num * 15);
|
|
69
|
+
case "em":
|
|
70
|
+
// Approximate: 1em ≈ 12pt
|
|
71
|
+
return Math.round(num * 240);
|
|
72
|
+
default:
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Parse an ODF length value to EMU (English Metric Units). */
|
|
77
|
+
function odfLengthToEmu(value) {
|
|
78
|
+
if (!value) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
const match = value.match(/^(-?\d+(?:\.\d+)?)\s*(cm|mm|in|pt|pc|px)$/);
|
|
82
|
+
if (!match) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
const num = parseFloat(match[1]);
|
|
86
|
+
const unit = match[2];
|
|
87
|
+
switch (unit) {
|
|
88
|
+
case "cm":
|
|
89
|
+
return Math.round(num * EMU_PER_CM);
|
|
90
|
+
case "mm":
|
|
91
|
+
return Math.round((num * EMU_PER_CM) / 10);
|
|
92
|
+
case "in":
|
|
93
|
+
return Math.round(num * EMU_PER_INCH);
|
|
94
|
+
case "pt":
|
|
95
|
+
return Math.round(num * EMU_PER_POINT);
|
|
96
|
+
case "pc":
|
|
97
|
+
return Math.round(num * EMU_PER_POINT * 12); // 1 pica = 12 points
|
|
98
|
+
case "px":
|
|
99
|
+
return Math.round(num * EMU_PER_PX);
|
|
100
|
+
default:
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** Convert twips to ODF length string (cm). */
|
|
105
|
+
function twipsToCm(twips) {
|
|
106
|
+
return (twips / 567).toFixed(3) + "cm";
|
|
107
|
+
}
|
|
108
|
+
/** Convert EMU to ODF length string (cm). */
|
|
109
|
+
function emuToCm(emu) {
|
|
110
|
+
return (emu / EMU_PER_CM).toFixed(3) + "cm";
|
|
111
|
+
}
|
|
112
|
+
/** Convert half-points to pt string. */
|
|
113
|
+
function halfPointsToPt(hp) {
|
|
114
|
+
return (hp / 2).toString() + "pt";
|
|
115
|
+
}
|
|
116
|
+
/** Parse a font size string (e.g. "12pt") to half-points. */
|
|
117
|
+
function parseFontSizeToHalfPoints(value) {
|
|
118
|
+
if (!value) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
const match = value.match(/^(\d+(?:\.\d+)?)\s*pt$/);
|
|
122
|
+
if (!match) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
return Math.round(parseFloat(match[1]) * 2);
|
|
126
|
+
}
|
|
127
|
+
/** Parse a 6-digit hex color from ODF format (#RRGGBB). */
|
|
128
|
+
function parseOdfColor(value) {
|
|
129
|
+
if (!value) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
const match = value.match(/^#([0-9a-fA-F]{6})$/);
|
|
133
|
+
if (!match) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
return match[1].toUpperCase();
|
|
137
|
+
}
|
|
138
|
+
/** Convert a hex color to ODF format (#RRGGBB). */
|
|
139
|
+
function colorToOdf(hex) {
|
|
140
|
+
return `#${hex}`;
|
|
141
|
+
}
|
|
142
|
+
// =============================================================================
|
|
143
|
+
// XML Element Query Helpers (Namespace-Aware)
|
|
144
|
+
// =============================================================================
|
|
145
|
+
/**
|
|
146
|
+
* Find a child element by namespace-prefixed name.
|
|
147
|
+
* Tries both "prefix:local" and just "local" for flexibility.
|
|
148
|
+
*/
|
|
149
|
+
function findNsChild(el, prefix, local) {
|
|
150
|
+
return findChild(el, `${prefix}:${local}`) ?? findChild(el, local);
|
|
151
|
+
}
|
|
152
|
+
/** Find all children by namespace-prefixed name. */
|
|
153
|
+
function findNsChildren(el, prefix, local) {
|
|
154
|
+
const result = findChildren(el, `${prefix}:${local}`);
|
|
155
|
+
if (result.length > 0) {
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
return findChildren(el, local);
|
|
159
|
+
}
|
|
160
|
+
/** Get an attribute value with a namespace prefix. */
|
|
161
|
+
function nsAttr(el, prefix, local) {
|
|
162
|
+
return el.attributes[`${prefix}:${local}`] ?? el.attributes[local];
|
|
163
|
+
}
|
|
164
|
+
// =============================================================================
|
|
165
|
+
// ODF Style Parsing
|
|
166
|
+
// =============================================================================
|
|
167
|
+
/** Parse style properties from a style:style element. */
|
|
168
|
+
function parseOdfStyle(el) {
|
|
169
|
+
const name = nsAttr(el, "style", "name") ?? "";
|
|
170
|
+
const family = nsAttr(el, "style", "family") ?? "";
|
|
171
|
+
const parentStyle = nsAttr(el, "style", "parent-style-name");
|
|
172
|
+
let paragraphProperties;
|
|
173
|
+
let textProperties;
|
|
174
|
+
let tableProperties;
|
|
175
|
+
let tableColumnProperties;
|
|
176
|
+
let tableCellProperties;
|
|
177
|
+
const pPropsEl = findNsChild(el, "style", "paragraph-properties");
|
|
178
|
+
if (pPropsEl) {
|
|
179
|
+
paragraphProperties = {
|
|
180
|
+
textAlign: nsAttr(pPropsEl, "fo", "text-align"),
|
|
181
|
+
marginTop: nsAttr(pPropsEl, "fo", "margin-top"),
|
|
182
|
+
marginBottom: nsAttr(pPropsEl, "fo", "margin-bottom"),
|
|
183
|
+
marginLeft: nsAttr(pPropsEl, "fo", "margin-left"),
|
|
184
|
+
marginRight: nsAttr(pPropsEl, "fo", "margin-right"),
|
|
185
|
+
textIndent: nsAttr(pPropsEl, "fo", "text-indent"),
|
|
186
|
+
lineHeight: nsAttr(pPropsEl, "fo", "line-height"),
|
|
187
|
+
breakBefore: nsAttr(pPropsEl, "fo", "break-before"),
|
|
188
|
+
keepWithNext: nsAttr(pPropsEl, "fo", "keep-with-next")
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const tPropsEl = findNsChild(el, "style", "text-properties");
|
|
192
|
+
if (tPropsEl) {
|
|
193
|
+
textProperties = {
|
|
194
|
+
fontName: nsAttr(tPropsEl, "style", "font-name") ?? nsAttr(tPropsEl, "fo", "font-family"),
|
|
195
|
+
fontSize: nsAttr(tPropsEl, "fo", "font-size"),
|
|
196
|
+
fontWeight: nsAttr(tPropsEl, "fo", "font-weight"),
|
|
197
|
+
fontStyle: nsAttr(tPropsEl, "fo", "font-style"),
|
|
198
|
+
textDecoration: nsAttr(tPropsEl, "style", "text-underline-style") ??
|
|
199
|
+
nsAttr(tPropsEl, "style", "text-line-through-style"),
|
|
200
|
+
color: nsAttr(tPropsEl, "fo", "color"),
|
|
201
|
+
backgroundColor: nsAttr(tPropsEl, "fo", "background-color"),
|
|
202
|
+
textPosition: nsAttr(tPropsEl, "style", "text-position"),
|
|
203
|
+
fontVariant: nsAttr(tPropsEl, "fo", "font-variant"),
|
|
204
|
+
letterSpacing: nsAttr(tPropsEl, "fo", "letter-spacing")
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const tablePropsEl = findNsChild(el, "style", "table-properties");
|
|
208
|
+
if (tablePropsEl) {
|
|
209
|
+
tableProperties = {
|
|
210
|
+
width: nsAttr(tablePropsEl, "style", "width"),
|
|
211
|
+
align: nsAttr(tablePropsEl, "table", "align") ?? nsAttr(tablePropsEl, "fo", "margin-left")
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const tableColPropsEl = findNsChild(el, "style", "table-column-properties");
|
|
215
|
+
if (tableColPropsEl) {
|
|
216
|
+
tableColumnProperties = {
|
|
217
|
+
columnWidth: nsAttr(tableColPropsEl, "style", "column-width")
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const tableCellPropsEl = findNsChild(el, "style", "table-cell-properties");
|
|
221
|
+
if (tableCellPropsEl) {
|
|
222
|
+
tableCellProperties = {
|
|
223
|
+
padding: nsAttr(tableCellPropsEl, "fo", "padding"),
|
|
224
|
+
borderTop: nsAttr(tableCellPropsEl, "fo", "border-top"),
|
|
225
|
+
borderBottom: nsAttr(tableCellPropsEl, "fo", "border-bottom"),
|
|
226
|
+
borderLeft: nsAttr(tableCellPropsEl, "fo", "border-left"),
|
|
227
|
+
borderRight: nsAttr(tableCellPropsEl, "fo", "border-right"),
|
|
228
|
+
backgroundColor: nsAttr(tableCellPropsEl, "fo", "background-color"),
|
|
229
|
+
verticalAlign: nsAttr(tableCellPropsEl, "style", "vertical-align")
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
name,
|
|
234
|
+
family,
|
|
235
|
+
parentStyle,
|
|
236
|
+
paragraphProperties,
|
|
237
|
+
textProperties,
|
|
238
|
+
tableProperties,
|
|
239
|
+
tableColumnProperties,
|
|
240
|
+
tableCellProperties
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
/** Parse page layout properties from a style:page-layout element. */
|
|
244
|
+
function parsePageLayout(el) {
|
|
245
|
+
const propsEl = findNsChild(el, "style", "page-layout-properties");
|
|
246
|
+
if (!propsEl) {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
pageWidth: nsAttr(propsEl, "fo", "page-width"),
|
|
251
|
+
pageHeight: nsAttr(propsEl, "fo", "page-height"),
|
|
252
|
+
marginTop: nsAttr(propsEl, "fo", "margin-top"),
|
|
253
|
+
marginBottom: nsAttr(propsEl, "fo", "margin-bottom"),
|
|
254
|
+
marginLeft: nsAttr(propsEl, "fo", "margin-left"),
|
|
255
|
+
marginRight: nsAttr(propsEl, "fo", "margin-right")
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
// =============================================================================
|
|
259
|
+
// ODF Content Parsing → DocxDocument Model
|
|
260
|
+
// =============================================================================
|
|
261
|
+
/** Convert ODF text alignment to OOXML alignment. */
|
|
262
|
+
function odfAlignToAlignment(align) {
|
|
263
|
+
if (!align) {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
switch (align) {
|
|
267
|
+
case "start":
|
|
268
|
+
case "left":
|
|
269
|
+
return "left";
|
|
270
|
+
case "center":
|
|
271
|
+
return "center";
|
|
272
|
+
case "end":
|
|
273
|
+
case "right":
|
|
274
|
+
return "right";
|
|
275
|
+
case "justify":
|
|
276
|
+
return "both";
|
|
277
|
+
default:
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/** Convert ODF paragraph properties to OOXML ParagraphProperties. */
|
|
282
|
+
function odfParagraphPropsToDocx(props, styleName) {
|
|
283
|
+
if (!props && !styleName) {
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
const alignment = odfAlignToAlignment(props?.textAlign);
|
|
287
|
+
let indent;
|
|
288
|
+
const leftTwips = odfLengthToTwips(props?.marginLeft);
|
|
289
|
+
const rightTwips = odfLengthToTwips(props?.marginRight);
|
|
290
|
+
const firstLineTwips = odfLengthToTwips(props?.textIndent);
|
|
291
|
+
if (leftTwips !== undefined || rightTwips !== undefined || firstLineTwips !== undefined) {
|
|
292
|
+
indent = {
|
|
293
|
+
...(leftTwips !== undefined ? { left: leftTwips } : {}),
|
|
294
|
+
...(rightTwips !== undefined ? { right: rightTwips } : {}),
|
|
295
|
+
...(firstLineTwips !== undefined
|
|
296
|
+
? firstLineTwips >= 0
|
|
297
|
+
? { firstLine: firstLineTwips }
|
|
298
|
+
: { hanging: -firstLineTwips }
|
|
299
|
+
: {})
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
let spacing;
|
|
303
|
+
const beforeTwips = odfLengthToTwips(props?.marginTop);
|
|
304
|
+
const afterTwips = odfLengthToTwips(props?.marginBottom);
|
|
305
|
+
if (beforeTwips !== undefined || afterTwips !== undefined) {
|
|
306
|
+
spacing = {
|
|
307
|
+
...(beforeTwips !== undefined ? { before: beforeTwips } : {}),
|
|
308
|
+
...(afterTwips !== undefined ? { after: afterTwips } : {})
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const keepNext = props?.keepWithNext === "always" || props?.keepWithNext === "true" ? true : undefined;
|
|
312
|
+
const pageBreakBefore = props?.breakBefore === "page" ? true : undefined;
|
|
313
|
+
const result = {
|
|
314
|
+
...(styleName ? { style: styleName } : {}),
|
|
315
|
+
...(alignment ? { alignment } : {}),
|
|
316
|
+
...(indent ? { indent } : {}),
|
|
317
|
+
...(spacing ? { spacing } : {}),
|
|
318
|
+
...(keepNext !== undefined ? { keepNext } : {}),
|
|
319
|
+
...(pageBreakBefore !== undefined ? { pageBreakBefore } : {})
|
|
320
|
+
};
|
|
321
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
322
|
+
}
|
|
323
|
+
/** Convert ODF text properties to OOXML RunProperties. */
|
|
324
|
+
function odfTextPropsToDocx(props) {
|
|
325
|
+
if (!props) {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
const font = props.fontName || undefined;
|
|
329
|
+
const size = parseFontSizeToHalfPoints(props.fontSize);
|
|
330
|
+
const bold = props.fontWeight === "bold" ? true : undefined;
|
|
331
|
+
const italic = props.fontStyle === "italic" ? true : undefined;
|
|
332
|
+
let underline;
|
|
333
|
+
if (props.textDecoration && props.textDecoration !== "none") {
|
|
334
|
+
underline = "single";
|
|
335
|
+
}
|
|
336
|
+
let strike;
|
|
337
|
+
if (props.textDecoration === "line-through") {
|
|
338
|
+
strike = true;
|
|
339
|
+
underline = undefined;
|
|
340
|
+
}
|
|
341
|
+
const color = parseOdfColor(props.color);
|
|
342
|
+
const smallCaps = props.fontVariant === "small-caps" ? true : undefined;
|
|
343
|
+
const spacingTwips = odfLengthToTwips(props.letterSpacing);
|
|
344
|
+
let vertAlign;
|
|
345
|
+
if (props.textPosition) {
|
|
346
|
+
if (props.textPosition.startsWith("super") || props.textPosition.startsWith("33%")) {
|
|
347
|
+
vertAlign = "superscript";
|
|
348
|
+
}
|
|
349
|
+
else if (props.textPosition.startsWith("sub") || props.textPosition.startsWith("-33%")) {
|
|
350
|
+
vertAlign = "subscript";
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const result = {
|
|
354
|
+
...(font ? { font } : {}),
|
|
355
|
+
...(size !== undefined ? { size } : {}),
|
|
356
|
+
...(bold !== undefined ? { bold } : {}),
|
|
357
|
+
...(italic !== undefined ? { italic } : {}),
|
|
358
|
+
...(underline !== undefined ? { underline } : {}),
|
|
359
|
+
...(strike !== undefined ? { strike } : {}),
|
|
360
|
+
...(color !== undefined ? { color } : {}),
|
|
361
|
+
...(smallCaps !== undefined ? { smallCaps } : {}),
|
|
362
|
+
...(spacingTwips !== undefined ? { spacing: spacingTwips } : {}),
|
|
363
|
+
...(vertAlign !== undefined ? { vertAlign } : {})
|
|
364
|
+
};
|
|
365
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
366
|
+
}
|
|
367
|
+
/** Parse inline text spans (text:span) within a paragraph. */
|
|
368
|
+
function parseTextSpan(el, styles) {
|
|
369
|
+
const styleName = nsAttr(el, "text", "style-name");
|
|
370
|
+
const style = styleName ? styles.get(styleName) : undefined;
|
|
371
|
+
const runProps = odfTextPropsToDocx(style?.textProperties);
|
|
372
|
+
const content = [];
|
|
373
|
+
for (const child of el.children) {
|
|
374
|
+
if (child.type === "text") {
|
|
375
|
+
if (child.value) {
|
|
376
|
+
content.push({ type: "text", text: child.value });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
else if (child.type === "element") {
|
|
380
|
+
const local = getLocalName(child.name);
|
|
381
|
+
if (local === "s") {
|
|
382
|
+
// text:s — multiple spaces
|
|
383
|
+
const count = parseInt(nsAttr(child, "text", "c") ?? "1", 10);
|
|
384
|
+
content.push({ type: "text", text: " ".repeat(count) });
|
|
385
|
+
}
|
|
386
|
+
else if (local === "tab") {
|
|
387
|
+
content.push({ type: "tab" });
|
|
388
|
+
}
|
|
389
|
+
else if (local === "line-break") {
|
|
390
|
+
content.push({ type: "break" });
|
|
391
|
+
}
|
|
392
|
+
else if (local === "span") {
|
|
393
|
+
// Nested span — flatten into current run content
|
|
394
|
+
const nestedRun = parseTextSpan(child, styles);
|
|
395
|
+
content.push(...nestedRun.content);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
...(runProps ? { properties: runProps } : {}),
|
|
401
|
+
content
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
/** Parse a text:p or text:h element into a Paragraph. */
|
|
405
|
+
function parseParagraph(el, styles, listContext) {
|
|
406
|
+
const styleName = nsAttr(el, "text", "style-name");
|
|
407
|
+
const style = styleName ? styles.get(styleName) : undefined;
|
|
408
|
+
const isHeading = getLocalName(el.name) === "h";
|
|
409
|
+
const outlineLevel = isHeading
|
|
410
|
+
? parseInt(nsAttr(el, "text", "outline-level") ?? "1", 10)
|
|
411
|
+
: undefined;
|
|
412
|
+
let paraProps = odfParagraphPropsToDocx(style?.paragraphProperties, styleName);
|
|
413
|
+
// If this is a heading, add outline level
|
|
414
|
+
if (outlineLevel !== undefined && outlineLevel >= 1 && outlineLevel <= 9) {
|
|
415
|
+
paraProps = {
|
|
416
|
+
...paraProps,
|
|
417
|
+
outlineLevel: outlineLevel - 1,
|
|
418
|
+
style: styleName ?? `Heading${outlineLevel}`
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
// If inside a list, add numbering reference
|
|
422
|
+
if (listContext) {
|
|
423
|
+
paraProps = {
|
|
424
|
+
...paraProps,
|
|
425
|
+
numbering: { numId: 1, level: listContext.level }
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
const children = [];
|
|
429
|
+
for (const child of el.children) {
|
|
430
|
+
if (child.type === "text") {
|
|
431
|
+
if (child.value) {
|
|
432
|
+
children.push({
|
|
433
|
+
content: [{ type: "text", text: child.value }]
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
else if (child.type === "element") {
|
|
438
|
+
const local = getLocalName(child.name);
|
|
439
|
+
if (local === "span") {
|
|
440
|
+
children.push(parseTextSpan(child, styles));
|
|
441
|
+
}
|
|
442
|
+
else if (local === "a") {
|
|
443
|
+
// Hyperlink. Pass the href through sanitizeUrl so dangerous
|
|
444
|
+
// schemes (javascript:, vbscript:, data: with executable payloads,
|
|
445
|
+
// etc.) coming from an untrusted ODT do not survive into the DOCX
|
|
446
|
+
// model. Unsafe links degrade to plain text — the caller still sees
|
|
447
|
+
// the link's children but no clickable hyperlink is created.
|
|
448
|
+
const rawHref = nsAttr(child, "xlink", "href");
|
|
449
|
+
const safeHref = sanitizeUrl(rawHref);
|
|
450
|
+
const runs = [];
|
|
451
|
+
for (const linkChild of child.children) {
|
|
452
|
+
if (linkChild.type === "text") {
|
|
453
|
+
if (linkChild.value) {
|
|
454
|
+
runs.push({ content: [{ type: "text", text: linkChild.value }] });
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
else if (linkChild.type === "element" && getLocalName(linkChild.name) === "span") {
|
|
458
|
+
runs.push(parseTextSpan(linkChild, styles));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (safeHref) {
|
|
462
|
+
children.push({
|
|
463
|
+
type: "hyperlink",
|
|
464
|
+
url: safeHref,
|
|
465
|
+
children: runs
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
else if (rawHref && rawHref.startsWith("#")) {
|
|
469
|
+
// Internal anchor — sanitizeUrl rejects fragment-only URLs but
|
|
470
|
+
// they're safe and meaningful. Preserve as a Hyperlink with
|
|
471
|
+
// `anchor` set, mirroring how DOCX represents in-document
|
|
472
|
+
// links.
|
|
473
|
+
children.push({
|
|
474
|
+
type: "hyperlink",
|
|
475
|
+
anchor: rawHref.slice(1),
|
|
476
|
+
children: runs
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
// Drop the wrapper — preserve text content as plain runs so no
|
|
481
|
+
// user-visible content disappears.
|
|
482
|
+
for (const r of runs) {
|
|
483
|
+
children.push(r);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
else if (local === "s") {
|
|
488
|
+
const count = parseInt(nsAttr(child, "text", "c") ?? "1", 10);
|
|
489
|
+
children.push({ content: [{ type: "text", text: " ".repeat(count) }] });
|
|
490
|
+
}
|
|
491
|
+
else if (local === "tab") {
|
|
492
|
+
children.push({ content: [{ type: "tab" }] });
|
|
493
|
+
}
|
|
494
|
+
else if (local === "line-break") {
|
|
495
|
+
children.push({ content: [{ type: "break" }] });
|
|
496
|
+
}
|
|
497
|
+
else if (local === "frame") {
|
|
498
|
+
// Inline image frame
|
|
499
|
+
const imageRun = parseDrawFrame(child);
|
|
500
|
+
if (imageRun) {
|
|
501
|
+
children.push(imageRun);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
else if (local === "bookmark-start") {
|
|
505
|
+
const bkName = nsAttr(child, "text", "name") ?? "";
|
|
506
|
+
children.push({ type: "bookmarkStart", id: 0, name: bkName });
|
|
507
|
+
}
|
|
508
|
+
else if (local === "bookmark-end") {
|
|
509
|
+
children.push({ type: "bookmarkEnd", id: 0 });
|
|
510
|
+
}
|
|
511
|
+
else if (local === "note") {
|
|
512
|
+
// Footnote/endnote — extract text content
|
|
513
|
+
const noteBodyEl = findNsChild(child, "text", "note-body");
|
|
514
|
+
if (noteBodyEl) {
|
|
515
|
+
const noteText = textContent(noteBodyEl);
|
|
516
|
+
if (noteText) {
|
|
517
|
+
children.push({ content: [{ type: "text", text: `[${noteText}]` }] });
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
type: "paragraph",
|
|
525
|
+
...(paraProps ? { properties: paraProps } : {}),
|
|
526
|
+
children
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
/** Parse a draw:frame element for inline images. */
|
|
530
|
+
function parseDrawFrame(el) {
|
|
531
|
+
const width = odfLengthToEmu(nsAttr(el, "svg", "width"));
|
|
532
|
+
const height = odfLengthToEmu(nsAttr(el, "svg", "height"));
|
|
533
|
+
const name = nsAttr(el, "draw", "name");
|
|
534
|
+
const imageEl = findNsChild(el, "draw", "image") ?? findChild(el, "draw:image") ?? findChild(el, "image");
|
|
535
|
+
if (!imageEl) {
|
|
536
|
+
return undefined;
|
|
537
|
+
}
|
|
538
|
+
const href = nsAttr(imageEl, "xlink", "href");
|
|
539
|
+
if (!href) {
|
|
540
|
+
return undefined;
|
|
541
|
+
}
|
|
542
|
+
const imageContent = {
|
|
543
|
+
type: "image",
|
|
544
|
+
rId: href, // Use the path as rId placeholder; resolved during image collection
|
|
545
|
+
width: width ?? EMU_PER_INCH, // Default 1 inch
|
|
546
|
+
height: height ?? EMU_PER_INCH,
|
|
547
|
+
...(name ? { name } : {})
|
|
548
|
+
};
|
|
549
|
+
return {
|
|
550
|
+
content: [imageContent]
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
/** Parse a text:list element. */
|
|
554
|
+
function parseList(el, styles, parentLevel) {
|
|
555
|
+
const listStyleName = nsAttr(el, "text", "style-name");
|
|
556
|
+
const paragraphs = [];
|
|
557
|
+
const level = parentLevel;
|
|
558
|
+
const items = findNsChildren(el, "text", "list-item");
|
|
559
|
+
for (const item of items) {
|
|
560
|
+
for (const child of item.children) {
|
|
561
|
+
if (child.type !== "element") {
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
const local = getLocalName(child.name);
|
|
565
|
+
if (local === "p" || local === "h") {
|
|
566
|
+
paragraphs.push(parseParagraph(child, styles, { listStyleName, level }));
|
|
567
|
+
}
|
|
568
|
+
else if (local === "list") {
|
|
569
|
+
// Nested list
|
|
570
|
+
paragraphs.push(...parseList(child, styles, level + 1));
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return paragraphs;
|
|
575
|
+
}
|
|
576
|
+
/** Parse a table:table element into a Table. */
|
|
577
|
+
function parseTable(el, styles) {
|
|
578
|
+
const tableStyleName = nsAttr(el, "table", "style-name");
|
|
579
|
+
const tableStyle = tableStyleName ? styles.get(tableStyleName) : undefined;
|
|
580
|
+
// Parse column definitions for widths
|
|
581
|
+
const columnWidths = [];
|
|
582
|
+
const colElements = findNsChildren(el, "table", "table-column");
|
|
583
|
+
for (const colEl of colElements) {
|
|
584
|
+
const colStyleName = nsAttr(colEl, "table", "style-name");
|
|
585
|
+
const colStyle = colStyleName ? styles.get(colStyleName) : undefined;
|
|
586
|
+
const width = odfLengthToTwips(colStyle?.tableColumnProperties?.columnWidth);
|
|
587
|
+
const repeatCount = parseInt(nsAttr(colEl, "table", "number-columns-repeated") ?? "1", 10);
|
|
588
|
+
for (let i = 0; i < repeatCount; i++) {
|
|
589
|
+
columnWidths.push(width ?? 2000);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
// Parse rows
|
|
593
|
+
const rows = [];
|
|
594
|
+
const rowElements = findNsChildren(el, "table", "table-row");
|
|
595
|
+
for (const rowEl of rowElements) {
|
|
596
|
+
const cells = [];
|
|
597
|
+
// Iterate in document order (preserve natural order)
|
|
598
|
+
const orderedCells = [];
|
|
599
|
+
for (const child of rowEl.children) {
|
|
600
|
+
if (child.type === "element") {
|
|
601
|
+
const childLocal = getLocalName(child.name);
|
|
602
|
+
if (childLocal === "table-cell" || childLocal === "covered-table-cell") {
|
|
603
|
+
orderedCells.push(child);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
for (const cellEl of orderedCells) {
|
|
608
|
+
const cellLocal = getLocalName(cellEl.name);
|
|
609
|
+
const isCovered = cellLocal === "covered-table-cell";
|
|
610
|
+
const gridSpan = parseInt(nsAttr(cellEl, "table", "number-columns-spanned") ?? "1", 10);
|
|
611
|
+
const rowSpan = parseInt(nsAttr(cellEl, "table", "number-rows-spanned") ?? "1", 10);
|
|
612
|
+
const cellProps = {
|
|
613
|
+
...(gridSpan > 1 ? { gridSpan } : {}),
|
|
614
|
+
...(rowSpan > 1 ? { rowSpan } : {}),
|
|
615
|
+
...(isCovered ? { verticalMerge: "continue" } : {})
|
|
616
|
+
};
|
|
617
|
+
// Parse cell content
|
|
618
|
+
const cellContent = [];
|
|
619
|
+
for (const cellChild of cellEl.children) {
|
|
620
|
+
if (cellChild.type !== "element") {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
const cellChildLocal = getLocalName(cellChild.name);
|
|
624
|
+
if (cellChildLocal === "p" || cellChildLocal === "h") {
|
|
625
|
+
cellContent.push(parseParagraph(cellChild, styles));
|
|
626
|
+
}
|
|
627
|
+
else if (cellChildLocal === "table") {
|
|
628
|
+
cellContent.push(parseTable(cellChild, styles));
|
|
629
|
+
}
|
|
630
|
+
else if (cellChildLocal === "list") {
|
|
631
|
+
cellContent.push(...parseList(cellChild, styles, 0));
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
// Ensure at least one paragraph per cell
|
|
635
|
+
if (cellContent.length === 0) {
|
|
636
|
+
cellContent.push({ type: "paragraph", children: [] });
|
|
637
|
+
}
|
|
638
|
+
cells.push({
|
|
639
|
+
...(Object.keys(cellProps).length > 0 ? { properties: cellProps } : {}),
|
|
640
|
+
content: cellContent
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
rows.push({ cells });
|
|
644
|
+
}
|
|
645
|
+
// Build table properties
|
|
646
|
+
let tableProps;
|
|
647
|
+
const widthTwips = odfLengthToTwips(tableStyle?.tableProperties?.width);
|
|
648
|
+
if (widthTwips !== undefined || tableStyleName) {
|
|
649
|
+
tableProps = {
|
|
650
|
+
...(tableStyleName ? { style: tableStyleName } : {}),
|
|
651
|
+
...(widthTwips !== undefined ? { width: { value: widthTwips, type: "dxa" } } : {})
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
return {
|
|
655
|
+
type: "table",
|
|
656
|
+
...(tableProps ? { properties: tableProps } : {}),
|
|
657
|
+
...(columnWidths.length > 0 ? { columnWidths } : {}),
|
|
658
|
+
rows
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
/** Parse document body content from the office:body/office:text element. */
|
|
662
|
+
function parseDocumentBody(bodyEl, styles) {
|
|
663
|
+
const content = [];
|
|
664
|
+
for (const child of bodyEl.children) {
|
|
665
|
+
if (child.type !== "element") {
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
const local = getLocalName(child.name);
|
|
669
|
+
if (local === "p" || local === "h") {
|
|
670
|
+
content.push(parseParagraph(child, styles));
|
|
671
|
+
}
|
|
672
|
+
else if (local === "table") {
|
|
673
|
+
content.push(parseTable(child, styles));
|
|
674
|
+
}
|
|
675
|
+
else if (local === "list") {
|
|
676
|
+
content.push(...parseList(child, styles, 0));
|
|
677
|
+
}
|
|
678
|
+
else if (local === "section") {
|
|
679
|
+
// Sections in ODF are logical containers; flatten their content
|
|
680
|
+
content.push(...parseDocumentBody(child, styles));
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return content;
|
|
684
|
+
}
|
|
685
|
+
/** Get the local name from a possibly prefixed XML name. */
|
|
686
|
+
function getLocalName(name) {
|
|
687
|
+
const colonIdx = name.indexOf(":");
|
|
688
|
+
if (colonIdx >= 0) {
|
|
689
|
+
return name.substring(colonIdx + 1);
|
|
690
|
+
}
|
|
691
|
+
return name;
|
|
692
|
+
}
|
|
693
|
+
// =============================================================================
|
|
694
|
+
// Meta Parsing
|
|
695
|
+
// =============================================================================
|
|
696
|
+
/** Parse meta.xml into CoreProperties. */
|
|
697
|
+
function parseMetaXml(xml) {
|
|
698
|
+
let doc;
|
|
699
|
+
try {
|
|
700
|
+
doc = parseXml(xml);
|
|
701
|
+
}
|
|
702
|
+
catch {
|
|
703
|
+
return undefined;
|
|
704
|
+
}
|
|
705
|
+
const root = doc.root;
|
|
706
|
+
const metaEl = findNsChild(root, "office", "meta") ?? findChild(root, "office:meta") ?? root;
|
|
707
|
+
const title = getMetaText(metaEl, "dc", "title");
|
|
708
|
+
const subject = getMetaText(metaEl, "dc", "subject");
|
|
709
|
+
const creator = getMetaText(metaEl, "dc", "creator") ?? getMetaText(metaEl, "meta", "initial-creator");
|
|
710
|
+
const description = getMetaText(metaEl, "dc", "description");
|
|
711
|
+
const keywords = getMetaText(metaEl, "meta", "keyword");
|
|
712
|
+
const createdStr = getMetaText(metaEl, "meta", "creation-date");
|
|
713
|
+
const modifiedStr = getMetaText(metaEl, "dc", "date");
|
|
714
|
+
const created = createdStr ? new Date(createdStr) : undefined;
|
|
715
|
+
const modified = modifiedStr ? new Date(modifiedStr) : undefined;
|
|
716
|
+
const result = {
|
|
717
|
+
...(title ? { title } : {}),
|
|
718
|
+
...(subject ? { subject } : {}),
|
|
719
|
+
...(creator ? { creator } : {}),
|
|
720
|
+
...(description ? { description } : {}),
|
|
721
|
+
...(keywords ? { keywords } : {}),
|
|
722
|
+
...(created && !isNaN(created.getTime()) ? { created } : {}),
|
|
723
|
+
...(modified && !isNaN(modified.getTime()) ? { modified } : {})
|
|
724
|
+
};
|
|
725
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
726
|
+
}
|
|
727
|
+
/** Get text content of a metadata element. */
|
|
728
|
+
function getMetaText(parent, prefix, local) {
|
|
729
|
+
const el = findNsChild(parent, prefix, local);
|
|
730
|
+
if (!el) {
|
|
731
|
+
return undefined;
|
|
732
|
+
}
|
|
733
|
+
const text = textContent(el).trim();
|
|
734
|
+
return text || undefined;
|
|
735
|
+
}
|
|
736
|
+
// =============================================================================
|
|
737
|
+
// Page Layout Parsing
|
|
738
|
+
// =============================================================================
|
|
739
|
+
/** Parse page layout from styles.xml into SectionProperties. */
|
|
740
|
+
function parsePageLayoutToSection(pageLayout) {
|
|
741
|
+
if (!pageLayout) {
|
|
742
|
+
return undefined;
|
|
743
|
+
}
|
|
744
|
+
let pageSize;
|
|
745
|
+
const widthTwips = odfLengthToTwips(pageLayout.pageWidth);
|
|
746
|
+
const heightTwips = odfLengthToTwips(pageLayout.pageHeight);
|
|
747
|
+
if (widthTwips !== undefined && heightTwips !== undefined) {
|
|
748
|
+
pageSize = {
|
|
749
|
+
width: widthTwips,
|
|
750
|
+
height: heightTwips,
|
|
751
|
+
...(widthTwips > heightTwips ? { orientation: "landscape" } : {})
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
let margins;
|
|
755
|
+
const marginTop = odfLengthToTwips(pageLayout.marginTop);
|
|
756
|
+
const marginBottom = odfLengthToTwips(pageLayout.marginBottom);
|
|
757
|
+
const marginLeft = odfLengthToTwips(pageLayout.marginLeft);
|
|
758
|
+
const marginRight = odfLengthToTwips(pageLayout.marginRight);
|
|
759
|
+
if (marginTop !== undefined ||
|
|
760
|
+
marginBottom !== undefined ||
|
|
761
|
+
marginLeft !== undefined ||
|
|
762
|
+
marginRight !== undefined) {
|
|
763
|
+
margins = {
|
|
764
|
+
top: marginTop ?? 1440,
|
|
765
|
+
right: marginRight ?? 1440,
|
|
766
|
+
bottom: marginBottom ?? 1440,
|
|
767
|
+
left: marginLeft ?? 1440
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
if (!pageSize && !margins) {
|
|
771
|
+
return undefined;
|
|
772
|
+
}
|
|
773
|
+
return {
|
|
774
|
+
...(pageSize ? { pageSize } : {}),
|
|
775
|
+
...(margins ? { margins } : {})
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
// =============================================================================
|
|
779
|
+
// Styles Conversion → StyleDef[]
|
|
780
|
+
// =============================================================================
|
|
781
|
+
/** Convert parsed ODF styles to DocxDocument style definitions. */
|
|
782
|
+
function convertStylesToStyleDefs(styles) {
|
|
783
|
+
const defs = [];
|
|
784
|
+
for (const [, style] of styles) {
|
|
785
|
+
// Only convert named/non-automatic styles
|
|
786
|
+
if (!style.name) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
let type;
|
|
790
|
+
switch (style.family) {
|
|
791
|
+
case "paragraph":
|
|
792
|
+
type = "paragraph";
|
|
793
|
+
break;
|
|
794
|
+
case "text":
|
|
795
|
+
type = "character";
|
|
796
|
+
break;
|
|
797
|
+
case "table":
|
|
798
|
+
type = "table";
|
|
799
|
+
break;
|
|
800
|
+
default:
|
|
801
|
+
continue; // Skip unsupported style families
|
|
802
|
+
}
|
|
803
|
+
const def = {
|
|
804
|
+
type,
|
|
805
|
+
styleId: style.name,
|
|
806
|
+
name: style.name,
|
|
807
|
+
...(style.parentStyle ? { basedOn: style.parentStyle } : {}),
|
|
808
|
+
...(style.paragraphProperties
|
|
809
|
+
? { paragraphProperties: odfParagraphPropsToDocx(style.paragraphProperties) }
|
|
810
|
+
: {}),
|
|
811
|
+
...(style.textProperties ? { runProperties: odfTextPropsToDocx(style.textProperties) } : {})
|
|
812
|
+
};
|
|
813
|
+
defs.push(def);
|
|
814
|
+
}
|
|
815
|
+
return defs;
|
|
816
|
+
}
|
|
817
|
+
// =============================================================================
|
|
818
|
+
// ODT list numbering definition (numId=1)
|
|
819
|
+
// =============================================================================
|
|
820
|
+
/**
|
|
821
|
+
* Walk the converted body and, if any paragraph references `numbering.numId`,
|
|
822
|
+
* synthesize the corresponding `abstractNumberings` and `numberingInstances`
|
|
823
|
+
* so Word renders bullet/number markers. Without this the list paragraphs
|
|
824
|
+
* carry a `numId` that resolves to nothing in the produced document and the
|
|
825
|
+
* markers are invisible.
|
|
826
|
+
*
|
|
827
|
+
* The reader currently only emits a single bulleted abstract definition for
|
|
828
|
+
* `numId === 1` (matching parseParagraph above). Numbered/multi-level ODT
|
|
829
|
+
* lists fall back to bullets for now — preserving the visible structure is
|
|
830
|
+
* the primary goal.
|
|
831
|
+
*/
|
|
832
|
+
function buildOdtNumberingDefs(body) {
|
|
833
|
+
if (!hasListParagraph(body)) {
|
|
834
|
+
return {};
|
|
835
|
+
}
|
|
836
|
+
const tabSuffix = "tab";
|
|
837
|
+
const bulletChars = ["•", "◦", "▪", "•", "◦", "▪", "•", "◦", "▪"];
|
|
838
|
+
const levels = bulletChars.map((ch, level) => ({
|
|
839
|
+
level,
|
|
840
|
+
format: "bullet",
|
|
841
|
+
text: ch,
|
|
842
|
+
start: 1,
|
|
843
|
+
paragraphProperties: {
|
|
844
|
+
indent: { left: 720 * (level + 1), hanging: 360 }
|
|
845
|
+
},
|
|
846
|
+
suffix: tabSuffix
|
|
847
|
+
}));
|
|
848
|
+
return {
|
|
849
|
+
abstractNumberings: [{ abstractNumId: 1, levels }],
|
|
850
|
+
numberingInstances: [{ numId: 1, abstractNumId: 1 }]
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
function hasListParagraph(blocks) {
|
|
854
|
+
for (const block of blocks) {
|
|
855
|
+
if (block.type === "paragraph") {
|
|
856
|
+
if (block.properties?.numbering) {
|
|
857
|
+
return true;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
else if (block.type === "table") {
|
|
861
|
+
for (const row of block.rows) {
|
|
862
|
+
for (const cell of row.cells) {
|
|
863
|
+
if (hasListParagraph(cell.content)) {
|
|
864
|
+
return true;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return false;
|
|
871
|
+
}
|
|
872
|
+
// =============================================================================
|
|
873
|
+
// readOdt — Main Entry Point
|
|
874
|
+
// =============================================================================
|
|
875
|
+
/**
|
|
876
|
+
* Read an ODT file and convert to DocxDocument model.
|
|
877
|
+
*
|
|
878
|
+
* Extracts the ZIP archive, parses content.xml, styles.xml, and meta.xml,
|
|
879
|
+
* and produces a unified DocxDocument representation.
|
|
880
|
+
*
|
|
881
|
+
* @param buffer - The ODT file as a Uint8Array.
|
|
882
|
+
* @returns A DocxDocument representing the ODT content.
|
|
883
|
+
* @throws {DocxParseError} If the ODT file is malformed or missing required parts.
|
|
884
|
+
*
|
|
885
|
+
* @stability experimental
|
|
886
|
+
*/
|
|
887
|
+
export async function readOdt(buffer) {
|
|
888
|
+
// Extract ZIP contents
|
|
889
|
+
const reader = unzip(buffer);
|
|
890
|
+
const entries = new Map();
|
|
891
|
+
for await (const entry of reader.entries()) {
|
|
892
|
+
const data = await entry.bytes();
|
|
893
|
+
const path = entry.path.replace(/^\//, "").replace(/\\/g, "/");
|
|
894
|
+
entries.set(path, data);
|
|
895
|
+
}
|
|
896
|
+
const decoder = utf8Decoder;
|
|
897
|
+
// Parse content.xml (required)
|
|
898
|
+
const contentData = entries.get("content.xml");
|
|
899
|
+
if (!contentData) {
|
|
900
|
+
throw new DocxParseError("Required ODT part not found: content.xml");
|
|
901
|
+
}
|
|
902
|
+
const contentXml = decoder.decode(contentData);
|
|
903
|
+
const contentDoc = parseXml(contentXml);
|
|
904
|
+
// Parse styles.xml (optional)
|
|
905
|
+
const stylesData = entries.get("styles.xml");
|
|
906
|
+
let stylesXml;
|
|
907
|
+
if (stylesData) {
|
|
908
|
+
stylesXml = decoder.decode(stylesData);
|
|
909
|
+
}
|
|
910
|
+
// Parse meta.xml (optional)
|
|
911
|
+
const metaData = entries.get("meta.xml");
|
|
912
|
+
let coreProperties;
|
|
913
|
+
if (metaData) {
|
|
914
|
+
coreProperties = parseMetaXml(decoder.decode(metaData));
|
|
915
|
+
}
|
|
916
|
+
// Collect all styles (from both content.xml and styles.xml)
|
|
917
|
+
const allStyles = new Map();
|
|
918
|
+
let pageLayoutProps;
|
|
919
|
+
// Parse styles from styles.xml
|
|
920
|
+
if (stylesXml) {
|
|
921
|
+
const stylesDoc = parseXml(stylesXml);
|
|
922
|
+
const stylesRoot = stylesDoc.root;
|
|
923
|
+
// Named styles (office:styles)
|
|
924
|
+
const officeStylesEl = findNsChild(stylesRoot, "office", "styles");
|
|
925
|
+
if (officeStylesEl) {
|
|
926
|
+
for (const child of officeStylesEl.children) {
|
|
927
|
+
if (child.type === "element" && getLocalName(child.name) === "style") {
|
|
928
|
+
const s = parseOdfStyle(child);
|
|
929
|
+
if (s.name) {
|
|
930
|
+
allStyles.set(s.name, s);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
// Automatic styles in styles.xml (office:automatic-styles)
|
|
936
|
+
const autoStylesEl = findNsChild(stylesRoot, "office", "automatic-styles");
|
|
937
|
+
if (autoStylesEl) {
|
|
938
|
+
for (const child of autoStylesEl.children) {
|
|
939
|
+
if (child.type === "element") {
|
|
940
|
+
const childLocal = getLocalName(child.name);
|
|
941
|
+
if (childLocal === "style") {
|
|
942
|
+
const s = parseOdfStyle(child);
|
|
943
|
+
if (s.name) {
|
|
944
|
+
allStyles.set(s.name, s);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
else if (childLocal === "page-layout") {
|
|
948
|
+
pageLayoutProps = parsePageLayout(child);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
// Master page styles for page layout reference
|
|
954
|
+
const masterStylesEl = findNsChild(stylesRoot, "office", "master-styles");
|
|
955
|
+
if (masterStylesEl && !pageLayoutProps) {
|
|
956
|
+
// Look for the page layout reference in master pages
|
|
957
|
+
for (const child of masterStylesEl.children) {
|
|
958
|
+
if (child.type === "element" && getLocalName(child.name) === "master-page") {
|
|
959
|
+
const pageLayoutName = nsAttr(child, "style", "page-layout-name");
|
|
960
|
+
if (pageLayoutName && autoStylesEl) {
|
|
961
|
+
for (const autoChild of autoStylesEl.children) {
|
|
962
|
+
if (autoChild.type === "element" &&
|
|
963
|
+
getLocalName(autoChild.name) === "page-layout" &&
|
|
964
|
+
nsAttr(autoChild, "style", "name") === pageLayoutName) {
|
|
965
|
+
pageLayoutProps = parsePageLayout(autoChild);
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
break;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
// Parse automatic styles from content.xml
|
|
976
|
+
const contentAutoStylesEl = findNsChild(contentDoc.root, "office", "automatic-styles");
|
|
977
|
+
if (contentAutoStylesEl) {
|
|
978
|
+
for (const child of contentAutoStylesEl.children) {
|
|
979
|
+
if (child.type === "element") {
|
|
980
|
+
const childLocal = getLocalName(child.name);
|
|
981
|
+
if (childLocal === "style") {
|
|
982
|
+
const s = parseOdfStyle(child);
|
|
983
|
+
if (s.name) {
|
|
984
|
+
allStyles.set(s.name, s);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
else if (childLocal === "page-layout" && !pageLayoutProps) {
|
|
988
|
+
pageLayoutProps = parsePageLayout(child);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
// Parse document body
|
|
994
|
+
const bodyEl = findNsChild(contentDoc.root, "office", "body");
|
|
995
|
+
if (!bodyEl) {
|
|
996
|
+
throw new DocxParseError("Invalid ODT: missing office:body element");
|
|
997
|
+
}
|
|
998
|
+
const textEl = findNsChild(bodyEl, "office", "text");
|
|
999
|
+
if (!textEl) {
|
|
1000
|
+
throw new DocxParseError("Invalid ODT: missing office:text element");
|
|
1001
|
+
}
|
|
1002
|
+
const body = parseDocumentBody(textEl, allStyles);
|
|
1003
|
+
// Convert styles to StyleDef[]
|
|
1004
|
+
const styleDefs = convertStylesToStyleDefs(allStyles);
|
|
1005
|
+
// Parse section properties from page layout
|
|
1006
|
+
const sectionProperties = parsePageLayoutToSection(pageLayoutProps);
|
|
1007
|
+
// Collect images
|
|
1008
|
+
const images = [];
|
|
1009
|
+
for (const [path, data] of entries) {
|
|
1010
|
+
if (path.startsWith("Pictures/") && data.length > 0) {
|
|
1011
|
+
const ext = path.substring(path.lastIndexOf(".") + 1).toLowerCase();
|
|
1012
|
+
let mediaType;
|
|
1013
|
+
switch (ext) {
|
|
1014
|
+
case "png":
|
|
1015
|
+
mediaType = "image/png";
|
|
1016
|
+
break;
|
|
1017
|
+
case "jpg":
|
|
1018
|
+
case "jpeg":
|
|
1019
|
+
mediaType = "image/jpeg";
|
|
1020
|
+
break;
|
|
1021
|
+
case "gif":
|
|
1022
|
+
mediaType = "image/gif";
|
|
1023
|
+
break;
|
|
1024
|
+
case "svg":
|
|
1025
|
+
mediaType = "image/svg+xml";
|
|
1026
|
+
break;
|
|
1027
|
+
case "emf":
|
|
1028
|
+
mediaType = "image/x-emf";
|
|
1029
|
+
break;
|
|
1030
|
+
case "wmf":
|
|
1031
|
+
mediaType = "image/x-wmf";
|
|
1032
|
+
break;
|
|
1033
|
+
case "tiff":
|
|
1034
|
+
case "tif":
|
|
1035
|
+
mediaType = "image/tiff";
|
|
1036
|
+
break;
|
|
1037
|
+
case "bmp":
|
|
1038
|
+
mediaType = "image/bmp";
|
|
1039
|
+
break;
|
|
1040
|
+
default:
|
|
1041
|
+
mediaType = "application/octet-stream";
|
|
1042
|
+
break;
|
|
1043
|
+
}
|
|
1044
|
+
images.push({
|
|
1045
|
+
rId: path,
|
|
1046
|
+
data,
|
|
1047
|
+
mediaType: mediaType,
|
|
1048
|
+
fileName: path.substring(path.lastIndexOf("/") + 1)
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
const doc = {
|
|
1053
|
+
body,
|
|
1054
|
+
...(sectionProperties ? { sectionProperties } : {}),
|
|
1055
|
+
...(styleDefs.length > 0 ? { styles: styleDefs } : {}),
|
|
1056
|
+
...(coreProperties ? { coreProperties } : {}),
|
|
1057
|
+
...(images.length > 0 ? { images } : {}),
|
|
1058
|
+
...buildOdtNumberingDefs(body)
|
|
1059
|
+
};
|
|
1060
|
+
return doc;
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Build a deterministic, ZIP-safe `Pictures/` path for each image in the
|
|
1064
|
+
* document. The same rId → path map is consumed by both the content
|
|
1065
|
+
* writer (`xlink:href`) and the archive writer so that what we point at
|
|
1066
|
+
* in content.xml actually exists in the package.
|
|
1067
|
+
*
|
|
1068
|
+
* Image identifiers (`rId`, `fileName`) come from arbitrary upstream
|
|
1069
|
+
* input — including untrusted DOCX files round-tripped through readDocx.
|
|
1070
|
+
* Without sanitisation a hostile rId like `../../etc/passwd` would
|
|
1071
|
+
* produce both an out-of-tree ZIP entry name and an out-of-package
|
|
1072
|
+
* `xlink:href`, which is a real vector for confusing downstream ODT
|
|
1073
|
+
* readers and tooling that resolves relative paths.
|
|
1074
|
+
*/
|
|
1075
|
+
function buildOdtImagePathMap(images) {
|
|
1076
|
+
const byRId = new Map();
|
|
1077
|
+
const byFileName = new Map();
|
|
1078
|
+
if (!images) {
|
|
1079
|
+
return { byRId, byFileName };
|
|
1080
|
+
}
|
|
1081
|
+
const used = new Set();
|
|
1082
|
+
// Allocate a unique `Pictures/<safe>` path for each image. We generate
|
|
1083
|
+
// a single counter-based fallback when the preferred name is empty or
|
|
1084
|
+
// collides with an entry that's already taken; the previous version
|
|
1085
|
+
// double-incremented `counter` (once inside the collision loop, once
|
|
1086
|
+
// after) producing sparse names like `image1.bin`, `image3.bin`, ….
|
|
1087
|
+
let counter = 1;
|
|
1088
|
+
const allocate = (preferred) => {
|
|
1089
|
+
const safeBase = sanitizeOdtPictureName(preferred);
|
|
1090
|
+
let candidate = safeBase || `image${counter++}.bin`;
|
|
1091
|
+
while (used.has(candidate)) {
|
|
1092
|
+
// Suffix a fresh counter to disambiguate; preserve the extension
|
|
1093
|
+
// when we have one so downstream readers still classify the file
|
|
1094
|
+
// correctly. `_bin` was the previous fallback when no extension
|
|
1095
|
+
// was available — keep that behaviour for the no-base case.
|
|
1096
|
+
const c = counter++;
|
|
1097
|
+
if (safeBase) {
|
|
1098
|
+
const dot = safeBase.lastIndexOf(".");
|
|
1099
|
+
const stem = dot >= 0 ? safeBase.slice(0, dot) : safeBase;
|
|
1100
|
+
const ext = dot >= 0 ? safeBase.slice(dot) : "";
|
|
1101
|
+
candidate = `${stem}_${c}${ext}`;
|
|
1102
|
+
}
|
|
1103
|
+
else {
|
|
1104
|
+
candidate = `image${c}.bin`;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
used.add(candidate);
|
|
1108
|
+
return `Pictures/${candidate}`;
|
|
1109
|
+
};
|
|
1110
|
+
for (const image of images) {
|
|
1111
|
+
const path = allocate(image.fileName ?? image.rId);
|
|
1112
|
+
if (image.rId) {
|
|
1113
|
+
byRId.set(image.rId, path);
|
|
1114
|
+
}
|
|
1115
|
+
if (image.fileName) {
|
|
1116
|
+
// Two distinct images might share a fileName (the inputs are
|
|
1117
|
+
// attacker-controlled in the round-trip path). The first one to
|
|
1118
|
+
// claim a fileName wins the byFileName lookup; later collisions
|
|
1119
|
+
// are still reachable via their own rId in byRId. Without this
|
|
1120
|
+
// guard the second insertion would silently overwrite the first
|
|
1121
|
+
// and inline-image lookups via fileName would resolve to the
|
|
1122
|
+
// wrong binary.
|
|
1123
|
+
if (!byFileName.has(image.fileName)) {
|
|
1124
|
+
byFileName.set(image.fileName, path);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return { byRId, byFileName };
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Sanitise a single ODT picture file-name component. Strips path
|
|
1132
|
+
* separators, parent-directory traversal, leading dots, and anything
|
|
1133
|
+
* outside a conservative whitelist. Returns an empty string if no usable
|
|
1134
|
+
* characters remain (caller falls back to a generated name).
|
|
1135
|
+
*/
|
|
1136
|
+
function sanitizeOdtPictureName(raw) {
|
|
1137
|
+
if (!raw) {
|
|
1138
|
+
return "";
|
|
1139
|
+
}
|
|
1140
|
+
// Drop directory components — we only ever want a leaf file name.
|
|
1141
|
+
const lastSep = Math.max(raw.lastIndexOf("/"), raw.lastIndexOf("\\"));
|
|
1142
|
+
let leaf = lastSep >= 0 ? raw.substring(lastSep + 1) : raw;
|
|
1143
|
+
// Strip leading dots so names like "..png" or ".htaccess" don't sneak
|
|
1144
|
+
// through with attribute meaning to filesystems.
|
|
1145
|
+
while (leaf.startsWith(".")) {
|
|
1146
|
+
leaf = leaf.substring(1);
|
|
1147
|
+
}
|
|
1148
|
+
// Whitelist alnum, dash, underscore, dot. Replace everything else with
|
|
1149
|
+
// underscore. Empty result triggers fallback in the caller.
|
|
1150
|
+
leaf = leaf.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
1151
|
+
// Avoid pathological double-dot anywhere mid-name (e.g. "foo..bin").
|
|
1152
|
+
leaf = leaf.replace(/\.{2,}/g, ".");
|
|
1153
|
+
return leaf;
|
|
1154
|
+
}
|
|
1155
|
+
// =============================================================================
|
|
1156
|
+
// writeOdt — Main Entry Point
|
|
1157
|
+
// =============================================================================
|
|
1158
|
+
/**
|
|
1159
|
+
* Convert a DocxDocument to ODT (OpenDocument Text) format.
|
|
1160
|
+
*
|
|
1161
|
+
* Generates the ZIP archive structure with content.xml, styles.xml,
|
|
1162
|
+
* meta.xml, and META-INF/manifest.xml.
|
|
1163
|
+
*
|
|
1164
|
+
* @param doc - The DocxDocument to convert.
|
|
1165
|
+
* @returns A Uint8Array containing the ODT ZIP archive.
|
|
1166
|
+
*
|
|
1167
|
+
* @stability experimental
|
|
1168
|
+
*/
|
|
1169
|
+
export async function writeOdt(doc) {
|
|
1170
|
+
const encoder = utf8Encoder;
|
|
1171
|
+
const archive = zip();
|
|
1172
|
+
// Mimetype MUST be the first entry in the ZIP, uncompressed (ODF spec requirement)
|
|
1173
|
+
archive.add("mimetype", encoder.encode("application/vnd.oasis.opendocument.text"), { level: 0 });
|
|
1174
|
+
// Compute a sanitised rId → Pictures/<safe>.<ext> map up front so the
|
|
1175
|
+
// content writer and archive writer agree on every image path.
|
|
1176
|
+
const imageMap = buildOdtImagePathMap(doc.images);
|
|
1177
|
+
// Collect image paths for manifest
|
|
1178
|
+
const imagePaths = [];
|
|
1179
|
+
// Generate content.xml — the writer reads from imageMap so href values
|
|
1180
|
+
// are guaranteed in-package and free of traversal sequences.
|
|
1181
|
+
const contentXml = generateContentXml(doc, imagePaths, imageMap);
|
|
1182
|
+
archive.add("content.xml", encoder.encode(contentXml));
|
|
1183
|
+
// Generate styles.xml
|
|
1184
|
+
const stylesXml = generateStylesXml(doc);
|
|
1185
|
+
archive.add("styles.xml", encoder.encode(stylesXml));
|
|
1186
|
+
// Generate meta.xml
|
|
1187
|
+
const metaXml = generateMetaXml(doc);
|
|
1188
|
+
archive.add("meta.xml", encoder.encode(metaXml));
|
|
1189
|
+
// Add images at the sanitised paths.
|
|
1190
|
+
if (doc.images) {
|
|
1191
|
+
for (const image of doc.images) {
|
|
1192
|
+
if (!image.data) {
|
|
1193
|
+
continue;
|
|
1194
|
+
}
|
|
1195
|
+
const imgPath = (image.rId ? imageMap.byRId.get(image.rId) : undefined) ??
|
|
1196
|
+
(image.fileName ? imageMap.byFileName.get(image.fileName) : undefined);
|
|
1197
|
+
if (!imgPath) {
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
archive.add(imgPath, image.data);
|
|
1201
|
+
imagePaths.push(imgPath);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
// Generate META-INF/manifest.xml
|
|
1205
|
+
const manifestXml = generateManifestXml(imagePaths);
|
|
1206
|
+
archive.add("META-INF/manifest.xml", encoder.encode(manifestXml));
|
|
1207
|
+
return archive.bytes();
|
|
1208
|
+
}
|
|
1209
|
+
/** Create a new write context. */
|
|
1210
|
+
function createWriteContext(imageMap) {
|
|
1211
|
+
return {
|
|
1212
|
+
runStyleMap: new Map(),
|
|
1213
|
+
runStyleProps: new Map(),
|
|
1214
|
+
nextRunStyleId: 1,
|
|
1215
|
+
bookmarkNames: new Map(),
|
|
1216
|
+
imagePathByRId: imageMap?.byRId ?? new Map(),
|
|
1217
|
+
imagePathByFileName: imageMap?.byFileName ?? new Map()
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Generate a deterministic cache key for RunProperties.
|
|
1222
|
+
* Only includes formatting-relevant properties.
|
|
1223
|
+
*/
|
|
1224
|
+
function runPropsKey(props) {
|
|
1225
|
+
const parts = [];
|
|
1226
|
+
if (props.font) {
|
|
1227
|
+
const fontName = typeof props.font === "string" ? props.font : props.font.ascii;
|
|
1228
|
+
if (fontName) {
|
|
1229
|
+
parts.push(`f:${fontName}`);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
if (props.size !== undefined) {
|
|
1233
|
+
parts.push(`s:${props.size}`);
|
|
1234
|
+
}
|
|
1235
|
+
if (props.bold) {
|
|
1236
|
+
parts.push("b");
|
|
1237
|
+
}
|
|
1238
|
+
if (props.italic) {
|
|
1239
|
+
parts.push("i");
|
|
1240
|
+
}
|
|
1241
|
+
if (props.underline) {
|
|
1242
|
+
parts.push("u");
|
|
1243
|
+
}
|
|
1244
|
+
if (props.strike) {
|
|
1245
|
+
parts.push("st");
|
|
1246
|
+
}
|
|
1247
|
+
if (props.color) {
|
|
1248
|
+
const colorVal = typeof props.color === "string" ? props.color : props.color.val;
|
|
1249
|
+
if (colorVal) {
|
|
1250
|
+
parts.push(`c:${colorVal}`);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
if (props.smallCaps) {
|
|
1254
|
+
parts.push("sc");
|
|
1255
|
+
}
|
|
1256
|
+
if (props.vertAlign) {
|
|
1257
|
+
parts.push(`va:${props.vertAlign}`);
|
|
1258
|
+
}
|
|
1259
|
+
if (props.spacing !== undefined) {
|
|
1260
|
+
parts.push(`sp:${props.spacing}`);
|
|
1261
|
+
}
|
|
1262
|
+
return parts.join("|");
|
|
1263
|
+
}
|
|
1264
|
+
/** Get or create an automatic style name for the given RunProperties. */
|
|
1265
|
+
function getRunAutoStyleName(ctx, props) {
|
|
1266
|
+
const key = runPropsKey(props);
|
|
1267
|
+
let name = ctx.runStyleMap.get(key);
|
|
1268
|
+
if (!name) {
|
|
1269
|
+
name = `T${ctx.nextRunStyleId++}`;
|
|
1270
|
+
ctx.runStyleMap.set(key, name);
|
|
1271
|
+
ctx.runStyleProps.set(name, props);
|
|
1272
|
+
}
|
|
1273
|
+
return name;
|
|
1274
|
+
}
|
|
1275
|
+
/** Generate the content.xml for the ODT package. */
|
|
1276
|
+
function generateContentXml(doc, imagePaths, imageMap) {
|
|
1277
|
+
// First pass: write body to collect automatic run styles
|
|
1278
|
+
const ctx = createWriteContext(imageMap);
|
|
1279
|
+
const bodyWriter = new XmlWriter();
|
|
1280
|
+
bodyWriter.openNode("office:body");
|
|
1281
|
+
bodyWriter.openNode("office:text");
|
|
1282
|
+
for (const block of doc.body) {
|
|
1283
|
+
writeBodyContent(bodyWriter, block, doc, imagePaths, ctx);
|
|
1284
|
+
}
|
|
1285
|
+
bodyWriter.closeNode(); // office:text
|
|
1286
|
+
bodyWriter.closeNode(); // office:body
|
|
1287
|
+
const bodyXml = bodyWriter.xml;
|
|
1288
|
+
// Second pass: assemble the full content.xml with collected automatic styles
|
|
1289
|
+
const w = new XmlWriter();
|
|
1290
|
+
w.openXml();
|
|
1291
|
+
w.openNode("office:document-content", {
|
|
1292
|
+
"xmlns:office": NS.office,
|
|
1293
|
+
"xmlns:style": NS.style,
|
|
1294
|
+
"xmlns:text": NS.text,
|
|
1295
|
+
"xmlns:table": NS.table,
|
|
1296
|
+
"xmlns:draw": NS.draw,
|
|
1297
|
+
"xmlns:fo": NS.fo,
|
|
1298
|
+
"xmlns:xlink": NS.xlink,
|
|
1299
|
+
"xmlns:svg": NS.svg,
|
|
1300
|
+
"office:version": "1.3"
|
|
1301
|
+
});
|
|
1302
|
+
// Automatic styles section
|
|
1303
|
+
w.openNode("office:automatic-styles");
|
|
1304
|
+
writeAutoStyles(w, doc);
|
|
1305
|
+
writeCollectedRunStyles(w, ctx);
|
|
1306
|
+
w.closeNode();
|
|
1307
|
+
// Body (inject pre-built body XML)
|
|
1308
|
+
w.writeRaw(bodyXml);
|
|
1309
|
+
w.closeNode(); // office:document-content
|
|
1310
|
+
return w.xml;
|
|
1311
|
+
}
|
|
1312
|
+
/** Write collected automatic run styles into the automatic-styles section. */
|
|
1313
|
+
function writeCollectedRunStyles(w, ctx) {
|
|
1314
|
+
for (const [styleName, props] of ctx.runStyleProps) {
|
|
1315
|
+
w.openNode("style:style", {
|
|
1316
|
+
"style:name": styleName,
|
|
1317
|
+
"style:family": "text"
|
|
1318
|
+
});
|
|
1319
|
+
writeTextPropertiesOdf(w, props);
|
|
1320
|
+
w.closeNode();
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
/** Write automatic styles based on document content. */
|
|
1324
|
+
function writeAutoStyles(w, doc) {
|
|
1325
|
+
// Output styles from the StyleDef array that exist in the document.
|
|
1326
|
+
if (doc.styles) {
|
|
1327
|
+
for (const styleDef of doc.styles) {
|
|
1328
|
+
writeStyleDef(w, styleDef);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
/** Write a single StyleDef as an ODF style. */
|
|
1333
|
+
function writeStyleDef(w, def) {
|
|
1334
|
+
const family = styleTypeToOdfFamily(def.type);
|
|
1335
|
+
if (!family) {
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
w.openNode("style:style", {
|
|
1339
|
+
"style:name": def.styleId,
|
|
1340
|
+
"style:family": family,
|
|
1341
|
+
...(def.basedOn ? { "style:parent-style-name": def.basedOn } : {}),
|
|
1342
|
+
...(def.name !== def.styleId ? { "style:display-name": def.name } : {})
|
|
1343
|
+
});
|
|
1344
|
+
// Paragraph properties
|
|
1345
|
+
if (def.paragraphProperties) {
|
|
1346
|
+
writeParagraphPropertiesOdf(w, def.paragraphProperties);
|
|
1347
|
+
}
|
|
1348
|
+
// Run/text properties
|
|
1349
|
+
if (def.runProperties) {
|
|
1350
|
+
writeTextPropertiesOdf(w, def.runProperties);
|
|
1351
|
+
}
|
|
1352
|
+
w.closeNode();
|
|
1353
|
+
}
|
|
1354
|
+
/** Convert DocxDocument style type to ODF style family. */
|
|
1355
|
+
function styleTypeToOdfFamily(type) {
|
|
1356
|
+
switch (type) {
|
|
1357
|
+
case "paragraph":
|
|
1358
|
+
return "paragraph";
|
|
1359
|
+
case "character":
|
|
1360
|
+
return "text";
|
|
1361
|
+
case "table":
|
|
1362
|
+
return "table";
|
|
1363
|
+
default:
|
|
1364
|
+
return undefined;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
/** Write paragraph properties as ODF elements. */
|
|
1368
|
+
function writeParagraphPropertiesOdf(w, props) {
|
|
1369
|
+
const attrs = {};
|
|
1370
|
+
if (props.alignment) {
|
|
1371
|
+
attrs["fo:text-align"] = alignmentToOdf(props.alignment);
|
|
1372
|
+
}
|
|
1373
|
+
if (props.indent) {
|
|
1374
|
+
if (props.indent.left !== undefined) {
|
|
1375
|
+
attrs["fo:margin-left"] = twipsToCm(props.indent.left);
|
|
1376
|
+
}
|
|
1377
|
+
if (props.indent.right !== undefined) {
|
|
1378
|
+
attrs["fo:margin-right"] = twipsToCm(props.indent.right);
|
|
1379
|
+
}
|
|
1380
|
+
if (props.indent.firstLine !== undefined) {
|
|
1381
|
+
attrs["fo:text-indent"] = twipsToCm(props.indent.firstLine);
|
|
1382
|
+
}
|
|
1383
|
+
else if (props.indent.hanging !== undefined) {
|
|
1384
|
+
attrs["fo:text-indent"] = twipsToCm(-props.indent.hanging);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
if (props.spacing) {
|
|
1388
|
+
if (props.spacing.before !== undefined) {
|
|
1389
|
+
attrs["fo:margin-top"] = twipsToCm(props.spacing.before);
|
|
1390
|
+
}
|
|
1391
|
+
if (props.spacing.after !== undefined) {
|
|
1392
|
+
attrs["fo:margin-bottom"] = twipsToCm(props.spacing.after);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
if (props.keepNext) {
|
|
1396
|
+
attrs["fo:keep-with-next"] = "always";
|
|
1397
|
+
}
|
|
1398
|
+
if (props.pageBreakBefore) {
|
|
1399
|
+
attrs["fo:break-before"] = "page";
|
|
1400
|
+
}
|
|
1401
|
+
if (Object.keys(attrs).length > 0) {
|
|
1402
|
+
w.leafNode("style:paragraph-properties", attrs);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
/** Write run/text properties as ODF elements. */
|
|
1406
|
+
function writeTextPropertiesOdf(w, props) {
|
|
1407
|
+
const attrs = {};
|
|
1408
|
+
if (props.font) {
|
|
1409
|
+
const fontName = typeof props.font === "string" ? props.font : props.font.ascii;
|
|
1410
|
+
if (fontName) {
|
|
1411
|
+
attrs["style:font-name"] = fontName;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
if (props.size !== undefined) {
|
|
1415
|
+
attrs["fo:font-size"] = halfPointsToPt(props.size);
|
|
1416
|
+
}
|
|
1417
|
+
if (props.bold) {
|
|
1418
|
+
attrs["fo:font-weight"] = "bold";
|
|
1419
|
+
}
|
|
1420
|
+
if (props.italic) {
|
|
1421
|
+
attrs["fo:font-style"] = "italic";
|
|
1422
|
+
}
|
|
1423
|
+
if (props.underline) {
|
|
1424
|
+
attrs["style:text-underline-style"] = "solid";
|
|
1425
|
+
attrs["style:text-underline-width"] = "auto";
|
|
1426
|
+
attrs["style:text-underline-color"] = "font-color";
|
|
1427
|
+
}
|
|
1428
|
+
if (props.strike) {
|
|
1429
|
+
attrs["style:text-line-through-style"] = "solid";
|
|
1430
|
+
}
|
|
1431
|
+
if (props.color) {
|
|
1432
|
+
const colorVal = typeof props.color === "string" ? props.color : props.color.val;
|
|
1433
|
+
if (colorVal && colorVal !== "auto") {
|
|
1434
|
+
attrs["fo:color"] = colorToOdf(colorVal);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
if (props.smallCaps) {
|
|
1438
|
+
attrs["fo:font-variant"] = "small-caps";
|
|
1439
|
+
}
|
|
1440
|
+
if (props.vertAlign === "superscript") {
|
|
1441
|
+
attrs["style:text-position"] = "super 58%";
|
|
1442
|
+
}
|
|
1443
|
+
else if (props.vertAlign === "subscript") {
|
|
1444
|
+
attrs["style:text-position"] = "sub 58%";
|
|
1445
|
+
}
|
|
1446
|
+
if (props.spacing !== undefined) {
|
|
1447
|
+
attrs["fo:letter-spacing"] = twipsToCm(props.spacing);
|
|
1448
|
+
}
|
|
1449
|
+
if (Object.keys(attrs).length > 0) {
|
|
1450
|
+
w.leafNode("style:text-properties", attrs);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
/** Convert OOXML alignment to ODF text-align value. */
|
|
1454
|
+
function alignmentToOdf(alignment) {
|
|
1455
|
+
switch (alignment) {
|
|
1456
|
+
case "left":
|
|
1457
|
+
case "start":
|
|
1458
|
+
return "start";
|
|
1459
|
+
case "center":
|
|
1460
|
+
return "center";
|
|
1461
|
+
case "right":
|
|
1462
|
+
case "end":
|
|
1463
|
+
return "end";
|
|
1464
|
+
case "both":
|
|
1465
|
+
return "justify";
|
|
1466
|
+
default:
|
|
1467
|
+
return "start";
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
/** Write a block-level content element to the XML writer. */
|
|
1471
|
+
function writeBodyContent(w, block, doc, imagePaths, ctx) {
|
|
1472
|
+
switch (block.type) {
|
|
1473
|
+
case "paragraph":
|
|
1474
|
+
writeParagraph(w, block, doc, imagePaths, ctx);
|
|
1475
|
+
break;
|
|
1476
|
+
case "table":
|
|
1477
|
+
writeTable(w, block, doc, imagePaths, ctx);
|
|
1478
|
+
break;
|
|
1479
|
+
case "tableOfContents":
|
|
1480
|
+
// Write TOC cached paragraphs if available
|
|
1481
|
+
if (block.cachedParagraphs) {
|
|
1482
|
+
for (const p of block.cachedParagraphs) {
|
|
1483
|
+
writeParagraph(w, p, doc, imagePaths, ctx);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
break;
|
|
1487
|
+
case "math":
|
|
1488
|
+
// Math blocks are rendered as paragraphs with math text
|
|
1489
|
+
w.openNode("text:p");
|
|
1490
|
+
for (const mc of block.content) {
|
|
1491
|
+
if (mc.type === "mathRun") {
|
|
1492
|
+
w.writeText(mc.text);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
w.closeNode();
|
|
1496
|
+
break;
|
|
1497
|
+
default:
|
|
1498
|
+
// Other block types (floatingImage, textBox, etc.) — emit as empty paragraph placeholder
|
|
1499
|
+
w.leafNode("text:p");
|
|
1500
|
+
break;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
/** Write a paragraph element. */
|
|
1504
|
+
function writeParagraph(w, para, doc, imagePaths, ctx) {
|
|
1505
|
+
const isHeading = para.properties?.outlineLevel !== undefined && para.properties.outlineLevel >= 0;
|
|
1506
|
+
const styleName = para.properties?.style;
|
|
1507
|
+
if (isHeading) {
|
|
1508
|
+
const level = (para.properties.outlineLevel ?? 0) + 1;
|
|
1509
|
+
w.openNode("text:h", {
|
|
1510
|
+
...(styleName ? { "text:style-name": styleName } : {}),
|
|
1511
|
+
"text:outline-level": String(level)
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
else {
|
|
1515
|
+
w.openNode("text:p", {
|
|
1516
|
+
...(styleName ? { "text:style-name": styleName } : {})
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
for (const child of para.children) {
|
|
1520
|
+
writeParagraphChild(w, child, doc, imagePaths, ctx);
|
|
1521
|
+
}
|
|
1522
|
+
w.closeNode();
|
|
1523
|
+
}
|
|
1524
|
+
/** Write a paragraph child (Run, Hyperlink, etc.). */
|
|
1525
|
+
function writeParagraphChild(w, child, doc, imagePaths, ctx) {
|
|
1526
|
+
if (isRun(child)) {
|
|
1527
|
+
writeRun(w, child, doc, imagePaths, ctx);
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
switch (child.type) {
|
|
1531
|
+
case "hyperlink": {
|
|
1532
|
+
// Defense in depth: even though hyperlinks coming through readDocx /
|
|
1533
|
+
// htmlImport / odtRead are already sanitized, models can be built
|
|
1534
|
+
// by hand. Run the URL through sanitizeUrl one more time before
|
|
1535
|
+
// writing it into the ODT, so a malicious model can't smuggle
|
|
1536
|
+
// javascript:/vbscript: URLs through this writer.
|
|
1537
|
+
const rawHref = child.url ?? (child.anchor ? `#${child.anchor}` : "");
|
|
1538
|
+
const href = rawHref.startsWith("#") ? rawHref : (sanitizeUrl(rawHref) ?? "");
|
|
1539
|
+
if (!href) {
|
|
1540
|
+
// Drop the link wrapper — write children as plain runs.
|
|
1541
|
+
for (const run of child.children) {
|
|
1542
|
+
writeRun(w, run, doc, imagePaths, ctx);
|
|
1543
|
+
}
|
|
1544
|
+
break;
|
|
1545
|
+
}
|
|
1546
|
+
w.openNode("text:a", {
|
|
1547
|
+
"xlink:type": "simple",
|
|
1548
|
+
"xlink:href": href
|
|
1549
|
+
});
|
|
1550
|
+
for (const run of child.children) {
|
|
1551
|
+
writeRun(w, run, doc, imagePaths, ctx);
|
|
1552
|
+
}
|
|
1553
|
+
w.closeNode();
|
|
1554
|
+
break;
|
|
1555
|
+
}
|
|
1556
|
+
case "bookmarkStart":
|
|
1557
|
+
ctx.bookmarkNames.set(child.id, child.name);
|
|
1558
|
+
w.leafNode("text:bookmark-start", { "text:name": child.name });
|
|
1559
|
+
break;
|
|
1560
|
+
case "bookmarkEnd": {
|
|
1561
|
+
const name = ctx.bookmarkNames.get(child.id);
|
|
1562
|
+
if (name !== undefined) {
|
|
1563
|
+
// Emit the matching name so range-aware ODT readers can pair the
|
|
1564
|
+
// start/end markers correctly.
|
|
1565
|
+
w.leafNode("text:bookmark-end", { "text:name": name });
|
|
1566
|
+
}
|
|
1567
|
+
else {
|
|
1568
|
+
// Stray end without a known start (shouldn't happen for documents
|
|
1569
|
+
// produced by this library) — emit with an empty name to keep the
|
|
1570
|
+
// XML well-formed.
|
|
1571
|
+
w.leafNode("text:bookmark-end", { "text:name": "" });
|
|
1572
|
+
}
|
|
1573
|
+
break;
|
|
1574
|
+
}
|
|
1575
|
+
case "insertedRun":
|
|
1576
|
+
writeRun(w, child.run, doc, imagePaths, ctx);
|
|
1577
|
+
break;
|
|
1578
|
+
case "deletedRun":
|
|
1579
|
+
// Deleted runs are typically not shown in the output
|
|
1580
|
+
break;
|
|
1581
|
+
default:
|
|
1582
|
+
// Other paragraph children (comments, etc.) — skip
|
|
1583
|
+
break;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
/** Write a Run (text:span or direct text). */
|
|
1587
|
+
function writeRun(w, run, doc, imagePaths, ctx) {
|
|
1588
|
+
const hasProps = run.properties && Object.keys(run.properties).length > 0;
|
|
1589
|
+
if (hasProps) {
|
|
1590
|
+
const styleName = getRunAutoStyleName(ctx, run.properties);
|
|
1591
|
+
w.openNode("text:span", { "text:style-name": styleName });
|
|
1592
|
+
}
|
|
1593
|
+
for (const content of run.content) {
|
|
1594
|
+
writeRunContent(w, content, doc, imagePaths, ctx);
|
|
1595
|
+
}
|
|
1596
|
+
if (hasProps) {
|
|
1597
|
+
w.closeNode();
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
/** Write run content elements. */
|
|
1601
|
+
function writeRunContent(w, content, doc, imagePaths, ctx) {
|
|
1602
|
+
switch (content.type) {
|
|
1603
|
+
case "text":
|
|
1604
|
+
writeOdfText(w, content.text);
|
|
1605
|
+
break;
|
|
1606
|
+
case "break":
|
|
1607
|
+
if (content.breakType === "page") {
|
|
1608
|
+
// ODF 1.2 §5.1.4 — `text:soft-page-break` is a hint that a layout
|
|
1609
|
+
// page break occurs at this position. Strict semantic page breaks
|
|
1610
|
+
// require `fo:break-before="page"` on the surrounding paragraph
|
|
1611
|
+
// automatic style; we emit the soft hint here so the break is at
|
|
1612
|
+
// least preserved (rather than silently dropped, which corrupted
|
|
1613
|
+
// documents on round-trip).
|
|
1614
|
+
w.leafNode("text:soft-page-break");
|
|
1615
|
+
}
|
|
1616
|
+
else if (content.breakType === "column") {
|
|
1617
|
+
// Column breaks degrade to a soft page break in ODF — better than
|
|
1618
|
+
// dropping silently.
|
|
1619
|
+
w.leafNode("text:soft-page-break");
|
|
1620
|
+
}
|
|
1621
|
+
else {
|
|
1622
|
+
w.leafNode("text:line-break");
|
|
1623
|
+
}
|
|
1624
|
+
break;
|
|
1625
|
+
case "tab":
|
|
1626
|
+
w.leafNode("text:tab");
|
|
1627
|
+
break;
|
|
1628
|
+
case "image": {
|
|
1629
|
+
// Write inline image as draw:frame
|
|
1630
|
+
const widthCm = emuToCm(content.width);
|
|
1631
|
+
const heightCm = emuToCm(content.height);
|
|
1632
|
+
// Resolve the safe Pictures/ path that writeOdt registered when it
|
|
1633
|
+
// built the image map. This keeps content.xml's xlink:href in
|
|
1634
|
+
// lockstep with the actual ZIP entry name, and prevents a hostile
|
|
1635
|
+
// rId from emitting a traversing `xlink:href`.
|
|
1636
|
+
const mapped = ctx.imagePathByRId.get(content.rId) ??
|
|
1637
|
+
(content.name ? ctx.imagePathByFileName.get(content.name) : undefined);
|
|
1638
|
+
const imgPath = mapped ?? `Pictures/${sanitizeOdtPictureName(content.rId) || "image.bin"}`;
|
|
1639
|
+
w.openNode("draw:frame", {
|
|
1640
|
+
"draw:name": content.name ?? "",
|
|
1641
|
+
"svg:width": widthCm,
|
|
1642
|
+
"svg:height": heightCm,
|
|
1643
|
+
"text:anchor-type": "as-char"
|
|
1644
|
+
});
|
|
1645
|
+
w.leafNode("draw:image", {
|
|
1646
|
+
"xlink:href": imgPath,
|
|
1647
|
+
"xlink:type": "simple",
|
|
1648
|
+
"xlink:show": "embed",
|
|
1649
|
+
"xlink:actuate": "onLoad"
|
|
1650
|
+
});
|
|
1651
|
+
w.closeNode();
|
|
1652
|
+
break;
|
|
1653
|
+
}
|
|
1654
|
+
case "carriageReturn":
|
|
1655
|
+
w.leafNode("text:line-break");
|
|
1656
|
+
break;
|
|
1657
|
+
case "noBreakHyphen":
|
|
1658
|
+
w.writeText("\u2011");
|
|
1659
|
+
break;
|
|
1660
|
+
case "softHyphen":
|
|
1661
|
+
w.writeText("\u00AD");
|
|
1662
|
+
break;
|
|
1663
|
+
default:
|
|
1664
|
+
// Other content types (field, footnoteRef, etc.) — skip for now
|
|
1665
|
+
break;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
/** Write text content, handling multiple spaces via text:s. */
|
|
1669
|
+
function writeOdfText(w, text) {
|
|
1670
|
+
// ODF collapses whitespace like HTML. Use text:s for multiple spaces.
|
|
1671
|
+
let i = 0;
|
|
1672
|
+
while (i < text.length) {
|
|
1673
|
+
const spaceStart = text.indexOf(" ", i);
|
|
1674
|
+
if (spaceStart < 0) {
|
|
1675
|
+
w.writeText(text.substring(i));
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1678
|
+
// Write text before the spaces
|
|
1679
|
+
if (spaceStart > i) {
|
|
1680
|
+
w.writeText(text.substring(i, spaceStart));
|
|
1681
|
+
}
|
|
1682
|
+
// Count consecutive spaces
|
|
1683
|
+
let spaceCount = 0;
|
|
1684
|
+
let j = spaceStart;
|
|
1685
|
+
while (j < text.length && text[j] === " ") {
|
|
1686
|
+
spaceCount++;
|
|
1687
|
+
j++;
|
|
1688
|
+
}
|
|
1689
|
+
// First space is implicit, rest use text:s
|
|
1690
|
+
w.writeText(" ");
|
|
1691
|
+
if (spaceCount > 1) {
|
|
1692
|
+
w.leafNode("text:s", { "text:c": String(spaceCount - 1) });
|
|
1693
|
+
}
|
|
1694
|
+
i = j;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
/** Write a table element. */
|
|
1698
|
+
function writeTable(w, table, doc, imagePaths, ctx) {
|
|
1699
|
+
w.openNode("table:table", {
|
|
1700
|
+
...(table.properties?.style ? { "table:style-name": table.properties.style } : {})
|
|
1701
|
+
});
|
|
1702
|
+
// Write column definitions
|
|
1703
|
+
if (table.columnWidths && table.columnWidths.length > 0) {
|
|
1704
|
+
for (const _width of table.columnWidths) {
|
|
1705
|
+
w.leafNode("table:table-column");
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
// Write rows
|
|
1709
|
+
for (const row of table.rows) {
|
|
1710
|
+
writeTableRow(w, row, doc, imagePaths, ctx);
|
|
1711
|
+
}
|
|
1712
|
+
w.closeNode();
|
|
1713
|
+
}
|
|
1714
|
+
/** Write a table row. */
|
|
1715
|
+
function writeTableRow(w, row, doc, imagePaths, ctx) {
|
|
1716
|
+
w.openNode("table:table-row");
|
|
1717
|
+
for (const cell of row.cells) {
|
|
1718
|
+
writeTableCell(w, cell, doc, imagePaths, ctx);
|
|
1719
|
+
}
|
|
1720
|
+
w.closeNode();
|
|
1721
|
+
}
|
|
1722
|
+
/** Write a table cell. */
|
|
1723
|
+
function writeTableCell(w, cell, doc, imagePaths, ctx) {
|
|
1724
|
+
const attrs = {};
|
|
1725
|
+
if (cell.properties?.gridSpan && cell.properties.gridSpan > 1) {
|
|
1726
|
+
attrs["table:number-columns-spanned"] = String(cell.properties.gridSpan);
|
|
1727
|
+
}
|
|
1728
|
+
if (cell.properties?.rowSpan && cell.properties.rowSpan > 1) {
|
|
1729
|
+
attrs["table:number-rows-spanned"] = String(cell.properties.rowSpan);
|
|
1730
|
+
}
|
|
1731
|
+
if (cell.properties?.verticalMerge === "continue") {
|
|
1732
|
+
w.leafNode("table:covered-table-cell");
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
w.openNode("table:table-cell", attrs);
|
|
1736
|
+
for (const content of cell.content) {
|
|
1737
|
+
if (content.type === "paragraph") {
|
|
1738
|
+
writeParagraph(w, content, doc, imagePaths, ctx);
|
|
1739
|
+
}
|
|
1740
|
+
else if (content.type === "table") {
|
|
1741
|
+
writeTable(w, content, doc, imagePaths, ctx);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
w.closeNode();
|
|
1745
|
+
}
|
|
1746
|
+
// =============================================================================
|
|
1747
|
+
// Styles XML Generation
|
|
1748
|
+
// =============================================================================
|
|
1749
|
+
/** Generate styles.xml for the ODT package. */
|
|
1750
|
+
function generateStylesXml(doc) {
|
|
1751
|
+
const w = new XmlWriter();
|
|
1752
|
+
w.openXml();
|
|
1753
|
+
w.openNode("office:document-styles", {
|
|
1754
|
+
"xmlns:office": NS.office,
|
|
1755
|
+
"xmlns:style": NS.style,
|
|
1756
|
+
"xmlns:text": NS.text,
|
|
1757
|
+
"xmlns:table": NS.table,
|
|
1758
|
+
"xmlns:fo": NS.fo,
|
|
1759
|
+
"xmlns:draw": NS.draw,
|
|
1760
|
+
"xmlns:svg": NS.svg,
|
|
1761
|
+
"office:version": "1.3"
|
|
1762
|
+
});
|
|
1763
|
+
// Office styles (named styles)
|
|
1764
|
+
w.openNode("office:styles");
|
|
1765
|
+
// Default paragraph style
|
|
1766
|
+
w.openNode("style:default-style", { "style:family": "paragraph" });
|
|
1767
|
+
if (doc.docDefaults?.paragraphProperties) {
|
|
1768
|
+
writeParagraphPropertiesOdf(w, doc.docDefaults.paragraphProperties);
|
|
1769
|
+
}
|
|
1770
|
+
if (doc.docDefaults?.runProperties) {
|
|
1771
|
+
writeTextPropertiesOdf(w, doc.docDefaults.runProperties);
|
|
1772
|
+
}
|
|
1773
|
+
else {
|
|
1774
|
+
w.leafNode("style:text-properties", {
|
|
1775
|
+
"fo:font-size": "12pt",
|
|
1776
|
+
"style:font-name": "Times New Roman"
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
w.closeNode();
|
|
1780
|
+
// Default table style
|
|
1781
|
+
w.openNode("style:default-style", { "style:family": "table" });
|
|
1782
|
+
w.closeNode();
|
|
1783
|
+
w.closeNode(); // office:styles
|
|
1784
|
+
// Automatic styles (page layout)
|
|
1785
|
+
w.openNode("office:automatic-styles");
|
|
1786
|
+
// Page layout
|
|
1787
|
+
w.openNode("style:page-layout", { "style:name": "pm1" });
|
|
1788
|
+
const pageProps = {};
|
|
1789
|
+
if (doc.sectionProperties?.pageSize) {
|
|
1790
|
+
pageProps["fo:page-width"] = twipsToCm(doc.sectionProperties.pageSize.width);
|
|
1791
|
+
pageProps["fo:page-height"] = twipsToCm(doc.sectionProperties.pageSize.height);
|
|
1792
|
+
if (doc.sectionProperties.pageSize.orientation === "landscape") {
|
|
1793
|
+
pageProps["style:print-orientation"] = "landscape";
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
else {
|
|
1797
|
+
// Default A4
|
|
1798
|
+
pageProps["fo:page-width"] = "21.001cm";
|
|
1799
|
+
pageProps["fo:page-height"] = "29.700cm";
|
|
1800
|
+
}
|
|
1801
|
+
if (doc.sectionProperties?.margins) {
|
|
1802
|
+
const m = doc.sectionProperties.margins;
|
|
1803
|
+
pageProps["fo:margin-top"] = twipsToCm(m.top);
|
|
1804
|
+
pageProps["fo:margin-bottom"] = twipsToCm(m.bottom);
|
|
1805
|
+
pageProps["fo:margin-left"] = twipsToCm(m.left);
|
|
1806
|
+
pageProps["fo:margin-right"] = twipsToCm(m.right);
|
|
1807
|
+
}
|
|
1808
|
+
else {
|
|
1809
|
+
// Default 1 inch margins
|
|
1810
|
+
pageProps["fo:margin-top"] = "2.540cm";
|
|
1811
|
+
pageProps["fo:margin-bottom"] = "2.540cm";
|
|
1812
|
+
pageProps["fo:margin-left"] = "2.540cm";
|
|
1813
|
+
pageProps["fo:margin-right"] = "2.540cm";
|
|
1814
|
+
}
|
|
1815
|
+
w.leafNode("style:page-layout-properties", pageProps);
|
|
1816
|
+
w.closeNode(); // style:page-layout
|
|
1817
|
+
w.closeNode(); // office:automatic-styles
|
|
1818
|
+
// Master styles
|
|
1819
|
+
w.openNode("office:master-styles");
|
|
1820
|
+
w.leafNode("style:master-page", {
|
|
1821
|
+
"style:name": "Default",
|
|
1822
|
+
"style:page-layout-name": "pm1"
|
|
1823
|
+
});
|
|
1824
|
+
w.closeNode();
|
|
1825
|
+
w.closeNode(); // office:document-styles
|
|
1826
|
+
return w.xml;
|
|
1827
|
+
}
|
|
1828
|
+
// =============================================================================
|
|
1829
|
+
// Meta XML Generation
|
|
1830
|
+
// =============================================================================
|
|
1831
|
+
/** Generate meta.xml for the ODT package. */
|
|
1832
|
+
function generateMetaXml(doc) {
|
|
1833
|
+
const w = new XmlWriter();
|
|
1834
|
+
w.openXml();
|
|
1835
|
+
w.openNode("office:document-meta", {
|
|
1836
|
+
"xmlns:office": NS.office,
|
|
1837
|
+
"xmlns:meta": NS.meta,
|
|
1838
|
+
"xmlns:dc": NS.dc,
|
|
1839
|
+
"office:version": "1.3"
|
|
1840
|
+
});
|
|
1841
|
+
w.openNode("office:meta");
|
|
1842
|
+
if (doc.coreProperties) {
|
|
1843
|
+
const cp = doc.coreProperties;
|
|
1844
|
+
if (cp.title) {
|
|
1845
|
+
w.leafNode("dc:title", undefined, cp.title);
|
|
1846
|
+
}
|
|
1847
|
+
if (cp.subject) {
|
|
1848
|
+
w.leafNode("dc:subject", undefined, cp.subject);
|
|
1849
|
+
}
|
|
1850
|
+
if (cp.creator) {
|
|
1851
|
+
w.leafNode("meta:initial-creator", undefined, cp.creator);
|
|
1852
|
+
w.leafNode("dc:creator", undefined, cp.creator);
|
|
1853
|
+
}
|
|
1854
|
+
if (cp.description) {
|
|
1855
|
+
w.leafNode("dc:description", undefined, cp.description);
|
|
1856
|
+
}
|
|
1857
|
+
if (cp.keywords) {
|
|
1858
|
+
w.leafNode("meta:keyword", undefined, cp.keywords);
|
|
1859
|
+
}
|
|
1860
|
+
if (cp.created) {
|
|
1861
|
+
w.leafNode("meta:creation-date", undefined, cp.created.toISOString());
|
|
1862
|
+
}
|
|
1863
|
+
if (cp.modified) {
|
|
1864
|
+
w.leafNode("dc:date", undefined, cp.modified.toISOString());
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
// Generator
|
|
1868
|
+
w.leafNode("meta:generator", undefined, "excelts/odt");
|
|
1869
|
+
w.closeNode(); // office:meta
|
|
1870
|
+
w.closeNode(); // office:document-meta
|
|
1871
|
+
return w.xml;
|
|
1872
|
+
}
|
|
1873
|
+
// =============================================================================
|
|
1874
|
+
// Manifest XML Generation
|
|
1875
|
+
// =============================================================================
|
|
1876
|
+
/** Generate META-INF/manifest.xml for the ODT package. */
|
|
1877
|
+
function generateManifestXml(imagePaths) {
|
|
1878
|
+
const w = new XmlWriter();
|
|
1879
|
+
w.openXml();
|
|
1880
|
+
w.openNode("manifest:manifest", {
|
|
1881
|
+
"xmlns:manifest": NS.manifest,
|
|
1882
|
+
"manifest:version": "1.3"
|
|
1883
|
+
});
|
|
1884
|
+
// Root entry
|
|
1885
|
+
w.leafNode("manifest:file-entry", {
|
|
1886
|
+
"manifest:full-path": "/",
|
|
1887
|
+
"manifest:version": "1.3",
|
|
1888
|
+
"manifest:media-type": "application/vnd.oasis.opendocument.text"
|
|
1889
|
+
});
|
|
1890
|
+
// Content parts
|
|
1891
|
+
w.leafNode("manifest:file-entry", {
|
|
1892
|
+
"manifest:full-path": "content.xml",
|
|
1893
|
+
"manifest:media-type": "text/xml"
|
|
1894
|
+
});
|
|
1895
|
+
w.leafNode("manifest:file-entry", {
|
|
1896
|
+
"manifest:full-path": "styles.xml",
|
|
1897
|
+
"manifest:media-type": "text/xml"
|
|
1898
|
+
});
|
|
1899
|
+
w.leafNode("manifest:file-entry", {
|
|
1900
|
+
"manifest:full-path": "meta.xml",
|
|
1901
|
+
"manifest:media-type": "text/xml"
|
|
1902
|
+
});
|
|
1903
|
+
// Images
|
|
1904
|
+
for (const imgPath of imagePaths) {
|
|
1905
|
+
const ext = imgPath.substring(imgPath.lastIndexOf(".") + 1).toLowerCase();
|
|
1906
|
+
let mediaType;
|
|
1907
|
+
switch (ext) {
|
|
1908
|
+
case "png":
|
|
1909
|
+
mediaType = "image/png";
|
|
1910
|
+
break;
|
|
1911
|
+
case "jpg":
|
|
1912
|
+
case "jpeg":
|
|
1913
|
+
mediaType = "image/jpeg";
|
|
1914
|
+
break;
|
|
1915
|
+
case "gif":
|
|
1916
|
+
mediaType = "image/gif";
|
|
1917
|
+
break;
|
|
1918
|
+
case "svg":
|
|
1919
|
+
mediaType = "image/svg+xml";
|
|
1920
|
+
break;
|
|
1921
|
+
default:
|
|
1922
|
+
mediaType = "application/octet-stream";
|
|
1923
|
+
break;
|
|
1924
|
+
}
|
|
1925
|
+
w.leafNode("manifest:file-entry", {
|
|
1926
|
+
"manifest:full-path": imgPath,
|
|
1927
|
+
"manifest:media-type": mediaType
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
w.closeNode(); // manifest:manifest
|
|
1931
|
+
return w.xml;
|
|
1932
|
+
}
|