Merge from trunk

This commit is contained in:
Charles Haley 2012-09-11 14:57:39 +02:00
commit 81b14b4d7c
11 changed files with 123 additions and 57 deletions

View File

@ -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"),
@ -38,4 +38,4 @@ class HoustonChronicle(BasicNewsRecipe):
] ]

View File

@ -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.

View File

@ -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();

View File

@ -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]

View File

@ -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' %

View File

@ -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)

View File

@ -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"]
} }

View File

@ -267,34 +267,43 @@ 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:
from lxml.builder import ElementMaker fl = fl.group(1)
E = ElementMaker(namespace=XHTML_NS) if fl == 'first-letter' and getattr(self.oeb,
for elem in matches: 'plumber_output_format', '').lower() == u'mobi':
for x in elem.iter(): # Fake first-letter
if x.text: from lxml.builder import ElementMaker
punctuation_chars = [] E = ElementMaker(namespace=XHTML_NS)
text = unicode(x.text) for elem in matches:
while text: for x in elem.iter():
if not unicodedata.category(text[0]).startswith('P'): if x.text:
break punctuation_chars = []
punctuation_chars.append(text[0]) text = unicode(x.text)
text = text[1:] while text:
category = unicodedata.category(text[0])
if category[0] not in {'P', 'Z'}:
break
punctuation_chars.append(text[0])
text = text[1:]
special_text = u''.join(punctuation_chars) + \ special_text = u''.join(punctuation_chars) + \
(text[0] if text else u'') (text[0] if text else u'')
span = E.span(special_text) span = E.span(special_text)
span.tail = text[1:] span.tail = text[1:]
x.text = None x.text = None
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}

View File

@ -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,25 +357,51 @@ 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
if cssdict: pseudo_classes = style.pseudo_classes(self.filter_css)
items = cssdict.items() if cssdict or pseudo_classes:
items.sort() keep_classes = set()
css = u';\n'.join(u'%s: %s' % (key, val) for key, val in items)
classes = node.get('class', '').strip() or 'calibre' if cssdict:
klass = STRIPNUM.sub('', classes.split()[0].replace('_', '')) items = cssdict.items()
if css in styles: items.sort()
match = styles[css] css = u';\n'.join(u'%s: %s' % (key, val) for key, val in items)
else: classes = node.get('class', '').strip() or 'calibre'
match = klass + str(names[klass] or '') klass = STRIPNUM.sub('', classes.split()[0].replace('_', ''))
styles[css] = match if css in styles:
names[klass] += 1 match = styles[css]
node.attrib['class'] = match else:
match = klass + str(names[klass] or '')
styles[css] = match
names[klass] += 1
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:

View File

@ -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):