Move testVlookupWholeColumnRange and providerVlookupWholeColumnRange
from ParseFormulaTest to the correct location VLookupTest, which
extends AllSetupTeardown. Adapted to use $this->getSpreadsheet()
instead of managing the Spreadsheet lifecycle manually.
A table whose range covers its header row alone makes Excel report the
workbook as unreadable; it repairs the file by dropping the table, so the
markup the table was written for is lost without a word.
Excel itself never writes such a table. Asked to make one over a single row
of headings, it writes the table over the row below as well and leaves that
row empty — its `sheetData` holds no cell for it, and the sheet dimension
stays at the headings. The writer now does the same, so what is written is
what Excel would have written.
The row below is taken only when it holds nothing. A table silently
swallowing a row that belongs to something else would change what the sheet
says, so that case throws instead, naming the row in the way.
When resolving the end reference of a whole-column range (e.g. $A:$F),
getHighestDataRow($col) was called for the specific end column. If that
column contains no data it returns 1, producing an inverted range such as
A4:F1. This caused VLOOKUP and similar functions to return #N/A when the
formula used a whole-column reference across sheets where data only exists
in the left-hand columns.
Fix: call getHighestDataRow() without a column argument so the overall
highest data row across all columns is used for the end reference.
Reproducer:
Sheet2!A:D has data in cols A–C only; $A:$F produced A4:F1 → #N/A.
After fix: A1:F4 → VLOOKUP finds the value correctly.
Rewrite the test to write the spreadsheet, read it back and check
getValue() and getCalculatedValue(), following DurationTest, instead of
matching against the generated content.xml.
convertFormula() ran the cell reference, defined name and function name
conversions over the whole formula, so anything that looked like a reference
inside a quoted string was rewritten: ="THIS IS E1" was written out as
="THIS IS [.E1]". The comma to semicolon replacement had the same problem,
turning =IF(A1>1,"yes, really","no") into a formula whose text reads
"yes; really".
Split the formula on string literals and convert only the segments outside
of them. A range cannot be interrupted by a string literal, so every
reference still sees the context the conversion relies on.
Fixes#4454
Based on recent discussions in #2938, it can do a bit better with date cells that do not use a recognized date style. Improve differentiating `date` from `date+time`, and in deciding whether 4-digit years have been used in the input cells. This causes a change to one test, where the old result incorrectly expected a 2-digit year (you can verify by opening the spreadsheet in question that it is saved with a 4-digit year).