diff --git a/CHANGES b/CHANGES index 01f56f4..3250ea2 100644 --- a/CHANGES +++ b/CHANGES @@ -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 diff --git a/setup.cfg b/setup.cfg index 3061be0..1beb0c5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ebookmaker -version = 0.14.1 +version = 0.14.2 [options] package_dir= diff --git a/setup.py b/setup.py index cb02cc2..58597f0 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from setuptools import setup -VERSION = '0.14.1' +VERSION = '0.14.2' if __name__ == "__main__": diff --git a/src/ebookmaker/ParserFactory.py b/src/ebookmaker/ParserFactory.py index cc7c39e..ac91be2 100644 --- a/src/ebookmaker/ParserFactory.py +++ b/src/ebookmaker/ParserFactory.py @@ -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)): @@ -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() diff --git a/src/ebookmaker/Version.py b/src/ebookmaker/Version.py index c45d9d7..137afc3 100644 --- a/src/ebookmaker/Version.py +++ b/src/ebookmaker/Version.py @@ -1,2 +1,2 @@ -VERSION = '0.14.1' +VERSION = '0.14.2' GENERATOR = 'Ebookmaker %s by Project Gutenberg' diff --git a/src/ebookmaker/parsers/GutenbergTextParser.py b/src/ebookmaker/parsers/GutenbergTextParser.py index 62fed67..48b8a06 100644 --- a/src/ebookmaker/parsers/GutenbergTextParser.py +++ b/src/ebookmaker/parsers/GutenbergTextParser.py @@ -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. """ @@ -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) @@ -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 = [] diff --git a/src/ebookmaker/parsers/boilerplate.py b/src/ebookmaker/parsers/boilerplate.py index 2093a84..75588c9 100644 --- a/src/ebookmaker/parsers/boilerplate.py +++ b/src/ebookmaker/parsers/boilerplate.py @@ -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) diff --git a/src/ebookmaker/writers/EpubWriter.py b/src/ebookmaker/writers/EpubWriter.py index a1dd42b..6043855 100644 --- a/src/ebookmaker/writers/EpubWriter.py +++ b/src/ebookmaker/writers/EpubWriter.py @@ -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}'): diff --git a/src/ebookmaker/writers/HTMLWriter.py b/src/ebookmaker/writers/HTMLWriter.py index 3d01b96..99750c7 100644 --- a/src/ebookmaker/writers/HTMLWriter.py +++ b/src/ebookmaker/writers/HTMLWriter.py @@ -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 diff --git a/src/ebookmaker/writers/TxtWriter.py b/src/ebookmaker/writers/TxtWriter.py index 1d59c7c..1bb68fd 100644 --- a/src/ebookmaker/writers/TxtWriter.py +++ b/src/ebookmaker/writers/TxtWriter.py @@ -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() diff --git a/tests/test_txt.py b/tests/test_txt.py index 88e4c30..fb49aec 100755 --- a/tests/test_txt.py +++ b/tests/test_txt.py @@ -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): @@ -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)) - \ No newline at end of file + + 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)