0.9.20+ - fixed EPUB to PDF problem when rendering from catalog

This commit is contained in:
GRiker 2013-02-25 17:47:56 -07:00
commit 72d0301a7c
42 changed files with 682 additions and 249 deletions

View File

@ -616,7 +616,10 @@ or a Remote Desktop solution.
If you must share the actual library, use a file syncing tool like
DropBox or rsync or Microsoft SkyDrive instead of a networked drive. Even with
these tools there is danger of data corruption/loss, so only do this if you are
willing to live with that risk.
willing to live with that risk. In particular, be aware that **Google Drive**
is incompatible with |app|, if you put your |app| library in Google Drive, you
*will* suffer data loss. See
`this thread <http://www.mobileread.com/forums/showthread.php?t=205581>`_ for details.
Content From The Web
---------------------

View File

@ -0,0 +1,27 @@
from calibre.web.feeds.news import BasicNewsRecipe
import re
class AdvancedUserRecipe1361743898(BasicNewsRecipe):
title = u'Democracy Journal'
description = '''A journal of ideas. Published quarterly.'''
__author__ = u'David Nye'
language = 'en'
oldest_article = 90
max_articles_per_feed = 30
no_stylesheets = True
auto_cleanup = True
def parse_index(self):
articles = []
feeds = []
soup = self.index_to_soup("http://www.democracyjournal.org")
for x in soup.findAll(href=re.compile("http://www\.democracyjournal\.org/\d*/.*php$")):
url = x.get('href')
title = self.tag_to_string(x)
articles.append({'title':title, 'url':url, 'description':'', 'date':''})
feeds.append(('Articles', articles))
return feeds
def print_version(self, url):
return url + '?page=all'

View File

@ -0,0 +1,27 @@
# coding=utf-8
# https://github.com/iemejia/calibrecolombia
'''
http://www.elmalpensante.com/
'''
from calibre.web.feeds.news import BasicNewsRecipe
class ElMalpensante(BasicNewsRecipe):
title = u'El Malpensante'
language = 'es_CO'
__author__ = 'Ismael Mejia <iemejia@gmail.com>'
cover_url = 'http://elmalpensante.com/img/layout/logo.gif'
description = 'El Malpensante'
oldest_article = 30
simultaneous_downloads = 20
#tags = 'news, sport, blog'
use_embedded_content = True
remove_empty_feeds = True
max_articles_per_feed = 100
feeds = [(u'Artículos', u'http://www.elmalpensante.com/articulosRSS.php'),
(u'Malpensantías', u'http://www.elmalpensante.com/malpensantiasRSS.php'),
(u'Margaritas', u'http://www.elmalpensante.com/margaritasRSS.php'),
# This one is almost the same as articulos so we leave articles
# (u'Noticias', u'http://www.elmalpensante.com/noticiasRSS.php'),
]

View File

@ -0,0 +1,12 @@
from calibre.web.feeds.news import BasicNewsRecipe
class BasicUserRecipe1361379046(BasicNewsRecipe):
title = u'Geopolityka.org'
language = 'pl'
__author__ = 'chemik111'
oldest_article = 15
max_articles_per_feed = 100
auto_cleanup = True
feeds = [(u'Rss', u'http://geopolityka.org/index.php?format=feed&type=rss')]

68
recipes/hnonline.recipe Normal file
View File

@ -0,0 +1,68 @@
from calibre.web.feeds.news import BasicNewsRecipe
import re
class HNonlineRecipe(BasicNewsRecipe):
__license__ = 'GPL v3'
__author__ = 'lacike'
language = 'sk'
version = 1
title = u'HNonline'
publisher = u'HNonline'
category = u'News, Newspaper'
description = u'News from Slovakia'
cover_url = u'http://hnonline.sk/img/sk/_relaunch/logo2.png'
oldest_article = 1
max_articles_per_feed = 100
use_embedded_content = False
remove_empty_feeds = True
no_stylesheets = True
remove_javascript = True
# Feeds from: http://rss.hnonline.sk, for listing see http://rss.hnonline.sk/prehlad
feeds = []
feeds.append((u'HNonline|Ekonomika a firmy', u'http://rss.hnonline.sk/?p=kC1000'))
feeds.append((u'HNonline|Slovensko', u'http://rss.hnonline.sk/?p=kC2000'))
feeds.append((u'HNonline|Svet', u'http://rss.hnonline.sk/?p=kC3000'))
feeds.append((u'HNonline|\u0160port', u'http://rss.hnonline.sk/?p=kC4000'))
feeds.append((u'HNonline|Online rozhovor', u'http://rss.hnonline.sk/?p=kCR000'))
feeds.append((u'FinWeb|Spr\u00E1vy zo sveta financi\u00ED', u'http://rss.finweb.hnonline.sk/spravodajstvo'))
feeds.append((u'FinWeb|Koment\u00E1re a anal\u00FDzy', u'http://rss.finweb.hnonline.sk/?p=kPC200'))
feeds.append((u'FinWeb|Invest\u00EDcie', u'http://rss.finweb.hnonline.sk/?p=kPC300'))
feeds.append((u'FinWeb|Svet akci\u00ED', u'http://rss.finweb.hnonline.sk/?p=kPC400'))
feeds.append((u'FinWeb|Rozhovory', u'http://rss.finweb.hnonline.sk/?p=kPC500'))
feeds.append((u'FinWeb|T\u00E9ma t\u00FD\u017Ed\u0148a', u'http://rss.finweb.hnonline.sk/?p=kPC600'))
feeds.append((u'FinWeb|Rebr\u00ED\u010Dky', u'http://rss.finweb.hnonline.sk/?p=kPC700'))
feeds.append((u'HNstyle|Kult\u00FAra', u'http://style.hnonline.sk/?p=kTC100'))
feeds.append((u'HNstyle|Auto-moto', u'http://style.hnonline.sk/?p=kTC200'))
feeds.append((u'HNstyle|Digit\u00E1l', u'http://style.hnonline.sk/?p=kTC300'))
feeds.append((u'HNstyle|Veda', u'http://style.hnonline.sk/?p=kTCV00'))
feeds.append((u'HNstyle|Dizajn', u'http://style.hnonline.sk/?p=kTC400'))
feeds.append((u'HNstyle|Cestovanie', u'http://style.hnonline.sk/?p=kTCc00'))
feeds.append((u'HNstyle|V\u00EDkend', u'http://style.hnonline.sk/?p=kTC800'))
feeds.append((u'HNstyle|Gastro', u'http://style.hnonline.sk/?p=kTC600'))
feeds.append((u'HNstyle|M\u00F3da', u'http://style.hnonline.sk/?p=kTC700'))
feeds.append((u'HNstyle|Modern\u00E1 \u017Eena', u'http://style.hnonline.sk/?p=kTCA00'))
feeds.append((u'HNstyle|Pre\u010Do nie?!', u'http://style.hnonline.sk/?p=k7C000'))
keep_only_tags = []
keep_only_tags.append(dict(name = 'h1', attrs = {'class': 'detail-titulek'}))
keep_only_tags.append(dict(name = 'div', attrs = {'class': 'detail-podtitulek'}))
keep_only_tags.append(dict(name = 'div', attrs = {'class': 'detail-perex'}))
keep_only_tags.append(dict(name = 'div', attrs = {'class': 'detail-text'}))
remove_tags = []
#remove_tags.append(dict(name = 'div', attrs = {'id': re.compile('smeplayer.*')}))
remove_tags_after = []
#remove_tags_after = [dict(name = 'p', attrs = {'class': 'autor_line'})]
extra_css = '''
@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/LiberationSans.ttf)}
body {font-family: sans1, serif1;}
'''

