Skip to content

fix(sheet): omit hidden rows and columns in OOXML xlsx - #90

Open
HaoChiBao wants to merge 1 commit into
firecrawl:mainfrom
HaoChiBao:fix/hidden-rows-cols-9
Open

fix(sheet): omit hidden rows and columns in OOXML xlsx#90
HaoChiBao wants to merge 1 commit into
firecrawl:mainfrom
HaoChiBao:fix/hidden-rows-cols-9

Conversation

@HaoChiBao

@HaoChiBao HaoChiBao commented Aug 13, 2026

Copy link
Copy Markdown

Related to #9 (row/column half). Does not close #9; coexist with #39 (sheet-level half).

Summary

  • Stream <row hidden> / <col hidden> from OOXML worksheet parts (calamine does not expose them)
  • Omit those axes when building Document/Markdown tables; adjust merges to visible cells
  • Scope: xlsx/xlsm OOXML only. Binary xls/xlsb and ODS unchanged

Out of scope

Test plan

  • Synthetic xlsx fixture: visible cell kept; hidden row/col text absent from markdown and document
  • Existing merge fixture tests still pass
  • cargo fmt, clippy -D warnings, cargo test --locked --lib

Summary by cubic

Omits hidden rows and columns in OOXML .xlsx/.xlsm so hidden content no longer appears in generated tables and Markdown. Previously we rendered hidden axes because calamine does not expose <row hidden>/<col hidden>; now we stream visibility from worksheet parts and adjust merges.

  • Scope: OOXML only; .xls/.xlsb and ODS behave as before.
  • Merges: drop a merge if its origin is hidden; otherwise compute span from visible rows/cols so covered cells align with what remains visible.
  • Implementation: new visibility module loads hidden axes per sheet by reading xl/workbook.xml, xl/_rels/workbook.xml.rels, and each worksheet XML with resource caps; if a part can’t be read within limits, we warn and leave that sheet unfiltered.

Written for commit ba58622. Summary will update on new commits.

Review in cubic

Calamine does not expose worksheet row/col hidden attrs, so stream them
from sheetN.xml and omit those axes when building tables. Addresses the
remaining row/column half of firecrawl#9 without racing PR firecrawl#39 sheet visibility.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/formats/sheet/visibility.rs">

<violation number="1" location="src/formats/sheet/visibility.rs:50">
P1: A workbook with many large worksheet parts can make this visibility pass decompress far beyond the repository-wide archive budget. Use the bounded `Package` reader or track the archive-wide byte and entry budgets while loading these parts.</violation>

<violation number="2" location="src/formats/sheet/visibility.rs:215">
P2: When a worksheet contains vendor extension markup named `row` or `col`, `parse_sheet_hidden` treats it as SpreadsheetML visibility metadata and can omit visible cells. Restrict elements to the SpreadsheetML worksheet namespace and visibility attributes to the required unqualified names.</violation>
</file>

<file name="src/formats/sheet/mod.rs">

<violation number="1" location="src/formats/sheet/mod.rs:88">
P2: When a merged range has a hidden top-left row or column, this `continue` bypasses registration of its visible covered cells. Any raw values in those cells then leak into the document and Markdown; blank or suppress the visible covered positions while dropping the hidden origin.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

/// logged and that sheet is omitted from the map (conversion continues
/// without filtering that sheet).
pub(super) fn load_hidden_axes(bytes: &[u8]) -> HashMap<String, SheetHidden> {
let mut zip = match ZipArchive::new(Cursor::new(bytes)) {

@cubic-dev-ai cubic-dev-ai Bot Aug 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: A workbook with many large worksheet parts can make this visibility pass decompress far beyond the repository-wide archive budget. Use the bounded Package reader or track the archive-wide byte and entry budgets while loading these parts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/formats/sheet/visibility.rs, line 50:

<comment>A workbook with many large worksheet parts can make this visibility pass decompress far beyond the repository-wide archive budget. Use the bounded `Package` reader or track the archive-wide byte and entry budgets while loading these parts.</comment>

<file context>
@@ -0,0 +1,329 @@
+/// logged and that sheet is omitted from the map (conversion continues
+/// without filtering that sheet).
+pub(super) fn load_hidden_axes(bytes: &[u8]) -> HashMap<String, SheetHidden> {
+    let mut zip = match ZipArchive::new(Cursor::new(bytes)) {
+        Ok(z) => z,
+        Err(_) => return HashMap::new(),
</file context>
Fix with cubic

match reader.read_event_into(&mut buf) {
Ok(Event::Start(e) | Event::Empty(e)) => {
let local = local_name(reader.decoder(), e.name());
match local.as_str() {

@cubic-dev-ai cubic-dev-ai Bot Aug 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a worksheet contains vendor extension markup named row or col, parse_sheet_hidden treats it as SpreadsheetML visibility metadata and can omit visible cells. Restrict elements to the SpreadsheetML worksheet namespace and visibility attributes to the required unqualified names.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/formats/sheet/visibility.rs, line 215:

<comment>When a worksheet contains vendor extension markup named `row` or `col`, `parse_sheet_hidden` treats it as SpreadsheetML visibility metadata and can omit visible cells. Restrict elements to the SpreadsheetML worksheet namespace and visibility attributes to the required unqualified names.</comment>

<file context>
@@ -0,0 +1,329 @@
+        match reader.read_event_into(&mut buf) {
+            Ok(Event::Start(e) | Event::Empty(e)) => {
+                let local = local_name(reader.decoder(), e.name());
+                match local.as_str() {
+                    "col" => {
+                        let mut min = None;
</file context>
Fix with cubic

Comment thread src/formats/sheet/mod.rs
Comment on lines +88 to +92
if axis_row_hidden(hidden, start.0 + r0 as u32)
|| axis_col_hidden(hidden, start.1 + c0 as u32)
{
continue;
}

@cubic-dev-ai cubic-dev-ai Bot Aug 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a merged range has a hidden top-left row or column, this continue bypasses registration of its visible covered cells. Any raw values in those cells then leak into the document and Markdown; blank or suppress the visible covered positions while dropping the hidden origin.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/formats/sheet/mod.rs, line 88:

<comment>When a merged range has a hidden top-left row or column, this `continue` bypasses registration of its visible covered cells. Any raw values in those cells then leak into the document and Markdown; blank or suppress the visible covered positions while dropping the hidden origin.</comment>

<file context>
@@ -65,22 +76,51 @@ pub fn parse(bytes: &[u8]) -> Result<Document, ConvertError> {
+            }
+            // Value lives at the merge origin; if that axis is hidden, drop
+            // the merge so remaining visible covered cells stay empty.
+            if axis_row_hidden(hidden, start.0 + r0 as u32)
+                || axis_col_hidden(hidden, start.1 + c0 as u32)
+            {
</file context>
Suggested change
if axis_row_hidden(hidden, start.0 + r0 as u32)
|| axis_col_hidden(hidden, start.1 + c0 as u32)
{
continue;
}
if axis_row_hidden(hidden, start.0 + r0 as u32)
|| axis_col_hidden(hidden, start.1 + c0 as u32)
{
for &r in &visible_rows {
for &c in &visible_cols {
covered.insert((r, c));
}
}
continue;
}
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

XLSX conversion silently treats hidden rows and columns as visible

1 participant