merge from trunk

This commit is contained in:
ldolse 2011-02-06 13:31:47 +08:00
commit 6c495f6a5a
22 changed files with 381 additions and 399 deletions

View File

@ -55,7 +55,7 @@
- title: "Add search to the plugin preferences dialog" - title: "Add search to the plugin preferences dialog"
bug fixes: bug fixes:
- title: "Fix a bug that could cause fiels to be lost when changing metadata on east asian windows installs if the title and/or author is very long." - title: "Fix a bug that could cause files to be lost when changing metadata on east asian windows installs if the title and/or author is very long."
tickets: [8620] tickets: [8620]
- title: "Tag browser: Fix searching with items in a user category not owrking if the main category is hidden" - title: "Tag browser: Fix searching with items in a user category not owrking if the main category is hidden"
@ -88,7 +88,7 @@
- title: "Do not discard the result of a conversion if the user opens the edit metadata dialog while the conversion is running" - title: "Do not discard the result of a conversion if the user opens the edit metadata dialog while the conversion is running"
tickets: [8672] tickets: [8672]
- title: "CHM Input: When the chm file lacks a hhc, lookf for index.html instead" - title: "CHM Input: When the chm file lacks a hhc, look for index.html instead"
tickets: [8688] tickets: [8688]
- title: "EPUB Input: Filter some invalid media types from the spine" - title: "EPUB Input: Filter some invalid media types from the spine"

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

View File

@ -113,8 +113,8 @@ table.cbj_header tr.cbj_series {
/* display:none; */ /* display:none; */
} }
table.cbj_header tr.cbj_pubdate { table.cbj_header tr.cbj_pubdata {
/* Uncomment the next line to remove 'Published' from banner section */ /* Uncomment the next line to remove 'Published (year of publication)' from banner section */
/* display:none; */ /* display:none; */
} }

View File

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
__license__ = 'GPL v3'
__copyright__ = '2011, Attis <attis@attis.one.pl>'
__version__ = 'v. 0.1'
import re
from calibre.web.feeds.recipes import BasicNewsRecipe
class KopalniaWiedzy(BasicNewsRecipe):
title = u'Kopalnia Wiedzy'
publisher = u'Kopalnia Wiedzy'
description = u'Ciekawostki ze świata nauki i techniki'
encoding = 'utf-8'
__author__ = 'Attis'
language = 'pl'
oldest_article = 7
max_articles_per_feed = 100
INDEX = u'http://kopalniawiedzy.pl/'
remove_javascript = True
no_stylesheets = True
remove_tags = [{'name':'p', 'attrs': {'class': 'keywords'} }]
remove_tags_after = dict(attrs={'class':'ad-square'})
keep_only_tags = [dict(name="div", attrs={'id':'articleContent'})]
extra_css = '.topimage {margin-top: 30px}'
preprocess_regexps = [
(re.compile(u'<a .* rel="lightboxText" .*><img (.*)></a>'),
lambda match: '<img class="topimage" ' + match.group(1) + '>' ),
(re.compile(u'<br /><br />'),
lambda match: '<br\/>')
]
feeds = [
(u'Biologia', u'http://kopalniawiedzy.pl/wiadomosci_biologia.rss'),
(u'Medycyna', u'http://kopalniawiedzy.pl/wiadomosci_medycyna.rss'),
(u'Psychologia', u'http://kopalniawiedzy.pl/wiadomosci_psychologia.rss'),
(u'Technologie', u'http://kopalniawiedzy.pl/wiadomosci_technologie.rss'),
(u'Ciekawostki', u'http://kopalniawiedzy.pl/wiadomosci_ciekawostki.rss'),
(u'Artykuły', u'http://kopalniawiedzy.pl/artykuly.rss')
]
def is_link_wanted(self, url, tag):
return tag['class'] == 'next'
def remove_beyond(self, tag, next):
while tag is not None and getattr(tag, 'name', None) != 'body':
after = getattr(tag, next)
while after is not None:
ns = getattr(tag, next)
after.extract()
after = ns
tag = tag.parent
def append_page(self, soup, appendtag, position):
pager = soup.find('a',attrs={'class':'next'})
if pager:
nexturl = self.INDEX + pager['href']
soup2 = self.index_to_soup(nexturl)
texttag = soup2.find('div', attrs={'id':'articleContent'})
tag = texttag.find(attrs={'class':'pages'})
self.remove_beyond(tag, 'nextSibling')
newpos = len(texttag.contents)
self.append_page(soup2,texttag,newpos)
appendtag.insert(position,texttag)
def preprocess_html(self, soup):
self.append_page(soup, soup.body, 3)
for item in soup.findAll('div',attrs={'class':'pages'}):
item.extract()
for item in soup.findAll('p', attrs={'class':'wykop'}):
item.extract()
return soup

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
__license__ = 'GPL v3'
__copyright__ = '2011, Attis <attis@attis.one.pl>'
__version__ = 'v. 0.1'
import re
from calibre.web.feeds.recipes import BasicNewsRecipe
class KorespondentPL(BasicNewsRecipe):
title = u'Korespondent.pl'
publisher = u'Korespondent.pl'
description = u'Centrum wolnorynkowe - serwis ludzi wolnych'
encoding = 'utf-8'
__author__ = 'Attis'
language = 'pl'
oldest_article = 15
max_articles_per_feed = 100
remove_javascript = True
no_stylesheets = True
keep_only_tags = [dict(name='div', attrs={'class':'publicystyka'})]
remove_tags = [{'name': 'meta'}, {'name':'div', 'attrs': {'class': 'zdjecie'} }]
extra_css = '.naglowek {font-size: small}\n .tytul {font-size: x-large; padding-bottom: 10px; padding-top: 30px} \n .external {font-size: small}'
preprocess_regexps = [
(re.compile(u'<a href="index\.php.*>(.*)</a>'),
lambda match: match.group(1) ),
(re.compile(u'<i>'),
lambda match:'<i class="external">' ),
(re.compile(u'<p></p>Więcej'),
lambda match:'Więcej' ),
(re.compile(u'target="_blank"'),
lambda match:'target="_blank" class="external"' ),
(re.compile(u'<p align="center">\nPoczytaj inne teksty w <a href="http://www.korespondent.pl">Serwisie wolnorynkowym Korespondent.pl</a>.*</body>', re.DOTALL|re.IGNORECASE),
lambda match: '</div></body>'),
]
feeds = [(u'Serwis informacyjny', u'http://korespondent.pl/rss.xml')]

View File

