Let a workbook, a column or a cell say what an empty cell should hold - #143
Merged
Conversation
writexl writes an empty cell wherever a value has none, which is not always what a reader wants to see (#76). `na` names a stand-in: write_xlsx(df, path, na = "not measured") It is set at three scopes -- xl_properties(na =) for a workbook, with write_xlsx(na =) as the shorthand; xl_col_spec(na =) for a column, which is usually the right place, since a substitute that suits a numeric column rarely suits a date one; and xl_cell_general(na =) for a single cell. The innermost one that is set wins, and NA at any level means "nothing set here", so a cell falls through to its column and a column to its workbook. The substitute keeps its own type. `na = 0` writes a number and leaves a numeric column numeric; `na = "none"` makes that column mixed, which a reader such as readxl then returns as character. That falls out of routing the value through write_atomic_value(), the same dispatcher an ordinary cell value goes through -- so every type writexl can write, `na` can write, with no second type table to keep in step. NaN is covered alongside NA, having no representation either; Inf is not, since it already writes as "Inf". The default is `na = NA`, and it is not a behaviour change: an unset `na` reaches C as R_NilValue, write_na() declines it, and the same worksheet_write_ blank() call runs as before. A test asserts that byte for byte over a whole workbook rather than by inspection -- `created` is pinned so the only thing that could differ is the change under test. A cell with content is untouched: the substitution sits at the point where write_atomic_value() reports it had nothing to write, which is already after the formula, hyperlink and rich-string branches. So a formula cell keeps its formula, and only a cell with no content at all takes the stand-in.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #76.
writexl writes an empty cell wherever a value has none, which is not always
what a reader wants to see.
nanames a stand-in:Three scopes, innermost wins
xl_properties(na = )—write_xlsx(na = )is the shorthandxl_col_spec(na = )xl_cell_general(na = )NAat any level means "nothing set here", so a cell falls through to itscolumn and a column to its workbook. A per-cell
namay be a list, and aNULLelement leaves that one cell to inherit.
Columns are usually the right place: a substitute that suits a numeric column
rarely suits a date one.
The substitute keeps its own type
na = 0writes a number and leaves a numeric column numeric.na = "none"makes that column mixed, which a reader such as readxl then returns as
character — the documented cost of asking for a non-numeric value in a numeric
column.
That falls out of routing the value through
write_atomic_value(), the samedispatcher an ordinary cell value goes through. Every type writexl can write,
nacan write, and there is no second type table to keep in step.NaNis covered alongsideNA, having no representation either.Infis not,since it already writes as
"Inf".The default is not a behaviour change, and the test proves it
An unset
nareaches C asR_NilValue,write_na()declines it, and the sameworksheet_write_blank()call runs as before.Rather than assert that, a test compares whole workbooks byte for byte with
createdpinned, so the only thing that could differ is the change under testrather than the timestamp libxlsxwriter stamps on each run. This is the whole
compatibility story for a package whose reverse dependencies all write data
frames with NAs in them, so it is worth pinning at that strength.
A cell with content is untouched
The substitution sits at the point where
write_atomic_value()reports it hadnothing to write — which is already after the formula, hyperlink and
rich-string branches. So a formula cell keeps its formula, and only a cell with
no content at all takes the stand-in. A formatted blank keeps its format, as it
did before.
The header row is never substituted: it is always written, so it has no missing
value to stand in for. A test pins that too.
Three decisions made while implementing
write_xlsx(na = )errors whenxis already anxl_workbook, pointingat
xl_properties(na = ). The workbook carries its own properties, so theshorthand would otherwise be silently ignored — the same trap
col_namesalready has, but this one is new so it can fail loudly instead.
naon a one-cell column was silently truncating. Caught by atest written for something else; it now errors naming the length it wanted.
xl_cell_general()'s documented promise changed. "Usevalue = NAforan explicit empty cell" stops being true once a workbook
nais set, so thewording is updated and
nais documented as how a cell asks for its blankback.
Known limitation
There is no spelling for "this column writes a blank even though the workbook
substitutes something".
NAmeans inherit at the inner scopes, so it cannotalso mean override-to-blank.
na = ""gets an empty string, which is close butnot the same cell. Easy to add later if anyone wants it.
Verification
R CMD check: 0 errors, 0 warnings, 0 noteslintr::lint_package(): 0src/write_xlsx.cat 99.26%, the samedefensive lines as before
src/libxlsxwriter/untouched🤖 Generated with Claude Code