Registry / RepositoryTypeScriptRustPython
Rollpie
ReadmeFiles
Versions
Info
Download
0.1.098.9 KB2026-09-14
Version
0.1.0
Copyright
Rollpie, Irohabook
Publisher
math
Published
2026-09-14
Size
98.9 KB
Downloads
0
Checksum
bdac84a1e4fe4dd3505b93c2781b1f15833b5189c2e4ee976225fa3c512885cc
Dependencies
None

README.md

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# folio

A Rust library that reads and writes docx, xlsx and pptx. It has no dependencies.

All three formats work.
They share one base of ZIP, XML and the package, and the xlsx, docx and pptx layers sit on top of it.

The older formats (.xls, .doc, .ppt) can be read too.
`Book::open`, `Document::open` and `Presentation::open` look at the first bytes of the file to tell the formats apart.
The caller does not need to care which format a file is in.

```
     1| A=Name B=Number C=Date
     2| A=first B=3.5 C=2026-03-31 00:00:00
     3| A=second B=-1 C=2026-09-08 13:45:30
```

The Japanese documentation is in [README.ja.md](./README.ja.md).

## Huge sheets

Both were measured on the same machine with an xlsx of 1 million rows by 5 columns.
Whether writing or reading, folio's memory peaks at 17MB.
It never holds the whole sheet in memory, and passes the rows through one at a time, in order.

```
             write       read        peak memory
folio        4.4 s       1.0 s       17MB
openpyxl     26.5 s      27.3 s      114MB   (read_only)
openpyxl                 32.7 s      1790MB  (normal load)
```

Reading is 27 times faster and writing 6 times faster.
A normal load needs 1.8GB, and folio does the same with 17MB.
The finished file was 24MB from folio and 26MB from openpyxl.

## Usage

To read, open a sheet and take its rows one by one, in order.

```
use folio::book::Book;
use folio::cell::Value;
use std::path::Path;

let mut book = Book::open(Path::new("data.xlsx"))?;

for one in book.sheet() {
	println!("{}", one.name);
}

let mut sheet = book.read("Sheet1")?;

while let Some(row) = sheet.read()? {
	for cell in &row.cell {
		println!("column {} {:?}", cell.column, cell.value);
	}
}
```

To create a workbook, pass all the sheet names first.
`[Content_Types].xml` goes at the start of the file, so the set of sheets has to be decided before writing begins.

```
use folio::book::Writer;

let mut writer = Writer::create(Path::new("out.xlsx"), &["Data"])?;
writer.start("Data")?;
writer.row(&["Name".into(), 3.5.into(), true.into()])?;
writer.finish()?;
```

To skip row numbers or columns, use `place`. Formats are given here as well.

```
use folio::cell::Cell;
use folio::style::Format;

writer.place(10, &[
	Cell::new(1, "sparse".into()),
	Cell::styled(3, serial.into(), Format::Date.index()),
])?;
```

## Editing

`Writer::edit` copies everything except the sheets being rewritten, still compressed.
Nothing is decompressed or compressed again, so it is fast, and parts folio does not know (charts, pivot tables, images, print settings) are kept as they are.

```
let source = Book::open(Path::new("data.xlsx"))?;
let mut writer = Writer::edit(source, Path::new("out.xlsx"), &["Summary"])?;
writer.start("Summary")?;
writer.row(&["rewritten".into(), 999.into()])?;
writer.finish()?;
```

A rewritten sheet also keeps everything outside `<sheetData>`.
Column widths, merged cells, conditional formatting and references to drawings are not lost.
To keep them, the original sheet is read through once, which takes time on a large sheet.

## What a cell holds

`Value` has five variants.

- `Empty` - nothing. A cell that has only a format is also `Empty`
- `Number(f64)` - a number. Dates are stored here too
- `Text(String)` - text
- `Boolean(bool)` - true or false
- `Error(String)` - an error such as `#N/A`

An xlsx cell has no date type.
A date is stored as a number, and it is read as a date when the cell's number format is a date format.

