mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Merge from trunk
This commit is contained in:
commit
81b14b4d7c
@ -15,11 +15,11 @@ class HoustonChronicle(BasicNewsRecipe):
|
|||||||
remove_attributes = ['style']
|
remove_attributes = ['style']
|
||||||
auto_cleanup = True
|
auto_cleanup = True
|
||||||
|
|
||||||
oldest_article = 2.0
|
oldest_article = 3.0
|
||||||
|
|
||||||
#keep_only_tags = {'class':lambda x: x and ('hst-articletitle' in x or
|
#keep_only_tags = {'class':lambda x: x and ('hst-articletitle' in x or
|
||||||
#'hst-articletext' in x or 'hst-galleryitem' in x)}
|
#'hst-articletext' in x or 'hst-galleryitem' in x)}
|
||||||
#remove_attributes = ['xmlns']
|
remove_attributes = ['xmlns']
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
('News', "http://www.chron.com/rss/feed/News-270.php"),
|
('News', "http://www.chron.com/rss/feed/News-270.php"),
|
||||||
|
@ -65,7 +65,7 @@ class WallStreetJournal(BasicNewsRecipe):
|
|||||||
br['password'] = self.password
|
br['password'] = self.password
|
||||||
res = br.submit()
|
res = br.submit()
|
||||||
raw = res.read()
|
raw = res.read()
|
||||||
if '>Log Out<' not in raw:
|
if 'Welcome,' not in raw and '>Logout<' not in raw and '>Log Out<' not in raw:
|
||||||
raise ValueError('Failed to log in to wsj.com, check your '
|
raise ValueError('Failed to log in to wsj.com, check your '
|
||||||
'username and password')
|
'username and password')
|
||||||
return br
|
return br
|
||||||
|
Binary file not shown.
@ -187,7 +187,7 @@ MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () {
|
|||||||
// fill it with the proper elements,
|
// fill it with the proper elements,
|
||||||
// and clean up the bbox
|
// and clean up the bbox
|
||||||
//
|
//
|
||||||
line = BBOX();
|
var line = BBOX();
|
||||||
state.first = broken; state.last = true;
|
state.first = broken; state.last = true;
|
||||||
this.SVGmoveLine(start,end,line,state,values);
|
this.SVGmoveLine(start,end,line,state,values);
|
||||||
line.Clean();
|
line.Clean();
|
||||||
|
@ -47,9 +47,9 @@ class MTP_DEVICE(BASE):
|
|||||||
from calibre.library.save_to_disk import config
|
from calibre.library.save_to_disk import config
|
||||||
self._prefs = p = JSONConfig('mtp_devices')
|
self._prefs = p = JSONConfig('mtp_devices')
|
||||||
p.defaults['format_map'] = self.FORMATS
|
p.defaults['format_map'] = self.FORMATS
|
||||||
p.defaults['send_to'] = ['eBooks/import',
|
p.defaults['send_to'] = ['Books', 'eBooks/import', 'eBooks',
|
||||||
'wordplayer/calibretransfer', 'Books', 'sdcard/ebooks',
|
'wordplayer/calibretransfer', 'sdcard/ebooks',
|
||||||
'eBooks', 'kindle']
|
'kindle']
|
||||||
p.defaults['send_template'] = config().parse().send_template
|
p.defaults['send_template'] = config().parse().send_template
|
||||||
p.defaults['blacklist'] = []
|
p.defaults['blacklist'] = []
|
||||||
p.defaults['history'] = {}
|
p.defaults['history'] = {}
|
||||||
@ -300,7 +300,7 @@ class MTP_DEVICE(BASE):
|
|||||||
p = path
|
p = path
|
||||||
break
|
break
|
||||||
if p is None:
|
if p is None:
|
||||||
p = 'eBooks'
|
p = 'Books'
|
||||||
self.location_paths[loc] = p
|
self.location_paths[loc] = p
|
||||||
|
|
||||||
return self.location_paths[on_card]
|
return self.location_paths[on_card]
|
||||||
|
@ -66,6 +66,7 @@ class RecipeInput(InputFormatPlugin):
|
|||||||
if os.access(recipe_or_file, os.R_OK):
|
if os.access(recipe_or_file, os.R_OK):
|
||||||
self.recipe_source = open(recipe_or_file, 'rb').read()
|
self.recipe_source = open(recipe_or_file, 'rb').read()
|
||||||
recipe = compile_recipe(self.recipe_source)
|
recipe = compile_recipe(self.recipe_source)
|
||||||
|
log('Using custom recipe')
|
||||||
else:
|
else:
|
||||||
from calibre.web.feeds.recipes.collection import \
|
from calibre.web.feeds.recipes.collection import \
|
||||||
get_builtin_recipe_by_title
|
get_builtin_recipe_by_title
|
||||||
@ -87,12 +88,15 @@ class RecipeInput(InputFormatPlugin):
|
|||||||
'back to builtin one')
|
'back to builtin one')
|
||||||
builtin = True
|
builtin = True
|
||||||
if builtin:
|
if builtin:
|
||||||
|
log('Using bundled builtin recipe')
|
||||||
raw = get_builtin_recipe_by_title(title, log=log,
|
raw = get_builtin_recipe_by_title(title, log=log,
|
||||||
download_recipe=False)
|
download_recipe=False)
|
||||||
if raw is None:
|
if raw is None:
|
||||||
raise ValueError('Failed to find builtin recipe: '+title)
|
raise ValueError('Failed to find builtin recipe: '+title)
|
||||||
recipe = compile_recipe(raw)
|
recipe = compile_recipe(raw)
|
||||||
self.recipe_source = raw
|
self.recipe_source = raw
|
||||||
|
else:
|
||||||
|
log('Using downloaded builtin recipe')
|
||||||
|
|
||||||
if recipe is None:
|
if recipe is None:
|
||||||
raise ValueError('%r is not a valid recipe file or builtin recipe' %
|
raise ValueError('%r is not a valid recipe file or builtin recipe' %
|
||||||
|
@ -1009,6 +1009,8 @@ OptionRecommendation(name='search_replace',
|
|||||||
|
|
||||||
pr(0., _('Running transforms on ebook...'))
|
pr(0., _('Running transforms on ebook...'))
|
||||||
|
|
||||||
|
self.oeb.plumber_output_format = self.output_fmt or ''
|
||||||
|
|
||||||
from calibre.ebooks.oeb.transforms.guide import Clean
|
from calibre.ebooks.oeb.transforms.guide import Clean
|
||||||
Clean()(self.oeb, self.opts)
|
Clean()(self.oeb, self.opts)
|
||||||
pr(0.1)
|
pr(0.1)
|
||||||
@ -1120,7 +1122,7 @@ OptionRecommendation(name='search_replace',
|
|||||||
self.log.info('Creating %s...'%self.output_plugin.name)
|
self.log.info('Creating %s...'%self.output_plugin.name)
|
||||||
our = CompositeProgressReporter(0.67, 1., self.ui_reporter)
|
our = CompositeProgressReporter(0.67, 1., self.ui_reporter)
|
||||||
self.output_plugin.report_progress = our
|
self.output_plugin.report_progress = our
|
||||||
our(0., _('Creating')+' %s'%self.output_plugin.name)
|
our(0., _('Running %s plugin')%self.output_plugin.name)
|
||||||
with self.output_plugin:
|
with self.output_plugin:
|
||||||
self.output_plugin.convert(self.oeb, self.output, self.input_plugin,
|
self.output_plugin.convert(self.oeb, self.output, self.input_plugin,
|
||||||
self.opts, self.log)
|
self.opts, self.log)
|
||||||
|
@ -39,7 +39,7 @@ class MathJax
|
|||||||
showMathMenu: false,
|
showMathMenu: false,
|
||||||
extensions: ["tex2jax.js", "asciimath2jax.js", "mml2jax.js"],
|
extensions: ["tex2jax.js", "asciimath2jax.js", "mml2jax.js"],
|
||||||
jax: ["input/TeX","input/MathML","input/AsciiMath","output/SVG"],
|
jax: ["input/TeX","input/MathML","input/AsciiMath","output/SVG"],
|
||||||
// SVG : { linebreaks : { automatic : true } },
|
SVG : { linebreaks : { automatic : true } },
|
||||||
TeX: {
|
TeX: {
|
||||||
extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"]
|
extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"]
|
||||||
}
|
}
|
||||||
|
@ -267,13 +267,18 @@ class Stylizer(object):
|
|||||||
rules.sort()
|
rules.sort()
|
||||||
self.rules = rules
|
self.rules = rules
|
||||||
self._styles = {}
|
self._styles = {}
|
||||||
|
pseudo_pat = re.compile(ur':(first-letter|first-line|link|hover|visited|active|focus)', re.I)
|
||||||
for _, _, cssdict, text, _ in rules:
|
for _, _, cssdict, text, _ in rules:
|
||||||
fl = ':first-letter' in text
|
fl = pseudo_pat.search(text)
|
||||||
if fl:
|
if fl is not None:
|
||||||
text = text.replace(':first-letter', '')
|
text = text.replace(fl.group(), '')
|
||||||
selector = get_css_selector(text)
|
selector = get_css_selector(text)
|
||||||
matches = selector(tree, self.logger)
|
matches = selector(tree, self.logger)
|
||||||
if fl:
|
if fl is not None:
|
||||||
|
fl = fl.group(1)
|
||||||
|
if fl == 'first-letter' and getattr(self.oeb,
|
||||||
|
'plumber_output_format', '').lower() == u'mobi':
|
||||||
|
# Fake first-letter
|
||||||
from lxml.builder import ElementMaker
|
from lxml.builder import ElementMaker
|
||||||
E = ElementMaker(namespace=XHTML_NS)
|
E = ElementMaker(namespace=XHTML_NS)
|
||||||
for elem in matches:
|
for elem in matches:
|
||||||
@ -282,7 +287,8 @@ class Stylizer(object):
|
|||||||
punctuation_chars = []
|
punctuation_chars = []
|
||||||
text = unicode(x.text)
|
text = unicode(x.text)
|
||||||
while text:
|
while text:
|
||||||
if not unicodedata.category(text[0]).startswith('P'):
|
category = unicodedata.category(text[0])
|
||||||
|
if category[0] not in {'P', 'Z'}:
|
||||||
break
|
break
|
||||||
punctuation_chars.append(text[0])
|
punctuation_chars.append(text[0])
|
||||||
text = text[1:]
|
text = text[1:]
|
||||||
@ -295,6 +301,9 @@ class Stylizer(object):
|
|||||||
x.insert(0, span)
|
x.insert(0, span)
|
||||||
self.style(span)._update_cssdict(cssdict)
|
self.style(span)._update_cssdict(cssdict)
|
||||||
break
|
break
|
||||||
|
else: # Element pseudo-class
|
||||||
|
for elem in matches:
|
||||||
|
self.style(elem)._update_pseudo_class(fl, cssdict)
|
||||||
else:
|
else:
|
||||||
for elem in matches:
|
for elem in matches:
|
||||||
self.style(elem)._update_cssdict(cssdict)
|
self.style(elem)._update_cssdict(cssdict)
|
||||||
@ -495,6 +504,7 @@ class Style(object):
|
|||||||
self._height = None
|
self._height = None
|
||||||
self._lineHeight = None
|
self._lineHeight = None
|
||||||
self._bgcolor = None
|
self._bgcolor = None
|
||||||
|
self._pseudo_classes = {}
|
||||||
stylizer._styles[element] = self
|
stylizer._styles[element] = self
|
||||||
|
|
||||||
def set(self, prop, val):
|
def set(self, prop, val):
|
||||||
@ -506,6 +516,11 @@ class Style(object):
|
|||||||
def _update_cssdict(self, cssdict):
|
def _update_cssdict(self, cssdict):
|
||||||
self._style.update(cssdict)
|
self._style.update(cssdict)
|
||||||
|
|
||||||
|
def _update_pseudo_class(self, name, cssdict):
|
||||||
|
orig = self._pseudo_classes.get(name, {})
|
||||||
|
orig.update(cssdict)
|
||||||
|
self._pseudo_classes[name] = orig
|
||||||
|
|
||||||
def _apply_style_attr(self, url_replacer=None):
|
def _apply_style_attr(self, url_replacer=None):
|
||||||
attrib = self._element.attrib
|
attrib = self._element.attrib
|
||||||
if 'style' not in attrib:
|
if 'style' not in attrib:
|
||||||
@ -778,3 +793,14 @@ class Style(object):
|
|||||||
|
|
||||||
def cssdict(self):
|
def cssdict(self):
|
||||||
return dict(self._style)
|
return dict(self._style)
|
||||||
|
|
||||||
|
def pseudo_classes(self, filter_css):
|
||||||
|
if filter_css:
|
||||||
|
css = copy.deepcopy(self._pseudo_classes)
|
||||||
|
for psel, cssdict in css.iteritems():
|
||||||
|
for k in filter_css:
|
||||||
|
cssdict.pop(k, None)
|
||||||
|
else:
|
||||||
|
css = self._pseudo_classes
|
||||||
|
return {k:v for k, v in css.iteritems() if v}
|
||||||
|
|
||||||
|
@ -222,7 +222,7 @@ class CSSFlattener(object):
|
|||||||
value = 0.0
|
value = 0.0
|
||||||
cssdict[property] = "%0.5fem" % (value / fsize)
|
cssdict[property] = "%0.5fem" % (value / fsize)
|
||||||
|
|
||||||
def flatten_node(self, node, stylizer, names, styles, psize, item_id):
|
def flatten_node(self, node, stylizer, names, styles, pseudo_styles, psize, item_id):
|
||||||
if not isinstance(node.tag, basestring) \
|
if not isinstance(node.tag, basestring) \
|
||||||
or namespace(node.tag) != XHTML_NS:
|
or namespace(node.tag) != XHTML_NS:
|
||||||
return
|
return
|
||||||
@ -357,6 +357,10 @@ class CSSFlattener(object):
|
|||||||
cssdict.get('text-align', None) not in ('center', 'right')):
|
cssdict.get('text-align', None) not in ('center', 'right')):
|
||||||
cssdict['text-indent'] = "%1.1fem" % indent_size
|
cssdict['text-indent'] = "%1.1fem" % indent_size
|
||||||
|
|
||||||
|
pseudo_classes = style.pseudo_classes(self.filter_css)
|
||||||
|
if cssdict or pseudo_classes:
|
||||||
|
keep_classes = set()
|
||||||
|
|
||||||
if cssdict:
|
if cssdict:
|
||||||
items = cssdict.items()
|
items = cssdict.items()
|
||||||
items.sort()
|
items.sort()
|
||||||
@ -370,12 +374,34 @@ class CSSFlattener(object):
|
|||||||
styles[css] = match
|
styles[css] = match
|
||||||
names[klass] += 1
|
names[klass] += 1
|
||||||
node.attrib['class'] = match
|
node.attrib['class'] = match
|
||||||
|
keep_classes.add(match)
|
||||||
|
|
||||||
|
for psel, cssdict in pseudo_classes.iteritems():
|
||||||
|
items = sorted(cssdict.iteritems())
|
||||||
|
css = u';\n'.join(u'%s: %s' % (key, val) for key, val in items)
|
||||||
|
pstyles = pseudo_styles[psel]
|
||||||
|
if css in pstyles:
|
||||||
|
match = pstyles[css]
|
||||||
|
else:
|
||||||
|
# We have to use a different class for each psel as
|
||||||
|
# otherwise you can have incorrect styles for a situation
|
||||||
|
# like: a:hover { color: red } a:link { color: blue } a.x:hover { color: green }
|
||||||
|
# If the pcalibre class for a:hover and a:link is the same,
|
||||||
|
# then the class attribute for a.x tags will contain both
|
||||||
|
# that class and the class for a.x:hover, which is wrong.
|
||||||
|
klass = 'pcalibre'
|
||||||
|
match = klass + str(names[klass] or '')
|
||||||
|
pstyles[css] = match
|
||||||
|
names[klass] += 1
|
||||||
|
keep_classes.add(match)
|
||||||
|
node.attrib['class'] = ' '.join(keep_classes)
|
||||||
|
|
||||||
elif 'class' in node.attrib:
|
elif 'class' in node.attrib:
|
||||||
del node.attrib['class']
|
del node.attrib['class']
|
||||||
if 'style' in node.attrib:
|
if 'style' in node.attrib:
|
||||||
del node.attrib['style']
|
del node.attrib['style']
|
||||||
for child in node:
|
for child in node:
|
||||||
self.flatten_node(child, stylizer, names, styles, psize, item_id)
|
self.flatten_node(child, stylizer, names, styles, pseudo_styles, psize, item_id)
|
||||||
|
|
||||||
def flatten_head(self, item, href, global_href):
|
def flatten_head(self, item, href, global_href):
|
||||||
html = item.data
|
html = item.data
|
||||||
@ -446,7 +472,7 @@ class CSSFlattener(object):
|
|||||||
|
|
||||||
def flatten_spine(self):
|
def flatten_spine(self):
|
||||||
names = defaultdict(int)
|
names = defaultdict(int)
|
||||||
styles = {}
|
styles, pseudo_styles = {}, defaultdict(dict)
|
||||||
for item in self.oeb.spine:
|
for item in self.oeb.spine:
|
||||||
html = item.data
|
html = item.data
|
||||||
stylizer = self.stylizers[item]
|
stylizer = self.stylizers[item]
|
||||||
@ -454,10 +480,20 @@ class CSSFlattener(object):
|
|||||||
self.specializer(item, stylizer)
|
self.specializer(item, stylizer)
|
||||||
body = html.find(XHTML('body'))
|
body = html.find(XHTML('body'))
|
||||||
fsize = self.context.dest.fbase
|
fsize = self.context.dest.fbase
|
||||||
self.flatten_node(body, stylizer, names, styles, fsize, item.id)
|
self.flatten_node(body, stylizer, names, styles, pseudo_styles, fsize, item.id)
|
||||||
items = [(key, val) for (val, key) in styles.items()]
|
items = [(key, val) for (val, key) in styles.items()]
|
||||||
items.sort()
|
items.sort()
|
||||||
|
# :hover must come after link and :active must come after :hover
|
||||||
|
psels = sorted(pseudo_styles.iterkeys(), key=lambda x :
|
||||||
|
{'hover':1, 'active':2}.get(x, 0))
|
||||||
|
for psel in psels:
|
||||||
|
styles = pseudo_styles[psel]
|
||||||
|
if not styles: continue
|
||||||
|
x = sorted(((k+':'+psel, v) for v, k in styles.iteritems()))
|
||||||
|
items.extend(x)
|
||||||
|
|
||||||
css = ''.join(".%s {\n%s;\n}\n\n" % (key, val) for key, val in items)
|
css = ''.join(".%s {\n%s;\n}\n\n" % (key, val) for key, val in items)
|
||||||
|
|
||||||
href = self.replace_css(css)
|
href = self.replace_css(css)
|
||||||
global_css = self.collect_global_css()
|
global_css = self.collect_global_css()
|
||||||
for item in self.oeb.spine:
|
for item in self.oeb.spine:
|
||||||
|
@ -88,11 +88,9 @@ class ShareConnMenu(QMenu): # {{{
|
|||||||
from calibre.utils.mdns import get_external_ip, verify_ipV4_address
|
from calibre.utils.mdns import get_external_ip, verify_ipV4_address
|
||||||
text = _('Start Content Server')
|
text = _('Start Content Server')
|
||||||
if running:
|
if running:
|
||||||
listen_on = verify_ipV4_address(tweaks['server_listen_on'])
|
listen_on = (verify_ipV4_address(tweaks['server_listen_on']) or
|
||||||
if listen_on:
|
get_external_ip())
|
||||||
text = _('Stop Content Server') + ' [%s]'%listen_on
|
text = _('Stop Content Server') + ' [%s]'%listen_on
|
||||||
else:
|
|
||||||
text = _('Stop Content Server') + ' [%s]'%get_external_ip()
|
|
||||||
self.toggle_server_action.setText(text)
|
self.toggle_server_action.setText(text)
|
||||||
|
|
||||||
def hide_smartdevice_menus(self):
|
def hide_smartdevice_menus(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user