@ -149,17 +149,17 @@ class HeuristicProcessor(object):
] ]
ITALICIZE_STYLE_PATS = [ ITALICIZE_STYLE_PATS = [
r'(?msu)(?<=\s)_(?P<words>\S[^_]{0,40}?\S)?_(?=[\s\.,\!\?])', r'(?msu)(?<=[\s>])_(?P<words>[^_]+)?_',
r'(?msu)(?<=\s)/(?P<words>\S[^/]{0,40}?\S)?/(?=[\s\.,\!\?])', r'(?msu)(?<=[\s>])/(?P<words>[^/]+)?/',
r'(?msu)(?<=\s)~~(?P<words>\S[^~]{0,40}?\S)?~~(?=[\s\.,\!\?])', r'(?msu)(?<=[\s>])~~(?P<words>[^~]+)?~~',
r'(?msu)(?<=\s)\*(?P<words>\S[^\*]{0,40}?\S)?\*(?=[\s\.,\!\?])', r'(?msu)(?<=[\s>])\*(?P<words>[^\*]+)?\*',
r'(?msu)(?<=\s)~(?P<words>\S[^~]{0,40}?\S)?~(?=[\s\.,\!\?])', r'(?msu)(?<=[\s>])~(?P<words>[^~]+)?~',
r'(?msu)(?<=\s)_/(?P<words>\S[^/_]{0,40}?\S)?/_(?=[\s\.,\!\?])', r'(?msu)(?<=[\s>])_/(?P<words>[^/_]+)?/_',
r'(?msu)(?<=\s)_\*(?P<words>\S[^\*_]{0,40}?\S)?\*_(?=[\s\.,\!\?])', r'(?msu)(?<=[\s>])_\*(?P<words>[^\*_]+)?\*_',
r'(?msu)(?<=\s)\*/(?P<words>\S[^/\*]{0,40}?\S)?/\*(?=[\s\.,\!\?])', r'(?msu)(?<=[\s>])\*/(?P<words>[^/\*]+)?/\*',
r'(?msu)(?<=\s)_\*/(?P<words>\S[^\*_]{0,40}?\S)?/\*_(?=[\s\.,\!\?])', r'(?msu)(?<=[\s>])_\*/(?P<words>[^\*_]+)?/\*_',
r'(?msu)(?<=\s)/:(?P<words>\S[^:/]{0,40}?\S)?:/(?=[\s\.,\!\?])', r'(?msu)(?<=[\s>])/:(?P<words>[^:/]+)?:/',
r'(?msu)(?<=\s)\|:(?P<words>\S[^:\|]{0,40}?\S)?:\|(?=[\s\.,\!\?])', r'(?msu)(?<=[\s>])\|:(?P<words>[^:\|]+)?:\|',
] ]
for word in ITALICIZE_WORDS: for word in ITALICIZE_WORDS:

View File

