Skip to content

fix(xlsx): preserve cell hyperlinks - #198

Open
sean0407 wants to merge 2 commits into
firecrawl:mainfrom
sean0407:codex/anydoc-192-xlsx-cell-hyperlinks
Open

sean0407 wants to merge 2 commits into
firecrawl:mainfrom
sean0407:codex/anydoc-192-xlsx-cell-hyperlinks

Conversation

@sean0407

@sean0407 sean0407 commented Oct 18, 2026 •

Copy link
Copy Markdown

Summary

XLSX worksheet cell
- plain text when a hyperlink relationship is present
+ linked text using the worksheet relationship target

Closes #192. Resolve worksheet hyperlink relationships and render linked cell content using the existing document link model.

Evidence

  • Before: an XLSX cell displaying Firecrawl docs converted as plain text, while the equivalent ODS cell became [Firecrawl docs](https://docs.firecrawl.dev).
  • After: the regression test verifies external worksheet relationship URLs and in-sheet locations become cell links.
  • cargo test -p anydoc --locked --offline: 287 unit tests passed; integration tests 2 + 1 passed; 9 snapshot tests passed, 1 ignored; doc tests 0.
  • cargo fmt --all -- --check: passed.

Merge Danger

Door: Two-way; the change can be reverted in one commit.

Blast Radius: Narrow; only XLSX cell hyperlinks affect Markdown output.


Summary by cubic

Fixes #192: preserves XLSX cell hyperlinks so linked cell content renders as a link instead of plain text when the worksheet is converted.

  • Resolves worksheet relationships for external hyperlink targets and location attributes for in-sheet anchors, mapping them onto the existing document link model.
  • Wraps hyperlinked cell content in Inline::Link when the cell has a resolvable target; cells without targets keep their plain text, and in-sheet links render only when the target cell exists.
  • Adds regression tests covering external URLs, in-sheet locations, and rendered target anchors.

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

View guided diff

@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.

2 issues found across 1 file

Confidence score: 3/5

  • In xlsx.rs, range-valued hyperlinks are dropped, so links may be missing from cells in the range. Parse the range and apply the target only to populated cells.
  • In xlsx.rs, external links with a location lose their relationship URL and become workbook anchors. Combine the relationship target with the location.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


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

<violation number="1" location="src/formats/sheet/xlsx.rs:330">
P2: Range-valued `hyperlink@ref` attributes are discarded because this path accepts only a single cell reference. Parse the range and apply the target to each populated cell it covers without materializing unbounded blank ranges.</violation>

<violation number="2" location="src/formats/sheet/xlsx.rs:334">
P2: This branch discards `r:id` whenever `location` is present, so external HTML links with a fragment become workbook anchors and lose their external URL. Combine the relationship target with `location` when the relationship is external.</violation>
</file>

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

View guided diff | Re-trigger cubic

Comment thread src/formats/sheet/xlsx.rs
Comment thread src/formats/sheet/xlsx.rs
.into_iter()
.flat_map(|links| links.find_all(ns::SML, "hyperlink"))
{
let Some(at) = link.attr_unqualified("ref").and_then(parse_ref) else {

@cubic-dev-ai cubic-dev-ai Bot Oct 18, 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: Range-valued hyperlink@ref attributes are discarded because this path accepts only a single cell reference. Parse the range and apply the target to each populated cell it covers without materializing unbounded blank ranges.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/formats/sheet/xlsx.rs, line 330:

<comment>Range-valued `hyperlink@ref` attributes are discarded because this path accepts only a single cell reference. Parse the range and apply the target to each populated cell it covers without materializing unbounded blank ranges.</comment>

<file context>
@@ -314,6 +322,31 @@ fn read_sheet(
+        .into_iter()
+        .flat_map(|links| links.find_all(ns::SML, "hyperlink"))
+    {
+        let Some(at) = link.attr_unqualified("ref").and_then(parse_ref) else {
+            continue;
+        };
</file context>
Fix with cubic

Comment thread src/formats/sheet/xlsx.rs
continue;
};
let target = if let Some(location) = link.attr_unqualified("location") {
Some(LinkTarget::Anchor(location.to_string()))

@cubic-dev-ai cubic-dev-ai Bot Oct 18, 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: This branch discards r:id whenever location is present, so external HTML links with a fragment become workbook anchors and lose their external URL. Combine the relationship target with location when the relationship is external.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/formats/sheet/xlsx.rs, line 334:

<comment>This branch discards `r:id` whenever `location` is present, so external HTML links with a fragment become workbook anchors and lose their external URL. Combine the relationship target with `location` when the relationship is external.</comment>

<file context>
@@ -314,6 +322,31 @@ fn read_sheet(
+            continue;
+        };
+        let target = if let Some(location) = link.attr_unqualified("location") {
+            Some(LinkTarget::Anchor(location.to_string()))
+        } else {
+            link.attr_qualified(ns::R, "id")
</file context>
Fix with cubic

@rahul05ranjan rahul05ranjan 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.

Traced this through relationships.rs and fields.rs: the r:id path filters on the hyperlink relationship type before classify_rel_target, TargetMode is what decides External vs Relative, and a location wins as an Anchor — that ordering matches how Excel writes internal vs external links. I also confirmed read_rels returns an empty Relationships when a sheet has no rels part, so plain worksheets aren't affected. Wrapping only cells that actually made it into cells is the right call (a hyperlink on an empty cell is meaningless here), and the test covers both the external-relationship and sheet-location cases. Looks good to me.

@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.

1 issue found across 1 file (changes from recent commits).

Confidence score: 5/5

  • Repeated links to the same target in src/formats/sheet/xlsx.rs can produce duplicate <a id> attributes. Deduplicate target locations before creating Inline::Anchor nodes.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


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

<violation number="1" location="src/formats/sheet/xlsx.rs:98">
P3: Repeated links to one target cell create duplicate `<a id>` attributes. Deduplicate each target location before converting the IDs to `Inline::Anchor` nodes.</violation>
</file>

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

View guided diff | Re-trigger cubic

Comment thread src/formats/sheet/xlsx.rs
let Some((target_sheet, at)) = cell_location_target(source_name, location) else {
continue;
};
anchors_by_sheet

@cubic-dev-ai cubic-dev-ai Bot Oct 21, 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.

P3: Repeated links to one target cell create duplicate <a id> attributes. Deduplicate each target location before converting the IDs to Inline::Anchor nodes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/formats/sheet/xlsx.rs, line 98:

<comment>Repeated links to one target cell create duplicate `<a id>` attributes. Deduplicate each target location before converting the IDs to `Inline::Anchor` nodes.</comment>

<file context>
@@ -75,6 +75,35 @@ pub(super) fn parse(pkg: &mut Package, wb_part: &str) -> Result<Document, Conver
+            let Some((target_sheet, at)) = cell_location_target(source_name, location) else {
+                continue;
+            };
+            anchors_by_sheet
+                .entry(target_sheet.to_lowercase())
+                .or_default()
</file context>
Fix with cubic

This branch has not been deployed

No deployments
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: cell hyperlinks are dropped, while ODS preserves the same link

2 participants