Skip to content
Closed
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
8 changes: 8 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@

0.14.3 August xx, 1996
- 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

0.14.2 July 4, 2026
- `text-decoration` for `a` should be reset to `underline`, not initial. Fixes #316.

0.14.1 June 12, 2026
- fixed bug where text directly under body is deleted.
- updated idna and beautifulsoup dependencies
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[metadata]
name = ebookmaker

version = 0.14.1
version = 0.14.2

[options]
package_dir=
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from setuptools import setup

VERSION = '0.14.1'
VERSION = '0.14.2'

if __name__ == "__main__":

Expand Down
4 changes: 2 additions & 2 deletions src/ebookmaker/ParserFactory.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def create(cls, url, attribs=None):
if attribs is None:
attribs = parsers.ParserAttributes()

# debug("Need parser for %s" % url)
debug("Need parser for %s" % url)

# first check if input url is in output directory (we've already made it!)
if gg.is_same_path(os.path.abspath(options.outputdir), os.path.dirname(url)):
Expand All @@ -101,7 +101,7 @@ def create(cls, url, attribs=None):


if url in cls.parsers:
# debug("... reusing parser for %s" % url)
debug("... reusing parser for %s" % url)
# reuse same parser, maybe already filled with data
parser = cls.parsers[url]
parser.reset()
Expand Down
2 changes: 1 addition & 1 deletion src/ebookmaker/Version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
VERSION = '0.14.1'
VERSION = '0.14.2'
GENERATOR = 'Ebookmaker %s by Project Gutenberg'
25 changes: 17 additions & 8 deletions src/ebookmaker/parsers/GutenbergTextParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,21 @@ def __init__(self, attribs=None):
self.body = 0
self.max_blanks = 0
self.pars = []
self.text = ""
self.pg_header = ""
self.pg_footer = ""


def unicode_content(self):
if self.text == '':
text = HTMLParserBase.unicode_content(self)
self.text, self.pg_header, self.pg_footer = strip_headers_from_txt(text)
if 'x-header' in self.pg_header and options.production:
error('header marker is missing in %s', self.attribs.url)
if 'x-header' in self.pg_footer and options.production:
error('footer marker is missing in %s', self.attribs.url)
return self.pg_header + self.text + self.pg_footer


def get_charset_from_meta(self):
""" Parse text for hints about charset. """
Expand Down Expand Up @@ -626,12 +641,6 @@ def parse(self):
return

text = self.unicode_content()
text, pg_header, pg_footer = strip_headers_from_txt(text)
if 'x-header' in pg_header and options.production:
error('header marker is missing in %s', self.attribs.url)
if 'x-header' in pg_footer and options.production:
error('footer marker is missing in %s', self.attribs.url)

text = parsers.RE_RESTRICTED.sub('', text)
text = gg.xmlspecialchars(text)

Expand Down Expand Up @@ -684,12 +693,12 @@ def parse(self):

for body in xpath(self.xhtml, '//xhtml:body'):
xhtmlparser = lxml.html.XHTMLParser(huge_tree=True)
body.append(etree.fromstring(pg_header, xhtmlparser))
body.append(etree.fromstring(self.pg_header, xhtmlparser))
for par in self.pars:
p = etree.fromstring(self.ship_out(par), xhtmlparser)
p.tail = '\n\n'
body.append(p)
body.append(etree.fromstring(pg_footer, xhtmlparser))
body.append(etree.fromstring(self.pg_footer, xhtmlparser))

self.pars = []

Expand Down
1 change: 1 addition & 0 deletions src/ebookmaker/parsers/boilerplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ def strip_headers_from_txt(text):
'''
when input is plain text, strip the heaters and return (stripped_text, pg_header, pg_footer)
'''
debug('stripping headers from txt')
def markers_split(text, markers):
for marker in markers:
divider = marker.search(text)
Expand Down
8 changes: 8 additions & 0 deletions src/ebookmaker/writers/EpubWriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,14 @@ def fix_html5(xhtml):
tag.tag = NS.xhtml.div
writers.HTMLWriter.add_class(tag, newtag)

# replace html5 block tags in forbidden contexts
for badblock in [('a', 'p'), ('a', 'div')]:
tag, blocktag = badblock
for block in xpath(xhtml, f'//xhtml:{tag}/xhtml:{blocktag}'):
usedtags.add(block)
block.tag = NS.xhtml.span
writers.HTMLWriter.add_class(block, f'{tag}_{blocktag}')

# replace html5 inline tags
for newtag in ['u', 'ruby', 'rt', 'rp']:
for tag in xpath(xhtml, f'//xhtml:{newtag}'):
Expand Down
2 changes: 1 addition & 1 deletion src/ebookmaker/writers/HTMLWriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ def style_filter(style, patt=SHOULD_MOVE ):
# don't let the source change the body bgcolor or text color
new_rule = css.CSSStyleRule(selectorText='body', style='background:initial;color:initial')
sheet.add(new_rule)
new_rule = css.CSSStyleRule(selectorText='a', style='text-decoration:initial')
new_rule = css.CSSStyleRule(selectorText='a', style='text-decoration:underline')
sheet.add(new_rule)

@staticmethod
Expand Down
1 change: 1 addition & 0 deletions src/ebookmaker/writers/TxtWriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@


def insert_boilerplate(job, text):
debug("inserting boilerplate")
text, header, footer = strip_headers_from_txt(text)
pg_header = pgheader(job.dc).text_content()
pg_footer = pgfooter(job.dc).text_content()
Expand Down
16 changes: 15 additions & 1 deletion tests/test_txt.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@


import ebookmaker
from ebookmaker.ParserFactory import load_parsers, ParserFactory
from ebookmaker.CommonCode import Options

options = Options()

class TestFromTxt(unittest.TestCase):
def setUp(self):
Expand Down Expand Up @@ -33,4 +37,14 @@ def test_69030(self):
for out in outs:
self.assertTrue(os.path.exists(os.path.join(self.out_dir, out % book_id)))
os.remove(os.path.join(self.out_dir, out % book_id))


def test_parser(self):
load_parsers()
options.outputdir = ''
book_id = '69030'
dir = os.path.join(self.sample_dir, book_id)
srcfile = os.path.join(dir, '%s-0.txt' % book_id)
parser = ParserFactory.create(srcfile)
self.assertTrue(len(parser.unicode_content()) > len(parser.text))
self.assertTrue(len(parser.pg_header) > 500)
self.assertTrue(len(parser.pg_footer) > 1500)