@ -109,7 +109,7 @@ def get_rating(rating, rchar, e_rchar):
def render_jacket(mi, output_profile, def render_jacket(mi, output_profile,
alt_title=_('Unknown'), alt_tags=[], alt_comments='', alt_title=_('Unknown'), alt_tags=[], alt_comments='',
alt_publisher=('Unknown publisher')): alt_publisher=('')):
css = P('jacket/stylesheet.css', data=True).decode('utf-8') css = P('jacket/stylesheet.css', data=True).decode('utf-8')
try: try:
@ -127,7 +127,7 @@ def render_jacket(mi, output_profile,
try: try:
publisher = mi.publisher if mi.publisher else alt_publisher publisher = mi.publisher if mi.publisher else alt_publisher
except: except:
publisher = _('Unknown publisher') publisher = ''
try: try:
pubdate = strftime(u'%Y', mi.pubdate.timetuple()) pubdate = strftime(u'%Y', mi.pubdate.timetuple())

View File

@ -226,7 +226,7 @@ class ParseRtf:
try: try:
return_value = process_tokens_obj.process_tokens() return_value = process_tokens_obj.process_tokens()
except InvalidRtfException, msg: except InvalidRtfException, msg:
#Check to see if the file is correctly encoded # Check to see if the file is correctly encoded
encode_obj = default_encoding.DefaultEncoding( encode_obj = default_encoding.DefaultEncoding(
in_file = self.__temp_file, in_file = self.__temp_file,
run_level = self.__run_level, run_level = self.__run_level,
@ -237,14 +237,14 @@ class ParseRtf:
check_encoding_obj = check_encoding.CheckEncoding( check_encoding_obj = check_encoding.CheckEncoding(
bug_handler = RtfInvalidCodeException, bug_handler = RtfInvalidCodeException,
) )
enc = 'cp' + encode_obj.get_codepage() enc = encode_obj.get_codepage()
if enc == 'cp10000': if enc != 'mac_roman':
enc = 'mac_roman' enc = 'cp' + enc
msg = 'Exception in token processing' msg = '%s\nException in token processing' % str(msg)
if check_encoding_obj.check_encoding(self.__file, enc): if check_encoding_obj.check_encoding(self.__file, enc):
file_name = self.__file if isinstance(self.__file, str) \ file_name = self.__file if isinstance(self.__file, str) \
else self.__file.encode('utf-8') else self.__file.encode('utf-8')
msg = 'File %s does not appear to be correctly encoded.\n' % file_name msg +='\nFile %s does not appear to be correctly encoded.\n' % file_name
try: try:
os.remove(self.__temp_file) os.remove(self.__temp_file)
except OSError: except OSError:

View File

@ -210,7 +210,7 @@ class Colors:
hex_num = self.__color_dict.get(num) hex_num = self.__color_dict.get(num)
if hex_num is None: if hex_num is None:
hex_num = '0' hex_num = '0'
if self.__run_level > 5: if self.__run_level > 3:
msg = 'no value in self.__color_dict' \ msg = 'no value in self.__color_dict' \
'for key %s at line %d\n' % (num, self.__line) 'for key %s at line %d\n' % (num, self.__line)
raise self.__bug_handler, msg raise self.__bug_handler, msg

View File

@ -786,21 +786,23 @@ class ProcessTokens:
token = line.replace("\n","") token = line.replace("\n","")
line_count += 1 line_count += 1
if line_count == 1 and token != '\\{': if line_count == 1 and token != '\\{':
msg = 'Invalid RTF: document doesn\'t start with {\n' msg = '\nInvalid RTF: document doesn\'t start with {\n'
raise self.__exception_handler, msg raise self.__exception_handler, msg
elif line_count == 2 and token[0:4] != '\\rtf': elif line_count == 2 and token[0:4] != '\\rtf':
msg = 'Invalid RTF: document doesn\'t start with \\rtf \n' msg = '\nInvalid RTF: document doesn\'t start with \\rtf \n'
raise self.__exception_handler, msg raise self.__exception_handler, msg
the_index = token.find('\\ ') the_index = token.find('\\ ')
if token is not None and the_index > -1: if token is not None and the_index > -1:
msg = 'Invalid RTF: token "\\ " not valid.\n' msg = '\nInvalid RTF: token "\\ " not valid.\nError at line %d'\
% line_count
raise self.__exception_handler, msg raise self.__exception_handler, msg
elif token[:1] == "\\": elif token[:1] == "\\":
try: try:
token.decode('us-ascii') token.decode('us-ascii')
except UnicodeError, msg: except UnicodeError, msg:
msg = 'Invalid RTF: Tokens not ascii encoded.\n%s' % str(msg) msg = '\nInvalid RTF: Tokens not ascii encoded.\n%s\nError at line %d'\
% (str(msg), line_count)
raise self.__exception_handler, msg raise self.__exception_handler, msg
line = self.process_cw(token) line = self.process_cw(token)
if line is not None: if line is not None:
@ -816,7 +818,7 @@ class ProcessTokens:
write_obj.write('tx<nu<__________<%s\n' % field) write_obj.write('tx<nu<__________<%s\n' % field)
if not line_count: if not line_count:
msg = 'Invalid RTF: file appears to be empty.\n' msg = '\nInvalid RTF: file appears to be empty.\n'
raise self.__exception_handler, msg raise self.__exception_handler, msg
copy_obj = copy.Copy(bug_handler = self.__bug_handler) copy_obj = copy.Copy(bug_handler = self.__bug_handler)
@ -827,7 +829,7 @@ class ProcessTokens:
bad_brackets = self.__check_brackets(self.__file) bad_brackets = self.__check_brackets(self.__file)
if bad_brackets: if bad_brackets:
msg = 'Invalid RTF: document does not have matching brackets.\n' msg = '\nInvalid RTF: document does not have matching brackets.\n'
raise self.__exception_handler, msg raise self.__exception_handler, msg
else: else:
return self.__return_code return self.__return_code

View File

@ -117,6 +117,7 @@ class Tokenize:
input_file = self.__replace_spchar.mreplace(input_file) input_file = self.__replace_spchar.mreplace(input_file)
# this is for older RTF # this is for older RTF
input_file = self.__par_exp.sub('\n\\par \n', input_file) input_file = self.__par_exp.sub('\n\\par \n', input_file)
input_file = self.__cwdigit_exp.sub("\g<1>\n\g<2>", input_file)
input_file = self.__ms_hex_exp.sub("\\mshex0\g<1> ", input_file) input_file = self.__ms_hex_exp.sub("\\mshex0\g<1> ", input_file)
input_file = self.__utf_ud.sub("\\{\\uc0 \g<1>\\}", input_file) input_file = self.__utf_ud.sub("\\{\\uc0 \g<1>\\}", input_file)
#remove \n in bin data #remove \n in bin data
@ -139,17 +140,17 @@ class Tokenize:
"\\_": "\\_ ", "\\_": "\\_ ",
"\\:": "\\: ", "\\:": "\\: ",
"\\-": "\\- ", "\\-": "\\- ",
# turn into a generic token to eliminate special #turn into a generic token to eliminate special
# cases and make processing easier #cases and make processing easier
"\\{": "\\ob ", "\\{": "\\ob ",
# turn into a generic token to eliminate special #turn into a generic token to eliminate special
# cases and make processing easier #cases and make processing easier
"\\}": "\\cb ", "\\}": "\\cb ",
# put a backslash in front of to eliminate special cases and #put a backslash in front of to eliminate special cases and
# make processing easier #make processing easier
"{": "\\{", "{": "\\{",
# put a backslash in front of to eliminate special cases and #put a backslash in front of to eliminate special cases and
# make processing easier #make processing easier
"}": "\\}", "}": "\\}",
} }
self.__replace_spchar = MReplace(SIMPLE_RPL) self.__replace_spchar = MReplace(SIMPLE_RPL)
@ -165,21 +166,9 @@ class Tokenize:
#remove \n from endline char #remove \n from endline char
self.__splitexp = re.compile(r"(\\[{}]|\n|\\[^\s\\{}&]+(?:[ \t\r\f\v])?)") self.__splitexp = re.compile(r"(\\[{}]|\n|\\[^\s\\{}&]+(?:[ \t\r\f\v])?)")
#this is for old RTF #this is for old RTF
self.__par_exp = re.compile(r'\\\n+') self.__par_exp = re.compile(r'(\\\n+|\\ )')
#handle cw using a digit as argument and without space as delimiter #handle cw using a digit as argument and without space as delimiter
self.__cwdigit_exp = re.compile(r"(\\[a-zA-Z]+[\-0-9]+)([^0-9 \\]+)") self.__cwdigit_exp = re.compile(r"(\\[a-zA-Z]+[\-0-9]+)([^0-9 \\]+)")
#self.__bin_exp = re.compile(r"\\bin(-?\d{1,8}) {0,1}")
#self.__utf_exp = re.compile(r"^\\u(-?\d{3,6})")
#self.__splitexp = re.compile(r"(\\[\\{}]|{|}|\n|\\[^\s\\{}&]+(?:\s)?)")
#self.__remove_line = re.compile(r'\n+')
##self.num_exp = re.compile(r"(\*|:|[a-zA-Z]+)(.*)")
def __correct_spliting(self, token):
match_obj = re.search(self.__cwdigit_exp, token)
if match_obj is None:
return token
else:
return '%s\n%s' % (match_obj.group(1), match_obj.group(2))
def tokenize(self): def tokenize(self):
"""Main class for handling other methods. Reads the file \ """Main class for handling other methods. Reads the file \
@ -196,8 +185,6 @@ class Tokenize:
tokens = map(self.__unicode_process, tokens) tokens = map(self.__unicode_process, tokens)
#remove empty items created by removing \uc #remove empty items created by removing \uc
tokens = filter(lambda x: len(x) > 0, tokens) tokens = filter(lambda x: len(x) > 0, tokens)
#handles bothersome cases
tokens = map(self.__correct_spliting, tokens)
#write #write
with open(self.__write_to, 'wb') as write_obj: with open(self.__write_to, 'wb') as write_obj:

View File

@ -12,7 +12,7 @@ from calibre.ebooks.chardet import detect
from calibre.ebooks.txt.processor import convert_basic, convert_markdown, \ from calibre.ebooks.txt.processor import convert_basic, convert_markdown, \
separate_paragraphs_single_line, separate_paragraphs_print_formatted, \ separate_paragraphs_single_line, separate_paragraphs_print_formatted, \
preserve_spaces, detect_paragraph_type, detect_formatting_type, \ preserve_spaces, detect_paragraph_type, detect_formatting_type, \
normalize_line_endings, convert_textile normalize_line_endings, convert_textile, remove_indents
from calibre import _ent_pat, xml_entity_to_unicode from calibre import _ent_pat, xml_entity_to_unicode
class TXTInput(InputFormatPlugin): class TXTInput(InputFormatPlugin):
@ -47,6 +47,9 @@ class TXTInput(InputFormatPlugin):
OptionRecommendation(name='preserve_spaces', recommended_value=False, OptionRecommendation(name='preserve_spaces', recommended_value=False,
help=_('Normally extra spaces are condensed into a single space. ' help=_('Normally extra spaces are condensed into a single space. '
'With this option all spaces will be displayed.')), 'With this option all spaces will be displayed.')),
OptionRecommendation(name='txt_in_remove_indents', recommended_value=False,
help=_('Normally extra space at the beginning of lines is retained. '
'With this option they will be removed.')),
OptionRecommendation(name="markdown_disable_toc", recommended_value=False, OptionRecommendation(name="markdown_disable_toc", recommended_value=False,
help=_('Do not insert a Table of Contents into the output text.')), help=_('Do not insert a Table of Contents into the output text.')),
]) ])
@ -77,20 +80,6 @@ class TXTInput(InputFormatPlugin):
# Normalize line endings # Normalize line endings
txt = normalize_line_endings(txt) txt = normalize_line_endings(txt)
# Detect formatting
if options.formatting_type == 'auto':
options.formatting_type = detect_formatting_type(txt)
log.debug('Auto detected formatting as %s' % options.formatting_type)
if options.formatting_type == 'heuristic':
setattr(options, 'enable_heuristics', True)
setattr(options, 'markup_chapter_headings', True)
setattr(options, 'italicize_common_cases', True)
setattr(options, 'fix_indents', True)
setattr(options, 'delete_blank_paragraphs', True)
setattr(options, 'format_scene_breaks', True)
setattr(options, 'dehyphenate', True)
# Determine the paragraph type of the document. # Determine the paragraph type of the document.
if options.paragraph_type == 'auto': if options.paragraph_type == 'auto':
options.paragraph_type = detect_paragraph_type(txt) options.paragraph_type = detect_paragraph_type(txt)
@ -100,15 +89,23 @@ class TXTInput(InputFormatPlugin):
else: else:
log.debug('Auto detected paragraph type as %s' % options.paragraph_type) log.debug('Auto detected paragraph type as %s' % options.paragraph_type)
# Detect formatting
if options.formatting_type == 'auto':
options.formatting_type = detect_formatting_type(txt)
log.debug('Auto detected formatting as %s' % options.formatting_type)
if options.formatting_type == 'heuristic':
setattr(options, 'enable_heuristics', True)
setattr(options, 'unwrap_lines', False)
if options.txt_in_remove_indents:
txt = remove_indents(txt)
# Preserve spaces will replace multiple spaces to a space # Preserve spaces will replace multiple spaces to a space
# followed by the &nbsp; entity. # followed by the &nbsp; entity.
if options.preserve_spaces: if options.preserve_spaces:
txt = preserve_spaces(txt) txt = preserve_spaces(txt)
# Get length for hyphen removal and punctuation unwrap
docanalysis = DocAnalysis('txt', txt)
length = docanalysis.line_length(.5)
# Reformat paragraphs to block formatting based on the detected type. # Reformat paragraphs to block formatting based on the detected type.
# We don't check for block because the processor assumes block. # We don't check for block because the processor assumes block.
# single and print at transformed to block for processing. # single and print at transformed to block for processing.
@ -119,9 +116,17 @@ class TXTInput(InputFormatPlugin):
elif options.paragraph_type == 'unformatted': elif options.paragraph_type == 'unformatted':
from calibre.ebooks.conversion.utils import HeuristicProcessor from calibre.ebooks.conversion.utils import HeuristicProcessor
# unwrap lines based on punctuation # unwrap lines based on punctuation
docanalysis = DocAnalysis('txt', txt)
length = docanalysis.line_length(.5)
preprocessor = HeuristicProcessor(options, log=getattr(self, 'log', None)) preprocessor = HeuristicProcessor(options, log=getattr(self, 'log', None))
txt = preprocessor.punctuation_unwrap(length, txt, 'txt') txt = preprocessor.punctuation_unwrap(length, txt, 'txt')
if getattr(options, 'enable_heuristics', False) and getattr(options, 'dehyphenate', False):
docanalysis = DocAnalysis('txt', txt)
length = docanalysis.line_length(.5)
dehyphenator = Dehyphenator(options.verbose, log=self.log)
txt = dehyphenator(txt,'txt', length)
# Process the text using the appropriate text processor. # Process the text using the appropriate text processor.
html = '' html = ''
if options.formatting_type == 'markdown': if options.formatting_type == 'markdown':
@ -134,14 +139,8 @@ class TXTInput(InputFormatPlugin):
elif options.formatting_type == 'textile': elif options.formatting_type == 'textile':
log.debug('Running text through textile conversion...') log.debug('Running text through textile conversion...')
html = convert_textile(txt) html = convert_textile(txt)
else: else:
log.debug('Running text through basic conversion...') log.debug('Running text through basic conversion...')
if options.formatting_type == 'heuristic':
# Dehyphenate
dehyphenator = Dehyphenator(options.verbose, log=self.log)
txt = dehyphenator(txt,'txt', length)
flow_size = getattr(options, 'flow_size', 0) flow_size = getattr(options, 'flow_size', 0)
html = convert_basic(txt, epub_split_size_kb=flow_size) html = convert_basic(txt, epub_split_size_kb=flow_size)

