epub-generator 0.1.1__tar.gz → 0.1.2__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.2}/PKG-INFO +35 -5
- {epub_generator-0.1.1 → epub_generator-0.1.2}/README.md +34 -4
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/data/style.css.jinja +12 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/generation/gen_asset.py +22 -8
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/generation/gen_chapter.py +15 -4
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/generation/gen_epub.py +14 -1
- epub_generator-0.1.2/epub_generator/generation/xml_utils.py +31 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/types.py +6 -7
- {epub_generator-0.1.1 → epub_generator-0.1.2}/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.2}/LICENSE +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/__init__.py +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/context.py +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/data/container.xml.jinja +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/data/content.opf.jinja +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/data/cover.xhtml.jinja +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/data/mimetype.jinja +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/data/nav.xhtml.jinja +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/data/part.xhtml.jinja +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/generation/__init__.py +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/generation/gen_nav.py +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/generation/gen_toc.py +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/i18n.py +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/epub_generator/options.py +0 -0
- {epub_generator-0.1.1 → epub_generator-0.1.2}/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.2
|
|
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
|
|
@@ -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
|
|
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
|
|
@@ -250,6 +250,8 @@ 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
256
|
from epub_generator import generate_epub, EpubData, TocItem, Chapter, Text, TextKind, Formula, LaTeXRender
|
|
255
257
|
|
|
@@ -260,7 +262,7 @@ epub_data = EpubData(
|
|
|
260
262
|
get_chapter=lambda: Chapter(
|
|
261
263
|
elements=[
|
|
262
264
|
Text(kind=TextKind.BODY, content=["Pythagorean theorem:"]),
|
|
263
|
-
Formula(latex_expression="x^2 + y^2 = z^2"), #
|
|
265
|
+
Formula(latex_expression="x^2 + y^2 = z^2"), # Block-level formula
|
|
264
266
|
]
|
|
265
267
|
),
|
|
266
268
|
),
|
|
@@ -271,6 +273,34 @@ 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
|
+
Text(
|
|
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
|
+
|
|
274
304
|
### Add Prefaces
|
|
275
305
|
|
|
276
306
|
```python
|
|
@@ -392,7 +422,7 @@ class Chapter:
|
|
|
392
422
|
@dataclass
|
|
393
423
|
class Text:
|
|
394
424
|
kind: TextKind # BODY | HEADLINE | QUOTE
|
|
395
|
-
content: list[str | Mark]
|
|
425
|
+
content: list[str | Mark | Formula] # Text with optional marks and inline formulas
|
|
396
426
|
```
|
|
397
427
|
|
|
398
428
|
- **`Image`**: Image reference
|
|
@@ -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
|
|
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
|
|
@@ -223,6 +223,8 @@ 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
229
|
from epub_generator import generate_epub, EpubData, TocItem, Chapter, Text, TextKind, Formula, LaTeXRender
|
|
228
230
|
|
|
@@ -233,7 +235,7 @@ epub_data = EpubData(
|
|
|
233
235
|
get_chapter=lambda: Chapter(
|
|
234
236
|
elements=[
|
|
235
237
|
Text(kind=TextKind.BODY, content=["Pythagorean theorem:"]),
|
|
236
|
-
Formula(latex_expression="x^2 + y^2 = z^2"), #
|
|
238
|
+
Formula(latex_expression="x^2 + y^2 = z^2"), # Block-level formula
|
|
237
239
|
]
|
|
238
240
|
),
|
|
239
241
|
),
|
|
@@ -244,6 +246,34 @@ 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
|
+
Text(
|
|
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
|
+
|
|
247
277
|
### Add Prefaces
|
|
248
278
|
|
|
249
279
|
```python
|
|
@@ -365,7 +395,7 @@ class Chapter:
|
|
|
365
395
|
@dataclass
|
|
366
396
|
class Text:
|
|
367
397
|
kind: TextKind # BODY | HEADLINE | QUOTE
|
|
368
|
-
content: list[str | Mark]
|
|
398
|
+
content: list[str | Mark | Formula] # Text with optional marks and inline formulas
|
|
369
399
|
```
|
|
370
400
|
|
|
371
401
|
- **`Image`**: Image reference
|
|
@@ -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
|
|
|
@@ -13,8 +13,8 @@ from ..types import (
|
|
|
13
13
|
Text,
|
|
14
14
|
TextKind,
|
|
15
15
|
)
|
|
16
|
-
from .xml_utils import serialize_element, set_epub_type
|
|
17
16
|
from .gen_asset import process_formula, process_image, process_table
|
|
17
|
+
from .xml_utils import serialize_element, set_epub_type
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def generate_chapter(
|
|
@@ -98,7 +98,7 @@ def _render_content_block(context: Context, block: ContentBlock) -> Element | No
|
|
|
98
98
|
else:
|
|
99
99
|
raise ValueError(f"Unknown TextKind: {block.kind}")
|
|
100
100
|
|
|
101
|
-
_render_text_content(container, block.content)
|
|
101
|
+
_render_text_content(context, container, block.content)
|
|
102
102
|
|
|
103
103
|
if block.kind == TextKind.QUOTE:
|
|
104
104
|
blockquote = Element("blockquote")
|
|
@@ -111,7 +111,7 @@ def _render_content_block(context: Context, block: ContentBlock) -> Element | No
|
|
|
111
111
|
return process_table(context, block)
|
|
112
112
|
|
|
113
113
|
elif isinstance(block, Formula):
|
|
114
|
-
return process_formula(context, block)
|
|
114
|
+
return process_formula(context, block, inline_mode=False)
|
|
115
115
|
|
|
116
116
|
elif isinstance(block, Image):
|
|
117
117
|
return process_image(context, block)
|
|
@@ -120,7 +120,7 @@ def _render_content_block(context: Context, block: ContentBlock) -> Element | No
|
|
|
120
120
|
return None
|
|
121
121
|
|
|
122
122
|
|
|
123
|
-
def _render_text_content(parent: Element, content: list[str | Mark]) -> None:
|
|
123
|
+
def _render_text_content(context: Context, parent: Element, content: list[str | Mark | Formula]) -> None:
|
|
124
124
|
"""Render text content with inline citation marks."""
|
|
125
125
|
current_element = parent
|
|
126
126
|
for item in content:
|
|
@@ -135,6 +135,17 @@ def _render_text_content(parent: Element, content: list[str | Mark]) -> None:
|
|
|
135
135
|
current_element.tail = item
|
|
136
136
|
else:
|
|
137
137
|
current_element.tail += item
|
|
138
|
+
|
|
139
|
+
elif isinstance(item, Formula):
|
|
140
|
+
formula_element = process_formula(
|
|
141
|
+
context=context,
|
|
142
|
+
formula=item,
|
|
143
|
+
inline_mode=True,
|
|
144
|
+
)
|
|
145
|
+
if formula_element is not None:
|
|
146
|
+
parent.append(formula_element)
|
|
147
|
+
current_element = formula_element
|
|
148
|
+
|
|
138
149
|
elif isinstance(item, Mark):
|
|
139
150
|
# EPUB 3.0 noteref with semantic attributes
|
|
140
151
|
anchor = Element("a")
|
|
@@ -8,7 +8,7 @@ from zipfile import ZipFile
|
|
|
8
8
|
from ..context import Context, Template
|
|
9
9
|
from ..i18n import I18N
|
|
10
10
|
from ..options import LaTeXRender, TableRender
|
|
11
|
-
from ..types import EpubData, Formula
|
|
11
|
+
from ..types import EpubData, Formula, Text
|
|
12
12
|
from .gen_chapter import generate_chapter
|
|
13
13
|
from .gen_nav import gen_nav
|
|
14
14
|
from .gen_toc import NavPoint, gen_toc
|
|
@@ -136,9 +136,22 @@ def _write_chapters_from_data(
|
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
def _chapter_has_formula(chapter) -> bool:
|
|
139
|
+
"""Check if chapter contains any formulas (block-level or inline)."""
|
|
139
140
|
for element in chapter.elements:
|
|
140
141
|
if isinstance(element, Formula):
|
|
141
142
|
return True
|
|
143
|
+
if isinstance(element, Text):
|
|
144
|
+
for item in element.content:
|
|
145
|
+
if isinstance(item, Formula):
|
|
146
|
+
return True
|
|
147
|
+
for footnote in chapter.footnotes:
|
|
148
|
+
for content_block in footnote.contents:
|
|
149
|
+
if isinstance(content_block, Formula):
|
|
150
|
+
return True
|
|
151
|
+
if isinstance(content_block, Text):
|
|
152
|
+
for item in content_block.content:
|
|
153
|
+
if isinstance(item, Formula):
|
|
154
|
+
return True
|
|
142
155
|
return False
|
|
143
156
|
|
|
144
157
|
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
|
|
@@ -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 Text:
|
|
111
|
+
kind: TextKind
|
|
112
|
+
"""Kind of text block."""
|
|
113
|
+
content: list[str | Mark | Formula]
|
|
114
|
+
"""Text content with optional citation marks."""
|
|
116
115
|
|
|
117
116
|
@dataclass
|
|
118
117
|
class Footnote:
|
|
@@ -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
|
|
File without changes
|