```
let style = sheet.style();
let epoch1904 = sheet.epoch1904();

while let Some(row) = sheet.read()? {
	for cell in &row.cell {
		if let Value::Number(number) = &cell.value {
			if style.is_date(cell.style) {
				let when = folio::date::moment(*number, epoch1904);
				println!("{}-{:02}-{:02}", when.year, when.month, when.day);
			}
		}
	}
}
```

## JSON output

For reading from other languages, `examples/json.rs` writes each row as one line of JSON (JSON Lines).
Given a sheet name, it writes that sheet's rows. Without one, it writes the list of sheets.

```
cargo run --release --example json -- data.xlsx
cargo run --release --example json -- data.xlsx Sheet1
```

Each line has this shape. Empty cells are left out.

```
{"number":2,"cell":[{"column":1,"letter":"A","value":"first"},{"column":2,"letter":"B","value":3.5}]}
```

Values go in `value` with their JSON types: text is a string, a number is a number, and true or false is a boolean.
A number whose format is a date stays a number in `value`, and `date` is added with the date and time in the form `2026-03-31T00:00:00`.
An error has `error` in place of `value`. A cell with a formula also gets `formula`.

From Rust, `row` and `sheet` in `folio::json` build strings of the same shape.

## docx

The body is split into paragraphs and tables, which come in order.
When only the text is needed, `text` returns all of it at once.

```
use folio::document::{Block, Document};

let mut document = Document::open(Path::new("file.docx"))?;
println!("{}", document.text()?);

let mut body = document.read()?;

while let Some(block) = body.read()? {
	match block {
		Block::Paragraph(one) => println!("{} {}", one.style, one.text()),
		Block::Table(one) => println!("table of {} rows", one.row.len()),
	}
}
```

A paragraph is a sequence of runs (`Run`), each a stretch of text with one format.
When only part of a sentence is bold, that part has a different format, so the sentence is split into several runs.
A `Run` holds bold, italic, underline, strikethrough, size, color and typeface.
A line break goes into the text as `\n`, and a tab as `\t`.

To create a document, write paragraphs and tables in order.
The paragraph styles Normal, Title and Heading1 to Heading3 are provided.

```
use folio::document::Writer;
use folio::paragraph::{Paragraph, Run};
use folio::table::{Row, Table};

let mut writer = Writer::create(Path::new("out.docx"))?;
writer.paragraph(&Paragraph::styled("Heading", "Heading1"))?;
writer.paragraph(&Paragraph::new("This is the body."))?;
writer.table(&Table::new(vec![Row::new(&["a", "b"])]))?;
writer.finish()?;
```

`Writer::edit` rewrites only the body and copies the other parts still compressed.
The page size, margins and references to headers and footers, which come after the body, are kept too.

```
let source = Document::open(Path::new("file.docx"))?;
let mut writer = Writer::edit(source, Path::new("out.docx"))?;
writer.paragraph(&Paragraph::new("Rewritten."))?;
writer.finish()?;
```

Measured with a docx of 20,000 paragraphs:

```
             write       read
folio        0.04 s      0.01 s
python-docx  2.5 s       0.39 s
```

## pptx

A slide is a sequence of shapes. Text sits inside shapes, and cannot be placed outside one.
Positions and sizes are in EMU, and `INCH` is one inch.

```
use folio::presentation::Presentation;

let mut file = Presentation::open(Path::new("deck.pptx"))?;

for index in 0..file.count() {
	let one = file.read(index)?;

	for shape in &one.shape {
		println!("{} {}", shape.name, shape.body());
	}
}
```

To create a presentation, pass the number of slides first.

```
use folio::presentation::Writer;
use folio::slide::{INCH, Shape, Slide};

let mut writer = Writer::create(Path::new("out.pptx"), 1)?;
let title = Shape::text("Title", INCH, INCH, INCH * 11, INCH, &["folio"]);
writer.slide(0, &Slide::new(vec![title]))?;
writer.finish()?;
```