View File

@ -24,14 +24,14 @@ def clean_txt(txt):
# all line breaks with \n. # all line breaks with \n.
txt = '\n'.join([line.rstrip() for line in txt.splitlines()]) txt = '\n'.join([line.rstrip() for line in txt.splitlines()])
# Replace whitespace at the beginning of the list with &nbsp; # Replace whitespace at the beginning of the line with &nbsp;
txt = re.sub('(?m)(?P<space>[ ]+)', lambda mo: '&nbsp;' * mo.groups('space').count(' '), txt) txt = re.sub('(?m)(?P<space>^[ ]+)(?=.)', lambda mo: '&nbsp;' * mo.groups('space').count(' '), txt)
txt = re.sub('(?m)(?P<space>[\t]+)', lambda mo: '&nbsp;' * 4 * mo.groups('space').count('\t'), txt) txt = re.sub('(?m)(?P<space>^[\t]+)(?=.)', lambda mo: '&nbsp;' * 4 * mo.groups('space').count('\t'), txt)
# Condense redundant spaces # Condense redundant spaces
txt = re.sub('[ ]{2,}', ' ', txt) txt = re.sub('[ ]{2,}', ' ', txt)
# Remove blank lines from the beginning and end of the document. # Remove blank space from the beginning and end of the document.
txt = re.sub('^\s+(?=.)', '', txt) txt = re.sub('^\s+(?=.)', '', txt)
txt = re.sub('(?<=.)\s+$', '', txt) txt = re.sub('(?<=.)\s+$', '', txt)
# Remove excessive line breaks. # Remove excessive line breaks.
@ -107,6 +107,10 @@ def preserve_spaces(txt):
txt = txt.replace('\t', '&nbsp;&nbsp;&nbsp;&nbsp;') txt = txt.replace('\t', '&nbsp;&nbsp;&nbsp;&nbsp;')
return txt return txt
def remove_indents(txt):
txt = re.sub('(?miu)^\s+', '', txt)
return txt
def opf_writer(path, opf_name, manifest, spine, mi): def opf_writer(path, opf_name, manifest, spine, mi):
opf = OPFCreator(path, mi) opf = OPFCreator(path, mi)
opf.create_manifest(manifest) opf.create_manifest(manifest)

