LibreOffice-skills libreoffice-calc
Use when creating, editing, formatting, exporting, or extracting LibreOffice Calc (.ods) spreadsheets via UNO, including session-based cell and range edits, sheets, named ranges, validation, charts, patch workflows, and snapshots.
install
source · Clone the upstream repo
git clone https://github.com/dfk1352/LibreOffice-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/dfk1352/LibreOffice-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/libreoffice-calc" ~/.claude/skills/dfk1352-libreoffice-skills-libreoffice-calc && rm -rf "$T"
manifest:
skills/libreoffice-calc/SKILL.mdsource content
LibreOffice Calc
Use the bundled
calc modules for UNO-backed Calc spreadsheet work.
All paths must be absolute. Bundled modules live under scripts/ in this
skill directory, so set PYTHONPATH=<skill_base_dir>/scripts.
If setup or runtime issues appear, check references/troubleshooting.md.
API Surface
# Non-session utilities create_spreadsheet(path, source=None) # source: path to .json or .xml file to import export_spreadsheet(path, output_path, export_format) # formats: "pdf", "xlsx", "csv" snapshot_area(doc_path, output_path, sheet="Sheet1", row=0, col=0, width=None, height=None, dpi=150) # Session (primary editing API) CalcSession(path) -> context manager CalcSession methods: read_cell(target: CalcTarget) -> dict[str, object] write_cell(target: CalcTarget, value, value_type="auto") read_range(target: CalcTarget) -> list[list[dict[str, object]]] write_range(target: CalcTarget, data) format_range(target: CalcTarget, formatting: CellFormatting) list_sheets() -> list[dict[str, object]] add_sheet(name, index=None) rename_sheet(target: CalcTarget, new_name) delete_sheet(target: CalcTarget) define_named_range(name, target: CalcTarget) get_named_range(target: CalcTarget) -> dict[str, object] delete_named_range(target: CalcTarget) set_validation(target: CalcTarget, rule: ValidationRule) clear_validation(target: CalcTarget) create_chart(target: CalcTarget, spec: ChartSpec) update_chart(target: CalcTarget, spec: ChartSpec) delete_chart(target: CalcTarget) recalculate() patch(patch_text, mode="atomic") -> PatchApplyResult export(output_path, export_format) reset() close(save=True) # Standalone patch utility patch(path, patch_text, mode="atomic") -> PatchApplyResult
Structured Targets: CalcTarget
CalcTargetfrom calc import CalcTarget CalcTarget( kind="cell" | "range" | "sheet" | "named_range" | "chart", sheet=None, sheet_index=None, row=None, col=None, end_row=None, end_col=None, name=None, index=None, )
Target kinds
| Kind | Supported fields | Use |
|---|---|---|
| or , , | Read or write one cell |
| or , , , , | Read, write, format, validate, or chart a rectangular range |
| or | Rename or delete one sheet |
| | Inspect or delete one named range |
| or , plus or | Update or delete one chart |
Resolution rules
- Coordinates are zero-based and must be non-negative.
andsheet
are mutually exclusive.sheet_index
andname
are mutually exclusive.index- Range targets must keep
andend_row >= row
.end_col >= col - Chart targets must identify one sheet plus one chart selector.
- Calc does not auto-convert a one-cell range into a cell target; keep those shapes explicit.
Cell Read Results
read_cell() and read_range() return cell dictionaries with the same shape:
{ "value": 100.0, "formula": None, "error": None, "type": "number", "raw": 100.0, }
Empty cells return
type="empty" with value=None:
{"value": None, "formula": None, "error": None, "type": "empty", "raw": None}
Formula cells use
type="formula"; when Calc reports a formula error, error
is populated and value becomes None.
Boolean values (
True/False) are stored as numeric 1.0/0.0. Reading back
a cell written with a Python bool returns type="number" with value=1.0 or
value=0.0, not a text string.
Formatting Payload: CellFormatting
CellFormattingfrom calc import CellFormatting CellFormatting( bold=None, italic=None, font_name=None, font_size=None, color=None, # named color or integer number_format=None, # "currency" | "percentage" | "date" | "time" )
Notes:
- At least one formatting field must be set.
accepts a named color orcolor
integer.0xRRGGBB
works for both aformat_range()
target and a rectangularcell
target.range
Validation Payload: ValidationRule
ValidationRulefrom calc import ValidationRule ValidationRule( type="whole", condition="between", value1=1, value2=10, show_error=True, error_message="Enter a value from 1 to 10.", show_input=True, input_title="Allowed values", input_message="Only integers from 1 to 10 are valid.", ignore_blank=True, error_style=0, )
Supported
type values:
anywholedecimaldatetimetext_lengthlist
Supported
condition values:
betweennot_betweenequalnot_equalgreater_thanless_thangreater_or_equalless_or_equal
Chart Payload: ChartSpec
ChartSpecfrom calc import CalcTarget, ChartSpec ChartSpec( chart_type="line", data_range=CalcTarget( kind="range", sheet="Data", row=0, col=0, end_row=5, end_col=1, ), anchor_row=7, anchor_col=0, width=10000, height=7000, title="Revenue Trend", )
Notes:
must be one ofchart_type
,bar
,line
, orpie
.scatter
andwidth
use Calc chart rectangle units (the same units the packaged API already accepts).height- Create charts by targeting a sheet; update or delete charts by targeting a chart.
Patch DSL
Use
patch() or session.patch() to apply ordered spreadsheet operations.
[operation] type = write_range target.kind = range target.sheet = Revenue Data target.row = 0 target.col = 0 target.end_row = 2 target.end_col = 1 data <<JSON [["Label", "Value"], ["Revenue", 100], ["Cost", 80]] JSON [operation] type = format_range target.kind = range target.sheet = Revenue Data target.row = 1 target.col = 1 target.end_row = 2 target.end_col = 1 format.number_format = currency format.bold = true [operation] type = create_chart target.kind = sheet target.sheet = Revenue Data chart.chart_type = line chart.data_range.kind = range chart.data_range.sheet = Revenue Data chart.data_range.row = 0 chart.data_range.col = 0 chart.data_range.end_row = 2 chart.data_range.end_col = 1 chart.anchor_row = 5 chart.anchor_col = 0 chart.width = 9000 chart.height = 6000 chart.title = Revenue Trend
Supported operation types
write_cellwrite_rangeformat_rangeadd_sheetrename_sheetdelete_sheetdefine_named_rangedelete_named_rangeset_validationclear_validationcreate_chartupdate_chartdelete_chartrecalculate
Patch value rules
- Use
fields for the primary target.target.* - Use
fields forformat.*
.CellFormatting - Use
fields forrule.*
.ValidationRule - Use
fields forchart.*
; chart source ranges useChartSpec
.chart.data_range.*
must be valid JSON.data- Heredoc blocks are supported with
for multiline JSON or text.<<TAG ... TAG
Modes
stops on first failure, resets the session, and persists nothing.atomic
keeps successful earlier operations and records failures.best_effort
PatchApplyResult fields:
mode
=overall_status"ok" | "partial" | "failed"
= list ofoperationsPatchOperationResultdocument_persisted
For standalone
patch(path, ...), document_persisted means the changes were
saved to disk. For session.patch(...), it means the patch produced successful
mutations in the current open session state.
Example: Build a Spreadsheet in Session
from pathlib import Path from calc import ( CalcSession, CalcTarget, CellFormatting, ChartSpec, ValidationRule, ) from calc.core import create_spreadsheet output = str(Path("test-output/revenue-report.ods").resolve()) create_spreadsheet(output) with CalcSession(output) as session: session.rename_sheet(CalcTarget(kind="sheet", sheet="Sheet1"), "Revenue Data") session.add_sheet("Summary") session.write_range( CalcTarget(kind="range", sheet="Revenue Data", row=0, col=0, end_row=2, end_col=1), [["Label", "Value"], ["Revenue", 100], ["Cost", 80]], ) session.format_range( CalcTarget(kind="range", sheet="Revenue Data", row=1, col=1, end_row=2, end_col=1), CellFormatting(number_format="currency", bold=True), ) session.define_named_range( "RevenueValues", CalcTarget(kind="range", sheet="Revenue Data", row=1, col=1, end_row=2, end_col=1), ) session.set_validation( CalcTarget(kind="range", sheet="Revenue Data", row=1, col=1, end_row=2, end_col=1), ValidationRule(type="whole", condition="greater_than", value1=0), ) session.create_chart( CalcTarget(kind="sheet", sheet="Revenue Data"), ChartSpec( chart_type="line", data_range=CalcTarget( kind="range", sheet="Revenue Data", row=0, col=0, end_row=2, end_col=1, ), anchor_row=5, anchor_col=0, width=9000, height=6000, title="Revenue Trend", ), ) session.recalculate()
Example: Patch an Existing Spreadsheet
from calc import patch result = patch( "/abs/path/revenue-report.ods", """ [operation] type = write_cell target.kind = cell target.sheet = Summary target.row = 1 target.col = 1 value = Ready value_type = text [operation] type = format_range target.kind = cell target.sheet = Summary target.row = 1 target.col = 1 format.bold = true [operation] type = recalculate """, mode="best_effort", ) print(result.overall_status)
Snapshots
from pathlib import Path from calc import snapshot_area result = snapshot_area(doc_path, "/tmp/revenue.png", sheet="Revenue Data", row=0, col=0, dpi=150) print(result.file_path, result.width, result.height) Path(result.file_path).unlink(missing_ok=True) # clean up used snapshots
Use snapshots to verify chart placement, formatting, and sheet layout before delivery.
output dimensions can differ slightly from the requested size; treat small pixel differences as normal.snapshot_area()
Common Mistakes
- Passing a relative path; UNO-facing Calc APIs expect absolute file paths.
- Mixing up
andcell
targets; Calc keeps them distinct even for one-cell selections.range - Using one-based coordinates; rows and columns are zero-based.
- Assuming
picks a random later target name; whencreate_chart()
is set, targeting the chart by that same name is the safest follow-up pattern.title - The packaged API does not expose chart-listing or validation-inspection helpers; deeper verification may require direct UNO inspection in advanced workflows.
- Forgetting
fields when patching chart operations.chart.data_range.* - Expecting exact requested PNG dimensions from
; Calc export can differ by a small amount.snapshot_area() - Forgetting to clean up captured snapshots after inspection.
- Calling session methods after
.session.close() - JSON / XML import requires LibreOffice 26.2+.