open-document-lib 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- open_document_lib-1.0.0/LICENSE +21 -0
- open_document_lib-1.0.0/PKG-INFO +194 -0
- open_document_lib-1.0.0/README.md +468 -0
- open_document_lib-1.0.0/docs/library-api.md +154 -0
- open_document_lib-1.0.0/odf_lib/__init__.py +108 -0
- open_document_lib-1.0.0/odf_lib/citation_mapping.py +240 -0
- open_document_lib-1.0.0/odf_lib/odf_common.py +1625 -0
- open_document_lib-1.0.0/odf_lib/py.typed +0 -0
- open_document_lib-1.0.0/open_document_lib.egg-info/PKG-INFO +194 -0
- open_document_lib-1.0.0/open_document_lib.egg-info/SOURCES.txt +40 -0
- open_document_lib-1.0.0/open_document_lib.egg-info/dependency_links.txt +1 -0
- open_document_lib-1.0.0/open_document_lib.egg-info/requires.txt +16 -0
- open_document_lib-1.0.0/open_document_lib.egg-info/top_level.txt +1 -0
- open_document_lib-1.0.0/pyproject.toml +75 -0
- open_document_lib-1.0.0/setup.cfg +4 -0
- open_document_lib-1.0.0/tests/test_benchmarks.py +35 -0
- open_document_lib-1.0.0/tests/test_citations.py +402 -0
- open_document_lib-1.0.0/tests/test_corpus.py +141 -0
- open_document_lib-1.0.0/tests/test_cross_refs.py +391 -0
- open_document_lib-1.0.0/tests/test_dao_template.py +114 -0
- open_document_lib-1.0.0/tests/test_docs.py +53 -0
- open_document_lib-1.0.0/tests/test_edge_cases.py +306 -0
- open_document_lib-1.0.0/tests/test_examples.py +68 -0
- open_document_lib-1.0.0/tests/test_flat_odf.py +233 -0
- open_document_lib-1.0.0/tests/test_footnotes.py +309 -0
- open_document_lib-1.0.0/tests/test_install.py +105 -0
- open_document_lib-1.0.0/tests/test_lib_odf_common.py +563 -0
- open_document_lib-1.0.0/tests/test_libreoffice_integration.py +102 -0
- open_document_lib-1.0.0/tests/test_math.py +199 -0
- open_document_lib-1.0.0/tests/test_meta_lifecycle.py +142 -0
- open_document_lib-1.0.0/tests/test_odg_connectors.py +159 -0
- open_document_lib-1.0.0/tests/test_odg_gluepoints.py +133 -0
- open_document_lib-1.0.0/tests/test_odg_groups.py +171 -0
- open_document_lib-1.0.0/tests/test_odp_animations.py +229 -0
- open_document_lib-1.0.0/tests/test_odp_master.py +120 -0
- open_document_lib-1.0.0/tests/test_odp_transitions.py +141 -0
- open_document_lib-1.0.0/tests/test_ods_charts.py +271 -0
- open_document_lib-1.0.0/tests/test_ods_named_ranges.py +196 -0
- open_document_lib-1.0.0/tests/test_ods_validation.py +184 -0
- open_document_lib-1.0.0/tests/test_property.py +139 -0
- open_document_lib-1.0.0/tests/test_schema_validation.py +165 -0
- open_document_lib-1.0.0/tests/test_smoke.py +100 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patrick Leiverkus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: open-document-lib
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Standard-library toolkit for reading, editing, and writing OpenDocument Format files (ODT, ODP, ODS, ODG)
|
|
5
|
+
Author: Patrick Leiverkus
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/leiverkus/open-document-skills
|
|
8
|
+
Project-URL: Repository, https://github.com/leiverkus/open-document-skills
|
|
9
|
+
Project-URL: Changelog, https://github.com/leiverkus/open-document-skills/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Issues, https://github.com/leiverkus/open-document-skills/issues
|
|
11
|
+
Keywords: opendocument,odf,odt,ods,odp,odg,libreoffice,xml,documents,spreadsheets
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Office/Business
|
|
22
|
+
Classifier: Topic :: Text Processing :: Markup :: XML
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Provides-Extra: odf
|
|
28
|
+
Requires-Dist: odfpy>=1.4.0; extra == "odf"
|
|
29
|
+
Provides-Extra: scholarly
|
|
30
|
+
Requires-Dist: bibtexparser>=1.4; extra == "scholarly"
|
|
31
|
+
Provides-Extra: validate
|
|
32
|
+
Requires-Dist: lxml>=4.9; extra == "validate"
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff>=0.9; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
37
|
+
Requires-Dist: hypothesis>=6.0; extra == "dev"
|
|
38
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# open-document-lib
|
|
42
|
+
|
|
43
|
+
A standard-library toolkit for reading, editing, and writing **OpenDocument
|
|
44
|
+
Format** files — text documents (`.odt`), presentations (`.odp`),
|
|
45
|
+
spreadsheets (`.ods`), and drawings (`.odg`) — plus their flat (single-XML)
|
|
46
|
+
variants.
|
|
47
|
+
|
|
48
|
+
`open-document-lib` is the shared library behind the
|
|
49
|
+
[open-document-skills](https://github.com/leiverkus/open-document-skills)
|
|
50
|
+
agent skills. The core has **no dependencies** beyond the Python standard
|
|
51
|
+
library; a few helpers opt into `lxml` or `bibtexparser` when present.
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install open-document-lib
|
|
57
|
+
|
|
58
|
+
# optional extras
|
|
59
|
+
pip install open-document-lib[validate] # lxml — RelaxNG schema validation
|
|
60
|
+
pip install open-document-lib[scholarly] # bibtexparser — BibTeX citation ingest
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Requires Python 3.10+. The package ships `py.typed`, so type checkers see
|
|
64
|
+
its annotations.
|
|
65
|
+
|
|
66
|
+
## Quick start
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from pathlib import Path
|
|
70
|
+
from xml.etree import ElementTree as ET
|
|
71
|
+
from odf_lib import (
|
|
72
|
+
parse_xml_from_zip, xml_bytes, write_odf_with_replacements,
|
|
73
|
+
replace_text_in_element, update_meta_for_edit,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
src = Path("report.odt")
|
|
77
|
+
content = parse_xml_from_zip(src, "content.xml")
|
|
78
|
+
|
|
79
|
+
# Structure-preserving find/replace across the document body.
|
|
80
|
+
text_ns = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"
|
|
81
|
+
for para in content.iter(f"{{{text_ns}}}p"):
|
|
82
|
+
replace_text_in_element(para, "{{CLIENT}}", "ACME GmbH")
|
|
83
|
+
|
|
84
|
+
# Stamp the edit into meta.xml (modification date, generator, cycle count).
|
|
85
|
+
meta = parse_xml_from_zip(src, "meta.xml")
|
|
86
|
+
# update_meta_for_edit needs a namespace map + qualified-name helper;
|
|
87
|
+
# the skills' *_common.py wrappers supply these.
|
|
88
|
+
|
|
89
|
+
write_odf_with_replacements(
|
|
90
|
+
src, Path("report-out.odt"),
|
|
91
|
+
{"content.xml": xml_bytes(content)},
|
|
92
|
+
"application/vnd.oasis.opendocument.text",
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Flat-ODF round-trip:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from odf_lib import pack_flat_odf, unpack_flat_odf
|
|
100
|
+
|
|
101
|
+
pack_flat_odf(Path("deck.odp"), Path("deck.fodp")) # ZIP → single XML
|
|
102
|
+
unpack_flat_odf(Path("deck.fodp"), Path("deck.odp")) # XML → ZIP package
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API reference
|
|
106
|
+
|
|
107
|
+
Everything below is exported directly from the `odf_lib` package and is
|
|
108
|
+
covered by semantic versioning from 1.0 onward. Anything in
|
|
109
|
+
`odf_lib.odf_common` that is **not** listed here (notably `_`-prefixed
|
|
110
|
+
helpers) is internal and may change without notice.
|
|
111
|
+
|
|
112
|
+
### Constants
|
|
113
|
+
|
|
114
|
+
| Name | Description |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `VERSION` | Library version string (also `odf_lib.__version__`). |
|
|
117
|
+
| `ODF_NAMESPACES` | `dict[str, str]` of ODF namespace prefixes → URIs. |
|
|
118
|
+
| `FLAT_EXTENSIONS` | Mapping of ODF mimetype → flat-file extension (`.fodt`, …). |
|
|
119
|
+
|
|
120
|
+
### ZIP / XML core
|
|
121
|
+
|
|
122
|
+
| Signature | Description |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `parse_xml_from_zip(path, member) -> ET.Element` | Parse one XML member of an ODF ZIP. |
|
|
125
|
+
| `xml_bytes(root) -> bytes` | Serialize an element to UTF-8 bytes with XML declaration. |
|
|
126
|
+
| `write_odf_with_replacements(input_path, output_path, replacements, mimetype_value) -> None` | Copy an ODF package, swapping named members; `mimetype` stays first and stored. |
|
|
127
|
+
| `pack_dir_as_odf(source_dir, output_path, mimetype_value) -> None` | Repack an extracted directory into a valid ODF file. |
|
|
128
|
+
| `copy_into_package(input_path, output_path, package_path, source, replacements, mimetype_value) -> None` | Add a single file to a package plus member replacements. |
|
|
129
|
+
| `copy_with_multiple_members(input_path, output_path, new_members, replacements, mimetype_value) -> None` | Add several new members in one pass (e.g. `Object N/` sub-packages). |
|
|
130
|
+
| `unpack_to_temp(path) -> tempfile.TemporaryDirectory` | Extract a package to a managed temp directory. |
|
|
131
|
+
|
|
132
|
+
### Manifest and media
|
|
133
|
+
|
|
134
|
+
| Signature | Description |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `ensure_manifest_entry(manifest_root, full_path, media_type, ns, q_fn) -> None` | Add or update a `manifest:file-entry`. |
|
|
137
|
+
| `media_type_for(path) -> str` | MIME type from a file extension. |
|
|
138
|
+
| `sniff_image_mime(path) -> str` | MIME type from magic bytes, with extension fallback. |
|
|
139
|
+
| `unique_picture_name(existing, image) -> str` | Collision-free `Pictures/` filename. |
|
|
140
|
+
| `unique_object_name(existing) -> str` | Next free `Object N` sub-package name. |
|
|
141
|
+
|
|
142
|
+
### Metadata
|
|
143
|
+
|
|
144
|
+
| Signature | Description |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `update_meta_for_edit(meta_root, ns, q_fn) -> None` | Refresh `meta:modification-date`/`generator` and bump `editing-cycles`. |
|
|
147
|
+
|
|
148
|
+
### Flat ODF
|
|
149
|
+
|
|
150
|
+
| Signature | Description |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `pack_flat_odf(input_zip, output_flat) -> None` | Convert a zipped ODF to flat single-XML form (pictures and `Object N/` sub-packages inlined). |
|
|
153
|
+
| `unpack_flat_odf(input_flat, output_zip) -> None` | Convert a flat ODF back to a zipped package and rebuild the manifest. |
|
|
154
|
+
|
|
155
|
+
### Text walker, locator, insertion
|
|
156
|
+
|
|
157
|
+
| Signature | Description |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `replace_text_in_element(element, old, new) -> int` | Structure-preserving find/replace across text and child tails. |
|
|
160
|
+
| `replace_pattern_with_element_in_element(element, pattern, factory) -> int` | Replace regex matches with generated elements. |
|
|
161
|
+
| `find_text_position_in_element(element, needle) -> tuple \| None` | Locate `needle`, returning `(node, "text"\|"tail", offset)`. |
|
|
162
|
+
| `insert_after_text_in_element(element, anchor, new_element) -> bool` | Splice an element in right after an anchor string. |
|
|
163
|
+
| `insert_in_paragraph(paragraph, position, new_element) -> None` | Insert at the `start` or `end` of a paragraph. |
|
|
164
|
+
| `wrap_text_with_pair_in_element(element, start_anchor, end_anchor, start_element, end_element) -> bool` | Wrap an intra-paragraph text range with a start/end pair. |
|
|
165
|
+
| `wrap_text_across_elements(elements, start_anchor, end_anchor, start_element, end_element) -> bool` | Same, spanning multiple paragraphs. |
|
|
166
|
+
| `ensure_sequence_declarations(text_root, names, ns) -> None` | Ensure `text:sequence-decl` entries exist. |
|
|
167
|
+
| `clear_children(element) -> None` | Remove all children of an element. |
|
|
168
|
+
| `local_name(tag) -> str` | Local name from a Clark-notation tag. |
|
|
169
|
+
|
|
170
|
+
### Styles and pictures
|
|
171
|
+
|
|
172
|
+
| Signature | Description |
|
|
173
|
+
|---|---|
|
|
174
|
+
| `inject_styles_from_file(input_path, styles_path, output_path, mimetype_value) -> list[str]` | Replace `styles.xml`; returns dangling style references. |
|
|
175
|
+
| `embed_pictures(input_path, pictures, output_path, mimetype_value, ns, q_fn) -> None` | Bulk-add images to `Pictures/` and the manifest. |
|
|
176
|
+
|
|
177
|
+
### Schema validation
|
|
178
|
+
|
|
179
|
+
| Signature | Description |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `ensure_schema(name) -> Path` | Download and cache an OASIS ODF 1.3 RelaxNG schema (`content`/`manifest`). |
|
|
182
|
+
| `validate_against_schema(xml_bytes_input, schema_name) -> tuple[bool, list[str]]` | Validate XML bytes against a cached schema (requires `lxml`). |
|
|
183
|
+
|
|
184
|
+
### External tooling
|
|
185
|
+
|
|
186
|
+
| Signature | Description |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `find_soffice() -> str` | Locate the LibreOffice `soffice` binary; raises if absent. |
|
|
189
|
+
| `find_pandoc() -> str \| None` | Locate the `pandoc` binary. |
|
|
190
|
+
| `latex_to_mathml(latex) -> bytes` | Convert a LaTeX snippet to MathML via Pandoc. |
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT. See [LICENSE](https://github.com/leiverkus/open-document-skills/blob/main/LICENSE).
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# Open Document Skills
|
|
2
|
+
|
|
3
|
+
[](https://github.com/leiverkus/open-document-skills/actions/workflows/tests.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://github.com/leiverkus/open-document-skills/releases)
|
|
6
|
+
|
|
7
|
+
**Native ODT / ODP / ODS / ODG generation and editing for agents — no DOCX round-trips, no LibreOffice dependency for the core path.**
|
|
8
|
+
|
|
9
|
+
Four self-contained skills for Codex, Claude Code, and OpenCode that teach an agent to create, inspect, and edit OpenDocument files directly via Python (stdlib only). Edits preserve inline structure (`text:span`, `text:note`, `text:bookmark`, `text:a`), `meta.xml` is updated on every save, and flat single-XML formats (`.fodt`/`.fodp`/`.fods`/`.fodg`) give you Git-friendly diffs. LibreOffice is optional and only needed for rendering, recalculation, and PDF export.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Generate, edit, validate, version — all from the agent shell:
|
|
13
|
+
python skills/odt/scripts/create_minimal_odt.py spec.json doc.odt
|
|
14
|
+
python skills/odt/scripts/replace_text.py doc.odt "{{NAME}}" "Patrick" -o out.odt
|
|
15
|
+
python skills/odt/scripts/pack_fodt.py out.odt -o out.fodt # diff-friendly XML
|
|
16
|
+
python skills/odt/scripts/validate_refs.py out.odt
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Skills at a glance
|
|
20
|
+
|
|
21
|
+
| Skill | LibreOffice app | Smithery | Triggers |
|
|
22
|
+
| --- | --- | --- | --- |
|
|
23
|
+
| [`odt`](skills/odt) | Writer | [smithery.ai/skills/leiverkus/odt](https://smithery.ai/skills/leiverkus/odt) | edit ODT, footnotes, citations (BibTeX/CSL-JSON), bookmarks, cross-references, figure/table sequences, MathML formulas, render to PDF |
|
|
24
|
+
| [`odp`](skills/odp) | Impress | [smithery.ai/skills/leiverkus/odp](https://smithery.ai/skills/leiverkus/odp) | clone slide, edit notes, add image, animations (entrance/exit/emphasis/motion), slide transitions, master-page customization (background, header/footer, logo), render deck |
|
|
25
|
+
| [`ods`](skills/ods) | Calc | [smithery.ai/skills/leiverkus/ods](https://smithery.ai/skills/leiverkus/ods) | set cells/formulas, named ranges, dropdowns + data validation, embedded charts (bar/line/pie/scatter), export CSV, recalculate |
|
|
26
|
+
| [`odg`](skills/odg) | Draw | [smithery.ai/skills/leiverkus/odg](https://smithery.ai/skills/leiverkus/odg) | edit labels, add shape image, glue points, connectors with shape binding, groups, flowcharts, org charts, export SVG/PNG |
|
|
27
|
+
|
|
28
|
+
## Why use these
|
|
29
|
+
|
|
30
|
+
- **Native ODF, not converted from DOCX.** No font drift, no lost styles, no PDF round-trips.
|
|
31
|
+
- **Stdlib-only core.** Every generator, validator, and edit script runs without `pip install` — `xml.etree.ElementTree` and `zipfile` only. LibreOffice is needed only for rendering and recalculation.
|
|
32
|
+
- **Structure-preserving edits.** `replace_text` keeps footnotes, hyperlinks, and inline formatting intact. `add_image` updates the manifest and `meta.xml`. `replace_cells` handles typed values and formulas.
|
|
33
|
+
- **Audit-friendly.** Every edit writes `meta:modification-date`, `meta:generator`, and increments `meta:editing-cycles`. Pack to `.fodt` and `git diff` works.
|
|
34
|
+
- **Tested.** 76 unit + integration tests run on every push; CI installs LibreOffice so the render/recalc paths are exercised too.
|
|
35
|
+
|
|
36
|
+
## What this is not
|
|
37
|
+
|
|
38
|
+
Not a LibreOffice replacement. Not a substitute for full ODF feature coverage (tracked changes, complex TOCs, Impress animations, Calc pivots, Draw glue points, RelaxNG schema validation are explicit non-goals — see [Current Limits](#current-limits)). The goal is to make the 80% of ODF automation that agents need safe, repeatable, and dependency-light.
|
|
39
|
+
|
|
40
|
+
## Repository Layout
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
skills/
|
|
44
|
+
odt/
|
|
45
|
+
SKILL.md
|
|
46
|
+
scripts/
|
|
47
|
+
odp/
|
|
48
|
+
SKILL.md
|
|
49
|
+
scripts/
|
|
50
|
+
ods/
|
|
51
|
+
SKILL.md
|
|
52
|
+
scripts/
|
|
53
|
+
odg/
|
|
54
|
+
SKILL.md
|
|
55
|
+
scripts/
|
|
56
|
+
tests/
|
|
57
|
+
fixtures/
|
|
58
|
+
test_smoke.py
|
|
59
|
+
test_edge_cases.py
|
|
60
|
+
test_libreoffice_integration.py
|
|
61
|
+
scripts/
|
|
62
|
+
install_skills.py
|
|
63
|
+
.claude-plugin/
|
|
64
|
+
plugin.json
|
|
65
|
+
examples/
|
|
66
|
+
README.md
|
|
67
|
+
build_examples.py
|
|
68
|
+
*.json
|
|
69
|
+
docs/
|
|
70
|
+
index.md
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Each skill is MIT-licensed and also contains its own `LICENSE.txt`.
|
|
74
|
+
|
|
75
|
+
## Documentation
|
|
76
|
+
|
|
77
|
+
Detailed documentation lives in [docs/index.md](docs/index.md):
|
|
78
|
+
|
|
79
|
+
- [Installation](docs/installation.md)
|
|
80
|
+
- [Agent Compatibility](docs/agent-compatibility.md)
|
|
81
|
+
- [OpenDocument Workflows](docs/workflows.md)
|
|
82
|
+
- [Script Reference](docs/script-reference.md)
|
|
83
|
+
|
|
84
|
+
## Installation
|
|
85
|
+
|
|
86
|
+
The skills are available through three channels — pick what fits your setup:
|
|
87
|
+
|
|
88
|
+
### Smithery (recommended for individual skills)
|
|
89
|
+
|
|
90
|
+
Browse and install via [smithery.ai](https://smithery.ai). Install one or more skills directly through the Smithery UI: [odt](https://smithery.ai/skills/leiverkus/odt), [odp](https://smithery.ai/skills/leiverkus/odp), [ods](https://smithery.ai/skills/leiverkus/ods), [odg](https://smithery.ai/skills/leiverkus/odg).
|
|
91
|
+
|
|
92
|
+
### Open Agent Skills CLI
|
|
93
|
+
|
|
94
|
+
The [vercel-labs/skills](https://github.com/vercel-labs/skills) CLI installs across Claude Code, Codex, Cursor, OpenCode, and 50+ other agents:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# List skills in the repo
|
|
98
|
+
npx skills add leiverkus/open-document-skills --list
|
|
99
|
+
|
|
100
|
+
# Install all four globally for Claude Code
|
|
101
|
+
npx skills add leiverkus/open-document-skills --skill '*' -a claude-code -g
|
|
102
|
+
|
|
103
|
+
# Install only ODT into your project
|
|
104
|
+
npx skills add leiverkus/open-document-skills --skill odt
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Bundled installer (Codex / OpenCode / Claude Code)
|
|
108
|
+
|
|
109
|
+
Install all four skills at once via the bundled Python installer:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
python3 scripts/install_skills.py
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
By default, the installer writes to `$CODEX_HOME/skills` when `CODEX_HOME` is set, otherwise to:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
~/.codex/skills
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
If your setup uses the older `.agents` directory, install there explicitly:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
python3 scripts/install_skills.py --target agents
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Existing skill directories are skipped by default. To intentionally overwrite the installed copies:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
python3 scripts/install_skills.py --target agents --replace
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
For OpenCode global skills:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
python3 scripts/install_skills.py --target opencode
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
To install project-local OpenCode skills:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
python3 scripts/install_skills.py --target opencode --dest .opencode/skills
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
For Claude Code, this repository can be used as a skill-focused plugin because it contains `.claude-plugin/plugin.json` and a top-level `skills/` directory. To create a plugin bundle at a chosen destination:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
python3 scripts/install_skills.py --target claude --dest ./dist/open-document-skills
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Then add or install that plugin directory in Claude Code.
|
|
152
|
+
|
|
153
|
+
To install from a local checkout:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
git clone https://github.com/leiverkus/open-document-skills.git
|
|
157
|
+
cd open-document-skills
|
|
158
|
+
python3 scripts/install_skills.py
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Python library
|
|
162
|
+
|
|
163
|
+
The shared library behind the four skills is published to PyPI as
|
|
164
|
+
[`open-document-lib`](https://pypi.org/project/open-document-lib/). Use it
|
|
165
|
+
directly in any Python project — no skill bundling required:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
pip install open-document-lib
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from odf_lib import pack_flat_odf, replace_text_in_element, validate_against_schema
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The core has no dependencies beyond the standard library; `[validate]`
|
|
176
|
+
pulls in `lxml` for RelaxNG validation and `[scholarly]` pulls in
|
|
177
|
+
`bibtexparser`. See [docs/library-api.md](docs/library-api.md) for the
|
|
178
|
+
full public API.
|
|
179
|
+
|
|
180
|
+
## Requirements
|
|
181
|
+
|
|
182
|
+
Core scripts use only the Python standard library.
|
|
183
|
+
|
|
184
|
+
Recommended optional tools:
|
|
185
|
+
|
|
186
|
+
- LibreOffice, for rendering/export/recalculation workflows
|
|
187
|
+
- `pdftoppm` from Poppler, when you want PDF pages rendered to images
|
|
188
|
+
- Pandoc, for some conversion fallback workflows
|
|
189
|
+
|
|
190
|
+
Install all optional tools on macOS with Homebrew:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
brew install --cask libreoffice
|
|
194
|
+
brew install poppler pandoc
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Install all optional tools on Windows with winget:
|
|
198
|
+
|
|
199
|
+
```powershell
|
|
200
|
+
winget install --id TheDocumentFoundation.LibreOffice -e
|
|
201
|
+
winget install --id oschwartz10612.Poppler -e
|
|
202
|
+
winget install --id JohnMacFarlane.Pandoc -e
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Install all optional tools on Ubuntu with apt:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
sudo apt-get update
|
|
209
|
+
sudo apt-get install -y libreoffice poppler-utils pandoc
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
LibreOffice usually provides `soffice` inside the app bundle, not directly on the shell `PATH`:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
/Applications/LibreOffice.app/Contents/MacOS/soffice
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The render/recalc scripts look for that macOS path automatically. They also check common Linux and Windows locations.
|
|
219
|
+
|
|
220
|
+
## Skills
|
|
221
|
+
|
|
222
|
+
### ODT
|
|
223
|
+
|
|
224
|
+
OpenDocument Text / LibreOffice Writer.
|
|
225
|
+
|
|
226
|
+
Focus:
|
|
227
|
+
|
|
228
|
+
- template-first document editing
|
|
229
|
+
- direct ODT XML generation
|
|
230
|
+
- headings, paragraphs, lists, tables, footnotes, images
|
|
231
|
+
- style/page-layout awareness
|
|
232
|
+
- PDF QA through LibreOffice
|
|
233
|
+
|
|
234
|
+
Useful scripts:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
python skills/odt/scripts/create_minimal_odt.py document.json output.odt
|
|
238
|
+
python skills/odt/scripts/extract_text.py output.odt
|
|
239
|
+
python skills/odt/scripts/inspect_package.py output.odt
|
|
240
|
+
python skills/odt/scripts/replace_text.py input.odt "{{NAME}}" "Patrick Leiverkus" -o output.odt
|
|
241
|
+
python skills/odt/scripts/add_image.py input.odt figure.png -o output.odt
|
|
242
|
+
python skills/odt/scripts/add_footnote.py input.odt --anchor "claim" --body "Source: ..." -o output.odt
|
|
243
|
+
python skills/odt/scripts/fill_citations.py template.odt --source refs.bib -o output.odt
|
|
244
|
+
python skills/odt/scripts/add_bookmark.py input.odt --name K1 --anchor "Chapter 1" -o output.odt
|
|
245
|
+
python skills/odt/scripts/add_math.py input.odt --latex 'E = mc^2' --anchor "Equation" -o output.odt
|
|
246
|
+
python skills/odt/scripts/pack_fodt.py output.odt -o output.fodt
|
|
247
|
+
python skills/odt/scripts/validate_refs.py output.odt
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Script reference: see [docs/script-reference.md](docs/script-reference.md).
|
|
251
|
+
|
|
252
|
+
### ODP
|
|
253
|
+
|
|
254
|
+
OpenDocument Presentation / LibreOffice Impress.
|
|
255
|
+
|
|
256
|
+
Focus:
|
|
257
|
+
|
|
258
|
+
- template-first presentations
|
|
259
|
+
- direct ODP XML generation
|
|
260
|
+
- `draw:page`, speaker notes, master pages
|
|
261
|
+
- slide text/media inspection
|
|
262
|
+
- package and visual QA
|
|
263
|
+
|
|
264
|
+
Useful scripts:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
python skills/odp/scripts/create_minimal_odp.py slides.json output.odp
|
|
268
|
+
python skills/odp/scripts/extract_text.py output.odp
|
|
269
|
+
python skills/odp/scripts/inspect_package.py output.odp
|
|
270
|
+
python skills/odp/scripts/clone_slide.py template.odp --source-slide 1 --name "Agenda" -o output.odp
|
|
271
|
+
python skills/odp/scripts/add_image.py input.odp figure.png -o output.odp
|
|
272
|
+
python skills/odp/scripts/validate_refs.py output.odp
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Script reference: see [docs/script-reference.md](docs/script-reference.md).
|
|
276
|
+
|
|
277
|
+
### ODS
|
|
278
|
+
|
|
279
|
+
OpenDocument Spreadsheet / LibreOffice Calc.
|
|
280
|
+
|
|
281
|
+
Focus:
|
|
282
|
+
|
|
283
|
+
- direct ODS XML generation
|
|
284
|
+
- template-first spreadsheet editing
|
|
285
|
+
- typed cell values
|
|
286
|
+
- formulas
|
|
287
|
+
- repeated rows/cells
|
|
288
|
+
- CSV export and formula QA
|
|
289
|
+
|
|
290
|
+
Useful scripts:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
python skills/ods/scripts/create_minimal_ods.py workbook.json output.ods
|
|
294
|
+
python skills/ods/scripts/extract_sheets.py output.ods
|
|
295
|
+
python skills/ods/scripts/extract_formulas.py output.ods
|
|
296
|
+
python skills/ods/scripts/replace_cells.py input.ods 'Data!B2=42' 'Data!C2=formula:of:=[.B2]*2' -o output.ods
|
|
297
|
+
python skills/ods/scripts/export_csv.py output.ods --sheet Data --output data.csv
|
|
298
|
+
python skills/ods/scripts/validate_refs.py output.ods
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Script reference: see [docs/script-reference.md](docs/script-reference.md).
|
|
302
|
+
|
|
303
|
+
### ODG
|
|
304
|
+
|
|
305
|
+
OpenDocument Graphics / LibreOffice Draw.
|
|
306
|
+
|
|
307
|
+
Focus:
|
|
308
|
+
|
|
309
|
+
- direct ODG XML generation
|
|
310
|
+
- template-first diagram editing
|
|
311
|
+
- vector shapes, text boxes, lines, connectors, images
|
|
312
|
+
- geometry inspection
|
|
313
|
+
- PDF/SVG/PNG export QA
|
|
314
|
+
|
|
315
|
+
Useful scripts:
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
python skills/odg/scripts/create_minimal_odg.py drawing.json output.odg
|
|
319
|
+
python skills/odg/scripts/extract_text.py output.odg
|
|
320
|
+
python skills/odg/scripts/extract_shapes.py output.odg
|
|
321
|
+
python skills/odg/scripts/inspect_package.py output.odg
|
|
322
|
+
python skills/odg/scripts/replace_text.py input.odg "{{LABEL}}" "Updated label" -o output.odg
|
|
323
|
+
python skills/odg/scripts/validate_refs.py output.odg
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Script reference: see [docs/script-reference.md](docs/script-reference.md).
|
|
327
|
+
|
|
328
|
+
## Testing
|
|
329
|
+
|
|
330
|
+
Run the test suite:
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
python -m unittest discover -s tests
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
The tests create minimal ODT, ODP, ODS, and ODG files, then exercise extraction, validation, editing, media insertion, and export helpers.
|
|
337
|
+
|
|
338
|
+
LibreOffice integration tests are included. They render ODT/ODP/ODG files and recalculate ODS files when `soffice` is available. If LibreOffice is not available, those tests are skipped.
|
|
339
|
+
|
|
340
|
+
GitHub Actions runs the same suite on every push and pull request. The workflow installs LibreOffice and Poppler with `apt` on Ubuntu so the LibreOffice integration tests run in CI instead of being skipped.
|
|
341
|
+
|
|
342
|
+
Reusable example inputs live in `tests/fixtures/`:
|
|
343
|
+
|
|
344
|
+
- `odt_document.json`
|
|
345
|
+
- `odp_slides.json`
|
|
346
|
+
- `ods_workbook.json`
|
|
347
|
+
- `odg_drawing.json`
|
|
348
|
+
- `image.svg`
|
|
349
|
+
|
|
350
|
+
## Examples
|
|
351
|
+
|
|
352
|
+
Runnable examples live in `examples/`. They are meant as a practical first test layer for users of the skills:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
python examples/build_examples.py
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
This creates ODT, ODP, ODS, and ODG files in `examples/output/`, then validates their package references. The generated output directory is ignored by Git.
|
|
359
|
+
|
|
360
|
+
For optional LibreOffice QA:
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
python examples/build_examples.py --render
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
On macOS, add `--png` when Poppler is installed with Homebrew and PNG page previews are useful:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
python examples/build_examples.py --render --png
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## LibreOffice Workflows
|
|
373
|
+
|
|
374
|
+
Some workflows are intentionally optional because they require LibreOffice:
|
|
375
|
+
|
|
376
|
+
- render ODT/ODP/ODG to PDF or images
|
|
377
|
+
- export ODG to SVG/PNG
|
|
378
|
+
- recalculate ODS formulas
|
|
379
|
+
- round-trip conversions from DOCX/PPTX/XLSX or Markdown/HTML
|
|
380
|
+
|
|
381
|
+
The skills treat these as QA or interoperability steps. Native ODF package generation and XML-safe edits remain the preferred path when the target deliverable is an ODF file.
|
|
382
|
+
|
|
383
|
+
## Flat ODF (Git-friendly)
|
|
384
|
+
|
|
385
|
+
Every format has `pack_*` and `unpack_*` scripts that convert between the zipped ODF package and a flat single-XML file (`.fodt`, `.fodp`, `.fods`, `.fodg`). The flat form is part of the OASIS specification, opens directly in LibreOffice, and produces readable diffs under Git. Embedded images are inlined as base64 on pack and extracted back to `Pictures/` on unpack.
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
python skills/odt/scripts/pack_fodt.py document.odt -o document.fodt
|
|
389
|
+
git diff document.fodt
|
|
390
|
+
python skills/odt/scripts/unpack_fodt.py document.fodt -o document.odt
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
See [docs/workflows.md](docs/workflows.md#flat-odf-git-friendly) for details.
|
|
394
|
+
|
|
395
|
+
## Performance
|
|
396
|
+
|
|
397
|
+
The helpers are pure-Python and stream through ZIP packages without loading
|
|
398
|
+
LibreOffice. End-to-end CLI latency on a representative laptop:
|
|
399
|
+
|
|
400
|
+
| Format | Document | Create | Edit | Validate |
|
|
401
|
+
|--------|----------|--------|------|----------|
|
|
402
|
+
| ODT | 2000 paragraphs | 55 ms | 54 ms (`replace_text`) | 48 ms |
|
|
403
|
+
| ODS | 100 000 cells (1000×100) | 398 ms | 428 ms (`replace_cells`) | 689 ms |
|
|
404
|
+
| ODP | 100 slides | 45 ms | 42 ms (`clone_slide`) | 42 ms |
|
|
405
|
+
| ODG | 500 shapes | 47 ms | — | 44 ms |
|
|
406
|
+
|
|
407
|
+
Every timing includes Python interpreter startup (~40 ms), so small
|
|
408
|
+
documents are startup-bound; large spreadsheets are the heaviest case and
|
|
409
|
+
still finish well under a second. Reproduce or re-measure with
|
|
410
|
+
[`benchmarks/run_benchmarks.py`](benchmarks/README.md). Numbers are
|
|
411
|
+
indicative and machine-dependent.
|
|
412
|
+
|
|
413
|
+
## Current Limits
|
|
414
|
+
|
|
415
|
+
The scripts are intentionally small and conservative, but production-deep
|
|
416
|
+
across all four formats.
|
|
417
|
+
|
|
418
|
+
They cover:
|
|
419
|
+
|
|
420
|
+
- direct generation and template-based editing
|
|
421
|
+
- package validation, including optional RelaxNG validation against the
|
|
422
|
+
OASIS ODF 1.3 schema (`validate_refs.py --strict`, all four formats)
|
|
423
|
+
- text/formula/shape extraction
|
|
424
|
+
- XML-safe replacements that preserve inline `text:span`, `text:note`, `text:bookmark`, and `text:a`
|
|
425
|
+
- scholarly authoring — footnotes, endnotes, citations (BibTeX/CSL-JSON), cross-references, MathML
|
|
426
|
+
- spreadsheets — named ranges, data validation, embedded charts
|
|
427
|
+
- presentations — animations, slide transitions, master-page customization
|
|
428
|
+
- drawings — connectors with shape binding, glue points, shape groups
|
|
429
|
+
- image embedding with magic-byte MIME detection
|
|
430
|
+
- `meta.xml` lifecycle updates on every edit (`modification-date`, `generator`, `editing-cycles`)
|
|
431
|
+
- flat ODF (`.fodt`/`.fodp`/`.fods`/`.fodg`) roundtrip
|
|
432
|
+
|
|
433
|
+
They intentionally do **not** attempt to model every OpenDocument feature.
|
|
434
|
+
Out of scope — use LibreOffice for these:
|
|
435
|
+
|
|
436
|
+
- tracked changes and comments
|
|
437
|
+
- generated indexes and tables of contents (LibreOffice builds these from the markers the skills set)
|
|
438
|
+
- Calc pivot tables, conditional formatting, and cell protection
|
|
439
|
+
- full Impress slide-master hierarchies beyond simple page layouts
|
|
440
|
+
- DOCX/PPTX/XLSX import-and-edit — use `pandoc` if you need those round-trips
|
|
441
|
+
|
|
442
|
+
See [ROADMAP.md](ROADMAP.md) for what is planned next.
|
|
443
|
+
|
|
444
|
+
## Development
|
|
445
|
+
|
|
446
|
+
Recommended loop:
|
|
447
|
+
|
|
448
|
+
```bash
|
|
449
|
+
python -m unittest discover -s tests
|
|
450
|
+
git status --short
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
When adding a new script or behavior:
|
|
454
|
+
|
|
455
|
+
1. Add the smallest useful script interface.
|
|
456
|
+
2. Add or update a smoke test.
|
|
457
|
+
3. Run local tests.
|
|
458
|
+
4. Push and let GitHub Actions verify the repo.
|
|
459
|
+
|
|
460
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development and release checklist.
|
|
461
|
+
|
|
462
|
+
## Release Status
|
|
463
|
+
|
|
464
|
+
Current release: `v0.9.0` — a robustness release. Every helper is now exercised against a committed corpus of 17 LibreOffice-native ODF fixtures (`tests/test_corpus.py`), which uncovered and fixed two foreign-ODF bugs in `validate_refs` and the flat-ODF roundtrip. No new features — all four skills (ODT/ODP/ODS/ODG) remain at production-level depth. See [ROADMAP.md](ROADMAP.md) for v1.0 (PyPI publication + final polish + ecosystem maturity).
|
|
465
|
+
|
|
466
|
+
## License
|
|
467
|
+
|
|
468
|
+
MIT. See [LICENSE](LICENSE).
|