View File

@ -55,6 +55,7 @@ class TXTMLizer(object):
self.log.info('Converting XHTML to TXT...') self.log.info('Converting XHTML to TXT...')
self.oeb_book = oeb_book self.oeb_book = oeb_book
self.opts = opts self.opts = opts
self.toc_titles = []
self.toc_ids = [] self.toc_ids = []
self.last_was_heading = False self.last_was_heading = False
@ -94,8 +95,8 @@ class TXTMLizer(object):
if getattr(self.opts, 'inline_toc', None): if getattr(self.opts, 'inline_toc', None):
self.log.debug('Generating table of contents...') self.log.debug('Generating table of contents...')
toc.append(u'%s\n\n' % _(u'Table of Contents:')) toc.append(u'%s\n\n' % _(u'Table of Contents:'))
for item in self.oeb_book.toc: for item in self.toc_titles:
toc.append(u'* %s\n\n' % item.title) toc.append(u'* %s\n\n' % item)
return ''.join(toc) return ''.join(toc)
def create_flat_toc(self, nodes): def create_flat_toc(self, nodes):
@ -103,6 +104,7 @@ class TXTMLizer(object):
Turns a hierarchical list of TOC href's into a flat list. Turns a hierarchical list of TOC href's into a flat list.
''' '''
for item in nodes: for item in nodes:
self.toc_titles.append(item.title)
self.toc_ids.append(item.href) self.toc_ids.append(item.href)
self.create_flat_toc(item.nodes) self.create_flat_toc(item.nodes)

View File

@ -94,6 +94,7 @@ class ShareConnMenu(QMenu): # {{{
I('mail.png'), _('Email to') + ' ' +account) I('mail.png'), _('Email to') + ' ' +account)
self.addAction(ac) self.addAction(ac)
self.email_actions.append(ac) self.email_actions.append(ac)
ac.a_s.connect(sync_menu.action_triggered)
action1.a_s.connect(sync_menu.action_triggered) action1.a_s.connect(sync_menu.action_triggered)
action2.a_s.connect(sync_menu.action_triggered) action2.a_s.connect(sync_menu.action_triggered)
ac = self.addMenu(self.email_to_and_delete_menu) ac = self.addMenu(self.email_to_and_delete_menu)

View File

