Skip to content

Commit e1cd826

Browse files
committed
add skipEmptyValues/skipBlankValues to iterateRows across all bindings
1 parent 73cd6d6 commit e1cd826

File tree

11 files changed

+355
-54
lines changed

11 files changed

+355
-54
lines changed

bindings/nodejs/__test__/readonly.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,3 +769,63 @@ describe("iterateRows metadata flags", () => {
769769
expect(rows[0].cells[0].formula).toBeUndefined();
770770
});
771771
});
772+
773+
describe("iterateRows skip flags", () => {
774+
it("skipEmptyValues filters empty cells but keeps empty strings", () => {
775+
const wb = new Workbook();
776+
const sheet = wb.getSheet(0);
777+
sheet.setCell("A1", "merged");
778+
sheet.mergeCells("A1:C1");
779+
sheet.setCell("A2", "");
780+
781+
const rows = [...sheet.iterateRows({ includeMergeInfo: true, skipEmptyValues: true })];
782+
expect(rows).toHaveLength(2);
783+
784+
const mergedRow = rows.find(r => r.index === 0);
785+
expect(mergedRow).toBeDefined();
786+
expect(mergedRow!.cells.map(c => c.col)).toEqual([0]);
787+
expect(mergedRow!.cells[0].value).toBe("merged");
788+
789+
const emptyStringRow = rows.find(r => r.index === 1);
790+
expect(emptyStringRow).toBeDefined();
791+
expect(emptyStringRow!.cells).toHaveLength(1);
792+
expect(emptyStringRow!.cells[0].col).toBe(0);
793+
expect(emptyStringRow!.cells[0].value).toBe("");
794+
});
795+
796+
it("skipBlankValues filters both empty and empty-string cells", () => {
797+
const wb = new Workbook();
798+
const sheet = wb.getSheet(0);
799+
sheet.setCell("A1", 10);
800+
sheet.setCell("B1", "");
801+
sheet.setCell("A2", "merged");
802+
sheet.mergeCells("A2:C2");
803+
804+
const rows = [...sheet.iterateRows({ includeMergeInfo: true, skipBlankValues: true })];
805+
expect(rows).toHaveLength(2);
806+
807+
const firstRow = rows.find(r => r.index === 0);
808+
expect(firstRow).toBeDefined();
809+
expect(firstRow!.cells.map(c => c.col)).toEqual([0]);
810+
expect(firstRow!.cells[0].value).toBe("10");
811+
812+
const mergedRow = rows.find(r => r.index === 1);
813+
expect(mergedRow).toBeDefined();
814+
expect(mergedRow!.cells.map(c => c.col)).toEqual([0]);
815+
expect(mergedRow!.cells[0].value).toBe("merged");
816+
});
817+
818+
it("omits rows whose cells are fully filtered out", () => {
819+
const wb = new Workbook();
820+
const sheet = wb.getSheet(0);
821+
sheet.setCell("A1", "");
822+
sheet.setCell("B2", 42);
823+
824+
const rows = [...sheet.iterateRows({ skipBlankValues: true })];
825+
expect(rows).toHaveLength(1);
826+
expect(rows[0].index).toBe(1);
827+
expect(rows[0].cells).toHaveLength(1);
828+
expect(rows[0].cells[0].col).toBe(1);
829+
expect(rows[0].cells[0].value).toBe("42");
830+
});
831+
});

bindings/nodejs/generated.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,10 @@ export interface JsRowsOptions {
808808
includeFormulas?: boolean
809809
/** Include IMAGE() metadata. */
810810
includeImages?: boolean
811+
/** Skip cells whose raw value is empty (CellValue::Empty). */
812+
skipEmptyValues?: boolean
813+
/** Skip cells whose raw value is blank (empty, empty string, or empty rich text). */
814+
skipBlankValues?: boolean
811815
}
812816

813817
/** Font properties for a rich text run (all fields optional — unset inherits cell style). */

bindings/nodejs/src/types.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ pub struct JsRowsOptions {
6868
pub include_formulas: Option<bool>,
6969
/// Include IMAGE() metadata.
7070
pub include_images: Option<bool>,
71+
/// Skip cells whose raw value is empty (CellValue::Empty).
72+
pub skip_empty_values: Option<bool>,
73+
/// Skip cells whose raw value is blank (empty, empty string, or empty rich text).
74+
pub skip_blank_values: Option<bool>,
7175
}
7276

7377
/// Color representation. The `colorType` field indicates the variant:

