@opencraw/office-reader 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +201 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +6 -0
- package/dist/pptx.d.ts +1 -0
- package/dist/pptx.esm.js +5 -0
- package/dist/read-pptx.use-case.esm.js +591 -0
- package/dist/read-source.client.esm.js +289 -0
- package/dist/read-xlsx.use-case.esm.js +381 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/ooxml-package/index.d.ts +7 -0
- package/dist/src/ooxml-package/ooxml-package.client.d.ts +47 -0
- package/dist/src/ooxml-package/relationships.mapper.d.ts +27 -0
- package/dist/src/ooxml-package/xml-walk.algorithm.d.ts +37 -0
- package/dist/src/presentation/chart.mapper.d.ts +13 -0
- package/dist/src/presentation/deck.model.d.ts +56 -0
- package/dist/src/presentation/index.d.ts +6 -0
- package/dist/src/presentation/notes.mapper.d.ts +9 -0
- package/dist/src/presentation/placeholder-geometry.mapper.d.ts +53 -0
- package/dist/src/presentation/read-pptx.use-case.d.ts +32 -0
- package/dist/src/presentation/slide.mapper.d.ts +24 -0
- package/dist/src/read-error/index.d.ts +3 -0
- package/dist/src/read-error/office-read.error.d.ts +29 -0
- package/dist/src/source-bytes/index.d.ts +3 -0
- package/dist/src/source-bytes/read-source.client.d.ts +18 -0
- package/dist/src/spreadsheet/cell-value.algorithm.d.ts +47 -0
- package/dist/src/spreadsheet/index.d.ts +6 -0
- package/dist/src/spreadsheet/number-formats.mapper.d.ts +10 -0
- package/dist/src/spreadsheet/read-xlsx.use-case.d.ts +27 -0
- package/dist/src/spreadsheet/shared-strings.mapper.d.ts +10 -0
- package/dist/src/spreadsheet/workbook.model.d.ts +35 -0
- package/dist/src/spreadsheet/worksheet.mapper.d.ts +23 -0
- package/dist/xlsx.d.ts +1 -0
- package/dist/xlsx.esm.js +5 -0
- package/package.json +77 -0
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# @opencraw/office-reader
|
|
2
|
+
|
|
3
|
+
Reads Office files into plain objects:
|
|
4
|
+
|
|
5
|
+
- **workbooks** (`.xlsx`, `.xlsm`) as sheets of cells, with their merged ranges and hidden rows;
|
|
6
|
+
- **presentations** (`.pptx`, `.pptm`, `.ppsx`) as slides of positioned text boxes, tables, chart data and
|
|
7
|
+
speaker notes.
|
|
8
|
+
|
|
9
|
+
- **What the file holds, faithfully.** Formulas give their cached value, and nothing is evaluated. Dates are
|
|
10
|
+
dates, in both the 1900 and 1904 systems. Merged ranges and hidden rows and sheets are reported, not
|
|
11
|
+
guessed away.
|
|
12
|
+
- **Runs anywhere.** Two small dependencies ([fflate](https://github.com/101arrowz/fflate) and
|
|
13
|
+
[htmlparser2](https://github.com/fb55/htmlparser2)), and no Node built-in except when you pass a path. It
|
|
14
|
+
works in Node, browsers, workers and edge runtimes.
|
|
15
|
+
- **Safe on hostile files.** Zip entries are capped by size. XML entities a file declares are never expanded
|
|
16
|
+
(no "billion laughs"), and no external entity is ever fetched (no XXE). Nothing in the file runs.
|
|
17
|
+
|
|
18
|
+
It is part of [OpenCraw](https://github.com/russoedu/open.craw), which uses it to crawl price lists and
|
|
19
|
+
incentive sheets, but it depends on nothing from it.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install @opencraw/office-reader
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Read a workbook
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { readXlsx } from '@opencraw/office-reader/xlsx'
|
|
31
|
+
|
|
32
|
+
const book = await readXlsx('./listino.xlsx')
|
|
33
|
+
for (const sheet of book.sheets) {
|
|
34
|
+
console.log(sheet.name, sheet.rows.length)
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// { date1904: false, sheets: [
|
|
40
|
+
// { name: 'Incentivi giugno', hidden: false,
|
|
41
|
+
// rows: [
|
|
42
|
+
// ['Incentivi concessionari – giugno 2026'],
|
|
43
|
+
// [],
|
|
44
|
+
// ['Marca', 'Modello', 'Prezzo', null, 'Sconto', 'Valido dal', 'Attivo', 'Nota'],
|
|
45
|
+
// [null, null, 'Listino', 'Netto'],
|
|
46
|
+
// ['Fiat', 'Pandina', 15950, 13955.625, 0.125, Date(2026-06-01), true, 'Solo rottamazione'],
|
|
47
|
+
// [null, 'Pandina Cross', 17950, 15706.25, 0.125, Date(2026-06-01T09:30), false, { error: '#DIV/0!' }],
|
|
48
|
+
// …
|
|
49
|
+
// ],
|
|
50
|
+
// hiddenRows: [6],
|
|
51
|
+
// merges: ['A1:H1', 'A3:A4', 'B3:B4', 'C3:D3', 'E3:E4', 'F3:F4', 'A5:A6'] },
|
|
52
|
+
// { name: 'Archivio', hidden: true, … } ] }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Sources
|
|
56
|
+
|
|
57
|
+
`readXlsx(source, options?)` takes any of these:
|
|
58
|
+
|
|
59
|
+
| Source | Notes |
|
|
60
|
+
|---|---|
|
|
61
|
+
| a path (`string`), or a `file:` `URL` | Node only. A string is always a path. |
|
|
62
|
+
| `Uint8Array`, `Buffer`, `ArrayBuffer`, any `ArrayBufferView` | |
|
|
63
|
+
| `Blob`, `File` | An upload in a browser or a server framework. |
|
|
64
|
+
| `ReadableStream<Uint8Array>` | `response.body` from `fetch`, for example. |
|
|
65
|
+
| any `AsyncIterable<Uint8Array>` | Node streams: `fs.createReadStream(path)`, a request body. |
|
|
66
|
+
|
|
67
|
+
It never fetches: pass `await (await fetch(url)).arrayBuffer()` or `response.body`.
|
|
68
|
+
|
|
69
|
+
### Options
|
|
70
|
+
|
|
71
|
+
| Option | Default | |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `sheets` | all | A name, a `RegExp`, or `({ name, hidden }) => boolean`. Unselected sheets are never inflated. |
|
|
74
|
+
| `values` | `'typed'` | `'typed'` or `'text'` (below). |
|
|
75
|
+
| `limits` | 256 MiB per entry, 512 MiB per file | `{ entryBytes, totalBytes }`: what the zip entries may declare. |
|
|
76
|
+
|
|
77
|
+
### Values
|
|
78
|
+
|
|
79
|
+
| Cell | `values: 'typed'` | `values: 'text'` |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| text, rich text | `'Solo rottamazione'` | same |
|
|
82
|
+
| number | `13955.625` | `'13955.625'` (shortest round-trip form: `78.6`, not `78.599999999999994`) |
|
|
83
|
+
| percentage | `0.125` (display formats are not applied) | `'0.125'` |
|
|
84
|
+
| date / date-time | `Date` holding the wall-clock time as UTC | `'2026-06-01'` / `'2026-06-01T09:30:00'` |
|
|
85
|
+
| time of day | `Date` on Excel's day zero (`1899-12-30T12:00Z`) | `'12:00:00'` |
|
|
86
|
+
| boolean | `true` | `'true'` |
|
|
87
|
+
| error | `{ error: '#DIV/0!' }` | `'#DIV/0!'` |
|
|
88
|
+
| formula | its cached value, as above | same |
|
|
89
|
+
| empty | `null` | `''` |
|
|
90
|
+
|
|
91
|
+
Spreadsheets have no time zones, so a `Date` carries the wall-clock time in its UTC fields. Read it with
|
|
92
|
+
`getUTCHours()` or `toISOString()`, not `getHours()`.
|
|
93
|
+
|
|
94
|
+
### Sheets
|
|
95
|
+
|
|
96
|
+
Each sheet is `{ name, hidden, rows, hiddenRows, merges }`:
|
|
97
|
+
|
|
98
|
+
- `rows`: top to bottom from row 1. Each row runs to its last stored cell, so rows can be ragged and empty
|
|
99
|
+
rows are `[]`.
|
|
100
|
+
- `hidden`: the sheet is hidden or very hidden in the workbook.
|
|
101
|
+
- `hiddenRows`: hidden rows, 0-based.
|
|
102
|
+
- `merges`: merged ranges as A1 references. A merged range's value sits in its **top-left cell only**, as the
|
|
103
|
+
file stores it. To read the table a person sees, copy that value into every cell the range covers.
|
|
104
|
+
- Chart sheets hold no cells and are left out.
|
|
105
|
+
|
|
106
|
+
### Errors
|
|
107
|
+
|
|
108
|
+
A file that cannot be read throws `OfficeReadError`. Its `code` is one of the following, and its message
|
|
109
|
+
says what to do:
|
|
110
|
+
|
|
111
|
+
| `code` | The file is |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `legacy-format` | a legacy binary `.xls`, `.ppt` or `.doc`: save it as `.xlsx` / `.pptx`, or export it as PDF |
|
|
114
|
+
| `encrypted` | password-protected |
|
|
115
|
+
| `unsupported-format` | an OpenDocument `.ods` / `.odp` |
|
|
116
|
+
| `not-xlsx` / `not-pptx` | a zip package of the other kind (a `.pptx` given to `readXlsx`, say) |
|
|
117
|
+
| `not-zip` | not a zip at all |
|
|
118
|
+
| `too-large` | past the `limits` |
|
|
119
|
+
| `malformed` | damaged: a part does not inflate |
|
|
120
|
+
| `bad-source` | not something to read bytes from (an `http:` URL, a text stream) |
|
|
121
|
+
|
|
122
|
+
## Read a presentation
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { readPptx } from '@opencraw/office-reader/pptx'
|
|
126
|
+
|
|
127
|
+
const deck = await readPptx('./incentivi.pptx')
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
// { width: 960, height: 540, slides: [
|
|
132
|
+
// { number: 1, title: 'Incentivi giugno', hidden: false,
|
|
133
|
+
// shapes: [{ x: 60, y: 30, width: 840, height: 60, text: 'Incentivi giugno', placeholder: 'title' }],
|
|
134
|
+
// tables: [{ name: 'table 1', hidden: false,
|
|
135
|
+
// rows: [['Incentivi giugno 2026', '', '', ''], ['Modello', 'Prezzo', '', 'Sconto'], ['', 'Listino', 'Netto', ''], …],
|
|
136
|
+
// hiddenRows: [], merges: ['A1:D1', 'A2:A3', 'B2:C2', 'D2:D3'] }],
|
|
137
|
+
// charts: [], notes: 'Prezzi IVA inclusa.\nValidi fino al 30 giugno.' },
|
|
138
|
+
// { number: 3, title: 'Vendite', …,
|
|
139
|
+
// charts: [{ type: 'bar', title: 'Immatricolazioni',
|
|
140
|
+
// series: [{ name: 'Pandina', categories: ['Aprile', 'Maggio', 'Giugno'], values: [1200, 1350.5, 1410] }, …] }] },
|
|
141
|
+
// …] }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`readPptx(source, options?)` takes the same sources as `readXlsx`. Its options:
|
|
145
|
+
|
|
146
|
+
| Option | Default | |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `slides` | all | Numbers from 1 (`[2, 5]`), a `RegExp` on titles, or `({ number, title, hidden }) => boolean`. |
|
|
149
|
+
| `values` | `'typed'` | Chart values: numbers (`null` where a point is missing), or `'text'`. |
|
|
150
|
+
| `notes`, `charts` | `true` | `false` skips reading them. |
|
|
151
|
+
| `limits` | as above | |
|
|
152
|
+
|
|
153
|
+
What each slide holds:
|
|
154
|
+
|
|
155
|
+
- **`shapes`:** its text boxes in reading order (top to bottom, left to right), in points from the top-left
|
|
156
|
+
corner.
|
|
157
|
+
- A title or body placeholder with no position of its own takes the one its layout gives it, else the one
|
|
158
|
+
its master gives it, the way PowerPoint draws it.
|
|
159
|
+
- Boxes inside a group are placed through the group's scaling.
|
|
160
|
+
- Slide numbers, dates and footers are left out.
|
|
161
|
+
- **`tables`:** its native tables as sheets, with their merged cells (`gridSpan`, `rowSpan`) as ranges. A
|
|
162
|
+
cell inside a merge is `''`.
|
|
163
|
+
- **`charts`:** the type, the title and each series' name, categories and values, from the data the chart
|
|
164
|
+
caches next to its formulas. The embedded workbook is not needed.
|
|
165
|
+
- **`notes`:** the speaker notes.
|
|
166
|
+
- **`hidden`:** hidden in a slideshow.
|
|
167
|
+
|
|
168
|
+
Slides come in presentation order, which is not always the order of the files inside the zip.
|
|
169
|
+
|
|
170
|
+
Tested against the 100 presentations of Apache POI's test corpus. It reads 90 of them (562 slides, 41 tables,
|
|
171
|
+
28 charts), and every placeholder gets a position. The other 10 are fuzzer cases and a truncated zip, all
|
|
172
|
+
refused with an `OfficeReadError`.
|
|
173
|
+
|
|
174
|
+
## How it compares
|
|
175
|
+
|
|
176
|
+
| | office-reader | SheetJS (`xlsx` on npm) | ExcelJS | read-excel-file |
|
|
177
|
+
|---|---|---|---|---|
|
|
178
|
+
| Merged ranges | yes | yes | yes | no |
|
|
179
|
+
| Hidden rows and sheets | yes | yes | yes | no |
|
|
180
|
+
| Error cells | `{ error }` | yes | yes | `null` |
|
|
181
|
+
| Dependencies | 2 | 7 | 9, including archiver and tmp | 4 |
|
|
182
|
+
| Browser, workers, edge | yes | yes | Node-first | yes |
|
|
183
|
+
| Writes files | no | yes | yes | no |
|
|
184
|
+
|
|
185
|
+
For presentations, no JavaScript library we found reads positions, tables and chart data. officeparser
|
|
186
|
+
returns flattened text, and the others are text-only or browser renderers.
|
|
187
|
+
|
|
188
|
+
SheetJS's npm copy (0.18.5) carries two high-severity advisories (prototype pollution, ReDoS); the fixed
|
|
189
|
+
versions are published on its own CDN only. Choose SheetJS or ExcelJS when you need to **write** workbooks,
|
|
190
|
+
evaluate formulas, or read `.xls` and `.ods`. This package only reads.
|
|
191
|
+
|
|
192
|
+
Tested against the 367 workbooks of Apache POI's test corpus, real files and fuzzer cases alike. It reads
|
|
193
|
+
350 of them and refuses the other 17 with an `OfficeReadError`: encrypted, truncated or corrupted files.
|
|
194
|
+
|
|
195
|
+
## Not supported
|
|
196
|
+
|
|
197
|
+
- **Writing files.**
|
|
198
|
+
- **Evaluating formulas, and applying display formats.** A percentage stays `0.125` and a price stays `15950`.
|
|
199
|
+
- **Legacy `.xls`, `.ppt`, `.xlsb` and OpenDocument `.ods` / `.odp`.** These are refused with a code.
|
|
200
|
+
- **In workbooks:** charts, pivot tables, images, comments and data validation.
|
|
201
|
+
- **In presentations:** SmartArt text, text inside images (no OCR), animations and themes.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { O as OfficeReadError } from './read-source.client.esm.js';
|
|
2
|
+
export { r as readPptx } from './read-pptx.use-case.esm.js';
|
|
3
|
+
export { r as readXlsx } from './read-xlsx.use-case.esm.js';
|
|
4
|
+
import 'fflate';
|
|
5
|
+
import 'htmlparser2';
|
|
6
|
+
//# sourceMappingURL=index.esm.js.map
|
package/dist/pptx.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/presentation/index.js";
|