@ -6,157 +6,38 @@ __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from PyQt4.Qt import QLineEdit, QListView, QAbstractListModel, Qt, QTimer, \ from PyQt4.Qt import QLineEdit, QAbstractListModel, Qt, \
QApplication, QPoint, QItemDelegate, QStyleOptionViewItem, \ QApplication, QCompleter
QStyle, QEvent, pyqtSignal
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
from calibre.utils.icu import sort_key, lower from calibre.utils.icu import sort_key, lower
from calibre.gui2 import NONE from calibre.gui2 import NONE
from calibre.gui2.widgets import EnComboBox from calibre.gui2.widgets import EnComboBox
class CompleterItemDelegate(QItemDelegate): # {{{
''' Renders the current item as thought it were selected '''
def __init__(self, view):
self.view = view
QItemDelegate.__init__(self, view)
def paint(self, p, opt, idx):
opt = QStyleOptionViewItem(opt)
opt.showDecorationSelected = True
if self.view.currentIndex() == idx:
opt.state |= QStyle.State_HasFocus
QItemDelegate.paint(self, p, opt, idx)
# }}}
class CompleteWindow(QListView): # {{{
'''
The completion popup. For keyboard and mouse handling see
:meth:`eventFilter`.
'''
#: This signal is emitted when the user selects one of the listed
#: completions, by mouse or keyboard
completion_selected = pyqtSignal(object)
def __init__(self, widget, model):
self.widget = widget
QListView.__init__(self)
self.setVisible(False)
self.setParent(None, Qt.Popup)
self.setAlternatingRowColors(True)
self.setFocusPolicy(Qt.NoFocus)
self._d = CompleterItemDelegate(self)
self.setItemDelegate(self._d)
self.setModel(model)
self.setFocusProxy(widget)
self.installEventFilter(self)
self.clicked.connect(self.do_selected)
self.entered.connect(self.do_entered)
self.setMouseTracking(True)
def do_entered(self, idx):
if idx.isValid():
self.setCurrentIndex(idx)
def do_selected(self, idx=None):
idx = self.currentIndex() if idx is None else idx
if idx.isValid():
data = unicode(self.model().data(idx, Qt.DisplayRole))
self.completion_selected.emit(data)
self.hide()
def eventFilter(self, o, e):
if o is not self:
return False
if e.type() == e.KeyPress:
key = e.key()
if key in (Qt.Key_Escape, Qt.Key_Backtab) or \
(key == Qt.Key_F4 and (e.modifiers() & Qt.AltModifier)):
self.hide()
return True
elif key in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab):
if key == Qt.Key_Tab and not self.currentIndex().isValid():
if self.model().rowCount() > 0:
self.setCurrentIndex(self.model().index(0))
self.do_selected()
return True
elif key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp,
Qt.Key_PageDown):
return False
# Send key event to associated line edit
self.widget.eat_focus_out = False
try:
self.widget.event(e)
finally:
self.widget.eat_focus_out = True
if not self.widget.hasFocus():
# Line edit lost focus
self.hide()
if e.isAccepted():
# Line edit consumed event
return True
elif e.type() == e.MouseButtonPress:
# Hide popup if user clicks outside it, otherwise
# pass event to popup
if not self.underMouse():
self.hide()
return True
elif e.type() in (e.InputMethod, e.ShortcutOverride):
QApplication.sendEvent(self.widget, e)
return False # Do not filter this event
# }}}
class CompleteModel(QAbstractListModel): class CompleteModel(QAbstractListModel):
def __init__(self, parent=None): def __init__(self, parent=None):
QAbstractListModel.__init__(self, parent) QAbstractListModel.__init__(self, parent)
self.sep = ','
self.space_before_sep = False
self.items = [] self.items = []
self.lowered_items = []
self.matches = []
def set_items(self, items): def set_items(self, items):
items = [unicode(x.strip()) for x in items] items = [unicode(x.strip()) for x in items]
self.items = list(sorted(items, key=lambda x: sort_key(x))) self.items = list(sorted(items, key=lambda x: sort_key(x)))
self.lowered_items = [lower(x) for x in self.items] self.lowered_items = [lower(x) for x in self.items]
self.matches = []
self.reset() self.reset()
def rowCount(self, *args): def rowCount(self, *args):
return len(self.matches) return len(self.items)
def data(self, index, role): def data(self, index, role):
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
r = index.row() r = index.row()
try: try:
return self.matches[r] return self.items[r]
except IndexError: except IndexError:
pass pass
return NONE return NONE
def get_matches(self, prefix):
'''
Return all matches that (case insensitively) start with prefix
'''
prefix = lower(prefix)
ans = []
if prefix:
for i, test in enumerate(self.lowered_items):
if test.startswith(prefix):
ans.append(self.items[i])
return ans
def update_matches(self, matches):
self.matches = matches
self.reset()
class MultiCompleteLineEdit(QLineEdit): class MultiCompleteLineEdit(QLineEdit):
''' '''
@ -170,16 +51,26 @@ class MultiCompleteLineEdit(QLineEdit):
''' '''
def __init__(self, parent=None): def __init__(self, parent=None):
self.eat_focus_out = True
self.max_visible_items = 7
self.current_prefix = None
QLineEdit.__init__(self, parent) QLineEdit.__init__(self, parent)
self.sep = ','
self.space_before_sep = False
self._model = CompleteModel(parent=self) self._model = CompleteModel(parent=self)
self.complete_window = CompleteWindow(self, self._model) self._completer = c = QCompleter(self._model, self)
c.setWidget(self)
c.setCompletionMode(QCompleter.PopupCompletion)
c.setCaseSensitivity(Qt.CaseInsensitive)
c.setModelSorting(QCompleter.CaseInsensitivelySortedModel)
c.setCompletionRole(Qt.DisplayRole)
p = c.popup()
p.setMouseTracking(True)
p.entered.connect(self.item_entered)
c.popup().setAlternatingRowColors(True)
c.activated.connect(self.completion_selected,
type=Qt.QueuedConnection)
self.textEdited.connect(self.text_edited) self.textEdited.connect(self.text_edited)
self.complete_window.completion_selected.connect(self.completion_selected)
self.installEventFilter(self)
# Interface {{{ # Interface {{{
def update_items_cache(self, complete_items): def update_items_cache(self, complete_items):
@ -193,33 +84,23 @@ class MultiCompleteLineEdit(QLineEdit):
# }}} # }}}
def eventFilter(self, o, e): def item_entered(self, idx):
if self.eat_focus_out and o is self and e.type() == QEvent.FocusOut: self._completer.popup().setCurrentIndex(idx)
if self.complete_window.isVisible():
return True # Filter this event since the cw is visible
return QLineEdit.eventFilter(self, o, e)
def hide_completion_window(self):
self.complete_window.hide()
def text_edited(self, *args): def text_edited(self, *args):
self.update_completions() self.update_completions()
self._completer.complete()
def update_completions(self): def update_completions(self):
' Update the list of completions ' ' Update the list of completions '
if not self.complete_window.isVisible() and not self.hasFocus():
return
cpos = self.cursorPosition() cpos = self.cursorPosition()
text = unicode(self.text()) text = unicode(self.text())
prefix = text[:cpos] prefix = text[:cpos]
self.current_prefix = prefix self.current_prefix = prefix
complete_prefix = prefix.lstrip() complete_prefix = prefix.lstrip()
if self.sep: if self.sep:
complete_prefix = prefix = prefix.split(self.sep)[-1].lstrip() complete_prefix = prefix.split(self.sep)[-1].lstrip()
self._completer.setCompletionPrefix(complete_prefix)
matches = self._model.get_matches(complete_prefix)
self.update_complete_window(matches)
def get_completed_text(self, text): def get_completed_text(self, text):
''' '''
@ -246,7 +127,7 @@ class MultiCompleteLineEdit(QLineEdit):
def completion_selected(self, text): def completion_selected(self, text):
prefix_len, ctext = self.get_completed_text(text) prefix_len, ctext = self.get_completed_text(unicode(text))
if self.sep is None: if self.sep is None:
self.setText(ctext) self.setText(ctext)
self.setCursorPosition(len(ctext)) self.setCursorPosition(len(ctext))
@ -255,60 +136,6 @@ class MultiCompleteLineEdit(QLineEdit):
self.setText(ctext) self.setText(ctext)
self.setCursorPosition(cursor_pos - prefix_len + len(text)) self.setCursorPosition(cursor_pos - prefix_len + len(text))
def update_complete_window(self, matches):
self._model.update_matches(matches)
if matches:
self.show_complete_window()
else:
self.complete_window.hide()
def position_complete_window(self):
popup = self.complete_window
screen = QApplication.desktop().availableGeometry(self)
h = (popup.sizeHintForRow(0) * min(self.max_visible_items,
popup.model().rowCount()) + 3) + 3
hsb = popup.horizontalScrollBar()
if hsb and hsb.isVisible():
h += hsb.sizeHint().height()
rh = self.height()
pos = self.mapToGlobal(QPoint(0, self.height() - 2))
w = self.width()
if w > screen.width():
w = screen.width()
if (pos.x() + w) > (screen.x() + screen.width()):
pos.setX(screen.x() + screen.width() - w)
if (pos.x() < screen.x()):
pos.setX(screen.x())
top = pos.y() - rh - screen.top() + 2
bottom = screen.bottom() - pos.y()
h = max(h, popup.minimumHeight())
if h > bottom:
h = min(max(top, bottom), h)
if top > bottom:
pos.setY(pos.y() - h - rh + 2)
popup.setGeometry(pos.x(), pos.y(), w, h)
def show_complete_window(self):
self.position_complete_window()
self.complete_window.show()
def moveEvent(self, ev):
ret = QLineEdit.moveEvent(self, ev)
QTimer.singleShot(0, self.position_complete_window)
return ret
def resizeEvent(self, ev):
ret = QLineEdit.resizeEvent(self, ev)
QTimer.singleShot(0, self.position_complete_window)
return ret
@dynamic_property @dynamic_property
def all_items(self): def all_items(self):
def fget(self): def fget(self):
@ -317,22 +144,6 @@ class MultiCompleteLineEdit(QLineEdit):
self._model.set_items(items) self._model.set_items(items)
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
@dynamic_property
def sep(self):
def fget(self):
return self._model.sep
def fset(self, val):
self._model.sep = val
return property(fget=fget, fset=fset)
@dynamic_property
def space_before_sep(self):
def fget(self):
return self._model.space_before_sep
def fset(self, val):
self._model.space_before_sep = val
return property(fget=fget, fset=fset)
class MultiCompleteComboBox(EnComboBox): class MultiCompleteComboBox(EnComboBox):
def __init__(self, *args): def __init__(self, *args):

