odf
Pure-Crystal OASIS OpenDocument Format 1.3 library shard with zero runtime dependencies, starting with full .ods spreadsheet reading and writing and architected for community expansion across .odt, .odp, and .odg.
GitLab Project Metadata:
- Description: Pure-Crystal OASIS OpenDocument Format 1.3 library shard with zero runtime dependencies, providing native .ods spreadsheet generation, live OpenFormula cells, XXE-safe reading, and an extensible core for .odt, .odp, and .odg.
- Topics:
crystal,crystal-lang,opendocument,odf,ods,libreoffice,spreadsheet,openformula,foss,gplv3
Overview
odf (module Odf) is a native, pure-Crystal implementation of the OASIS OpenDocument Format (ODF 1.3), built exclusively on Crystal's standard library (XML, Compress::Zip, and Digest::CRC32).
It requires zero C bindings, zero external shards, and zero Python/Java subprocesses, allowing Crystal CLI tools, web services, and financial engines to compile into self-contained static binaries (including Alpine/musl targets) while generating and reading standards-compliant LibreOffice/Collabora/Nextcloud Office documents.
Scope and Extensible Architecture
odf separates the ODF 1.3 package, security, and stylesheet layer from document-domain modules:
| Layer/Module | MIME Type and Extension | Target Release | Status |
|---|---|---|---|
Odf::Package & Odf::Xml |
ZIP container, STORED mimetype, manifest.xml, XXE/Zip-bomb/symlink guards |
v0.1.0 |
Implemented — v0.1.0 |
Odf::Styles & Odf::Formula |
Fonts, cell/border/number/currency/date styles, page layouts, OpenFormula of:= |
v0.1.0 |
Implemented — v0.1.0 |
Odf::Spreadsheet Writer |
application/vnd.oasis.opendocument.spreadsheet, .ods |
v0.1.0 |
Implemented — v0.1.0 |
Odf::Spreadsheet Reader |
application/vnd.oasis.opendocument.spreadsheet, .ods |
v0.1.0 |
Implemented — v0.1.0 |
Odf::Text |
application/vnd.oasis.opendocument.text, .odt |
v0.2.0+ |
Open for Community, Phase 3 |
Odf::Presentation |
application/vnd.oasis.opendocument.presentation, .odp |
v0.3.0+ |
Open for Community, Phase 3 |
Odf::Drawing |
application/vnd.oasis.opendocument.graphics, .odg |
v0.4.0+ |
Open for Community, Phase 3 |
v0.1.0 Capabilities
- Strict ODF 1.3 Package Conformance: Emits ZIP archives with an uncompressed
CompressionMethod::STOREDmimetypeentry and precomputedCRC32at byte offset 30, validMETA-INF/manifest.xml,styles.xml, andcontent.xml. - Declarative
Odf::SpreadsheetWorkbook Writer:- Multiple worksheets, explicit column widths, and row heights.
- Typed cells:
text,integer,float,currency,percent,boolean,date, andtime. - Live OpenFormula (
of:=...) expression cells with deterministic cached numeric/string fallback values. - Custom cell styles: typography, background fills, borders, horizontal/vertical alignment, number precision, currency symbols, and conditional negative-number coloring.
- Layout controls: merged cell spans (
number-columns-spannedandnumber-rows-spanned), frozen header rows/columns, landscape/portrait print scaling, and autofiltertable:database-rangedefinitions.
- Defensive
Odf::SpreadsheetReader:- XXE-safe XML parsing enforcing
XML::ParserOptions::NONETand disabling external entity expansion. - Decompression byte-budget quotas (
max_entry_bytes,max_total_bytes, andmax_entries) preventing Zip-bomb denial-of-service. - Automatic expansion of ODF
table:number-columns-repeatedandtable:number-rows-repeatedrun-length attributes with bounded cell-allocation limits.
- XXE-safe XML parsing enforcing
- Open
Odf::DocumentExtension Contract: Any ODF document class implementing#media_type,#styles_registry, and#emit_body(with optional#emit_automatic_styles,#settings_xml, and#extra_entrieshooks) plugs directly intoOdf::Package::Writerwithout modifying archive or manifest internals.
Installation
Add odf to your application's shard.yml:
dependencies:
odf:
gitlab: renich/odf
version: ~> 0.1.0
Then run:
shards install
Usage
Writing a Multi-Sheet .ods Workbook with Live Formulas
require "odf"
workbook = Odf::Spreadsheet::Workbook.new do |wb|
wb.currency_style("usd", symbol: "$", decimals: 2, negative_red: true)
wb.sheet("Hardware_BOM") do |sheet|
sheet.columns("22mm", "64mm", "38mm", "42mm")
sheet.freeze(rows: 1)
sheet.header_row(["Qty", "Component", "Unit Price", "Line Total"])
sheet.row do |row|
row.integer(9)
row.text("Dell PowerEdge R7725 Ceph OSD")
row.currency(638_017.63, style: "usd")
row.formula("=[.A2]*[.C2]", cached: 5_742_158.67, style: "usd")
end
sheet.row do |row|
row.integer(5)
row.text("Dell PowerSwitch Z9432F-ON 400GbE")
row.currency(24_750.00, style: "usd")
row.formula("=[.A3]*[.C3]", cached: 123_750.00, style: "usd")
end
sheet.row do |row|
row.empty
row.text("Total Discounted CapEx", bold: true)
row.empty
row.formula("=SUM([.D2:.D3])", cached: 5_865_908.67, style: "usd", bold: true)
end
sheet.autofilter("A1:D3")
end
end
workbook.save("cluster_bom.ods")
Reading an .ods Workbook Safely
require "odf"
doc = Odf::Spreadsheet::Reader.open("cluster_bom.ods")
sheet = doc.sheet("Hardware_BOM")
sheet.rows.each_with_index do |row, idx|
puts "Row #{idx + 1}: #{row.map(&.to_s).join(" | ")}"
end
Community Contributions Beyond Spreadsheets
v0.1.0 focuses deliberately on Odf::Spreadsheet (.ods) so the core package, style registry, and security boundaries are battle-tested in production (cl8r, dq, qtr).
Community contributors interested in adding:
Odf::Text(.odt) — headings, paragraphs, lists, tables, headers/footers, and page breaksOdf::Presentation(.odp) — master slide layouts, text frames, speaker notes, and SVG embedsOdf::Drawing(.odg) — vector shapes, connectors, and layered canvases
are warmly encouraged to join! Check Extensibility Contract, Phase 3 Roadmap, and CONTRIBUTING.rst to get started.
Build System Targets
| Target | Description |
|---|---|
make setup |
Installs dev shards (ameba, flaw), configures .githooks, and runs verification |
make test |
Runs the Crystal spec suite (crystal spec --error-trace) |
make lint |
Runs crystal tool format --check, Ameba static analysis, and Flaw SAST scanner |
make diagrams |
Compiles D2 architecture and roadmap .d2 diagrams to .svg |
make doc-check |
Lints all reStructuredText documentation with crstlint and rstcheck |
make check |
Runs doc-check, test, and lint |
make coverage |
Generates HTML code coverage reports under coverage/ using kcov |
make ci |
Runs the non-mutating CI verification pipeline |
make docs |
Builds Crystal API reference and Sphinx HTML/dirhtml documentation |
Documentation Suite
Complete specifications, Architecture Decision Records, and phased roadmaps live under docs/:
| Domain | Scope | Entry Point |
|---|---|---|
| Business Context | Ecosystem gap, FOSS document sovereignty, stakeholders, and metrics | Business Specs |
| Functional Specs | Requirements [FUNC-001]..[FUNC-008] for package, styles, .ods, and extensibility |
Functional Specs |
| Technical Blueprint | Modules [TECH-001]..[TECH-007], Crystal types, security limits, and coding rules |
Technical Specs |
| ADR Log | Architectural rationale for zero-dependency ODF 1.3 design and licensing | ADR Index |
| Project Roadmap | Sequenced Phases 1–3 with task dependencies, critical path, and D2 diagrams | Project Roadmap |
| User & Dev Guides | Quickstart guide and Odf::Document extension tutorial |
User Manual |
Contributing and Code of Honor
All human and AI contributions must uphold the Universal Code of Honor and follow the Contributing Guidelines.
License
- Software Source Code: GNU General Public License v3.0 or later (LICENSE).
- Documentation: GNU Free Documentation License v1.3 or later, with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts (LICENSE-DOCS).
- Embedded Documentation Code Examples: Dual-licensed under the
MITLicense andGPL-3.0-or-laterso snippets inREADME.mdanddocs/can be copied freely into downstream projects.
Copyleft © 2026 Rénich Bon Ćirić renich@evalinux.com.
Support and Donations
If you find this shard useful and wish to support its ongoing development, please consider donating:
odf
- 0
- 0
- 0
- 0
- 2
- 2 days ago
- October 29, 2026
GNU General Public License v3.0 or later
Thu, 29 Oct 2026 08:53:17 GMT