@dkulyk noticed a case which wasn't handled properly. I believe the fix works for everything ... except `COLUMNS` and `ROWS`, where Excel seems to ignore the sheet, and I just can't figure out how to do it.
Fix#5042. `Shared\File::realpath` is used to normalize file name references in a zip file. It can be used for non-zip file names as well, but zip members is its primary use, and `file_exists` does not work with zip file members. Such usage leads to the issue to which this PR is linked, and there have been other instances in past. In particular, issue #3553 was addressed by editing it out a leading slash in such references; it is not needed, and neither Excel nor PhpSpreadsheet generate one, but it's legal. The fix for 3553 addressed only loading a spreadsheet; but `listWorksheetInfo` and `listWorksheetNames` are subject to the same problem, which is this issue.
The normalization that `realpath` does is already built into `File::realpath` using a well-documented algorithm. So neither `filepath` nor `file_exists` is needed there, and they are removed. There are other problems with the current implementation. It looks only for Unix, not Windows, file separators. And a very peculiar problem arises when the zip entry name matches a file path in the current working directory. These problems all go away with this PR.
It is very likely that some manipulation of the zip entry names can be removed entirely from `Reader\Xlsx`. I may look into this in future, but I'm not going looking for trouble now. The fix for 3553 remains in place, even though no changes are made to match it in `listWorksheet...`, so, chances are the 3553 change could be dropped. As I say, maybe some day.
Fix#5032. When evaluating formulas, stop iterating a cell range when the sheet on which the range is located does not exist.
While testing, discovered some commented-out tests in `ParseFormulaTest`, having nothing to do with this change, that could now be commented back in.
Unused directory slots must contain NOSTREAM in their sibling and child
pointers under MS-CFB 2.6.3. Zero-filled padding incorrectly points to the
root entry, so serialize the required pointer values while keeping every
other unused byte zero.
Add byte-level padding regressions and behavioral coverage for UTF-16
boundaries, numeric code-unit ordering, invalid nested names before output,
independent storage name scopes, caller object preservation, and distinct
payload round-trips. These assertions catch failures that complete line
coverage alone does not establish.
Document nested storage support, apply project formatting, and add a
changelog entry for the PPS conformance changes.
Validation: 265 tests / 2690 assertions; targeted PHPStan, PHPCS,
PHP-CS-Fixer, and whitespace checks passed.
Tcpdf is an optional package, but we do make a number of tests of it in our unit test suite. We will continue to support it for now. We are considering dropping support for it at the next PhpSpreadsheet major release.
As an early assessment, there is much too much work involved supporting `tc-lib-pdf` directly; we are not likely ever to move in that direction.
Tcpdf V7 is a bridge version. It is not deprecated (yet), and does not require much in the way of coding changes, but it requires users to supply their own font files; this is relatively easily done in Unix, but it is much harder in Windows. In addition, our test suite can't test satisfactorily with both V6 and V7. We may at some point consider supporting V7, but it is likely that the Windows difficulties will have to be dealt with before we can do so.
This PR includes some very minor coding changes which will not harm V6 and will help in migrating to V7 if we choose that route.
Minor changes due to Phpstan (statements where I had previously flagged that I thought it was wrong).
TCPDF V6 officially deprecated. We will continue to support for now.
The Ods Writer skipped every Worksheet\Table. LibreOffice keeps a table
as a named table:database-range, and from its header row it tags the
header cells of an exported PDF as /TH /Scope /Column (LO master).
The writer now writes one per table, with contains-header and the filter
buttons, after the named expressions as the schema orders them; a table
with no name gets one no other table has. The reader reads a named
database range back as a table and, as LibreOffice does, drops one whose
name is taken or cannot be a table. LibreOffice's anonymous ranges stay
a sheet filter, which now lands on the sheet its address names instead
of the last sheet; a range of a sheet that was not loaded is skipped.
This is a follow-up to PR #5008 issue #5007. Ods and Csv Writers were inappropriately writing empty hyperlinks. Collateral to this problem, Xlsx Writer was throwing an exception when attempting to rewrite a spreadsheet with these empty hyperlinks. The PR fixed the problem with the Ods and Csv Writers, so the problem would no longer show up in Xlsx Writer when loading and saving their output. However, Xlsx Writer should not have thrown in the first place - it would be sufficient for it to ignore the empty entries rather than throwing because of them. This PR fixes that problem.
Xls files store colors as indexes into a palette that the Writer builds
while it walks the workbook styles. addColor() picked the next custom
index from the most recently inserted entry (end()) instead of the
highest index in use.
A color matching an existing palette entry is cached under its exact RGB
string together with the matched index. When the same color is added
later under a differently-cased key (e.g. `ffffff` after `FFFFFF`), that
old, lower index is appended to the cache and "last used" moves
backwards. The next custom color then reuses and overwrites the palette
slot of an earlier custom color, so e.g. gray text is rendered in the
blue used by a fill.
Base the next index on max() so a custom color never lands on an
occupied palette slot.
LibreOffice writes the link of a cell that is not plain text (a number,
a boolean, a date, a formula) as style:hyperlink and loext:hyperlink in
the style:table-cell-properties of the cell's automatic style, which is
how it saves a Xlsx file with such links as Ods. The reader looked only
for text:a, so those links were lost.
The reader now maps each cell style to its link and uses it when the
cell has no text:a. A cell with no style of its own takes the default
cell style of its column, as LibreOffice does for A1 in the test file.
The Ods Writer wrote text:a only for string cells, so a link on a
number, boolean, date or formula cell was lost. The Ods Reader looked
for text:a only in string cells, so it would not have read one back.
All cell types now go through one helper that writes the displayed text,
wrapped in text:a when the cell has a hyperlink. The helper asks
hasHyperlink() first, since getHyperlink() would add an empty one. The
Reader takes text:a from the paragraphs of any cell type.