View File

@ -16,7 +16,8 @@ class PluginWidget(Widget, Ui_Form):
def __init__(self, parent, get_option, get_help, db=None, book_id=None): def __init__(self, parent, get_option, get_help, db=None, book_id=None):
Widget.__init__(self, parent, Widget.__init__(self, parent,
['paragraph_type', 'formatting_type', 'markdown_disable_toc', 'preserve_spaces']) ['paragraph_type', 'formatting_type', 'markdown_disable_toc',
'preserve_spaces', 'txt_in_remove_indents'])
self.db, self.book_id = db, book_id self.db, self.book_id = db, book_id
for x in get_option('paragraph_type').option.choices: for x in get_option('paragraph_type').option.choices:
self.opt_paragraph_type.addItem(x) self.opt_paragraph_type.addItem(x)

View File

@ -7,57 +7,95 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>518</width> <width>518</width>
<height>300</height> <height>353</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Structure</string>
</property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text"> <property name="text">
<string>Paragraph style:</string> <string>Paragraph style:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QComboBox" name="opt_paragraph_type"/> <widget class="QComboBox" name="opt_paragraph_type">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item> </item>
<item row="5" column="0" colspan="2"> <item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Formatting style:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="opt_formatting_type">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Common</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="opt_preserve_spaces"> <widget class="QCheckBox" name="opt_preserve_spaces">
<property name="text"> <property name="text">
<string>Preserve &amp;spaces</string> <string>Preserve &amp;spaces</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="0" colspan="2"> <item>
<spacer name="verticalSpacer"> <widget class="QCheckBox" name="opt_txt_in_remove_indents">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>213</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="opt_formatting_type"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
<string>Formatting style:</string> <string>Remove indents at the beginning of lines</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" rowspan="2" colspan="2"> </layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
<property name="title"> <property name="title">
<string>Markdown Options</string> <string>Markdown</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
@ -83,6 +121,19 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>213</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View File

@ -11,7 +11,7 @@ from threading import Thread
from Queue import Queue, Empty from Queue import Queue, Empty
from functools import partial from functools import partial
from PyQt4.Qt import QObject, Qt, pyqtSignal, QTimer, QDialog, \ from PyQt4.Qt import QObject, QTimer, QDialog, \
QVBoxLayout, QTextBrowser, QLabel, QGroupBox, QDialogButtonBox QVBoxLayout, QTextBrowser, QLabel, QGroupBox, QDialogButtonBox
from calibre.ebooks.metadata.fetch import search, get_social_metadata from calibre.ebooks.metadata.fetch import search, get_social_metadata
@ -163,27 +163,23 @@ class DownloadMetadata(Thread):
class DoDownload(QObject): class DoDownload(QObject):
idle_process = pyqtSignal()
def __init__(self, parent, title, db, ids, get_covers, set_metadata=True, def __init__(self, parent, title, db, ids, get_covers, set_metadata=True,
get_social_metadata=True): get_social_metadata=True):
QObject.__init__(self, parent) QObject.__init__(self, parent)
self.pd = ProgressDialog(title, min=0, max=0, parent=parent) self.pd = ProgressDialog(title, min=0, max=0, parent=parent)
self.pd.canceled_signal.connect(self.cancel) self.pd.canceled_signal.connect(self.cancel)
self.idle_process.connect(self.do_one, type=Qt.QueuedConnection)
self.downloader = None self.downloader = None
self.create = partial(DownloadMetadata, db, ids, get_covers, self.create = partial(DownloadMetadata, db, ids, get_covers,
set_metadata=set_metadata, set_metadata=set_metadata,
get_social_metadata=get_social_metadata) get_social_metadata=get_social_metadata)
self.timer = QTimer(self)
self.get_covers = get_covers self.get_covers = get_covers
self.timer.timeout.connect(self.do_one, type=Qt.QueuedConnection)
self.db = db self.db = db
self.updated = set([]) self.updated = set([])
self.total = len(ids) self.total = len(ids)
self.keep_going = True
def exec_(self): def exec_(self):
self.timer.start(50) QTimer.singleShot(50, self.do_one)
ret = self.pd.exec_() ret = self.pd.exec_()
if getattr(self.downloader, 'exception', None) is not None and \ if getattr(self.downloader, 'exception', None) is not None and \
ret == self.pd.Accepted: ret == self.pd.Accepted:
@ -194,11 +190,14 @@ class DoDownload(QObject):
return ret return ret
def cancel(self, *args): def cancel(self, *args):
self.timer.stop() self.keep_going = False
self.downloader.keep_going = False self.downloader.keep_going = False
self.pd.reject() self.pd.reject()
def do_one(self): def do_one(self):
try:
if not self.keep_going:
return
if self.downloader is None: if self.downloader is None:
self.downloader = self.create() self.downloader = self.create()
self.downloader.start() self.downloader.start()
@ -210,7 +209,6 @@ class DoDownload(QObject):
except Empty: except Empty:
pass pass
if not self.downloader.is_alive(): if not self.downloader.is_alive():
self.timer.stop()
while True: while True:
try: try:
r = self.downloader.results.get_nowait() r = self.downloader.results.get_nowait()
@ -218,6 +216,11 @@ class DoDownload(QObject):
except Empty: except Empty:
break break
self.pd.accept() self.pd.accept()
return
except:
self.cancel()
raise
QTimer.singleShot(50, self.do_one)
def handle_result(self, r): def handle_result(self, r):
id_, typ, ok, title = r id_, typ, ok, title = r

View File

