mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
Sync to trunk.
This commit is contained in:
commit
ed941e3f8c
@ -6,7 +6,7 @@ class HBR(BasicNewsRecipe):
|
|||||||
title = 'Harvard Business Review Blogs'
|
title = 'Harvard Business Review Blogs'
|
||||||
description = 'To subscribe go to http://hbr.harvardbusiness.org'
|
description = 'To subscribe go to http://hbr.harvardbusiness.org'
|
||||||
needs_subscription = True
|
needs_subscription = True
|
||||||
__author__ = 'Kovid Goyal and Sujata Raman, enhanced by BrianG'
|
__author__ = 'Kovid Goyal, enhanced by BrianG'
|
||||||
language = 'en'
|
language = 'en'
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
|
|
||||||
|
@ -603,10 +603,11 @@ from calibre.devices.eslick.driver import ESLICK, EBK52
|
|||||||
from calibre.devices.nuut2.driver import NUUT2
|
from calibre.devices.nuut2.driver import NUUT2
|
||||||
from calibre.devices.iriver.driver import IRIVER_STORY
|
from calibre.devices.iriver.driver import IRIVER_STORY
|
||||||
from calibre.devices.binatone.driver import README
|
from calibre.devices.binatone.driver import README
|
||||||
from calibre.devices.hanvon.driver import N516, EB511, ALEX, AZBOOKA, THEBOOK
|
from calibre.devices.hanvon.driver import (N516, EB511, ALEX, AZBOOKA, THEBOOK,
|
||||||
|
LIBREAIR)
|
||||||
from calibre.devices.edge.driver import EDGE
|
from calibre.devices.edge.driver import EDGE
|
||||||
from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS, \
|
from calibre.devices.teclast.driver import (TECLAST_K3, NEWSMY, IPAPYRUS,
|
||||||
SOVOS, PICO, SUNSTECH_EB700, ARCHOS7O, STASH, WEXLER
|
SOVOS, PICO, SUNSTECH_EB700, ARCHOS7O, STASH, WEXLER)
|
||||||
from calibre.devices.sne.driver import SNE
|
from calibre.devices.sne.driver import SNE
|
||||||
from calibre.devices.misc import (PALMPRE, AVANT, SWEEX, PDNOVEL,
|
from calibre.devices.misc import (PALMPRE, AVANT, SWEEX, PDNOVEL,
|
||||||
GEMEI, VELOCITYMICRO, PDNOVEL_KOBO, LUMIREAD, ALURATEK_COLOR,
|
GEMEI, VELOCITYMICRO, PDNOVEL_KOBO, LUMIREAD, ALURATEK_COLOR,
|
||||||
@ -716,7 +717,7 @@ plugins += [
|
|||||||
EB600,
|
EB600,
|
||||||
README,
|
README,
|
||||||
N516,
|
N516,
|
||||||
THEBOOK,
|
THEBOOK, LIBREAIR,
|
||||||
EB511,
|
EB511,
|
||||||
ELONEX,
|
ELONEX,
|
||||||
TECLAST_K3,
|
TECLAST_K3,
|
||||||
|
@ -52,6 +52,18 @@ class THEBOOK(N516):
|
|||||||
EBOOK_DIR_MAIN = 'My books'
|
EBOOK_DIR_MAIN = 'My books'
|
||||||
WINDOWS_CARD_A_MEM = '_FILE-STOR_GADGE'
|
WINDOWS_CARD_A_MEM = '_FILE-STOR_GADGE'
|
||||||
|
|
||||||
|
class LIBREAIR(N516):
|
||||||
|
name = 'Libre Air Driver'
|
||||||
|
gui_name = 'Libre Air'
|
||||||
|
description = _('Communicate with the Libre Air reader.')
|
||||||
|
author = 'Kovid Goyal'
|
||||||
|
FORMATS = ['epub', 'mobi', 'prc', 'fb2', 'rtf', 'txt', 'pdf']
|
||||||
|
|
||||||
|
BCD = [0x399]
|
||||||
|
VENDOR_NAME = 'ALURATEK'
|
||||||
|
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '_FILE-STOR_GADGET'
|
||||||
|
EBOOK_DIR_MAIN = 'Books'
|
||||||
|
|
||||||
class ALEX(N516):
|
class ALEX(N516):
|
||||||
|
|
||||||
name = 'Alex driver'
|
name = 'Alex driver'
|
||||||
|
@ -394,6 +394,13 @@ class EPUBOutput(OutputFormatPlugin):
|
|||||||
for tag in XPath('//h:img[@src]')(root):
|
for tag in XPath('//h:img[@src]')(root):
|
||||||
tag.set('src', tag.get('src', '').replace('&', ''))
|
tag.set('src', tag.get('src', '').replace('&', ''))
|
||||||
|
|
||||||
|
# ADE whimpers in fright when it encounters a <td> outside a
|
||||||
|
# <table>
|
||||||
|
in_table = XPath('ancestor::h:table')
|
||||||
|
for tag in XPath('//h:td|//h:tr|//h:th')(root):
|
||||||
|
if not in_table(tag):
|
||||||
|
tag.tag = XHTML('div')
|
||||||
|
|
||||||
special_chars = re.compile(u'[\u200b\u00ad]')
|
special_chars = re.compile(u'[\u200b\u00ad]')
|
||||||
for elem in root.iterdescendants():
|
for elem in root.iterdescendants():
|
||||||
if getattr(elem, 'text', False):
|
if getattr(elem, 'text', False):
|
||||||
@ -413,7 +420,7 @@ class EPUBOutput(OutputFormatPlugin):
|
|||||||
rule.style.removeProperty('margin-left')
|
rule.style.removeProperty('margin-left')
|
||||||
# padding-left breaks rendering in webkit and gecko
|
# padding-left breaks rendering in webkit and gecko
|
||||||
rule.style.removeProperty('padding-left')
|
rule.style.removeProperty('padding-left')
|
||||||
# Change whitespace:pre to pre-line to accommodate readers that
|
# Change whitespace:pre to pre-wrap to accommodate readers that
|
||||||
# cannot scroll horizontally
|
# cannot scroll horizontally
|
||||||
for rule in stylesheet.data.cssRules.rulesOfType(CSSRule.STYLE_RULE):
|
for rule in stylesheet.data.cssRules.rulesOfType(CSSRule.STYLE_RULE):
|
||||||
style = rule.style
|
style = rule.style
|
||||||
|
@ -442,9 +442,12 @@ class MobiMLizer(object):
|
|||||||
if tag in TABLE_TAGS and self.ignore_tables:
|
if tag in TABLE_TAGS and self.ignore_tables:
|
||||||
tag = 'span' if tag == 'td' else 'div'
|
tag = 'span' if tag == 'td' else 'div'
|
||||||
|
|
||||||
# GR: Added 'width', 'border' and 'scope'
|
if tag == 'table':
|
||||||
|
css = style.cssdict()
|
||||||
|
if 'border' in css or 'border-width' in css:
|
||||||
|
elem.set('border', '1')
|
||||||
if tag in TABLE_TAGS:
|
if tag in TABLE_TAGS:
|
||||||
for attr in ('rowspan', 'colspan','width','border','scope'):
|
for attr in ('rowspan', 'colspan', 'width', 'border', 'scope'):
|
||||||
if attr in elem.attrib:
|
if attr in elem.attrib:
|
||||||
istate.attrib[attr] = elem.attrib[attr]
|
istate.attrib[attr] = elem.attrib[attr]
|
||||||
if tag == 'q':
|
if tag == 'q':
|
||||||
|
@ -348,7 +348,6 @@ class MobiReader(object):
|
|||||||
self.processed_html = self.remove_random_bytes(self.processed_html)
|
self.processed_html = self.remove_random_bytes(self.processed_html)
|
||||||
root = soupparser.fromstring(self.processed_html)
|
root = soupparser.fromstring(self.processed_html)
|
||||||
|
|
||||||
|
|
||||||
if root.tag != 'html':
|
if root.tag != 'html':
|
||||||
self.log.warn('File does not have opening <html> tag')
|
self.log.warn('File does not have opening <html> tag')
|
||||||
nroot = html.fromstring('<html><head></head><body></body></html>')
|
nroot = html.fromstring('<html><head></head><body></body></html>')
|
||||||
|
@ -597,7 +597,7 @@ class DeviceMenu(QMenu): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class DeviceSignals(QObject):
|
class DeviceSignals(QObject): # {{{
|
||||||
#: This signal is emitted once, after metadata is downloaded from the
|
#: This signal is emitted once, after metadata is downloaded from the
|
||||||
#: connected device.
|
#: connected device.
|
||||||
#: The sequence: gui.device_manager.is_device_connected will become True,
|
#: The sequence: gui.device_manager.is_device_connected will become True,
|
||||||
@ -614,6 +614,7 @@ class DeviceSignals(QObject):
|
|||||||
device_connection_changed = pyqtSignal(object)
|
device_connection_changed = pyqtSignal(object)
|
||||||
|
|
||||||
device_signals = DeviceSignals()
|
device_signals = DeviceSignals()
|
||||||
|
# }}}
|
||||||
|
|
||||||
class DeviceMixin(object): # {{{
|
class DeviceMixin(object): # {{{
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
import os, socket, time
|
import os, socket, time
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from threading import Thread
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
|
|
||||||
from calibre.utils.smtp import (compose_mail, sendmail, extract_email_address,
|
from calibre.utils.smtp import (compose_mail, sendmail, extract_email_address,
|
||||||
@ -22,9 +23,30 @@ from calibre.library.save_to_disk import get_components
|
|||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
from calibre.gui2.threaded_jobs import ThreadedJob
|
from calibre.gui2.threaded_jobs import ThreadedJob
|
||||||
|
|
||||||
|
class Worker(Thread):
|
||||||
|
|
||||||
|
def __init__(self, func, args):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.daemon = True
|
||||||
|
self.exception = self.tb = None
|
||||||
|
self.func, self.args = func, args
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
#time.sleep(1000)
|
||||||
|
try:
|
||||||
|
self.func(*self.args)
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
self.exception = e
|
||||||
|
self.tb = traceback.format_exc()
|
||||||
|
finally:
|
||||||
|
self.func = self.args = None
|
||||||
|
|
||||||
|
|
||||||
class Sendmail(object):
|
class Sendmail(object):
|
||||||
|
|
||||||
MAX_RETRIES = 1
|
MAX_RETRIES = 1
|
||||||
|
TIMEOUT = 15 * 60 # seconds
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.calculate_rate_limit()
|
self.calculate_rate_limit()
|
||||||
@ -42,22 +64,32 @@ class Sendmail(object):
|
|||||||
abort=None, notifications=None):
|
abort=None, notifications=None):
|
||||||
|
|
||||||
try_count = 0
|
try_count = 0
|
||||||
while try_count <= self.MAX_RETRIES:
|
while True:
|
||||||
if try_count > 0:
|
if try_count > 0:
|
||||||
log('\nRetrying in %d seconds...\n' %
|
log('\nRetrying in %d seconds...\n' %
|
||||||
self.rate_limit)
|
self.rate_limit)
|
||||||
try:
|
worker = Worker(self.sendmail,
|
||||||
self.sendmail(attachment, aname, to, subject, text, log)
|
(attachment, aname, to, subject, text, log))
|
||||||
try_count = self.MAX_RETRIES
|
worker.start()
|
||||||
log('Email successfully sent')
|
start_time = time.time()
|
||||||
except:
|
while worker.is_alive():
|
||||||
|
worker.join(0.2)
|
||||||
if abort.is_set():
|
if abort.is_set():
|
||||||
|
log('Sending aborted by user')
|
||||||
return
|
return
|
||||||
if try_count == self.MAX_RETRIES:
|
if time.time() - start_time > self.TIMEOUT:
|
||||||
raise
|
log('Sending timed out')
|
||||||
log.exception('\nSending failed...\n')
|
raise Exception(
|
||||||
|
'Sending email %r to %r timed out, aborting'% (subject,
|
||||||
|
to))
|
||||||
|
if worker.exception is None:
|
||||||
|
log('Email successfully sent')
|
||||||
|
return
|
||||||
|
log.error('\nSending failed...\n')
|
||||||
|
log.debug(worker.tb)
|
||||||
try_count += 1
|
try_count += 1
|
||||||
|
if try_count > self.MAX_RETRIES:
|
||||||
|
raise worker.exception
|
||||||
|
|
||||||
def sendmail(self, attachment, aname, to, subject, text, log):
|
def sendmail(self, attachment, aname, to, subject, text, log):
|
||||||
while time.time() - self.last_send_time <= self.rate_limit:
|
while time.time() - self.last_send_time <= self.rate_limit:
|
||||||
@ -90,7 +122,7 @@ def send_mails(jobnames, callback, attachments, to_s, subjects,
|
|||||||
attachments, to_s, subjects, texts, attachment_names):
|
attachments, to_s, subjects, texts, attachment_names):
|
||||||
description = _('Email %s to %s') % (name, to)
|
description = _('Email %s to %s') % (name, to)
|
||||||
job = ThreadedJob('email', description, gui_sendmail, (attachment, aname, to,
|
job = ThreadedJob('email', description, gui_sendmail, (attachment, aname, to,
|
||||||
subject, text), {}, callback, killable=False)
|
subject, text), {}, callback)
|
||||||
job_manager.run_threaded_job(job)
|
job_manager.run_threaded_job(job)
|
||||||
|
|
||||||
|
|
||||||
|
@ -457,7 +457,7 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
|||||||
|
|
||||||
def kill_job(self, *args):
|
def kill_job(self, *args):
|
||||||
if question_dialog(self, _('Are you sure?'), _('Do you really want to stop the selected job?')):
|
if question_dialog(self, _('Are you sure?'), _('Do you really want to stop the selected job?')):
|
||||||
for index in self.jobs_view.selectedIndexes():
|
for index in self.jobs_view.selectionModel().selectedRows():
|
||||||
row = index.row()
|
row = index.row()
|
||||||
self.model.kill_job(row, self)
|
self.model.kill_job(row, self)
|
||||||
|
|
||||||
|
@ -1110,6 +1110,8 @@ class DeviceBooksModel(BooksModel): # {{{
|
|||||||
if self.last_search:
|
if self.last_search:
|
||||||
self.searched.emit(True)
|
self.searched.emit(True)
|
||||||
|
|
||||||
|
def research(self, reset=True):
|
||||||
|
self.search(self.last_search, reset)
|
||||||
|
|
||||||
def sort(self, col, order, reset=True):
|
def sort(self, col, order, reset=True):
|
||||||
descending = order != Qt.AscendingOrder
|
descending = order != Qt.AscendingOrder
|
||||||
@ -1171,6 +1173,8 @@ class DeviceBooksModel(BooksModel): # {{{
|
|||||||
self.custom_columns = {}
|
self.custom_columns = {}
|
||||||
self.db = db
|
self.db = db
|
||||||
self.map = list(range(0, len(db)))
|
self.map = list(range(0, len(db)))
|
||||||
|
self.research(reset=False)
|
||||||
|
self.resort()
|
||||||
|
|
||||||
def cover(self, row):
|
def cover(self, row):
|
||||||
item = self.db[self.map[row]]
|
item = self.db[self.map[row]]
|
||||||
@ -1319,8 +1323,6 @@ class DeviceBooksModel(BooksModel): # {{{
|
|||||||
ans = Qt.AlignVCenter | ALIGNMENT_MAP[self.alignment_map.get(cname,
|
ans = Qt.AlignVCenter | ALIGNMENT_MAP[self.alignment_map.get(cname,
|
||||||
'left')]
|
'left')]
|
||||||
return QVariant(ans)
|
return QVariant(ans)
|
||||||
|
|
||||||
|
|
||||||
return NONE
|
return NONE
|
||||||
|
|
||||||
def headerData(self, section, orientation, role):
|
def headerData(self, section, orientation, role):
|
||||||
|
@ -48,7 +48,7 @@ class BooksView(QTableView): # {{{
|
|||||||
files_dropped = pyqtSignal(object)
|
files_dropped = pyqtSignal(object)
|
||||||
add_column_signal = pyqtSignal()
|
add_column_signal = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent, modelcls=BooksModel):
|
def __init__(self, parent, modelcls=BooksModel, use_edit_metadata_dialog=True):
|
||||||
QTableView.__init__(self, parent)
|
QTableView.__init__(self, parent)
|
||||||
|
|
||||||
self.setEditTriggers(self.EditKeyPressed)
|
self.setEditTriggers(self.EditKeyPressed)
|
||||||
@ -60,8 +60,12 @@ class BooksView(QTableView): # {{{
|
|||||||
elif tweaks['doubleclick_on_library_view'] == 'edit_metadata':
|
elif tweaks['doubleclick_on_library_view'] == 'edit_metadata':
|
||||||
# Must not enable single-click to edit, or the field will remain
|
# Must not enable single-click to edit, or the field will remain
|
||||||
# open in edit mode underneath the edit metadata dialog
|
# open in edit mode underneath the edit metadata dialog
|
||||||
self.doubleClicked.connect(
|
if use_edit_metadata_dialog:
|
||||||
partial(parent.iactions['Edit Metadata'].edit_metadata, checked=False))
|
self.doubleClicked.connect(
|
||||||
|
partial(parent.iactions['Edit Metadata'].edit_metadata,
|
||||||
|
checked=False))
|
||||||
|
else:
|
||||||
|
self.setEditTriggers(self.DoubleClicked|self.editTriggers())
|
||||||
|
|
||||||
self.drag_allowed = True
|
self.drag_allowed = True
|
||||||
self.setDragEnabled(True)
|
self.setDragEnabled(True)
|
||||||
@ -792,7 +796,8 @@ class BooksView(QTableView): # {{{
|
|||||||
class DeviceBooksView(BooksView): # {{{
|
class DeviceBooksView(BooksView): # {{{
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
BooksView.__init__(self, parent, DeviceBooksModel)
|
BooksView.__init__(self, parent, DeviceBooksModel,
|
||||||
|
use_edit_metadata_dialog=False)
|
||||||
self.can_add_columns = False
|
self.can_add_columns = False
|
||||||
self.columns_resized = False
|
self.columns_resized = False
|
||||||
self.resize_on_select = False
|
self.resize_on_select = False
|
||||||
|
@ -462,11 +462,11 @@ class EditRules(QWidget): # {{{
|
|||||||
self.l = l = QGridLayout(self)
|
self.l = l = QGridLayout(self)
|
||||||
self.setLayout(l)
|
self.setLayout(l)
|
||||||
|
|
||||||
self.l1 = l1 = QLabel(_(
|
self.l1 = l1 = QLabel('<p>'+_(
|
||||||
'You can control the color of columns in the'
|
'You can control the color of columns in the'
|
||||||
' book list by creating "rules" that tell calibre'
|
' book list by creating "rules" that tell calibre'
|
||||||
' what color to use. Click the Add Rule button below'
|
' what color to use. Click the Add Rule button below'
|
||||||
' to get started. You can change an existing rule by double'
|
' to get started.<p>You can <b>change an existing rule</b> by double'
|
||||||
' clicking it.'))
|
' clicking it.'))
|
||||||
l1.setWordWrap(True)
|
l1.setWordWrap(True)
|
||||||
l.addWidget(l1, 0, 0, 1, 2)
|
l.addWidget(l1, 0, 0, 1, 2)
|
||||||
|
@ -610,7 +610,7 @@ class TagTreeItem(object): # {{{
|
|||||||
self.temporary = temporary
|
self.temporary = temporary
|
||||||
self.tag = Tag(data, category=category_key,
|
self.tag = Tag(data, category=category_key,
|
||||||
is_editable=category_key not in ['news', 'search', 'identifiers'],
|
is_editable=category_key not in ['news', 'search', 'identifiers'],
|
||||||
is_searchable=category_key not in ['news', 'search'])
|
is_searchable=category_key not in ['search'])
|
||||||
|
|
||||||
elif self.type == self.TAG:
|
elif self.type == self.TAG:
|
||||||
self.icon_state_map[0] = QVariant(data.icon)
|
self.icon_state_map[0] = QVariant(data.icon)
|
||||||
@ -1642,7 +1642,13 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
|
|
||||||
for node in self.category_nodes:
|
for node in self.category_nodes:
|
||||||
if node.tag.state:
|
if node.tag.state:
|
||||||
ans.append('%s:%s'%(node.category_key, node_searches[node.tag.state]))
|
if node.category_key == "news":
|
||||||
|
if node_searches[node.tag.state] == 'true':
|
||||||
|
ans.append('tags:=news')
|
||||||
|
else:
|
||||||
|
ans.append('( not tags:=news )')
|
||||||
|
else:
|
||||||
|
ans.append('%s:%s'%(node.category_key, node_searches[node.tag.state]))
|
||||||
|
|
||||||
key = node.category_key
|
key = node.category_key
|
||||||
for tag_item in node.child_tags():
|
for tag_item in node.child_tags():
|
||||||
|
@ -26,7 +26,7 @@ from calibre.library.custom_columns import CustomColumns
|
|||||||
from calibre.library.sqlite import connect, IntegrityError
|
from calibre.library.sqlite import connect, IntegrityError
|
||||||
from calibre.library.prefs import DBPrefs
|
from calibre.library.prefs import DBPrefs
|
||||||
from calibre.ebooks.metadata.book.base import Metadata
|
from calibre.ebooks.metadata.book.base import Metadata
|
||||||
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
|
from calibre.constants import preferred_encoding, iswindows, filesystem_encoding
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.customize.ui import run_plugins_on_import
|
from calibre.customize.ui import run_plugins_on_import
|
||||||
from calibre import isbytestring
|
from calibre import isbytestring
|
||||||
@ -188,8 +188,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
apply_default_prefs = not os.path.exists(self.dbpath)
|
apply_default_prefs = not os.path.exists(self.dbpath)
|
||||||
self.connect()
|
self.connect()
|
||||||
|
|
||||||
self.is_case_sensitive = not iswindows and not isosx and \
|
self.is_case_sensitive = (not iswindows and
|
||||||
not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB'))
|
not os.path.exists(self.dbpath.replace('metadata.db',
|
||||||
|
'MeTAdAtA.dB')))
|
||||||
SchemaUpgrade.__init__(self)
|
SchemaUpgrade.__init__(self)
|
||||||
# Guarantee that the library_id is set
|
# Guarantee that the library_id is set
|
||||||
self.library_id
|
self.library_id
|
||||||
@ -606,7 +607,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
f = self.format(id, format, index_is_id=True, as_file=True)
|
f = self.format(id, format, index_is_id=True, as_file=True)
|
||||||
if f is None:
|
if f is None:
|
||||||
continue
|
continue
|
||||||
with tempfile.SpooledTemporaryFile(max_size=100*(1024**2)) as stream:
|
with tempfile.SpooledTemporaryFile(max_size=30*(1024**2)) as stream:
|
||||||
with f:
|
with f:
|
||||||
shutil.copyfileobj(f, stream)
|
shutil.copyfileobj(f, stream)
|
||||||
stream.seek(0)
|
stream.seek(0)
|
||||||
|
@ -189,7 +189,7 @@ Extra CSS
|
|||||||
|
|
||||||
This option allows you to specify arbitrary CSS that will be applied to all HTML files in the
|
This option allows you to specify arbitrary CSS that will be applied to all HTML files in the
|
||||||
input. This CSS is applied with very high priority and so should override most CSS present in
|
input. This CSS is applied with very high priority and so should override most CSS present in
|
||||||
the input document itself. You can use this setting to fine tune the presentation/layout of your
|
the **input document** itself. You can use this setting to fine tune the presentation/layout of your
|
||||||
document. For example, if you want all paragraphs of class `endnote` to be right aligned, just
|
document. For example, if you want all paragraphs of class `endnote` to be right aligned, just
|
||||||
add::
|
add::
|
||||||
|
|
||||||
@ -200,7 +200,8 @@ or if you want to change the indentation of all paragraphs::
|
|||||||
p { text-indent: 5mm; }
|
p { text-indent: 5mm; }
|
||||||
|
|
||||||
:guilabel:`Extra CSS` is a very powerful option, but you do need an understanding of how CSS works
|
:guilabel:`Extra CSS` is a very powerful option, but you do need an understanding of how CSS works
|
||||||
to use it to its full potential.
|
to use it to its full potential. You can use the debug pipeline option described above to see what
|
||||||
|
CSS is present in your input document.
|
||||||
|
|
||||||
Miscellaneous
|
Miscellaneous
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
@ -417,7 +417,7 @@ You might find the following tips useful.
|
|||||||
|
|
||||||
* Create a custom composite column to test templates. Once you have the column, you can change its template simply by double-clicking on the column. Hide the column when you are not testing.
|
* Create a custom composite column to test templates. Once you have the column, you can change its template simply by double-clicking on the column. Hide the column when you are not testing.
|
||||||
* Templates can use other templates by referencing a composite custom column.
|
* Templates can use other templates by referencing a composite custom column.
|
||||||
* In a plugboard, you can set a field to empty (or whatever is equivalent to empty) by using the special template ``{null}``. This template will always evaluate to an empty string.
|
* In a plugboard, you can set a field to empty (or whatever is equivalent to empty) by using the special template ``{}``. This template will always evaluate to an empty string.
|
||||||
* The technique described above to show numbers even if they have a zero value works with the standard field series_index.
|
* The technique described above to show numbers even if they have a zero value works with the standard field series_index.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
@ -317,6 +317,9 @@ def feed_from_xml(raw_xml, title=None, oldest_article=7,
|
|||||||
max_articles_per_feed=100,
|
max_articles_per_feed=100,
|
||||||
get_article_url=lambda item: item.get('link', None),
|
get_article_url=lambda item: item.get('link', None),
|
||||||
log=default_log):
|
log=default_log):
|
||||||
|
# Handle unclosed escaped entities. They trip up feedparser and HBR for one
|
||||||
|
# generates them
|
||||||
|
raw_xml = re.sub(r'(&#\d+)([^0-9;])', r'\1;\2', raw_xml)
|
||||||
feed = parse(raw_xml)
|
feed = parse(raw_xml)
|
||||||
pfeed = Feed(get_article_url=get_article_url, log=log)
|
pfeed = Feed(get_article_url=get_article_url, log=log)
|
||||||
pfeed.populate_from_feed(feed, title=title,
|
pfeed.populate_from_feed(feed, title=title,
|
||||||
|
@ -13,8 +13,8 @@ from functools import partial
|
|||||||
from contextlib import nested, closing
|
from contextlib import nested, closing
|
||||||
|
|
||||||
|
|
||||||
from calibre import browser, __appname__, iswindows, \
|
from calibre import (browser, __appname__, iswindows,
|
||||||
strftime, preferred_encoding, as_unicode
|
strftime, preferred_encoding, as_unicode)
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag
|
||||||
from calibre.ebooks.metadata.opf2 import OPFCreator
|
from calibre.ebooks.metadata.opf2 import OPFCreator
|
||||||
from calibre import entity_to_unicode
|
from calibre import entity_to_unicode
|
||||||
|
@ -101,6 +101,7 @@ def get_custom_recipe_collection(*args):
|
|||||||
if recipe_class is not None:
|
if recipe_class is not None:
|
||||||
rmap['custom:%s'%id_] = recipe_class
|
rmap['custom:%s'%id_] = recipe_class
|
||||||
except:
|
except:
|
||||||
|
print 'Failed to load recipe from: %r'%fname
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
continue
|
continue
|
||||||
|
Loading…
x
Reference in New Issue
Block a user