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']
auto_cleanup = True
oldest_article = 2.0
oldest_article = 3.0
#keep_only_tags = {'class':lambda x: x and ('hst-articletitle' in x or
#'hst-articletext' in x or 'hst-galleryitem' in x)}
#remove_attributes = ['xmlns']
remove_attributes = ['xmlns']
feeds = [
('News', "http://www.chron.com/rss/feed/News-270.php"),

View File

@ -65,7 +65,7 @@ class WallStreetJournal(BasicNewsRecipe):
br['password'] = self.password
res = br.submit()
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 '
'username and password')
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,
// and clean up the bbox
//
line = BBOX();
var line = BBOX();
state.first = broken; state.last = true;
this.SVGmoveLine(start,end,line,state,values);
line.Clean();

View File

@ -47,9 +47,9 @@ class MTP_DEVICE(BASE):
from calibre.library.save_to_disk import config
self._prefs = p = JSONConfig('mtp_devices')
p.defaults['format_map'] = self.FORMATS
p.defaults['send_to'] = ['eBooks/import',
'wordplayer/calibretransfer', 'Books', 'sdcard/ebooks',
'eBooks', 'kindle']
p.defaults['send_to'] = ['Books', 'eBooks/import', 'eBooks',
'wordplayer/calibretransfer', 'sdcard/ebooks',
'kindle']
p.defaults['send_template'] = config().parse().send_template
p.defaults['blacklist'] = []
p.defaults['history'] = {}
@ -300,7 +300,7 @@ class MTP_DEVICE(BASE):
p = path
break
if p is None:
p = 'eBooks'
p = 'Books'
self.location_paths[loc] = p
return self.location_paths[on_card]

View File

