Skip to content

Add AreaReference support for structured table references#1750

Open
ken-swyfft wants to merge 6 commits into
nissl-lab:masterfrom
swyfft-insurance:fix/ks/20260323_area-reference-structured-table-refs
Open

Add AreaReference support for structured table references#1750
ken-swyfft wants to merge 6 commits into
nissl-lab:masterfrom
swyfft-insurance:fix/ks/20260323_area-reference-structured-table-refs

Conversation

@ken-swyfft
Copy link
Copy Markdown
Contributor

@ken-swyfft ken-swyfft commented Mar 23, 2026

Summary

Adds a constructor overload to AreaReference that transparently handles Excel structured table references (e.g., Table7[#Headers], Table1[[#Data],[Column1]]):

new AreaReference(reference, version, workbook)

When a workbook is provided, structured table references are detected and resolved automatically against the workbook's table definitions. Regular A1-style references pass through unchanged. Without a workbook, behavior is identical to before.

Also adds AreaReference.IsStructuredReference(string) for callers that need to detect structured references without resolving them.

Includes a fix for flaky TestNPOI1469 (timestamp rounding, unrelated).

Problem

When a named range's RefersToFormula contains a structured table reference, new AreaReference(formula, version) throws ArgumentException because CellReference.SeparateRefParts only handles A1-style references. NPOI already has full structured reference parsing in FormulaParser, but it was only accessible during formula evaluation — not for generic reference resolution from AreaReference.

This is a real-world issue: Excel workbooks that use Tables (ListObjects) often have named ranges pointing to structured references like Table7[#Headers] or Table7[[#Headers],[Column1]]. Code that resolves named ranges to cell positions via AreaReference crashes on these workbooks.

Usage

// Create an IFormulaParsingWorkbook from your XSSFWorkbook:
var fpb = XSSFEvaluationWorkbook.Create((XSSFWorkbook)workbook);

// The constructor transparently handles both structured and regular references:
var namedRange = workbook.GetName("FormName");
var area = new AreaReference(namedRange.RefersToFormula, SpreadsheetVersion.EXCEL2007, fpb);

// area.FirstCell / area.LastCell now contain concrete cell coordinates,
// whether the formula was "Sheet1!$A$1:$C$7" or "Table7[#Headers]".

No special branching needed — structured references "just work."

For [#This Row] references that depend on the formula's row position, pass the row index:

var area = new AreaReference("Table1[#This Row]", SpreadsheetVersion.EXCEL2007, fpb, rowIndex: 5);

Approach

The existing 2-parameter constructor chains to a new 4-parameter overload:

public AreaReference(String reference, SpreadsheetVersion version)
    : this(reference, version, null, 0) { }

public AreaReference(String reference, SpreadsheetVersion version,
    IFormulaParsingWorkbook workbook, int rowIndex = 0) { ... }

When workbook is non-null and the reference matches structured reference syntax, the constructor delegates to the existing FormulaParser.ParseStructuredReference(). Otherwise, the original A1-style parsing runs unchanged.

Tests

18 tests in TestAreaReferenceStructuredReferences organized in three groups:

Structured reference resolution (with workbook):

  • #Headers, #Data, #All, #This Row specifiers
  • Single column, column range, headers+column combinations
  • Invalid table name → KeyNotFoundException

Backward compatibility (without workbook):

  • Regular references still parse correctly (single cell, multi-cell, sheet-qualified, absolute, whole-column)
  • Structured references still throw ArgumentException

Workbook + regular references:

  • Regular A1-style references pass through correctly when a workbook is provided
  • Sheet-qualified and single-cell references work
  • Null workbook behaves identically to 2-param constructor

All existing tests continue to pass.

ken-swyfft and others added 3 commits March 23, 2026 09:48
…erence()

AreaReference and CellReference only handle A1-style cell references.
When a named range's RefersToFormula contains a structured table reference
(e.g., Table7[#Headers], Table1[[#Data],[Column1]]), the AreaReference
constructor throws ArgumentException.

This adds two static methods to AreaReference:
- IsStructuredReference(string) - detects structured reference syntax
- CreateFromStructuredReference(string, version, workbook, rowIndex) -
  resolves structured refs to concrete AreaReference by delegating to
  the existing FormulaParser.ParseStructuredReference()

The existing constructor behavior is unchanged - callers must opt in
to structured reference support by using the new methods.

Includes 12 tests covering #Headers, #Data, #All, #This Row,
single column, column range, headers+column, and error cases.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DateTime.UtcNow has sub-second precision. ToString("HH:mm:ss") truncates
(e.g., 16:56:17.987 -> "16:56:17"), but DataFormatter rounds the Excel
OLE date double, producing "16:56:18". Truncate to whole seconds before
the test starts so both paths agree.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace CreateFromStructuredReference() with a constructor overload:
  new AreaReference(reference, version, workbook, rowIndex)

The existing 2-param constructor chains to the new 4-param one. When a
workbook is provided and the reference is a structured table reference,
it resolves automatically. Regular references pass through unchanged.
Without a workbook, behavior is identical to before.

This is more ergonomic for callers — structured references "just work"
without needing to check IsStructuredReference() first.

Tests expanded from 12 to 18: added coverage for constructor chaining
with regular refs (single cell, multi-cell, sheet-qualified, absolute,
whole-column), null workbook pass-through, and workbook + regular ref.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ken-swyfft
Copy link
Copy Markdown
Contributor Author

@tonyqus - Just as an FYI, we ran into this in a production scenario that wasn't adequately covered by our tests, so it hadn't turned up in our previous testing.

Copy link
Copy Markdown
Contributor Author

@ken-swyfft ken-swyfft left a comment

Choose a reason for hiding this comment

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

Overall: Approve. Clean, well-scoped change that solves a real problem. Reuses existing FormulaParser infrastructure rather than duplicating structured reference parsing. Constructor chaining preserves full backward compatibility. Test coverage is thorough — especially the backward compat tests after the constructor refactor.

A few optional suggestions inline — none are blocking.

Comment thread main/SS/Util/AreaReference.cs
Comment thread main/SS/Util/AreaReference.cs
Comment thread main/SS/Util/AreaReference.cs
Comment thread testcases/ooxml/SS/Util/TestAreaReferenceStructuredReferences.cs
- Document possible exceptions (KeyNotFoundException, FormulaParseException,
  InvalidOperationException) in constructor XML doc
- Add performance note about FormulaParser allocation in remarks
- Clarify isAbsolute/isRelative inversion with inline comment
- Note regex false-positive behavior in code comment
- Document exception types in test for invalid table name

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds structured table reference support to AreaReference by introducing a workbook-aware constructor overload that resolves Excel table references (e.g., Table1[#Headers]) into concrete cell ranges, plus a helper to detect structured references. Also stabilizes a flaky timestamp-related test.

Changes:

  • Add AreaReference(String, SpreadsheetVersion, IFormulaParsingWorkbook, int rowIndex = 0) to resolve structured references via FormulaParser.ParseStructuredReference when a workbook is provided.
  • Add AreaReference.IsStructuredReference(string) for structured reference detection.
  • Fix TestNPOI1469 flakiness by truncating timestamps to whole seconds before formatting/comparison.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
main/SS/Util/AreaReference.cs Adds workbook-aware parsing path for structured references and exposes structured-reference detection helper.
testcases/ooxml/SS/Util/TestAreaReferenceStructuredReferences.cs New NUnit tests covering structured resolution, backward compatibility, and workbook + regular references.
testcases/main/HSSF/UserModel/TestCellStyle.cs Stabilizes TestNPOI1469 by avoiding sub-second rounding mismatches.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread main/SS/Util/AreaReference.cs Outdated
/// The 0-based row index of the cell containing the reference.
/// Only needed for <c>[#This Row]</c> or <c>@</c> specifiers; pass 0 otherwise.
/// </param>
/// <exception cref="KeyNotFoundException">
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

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

The XML doc cref KeyNotFoundException is in System.Collections.Generic, but this file doesn’t import that namespace or fully-qualify the type. This will produce an unresolved-cref compiler warning (and can fail builds if warnings are treated as errors). Add using System.Collections.Generic; or change the cref to System.Collections.Generic.KeyNotFoundException.

Suggested change
/// <exception cref="KeyNotFoundException">
/// <exception cref="System.Collections.Generic.KeyNotFoundException">

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Reply from Claude (AI assistant) on Ken's behalf]

Good catch — fixed in 2347590. Switched the cref to System.Collections.Generic.KeyNotFoundException rather than adding a new using, since that namespace isn't used elsewhere in this file.

Comment thread main/SS/Util/AreaReference.cs Outdated
/// <returns><c>true</c> if the reference is a structured table reference; otherwise <c>false</c>.</returns>
public static bool IsStructuredReference(String reference)
{
return Table.IsStructuredReference.IsMatch(reference);
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

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

IsStructuredReference currently delegates to Table.IsStructuredReference.IsMatch(reference), which (1) throws on null and (2) uses an unanchored regex, so it returns true when a structured-reference-looking substring appears inside a larger string. Since the method name/docs imply the whole input is a structured reference, consider returning false for null and ensuring the match spans the entire string (e.g., via Match + Index/Length checks or trimming + ^...$).

Suggested change
return Table.IsStructuredReference.IsMatch(reference);
if (reference == null)
{
return false;
}
var match = Table.IsStructuredReference.Match(reference);
return match.Success && match.Index == 0 && match.Length == reference.Length;

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Reply from Claude (AI assistant) on Ken's behalf]

Fixed in 2347590. Added a null guard and switched from IsMatch to Match with Index == 0 && Length == reference.Length so the regex has to span the whole input — substrings like "prefix Table1[#Headers]" no longer false-positive. Also added three new tests in TestAreaReferenceStructuredReferences covering null, embedded-substring, and empty-string inputs.

ken-swyfft and others added 2 commits May 13, 2026 09:44
…uredReference

Fully qualify the `KeyNotFoundException` cref so it resolves without
needing a `using System.Collections.Generic;` in this file.

Make `IsStructuredReference` return false for null input and require
the regex to span the entire string, so an embedded bracketed substring
in a larger value does not false-positive into the structured-reference
parse path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants