Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGES
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@

0.14.3 August xx, 1996
0.14.3 August ??, 2026
- handling of inline SVG has been improved.
- the inline SVG element is now handled whether or not it has set the svg namespace.
- inline SVG is changed to standalone files in EPUB2
- inline html5 SVG now adapted for EPUB3, which cares about namespaces
- an error is no longer emitted when rst output is requested from html input
- refactored boilerplate stripping for txt files so it only needs to be done once per book. boilerplate is now detected in the text Parser, instead of in the text Writer.
- `a` tags can't contain block tags in xhtml, so div in `a` and `p` in `a` cause EPUB2 validation errors, so Ebookmaker now changes `p` and `div` occurring in `a` to `span` for EPUB2. I'm not sure why anyone would want to use this markup in a book. Fixes #333

Expand Down
3 changes: 3 additions & 0 deletions src/ebookmaker/HTMLChunker.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ def shipout_chunk(self, attribs, chunk_id = None, comment = None):
for e in xpath(self.chunk, '//mathml:math'):
attribs.rel.add('mathml')
break
for e in xpath(self.chunk, '//xhtml:svg'):
attribs.rel.add('svg')
break
for e in xpath(self.chunk, '//svg:svg'):
attribs.rel.add('svg')
break
Expand Down
8 changes: 8 additions & 0 deletions src/ebookmaker/parsers/ImageParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from PIL import Image, ImageFile
from lxml import etree

from libgutenberg.GutenbergGlobals import NS
from libgutenberg.Logger import debug, critical, error
from libgutenberg.MediaTypes import mediatypes as mt
from ebookmaker.parsers import ParserBase
Expand Down Expand Up @@ -171,6 +172,7 @@ def serialize(self):
atts_to_remove = ['data-variant', 'focusable', 'role']
try:
tree = etree.parse(io.BytesIO(self.image_data))
svg = tree.getroot()
except etree.XMLSyntaxError as e:
critical(f'SVG image {self.attribs.url} was badly formed XML: {e}')
return self.image_data
Expand All @@ -181,5 +183,11 @@ def serialize(self):
for att in copy.copy(element.attrib):
if att.startswith('aria-'):
del element.attrib[att]
# strip namespaces hanging around, perhaps because it's an extracted image
element.tag = etree.QName(element).localname
etree.cleanup_namespaces(tree)
# restore the root namespace
svg.tag = NS.svg.svg

self.image_data = etree.tostring(tree, encoding="utf-8")
return self.image_data
4 changes: 4 additions & 0 deletions src/ebookmaker/writers/Epub3Writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,10 @@ def html_for_epub3(xhtml):
# add namespace to math elements
for e in xpath(xhtml, "//xhtml:math"):
e.attrib['xmlns'] = "http://www.w3.org/1998/Math/MathML"
# add namespace to svg
for e in xpath(xhtml, "//xhtml:svg"):
e.attrib['xmlns'] = "http://www.w3.org/2000/svg"


@staticmethod
def fix_incompatible_css(sheet):
Expand Down
46 changes: 41 additions & 5 deletions src/ebookmaker/writers/EpubWriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1125,11 +1125,40 @@ def fix_html5(xhtml):
tag.clear()
tag.text = text

@staticmethod
def extract_svg(xhtml, parent_url):
"""
convert embedded svg elements to stand-alone svg files that work better n EPUB2.





"""
svgnum = 0
new_parsers = []
for svg in xpath(xhtml, '//xhtml:svg'):
# make a new parser
attribs = parsers.ParserAttributes()
svg_parser = parsers.ImageParser.Parser(attribs=attribs)
attribs.mediatype = mt.svg
attribs.id = f'extracted_svg_{svgnum}'
attribs.url = urllib.parse.urljoin(parent_url, f'images/extracted_svg_{svgnum}.svg')
svg_parser.image_data = etree.tostring(svg)

# turn the svg element into an img element
for att in svg.attrib:
if att not in {"alt", "class", "dir", "height", "id", "ismap", "lang", "longdesc",
"src", "title", "usemap", "width", "xml:lang"}:
del svg.attrib[att]
for title in xpath(svg, '//xhtml:title'):
svg.attrib['alt'] = title.text_content() or ''
break
svg.tag = NS.xhtml.img
svg.attrib['src'] = attribs.url
svg.attrib['id'] = attribs.id
for child in list(svg):
svg.remove(child)

# return the new parsers
new_parsers.append(svg_parser)
return new_parsers

@staticmethod
def strip_links(xhtml, manifest):
Expand Down Expand Up @@ -1352,6 +1381,7 @@ def shipout(self, job, parserlist, ncx):
if p.mediatype() == mt.xhtml:
opf.spine_item_from_parser(p)
else:
debug(f'adding {p.attribs.url} to manifest')
opf.manifest_item_from_parser(p)
except Exception as what:
error("Could not process file %s: %s" % (p.attribs.url, what))
Expand Down Expand Up @@ -1457,8 +1487,14 @@ def build(self, job):
p.remap_links(idmap)

xhtml.make_links_absolute(base_url=p.attribs.url)
self.fix_html5(xhtml)
new_parsers = self.extract_svg(xhtml, p.attribs.url)

# add the new parsers to parser list so the get handled properly
parserlist.extend(new_parsers)
job.spider.parsers.extend(new_parsers)

self.fix_html5(xhtml)

strip_classes = self.get_classes_with_prop(xhtml)
strip_classes = strip_classes.intersection(STRIP_CLASSES)
if strip_classes:
Expand Down
5 changes: 5 additions & 0 deletions src/ebookmaker/writers/RSTWriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ def build (self, job):
debug ("Creating RST file: %s" % filename)

parser = ParserFactory.ParserFactory.create (job.url)

has_txt_source = 'text/plain' in str(parser.attribs.orig_mediatype)
if not has_txt_source:
debug("needs plain text file for conversion: %s from %s", filename, job.url)
return

data = parser.preprocess ('utf-8').encode ('utf-8')

Expand Down