@ -66,6 +66,7 @@ class RecipeInput(InputFormatPlugin):
if os.access(recipe_or_file, os.R_OK):
self.recipe_source = open(recipe_or_file, 'rb').read()
recipe = compile_recipe(self.recipe_source)
log('Using custom recipe')
else:
from calibre.web.feeds.recipes.collection import \
get_builtin_recipe_by_title
@ -87,12 +88,15 @@ class RecipeInput(InputFormatPlugin):
'back to builtin one')
builtin = True
if builtin:
log('Using bundled builtin recipe')
raw = get_builtin_recipe_by_title(title, log=log,
download_recipe=False)
if raw is None:
raise ValueError('Failed to find builtin recipe: '+title)
recipe = compile_recipe(raw)
self.recipe_source = raw
else:
log('Using downloaded builtin recipe')
if recipe is None:
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...'))
self.oeb.plumber_output_format = self.output_fmt or ''
from calibre.ebooks.oeb.transforms.guide import Clean
Clean()(self.oeb, self.opts)
pr(0.1)
@ -1120,7 +1122,7 @@ OptionRecommendation(name='search_replace',
self.log.info('Creating %s...'%self.output_plugin.name)
our = CompositeProgressReporter(0.67, 1., self.ui_reporter)
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:
self.output_plugin.convert(self.oeb, self.output, self.input_plugin,
self.opts, self.log)

View File

@ -39,7 +39,7 @@ class MathJax
showMathMenu: false,
extensions: ["tex2jax.js", "asciimath2jax.js", "mml2jax.js"],
jax: ["input/TeX","input/MathML","input/AsciiMath","output/SVG"],
// SVG : { linebreaks : { automatic : true } },
SVG : { linebreaks : { automatic : true } },
TeX: {
extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"]
}

View File

@ -267,34 +267,43 @@ class Stylizer(object):
rules.sort()
self.rules = rules
self._styles = {}
pseudo_pat = re.compile(ur':(first-letter|first-line|link|hover|visited|active|focus)', re.I)
for _, _, cssdict, text, _ in rules:
fl = ':first-letter' in text
if fl:
text = text.replace(':first-letter', '')
fl = pseudo_pat.search(text)
if fl is not None:
text = text.replace(fl.group(), '')
selector = get_css_selector(text)
matches = selector(tree, self.logger)
if fl:
from lxml.builder import ElementMaker
E = ElementMaker(namespace=XHTML_NS)
for elem in matches:
for x in elem.iter():
if x.text:
punctuation_chars = []
text = unicode(x.text)
while text:
if not unicodedata.category(text[0]).startswith('P'):
break
punctuation_chars.append(text[0])
text = text[1:]
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
E = ElementMaker(namespace=XHTML_NS)
for elem in matches:
for x in elem.iter():
if x.text:
punctuation_chars = []
text = unicode(x.text)
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) + \
(text[0] if text else u'')
span = E.span(special_text)
span.tail = text[1:]
x.text = None
x.insert(0, span)
self.style(span)._update_cssdict(cssdict)
break
special_text = u''.join(punctuation_chars) + \
(text[0] if text else u'')
span = E.span(special_text)
span.tail = text[1:]
x.text = None
x.insert(0, span)
self.style(span)._update_cssdict(cssdict)
break
else: # Element pseudo-class
for elem in matches:
self.style(elem)._update_pseudo_class(fl, cssdict)
else:
for elem in matches:
self.style(elem)._update_cssdict(cssdict)
@ -495,6 +504,7 @@ class Style(object):
self._height = None
self._lineHeight = None
self._bgcolor = None
self._pseudo_classes = {}
stylizer._styles[element] = self
def set(self, prop, val):
@ -506,6 +516,11 @@ class Style(object):
def _update_cssdict(self, 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):
attrib = self._element.attrib
if 'style' not in attrib:
@ -778,3 +793,14 @@ class Style(object):
def cssdict(self):
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
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) \
or namespace(node.tag) != XHTML_NS:
return
@ -357,25 +357,51 @@ class CSSFlattener(object):
cssdict.get('text-align', None) not in ('center', 'right')):
cssdict['text-indent'] = "%1.1fem" % indent_size
if cssdict:
items = cssdict.items()
items.sort()
css = u';\n'.join(u'%s: %s' % (key, val) for key, val in items)
classes = node.get('class', '').strip() or 'calibre'
klass = STRIPNUM.sub('', classes.split()[0].replace('_', ''))
if css in styles:
match = styles[css]
else:
match = klass + str(names[klass] or '')
styles[css] = match
names[klass] += 1
node.attrib['class'] = match
pseudo_classes = style.pseudo_classes(self.filter_css)
if cssdict or pseudo_classes:
keep_classes = set()
if cssdict:
items = cssdict.items()
items.sort()
css = u';\n'.join(u'%s: %s' % (key, val) for key, val in items)
classes = node.get('class', '').strip() or 'calibre'
klass = STRIPNUM.sub('', classes.split()[0].replace('_', ''))
if css in styles:
match = styles[css]
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:
del node.attrib['class']
if 'style' in node.attrib:
del node.attrib['style']
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):
html = item.data
@ -446,7 +472,7 @@ class CSSFlattener(object):
def flatten_spine(self):
names = defaultdict(int)
styles = {}
styles, pseudo_styles = {}, defaultdict(dict)
for item in self.oeb.spine:
html = item.data
stylizer = self.stylizers[item]
@ -454,10 +480,20 @@ class CSSFlattener(object):
self.specializer(item, stylizer)
body = html.find(XHTML('body'))
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.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)
href = self.replace_css(css)
global_css = self.collect_global_css()
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
text = _('Start Content Server')
if running:
listen_on = verify_ipV4_address(tweaks['server_listen_on'])
if listen_on:
text = _('Stop Content Server') + ' [%s]'%listen_on
else:
text = _('Stop Content Server') + ' [%s]'%get_external_ip()
listen_on = (verify_ipV4_address(tweaks['server_listen_on']) or
get_external_ip())
text = _('Stop Content Server') + ' [%s]'%listen_on
self.toggle_server_action.setText(text)
def hide_smartdevice_menus(self):