odt2web 0.8.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.
odt2web-0.8.0/PKG-INFO ADDED
@@ -0,0 +1,400 @@
1
+ Metadata-Version: 2.4
2
+ Name: odt2web
3
+ Version: 0.8.0
4
+ Summary: Converts OpenDocument Text (.odt) documents to semantic HTML + CSS.
5
+ Author-email: "Diego C. Sampaio" <slavelix@ymail.com>
6
+ License-Expression: AGPL-3.0-only
7
+ Project-URL: Homepage, https://github.com/kriptolix/Asteria-Project
8
+ Project-URL: Repository, https://github.com/kriptolix/Asteria-Project
9
+ Project-URL: Issues, https://github.com/kriptolix/Asteria-Project/issues
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Classifier: Topic :: Documentation
16
+ Classifier: Topic :: Text Processing
17
+ Requires-Python: >=3.13
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: PyYAML>=6.0
20
+
21
+ # odt2web
22
+
23
+ Python library for converting OpenDocument Text (`.odt`) documents into
24
+ semantic HTML, with optional CSS and image extraction.
25
+
26
+ Preserves the semantic and structural intent of the document, not its
27
+ printed-page appearance.
28
+
29
+ ## Table of contents
30
+
31
+ - [Installation](#installation)
32
+ - [Basic usage](#basic-usage)
33
+ - [`ConversionResult`](#conversionresult)
34
+ - [Architecture](#architecture)
35
+ - [Style system](#style-system)
36
+ - [Code blocks](#code-blocks)
37
+ - [Columns](#columns)
38
+ - [Assets](#assets)
39
+ - [Metadata](#metadata)
40
+ - [Front matter (SSGs)](#front-matter-ssgs)
41
+ - [Embedded raw HTML (`:::html`)](#embedded-raw-html-html)
42
+ - [Security](#security)
43
+ - [CLI](#cli)
44
+ - [Configuration (YAML)](#configuration-yaml)
45
+ - [API reference](#api-reference)
46
+ - [Extensibility](#extensibility)
47
+ - [Tests](#tests)
48
+ - [Known limitations](#known-limitations)
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ pip install .
54
+ ```
55
+
56
+ ## Basic usage
57
+
58
+ ```python
59
+ from odt2web import convert
60
+
61
+ result = convert("article.odt")
62
+ print(result.html)
63
+ print(result.css)
64
+
65
+ result = convert(
66
+ "article.odt",
67
+ output_dir="dist/",
68
+ css=True,
69
+ extract_images=True,
70
+ )
71
+ ```
72
+
73
+ From bytes:
74
+
75
+ ```python
76
+ from odt2web import convert_bytes
77
+
78
+ result = convert_bytes(odt_bytes)
79
+ ```
80
+
81
+ ## `ConversionResult`
82
+
83
+ | Attribute | Type | Description |
84
+ |---|---|---|
85
+ | `result.html` | `str` | The generated HTML. |
86
+ | `result.css` | `str \| None` | Generated CSS, if `css=True`. |
87
+ | `result.assets` | `list[Asset]` | Extracted images/assets. |
88
+ | `result.metadata` | `Metadata` | Document metadata (title, author, description — see [Metadata](#metadata)). |
89
+ | `result.front_matter` | `dict \| None` | Parsed front matter block, if present — see [Front matter](#front-matter-ssgs). |
90
+ | `result.warnings` | `list[Warning]` | Non-fatal issues found during conversion. |
91
+
92
+ ## Architecture
93
+
94
+ ```
95
+ ODT → Package Reader → Document Model → Semantic Renderer → HTML + CSS
96
+ ```
97
+
98
+ - **Package Reader** (`odt2web.reader`): reads the `.odt` as a ZIP
99
+ (`content.xml`, `styles.xml`, `meta.xml`, `manifest.xml`, `Pictures/`),
100
+ without depending on LibreOffice.
101
+ - **Document Model** (`odt2web.model`): a custom intermediate representation
102
+ (`Document`, `Section`, `Heading`, `Paragraph`, `Table`, ...), decoupled
103
+ from any output format.
104
+ - **Semantic Renderer** (`odt2web.renderer`, `odt2web.css`): converts the
105
+ model into HTML and generates only the CSS required by the resources
106
+ actually used.
107
+
108
+ ## Style system
109
+
110
+ Configurable default mapping:
111
+
112
+ ```python
113
+ style_map = {
114
+ "Title": "h1",
115
+ "Heading 1": "h2",
116
+ "Heading 2": "h3",
117
+ "Heading 3": "h4",
118
+ "Quotation": "blockquote",
119
+ "Text Body": "p",
120
+ }
121
+ ```
122
+
123
+ Custom styles (tag + class):
124
+
125
+ ```python
126
+ style_map = {
127
+ "Lead": {"tag": "p", "class": "lead"},
128
+ "Warning": {"tag": "aside", "class": "warning"},
129
+ }
130
+
131
+ result = convert("article.odt", style_map=style_map)
132
+ ```
133
+
134
+ Style resolution follows the ODT style inheritance chain (a paragraph often
135
+ uses an automatic style generated by the editor, e.g. `P1`, whose parent is
136
+ the actual named style) and matches against `style_map` first, then against
137
+ the default mapping above.
138
+
139
+ ## Code blocks
140
+
141
+ For code blocks (consecutive paragraphs using LibreOffice's "Preformatted
142
+ Text" style, or any style mapped to `pre`), the library:
143
+
144
+ - merges consecutive paragraphs into a single block instead of creating one
145
+ `<pre>` per line;
146
+ - generates `<pre><code class="language-xxx">...</code></pre>` — the markup
147
+ expected by highlighters;
148
+ - does not apply bold/italic/etc. formatting to the content — inline
149
+ formatting inside code would break copy-paste and highlighter
150
+ tokenization.
151
+
152
+ The library itself does not perform syntax highlighting — that is handled
153
+ by the highlighter you already use on your website (client-side or during
154
+ the SSG build). The library's job is simply to provide HTML in the correct
155
+ format.
156
+
157
+ The language of a code block can be specified in two ways:
158
+
159
+ 1. **Marker at the beginning of the block** (Markdown-style): fencing the
160
+ block with an opening line of three backticks followed by the language
161
+ name (e.g. `python`) defines the language and is removed from the final
162
+ HTML. This does not require creating any new style in the ODT.
163
+ 2. **Class in `style_map`**: if you prefer dedicated ODT styles for each
164
+ language (e.g. "Code Python", "Code JS"), map each one to a
165
+ `language-xxx` class:
166
+
167
+ ```python
168
+ style_map = {
169
+ "Code Python": {"tag": "pre", "class": "language-python"},
170
+ "Code JS": {"tag": "pre", "class": "language-javascript"},
171
+ }
172
+ ```
173
+
174
+ Inline code (a word or short snippet within a paragraph) uses LibreOffice's
175
+ native "Source Text" character style and is automatically converted to
176
+ `<code>...</code>`, with no additional configuration.
177
+
178
+ ## Columns
179
+
180
+ ODT sections with multiple columns generate:
181
+
182
+ ```html
183
+ <section class="odt-columns odt-columns-2">...</section>
184
+ ```
185
+
186
+ ```css
187
+ .odt-columns-2 {
188
+ column-count: 2;
189
+ column-gap: 1.27cm;
190
+ }
191
+ ```
192
+
193
+ The number of columns and the spacing are read directly from the ODT; text
194
+ distribution is delegated to CSS (`column-count`) and is never calculated
195
+ manually.
196
+
197
+ ## Assets
198
+
199
+ ```python
200
+ result = convert("article.odt", assets_dir="assets/")
201
+ ```
202
+
203
+ ```
204
+ dist/
205
+ ├── index.html
206
+ ├── style.css
207
+ └── assets/
208
+ ├── image-001.png
209
+ └── image-002.jpg
210
+ ```
211
+
212
+ ## Metadata
213
+
214
+ ```python
215
+ result.metadata.title
216
+ result.metadata.author
217
+ result.metadata.description
218
+ ```
219
+
220
+ ## Front matter (SSGs)
221
+
222
+ If the document starts with a block delimited by `---`, in the following
223
+ format:
224
+
225
+ ```yaml
226
+ ---
227
+ title: My First Article
228
+ date: 2026-08-20
229
+ author: Author
230
+ tags: Python, Web
231
+ categories: Technology
232
+ ---
233
+ ```
234
+
235
+ (either as consecutive paragraphs — one per line — or as a single paragraph
236
+ with manual line breaks inserted using Shift+Enter), it is recognized,
237
+ removed from the normal text flow, and converted to:
238
+
239
+ ```html
240
+ <div class="ssg-frontmatter" data-ssg="frontmatter">
241
+ <meta data-key="title" content="My First Article">
242
+ <meta data-key="date" content="2026-08-20">
243
+ <meta data-key="author" content="Author">
244
+ <meta data-key="tags" content="Python, Web">
245
+ <meta data-key="categories" content="Technology">
246
+ </div>
247
+ ```
248
+
249
+ It is always placed as the first content in the generated HTML. The
250
+ key-value pairs are also available in `result.front_matter` (`dict`). If
251
+ there is no block, the value is `None` and nothing is added to the HTML.
252
+ Empty paragraphs and page breaks preceding the block (common in editor
253
+ templates) do not prevent detection.
254
+
255
+ ## Embedded raw HTML (`:::html`)
256
+
257
+ For embeds (iframes, third-party widgets, etc.), instead of introducing a
258
+ syntax for each individual feature, the library provides a generic escape
259
+ hatch: a literal HTML block delimited by markers, written directly in the
260
+ `.odt`:
261
+
262
+ ```
263
+ :::html
264
+ <iframe src="https://example.com/map" width="600" height="400"></iframe>
265
+ :::
266
+ ```
267
+
268
+ (This works both as consecutive paragraphs and as a single paragraph with
269
+ manual line breaks, anywhere in the document.)
270
+
271
+ It is passed through without escaping, but this is opt-in:
272
+
273
+ ```python
274
+ result = convert("article.odt", allow_raw_html=True)
275
+ ```
276
+
277
+ By default (`allow_raw_html=False`), the block appears escaped as literal
278
+ text instead of being executed as HTML, with a warning in
279
+ `result.warnings` — the `.odt` is treated as untrusted input by default
280
+ (see [Security](#security)). Only enable this for documents from authors
281
+ you trust.
282
+
283
+ The content undergoes basic typographic normalization (curly quotes →
284
+ straight quotes) before becoming HTML, since LibreOffice's autocorrect
285
+ often changes `"` to `“`/`”`, which would break attributes such as
286
+ `src="..."`.
287
+
288
+ In the CLI: `odt2web article.odt --allow-raw-html`. In `config.yaml`:
289
+ `output.allow_raw_html: true`.
290
+
291
+ ## Security
292
+
293
+ The `.odt` is treated as untrusted input (`odt2web.security`): text is
294
+ always escaped; URLs (`href`, referenced images) are checked against an
295
+ allowlist of permitted schemes (`http`, `https`, `mailto`, `tel`, relative
296
+ URLs) — schemes such as `javascript:` or `file:` are removed.
297
+
298
+ No macros or embedded ODT content are executed. The only escape hatch that
299
+ emits unescaped content is the `:::html` block (above), and only when
300
+ explicitly enabled.
301
+
302
+ ## CLI
303
+
304
+ ```bash
305
+ odt2web article.odt
306
+ odt2web article.odt -o dist/
307
+ odt2web article.odt --css
308
+ odt2web article.odt --assets assets/
309
+ odt2web article.odt --fragment
310
+ odt2web article.odt --allow-raw-html
311
+ odt2web article.odt --config config.yaml
312
+ odt2web --check article.odt
313
+ ```
314
+
315
+ CLI default: complete HTML document (use `--fragment` to generate only the
316
+ fragment). In the Python API it is the opposite — see `document=` below.
317
+
318
+ ## Configuration (YAML)
319
+
320
+ ```yaml
321
+ output:
322
+ document: false
323
+ css: true
324
+ assets: assets
325
+ allow_raw_html: false
326
+
327
+ columns:
328
+ enabled: true
329
+
330
+ styles:
331
+ Title: h1
332
+ Heading 1: h2
333
+ Heading 2: h3
334
+ Quotation: blockquote
335
+ Lead:
336
+ tag: p
337
+ class: lead
338
+ ```
339
+
340
+ ```python
341
+ convert("article.odt", config="config.yaml")
342
+ ```
343
+
344
+ Priority: explicit argument passed to the call > configuration file >
345
+ library default.
346
+
347
+ ## API reference
348
+
349
+ ```python
350
+ convert(
351
+ path,
352
+ *,
353
+ output_dir=None, # writes index.html/style.css/assets to this directory
354
+ css=True, # automatically generates CSS
355
+ extract_images=True, # extracts embedded images into separate files
356
+ assets_dir="assets", # subdirectory (relative to the HTML) for assets
357
+ document=False, # True = complete HTML document; False = fragment
358
+ style_map=None, # ODT style -> HTML mapping
359
+ custom_renderers=None, # additional renderers beyond those registered via @register_renderer
360
+ allow_raw_html=False, # enables :::html blocks without escaping
361
+ config=None, # path to a YAML configuration file
362
+ )
363
+ ```
364
+
365
+ - **`convert_bytes(data, **same_kwargs)`** — same functionality, using
366
+ in-memory bytes.
367
+ - **`check(path)`** — validates the `.odt` without generating HTML; returns
368
+ `list[Warning]` and raises `InvalidOdtError` if the package cannot be
369
+ read.
370
+
371
+ ## Extensibility
372
+
373
+ ```python
374
+ from odt2web import register_renderer
375
+ from odt2web.renderer import render_inline
376
+
377
+ @register_renderer("custom:note")
378
+ def render_note(node, context):
379
+ return f"<aside class='note'>{render_inline(node.children, context)}</aside>"
380
+
381
+ style_map = {"Warning": {"tag": "custom:note"}}
382
+ ```
383
+
384
+ A complete list of available CSS classes can be found [here](DOCS.md).
385
+
386
+ ## Known limitations
387
+
388
+ - Front matter, `:::html` blocks, and code block merging operate at the
389
+ document's "loose" level (outside lists/tables); inside a table cell or
390
+ list item, each paragraph continues to be processed individually.
391
+ - Table column widths and endnotes separated from footnotes are not implemented — the architecture (an intermediate model independent of HTML) already supports these without
392
+ structural changes.
393
+
394
+ ## License
395
+
396
+ AGPL-3.0.
397
+
398
+ ## Contributing
399
+
400
+ Issues and pull requests are welcome.