BIN
recipes/icons/hnonline.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

View File

@ -0,0 +1,59 @@
__license__ = 'GPL v3'
__copyright__ = '2013, Darko Miletic <darko.miletic at gmail.com>'
'''
www.nezavisne.com
'''
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class NezavisneNovine(BasicNewsRecipe):
title = 'Nezavisne novine'
__author__ = 'Darko Miletic'
description = 'Nezavisne novine - Najnovije vijesti iz BiH, Srbije, Hrvatske, Crne Gore i svijeta'
publisher = 'NIGP "DNN"'
category = 'news, politics, Bosnia, Balcans'
oldest_article = 2
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = False
language = 'sr'
remove_empty_feeds = True
publication_type = 'newspaper'
cover_url = strftime('http://pdf.nezavisne.com/slika/novina/nezavisne_novine.jpg?v=%Y%m%d')
masthead_url = 'http://www.nezavisne.com/slika/osnova/nezavisne-novine-logo.gif'
extra_css = """
body{font-family: Arial,Helvetica,sans-serif }
img{margin-bottom: 0.4em; display:block}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
keep_only_tags = [dict(name='div', attrs={'class':'vijest'})]
remove_tags_after = dict(name='div', attrs={'id':'wrap'})
remove_tags = [
dict(name=['meta','link','iframe','object'])
,dict(name='div', attrs={'id':'wrap'})
]
remove_attributes=['lang','xmlns:fb','xmlns:og']
feeds = [
(u'Novosti' , u'http://feeds.feedburner.com/Novosti-NezavisneNovine' )
,(u'Posao' , u'http://feeds.feedburner.com/Posao-NezavisneNovine' )
,(u'Sport' , u'http://feeds.feedburner.com/Sport-NezavisneNovine' )
,(u'Komentar' , u'http://feeds.feedburner.com/Komentari-NezavisneNovine' )
,(u'Umjetnost i zabava' , u'http://feeds.feedburner.com/UmjetnostIZabava-NezavisneNovine' )
,(u'Život i stil' , u'http://feeds.feedburner.com/ZivotIStil-NezavisneNovine' )
,(u'Auto' , u'http://feeds.feedburner.com/Auto-NezavisneNovine' )
,(u'Nauka i tehnologija', u'http://feeds.feedburner.com/NaukaITehnologija-NezavisneNovine')
]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return soup

View File

@ -0,0 +1,33 @@
# coding=utf-8
# https://github.com/iemejia/calibrecolombia
'''
http://www.cromos.com.co/
'''
from calibre.web.feeds.news import BasicNewsRecipe
class ElMalpensante(BasicNewsRecipe):
title = u'Revista Cromos'
language = 'es_CO'
__author__ = 'Ismael Mejia <iemejia@gmail.com>'
cover_url = 'http://www.cromos.com.co/sites/cromos.com.co/themes/cromos_theme/images/logo_morado.gif'
description = 'Revista Cromos'
oldest_article = 7
simultaneous_downloads = 20
#tags = 'news, sport, blog'
use_embedded_content = True
remove_empty_feeds = True
max_articles_per_feed = 100
feeds = [(u'Cromos', u'http://www.cromos.com.co/rss.xml'),
(u'Moda', u'http://www.cromos.com.co/moda/feed'),
(u'Estilo de Vida', u'http://www.cromos.com.co/estilo-de-vida/feed'),
(u'Cuidado Personal', u'http://www.cromos.com.co/estilo-de-vida/cuidado-personal/feed'),
(u'Salud y Alimentación', u'http://www.cromos.com.co/estilo-de-vida/salud-y-alimentacion/feed'),
(u'Personajes', u'http://www.cromos.com.co/personajes/feed'),
(u'Actualidad', u'http://www.cromos.com.co/personajes/actualidad/feed'),
(u'Espectáculo', u'http://www.cromos.com.co/personajes/espectaculo/feed'),
(u'Reportajes', u'http://www.cromos.com.co/reportajes/feed'),
(u'Eventos', u'http://www.cromos.com.co/eventos/feed'),
(u'Modelos', u'http://www.cromos.com.co/modelos/feed'),
]

View File

@ -1,24 +1,38 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
'''
sciencenews.org
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Sciencenews(BasicNewsRecipe):
title = u'ScienceNews'
__author__ = u'Darko Miletic and Sujata Raman'
description = u"Science News is an award-winning weekly newsmagazine covering the most important research in all fields of science. Its 16 pages each week are packed with short, accurate articles that appeal to both general readers and scientists. Published since 1922, the magazine now reaches about 150,000 subscribers and more than 1 million readers. These are the latest News Items from Science News."
class ScienceNewsIssue(BasicNewsRecipe):
title = u'Science News Recent Issues'
__author__ = u'Darko Miletic, Sujata Raman and Starson17'
description = u'''Science News is an award-winning weekly
newsmagazine covering the most important research in all fields of science.
Its 16 pages each week are packed with short, accurate articles that appeal
to both general readers and scientists. Published since 1922, the magazine
now reaches about 150,000 subscribers and more than 1 million readers.
These are the latest News Items from Science News. This recipe downloads
the last 30 days worth of articles.'''
category = u'Science, Technology, News'
publisher = u'Society for Science & the Public'
oldest_article = 30
language = 'en'
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
auto_cleanup = True
timefmt = ' [%A, %d %B, %Y]'
recursions = 1
remove_attributes = ['style']
conversion_options = {'linearize_tables' : True
, 'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
extra_css = '''
.content_description{font-family:georgia ;font-size:x-large; color:#646464 ; font-weight:bold;}
@ -27,36 +41,33 @@ class Sciencenews(BasicNewsRecipe):
.content_edition{font-family:helvetica,arial ;font-size: xx-small ;}
.exclusive{color:#FF0000 ;}
.anonymous{color:#14487E ;}
.content_content{font-family:helvetica,arial ;font-size: x-small ; color:#000000;}
.description{color:#585858;font-family:helvetica,arial ;font-size: xx-small ;}
.content_content{font-family:helvetica,arial ;font-size: medium ; color:#000000;}
.description{color:#585858;font-family:helvetica,arial ;font-size: large ;}
.credit{color:#A6A6A6;font-family:helvetica,arial ;font-size: xx-small ;}
'''
#keep_only_tags = [ dict(name='div', attrs={'id':'column_action'}) ]
#remove_tags_after = dict(name='ul', attrs={'id':'content_functions_bottom'})
#remove_tags = [
#dict(name='ul', attrs={'id':'content_functions_bottom'})
#,dict(name='div', attrs={'id':['content_functions_top','breadcrumb_content']})
#,dict(name='img', attrs={'class':'icon'})
#,dict(name='div', attrs={'class': 'embiggen'})
#]
keep_only_tags = [ dict(name='div', attrs={'class':'content_content'}),
dict(name='ul', attrs={'id':'toc'})
]
feeds = [(u"Science News / News Items", u'http://sciencenews.org/index.php/feed/type/news/name/news.rss/view/feed/name/all.rss')]
feeds = [(u"Science News Current Issues", u'http://www.sciencenews.org/view/feed/type/edition/name/issues.rss')]
match_regexps = [
r'www.sciencenews.org/view/feature/id/',
r'www.sciencenews.org/view/generic/id'
]
def get_cover_url(self):
cover_url = None
index = 'http://www.sciencenews.org/view/home'
soup = self.index_to_soup(index)
link_item = soup.find(name = 'img',alt = "issue")
print link_item
if link_item:
cover_url = 'http://www.sciencenews.org' + link_item['src'] + '.jpg'
return cover_url
#def preprocess_html(self, soup):
#for tag in soup.findAll(name=['span']):
#tag.name = 'div'
#return soup
def preprocess_html(self, soup):
for tag in soup.findAll(name=['span']):
tag.name = 'div'
return soup

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# https://github.com/iemejia/calibrecolombia
'''
http://www.unperiodico.unal.edu.co/
'''
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class UNPeriodico(BasicNewsRecipe):
title = u'UN Periodico'
language = 'es_CO'
__author__ = 'Ismael Mejia <iemejia@gmail.com>'
cover_url = 'http://www.unperiodico.unal.edu.co/fileadmin/templates/periodico/img/logoperiodico.png'
description = 'UN Periodico'
oldest_article = 30
max_articles_per_feed = 100
publication_type = 'newspaper'
feeds = [
(u'UNPeriodico', u'http://www.unperiodico.unal.edu.co/rss/type/rss2/')
]

View File

@ -55,20 +55,14 @@ class WallStreetJournal(BasicNewsRecipe):
]
remove_tags_after = [dict(id="article_story_body"), {'class':"article story"},]
use_javascript_to_login = True
def get_browser(self):
br = BasicNewsRecipe.get_browser(self)
if self.username is not None and self.password is not None:
br.open('http://commerce.wsj.com/auth/login')
br.select_form(nr=1)
br['user'] = self.username
br['password'] = self.password
res = br.submit()
raw = res.read()
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
def javascript_login(self, br, username, password):
br.visit('https://id.wsj.com/access/pages/wsj/us/login_standalone.html?mg=com-wsj', timeout=120)
f = br.select_form(nr=0)
f['username'] = username
f['password'] = password
br.submit(timeout=120)
def populate_article_metadata(self, article, soup, first):
if first and hasattr(self, 'add_toc_thumbnail'):

View File

@ -88,7 +88,7 @@ class ZeitEPUBAbo(BasicNewsRecipe):
(re.compile(u' \u00AB'), lambda match: u'\u00AB '), # before closing quotation
(re.compile(u'\u00BB '), lambda match: u' \u00BB'), # after opening quotation
# filtering for spaces in large numbers for better readability
(re.compile(r'(?<=\d\d)(?=\d\d\d[ ,\.;\)<\?!-])'), lambda match: u'\u2008'), # end of the number with some character following
(re.compile(r'(?<=\d\d)(?=\d\d\d[ ,;\)<\?!-])'), lambda match: u'\u2008'), # end of the number with some character following
(re.compile(r'(?<=\d\d)(?=\d\d\d. )'), lambda match: u'\u2008'), # end of the number with full-stop following, then space is necessary (avoid file names)
(re.compile(u'(?<=\d)(?=\d\d\d\u2008)'), lambda match: u'\u2008'), # next level
(re.compile(u'(?<=\d)(?=\d\d\d\u2008)'), lambda match: u'\u2008'), # next level

Binary file not shown.

View File

@ -517,3 +517,10 @@ default_tweak_format = None
# your library and your personal editing style.
preselect_first_completion = False
#: Recognize numbers inside text when sorting
# This means that when sorting on text fields like title the text "Book 2"
# will sort before the text "Book 100". If you want this behavior, set
# numeric_collation = True note that doing so will cause problems with text
# that starts with numbers and is a little slower.
numeric_collation = False

View File

@ -20,7 +20,7 @@ from calibre.ptempfile import PersistentTemporaryFile
from calibre.db.schema_upgrades import SchemaUpgrade
from calibre.library.field_metadata import FieldMetadata
from calibre.ebooks.metadata import title_sort, author_to_author_sort
from calibre.utils.icu import strcmp
from calibre.utils.icu import sort_key
from calibre.utils.config import to_json, from_json, prefs, tweaks
from calibre.utils.date import utcfromtimestamp, parse_date
from calibre.utils.filenames import (is_case_sensitive, samefile, hardlink_file)
@ -172,7 +172,9 @@ def _author_to_author_sort(x):
return author_to_author_sort(x.replace('|', ','))
def icu_collator(s1, s2):
return strcmp(force_unicode(s1, 'utf-8'), force_unicode(s2, 'utf-8'))
return cmp(sort_key(force_unicode(s1, 'utf-8')),
sort_key(force_unicode(s2, 'utf-8')))
# }}}
# Unused aggregators {{{

View File

@ -211,6 +211,12 @@ class Cache(object):
self.fields['ondevice'] = create_field('ondevice',
VirtualTable('ondevice'))
for name, field in self.fields.iteritems():
if name[0] == '#' and name.endswith('_index'):
field.series_field = self.fields[name[:-len('_index')]]
elif name == 'series_index':
field.series_field = self.fields['series']
@read_api
def field_for(self, name, book_id, default_value=None):
'''
@ -609,11 +615,11 @@ class Cache(object):
icon_map=icon_map)
@write_api
def set_field(self, name, book_id_to_val_map):
def set_field(self, name, book_id_to_val_map, allow_case_change=True):
# TODO: Specialize title/authors to also update path
# TODO: Handle updating caches used by composite fields
dirtied = self.fields[name].writer.set_books(
book_id_to_val_map, self.backend)
book_id_to_val_map, self.backend, allow_case_change=allow_case_change)
return dirtied
# }}}

View File

@ -46,6 +46,7 @@ class Field(object):
elif name == 'languages':
self.category_formatter = calibre_langcode_to_name
self.writer = Writer(self)
self.series_field = None
@property
def metadata(self):

View File

@ -22,6 +22,10 @@ class WritingTest(BaseTest):
def create_getter(self, name, getter=None):
if getter is None:
if name.endswith('_index'):
ans = lambda db:partial(db.get_custom_extra, index_is_id=True,
label=name[1:].replace('_index', ''))
else:
ans = lambda db:partial(db.get_custom, label=name[1:],
index_is_id=True)
else:
@ -41,11 +45,11 @@ class WritingTest(BaseTest):
self.create_setter(name, setter))
def run_tests(self, tests):
cl = self.cloned_library
results = {}
for test in tests:
results[test] = []
for val in test.vals:
cl = self.cloned_library
cache = self.init_cache(cl)
cache.set_field(test.name, {1: val})
cached_res = cache.field_for(test.name, 1)
@ -53,6 +57,12 @@ class WritingTest(BaseTest):
db = self.init_old(cl)
getter = test.getter(db)
sqlite_res = getter(1)
if test.name.endswith('_index'):
val = float(val) if val is not None else 1.0
self.assertEqual(sqlite_res, val,
'Failed setting for %s with value %r, sqlite value not the same. val: %r != sqlite_val: %r'%(
test.name, val, val, sqlite_res))
else:
test.setter(db)(1, val)
old_cached_res = getter(1)
self.assertEqual(old_cached_res, cached_res,
@ -65,11 +75,17 @@ class WritingTest(BaseTest):
test.name, old_sqlite_res, sqlite_res))
del db
def test_one_one(self):
'Test setting of values in one-one fields'
tests = []
tests = [self.create_test('#yesno', (True, False, 'true', 'false', None))]
for name, getter, setter in (
('#series_index', None, None),
('series_index', 'series_index', 'set_series_index'),
('#float', None, None),
):
vals = ['1.5', None, 0, 1.0]
tests.append(self.create_test(name, tuple(vals), getter, setter))
for name, getter, setter in (
('pubdate', 'pubdate', 'set_pubdate'),
('timestamp', 'timestamp', 'set_timestamp'),
@ -78,6 +94,25 @@ class WritingTest(BaseTest):
tests.append(self.create_test(
name, ('2011-1-12', UNDEFINED_DATE, None), getter, setter))
for name, getter, setter in (
('title', 'title', 'set_title'),
('uuid', 'uuid', 'set_uuid'),
('author_sort', 'author_sort', 'set_author_sort'),
('sort', 'title_sort', 'set_title_sort'),
('#comments', None, None),
('comments', 'comments', 'set_comment'),
):
vals = ['something', None]
if name not in {'comments', '#comments'}:
# Setting text column to '' returns None in the new backend
# and '' in the old. I think None is more correct.
vals.append('')
if name == 'comments':
# Again new behavior of deleting comment rather than setting
# empty string is more correct.
vals.remove(None)
tests.append(self.create_test(name, tuple(vals), getter, setter))
self.run_tests(tests)
def tests():

View File

@ -13,11 +13,13 @@ from datetime import datetime
from calibre.constants import preferred_encoding, ispy3
from calibre.utils.date import (parse_only_date, parse_date, UNDEFINED_DATE,
isoformat)
if ispy3:
unicode = str
# Convert data into values suitable for the db {{{
if ispy3:
unicode = str
def sqlite_datetime(x):
return isoformat(x, sep=' ') if isinstance(x, datetime) else x
def single_text(x):
if x is None:
@ -98,17 +100,19 @@ def get_adapter(name, metadata):
if name == 'title':
return lambda x: ans(x) or _('Unknown')
if name == 'author_sort':
return lambda x: ans(x) or ''
if name == 'authors':
return lambda x: ans(x) or (_('Unknown'),)
if name in {'timestamp', 'last_modified'}:
return lambda x: ans(x) or UNDEFINED_DATE
if name == 'series_index':
return lambda x: 1.0 if ans(x) is None else ans(x)
return ans
# }}}
def sqlite_datetime(x):
return isoformat(x, sep=' ') if isinstance(x, datetime) else x
# One-One fields {{{
def one_one_in_books(book_id_val_map, db, field, *args):
'Set a one-one field in the books table'
if book_id_val_map:
@ -134,6 +138,22 @@ def one_one_in_other(book_id_val_map, db, field, *args):
field.table.book_col_map.update(updated)
return set(book_id_val_map)
def custom_series_index(book_id_val_map, db, field, *args):
series_field = field.series_field
sequence = []
for book_id, sidx in book_id_val_map.iteritems():
if sidx is None:
sidx = 1.0
ids = series_field.ids_for_book(book_id)
if ids:
sequence.append((sidx, book_id, ids[0]))
field.table.book_col_map[book_id] = sidx
if sequence:
db.conn.executemany('UPDATE %s SET %s=? WHERE book=? AND value=?'%(
field.metadata['table'], field.metadata['column']), sequence)
return {s[0] for s in sequence}
# }}}
def dummy(book_id_val_map, *args):
return set()
@ -148,16 +168,19 @@ class Writer(object):
if dt == 'composite' or field.name in {
'id', 'cover', 'size', 'path', 'formats', 'news'}:
self.set_books_func = dummy
elif self.name[0] == '#' and self.name.endswith('_index'):
self.set_books_func = custom_series_index
elif field.is_many:
# TODO: Implement this
pass
# TODO: Remember to change commas to | when writing authors to sqlite
else:
self.set_books_func = (one_one_in_books if field.metadata['table']
== 'books' else one_one_in_other)
if self.name in {'timestamp', 'uuid'}:
if self.name in {'timestamp', 'uuid', 'sort'}:
self.accept_vals = bool
def set_books(self, book_id_val_map, db):
def set_books(self, book_id_val_map, db, allow_case_change=True):
book_id_val_map = {k:self.adapter(v) for k, v in
book_id_val_map.iteritems() if self.accept_vals(v)}
if not book_id_val_map:

View File

@ -22,13 +22,14 @@ class IRIVER_STORY(USBMS):
FORMATS = ['epub', 'fb2', 'pdf', 'djvu', 'txt']
VENDOR_ID = [0x1006]
PRODUCT_ID = [0x4023, 0x4024, 0x4025, 0x4034]
BCD = [0x0323, 0x0326]
PRODUCT_ID = [0x4023, 0x4024, 0x4025, 0x4034, 0x4037]
BCD = [0x0323, 0x0326, 0x226]
VENDOR_NAME = 'IRIVER'
WINDOWS_MAIN_MEM = ['STORY', 'STORY_EB05', 'STORY_WI-FI', 'STORY_EB07']
WINDOWS_MAIN_MEM = ['STORY', 'STORY_EB05', 'STORY_WI-FI', 'STORY_EB07',
'STORY_EB12']
WINDOWS_MAIN_MEM = re.compile(r'(%s)&'%('|'.join(WINDOWS_MAIN_MEM)))
WINDOWS_CARD_A_MEM = ['STORY', 'STORY_SD']
WINDOWS_CARD_A_MEM = ['STORY', 'STORY_SD', 'STORY_EB12_SD']
WINDOWS_CARD_A_MEM = re.compile(r'(%s)&'%('|'.join(WINDOWS_CARD_A_MEM)))
#OSX_MAIN_MEM = 'Kindle Internal Storage Media'

View File

@ -6,7 +6,7 @@ import os, time, sys
from calibre.constants import preferred_encoding, DEBUG
from calibre import isbytestring, force_unicode
from calibre.utils.icu import strcmp
from calibre.utils.icu import sort_key
from calibre.devices.usbms.books import Book as Book_
from calibre.devices.usbms.books import CollectionsBookList
@ -239,8 +239,7 @@ class KTCollectionsBookList(CollectionsBookList):
if y is None:
return -1
if isinstance(x, basestring) and isinstance(y, basestring):
c = strcmp(force_unicode(x), force_unicode(y))
else:
x, y = sort_key(force_unicode(x)), sort_key(force_unicode(y))
c = cmp(x, y)
if c != 0:
return c

View File

@ -13,7 +13,7 @@ from calibre.devices.interface import BookList as _BookList
from calibre.constants import preferred_encoding
from calibre import isbytestring, force_unicode
from calibre.utils.config import device_prefs, tweaks
from calibre.utils.icu import strcmp
from calibre.utils.icu import sort_key
from calibre.utils.formatter import EvalFormatter
class Book(Metadata):
@ -281,8 +281,7 @@ class CollectionsBookList(BookList):
if y is None:
return -1
if isinstance(x, basestring) and isinstance(y, basestring):
c = strcmp(force_unicode(x), force_unicode(y))
else:
x, y = sort_key(force_unicode(x)), sort_key(force_unicode(y))
c = cmp(x, y)
if c != 0:
return c

View File

@ -75,6 +75,13 @@ class PagedDisplay
this.margin_side = margin_side
this.margin_bottom = margin_bottom
handle_rtl_body: (body_style) ->
if body_style.direction == "rtl"
for node in document.body.childNodes
if node.nodeType == node.ELEMENT_NODE and window.getComputedStyle(node).direction == "rtl"
node.style.setProperty("direction", "rtl")
document.body.style.direction = "ltr"
layout: (is_single_page=false) ->
# start_time = new Date().getTime()
body_style = window.getComputedStyle(document.body)
@ -84,6 +91,7 @@ class PagedDisplay
# Check if the current document is a full screen layout like
# cover, if so we treat it specially.
single_screen = (document.body.scrollHeight < window.innerHeight + 75)
this.handle_rtl_body(body_style)
first_layout = true
ww = window.innerWidth

View File

@ -45,13 +45,13 @@ def load_html(path, view, codec='utf-8', mime_type=None,
html = f.read().decode(codec, 'replace')
html = EntityDeclarationProcessor(html).processed_html
self_closing_pat = re.compile(r'<\s*([A-Za-z1-6]+)([^>]*)/\s*>')
self_closing_pat = re.compile(r'<\s*([:A-Za-z0-9-]+)([^>]*)/\s*>')
html = self_closing_pat.sub(self_closing_sub, html)
loading_url = QUrl.fromLocalFile(path)
pre_load_callback(loading_url)
if force_as_html or re.search(r'<[:a-zA-Z]*svg', html) is None:
if force_as_html or re.search(r'<[:a-zA-Z0-9-]*svg', html) is None:
view.setHtml(html, loading_url)
else:
view.setContent(QByteArray(html.encode(codec)), mime_type,

View File

@ -174,6 +174,7 @@ def gui_polish(data):
files = data.pop('files')
if not data.pop('metadata'):
data.pop('opf')
if not data.pop('do_cover'):
data.pop('cover')
file_map = {x:x for x in files}
opts = ALL_OPTS.copy()

View File

@ -9,10 +9,11 @@ __docformat__ = 'restructuredtext en'
import os, sys
from calibre import prints
from calibre import prints, as_unicode
from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS, XPath
from calibre.ebooks.oeb.polish.container import OEB_FONTS
from calibre.utils.fonts.sfnt.subset import subset
from calibre.utils.fonts.sfnt.errors import UnsupportedFont
from calibre.utils.fonts.utils import get_font_names
def remove_font_face_rules(container, sheet, remove_names, base):
@ -46,9 +47,16 @@ def subset_all_fonts(container, font_stats, report):
raw = f.read()
font_name = get_font_names(raw)[-1]
warnings = []
container.log('Subsetting font: %s'%font_name)
container.log('Subsetting font: %s'%(font_name or name))
try:
nraw, old_sizes, new_sizes = subset(raw, chars,
warnings=warnings)
except UnsupportedFont as e:
container.log.warning(
'Unsupported font: %s, ignoring. Error: %s'%(
name, as_unicode(e)))
continue
for w in warnings:
container.log.warn(w)
olen = sum(old_sizes.itervalues())

View File

@ -363,7 +363,10 @@ class CSSFlattener(object):
cssdict['font-weight'] = 'normal' # ADE chokes on font-weight medium
fsize = font_size
if not self.context.disable_font_rescaling:
is_drop_cap = (cssdict.get('float', None) == 'left' and 'font-size' in
cssdict and len(node) == 0 and node.text and
len(node.text) == 1)
if not self.context.disable_font_rescaling and not is_drop_cap:
_sbase = self.sbase if self.sbase is not None else \
self.context.source.fbase
dyn_rescale = dynamic_rescale_factor(node)
@ -382,7 +385,7 @@ class CSSFlattener(object):
try:
minlh = self.context.minimum_line_height / 100.
if style['line-height'] < minlh * fsize:
if not is_drop_cap and style['line-height'] < minlh * fsize:
cssdict['line-height'] = str(minlh)
except:
self.oeb.logger.exception('Failed to set minimum line-height')

View File

@ -13,9 +13,10 @@ from operator import itemgetter
from collections import Counter, OrderedDict
from future_builtins import map
from calibre import as_unicode
from calibre.ebooks.pdf.render.common import (Array, String, Stream,
Dictionary, Name)
from calibre.utils.fonts.sfnt.subset import pdf_subset
from calibre.utils.fonts.sfnt.subset import pdf_subset, UnsupportedFont
STANDARD_FONTS = {
'Times-Roman', 'Helvetica', 'Courier', 'Symbol', 'Times-Bold',
@ -150,12 +151,16 @@ class Font(object):
self.used_glyphs = set()
def embed(self, objects):
def embed(self, objects, debug):
self.font_descriptor['FontFile'+('3' if self.is_otf else '2')
] = objects.add(self.font_stream)
self.write_widths(objects)
self.write_to_unicode(objects)
try:
pdf_subset(self.metrics.sfnt, self.used_glyphs)
except UnsupportedFont as e:
debug('Subsetting of %s not supported, embedding full font. Error: %s'%(
self.metrics.names.get('full_name', 'Unknown'), as_unicode(e)))
if self.is_otf:
self.font_stream.write(self.metrics.sfnt['CFF '].raw)
else:
@ -221,7 +226,7 @@ class FontManager(object):
}))
return self.std_map[name]
def embed_fonts(self):
def embed_fonts(self, debug):
for font in self.fonts:
font.embed(self.objects)
font.embed(self.objects, debug)

View File

@ -488,7 +488,7 @@ class PDFStream(object):
def end(self):
if self.current_page.getvalue():
self.end_page()
self.font_manager.embed_fonts()
self.font_manager.embed_fonts(self.debug)
inforef = self.objects.add(self.info)
self.links.add_links()
self.objects.pdf_serialize(self.stream)

View File

@ -44,13 +44,18 @@ class Polish(QDialog): # {{{
_('<h3>Smarten punctuation</h3>%s')%HELP['smarten_punctuation'],
'metadata':_('<h3>Updating metadata</h3>'
'<p>This will update all metadata and covers in the'
'<p>This will update all metadata <i>except</i> the cover in the'
' ebook files to match the current metadata in the'
' calibre library.</p><p>If the ebook file does not have'
' an identifiable cover, a new cover is inserted.</p>'
' calibre library.</p>'
' <p>Note that most ebook'
' formats are not capable of supporting all the'
' metadata in calibre.</p>'),
' metadata in calibre.</p><p>There is a separate option to'
' update the cover.</p>'),
'do_cover': _('<p>Update the covers in the ebook files to match the'
' current cover in the calibre library.</p>'
'<p>If the ebook file does not have'
' an identifiable cover, a new cover is inserted.</p>'
),
'jacket':_('<h3>Book Jacket</h3>%s')%HELP['jacket'],
'remove_jacket':_('<h3>Remove Book Jacket</h3>%s')%HELP['remove_jacket'],
}
@ -63,11 +68,12 @@ class Polish(QDialog): # {{{
count = 0
self.all_actions = OrderedDict([
('subset', _('Subset all embedded fonts')),
('smarten_punctuation', _('Smarten punctuation')),
('metadata', _('Update metadata in book files')),
('jacket', _('Add metadata as a "book jacket" page')),
('remove_jacket', _('Remove a previously inserted book jacket')),
('subset', _('&Subset all embedded fonts')),
('smarten_punctuation', _('Smarten &punctuation')),
('metadata', _('Update &metadata in the book files')),
('do_cover', _('Update the &cover in the book files')),
('jacket', _('Add metadata as a "book &jacket" page')),
('remove_jacket', _('&Remove a previously inserted book jacket')),
])
prefs = gprefs.get('polishing_settings', {})
for name, text in self.all_actions.iteritems():
@ -243,8 +249,10 @@ class Polish(QDialog): # {{{
cover = os.path.join(base, 'cover.jpg')
if db.copy_cover_to(book_id, cover, index_is_id=True):
data['cover'] = cover
is_orig = {}
for fmt in formats:
ext = fmt.replace('ORIGINAL_', '').lower()
is_orig[ext.upper()] = 'ORIGINAL_' in fmt
with open(os.path.join(base, '%s.%s'%(book_id, ext)), 'wb') as f:
db.copy_format_to(book_id, fmt, f, index_is_id=True)
data['files'].append(f.name)
@ -257,7 +265,7 @@ class Polish(QDialog): # {{{
self.pd.set_msg(_('Queueing book %(nums)s of %(tot)s (%(title)s)')%dict(
nums=num, tot=len(self.book_id_map), title=mi.title))
self.jobs.append((desc, data, book_id, base))
self.jobs.append((desc, data, book_id, base, is_orig))
# }}}
class Report(QDialog): # {{{
@ -404,11 +412,11 @@ class PolishAction(InterfaceAction):
d = Polish(self.gui.library_view.model().db, book_id_map, parent=self.gui)
if d.exec_() == d.Accepted and d.jobs:
show_reports = bool(d.show_reports.isChecked())
for desc, data, book_id, base in reversed(d.jobs):
for desc, data, book_id, base, is_orig in reversed(d.jobs):
job = self.gui.job_manager.run_job(
Dispatcher(self.book_polished), 'gui_polish', args=(data,),
description=desc)
job.polish_args = (book_id, base, data['files'], show_reports)
job.polish_args = (book_id, base, data['files'], show_reports, is_orig)
if d.jobs:
self.gui.jobs_pointer.start()
self.gui.status_bar.show_message(
@ -419,11 +427,11 @@ class PolishAction(InterfaceAction):
self.gui.job_exception(job)
return
db = self.gui.current_db
book_id, base, files, show_reports = job.polish_args
book_id, base, files, show_reports, is_orig = job.polish_args
fmts = set()
for path in files:
fmt = path.rpartition('.')[-1].upper()
if tweaks['save_original_format_when_polishing']:
if tweaks['save_original_format_when_polishing'] and not is_orig[fmt]:
fmts.add(fmt)
db.save_original_format(book_id, fmt, notify=False)
with open(path, 'rb') as f:

View File

@ -327,6 +327,13 @@ class EditorWidget(QWebView): # {{{
else:
return QWebView.keyReleaseEvent(self, ev)
def contextMenuEvent(self, ev):
menu = self.page().createStandardContextMenu()
paste = self.pageAction(QWebPage.Paste)
for action in menu.actions():
if action == paste:
menu.insertAction(action, self.pageAction(QWebPage.PasteAndMatchStyle))
menu.exec_(ev.globalPos())
# }}}

View File

@ -622,7 +622,6 @@ class BulkBase(Base):
return
val = self.gui_val
val = self.normalize_ui_val(val)
if val != self.initial_val:
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
def make_widgets(self, parent, main_widget_class, extra_label_text=''):
@ -1030,7 +1029,6 @@ class BulkText(BulkBase):
else:
val = self.gui_val
val = self.normalize_ui_val(val)
if val != self.initial_val:
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
def getter(self):

View File

@ -369,7 +369,7 @@ def build_pipe(print_error=True):
t.start()
t.join(3.0)
if t.is_alive():
if iswindows():
if iswindows:
cant_start()
else:
f = os.path.expanduser('~/.calibre_calibre GUI.lock')

View File

@ -790,8 +790,7 @@ class DocumentView(QWebView): # {{{
self.manager.load_started()
load_html(path, self, codec=getattr(path, 'encoding', 'utf-8'), mime_type=getattr(path,
'mime_type', 'text/html'), pre_load_callback=callback,
force_as_html=True)
'mime_type', 'text/html'), pre_load_callback=callback)
entries = set()
for ie in getattr(path, 'index_entries', []):
if ie.start_anchor:

View File

@ -725,13 +725,15 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.view.shrink_fonts()
def magnification_changed(self, val):
tt = _('%(which)s font size [%(sc)s]\nCurrent magnification: %(mag).1f')
tt = '%(action)s [%(sc)s]\n'+_('Current magnification: %(mag).1f')
sc = unicode(self.action_font_size_larger.shortcut().toString())
self.action_font_size_larger.setToolTip(
tt %dict(which=_('Increase'), mag=val, sc=sc))
tt %dict(action=unicode(self.action_font_size_larger.text()),
mag=val, sc=sc))
sc = unicode(self.action_font_size_smaller.shortcut().toString())
self.action_font_size_smaller.setToolTip(
tt %dict(which=_('Decrease'), mag=val, sc=sc))
tt %dict(action=unicode(self.action_font_size_smaller.text()),
mag=val, sc=sc))
self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)

View File

@ -955,8 +955,8 @@ class LayoutButton(QToolButton):
def set_state_to_hide(self, *args):
self.setChecked(True)
label = _('Hide')
self.setText(label + ' ' + self.label+ u' (%s)'%self.shortcut)
self.setText(_('Hide %(label)s %(shortcut)s'%dict(
label=self.label, shortcut=self.shortcut)))
self.setToolTip(self.text())
self.setStatusTip(self.text())

View File

@ -20,7 +20,7 @@ from calibre.ebooks.metadata import title_sort, author_to_author_sort
from calibre.utils.date import parse_date, isoformat, local_tz, UNDEFINED_DATE
from calibre import isbytestring, force_unicode
from calibre.constants import iswindows, DEBUG, plugins
from calibre.utils.icu import strcmp
from calibre.utils.icu import sort_key
from calibre import prints
from dateutil.tz import tzoffset
@ -189,7 +189,8 @@ def pynocase(one, two, encoding='utf-8'):
return cmp(one.lower(), two.lower())
def icu_collator(s1, s2):
return strcmp(force_unicode(s1, 'utf-8'), force_unicode(s2, 'utf-8'))
return cmp(sort_key(force_unicode(s1, 'utf-8')),
sort_key(force_unicode(s2, 'utf-8')))
def load_c_extensions(conn, debug=DEBUG):
try:

View File

@ -5,8 +5,8 @@
msgid ""
msgstr ""
"Project-Id-Version: calibre 0.9.20\n"
"POT-Creation-Date: 2013-02-22 10:18+IST\n"
"PO-Revision-Date: 2013-02-22 10:18+IST\n"
"POT-Creation-Date: 2013-02-24 10:08+IST\n"
"PO-Revision-Date: 2013-02-24 10:08+IST\n"
"Last-Translator: Automatically generated\n"
"Language-Team: LANGUAGE\n"
"MIME-Version: 1.0\n"
@ -24,8 +24,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/db/cache.py:124
#: /home/kovid/work/calibre/src/calibre/db/cache.py:127
#: /home/kovid/work/calibre/src/calibre/db/cache.py:138
#: /home/kovid/work/calibre/src/calibre/db/write.py:100
#: /home/kovid/work/calibre/src/calibre/db/write.py:102
#: /home/kovid/work/calibre/src/calibre/db/write.py:106
#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:383
#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:384
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:114
@ -882,8 +882,8 @@ msgstr ""
msgid "Disable the named plugin"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/db/backend.py:321
#: /home/kovid/work/calibre/src/calibre/db/backend.py:330
#: /home/kovid/work/calibre/src/calibre/db/backend.py:323
#: /home/kovid/work/calibre/src/calibre/db/backend.py:332
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:322
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library.py:98
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:749
@ -896,7 +896,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/db/cache.py:152
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:666
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:67
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:678
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:677
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1030
#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:887
#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:910
@ -908,25 +908,25 @@ msgstr ""
msgid "%(tt)sAverage rating is %(rating)3.1f"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/db/fields.py:232
#: /home/kovid/work/calibre/src/calibre/db/fields.py:233
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1187
msgid "Main"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/db/fields.py:234
#: /home/kovid/work/calibre/src/calibre/db/fields.py:235
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:77
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1189
msgid "Card A"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/db/fields.py:236
#: /home/kovid/work/calibre/src/calibre/db/fields.py:237
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:79
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1191
msgid "Card B"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/db/fields.py:471
#: /home/kovid/work/calibre/src/calibre/db/fields.py:486
#: /home/kovid/work/calibre/src/calibre/db/fields.py:472
#: /home/kovid/work/calibre/src/calibre/db/fields.py:487
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2822
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:106
#: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:448
@ -3479,7 +3479,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:666
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:67
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:678
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:677
msgid "No"
msgstr ""
@ -3928,7 +3928,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/main.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:392
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:400
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:197
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:132
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:223
@ -4034,27 +4034,27 @@ msgstr ""
msgid "Polishing took: %.1f seconds"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/main.py:201
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/main.py:202
msgid "Path to a cover image. Changes the cover specified in the ebook. If no cover is present, or the cover is not properly identified, inserts a new cover."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/main.py:204
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/main.py:205
msgid "Path to an OPF file. The metadata in the book is updated from the OPF file."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/main.py:209
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/main.py:210
msgid "Produce more verbose output, useful for debugging."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/main.py:219
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/main.py:220
msgid "You must provide the input file to polish"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/main.py:223
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/main.py:224
msgid "Unknown extra arguments"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/main.py:241
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/main.py:242
msgid "You must specify at least one action to perform"
msgstr ""
@ -4385,7 +4385,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:192
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:256
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:293
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:377
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:385
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:82
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:271
msgid "No books selected"
@ -5393,165 +5393,175 @@ msgid "<h3>Smarten punctuation</h3>%s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:46
msgid "<h3>Updating metadata</h3><p>This will update all metadata and covers in the ebook files to match the current metadata in the calibre library.</p><p>If the ebook file does not have an identifiable cover, a new cover is inserted.</p> <p>Note that most ebook formats are not capable of supporting all the metadata in calibre.</p>"
msgid "<h3>Updating metadata</h3><p>This will update all metadata <i>except</i> the cover in the ebook files to match the current metadata in the calibre library.</p> <p>Note that most ebook formats are not capable of supporting all the metadata in calibre.</p><p>There is a separate option to update the cover.</p>"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:54
msgid "<p>Update the covers in the ebook files to match the current cover in the calibre library.</p><p>If the ebook file does not have an identifiable cover, a new cover is inserted.</p>"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:59
#, python-format
msgid "<h3>Book Jacket</h3>%s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:55
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:60
#, python-format
msgid "<h3>Remove Book Jacket</h3>%s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:61
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:66
msgid "Select actions to perform:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:66
msgid "Subset all embedded fonts"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:71
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:249
msgid "&Subset all embedded fonts"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:67
msgid "Smarten punctuation"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:72
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:240
msgid "Smarten &punctuation"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:68
msgid "Update metadata in book files"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:73
msgid "Update &metadata in the book files"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:69
msgid "Add metadata as a \"book jacket\" page"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:74
msgid "Update the &cover in the book files"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:70
msgid "Remove a previously inserted book jacket"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:75
msgid "Add metadata as a \"book &jacket\" page"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:80
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:76
msgid "&Remove a previously inserted book jacket"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:86
msgid "About"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:99
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:105
msgid "Show &report"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:101
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:107
msgid "Show a report of all the actions performed after polishing is completed"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:107
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:113
msgid "&Save Settings"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:109
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:115
msgid "&Load Settings"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:112
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:118
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/duplicates.py:47
msgid "Select &all"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:114
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:120
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/duplicates.py:49
msgid "Select &none"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:130
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:195
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:136
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:201
msgid "No actions selected"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:131
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:137
msgid "You must select at least one action before saving"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:133
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:139
msgid "Choose name"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:134
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:140
msgid "Choose a name for these settings"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:154
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:160
msgid "Remove saved settings"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:196
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:202
msgid "You must select at least one action, or click Cancel."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:210
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:216
msgid "Queueing books for polishing"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:252
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:260
#, python-format
msgid "Polish %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:253
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:261
#, python-format
msgid "Polish book %(nums)s of %(tot)s (%(title)s)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:257
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:265
#, python-format
msgid "Queueing book %(nums)s of %(tot)s (%(title)s)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:283
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:291
#, python-format
msgid "Ignore remaining %d reports"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:290
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:298
msgid "View full &log"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:313
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:321
#, python-format
msgid "Polishing of %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:319
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:327
#, python-format
msgid "The original file has been saved as %s."
msgid_plural "The original files have been saved as %s."
msgstr[0] ""
msgstr[1] ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:321
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:329
msgid " and "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:324
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:332
msgid "If you polish again, the polishing will run on the originals."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:359
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:367
msgid "P"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:359
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:367
msgid "Polish books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:376
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:389
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:384
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:397
msgid "Cannot polish"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:390
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:398
#, python-format
msgid "Polishing is only supported for books in the %s formats. Convert to one of those formats before polishing."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:415
#: /home/kovid/work/calibre/src/calibre/gui2/actions/polish.py:423
#, python-format
msgid "Start polishing of %d book(s)"
msgstr ""
@ -7317,10 +7327,6 @@ msgstr ""
msgid "Text &justification:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:240
msgid "Smarten &punctuation"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:241
msgid "&Transliterate unicode characters to ASCII"
msgstr ""
@ -7353,10 +7359,6 @@ msgstr ""
msgid "&Disable font size rescaling"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:249
msgid "&Subset all embedded fonts"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:16
msgid "LRF Output"
msgstr ""
@ -8263,10 +8265,10 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:116
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:153
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:187
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:682
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:723
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:746
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:797
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:681
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:722
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:745
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:796
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:348
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:356
#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:83
@ -8279,23 +8281,23 @@ msgid "Undefined"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:130
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:754
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:753
msgid "star(s)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:131
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:755
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:754
msgid "Unrated"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:174
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:784
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:783
#, python-format
msgid "Set '%s' to today"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:176
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:786
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:785
#, python-format
msgid "Clear '%s'"
msgstr ""
@ -8320,35 +8322,35 @@ msgstr ""
msgid "The enumeration \"{0}\" contains an invalid value that will be set to the default"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:637
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:636
msgid "Apply changes"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:830
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:829
msgid "Remove series"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:833
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:832
msgid "Automatically number books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:836
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:835
msgid "Force numbers to start with "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:906
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:905
msgid "The enumeration \"{0}\" contains invalid values that will not appear in the list"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:950
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:949
msgid "Remove all tags"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:970
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:969
msgid "tags to add"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:977
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:976
msgid "tags to remove"
msgstr ""
@ -9343,7 +9345,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:122
#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:160
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:543
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:751
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:753
msgid "No matches found"
msgstr ""
@ -16154,7 +16156,7 @@ msgid "Options to customize the ebook viewer"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config.py:30
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1146
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1148
msgid "Remember last used window size"
msgstr ""
@ -16724,83 +16726,73 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:728
#, python-format
msgid ""
"%(which)s font size [%(sc)s]\n"
"Current magnification: %(mag).1f"
msgid "Current magnification: %(mag).1f"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:731
msgid "Increase"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:734
msgid "Decrease"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:752
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:754
#, python-format
msgid "No matches found for: %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:801
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:803
msgid "Loading flow..."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:879
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:881
#, python-format
msgid "Laying out %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:946
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:948
#, python-format
msgid "Bookmark #%d"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:950
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:952
msgid "Add bookmark"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:951
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:953
msgid "Enter title for bookmark:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:962
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:964
msgid "Manage Bookmarks"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1004
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1006
msgid "Loading ebook..."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1017
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1019
msgid "Could not open ebook"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1018
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1020
msgid "Unknown error"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1133
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1135
msgid "Options to control the ebook viewer"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1140
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1142
msgid "If specified, viewer window will try to come to the front when started."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1143
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1145
msgid "If specified, viewer window will try to open full screen when started."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1148
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1150
msgid "Print javascript alert and console messages to the console"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1150
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1152
msgid "The position at which to open the specified book. The position is a location as displayed in the top left corner of the viewer."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1157
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1159
msgid ""
"%prog [options] file\n"
"\n"
@ -16914,7 +16906,8 @@ msgid "Show"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:958
msgid "Hide"
#, python-format
msgid "Hide %(label)s %(shortcut)s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:995
@ -19363,6 +19356,18 @@ msgstr ""
msgid "pm"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:204
msgid "&Copy"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:205
msgid "Select All"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:206
msgid "Copy &Link location"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:56
msgid "Choose theme (needs restart)"
msgstr ""
@ -19988,3 +19993,11 @@ msgstr ""
#: /home/kovid/work/calibre/resources/default_tweaks.py:512
msgid "This means that you can make changes and press Enter and your changes will\nnot be overwritten by a matching completion. However, if you wish to use the\ncompletions you will now have to press Tab to select one before pressing\nEnter. Which technique you prefer will depend on the state of metadata in\nyour library and your personal editing style."
msgstr ""
#: /home/kovid/work/calibre/resources/default_tweaks.py:519
msgid "Recognize numbers inside text when sorting"
msgstr ""
#: /home/kovid/work/calibre/resources/default_tweaks.py:520
msgid "This means that when sorting on text fields like title the text \"Book 2\"\nwill sort before the text \"Book 100\". If you want this behavior, set\nnumeric_collation = True note that doing so will cause problems with text\nthat starts with numbers and is a little slower."
msgstr ""

View File

@ -110,6 +110,21 @@ icu_Collator_set_strength(icu_Collator *self, PyObject *val, void *closure) {
}
// }}}
// Collator.numeric {{{
static PyObject *
icu_Collator_get_numeric(icu_Collator *self, void *closure) {
UErrorCode status = U_ZERO_ERROR;
return Py_BuildValue("O", (ucol_getAttribute(self->collator, UCOL_NUMERIC_COLLATION, &status) == UCOL_ON) ? Py_True : Py_False);
}
static int
icu_Collator_set_numeric(icu_Collator *self, PyObject *val, void *closure) {
UErrorCode status = U_ZERO_ERROR;
ucol_setAttribute(self->collator, UCOL_NUMERIC_COLLATION, (PyObject_IsTrue(val)) ? UCOL_ON : UCOL_OFF, &status);
return 0;
}
// }}}
// Collator.actual_locale {{{
static PyObject *
icu_Collator_actual_locale(icu_Collator *self, void *closure) {
@ -415,6 +430,10 @@ static PyGetSetDef icu_Collator_getsetters[] = {
(char *)"The strength of this collator.",
NULL},
{(char *)"numeric",
(getter)icu_Collator_get_numeric, (setter)icu_Collator_set_numeric,
(char *)"If True the collator sorts contiguous digits as numbers rather than strings, so 2 will sort before 10.",
NULL},
{NULL} /* Sentinel */
};

View File

@ -12,7 +12,7 @@ from functools import partial
from calibre.constants import plugins
from calibre.utils.config_base import tweaks
_icu = _collator = _primary_collator = _secondary_collator = None
_icu = _collator = _primary_collator = _sort_collator = None
_locale = None
_none = u''
@ -41,6 +41,7 @@ def load_icu():
return _icu
def load_collator():
'The default collator for most locales takes both case and accented letters into account'
global _collator
if _collator is None:
icu = load_icu()
@ -49,18 +50,25 @@ def load_collator():
return _collator
def primary_collator():
'Ignores case differences and accented characters'
global _primary_collator
if _primary_collator is None:
_primary_collator = _collator.clone()
_primary_collator.strength = _icu.UCOL_PRIMARY
return _primary_collator
def secondary_collator():
global _secondary_collator
if _secondary_collator is None:
_secondary_collator = _collator.clone()
_secondary_collator.strength = _icu.UCOL_SECONDARY
return _secondary_collator
def sort_collator():
'Ignores case differences and recognizes numbers in strings'
global _sort_collator
if _sort_collator is None:
_sort_collator = _collator.clone()
_sort_collator.strength = _icu.UCOL_SECONDARY
if tweaks['numeric_collation']:
try:
_sort_collator.numeric = True
except AttributeError:
pass
return _sort_collator
def py_sort_key(obj):
if not obj:
@ -72,15 +80,15 @@ def icu_sort_key(collator, obj):
return _none2
try:
try:
return _secondary_collator.sort_key(obj)
return _sort_collator.sort_key(obj)
except AttributeError:
return secondary_collator().sort_key(obj)
return sort_collator().sort_key(obj)
except TypeError:
if isinstance(obj, unicode):
obj = obj.replace(u'\0', u'')
else:
obj = obj.replace(b'\0', b'')
return _secondary_collator.sort_key(obj)
return _sort_collator.sort_key(obj)
def icu_change_case(upper, locale, obj):
func = _icu.upper if upper else _icu.lower
@ -233,9 +241,9 @@ def collation_order(a):
if _icu_not_ok:
return (ord(a[0]), 1) if a else (0, 0)
try:
return icu_collation_order(_secondary_collator, a)
return icu_collation_order(_sort_collator, a)
except AttributeError:
return icu_collation_order(secondary_collator(), a)
return icu_collation_order(sort_collator(), a)
################################################################################
@ -333,6 +341,7 @@ pêché'''
german = create(german)
c = _icu.Collator('de')
c.numeric = True
gs = list(sorted(german, key=c.sort_key))
if gs != create(german_good):
print 'German sorting failed'
@ -340,6 +349,7 @@ pêché'''
print
french = create(french)
c = _icu.Collator('fr')
c.numeric = True
fs = list(sorted(french, key=c.sort_key))
if fs != create(french_good):
print 'French sorting failed (note that French fails with icu < 4.6)'
@ -388,6 +398,25 @@ pêché'''
print 'startswith() failed'
return
print '\nTesting collation_order()'
for group in [
('Šaa', 'Smith', 'Solženicyn', 'Štepánek'),
('calibre', 'Charon', 'Collins'),
('01', '1'),
('1', '11', '13'),
]:
last = None
for x in group:
val = icu_collation_order(sort_collator(), x)
if val[1] != 1:
prints('collation_order() returned incorrect length for', x)
if last is None:
last = val
else:
if val != last:
prints('collation_order() returned incorrect value for', x)
last = val
# }}}
if __name__ == '__main__':

View File

@ -201,6 +201,9 @@ if False:
_('am')
# NOTE: Post Meridian (i.e. like 10:00 pm)
_('pm')
_('&Copy')
_('Select All')
_('Copy &Link location')
_lcase_map = {}
for k in _extra_lang_codes: