omni-viewer-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +80 -0
- package/THIRD_PARTY_NOTICES.md +49 -0
- package/dist/assets/audio-engine/audio_engine.mjs +2 -0
- package/dist/assets/audio-engine/audio_engine.wasm +0 -0
- package/dist/assets/pdfjs/pdf.worker.min.mjs +21 -0
- package/dist/host/index.d.ts +133 -0
- package/dist/host/index.js +10 -0
- package/dist/i18n/catalog.en.d.ts +1 -0
- package/dist/i18n/catalog.en.js +411 -0
- package/dist/i18n/catalog.ko.d.ts +1 -0
- package/dist/i18n/catalog.ko.js +68 -0
- package/dist/i18n/index.d.ts +18 -0
- package/dist/i18n/index.js +41 -0
- package/dist/licenses/emscripten.txt +101 -0
- package/dist/licenses/kissfft.txt +28 -0
- package/dist/licenses/pdfjs-dist.txt +177 -0
- package/dist/parsers/a2l/index.d.ts +1 -0
- package/dist/parsers/a2l/index.js +1 -0
- package/dist/parsers/archive/index.d.ts +22 -0
- package/dist/parsers/archive/index.js +46 -0
- package/dist/parsers/archive/model.d.ts +29 -0
- package/dist/parsers/archive/model.js +1 -0
- package/dist/parsers/archive/worker.d.ts +21 -0
- package/dist/parsers/archive/worker.js +1 -0
- package/dist/parsers/arxml/index.d.ts +1 -0
- package/dist/parsers/arxml/index.js +1 -0
- package/dist/parsers/asc/index.d.ts +1 -0
- package/dist/parsers/asc/index.js +1 -0
- package/dist/parsers/audio/index.d.ts +9 -0
- package/dist/parsers/audio/index.js +46 -0
- package/dist/parsers/automotive/index.d.ts +33 -0
- package/dist/parsers/automotive/index.js +973 -0
- package/dist/parsers/blf/index.d.ts +1 -0
- package/dist/parsers/blf/index.js +1 -0
- package/dist/parsers/csv/delimiter.d.ts +31 -0
- package/dist/parsers/csv/delimiter.js +151 -0
- package/dist/parsers/csv/index.d.ts +30 -0
- package/dist/parsers/csv/index.js +108 -0
- package/dist/parsers/csv/parseText.d.ts +20 -0
- package/dist/parsers/csv/parseText.js +108 -0
- package/dist/parsers/csv/sort.d.ts +26 -0
- package/dist/parsers/csv/sort.js +224 -0
- package/dist/parsers/csv/statistics.d.ts +49 -0
- package/dist/parsers/csv/statistics.js +150 -0
- package/dist/parsers/dbc/index.d.ts +56 -0
- package/dist/parsers/dbc/index.js +94 -0
- package/dist/parsers/doc-binary/index.d.ts +35 -0
- package/dist/parsers/doc-binary/index.js +267 -0
- package/dist/parsers/doc-binary/vscode-parser.d.ts +142 -0
- package/dist/parsers/doc-binary/vscode-parser.js +3747 -0
- package/dist/parsers/excel/index.d.ts +17 -0
- package/dist/parsers/excel/index.js +406 -0
- package/dist/parsers/excel/model.d.ts +80 -0
- package/dist/parsers/excel/model.js +48 -0
- package/dist/parsers/excel/serialize.d.ts +3 -0
- package/dist/parsers/excel/serialize.js +78 -0
- package/dist/parsers/hdf5/index.d.ts +75 -0
- package/dist/parsers/hdf5/index.js +658 -0
- package/dist/parsers/hdf5/node.d.ts +21 -0
- package/dist/parsers/hdf5/node.js +95 -0
- package/dist/parsers/json/index.d.ts +15 -0
- package/dist/parsers/json/index.js +102 -0
- package/dist/parsers/json/model.d.ts +42 -0
- package/dist/parsers/json/model.js +140 -0
- package/dist/parsers/json/parse.d.ts +22 -0
- package/dist/parsers/json/parse.js +279 -0
- package/dist/parsers/json/tokenizer.d.ts +17 -0
- package/dist/parsers/json/tokenizer.js +175 -0
- package/dist/parsers/jsonl/index.d.ts +14 -0
- package/dist/parsers/jsonl/index.js +18 -0
- package/dist/parsers/jsonl/model.d.ts +23 -0
- package/dist/parsers/jsonl/model.js +1 -0
- package/dist/parsers/jsonl/parse.d.ts +7 -0
- package/dist/parsers/jsonl/parse.js +34 -0
- package/dist/parsers/markdown/index.d.ts +37 -0
- package/dist/parsers/markdown/index.js +59 -0
- package/dist/parsers/mat/index.d.ts +31 -0
- package/dist/parsers/mat/index.js +426 -0
- package/dist/parsers/mermaid/index.d.ts +5 -0
- package/dist/parsers/mermaid/index.js +3 -0
- package/dist/parsers/mf4/index.d.ts +1 -0
- package/dist/parsers/mf4/index.js +1 -0
- package/dist/parsers/parquet/index.d.ts +21 -0
- package/dist/parsers/parquet/index.js +68 -0
- package/dist/parsers/pcap/index.d.ts +1 -0
- package/dist/parsers/pcap/index.js +1 -0
- package/dist/parsers/pcapng/index.d.ts +1 -0
- package/dist/parsers/pcapng/index.js +1 -0
- package/dist/parsers/plantuml/index.d.ts +5 -0
- package/dist/parsers/plantuml/index.js +4 -0
- package/dist/parsers/ppt-binary/index.d.ts +5 -0
- package/dist/parsers/ppt-binary/index.js +62 -0
- package/dist/parsers/ppt-binary/pptBinaryBuildContext.d.ts +12 -0
- package/dist/parsers/ppt-binary/pptBinaryBuildContext.js +40 -0
- package/dist/parsers/ppt-binary/pptBinaryContainer.d.ts +4 -0
- package/dist/parsers/ppt-binary/pptBinaryContainer.js +174 -0
- package/dist/parsers/ppt-binary/pptBinaryTypes.d.ts +125 -0
- package/dist/parsers/ppt-binary/pptBinaryTypes.js +1 -0
- package/dist/parsers/ppt-binary/pptSlideLayouts.d.ts +9 -0
- package/dist/parsers/ppt-binary/pptSlideLayouts.js +510 -0
- package/dist/parsers/ppt-binary/vscode-parser.d.ts +116 -0
- package/dist/parsers/ppt-binary/vscode-parser.js +2853 -0
- package/dist/parsers/ppt-binary/vscode.d.ts +4 -0
- package/dist/parsers/ppt-binary/vscode.js +12 -0
- package/dist/parsers/pptx/index.d.ts +15 -0
- package/dist/parsers/pptx/index.js +88 -0
- package/dist/parsers/pptx/vscode-parser.d.ts +167 -0
- package/dist/parsers/pptx/vscode-parser.js +1503 -0
- package/dist/parsers/pptx/vscode.d.ts +4 -0
- package/dist/parsers/pptx/vscode.js +18 -0
- package/dist/parsers/pptx/zip-reader.d.ts +3 -0
- package/dist/parsers/pptx/zip-reader.js +20 -0
- package/dist/parsers/proto/index.d.ts +90 -0
- package/dist/parsers/proto/index.js +197 -0
- package/dist/parsers/psd/index.d.ts +16 -0
- package/dist/parsers/psd/index.js +10 -0
- package/dist/parsers/shapefile/dbf.d.ts +23 -0
- package/dist/parsers/shapefile/dbf.js +112 -0
- package/dist/parsers/shapefile/index.d.ts +44 -0
- package/dist/parsers/shapefile/index.js +119 -0
- package/dist/parsers/shapefile/reproject.d.ts +10 -0
- package/dist/parsers/shapefile/reproject.js +41 -0
- package/dist/parsers/slide-model.d.ts +106 -0
- package/dist/parsers/slide-model.js +1 -0
- package/dist/parsers/toml/index.d.ts +15 -0
- package/dist/parsers/toml/index.js +18 -0
- package/dist/parsers/toml/model.d.ts +15 -0
- package/dist/parsers/toml/model.js +1 -0
- package/dist/parsers/toml/parse.d.ts +16 -0
- package/dist/parsers/toml/parse.js +266 -0
- package/dist/parsers/types.d.ts +103 -0
- package/dist/parsers/types.js +106 -0
- package/dist/parsers/video/index.d.ts +6 -0
- package/dist/parsers/video/index.js +2 -0
- package/dist/parsers/yaml/index.d.ts +16 -0
- package/dist/parsers/yaml/index.js +58 -0
- package/dist/parsers/yaml/model.d.ts +22 -0
- package/dist/parsers/yaml/model.js +1 -0
- package/dist/parsers/yaml/self-loading.d.ts +3 -0
- package/dist/parsers/yaml/self-loading.js +19 -0
- package/dist/parsers/zip-scan.d.ts +8 -0
- package/dist/parsers/zip-scan.js +80 -0
- package/dist/registry/container.d.ts +17 -0
- package/dist/registry/container.js +49 -0
- package/dist/registry/index.d.ts +133 -0
- package/dist/registry/index.js +348 -0
- package/dist/registry/probe.d.ts +3 -0
- package/dist/registry/probe.js +177 -0
- package/dist/registry/sniff.d.ts +17 -0
- package/dist/registry/sniff.js +57 -0
- package/dist/styles/archive.css +10 -0
- package/dist/styles/audio.css +66 -0
- package/dist/styles/automotive.css +1 -0
- package/dist/styles/csv.css +262 -0
- package/dist/styles/excel.css +212 -0
- package/dist/styles/fallback.css +48 -0
- package/dist/styles/hdf5.css +3 -0
- package/dist/styles/hwp.css +12 -0
- package/dist/styles/image.css +115 -0
- package/dist/styles/json.css +342 -0
- package/dist/styles/jsonl.css +1 -0
- package/dist/styles/markdown.css +10 -0
- package/dist/styles/mat.css +2 -0
- package/dist/styles/mermaid.css +1 -0
- package/dist/styles/parquet.css +2 -0
- package/dist/styles/pdf.css +551 -0
- package/dist/styles/plantuml.css +1 -0
- package/dist/styles/ppt.css +1 -0
- package/dist/styles/proto.css +2 -0
- package/dist/styles/psd.css +1 -0
- package/dist/styles/shapefile.css +25 -0
- package/dist/styles/toml.css +1 -0
- package/dist/styles/video.css +74 -0
- package/dist/styles/word.css +685 -0
- package/dist/styles/yaml.css +1 -0
- package/dist/viewers/a2l/index.d.ts +15 -0
- package/dist/viewers/a2l/index.js +5 -0
- package/dist/viewers/archive/archive-preview-decoder.d.ts +4 -0
- package/dist/viewers/archive/archive-preview-decoder.js +223 -0
- package/dist/viewers/archive/controller.d.ts +24 -0
- package/dist/viewers/archive/controller.js +18 -0
- package/dist/viewers/archive/index.d.ts +25 -0
- package/dist/viewers/archive/index.js +230 -0
- package/dist/viewers/archive/preview.d.ts +2 -0
- package/dist/viewers/archive/preview.js +2 -0
- package/dist/viewers/archive/styles.d.ts +1 -0
- package/dist/viewers/archive/styles.js +11 -0
- package/dist/viewers/arxml/index.d.ts +15 -0
- package/dist/viewers/arxml/index.js +5 -0
- package/dist/viewers/asc/index.d.ts +15 -0
- package/dist/viewers/asc/index.js +5 -0
- package/dist/viewers/audio/controller.d.ts +33 -0
- package/dist/viewers/audio/controller.js +49 -0
- package/dist/viewers/audio/engine.d.ts +44 -0
- package/dist/viewers/audio/engine.js +108 -0
- package/dist/viewers/audio/index.d.ts +92 -0
- package/dist/viewers/audio/index.js +448 -0
- package/dist/viewers/audio/self-loading.d.ts +7 -0
- package/dist/viewers/audio/self-loading.js +42 -0
- package/dist/viewers/audio/styles.d.ts +1 -0
- package/dist/viewers/audio/styles.js +73 -0
- package/dist/viewers/automotive/index.d.ts +8 -0
- package/dist/viewers/automotive/index.js +152 -0
- package/dist/viewers/automotive/styles.d.ts +1 -0
- package/dist/viewers/automotive/styles.js +1 -0
- package/dist/viewers/avro/index.d.ts +15 -0
- package/dist/viewers/avro/index.js +5 -0
- package/dist/viewers/bag/index.d.ts +15 -0
- package/dist/viewers/bag/index.js +5 -0
- package/dist/viewers/blf/index.d.ts +15 -0
- package/dist/viewers/blf/index.js +5 -0
- package/dist/viewers/csv/controller.d.ts +126 -0
- package/dist/viewers/csv/controller.js +598 -0
- package/dist/viewers/csv/index.d.ts +48 -0
- package/dist/viewers/csv/index.js +846 -0
- package/dist/viewers/csv/styles.d.ts +1 -0
- package/dist/viewers/csv/styles.js +267 -0
- package/dist/viewers/db3/index.d.ts +15 -0
- package/dist/viewers/db3/index.js +5 -0
- package/dist/viewers/dbc/index.d.ts +15 -0
- package/dist/viewers/dbc/index.js +5 -0
- package/dist/viewers/diagram.d.ts +14 -0
- package/dist/viewers/diagram.js +95 -0
- package/dist/viewers/excel/controller.d.ts +108 -0
- package/dist/viewers/excel/controller.js +548 -0
- package/dist/viewers/excel/grid.d.ts +28 -0
- package/dist/viewers/excel/grid.js +99 -0
- package/dist/viewers/excel/index.d.ts +37 -0
- package/dist/viewers/excel/index.js +620 -0
- package/dist/viewers/excel/self-loading.d.ts +6 -0
- package/dist/viewers/excel/self-loading.js +13 -0
- package/dist/viewers/excel/styles.d.ts +1 -0
- package/dist/viewers/excel/styles.js +216 -0
- package/dist/viewers/fallback/index.d.ts +17 -0
- package/dist/viewers/fallback/index.js +130 -0
- package/dist/viewers/fallback/styles.d.ts +1 -0
- package/dist/viewers/fallback/styles.js +49 -0
- package/dist/viewers/hdf5/index.d.ts +19 -0
- package/dist/viewers/hdf5/index.js +170 -0
- package/dist/viewers/hdf5/styles.d.ts +1 -0
- package/dist/viewers/hdf5/styles.js +4 -0
- package/dist/viewers/hwp/controller.d.ts +19 -0
- package/dist/viewers/hwp/controller.js +22 -0
- package/dist/viewers/hwp/index.d.ts +39 -0
- package/dist/viewers/hwp/index.js +271 -0
- package/dist/viewers/hwp/self-loading.d.ts +2 -0
- package/dist/viewers/hwp/self-loading.js +6 -0
- package/dist/viewers/hwp/styles.d.ts +1 -0
- package/dist/viewers/hwp/styles.js +13 -0
- package/dist/viewers/image/controller.d.ts +210 -0
- package/dist/viewers/image/controller.js +336 -0
- package/dist/viewers/image/decode.d.ts +54 -0
- package/dist/viewers/image/decode.js +150 -0
- package/dist/viewers/image/index.d.ts +38 -0
- package/dist/viewers/image/index.js +749 -0
- package/dist/viewers/image/limits.d.ts +18 -0
- package/dist/viewers/image/limits.js +20 -0
- package/dist/viewers/image/styles.d.ts +1 -0
- package/dist/viewers/image/styles.js +119 -0
- package/dist/viewers/json/controller.d.ts +89 -0
- package/dist/viewers/json/controller.js +411 -0
- package/dist/viewers/json/index.d.ts +25 -0
- package/dist/viewers/json/index.js +503 -0
- package/dist/viewers/json/styles.d.ts +1 -0
- package/dist/viewers/json/styles.js +346 -0
- package/dist/viewers/json/transforms.d.ts +27 -0
- package/dist/viewers/json/transforms.js +253 -0
- package/dist/viewers/jsonl/controller.d.ts +48 -0
- package/dist/viewers/jsonl/controller.js +136 -0
- package/dist/viewers/jsonl/index.d.ts +22 -0
- package/dist/viewers/jsonl/index.js +184 -0
- package/dist/viewers/jsonl/styles.d.ts +1 -0
- package/dist/viewers/jsonl/styles.js +1 -0
- package/dist/viewers/markdown/controller.d.ts +50 -0
- package/dist/viewers/markdown/controller.js +62 -0
- package/dist/viewers/markdown/index.d.ts +64 -0
- package/dist/viewers/markdown/index.js +425 -0
- package/dist/viewers/markdown/math.d.ts +21 -0
- package/dist/viewers/markdown/math.js +100 -0
- package/dist/viewers/markdown/self-loading.d.ts +5 -0
- package/dist/viewers/markdown/self-loading.js +48 -0
- package/dist/viewers/markdown/styles.d.ts +1 -0
- package/dist/viewers/markdown/styles.js +11 -0
- package/dist/viewers/mat/index.d.ts +19 -0
- package/dist/viewers/mat/index.js +113 -0
- package/dist/viewers/mat/styles.d.ts +1 -0
- package/dist/viewers/mat/styles.js +3 -0
- package/dist/viewers/media.d.ts +9 -0
- package/dist/viewers/media.js +77 -0
- package/dist/viewers/mermaid/index.d.ts +17 -0
- package/dist/viewers/mermaid/index.js +6 -0
- package/dist/viewers/mermaid/self-loading.d.ts +2 -0
- package/dist/viewers/mermaid/self-loading.js +2 -0
- package/dist/viewers/mf4/index.d.ts +15 -0
- package/dist/viewers/mf4/index.js +5 -0
- package/dist/viewers/parquet/controller.d.ts +37 -0
- package/dist/viewers/parquet/controller.js +26 -0
- package/dist/viewers/parquet/index.d.ts +23 -0
- package/dist/viewers/parquet/index.js +134 -0
- package/dist/viewers/parquet/styles.d.ts +1 -0
- package/dist/viewers/parquet/styles.js +3 -0
- package/dist/viewers/pcap/index.d.ts +15 -0
- package/dist/viewers/pcap/index.js +5 -0
- package/dist/viewers/pcapng/index.d.ts +15 -0
- package/dist/viewers/pcapng/index.js +5 -0
- package/dist/viewers/pdf/controller.d.ts +120 -0
- package/dist/viewers/pdf/controller.js +191 -0
- package/dist/viewers/pdf/editing.d.ts +26 -0
- package/dist/viewers/pdf/editing.js +265 -0
- package/dist/viewers/pdf/index.d.ts +88 -0
- package/dist/viewers/pdf/index.js +1400 -0
- package/dist/viewers/pdf/self-loading.d.ts +6 -0
- package/dist/viewers/pdf/self-loading.js +14 -0
- package/dist/viewers/pdf/styles.d.ts +1 -0
- package/dist/viewers/pdf/styles.js +555 -0
- package/dist/viewers/plantuml/index.d.ts +17 -0
- package/dist/viewers/plantuml/index.js +6 -0
- package/dist/viewers/plantuml/self-loading.d.ts +2 -0
- package/dist/viewers/plantuml/self-loading.js +2 -0
- package/dist/viewers/ppt/chart.d.ts +8 -0
- package/dist/viewers/ppt/chart.js +83 -0
- package/dist/viewers/ppt/controller.d.ts +34 -0
- package/dist/viewers/ppt/controller.js +27 -0
- package/dist/viewers/ppt/index.d.ts +33 -0
- package/dist/viewers/ppt/index.js +101 -0
- package/dist/viewers/ppt/render.d.ts +5 -0
- package/dist/viewers/ppt/render.js +144 -0
- package/dist/viewers/ppt/self-loading.d.ts +9 -0
- package/dist/viewers/ppt/self-loading.js +4 -0
- package/dist/viewers/ppt/styles.d.ts +1 -0
- package/dist/viewers/ppt/styles.js +1 -0
- package/dist/viewers/proto/index.d.ts +17 -0
- package/dist/viewers/proto/index.js +170 -0
- package/dist/viewers/proto/styles.d.ts +1 -0
- package/dist/viewers/proto/styles.js +2 -0
- package/dist/viewers/psd/index.d.ts +27 -0
- package/dist/viewers/psd/index.js +39 -0
- package/dist/viewers/psd/self-loading.d.ts +10 -0
- package/dist/viewers/psd/self-loading.js +20 -0
- package/dist/viewers/psd/styles.d.ts +1 -0
- package/dist/viewers/psd/styles.js +1 -0
- package/dist/viewers/reqif/index.d.ts +15 -0
- package/dist/viewers/reqif/index.js +5 -0
- package/dist/viewers/shapefile/index.d.ts +35 -0
- package/dist/viewers/shapefile/index.js +336 -0
- package/dist/viewers/shapefile/self-loading.d.ts +7 -0
- package/dist/viewers/shapefile/self-loading.js +32 -0
- package/dist/viewers/shapefile/styles.d.ts +1 -0
- package/dist/viewers/shapefile/styles.js +28 -0
- package/dist/viewers/stp/index.d.ts +15 -0
- package/dist/viewers/stp/index.js +5 -0
- package/dist/viewers/structured-styles.d.ts +1 -0
- package/dist/viewers/structured-styles.js +1 -0
- package/dist/viewers/structured.d.ts +36 -0
- package/dist/viewers/structured.js +138 -0
- package/dist/viewers/toml/controller.d.ts +38 -0
- package/dist/viewers/toml/controller.js +61 -0
- package/dist/viewers/toml/index.d.ts +18 -0
- package/dist/viewers/toml/index.js +34 -0
- package/dist/viewers/types.d.ts +36 -0
- package/dist/viewers/types.js +9 -0
- package/dist/viewers/video/controller.d.ts +29 -0
- package/dist/viewers/video/controller.js +54 -0
- package/dist/viewers/video/index.d.ts +18 -0
- package/dist/viewers/video/index.js +305 -0
- package/dist/viewers/video/styles.d.ts +1 -0
- package/dist/viewers/video/styles.js +79 -0
- package/dist/viewers/word/controller.d.ts +19 -0
- package/dist/viewers/word/controller.js +22 -0
- package/dist/viewers/word/docx-preprocess.d.ts +63 -0
- package/dist/viewers/word/docx-preprocess.js +136 -0
- package/dist/viewers/word/index.d.ts +32 -0
- package/dist/viewers/word/index.js +187 -0
- package/dist/viewers/word/normalize-docx.d.ts +2 -0
- package/dist/viewers/word/normalize-docx.js +50 -0
- package/dist/viewers/word/paginate.d.ts +1 -0
- package/dist/viewers/word/paginate.js +1035 -0
- package/dist/viewers/word/self-loading.d.ts +2 -0
- package/dist/viewers/word/self-loading.js +7 -0
- package/dist/viewers/word/styles.d.ts +1 -0
- package/dist/viewers/word/styles.js +686 -0
- package/dist/viewers/yaml/controller.d.ts +42 -0
- package/dist/viewers/yaml/controller.js +41 -0
- package/dist/viewers/yaml/index.d.ts +21 -0
- package/dist/viewers/yaml/index.js +20 -0
- package/package.json +375 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type * as XLSX from 'xlsx';
|
|
2
|
+
import type { ParseOptions, ParseOutcome } from '../types.js';
|
|
3
|
+
import type { ExcelWorkbook } from './model.js';
|
|
4
|
+
export type { CellRange, CellStyle, ExcelCell, ExcelCellType, ExcelSheet, ExcelWorkbook, SheetAoa } from './model.js';
|
|
5
|
+
export { sheetToAoa } from './model.js';
|
|
6
|
+
export { serializeWorkbook } from './serialize.js';
|
|
7
|
+
/** SheetJS is provided by the caller; the base entry never imports it. */
|
|
8
|
+
export interface ExcelParseDeps {
|
|
9
|
+
xlsx: typeof XLSX;
|
|
10
|
+
}
|
|
11
|
+
/** Input ownership: the parser consumes the input (DESIGN.md §3-①). */
|
|
12
|
+
export declare const EXCEL_INPUT_OWNERSHIP: "consumes";
|
|
13
|
+
/** Default cap on the archive's declared uncompressed total (matches
|
|
14
|
+
* ARCHIVE_DEFAULT_LIMITS.maxDecompressedBytes); override via
|
|
15
|
+
* `options.limits.maxDecompressedBytes`. */
|
|
16
|
+
export declare const EXCEL_MAX_DECOMPRESSED_BYTES: number;
|
|
17
|
+
export declare function parseExcel(input: Uint8Array, deps: ExcelParseDeps, options?: ParseOptions): ParseOutcome<ExcelWorkbook>;
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
// Excel parser entry — the core contract surface (docs/viewers/excel.md §2,
|
|
2
|
+
// DESIGN.md §3-①). parseExcel(bytes) -> ParseOutcome<ExcelWorkbook>.
|
|
3
|
+
//
|
|
4
|
+
// SheetJS (`xlsx`) is an optional peer dependency and is **injected** rather
|
|
5
|
+
// than imported at runtime (ExcelParseDeps), so this base entry carries no
|
|
6
|
+
// `import 'xlsx'` — the bundler of a platform that doesn't use the Excel viewer
|
|
7
|
+
// never has to resolve it (ADR 14, X8). Type-only imports below are erased.
|
|
8
|
+
//
|
|
9
|
+
// Input-caused failures never throw; they return `status: 'failed'` (ADR 11).
|
|
10
|
+
// Input ownership is `consumes` — after parsing, the original bytes are not
|
|
11
|
+
// needed (no verbatim view), so a Worker may transfer them (X4, DESIGN §3-①).
|
|
12
|
+
import { DEFAULT_LIMITS, LimitTracker } from '../types.js';
|
|
13
|
+
import { declaredZipUncompressedBytes } from '../zip-scan.js';
|
|
14
|
+
export { sheetToAoa } from './model.js';
|
|
15
|
+
export { serializeWorkbook } from './serialize.js';
|
|
16
|
+
/** Input ownership: the parser consumes the input (DESIGN.md §3-①). */
|
|
17
|
+
export const EXCEL_INPUT_OWNERSHIP = 'consumes';
|
|
18
|
+
/** Default cap on the archive's declared uncompressed total (matches
|
|
19
|
+
* ARCHIVE_DEFAULT_LIMITS.maxDecompressedBytes); override via
|
|
20
|
+
* `options.limits.maxDecompressedBytes`. */
|
|
21
|
+
export const EXCEL_MAX_DECOMPRESSED_BYTES = 1024 * 1024 * 1024;
|
|
22
|
+
export function parseExcel(input, deps, options = {}) {
|
|
23
|
+
const started = Date.now();
|
|
24
|
+
const limits = { ...DEFAULT_LIMITS, ...options.limits };
|
|
25
|
+
const diagnostics = [];
|
|
26
|
+
const finish = (result) => ({
|
|
27
|
+
result,
|
|
28
|
+
execution: {
|
|
29
|
+
workerUsed: false,
|
|
30
|
+
// maxDecompressedBytes is checked against the ZIP central directory's
|
|
31
|
+
// declared sizes before read (zip-scan.ts), but SheetJS decompresses
|
|
32
|
+
// internally, so an archive that lies in its headers is not bounded
|
|
33
|
+
// during inflate — only maxInputBytes (pre-load) and maxEntries
|
|
34
|
+
// (post-read cell cap) are hard here (X4, follow-up: Worker).
|
|
35
|
+
hardLimitEnforced: false,
|
|
36
|
+
elapsedMillis: Date.now() - started
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
if (input.byteLength > limits.maxInputBytes) {
|
|
40
|
+
return finish(fail('limit-exceeded', false, 'diag.limit-exceeded.input', diagnostics, {
|
|
41
|
+
maxBytes: limits.maxInputBytes
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
if (options.signal?.aborted) {
|
|
45
|
+
return finish(fail('aborted', true, 'diag.aborted', diagnostics));
|
|
46
|
+
}
|
|
47
|
+
// Zip-bomb pre-scan: reject archives whose central directory declares more
|
|
48
|
+
// uncompressed data than the cap before SheetJS inflates anything. Legacy
|
|
49
|
+
// .xls (CFB) and other non-ZIP inputs return null and pass through.
|
|
50
|
+
const maxDecompressed = limits.maxDecompressedBytes ?? EXCEL_MAX_DECOMPRESSED_BYTES;
|
|
51
|
+
const declared = declaredZipUncompressedBytes(input);
|
|
52
|
+
if (declared !== null && declared > maxDecompressed) {
|
|
53
|
+
return finish(fail('limit-exceeded', false, 'diag.limit-exceeded.decompressed', diagnostics, {
|
|
54
|
+
maxBytes: maxDecompressed
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
let wb;
|
|
58
|
+
try {
|
|
59
|
+
wb = deps.xlsx.read(input, {
|
|
60
|
+
type: 'array',
|
|
61
|
+
cellDates: true,
|
|
62
|
+
cellNF: true,
|
|
63
|
+
cellText: true,
|
|
64
|
+
cellStyles: true // grid mode (X3): populate cell.s / !cols / !rows
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
const message = String(err?.message ?? err).toLowerCase();
|
|
69
|
+
if (message.includes('password') || message.includes('encrypt')) {
|
|
70
|
+
return finish(fail('password-required', true, 'diag.excel.password-required', diagnostics));
|
|
71
|
+
}
|
|
72
|
+
return finish(fail('invalid-format', false, 'diag.excel.invalid-format', diagnostics));
|
|
73
|
+
}
|
|
74
|
+
const date1904 = readDate1904(wb);
|
|
75
|
+
const sheetNames = Array.isArray(wb.SheetNames) ? [...wb.SheetNames] : [];
|
|
76
|
+
const tracker = new LimitTracker(limits, options.signal);
|
|
77
|
+
const sheets = [];
|
|
78
|
+
let truncated = false;
|
|
79
|
+
let aborted = false;
|
|
80
|
+
let nonEmptySheets = 0;
|
|
81
|
+
for (const name of sheetNames) {
|
|
82
|
+
const ws = wb.Sheets[name];
|
|
83
|
+
const built = ws ? buildSheet(name, ws, deps.xlsx, tracker, limits.maxEntries) : emptySheet(name);
|
|
84
|
+
sheets.push(built.sheet);
|
|
85
|
+
if (built.sheet.usedRange)
|
|
86
|
+
nonEmptySheets++;
|
|
87
|
+
if (built.aborted) {
|
|
88
|
+
aborted = true;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
if (built.truncated) {
|
|
92
|
+
truncated = true;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (aborted) {
|
|
97
|
+
return finish(fail('aborted', true, 'diag.aborted', diagnostics));
|
|
98
|
+
}
|
|
99
|
+
if (sheetNames.length === 0 || nonEmptySheets === 0) {
|
|
100
|
+
diagnostics.push({
|
|
101
|
+
severity: 'info',
|
|
102
|
+
code: 'excel.empty-workbook',
|
|
103
|
+
messageKey: 'diag.excel.empty-workbook'
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const document = { sheetNames, sheets, date1904 };
|
|
107
|
+
if (truncated) {
|
|
108
|
+
diagnostics.push({
|
|
109
|
+
severity: 'warning',
|
|
110
|
+
code: 'limit-exceeded',
|
|
111
|
+
messageKey: 'diag.limit-exceeded.cells',
|
|
112
|
+
args: { count: tracker.entryCount }
|
|
113
|
+
});
|
|
114
|
+
return finish({ status: 'partial', document, diagnostics });
|
|
115
|
+
}
|
|
116
|
+
return finish({ status: 'ok', document, diagnostics });
|
|
117
|
+
}
|
|
118
|
+
function fail(code, retryable, messageKey, diagnostics, args) {
|
|
119
|
+
return {
|
|
120
|
+
status: 'failed',
|
|
121
|
+
failure: args ? { code, retryable, messageKey, args } : { code, retryable, messageKey },
|
|
122
|
+
diagnostics
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function emptySheet(name) {
|
|
126
|
+
return {
|
|
127
|
+
sheet: { name, usedRange: null, cells: [], rowCount: 0, columnCount: 0 },
|
|
128
|
+
truncated: false,
|
|
129
|
+
aborted: false
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function buildSheet(name, ws, xlsx, tracker, maxEntries) {
|
|
133
|
+
const usedRange = resolveUsedRange(ws, xlsx);
|
|
134
|
+
if (!usedRange) {
|
|
135
|
+
return emptySheet(name);
|
|
136
|
+
}
|
|
137
|
+
// Collect present cells only (sparse) and emit row-major for deterministic
|
|
138
|
+
// order regardless of the object key order SheetJS produced.
|
|
139
|
+
const addresses = [];
|
|
140
|
+
for (const key of Object.keys(ws)) {
|
|
141
|
+
if (key.charCodeAt(0) === 0x21 /* '!' */)
|
|
142
|
+
continue;
|
|
143
|
+
const decoded = xlsx.utils.decode_cell(key);
|
|
144
|
+
addresses.push({ r: decoded.r, c: decoded.c, addr: key });
|
|
145
|
+
}
|
|
146
|
+
addresses.sort((a, b) => (a.r - b.r) || (a.c - b.c));
|
|
147
|
+
const cells = [];
|
|
148
|
+
let truncated = false;
|
|
149
|
+
let aborted = false;
|
|
150
|
+
for (let i = 0; i < addresses.length; i++) {
|
|
151
|
+
const entry = addresses[i];
|
|
152
|
+
const raw = ws[entry.addr];
|
|
153
|
+
if (!raw || raw.t === 'z' || raw.t === undefined)
|
|
154
|
+
continue; // blank/stub
|
|
155
|
+
tracker.addEntries(1);
|
|
156
|
+
// Amortize abort/limit checks: every 512 cells or once over the cap.
|
|
157
|
+
if (i % 512 === 0 || tracker.entryCount > maxEntries) {
|
|
158
|
+
const violation = tracker.checkpoint();
|
|
159
|
+
if (violation?.kind === 'aborted') {
|
|
160
|
+
aborted = true;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
if (violation) {
|
|
164
|
+
truncated = true;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
cells.push(mapCell(entry.r, entry.c, raw));
|
|
169
|
+
}
|
|
170
|
+
const sheet = {
|
|
171
|
+
name,
|
|
172
|
+
usedRange,
|
|
173
|
+
cells,
|
|
174
|
+
rowCount: usedRange.r1 - usedRange.r0 + 1,
|
|
175
|
+
columnCount: usedRange.c1 - usedRange.c0 + 1
|
|
176
|
+
};
|
|
177
|
+
// Grid-mode presentation (X3): merges + column/row sizes. Optional — only
|
|
178
|
+
// set when present, so the data mode and existing snapshots are unaffected.
|
|
179
|
+
const merges = ws['!merges']?.map((m) => ({
|
|
180
|
+
r0: m.s.r,
|
|
181
|
+
c0: m.s.c,
|
|
182
|
+
r1: m.e.r,
|
|
183
|
+
c1: m.e.c
|
|
184
|
+
}));
|
|
185
|
+
if (merges && merges.length)
|
|
186
|
+
sheet.merges = merges;
|
|
187
|
+
const cols = ws['!cols'];
|
|
188
|
+
if (cols) {
|
|
189
|
+
const widths = {};
|
|
190
|
+
cols.forEach((col, c) => {
|
|
191
|
+
const px = colWidthPx(col);
|
|
192
|
+
if (px !== null)
|
|
193
|
+
widths[c] = px;
|
|
194
|
+
});
|
|
195
|
+
if (Object.keys(widths).length)
|
|
196
|
+
sheet.columnWidthsPx = widths;
|
|
197
|
+
}
|
|
198
|
+
const rows = ws['!rows'];
|
|
199
|
+
if (rows) {
|
|
200
|
+
const heights = {};
|
|
201
|
+
rows.forEach((row, r) => {
|
|
202
|
+
const px = rowHeightPx(row);
|
|
203
|
+
if (px !== null)
|
|
204
|
+
heights[r] = px;
|
|
205
|
+
});
|
|
206
|
+
if (Object.keys(heights).length)
|
|
207
|
+
sheet.rowHeightsPx = heights;
|
|
208
|
+
}
|
|
209
|
+
return { sheet, truncated, aborted };
|
|
210
|
+
}
|
|
211
|
+
/** Column width in px from SheetJS ColInfo (ported from omni-viewer-web). */
|
|
212
|
+
function colWidthPx(col) {
|
|
213
|
+
if (!col)
|
|
214
|
+
return null;
|
|
215
|
+
if (typeof col.wpx === 'number')
|
|
216
|
+
return clampPx(col.wpx, 24, 800);
|
|
217
|
+
if (typeof col.wch === 'number')
|
|
218
|
+
return clampPx(col.wch * 8 + 12, 24, 800);
|
|
219
|
+
if (typeof col.width === 'number')
|
|
220
|
+
return clampPx((col.width / 256) * 8 + 12, 24, 800);
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
/** Row height in px from SheetJS RowInfo (ported from omni-viewer-web). */
|
|
224
|
+
function rowHeightPx(row) {
|
|
225
|
+
if (!row)
|
|
226
|
+
return null;
|
|
227
|
+
if (typeof row.hpx === 'number')
|
|
228
|
+
return clampPx(row.hpx, 20, 240);
|
|
229
|
+
if (typeof row.hpt === 'number')
|
|
230
|
+
return clampPx((row.hpt * 96) / 72, 20, 240);
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
function clampPx(value, min, max) {
|
|
234
|
+
return Math.max(min, Math.min(max, Math.round(value)));
|
|
235
|
+
}
|
|
236
|
+
function resolveUsedRange(ws, xlsx) {
|
|
237
|
+
const ref = ws['!ref'];
|
|
238
|
+
if (ref) {
|
|
239
|
+
try {
|
|
240
|
+
const range = xlsx.utils.decode_range(ref);
|
|
241
|
+
if (range.e.r >= range.s.r && range.e.c >= range.s.c) {
|
|
242
|
+
return { r0: range.s.r, c0: range.s.c, r1: range.e.r, c1: range.e.c };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// fall through to manual reconstruction
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
let r0 = Infinity, c0 = Infinity, r1 = -1, c1 = -1;
|
|
250
|
+
for (const key of Object.keys(ws)) {
|
|
251
|
+
if (key.charCodeAt(0) === 0x21)
|
|
252
|
+
continue;
|
|
253
|
+
const cell = xlsx.utils.decode_cell(key);
|
|
254
|
+
if (cell.r < r0)
|
|
255
|
+
r0 = cell.r;
|
|
256
|
+
if (cell.c < c0)
|
|
257
|
+
c0 = cell.c;
|
|
258
|
+
if (cell.r > r1)
|
|
259
|
+
r1 = cell.r;
|
|
260
|
+
if (cell.c > c1)
|
|
261
|
+
c1 = cell.c;
|
|
262
|
+
}
|
|
263
|
+
if (r1 < 0 || c1 < 0)
|
|
264
|
+
return null;
|
|
265
|
+
return { r0, c0, r1, c1 };
|
|
266
|
+
}
|
|
267
|
+
function mapCell(r, c, cell) {
|
|
268
|
+
const w = cell.w != null ? String(cell.w) : (cell.v == null ? '' : String(cell.v));
|
|
269
|
+
let t;
|
|
270
|
+
let v;
|
|
271
|
+
switch (cell.t) {
|
|
272
|
+
case 'n':
|
|
273
|
+
t = 'number';
|
|
274
|
+
v = typeof cell.v === 'number' ? cell.v : Number(cell.v);
|
|
275
|
+
break;
|
|
276
|
+
case 'b':
|
|
277
|
+
t = 'boolean';
|
|
278
|
+
v = Boolean(cell.v);
|
|
279
|
+
break;
|
|
280
|
+
case 'd':
|
|
281
|
+
t = 'date';
|
|
282
|
+
v = cell.v instanceof Date ? dateToIso(cell.v) : String(cell.v ?? '');
|
|
283
|
+
break;
|
|
284
|
+
case 'e':
|
|
285
|
+
t = 'error';
|
|
286
|
+
v = w;
|
|
287
|
+
break;
|
|
288
|
+
default: // 's' | 'str' (formula string result) | anything else
|
|
289
|
+
t = 'text';
|
|
290
|
+
v = cell.v == null ? '' : String(cell.v);
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
const style = mapStyle(cell.s);
|
|
294
|
+
return style ? { r, c, t, v, w, style } : { r, c, t, v, w };
|
|
295
|
+
}
|
|
296
|
+
/** Map a SheetJS cell style object to the semantic CellStyle (grid mode, X3).
|
|
297
|
+
* Ported from omni-viewer-web `mapCellStyle`; stores semantic values, not CSS. */
|
|
298
|
+
function mapStyle(s) {
|
|
299
|
+
if (!s || typeof s !== 'object')
|
|
300
|
+
return undefined;
|
|
301
|
+
const style = s;
|
|
302
|
+
const out = {};
|
|
303
|
+
const font = style.font;
|
|
304
|
+
if (font?.bold)
|
|
305
|
+
out.bold = true;
|
|
306
|
+
if (font?.italic)
|
|
307
|
+
out.italic = true;
|
|
308
|
+
if (typeof font?.sz === 'number')
|
|
309
|
+
out.fontSizePx = Math.max(8, Math.min(64, font.sz));
|
|
310
|
+
const color = normalizeColor(font?.color);
|
|
311
|
+
if (color)
|
|
312
|
+
out.color = color;
|
|
313
|
+
const fill = style.fill;
|
|
314
|
+
const bg = normalizeColor(fill?.fgColor) ?? normalizeColor(fill?.bgColor);
|
|
315
|
+
if (bg && fill?.patternType && fill.patternType !== 'none')
|
|
316
|
+
out.background = bg;
|
|
317
|
+
const align = style.alignment;
|
|
318
|
+
const h = align?.horizontal !== undefined ? String(align.horizontal) : '';
|
|
319
|
+
if (h === 'left' || h === 'center' || h === 'right' || h === 'justify')
|
|
320
|
+
out.align = h;
|
|
321
|
+
else if (h === 'centerContinuous')
|
|
322
|
+
out.align = 'center';
|
|
323
|
+
else if (h === 'distributed')
|
|
324
|
+
out.align = 'justify';
|
|
325
|
+
const va = align?.vertical !== undefined ? String(align.vertical) : '';
|
|
326
|
+
if (va === 'top' || va === 'center' || va === 'bottom')
|
|
327
|
+
out.valign = va;
|
|
328
|
+
if (align?.wrapText)
|
|
329
|
+
out.wrap = true;
|
|
330
|
+
const border = style.border;
|
|
331
|
+
if (border) {
|
|
332
|
+
const edges = {};
|
|
333
|
+
const toEdge = (edge) => {
|
|
334
|
+
if (!edge || typeof edge !== 'object')
|
|
335
|
+
return undefined;
|
|
336
|
+
const e = edge;
|
|
337
|
+
const c = normalizeColor(e.color) ?? '#808080';
|
|
338
|
+
return `${borderStyle(typeof e.style === 'string' ? e.style : undefined)} ${c}`;
|
|
339
|
+
};
|
|
340
|
+
const top = toEdge(border.top);
|
|
341
|
+
const right = toEdge(border.right);
|
|
342
|
+
const bottom = toEdge(border.bottom);
|
|
343
|
+
const left = toEdge(border.left);
|
|
344
|
+
if (top)
|
|
345
|
+
edges.top = top;
|
|
346
|
+
if (right)
|
|
347
|
+
edges.right = right;
|
|
348
|
+
if (bottom)
|
|
349
|
+
edges.bottom = bottom;
|
|
350
|
+
if (left)
|
|
351
|
+
edges.left = left;
|
|
352
|
+
if (Object.keys(edges).length)
|
|
353
|
+
out.borders = edges;
|
|
354
|
+
}
|
|
355
|
+
return Object.keys(out).length ? out : undefined;
|
|
356
|
+
}
|
|
357
|
+
function normalizeColor(color) {
|
|
358
|
+
if (!color || typeof color !== 'object')
|
|
359
|
+
return undefined;
|
|
360
|
+
const rgb = color.rgb;
|
|
361
|
+
if (typeof rgb !== 'string')
|
|
362
|
+
return undefined;
|
|
363
|
+
const raw = rgb.replace('#', '');
|
|
364
|
+
const hex = raw.length > 6 ? raw.slice(-6) : raw;
|
|
365
|
+
return /^[0-9A-Fa-f]{6}$/.test(hex) ? `#${hex}` : undefined;
|
|
366
|
+
}
|
|
367
|
+
function borderStyle(style) {
|
|
368
|
+
switch (style) {
|
|
369
|
+
case 'medium': return '2px solid';
|
|
370
|
+
case 'thick': return '3px solid';
|
|
371
|
+
case 'dashed': return '1px dashed';
|
|
372
|
+
case 'dotted': return '1px dotted';
|
|
373
|
+
case 'double': return '3px double';
|
|
374
|
+
default: return '1px solid'; // thin / hair / unknown
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Format a SheetJS date cell to a calendar-preserving ISO string. SheetJS
|
|
379
|
+
* (>=0.20) constructs the Date from the serial's parts in UTC; reading them
|
|
380
|
+
* back with UTC getters round-trips the intended calendar date on any host
|
|
381
|
+
* timezone (deterministic), so we never call local getters (X5, determinism
|
|
382
|
+
* rule). Requires xlsx >=0.20.2 (the peer range) — 0.18 built these Dates
|
|
383
|
+
* from local-time parts, which this would misread.
|
|
384
|
+
*/
|
|
385
|
+
function dateToIso(d) {
|
|
386
|
+
if (Number.isNaN(d.getTime()))
|
|
387
|
+
return '';
|
|
388
|
+
const pad = (n, width = 2) => String(n).padStart(width, '0');
|
|
389
|
+
const y = pad(d.getUTCFullYear(), 4);
|
|
390
|
+
const mo = pad(d.getUTCMonth() + 1);
|
|
391
|
+
const da = pad(d.getUTCDate());
|
|
392
|
+
const h = d.getUTCHours();
|
|
393
|
+
const mi = d.getUTCMinutes();
|
|
394
|
+
const s = d.getUTCSeconds();
|
|
395
|
+
const ms = d.getUTCMilliseconds();
|
|
396
|
+
if (h === 0 && mi === 0 && s === 0 && ms === 0)
|
|
397
|
+
return `${y}-${mo}-${da}`;
|
|
398
|
+
const base = `${y}-${mo}-${da}T${pad(h)}:${pad(mi)}:${pad(s)}`;
|
|
399
|
+
return ms ? `${base}.${pad(ms, 3)}` : base;
|
|
400
|
+
}
|
|
401
|
+
function readDate1904(wb) {
|
|
402
|
+
// SheetJS types this as boolean | undefined, but files can carry 1/"1";
|
|
403
|
+
// coerce defensively without tripping strict comparisons.
|
|
404
|
+
const flag = wb.Workbook?.WBProps?.date1904;
|
|
405
|
+
return flag === true || flag === 1 || flag === '1' || flag === 'true';
|
|
406
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export type ExcelCellType = 'number' | 'text' | 'boolean' | 'date' | 'error';
|
|
2
|
+
/**
|
|
3
|
+
* Semantic (non-CSS) presentation attributes for the grid render mode (X3).
|
|
4
|
+
* Populated only when the parser reads styles; the data (AOA) mode ignores it.
|
|
5
|
+
* Colors are `#rrggbb`; borders are CSS shorthand strings (e.g. '1px solid #333').
|
|
6
|
+
*/
|
|
7
|
+
export interface CellStyle {
|
|
8
|
+
bold?: boolean;
|
|
9
|
+
italic?: boolean;
|
|
10
|
+
fontSizePx?: number;
|
|
11
|
+
color?: string;
|
|
12
|
+
background?: string;
|
|
13
|
+
align?: 'left' | 'center' | 'right' | 'justify';
|
|
14
|
+
valign?: 'top' | 'center' | 'bottom';
|
|
15
|
+
wrap?: boolean;
|
|
16
|
+
borders?: {
|
|
17
|
+
top?: string;
|
|
18
|
+
right?: string;
|
|
19
|
+
bottom?: string;
|
|
20
|
+
left?: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Inclusive 0-based cell range (used for merges and used ranges). */
|
|
24
|
+
export interface CellRange {
|
|
25
|
+
r0: number;
|
|
26
|
+
c0: number;
|
|
27
|
+
r1: number;
|
|
28
|
+
c1: number;
|
|
29
|
+
}
|
|
30
|
+
export interface ExcelCell {
|
|
31
|
+
/** 0-based absolute row index (sheet origin). */
|
|
32
|
+
r: number;
|
|
33
|
+
/** 0-based absolute column index. */
|
|
34
|
+
c: number;
|
|
35
|
+
t: ExcelCellType;
|
|
36
|
+
/**
|
|
37
|
+
* Raw value. Dates are calendar-preserving ISO-8601 strings (see
|
|
38
|
+
* `dateToIso`); numbers are float64 (SheetJS reads workbook numbers as JS
|
|
39
|
+
* numbers, so values beyond 2^53 already lost precision upstream — X5);
|
|
40
|
+
* errors carry the error text (e.g. '#DIV/0!').
|
|
41
|
+
*/
|
|
42
|
+
v: string | number | boolean | null;
|
|
43
|
+
/** Display text as formatted by the source file's number format (`w`). */
|
|
44
|
+
w: string;
|
|
45
|
+
/** Presentation attributes for grid mode (X3); absent when unstyled. */
|
|
46
|
+
style?: CellStyle;
|
|
47
|
+
}
|
|
48
|
+
export interface ExcelSheet {
|
|
49
|
+
name: string;
|
|
50
|
+
/** Inclusive 0-based bounds of the used range, or null when empty. */
|
|
51
|
+
usedRange: CellRange | null;
|
|
52
|
+
/** Sparse — only cells present in the file, row-major (r, then c). */
|
|
53
|
+
cells: ExcelCell[];
|
|
54
|
+
rowCount: number;
|
|
55
|
+
columnCount: number;
|
|
56
|
+
/** Merged cell ranges (grid mode, X3). */
|
|
57
|
+
merges?: CellRange[];
|
|
58
|
+
/** Absolute column index → width in px (grid mode, X3). */
|
|
59
|
+
columnWidthsPx?: Record<number, number>;
|
|
60
|
+
/** Absolute row index → height in px (grid mode, X3). */
|
|
61
|
+
rowHeightsPx?: Record<number, number>;
|
|
62
|
+
}
|
|
63
|
+
export interface ExcelWorkbook {
|
|
64
|
+
sheetNames: string[];
|
|
65
|
+
sheets: ExcelSheet[];
|
|
66
|
+
/** 1904 date system flag (affects how date serials map to calendar dates). */
|
|
67
|
+
date1904: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface SheetAoa {
|
|
70
|
+
headers: string[];
|
|
71
|
+
rows: string[][];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Derive a dense AOA (header row + data rows) of display strings from a sparse
|
|
75
|
+
* sheet. Missing cells become '' (X5). When `hasHeader`, the first used row is
|
|
76
|
+
* the header; otherwise headers are empty and every used row is a data row.
|
|
77
|
+
* Pure and deterministic — the core viewer/controller calls this; the parser
|
|
78
|
+
* does not, keeping the model grid-shaped.
|
|
79
|
+
*/
|
|
80
|
+
export declare function sheetToAoa(sheet: ExcelSheet, hasHeader?: boolean): SheetAoa;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Excel document model (docs/viewers/excel.md §2, X1). The parser preserves a
|
|
2
|
+
// sparse, coordinate-bearing grid — cell address + type + raw value (`v`) +
|
|
3
|
+
// the source file's formatted text (`w`). The AOA text table that the core
|
|
4
|
+
// viewer renders is *derived* from this model (see `sheetToAoa`), so the model
|
|
5
|
+
// stays the single source and web can keep its own grid renderer on top of it
|
|
6
|
+
// (ADR 33). Cell styles / merges / column widths are optional follow-up fields
|
|
7
|
+
// (X3) and are not populated in v1.
|
|
8
|
+
/**
|
|
9
|
+
* Derive a dense AOA (header row + data rows) of display strings from a sparse
|
|
10
|
+
* sheet. Missing cells become '' (X5). When `hasHeader`, the first used row is
|
|
11
|
+
* the header; otherwise headers are empty and every used row is a data row.
|
|
12
|
+
* Pure and deterministic — the core viewer/controller calls this; the parser
|
|
13
|
+
* does not, keeping the model grid-shaped.
|
|
14
|
+
*/
|
|
15
|
+
export function sheetToAoa(sheet, hasHeader = true) {
|
|
16
|
+
if (!sheet.usedRange)
|
|
17
|
+
return { headers: [], rows: [] };
|
|
18
|
+
const { r0, c0, r1, c1 } = sheet.usedRange;
|
|
19
|
+
const width = c1 - c0 + 1;
|
|
20
|
+
// Bucket cell display text by row for O(cells) fill instead of O(rows*cols)
|
|
21
|
+
// address lookups.
|
|
22
|
+
const byRow = new Map();
|
|
23
|
+
for (const cell of sheet.cells) {
|
|
24
|
+
let row = byRow.get(cell.r);
|
|
25
|
+
if (!row) {
|
|
26
|
+
row = new Array(width).fill('');
|
|
27
|
+
byRow.set(cell.r, row);
|
|
28
|
+
}
|
|
29
|
+
const col = cell.c - c0;
|
|
30
|
+
if (col >= 0 && col < width)
|
|
31
|
+
row[col] = cell.w;
|
|
32
|
+
}
|
|
33
|
+
const rowAt = (r) => byRow.get(r) ?? new Array(width).fill('');
|
|
34
|
+
let headers;
|
|
35
|
+
let dataStart;
|
|
36
|
+
if (hasHeader) {
|
|
37
|
+
headers = rowAt(r0);
|
|
38
|
+
dataStart = r0 + 1;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
headers = new Array(width).fill('');
|
|
42
|
+
dataStart = r0;
|
|
43
|
+
}
|
|
44
|
+
const rows = [];
|
|
45
|
+
for (let r = dataStart; r <= r1; r++)
|
|
46
|
+
rows.push(rowAt(r));
|
|
47
|
+
return { headers, rows };
|
|
48
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Type-preserving XLSX export (docs/viewers/excel.md §2, X7). Re-serializes the
|
|
2
|
+
// original parsed model — using each cell's typed value (`v`/`t`), NOT the
|
|
3
|
+
// display string (`w`) — so numbers stay numbers, booleans stay booleans, and
|
|
4
|
+
// dates stay date-typed cells. Building a worksheet from `w` strings would make
|
|
5
|
+
// everything a text cell (the bug this replaces).
|
|
6
|
+
//
|
|
7
|
+
// Dates: the model stores a calendar-preserving ISO string (model.ts). We
|
|
8
|
+
// rebuild a Date from its numeric components — `Date.UTC(y, m, d, …)`, which is
|
|
9
|
+
// well-defined (not the impl-defined string parse the parser avoided) — and let
|
|
10
|
+
// SheetJS serialize it. SheetJS (>=0.20) maps Dates to serials from their UTC
|
|
11
|
+
// parts, and its own Date→serial→date round-trip is exact, whereas
|
|
12
|
+
// hand-computed serials pick up a sub-minute read-back rounding error.
|
|
13
|
+
// `xlsx` is injected (base entries never import it — ADR 14).
|
|
14
|
+
const ISO_RE = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?)?$/;
|
|
15
|
+
export function serializeWorkbook(workbook, xlsx) {
|
|
16
|
+
const wb = xlsx.utils.book_new();
|
|
17
|
+
const sheets = workbook.sheets.length
|
|
18
|
+
? workbook.sheets
|
|
19
|
+
: [{ name: 'Sheet1', usedRange: null, cells: [], rowCount: 0, columnCount: 0 }];
|
|
20
|
+
for (const sheet of sheets) {
|
|
21
|
+
const ws = {};
|
|
22
|
+
for (const cell of sheet.cells) {
|
|
23
|
+
const address = xlsx.utils.encode_cell({ r: cell.r, c: cell.c });
|
|
24
|
+
ws[address] = toCellObject(cell);
|
|
25
|
+
}
|
|
26
|
+
ws['!ref'] = sheet.usedRange
|
|
27
|
+
? xlsx.utils.encode_range({
|
|
28
|
+
s: { r: sheet.usedRange.r0, c: sheet.usedRange.c0 },
|
|
29
|
+
e: { r: sheet.usedRange.r1, c: sheet.usedRange.c1 }
|
|
30
|
+
})
|
|
31
|
+
: 'A1';
|
|
32
|
+
// book_append_sheet enforces the 31-char / uniqueness rules SheetJS
|
|
33
|
+
// expects; it also wires SheetNames + Sheets.
|
|
34
|
+
xlsx.utils.book_append_sheet(wb, ws, sheet.name);
|
|
35
|
+
}
|
|
36
|
+
if (workbook.date1904) {
|
|
37
|
+
wb.Workbook = { ...(wb.Workbook ?? {}), WBProps: { ...(wb.Workbook?.WBProps ?? {}), date1904: true } };
|
|
38
|
+
}
|
|
39
|
+
return new Uint8Array(xlsx.write(wb, { type: 'array', bookType: 'xlsx' }));
|
|
40
|
+
}
|
|
41
|
+
function toCellObject(cell) {
|
|
42
|
+
switch (cell.t) {
|
|
43
|
+
case 'number':
|
|
44
|
+
return { t: 'n', v: typeof cell.v === 'number' ? cell.v : Number(cell.v) };
|
|
45
|
+
case 'boolean':
|
|
46
|
+
return { t: 'b', v: Boolean(cell.v) };
|
|
47
|
+
case 'date': {
|
|
48
|
+
const date = typeof cell.v === 'string' ? isoToDate(cell.v) : null;
|
|
49
|
+
if (!date) {
|
|
50
|
+
// Non-ISO date value — keep the visible text rather than guess.
|
|
51
|
+
return { t: 's', v: cell.w || String(cell.v ?? '') };
|
|
52
|
+
}
|
|
53
|
+
// A date-typed cell + date number format makes SheetJS emit a serial
|
|
54
|
+
// and read it back as a date on the next parse (type preserved).
|
|
55
|
+
return { t: 'd', v: date, z: 'yyyy-mm-dd' };
|
|
56
|
+
}
|
|
57
|
+
case 'error':
|
|
58
|
+
return { t: 's', v: cell.w };
|
|
59
|
+
default:
|
|
60
|
+
return { t: 's', v: cell.v == null ? '' : String(cell.v) };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Rebuild a Date from ISO components via `Date.UTC` — mirrors the parser's
|
|
64
|
+
* UTC-getter formatting (SheetJS >=0.20 serializes date cells from UTC
|
|
65
|
+
* parts) so the calendar date round-trips. */
|
|
66
|
+
function isoToDate(iso) {
|
|
67
|
+
const m = ISO_RE.exec(iso);
|
|
68
|
+
if (!m)
|
|
69
|
+
return null;
|
|
70
|
+
const year = Number(m[1]);
|
|
71
|
+
const month = Number(m[2]);
|
|
72
|
+
const day = Number(m[3]);
|
|
73
|
+
const hour = m[4] !== undefined ? Number(m[4]) : 0;
|
|
74
|
+
const minute = m[5] !== undefined ? Number(m[5]) : 0;
|
|
75
|
+
const second = m[6] !== undefined ? Number(m[6]) : 0;
|
|
76
|
+
const millis = m[7] !== undefined ? Number(m[7].padEnd(3, '0')) : 0;
|
|
77
|
+
return new Date(Date.UTC(year, month - 1, day, hour, minute, second, millis));
|
|
78
|
+
}
|