Pharo-OpenDocument

Description

OpenDocument driver for Pharo

Details

Source
GitHub
Dialect
pharo (25% confidence)
Created
Aug. 25, 2026
Updated
Aug. 26, 2026

README excerpt

# Pharo OpenDocument (POD)

A free-standing **OpenDocument Format (ODF)** engine for [Pharo](https://pharo.org),
written in pure Pharo Smalltalk. POD reads and writes OASIS OpenDocument files —
starting with **OpenDocument Text (`.odt`)** — as a proper object model, with no
external native dependencies beyond XML parsing/writing.

All classes are prefixed **`POD`**.

> Status: early but working. POD authors `.odt` files that open cleanly in
> LibreOffice, reads existing `.odt` documents back into an editable model, and
> round-trips real LibreOffice documents (preserving the parts it does not model).

## Why

POD was extracted from work on the **PharOffice** suite to give the Pharo community
a reusable, standalone ODF engine. It is intentionally decoupled from any UI so it
can back a word processor, a report generator, a document-conversion service, or a
head-less pipeline.

## Features

- **ODF package layer** — `PODPackage` reads/writes the ODF ZIP container with the
  mandatory uncompressed `mimetype` first entry, a spec-correct `META-INF/manifest.xml`,
  and pass-through of parts it does not model (round-trip fidelity).
- **Text document model** — `PODTextDocument` with a rich content tree: paragraphs,
  headings, styled spans, line breaks/tabs, bullet/number lists, and tables.
- **Styles** — `PODStyle` captures automatic and common styles as ODF
  `style:*-properties`, round-tripped without loss of the properties it carries.
- **Metadata** — `PODMetadata` reads/writes `meta.xml` (title, creator, dates, …).
- **Reader** — `PODTextReader` parses `content.xml` into the model; existing
  LibreOffice/OpenOffice documents load and re-save.
- **Pharo `Text` bridge (bidirectional)** — `PODRichTextImporter` converts a native
  Pharo rich `Text` into an ODF document and `PODPharoTextExporter` /
  `PODTextDocument>>asPharoText` converts one back, preserving emphasis
  (bold/italic/underline/strikethrough), colour, font family/size, and paragraph
  alignment. One-call savers included.

## Quick start

```smalltalk
"Author a document"
| doc |
doc := PODTextDocument new.
doc metadata title: 'My Report'; creator: 'Me'.
doc addHeading: 'Introduction' level: 1.
doc addParagraph: 'The quick brown fox.'.
(doc add: (PODParagraph text: 'Some '))
    addSpan: (PODSpan text: 'emphasis' styleName: 'Emphasis').
doc add: (PODList new addText: 'First'; addText: 'Second'; yourself).
doc add: ((PODTable named: 'Grid')
    addRowWithTexts: #('A' 'B');
    addRowWithTexts: #('C' 'D'); yourself).
doc writeToFile: 'report.odt'.

"Read a document back"
| loaded |
loaded := PODDocument fromFile: 'report.odt'.
loaded text.          "=> whole-document plain text"
loaded body.          "=> ordered collection of block elements"

"Save a Pharo Text (rich text) as ODF, and load one back"
PODTextDocument saveText: aPharoText titled: 'Export' toFile: 'export.odt'.
(PODDocument fromFile: 'export.odt') asPharoText.   "=> a Pharo Text with runs"
```

## Installation

```smalltalk
Metacello new
    baseline: 'PharoOpenDocument';
    repository: 'github://riverdusty/Pharo-OpenDocument:main/src';
    load.
```

Dependencies (`XML-Parser`, `XML-Writer`) are declared in the baseline and loaded
automatically.

## Architecture

| Package | Contents |
|---|---|
| `PharoOpenDocument-Core` | `PODPackage`, `PODManifest(+Entry)`, `PODMetadata`, `PODNamespaces`, abstract `PODDocument` |
| `PharoOpenDocument-Text` | Content model (`PODElement` tree), `PODStyle`, `PODTextDocument`, `PODTextReader`, `PODRichTextImporter`, `PODPharoTextExporter` |
| `PharoOpenDocument-Tests` | SUnit suite |

Content elements serialise themselves (`writeXmlOn:`); the reader centralises parsing
and dispatch. A document keeps its source `PODPackage`, so unmodeled parts
(`styles.xml`, `settings.xml`, …) survive a read-modify-write cycle byte-for-byte.

## ODF conformance notes

Learned by validating against LibreOffice and baked into the engine:

- **`mimetype` must be the first ZIP entry and stored (uncompressed)** — enforced by
  `PODPackage`.
- **Manifest `full-path` values are relative** (`content.xml`), never `/content.xml`;
  only the whole-document entry is `/`. A leading slash makes LibreOffice reject the
  package as corrupt.
- **`office:version`** is emitted from a single configurable source
  (`PODNamespaces class >> odfVersion`, default `1.4`). Current LibreOffice validates
  the declared version strictly.
- **`content.xml` is not pretty-printed** — `text:p`/`text:h`/`text:span` are mixed
  content, so indentation whitespace would become literal document text.

## Development

The engine is built and verified against a live Pharo image over
[SmalltalkGenie](https://github.com/KentBeck/SmalltalkGenie). The `tools/` directory
holds the reproducible build drivers (`build_*.py`), the Tonel exporter (`export.py`),
and the Genie helper (`genie.py`). `samples/` holds generated example documents.

## Roadmap

- Spreadsheet (`.ods`) and presentation (`.odp`) document types
- Richer style modelling (page layouts, list styles, table styles)
- Images and embedded objects (binary parts as assets)
- Full ODF whitespace-normalisation on read for foreign pretty-printed files

## License

MIT (intended for contribution to the Pharo community).
← Back to results