python-calamine-reducto 0.7.18__tar.gz → 0.7.20__tar.gz
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.
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/Cargo.lock +2 -2
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/Cargo.toml +1 -1
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/PKG-INFO +1 -1
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/README.md +2 -1
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/python/python_calamine/_python_calamine.pyi +40 -1
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/src/types/grid.rs +99 -23
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/src/types/workbook.rs +584 -79
- python_calamine_reducto-0.7.20/tests/create_hyperlink_fixture.py +30 -0
- python_calamine_reducto-0.7.20/tests/data/hyperlink_test.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/test_band.py +18 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/test_bounded_reads.py +13 -0
- python_calamine_reducto-0.7.20/tests/test_gap_compression.py +258 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/test_images.py +83 -2
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/.github/workflows/CI.yml +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/.github/workflows/release.yml +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/.gitignore +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/.pre-commit-config.yaml +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/.python-version +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/CLAUDE.md +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/LICENSE +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/build.rs +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/pyproject.toml +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/python/python_calamine/__init__.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/python/python_calamine/py.typed +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/renovate.json +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/src/lib.rs +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/src/types/cell.rs +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/src/types/errors.rs +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/src/types/mod.rs +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/src/types/sheet.rs +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/src/types/table.rs +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/__init__.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/conftest.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/create_empty_merge_fixture.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/create_hidden_col_range_fixture.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/create_richtext_fixture.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/create_test_fixture.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/any_sheets.ods +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/any_sheets.xls +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/any_sheets.xlsb +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/any_sheets.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/base.ods +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/base.xls +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/base.xlsb +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/base.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/dropdown_test.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/empty_file.ods +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/empty_file.xlsb +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/empty_file.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/empty_merge_test.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/formula_test.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/grid_test.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/hidden_col_range_test.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/image_test.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/issue139.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/multi_image_test.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/password.ods +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/password.xls +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/password.xlsb +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/password.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/richtext_test.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/table-multiple.xlsb +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/table-multiple.xlsx +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/data/temperature-table.xlsb +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/test_base.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/test_grid.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/test_pandas.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/test_sheet_type.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/test_tables.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/test_trim_header_only.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/test_uncached_formulas.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/tests/xlsx_builder.py +0 -0
- {python_calamine_reducto-0.7.18 → python_calamine_reducto-0.7.20}/uv.lock +0 -0
|
@@ -80,7 +80,7 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|
|
80
80
|
[[package]]
|
|
81
81
|
name = "calamine"
|
|
82
82
|
version = "0.32.0"
|
|
83
|
-
source = "git+https://github.com/reductoai/calamine.git?branch=main#
|
|
83
|
+
source = "git+https://github.com/reductoai/calamine.git?branch=main#5a6418cb25f78e47a841f51c42c85363ee2cd06b"
|
|
84
84
|
dependencies = [
|
|
85
85
|
"atoi_simd",
|
|
86
86
|
"byteorder",
|
|
@@ -612,7 +612,7 @@ dependencies = [
|
|
|
612
612
|
|
|
613
613
|
[[package]]
|
|
614
614
|
name = "python-calamine-reducto"
|
|
615
|
-
version = "0.7.
|
|
615
|
+
version = "0.7.20"
|
|
616
616
|
dependencies = [
|
|
617
617
|
"calamine",
|
|
618
618
|
"chrono",
|
|
@@ -25,7 +25,8 @@ Our additions live on `main`.
|
|
|
25
25
|
- **`get_sheet_grid(name, rows, cols, include_styles=True)`** → `CellGrid` from the dense range.
|
|
26
26
|
- **`get_sheet_layout(name)`** → dict with row heights, column widths, defaults
|
|
27
27
|
- **`get_sheet_formulas`**, **`get_sheet_merged_ranges`**, **`get_sheet_images`**,
|
|
28
|
-
**`get_sheet_data_validations`**, **`
|
|
28
|
+
**`get_sheet_data_validations`**, **`get_sheet_hyperlinks`**, **`get_sheet_hyperlink_formulas`**,
|
|
29
|
+
**`get_sheet_freeze_panes`**,
|
|
29
30
|
**`get_sheet_filled_count`**, **`get_sheet_uncached_formula_count`**,
|
|
30
31
|
**`get_all_tables_metadata`**
|
|
31
32
|
|
|
@@ -204,6 +204,21 @@ class DataValidation(typing.TypedDict):
|
|
|
204
204
|
formula2: str | None
|
|
205
205
|
allow_blank: bool
|
|
206
206
|
|
|
207
|
+
@typing.type_check_only
|
|
208
|
+
class Hyperlink(typing.TypedDict):
|
|
209
|
+
ref: str
|
|
210
|
+
"""Cell or range reference, e.g. `A1` or `A1:C3`."""
|
|
211
|
+
rel_id: str | None
|
|
212
|
+
"""The `r:id` of the link, if any."""
|
|
213
|
+
target: str | None
|
|
214
|
+
"""Relationship target, e.g. a URL. `None` for a link with only a `location`, and for an
|
|
215
|
+
`r:id` that has no hyperlink relationship."""
|
|
216
|
+
location: str | None
|
|
217
|
+
"""Place inside the target or the workbook, e.g. `Sheet2!A1` or a URL fragment."""
|
|
218
|
+
display: str | None
|
|
219
|
+
external: bool
|
|
220
|
+
"""`True` when the relationship has `TargetMode="External"`."""
|
|
221
|
+
|
|
207
222
|
@typing.final
|
|
208
223
|
class CellProxy:
|
|
209
224
|
"""One cell of a `CellGrid`. Read a field with `cell["value"]`."""
|
|
@@ -218,6 +233,14 @@ class CellGrid:
|
|
|
218
233
|
def shape(self) -> tuple[int, int]: ...
|
|
219
234
|
@property
|
|
220
235
|
def size(self) -> int: ...
|
|
236
|
+
@property
|
|
237
|
+
def row_coords(self) -> npt.NDArray[np.int32]:
|
|
238
|
+
"""Grid row -> 0-based sheet row: `arange` plus the band top, unless gap compression engaged."""
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def col_coords(self) -> npt.NDArray[np.int32]:
|
|
242
|
+
"""Grid column -> 0-based sheet column."""
|
|
243
|
+
|
|
221
244
|
@property
|
|
222
245
|
def uncached_formula_count(self) -> int | None:
|
|
223
246
|
"""Formula cells with no cached value (streaming xlsx grids), else `None`."""
|
|
@@ -253,7 +276,9 @@ class CellGrid:
|
|
|
253
276
|
def apply_formatting_rust(self) -> None:
|
|
254
277
|
"""Format each value with its number format (SSF, in Rust)."""
|
|
255
278
|
|
|
256
|
-
def apply_dateutil_reformat(
|
|
279
|
+
def apply_dateutil_reformat(
|
|
280
|
+
self, date_parse: Callable[[str], float | None]
|
|
281
|
+
) -> None:
|
|
257
282
|
"""Reformat text cells that have a date format. `date_parse` gives a date serial."""
|
|
258
283
|
|
|
259
284
|
def apply_raw_as_formatted(self) -> None: ...
|
|
@@ -385,10 +410,14 @@ class CalamineWorkbook:
|
|
|
385
410
|
trigger_density: float = 0.001,
|
|
386
411
|
coverage: float = 0.999,
|
|
387
412
|
trim_header_only: bool = False,
|
|
413
|
+
max_empty_run: int | None = None,
|
|
388
414
|
) -> tuple[CellGrid, int, int, int]:
|
|
389
415
|
"""Stream a grid. Returns `(grid, declared_rows, declared_cols, dropped_cells)`.
|
|
390
416
|
|
|
391
417
|
`trim_header_only` also drops the trailing columns whose only value is in the header row.
|
|
418
|
+
`max_empty_run` keeps at most that many rows (and columns) of each empty run inside the
|
|
419
|
+
trimmed bounds. It engages only above `trigger_cells`, and only when it halves the grid.
|
|
420
|
+
`coord` and `merge_parent` stay in sheet positions; `row_coords`/`col_coords` map them.
|
|
392
421
|
"""
|
|
393
422
|
|
|
394
423
|
def get_sheet_filled_count(self, name: str) -> tuple[int, int, int]:
|
|
@@ -416,6 +445,16 @@ class CalamineWorkbook:
|
|
|
416
445
|
"""`(data, extension, anchor_row, anchor_col)` for each image."""
|
|
417
446
|
|
|
418
447
|
def get_sheet_data_validations(self, name: str) -> list[DataValidation]: ...
|
|
448
|
+
def get_sheet_hyperlinks(
|
|
449
|
+
self, name: str, limit: int | None = None
|
|
450
|
+
) -> list[Hyperlink]:
|
|
451
|
+
"""The links of the sheet's `<hyperlinks>` element, at most `limit`. Empty for xlsb, xls, and ods."""
|
|
452
|
+
|
|
453
|
+
def get_sheet_hyperlink_formulas(
|
|
454
|
+
self, name: str, limit: int | None = None
|
|
455
|
+
) -> list[tuple[int, int, str]]:
|
|
456
|
+
"""`(row, col, formula)` for each formula that contains `HYPERLINK`, at most `limit`. Empty for xlsb, xls, and ods."""
|
|
457
|
+
|
|
419
458
|
def get_sheet_freeze_panes(self, name: str) -> tuple[int, int]:
|
|
420
459
|
"""`(frozen_rows, frozen_cols)`."""
|
|
421
460
|
|
|
@@ -185,7 +185,8 @@ pub struct CellGrid {
|
|
|
185
185
|
// Styles (None when styling skipped for large sheets)
|
|
186
186
|
pub(crate) styles: Option<CellStyles>,
|
|
187
187
|
|
|
188
|
-
// Merge map: (row, col) -> (parent_row, parent_col),
|
|
188
|
+
// Merge map: (row, col) -> (parent_row, parent_col), both grid indexes. An anchor
|
|
189
|
+
// above the grid (a band) is negative. `anchor_position` maps it for `merge_parent`.
|
|
189
190
|
pub(crate) merge_map: HashMap<(usize, usize), (i32, i32)>,
|
|
190
191
|
|
|
191
192
|
// Mutable boolean fields
|
|
@@ -211,6 +212,14 @@ pub struct CellGrid {
|
|
|
211
212
|
pub(crate) coord_row_offset: usize,
|
|
212
213
|
pub(crate) coord_col_offset: usize,
|
|
213
214
|
|
|
215
|
+
// Added to a merge anchor for `merge_parent` (the band top for `propagate_merges`).
|
|
216
|
+
pub(crate) anchor_offset: (i32, i32),
|
|
217
|
+
|
|
218
|
+
// Gap compression: grid row/col -> 0-based sheet row/col, ascending. When set,
|
|
219
|
+
// `coord` and `merge_parent` use them and the offsets are 0.
|
|
220
|
+
pub(crate) row_coords: Option<Vec<i32>>,
|
|
221
|
+
pub(crate) col_coords: Option<Vec<i32>>,
|
|
222
|
+
|
|
214
223
|
// Formula cells with no cached value in the source sheet. None when unknown
|
|
215
224
|
// (non-xlsx, dense path, or a derived grid).
|
|
216
225
|
pub(crate) uncached_formula_count: Option<u64>,
|
|
@@ -259,6 +268,9 @@ impl CellGrid {
|
|
|
259
268
|
merge_parent_default,
|
|
260
269
|
coord_row_offset: 0,
|
|
261
270
|
coord_col_offset: 0,
|
|
271
|
+
anchor_offset: (0, 0),
|
|
272
|
+
row_coords: None,
|
|
273
|
+
col_coords: None,
|
|
262
274
|
uncached_formula_count: None,
|
|
263
275
|
}
|
|
264
276
|
}
|
|
@@ -280,6 +292,38 @@ impl CellGrid {
|
|
|
280
292
|
row * self.cols + col
|
|
281
293
|
}
|
|
282
294
|
|
|
295
|
+
/// The 0-based sheet row of a grid row.
|
|
296
|
+
#[inline]
|
|
297
|
+
fn sheet_row(&self, row: usize) -> usize {
|
|
298
|
+
match &self.row_coords {
|
|
299
|
+
Some(coords) => coords[row] as usize,
|
|
300
|
+
None => row + self.coord_row_offset,
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/// The 0-based sheet column of a grid column.
|
|
305
|
+
#[inline]
|
|
306
|
+
fn sheet_col(&self, col: usize) -> usize {
|
|
307
|
+
match &self.col_coords {
|
|
308
|
+
Some(coords) => coords[col] as usize,
|
|
309
|
+
None => col + self.coord_col_offset,
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/// The flat index of a merge anchor, `None` when the anchor is outside the grid.
|
|
314
|
+
fn anchor_index(&self, pr: i32, pc: i32) -> Option<usize> {
|
|
315
|
+
let (row, col) = (usize::try_from(pr).ok()?, usize::try_from(pc).ok()?);
|
|
316
|
+
(row < self.rows && col < self.cols).then(|| self.idx(row, col))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/// The `merge_parent` of a merge anchor. A compressed grid keeps every anchor.
|
|
320
|
+
fn anchor_position(&self, pr: i32, pc: i32) -> (i32, i32) {
|
|
321
|
+
match (&self.row_coords, &self.col_coords) {
|
|
322
|
+
(Some(rows), Some(cols)) => (rows[pr as usize], cols[pc as usize]),
|
|
323
|
+
_ => (pr + self.anchor_offset.0, pc + self.anchor_offset.1),
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
283
327
|
/// `fill_numpy_array` empties the per-cell storage (`header` first). A later
|
|
284
328
|
/// cell access would index an empty `Vec`, so refuse it.
|
|
285
329
|
fn ensure_not_drained(&self) -> PyResult<()> {
|
|
@@ -324,7 +368,7 @@ impl CellGrid {
|
|
|
324
368
|
// Both parent and child cells — openpyxl uses value emptiness, not coordinates.
|
|
325
369
|
match self.merge_map.get(&(row, col)) {
|
|
326
370
|
Some(&(pr, pc)) if matches!(self.raw_values[self.idx(row, col)], RawValue::Empty) => {
|
|
327
|
-
(pr, pc)
|
|
371
|
+
self.anchor_position(pr, pc)
|
|
328
372
|
}
|
|
329
373
|
_ => self.merge_parent_default,
|
|
330
374
|
}
|
|
@@ -368,8 +412,8 @@ impl CellGrid {
|
|
|
368
412
|
}
|
|
369
413
|
"coord" => {
|
|
370
414
|
let arr = [
|
|
371
|
-
(
|
|
372
|
-
(
|
|
415
|
+
(self.sheet_row(row) + 1) as i32,
|
|
416
|
+
(self.sheet_col(col) + 1) as i32,
|
|
373
417
|
];
|
|
374
418
|
let numpy = py.import("numpy")?;
|
|
375
419
|
let result = numpy.call_method1("array", (arr.to_vec(), "int32"))?;
|
|
@@ -508,15 +552,8 @@ impl CellGrid {
|
|
|
508
552
|
// For merged cells, use parent's style
|
|
509
553
|
let (row, col) = (idx / self.cols, idx % self.cols);
|
|
510
554
|
let style_idx = match self.merge_map.get(&(row, col)) {
|
|
511
|
-
Some(&(pr, pc))
|
|
512
|
-
|
|
513
|
-
if pidx < total {
|
|
514
|
-
pidx
|
|
515
|
-
} else {
|
|
516
|
-
idx
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
_ => idx,
|
|
555
|
+
Some(&(pr, pc)) => self.anchor_index(pr, pc).unwrap_or(idx),
|
|
556
|
+
None => idx,
|
|
520
557
|
};
|
|
521
558
|
let id = styles.ids[style_idx];
|
|
522
559
|
let (font, fill) = match names.entry(id) {
|
|
@@ -562,26 +599,43 @@ impl CellGrid {
|
|
|
562
599
|
if r < self.rows && c < self.cols {
|
|
563
600
|
let idx = r * self.cols + c;
|
|
564
601
|
if self.raw_values[idx].is_empty() {
|
|
565
|
-
data[idx * 2] = pr;
|
|
566
|
-
data[idx * 2 + 1] = pc;
|
|
602
|
+
(data[idx * 2], data[idx * 2 + 1]) = self.anchor_position(pr, pc);
|
|
567
603
|
}
|
|
568
604
|
}
|
|
569
605
|
}
|
|
570
606
|
data
|
|
571
607
|
}
|
|
572
608
|
|
|
573
|
-
/// `coord` as flat 1-based `(row, col)`
|
|
609
|
+
/// `coord` as flat 1-based `(row, col)` sheet positions.
|
|
574
610
|
fn coord_pairs(&self) -> Vec<i32> {
|
|
611
|
+
let col_coords: Vec<i32> = (0..self.cols)
|
|
612
|
+
.map(|c| (self.sheet_col(c) + 1) as i32)
|
|
613
|
+
.collect();
|
|
575
614
|
let mut data = Vec::with_capacity(self.rows * self.cols * 2);
|
|
576
615
|
for r in 0..self.rows {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
data.push(
|
|
616
|
+
let row = (self.sheet_row(r) + 1) as i32;
|
|
617
|
+
for &col in &col_coords {
|
|
618
|
+
data.push(row);
|
|
619
|
+
data.push(col);
|
|
580
620
|
}
|
|
581
621
|
}
|
|
582
622
|
data
|
|
583
623
|
}
|
|
584
624
|
|
|
625
|
+
/// 0-based sheet indexes of the grid rows or columns, as a numpy `int32` array:
|
|
626
|
+
/// a copy of the compressed `coords`, else `offset..offset + n`.
|
|
627
|
+
fn sheet_coords<'py>(
|
|
628
|
+
py: Python<'py>,
|
|
629
|
+
coords: Option<&[i32]>,
|
|
630
|
+
n: usize,
|
|
631
|
+
offset: usize,
|
|
632
|
+
) -> Bound<'py, PyArray1<i32>> {
|
|
633
|
+
match coords {
|
|
634
|
+
Some(coords) => PyArray1::from_slice(py, coords),
|
|
635
|
+
None => PyArray1::from_iter(py, (offset..offset + n).map(|i| i as i32)),
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
585
639
|
fn font_bolds(&self) -> Vec<bool> {
|
|
586
640
|
match &self.styles {
|
|
587
641
|
Some(styles) => styles
|
|
@@ -752,6 +806,29 @@ impl CellGrid {
|
|
|
752
806
|
self.rows * self.cols
|
|
753
807
|
}
|
|
754
808
|
|
|
809
|
+
/// Grid row -> 0-based sheet row: `arange` plus the coord offset (the band top),
|
|
810
|
+
/// unless gap compression engaged.
|
|
811
|
+
#[getter]
|
|
812
|
+
fn row_coords<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1<i32>> {
|
|
813
|
+
Self::sheet_coords(
|
|
814
|
+
py,
|
|
815
|
+
self.row_coords.as_deref(),
|
|
816
|
+
self.rows,
|
|
817
|
+
self.coord_row_offset,
|
|
818
|
+
)
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/// Grid column -> 0-based sheet column, like `row_coords`.
|
|
822
|
+
#[getter]
|
|
823
|
+
fn col_coords<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1<i32>> {
|
|
824
|
+
Self::sheet_coords(
|
|
825
|
+
py,
|
|
826
|
+
self.col_coords.as_deref(),
|
|
827
|
+
self.cols,
|
|
828
|
+
self.coord_col_offset,
|
|
829
|
+
)
|
|
830
|
+
}
|
|
831
|
+
|
|
755
832
|
/// Formula cells with no cached value, counted by `get_sheet_grid_streaming`.
|
|
756
833
|
/// `None` when unknown (non-xlsx workbook or a grid from another builder).
|
|
757
834
|
#[getter]
|
|
@@ -1110,7 +1187,7 @@ impl CellGrid {
|
|
|
1110
1187
|
self.merge_map.iter().map(|(&k, &v)| (k, v)).collect();
|
|
1111
1188
|
for ((row, col), (pr, pc)) in entries {
|
|
1112
1189
|
// Skip if this IS the parent
|
|
1113
|
-
if pr == row as i32
|
|
1190
|
+
if (pr, pc) == (row as i32, col as i32) {
|
|
1114
1191
|
continue;
|
|
1115
1192
|
}
|
|
1116
1193
|
let idx = row * self.cols + col;
|
|
@@ -1119,9 +1196,8 @@ impl CellGrid {
|
|
|
1119
1196
|
.as_ref()
|
|
1120
1197
|
.map(|s| s.is_empty())
|
|
1121
1198
|
.unwrap_or(true);
|
|
1122
|
-
if child_empty
|
|
1123
|
-
let parent_idx =
|
|
1124
|
-
if parent_idx < self.formatted_values.len() {
|
|
1199
|
+
if child_empty {
|
|
1200
|
+
if let Some(parent_idx) = self.anchor_index(pr, pc) {
|
|
1125
1201
|
self.formatted_values[idx] = self.formatted_values[parent_idx].clone();
|
|
1126
1202
|
}
|
|
1127
1203
|
}
|