epub-generator 0.1.2__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.
Files changed (25) hide show
  1. {epub_generator-0.1.2 → epub_generator-0.1.3}/PKG-INFO +74 -30
  2. {epub_generator-0.1.2 → epub_generator-0.1.3}/README.md +73 -29
  3. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/__init__.py +4 -2
  4. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/generation/gen_chapter.py +21 -5
  5. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/generation/gen_epub.py +7 -6
  6. epub_generator-0.1.3/epub_generator/html_tag.py +11 -0
  7. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/types.py +17 -4
  8. {epub_generator-0.1.2 → epub_generator-0.1.3}/pyproject.toml +1 -1
  9. {epub_generator-0.1.2 → epub_generator-0.1.3}/LICENSE +0 -0
  10. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/context.py +0 -0
  11. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/data/container.xml.jinja +0 -0
  12. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/data/content.opf.jinja +0 -0
  13. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/data/cover.xhtml.jinja +0 -0
  14. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/data/mimetype.jinja +0 -0
  15. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/data/nav.xhtml.jinja +0 -0
  16. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/data/part.xhtml.jinja +0 -0
  17. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/data/style.css.jinja +0 -0
  18. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/generation/__init__.py +0 -0
  19. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/generation/gen_asset.py +0 -0
  20. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/generation/gen_nav.py +0 -0
  21. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/generation/gen_toc.py +0 -0
  22. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/generation/xml_utils.py +0 -0
  23. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/i18n.py +0 -0
  24. {epub_generator-0.1.2 → epub_generator-0.1.3}/epub_generator/options.py +0 -0
  25. {epub_generator-0.1.2 → 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.2
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, Text, TextKind
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
- Text(kind=TextKind.HEADLINE, content=["Chapter 1"]),
65
- Text(kind=TextKind.BODY, content=["This is the first paragraph."]),
66
- Text(kind=TextKind.BODY, content=["This is the second paragraph."]),
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,7 +80,7 @@ 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 (block-level and inline), 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
85
  - **Math Support**: LaTeX to MathML/SVG conversion with inline formula support
86
86
  - **Type Safe**: Full type annotations included
@@ -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, Text, TextKind
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
- Text(kind=TextKind.HEADLINE, content=["Chapter 1"]),
115
- Text(kind=TextKind.BODY, content=["Main content..."]),
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, Text, TextKind
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
- Text(kind=TextKind.BODY, content=["Content 1.1..."]),
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
- Text(kind=TextKind.BODY, content=["Content 1.2..."]),
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, Text, TextKind, Image
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
- Text(kind=TextKind.BODY, content=["Here's an image:"]),
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, Text, TextKind, Mark, Footnote
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
- Text(
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
- Text(kind=TextKind.BODY, content=["This is the footnote content."]),
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, Text, TextKind, Table
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
- Text(kind=TextKind.BODY, content=["Here's a table:"]),
232
+ TextBlock(kind=TextKind.BODY, content=["Here's a table:"]),
233
233
  Table(
234
234
  html_content="""
235
235
  <table>
@@ -253,7 +253,7 @@ generate_epub(epub_data, "book_with_tables.epub")
253
253
  Block-level formulas:
254
254
 
255
255
  ```python
256
- from epub_generator import generate_epub, EpubData, TocItem, Chapter, Text, TextKind, Formula, LaTeXRender
256
+ from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Formula, LaTeXRender
257
257
 
258
258
  epub_data = EpubData(
259
259
  chapters=[
@@ -261,7 +261,7 @@ epub_data = EpubData(
261
261
  title="Chapter 1",
262
262
  get_chapter=lambda: Chapter(
263
263
  elements=[
264
- Text(kind=TextKind.BODY, content=["Pythagorean theorem:"]),
264
+ TextBlock(kind=TextKind.BODY, content=["Pythagorean theorem:"]),
265
265
  Formula(latex_expression="x^2 + y^2 = z^2"), # Block-level formula
266
266
  ]
267
267
  ),
@@ -282,7 +282,7 @@ epub_data = EpubData(
282
282
  title="Chapter 1",
283
283
  get_chapter=lambda: Chapter(
284
284
  elements=[
285
- Text(
285
+ TextBlock(
286
286
  kind=TextKind.BODY,
287
287
  content=[
288
288
  "The Pythagorean theorem ",
@@ -301,10 +301,45 @@ epub_data = EpubData(
301
301
  generate_epub(epub_data, "book_with_inline_math.epub", latex_render=LaTeXRender.MATHML)
302
302
  ```
303
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
+
304
339
  ### Add Prefaces
305
340
 
306
341
  ```python
307
- from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, Text, TextKind
342
+ from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
308
343
 
309
344
  epub_data = EpubData(
310
345
  meta=BookMeta(title="Book with Prefaces"),
@@ -313,8 +348,8 @@ epub_data = EpubData(
313
348
  title="Preface",
314
349
  get_chapter=lambda: Chapter(
315
350
  elements=[
316
- Text(kind=TextKind.HEADLINE, content=["Preface"]),
317
- Text(kind=TextKind.BODY, content=["This is the preface content..."]),
351
+ TextBlock(kind=TextKind.HEADLINE, content=["Preface"]),
352
+ TextBlock(kind=TextKind.BODY, content=["This is the preface content..."]),
318
353
  ]
319
354
  ),
320
355
  ),
@@ -324,7 +359,7 @@ epub_data = EpubData(
324
359
  title="Chapter 1",
325
360
  get_chapter=lambda: Chapter(
326
361
  elements=[
327
- Text(kind=TextKind.BODY, content=["Main content..."]),
362
+ TextBlock(kind=TextKind.BODY, content=["Main content..."]),
328
363
  ]
329
364
  ),
330
365
  ),
@@ -417,12 +452,12 @@ class Chapter:
417
452
 
418
453
  `ContentBlock` is a union of:
419
454
 
420
- - **`Text`**: Text paragraph
455
+ - **`TextBlock`**: Text paragraph
421
456
  ```python
422
457
  @dataclass
423
- class Text:
424
- kind: TextKind # BODY | HEADLINE | QUOTE
425
- content: list[str | Mark | Formula] # Text with optional marks and inline formulas
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
426
461
  ```
427
462
 
428
463
  - **`Image`**: Image reference
@@ -447,6 +482,15 @@ class Chapter:
447
482
  latex_expression: str # LaTeX expression
448
483
  ```
449
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
+
450
494
  #### `Footnote`
451
495
 
452
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, Text, TextKind
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
- Text(kind=TextKind.HEADLINE, content=["Chapter 1"]),
38
- Text(kind=TextKind.BODY, content=["This is the first paragraph."]),
39
- Text(kind=TextKind.BODY, content=["This is the second paragraph."]),
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,7 +53,7 @@ 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 (block-level and inline), 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
58
  - **Math Support**: LaTeX to MathML/SVG conversion with inline formula support
59
59
  - **Type Safe**: Full type annotations included
@@ -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, Text, TextKind
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
- Text(kind=TextKind.HEADLINE, content=["Chapter 1"]),
88
- Text(kind=TextKind.BODY, content=["Main content..."]),
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, Text, TextKind
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
- Text(kind=TextKind.BODY, content=["Content 1.1..."]),
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
- Text(kind=TextKind.BODY, content=["Content 1.2..."]),
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, Text, TextKind, Image
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
- Text(kind=TextKind.BODY, content=["Here's an image:"]),
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, Text, TextKind, Mark, Footnote
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
- Text(
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
- Text(kind=TextKind.BODY, content=["This is the footnote content."]),
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, Text, TextKind, Table
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
- Text(kind=TextKind.BODY, content=["Here's a table:"]),
205
+ TextBlock(kind=TextKind.BODY, content=["Here's a table:"]),
206
206
  Table(
207
207
  html_content="""
208
208
  <table>
@@ -226,7 +226,7 @@ generate_epub(epub_data, "book_with_tables.epub")
226
226
  Block-level formulas:
227
227
 
228
228
  ```python
229
- from epub_generator import generate_epub, EpubData, TocItem, Chapter, Text, TextKind, Formula, LaTeXRender
229
+ from epub_generator import generate_epub, EpubData, TocItem, Chapter, TextBlock, TextKind, Formula, LaTeXRender
230
230
 
231
231
  epub_data = EpubData(
232
232
  chapters=[
@@ -234,7 +234,7 @@ epub_data = EpubData(
234
234
  title="Chapter 1",
235
235
  get_chapter=lambda: Chapter(
236
236
  elements=[
237
- Text(kind=TextKind.BODY, content=["Pythagorean theorem:"]),
237
+ TextBlock(kind=TextKind.BODY, content=["Pythagorean theorem:"]),
238
238
  Formula(latex_expression="x^2 + y^2 = z^2"), # Block-level formula
239
239
  ]
240
240
  ),
@@ -255,7 +255,7 @@ epub_data = EpubData(
255
255
  title="Chapter 1",
256
256
  get_chapter=lambda: Chapter(
257
257
  elements=[
258
- Text(
258
+ TextBlock(
259
259
  kind=TextKind.BODY,
260
260
  content=[
261
261
  "The Pythagorean theorem ",
@@ -274,10 +274,45 @@ epub_data = EpubData(
274
274
  generate_epub(epub_data, "book_with_inline_math.epub", latex_render=LaTeXRender.MATHML)
275
275
  ```
276
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
+
277
312
  ### Add Prefaces
278
313
 
279
314
  ```python
280
- from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, Text, TextKind
315
+ from epub_generator import generate_epub, EpubData, BookMeta, TocItem, Chapter, TextBlock, TextKind
281
316
 
282
317
  epub_data = EpubData(
283
318
  meta=BookMeta(title="Book with Prefaces"),
@@ -286,8 +321,8 @@ epub_data = EpubData(
286
321
  title="Preface",
287
322
  get_chapter=lambda: Chapter(
288
323
  elements=[
289
- Text(kind=TextKind.HEADLINE, content=["Preface"]),
290
- Text(kind=TextKind.BODY, content=["This is the preface content..."]),
324
+ TextBlock(kind=TextKind.HEADLINE, content=["Preface"]),
325
+ TextBlock(kind=TextKind.BODY, content=["This is the preface content..."]),
291
326
  ]
292
327
  ),
293
328
  ),
@@ -297,7 +332,7 @@ epub_data = EpubData(
297
332
  title="Chapter 1",
298
333
  get_chapter=lambda: Chapter(
299
334
  elements=[
300
- Text(kind=TextKind.BODY, content=["Main content..."]),
335
+ TextBlock(kind=TextKind.BODY, content=["Main content..."]),
301
336
  ]
302
337
  ),
303
338
  ),
@@ -390,12 +425,12 @@ class Chapter:
390
425
 
391
426
  `ContentBlock` is a union of:
392
427
 
393
- - **`Text`**: Text paragraph
428
+ - **`TextBlock`**: Text paragraph
394
429
  ```python
395
430
  @dataclass
396
- class Text:
397
- kind: TextKind # BODY | HEADLINE | QUOTE
398
- content: list[str | Mark | Formula] # Text with optional marks and inline formulas
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
399
434
  ```
400
435
 
401
436
  - **`Image`**: Image reference
@@ -420,6 +455,15 @@ class Chapter:
420
455
  latex_expression: str # LaTeX expression
421
456
  ```
422
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
+
423
467
  #### `Footnote`
424
468
 
425
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
- Text,
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
- "Text",
33
+ "TextBlock",
33
34
  "TextKind",
34
35
  "Table",
35
36
  "Formula",
37
+ "HTMLTag",
36
38
  "Image",
37
39
  "Footnote",
38
40
  "Mark",
@@ -7,10 +7,11 @@ from ..types import (
7
7
  Chapter,
8
8
  ContentBlock,
9
9
  Formula,
10
+ HTMLTag,
10
11
  Image,
11
12
  Mark,
12
13
  Table,
13
- Text,
14
+ TextBlock,
14
15
  TextKind,
15
16
  )
16
17
  from .gen_asset import process_formula, process_image, process_table
@@ -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, Text):
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(context, container, block.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)
@@ -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(context: Context, parent: Element, content: list[str | Mark | Formula]) -> 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:
@@ -136,6 +140,18 @@ def _render_text_content(context: Context, parent: Element, content: list[str |
136
140
  else:
137
141
  current_element.tail += item
138
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
+
139
155
  elif isinstance(item, Formula):
140
156
  formula_element = process_formula(
141
157
  context=context,
@@ -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, Text
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,21 +136,21 @@ 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:
139
140
  """Check if chapter contains any formulas (block-level or inline)."""
140
141
  for element in chapter.elements:
141
142
  if isinstance(element, Formula):
142
143
  return True
143
- if isinstance(element, Text):
144
- for item in element.content:
144
+ if isinstance(element, TextBlock):
145
+ for item in search_content(element.content):
145
146
  if isinstance(item, Formula):
146
147
  return True
147
148
  for footnote in chapter.footnotes:
148
149
  for content_block in footnote.contents:
149
150
  if isinstance(content_block, Formula):
150
151
  return True
151
- if isinstance(content_block, Text):
152
- for item in content_block.content:
152
+ if isinstance(content_block, TextBlock):
153
+ for item in search_content(content_block.content):
153
154
  if isinstance(item, Formula):
154
155
  return True
155
156
  return False
@@ -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
@@ -107,10 +107,10 @@ class Image:
107
107
  """Alt text (defaults to "image")"""
108
108
 
109
109
  @dataclass
110
- class Text:
110
+ class TextBlock:
111
111
  kind: TextKind
112
112
  """Kind of text block."""
113
- content: list[str | Mark | Formula]
113
+ content: list["str | Mark | Formula | HTMLTag"]
114
114
  """Text content with optional citation marks."""
115
115
 
116
116
  @dataclass
@@ -126,7 +126,7 @@ class Footnote:
126
126
  """Content blocks"""
127
127
 
128
128
 
129
- ContentBlock = Text | Table | Formula | Image
129
+ ContentBlock = TextBlock | Table | Formula | Image
130
130
  """Union of all content blocks that appear in main chapter content."""
131
131
 
132
132
  @dataclass
@@ -138,4 +138,17 @@ class Chapter:
138
138
  footnotes: list[Footnote] = field(default_factory=list)
139
139
  """Footnotes"""
140
140
 
141
- 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,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "epub-generator"
3
- version = "0.1.2"
3
+ version = "0.1.3"
4
4
  description = "A simple Python EPUB 3.0 generator with a single API call"
5
5
  authors = ["Tao Zeyu <i@taozeyu.com>"]
6
6
  license = "MIT"
File without changes