bindings/nodejs/src/worksheet_read.rs

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use super::{
77
catch_panic, to_napi_err, JsAutoFilter, JsColor, JsComment, JsCommentEntry,
88
JsConditionalFormatRule, JsDataValidation, JsFormulaCell, JsFreezePanes, JsHyperlink,
99
JsHyperlinkEntry, JsImageInfo, JsMergeSpan, JsMergedRegion, JsPageBreak, JsPageSetup, JsRow,
10-
JsRowCell, JsRowsOptions, JsSelection, JsSheetProtection, JsSpillSource, JsSplitPanes,
11-
JsStyle, JsTable, Worksheet,
10+
JsRowCell, JsRowsOptions, JsSelection, JsSheetProtection, JsSpillSource, JsSplitPanes, JsStyle,
11+
JsTable, Worksheet,
1212
};
1313

1414
#[napi]
@@ -184,6 +184,8 @@ impl Worksheet {
184184
let inc_comments = opts.and_then(|o| o.include_comments).unwrap_or(false);
185185
let inc_formulas = opts.and_then(|o| o.include_formulas).unwrap_or(false);
186186
let inc_images = opts.and_then(|o| o.include_images).unwrap_or(false);
187+
let skip_empty = opts.and_then(|o| o.skip_empty_values).unwrap_or(false);
188+
let skip_blank = opts.and_then(|o| o.skip_blank_values).unwrap_or(false);
187189

188190
// Compute the effective max row, extending for metadata sources.
189191
let mut max_row = ws.used_range().map(|r| r.end.row).unwrap_or(0);
@@ -215,10 +217,10 @@ impl Worksheet {
215217

216218
// Build the set of (row, col) coordinates to include.
217219
// Start with non-empty value cells.
218-
let mut coords: std::collections::BTreeSet<(u32, u16)> =
219-
ws.populated_cells_in_range(start_row, end_row)
220-
.into_iter()
221-
.collect();
220+
let mut coords: std::collections::BTreeSet<(u32, u16)> = ws
221+
.populated_cells_in_range(start_row, end_row)
222+
.into_iter()
223+
.collect();
222224

223225
// Add cells with metadata when their flag is enabled.
224226
if inc_styles {
@@ -268,10 +270,12 @@ impl Worksheet {
268270
let (row, col) = (*row, *col);
269271
if current_row != Some(row) {
270272
if let Some(prev_row) = current_row {
271-
rows.push(JsRow {
272-
index: prev_row,
273-
cells: std::mem::take(&mut current_cells),
274-
});
273+
if !current_cells.is_empty() {
274+
rows.push(JsRow {
275+
index: prev_row,
276+
cells: std::mem::take(&mut current_cells),
277+
});
278+
}
275279
}
276280
current_row = Some(row);
277281
}
@@ -286,6 +290,16 @@ impl Worksheet {
286290
ws.get_value_at(row, col).to_string()
287291
};
288292

293+
if skip_blank || skip_empty {
294+
let raw = ws.get_value_at(row, col);
295+
if skip_blank && raw.is_blank() {
296+
continue;
297+
}
298+
if skip_empty && raw.is_empty() {
299+
continue;
300+
}
301+
}
302+
289303
let style = if inc_styles {
290304
ws.cell_style_at(row, col).map(JsStyle::from)
291305
} else {
@@ -303,7 +317,11 @@ impl Worksheet {
303317

304318
let is_merged_secondary = if inc_merge {
305319
let v = ws.is_merged_secondary(row, col);
306-
if v { Some(true) } else { None }
320+
if v {
321+
Some(true)
322+
} else {
323+
None
324+
}
307325
} else {
308326
None
309327
};
@@ -352,10 +370,12 @@ impl Worksheet {
352370
}
353371

354372
if let Some(last_row) = current_row {
355-
rows.push(JsRow {
356-
index: last_row,
357-
cells: current_cells,
358-
});
373+
if !current_cells.is_empty() {
374+
rows.push(JsRow {
375+
index: last_row,
376+
cells: current_cells,
377+
});
378+
}
359379
}
360380

361381
Ok(rows)

bindings/python/src/worksheet_read.rs

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ fn sparse_rows_batch(
2828
include_comments: bool,
2929
include_formulas: bool,
3030
include_images: bool,
31+
skip_empty_values: bool,
32+
skip_blank_values: bool,
3133
) -> PyResult<Vec<PyRow>> {
3234
let wb = workbook.read().map_err(to_py_err)?;
3335
let ws = wb
@@ -100,10 +102,12 @@ fn sparse_rows_batch(
100102

101103
if current_row != Some(row) {
102104
if let Some(prev_row) = current_row {
103-
rows.push(PyRow {
104-
index: prev_row,
105-
cells: std::mem::take(&mut current_cells),
106-
});
105+
if !current_cells.is_empty() {
106+
rows.push(PyRow {
107+
index: prev_row,
108+
cells: std::mem::take(&mut current_cells),
109+
});
110+
}
107111
}
108112
current_row = Some(row);
109113
}
@@ -118,6 +122,16 @@ fn sparse_rows_batch(
118122
ws.get_value_at(row, col).to_string()
119123
};
120124

125+
if skip_blank_values || skip_empty_values {
126+
let raw = ws.get_value_at(row, col);
127+
if skip_blank_values && raw.is_blank() {
128+
continue;
129+
}
130+
if skip_empty_values && raw.is_empty() {
131+
continue;
132+
}
133+
}
134+
121135
let style = if include_styles {
122136
ws.cell_style_at(row, col).map(PyStyle::from)
123137
} else {
@@ -190,10 +204,12 @@ fn sparse_rows_batch(
190204
}
191205

192206
if let Some(last_row) = current_row {
193-
rows.push(PyRow {
194-
index: last_row,
195-
cells: current_cells,
196-
});
207+
if !current_cells.is_empty() {
208+
rows.push(PyRow {
209+
index: last_row,
210+
cells: current_cells,
211+
});
212+
}
197213
}
198214

199215
Ok(rows)
@@ -219,6 +235,8 @@ pub struct PyRowIterator {
219235
include_comments: bool,
220236
include_formulas: bool,
221237
include_images: bool,
238+
skip_empty_values: bool,
239+
skip_blank_values: bool,
222240
next_row: u32,
223241
max_row: u32,
224242
buffer: Vec<PyRow>,
@@ -254,6 +272,8 @@ impl PyRowIterator {
254272
self.include_comments,
255273
self.include_formulas,
256274
self.include_images,
275+
self.skip_empty_values,
276+
self.skip_blank_values,
257277
)?;
258278
self.cursor = 0;
259279

@@ -388,7 +408,9 @@ impl PyWorksheet {
388408
include_hyperlinks=None,
389409
include_comments=None,
390410
include_formulas=None,
391-
include_images=None
411+
include_images=None,
412+
skip_empty_values=false,
413+
skip_blank_values=false
392414
))]
393415
fn get_rows_batch(
394416
&self,
@@ -402,6 +424,8 @@ impl PyWorksheet {
402424
include_comments: Option<bool>,
403425
include_formulas: Option<bool>,
404426
include_images: Option<bool>,
427+
skip_empty_values: bool,
428+
skip_blank_values: bool,
405429
) -> PyResult<Vec<PyRow>> {
406430
sparse_rows_batch(
407431
&self.workbook,
@@ -416,6 +440,8 @@ impl PyWorksheet {
416440
include_comments.unwrap_or(false),
417441
include_formulas.unwrap_or(false),
418442
include_images.unwrap_or(false),
443+
skip_empty_values,
444+
skip_blank_values,
419445
)
420446
}
421447

@@ -428,7 +454,9 @@ impl PyWorksheet {
428454
include_hyperlinks=None,
429455
include_comments=None,
430456
include_formulas=None,
431-
include_images=None
457+
include_images=None,
458+
skip_empty_values=false,
459+
skip_blank_values=false
432460
))]
433461
fn iterate_rows(
434462
&self,
@@ -440,6 +468,8 @@ impl PyWorksheet {
440468
include_comments: Option<bool>,
441469
include_formulas: Option<bool>,
442470
include_images: Option<bool>,
471+
skip_empty_values: bool,
472+
skip_blank_values: bool,
443473
) -> PyResult<PyRowIterator> {
444474
Ok(PyRowIterator {
445475
workbook: Arc::clone(&self.workbook),
@@ -452,6 +482,8 @@ impl PyWorksheet {
452482
include_comments: include_comments.unwrap_or(false),
453483
include_formulas: include_formulas.unwrap_or(false),
454484
include_images: include_images.unwrap_or(false),
485+
skip_empty_values,
486+
skip_blank_values,
455487
next_row: 0,
456488
max_row: worksheet_max_row(&self.workbook, self.sheet_index)?,
457489
buffer: Vec::new(),

0 commit comments

Comments
 (0)