`Writer::edit` replaces only the slides being rewritten.
What lies outside the shapes, such as the background, transitions and placeholder formats, is kept.
Slides that are not rewritten, and the other parts, are copied still compressed.

```
let source = Presentation::open(Path::new("deck.pptx"))?;
let mut writer = Writer::edit(source, Path::new("out.pptx"), &[1])?;
writer.slide(0, &Slide::new(vec![shape]))?;
writer.finish()?;
```

## Older formats

.xls, .doc and .ppt are not ZIP files.
Each keeps its own binary data in a different container called a compound file, and the data is not XML either.
These formats can only be read. To write, create the file again in the newer format.

```
use folio::book::Book;

// decided by the first bytes, not by the extension
let mut book = Book::open(Path::new("old.xls"))?;
let mut sheet = book.read("Sheet1")?;

while let Some(row) = sheet.read()? {
	// the same code as for xlsx
}
```

Excel 97 and later (BIFF8) and Word 97 and later can be read.
Earlier formats store text in a locale-specific encoding that cannot be converted back, so they are refused with a clear error.
Files protected with a password are refused as well.

Some things are not returned from the older formats.
From .xls, the text of formulas is not returned, although the results saved with the file are.
From .doc, character formatting such as bold is not returned, nor the grouping of table cells into rows and columns.
Each cell comes out as a separate paragraph.
From .ppt, the position and size of shapes are not returned.

## Internals

The base is shared by all three formats.
From the top, it is layered as package, XML, ZIP and compression.

- `book` - workbooks: open, create, edit
- `sheet` - reads a sheet row by row, and writes rows
- `cell`, `shared`, `style`, `date` - cell values, shared strings, number formats, date numbers
- `json` - writes rows and the sheet list as JSON
- `document` - documents: open, create, edit
- `paragraph`, `table` - paragraphs and runs, docx tables
- `presentation` - presentations: open, create, edit
- `slide` - one slide: its shapes and the paragraphs inside them
- `package` - OPC: parts, relationships, and carrying over the parts that are not touched
- `xml` - a reader that returns one event at a time, and escaping for writing
- `zip` - reading and writing ZIP, with ZIP64 and copying entries without decompressing them
- `inflate`, `deflate`, `crc` - deflate decompression and compression
- `cfb` - the container of the older formats (compound file)
- `xls`, `doc`, `ppt` - the contents of the older formats

Decompression runs on a separate thread.
It works at the same time as the reading side, so there is less waiting.
The compressed bytes are not read all at once either.
They are passed from the file a little at a time.

## What it does not do

- The older formats can be read but not written
- Files encrypted with a password are not read
- Formulas are kept as text and never calculated
- Fonts and borders are not read. When editing, parts that are not rewritten are copied whole, so they are not lost
- New drawings or merged cells cannot be added to a rewritten sheet. Those already there are kept
- docx images, comments, footnotes and tables of contents cannot be built. When editing, parts that are not rewritten are copied whole, so they are kept
- docx table borders can be read only when the table itself specifies them. Borders that come from a style do not appear
- pptx shapes are rectangles only. Pictures, tables and charts cannot be built. Slides that are not rewritten are copied whole, so they are kept
- The position and format a pptx shape inherits from its placeholder are not read. Only what the shape itself specifies is returned
- Error messages are in Japanese only. The examples also print their labels in Japanese

## Trying it

```
cargo test
cargo run --release --example show -- data.xlsx
cargo run --release --example show -- data.xlsx Sheet1
cargo run --release --example show -- file.docx
cargo run --release --example show -- deck.pptx
cargo run --release --example show -- old.xls Sheet1
cargo run --release --example json -- data.xlsx Sheet1
cargo run --release --example scale -- 1000000
```

## License

Dual licensed under MIT and Apache-2.0. Take whichever you prefer.

- [LICENSE-MIT](./LICENSE-MIT)
- [LICENSE-APACHE](./LICENSE-APACHE)