The XLSX row of the formats table, a paragraph in one cell, becomes an Excel files section: what's read, what's written, and a table of what changes on the way. The .012 format gets a versions table and one table of the fields that need no version (charts and arithmetic among them, which weren't described), instead of a paragraph growing a sentence per field. Rules in Excel split into a conditional format table and a data validation table. How column and row formats behave moves to data.md; files.md keeps their field. TestFileDocNamesFields checks files.md names every field of the format, and macros.md a macro's.
- commit
- e389ff447c3d071a77e1915edc64cc00d0863ba9
- parent
- acea83599ea1e672bb5a6bc6bef4d35adb50a5d0
- author
- Dami <dami.etoile@gmail.com>
| M | docs/ | 8 | + |
| M | docs/ | 249 | ++++++++++++++++++------------ |
| A | internal/ | 56 | +++++++ |
@@ -54,6 +54,14 @@ right-click menus, or Ctrl+Alt+= and Ctrl+Alt+- (rows, or columns when whole
5454 columns are selected). Formulas, names, charts, filters and widths follow.5555 Drag a column header's edge to resize it, or use Format > Column width.5656 57+Formatting whole columns or rows (select them with Ctrl+Space or58+Shift+Space, or click their headers) keeps the format on the column or59+row, as Sheets does, rather than on each of their million cells: every60+cell of it shows the format unless it has its own, and a cell typed into61+later takes it. A cell's format comes from the cell, else its row, else62+its column, else the whole sheet's (Ctrl+A twice, then a format); the63+number format and the text style fall back separately.64+5765 ## Freeze5866 5967 View > Freeze keeps rows or columns on screen while the rest scrolls, marked@@ -1,30 +1,83 @@
11 # Files22 3-Spreadsheets save as `.012` files (JSON, one line per cell), with every4-sheet. Other formats come5-in through File > Import, File > Open or the command line, and go out6-through File > Download. Imports run in the background with a progress7-bar; Esc cancels. Saving an imported sheet asks whether to save it as a8-`.012` file or download it back in its format.3+Spreadsheets save as `.012` files: JSON with one line per cell, every4+sheet in one file. Other formats come in through File > Import, File >5+Open or the command line (`012 sales.xlsx`), and go out through File >6+Download.97 10-File > Import asks where the data goes, as Sheets' Import location does11-(a new, empty spreadsheet is simply replaced):8+129 13-| Location | Does |14-|---|---|15-| Insert new sheet(s) | Adds the file's sheets after the sheet shown: every sheet of an `.xlsx`, named after the file for other formats. A name already taken gets a number (`Sales 2`) and the file's formulas follow it; named ranges come along unless their name is taken. One undo step, and the spreadsheet stays the file you're editing |16-| Replace current sheet | Puts the data in place of the sheet shown, keeping its name and position, so formulas and named ranges that read it read the new data. Its charts that fit the new data stay: a chart that drew a whole table is re-pointed to the table the file has at the same corner when it has as many columns (rows for a chart by row), so last month's chart draws this month's rows, and one of part of a table, or of whole columns or rows, keeps its range. A chart whose table now has other columns, or whose range is empty now, is removed. The context line lists the removed charts first, then which were re-pointed and which kept their range. One undo step, which brings the removed charts back. Not offered for `.xlsx`, which holds several sheets |17-| Replace spreadsheet | Opens the file instead, as File > Open and the command line do, asking first when there are unsaved changes |10+## Import and download11+12+Imports run in the background with a progress bar; Esc cancels. Saving13+an imported sheet asks whether to save it as a `.012` file or download14+it back in its format.1815 1916 | Format | Import | Download |2017 |---|---|---|21-| CSV, TSV | Delimiter (`,` `;` tab `\|`), UTF-8 BOM, UTF-16 and Windows-1252 detected; entries become numbers, dates, currency and percentages as if typed; formulas stay text | Values as shown, as Sheets' Download does |22-| Excel `.xlsx` | Every sheet, opening on the one Excel showed: values, formulas (references between sheets too), workbook named ranges, number formats, bold, italic, underline, strikethrough, alignment, column widths, column and row styles, frozen panes, filters (see [Filters in XLSX](#filters-in-xlsx)), notes (Excel's notes, its legacy comments; threaded comments aren't read), shared formulas, dates in the 1904 system; sheets Excel hid stay hidden (unless it's the one Excel showed), conditional formatting and data validation (see [Rules in XLSX](#rules-in-xlsx)). Formulas 012 can't read (unknown functions) keep their values; Excel pivot tables come in as the values they showed. A sheet Excel protects comes in unprotected, with a note saying so, since Excel's protection locks cells where 012's only warns. Files past the reader's limits (a zip bomb, 1 GB in one part, 2 GB in all, cells past XFD1048576) are refused with a message saying which | Every sheet, the same, with formulas in Excel's syntax and their results cached, column and row formats as column and row styles, frozen rows and columns as frozen panes, a filter as Excel's with the rows it hides hidden, and notes as Excel's notes (which Sheets reads as notes), and conditional formats and data validation as Excel's. Protected ranges aren't written. A sheet whose name Excel can't take as is (spaces at its ends, or the same as another's but for them and case) gets one it can (without the spaces, with a number when two would clash, e.g. `Plan (2)`), and the formulas and named ranges naming it name that. JEV functions and `#AND#` save as values, and so do formulas naming a sheet that doesn't exist (their `#REF!`; Excel would refuse the reference), and pivot tables: Excel gets the results, not a pivot. The download's result counts the formulas saved as values, with an example |18+| CSV, TSV | Delimiter (`,` `;` tab `\|`), UTF-8 BOM, UTF-16 and Windows-1252 detected; entries become numbers, dates, currency and percentages as if typed; formulas stay text | Values as shown, of the sheet shown, as Sheets' Download does; a selection of whole columns or rows downloads their data, not a million blank lines |19+| Excel `.xlsx` | Every sheet, with formulas, formats and most of what a sheet holds: see [Excel files](#excel-files) | The same |2320 | SQLite | Pick a table or view, or type a query; a header row names the columns | The sheet shown or the selection as a table, first row as column names; a table of that name is replaced |2421 | Parquet | Every column, with dates and timestamps; lists joined with commas | |2522 | Lotus 1-2-3 `.wk1`, `.wks` | Numbers, labels with their alignment, formats, column widths, formulas translated (references, operators, `@SUM`, `@AVG`, `@IF`, `@ROUND`, `@PMT` and 60 more) or kept as values | |2623 27-## Filters in XLSX24+Formats other than XLSX import as one sheet named after the file (or the25+SQLite table).26+27+File > Import asks where the data goes, as Sheets' Import location does28+(a new, empty spreadsheet is simply replaced):29+30+| Location | Does |31+|---|---|32+| Insert new sheet(s) | Adds the file's sheets after the sheet shown. A name already taken gets a number (`Sales 2`) and the file's formulas follow it; named ranges come along unless their name is taken. One undo step, and the spreadsheet stays the file you're editing |33+| Replace current sheet | Puts the data in place of the sheet shown, keeping its name and position, so formulas and named ranges that read it read the new data. One undo step. Not offered for `.xlsx`, which holds several sheets |34+| Replace spreadsheet | Opens the file instead, as File > Open and the command line do, asking first when there are unsaved changes |35+36+Replacing the current sheet keeps the charts that fit the new data. A37+chart that drew a whole table is re-pointed to the table the file has at38+the same corner when it has as many columns (rows for a chart by row),39+so last month's chart draws this month's rows; a chart of part of a40+table, or of whole columns or rows, keeps its range. A chart whose table41+now has other columns, or whose range is empty, is removed. The context42+line lists the removed charts first, then which were re-pointed and43+which kept their range, and undo brings the removed ones back.44+45+**Size.** A sheet is 1,048,576 rows by 16,384 columns (A to XFD), as in46+Excel. Imports keep at most `max-cells` cells ([config.md](config.md#max-cells),47+two million by default, about 600 MB): whole rows, as many as fit, and48+the context line says how many rows were left out, e.g. `only the first49+166,666 rows fit in max-cells (2,000,000 cells); 12,000 rows left out`.50+Data past the grid's edges is left out the same way. WK1 files keep their51+own 8,192 by 256. See [limits.md](limits.md#imports) for speeds.52+53+## Excel files54+55+**Read:** every sheet, opening on the one Excel showed; values, formulas56+(references between sheets too, shared formulas), workbook named ranges,57+number formats, bold, italic, underline, strikethrough, alignment,58+column widths, column and row styles, frozen panes, [filters](#filters-in-excel),59+notes (Excel's notes, its legacy comments; threaded comments aren't60+read), [conditional formatting and data validation](#rules-in-excel),61+dates in the 1904 system, and hidden sheets (unless it's the one Excel62+showed).63+64+**Written:** the same, with formulas in Excel's syntax and their results65+cached, column and row formats as column and row styles, frozen rows and66+columns as frozen panes, a filter as Excel's with the rows it hides67+hidden, and notes as Excel's notes (which Sheets reads as notes).68+69+What changes on the way:70+71+| | |72+|---|---|73+| Formulas 012 can't read (unknown functions) | Keep their values |74+| Excel pivot tables | Come in as the values they showed; 012's go out as their results, not a pivot |75+| Excel's sheet protection | Comes in unprotected, with a note saying so: Excel's protection locks cells where 012's only warns. Protected ranges aren't written |76+| JEV functions, `#AND#`, formulas naming a sheet that doesn't exist | Go out as values (their `#REF!`, for a missing sheet: Excel would refuse the reference). The download's result counts the formulas saved as values, with an example |77+| A sheet name Excel can't take as is (spaces at its ends, or the same as another's but for them and case) | Written as one it can: without the spaces, with a number when two would clash (`Plan (2)`); formulas and named ranges naming it name that |78+| Files past the reader's limits (a zip bomb, 1 GB in one part, 2 GB in all, cells past XFD1048576) | Refused, with a message saying which |79+80+### Filters in Excel2881 2982 A sheet's filter goes out as Excel's AutoFilter over the same range, and3083 an AutoFilter comes in as a filter:@@ -48,13 +101,13 @@ grouped by year or month, and filtering by color or icon. The filter
48101 itself still comes in over its range. Filters of Excel tables (as49102 opposed to the sheet's AutoFilter) aren't read.50103 51-## Rules in XLSX104+### Rules in Excel52105 53-Conditional formats ([data.md](data.md#conditional-formatting)) go out54-as Excel's conditional formatting and data validation as its data55-validation, and come back:106+[Conditional formats](data.md#conditional-formatting) go out as Excel's107+conditional formatting, and [data validation](data.md#data-validation)108+as its data validation, and both come back:56109 57-| 012 | Excel |110+| Conditional format | Excel |58111 |---|---|59112 | Is empty, is not empty | `containsBlanks`, `notContainsBlanks` |60113 | Text contains, does not contain, starts with, ends with | `containsText`, `notContainsText`, `beginsWith`, `endsWith` |@@ -65,6 +118,9 @@ validation, and come back:
65118 | Custom formula is | `expression`, in Excel's syntax |66119 | Color scale | `colorScale` with its points (`min`, `max`, `num`, `percent`, `percentile`) |67120 | A rule's style | a differential style (dxf): bold, italic, underline, strikethrough, the text color and a solid fill |121+122+| Data validation | Excel |123+|---|---|68124 | Dropdown, from a range | `list` of the items in quotes, or of the range (`Lists!$A$1:$A$20`) |69125 | Checkbox | `list` of `TRUE,FALSE`, which comes back as a checkbox |70126 | Number, date, text length | `decimal`, `date`, `textLength` with the operator; any date as a date greater than 0 |@@ -89,10 +145,27 @@ formula with no Excel equivalent (`#AND#`) leaves its rule out, and so
89145 do list items with commas or quotes, or longer than Excel's 25590146 characters.91147 92-## The native format148+## Saving149+150+Saves are atomic: 012 writes a temporary file and renames it. Saving over151+the open file when something else wrote it since it was opened or last152+saved (another program, or another session of [012 serve](ssh.md)) asks153+first: Enter overwrites, S saves under another name, Esc cancels. Save154+as (and `:w name`, `:wq name` with vim keys) onto another file that155+exists asks the same way: Enter replaces it, Esc cancels, and cancelling156+`:wq`'s question cancels its quit too.157+158+Under `012 serve`, a session that idles out or is ended by the server159+stopping keeps its unsaved changes in `.012-recovery/` in the served160+directory, and the next session opening that file offers them back: see161+[ssh.md](ssh.md#unsaved-work).162+163+## The .012 format93164 94165 `.012` files are JSON with one line per cell, keyed by address, so diffs95-read naturally and files merge reasonably in version control:166+read naturally and files merge reasonably in version control. A cell167+without formatting is just what was typed; a formatted cell is a small168+object:96169 97170 ```json98171 {@@ -101,83 +174,65 @@ read naturally and files merge reasonably in version control:
101174 "cells": {102175 "A1": "Rent",103176 "B1": {"input":"1450","format":"currency","decimals":2},177+ "B2": {"input":"95","note":"Due on the 1st"},104178 "B3": "=SUM(B1:B2)"105179 }106180 }107181 ```108182 109-A cell without formatting is just what was typed; a formatted cell is a110-small object. Version 3 adds named ranges, frozen panes and a filter, and is111-only written when a sheet uses one of them, so older builds of 012 can open112-everything else (version 4 adds several sheets and version 5 pivot tables,113-below). Charts are an optional `charts` field that older builds114-ignore; a chart's options are fields left out at their defaults, which115-builds without them draw with the defaults, while builds that chart but116-lack a chart's type (area, scatter) can't open the file. A cell's `note`117-(the cell is then an object, e.g. `{"input":"1450","note":"Due on the 1st"}`)118-and a sheet's `protected` list of protected ranges119-(`{"range":"B2:C9","description":"Totals"}`, or `{"sheet":true}`) are120-optional too and raise no version. Saves are atomic: 012 writes a temporary file and renames it. Saving over121-the open file when something else wrote it since it was opened or last122-saved (another program, or another session of [012 serve](ssh.md)) asks123-first: Enter overwrites, S saves under another name, Esc cancels. Save124-as (and `:w name`, `:wq name` with vim keys) onto another file that125-exists asks the same way: Enter replaces it, Esc cancels, and cancelling126-`:wq`'s question cancels its quit too.127-128-Under [012 serve](ssh.md), a session that idles out or is ended by the129-server stopping keeps its unsaved changes in `.012-recovery/` in the130-served directory, and the next session opening that file offers them131-back: see [ssh.md](ssh.md#unsaved-work).183+A formatted cell has its `input` and the formatting that isn't the184+default: `format` (as `number_format` in [macros](macros.md#cells) names185+it: `currency`, `percent`, `date`, ...), `decimals`, `pattern` for a186+custom format, `bold`, `italic`, `underline`, `strikethrough` and187+`align` (`left`, `center`, `right`).132188 133-Macros are an optional `macros` list, one macro per line with its134-Starlark script as a string, and `macroOrigin`, the computer they were135-made or trusted on; neither raises the version, and older builds ignore136-them. Opening a file never runs its macros. See [macros.md](macros.md#in-the-file).189+### Versions137190 138-Other formats import as one sheet named after the file (or the SQLite139-table); CSV and TSV downloads write the sheet shown, as Sheets' do, and140-a selection of whole columns or rows downloads their data, not a million141-blank lines.191+012 writes the lowest version that holds the workbook, so older builds192+open what they can, and refuses a version newer than it knows rather193+than lose sheets or pivots. Version 1 files (cells as plain strings)194+still load.142195 143-## Size196+| Version | Written when | Adds |197+|---|---|---|198+| 2 | One sheet, using none of the below | `cells`, `widths` |199+| 3 | A sheet has named ranges, frozen panes or a filter | `names` (each name's range, `#REF!` once its cells were deleted), `freeze` (`rows`, `cols`), `filter` (its `range`, and `columns` by letter, each with `hidden` values or a `condition` and its `value`) |200+| 4 | Several sheets, or a formula naming a sheet | `sheets`, a list; see [Several sheets](#several-sheets) |201+| 5 | A pivot table | a sheet's `pivot`; see [Pivot tables](#pivot-tables) |144202 145-A sheet is 1,048,576 rows by 16,384 columns (A to XFD), as in Excel.146-Imports keep at most `max-cells` cells ([config.md](config.md), two147-million by default, about 600 MB): whole rows, as many as fit, and the148-context line says how many rows were left out, e.g. `only the first149-166,666 rows fit in max-cells (2,000,000 cells); 12,000 rows left out`.150-Data past the grid's edges is left out the same way. WK1 files keep their151-own 8,192 by 256. Pastes and fills that would write more than `max-cells`152-cells at once are refused. See [limits.md](limits.md).203+### Fields that need no version153204 154-## Column and row formats205+Builds that don't know these fields ignore them (and drop them if they206+save), so they raise no version:155207 156-Formatting whole columns or rows (select them with Ctrl+Space or157-Shift+Space, or click their headers) keeps the format on the column or158-row, as Sheets does, rather than on each of their million cells: every159-cell of it shows the format unless it has its own, and a cell typed into160-later takes it. A cell's format comes from the cell, else its row, else161-its column, else the whole sheet's (Ctrl+A twice, then a format); the162-number format and the text style fall back separately. The file keeps163-them in a `lines` field after the widths, runs of lines with the same164-formatting together, and older builds ignore it:208+| Field | On | Holds |209+|---|---|---|210+| `note` | a cell | Its [note](data.md#notes) |211+| `own` | a cell | `true` when its formatting is its own, not its column's or row's (Automatic in a currency column) |212+| `lines` | a sheet | [Column and row formats](#column-and-row-formats) |213+| `name`, `hidden` | a sheet | Its name when renamed; `true` when hidden (a file whose sheets are all hidden opens with the first one shown) |214+| `charts` | a sheet | One chart per line: `type` (`column`, `bar`, `line`, `pie`, `area`, `scatter`), `data`, `at`, `width`, `height`, `byRow`, `header`, `labels`, `title`, and options left out at their defaults: `stack` (`stacked`, `percent`), `trend`, `min`, `max`, `log`, `gridlines` (only when off), `legend` (`right`, `none`). A build that charts but lacks a chart's type refuses the file |215+| `protected` | a sheet | [Protected ranges](data.md#protected-sheets-and-ranges): `{"range":"B2:C9","description":"Totals"}`, or `{"sheet":true}` |216+| `conditionalFormats`, `validations` | a sheet | [Rules](#conditional-formats-and-data-validation), one per line |217+| `arithmetic` | the workbook | `decimal` for [decimal arithmetic](formulas.md#decimal-arithmetic) |218+| `macros`, `macroOrigin` | the workbook | Macros as Starlark scripts, and the computer they were made or trusted on: see [macros.md](macros.md#in-the-file). Opening a file never runs them |219+220+### Column and row formats221+222+[Formats of whole columns and rows](data.md#rows-and-columns) are kept223+in a `lines` field after the widths, runs of lines with the same224+formatting together; `A:XFD` is the whole sheet's format:165225 166226 ```json167227 "lines": {"A:XFD": {"italic":true}, "B:D": {"format":"currency","decimals":2}, "1:1": {"bold":true}},168228 ```169229 170-`A:XFD` is the whole sheet's format. A cell whose formatting its lines171-can't express (Automatic in a currency column) has `"own": true`.230+### Several sheets172231 173-## Several sheets174-175-A file of one sheet, whose formulas name no sheet, keeps the single-sheet176-layout above, so older builds of 012 open it; a renamed sheet adds a `name`177-field they ignore. Anything else is version 4: the sheets are a list, each178-with its name and the fields a version 3 file has at the top, and named179-ranges say their sheet. The workbook's settings (named ranges, decimal180-arithmetic, the sheet shown when saved) stay at the top:232+In version 4 the sheets are a list, each with its name and the fields a233+version 3 file has at the top, and named ranges say their sheet. The234+workbook's settings (named ranges, decimal arithmetic, the sheet shown235+when saved as `active`) stay at the top:181236 182237 ```json183238 {@@ -194,6 +249,7 @@ arithmetic, the sheet shown when saved) stay at the top:
194249 },195250 {196251 "name": "Q3 plan",252+ "hidden": true,197253 "cells": {198254 "A1": "=Budget!B1*3"199255 }@@ -202,22 +258,12 @@ arithmetic, the sheet shown when saved) stay at the top:
202258 }203259 ```204260 205-Older builds refuse version 4 files rather than lose sheets.206-207-A hidden sheet (Hide sheet on its tab) has `"hidden": true` after its208-name. It needs no version bump: builds without hidden sheets ignore the209-field and show the sheet. A file whose sheets are all hidden opens with210-the first one shown. XLSX downloads write hidden sheets hidden, and211-sheets hidden in Excel import hidden.212-213-## Pivot tables261+### Pivot tables214262 215-A workbook with a pivot table is version 5: version 4 with a `pivot`216-field, on one line after the cells of the pivot's sheet, holding its217-definition. The results are never saved; they are computed again when218-the file opens, so the file stays small and can't disagree with its219-data. Builds that know only version 4 refuse the file rather than show an220-empty sheet; a workbook without pivots is still written as version 4.263+A pivot's sheet has a `pivot` field, on one line after its cells,264+holding the definition. The results are never saved; they are computed265+again when the file opens, so the file stays small and can't disagree266+with its data.221267 222268 ```json223269 {@@ -240,11 +286,10 @@ empty sheet; a workbook without pivots is still written as version 4.
240286 `condition` with its `value`.241287 - `rowTotals` and `columnTotals` are the grand total row and column.242288 243-## Conditional formats and data validation289+### Conditional formats and data validation244290 245-A sheet's rules are two optional fields after its cells and charts, one246-rule per line; they need no version, and older builds ignore them247-(losing the rules, as they lose charts):291+A sheet's rules are two lists after its cells and charts, one rule per292+line:248293 249294 ```json250295 "conditionalFormats": [@@ -0,0 +1,56 @@
1+package sheet2+3+import (4+ "os"5+ "reflect"6+ "strings"7+ "testing"8+)9+10+// TestFileDocNamesFields checks docs/files.md names every field the .01211+// format has, and docs/macros.md a macro's, so a new field comes with its12+// documentation.13+func TestFileDocNamesFields(t *testing.T) {14+ files := readDoc(t, "../../docs/files.md")15+ macros := readDoc(t, "../../docs/macros.md")16+ seen := map[reflect.Type]bool{reflect.TypeFor[fileMacro](): true}17+ text := files18+ var walk func(reflect.Type, string)19+ walk = func(ty reflect.Type, path string) {20+ for ty.Kind() == reflect.Pointer || ty.Kind() == reflect.Slice || ty.Kind() == reflect.Map {21+ ty = ty.Elem()22+ }23+ if ty.Kind() != reflect.Struct || seen[ty] {24+ return25+ }26+ seen[ty] = true27+ for f := range ty.Fields() {28+ if f.Anonymous {29+ walk(f.Type, path)30+ continue31+ }32+ name, _, _ := strings.Cut(f.Tag.Get("json"), ",")33+ if name == "" || name == "-" {34+ continue35+ }36+ if !strings.Contains(text, "`"+name+"`") && !strings.Contains(text, `"`+name+`"`) {37+ t.Errorf("the docs don't name the field %s.%s", path, name)38+ }39+ walk(f.Type, path+"."+name)40+ }41+ }42+ for _, v := range []any{fileFormat{}, fileCell{}, fileChart{}} {43+ walk(reflect.TypeOf(v), reflect.TypeOf(v).Name())44+ }45+ text, seen = macros, map[reflect.Type]bool{}46+ walk(reflect.TypeFor[fileMacro](), "fileMacro")47+}48+49+func readDoc(t *testing.T, path string) string {50+ t.Helper()51+ data, err := os.ReadFile(path)52+ if err != nil {53+ t.Fatal(err)54+ }55+ return string(data)56+}