Releases: antnardo/odsslicer
Release list
0.14.1
The four issues left open in 0.14.0, all fixed with LibreOffice as the oracle.
Fixed
- Deleting the row or the column a chart, a shape or an image is anchored in no longer deletes it (#24): it moves into the cell taking its place, at the same address, as LibreOffice keeps it. A frame anchored inside a table went with the cell it hung from, its object left in the file with nothing pointing at it.
- A row formatted as a whole keeps its formatting through a save (#23) — no edit was needed to lose it, reading the sheet was enough. Checked against LibreOffice on its own file: 5 yellow cells left in a whole-yellow row before, 16,383 now.
- Inserted columns take the width of the column they push right (#25), its visibility and its default cell style too, where they took the default width.
- A
{...}placeholder that is not a row or column number is refused (#26) instead of being dropped with its braces:'"{"&A1&"}"'was silently stored asof:="&A1&", a different formula. The message points at the doubling that writes a literal brace.
998 tests, mypy and ruff pass; 240 real .ods files swept against 0.14.0 with no difference in what is read, no grid deviation and no wrong write.
Full changelog: https://github.com/antnardo/odsslicer/blob/master/CHANGELOG.md
0.14.0
Eleven problems found testing 0.13.2 on real LibreOffice files, and the braces bug it shipped with.
Fixed
- What the rows below the data hold is no longer deleted when the sheet grows or rows are inserted (#10) — a chart, a shape or a note alone in the last row went out of the file at the first edit. A report template lost the chart anchored below its table as soon as a day was added.
- Edits no longer rewrite a formula holding braces (#22), present since 0.13.2: a LaTeX formula lost its braces, and
="{"&A6&"}"became"&[.A7]&". - Charts follow insertions, deletions and
rename_sheet(#15) — their ranges and their end cell. - A note counts as content, as in LibreOffice: the grid reaches one on an empty cell, and a plain save no longer deletes a note far to the right of the data.
cell.valuetakes what numpy and pandas hand out (#11): numpy integers, booleans,datetime64,timedelta64anddecimal.DecimalraisedTypeError. A NaN now leaves the cell empty instead of being written asoffice:value="nan", which LibreOffice counted as 0 in sums.- Deleted references become
#REF!, grouped column definitions are counted, references to other sheets are followed as LibreOffice writes them, long dates and fractions of a second read right (#12, #13, #14, #16).
Added
NumberFormat.create(..., elapsed=True)makes LibreOffice's[HH]:MM, where 26 hours show as 26:00 (#17).
Changed
- A duration in a format counting time in full reads as a
timedelta, whatever its length (#20), so a timesheet column no longer mixestimeandtimedelta. - Writing into a merge's master cell keeps the merge (#19), as typing into it does in LibreOffice.
odsslicer.__all__names the public API, the code is in line with the project's ruff rules, and CI checks them.- The three deviations from LibreOffice left open are documented (#23, #24, #25).
Measured on 500 real .ods files: no grid deviation, no wrong write or insertion, nothing lost — where 0.13.2 made 99 wrong insertions and lost 4 drawings.
Full changelog: https://github.com/antnardo/odsslicer/blob/master/CHANGELOG.md
0.13.2
Bug-fix release.
Fixed
recalculate()no longer has LibreOffice copy its result over the workbook in place (#9), and neither doessave(recalculate=True). LibreOffice saved the recalculated workbook to a temporary file of its own, then copied it over the workbook: a program reading the workbook meanwhile could get a partial zip (BadZipFile), and a copy stopped midway could leave it unreadable, assave()could until 0.13.0. LibreOffice now saves its result to a temporary file in the workbook's folder, which is renamed over the workbook once LibreOffice reports the save complete: a reader gets the old version or the new one, and a failed run leaves the workbook as it was, with no temporary file behind. LibreOffice still loads the workbook where it is, so that its links resolve and its folder is trusted as before, and saves it in its own format. The workbook keeps its permissions; a read-only one now raisesPermissionErrorbefore LibreOffice starts, where it gaveRuntimeError.- A timeout or Ctrl-C now stops LibreOffice itself, on macOS and Linux.
recalculate()killed the process it had started, which is often a wrapper — Homebrew'ssofficeis a shell script that runs the real one — and LibreOffice carried on: it rewrote the workbook seconds afterrecalculate()had raisedRuntimeError. LibreOffice now runs in a process group of its own, killed as a whole, and the lock files it leaves next to the workbook are deleted. - When the recalculation script fails inside LibreOffice,
RuntimeErrorgives its traceback, where it said that the script apparently didn't execute.
Full changelog: https://github.com/antnardo/odsslicer/blob/master/CHANGELOG.md
0.13.1
Bug-fix release.
Fixed
- Writing past the columns of a run of repeated rows no longer corrupts the run (#5). LibreOffice stores a run of identical rows, empty ones included, as one repeated element. Writing a range past the sheet's width inside such a run saved its first value onto every row of the run and the next ones further right, one column in two —
D2:D4 = [[1], [2], [3]]gave D = 1 and F = 2 on all of rows 2 to 6 — while the sheet read back as expected until the save. Growing the sheet now widens the run's element once, and writing unrolls it first, as it already did within the sheet's width. delete_columnno longer raisesValueErroron a run of repeated rows whose cells are repeated too, which is how LibreOffice writes empty ones.- On a sheet ending in a run of repeated empty rows, writing inside the run no longer raises
IndexError, and writing below it no longer lands one row too low. - Empty rows and columns between data no longer shift the cells after them (#6). Loading left out of the grid any run of more than 10 empty columns, and any repeated element of more than 1,000 empty rows, wherever they stood: with data in A1 and Z1 alone,
sheet["B1"]read Z1's value andsheet["Z1"]readNone, and writing to Z1 landed in AX1. Only padding after the data is left out now, sosheet.sizegrows for sheets with such gaps. It shrinks for sheets with empty rows past the padding, a million rows down, which the grid showed just below the data: writing there landed at the bottom of the sheet. - Writing past the data no longer lands beyond the formatted columns that follow it. LibreOffice writes empty cells as far as the last formatted column. Past 10 of them, the grid left them out but the file kept them, and new cells went after them: with 15,
sheet["D1"]landed in S1. The sheet now grows into those cells, keeping their formatting. - Writing into a merged cell whose range runs into columns left out of the grid — Excel merges notes across empty columns past the data — no longer raises
IndexError; nor does undoing such a merge, or deleting or inserting rows or columns through it. - A date or time written into a cell with no number format no longer shows as a number (#7). It was stored with its type but no format: past the sheet's declared columns, where it grows, LibreOffice showed 2022-03-07 as 44627, and within them a duration of 128:45 as 08:45:00. Such a cell now gets the format LibreOffice would give the value if typed there, the standard one of the document's locale:
07/03/22,07/03/22 13:45and09:30:00in a fr-FR document,03/07/22,03/07/22 01:45 PMand09:30:00 AMin en-US,[HH]:MM:SSfor a duration everywhere. The formats of 67 locales are built in, taken from LibreOffice 25.8; any other locale, or a document with no language, gets ISO 8601. A cell that has a number format keeps it, as in LibreOffice, and the displayed text of a date or time now follows the cell's own format first, before an example taken from another cell. - A cell with no style of its own resolves to its column's default cell style, where LibreOffice keeps the formatting of a column formatted as a whole:
cell.stylereads it, setting a style property on such a cell carries the column's formatting over instead of dropping it, background and number format included, andSheet.copyandSheet.sortcarry it along. - A time format with AM/PM showed 13:45 as
13:45 PMin the displayed text odsslicer writes: it now counts the hours on a 12-hour clock,01:45 PM. - The displayed text of a date in a format LibreOffice takes from the system (
number:format-source="language", as it saves a column in the system's short date) is no longer rendered from that format's placeholder elements —7/3/22where LibreOffice shows07/03/2022on a French macOS — but taken from another cell's, else written in ISO 8601.
Full changelog: https://github.com/antnardo/odsslicer/blob/master/CHANGELOG.md
0.13.0
Feature and bug-fix release.
Added
recalculate(path, update_links=True), andsave(..., recalculate=True, update_links=True), have LibreOffice update the workbook's links before recalculating: a formula reading another workbook, written out or built byINDIRECT(), computes instead of coming back asErr:540, and from that file as it is now rather than from values LibreOffice saved from it earlier. Opt-in, for workbooks you trust: it lets a workbook read any file and fetch any URL its formulas name, which is what LibreOffice's own check guards against. For the run, the throwaway profile trusts the workbook's folder, and the workbook's macros stay disabled.save()raisesValueErrorforupdate_links=Truewithoutrecalculate=True; withoutupdate_links, nothing changes.
Fixed
save()no longer truncates the workbook before rewriting it (#8). It wrote the new zip straight into its target, the source file by default: a program reading the file meanwhile got a partial zip (BadZipFile), and a save stopped midway — an exception, Ctrl-C, a crash — left the workbook unreadable, its previous version gone. The new file is now written under a temporary name in the same folder, flushed to disk, then renamed over the target withos.replace(): a reader gets the old version or the new one, and an interrupted save leaves the old one as it was, with no temporary file behind. The saved file keeps the permissions of the one it replaces, a symbolic link is followed, and a read-only file still raisesPermissionError. Saving now needs write access to the folder, and another hard link to the old file keeps the old content. Withrecalculate=True, LibreOffice's own save, which follows, still copies its result over the file in place.
Documentation
- Recalculating from LibreOffice's command line (DOCS.md, section 7):
soffice --headless --convert-to ods, how to force a full recalculation on load (the profile's Recalculation on File Load setting,ODFRecalcMode, set through a throwaway-env:UserInstallationprofile), why the result has to be written elsewhere and moved back (converting onto the input fails, yetsofficeexits with status 0), and when to preferrecalculate(), which also refreshes pivot tables. - References to other workbooks come back as
Err:540after a headless recalculation that does not update links — andINDIRECT()ones even after a plain conversion, since volatile formulas are recomputed on every load. No command-line option or configuration setting lifts it:--convert-tonever allows link updates. What does, and is now documented: loading the workbook withUpdateDocMode=FULL_UPDATEfrom a folder the profile trusts, spelled as LibreOffice spells URLs, and dispatching.uno:UpdateTableLinksbefore recalculating — whatupdate_links=Truedoes.
Full changelog: https://github.com/antnardo/odsslicer/blob/master/CHANGELOG.md
0.12.3
Bug-fix release.
Fixed
- A date-time cell, or a duration of 24 hours or more, no longer makes its whole sheet unreadable (#4). Dates and times were parsed with fixed patterns that took nothing but a bare date and a whole-second duration under a day, so loading a sheet holding
30/11/2023 13:00,128:45:00, a negative duration or a fraction of a second raisedValueError— even forsheet["A1"]. A date-time now reads as adatetime.datetime, and a duration as adatetime.timewhen it fits in a day, fractional seconds included, or as adatetime.timedeltaotherwise. Everything that read before reads the same. - Date-times and durations can be written:
cell.valueacceptsdatetime.datetimeanddatetime.timedelta, which raisedTypeError— so neither couldSheet.copynorSheet.sortmove such a cell — and stores them as LibreOffice does. Adatetime.timekeeps its microseconds, which were dropped.Sheet.sortorders a column mixing dates and date-times, or times and durations. - A value that cannot be read as its declared type falls back to its displayed text instead of failing the sheet (a malformed date, a duration counted in months, an unknown value type): one
UserWarningper sheet names such cells, andcell.raw_valuekeeps the value as written. - Writing a date into a cell whose format shows a time no longer raises
AttributeErrorwhen no other cell shows how to display it: it shows as its midnight. - The source distribution no longer ships the
rsc/folder (the wheels were never affected).setuptools-scmpacks every tracked file, and that folder held development references — the OASIS OpenDocument 1.2 specification (two PDFs, under OASIS copyright rather than the package's MIT license), a blank LibreOffice document and raw XML notes — enough to take the 0.12.2 sdist to 4.3 MB instead of about 300 KB. Neither the package nor the tests used it.
Full changelog: https://github.com/antnardo/odsslicer/blob/master/CHANGELOG.md
0.12.2
Bug-fix release.
Fixed
- Reading every cell of an empty sheet no longer crashes (#3). A selection holding no cells at all (
sheet[:, :]on a sheet with no rows) was mistaken for a single cell, so.to_list()raisedAttributeError: 'list' object has no attribute 'value'— hit by any plain scan over a workbook that happens to contain one empty sheet. Such a selection is now a zero-length row:.to_list()returns[],.sizeis(0,), and writing to it does nothing.
Full changelog: https://github.com/antnardo/odsslicer/blob/master/CHANGELOG.md
0.12.1
Bug-fix release.
Fixed
- A cell holding several lines no longer reads back as its first line only. ODF stores a multi-line cell (Ctrl+Enter in a spreadsheet) as one
<text:p>per line;cell.text— and a stringcell.value— only looked at the first one, silently dropping the rest of what the spreadsheet displays. All the paragraphs are now joined with\n, a comment's own paragraphs still excluded. - Writing a value containing
\nnow writes one paragraph per line, the way applications do: a literal newline inside a single paragraph is plain whitespace to ODF and would not survive a round trip through a stricter reader. Rewriting a cell with fewer lines drops the paragraphs left over.
Full changelog: https://github.com/antnardo/odsslicer/blob/master/CHANGELOG.md
0.12.0
Row and column insertion.
Added
Sheet.insert_rows(row, count=1)/insert_row(row)andSheet.insert_columns(col, count=1)/insert_column(col)— insert blank rows or columns, like a spreadsheet's "insert rows above": formula references anywhere in the document follow the cells they point at (a range straddling the insertion point stretches), a merge straddling it grows, and column widths stay with their columns. On files that declare the full application grid through filler rows/columns (LibreOffice, Excel), the filler is given back so the document never exceeds the maximum grid size.
Changed
- The formula-reference rewriting behind
delete_rows/delete_columnnow shares one implementation with insertion (no behaviour change).
Full changelog: https://github.com/antnardo/odsslicer/blob/master/CHANGELOG.md
0.11.1
Bug-fix release.
Fixed
- Styling a cell twice no longer loses the first properties in the spreadsheet (#1). Forking a cell its own private style linked back to the previous one through
style:parent-style-name— but only a named style (fromstyles.xml) is an addressable ancestor: LibreOffice ignores an automatic parent and falls back toDefault, so borders, bold, alignment and the rest silently disappeared on screen, whileodsslicer— resolving the chain itself — still read them back. Forks now carry the resolved properties (and the number format) in their own XML, and only link to a genuinely named ancestor. Files written by 0.9–0.11 are repaired cell by cell as soon as a style is written to them again.
Added
benchmarks/compare_readers.pyand a "How it compares to other readers" table in DOCS.md: measured read-speed/memory comparison againstodfdoandpython-calamineon a purely numeric matrix — quantifying the README's advice that pure bulk reading ispython-calamine's territory, not ours.
Full changelog: https://github.com/antnardo/odsslicer/blob/master/CHANGELOG.md