@ -9,7 +9,7 @@ Logic for setting up conversion jobs
import cPickle, os import cPickle, os
from PyQt4.Qt import QDialog, QProgressDialog, QString, QTimer, SIGNAL from PyQt4.Qt import QDialog, QProgressDialog, QString, QTimer
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.gui2 import warning_dialog, question_dialog from calibre.gui2 import warning_dialog, question_dialog
@ -24,7 +24,8 @@ from calibre.ebooks.conversion.config import GuiRecommendations, \
load_defaults, load_specifics, save_specifics load_defaults, load_specifics, save_specifics
from calibre.gui2.convert import bulk_defaults_for_input_format from calibre.gui2.convert import bulk_defaults_for_input_format
def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format=None): def convert_single_ebook(parent, db, book_ids, auto_conversion=False, # {{{
out_format=None):
changed = False changed = False
jobs = [] jobs = []
bad = [] bad = []
@ -95,7 +96,9 @@ def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format
msg).exec_() msg).exec_()
return jobs, changed, bad return jobs, changed, bad
# }}}
# Bulk convert {{{
def convert_bulk_ebook(parent, queue, db, book_ids, out_format=None, args=[]): def convert_bulk_ebook(parent, queue, db, book_ids, out_format=None, args=[]):
total = len(book_ids) total = len(book_ids)
if total == 0: if total == 0:
@ -125,14 +128,11 @@ class QueueBulk(QProgressDialog):
self.parent = parent self.parent = parent
self.use_saved_single_settings = use_saved_single_settings self.use_saved_single_settings = use_saved_single_settings
self.i, self.bad, self.jobs, self.changed = 0, [], [], False self.i, self.bad, self.jobs, self.changed = 0, [], [], False
self.timer = QTimer(self) QTimer.singleShot(0, self.do_book)
self.connect(self.timer, SIGNAL('timeout()'), self.do_book)
self.timer.start()
self.exec_() self.exec_()
def do_book(self): def do_book(self):
if self.i >= len(self.book_ids): if self.i >= len(self.book_ids):
self.timer.stop()
return self.do_queue() return self.do_queue()
book_id = self.book_ids[self.i] book_id = self.book_ids[self.i]
self.i += 1 self.i += 1
@ -191,6 +191,7 @@ class QueueBulk(QProgressDialog):
self.setValue(self.i) self.setValue(self.i)
except NoSupportedInputFormats: except NoSupportedInputFormats:
self.bad.append(book_id) self.bad.append(book_id)
QTimer.singleShot(0, self.do_book)
def do_queue(self): def do_queue(self):
self.hide() self.hide()
@ -209,7 +210,9 @@ class QueueBulk(QProgressDialog):
self.jobs.reverse() self.jobs.reverse()
self.queue(self.jobs, self.changed, self.bad, *self.args) self.queue(self.jobs, self.changed, self.bad, *self.args)
def fetch_scheduled_recipe(arg): # }}}
def fetch_scheduled_recipe(arg): # {{{
fmt = prefs['output_format'].lower() fmt = prefs['output_format'].lower()
pt = PersistentTemporaryFile(suffix='_recipe_out.%s'%fmt.lower()) pt = PersistentTemporaryFile(suffix='_recipe_out.%s'%fmt.lower())
pt.close() pt.close()
@ -250,7 +253,9 @@ def fetch_scheduled_recipe(arg):
return 'gui_convert', args, _('Fetch news from ')+arg['title'], fmt.upper(), [pt] return 'gui_convert', args, _('Fetch news from ')+arg['title'], fmt.upper(), [pt]
def generate_catalog(parent, dbspec, ids, device_manager, db): # }}}
def generate_catalog(parent, dbspec, ids, device_manager, db): # {{{
from calibre.gui2.dialogs.catalog import Catalog from calibre.gui2.dialogs.catalog import Catalog
# Build the Catalog dialog in gui2.dialogs.catalog # Build the Catalog dialog in gui2.dialogs.catalog
@ -308,8 +313,9 @@ def generate_catalog(parent, dbspec, ids, device_manager, db):
# Which then calls gui2.convert.gui_conversion:gui_catalog() with the args inline # Which then calls gui2.convert.gui_conversion:gui_catalog() with the args inline
return 'gui_catalog', args, _('Generate catalog'), out.name, d.catalog_sync, \ return 'gui_catalog', args, _('Generate catalog'), out.name, d.catalog_sync, \
d.catalog_title d.catalog_title
# }}}
def convert_existing(parent, db, book_ids, output_format): def convert_existing(parent, db, book_ids, output_format): # {{{
already_converted_ids = [] already_converted_ids = []
already_converted_titles = [] already_converted_titles = []
for book_id in book_ids: for book_id in book_ids:
@ -325,3 +331,5 @@ def convert_existing(parent, db, book_ids, output_format):
book_ids = [x for x in book_ids if x not in already_converted_ids] book_ids = [x for x in book_ids if x not in already_converted_ids]
return book_ids return book_ids
# }}}

View File

@ -4442,6 +4442,7 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
# Insert the link to the series or remove <a class="series"> # Insert the link to the series or remove <a class="series">
aTag = body.find('a', attrs={'class':'series_id'}) aTag = body.find('a', attrs={'class':'series_id'})
if aTag:
if book['series']: if book['series']:
if self.opts.generate_series: if self.opts.generate_series:
aTag['href'] = "%s.html#%s_series" % ('BySeries', aTag['href'] = "%s.html#%s_series" % ('BySeries',
@ -4449,39 +4450,31 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
else: else:
aTag.extract() aTag.extract()
# Insert the author link (always) # Insert the author link
aTag = body.find('a', attrs={'class':'author'}) aTag = body.find('a', attrs={'class':'author'})
if self.opts.generate_authors: if self.opts.generate_authors and aTag:
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor",
self.generateAuthorAnchor(book['author'])) self.generateAuthorAnchor(book['author']))
if publisher == ' ': if publisher == ' ':
try:
publisherTag = body.find('td', attrs={'class':'publisher'}) publisherTag = body.find('td', attrs={'class':'publisher'})
if publisherTag:
publisherTag.contents[0].replaceWith('&nbsp;') publisherTag.contents[0].replaceWith('&nbsp;')
except:
pass
if not genres: if not genres:
try:
genresTag = body.find('p',attrs={'class':'genres'}) genresTag = body.find('p',attrs={'class':'genres'})
if genresTag:
genresTag.extract() genresTag.extract()
except:
pass
if not formats: if not formats:
try:
formatsTag = body.find('p',attrs={'class':'formats'}) formatsTag = body.find('p',attrs={'class':'formats'})
if formatsTag:
formatsTag.extract() formatsTag.extract()
except:
pass
if note_content == '': if note_content == '':
try:
tdTag = body.find('td', attrs={'class':'notes'}) tdTag = body.find('td', attrs={'class':'notes'})
if tdTag:
tdTag.contents[0].replaceWith('&nbsp;') tdTag.contents[0].replaceWith('&nbsp;')
except:
pass
emptyTags = body.findAll('td', attrs={'class':'empty'}) emptyTags = body.findAll('td', attrs={'class':'empty'})
for mt in emptyTags: for mt in emptyTags: