epub-generator 0.1.1__tar.gz → 0.1.3__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.
- {epub_generator-0.1.1 → epub_generator-0.1.3}/PKG-INFO +105 -31
- {epub_generator-0.1.1 → epub_generator-0.1.3}/README.md +104 -30
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/__init__.py +4 -2
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/data/style.css.jinja +12 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/generation/gen_asset.py +22 -8
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/generation/gen_chapter.py +34 -7
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/generation/gen_epub.py +16 -2
- epub_generator-0.1.3/epub_generator/generation/xml_utils.py +31 -0
- epub_generator-0.1.3/epub_generator/html_tag.py +11 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/types.py +21 -9
- {epub_generator-0.1.1 → epub_generator-0.1.3}/pyproject.toml +1 -1
- epub_generator-0.1.1/epub_generator/generation/xml_utils.py +0 -18
- {epub_generator-0.1.1 → epub_generator-0.1.3}/LICENSE +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/context.py +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/data/container.xml.jinja +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/data/content.opf.jinja +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/data/cover.xhtml.jinja +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/data/mimetype.jinja +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/data/nav.xhtml.jinja +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/data/part.xhtml.jinja +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/generation/__init__.py +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/generation/gen_nav.py +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/generation/gen_toc.py +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/i18n.py +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/options.py +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.3}/epub_generator/template.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: epub-generator
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: A simple Python EPUB 3.0 generator with a single API call
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: epub,epub3,ebook,generator,publishing
|
|
@@ -48,7 +48,7 @@ pip install epub-generator
|
|
|
48
48
|
### Generate Your First Book in 5 Minutes
|
|
49
49
|
|
|
50
50
|
```python
|
|
51
|
-
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter,
|
|
51
|
+
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
|
|
52
52
|
|
|
53
53
|
# Prepare book data
|
|
54
54
|
epub_data = EpubData(
|
|
@@ -61,9 +61,9 @@ epub_data = EpubData(
|
|
|
61
61
|
title="Chapter 1",
|
|
62
62
|
get_chapter=lambda: Chapter(
|
|
63
63
|
elements=[
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
TextBlock(kind=TextKind.HEADLINE, content=["Chapter 1"]),
|
|
65
|
+
TextBlock(kind=TextKind.BODY, content=["This is the first paragraph."]),
|
|
66
|
+
TextBlock(kind=TextKind.BODY, content=["This is the second paragraph."]),
|
|
67
67
|
]
|
|
68
68
|
),
|
|
69
69
|
),
|
|
@@ -80,9 +80,9 @@ That's it! You now have a valid EPUB 3.0 ebook file.
|
|
|
80
80
|
|
|
81
81
|
- **Minimal API**: Just one function call `generate_epub()`
|
|
82
82
|
- **EPUB 3.0**: Generates standards-compliant EPUB 3.0 format
|
|
83
|
-
- **Rich Content**: Supports text, images, tables, math formulas, footnotes
|
|
83
|
+
- **Rich Content**: Supports text, images, tables, math formulas (block-level and inline), footnotes, custom HTML tags
|
|
84
84
|
- **Flexible Structure**: Nested chapters, prefaces, cover images
|
|
85
|
-
- **Math Support**: LaTeX to MathML conversion
|
|
85
|
+
- **Math Support**: LaTeX to MathML/SVG conversion with inline formula support
|
|
86
86
|
- **Type Safe**: Full type annotations included
|
|
87
87
|
|
|
88
88
|
## Advanced Usage
|
|
@@ -92,7 +92,7 @@ That's it! You now have a valid EPUB 3.0 ebook file.
|
|
|
92
92
|
```python
|
|
93
93
|
from datetime import datetime, timezone
|
|
94
94
|
from pathlib import Path
|
|
95
|
-
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter,
|
|
95
|
+
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
|
|
96
96
|
|
|
97
97
|
epub_data = EpubData(
|
|
98
98
|
meta=BookMeta(
|
|
@@ -111,8 +111,8 @@ epub_data = EpubData(
|
|
|
111
111
|
title="Chapter 1",
|
|
112
112
|
get_chapter=lambda: Chapter(
|
|
113
113
|
elements=[
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
TextBlock(kind=TextKind.HEADLINE, content=["Chapter 1"]),
|
|
115
|
+
TextBlock(kind=TextKind.BODY, content=["Main content..."]),
|
|
116
116
|
]
|
|
117
117
|
),
|
|
118
118
|
),
|
|
@@ -125,7 +125,7 @@ generate_epub(epub_data, "book_with_cover.epub")
|
|
|
125
125
|
### Nested Chapter Structure
|
|
126
126
|
|
|
127
127
|
```python
|
|
128
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
128
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind
|
|
129
129
|
|
|
130
130
|
epub_data = EpubData(
|
|
131
131
|
chapters=[
|
|
@@ -136,7 +136,7 @@ epub_data = EpubData(
|
|
|
136
136
|
title="Chapter 1.1",
|
|
137
137
|
get_chapter=lambda: Chapter(
|
|
138
138
|
elements=[
|
|
139
|
-
|
|
139
|
+
TextBlock(kind=TextKind.BODY, content=["Content 1.1..."]),
|
|
140
140
|
]
|
|
141
141
|
),
|
|
142
142
|
),
|
|
@@ -144,7 +144,7 @@ epub_data = EpubData(
|
|
|
144
144
|
title="Chapter 1.2",
|
|
145
145
|
get_chapter=lambda: Chapter(
|
|
146
146
|
elements=[
|
|
147
|
-
|
|
147
|
+
TextBlock(kind=TextKind.BODY, content=["Content 1.2..."]),
|
|
148
148
|
]
|
|
149
149
|
),
|
|
150
150
|
),
|
|
@@ -160,7 +160,7 @@ generate_epub(epub_data, "book_with_nested_chapters.epub")
|
|
|
160
160
|
|
|
161
161
|
```python
|
|
162
162
|
from pathlib import Path
|
|
163
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
163
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Image
|
|
164
164
|
|
|
165
165
|
epub_data = EpubData(
|
|
166
166
|
chapters=[
|
|
@@ -168,7 +168,7 @@ epub_data = EpubData(
|
|
|
168
168
|
title="Chapter 1",
|
|
169
169
|
get_chapter=lambda: Chapter(
|
|
170
170
|
elements=[
|
|
171
|
-
|
|
171
|
+
TextBlock(kind=TextKind.BODY, content=["Here's an image:"]),
|
|
172
172
|
Image(
|
|
173
173
|
path=Path("image.png"), # Image path
|
|
174
174
|
alt_text="Image description",
|
|
@@ -185,7 +185,7 @@ generate_epub(epub_data, "book_with_images.epub")
|
|
|
185
185
|
### Add Footnotes
|
|
186
186
|
|
|
187
187
|
```python
|
|
188
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
188
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Mark, Footnote
|
|
189
189
|
|
|
190
190
|
epub_data = EpubData(
|
|
191
191
|
chapters=[
|
|
@@ -193,7 +193,7 @@ epub_data = EpubData(
|
|
|
193
193
|
title="Chapter 1",
|
|
194
194
|
get_chapter=lambda: Chapter(
|
|
195
195
|
elements=[
|
|
196
|
-
|
|
196
|
+
TextBlock(
|
|
197
197
|
kind=TextKind.BODY,
|
|
198
198
|
content=[
|
|
199
199
|
"This is text with a footnote",
|
|
@@ -206,7 +206,7 @@ epub_data = EpubData(
|
|
|
206
206
|
Footnote(
|
|
207
207
|
id=1,
|
|
208
208
|
contents=[
|
|
209
|
-
|
|
209
|
+
TextBlock(kind=TextKind.BODY, content=["This is the footnote content."]),
|
|
210
210
|
],
|
|
211
211
|
),
|
|
212
212
|
],
|
|
@@ -221,7 +221,7 @@ generate_epub(epub_data, "book_with_footnotes.epub")
|
|
|
221
221
|
### Add Tables
|
|
222
222
|
|
|
223
223
|
```python
|
|
224
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
224
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Table
|
|
225
225
|
|
|
226
226
|
epub_data = EpubData(
|
|
227
227
|
chapters=[
|
|
@@ -229,7 +229,7 @@ epub_data = EpubData(
|
|
|
229
229
|
title="Chapter 1",
|
|
230
230
|
get_chapter=lambda: Chapter(
|
|
231
231
|
elements=[
|
|
232
|
-
|
|
232
|
+
TextBlock(kind=TextKind.BODY, content=["Here's a table:"]),
|
|
233
233
|
Table(
|
|
234
234
|
html_content="""
|
|
235
235
|
<table>
|
|
@@ -250,8 +250,10 @@ generate_epub(epub_data, "book_with_tables.epub")
|
|
|
250
250
|
|
|
251
251
|
### Add Math Formulas
|
|
252
252
|
|
|
253
|
+
Block-level formulas:
|
|
254
|
+
|
|
253
255
|
```python
|
|
254
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
256
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Formula, LaTeXRender
|
|
255
257
|
|
|
256
258
|
epub_data = EpubData(
|
|
257
259
|
chapters=[
|
|
@@ -259,8 +261,8 @@ epub_data = EpubData(
|
|
|
259
261
|
title="Chapter 1",
|
|
260
262
|
get_chapter=lambda: Chapter(
|
|
261
263
|
elements=[
|
|
262
|
-
|
|
263
|
-
Formula(latex_expression="x^2 + y^2 = z^2"), #
|
|
264
|
+
TextBlock(kind=TextKind.BODY, content=["Pythagorean theorem:"]),
|
|
265
|
+
Formula(latex_expression="x^2 + y^2 = z^2"), # Block-level formula
|
|
264
266
|
]
|
|
265
267
|
),
|
|
266
268
|
),
|
|
@@ -271,10 +273,73 @@ epub_data = EpubData(
|
|
|
271
273
|
generate_epub(epub_data, "book_with_math.epub", latex_render=LaTeXRender.MATHML)
|
|
272
274
|
```
|
|
273
275
|
|
|
276
|
+
Inline formulas embedded in text:
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
epub_data = EpubData(
|
|
280
|
+
chapters=[
|
|
281
|
+
TocItem(
|
|
282
|
+
title="Chapter 1",
|
|
283
|
+
get_chapter=lambda: Chapter(
|
|
284
|
+
elements=[
|
|
285
|
+
TextBlock(
|
|
286
|
+
kind=TextKind.BODY,
|
|
287
|
+
content=[
|
|
288
|
+
"The Pythagorean theorem ",
|
|
289
|
+
Formula(latex_expression="a^2 + b^2 = c^2"), # Inline formula
|
|
290
|
+
" is fundamental. Einstein's equation ",
|
|
291
|
+
Formula(latex_expression="E = mc^2"),
|
|
292
|
+
" shows mass-energy equivalence.",
|
|
293
|
+
],
|
|
294
|
+
),
|
|
295
|
+
]
|
|
296
|
+
),
|
|
297
|
+
),
|
|
298
|
+
],
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
generate_epub(epub_data, "book_with_inline_math.epub", latex_render=LaTeXRender.MATHML)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Add Custom HTML Tags
|
|
305
|
+
|
|
306
|
+
You can embed custom HTML tags within text content:
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, HTMLTag
|
|
310
|
+
|
|
311
|
+
epub_data = EpubData(
|
|
312
|
+
chapters=[
|
|
313
|
+
TocItem(
|
|
314
|
+
title="Chapter 1",
|
|
315
|
+
get_chapter=lambda: Chapter(elements=[TextBlock(
|
|
316
|
+
kind=TextKind.BODY,
|
|
317
|
+
content=[
|
|
318
|
+
"This is normal text with ",
|
|
319
|
+
HTMLTag(
|
|
320
|
+
name="span",
|
|
321
|
+
attributes=[("class", "highlight"), ("style", "color: red;")],
|
|
322
|
+
content=["highlighted content"],
|
|
323
|
+
),
|
|
324
|
+
" and more text with ",
|
|
325
|
+
HTMLTag(
|
|
326
|
+
name="strong",
|
|
327
|
+
content=["bold text"],
|
|
328
|
+
),
|
|
329
|
+
".",
|
|
330
|
+
],
|
|
331
|
+
)]),
|
|
332
|
+
),
|
|
333
|
+
],
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
generate_epub(epub_data, "book_with_html_tags.epub")
|
|
337
|
+
```
|
|
338
|
+
|
|
274
339
|
### Add Prefaces
|
|
275
340
|
|
|
276
341
|
```python
|
|
277
|
-
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter,
|
|
342
|
+
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
|
|
278
343
|
|
|
279
344
|
epub_data = EpubData(
|
|
280
345
|
meta=BookMeta(title="Book with Prefaces"),
|
|
@@ -283,8 +348,8 @@ epub_data = EpubData(
|
|
|
283
348
|
title="Preface",
|
|
284
349
|
get_chapter=lambda: Chapter(
|
|
285
350
|
elements=[
|
|
286
|
-
|
|
287
|
-
|
|
351
|
+
TextBlock(kind=TextKind.HEADLINE, content=["Preface"]),
|
|
352
|
+
TextBlock(kind=TextKind.BODY, content=["This is the preface content..."]),
|
|
288
353
|
]
|
|
289
354
|
),
|
|
290
355
|
),
|
|
@@ -294,7 +359,7 @@ epub_data = EpubData(
|
|
|
294
359
|
title="Chapter 1",
|
|
295
360
|
get_chapter=lambda: Chapter(
|
|
296
361
|
elements=[
|
|
297
|
-
|
|
362
|
+
TextBlock(kind=TextKind.BODY, content=["Main content..."]),
|
|
298
363
|
]
|
|
299
364
|
),
|
|
300
365
|
),
|
|
@@ -387,12 +452,12 @@ class Chapter:
|
|
|
387
452
|
|
|
388
453
|
`ContentBlock` is a union of:
|
|
389
454
|
|
|
390
|
-
- **`
|
|
455
|
+
- **`TextBlock`**: Text paragraph
|
|
391
456
|
```python
|
|
392
457
|
@dataclass
|
|
393
|
-
class
|
|
394
|
-
kind: TextKind
|
|
395
|
-
content: list[str | Mark]
|
|
458
|
+
class TextBlock:
|
|
459
|
+
kind: TextKind # BODY | HEADLINE | QUOTE
|
|
460
|
+
content: list[str | Mark | Formula | HTMLTag] # Text with optional marks, inline formulas, and HTML tags
|
|
396
461
|
```
|
|
397
462
|
|
|
398
463
|
- **`Image`**: Image reference
|
|
@@ -417,6 +482,15 @@ class Chapter:
|
|
|
417
482
|
latex_expression: str # LaTeX expression
|
|
418
483
|
```
|
|
419
484
|
|
|
485
|
+
- **`HTMLTag`**: HTML tag
|
|
486
|
+
```python
|
|
487
|
+
@dataclass
|
|
488
|
+
class HTMLTag:
|
|
489
|
+
name: str # Tag name (e.g., "span", "div")
|
|
490
|
+
attributes: list[tuple[str, str]] = [] # List of (attribute, value) pairs
|
|
491
|
+
content: list[str | Mark | Formula | HTMLTag] = [] # Inner HTML content
|
|
492
|
+
```
|
|
493
|
+
|
|
420
494
|
#### `Footnote`
|
|
421
495
|
|
|
422
496
|
Footnote/citation.
|
|
@@ -21,7 +21,7 @@ pip install epub-generator
|
|
|
21
21
|
### Generate Your First Book in 5 Minutes
|
|
22
22
|
|
|
23
23
|
```python
|
|
24
|
-
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter,
|
|
24
|
+
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
|
|
25
25
|
|
|
26
26
|
# Prepare book data
|
|
27
27
|
epub_data = EpubData(
|
|
@@ -34,9 +34,9 @@ epub_data = EpubData(
|
|
|
34
34
|
title="Chapter 1",
|
|
35
35
|
get_chapter=lambda: Chapter(
|
|
36
36
|
elements=[
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
TextBlock(kind=TextKind.HEADLINE, content=["Chapter 1"]),
|
|
38
|
+
TextBlock(kind=TextKind.BODY, content=["This is the first paragraph."]),
|
|
39
|
+
TextBlock(kind=TextKind.BODY, content=["This is the second paragraph."]),
|
|
40
40
|
]
|
|
41
41
|
),
|
|
42
42
|
),
|
|
@@ -53,9 +53,9 @@ That's it! You now have a valid EPUB 3.0 ebook file.
|
|
|
53
53
|
|
|
54
54
|
- **Minimal API**: Just one function call `generate_epub()`
|
|
55
55
|
- **EPUB 3.0**: Generates standards-compliant EPUB 3.0 format
|
|
56
|
-
- **Rich Content**: Supports text, images, tables, math formulas, footnotes
|
|
56
|
+
- **Rich Content**: Supports text, images, tables, math formulas (block-level and inline), footnotes, custom HTML tags
|
|
57
57
|
- **Flexible Structure**: Nested chapters, prefaces, cover images
|
|
58
|
-
- **Math Support**: LaTeX to MathML conversion
|
|
58
|
+
- **Math Support**: LaTeX to MathML/SVG conversion with inline formula support
|
|
59
59
|
- **Type Safe**: Full type annotations included
|
|
60
60
|
|
|
61
61
|
## Advanced Usage
|
|
@@ -65,7 +65,7 @@ That's it! You now have a valid EPUB 3.0 ebook file.
|
|
|
65
65
|
```python
|
|
66
66
|
from datetime import datetime, timezone
|
|
67
67
|
from pathlib import Path
|
|
68
|
-
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter,
|
|
68
|
+
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
|
|
69
69
|
|
|
70
70
|
epub_data = EpubData(
|
|
71
71
|
meta=BookMeta(
|
|
@@ -84,8 +84,8 @@ epub_data = EpubData(
|
|
|
84
84
|
title="Chapter 1",
|
|
85
85
|
get_chapter=lambda: Chapter(
|
|
86
86
|
elements=[
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
TextBlock(kind=TextKind.HEADLINE, content=["Chapter 1"]),
|
|
88
|
+
TextBlock(kind=TextKind.BODY, content=["Main content..."]),
|
|
89
89
|
]
|
|
90
90
|
),
|
|
91
91
|
),
|
|
@@ -98,7 +98,7 @@ generate_epub(epub_data, "book_with_cover.epub")
|
|
|
98
98
|
### Nested Chapter Structure
|
|
99
99
|
|
|
100
100
|
```python
|
|
101
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
101
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind
|
|
102
102
|
|
|
103
103
|
epub_data = EpubData(
|
|
104
104
|
chapters=[
|
|
@@ -109,7 +109,7 @@ epub_data = EpubData(
|
|
|
109
109
|
title="Chapter 1.1",
|
|
110
110
|
get_chapter=lambda: Chapter(
|
|
111
111
|
elements=[
|
|
112
|
-
|
|
112
|
+
TextBlock(kind=TextKind.BODY, content=["Content 1.1..."]),
|
|
113
113
|
]
|
|
114
114
|
),
|
|
115
115
|
),
|
|
@@ -117,7 +117,7 @@ epub_data = EpubData(
|
|
|
117
117
|
title="Chapter 1.2",
|
|
118
118
|
get_chapter=lambda: Chapter(
|
|
119
119
|
elements=[
|
|
120
|
-
|
|
120
|
+
TextBlock(kind=TextKind.BODY, content=["Content 1.2..."]),
|
|
121
121
|
]
|
|
122
122
|
),
|
|
123
123
|
),
|
|
@@ -133,7 +133,7 @@ generate_epub(epub_data, "book_with_nested_chapters.epub")
|
|
|
133
133
|
|
|
134
134
|
```python
|
|
135
135
|
from pathlib import Path
|
|
136
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
136
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Image
|
|
137
137
|
|
|
138
138
|
epub_data = EpubData(
|
|
139
139
|
chapters=[
|
|
@@ -141,7 +141,7 @@ epub_data = EpubData(
|
|
|
141
141
|
title="Chapter 1",
|
|
142
142
|
get_chapter=lambda: Chapter(
|
|
143
143
|
elements=[
|
|
144
|
-
|
|
144
|
+
TextBlock(kind=TextKind.BODY, content=["Here's an image:"]),
|
|
145
145
|
Image(
|
|
146
146
|
path=Path("image.png"), # Image path
|
|
147
147
|
alt_text="Image description",
|
|
@@ -158,7 +158,7 @@ generate_epub(epub_data, "book_with_images.epub")
|
|
|
158
158
|
### Add Footnotes
|
|
159
159
|
|
|
160
160
|
```python
|
|
161
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
161
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Mark, Footnote
|
|
162
162
|
|
|
163
163
|
epub_data = EpubData(
|
|
164
164
|
chapters=[
|
|
@@ -166,7 +166,7 @@ epub_data = EpubData(
|
|
|
166
166
|
title="Chapter 1",
|
|
167
167
|
get_chapter=lambda: Chapter(
|
|
168
168
|
elements=[
|
|
169
|
-
|
|
169
|
+
TextBlock(
|
|
170
170
|
kind=TextKind.BODY,
|
|
171
171
|
content=[
|
|
172
172
|
"This is text with a footnote",
|
|
@@ -179,7 +179,7 @@ epub_data = EpubData(
|
|
|
179
179
|
Footnote(
|
|
180
180
|
id=1,
|
|
181
181
|
contents=[
|
|
182
|
-
|
|
182
|
+
TextBlock(kind=TextKind.BODY, content=["This is the footnote content."]),
|
|
183
183
|
],
|
|
184
184
|
),
|
|
185
185
|
],
|
|
@@ -194,7 +194,7 @@ generate_epub(epub_data, "book_with_footnotes.epub")
|
|
|
194
194
|
### Add Tables
|
|
195
195
|
|
|
196
196
|
```python
|
|
197
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
197
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Table
|
|
198
198
|
|
|
199
199
|
epub_data = EpubData(
|
|
200
200
|
chapters=[
|
|
@@ -202,7 +202,7 @@ epub_data = EpubData(
|
|
|
202
202
|
title="Chapter 1",
|
|
203
203
|
get_chapter=lambda: Chapter(
|
|
204
204
|
elements=[
|
|
205
|
-
|
|
205
|
+
TextBlock(kind=TextKind.BODY, content=["Here's a table:"]),
|
|
206
206
|
Table(
|
|
207
207
|
html_content="""
|
|
208
208
|
<table>
|
|
@@ -223,8 +223,10 @@ generate_epub(epub_data, "book_with_tables.epub")
|
|
|
223
223
|
|
|
224
224
|
### Add Math Formulas
|
|
225
225
|
|
|
226
|
+
Block-level formulas:
|
|
227
|
+
|
|
226
228
|
```python
|
|
227
|
-
from epub_generator import generate_epub, EpubData, TocItem, Chapter,
|
|
229
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Formula, LaTeXRender
|
|
228
230
|
|
|
229
231
|
epub_data = EpubData(
|
|
230
232
|
chapters=[
|
|
@@ -232,8 +234,8 @@ epub_data = EpubData(
|
|
|
232
234
|
title="Chapter 1",
|
|
233
235
|
get_chapter=lambda: Chapter(
|
|
234
236
|
elements=[
|
|
235
|
-
|
|
236
|
-
Formula(latex_expression="x^2 + y^2 = z^2"), #
|
|
237
|
+
TextBlock(kind=TextKind.BODY, content=["Pythagorean theorem:"]),
|
|
238
|
+
Formula(latex_expression="x^2 + y^2 = z^2"), # Block-level formula
|
|
237
239
|
]
|
|
238
240
|
),
|
|
239
241
|
),
|
|
@@ -244,10 +246,73 @@ epub_data = EpubData(
|
|
|
244
246
|
generate_epub(epub_data, "book_with_math.epub", latex_render=LaTeXRender.MATHML)
|
|
245
247
|
```
|
|
246
248
|
|
|
249
|
+
Inline formulas embedded in text:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
epub_data = EpubData(
|
|
253
|
+
chapters=[
|
|
254
|
+
TocItem(
|
|
255
|
+
title="Chapter 1",
|
|
256
|
+
get_chapter=lambda: Chapter(
|
|
257
|
+
elements=[
|
|
258
|
+
TextBlock(
|
|
259
|
+
kind=TextKind.BODY,
|
|
260
|
+
content=[
|
|
261
|
+
"The Pythagorean theorem ",
|
|
262
|
+
Formula(latex_expression="a^2 + b^2 = c^2"), # Inline formula
|
|
263
|
+
" is fundamental. Einstein's equation ",
|
|
264
|
+
Formula(latex_expression="E = mc^2"),
|
|
265
|
+
" shows mass-energy equivalence.",
|
|
266
|
+
],
|
|
267
|
+
),
|
|
268
|
+
]
|
|
269
|
+
),
|
|
270
|
+
),
|
|
271
|
+
],
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
generate_epub(epub_data, "book_with_inline_math.epub", latex_render=LaTeXRender.MATHML)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Add Custom HTML Tags
|
|
278
|
+
|
|
279
|
+
You can embed custom HTML tags within text content:
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, HTMLTag
|
|
283
|
+
|
|
284
|
+
epub_data = EpubData(
|
|
285
|
+
chapters=[
|
|
286
|
+
TocItem(
|
|
287
|
+
title="Chapter 1",
|
|
288
|
+
get_chapter=lambda: Chapter(elements=[TextBlock(
|
|
289
|
+
kind=TextKind.BODY,
|
|
290
|
+
content=[
|
|
291
|
+
"This is normal text with ",
|
|
292
|
+
HTMLTag(
|
|
293
|
+
name="span",
|
|
294
|
+
attributes=[("class", "highlight"), ("style", "color: red;")],
|
|
295
|
+
content=["highlighted content"],
|
|
296
|
+
),
|
|
297
|
+
" and more text with ",
|
|
298
|
+
HTMLTag(
|
|
299
|
+
name="strong",
|
|
300
|
+
content=["bold text"],
|
|
301
|
+
),
|
|
302
|
+
".",
|
|
303
|
+
],
|
|
304
|
+
)]),
|
|
305
|
+
),
|
|
306
|
+
],
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
generate_epub(epub_data, "book_with_html_tags.epub")
|
|
310
|
+
```
|
|
311
|
+
|
|
247
312
|
### Add Prefaces
|
|
248
313
|
|
|
249
314
|
```python
|
|
250
|
-
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter,
|
|
315
|
+
from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
|
|
251
316
|
|
|
252
317
|
epub_data = EpubData(
|
|
253
318
|
meta=BookMeta(title="Book with Prefaces"),
|
|
@@ -256,8 +321,8 @@ epub_data = EpubData(
|
|
|
256
321
|
title="Preface",
|
|
257
322
|
get_chapter=lambda: Chapter(
|
|
258
323
|
elements=[
|
|
259
|
-
|
|
260
|
-
|
|
324
|
+
TextBlock(kind=TextKind.HEADLINE, content=["Preface"]),
|
|
325
|
+
TextBlock(kind=TextKind.BODY, content=["This is the preface content..."]),
|
|
261
326
|
]
|
|
262
327
|
),
|
|
263
328
|
),
|
|
@@ -267,7 +332,7 @@ epub_data = EpubData(
|
|
|
267
332
|
title="Chapter 1",
|
|
268
333
|
get_chapter=lambda: Chapter(
|
|
269
334
|
elements=[
|
|
270
|
-
|
|
335
|
+
TextBlock(kind=TextKind.BODY, content=["Main content..."]),
|
|
271
336
|
]
|
|
272
337
|
),
|
|
273
338
|
),
|
|
@@ -360,12 +425,12 @@ class Chapter:
|
|
|
360
425
|
|
|
361
426
|
`ContentBlock` is a union of:
|
|
362
427
|
|
|
363
|
-
- **`
|
|
428
|
+
- **`TextBlock`**: Text paragraph
|
|
364
429
|
```python
|
|
365
430
|
@dataclass
|
|
366
|
-
class
|
|
367
|
-
kind: TextKind
|
|
368
|
-
content: list[str | Mark]
|
|
431
|
+
class TextBlock:
|
|
432
|
+
kind: TextKind # BODY | HEADLINE | QUOTE
|
|
433
|
+
content: list[str | Mark | Formula | HTMLTag] # Text with optional marks, inline formulas, and HTML tags
|
|
369
434
|
```
|
|
370
435
|
|
|
371
436
|
- **`Image`**: Image reference
|
|
@@ -390,6 +455,15 @@ class Chapter:
|
|
|
390
455
|
latex_expression: str # LaTeX expression
|
|
391
456
|
```
|
|
392
457
|
|
|
458
|
+
- **`HTMLTag`**: HTML tag
|
|
459
|
+
```python
|
|
460
|
+
@dataclass
|
|
461
|
+
class HTMLTag:
|
|
462
|
+
name: str # Tag name (e.g., "span", "div")
|
|
463
|
+
attributes: list[tuple[str, str]] = [] # List of (attribute, value) pairs
|
|
464
|
+
content: list[str | Mark | Formula | HTMLTag] = [] # Inner HTML content
|
|
465
|
+
```
|
|
466
|
+
|
|
393
467
|
#### `Footnote`
|
|
394
468
|
|
|
395
469
|
Footnote/citation.
|
|
@@ -8,10 +8,11 @@ from .types import (
|
|
|
8
8
|
EpubData,
|
|
9
9
|
Footnote,
|
|
10
10
|
Formula,
|
|
11
|
+
HTMLTag,
|
|
11
12
|
Image,
|
|
12
13
|
Mark,
|
|
13
14
|
Table,
|
|
14
|
-
|
|
15
|
+
TextBlock,
|
|
15
16
|
TextKind,
|
|
16
17
|
TocItem,
|
|
17
18
|
)
|
|
@@ -29,10 +30,11 @@ __all__ = [
|
|
|
29
30
|
"Chapter",
|
|
30
31
|
"ChapterGetter",
|
|
31
32
|
"ContentBlock",
|
|
32
|
-
"
|
|
33
|
+
"TextBlock",
|
|
33
34
|
"TextKind",
|
|
34
35
|
"Table",
|
|
35
36
|
"Formula",
|
|
37
|
+
"HTMLTag",
|
|
36
38
|
"Image",
|
|
37
39
|
"Footnote",
|
|
38
40
|
"Mark",
|
|
@@ -53,4 +53,16 @@ div.alt-wrapper th,
|
|
|
53
53
|
div.alt-wrapper th {
|
|
54
54
|
background-color: #f6f8fa;
|
|
55
55
|
font-weight: 600;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
span.formula-inline {
|
|
59
|
+
display: inline;
|
|
60
|
+
vertical-align: middle;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
span.formula-inline img {
|
|
64
|
+
display: inline-block;
|
|
65
|
+
vertical-align: middle;
|
|
66
|
+
margin: 0 0.2em;
|
|
67
|
+
max-height: 1.2em;
|
|
56
68
|
}
|
|
@@ -34,7 +34,12 @@ def process_table(context: Context, table: Table) -> Element | None:
|
|
|
34
34
|
return None
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def process_formula(
|
|
37
|
+
def process_formula(
|
|
38
|
+
context: Context,
|
|
39
|
+
formula: Formula,
|
|
40
|
+
inline_mode: bool,
|
|
41
|
+
) -> Element | None:
|
|
42
|
+
|
|
38
43
|
if context.latex_render == LaTeXRender.CLIPPING:
|
|
39
44
|
return None
|
|
40
45
|
|
|
@@ -43,22 +48,28 @@ def process_formula(context: Context, formula: Formula) -> Element | None:
|
|
|
43
48
|
return None
|
|
44
49
|
|
|
45
50
|
if context.latex_render == LaTeXRender.MATHML:
|
|
46
|
-
return _latex2mathml(
|
|
47
|
-
|
|
51
|
+
return _latex2mathml(
|
|
52
|
+
latex=latex_expr,
|
|
53
|
+
inline_mode=inline_mode,
|
|
54
|
+
)
|
|
48
55
|
elif context.latex_render == LaTeXRender.SVG:
|
|
49
56
|
svg_image = _latex_formula2svg(latex_expr)
|
|
50
57
|
if svg_image is None:
|
|
51
58
|
return None
|
|
52
59
|
file_name = context.add_asset(
|
|
53
|
-
data=svg_image,
|
|
54
|
-
media_type="image/svg+xml",
|
|
60
|
+
data=svg_image,
|
|
61
|
+
media_type="image/svg+xml",
|
|
55
62
|
file_ext=".svg",
|
|
56
63
|
)
|
|
57
64
|
img_element = Element("img")
|
|
58
65
|
img_element.set("src", f"../assets/{file_name}")
|
|
59
66
|
img_element.set("alt", "formula")
|
|
60
67
|
|
|
61
|
-
|
|
68
|
+
if inline_mode:
|
|
69
|
+
wrapper = Element("span", attrib={"class": "formula-inline"})
|
|
70
|
+
else:
|
|
71
|
+
wrapper = Element("div", attrib={"class": "alt-wrapper"})
|
|
72
|
+
|
|
62
73
|
wrapper.append(img_element)
|
|
63
74
|
return wrapper
|
|
64
75
|
|
|
@@ -83,9 +94,12 @@ def process_image(context: Context, image: Image) -> Element | None:
|
|
|
83
94
|
_ESCAPE_UNICODE_PATTERN = re.compile(r"&#x([0-9A-Fa-f]{5});")
|
|
84
95
|
|
|
85
96
|
|
|
86
|
-
def _latex2mathml(latex: str) -> None | Element:
|
|
97
|
+
def _latex2mathml(latex: str, inline_mode: bool) -> None | Element:
|
|
87
98
|
try:
|
|
88
|
-
html_latex = convert(
|
|
99
|
+
html_latex = convert(
|
|
100
|
+
latex=latex,
|
|
101
|
+
display="inline" if inline_mode else "block",
|
|
102
|
+
)
|
|
89
103
|
except Exception:
|
|
90
104
|
return None
|
|
91
105
|
|
|
@@ -7,14 +7,15 @@ from ..types import (
|
|
|
7
7
|
Chapter,
|
|
8
8
|
ContentBlock,
|
|
9
9
|
Formula,
|
|
10
|
+
HTMLTag,
|
|
10
11
|
Image,
|
|
11
12
|
Mark,
|
|
12
13
|
Table,
|
|
13
|
-
|
|
14
|
+
TextBlock,
|
|
14
15
|
TextKind,
|
|
15
16
|
)
|
|
16
|
-
from .xml_utils import serialize_element, set_epub_type
|
|
17
17
|
from .gen_asset import process_formula, process_image, process_table
|
|
18
|
+
from .xml_utils import serialize_element, set_epub_type
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def generate_chapter(
|
|
@@ -88,7 +89,7 @@ def _render_footnotes(
|
|
|
88
89
|
|
|
89
90
|
|
|
90
91
|
def _render_content_block(context: Context, block: ContentBlock) -> Element | None:
|
|
91
|
-
if isinstance(block,
|
|
92
|
+
if isinstance(block, TextBlock):
|
|
92
93
|
if block.kind == TextKind.HEADLINE:
|
|
93
94
|
container = Element("h1")
|
|
94
95
|
elif block.kind == TextKind.QUOTE:
|
|
@@ -98,8 +99,11 @@ def _render_content_block(context: Context, block: ContentBlock) -> Element | No
|
|
|
98
99
|
else:
|
|
99
100
|
raise ValueError(f"Unknown TextKind: {block.kind}")
|
|
100
101
|
|
|
101
|
-
_render_text_content(
|
|
102
|
-
|
|
102
|
+
_render_text_content(
|
|
103
|
+
context=context,
|
|
104
|
+
parent=container,
|
|
105
|
+
content=block.content,
|
|
106
|
+
)
|
|
103
107
|
if block.kind == TextKind.QUOTE:
|
|
104
108
|
blockquote = Element("blockquote")
|
|
105
109
|
blockquote.append(container)
|
|
@@ -111,7 +115,7 @@ def _render_content_block(context: Context, block: ContentBlock) -> Element | No
|
|
|
111
115
|
return process_table(context, block)
|
|
112
116
|
|
|
113
117
|
elif isinstance(block, Formula):
|
|
114
|
-
return process_formula(context, block)
|
|
118
|
+
return process_formula(context, block, inline_mode=False)
|
|
115
119
|
|
|
116
120
|
elif isinstance(block, Image):
|
|
117
121
|
return process_image(context, block)
|
|
@@ -120,7 +124,7 @@ def _render_content_block(context: Context, block: ContentBlock) -> Element | No
|
|
|
120
124
|
return None
|
|
121
125
|
|
|
122
126
|
|
|
123
|
-
def _render_text_content(parent: Element, content: list[str | Mark]) -> None:
|
|
127
|
+
def _render_text_content(context: Context, parent: Element, content: list[str | Mark | Formula | HTMLTag]) -> None:
|
|
124
128
|
"""Render text content with inline citation marks."""
|
|
125
129
|
current_element = parent
|
|
126
130
|
for item in content:
|
|
@@ -135,6 +139,29 @@ def _render_text_content(parent: Element, content: list[str | Mark]) -> None:
|
|
|
135
139
|
current_element.tail = item
|
|
136
140
|
else:
|
|
137
141
|
current_element.tail += item
|
|
142
|
+
|
|
143
|
+
elif isinstance(item, HTMLTag):
|
|
144
|
+
tag_element = Element(item.name)
|
|
145
|
+
for attr, value in item.attributes:
|
|
146
|
+
tag_element.set(attr, value)
|
|
147
|
+
_render_text_content(
|
|
148
|
+
context=context,
|
|
149
|
+
parent=tag_element,
|
|
150
|
+
content=item.content,
|
|
151
|
+
)
|
|
152
|
+
parent.append(tag_element)
|
|
153
|
+
current_element = tag_element
|
|
154
|
+
|
|
155
|
+
elif isinstance(item, Formula):
|
|
156
|
+
formula_element = process_formula(
|
|
157
|
+
context=context,
|
|
158
|
+
formula=item,
|
|
159
|
+
inline_mode=True,
|
|
160
|
+
)
|
|
161
|
+
if formula_element is not None:
|
|
162
|
+
parent.append(formula_element)
|
|
163
|
+
current_element = formula_element
|
|
164
|
+
|
|
138
165
|
elif isinstance(item, Mark):
|
|
139
166
|
# EPUB 3.0 noteref with semantic attributes
|
|
140
167
|
anchor = Element("a")
|
|
@@ -6,9 +6,10 @@ from uuid import uuid4
|
|
|
6
6
|
from zipfile import ZipFile
|
|
7
7
|
|
|
8
8
|
from ..context import Context, Template
|
|
9
|
+
from ..html_tag import search_content
|
|
9
10
|
from ..i18n import I18N
|
|
10
11
|
from ..options import LaTeXRender, TableRender
|
|
11
|
-
from ..types import EpubData, Formula
|
|
12
|
+
from ..types import Chapter, EpubData, Formula, TextBlock
|
|
12
13
|
from .gen_chapter import generate_chapter
|
|
13
14
|
from .gen_nav import gen_nav
|
|
14
15
|
from .gen_toc import NavPoint, gen_toc
|
|
@@ -135,10 +136,23 @@ def _write_chapters_from_data(
|
|
|
135
136
|
assert_not_aborted()
|
|
136
137
|
|
|
137
138
|
|
|
138
|
-
def _chapter_has_formula(chapter) -> bool:
|
|
139
|
+
def _chapter_has_formula(chapter: Chapter) -> bool:
|
|
140
|
+
"""Check if chapter contains any formulas (block-level or inline)."""
|
|
139
141
|
for element in chapter.elements:
|
|
140
142
|
if isinstance(element, Formula):
|
|
141
143
|
return True
|
|
144
|
+
if isinstance(element, TextBlock):
|
|
145
|
+
for item in search_content(element.content):
|
|
146
|
+
if isinstance(item, Formula):
|
|
147
|
+
return True
|
|
148
|
+
for footnote in chapter.footnotes:
|
|
149
|
+
for content_block in footnote.contents:
|
|
150
|
+
if isinstance(content_block, Formula):
|
|
151
|
+
return True
|
|
152
|
+
if isinstance(content_block, TextBlock):
|
|
153
|
+
for item in search_content(content_block.content):
|
|
154
|
+
if isinstance(item, Formula):
|
|
155
|
+
return True
|
|
142
156
|
return False
|
|
143
157
|
|
|
144
158
|
def _write_basic_files(
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from xml.etree.ElementTree import Element, tostring
|
|
3
|
+
|
|
4
|
+
_EPUB_NS = "http://www.idpf.org/2007/ops"
|
|
5
|
+
_MATHML_NS = "http://www.w3.org/1998/Math/MathML"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def set_epub_type(element: Element, epub_type: str) -> None:
|
|
9
|
+
element.set(f"{{{_EPUB_NS}}}type", epub_type)
|
|
10
|
+
|
|
11
|
+
def serialize_element(element: Element) -> str:
|
|
12
|
+
xml_string = tostring(element, encoding="unicode")
|
|
13
|
+
for prefix, namespace_uri, keep_xmlns in (
|
|
14
|
+
("epub", _EPUB_NS, False), # EPUB namespace: remove xmlns (declared at root)
|
|
15
|
+
("m", _MATHML_NS, True), # MathML namespace: keep xmlns with clean prefix
|
|
16
|
+
):
|
|
17
|
+
xml_string = xml_string.replace(f"{{{namespace_uri}}}", f"{prefix}:")
|
|
18
|
+
pattern = r"xmlns:(ns\d+)=\"" + re.escape(namespace_uri) + r"\""
|
|
19
|
+
matches = re.findall(pattern, xml_string)
|
|
20
|
+
|
|
21
|
+
for ns_prefix in matches:
|
|
22
|
+
if keep_xmlns:
|
|
23
|
+
xml_string = xml_string.replace(
|
|
24
|
+
f" xmlns:{ns_prefix}=\"{namespace_uri}\"",
|
|
25
|
+
f" xmlns:{prefix}=\"{namespace_uri}\""
|
|
26
|
+
)
|
|
27
|
+
else:
|
|
28
|
+
xml_string = xml_string.replace(f" xmlns:{ns_prefix}=\"{namespace_uri}\"", "")
|
|
29
|
+
xml_string = xml_string.replace(f"{ns_prefix}:", f"{prefix}:")
|
|
30
|
+
|
|
31
|
+
return xml_string
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from typing import Generator
|
|
2
|
+
|
|
3
|
+
from .types import Formula, HTMLTag, Mark
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def search_content(content: list[str | Mark | Formula | HTMLTag]) -> Generator[str | Mark | Formula, None, None]:
|
|
7
|
+
for child in content:
|
|
8
|
+
if isinstance(child, HTMLTag):
|
|
9
|
+
yield from search_content(child.content)
|
|
10
|
+
else:
|
|
11
|
+
yield child
|
|
@@ -83,13 +83,6 @@ class Mark:
|
|
|
83
83
|
id: int
|
|
84
84
|
"""Citation ID, matches Footnote.id"""
|
|
85
85
|
|
|
86
|
-
@dataclass
|
|
87
|
-
class Text:
|
|
88
|
-
kind: TextKind
|
|
89
|
-
"""Kind of text block."""
|
|
90
|
-
content: list[str | Mark]
|
|
91
|
-
"""Text content with optional citation marks."""
|
|
92
|
-
|
|
93
86
|
@dataclass
|
|
94
87
|
class Table:
|
|
95
88
|
"""HTML table."""
|
|
@@ -113,6 +106,12 @@ class Image:
|
|
|
113
106
|
alt_text: str = "image"
|
|
114
107
|
"""Alt text (defaults to "image")"""
|
|
115
108
|
|
|
109
|
+
@dataclass
|
|
110
|
+
class TextBlock:
|
|
111
|
+
kind: TextKind
|
|
112
|
+
"""Kind of text block."""
|
|
113
|
+
content: list["str | Mark | Formula | HTMLTag"]
|
|
114
|
+
"""Text content with optional citation marks."""
|
|
116
115
|
|
|
117
116
|
@dataclass
|
|
118
117
|
class Footnote:
|
|
@@ -127,7 +126,7 @@ class Footnote:
|
|
|
127
126
|
"""Content blocks"""
|
|
128
127
|
|
|
129
128
|
|
|
130
|
-
ContentBlock =
|
|
129
|
+
ContentBlock = TextBlock | Table | Formula | Image
|
|
131
130
|
"""Union of all content blocks that appear in main chapter content."""
|
|
132
131
|
|
|
133
132
|
@dataclass
|
|
@@ -139,4 +138,17 @@ class Chapter:
|
|
|
139
138
|
footnotes: list[Footnote] = field(default_factory=list)
|
|
140
139
|
"""Footnotes"""
|
|
141
140
|
|
|
142
|
-
ChapterGetter = Callable[[], Chapter]
|
|
141
|
+
ChapterGetter = Callable[[], Chapter]
|
|
142
|
+
|
|
143
|
+
@dataclass
|
|
144
|
+
class HTMLTag:
|
|
145
|
+
"""Generic HTML tag representation."""
|
|
146
|
+
|
|
147
|
+
name: str
|
|
148
|
+
"""Tag name"""
|
|
149
|
+
|
|
150
|
+
attributes: list[tuple[str, str]] = field(default_factory=list)
|
|
151
|
+
"""List of (attribute, value) pairs"""
|
|
152
|
+
|
|
153
|
+
content: list["str | Mark | Formula | HTMLTag"] = field(default_factory=list)
|
|
154
|
+
"""Inner HTML content"""
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
from xml.etree.ElementTree import Element, tostring
|
|
3
|
-
|
|
4
|
-
_EPUB_NS = "http://www.idpf.org/2007/ops"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def set_epub_type(element: Element, epub_type: str) -> None:
|
|
8
|
-
element.set(f"{{{_EPUB_NS}}}type", epub_type)
|
|
9
|
-
|
|
10
|
-
def serialize_element(element: Element) -> str:
|
|
11
|
-
xml_string = tostring(element, encoding="unicode")
|
|
12
|
-
xml_string = xml_string.replace(f"{{{_EPUB_NS}}}", "epub:")
|
|
13
|
-
ns_pattern = r'xmlns:(ns\d+)="' + re.escape(_EPUB_NS) + r'"'
|
|
14
|
-
matches = re.findall(ns_pattern, xml_string)
|
|
15
|
-
for ns_prefix in matches:
|
|
16
|
-
xml_string = xml_string.replace(f' xmlns:{ns_prefix}="{_EPUB_NS}"', "")
|
|
17
|
-
xml_string = xml_string.replace(f"{ns_prefix}:", "epub:")
|
|
18
|
-
return xml_string
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|