GwR patches for 'change_justification'

This commit is contained in:
GRiker 2010-04-23 05:23:22 -06:00
commit a8db54b6fa
29 changed files with 361 additions and 73 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -119,5 +119,7 @@ class Guardian(BasicNewsRecipe):
raise NotImplementedError
def postprocess_html(self,soup,first):
return soup.findAll('html')[0]

View File

@ -0,0 +1,50 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
'''
kurier.at
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Kurier(BasicNewsRecipe):
title = 'Kurier'
__author__ = 'Darko Miletic'
description = 'News from Austria'
publisher = 'KURIER'
category = 'news, politics, Austria'
oldest_article = 2
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'cp1252'
use_embedded_content = False
language = 'de_AT'
remove_empty_feeds = True
publication_type = 'newspaper'
extra_css = ' body{font-family: Verdana,Helvetica,sans-serif } img{margin-bottom: 0.4em} .bild_us{font-size: x-small} '
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
remove_tags = [dict(attrs={'class':['contenttabs','drucken','versenden','leserbrief','kommentieren','addthis_button']})]
keep_only_tags = [dict(attrs={'id':'content'})]
remove_tags_after = dict(attrs={'id':'author'})
remove_attributes = ['width','height']
feeds = [
(u'Nachrichten', u'http://kurier.at/rss/nachrichten_nachrichten_rss.xml' )
,(u'Techno' , u'http://kurier.at/rss/techno_techno_rss.xml' )
,(u'Wirtschaft' , u'http://kurier.at/rss/wirtschaft_wirtschaft_rss.xml' )
,(u'Kultur' , u'http://kurier.at/rss/kultur_kultur_rss.xml' )
,(u'Freizeit' , u'http://kurier.at/rss/freizeit_freizeit_rss.xml' )
,(u'Wetter' , u'http://kurier.at/rss/oewetter_rss.xml' )
,(u'Verkehr' , u'http://kurier.at/rss/verkehr_rss.xml' )
]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return self.adeify_images(soup)

View File

@ -22,21 +22,36 @@ class LaRepublica(BasicNewsRecipe):
language = 'it'
timefmt = '[%a, %d %b, %Y]'
oldest_article = 1
oldest_article = 5
max_articles_per_feed = 100
use_embedded_content = False
recursion = 10
remove_javascript = True
def get_article_url(self, article):
link = article.get('id', article.get('guid', None))
if link is None:
return article
return link
keep_only_tags = [dict(name='div', attrs={'class':'articolo'}),
dict(name='div', attrs={'class':'body-text'}),
dict(name='div', attrs={'class':'page-content'}),
dict(name='div', attrs={'id':'contA'})
]
keep_only_tags = [dict(name='div', attrs={'class':'articolo'})]
remove_tags = [
dict(name=['object','link']),
dict(name='span',attrs={'class':'linkindice'}),
dict(name='div',attrs={'class':'bottom-mobile'}),
dict(name='div',attrs={'id':['rssdiv','blocco']})
dict(name='div', attrs={'class':'bottom-mobile'}),
dict(name='div', attrs={'id':['rssdiv','blocco']}),
dict(name='div', attrs={'class':'utility'}),
dict(name='div', attrs={'class':'generalbox'})
]
remove_tags_after = [
dict(name='div',attrs={'id':'ugc_linkUpload'})
]
feeds = [
(u'Repubblica Rilievo', u'http://www.repubblica.it/rss/homepage/rss2.0.xml'),
@ -48,8 +63,12 @@ class LaRepublica(BasicNewsRecipe):
(u'Repubblica Tecnologia', u'http://www.repubblica.it/rss/tecnologia/rss2.0.xml'),
(u'Repubblica Scuola e Universita', u'http://www.repubblica.it/rss/scuola_e_universita/rss2.0.xml'),
(u'Repubblica Ambiente', u'http://www.repubblica.it/rss/ambiente/rss2.0.xml'),
(u'Repubblica Cultura', u'http://www.repubblica.it/rss/spettacoli_e_cultura/rss2.0.xml'),
(u'Repubblica Persone', u'http://www.repubblica.it/rss/persone/rss2.0.xml'),
(u'Repubblica Sport', u'http://www.repubblica.it/rss/sport/rss2.0.xml'),
(u'Repubblica Calcio', u'http://www.repubblica.it/rss/sport/calcio/rss2.0.xml')
]
(u'Repubblica Cultura', u'http://www.repubblica.it/rss/spettacoli_e_cultura/rss2.0.xml'),
(u'Repubblica Persone', u'http://www.repubblica.it/rss/persone/rss2.0.xml'),
(u'Repubblica Sport', u'http://www.repubblica.it/rss/sport/rss2.0.xml'),
(u'Repubblica Calcio', u'http://www.repubblica.it/rss/sport/calcio/rss2.0.xml'),
(u'Repubblica Motori', u'http://www.repubblica.it/rss/motori/rss2.0.xml'),
(u'Repubblica Roma', u'http://roma.repubblica.it/rss/rss2.0.xml'),
(u'Repubblica Torino', u'http://torino.repubblica.it/rss/rss2.0.xml')
]

View File

@ -0,0 +1,18 @@
from calibre.web.feeds.news import BasicNewsRecipe
class nprmusic(BasicNewsRecipe):
title = 'NPR Music Blogs'
__author__ = 'cix3'
timefmt = ' [%b %d, %Y]'
language = 'en'
oldest_article = 30
max_articles_per_feed = 100
no_stylesheets = True
remove_tags = [dict(name='div', attrs={'id':['logo', 'comments', 'related_objects', 'inset module', 'footer', 'strip_control', 'header', 'navigation']}), dict(name='hr'), dict(name='img')]
feeds = [
('A Blog Supreme', 'http://www.npr.org/blogs/ablogsupreme/index.xml'),
('All Songs Considered', 'http://www.npr.org/blogs/allsongs/index.xml'),
('Monitor Mix', 'http://www.npr.org/blogs/monitormix/index.xml')]

View File

@ -377,8 +377,9 @@ class USAToday(BasicNewsRecipe):
if byline:
byline['class'] = 'byline'
# Replace comma with middot
byline.contents[0].replaceWith(re.sub(","," &middot;", byline.renderContents()))
return byline.renderContents()
byline.contents[0].replaceWith(re.sub(u",", u" &middot;",
byline.renderContents(encoding=None)))
return byline.renderContents(encoding=None)
else :
paras = soup.findAll(text=True)
for para in paras:

View File

@ -0,0 +1,33 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
'''
www.virtualshackles.com
'''
from calibre.web.feeds.recipes import BasicNewsRecipe
class Virtualshackles(BasicNewsRecipe):
title = 'Virtual Shackles'
__author__ = 'Darko Miletic'
description = "The adventures of Orion and Jack, making games they'd never play for people they don't like."
category = 'virtual shackles, virtualshackles, games, webcomic, comic, video game, orion, jack'
oldest_article = 10
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = True
encoding = 'cp1252'
publisher = 'Virtual Shackles'
language = 'en'
publication_type = 'comic'
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
}
feeds = [(u'Virtual Shackles', u'http://feeds2.feedburner.com/virtualshackles' )]
def preprocess_html(self, soup):
return self.adeify_images(soup)

View File

@ -399,38 +399,49 @@ def my_unichr(num):
except ValueError:
return u'?'
def entity_to_unicode(match, exceptions=[], encoding='cp1252'):
def entity_to_unicode(match, exceptions=[], encoding='cp1252',
result_exceptions={}):
'''
@param match: A match object such that '&'+match.group(1)';' is the entity.
@param exceptions: A list of entities to not convert (Each entry is the name of the entity, for e.g. 'apos' or '#1234'
@param encoding: The encoding to use to decode numeric entities between 128 and 256.
:param match: A match object such that '&'+match.group(1)';' is the entity.
:param exceptions: A list of entities to not convert (Each entry is the name of the entity, for e.g. 'apos' or '#1234'
:param encoding: The encoding to use to decode numeric entities between 128 and 256.
If None, the Unicode UCS encoding is used. A common encoding is cp1252.
:param result_exceptions: A mapping of characters to entities. If the result
is in result_exceptions, result_exception[result] is returned instead.
Convenient way to specify exception for things like < or > that can be
specified by various actual entities.
'''
def check(ch):
return result_exceptions.get(ch, ch)
ent = match.group(1)
if ent in exceptions:
return '&'+ent+';'
if ent == 'apos':
return "'"
return check("'")
if ent == 'hellips':
ent = 'hellip'
if ent.startswith(u'#x'):
if ent.lower().startswith(u'#x'):
num = int(ent[2:], 16)
if encoding is None or num > 255:
return my_unichr(num)
return chr(num).decode(encoding)
return check(my_unichr(num))
return check(chr(num).decode(encoding))
if ent.startswith(u'#'):
try:
num = int(ent[1:])
except ValueError:
return '&'+ent+';'
if encoding is None or num > 255:
return my_unichr(num)
return check(my_unichr(num))
try:
return chr(num).decode(encoding)
return check(chr(num).decode(encoding))
except UnicodeDecodeError:
return my_unichr(num)
return check(my_unichr(num))
try:
return my_unichr(name2codepoint[ent])
return check(my_unichr(name2codepoint[ent]))
except KeyError:
return '&'+ent+';'

View File

@ -444,7 +444,7 @@ from calibre.devices.eslick.driver import ESLICK
from calibre.devices.nuut2.driver import NUUT2
from calibre.devices.iriver.driver import IRIVER_STORY
from calibre.devices.binatone.driver import README
from calibre.devices.hanvon.driver import N516, EB511
from calibre.devices.hanvon.driver import N516, EB511, ALEX
from calibre.devices.edge.driver import EDGE
from calibre.devices.teclast.driver import TECLAST_K3
from calibre.devices.sne.driver import SNE
@ -526,7 +526,8 @@ plugins += [
ELONEX,
TECLAST_K3,
EDGE,
SNE
SNE,
ALEX
]
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
x.__name__.endswith('MetadataReader')]

View File

@ -34,6 +34,22 @@ class N516(USBMS):
EBOOK_DIR_MAIN = 'e_book'
SUPPORTS_SUB_DIRS = True
class ALEX(N516):
name = 'Alex driver'
gui_name = 'SpringDesign Alex'
description = _('Communicate with the SpringDesign Alex eBook reader.')
author = 'Kovid Goyal'
FORMATS = ['epub', 'pdf']
VENDOR_NAME = 'ALEX'
WINDOWS_MAIN_MEM = 'READER'
MAIN_MEMORY_VOLUME_LABEL = 'Alex Internal Memory'
EBOOK_DIR_MAIN = 'eBooks'
SUPPORTS_SUB_DIRS = True
class EB511(USBMS):
name = 'Elonex EB 511 driver'
gui_name = 'EB 511'

View File

@ -322,7 +322,7 @@ class ComicInput(InputFormatPlugin):
('margin_bottom', 0, OptionRecommendation.HIGH),
('insert_blank_line', False, OptionRecommendation.HIGH),
('remove_paragraph_spacing', False, OptionRecommendation.HIGH),
('dont_justify', True, OptionRecommendation.HIGH),
('change_justification', 'left', OptionRecommendation.HIGH),
('dont_split_on_pagebreaks', True, OptionRecommendation.HIGH),
('chapter', None, OptionRecommendation.HIGH),
('page_breaks_brefore', None, OptionRecommendation.HIGH),

View File

@ -124,7 +124,7 @@ def add_pipeline_options(parser, plumber):
'linearize_tables',
'extra_css',
'margin_top', 'margin_left', 'margin_right',
'margin_bottom', 'dont_justify',
'margin_bottom', 'change_justification',
'insert_blank_line', 'remove_paragraph_spacing','remove_paragraph_spacing_indent_size',
'asciiize', 'remove_header', 'header_regex',
'remove_footer', 'footer_regex',

View File

@ -299,12 +299,13 @@ OptionRecommendation(name='margin_right',
help=_('Set the right margin in pts. Default is %default. '
'Note: 72 pts equals 1 inch')),
OptionRecommendation(name='dont_justify',
recommended_value=False, level=OptionRecommendation.LOW,
help=_('Do not force text to be justified in output. Whether text '
'is actually displayed justified or not depends on whether '
'the ebook format and reading device support justification.')
),
OptionRecommendation(name='change_justification',
recommended_value='original', level=OptionRecommendation.LOW,
choices=['left','justify','original'],
help=_('Specify optional justification override. A value of '
'"left" or "justify" overrides default justification.'
'A value of '
'"original" uses existing alignment.')),
OptionRecommendation(name='remove_paragraph_spacing',
recommended_value=False, level=OptionRecommendation.LOW,

View File

@ -130,7 +130,7 @@ class LRFOutput(OutputFormatPlugin):
])
recommendations = set([
('dont_justify', True, OptionRecommendation.HIGH),
('change_justification', 'original', OptionRecommendation.HIGH),
])
def convert_images(self, pages, opts, wide):

View File

@ -303,7 +303,12 @@ class MobiReader(object):
for pat in ENCODING_PATS:
self.processed_html = pat.sub('', self.processed_html)
e2u = functools.partial(entity_to_unicode,
exceptions=['lt', 'gt', 'amp', 'apos', 'quot', '#60', '#62'])
result_exceptions={
'<' : u'&lt;',
'>' : u'&gt;',
'&' : u'&amp;',
'"' : u'&quot;',
"'" : u'&apos;'})
self.processed_html = re.sub(r'&(\S+?);', e2u,
self.processed_html)
self.extract_images(processed_records, output_dir)
@ -619,6 +624,7 @@ class MobiReader(object):
opf.cover = None
cover = opf.cover
cover_copied = None
if cover is not None:
cover = cover.replace('/', os.sep)
if os.path.exists(cover):
@ -626,13 +632,19 @@ class MobiReader(object):
if os.path.exists(ncover):
os.remove(ncover)
shutil.copyfile(cover, ncover)
opf.cover = ncover.replace(os.sep, '/')
cover_copied = os.path.abspath(ncover)
opf.cover = ncover.replace(os.sep, '/')
manifest = [(htmlfile, 'application/xhtml+xml'),
(os.path.abspath('styles.css'), 'text/css')]
bp = os.path.dirname(htmlfile)
added = set([])
for i in getattr(self, 'image_names', []):
manifest.append((os.path.join(bp, 'images/', i), 'image/jpeg'))
path = os.path.join(bp, 'images', i)
added.add(path)
manifest.append((path, 'image/jpeg'))
if cover_copied is not None:
manifest.append((cover_copied, 'image/jpeg'))
opf.create_manifest(manifest)
opf.create_spine([os.path.basename(htmlfile)])

View File

@ -212,11 +212,12 @@ class EbookIterator(object):
cover = self.opf.cover
if self.ebook_ext in ('lit', 'mobi', 'prc', 'opf') and cover:
cfile = os.path.join(os.path.dirname(self.spine[0]),
'calibre_iterator_cover.html')
chtml = (TITLEPAGE%cover).encode('utf-8')
cfile = os.path.join(self.base, 'calibre_iterator_cover.html')
chtml = (TITLEPAGE%os.path.relpath(cover, self.base).replace(os.sep,
'/')).encode('utf-8')
open(cfile, 'wb').write(chtml)
self.spine[0:0] = [SpineItem(cfile)]
self.spine[0:0] = [SpineItem(cfile,
mime_type='application/xhtml+xml')]
self.delete_on_exit.append(cfile)
if self.opf.path_to_html_toc is not None and \

View File

@ -318,8 +318,8 @@ class Stylizer(object):
if text == 'inherit':
style['text-align'] = 'inherit'
else:
if text in ('left', 'justify'):
val = 'left' if self.opts.dont_justify else 'justify'
if text in ('left', 'justify') and self.opts.change_justification in ('left', 'justify'):
val = self.opts.change_justification
style['text-align'] = val
else:
style['text-align'] = text

View File

@ -138,8 +138,8 @@ class CSSFlattener(object):
float(self.context.margin_left))
bs.append('margin-right : %fpt'%\
float(self.context.margin_right))
bs.append('text-align: '+ \
('left' if self.context.dont_justify else 'justify'))
if self.context.change_justification != 'original':
bs.append('text-align: '+ self.context.change_justification)
body.set('style', '; '.join(bs))
stylizer = Stylizer(html, item.href, self.oeb, self.context, profile,
user_css=self.context.extra_css,

View File

@ -19,7 +19,7 @@ class LookAndFeelWidget(Widget, Ui_Form):
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
Widget.__init__(self, parent, 'look_and_feel',
['dont_justify', 'extra_css', 'base_font_size',
['change_justification', 'extra_css', 'base_font_size',
'font_size_mapping', 'line_height',
'linearize_tables',
'disable_font_rescaling', 'insert_blank_line',

View File

@ -84,7 +84,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<iconset>
<normaloff>:/images/wizard.svg</normaloff>:/images/wizard.svg</iconset>
</property>
<property name="iconSize">
@ -181,21 +181,7 @@
</item>
</layout>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="opt_insert_blank_line">
<property name="text">
<string>Insert &amp;blank line</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="opt_dont_justify">
<property name="text">
<string>No text &amp;justification</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QCheckBox" name="opt_linearize_tables">
<property name="text">
<string>&amp;Linearize tables</string>
@ -221,6 +207,42 @@
</layout>
</widget>
</item>
<item row="8" column="0">
<widget class="QCheckBox" name="opt_insert_blank_line">
<property name="text">
<string>Insert &amp;blank line</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Text justification:</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QComboBox" name="opt_change_justification">
<property name="currentIndex">
<number>2</number>
</property>
<item>
<property name="text">
<string>justify</string>
</property>
</item>
<item>
<property name="text">
<string>left</string>
</property>
</item>
<item>
<property name="text">
<string>original</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
<resources>

View File

@ -29,6 +29,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.recipe_model.do_refresh()
self.search = SearchBox2(self)
self.search.setMinimumContentsLength(25)
self.search.initialize('scheduler_search_history')
self.recipe_box.layout().insertWidget(0, self.search)
self.connect(self.search, SIGNAL('search(PyQt_PyObject,PyQt_PyObject)'),

View File

@ -7,9 +7,9 @@ from math import cos, sin, pi
from contextlib import closing
from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
QPen, QStyle, QPainter, \
QImage, QApplication, QMenu, \
QPainterPath, QLinearGradient, QBrush, \
QPen, QStyle, QPainter, QStyleOptionViewItemV4, \
QImage, QMenu, \
QStyledItemDelegate, QCompleter
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, pyqtSignal, \
SIGNAL, QObject, QSize, QModelIndex, QDate
@ -28,14 +28,15 @@ from calibre.ebooks.metadata import string_to_authors, fmt_sidx, \
from calibre.utils.config import tweaks
from calibre.utils.date import dt_factory, qt_to_dt, isoformat
class LibraryDelegate(QItemDelegate):
class LibraryDelegate(QStyledItemDelegate):
COLOR = QColor("blue")
SIZE = 16
PEN = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
def __init__(self, parent):
QItemDelegate.__init__(self, parent)
QStyledItemDelegate.__init__(self, parent)
self._parent = parent
self.dummy = QModelIndex()
self.star_path = QPainterPath()
self.star_path.moveTo(90, 50)
for i in range(1, 5):
@ -54,6 +55,9 @@ class LibraryDelegate(QItemDelegate):
return QSize(5*(self.SIZE), self.SIZE+4)
def paint(self, painter, option, index):
style = self._parent.style()
option = QStyleOptionViewItemV4(option)
self.initStyleOption(option, self.dummy)
num = index.model().data(index, Qt.DisplayRole).toInt()[0]
def draw_star():
painter.save()
@ -66,11 +70,10 @@ class LibraryDelegate(QItemDelegate):
painter.save()
if hasattr(QStyle, 'CE_ItemViewItem'):
QApplication.style().drawControl(QStyle.CE_ItemViewItem, option,
style.drawControl(QStyle.CE_ItemViewItem, option,
painter, self._parent)
elif option.state & QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.highlight())
self.drawFocus(painter, option, option.rect)
try:
painter.setRenderHint(QPainter.Antialiasing)
painter.setClipRect(option.rect)
@ -89,7 +92,7 @@ class LibraryDelegate(QItemDelegate):
painter.restore()
def createEditor(self, parent, option, index):
sb = QItemDelegate.createEditor(self, parent, option, index)
sb = QStyledItemDelegate.createEditor(self, parent, option, index)
sb.setMinimum(0)
sb.setMaximum(5)
return sb

View File

@ -194,6 +194,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.tool_bar2.insertSeparator(self.action_find_next)
self.setFocusPolicy(Qt.StrongFocus)
self.search = SearchBox2(self)
self.search.setMinimumContentsLength(20)
self.search.initialize('viewer_search_history')
self.search.setToolTip(_('Search for text in book'))
self.search.setMinimumWidth(200)

View File

@ -551,7 +551,8 @@ class LineEditECM(object):
self.setText(unicode(self.text()).swapcase())
def title_case(self):
self.setText(unicode(self.text()).title())
from calibre.utils.titlecase import titlecase
self.setText(titlecase(unicode(self.text())))
class EnLineEdit(LineEditECM, QLineEdit):

View File

@ -123,10 +123,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.is_case_sensitive = not iswindows and not isosx and \
not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB'))
SchemaUpgrade.__init__(self)
CustomColumns.__init__(self)
self.initialize_dynamic()
def initialize_dynamic(self):
CustomColumns.__init__(self)
template = '''\
(SELECT {query} FROM books_{table}_link AS link INNER JOIN
{table} ON(link.{link_col}={table}.id) WHERE link.book=books.id)
@ -1428,6 +1428,7 @@ books_series_link feeds
os.remove(self.dbpath)
shutil.copyfile(dest, self.dbpath)
self.connect()
self.initialize_dynamic()
self.refresh()
if os.path.exists(dest):
os.remove(dest)

View File

@ -81,7 +81,7 @@ Device Integration
What devices does |app| support?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
At the moment |app| has full support for the SONY PRS 300/500/505/600/700/900, Barnes & Noble Nook, Cybook Gen 3/Opus, Amazon Kindle 1/2/DX, Entourage Edge, Longshine ShineBook, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook 360, Italica, eClicto, Iriver Story, Airis dBook, Hanvon N515, Binatone Readme, Teclast K3, various Android phones and the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk.
At the moment |app| has full support for the SONY PRS 300/500/505/600/700/900, Barnes & Noble Nook, Cybook Gen 3/Opus, Amazon Kindle 1/2/DX, Entourage Edge, Longshine ShineBook, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook 360, Italica, eClicto, Iriver Story, Airis dBook, Hanvon N515, Binatone Readme, Teclast K3, SpringDesign Alex, various Android phones and the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk.
How can I help get my device supported in |app|?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

94
src/calibre/utils/titlecase.py Executable file
View File

@ -0,0 +1,94 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Original Perl version by: John Gruber http://daringfireball.net/ 10 May 2008
Python version by Stuart Colville http://muffinresearch.co.uk
License: http://www.opensource.org/licenses/mit-license.php
"""
import re
__all__ = ['titlecase']
__version__ = '0.5'
SMALL = 'a|an|and|as|at|but|by|en|for|if|in|of|on|or|the|to|v\.?|via|vs\.?'
PUNCT = r"""!"#$%&'()*+,\-./:;?@[\\\]_`{|}~"""
SMALL_WORDS = re.compile(r'^(%s)$' % SMALL, re.I)
INLINE_PERIOD = re.compile(r'[a-z][.][a-z]', re.I)
UC_ELSEWHERE = re.compile(r'[%s]*?[a-zA-Z]+[A-Z]+?' % PUNCT)
CAPFIRST = re.compile(r"^[%s]*?([A-Za-z])" % PUNCT)
SMALL_FIRST = re.compile(r'^([%s]*)(%s)\b' % (PUNCT, SMALL), re.I)
SMALL_LAST = re.compile(r'\b(%s)[%s]?$' % (SMALL, PUNCT), re.I)
SUBPHRASE = re.compile(r'([:.;?!][ ])(%s)' % SMALL)
APOS_SECOND = re.compile(r"^[dol]{1}[']{1}[a-z]+$", re.I)
ALL_CAPS = re.compile(r'^[A-Z\s%s]+$' % PUNCT)
UC_INITIALS = re.compile(r"^(?:[A-Z]{1}\.{1}|[A-Z]{1}\.{1}[A-Z]{1})+$")
MAC_MC = re.compile(r"^([Mm]a?c)(\w+)")
def titlecase(text):
"""
Titlecases input text
This filter changes all words to Title Caps, and attempts to be clever
about *un*capitalizing SMALL words like a/an/the in the input.
The list of "SMALL words" which are not capped comes from
the New York Times Manual of Style, plus 'vs' and 'v'.
"""
all_caps = ALL_CAPS.match(text)
words = re.split('\s', text)
line = []
for word in words:
if all_caps:
if UC_INITIALS.match(word):
line.append(word)
continue
else:
word = word.lower()
if APOS_SECOND.match(word):
word = word.replace(word[0], word[0].upper())
word = word.replace(word[2], word[2].upper())
line.append(word)
continue
if INLINE_PERIOD.search(word) or UC_ELSEWHERE.match(word):
line.append(word)
continue
if SMALL_WORDS.match(word):
line.append(word.lower())
continue
match = MAC_MC.match(word)
if match:
line.append("%s%s" % (match.group(1).capitalize(),
match.group(2).capitalize()))
continue
hyphenated = []
for item in word.split('-'):
hyphenated.append(CAPFIRST.sub(lambda m: m.group(0).upper(), item))
line.append("-".join(hyphenated))
result = " ".join(line)
result = SMALL_FIRST.sub(lambda m: '%s%s' % (
m.group(1),
m.group(2).capitalize()
), result)
result = SMALL_LAST.sub(lambda m: m.group(0).capitalize(), result)
result = SUBPHRASE.sub(lambda m: '%s%s' % (
m.group(1),
m.group(2).capitalize()
), result)
return result

View File

@ -113,7 +113,7 @@ class NewsItem(NewsTreeItem):
return NONE
def __cmp__(self, other):
return cmp(self.title, getattr(other, 'title', ''))
return cmp(self.title.lower(), getattr(other, 'title', '').lower())
class RecipeModel(QAbstractItemModel, SearchQueryParser):