fast_xlsx 0.5.0 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +8 -1
- data/bench/Gemfile.lock +2 -2
- data/ext/fast_xlsx/src/lib.rs +238 -12
- data/lib/fast_xlsx/version.rb +1 -1
- data/lib/fast_xlsx.rb +73 -0
- data/sig/fast_xlsx.rbs +12 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c78ccbcfe82884370ef3e64e4613812a91c521f479495184bd99df1cb7f1d36c
|
|
4
|
+
data.tar.gz: 86702f59f3c33c490ef3879e0178a7bcc9650fdba612bd1cd17ba24662b0392d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 629352aa7bf14685eef2d940f78b23e142f66b946b6d8f2eb36db0bd004574aefb2e04fd29fe2fc42e9684ac5d125725cfea5384d7a439b8f5d361411f68149c
|
|
7
|
+
data.tar.gz: f8755a00230b81f405339f9d123344bf827c3010d80f159c0b5408e2fbcc984d3fd94758fd65e7fa5562d0e8b23825cb0d0b791bf97057db013259afd49c3c96
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.6.0] - 2026-10-02
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `Worksheet#selection`, `#top_left_cell` (what is selected and scrolled to when the file opens), `#ignore_error` (turns off Excel's green-triangle warnings in a range) and `#unprotect_range` (a range users can edit on a protected sheet).
|
|
8
|
+
- `Worksheet#hide_rows`, `#hide_columns`, `#row_format` (default format for a row's cells) and `#default_row_height` (call it before `row_format`, `hide_rows` and `group_rows`, which copy the default into their rows; it raises otherwise).
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- `nil` written with a format (e.g. a border, or a row format in `append`) writes a blank cell with that format; the format was dropped.
|
|
13
|
+
|
|
3
14
|
## [0.5.0] - 2026-10-01
|
|
4
15
|
|
|
5
16
|
### Added
|
data/README.md
CHANGED
|
@@ -101,6 +101,9 @@ ws.zoom(150) # 10..400 percent
|
|
|
101
101
|
ws.tab_color("#C00000")
|
|
102
102
|
ws.hide_gridlines
|
|
103
103
|
ws.activate # Excel opens on this sheet (un-hides it if hidden)
|
|
104
|
+
ws.selection("B2:C3") # the cells selected when it opens
|
|
105
|
+
ws.top_left_cell("A50") # and the cell scrolled to the top left
|
|
106
|
+
ws.ignore_error("A2:A1000", :number_stored_as_text) # no green triangles (one kind per range; don't overlap ranges)
|
|
104
107
|
other.hide # the sheet Excel opens on can't be hidden: activate another one first
|
|
105
108
|
|
|
106
109
|
ws.page_setup(landscape: true, paper: :a4, # or :letter, :legal, :tabloid, :a3, :a5, Excel's paper number, 0 = printer default
|
|
@@ -130,6 +133,9 @@ Methods that take one cell (`write`, `write_comment`, `insert_image`, `insert_ch
|
|
|
130
133
|
```ruby
|
|
131
134
|
ws.freeze_panes(1, 0) # keep the first row visible while scrolling
|
|
132
135
|
ws.row_height(0, 30) # row 1, height in points
|
|
136
|
+
ws.default_row_height(18) # every other row; call it before row_format, hide_rows, group_rows
|
|
137
|
+
ws.row_format(0, header) # default for cells in row 1 written without a format
|
|
138
|
+
ws.hide_rows(10..20) # also hide_columns(3), an index or a Range
|
|
133
139
|
ws.merge_range(0, 0, 0, 3, "Q3 report", title) # merge A1:D1; the value can be any cell type
|
|
134
140
|
ws.page_breaks([50, 100]) # print a new page before rows 51 and 101
|
|
135
141
|
ws.vertical_page_breaks([8]) # and before column I
|
|
@@ -164,6 +170,7 @@ input = FastXlsx::Format.new(locked: false)
|
|
|
164
170
|
ws.write(1, 1, 0, input) # B2 stays editable
|
|
165
171
|
ws.protect # lock everything else
|
|
166
172
|
ws.protect(password: "secret", allow: %i[sort use_autofilter]) # or with a password and allowed actions
|
|
173
|
+
ws.unprotect_range("B2:D10", name: "Inputs") # a range users can still edit (optional password:)
|
|
167
174
|
```
|
|
168
175
|
|
|
169
176
|
`allow:` takes `:format_cells`, `:format_columns`, `:format_rows`, `:insert_columns`, `:insert_rows`, `:insert_links`, `:delete_columns`, `:delete_rows`, `:sort`, `:use_autofilter`, `:use_pivot_tables`, `:edit_scenarios`, `:edit_objects`. The password only stops editing in Excel; it does not encrypt the file.
|
|
@@ -258,7 +265,7 @@ Values are mapped by type:
|
|
|
258
265
|
| `FastXlsx::URL.new("https://…")`, `URL.new(url, text: "Title")` | hyperlink (optionally showing other text) |
|
|
259
266
|
| `FastXlsx::RichString.new(["Total: ", bold], "1,234")` | text with a format per segment |
|
|
260
267
|
| `true` / `false` | boolean |
|
|
261
|
-
| `nil` | empty cell |
|
|
268
|
+
| `nil` | empty cell (a blank cell with the format, when one is given) |
|
|
262
269
|
| anything else | `to_s` as string |
|
|
263
270
|
|
|
264
271
|
`<<` and `concat` append after the last row written to that worksheet. In `:constant` / `:low` memory mode rows are written to disk as you go, so fill each worksheet top to bottom.
|
data/bench/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ..
|
|
3
3
|
specs:
|
|
4
|
-
fast_xlsx (0.
|
|
4
|
+
fast_xlsx (0.6.0)
|
|
5
5
|
rb_sys (~> 0.9.130)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
@@ -77,7 +77,7 @@ DEPENDENCIES
|
|
|
77
77
|
CHECKSUMS
|
|
78
78
|
caxlsx (4.5.0) sha256=e3d98d859f148df05d5462086b5079b523f29c1766b569535f1d68629ce743ff
|
|
79
79
|
fast_excel (0.5.0) sha256=59c418bdcf586a6030798d4e3a9f575742badaceaf7bb90f38eee5feb5bdc478
|
|
80
|
-
fast_xlsx (0.
|
|
80
|
+
fast_xlsx (0.6.0)
|
|
81
81
|
ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df
|
|
82
82
|
ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39
|
|
83
83
|
ffi (1.17.4-arm-linux-gnu) sha256=d6dbddf7cb77bf955411af5f187a65b8cd378cb003c15c05697f5feee1cb1564
|
data/ext/fast_xlsx/src/lib.rs
CHANGED
|
@@ -13,7 +13,7 @@ use rust_xlsxwriter::{
|
|
|
13
13
|
ConditionalFormatCell, ConditionalFormatCellRule, ConditionalFormatDataBar,
|
|
14
14
|
ConditionalFormatFormula, ConditionalFormatText, ConditionalFormatTextRule,
|
|
15
15
|
ConditionalFormatValue, DataValidation, DataValidationRule, FormatAlign, FormatBorder,
|
|
16
|
-
FormatScript, FormatUnderline, IntoDataValidationValue, IntoExcelData, XlsxError,
|
|
16
|
+
FormatScript, FormatUnderline, IgnoreError, IntoDataValidationValue, IntoExcelData, XlsxError,
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
use rust_xlsxwriter::{
|
|
@@ -63,6 +63,15 @@ fn check_positive(ruby: &Ruby, what: &str, size: f64) -> Result<f64, Error> {
|
|
|
63
63
|
))
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// A range of rows is checked before any row changes, so it is all or nothing.
|
|
67
|
+
fn check_last_row(last: u32) -> Result<(), Error> {
|
|
68
|
+
if last < MAX_ROWS {
|
|
69
|
+
Ok(())
|
|
70
|
+
} else {
|
|
71
|
+
Err(xerr(XlsxError::RowColumnLimitError))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
66
75
|
fn check_size(ruby: &Ruby, what: &str, size: f64, max: f64) -> Result<(), Error> {
|
|
67
76
|
if (0.0..=max).contains(&size) {
|
|
68
77
|
return Ok(());
|
|
@@ -149,6 +158,12 @@ struct Worksheet {
|
|
|
149
158
|
merges: RefCell<Vec<(u32, u16, u32, u16)>>,
|
|
150
159
|
// Formats given with column_format: (first, last, style).
|
|
151
160
|
formatted_columns: RefCell<Vec<(u16, u16, Arc<Style>)>>,
|
|
161
|
+
// Formats given with row_format: (first, last, style).
|
|
162
|
+
formatted_rows: RefCell<Vec<(u32, u32, Arc<Style>)>>,
|
|
163
|
+
// Whether a row got options (format, hidden, outline level):
|
|
164
|
+
// rust_xlsxwriter copies the default row height into a row then, so
|
|
165
|
+
// default_row_height must come first.
|
|
166
|
+
row_options_set: Cell<bool>,
|
|
152
167
|
}
|
|
153
168
|
|
|
154
169
|
struct TableColumnFormat {
|
|
@@ -217,6 +232,8 @@ impl Workbook {
|
|
|
217
232
|
table_formats: RefCell::new(Vec::new()),
|
|
218
233
|
merges: RefCell::new(Vec::new()),
|
|
219
234
|
formatted_columns: RefCell::new(Vec::new()),
|
|
235
|
+
formatted_rows: RefCell::new(Vec::new()),
|
|
236
|
+
row_options_set: Cell::new(false),
|
|
220
237
|
})
|
|
221
238
|
}
|
|
222
239
|
|
|
@@ -418,7 +435,11 @@ impl CellValue {
|
|
|
418
435
|
format: Option<&rust_xlsxwriter::Format>,
|
|
419
436
|
) -> Result<(), Error> {
|
|
420
437
|
match self {
|
|
421
|
-
|
|
438
|
+
// nil writes nothing, unless it has a format (e.g. a border).
|
|
439
|
+
CellValue::Empty => match format {
|
|
440
|
+
Some(f) => ws.write_blank(row, col, f).map(|_| ()).map_err(xerr),
|
|
441
|
+
None => Ok(()),
|
|
442
|
+
},
|
|
422
443
|
CellValue::Text(s) => emit(ws, row, col, s.as_str(), format),
|
|
423
444
|
CellValue::Number(n) | CellValue::Date(n, _) => emit(ws, row, col, *n, format),
|
|
424
445
|
CellValue::Bool(b) => emit(ws, row, col, *b, format),
|
|
@@ -786,6 +807,29 @@ const PROTECTION_ALLOW: &[(&str, ProtectionFlag)] = &[
|
|
|
786
807
|
("edit_objects", |o| &mut o.edit_objects),
|
|
787
808
|
];
|
|
788
809
|
|
|
810
|
+
// Not IgnoreError::TwoDigitTextYear: rust_xlsxwriter 0.99 writes it as
|
|
811
|
+
// "TwoDigitTextYear", not the schema's "twoDigitTextYear", which Excel would
|
|
812
|
+
// flag as damaged content. Add it once that is fixed upstream.
|
|
813
|
+
const IGNORE_ERRORS: &[(&str, IgnoreError)] = &[
|
|
814
|
+
("number_stored_as_text", IgnoreError::NumberStoredAsText),
|
|
815
|
+
("formula_error", IgnoreError::FormulaError),
|
|
816
|
+
("formula_differs", IgnoreError::FormulaDiffers),
|
|
817
|
+
(
|
|
818
|
+
"formula_refers_to_empty_cells",
|
|
819
|
+
IgnoreError::FormulaRefersToEmptyCells,
|
|
820
|
+
),
|
|
821
|
+
("formula_omits_cells", IgnoreError::FormulaOmitsCells),
|
|
822
|
+
("data_validation_error", IgnoreError::DataValidationError),
|
|
823
|
+
(
|
|
824
|
+
"unlocked_cells_with_formula",
|
|
825
|
+
IgnoreError::UnlockedCellsWithFormula,
|
|
826
|
+
),
|
|
827
|
+
(
|
|
828
|
+
"inconsistent_column_formula",
|
|
829
|
+
IgnoreError::InconsistentColumnFormula,
|
|
830
|
+
),
|
|
831
|
+
];
|
|
832
|
+
|
|
789
833
|
// Excel's paper size codes; page_setup also takes the number itself.
|
|
790
834
|
const PAPER_SIZES: &[(&str, u8)] = &[
|
|
791
835
|
("letter", 1),
|
|
@@ -1030,11 +1074,13 @@ impl Worksheet {
|
|
|
1030
1074
|
|
|
1031
1075
|
// The format a value is written with: its own, else its table column's.
|
|
1032
1076
|
// A date cell takes that format's date variant (when it has no
|
|
1033
|
-
// num_format); with neither, its
|
|
1034
|
-
//
|
|
1035
|
-
// one get None, so rust_xlsxwriter applies any
|
|
1077
|
+
// num_format); with neither, its row_format's, else its column_format's
|
|
1078
|
+
// date variant (rust_xlsxwriter's order), else the default date format.
|
|
1079
|
+
// Other cells without one get None, so rust_xlsxwriter applies any row or
|
|
1080
|
+
// column format itself.
|
|
1036
1081
|
fn format_for<'a>(
|
|
1037
1082
|
tables: &'a [TableColumnFormat],
|
|
1083
|
+
rows: &'a [(u32, u32, Arc<Style>)],
|
|
1038
1084
|
columns: &'a [(u16, u16, Arc<Style>)],
|
|
1039
1085
|
row: u32,
|
|
1040
1086
|
col: u16,
|
|
@@ -1055,13 +1101,21 @@ impl Worksheet {
|
|
|
1055
1101
|
return Some(style.pick(date));
|
|
1056
1102
|
}
|
|
1057
1103
|
let time = date?;
|
|
1058
|
-
|
|
1104
|
+
let row_style = rows
|
|
1059
1105
|
.iter()
|
|
1060
1106
|
.rev()
|
|
1061
|
-
.find(|(first, last, _)| (*first..=*last).contains(&
|
|
1062
|
-
|
|
1107
|
+
.find(|(first, last, _)| (*first..=*last).contains(&row))
|
|
1108
|
+
.map(|(_, _, style)| style);
|
|
1109
|
+
let column_style = || {
|
|
1110
|
+
columns
|
|
1111
|
+
.iter()
|
|
1112
|
+
.rev()
|
|
1113
|
+
.find(|(first, last, _)| (*first..=*last).contains(&col))
|
|
1114
|
+
.map(|(_, _, style)| style)
|
|
1115
|
+
};
|
|
1116
|
+
match row_style.or_else(column_style) {
|
|
1063
1117
|
// None when it has a num_format: rust_xlsxwriter applies it.
|
|
1064
|
-
Some(
|
|
1118
|
+
Some(style) => style.for_date(time),
|
|
1065
1119
|
None => DEFAULTS.for_date(time),
|
|
1066
1120
|
}
|
|
1067
1121
|
}
|
|
@@ -1088,13 +1142,21 @@ impl Worksheet {
|
|
|
1088
1142
|
Ok(())
|
|
1089
1143
|
})?;
|
|
1090
1144
|
let tables = self.table_formats.borrow();
|
|
1145
|
+
let rows = self.formatted_rows.borrow();
|
|
1091
1146
|
let columns = self.formatted_columns.borrow();
|
|
1092
1147
|
self.with_ws(|ws| {
|
|
1093
1148
|
// Values were checked when converted, so only a bad row number
|
|
1094
1149
|
// fails here, and it fails on the first cell.
|
|
1095
1150
|
for (col, value, format) in &values {
|
|
1096
|
-
let format =
|
|
1097
|
-
|
|
1151
|
+
let format = Self::format_for(
|
|
1152
|
+
&tables,
|
|
1153
|
+
&rows,
|
|
1154
|
+
&columns,
|
|
1155
|
+
row,
|
|
1156
|
+
*col,
|
|
1157
|
+
format.as_deref(),
|
|
1158
|
+
value,
|
|
1159
|
+
);
|
|
1098
1160
|
value.write(ws, row, *col, format)?;
|
|
1099
1161
|
}
|
|
1100
1162
|
Ok(())
|
|
@@ -1138,13 +1200,22 @@ impl Worksheet {
|
|
|
1138
1200
|
rb_self.check_not_flushed(ruby, row)?;
|
|
1139
1201
|
let value = CellValue::from_ruby(ruby, v)?;
|
|
1140
1202
|
let tables = rb_self.table_formats.borrow();
|
|
1203
|
+
let rows = rb_self.formatted_rows.borrow();
|
|
1141
1204
|
let columns = rb_self.formatted_columns.borrow();
|
|
1142
1205
|
rb_self.with_ws(|ws| {
|
|
1143
1206
|
value.write(
|
|
1144
1207
|
ws,
|
|
1145
1208
|
row,
|
|
1146
1209
|
col,
|
|
1147
|
-
Self::format_for(
|
|
1210
|
+
Self::format_for(
|
|
1211
|
+
&tables,
|
|
1212
|
+
&rows,
|
|
1213
|
+
&columns,
|
|
1214
|
+
row,
|
|
1215
|
+
col,
|
|
1216
|
+
format.map(|f| &*f.0),
|
|
1217
|
+
&value,
|
|
1218
|
+
),
|
|
1148
1219
|
)
|
|
1149
1220
|
})?;
|
|
1150
1221
|
rb_self.advance(row);
|
|
@@ -1191,6 +1262,143 @@ impl Worksheet {
|
|
|
1191
1262
|
})
|
|
1192
1263
|
}
|
|
1193
1264
|
|
|
1265
|
+
// Row options are written with the row, so rows on disk ignore them.
|
|
1266
|
+
fn hide_rows(
|
|
1267
|
+
ruby: &Ruby,
|
|
1268
|
+
rb_self: Obj<Self>,
|
|
1269
|
+
first: u32,
|
|
1270
|
+
last: u32,
|
|
1271
|
+
) -> Result<Obj<Self>, Error> {
|
|
1272
|
+
rb_self.check_not_flushed(ruby, first)?;
|
|
1273
|
+
check_last_row(last)?;
|
|
1274
|
+
rb_self.with_ws(|ws| {
|
|
1275
|
+
for row in first..=last {
|
|
1276
|
+
ws.set_row_hidden(row).map_err(xerr)?;
|
|
1277
|
+
}
|
|
1278
|
+
Ok(())
|
|
1279
|
+
})?;
|
|
1280
|
+
rb_self.row_options_set.set(true);
|
|
1281
|
+
Ok(rb_self)
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
fn selection(
|
|
1285
|
+
rb_self: Obj<Self>,
|
|
1286
|
+
fr: u32,
|
|
1287
|
+
fc: u16,
|
|
1288
|
+
lr: u32,
|
|
1289
|
+
lc: u16,
|
|
1290
|
+
) -> Result<Obj<Self>, Error> {
|
|
1291
|
+
rb_self.with_ws(|ws| ws.set_selection(fr, fc, lr, lc).map(|_| ()).map_err(xerr))?;
|
|
1292
|
+
Ok(rb_self)
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
fn top_left_cell(rb_self: Obj<Self>, row: u32, col: u16) -> Result<Obj<Self>, Error> {
|
|
1296
|
+
rb_self.with_ws(|ws| ws.set_top_left_cell(row, col).map(|_| ()).map_err(xerr))?;
|
|
1297
|
+
Ok(rb_self)
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
#[allow(clippy::too_many_arguments)]
|
|
1301
|
+
fn ignore_error(
|
|
1302
|
+
ruby: &Ruby,
|
|
1303
|
+
rb_self: Obj<Self>,
|
|
1304
|
+
fr: u32,
|
|
1305
|
+
fc: u16,
|
|
1306
|
+
lr: u32,
|
|
1307
|
+
lc: u16,
|
|
1308
|
+
kind: Value,
|
|
1309
|
+
) -> Result<Obj<Self>, Error> {
|
|
1310
|
+
let error = choice(ruby, "error to ignore", kind, IGNORE_ERRORS)?;
|
|
1311
|
+
rb_self.with_ws(|ws| {
|
|
1312
|
+
ws.ignore_error_range(fr, fc, lr, lc, error)
|
|
1313
|
+
.map(|_| ())
|
|
1314
|
+
.map_err(xerr)
|
|
1315
|
+
})?;
|
|
1316
|
+
Ok(rb_self)
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
#[allow(clippy::too_many_arguments)]
|
|
1320
|
+
fn unprotect_range(
|
|
1321
|
+
rb_self: Obj<Self>,
|
|
1322
|
+
fr: u32,
|
|
1323
|
+
fc: u16,
|
|
1324
|
+
lr: u32,
|
|
1325
|
+
lc: u16,
|
|
1326
|
+
name: Option<String>,
|
|
1327
|
+
password: Option<String>,
|
|
1328
|
+
) -> Result<Obj<Self>, Error> {
|
|
1329
|
+
rb_self.with_ws(|ws| {
|
|
1330
|
+
ws.unprotect_range_with_options(
|
|
1331
|
+
fr,
|
|
1332
|
+
fc,
|
|
1333
|
+
lr,
|
|
1334
|
+
lc,
|
|
1335
|
+
name.as_deref().unwrap_or(""),
|
|
1336
|
+
password.as_deref().unwrap_or(""),
|
|
1337
|
+
)
|
|
1338
|
+
.map(|_| ())
|
|
1339
|
+
.map_err(xerr)
|
|
1340
|
+
})?;
|
|
1341
|
+
Ok(rb_self)
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
fn hide_columns(rb_self: Obj<Self>, first: u16, last: u16) -> Result<Obj<Self>, Error> {
|
|
1345
|
+
rb_self.with_ws(|ws| {
|
|
1346
|
+
ws.set_column_range_hidden(first, last)
|
|
1347
|
+
.map(|_| ())
|
|
1348
|
+
.map_err(xerr)
|
|
1349
|
+
})?;
|
|
1350
|
+
Ok(rb_self)
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
fn set_row_format(
|
|
1354
|
+
ruby: &Ruby,
|
|
1355
|
+
rb_self: &Self,
|
|
1356
|
+
first: u32,
|
|
1357
|
+
last: u32,
|
|
1358
|
+
format: &Format,
|
|
1359
|
+
) -> Result<(), Error> {
|
|
1360
|
+
rb_self.check_not_flushed(ruby, first)?;
|
|
1361
|
+
check_last_row(last)?;
|
|
1362
|
+
rb_self.with_ws(|ws| {
|
|
1363
|
+
for row in first..=last {
|
|
1364
|
+
ws.set_row_format(row, &format.0.format).map_err(xerr)?;
|
|
1365
|
+
}
|
|
1366
|
+
Ok(())
|
|
1367
|
+
})?;
|
|
1368
|
+
rb_self.row_options_set.set(true);
|
|
1369
|
+
rb_self
|
|
1370
|
+
.formatted_rows
|
|
1371
|
+
.borrow_mut()
|
|
1372
|
+
.push((first, last, format.0.clone()));
|
|
1373
|
+
Ok(())
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
fn default_row_height(
|
|
1377
|
+
ruby: &Ruby,
|
|
1378
|
+
rb_self: Obj<Self>,
|
|
1379
|
+
height: f64,
|
|
1380
|
+
) -> Result<Obj<Self>, Error> {
|
|
1381
|
+
// rust_xlsxwriter ignores 0.
|
|
1382
|
+
if height == 0.0 {
|
|
1383
|
+
return Err(Error::new(
|
|
1384
|
+
ruby.exception_arg_error(),
|
|
1385
|
+
"invalid default row height 0: use a number above 0",
|
|
1386
|
+
));
|
|
1387
|
+
}
|
|
1388
|
+
check_size(ruby, "row height", height, 409.0)?;
|
|
1389
|
+
if rb_self.row_options_set.get() {
|
|
1390
|
+
return Err(Error::new(
|
|
1391
|
+
ruby.get_inner(&ERROR),
|
|
1392
|
+
"call default_row_height before row_format, hide_rows, group_rows or row_height(row, 0): rows given those keep the earlier default",
|
|
1393
|
+
));
|
|
1394
|
+
}
|
|
1395
|
+
rb_self.with_ws(|ws| {
|
|
1396
|
+
ws.set_default_row_height(height);
|
|
1397
|
+
Ok(())
|
|
1398
|
+
})?;
|
|
1399
|
+
Ok(rb_self)
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1194
1402
|
fn set_column_format(&self, first: u16, last: u16, format: &Format) -> Result<(), Error> {
|
|
1195
1403
|
self.with_ws(|ws| {
|
|
1196
1404
|
ws.set_column_range_format(first, last, &format.0.format)
|
|
@@ -1239,6 +1447,10 @@ impl Worksheet {
|
|
|
1239
1447
|
) -> Result<Obj<Self>, Error> {
|
|
1240
1448
|
check_size(ruby, "row height", height, 409.0)?;
|
|
1241
1449
|
rb_self.with_ws(|ws| ws.set_row_height(row, height).map(|_| ()).map_err(xerr))?;
|
|
1450
|
+
// A height is kept as given; only 0 (hiding the row) copies the default.
|
|
1451
|
+
if height == 0.0 {
|
|
1452
|
+
rb_self.row_options_set.set(true);
|
|
1453
|
+
}
|
|
1242
1454
|
Ok(rb_self)
|
|
1243
1455
|
}
|
|
1244
1456
|
|
|
@@ -1344,9 +1556,11 @@ impl Worksheet {
|
|
|
1344
1556
|
}
|
|
1345
1557
|
let value = CellValue::from_ruby(ruby, v)?;
|
|
1346
1558
|
let tables = rb_self.table_formats.borrow();
|
|
1559
|
+
let rows = rb_self.formatted_rows.borrow();
|
|
1347
1560
|
let columns = rb_self.formatted_columns.borrow();
|
|
1348
1561
|
let format = Self::format_for(
|
|
1349
1562
|
&tables,
|
|
1563
|
+
&rows,
|
|
1350
1564
|
&columns,
|
|
1351
1565
|
first_row,
|
|
1352
1566
|
first_col,
|
|
@@ -1922,6 +2136,7 @@ impl Worksheet {
|
|
|
1922
2136
|
.map(|_| ())
|
|
1923
2137
|
.map_err(xerr)
|
|
1924
2138
|
})?;
|
|
2139
|
+
rb_self.row_options_set.set(true);
|
|
1925
2140
|
Ok(rb_self)
|
|
1926
2141
|
}
|
|
1927
2142
|
|
|
@@ -2012,6 +2227,17 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
2012
2227
|
"vertical_page_breaks",
|
|
2013
2228
|
method!(Worksheet::set_vertical_page_breaks, 1),
|
|
2014
2229
|
)?;
|
|
2230
|
+
ws.define_method("_hide_rows", method!(Worksheet::hide_rows, 2))?;
|
|
2231
|
+
ws.define_method("_hide_columns", method!(Worksheet::hide_columns, 2))?;
|
|
2232
|
+
ws.define_method("_row_format", method!(Worksheet::set_row_format, 3))?;
|
|
2233
|
+
ws.define_method(
|
|
2234
|
+
"default_row_height",
|
|
2235
|
+
method!(Worksheet::default_row_height, 1),
|
|
2236
|
+
)?;
|
|
2237
|
+
ws.define_method("_selection", method!(Worksheet::selection, 4))?;
|
|
2238
|
+
ws.define_method("_top_left_cell", method!(Worksheet::top_left_cell, 2))?;
|
|
2239
|
+
ws.define_method("_ignore_error", method!(Worksheet::ignore_error, 5))?;
|
|
2240
|
+
ws.define_method("_unprotect_range", method!(Worksheet::unprotect_range, 6))?;
|
|
2015
2241
|
ws.define_method("activate", method!(Worksheet::activate, 0))?;
|
|
2016
2242
|
ws.define_method("hide", method!(Worksheet::hide, 0))?;
|
|
2017
2243
|
ws.define_method("zoom", method!(Worksheet::zoom, 1))?;
|
data/lib/fast_xlsx/version.rb
CHANGED
data/lib/fast_xlsx.rb
CHANGED
|
@@ -226,6 +226,14 @@ module FastXlsx
|
|
|
226
226
|
# @param color [String, Integer] "#RRGGBB" or 0xRRGGBB
|
|
227
227
|
# @return [self]
|
|
228
228
|
#
|
|
229
|
+
# @!method default_row_height(height)
|
|
230
|
+
# Height of rows not given one with {#row_height}. Call it before
|
|
231
|
+
# {#row_format}, {#hide_rows} and {#group_rows}: rows given those keep
|
|
232
|
+
# the earlier default.
|
|
233
|
+
# @param height [Numeric] points, above 0 and up to 409
|
|
234
|
+
# @return [self]
|
|
235
|
+
# @raise [FastXlsx::Error] when called after those
|
|
236
|
+
#
|
|
229
237
|
# @!method hide_gridlines
|
|
230
238
|
# Hides the gridlines on screen (see {#page_setup} for printing).
|
|
231
239
|
# @return [self]
|
|
@@ -441,6 +449,71 @@ module FastXlsx
|
|
|
441
449
|
_protect(password, Array(allow))
|
|
442
450
|
end
|
|
443
451
|
|
|
452
|
+
# A range users can still edit on a protected sheet ({#protect}),
|
|
453
|
+
# optionally with its own password. Give each range its own name.
|
|
454
|
+
# @overload unprotect_range(*range, name: nil, password: nil)
|
|
455
|
+
# @param name [String, nil] shown in Excel's "Allow Edit Ranges"
|
|
456
|
+
# @return [self]
|
|
457
|
+
def unprotect_range(*range, name: nil, password: nil)
|
|
458
|
+
_unprotect_range(*CellRange.split(range).first, name, password)
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# The cells selected when the file opens.
|
|
462
|
+
# @param range a cell range (see {Worksheet})
|
|
463
|
+
# @return [self]
|
|
464
|
+
def selection(*range)
|
|
465
|
+
_selection(*CellRange.split(range).first)
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# The cell scrolled to the top left when the file opens.
|
|
469
|
+
# @param cell (row, col) or "B2"
|
|
470
|
+
# @return [self]
|
|
471
|
+
def top_left_cell(*cell)
|
|
472
|
+
_top_left_cell(*CellRange.cell(cell).first(2))
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
# Turns off one of Excel's warnings (green triangles) in the range, e.g.
|
|
476
|
+
# for codes stored as text. One kind per range (a rust_xlsxwriter limit);
|
|
477
|
+
# overlapping ranges are accepted, but Excel then uses only one of them.
|
|
478
|
+
# @overload ignore_error(*range, error)
|
|
479
|
+
# @param error [Symbol] :number_stored_as_text, :formula_error,
|
|
480
|
+
# :formula_differs, :formula_refers_to_empty_cells,
|
|
481
|
+
# :formula_omits_cells, :data_validation_error,
|
|
482
|
+
# :unlocked_cells_with_formula, :inconsistent_column_formula
|
|
483
|
+
# @return [self]
|
|
484
|
+
# @raise [FastXlsx::Error] when the range already has one
|
|
485
|
+
def ignore_error(*args)
|
|
486
|
+
range, (error, *) = CellRange.split(args, 1..1)
|
|
487
|
+
_ignore_error(*range, error)
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
# Each row in the range is kept (about 1 KB) until saving.
|
|
491
|
+
# @param rows [Integer, Range<Integer>]
|
|
492
|
+
# @return [self]
|
|
493
|
+
# @raise [FastXlsx::Error] in :constant / :low mode, for rows already on disk
|
|
494
|
+
def hide_rows(rows)
|
|
495
|
+
_hide_rows(*CellRange.bounds(rows))
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# @param columns [Integer, Range<Integer>]
|
|
499
|
+
# @return [self]
|
|
500
|
+
def hide_columns(columns)
|
|
501
|
+
_hide_columns(*CellRange.bounds(columns))
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# Default format for cells in these rows that are written without one.
|
|
505
|
+
# Use it or {#column_format} for a cell, not both: where both apply, Excel
|
|
506
|
+
# may combine them or show the row's. Each row is kept (about 1 KB) until
|
|
507
|
+
# saving, so style a whole sheet with {#column_format}.
|
|
508
|
+
# @param rows [Integer, Range<Integer>]
|
|
509
|
+
# @param format [Format]
|
|
510
|
+
# @return [self]
|
|
511
|
+
# @raise [FastXlsx::Error] in :constant / :low mode, for rows already on disk
|
|
512
|
+
def row_format(rows, format)
|
|
513
|
+
_row_format(*CellRange.bounds(rows), format)
|
|
514
|
+
self
|
|
515
|
+
end
|
|
516
|
+
|
|
444
517
|
# Default format for cells in these columns that are written without one.
|
|
445
518
|
# @param columns [Integer, Range<Integer>]
|
|
446
519
|
# @param format [Format]
|
data/sig/fast_xlsx.rbs
CHANGED
|
@@ -62,6 +62,18 @@ module FastXlsx
|
|
|
62
62
|
def <<: (Array[cell] row) -> self
|
|
63
63
|
def concat: (Array[Array[cell]] rows) -> self
|
|
64
64
|
def column_width: (Integer | Range[Integer] columns, Numeric width) -> self
|
|
65
|
+
def hide_rows: (Integer | Range[Integer] rows) -> self
|
|
66
|
+
def hide_columns: (Integer | Range[Integer] columns) -> self
|
|
67
|
+
def row_format: (Integer | Range[Integer] rows, Format format) -> self
|
|
68
|
+
def default_row_height: (Numeric height) -> self
|
|
69
|
+
def selection: (*cell_range range) -> self
|
|
70
|
+
def top_left_cell: (Integer row, Integer col) -> self
|
|
71
|
+
| (String cell) -> self
|
|
72
|
+
type ignorable_error = :number_stored_as_text | :formula_error | :formula_differs | :formula_refers_to_empty_cells
|
|
73
|
+
| :formula_omits_cells | :data_validation_error | :unlocked_cells_with_formula
|
|
74
|
+
| :inconsistent_column_formula
|
|
75
|
+
def ignore_error: (*cell_range | ignorable_error args) -> self
|
|
76
|
+
def unprotect_range: (*cell_range range, ?name: String?, ?password: String?) -> self
|
|
65
77
|
def column_format: (Integer | Range[Integer] columns, Format format) -> self
|
|
66
78
|
def autofit: () -> self
|
|
67
79
|
# A cell range: four 0-based numbers, an Excel reference ("A1:D10"), or
|