mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Edit book: Check book: Allow automatic fixing of various simple CSS errors
This commit is contained in:
parent
49cd1944db
commit
f08b238986
@ -10,9 +10,10 @@
|
|||||||
|
|
||||||
window.stylelint_results = [];
|
window.stylelint_results = [];
|
||||||
|
|
||||||
window.check_css = function(src) {
|
window.check_css = function(src, fix) {
|
||||||
stylelint.lint({
|
stylelint.lint({
|
||||||
code: src,
|
code: src,
|
||||||
|
fix: fix,
|
||||||
config: {
|
config: {
|
||||||
rules: {
|
rules: {
|
||||||
'annotation-no-unknown': true,
|
'annotation-no-unknown': true,
|
||||||
|
@ -22,14 +22,17 @@ from calibre.utils.webengine import secure_webengine, setup_profile
|
|||||||
class CSSParseError(BaseError):
|
class CSSParseError(BaseError):
|
||||||
level = ERROR
|
level = ERROR
|
||||||
is_parsing_error = True
|
is_parsing_error = True
|
||||||
|
FIXABLE_CSS_ERROR = False
|
||||||
|
|
||||||
|
|
||||||
class CSSError(BaseError):
|
class CSSError(BaseError):
|
||||||
level = ERROR
|
level = ERROR
|
||||||
|
FIXABLE_CSS_ERROR = False
|
||||||
|
|
||||||
|
|
||||||
class CSSWarning(BaseError):
|
class CSSWarning(BaseError):
|
||||||
level = WARN
|
level = WARN
|
||||||
|
FIXABLE_CSS_ERROR = False
|
||||||
|
|
||||||
|
|
||||||
def as_int_or_none(x):
|
def as_int_or_none(x):
|
||||||
@ -57,10 +60,15 @@ def message_to_error(message, name, line_offset, rule_metadata):
|
|||||||
line += line_offset
|
line += line_offset
|
||||||
ans = cls(title, name, line, col)
|
ans = cls(title, name, line, col)
|
||||||
ans.HELP = message.get('text') or ''
|
ans.HELP = message.get('text') or ''
|
||||||
|
if ans.HELP:
|
||||||
|
ans.HELP += '. '
|
||||||
ans.css_rule_id = rule_id
|
ans.css_rule_id = rule_id
|
||||||
m = rule_metadata.get(rule_id)
|
m = rule_metadata.get(rule_id) or {}
|
||||||
if m and 'url' in m:
|
if 'url' in m:
|
||||||
ans.HELP += '. ' + _('See <a href="{}">detailed description</a>.').format(m['url'])
|
ans.HELP += _('See <a href="{}">detailed description</a>.').format(m['url']) + ' '
|
||||||
|
if m.get('fixable'):
|
||||||
|
ans.FIXABLE_CSS_ERROR = True
|
||||||
|
ans.HELP += _('This error will be automatically fixed if you click "Try to correct all fixable errors" below.')
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
@ -107,7 +115,7 @@ class Worker(QWebEnginePage):
|
|||||||
if new_title == 'ready':
|
if new_title == 'ready':
|
||||||
self.ready = True
|
self.ready = True
|
||||||
if self.pending is not None:
|
if self.pending is not None:
|
||||||
self.check_css(self.pending)
|
self.check_css(*self.pending)
|
||||||
self.pending = None
|
self.pending = None
|
||||||
elif new_title == 'checked':
|
elif new_title == 'checked':
|
||||||
self.runJavaScript('window.get_css_results()', QWebEngineScript.ScriptWorldId.ApplicationWorld, self.check_done)
|
self.runJavaScript('window.get_css_results()', QWebEngineScript.ScriptWorldId.ApplicationWorld, self.check_done)
|
||||||
@ -119,17 +127,17 @@ class Worker(QWebEnginePage):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def check_css(self, src):
|
def check_css(self, src, fix=False):
|
||||||
self.working = True
|
self.working = True
|
||||||
self.runJavaScript(
|
self.runJavaScript(
|
||||||
f'window.check_css({json.dumps(src)})', QWebEngineScript.ScriptWorldId.ApplicationWorld)
|
f'window.check_css({json.dumps(src)}, {"true" if fix else "false"})', QWebEngineScript.ScriptWorldId.ApplicationWorld)
|
||||||
|
|
||||||
def check_css_when_ready(self, src):
|
def check_css_when_ready(self, src, fix=False):
|
||||||
if self.ready:
|
if self.ready:
|
||||||
self.check_css(src)
|
self.check_css(src, fix)
|
||||||
else:
|
else:
|
||||||
self.working = True
|
self.working = True
|
||||||
self.pending = src
|
self.pending = src, fix
|
||||||
|
|
||||||
def check_done(self, results):
|
def check_done(self, results):
|
||||||
self.working = False
|
self.working = False
|
||||||
@ -148,7 +156,8 @@ class Pool:
|
|||||||
w.work_done.connect(self.work_done)
|
w.work_done.connect(self.work_done)
|
||||||
self.workers.append(w)
|
self.workers.append(w)
|
||||||
|
|
||||||
def check_css(self, css_sources):
|
def check_css(self, css_sources, fix=False):
|
||||||
|
self.doing_fix = fix
|
||||||
self.pending = list(enumerate(css_sources))
|
self.pending = list(enumerate(css_sources))
|
||||||
self.results = list(repeat(None, len(css_sources)))
|
self.results = list(repeat(None, len(css_sources)))
|
||||||
self.working = True
|
self.working = True
|
||||||
@ -166,7 +175,7 @@ class Pool:
|
|||||||
if not w.working:
|
if not w.working:
|
||||||
idx, src = self.pending.pop()
|
idx, src = self.pending.pop()
|
||||||
w.result_idx = idx
|
w.result_idx = idx
|
||||||
w.check_css_when_ready(src)
|
w.check_css_when_ready(src, self.doing_fix)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
@ -191,14 +200,14 @@ class Pool:
|
|||||||
pool = Pool()
|
pool = Pool()
|
||||||
shutdown = pool.shutdown
|
shutdown = pool.shutdown
|
||||||
atexit.register(shutdown)
|
atexit.register(shutdown)
|
||||||
Job = namedtuple('Job', 'name css line_offset')
|
Job = namedtuple('Job', 'name css line_offset fix_data')
|
||||||
|
|
||||||
|
|
||||||
def create_job(name, css, line_offset=0, is_declaration=False):
|
def create_job(name, css, line_offset=0, is_declaration=False, fix_data=None):
|
||||||
if is_declaration:
|
if is_declaration:
|
||||||
css = 'div{\n' + css + '\n}'
|
css = 'div{\n' + css + '\n}'
|
||||||
line_offset -= 1
|
line_offset -= 1
|
||||||
return Job(name, css, line_offset)
|
return Job(name, css, line_offset, fix_data)
|
||||||
|
|
||||||
|
|
||||||
def check_css(jobs):
|
def check_css(jobs):
|
||||||
|
@ -4,21 +4,23 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
from polyglot.builtins import iteritems
|
from collections import namedtuple
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES
|
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES
|
||||||
from calibre.ebooks.oeb.polish.utils import guess_type
|
from calibre.ebooks.oeb.polish.check.base import WARN, run_checkers
|
||||||
from calibre.ebooks.oeb.polish.cover import is_raster_image
|
|
||||||
from calibre.ebooks.oeb.polish.check.base import run_checkers, WARN
|
|
||||||
from calibre.ebooks.oeb.polish.check.parsing import (
|
|
||||||
check_filenames, check_xml_parsing, fix_style_tag,
|
|
||||||
check_html_size, check_ids, check_markup, EmptyFile, check_encoding_declarations)
|
|
||||||
from calibre.ebooks.oeb.polish.check.images import check_raster_images
|
|
||||||
from calibre.ebooks.oeb.polish.check.links import check_links, check_mimetypes, check_link_destinations
|
|
||||||
from calibre.ebooks.oeb.polish.check.fonts import check_fonts
|
from calibre.ebooks.oeb.polish.check.fonts import check_fonts
|
||||||
|
from calibre.ebooks.oeb.polish.check.images import check_raster_images
|
||||||
|
from calibre.ebooks.oeb.polish.check.links import (
|
||||||
|
check_link_destinations, check_links, check_mimetypes,
|
||||||
|
)
|
||||||
from calibre.ebooks.oeb.polish.check.opf import check_opf
|
from calibre.ebooks.oeb.polish.check.opf import check_opf
|
||||||
from polyglot.builtins import as_unicode
|
from calibre.ebooks.oeb.polish.check.parsing import (
|
||||||
|
EmptyFile, check_encoding_declarations, check_filenames, check_html_size, check_ids,
|
||||||
|
check_markup, check_xml_parsing, fix_style_tag,
|
||||||
|
)
|
||||||
|
from calibre.ebooks.oeb.polish.cover import is_raster_image
|
||||||
|
from calibre.ebooks.oeb.polish.utils import guess_type
|
||||||
|
from polyglot.builtins import as_unicode, iteritems
|
||||||
|
|
||||||
XML_TYPES = frozenset(map(guess_type, ('a.xml', 'a.svg', 'a.opf', 'a.ncx'))) | {'application/oebps-page-map+xml'}
|
XML_TYPES = frozenset(map(guess_type, ('a.xml', 'a.svg', 'a.opf', 'a.ncx'))) | {'application/oebps-page-map+xml'}
|
||||||
|
|
||||||
@ -105,6 +107,48 @@ def run_checks(container):
|
|||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
CSSFix = namedtuple('CSSFix', 'original_css elem attribute')
|
||||||
|
|
||||||
|
|
||||||
|
def fix_css(container):
|
||||||
|
from calibre.ebooks.oeb.polish.check.css import create_job, pool
|
||||||
|
jobs = []
|
||||||
|
|
||||||
|
for name, mt in iteritems(container.mime_map):
|
||||||
|
if mt in OEB_STYLES:
|
||||||
|
css = container.raw_data(name, decode=True)
|
||||||
|
jobs.append(create_job(name, css, fix_data=CSSFix(css, None, '')))
|
||||||
|
elif mt in OEB_DOCS:
|
||||||
|
root = container.parsed(name)
|
||||||
|
for style in root.xpath('//*[local-name()="style"]'):
|
||||||
|
if style.get('type', 'text/css') == 'text/css' and style.text:
|
||||||
|
jobs.append(create_job(name, style.text, fix_data=CSSFix(style.text, style, '')))
|
||||||
|
for elem in root.xpath('//*[@style]'):
|
||||||
|
raw = elem.get('style')
|
||||||
|
if raw:
|
||||||
|
jobs.append(create_job(name, raw, is_declaration=True, fix_data=CSSFix(raw, elem, 'style')))
|
||||||
|
results = pool.check_css([j.css for j in jobs], fix=True)
|
||||||
|
changed = False
|
||||||
|
for job, result in zip(jobs, results):
|
||||||
|
if result['type'] == 'error':
|
||||||
|
continue
|
||||||
|
fx = job.fix_data
|
||||||
|
fixed_css = result['results']['output']
|
||||||
|
if fixed_css == fx.original_css:
|
||||||
|
continue
|
||||||
|
changed = True
|
||||||
|
if fx.elem is None:
|
||||||
|
with container.open(job.name, 'wb') as f:
|
||||||
|
f.write(fixed_css.encode('utf-8'))
|
||||||
|
else:
|
||||||
|
if fx.attribute:
|
||||||
|
fx.elem.set(fx.attribute, ' '.join(fixed_css.splitlines()[1:-1]))
|
||||||
|
else:
|
||||||
|
fx.elem.text = fixed_css
|
||||||
|
container.dirty(job.name)
|
||||||
|
return changed
|
||||||
|
|
||||||
|
|
||||||
def fix_errors(container, errors):
|
def fix_errors(container, errors):
|
||||||
# Fix parsing
|
# Fix parsing
|
||||||
changed = False
|
changed = False
|
||||||
@ -121,11 +165,17 @@ def fix_errors(container, errors):
|
|||||||
|
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
|
has_fixable_css_errors = False
|
||||||
for err in errors:
|
for err in errors:
|
||||||
|
if getattr(err, 'FIXABLE_CSS_ERROR', False):
|
||||||
|
has_fixable_css_errors = True
|
||||||
if err.INDIVIDUAL_FIX:
|
if err.INDIVIDUAL_FIX:
|
||||||
if err(container) is not False:
|
if err(container) is not False:
|
||||||
# Assume changed unless fixer explicitly says no change (this
|
# Assume changed unless fixer explicitly says no change (this
|
||||||
# is because sometimes I forget to return True, and it is
|
# is because sometimes I forget to return True, and it is
|
||||||
# better to have a false positive than a false negative)
|
# better to have a false positive than a false negative)
|
||||||
changed = True
|
changed = True
|
||||||
|
if has_fixable_css_errors:
|
||||||
|
if fix_css(container):
|
||||||
|
changed = True
|
||||||
return changed
|
return changed
|
||||||
|
Loading…
x
Reference in New Issue
Block a user