Skip to content

fix(onlyoffice): a decimal typed into a CSV keeps the file's comma - #121

Merged
brkfun merged 1 commit into
BRF-Tech:mainfrom
berkbasarir:fix/csv-edited-decimal-comma
Oct 17, 2026
Merged

brkfun merged 1 commit into
BRF-Tech:mainfrom
berkbasarir:fix/csv-edited-decimal-comma

Conversation

@berkbasarir

@berkbasarir berkbasarir commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

What

A decimal typed into a CSV in ONLYOFFICE lost the file's decimal comma. KeepCSV (#88) keeps the cells nobody touched; the cell somebody edited was written as ONLYOFFICE saved it, and for a decimal that is a point.

Seen on a production install (filex 0.56.3, Docs 9.4.0-129, editor in Turkish): a semicolon file with a Tutar column of 3,5 / 1250,75 / 0,25. 3,5 typed over as 4,5; every untouched cell came back byte for byte, the edited one as 4.5.

Reproduced and measured locally on onlyoffice/documentserver:9.4.0.1 (JWT off, its defaults; the CSV opened with UTF-8 and its delimiter in document.options as filex does; cells typed through the cell name box; the bytes are the file of the status 2 callback):

typed Turkish (tr-TR) English (en-US) German (de-DE)
4,5 4.5 "4,5" (text) 4.5
1.250,75 1250.75 text 1250.75
1,1 1.1000000000000001 text
12345678,123456 12345678.123455999 12345678123456 12345678.123455999
0,000001 9.9999999999999995e-07 text
4,5% 4.50% text 4.50%
4.5 4.May 4.5 04.Mai
-4.5 -4.5 -4.5 -4.5

In a comma-decimal language the typed decimal is a number, and the writer prints numbers with a point and %.17g. Untouched 3,5 cells are text to ONLYOFFICE in every language and come back as they were - so a column ends up mixed. The full table is in docs/CONTRIBUTING.md.

How

