mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Merge from trunk
This commit is contained in:
commit
56f4ab8e68
118
Changelog.yaml
118
Changelog.yaml
@ -19,6 +19,124 @@
|
|||||||
# new recipes:
|
# new recipes:
|
||||||
# - title:
|
# - title:
|
||||||
|
|
||||||
|
- version: 0.7.44
|
||||||
|
date: 2011-02-04
|
||||||
|
|
||||||
|
new features:
|
||||||
|
- title: "Nook Color driver: Send downloaded news to the My Files/Magazines folder on the Nook Color. Also when getting the list of books on the device look at all folders in My Files, not just My Files/Books."
|
||||||
|
|
||||||
|
- title: "MOBI Output: Use the book uuid as the ASIN field and set cdetype to EBOK to allow Amazon furthest read tracking to work with calibre generated MOBI files."
|
||||||
|
tickets: [8721]
|
||||||
|
|
||||||
|
- title: "Comic input: Add an option to override the image size in the generated comic. Useful if you have a device whose screen size is not coverred by one of the available output profiles."
|
||||||
|
tickets: [7837]
|
||||||
|
|
||||||
|
- title: "Add a restore database option to the Library maintenance menu in the GUI"
|
||||||
|
|
||||||
|
- title: "TXT Output: Allow output in the textile markup language"
|
||||||
|
|
||||||
|
- title: "PML Output: Create multi-level Table of Contents"
|
||||||
|
|
||||||
|
- title: "Driver for the Archos 7O"
|
||||||
|
|
||||||
|
- title: "Search and Replace in the Bulk metadata dialog can now operate on the title_sort field as well"
|
||||||
|
tickets: [8732]
|
||||||
|
|
||||||
|
- title: "Allow changing the case of authors/tags/series etc. via the edit metadata dialog"
|
||||||
|
|
||||||
|
- title: "Connect/share menu: Re-organize to make it a little less easy to select email and delete instead of just email by mistake"
|
||||||
|
|
||||||
|
- title: "Heuristics: Improved Scene break detection and add option to control what scene breaks are replaced by."
|
||||||
|
|
||||||
|
- title: "SONY driver: Add option to not preserve aspect ratio of cover thumbnails."
|
||||||
|
|
||||||
|
- title: "BiBTeX catalog: Add on device column when available"
|
||||||
|
|
||||||
|
- title: "Add search to the plugin preferences dialog"
|
||||||
|
|
||||||
|
bug fixes:
|
||||||
|
- 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]
|
||||||
|
|
||||||
|
- title: "Tag browser: Fix searching with items in a user category not owrking if the main category is hidden"
|
||||||
|
tickets: [8741]
|
||||||
|
|
||||||
|
- title: "Make completion for author/series/tags/etc. fields less disruptive"
|
||||||
|
|
||||||
|
- title: "Fix regression that broke the content server when user categories/custom columns are present"
|
||||||
|
|
||||||
|
- title: "Catalog generation: Handle user supplied templates more robustly"
|
||||||
|
|
||||||
|
- title: "Move the Tags to apply to newly added books option into Preferences->Adding books"
|
||||||
|
tickets: [8730]
|
||||||
|
|
||||||
|
- title: "Workaround for bug in Qt on OS X that caused crashes when reading metedata from two or more EPUB files with HTML covers that used embedded fonts. Now the embedded fonts are ignored on OS X."
|
||||||
|
tickets: [8643]
|
||||||
|
|
||||||
|
- title: "Fix regression that broke the use of the group searched terms tweak"
|
||||||
|
tickets: [8739]
|
||||||
|
|
||||||
|
- title: "Fix template program regression triggered by recursively calling the processor"
|
||||||
|
|
||||||
|
- title: "Fix mimetype sent by content server for PDB files"
|
||||||
|
|
||||||
|
- title: "OPF: Write title_sort as a calibre custom metadata field rather than as a file-as attribute on the title. This conforms to the OPF spec"
|
||||||
|
tickets: [7883]
|
||||||
|
|
||||||
|
- title: "SONY driver: Fix thumbnails being sent to SD card are sent to the wrong location. Also use correct thumbnail size so that the SONY does not regenerate the thumbnail on disconnect"
|
||||||
|
|
||||||
|
- title: "Do not discard the result of a conversion if the user opens the edit metadata dialog while the conversion is running"
|
||||||
|
tickets: [8672]
|
||||||
|
|
||||||
|
- title: "CHM Input: When the chm file lacks a hhc, look for index.html instead"
|
||||||
|
tickets: [8688]
|
||||||
|
|
||||||
|
- title: "EPUB Input: Filter some invalid media types from the spine"
|
||||||
|
|
||||||
|
- title: "RTF Input: More encoding handlig fixes."
|
||||||
|
tickets: [8678]
|
||||||
|
|
||||||
|
- title: "Linux binary build: Restore functioning of CALIBRE_DEVELOP_FROM, which was accidentally removed a few versions ago"
|
||||||
|
|
||||||
|
- title: "RTF Output: Retain html headings as rtf headings when converting to rtf. Also fix output of italics."
|
||||||
|
tickets: [8641, 8640]
|
||||||
|
|
||||||
|
- title: "LIT Input: Fix regression that broke handling of LIT files that contain txt data instead of html"
|
||||||
|
|
||||||
|
- title: "MOBI Input: Handle more non printing ASCII codes"
|
||||||
|
tickets: [8646]
|
||||||
|
|
||||||
|
- title: "Handle empty cover files more gracefully"
|
||||||
|
tickets: [8656]
|
||||||
|
|
||||||
|
- title: "Catalog geenration: Fix error when Pocketbook is connected and trying to geenrate catalog"
|
||||||
|
tickets: [8651]
|
||||||
|
|
||||||
|
- title: "Heuristics: Italicize common cases, reduce false positives."
|
||||||
|
|
||||||
|
- title: "Fix regression that caused reporting of device connection errors to break"
|
||||||
|
|
||||||
|
improved recipes:
|
||||||
|
- MSN Japan
|
||||||
|
- The Onion
|
||||||
|
- La Tribuna de
|
||||||
|
- Wall Street Journal
|
||||||
|
- "20 Minutos"
|
||||||
|
- LA Times
|
||||||
|
- Endgadget Japan
|
||||||
|
- Ledevoir
|
||||||
|
- Vijesti
|
||||||
|
|
||||||
|
new recipes:
|
||||||
|
- title: "Cinco Dias and BBC Mundo"
|
||||||
|
author: Luis Hernandez
|
||||||
|
|
||||||
|
- title: "Explosm"
|
||||||
|
author: Andromeda Rabbit
|
||||||
|
|
||||||
|
- title: "Cinco Dias"
|
||||||
|
author: Luis Hernandez
|
||||||
|
|
||||||
|
|
||||||
- version: 0.7.43
|
- version: 0.7.43
|
||||||
date: 2011-01-28
|
date: 2011-01-28
|
||||||
|
@ -1,57 +1,10 @@
|
|||||||
body { background-color: white; }
|
body { background-color: white; }
|
||||||
|
|
||||||
p.title {
|
|
||||||
margin-top:0em;
|
|
||||||
margin-bottom:0em;
|
|
||||||
text-align:center;
|
|
||||||
font-style:italic;
|
|
||||||
font-size:xx-large;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.series_id {
|
|
||||||
margin-top:0em;
|
|
||||||
margin-bottom:0em;
|
|
||||||
text-align:center;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.series_id {
|
a.series_id {
|
||||||
font-style:normal;
|
font-style:normal;
|
||||||
font-size:large;
|
font-size:large;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.author {
|
|
||||||
font-size:large;
|
|
||||||
margin-top:0em;
|
|
||||||
margin-bottom:0em;
|
|
||||||
text-align: center;
|
|
||||||
text-indent: 0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.author_index {
|
|
||||||
font-size:large;
|
|
||||||
font-weight:bold;
|
|
||||||
text-align:left;
|
|
||||||
margin-top:0px;
|
|
||||||
margin-bottom:-2px;
|
|
||||||
text-indent: 0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.genres {
|
|
||||||
font-style:normal;
|
|
||||||
margin-top:0.5em;
|
|
||||||
margin-bottom:0em;
|
|
||||||
text-align: left;
|
|
||||||
text-indent: 0.0in;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.formats {
|
|
||||||
font-size:90%;
|
|
||||||
margin-top:0em;
|
|
||||||
margin-bottom:0.5em;
|
|
||||||
text-align: left;
|
|
||||||
text-indent: 0.0in;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Minimize widows and orphans by logically grouping chunks
|
* Minimize widows and orphans by logically grouping chunks
|
||||||
* Some reports of problems with Sony (ADE) ereaders
|
* Some reports of problems with Sony (ADE) ereaders
|
||||||
@ -77,71 +30,6 @@ div.initial_letter {
|
|||||||
page-break-before:always;
|
page-break-before:always;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.author_title_letter_index {
|
|
||||||
font-size:x-large;
|
|
||||||
text-align:center;
|
|
||||||
font-weight:bold;
|
|
||||||
margin-top:0px;
|
|
||||||
margin-bottom:0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.date_index {
|
|
||||||
font-size:x-large;
|
|
||||||
text-align:center;
|
|
||||||
font-weight:bold;
|
|
||||||
margin-top:1em;
|
|
||||||
margin-bottom:0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.series {
|
|
||||||
font-style:italic;
|
|
||||||
margin-top:2px;
|
|
||||||
margin-bottom:0px;
|
|
||||||
margin-left:2em;
|
|
||||||
text-align:left;
|
|
||||||
text-indent:-2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.series_letter_index {
|
|
||||||
font-size:x-large;
|
|
||||||
text-align:center;
|
|
||||||
font-weight:bold;
|
|
||||||
margin-top:1em;
|
|
||||||
margin-bottom:0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.read_book {
|
|
||||||
text-align:left;
|
|
||||||
margin-top:0px;
|
|
||||||
margin-bottom:0px;
|
|
||||||
margin-left:2em;
|
|
||||||
text-indent:-2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.unread_book {
|
|
||||||
text-align:left;
|
|
||||||
margin-top:0px;
|
|
||||||
margin-bottom:0px;
|
|
||||||
margin-left:2em;
|
|
||||||
text-indent:-2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.wishlist_item {
|
|
||||||
text-align:left;
|
|
||||||
margin-top:0px;
|
|
||||||
margin-bottom:0px;
|
|
||||||
margin-left:2em;
|
|
||||||
text-indent:-2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.date_read {
|
|
||||||
text-align:left;
|
|
||||||
margin-top:0px;
|
|
||||||
margin-bottom:0px;
|
|
||||||
margin-left:6em;
|
|
||||||
text-indent:-6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr.annotations_divider {
|
hr.annotations_divider {
|
||||||
width:50%;
|
width:50%;
|
||||||
margin-left:1em;
|
margin-left:1em;
|
||||||
@ -175,6 +63,102 @@ hr.merged_comments_divider {
|
|||||||
border-left: solid white 0px;
|
border-left: solid white 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.date_read {
|
||||||
|
text-align:left;
|
||||||
|
margin-top:0px;
|
||||||
|
margin-bottom:0px;
|
||||||
|
margin-left:6em;
|
||||||
|
text-indent:-6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.author {
|
||||||
|
font-size:large;
|
||||||
|
margin-top:0em;
|
||||||
|
margin-bottom:0em;
|
||||||
|
text-align: center;
|
||||||
|
text-indent: 0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.author_index {
|
||||||
|
font-size:large;
|
||||||
|
font-weight:bold;
|
||||||
|
text-align:left;
|
||||||
|
margin-top:0px;
|
||||||
|
margin-bottom:-2px;
|
||||||
|
text-indent: 0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.author_title_letter_index {
|
||||||
|
font-size:x-large;
|
||||||
|
text-align:center;
|
||||||
|
font-weight:bold;
|
||||||
|
margin-top:0px;
|
||||||
|
margin-bottom:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.date_index {
|
||||||
|
font-size:x-large;
|
||||||
|
text-align:center;
|
||||||
|
font-weight:bold;
|
||||||
|
margin-top:1em;
|
||||||
|
margin-bottom:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.formats {
|
||||||
|
font-size:90%;
|
||||||
|
margin-top:0em;
|
||||||
|
margin-bottom:0.5em;
|
||||||
|
text-align: left;
|
||||||
|
text-indent: 0.0in;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.genres {
|
||||||
|
font-style:normal;
|
||||||
|
margin-top:0.5em;
|
||||||
|
margin-bottom:0em;
|
||||||
|
text-align: left;
|
||||||
|
text-indent: 0.0in;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.series {
|
||||||
|
font-style:italic;
|
||||||
|
margin-top:0.25em;
|
||||||
|
margin-bottom:0em;
|
||||||
|
margin-left:2em;
|
||||||
|
text-align:left;
|
||||||
|
text-indent:-2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.series_id {
|
||||||
|
margin-top:0em;
|
||||||
|
margin-bottom:0em;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.series_letter_index {
|
||||||
|
font-size:x-large;
|
||||||
|
text-align:center;
|
||||||
|
font-weight:bold;
|
||||||
|
margin-top:1em;
|
||||||
|
margin-bottom:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.title {
|
||||||
|
margin-top:0em;
|
||||||
|
margin-bottom:0em;
|
||||||
|
text-align:center;
|
||||||
|
font-style:italic;
|
||||||
|
font-size:xx-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.wishlist_item, p.unread_book, p.read_book {
|
||||||
|
text-align:left;
|
||||||
|
margin-top:0px;
|
||||||
|
margin-bottom:0px;
|
||||||
|
margin-left:2em;
|
||||||
|
text-indent:-2em;
|
||||||
|
}
|
||||||
|
|
||||||
td.publisher, td.date {
|
td.publisher, td.date {
|
||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
|
@ -30,6 +30,13 @@ defaults.
|
|||||||
series_index_auto_increment = 'next'
|
series_index_auto_increment = 'next'
|
||||||
|
|
||||||
|
|
||||||
|
# Should the completion separator be append
|
||||||
|
# to the end of the completed text to
|
||||||
|
# automatically begin a new completion operation.
|
||||||
|
# Can be either True or False
|
||||||
|
completer_append_separator = False
|
||||||
|
|
||||||
|
|
||||||
# The algorithm used to copy author to author_sort
|
# The algorithm used to copy author to author_sort
|
||||||
# Possible values are:
|
# Possible values are:
|
||||||
# invert: use "fn ln" -> "ln, fn" (the original algorithm)
|
# invert: use "fn ln" -> "ln, fn" (the original algorithm)
|
||||||
|
BIN
resources/images/news/kopalniawiedzy.png
Normal file
BIN
resources/images/news/kopalniawiedzy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 466 B |
BIN
resources/images/news/korespondent.png
Normal file
BIN
resources/images/news/korespondent.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 860 B |
@ -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; */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
55
resources/recipes/europa_press.recipe
Normal file
55
resources/recipes/europa_press.recipe
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__author__ = 'Luis Hernandez'
|
||||||
|
__copyright__ = 'Luis Hernandez<tolyluis@gmail.com>'
|
||||||
|
__version__ = 'v1.0'
|
||||||
|
__date__ = '30 January 2011'
|
||||||
|
|
||||||
|
'''
|
||||||
|
www.europapress.es
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'Europa Press'
|
||||||
|
author = 'Luis Hernandez'
|
||||||
|
description = 'spanish news agency'
|
||||||
|
|
||||||
|
oldest_article = 2
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
|
||||||
|
remove_javascript = True
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
|
||||||
|
language = 'es'
|
||||||
|
timefmt = '[%a, %d %b, %Y]'
|
||||||
|
|
||||||
|
remove_tags_before = dict(name='div' , attrs={'class':['nivel1 bg_3col']})
|
||||||
|
remove_tags_after = dict(name='div' , attrs={'id':['ImprimirEnviarNoticia']})
|
||||||
|
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='ul', attrs={'id':['entidadesNoticia','MenuSecciones']})
|
||||||
|
,dict(name='div', attrs={'id':['ImprimirEnviarNoticia','PublicidadSuperior','CabeceraDerecha','Comentarios','comentarios full fbConnectAPI','ComentarEstaNoticia','ctl00_Superior_Main_MasEnChance_cajamasnoticias','gl_chn','videos_portada_derecha','galeria_portada_central','galeria_portada_central_boxes']})
|
||||||
|
,dict(name='div', attrs={'class':['infoRelacionada','col_1','buscador','caja doblecolumna strong','CHANCE_EP_Encuesta_frontal text','seccionportada col_0','seccion header','text','pie caption_over']})
|
||||||
|
,dict(name='a', attrs={'class':['buscadorLabel']})
|
||||||
|
,dict(name='span', attrs={'class':['editado']})
|
||||||
|
,dict(name='table')
|
||||||
|
,dict(name='li')
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'Portada' , u'http://www.europapress.es/rss/rss.aspx')
|
||||||
|
,(u'Nacional' , u'http://www.europapress.es/rss/rss.aspx?ch=66')
|
||||||
|
,(u'Internacional' , u'http://www.europapress.es/rss/rss.aspx?ch=69')
|
||||||
|
,(u'Economia' , u'http://www.europapress.es/rss/rss.aspx?ch=136')
|
||||||
|
,(u'Deportes' , u'http://www.europapress.es/rss/rss.aspx?ch=67')
|
||||||
|
,(u'Cultura' , u'http://www.europapress.es/rss/rss.aspx?ch=126')
|
||||||
|
,(u'Sociedad' , u'http://www.europapress.es/rss/rss.aspx?ch=73')
|
||||||
|
,(u'Motor' , u'http://www.europapress.es/rss/rss.aspx?ch=435')
|
||||||
|
,(u'CHANCE' , u'http://www.europapress.es/rss/rss.aspx?ch=549')
|
||||||
|
,(u'Comunicados' , u'http://www.europapress.es/rss/rss.aspx?ch=137')
|
||||||
|
]
|
||||||
|
|
@ -35,7 +35,7 @@ class IrishTimes(BasicNewsRecipe):
|
|||||||
def print_version(self, url):
|
def print_version(self, url):
|
||||||
if url.count('rss.feedsportal.com'):
|
if url.count('rss.feedsportal.com'):
|
||||||
u = 'http://www.irishtimes.com' + \
|
u = 'http://www.irishtimes.com' + \
|
||||||
(((url[69:].replace('0C','/')).replace('0A','0'))).replace('0Bhtml/story01.htm','_pf.html')
|
(((url[70:].replace('0C','/')).replace('0A','0'))).replace('0Bhtml/story01.htm','_pf.html')
|
||||||
else:
|
else:
|
||||||
u = url.replace('.html','_pf.html')
|
u = url.replace('.html','_pf.html')
|
||||||
return u
|
return u
|
||||||
|
80
resources/recipes/kopalniawiedzy.recipe
Normal file
80
resources/recipes/kopalniawiedzy.recipe
Normal 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
|
40
resources/recipes/korespondent.recipe
Normal file
40
resources/recipes/korespondent.recipe
Normal 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')]
|
||||||
|
|
43
resources/recipes/radio_prague.recipe
Normal file
43
resources/recipes/radio_prague.recipe
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1291540961(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'Radio Praha'
|
||||||
|
__author__ = 'Francois Pellicaan'
|
||||||
|
description = 'News and information from and about The Czech republic. '
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
remove_empty_feeds = True
|
||||||
|
encoding = 'utf8'
|
||||||
|
publisher = 'Radio Prague'
|
||||||
|
category = 'News'
|
||||||
|
language = 'en_CZ'
|
||||||
|
publication_type = 'newsportal'
|
||||||
|
|
||||||
|
extra_css = 'h1 .section { display: block; text-transform: uppercase; font-size: 10px; margin-top: 4em; } \n .title { font-size: 14px; margin-top: 4em; } \n a.photo { display: block; clear:both; } \n .caption { font-size: 9px; display: block; clear:both; padding:0px 0px 20px 0px; } \n a { font-type: normal; }'
|
||||||
|
|
||||||
|
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='div', attrs={'class':['main']})
|
||||||
|
]
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='div', attrs={'class':['cleaner', 'options', 'toolsXXL']}),
|
||||||
|
dict(name='ul', attrs={'class':['tools']})
|
||||||
|
]
|
||||||
|
feeds = [
|
||||||
|
(u'Current Affairs', 'http://www.radio.cz/feeds/rss/en/themes/curraffrs.xml'),
|
||||||
|
(u'Society', 'http://www.radio.cz/feeds/rss/en/themes/society.xml'),
|
||||||
|
(u'European Union', 'http:http://www.radio.cz/feeds/rss/en/themes/eu.xml'),
|
||||||
|
(u'Foreign policy', 'http://www.radio.cz/feeds/rss/en/themes/foreignpolicy.xml'),
|
||||||
|
(u'Business', 'http://www.radio.cz/feeds/rss/en/themes/business.xml'),
|
||||||
|
(u'Culture', 'http://www.radio.cz/feeds/rss/en/themes/culture.xml'),
|
||||||
|
(u'Czechs abroad', 'http://www.radio.cz/feeds/rss/en/themes/czechabroad.xml'),
|
||||||
|
(u'History', 'http://www.radio.cz/feeds/rss/en/themes/history.xml'),
|
||||||
|
(u'Nature', 'http://www.radio.cz/feeds/rss/en/themes/nature.xml'),
|
||||||
|
(u'Science', 'http://www.radio.cz/feeds/rss/en/themes/science.xml'),
|
||||||
|
(u'Sport', 'http://www.radio.cz/feeds/rss/en/themes/sport.xml'),
|
||||||
|
(u'Travel', 'http://www.radio.cz/feeds/rss/en/themes/travel.xml'),
|
||||||
|
]
|
44
resources/recipes/radio_praha.recipe
Normal file
44
resources/recipes/radio_praha.recipe
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1291540961(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'Radio Praha'
|
||||||
|
__author__ = 'Francois Pellicaan'
|
||||||
|
description = u'Česká oficiální mezinárodní vysílací stanice.'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
remove_empty_feeds = True
|
||||||
|
encoding = 'utf8'
|
||||||
|
publisher = u'Český rozhlas'
|
||||||
|
category = 'News'
|
||||||
|
language = 'cs'
|
||||||
|
publication_type = 'newsportal'
|
||||||
|
|
||||||
|
extra_css = u'h1 .section { display: block; text-transform: uppercase; font-size: 10px; margin-top: 4em; } \n .title { font-size: 14px; margin-top: 4em; } \n a.photo { display: block; clear:both; } \n .caption { font-size: 9px; display: block; clear:both; padding:0px 0px 20px 0px; } \n a { font-type: normal; }'
|
||||||
|
|
||||||
|
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='div', attrs={'class':['main']})
|
||||||
|
]
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='div', attrs={'class':['cleaner', 'options', 'toolsXXL']}),
|
||||||
|
dict(name='ul', attrs={'class':['tools']})
|
||||||
|
]
|
||||||
|
feeds = [
|
||||||
|
(u'Domácí politika', 'http://www.radio.cz/feeds/rss/cs/oblast/dompol.xml'),
|
||||||
|
(u'Společnost', 'http://www.radio.cz/feeds/rss/cs/oblast/spolecnost.xml'),
|
||||||
|
(u'Evropská unie', 'http://www.radio.cz/feeds/rss/cs/oblast/eu.xml'),
|
||||||
|
(u'Zahraniční politika', 'http://www.radio.cz/feeds/rss/cs/oblast/zahrpol.xml'),
|
||||||
|
(u'Ekonomika', 'http://www.radio.cz/feeds/rss/cs/oblast/ekonomika.xml'),
|
||||||
|
(u'Kultura', 'http://www.radio.cz/feeds/rss/cs/oblast/kultura.xml'),
|
||||||
|
(u'Krajané', 'http://www.radio.cz/feeds/rss/cs/oblast/krajane.xml'),
|
||||||
|
(u'Historie', 'http://www.radio.cz/feeds/rss/cs/oblast/historie.xml'),
|
||||||
|
(u'Příroda', 'http://www.radio.cz/feeds/rss/cs/oblast/priroda.xml'),
|
||||||
|
(u'Věda', 'http://www.radio.cz/feeds/rss/cs/oblast/veda.xml'),
|
||||||
|
(u'Sport', 'http://www.radio.cz/feeds/rss/cs/oblast/sport.xml'),
|
||||||
|
(u'Cestování', 'http://www.radio.cz/feeds/rss/cs/oblast/cestovani.xml'),
|
||||||
|
]
|
@ -131,6 +131,7 @@ class WallStreetJournal(BasicNewsRecipe):
|
|||||||
'description':desc, 'date':''})
|
'description':desc, 'date':''})
|
||||||
|
|
||||||
self.log('\tFound WN article:', title)
|
self.log('\tFound WN article:', title)
|
||||||
|
self.log('\t\t', desc)
|
||||||
|
|
||||||
return articles
|
return articles
|
||||||
|
|
||||||
@ -157,17 +158,23 @@ class WallStreetJournal(BasicNewsRecipe):
|
|||||||
meta = a.find(attrs={'class':'meta_sectionName'})
|
meta = a.find(attrs={'class':'meta_sectionName'})
|
||||||
if meta is not None:
|
if meta is not None:
|
||||||
meta.extract()
|
meta.extract()
|
||||||
title = self.tag_to_string(a).strip() + ' [%s]'%self.tag_to_string(meta)
|
meta = self.tag_to_string(meta).strip()
|
||||||
|
if meta:
|
||||||
|
title = self.tag_to_string(a).strip() + ' [%s]'%meta
|
||||||
|
else:
|
||||||
|
title = self.tag_to_string(a).strip()
|
||||||
url = 'http://online.wsj.com'+a['href']
|
url = 'http://online.wsj.com'+a['href']
|
||||||
desc = ''
|
desc = ''
|
||||||
p = container.find('p')
|
for p in container.findAll('p'):
|
||||||
if p is not None:
|
|
||||||
desc = self.tag_to_string(p)
|
desc = self.tag_to_string(p)
|
||||||
|
if not 'Subscriber Content' in desc:
|
||||||
|
break
|
||||||
|
|
||||||
articles.append({'title':title, 'url':url,
|
articles.append({'title':title, 'url':url,
|
||||||
'description':desc, 'date':''})
|
'description':desc, 'date':''})
|
||||||
|
|
||||||
self.log('\tFound article:', title)
|
self.log('\tFound article:', title)
|
||||||
|
self.log('\t\t', desc)
|
||||||
|
|
||||||
return articles
|
return articles
|
||||||
|
|
||||||
|
@ -140,12 +140,17 @@ class WallStreetJournal(BasicNewsRecipe):
|
|||||||
meta = a.find(attrs={'class':'meta_sectionName'})
|
meta = a.find(attrs={'class':'meta_sectionName'})
|
||||||
if meta is not None:
|
if meta is not None:
|
||||||
meta.extract()
|
meta.extract()
|
||||||
title = self.tag_to_string(a).strip() + ' [%s]'%self.tag_to_string(meta)
|
meta = self.tag_to_string(meta).strip()
|
||||||
|
if meta:
|
||||||
|
title = self.tag_to_string(a).strip() + ' [%s]'%meta
|
||||||
|
else:
|
||||||
|
title = self.tag_to_string(a).strip()
|
||||||
url = 'http://online.wsj.com'+a['href']
|
url = 'http://online.wsj.com'+a['href']
|
||||||
desc = ''
|
desc = ''
|
||||||
p = container.find('p')
|
for p in container.findAll('p'):
|
||||||
if p is not None:
|
|
||||||
desc = self.tag_to_string(p)
|
desc = self.tag_to_string(p)
|
||||||
|
if not 'Subscriber Content' in desc:
|
||||||
|
break
|
||||||
|
|
||||||
articles.append({'title':title, 'url':url,
|
articles.append({'title':title, 'url':url,
|
||||||
'description':desc, 'date':''})
|
'description':desc, 'date':''})
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"re": "def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):\n return re.sub(pattern, replacement, val)\n",
|
"re": "def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):\n return re.sub(pattern, replacement, val)\n",
|
||||||
"add": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x + y)\n",
|
"add": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x + y)\n",
|
||||||
"lookup": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if len(args) == 2: # here for backwards compatibility\n if val:\n return formatter.vformat('{'+args[0].strip()+'}', [], kwargs)\n else:\n return formatter.vformat('{'+args[1].strip()+'}', [], kwargs)\n if (len(args) % 2) != 1:\n raise ValueError(_('lookup requires either 2 or an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return formatter.vformat('{' + args[i].strip() + '}', [], kwargs)\n if re.search(args[i], val):\n return formatter.vformat('{'+args[i+1].strip() + '}', [], kwargs)\n i += 2\n",
|
"lookup": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if len(args) == 2: # here for backwards compatibility\n if val:\n return formatter.vformat('{'+args[0].strip()+'}', [], kwargs)\n else:\n return formatter.vformat('{'+args[1].strip()+'}', [], kwargs)\n if (len(args) % 2) != 1:\n raise ValueError(_('lookup requires either 2 or an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return formatter.vformat('{' + args[i].strip() + '}', [], kwargs)\n if re.search(args[i], val):\n return formatter.vformat('{'+args[i+1].strip() + '}', [], kwargs)\n i += 2\n",
|
||||||
"template": "def evaluate(self, formatter, kwargs, mi, locals, template):\n template = template.replace('[[', '{').replace(']]', '}')\n return formatter.safe_format(template, kwargs, 'TEMPLATE', mi)\n",
|
"template": "def evaluate(self, formatter, kwargs, mi, locals, template):\n template = template.replace('[[', '{').replace(']]', '}')\n return formatter.__class__().safe_format(template, kwargs, 'TEMPLATE', mi)\n",
|
||||||
"print": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n print args\n return None\n",
|
"print": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n print args\n return None\n",
|
||||||
"titlecase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return titlecase(val)\n",
|
"titlecase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return titlecase(val)\n",
|
||||||
"test": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set):\n if val:\n return value_if_set\n else:\n return value_not_set\n",
|
"test": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set):\n if val:\n return value_if_set\n else:\n return value_not_set\n",
|
||||||
|
@ -43,7 +43,7 @@ class Stage3(Command):
|
|||||||
|
|
||||||
description = 'Stage 3 of the publish process'
|
description = 'Stage 3 of the publish process'
|
||||||
sub_commands = ['upload_user_manual', 'upload_demo', 'sdist',
|
sub_commands = ['upload_user_manual', 'upload_demo', 'sdist',
|
||||||
'upload_to_google_code', 'upload_to_sourceforge',
|
'upload_to_sourceforge', 'upload_to_google_code',
|
||||||
'tag_release', 'upload_to_server',
|
'tag_release', 'upload_to_server',
|
||||||
'upload_to_mobileread',
|
'upload_to_mobileread',
|
||||||
]
|
]
|
||||||
|
@ -324,7 +324,7 @@ class UploadToServer(Command):
|
|||||||
|
|
||||||
def run(self, opts):
|
def run(self, opts):
|
||||||
check_call('ssh divok rm -f %s/calibre-\*.tar.gz'%DOWNLOADS, shell=True)
|
check_call('ssh divok rm -f %s/calibre-\*.tar.gz'%DOWNLOADS, shell=True)
|
||||||
check_call('scp dist/calibre-*.tar.gz divok:%s/'%DOWNLOADS, shell=True)
|
#check_call('scp dist/calibre-*.tar.gz divok:%s/'%DOWNLOADS, shell=True)
|
||||||
check_call('gpg --armor --detach-sign dist/calibre-*.tar.gz',
|
check_call('gpg --armor --detach-sign dist/calibre-*.tar.gz',
|
||||||
shell=True)
|
shell=True)
|
||||||
check_call('scp dist/calibre-*.tar.gz.asc divok:%s/signatures/'%DOWNLOADS,
|
check_call('scp dist/calibre-*.tar.gz.asc divok:%s/signatures/'%DOWNLOADS,
|
||||||
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
__appname__ = 'calibre'
|
__appname__ = 'calibre'
|
||||||
__version__ = '0.7.43'
|
__version__ = '0.7.44'
|
||||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
@ -28,8 +28,8 @@ class HeuristicProcessor(object):
|
|||||||
self.linereg = re.compile('(?<=<p).*?(?=</p>)', re.IGNORECASE|re.DOTALL)
|
self.linereg = re.compile('(?<=<p).*?(?=</p>)', re.IGNORECASE|re.DOTALL)
|
||||||
self.blankreg = re.compile(r'\s*(?P<openline><p(?!\sclass=\"(softbreak|whitespace)\")[^>]*>)\s*(?P<closeline></p>)', re.IGNORECASE)
|
self.blankreg = re.compile(r'\s*(?P<openline><p(?!\sclass=\"(softbreak|whitespace)\")[^>]*>)\s*(?P<closeline></p>)', re.IGNORECASE)
|
||||||
self.anyblank = re.compile(r'\s*(?P<openline><p[^>]*>)\s*(?P<closeline></p>)', re.IGNORECASE)
|
self.anyblank = re.compile(r'\s*(?P<openline><p[^>]*>)\s*(?P<closeline></p>)', re.IGNORECASE)
|
||||||
self.multi_blank = re.compile(r'(\s*<p[^>]*>\s*</p>){2,}(?!\s*<h\d)', re.IGNORECASE)
|
self.multi_blank = re.compile(r'(\s*<p[^>]*>\s*</p>(\s*<div[^>]*>\s*</div>\s*)*){2,}(?!\s*<h\d)', re.IGNORECASE)
|
||||||
self.any_multi_blank = re.compile(r'(\s*<p[^>]*>\s*</p>){2,}', re.IGNORECASE)
|
self.any_multi_blank = re.compile(r'(\s*<p[^>]*>\s*</p>(\s*<div[^>]*>\s*</div>\s*)*){2,}', re.IGNORECASE)
|
||||||
self.line_open = "<(?P<outer>p|div)[^>]*>\s*(<(?P<inner1>font|span|[ibu])[^>]*>)?\s*(<(?P<inner2>font|span|[ibu])[^>]*>)?\s*(<(?P<inner3>font|span|[ibu])[^>]*>)?\s*"
|
self.line_open = "<(?P<outer>p|div)[^>]*>\s*(<(?P<inner1>font|span|[ibu])[^>]*>)?\s*(<(?P<inner2>font|span|[ibu])[^>]*>)?\s*(<(?P<inner3>font|span|[ibu])[^>]*>)?\s*"
|
||||||
self.line_close = "(</(?P=inner3)>)?\s*(</(?P=inner2)>)?\s*(</(?P=inner1)>)?\s*</(?P=outer)>"
|
self.line_close = "(</(?P=inner3)>)?\s*(</(?P=inner2)>)?\s*(</(?P=inner1)>)?\s*</(?P=outer)>"
|
||||||
self.single_blank = re.compile(r'(\s*<p[^>]*>\s*</p>)', re.IGNORECASE)
|
self.single_blank = re.compile(r'(\s*<p[^>]*>\s*</p>)', re.IGNORECASE)
|
||||||
@ -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:
|
||||||
@ -384,6 +384,8 @@ class HeuristicProcessor(object):
|
|||||||
html = re.sub(r"\s*<(font|[ibu]|em|strong)[^>]*>\s*(<(font|[ibu]|em|strong)[^>]*>\s*</(font|[ibu]|em|strong)>\s*){0,2}\s*</(font|[ibu]|em|strong)>", " ", html)
|
html = re.sub(r"\s*<(font|[ibu]|em|strong)[^>]*>\s*(<(font|[ibu]|em|strong)[^>]*>\s*</(font|[ibu]|em|strong)>\s*){0,2}\s*</(font|[ibu]|em|strong)>", " ", html)
|
||||||
html = re.sub(r"\s*<span[^>]*>\s*(<span[^>]>\s*</span>){0,2}\s*</span>\s*", " ", html)
|
html = re.sub(r"\s*<span[^>]*>\s*(<span[^>]>\s*</span>){0,2}\s*</span>\s*", " ", html)
|
||||||
html = re.sub(r"\s*<(font|[ibu]|em|strong)[^>]*>\s*(<(font|[ibu]|em|strong)[^>]*>\s*</(font|[ibu]|em|strong)>\s*){0,2}\s*</(font|[ibu]|em|strong)>", " ", html)
|
html = re.sub(r"\s*<(font|[ibu]|em|strong)[^>]*>\s*(<(font|[ibu]|em|strong)[^>]*>\s*</(font|[ibu]|em|strong)>\s*){0,2}\s*</(font|[ibu]|em|strong)>", " ", html)
|
||||||
|
# delete surrounding divs from empty paragraphs
|
||||||
|
html = re.sub('<div[^>]*>\s*<p[^>]*>\s*</p>\s*</div>', '<p> </p>', html)
|
||||||
# Empty heading tags
|
# Empty heading tags
|
||||||
html = re.sub(r'(?i)<h\d+>\s*</h\d+>', '', html)
|
html = re.sub(r'(?i)<h\d+>\s*</h\d+>', '', html)
|
||||||
self.deleted_nbsps = True
|
self.deleted_nbsps = True
|
||||||
@ -561,7 +563,6 @@ class HeuristicProcessor(object):
|
|||||||
# Determine whether the document uses interleaved blank lines
|
# Determine whether the document uses interleaved blank lines
|
||||||
self.blanks_between_paragraphs = self.analyze_blanks(html)
|
self.blanks_between_paragraphs = self.analyze_blanks(html)
|
||||||
|
|
||||||
#self.dump(html, 'before_chapter_markup')
|
|
||||||
# detect chapters/sections to match xpath or splitting logic
|
# detect chapters/sections to match xpath or splitting logic
|
||||||
|
|
||||||
if getattr(self.extra_opts, 'markup_chapter_headings', False):
|
if getattr(self.extra_opts, 'markup_chapter_headings', False):
|
||||||
|
@ -15,6 +15,7 @@ from calibre import guess_type, strftime
|
|||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||||
from calibre.ebooks.oeb.base import XPath, XHTML_NS, XHTML
|
from calibre.ebooks.oeb.base import XPath, XHTML_NS, XHTML
|
||||||
from calibre.library.comments import comments_to_html
|
from calibre.library.comments import comments_to_html
|
||||||
|
from calibre.utils.date import is_date_undefined
|
||||||
|
|
||||||
JACKET_XPATH = '//h:meta[@name="calibre-content" and @content="jacket"]'
|
JACKET_XPATH = '//h:meta[@name="calibre-content" and @content="jacket"]'
|
||||||
|
|
||||||
@ -109,7 +110,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,9 +128,12 @@ 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:
|
||||||
|
if is_date_undefined(mi.pubdate):
|
||||||
|
pubdate = ''
|
||||||
|
else:
|
||||||
pubdate = strftime(u'%Y', mi.pubdate.timetuple())
|
pubdate = strftime(u'%Y', mi.pubdate.timetuple())
|
||||||
except:
|
except:
|
||||||
pubdate = ''
|
pubdate = ''
|
||||||
@ -175,18 +179,23 @@ def render_jacket(mi, output_profile,
|
|||||||
soup = BeautifulSoup(generated_html)
|
soup = BeautifulSoup(generated_html)
|
||||||
if not series:
|
if not series:
|
||||||
series_tag = soup.find(attrs={'class':'cbj_series'})
|
series_tag = soup.find(attrs={'class':'cbj_series'})
|
||||||
|
if series_tag is not None:
|
||||||
series_tag.extract()
|
series_tag.extract()
|
||||||
if not rating:
|
if not rating:
|
||||||
rating_tag = soup.find(attrs={'class':'cbj_rating'})
|
rating_tag = soup.find(attrs={'class':'cbj_rating'})
|
||||||
|
if rating_tag is not None:
|
||||||
rating_tag.extract()
|
rating_tag.extract()
|
||||||
if not tags:
|
if not tags:
|
||||||
tags_tag = soup.find(attrs={'class':'cbj_tags'})
|
tags_tag = soup.find(attrs={'class':'cbj_tags'})
|
||||||
|
if tags_tag is not None:
|
||||||
tags_tag.extract()
|
tags_tag.extract()
|
||||||
if not pubdate:
|
if not pubdate:
|
||||||
pubdate_tag = soup.find(attrs={'class':'cbj_pubdate'})
|
pubdate_tag = soup.find(attrs={'class':'cbj_pubdata'})
|
||||||
|
if pubdate_tag is not None:
|
||||||
pubdate_tag.extract()
|
pubdate_tag.extract()
|
||||||
if output_profile.short_name != 'kindle':
|
if output_profile.short_name != 'kindle':
|
||||||
hr_tag = soup.find('hr', attrs={'class':'cbj_kindle_banner_hr'})
|
hr_tag = soup.find('hr', attrs={'class':'cbj_kindle_banner_hr'})
|
||||||
|
if hr_tag is not None:
|
||||||
hr_tag.extract()
|
hr_tag.extract()
|
||||||
|
|
||||||
return soup.renderContents(None)
|
return soup.renderContents(None)
|
||||||
|
@ -86,14 +86,18 @@ class PMLMLizer(object):
|
|||||||
# This is used for adding \CX tags chapter markers. This is separate
|
# This is used for adding \CX tags chapter markers. This is separate
|
||||||
# from the optional inline toc.
|
# from the optional inline toc.
|
||||||
self.toc = {}
|
self.toc = {}
|
||||||
for item in oeb_book.toc:
|
self.create_flat_toc(self.oeb_book.toc)
|
||||||
|
|
||||||
|
return self.pmlmlize_spine()
|
||||||
|
|
||||||
|
def create_flat_toc(self, nodes, level=0):
|
||||||
|
for item in nodes:
|
||||||
href, mid, id = item.href.partition('#')
|
href, mid, id = item.href.partition('#')
|
||||||
self.get_anchor_id(href, id)
|
self.get_anchor_id(href, id)
|
||||||
if not self.toc.get(href, None):
|
if not self.toc.get(href, None):
|
||||||
self.toc[href] = {}
|
self.toc[href] = {}
|
||||||
self.toc[href][id] = item.title
|
self.toc[href][id] = (item.title, level)
|
||||||
|
self.create_flat_toc(item.nodes, level + 1)
|
||||||
return self.pmlmlize_spine()
|
|
||||||
|
|
||||||
def pmlmlize_spine(self):
|
def pmlmlize_spine(self):
|
||||||
self.image_hrefs = {}
|
self.image_hrefs = {}
|
||||||
@ -255,9 +259,10 @@ class PMLMLizer(object):
|
|||||||
toc_page = page.href
|
toc_page = page.href
|
||||||
if self.toc.get(toc_page, None):
|
if self.toc.get(toc_page, None):
|
||||||
for toc_x in (toc_name, toc_id):
|
for toc_x in (toc_name, toc_id):
|
||||||
toc_title = self.toc[toc_page].get(toc_x, None)
|
toc_title, toc_depth = self.toc[toc_page].get(toc_x, (None, 0))
|
||||||
if toc_title:
|
if toc_title:
|
||||||
text.append('\\C0="%s"' % toc_title)
|
toc_depth = max(min(toc_depth, 4), 0)
|
||||||
|
text.append('\\C%s="%s"' % (toc_depth, toc_title))
|
||||||
|
|
||||||
# Process style information that needs holds a single tag
|
# Process style information that needs holds a single tag
|
||||||
# Commented out because every page in an OEB book starts with this style
|
# Commented out because every page in an OEB book starts with this style
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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, block_to_single_line
|
||||||
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.')),
|
||||||
])
|
])
|
||||||
@ -55,8 +58,10 @@ class TXTInput(InputFormatPlugin):
|
|||||||
accelerators):
|
accelerators):
|
||||||
self.log = log
|
self.log = log
|
||||||
log.debug('Reading text from file...')
|
log.debug('Reading text from file...')
|
||||||
|
length = 0
|
||||||
|
|
||||||
txt = stream.read()
|
txt = stream.read()
|
||||||
|
|
||||||
# Get the encoding of the document.
|
# Get the encoding of the document.
|
||||||
if options.input_encoding:
|
if options.input_encoding:
|
||||||
ienc = options.input_encoding
|
ienc = options.input_encoding
|
||||||
@ -70,23 +75,12 @@ class TXTInput(InputFormatPlugin):
|
|||||||
log.debug('No input encoding specified and could not auto detect using %s' % ienc)
|
log.debug('No input encoding specified and could not auto detect using %s' % ienc)
|
||||||
txt = txt.decode(ienc, 'replace')
|
txt = txt.decode(ienc, 'replace')
|
||||||
|
|
||||||
|
# Replace entities
|
||||||
txt = _ent_pat.sub(xml_entity_to_unicode, txt)
|
txt = _ent_pat.sub(xml_entity_to_unicode, txt)
|
||||||
|
|
||||||
# Normalize line endings
|
# Normalize line endings
|
||||||
txt = normalize_line_endings(txt)
|
txt = normalize_line_endings(txt)
|
||||||
|
|
||||||
if options.formatting_type == 'auto':
|
|
||||||
options.formatting_type = detect_formatting_type(txt)
|
|
||||||
|
|
||||||
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)
|
||||||
@ -96,50 +90,68 @@ 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)
|
||||||
|
|
||||||
|
# Reformat paragraphs to block formatting based on the detected type.
|
||||||
|
# We don't check for block because the processor assumes block.
|
||||||
|
# single and print at transformed to block for processing.
|
||||||
|
if options.paragraph_type == 'single':
|
||||||
|
txt = separate_paragraphs_single_line(txt)
|
||||||
|
elif options.paragraph_type == 'print':
|
||||||
|
txt = separate_paragraphs_print_formatted(txt)
|
||||||
|
txt = block_to_single_line(txt)
|
||||||
|
elif options.paragraph_type == 'unformatted':
|
||||||
|
from calibre.ebooks.conversion.utils import HeuristicProcessor
|
||||||
|
# unwrap lines based on punctuation
|
||||||
|
docanalysis = DocAnalysis('txt', txt)
|
||||||
|
length = docanalysis.line_length(.5)
|
||||||
|
preprocessor = HeuristicProcessor(options, log=getattr(self, 'log', None))
|
||||||
|
txt = preprocessor.punctuation_unwrap(length, txt, 'txt')
|
||||||
|
txt = separate_paragraphs_single_line(txt)
|
||||||
|
else:
|
||||||
|
txt = block_to_single_line(txt)
|
||||||
|
|
||||||
|
if getattr(options, 'enable_heuristics', False) and getattr(options, 'dehyphenate', False):
|
||||||
|
docanalysis = DocAnalysis('txt', txt)
|
||||||
|
if not length:
|
||||||
|
length = docanalysis.line_length(.5)
|
||||||
|
dehyphenator = Dehyphenator(options.verbose, log=self.log)
|
||||||
|
txt = dehyphenator(txt,'txt', length)
|
||||||
|
|
||||||
|
# User requested transformation on the text.
|
||||||
|
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 entity.
|
# followed by the 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
|
# Process the text using the appropriate text processor.
|
||||||
docanalysis = DocAnalysis('txt', txt)
|
html = ''
|
||||||
length = docanalysis.line_length(.5)
|
|
||||||
|
|
||||||
if options.formatting_type == 'markdown':
|
if options.formatting_type == 'markdown':
|
||||||
log.debug('Running text though markdown conversion...')
|
log.debug('Running text through markdown conversion...')
|
||||||
try:
|
try:
|
||||||
html = convert_markdown(txt, disable_toc=options.markdown_disable_toc)
|
html = convert_markdown(txt, disable_toc=options.markdown_disable_toc)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
raise ValueError('This txt file has malformed markup, it cannot be'
|
raise ValueError('This txt file has malformed markup, it cannot be'
|
||||||
' converted by calibre. See http://daringfireball.net/projects/markdown/syntax')
|
' converted by calibre. See http://daringfireball.net/projects/markdown/syntax')
|
||||||
elif options.formatting_type == 'textile':
|
elif options.formatting_type == 'textile':
|
||||||
log.debug('Running text though textile conversion...')
|
log.debug('Running text through textile conversion...')
|
||||||
html = convert_textile(txt)
|
html = convert_textile(txt)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Dehyphenate
|
log.debug('Running text through basic conversion...')
|
||||||
dehyphenator = Dehyphenator(options.verbose, log=self.log)
|
|
||||||
txt = dehyphenator(txt,'txt', length)
|
|
||||||
|
|
||||||
# We don't check for block because the processor assumes block.
|
|
||||||
# single and print at transformed to block for processing.
|
|
||||||
|
|
||||||
if options.paragraph_type == 'single' or options.paragraph_type == 'unformatted':
|
|
||||||
txt = separate_paragraphs_single_line(txt)
|
|
||||||
elif options.paragraph_type == 'print':
|
|
||||||
txt = separate_paragraphs_print_formatted(txt)
|
|
||||||
|
|
||||||
if options.paragraph_type == 'unformatted':
|
|
||||||
from calibre.ebooks.conversion.utils import HeuristicProcessor
|
|
||||||
# get length
|
|
||||||
|
|
||||||
# unwrap lines based on punctuation
|
|
||||||
preprocessor = HeuristicProcessor(options, log=getattr(self, 'log', None))
|
|
||||||
txt = preprocessor.punctuation_unwrap(length, txt, 'txt')
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
# Run the HTMLized text through the html processing plugin.
|
||||||
from calibre.customize.ui import plugin_for_input_format
|
from calibre.customize.ui import plugin_for_input_format
|
||||||
html_input = plugin_for_input_format('html')
|
html_input = plugin_for_input_format('html')
|
||||||
for opt in html_input.options:
|
for opt in html_input.options:
|
||||||
@ -158,6 +170,7 @@ class TXTInput(InputFormatPlugin):
|
|||||||
htmlfile.write(html.encode('utf-8'))
|
htmlfile.write(html.encode('utf-8'))
|
||||||
odi = options.debug_pipeline
|
odi = options.debug_pipeline
|
||||||
options.debug_pipeline = None
|
options.debug_pipeline = None
|
||||||
|
# Generate oeb from htl conversion.
|
||||||
oeb = html_input.convert(open(htmlfile.name, 'rb'), options, 'html', log,
|
oeb = html_input.convert(open(htmlfile.name, 'rb'), options, 'html', log,
|
||||||
{})
|
{})
|
||||||
options.debug_pipeline = odi
|
options.debug_pipeline = odi
|
||||||
|
@ -18,20 +18,24 @@ from calibre.utils.cleantext import clean_ascii_chars
|
|||||||
HTML_TEMPLATE = u'<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><title>%s</title></head><body>\n%s\n</body></html>'
|
HTML_TEMPLATE = u'<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><title>%s</title></head><body>\n%s\n</body></html>'
|
||||||
|
|
||||||
def clean_txt(txt):
|
def clean_txt(txt):
|
||||||
|
'''
|
||||||
|
Run transformations on the text to put it into
|
||||||
|
consistent state.
|
||||||
|
'''
|
||||||
if isbytestring(txt):
|
if isbytestring(txt):
|
||||||
txt = txt.decode('utf-8', 'replace')
|
txt = txt.decode('utf-8', 'replace')
|
||||||
# Strip whitespace from the end of the line. Also replace
|
# Strip whitespace from the end of the line. Also replace
|
||||||
# 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
|
# Replace whitespace at the beginning of the line with
|
||||||
txt = re.sub('(?m)(?P<space>[ ]+)', lambda mo: ' ' * mo.groups('space').count(' '), txt)
|
txt = re.sub('(?m)(?P<space>^[ ]+)(?=.)', lambda mo: ' ' * mo.groups('space').count(' '), txt)
|
||||||
txt = re.sub('(?m)(?P<space>[\t]+)', lambda mo: ' ' * 4 * mo.groups('space').count('\t'), txt)
|
txt = re.sub('(?m)(?P<space>^[\t]+)(?=.)', lambda mo: ' ' * 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.
|
||||||
@ -42,6 +46,15 @@ def clean_txt(txt):
|
|||||||
return txt
|
return txt
|
||||||
|
|
||||||
def split_txt(txt, epub_split_size_kb=0):
|
def split_txt(txt, epub_split_size_kb=0):
|
||||||
|
'''
|
||||||
|
Ensure there are split points for converting
|
||||||
|
to EPUB. A misdetected paragraph type can
|
||||||
|
result in the entire document being one giant
|
||||||
|
paragraph. In this case the EPUB parser will not
|
||||||
|
be able to determine where to split the file
|
||||||
|
to accomidate the EPUB file size limitation
|
||||||
|
and will fail.
|
||||||
|
'''
|
||||||
#Takes care if there is no point to split
|
#Takes care if there is no point to split
|
||||||
if epub_split_size_kb > 0:
|
if epub_split_size_kb > 0:
|
||||||
if isinstance(txt, unicode):
|
if isinstance(txt, unicode):
|
||||||
@ -59,6 +72,12 @@ def split_txt(txt, epub_split_size_kb=0):
|
|||||||
return txt
|
return txt
|
||||||
|
|
||||||
def convert_basic(txt, title='', epub_split_size_kb=0):
|
def convert_basic(txt, title='', epub_split_size_kb=0):
|
||||||
|
'''
|
||||||
|
Converts plain text to html by putting all paragraphs in
|
||||||
|
<p> tags. It condense and retains blank lines when necessary.
|
||||||
|
|
||||||
|
Requires paragraphs to be in single line format.
|
||||||
|
'''
|
||||||
txt = clean_txt(txt)
|
txt = clean_txt(txt)
|
||||||
txt = split_txt(txt, epub_split_size_kb)
|
txt = split_txt(txt, epub_split_size_kb)
|
||||||
|
|
||||||
@ -99,14 +118,28 @@ def separate_paragraphs_single_line(txt):
|
|||||||
return txt
|
return txt
|
||||||
|
|
||||||
def separate_paragraphs_print_formatted(txt):
|
def separate_paragraphs_print_formatted(txt):
|
||||||
txt = re.sub(u'(?miu)^(\t+|[ ]{2,})(?=.)', '\n\t', txt)
|
txt = re.sub(u'(?miu)^(?P<indent>\t+|[ ]{2,})(?=.)', lambda mo: '\n%s' % mo.group('indent'), txt)
|
||||||
|
return txt
|
||||||
|
|
||||||
|
def block_to_single_line(txt):
|
||||||
|
txt = re.sub(r'(?<=.)\n(?=.)', ' ', txt)
|
||||||
return txt
|
return txt
|
||||||
|
|
||||||
def preserve_spaces(txt):
|
def preserve_spaces(txt):
|
||||||
|
'''
|
||||||
|
Replaces spaces multiple spaces with entities.
|
||||||
|
'''
|
||||||
txt = re.sub('(?P<space>[ ]{2,})', lambda mo: ' ' + (' ' * (len(mo.group('space')) - 1)), txt)
|
txt = re.sub('(?P<space>[ ]{2,})', lambda mo: ' ' + (' ' * (len(mo.group('space')) - 1)), txt)
|
||||||
txt = txt.replace('\t', ' ')
|
txt = txt.replace('\t', ' ')
|
||||||
return txt
|
return txt
|
||||||
|
|
||||||
|
def remove_indents(txt):
|
||||||
|
'''
|
||||||
|
Remove whitespace at the beginning of each line.
|
||||||
|
'''
|
||||||
|
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)
|
||||||
@ -114,7 +147,10 @@ def opf_writer(path, opf_name, manifest, spine, mi):
|
|||||||
with open(os.path.join(path, opf_name), 'wb') as opffile:
|
with open(os.path.join(path, opf_name), 'wb') as opffile:
|
||||||
opf.render(opffile)
|
opf.render(opffile)
|
||||||
|
|
||||||
def split_string_separator(txt, size) :
|
def split_string_separator(txt, size):
|
||||||
|
'''
|
||||||
|
Splits the text by putting \n\n at the point size.
|
||||||
|
'''
|
||||||
if len(txt) > size:
|
if len(txt) > size:
|
||||||
txt = ''.join([re.sub(u'\.(?P<ends>[^.]*)$', '.\n\n\g<ends>',
|
txt = ''.join([re.sub(u'\.(?P<ends>[^.]*)$', '.\n\n\g<ends>',
|
||||||
txt[i:i+size], 1) for i in
|
txt[i:i+size], 1) for i in
|
||||||
@ -123,7 +159,7 @@ def split_string_separator(txt, size) :
|
|||||||
|
|
||||||
def detect_paragraph_type(txt):
|
def detect_paragraph_type(txt):
|
||||||
'''
|
'''
|
||||||
Tries to determine the formatting of the document.
|
Tries to determine the paragraph type of the document.
|
||||||
|
|
||||||
block: Paragraphs are separated by a blank line.
|
block: Paragraphs are separated by a blank line.
|
||||||
single: Each line is a paragraph.
|
single: Each line is a paragraph.
|
||||||
@ -166,6 +202,16 @@ def detect_paragraph_type(txt):
|
|||||||
|
|
||||||
|
|
||||||
def detect_formatting_type(txt):
|
def detect_formatting_type(txt):
|
||||||
|
'''
|
||||||
|
Tries to determine the formatting of the document.
|
||||||
|
|
||||||
|
markdown: Markdown formatting is used.
|
||||||
|
textile: Textile formatting is used.
|
||||||
|
heuristic: When none of the above formatting types are
|
||||||
|
detected heuristic is returned.
|
||||||
|
'''
|
||||||
|
# Keep a count of the number of format specific object
|
||||||
|
# that are found in the text.
|
||||||
markdown_count = 0
|
markdown_count = 0
|
||||||
textile_count = 0
|
textile_count = 0
|
||||||
|
|
||||||
@ -189,6 +235,8 @@ def detect_formatting_type(txt):
|
|||||||
# Links
|
# Links
|
||||||
textile_count += len(re.findall(r'"(?=".*?\()(\(.+?\))*[^\(]+?(\(.+?\))*":[^\s]+', txt))
|
textile_count += len(re.findall(r'"(?=".*?\()(\(.+?\))*[^\(]+?(\(.+?\))*":[^\s]+', txt))
|
||||||
|
|
||||||
|
# Decide if either markdown or textile is used in the text
|
||||||
|
# based on the number of unique formatting elements found.
|
||||||
if markdown_count > 5 or textile_count > 5:
|
if markdown_count > 5 or textile_count > 5:
|
||||||
if markdown_count > textile_count:
|
if markdown_count > textile_count:
|
||||||
return 'markdown'
|
return 'markdown'
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -6,156 +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.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):
|
||||||
'''
|
'''
|
||||||
@ -169,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):
|
||||||
@ -192,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):
|
||||||
'''
|
'''
|
||||||
@ -231,15 +113,21 @@ class MultiCompleteLineEdit(QLineEdit):
|
|||||||
cursor_pos = self.cursorPosition()
|
cursor_pos = self.cursorPosition()
|
||||||
before_text = unicode(self.text())[:cursor_pos]
|
before_text = unicode(self.text())[:cursor_pos]
|
||||||
after_text = unicode(self.text())[cursor_pos:]
|
after_text = unicode(self.text())[cursor_pos:]
|
||||||
after_parts = after_text.split(self.sep)
|
|
||||||
if len(after_parts) < 3 and not after_parts[-1].strip():
|
|
||||||
after_text = u''
|
|
||||||
prefix_len = len(before_text.split(self.sep)[-1].lstrip())
|
prefix_len = len(before_text.split(self.sep)[-1].lstrip())
|
||||||
return prefix_len, \
|
if tweaks['completer_append_separator']:
|
||||||
before_text[:cursor_pos - prefix_len] + text + after_text
|
prefix_len = len(before_text.split(self.sep)[-1].lstrip())
|
||||||
|
completed_text = before_text[:cursor_pos - prefix_len] + text + self.sep + ' ' + after_text
|
||||||
|
prefix_len = prefix_len - len(self.sep) - 1
|
||||||
|
if prefix_len < 0:
|
||||||
|
prefix_len = 0
|
||||||
|
else:
|
||||||
|
prefix_len = len(before_text.split(self.sep)[-1].lstrip())
|
||||||
|
completed_text = before_text[:cursor_pos - prefix_len] + text + after_text
|
||||||
|
return prefix_len, completed_text
|
||||||
|
|
||||||
|
|
||||||
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))
|
||||||
@ -248,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):
|
||||||
@ -310,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):
|
||||||
|
@ -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)
|
||||||
|
@ -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 &spaces</string>
|
<string>Preserve &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/>
|
||||||
|
@ -89,6 +89,7 @@ class MessageBox(QDialog, Ui_Dialog):
|
|||||||
(__version__, unicode(self.windowTitle()),
|
(__version__, unicode(self.windowTitle()),
|
||||||
unicode(self.msg.text()),
|
unicode(self.msg.text()),
|
||||||
unicode(self.det_msg.toPlainText())))
|
unicode(self.det_msg.toPlainText())))
|
||||||
|
if hasattr(self, 'ctc_button'):
|
||||||
self.ctc_button.setText(_('Copied'))
|
self.ctc_button.setText(_('Copied'))
|
||||||
|
|
||||||
def showEvent(self, ev):
|
def showEvent(self, ev):
|
||||||
|
@ -951,8 +951,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
for w in getattr(self, 'custom_column_widgets', []):
|
for w in getattr(self, 'custom_column_widgets', []):
|
||||||
self.books_to_refresh |= w.commit(self.id)
|
self.books_to_refresh |= w.commit(self.id)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
except IOError, err:
|
except (IOError, OSError) as err:
|
||||||
if err.errno == 13: # Permission denied
|
if getattr(err, 'errno', -1) == 13: # Permission denied
|
||||||
fname = err.filename if err.filename else 'file'
|
fname = err.filename if err.filename else 'file'
|
||||||
return error_dialog(self, _('Permission denied'),
|
return error_dialog(self, _('Permission denied'),
|
||||||
_('Could not open %s. Is it being used by another'
|
_('Could not open %s. Is it being used by another'
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
@ -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,26 +4450,30 @@ 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 == ' ':
|
||||||
publisherTag = body.find('td', attrs={'class':'publisher'})
|
publisherTag = body.find('td', attrs={'class':'publisher'})
|
||||||
|
if publisherTag:
|
||||||
publisherTag.contents[0].replaceWith(' ')
|
publisherTag.contents[0].replaceWith(' ')
|
||||||
|
|
||||||
if not genres:
|
if not genres:
|
||||||
genresTag = body.find('p',attrs={'class':'genres'})
|
genresTag = body.find('p',attrs={'class':'genres'})
|
||||||
|
if genresTag:
|
||||||
genresTag.extract()
|
genresTag.extract()
|
||||||
|
|
||||||
if not formats:
|
if not formats:
|
||||||
formatsTag = body.find('p',attrs={'class':'formats'})
|
formatsTag = body.find('p',attrs={'class':'formats'})
|
||||||
|
if formatsTag:
|
||||||
formatsTag.extract()
|
formatsTag.extract()
|
||||||
|
|
||||||
if note_content == '':
|
if note_content == '':
|
||||||
tdTag = body.find('td', attrs={'class':'notes'})
|
tdTag = body.find('td', attrs={'class':'notes'})
|
||||||
|
if tdTag:
|
||||||
tdTag.contents[0].replaceWith(' ')
|
tdTag.contents[0].replaceWith(' ')
|
||||||
|
|
||||||
emptyTags = body.findAll('td', attrs={'class':'empty'})
|
emptyTags = body.findAll('td', attrs={'class':'empty'})
|
||||||
|
@ -445,7 +445,7 @@ class CustomColumns(object):
|
|||||||
rv = self._set_custom(id, val, label=label, num=num, append=append,
|
rv = self._set_custom(id, val, label=label, num=num, append=append,
|
||||||
notify=notify, extra=extra,
|
notify=notify, extra=extra,
|
||||||
allow_case_change=allow_case_change)
|
allow_case_change=allow_case_change)
|
||||||
self.dirtied([id], commit=False)
|
self.dirtied(set([id])|rv, commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
return rv
|
return rv
|
||||||
|
@ -414,7 +414,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
row = self.data._data[index] if index_is_id else self.data[index]
|
row = self.data._data[index] if index_is_id else self.data[index]
|
||||||
return row[self.FIELD_MAP['path']].replace('/', os.sep)
|
return row[self.FIELD_MAP['path']].replace('/', os.sep)
|
||||||
|
|
||||||
|
|
||||||
def abspath(self, index, index_is_id=False, create_dirs=True):
|
def abspath(self, index, index_is_id=False, create_dirs=True):
|
||||||
'Return the absolute path to the directory containing this books files as a unicode string.'
|
'Return the absolute path to the directory containing this books files as a unicode string.'
|
||||||
path = os.path.join(self.library_path, self.path(index, index_is_id=index_is_id))
|
path = os.path.join(self.library_path, self.path(index, index_is_id=index_is_id))
|
||||||
@ -422,7 +421,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
def construct_path_name(self, id):
|
def construct_path_name(self, id):
|
||||||
'''
|
'''
|
||||||
Construct the directory name for this book based on its metadata.
|
Construct the directory name for this book based on its metadata.
|
||||||
@ -432,6 +430,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
authors = _('Unknown')
|
authors = _('Unknown')
|
||||||
author = ascii_filename(authors.split(',')[0])[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace')
|
author = ascii_filename(authors.split(',')[0])[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace')
|
||||||
title = ascii_filename(self.title(id, index_is_id=True))[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace')
|
title = ascii_filename(self.title(id, index_is_id=True))[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace')
|
||||||
|
while author[-1] in (' ', '.'):
|
||||||
|
author = author[:-1]
|
||||||
|
if not author:
|
||||||
|
author = ascii_filename(_('Unknown')).decode(filesystem_encoding, 'replace')
|
||||||
path = author + '/' + title + ' (%d)'%id
|
path = author + '/' + title + ' (%d)'%id
|
||||||
return path
|
return path
|
||||||
|
|
||||||
@ -1692,7 +1694,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
'''
|
'''
|
||||||
books_to_refresh = self._set_authors(id, authors,
|
books_to_refresh = self._set_authors(id, authors,
|
||||||
allow_case_change=allow_case_change)
|
allow_case_change=allow_case_change)
|
||||||
self.dirtied([id], commit=False)
|
self.dirtied(set([id])|books_to_refresh, commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.set_path(id, index_is_id=True)
|
self.set_path(id, index_is_id=True)
|
||||||
@ -1768,7 +1770,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
self.conn.execute('''DELETE FROM publishers WHERE (SELECT COUNT(id)
|
self.conn.execute('''DELETE FROM publishers WHERE (SELECT COUNT(id)
|
||||||
FROM books_publishers_link
|
FROM books_publishers_link
|
||||||
WHERE publisher=publishers.id) < 1''')
|
WHERE publisher=publishers.id) < 1''')
|
||||||
books_to_refresh = set()
|
books_to_refresh = set([])
|
||||||
if publisher:
|
if publisher:
|
||||||
case_change = False
|
case_change = False
|
||||||
if not isinstance(publisher, unicode):
|
if not isinstance(publisher, unicode):
|
||||||
@ -1793,7 +1795,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
bks = self.conn.get('''SELECT book FROM books_publishers_link
|
bks = self.conn.get('''SELECT book FROM books_publishers_link
|
||||||
WHERE publisher=?''', (aid,))
|
WHERE publisher=?''', (aid,))
|
||||||
books_to_refresh |= set([bk[0] for bk in bks])
|
books_to_refresh |= set([bk[0] for bk in bks])
|
||||||
self.dirtied([id], commit=False)
|
self.dirtied(set([id])|books_to_refresh, commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.set(id, self.FIELD_MAP['publisher'], publisher, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['publisher'], publisher, row_is_id=True)
|
||||||
@ -2206,7 +2208,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
bks = self.conn.get('SELECT book FROM books_tags_link WHERE tag=?',
|
bks = self.conn.get('SELECT book FROM books_tags_link WHERE tag=?',
|
||||||
(tid,))
|
(tid,))
|
||||||
books_to_refresh |= set([bk[0] for bk in bks])
|
books_to_refresh |= set([bk[0] for bk in bks])
|
||||||
self.dirtied([id], commit=False)
|
self.dirtied(set([id])|books_to_refresh, commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
tags = u','.join(self.get_tags(id))
|
tags = u','.join(self.get_tags(id))
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -4,9 +4,9 @@
|
|||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: calibre 0.7.43\n"
|
"Project-Id-Version: calibre 0.7.44\n"
|
||||||
"POT-Creation-Date: 2011-02-03 22:52+MST\n"
|
"POT-Creation-Date: 2011-02-04 11:04+MST\n"
|
||||||
"PO-Revision-Date: 2011-02-03 22:52+MST\n"
|
"PO-Revision-Date: 2011-02-04 11:04+MST\n"
|
||||||
"Last-Translator: Automatically generated\n"
|
"Last-Translator: Automatically generated\n"
|
||||||
"Language-Team: LANGUAGE\n"
|
"Language-Team: LANGUAGE\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
@ -10663,11 +10663,11 @@ msgstr ""
|
|||||||
msgid "The saved search name %s is already used."
|
msgid "The saved search name %s is already used."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1313
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1321
|
||||||
msgid "Find item in tag browser"
|
msgid "Find item in tag browser"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1316
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1324
|
||||||
msgid ""
|
msgid ""
|
||||||
"Search for items. This is a \"contains\" search; items containing the\n"
|
"Search for items. This is a \"contains\" search; items containing the\n"
|
||||||
"text anywhere in the name will be found. You can limit the search\n"
|
"text anywhere in the name will be found. You can limit the search\n"
|
||||||
@ -10677,59 +10677,59 @@ msgid ""
|
|||||||
"containing the text \"foo\""
|
"containing the text \"foo\""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1325
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1333
|
||||||
msgid "ALT+f"
|
msgid "ALT+f"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1329
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1337
|
||||||
msgid "F&ind"
|
msgid "F&ind"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1330
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1338
|
||||||
msgid "Find the first/next matching item"
|
msgid "Find the first/next matching item"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1337
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1345
|
||||||
msgid "Collapse all categories"
|
msgid "Collapse all categories"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1358
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1366
|
||||||
msgid "No More Matches.</b><p> Click Find again to go to first match"
|
msgid "No More Matches.</b><p> Click Find again to go to first match"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1371
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1379
|
||||||
msgid "Sort by name"
|
msgid "Sort by name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1371
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1379
|
||||||
msgid "Sort by popularity"
|
msgid "Sort by popularity"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1372
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1380
|
||||||
msgid "Sort by average rating"
|
msgid "Sort by average rating"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1375
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1383
|
||||||
msgid "Set the sort order for entries in the Tag Browser"
|
msgid "Set the sort order for entries in the Tag Browser"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1381
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1389
|
||||||
msgid "Match all"
|
msgid "Match all"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1381
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1389
|
||||||
msgid "Match any"
|
msgid "Match any"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1386
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1394
|
||||||
msgid "When selecting multiple entries in the Tag Browser match any or all of them"
|
msgid "When selecting multiple entries in the Tag Browser match any or all of them"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1390
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1398
|
||||||
msgid "Manage &user categories"
|
msgid "Manage &user categories"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1393
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1401
|
||||||
msgid "Add your own categories to the Tag Browser"
|
msgid "Add your own categories to the Tag Browser"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -11905,7 +11905,7 @@ msgstr ""
|
|||||||
msgid "No books available to include in catalog"
|
msgid "No books available to include in catalog"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/catalog.py:5012
|
#: /home/kovid/work/calibre/src/calibre/library/catalog.py:5024
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
"*** Adding 'By Authors' Section required for MOBI output ***"
|
"*** Adding 'By Authors' Section required for MOBI output ***"
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user