officesave/csv_keep.go (rebased on v0.57.0, so it covers both editors' SaveCSV): in a record somebody changed, and in a row they added, a saved cell that is a number with a point and digits (-?\d+\.\d+, nothing else) is written with a decimal comma - only on evidence:

  1. the file's delimiter is not a comma;
  2. the file's column holds a number with a decimal comma (3,5, 1.250,75) and none with a point or an exponent (3.5, 1.250, 1e3) or comma-grouped thousands (1,250,000); 1,250 alone is no evidence (the English editor reads it as 1250);
  3. the save keeps one of that column's decimal commas byte for byte in a record lined up with the file's - the column did not move;
  4. the value is not one the file holds in any cell, in any spelling (csvMovedNumbers, by float64 value). A column deleted, added or moved, or cells shifted along a row, put the file's own numbers at the column's place; those keep their point. Checked only when a comma was actually written: the kept file is then written a second time without the moved values.

16 or 17 significant digits that are exactly %.17g of their double become the shortest decimal of the same double (1.1000000000000001 -> 1,1), which is what was typed; any other text keeps its digits; more than 17 digits is no number the writer printed and gets nothing. An exponent, a percent, a date or a whole number is written as before.

P1 / P2. csvSame is unchanged: this is not a "same value" rule, it writes the save's own value in the column's way of writing it. csvKeepCheck accepts a comma cell only in a column the evidence named, only in the one spelling the writer produces, and only when it reads back (comma as the decimal mark) as the saved number. Anything else still falls back to RewriteCSV.

Why not the editor's language. The save carries no language, 4,5 typed in Turkish and 4.5 typed in English save the same 4.5, and people in both may edit one file. Storing the opener's locale would need a column on office_sessions and would still be wrong for a co-edited session. So the column decides, whoever typed. Documented consequence: an English user's 4.5 (or a Turkish user's apostrophe text '4.5) in such a column is written 4,5 too.

CSVKept.Commas counts these cells; the debug line onlyoffice callback: CSV cells kept gains commas.

Tests

  • officesave/csv_keep_comma_test.go, new (measured vectors of the report in officesavetest/docs94_comma.go; the callback case in onlyoffice/callback_csv_measured_test.go):
    • the reported file as Docs 9.4.0.1 saved it in Turkish, German and English, plus a Turkish session that edits 0,25 -> 1,1 and adds a row with 2,75, through KeepCSV and through the callback;
    • every typed value of the two measurement sessions (25 in three languages, 11 in two), byte for byte as measured (checked against the captured callback files);
    • the evidence: comma-separated file, mixed column, no kept comma, column deleted (also with a value two columns share), columns swapped, a cell deleted or inserted with the row shifted, an exponent moved or nobody touched, 1.5 moved, 1,250 thousands, 1,250,000, a typed value the file holds elsewhere;
    • appendCSVCommaDecimal, csvCommaNumber, csvThousands, csvPointNumber tables, and csvKeepCheck with and without comma columns.
  • FuzzKeepCSV seeds the new cases; its read-back property (sameRow) accepts the comma spelling, csvKeepCheck stays the strict one.

Checked

  • gofmt, go vet ./..., go build ./... clean; go test ./internal/officesave/... ./internal/onlyoffice/ ok, also with -race.
  • FuzzKeepCSV 120 s + 2 x 90 s before the rebase, 90 s after: pass.
  • BenchmarkKeepCSV (500,004 records / 54 MB, M-series): 0.59 s on main (v0.57.0), 0.63 s here (+7%), allocations unchanged (126 MB). The column classification rides on the index pass; csvPointNumber allocates nothing. BenchmarkKeepCSV_LiningUp unchanged.
  • node scripts/check-links.mjs: all resolve. check-doc-anchors.mjs not run locally (needs the docs-site install); CI runs it.
  • TestDownloadLink_AnotherTenantsHost and TestAdminShares_RowsCarryTheCanonicalURL fail in internal/api/handlers on my machine, as they did for fix(onlyoffice): a CSV saved from ONLYOFFICE keeps the cells nobody changed #88 on an untouched checkout.
  • The design was put through two rounds of adversarial review; the first found the shared-value column proof, single-row shifts, 1,250 thousands, >17-digit text and an allocation in the classifier, the second found exponents and 1.5 slipping past the moved-number check (it was by spelling, now by value). Each is a test case now.

Not done

  • No new e2e spec; e2e/tests/199-csv-onlyoffice.spec.ts is unchanged (its edit is a whole number). A fixture that would cover this: the release-gate file with a 3,5 column, edited to 4,5 with the editor in Turkish.
  • Docs 10 and the Office editor app were not measured: the rule needs a saved number with a point, so where an editor already writes the file's comma it does nothing.
  • No migration.

🤖 Generated with Claude Code

In the Turkish or the German editor a decimal comma typed is a number, and
Docs 9.4 saves every number with a point and up to 17 significant digits:
`3,5` typed over as `4,5` in a semicolon file went to the file as `4.5`,
`1,1` as `1.1000000000000001`, in a column of `3,5` and `1250,75` that
KeepCSV kept as they were (they are text to ONLYOFFICE).

KeepCSV now writes such a number with a decimal comma in the cells somebody
typed or added, on evidence only: a file not separated by commas; a column
of the file with decimal commas and no point, exponent or comma-grouped
thousands; a decimal comma of that column kept byte for byte in a row lined
up with the file's (the column did not move); and a value the file holds in
none of its cells (a number that moved there is the file's own). 16 or 17
digits that are exactly %.17g become the shortest decimal of the double.
csvKeepCheck accepts the comma only in those columns, and only for the same
number. Measured on Docs 9.4.0.1 in Turkish, English and German; the saved
bytes are in csv_keep_comma_test.go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@berkbasarir
berkbasarir force-pushed the fix/csv-edited-decimal-comma branch from 8b24ef4 to 7c54b31 Compare October 17, 2026 01:49
@brkfun

brkfun commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Thanks Berk, this is a careful fix. I reviewed it against our tree for 0.57.1: it applies cleanly on 0.57.0, every test passes (with -race too), 1.7M fuzz executions and a 20,000-save randomized check produced no check_failed, and every written cell reads back as the saved value.

I re-measured on Docs 9.4.0.1 (tr-TR and de-DE) and your vectors hold. Grouped whole numbers (1.250, 12.500, 1.250.000) come back whole, so no d.ddd text reaches appendCSVCommaDecimal.

It will ship in 0.57.1 with your authorship, and this pull request will show as merged when that release lands. On top of it we added:

  • the CONTRIBUTING thousands-separator sentence reworded to match your table (a whole part other than 0 and three or more digits after the comma);
  • two more cells that get the comma, in ONLYOFFICE.md: an untouched formula (=7/2 is saved 3.5, written 3,5) and a shifted number whose value ONLYOFFICE changed on the way (0.1234567 saved 0.1234570);
  • the Office editor app's CSV save pinned by a test in save_test.go and named in the CHANGELOG.

For later: a Turkish or German money column holding a grouped whole number (1.250) counts as a point column; treating d{1,3}(.ddd)+ as neutral, like 1,250, would cover it.

Cost: BenchmarkKeepCSV (55 MB) is roughly 8-12% slower. Fine for a save.

@brkfun
brkfun merged commit cbc86f8 into BRF-Tech:main Oct 17, 2026
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.

2 participants