KG updates

This commit is contained in:
GRiker 2011-05-19 06:41:39 -06:00
commit 0e9f711c5d
63 changed files with 1916 additions and 561 deletions

46
recipes/bild_de.recipe Normal file
View File

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
title = u'Bild.de'
__author__ = 'schuster'
oldest_article = 1
max_articles_per_feed = 50
no_stylesheets = True
use_embedded_content = False
language = 'de'
remove_javascript = True
# get cover from myspace
cover_url = 'http://a3.l3-images.myspacecdn.com/images02/56/0232f842170b4d349779f8379c27e073/l.jpg'
# set what to fetch on the site
remove_tags_before = dict(name = 'h2', attrs={'id':'cover'})
remove_tags_after = dict(name ='div', attrs={'class':'back'})
# thanx to kiklop74 for code (see sticky thread -> Recipes - Re-usable code)
# this one removes a lot of direct-link's
def preprocess_html(self, soup):
for alink in soup.findAll('a'):
if alink.string is not None:
tstr = alink.string
alink.replaceWith(tstr)
return soup
# remove the ad's
filter_regexps = [r'.\.smartadserver\.com']
def skip_ad_pages(self, soup):
return None
#get the real url behind .feedsportal.com and fetch the artikels
def get_article_url(self, article):
return article.get('id', article.get('guid', None))
#list of the rss source from www.bild.de
feeds = [(u'Überblick', u'http://rss.bild.de/bild.xml'),
(u'News', u'http://rss.bild.de/bild-news.xml'),
(u'Politik', u'http://rss.bild.de/bild-politik.xml'),
(u'Unterhaltung', u'http://rss.bild.de/bild-unterhaltung.xml'),
(u'Sport', u'http://rss.bild.de/bild-sport.xml'),
(u'Lifestyle', u'http://rss.bild.de/bild-lifestyle.xml'),
(u'Ratgeber', u'http://rss.bild.de/bild-ratgeber.xml')
]

View File

@ -0,0 +1,33 @@
from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
title = u'Börse-online'
__author__ = 'schuster'
oldest_article = 1
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
language = 'de'
remove_javascript = True
cover_url = 'http://www.dpv.de/images/1995/source.gif'
masthead_url = 'http://www.zeitschriften-cover.de/cover/boerse-online-cover-januar-2010-x1387.jpg'
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
'''
remove_tags_bevor = [dict(name='h3')]
remove_tags_after = [dict(name='div', attrs={'class':'artikelfuss'})]
remove_tags = [dict(attrs={'class':['moduleTopNav', 'moduleHeaderNav', 'text', 'blau', 'poll1150']}),
dict(id=['newsletterlayer', 'newsletterlayerClose', 'newsletterlayer_body', 'newsletterarray_error', 'newsletterlayer_emailadress', 'newsletterlayer_submit', 'kommentar']),
dict(name=['h2', 'Gesamtranking', 'h3',''])]
def print_version(self, url):
return url.replace('.html#nv=rss', '.html?mode=print')
feeds = [(u'Börsennachrichten', u'http://www.boerse-online.de/rss/')]

61
recipes/capital_de.recipe Normal file
View File

@ -0,0 +1,61 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1305470859(BasicNewsRecipe):
title = u'Capital.de'
language = 'de'
__author__ = 'schuster'
oldest_article =7
max_articles_per_feed = 35
no_stylesheets = True
remove_javascript = True
use_embedded_content = False
masthead_url = 'http://www.wirtschaftsmedien-shop.de/media/stores/wirtschaftsmedien/capital/teaser_large_abo.jpg'
cover_url = 'http://d1kb9jvg6ylufe.cloudfront.net/WebsiteCMS/de/unternehmen/linktipps/mainColumn/08/image/DE_Capital_bis20mm_SW.jpg'
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
'''
def print_version(self, url):
return url.replace ('nv=rss#utm_source=rss2&utm_medium=rss_feed&utm_campaign=/', 'mode=print')
remove_tags_bevor = [dict(name='td', attrs={'class':'textcell'})]
remove_tags_after = [dict(name='div', attrs={'class':'artikelsplit'})]
feeds = [ (u'Wirtschaftsmagazin', u'http://www.capital.de/rss/'),
(u'Unternehmen', u'http://www.capital.de/rss/unternehmen'),
(u'Finanz & Geldanlage', u'http://www.capital.de/rss/finanzen/geldanlage')]
def append_page(self, soup, appendtag, position):
pager = soup.find('div',attrs={'class':'artikelsplit'})
if pager:
nexturl = self.INDEX + pager.a['href']
soup2 = self.index_to_soup(nexturl)
texttag = soup2.find('div', attrs={'class':'printable'})
for it in texttag.findAll(style=True):
del it['style']
newpos = len(texttag.contents)
self.append_page(soup2,texttag,newpos)
texttag.extract()
appendtag.insert(position,texttag)
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
for item in soup.findAll('div', attrs={'class':'artikelsplit'}):
item.extract()
self.append_page(soup, soup.body, 3)
pager = soup.find('div',attrs={'class':'artikelsplit'})
if pager:
pager.extract()
return self.adeify_images(soup)
remove_tags = [dict(attrs={'class':['navSeitenAlle', 'kommentieren', 'teaserheader', 'teasercontent', 'info', 'zwischenhead', 'artikelsplit']}),
dict(id=['topNav', 'mainNav', 'subNav', 'socialmedia', 'footerRahmen', 'gatrixx_marktinformationen', 'pager', 'weitere']),
dict(span=['ratingtext', 'Gesamtranking', 'h3','']),
dict(rel=['canonical'])]

View File

@ -0,0 +1,34 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1305567197(BasicNewsRecipe):
title = u'Cosmopolitan.de'
__author__ = 'schuster'
oldest_article = 7
language = 'de'
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
remove_javascript = True
cover_url = 'http://www.cosmopolitan.com/cm/shared/site_images/print_this/cosmopolitan_logo.gif'
remove_tags_before = dict(name = 'h1', attrs={'class':'artikel'})
remove_tags_after = dict(name ='div', attrs={'class':'morePages'})
extra_css = '''
h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small;}
h1{ font-family:Arial,Helvetica,sans-serif; font-size:x-large; font-weight:bold;}
'''
remove_tags = [ dict(id='strong'),
dict(title='strong'),
dict(name='span'),
dict(name='li', attrs={'class':'large'}),
dict(name='ul', attrs={'class':'articleImagesPortrait clearfix'}),
dict(name='p', attrs={'class':'external'}),
dict(name='a', attrs={'target':'_blank'}),]
feeds = [ (u'Komplett', u'http://www.cosmopolitan.de/rss/allgemein.xml'),
(u'Mode', u'http://www.cosmopolitan.de/rss/mode.xml'),
(u'Beauty', u'http://www.cosmopolitan.de/rss/beauty.xml'),
(u'Liebe&Sex', u'http://www.cosmopolitan.de/rss/liebe.xml'),
(u'Psychologie', u'http://www.cosmopolitan.de/rss/psychologie.xml'),
(u'Job&Karriere', u'http://www.cosmopolitan.de/rss/job.xml'),
(u'Lifestyle', u'http://www.cosmopolitan.de/rss/lifestyle.xml'),
(u'Shopping', u'http://www.cosmopolitan.de/rss/shopping.xml'),
(u'Bildergalerien', u'http://www.cosmopolitan.de/rss/bildgalerien.xml')]

View File

@ -37,7 +37,7 @@ class DN_se(BasicNewsRecipe):
,(u'Kultur' , u'http://www.dn.se/kultur-rss' ) ,(u'Kultur' , u'http://www.dn.se/kultur-rss' )
] ]
keep_only_tags = [dict(name='div', attrs={'id':'article'})] keep_only_tags = [dict(name='div', attrs={'id':'article-content'})]
remove_tags_before = dict(name='h1') remove_tags_before = dict(name='h1')
remove_tags_after = dict(name='div',attrs={'id':'byline'}) remove_tags_after = dict(name='div',attrs={'id':'byline'})
remove_tags = [ remove_tags = [

View File

@ -1,19 +1,21 @@
import re
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1302341394(BasicNewsRecipe): class AdvancedUserRecipe1302341394(BasicNewsRecipe):
title = u'DvhN' title = u'DvhN'
oldest_article = 1 __author__ = 'Reijndert'
oldest_article = 7
max_articles_per_feed = 200 max_articles_per_feed = 200
__author__ = 'Reijndert'
no_stylesheets = True no_stylesheets = True
cover_url = 'http://www.dvhn.nl/template/Dagblad_v2.0/gfx/logo_DvhN.gif' cover_url = 'http://members.home.nl/apm.de.haas/calibre/DvhN.jpg'
language = 'nl' language = 'nl'
country = 'NL' country = 'NL'
version = 1 version = 1
publisher = u'Dagblad van het Noorden' publisher = u'Dagblad van het Noorden'
category = u'Nieuws' category = u'Nieuws'
description = u'Nieuws uit Noord Nederland' description = u'Nieuws uit Noord Nederland'
timefmt = ' %Y-%m-%d (%a)'
keep_only_tags = [dict(name='div', attrs={'id':'fullPicture'}) keep_only_tags = [dict(name='div', attrs={'id':'fullPicture'})
@ -21,11 +23,26 @@ class AdvancedUserRecipe1302341394(BasicNewsRecipe):
] ]
remove_tags = [ remove_tags = [
dict(name=['object','link','iframe','base']) dict(name='span',attrs={'class':'location'})
,dict(name='span',attrs={'class':'copyright'})
] ]
feeds = [(u'Drenthe', u'http://www.dvhn.nl/nieuws/drenthe/index.jsp?service=rss'), (u'Groningen', u'http://www.dvhn.nl/nieuws/groningen/index.jsp?service=rss'), (u'Nederland', u'http://www.dvhn.nl/nieuws/nederland/index.jsp?service=rss'), (u'Wereld', u'http://www.dvhn.nl/nieuws/wereld/index.jsp?service=rss'), (u'Economie', u'http://www.dvhn.nl/nieuws/economie/index.jsp?service=rss'), (u'Sport', u'http://www.dvhn.nl/nieuws/sport/index.jsp?service=rss'), (u'Cultuur', u'http://www.dvhn.nl/nieuws/kunst/index.jsp?service=rss'), (u'24 Uur', u'http://www.dvhn.nl/nieuws/24uurdvhn/index.jsp?service=rss&selectiontype=last24hours')] preprocess_regexps = [
(re.compile(r'<a.*?>'), lambda h1: '')
,(re.compile(r'</a>'), lambda h2: '')
,(re.compile(r'Word vriend van Dagblad van het Noorden op Facebook'), lambda h3: '')
,(re.compile(r'Volg Dagblad van het Noorden op Twitter'), lambda h3: '')
]
feeds = [(u'Drenthe', u'http://www.dvhn.nl/nieuws/drenthe/index.jsp?service=rss')
, (u'Groningen', u'http://www.dvhn.nl/nieuws/groningen/index.jsp?service=rss')
, (u'Nederland', u'http://www.dvhn.nl/nieuws/nederland/index.jsp?service=rss')
, (u'Wereld', u'http://www.dvhn.nl/nieuws/wereld/index.jsp?service=rss')
, (u'Economie', u'http://www.dvhn.nl/nieuws/economie/index.jsp?service=rss')
, (u'Sport', u'http://www.dvhn.nl/nieuws/sport/index.jsp?service=rss')
, (u'Cultuur', u'http://www.dvhn.nl/nieuws/kunst/index.jsp?service=rss')
, (u'24 Uur', u'http://www.dvhn.nl/nieuws/24uurdvhn/index.jsp?service=rss&selectiontype=last24hours')
]
extra_css = ''' extra_css = '''
body {font-family: verdana, arial, helvetica, geneva, sans-serif;} body {font-family: verdana, arial, helvetica, geneva, sans-serif;}

View File

@ -20,7 +20,7 @@ class Economist(BasicNewsRecipe):
INDEX = 'http://www.economist.com/printedition' INDEX = 'http://www.economist.com/printedition'
description = ('Global news and current affairs from a European' description = ('Global news and current affairs from a European'
' perspective. Best downloaded on Friday mornings (GMT)') ' perspective. Best downloaded on Friday mornings (GMT)')
extra_css = '.headline {font-size: x-large;} \n h2 { font-size: small; } \n h1 { font-size: medium; }'
oldest_article = 7.0 oldest_article = 7.0
cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg' cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
remove_tags = [ remove_tags = [

74
recipes/express_de.recipe Normal file
View File

@ -0,0 +1,74 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
title = u'Express.de'
__author__ = 'schuster'
oldest_article = 2
max_articles_per_feed = 50
no_stylesheets = True
use_embedded_content = False
language = 'de'
extra_css = '''
h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small;}
h1{ font-family:Arial,Helvetica,sans-serif; font-size:x-large; font-weight:bold;}
'''
remove_javascript = True
remove_tags_befor = [dict(name='div', attrs={'class':'Datum'})]
remove_tags_after = [dict(name='div', attrs={'class':'MoreNews'})]
remove_tags = [dict(id='kalaydo'),
dict(id='Header'),
dict(id='Searchline'),
dict(id='MainNav'),
dict(id='Logo'),
dict(id='MainLinkSpacer'),
dict(id='MainLinks'),
dict(title='Diese Seite Bookmarken'),
dict(name='span'),
dict(name='div', attrs={'class':'spacer_leftneu'}),
dict(name='div', attrs={'class':'button kalaydologo'}),
dict(name='div', attrs={'class':'button stellenneu'}),
dict(name='div', attrs={'class':'button autoneu'}),
dict(name='div', attrs={'class':'button immobilienneu'}),
dict(name='div', attrs={'class':'button kleinanzeigen'}),
dict(name='div', attrs={'class':'button tiereneu'}),
dict(name='div', attrs={'class':'button ferienwohnungen'}),
dict(name='div', attrs={'class':'button inserierenneu'}),
dict(name='div', attrs={'class':'spacer_rightneu'}),
dict(name='div', attrs={'class':'spacer_rightcorner'}),
dict(name='div', attrs={'class':'HeaderMetaNav'}),
dict(name='div', attrs={'class':'HeaderSearchOption'}),
dict(name='div', attrs={'class':'HeaderSearch'}),
dict(name='div', attrs={'class':'sbutton'}),
dict(name='div', attrs={'class':'active'}),
]
def preprocess_html(self, soup):
for alink in soup.findAll('a'):
if alink.string is not None:
tstr = alink.string
alink.replaceWith(tstr)
return soup
feeds = [(u'Top-Themen', u'http://www.express.de/home/-/2126/2126/-/view/asFeed/-/index.xml'),
(u'Regional - Köln', u'http://www.express.de/regional/koeln/-/2856/2856/-/view/asFeed/-/index.xml'),
(u'Regional - Bonn', u'http://www.express.de/regional/bonn/-/2860/2860/-/view/asFeed/-/index.xml'),
(u'Regional - Düsseldorf', u'http://www.express.de/regional/duesseldorf/-/2858/2858/-/view/asFeed/-/index.xml'),
(u'Regional - Region', u'http://www.express.de/regional/-/2178/2178/-/view/asFeed/-/index.xml'),
(u'Sport-News', u'http://www.express.de/sport/-/2176/2176/-/view/asFeed/-/index.xml'),
(u'Fussball-News', u'http://www.express.de/sport/fussball/-/3186/3186/-/view/asFeed/-/index.xml'),
(u'1.FC Köln News', u'http://www.express.de/sport/fussball/fc-koeln/-/3192/3192/-/view/asFeed/-/index.xml'),
(u'Alemannia Aachen News', u'http://www.express.de/sport/fussball/alemannia/-/3290/3290/-/view/asFeed/-/index.xml'),
(u'Borussia M~Gladbach', u'http://www.express.de/sport/fussball/gladbach/-/3286/3286/-/view/asFeed/-/index.xml'),
(u'Fortuna D~Dorf', u'http://www.express.de/sport/fussball/fortuna/-/3292/3292/-/view/asFeed/-/index.xml'),
(u'Basketball News', u'http://www.express.de/sport/basketball/-/3190/3190/-/view/asFeed/-/index.xml'),
(u'Big Brother', u'http://www.express.de/news/promi-show/big-brother/-/2402/2402/-/view/asFeed/-/index.xml'),
]

View File

@ -1,51 +1,38 @@
__license__ = 'GPL v3' from calibre.web.feeds.recipes import BasicNewsRecipe
__copyright__ = '2008-2009, Kovid Goyal <kovid at kovidgoyal.net>, Darko Miletic <darko at gmail.com>' class AdvancedUserRecipe1303841067(BasicNewsRecipe):
'''
Profile to download FAZ.net
'''
from calibre.web.feeds.news import BasicNewsRecipe title = u'Faz.net'
__author__ = 'schuster'
class FazNet(BasicNewsRecipe): remove_tags = [dict(attrs={'class':['right', 'ArrowLinkRight', 'ModulVerlagsInfo', 'left', 'Head']}),
title = 'FAZ NET' dict(id=['BreadCrumbs', 'tstag', 'FazFooterPrint']),
__author__ = 'Kovid Goyal, Darko Miletic' dict(name=['script', 'noscript', 'style'])]
oldest_article = 2
description = 'Frankfurter Allgemeine Zeitung' description = 'Frankfurter Allgemeine Zeitung'
publisher = 'FAZ Electronic Media GmbH' max_articles_per_feed = 100
category = 'news, politics, Germany' no_stylesheets = True
use_embedded_content = False use_embedded_content = False
language = 'de' language = 'de'
remove_javascript = True
max_articles_per_feed = 30 cover_url = 'http://www.faz.net/f30/Images/Logos/logo.gif'
no_stylesheets = True
encoding = 'utf-8'
remove_javascript = True
html2lrf_options = [
'--comment', description
, '--category', category
, '--publisher', publisher
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
keep_only_tags = [dict(name='div', attrs={'class':'Article'})]
remove_tags = [
dict(name=['object','link','embed','base'])
,dict(name='div', attrs={'class':['LinkBoxModulSmall','ModulVerlagsInfo']})
]
feeds = [ ('FAZ.NET', 'http://www.faz.net/s/Rub/Tpl~Epartner~SRss_.xml') ]
def print_version(self, url): def print_version(self, url):
article, sep, rest = url.partition('?') return url.replace('.html', '~Afor~Eprint.html')
return article.replace('.html', '~Afor~Eprint.html')
feeds = [(u'Politik', u'http://www.faz.net/s/RubA24ECD630CAE40E483841DB7D16F4211/Tpl~Epartner~SRss_.xml'),
(u'Wirtschaft', u'http://www.faz.net/s/RubC9401175958F4DE28E143E68888825F6/Tpl~Epartner~SRss_.xml'),
(u'Feuilleton', u'http://www.faz.net/s/RubCC21B04EE95145B3AC877C874FB1B611/Tpl~Epartner~SRss_.xml'),
(u'Sport', u'http://www.faz.net/s/Rub9F27A221597D4C39A82856B0FE79F051/Tpl~Epartner~SRss_.xml'),
(u'Gesellschaft', u'http://www.faz.net/s/Rub02DBAA63F9EB43CEB421272A670A685C/Tpl~Epartner~SRss_.xml'),
(u'Finanzen', u'http://www.faz.net/s/Rub4B891837ECD14082816D9E088A2D7CB4/Tpl~Epartner~SRss_.xml'),
(u'Wissen', u'http://www.faz.net/s/Rub7F4BEE0E0C39429A8565089709B70C44/Tpl~Epartner~SRss_.xml'),
(u'Reise', u'http://www.faz.net/s/RubE2FB5CA667054BDEA70FB3BC45F8D91C/Tpl~Epartner~SRss_.xml'),
(u'Technik & Motor', u'http://www.faz.net/s/Rub01E4D53776494844A85FDF23F5707AD8/Tpl~Epartner~SRss_.xml'),
(u'Beruf & Chance', u'http://www.faz.net/s/RubB1E10A8367E8446897468EDAA6EA0504/Tpl~Epartner~SRss_.xml'),
(u'Kunstmarkt', u'http://www.faz.net/s/RubBC09F7BF72A2405A96718ECBFB68FBFE/Tpl~Epartner~SRss_.xml'),
(u'Immobilien ', u'http://www.faz.net/s/RubFED172A9E10F46B3A5F01B02098C0C8D/Tpl~Epartner~SRss_.xml'),
(u'Rhein-Main Zeitung', u'http://www.faz.net/s/RubABE881A6669742C2A5EBCB5D50D7EBEE/Tpl~Epartner~SRss_.xml'),
(u'Atomdebatte ', u'http://www.faz.net/s/Rub469C43057F8C437CACC2DE9ED41B7950/Tpl~Epartner~SRss_.xml')
]
def preprocess_html(self, soup):
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>'
soup.head.insert(0,mtag)
del soup.body['onload']
for item in soup.findAll(style=True):
del item['style']
return soup

View File

@ -0,0 +1,64 @@
__license__ = 'GPL v3'
__copyright__ = '2011, Darko Miletic <darko.miletic at gmail.com>'
'''
www.financialsense.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class FinancialSense(BasicNewsRecipe):
title = 'Financial Sense'
__author__ = 'Darko Miletic'
description = 'Uncommon News & Views for the Wise Investor'
publisher = 'Financial Sense'
category = 'news, finances, politics, USA'
oldest_article = 2
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = False
language = 'en'
remove_empty_feeds = True
publication_type = 'newsportal'
masthead_url = 'http://www.financialsense.com/sites/default/files/logo.jpg'
extra_css = """
body{font-family: Arial,"Helvetica Neue",Helvetica,sans-serif }
img{margin-bottom: 0.4em; display:block}
h2{color: gray}
.name{margin-right: 5em}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
remove_tags =[dict(name=['meta','link','base','object','embed','iframe'])]
remove_tags_after=dict(attrs={'class':'vcard'})
keep_only_tags =[dict(attrs={'class':['title','post-meta','content','item-title','vcard']})]
remove_attributes=['lang','type']
feeds = [(u'Articles', u'http://feeds.feedburner.com/fso')]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
for item in soup.findAll('a'):
limg = item.find('img')
if item.string is not None:
str = item.string
item.replaceWith(str)
else:
if limg:
item.name = 'div'
item.attrs = []
else:
str = self.tag_to_string(item)
item.replaceWith(str)
for item in soup.findAll('img'):
if not item.has_key('alt'):
item['alt'] = 'image'
return soup

38
recipes/glamour.recipe Normal file
View File

@ -0,0 +1,38 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1305547242(BasicNewsRecipe):
title = u'Glamour (US)'
oldest_article = 21
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
language = 'en'
remove_javascript = True
__author__ = 'Anonymous'
remove_tags = [dict(name='div', attrs={'class':'articles_footer', 'class':'printoptions'})]
def print_version(self, url):
return url + '?printable=true'
def preprocess_html(self, soup):
for alink in soup.findAll('a'):
if alink.string is not None:
tstr = alink.string
alink.replaceWith(tstr)
return soup
feeds = [ (u'All Fashion', u'http://feeds.glamour.com/glamour/all_fashion'),
(u'All Beauty', u'http://feeds.glamour.com/glamour/all_beauty'),
(u'All Sex, Love & Life', u'http://feeds.glamour.com/glamour/sex_love_life'),
(u'All Health & Fitness', u'http://feeds.glamour.com/glamour/health_fitness'),
(u'Shopping', u'http://feeds.glamour.com/glamour/shopping'),
(u'Slaves to Fashion blog', u'http://feeds.glamour.com/glamour/slavestofashion'),
(u'The Girls in the Beauty Department', u'http://feeds.glamour.com/glamour/thegirlsinthebeautydepartment'),
(u'Smitten blog', u'http://feeds.glamour.com/glamour/smitten'),
(u'Save the Date', u'http://feeds.feedburner.com/glamour/save-the-date'),
(u'Single-ish blog', u'http://feeds.glamour.com/glamour/glamoursingle-ish'),
(u'Save the Date', u'http://feeds.feedburner.com/glamour/save-the-date'),
(u'Vitamin G blog', u'http://feeds.glamour.com/glamour/vitamin-g'),
(u'Margarita Shapes Up blog', u'http://feeds.glamour.com/glamour/margaritashapesup'),
(u'Little Miss Fortune blog', u'http://feeds.glamour.com/glamour/little-miss-fortune'),
]

View File

@ -1,83 +1,70 @@
#!/usr/bin/env python from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
from calibre.web.feeds.news import BasicNewsRecipe
class golem_ger(BasicNewsRecipe):
title = u'Golem.de' title = u'Golem.de'
language = 'de' __author__ = 'schuster'
__author__ = 'Kovid Goyal'
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 100 max_articles_per_feed = 10
language = 'de' no_stylesheets = True
lang = 'de-DE' use_embedded_content = False
no_stylesheets = True language = 'de'
encoding = 'iso-8859-1' cover_url = 'http://www.e-energy.de/images/logo_golem.jpg'
recursions = 1 masthead_url = 'http://www.golem.de/staticrl/images/logo.png'
match_regexps = [r'http://www.golem.de/.*.html']
keep_only_tags = [
dict(name='h1', attrs={'class':'artikelhead'}),
dict(name='p', attrs={'class':'teaser'}),
dict(name='div', attrs={'class':'artikeltext'}),
dict(name='h2', attrs={'id':'artikelhead'}),
]
remove_tags = [
dict(name='div', attrs={'id':['similarContent','topContentWrapper','storycarousel','aboveFootPromo','comments','toolbar','breadcrumbs','commentlink','sidebar','rightColumn']}),
dict(name='div', attrs={'class':['gg_embeddedSubText','gg_embeddedIndex gg_solid','gg_toOldGallery','golemGallery']}),
dict(name='img', attrs={'class':['gg_embedded','gg_embeddedIconRight gg_embeddedIconFS gg_cursorpointer']}),
dict(name='td', attrs={'class':['xsmall']}),
]
# remove_tags_after = [
# dict(name='div', attrs={'id':['contentad2']})
# ]
feeds = [
(u'Golem.de', u'http://rss.golem.de/rss.php?feed=ATOM1.0'),
(u'Audio/Video', u'http://rss.golem.de/rss.php?tp=av&feed=RSS2.0'),
(u'Foto', u'http://rss.golem.de/rss.php?tp=foto&feed=RSS2.0'),
(u'Games', u'http://rss.golem.de/rss.php?tp=games&feed=RSS2.0'),
(u'Internet', u'http://rss.golem.de/rss.php?tp=inet&feed=RSS1.0'),
(u'Mobil', u'http://rss.golem.de/rss.php?tp=mc&feed=ATOM1.0'),
(u'Internet', u'http://rss.golem.de/rss.php?tp=inet&feed=RSS1.0'),
(u'Politik/Recht', u'http://rss.golem.de/rss.php?tp=pol&feed=ATOM1.0'),
(u'Desktop-Applikationen', u'http://rss.golem.de/rss.php?tp=apps&feed=RSS2.0'),
(u'Software-Entwicklung', u'http://rss.golem.de/rss.php?tp=dev&feed=RSS2.0'),
(u'Wirtschaft', u'http://rss.golem.de/rss.php?tp=wirtschaft&feed=RSS2.0'),
(u'Hardware', u'http://rss.golem.de/rss.php?r=hw&feed=RSS2.0'),
(u'Software', u'http://rss.golem.de/rss.php?r=sw&feed=RSS2.0'),
(u'Networld', u'http://rss.golem.de/rss.php?r=nw&feed=RSS2.0'),
(u'Entertainment', u'http://rss.golem.de/rss.php?r=et&feed=RSS2.0'),
(u'TK', u'http://rss.golem.de/rss.php?r=tk&feed=RSS2.0'),
(u'E-Commerce', u'http://rss.golem.de/rss.php?r=ec&feed=RSS2.0'),
(u'Unternehmen/Maerkte', u'http://rss.golem.de/rss.php?r=wi&feed=RSS2.0')
]
feeds = [
(u'Golem.de', u'http://rss.golem.de/rss.php?feed=ATOM1.0'),
(u'Mobil', u'http://rss.golem.de/rss.php?tp=mc&feed=feed=RSS2.0'),
(u'OSS', u'http://rss.golem.de/rss.php?tp=oss&feed=RSS2.0'),
(u'Politik/Recht', u'http://rss.golem.de/rss.php?tp=pol&feed=RSS2.0'),
(u'Desktop-Applikationen', u'http://rss.golem.de/rss.php?tp=apps&feed=RSS2.0'),
(u'Software-Entwicklung', u'http://rss.golem.de/rss.php?tp=dev&feed=RSS2.0'),
]
extra_css = ''' extra_css = '''
h1 {color:#0066CC;font-family:Arial,Helvetica,sans-serif; font-size:30px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:20px;margin-bottom:2 em;} h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small;}
h2 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:22px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; } h1{ font-family:Arial,Helvetica,sans-serif; font-size:x-large; font-weight:bold;}
h3 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:x-small; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:normal; line-height:5px;}
h4 {color:#333333; font-family:Arial,Helvetica,sans-serif;font-size:13px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:13px; }
h5 {color:#333333; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:11px; text-transform:uppercase;}
.teaser {font-style:italic;font-size:12pt;margin-bottom:15pt;}
.xsmall{font-style:italic;font-size:x-small;}
.td{font-style:italic;font-size:x-small;}
img {align:left;}
''' '''
remove_javascript = True
remove_tags_befor = [dict(name='header', attrs={'class':'cluster-header'})]
remove_tags_after = [dict(name='p', attrs={'class':'meta'})]
remove_tags = [dict(rel='nofollow'),
dict(name='header', attrs={'id':'header'}),
dict(name='div', attrs={'class':'dh1'}),
dict(name='label', attrs={'class':'implied'}),
dict(name='section', attrs={'id':'comments'}),
dict(name='li', attrs={'class':'gg_prebackcounterItem'}),
dict(name='li', attrs={'class':'gg_prebackcounterItem gg_embeddedIndexCounter'}),
dict(name='img', attrs={'class':'gg_embeddedIconRight gg_embeddedIconFS gg_cursorpointer'}),
dict(name='div', attrs={'target':'_blank'})
]
def get_browser(self, *args, **kwargs):
from calibre import browser
kwargs['user_agent'] = 'mozilla'
return browser(*args, **kwargs)
def get_article_url(self, article):
return article.get('id', article.get('guid', None))
def preprocess_html(self, soup):
for alink in soup.findAll('a'):
if alink.string is not None:
tstr = alink.string
alink.replaceWith(tstr)
return soup
feeds = [(u'Audio/Video', u'http://rss.golem.de/rss.php?tp=av&feed=RSS2.0'),
(u'Foto', u'http://rss.golem.de/rss.php?tp=foto&feed=RSS2.0'),
(u'Games', u'http://rss.golem.de/rss.php?tp=games&feed=RSS2.0'),
(u'Handy', u'http://rss.golem.de/rss.php?tp=handy&feed=RSS2.0'),
(u'Internet', u'http://rss.golem.de/rss.php?tp=inet&feed=RSS2.0'),
(u'Mobile', u'http://rss.golem.de/rss.php?tp=mc&feed=RSS2.0'),
(u'OSS', u'http://rss.golem.de/rss.php?tp=oss&feed=RSS2.0'),
(u'Politik/Recht', u'http://rss.golem.de/rss.php?tp=pol&feed=RSS2.0'),
(u'Security', u'http://rss.golem.de/rss.php?tp=sec&feed=RSS2.0'),
(u'Desktop-Applikationen', u'http://rss.golem.de/rss.php?tp=apps&feed=RSS2.0'),
(u'Software-Entwicklung', u'http://rss.golem.de/rss.php?tp=dev&feed=RSS2.0'),
(u'Wirtschaft', u'http://rss.golem.de/rss.php?tp=wirtschaft&feed=RSS2.0'),
(u'Hardware', u'http://rss.golem.de/rss.php?r=hw&feed=RSS2.0'),
(u'Software', u'http://rss.golem.de/rss.php?r=sw&feed=RSS2.0'),
(u'Networld', u'http://rss.golem.de/rss.php?r=nw&feed=RSS2.0'),
(u'Entertainment', u'http://rss.golem.de/rss.php?r=et&feed=RSS2.0'),
(u'TK', u'http://rss.golem.de/rss.php?r=tk&feed=RSS2.0'),
(u'Wirtschaft', u'http://rss.golem.de/rss.php?r=wi&feed=RSS2.0'),
(u'E-Commerce', u'http://rss.golem.de/rss.php?r=ec&feed=RSS2.0')
]

View File

@ -0,0 +1,31 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1305547242(BasicNewsRecipe):
title = u'Good House Keeping'
language = 'en'
__author__ = 'Anonymous'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
remove_javascript = True
def print_version(self,url):
segments = url.split('/')
printURL = '/'.join(segments[0:3]) + '/print-this/' + '/'.join(segments[4:])
return printURL
def preprocess_html(self, soup):
for alink in soup.findAll('a'):
if alink.string is not None:
tstr = alink.string
alink.replaceWith(tstr)
return soup
feeds = [ (u'Recipes & Entertaining', u'http://www.goodhousekeeping.com/food/food-rss/?src=rss'),
(u'Home & House', u'http://www.goodhousekeeping.com/home/home-rss/?src=rss'),
(u'Diet & Health', u'http://www.goodhousekeeping.com/health/health-rss/?src=rss'),
(u'Beauty & Style', u'http://www.goodhousekeeping.com/beauty/beauty-rss/?src=rss'),
(u'Family & Pets', u'http://www.goodhousekeeping.com/family/family-rss/?src=rss'),
(u'Saving Money', u'http://www.goodhousekeeping.com/money/money-rss/?src=rss'),
]

View File

@ -0,0 +1,32 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1305547242(BasicNewsRecipe):
title = u'Good to Know (uk)'
oldest_article = 14
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
remove_javascript = True
__author__ = 'Anonymous'
language = 'en_GB'
remove_tags = [dict(name='div', attrs={'class':'articles_footer', 'class':'printoptions'})]
def print_version(self, url):
return url + '/print/1'
def preprocess_html(self, soup):
for alink in soup.findAll('a'):
if alink.string is not None:
tstr = alink.string
alink.replaceWith(tstr)
return soup
feeds = [ (u'Family Conception Advice', u'http://www.goodtoknow.co.uk/feeds/family.rss'),
(u'Family Health Advice', u'http://www.goodtoknow.co.uk/feeds/health.rss'),
(u'Diet Advice', u'http://www.goodtoknow.co.uk/feeds/diet.rss'),
(u'Food Advice', u'http://www.goodtoknow.co.uk/feeds/food.rss'),
(u'Sex Advice', u'http://www.goodtoknow.co.uk/feeds/sex.rss'),
(u'Easy Exercise', u'http://www.goodtoknow.co.uk/feeds/easyexercise.rss'),
(u'Recipes', u'http://www.goodtoknow.co.uk/feeds/recipes.rss'),
(u'Food Quick-tips', u'http://www.goodtoknow.co.uk/feeds/foodquicktips.rss'),
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
recipes/icons/osnews_pl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

32
recipes/impulse_de.recipe Normal file
View File

@ -0,0 +1,32 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1305470859(BasicNewsRecipe):
title = u'Impulse.de'
language = 'de'
__author__ = 'schuster'
oldest_article =14
max_articles_per_feed = 100
no_stylesheets = True
remove_javascript = True
use_embedded_content = False
cover_url = 'http://www.bvk.de/files/image/bilder/Logo%20Impulse.jpg'
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
'''
def print_version(self, url):
return url.replace ('#utm_source=rss2&utm_medium=rss_feed&utm_campaign=/', '?mode=print')
remove_tags_bevor = [dict(name='h1', attrs={'class':'h2'})]
remove_tags_after = [dict(name='div', attrs={'class':'artikelfuss'})]
feeds = [ (u'impulstest', u'http://www.impulse.de/rss/')]
remove_tags = [dict(attrs={'class':['navSeitenAlle', 'kommentieren', 'teaserheader', 'teasercontent', 'info', 'zwischenhead', 'kasten_artikel']}),
dict(id=['metaNav', 'impKopf', 'impTopNav', 'impSubNav', 'footerRahmen', 'gatrixx_marktinformationen', 'pager', 'weitere', 'socialmedia', 'rating_open']),
dict(span=['ratingtext', 'Gesamtranking', 'h3','']),
dict(rel=['canonical'])]

View File

@ -0,0 +1,79 @@
__license__ = 'GPL v3'
__copyright__ = '2011, Darko Miletic <darko.miletic at gmail.com>'
'''
www.iprofesional.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class iProfesional(BasicNewsRecipe):
title = 'iProfesional.com'
__author__ = 'Darko Miletic'
description = 'Las ultimas noticias sobre profesionales'
publisher = 'Emprendimientos Corporativos S.A.'
category = 'news, IT, impuestos, negocios, politics, Argentina'
oldest_article = 2
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = False
language = 'es_AR'
remove_empty_feeds = True
publication_type = 'nesportal'
masthead_url = 'http://www.iprofesional.com/img/logo-iprofesional.png'
extra_css = """
body{font-family: Arial,Helvetica,sans-serif }
img{margin-bottom: 0.4em; display:block}
.titulo-interior{font-family: Georgia,"Times New Roman",Times,serif}
.autor-nota{font-size: small; font-weight: bold; font-style: italic; color: gray}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
keep_only_tags = [dict(attrs={'class':['fecha','interior-nota']})]
remove_tags = [
dict(name=['meta','link','base','embed','object','iframe'])
,dict(attrs={'class':['menu-imprimir','guardarNota','IN-widget','fin','permalink']})
]
remove_attributes=['lang','xmlns:og','xmlns:fb']
feeds = [
(u'Ultimas noticias' , u'http://feeds.feedburner.com/iprofesional-principales-noticias')
,(u'Finanzas' , u'http://feeds.feedburner.com/iprofesional-finanzas' )
,(u'Impuestos' , u'http://feeds.feedburner.com/iprofesional-impuestos' )
,(u'Negocios' , u'http://feeds.feedburner.com/iprofesional-economia' )
,(u'Comercio Exterior' , u'http://feeds.feedburner.com/iprofesional-comercio-exterior' )
,(u'Tecnologia' , u'http://feeds.feedburner.com/iprofesional-tecnologia' )
,(u'Management' , u'http://feeds.feedburner.com/iprofesional-managment' )
,(u'Marketing' , u'http://feeds.feedburner.com/iprofesional-marketing' )
,(u'Legales' , u'http://feeds.feedburner.com/iprofesional-legales' )
,(u'Autos' , u'http://feeds.feedburner.com/iprofesional-autos' )
,(u'Vinos' , u'http://feeds.feedburner.com/iprofesional-vinos-bodegas' )
]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
for item in soup.findAll('a'):
limg = item.find('img')
if item.string is not None:
str = item.string
item.replaceWith(str)
else:
if limg:
item.name = 'div'
item.attrs = []
else:
str = self.tag_to_string(item)
item.replaceWith(str)
for item in soup.findAll('img'):
if not item.has_key('alt'):
item['alt'] = 'image'
return soup

22
recipes/max_planck.recipe Normal file
View File

@ -0,0 +1,22 @@
from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
title = u'Max-Planck-Inst.'
__author__ = 'schuster'
remove_tags = [dict(attrs={'class':['clearfix', 'lens', 'col2_box_list', 'col2_box_teaser group_ext no_print', 'dotted_line', 'col2_box_teaser', 'box_image small', 'bold', 'col2_box_teaser no_print', 'print_kontakt']}),
dict(id=['ie_clearing', 'col2', 'col2_content']),
dict(name=['script', 'noscript', 'style'])]
oldest_article = 30
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
language = 'de'
remove_javascript = True
def print_version(self, url):
split_url = url.split("/")
print_url = 'http://www.mpg.de/print/' + split_url[3]
return print_url
feeds = [(u'Forschung', u'http://www.mpg.de/de/forschung.rss')]

View File

@ -0,0 +1,10 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1305636254(BasicNewsRecipe):
title = u'Mens Health (US)'
language = 'en'
__author__ = 'Anonymous'
oldest_article = 14
max_articles_per_feed = 100
feeds = [(u'News', u'http://blogs.menshealth.com/health-headlines/feed')]

View File

@ -11,6 +11,20 @@ class Newsweek(BasicNewsRecipe):
BASE_URL = 'http://www.newsweek.com' BASE_URL = 'http://www.newsweek.com'
topics = {
'Culture' : '/tag/culture.html',
'Business' : '/tag/business.html',
'Society' : '/tag/society.html',
'Science' : '/tag/science.html',
'Education' : '/tag/education.html',
'Politics' : '/tag/politics.html',
'Health' : '/tag/health.html',
'World' : '/tag/world.html',
'Nation' : '/tag/nation.html',
'Technology' : '/tag/technology.html',
'Game Changers' : '/tag/game-changers.html',
}
keep_only_tags = dict(name='article', attrs={'class':'article-text'}) keep_only_tags = dict(name='article', attrs={'class':'article-text'})
remove_tags = [dict(attrs={'data-dartad':True})] remove_tags = [dict(attrs={'data-dartad':True})]
remove_attributes = ['property'] remove_attributes = ['property']
@ -21,14 +35,10 @@ class Newsweek(BasicNewsRecipe):
return soup return soup
def newsweek_sections(self): def newsweek_sections(self):
return [ for topic_name, topic_url in self.topics.iteritems():
('Nation', 'http://www.newsweek.com/tag/nation.html'), yield (topic_name,
('Society', 'http://www.newsweek.com/tag/society.html'), self.BASE_URL+topic_url)
('Culture', 'http://www.newsweek.com/tag/culture.html'),
('World', 'http://www.newsweek.com/tag/world.html'),
('Politics', 'http://www.newsweek.com/tag/politics.html'),
('Business', 'http://www.newsweek.com/tag/business.html'),
]
def newsweek_parse_section_page(self, soup): def newsweek_parse_section_page(self, soup):
for article in soup.findAll('article', about=True, for article in soup.findAll('article', about=True,

29
recipes/ngz.recipe Normal file
View File

@ -0,0 +1,29 @@
from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
title = u'NGZ-online'
__author__ = 'schuster'
remove_tags_before = dict(id='bu')
remove_tags_after = dict(id='noblock')
remove_tags = [dict(attrs={'class':['articleTools', 'post-tools', 'side_tool', 'nextArticleLink clearfix', 'liketext']}),
dict(id=['footer', 'toolsRight', 'articleInline', 'navigation', 'archive', 'side_search', 'blog_sidebar', 'side_tool', 'side_index', 'Verlinken', 'vorheriger', 'LESERKOMMENTARE', 'bei facebook', 'bei twitter', 'Schreiben Sie jetzt Ihre Meinung:', 'Thema', 'Ihr Beitrag', 'Ihr Name', 'Ich möchte über weitere Lesermeinungen zu diesem Artikel per E-Mail informiert werden.', 'banneroben', 'bannerrechts', 'inserieren', 'stellen', 'auto', 'immobilien', 'kleinanzeige', 'tiere', 'ferienwohnung', 'NGZ Card', 'Mediengruppe RP', 'Werben', 'Newsletter', 'Wetter', 'RSS', 'Abo', 'Anzeigen', 'Redaktion', 'Schulprojekte', 'Gast', 'Mein NGZ', 'Nachrichten', 'Sport', 'Wirtschaft', 'Stadt-Infos', 'Bilderserien', 'Bookmarken', 'del.icio.us', 'Mister Wong', 'YiGG', 'Webnews', 'Shortnews', 'Twitter', 'Newsider', 'Facebook', 'StudiVZ/MeinVZ', 'Versenden', 'Drucken']),
dict(name=['script', 'noscript', 'style'])]
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
language = 'de'
remove_javascript = True
cover_url = 'http://www.rhein-kreis-neuss-macht-sport.de/sport/includes/bilder/ngz_logo.jpg'
def print_version(self, url):
return url + '?ot=de.circit.rpo.PopupPageLayout.ot'
feeds = [
(u'Grevenbroich', u'http://www.ngz-online.de/app/feed/rss/grevenbroich'),
(u'Kreis Neuss', u'http://www.ngz-online.de/app/feed/rss/rheinkreisneuss'),
(u'Dormagen', u'http://www.ngz-online.de/app/feed/rss/dormagen'),
(u'J\xfcchen', u'http://www.ngz-online.de/app/feed/rss/juechen'),
(u'Rommerskirchen', u'http://www.ngz-online.de/app/feed/rss/rommerskirchen')
]

22
recipes/pro_physik.recipe Normal file
View File

@ -0,0 +1,22 @@
from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
title = u'Pro Physik'
__author__ = 'schuster'
oldest_article = 4
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
language = 'de'
remove_javascript = True
cover_url = 'http://www.pro-physik.de/Phy/images/site/prophysik_logo1.jpg'
def print_version(self, url):
return url.replace('leadArticle.do', 'print.do')
feeds = [(u'Hightech', u'http://www.pro-physik.de/Phy/hightechfeed.xml'),
(u'Forschung', u'http://www.pro-physik.de/Phy/forschungfeed.xml'),
(u'Magazin', u'http://www.pro-physik.de/Phy/magazinfeed.xml')]

28
recipes/spektrum.recipe Normal file
View File

@ -0,0 +1,28 @@
from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
title = u'Spektrum (der Wissenschaft)'
__author__ = 'schuster'
oldest_article = 7
max_articles_per_feed = 100
language = 'de'
cover_url = 'http://upload.wikimedia.org/wikipedia/de/3/3b/Spektrum_der_Wissenschaft_Logo.svg'
remove_tags = [dict(attrs={'class':['hauptnaviPkt gainlayout', 'hauptnaviButton', 'suchButton', 'suchbegriffKasten', 'loginButton', 'subnavigation', 'artikelInfoLeiste gainlayout', 'artikelTools', 'nurLetzteSeite', 'link', 'boxUnterArtikel', 'leserbriefeBlock', 'boxTitel', 'boxInhalt', 'sehrklein', 'boxabstand', 'werbeboxinhalt', 'rbabstand', 'bildlinks', 'rechtebox', 'denkmalbox', 'denkmalfrage']}),
dict(id=['pflip', 'verlagsleiste', 'bereich', 'bannerVertikal', 'headerLogoLink', 'kopf', 'topNavi', 'headerSchnellsuche', 'headerSchnellsucheWarten', 'navigation', 'navigationL', 'navigationR', 'inhalt', 'rechtespalte', 'sdwboxenshop', 'shopboxen', 'fuss']),
dict(name=['naservice'])]
def print_version(self,url):
newurl = url.replace('artikel/', 'sixcms/detail.php?id=')
return newurl + '&_druckversion=1'
feeds = [(u'Spektrum der Wissenschaft', u'http://www.spektrum.de/artikel/982623'),
(u'SpektrumDirekt', u'http://www.spektrumdirekt.de/artikel/996406'),
(u'Sterne und Weltraum', u'http://www.astronomie-heute.de/artikel/865248'),
(u'Gehirn & Geist', u'http://www.gehirn-und-geist.de/artikel/982626'),
(u'epoc', u'http://www.epoc.de/artikel/982625')
]
filter_regexps = [r'ads\.doubleclick\.net']

View File

@ -0,0 +1,24 @@
from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
title = u'Technology Review'
__author__ = 'schuster'
remove_tags_before = dict(id='keywords')
remove_tags_after = dict(id='kommentar')
remove_tags = [dict(attrs={'class':['navi_oben_pvg', 'navi_oben_tarifr', 'navi_oben_itm', 'navi_oben_eve', 'navi_oben_whi', 'navi_oben_abo', 'navi_oben_shop', 'navi_top_logo', 'navi_top_abschnitt', 'first']}),
dict(id=['footer', 'toolsRight', 'articleInline', 'navigation', 'archive', 'side_search', 'blog_sidebar', 'side_tool', 'side_index']),
dict(name=['script', 'noscript', 'style'])]
oldest_article = 4
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
language = 'de'
remove_javascript = True
def print_version(self, url):
return url + '?view=print'
feeds = [
(u'Technik News', u'http://www.heise.de/tr/news-atom.xml') ]

View File

@ -14,6 +14,7 @@ class UnitedDaily(BasicNewsRecipe):
(u'生活', u'http://udn.com/udnrss/life.xml'), (u'生活', u'http://udn.com/udnrss/life.xml'),
(u'綜合', u'http://udn.com/udnrss/education.xml'), (u'綜合', u'http://udn.com/udnrss/education.xml'),
(u'意見評論', u'http://udn.com/udnrss/opinion.xml'), (u'意見評論', u'http://udn.com/udnrss/opinion.xml'),
(u'校園博覽會', u'http://mag.udn.com/udnrss/campus_rss.xml'),
(u'大台北', u'http://udn.com/udnrss/local_taipei.xml'), (u'大台北', u'http://udn.com/udnrss/local_taipei.xml'),
(u'桃竹苗', u'http://udn.com/udnrss/local_tyhcml.xml'), (u'桃竹苗', u'http://udn.com/udnrss/local_tyhcml.xml'),
(u'中彰投', u'http://udn.com/udnrss/local_tcchnt.xml'), (u'中彰投', u'http://udn.com/udnrss/local_tcchnt.xml'),
@ -21,15 +22,21 @@ class UnitedDaily(BasicNewsRecipe):
(u'高屏離島', u'http://udn.com/udnrss/local_ksptisland.xml'), (u'高屏離島', u'http://udn.com/udnrss/local_ksptisland.xml'),
(u'基宜花東', u'http://udn.com/udnrss/local_klilhltt.xml'), (u'基宜花東', u'http://udn.com/udnrss/local_klilhltt.xml'),
(u'台灣百寶鄉', u'http://udn.com/udnrss/local_oddlyenough.xml'), (u'台灣百寶鄉', u'http://udn.com/udnrss/local_oddlyenough.xml'),
(u'台灣人物', u'http://mag.udn.com/udnrss/people_rss.xml'),
(u'兩岸要聞', u'http://udn.com/udnrss/mainland.xml'), (u'兩岸要聞', u'http://udn.com/udnrss/mainland.xml'),
(u'國際焦點', u'http://udn.com/udnrss/international.xml'), (u'國際焦點', u'http://udn.com/udnrss/international.xml'),
(u'台商經貿', u'http://udn.com/udnrss/financechina.xml'), (u'台商經貿', u'http://udn.com/udnrss/financechina.xml'),
(u'國際財經', u'http://udn.com/udnrss/financeworld.xml'), (u'國際財經', u'http://udn.com/udnrss/financeworld.xml'),
(u'全球觀察', u'http://mag.udn.com/udnrss/world_rss.xml'),
(u'財經焦點', u'http://udn.com/udnrss/financesfocus.xml'), (u'財經焦點', u'http://udn.com/udnrss/financesfocus.xml'),
(u'股市要聞', u'http://udn.com/udnrss/stock.xml'), (u'股市要聞', u'http://udn.com/udnrss/stock.xml'),
(u'股市快訊', u'http://udn.com/udnrss/stklatest.xml'), (u'股市快訊', u'http://udn.com/udnrss/stklatest.xml'),
(u'稅務法務', u'http://udn.com/udnrss/tax.xml'), (u'稅務法務', u'http://udn.com/udnrss/tax.xml'),
(u'房市情報', u'http://udn.com/udnrss/houses.xml'), (u'房市情報', u'http://udn.com/udnrss/houses.xml'),
(u'個人理財', u'http://mag.udn.com/udnrss/wealth_rss.xml'),
(u'研究報告', u'http://mag.udn.com/udnrss/report_rss.xml'),
(u'基金', u'http://mag.udn.com/udnrss/fund_rss.xml'),
(u'理財會客室', u'http://mag.udn.com/udnrss/m_forum_rss.xml'),
(u'棒球', u'http://udn.com/udnrss/baseball.xml'), (u'棒球', u'http://udn.com/udnrss/baseball.xml'),
(u'籃球', u'http://udn.com/udnrss/basketball.xml'), (u'籃球', u'http://udn.com/udnrss/basketball.xml'),
(u'體壇動態', u'http://udn.com/udnrss/sportsfocus.xml'), (u'體壇動態', u'http://udn.com/udnrss/sportsfocus.xml'),
@ -40,19 +47,24 @@ class UnitedDaily(BasicNewsRecipe):
(u'電影世界', u'http://udn.com/udnrss/movie.xml'), (u'電影世界', u'http://udn.com/udnrss/movie.xml'),
(u'流行音樂', u'http://udn.com/udnrss/music.xml'), (u'流行音樂', u'http://udn.com/udnrss/music.xml'),
(u'觀點專題', u'http://udn.com/udnrss/starssubject.xml'), (u'觀點專題', u'http://udn.com/udnrss/starssubject.xml'),
(u'消費流行', u'http://mag.udn.com/udnrss/happylife_rss.xml'),
(u'食樂指南', u'http://udn.com/udnrss/food.xml'), (u'食樂指南', u'http://udn.com/udnrss/food.xml'),
(u'數位資訊', u'http://mag.udn.com/udnrss/digital_rss.xml'),
(u'折扣好康', u'http://udn.com/udnrss/shopping.xml'), (u'折扣好康', u'http://udn.com/udnrss/shopping.xml'),
(u'發燒車訊', u'http://mag.udn.com/udnrss/car_rss.xml'),
(u'醫藥新聞', u'http://udn.com/udnrss/health.xml'), (u'醫藥新聞', u'http://udn.com/udnrss/health.xml'),
(u'家婦繽紛', u'http://udn.com/udnrss/benfen.xml'), (u'家婦繽紛', u'http://udn.com/udnrss/benfen.xml'),
(u'談星論命', u'http://udn.com/udnrss/astrology.xml'), (u'談星論命', u'http://udn.com/udnrss/astrology.xml'),
(u'文化副刊', u'http://udn.com/udnrss/reading.xml'), (u'文化副刊', u'http://udn.com/udnrss/reading.xml'),
(u'旅遊休閒', u'http://travel.udn.com/udnrss/travel_rss.xml'),
(u'健康醫藥', u'http://mag.udn.com/udnrss/life_rss.xml'),
] ]
extra_css = '''div[id='story_title'] {font-size:200%; font-weight:bold;}''' extra_css = '''div[id='story_title'] {font-size:200%; font-weight:bold;} td[class='story_title'] {font-size:200%; font-weight:bold;} td[class='story_title'] td[class='story_title']>div {font-size:200%; font-weight:bold;}'''
__author__ = 'Eddie Lau' __author__ = 'Eddie Lau'
__version__ = '1.0' __version__ = '1.1'
language = 'zh' language = 'zh-TW'
publisher = 'United Daily News Group' publisher = 'United Daily News Group'
description = 'United Daily (Taiwan)' description = 'United Daily (Taiwan)'
category = 'News, Chinese, Taiwan' category = 'News, Chinese, Taiwan'
@ -63,5 +75,12 @@ class UnitedDaily(BasicNewsRecipe):
conversion_options = {'linearize_tables':True} conversion_options = {'linearize_tables':True}
masthead_url = 'http://udn.com/NEWS/2004/images/logo_udn.gif' masthead_url = 'http://udn.com/NEWS/2004/images/logo_udn.gif'
cover_url = 'http://udn.com/NEWS/2004/images/logo_udn.gif' cover_url = 'http://udn.com/NEWS/2004/images/logo_udn.gif'
keep_only_tags = [dict(name='div', attrs={'id':['story_title','story_author', 'story']})] keep_only_tags = [dict(name='td', attrs={'class':['story_title']}),
dict(name='div', attrs={'id':['story_title']}),
dict(name='td', attrs={'class':['story_author']}),
dict(name='div', attrs={'id':['story_author']}),
dict(name='td', attrs={'class':['story']}),
dict(name='div', attrs={'id':['story']}),
]
remove_tags = [dict(name='div', attrs={'id':['mvouter']})] remove_tags = [dict(name='div', attrs={'id':['mvouter']})]

View File

@ -41,14 +41,19 @@ authors_completer_append_separator = False
#: Author sort name algorithm #: Author sort name algorithm
# 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 default algorithm) # invert: use "fn ln" -> "ln, fn"
# copy : copy author to author_sort without modification # copy : copy author to author_sort without modification
# comma : use 'copy' if there is a ',' in the name, otherwise use 'invert' # comma : use 'copy' if there is a ',' in the name, otherwise use 'invert'
# nocomma : "fn ln" -> "ln fn" (without the comma) # nocomma : "fn ln" -> "ln fn" (without the comma)
# When this tweak is changed, the author_sort values stored with each author # When this tweak is changed, the author_sort values stored with each author
# must be recomputed by right-clicking on an author in the left-hand tags pane, # must be recomputed by right-clicking on an author in the left-hand tags pane,
# selecting 'manage authors', and pressing 'Recalculate all author sort values'. # selecting 'manage authors', and pressing 'Recalculate all author sort values'.
# The author name suffixes are words that are ignored when they occur at the
# end of an author name. The case of the suffix is ignored and trailing
# periods are automatically handled.
author_sort_copy_method = 'comma' author_sort_copy_method = 'comma'
author_name_suffixes = ('Jr', 'Sr', 'Inc', 'Ph.D', 'Phd',
'MD', 'M.D', 'I', 'II', 'III', 'IV')
#: Use author sort in Tag Browser #: Use author sort in Tag Browser
# Set which author field to display in the tags pane (the list of authors, # Set which author field to display in the tags pane (the list of authors,

View File

@ -32,7 +32,6 @@ class Win32(VMInstaller):
FREEZE_TEMPLATE = 'python -OO setup.py {freeze_command} --no-ice' FREEZE_TEMPLATE = 'python -OO setup.py {freeze_command} --no-ice'
INSTALLER_EXT = 'msi' INSTALLER_EXT = 'msi'
SHUTDOWN_CMD = ['shutdown.exe', '-s', '-f', '-t', '0'] SHUTDOWN_CMD = ['shutdown.exe', '-s', '-f', '-t', '0']
BUILD_BUILD = ['python setup.py kakasi',] + VMInstaller.BUILD_BUILD
def download_installer(self): def download_installer(self):
installer = self.installer() installer = self.installer()

View File

@ -6,10 +6,10 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os, cPickle, re, anydbm, shutil, marshal, zipfile, glob import os, cPickle, re, shutil, marshal, zipfile, glob
from zlib import compress from zlib import compress
from setup import Command, basenames, __appname__, iswindows from setup import Command, basenames, __appname__
def get_opts_from_parser(parser): def get_opts_from_parser(parser):
def do_opt(opt): def do_opt(opt):
@ -34,12 +34,12 @@ class Kakasi(Command):
self.records = {} self.records = {}
src = self.j(self.KAKASI_PATH, 'kakasidict.utf8') src = self.j(self.KAKASI_PATH, 'kakasidict.utf8')
dest = self.j(self.RESOURCES, 'localization', dest = self.j(self.RESOURCES, 'localization',
'pykakasi','kanwadict2.db') 'pykakasi','kanwadict2.pickle')
base = os.path.dirname(dest) base = os.path.dirname(dest)
if not os.path.exists(base): if not os.path.exists(base):
os.makedirs(base) os.makedirs(base)
if self.newer(dest, src) or iswindows: if self.newer(dest, src):
self.info('\tGenerating Kanwadict') self.info('\tGenerating Kanwadict')
for line in open(src, "r"): for line in open(src, "r"):
@ -50,7 +50,7 @@ class Kakasi(Command):
dest = self.j(self.RESOURCES, 'localization', dest = self.j(self.RESOURCES, 'localization',
'pykakasi','itaijidict2.pickle') 'pykakasi','itaijidict2.pickle')
if self.newer(dest, src) or iswindows: if self.newer(dest, src):
self.info('\tGenerating Itaijidict') self.info('\tGenerating Itaijidict')
self.mkitaiji(src, dest) self.mkitaiji(src, dest)
@ -58,7 +58,7 @@ class Kakasi(Command):
dest = self.j(self.RESOURCES, 'localization', dest = self.j(self.RESOURCES, 'localization',
'pykakasi','kanadict2.pickle') 'pykakasi','kanadict2.pickle')
if self.newer(dest, src) or iswindows: if self.newer(dest, src):
self.info('\tGenerating kanadict') self.info('\tGenerating kanadict')
self.mkkanadict(src, dest) self.mkkanadict(src, dest)
@ -75,7 +75,7 @@ class Kakasi(Command):
continue continue
pair = re.sub(r'\\u([0-9a-fA-F]{4})', lambda x:unichr(int(x.group(1),16)), line) pair = re.sub(r'\\u([0-9a-fA-F]{4})', lambda x:unichr(int(x.group(1),16)), line)
dic[pair[0]] = pair[1] dic[pair[0]] = pair[1]
cPickle.dump(dic, open(dst, 'w'), protocol=-1) #pickle cPickle.dump(dic, open(dst, 'wb'), protocol=-1) #pickle
def mkkanadict(self, src, dst): def mkkanadict(self, src, dst):
dic = {} dic = {}
@ -87,7 +87,7 @@ class Kakasi(Command):
continue continue
(alpha, kana) = line.split(' ') (alpha, kana) = line.split(' ')
dic[kana] = alpha dic[kana] = alpha
cPickle.dump(dic, open(dst, 'w'), protocol=-1) #pickle cPickle.dump(dic, open(dst, 'wb'), protocol=-1) #pickle
def parsekdict(self, line): def parsekdict(self, line):
line = line.decode("utf-8").strip() line = line.decode("utf-8").strip()
@ -115,16 +115,11 @@ class Kakasi(Command):
self.records[key][kanji]=[(yomi, tail)] self.records[key][kanji]=[(yomi, tail)]
def kanwaout(self, out): def kanwaout(self, out):
try: with open(out, 'wb') as f:
# Needed as otherwise anydbm tries to create a gdbm db when the db dic = {}
# created on Unix is found for k, v in self.records.iteritems():
os.remove(out) dic[k] = compress(marshal.dumps(v))
except: cPickle.dump(dic, f, -1)
pass
dic = anydbm.open(out, 'n')
for (k, v) in self.records.iteritems():
dic[k] = compress(marshal.dumps(v))
dic.close()
def clean(self): def clean(self):
kakasi = self.j(self.RESOURCES, 'localization', 'pykakasi') kakasi = self.j(self.RESOURCES, 'localization', 'pykakasi')

View File

@ -630,6 +630,24 @@ def human_readable(size):
size = size[:-2] size = size[:-2]
return size + " " + suffix return size + " " + suffix
def remove_bracketed_text(src,
brackets={u'(':u')', u'[':u']', u'{':u'}'}):
from collections import Counter
counts = Counter()
buf = []
src = force_unicode(src)
rmap = dict([(v, k) for k, v in brackets.iteritems()])
for char in src:
if char in brackets:
counts[char] += 1
elif char in rmap:
idx = rmap[char]
if counts[idx] > 0:
counts[idx] -= 1
elif sum(counts.itervalues()) < 1:
buf.append(char)
return u''.join(buf)
if isosx: if isosx:
import glob, shutil import glob, shutil
fdir = os.path.expanduser('~/.fonts') fdir = os.path.expanduser('~/.fonts')

View File

@ -1,4 +1,5 @@
import os.path # -*- coding: utf-8 -*-
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
@ -1094,19 +1095,25 @@ plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions,
# Store plugins {{{ # Store plugins {{{
class StoreAmazonKindleStore(StoreBase): class StoreAmazonKindleStore(StoreBase):
name = 'Amazon Kindle' name = 'Amazon Kindle'
description = _('Kindle books from Amazon') description = _('Kindle books from Amazon.')
actual_plugin = 'calibre.gui2.store.amazon_plugin:AmazonKindleStore' actual_plugin = 'calibre.gui2.store.amazon_plugin:AmazonKindleStore'
class StoreAmazonDEKindleStore(StoreBase): class StoreAmazonDEKindleStore(StoreBase):
name = 'Amazon DE Kindle' name = 'Amazon DE Kindle'
description = _('Kindle eBooks') description = _('Kindle books from Amazon.de.')
actual_plugin = 'calibre.gui2.store.amazon_de_plugin:AmazonDEKindleStore' actual_plugin = 'calibre.gui2.store.amazon_de_plugin:AmazonDEKindleStore'
class StoreAmazonUKKindleStore(StoreBase): class StoreAmazonUKKindleStore(StoreBase):
name = 'Amazon UK Kindle' name = 'Amazon UK Kindle'
description = _('Kindle books from Amazon.uk') description = _('Kindle books from Amazon.uk.')
actual_plugin = 'calibre.gui2.store.amazon_uk_plugin:AmazonUKKindleStore' actual_plugin = 'calibre.gui2.store.amazon_uk_plugin:AmazonUKKindleStore'
class StoreArchiveOrgStore(StoreBase):
name = 'Archive.org'
description = _('Free Books : Download & Streaming : Ebook and Texts Archive : Internet Archive.')
actual_plugin = 'calibre.gui2.store.archive_org_plugin:ArchiveOrgStore'
class StoreBaenWebScriptionStore(StoreBase): class StoreBaenWebScriptionStore(StoreBase):
name = 'Baen WebScription' name = 'Baen WebScription'
description = _('Ebooks for readers.') description = _('Ebooks for readers.')
@ -1119,7 +1126,7 @@ class StoreBNStore(StoreBase):
class StoreBeamEBooksDEStore(StoreBase): class StoreBeamEBooksDEStore(StoreBase):
name = 'Beam EBooks DE' name = 'Beam EBooks DE'
description = _('der eBook Shop') description = _('Der eBook Shop.')
actual_plugin = 'calibre.gui2.store.beam_ebooks_de_plugin:BeamEBooksDEStore' actual_plugin = 'calibre.gui2.store.beam_ebooks_de_plugin:BeamEBooksDEStore'
class StoreBeWriteStore(StoreBase): class StoreBeWriteStore(StoreBase):
@ -1139,12 +1146,12 @@ class StoreEbookscomStore(StoreBase):
class StoreEPubBuyDEStore(StoreBase): class StoreEPubBuyDEStore(StoreBase):
name = 'EPUBBuy DE' name = 'EPUBBuy DE'
description = _('EPUBReaders eBook Shop') description = _('EPUBReaders eBook Shop.')
actual_plugin = 'calibre.gui2.store.epubbuy_de_plugin:EPubBuyDEStore' actual_plugin = 'calibre.gui2.store.epubbuy_de_plugin:EPubBuyDEStore'
class StoreEHarlequinStore(StoreBase): class StoreEHarlequinStore(StoreBase):
name = 'eHarlequin' name = 'eHarlequin'
description = _('entertain, enrich, inspire.') description = _('Entertain, enrich, inspire.')
actual_plugin = 'calibre.gui2.store.eharlequin_plugin:EHarlequinStore' actual_plugin = 'calibre.gui2.store.eharlequin_plugin:EHarlequinStore'
class StoreFeedbooksStore(StoreBase): class StoreFeedbooksStore(StoreBase):
@ -1154,9 +1161,14 @@ class StoreFeedbooksStore(StoreBase):
class StoreFoylesUKStore(StoreBase): class StoreFoylesUKStore(StoreBase):
name = 'Foyles UK' name = 'Foyles UK'
description = _('Foyles of London, online') description = _('Foyles of London, online.')
actual_plugin = 'calibre.gui2.store.foyles_uk_plugin:FoylesUKStore' actual_plugin = 'calibre.gui2.store.foyles_uk_plugin:FoylesUKStore'
class StoreGoogleBooksStore(StoreBase):
name = 'Google Books'
description = _('Google Books')
actual_plugin = 'calibre.gui2.store.google_books_plugin:GoogleBooksStore'
class StoreGutenbergStore(StoreBase): class StoreGutenbergStore(StoreBase):
name = 'Project Gutenberg' name = 'Project Gutenberg'
description = _('The first producer of free ebooks.') description = _('The first producer of free ebooks.')
@ -1174,9 +1186,14 @@ class StoreManyBooksStore(StoreBase):
class StoreMobileReadStore(StoreBase): class StoreMobileReadStore(StoreBase):
name = 'MobileRead' name = 'MobileRead'
description = _('Ebooks handcrafted with the utmost care') description = _('Ebooks handcrafted with the utmost care.')
actual_plugin = 'calibre.gui2.store.mobileread.mobileread_plugin:MobileReadStore' actual_plugin = 'calibre.gui2.store.mobileread.mobileread_plugin:MobileReadStore'
class StoreNextoStore(StoreBase):
name = 'Nexto'
description = _('Audiobooki mp3, ebooki, prasa - księgarnia internetowa.')
actual_plugin = 'calibre.gui2.store.nexto_plugin:NextoStore'
class StoreOpenLibraryStore(StoreBase): class StoreOpenLibraryStore(StoreBase):
name = 'Open Library' name = 'Open Library'
description = _('One web page for every book.') description = _('One web page for every book.')
@ -1189,26 +1206,27 @@ class StoreSmashwordsStore(StoreBase):
class StoreWaterstonesUKStore(StoreBase): class StoreWaterstonesUKStore(StoreBase):
name = 'Waterstones UK' name = 'Waterstones UK'
description = _('Feel every word') description = _('Feel every word.')
actual_plugin = 'calibre.gui2.store.waterstones_uk_plugin:WaterstonesUKStore' actual_plugin = 'calibre.gui2.store.waterstones_uk_plugin:WaterstonesUKStore'
class StoreWeightlessBooksStore(StoreBase): class StoreWeightlessBooksStore(StoreBase):
name = 'Weightless Books' name = 'Weightless Books'
description = '(e)Books That Don\'t Weigh You Down' description = '(e)Books That Don\'t Weigh You Down.'
actual_plugin = 'calibre.gui2.store.weightless_books_plugin:WeightlessBooksStore' actual_plugin = 'calibre.gui2.store.weightless_books_plugin:WeightlessBooksStore'
class StoreWizardsTowerBooksStore(StoreBase): class StoreWizardsTowerBooksStore(StoreBase):
name = 'Wizards Tower Books' name = 'Wizards Tower Books'
description = 'Wizard\'s Tower Press' description = 'Wizard\'s Tower Press.'
actual_plugin = 'calibre.gui2.store.wizards_tower_books_plugin:WizardsTowerBooksStore' actual_plugin = 'calibre.gui2.store.wizards_tower_books_plugin:WizardsTowerBooksStore'
plugins += [StoreAmazonKindleStore, StoreAmazonDEKindleStore, StoreAmazonUKKindleStore, plugins += [StoreArchiveOrgStore, StoreAmazonKindleStore, StoreAmazonDEKindleStore,
StoreBaenWebScriptionStore, StoreBNStore, StoreAmazonUKKindleStore, StoreBaenWebScriptionStore, StoreBNStore,
StoreBeamEBooksDEStore, StoreBeWriteStore, StoreBeamEBooksDEStore, StoreBeWriteStore,
StoreDieselEbooksStore, StoreEbookscomStore, StoreEPubBuyDEStore, StoreDieselEbooksStore, StoreEbookscomStore, StoreEPubBuyDEStore,
StoreEHarlequinStore, StoreFeedbooksStore, StoreEHarlequinStore, StoreFeedbooksStore,
StoreFoylesUKStore, StoreGutenbergStore, StoreKoboStore, StoreManyBooksStore, StoreFoylesUKStore, StoreGoogleBooksStore, StoreGutenbergStore,
StoreMobileReadStore, StoreOpenLibraryStore, StoreSmashwordsStore, StoreKoboStore, StoreManyBooksStore,
StoreMobileReadStore, StoreNextoStore, StoreOpenLibraryStore, StoreSmashwordsStore,
StoreWaterstonesUKStore, StoreWeightlessBooksStore, StoreWizardsTowerBooksStore] StoreWaterstonesUKStore, StoreWeightlessBooksStore, StoreWizardsTowerBooksStore]
# }}} # }}}

View File

@ -10,7 +10,7 @@ import os, sys, re
from urllib import unquote, quote from urllib import unquote, quote
from urlparse import urlparse from urlparse import urlparse
from calibre import relpath, guess_type from calibre import relpath, guess_type, remove_bracketed_text
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
@ -27,20 +27,37 @@ def authors_to_string(authors):
else: else:
return '' return ''
_bracket_pat = re.compile(r'[\[({].*?[})\]]') def author_to_author_sort(author, method=None):
def author_to_author_sort(author):
if not author: if not author:
return '' return u''
method = tweaks['author_sort_copy_method'] sauthor = remove_bracketed_text(author).strip()
if method == 'copy' or (method == 'comma' and ',' in author): tokens = sauthor.split()
if len(tokens) < 2:
return author return author
author = _bracket_pat.sub('', author).strip() if method is None:
tokens = author.split() method = tweaks['author_sort_copy_method']
if tokens and tokens[-1] not in ('Inc.', 'Inc'): if method == u'copy':
tokens = tokens[-1:] + tokens[:-1] return author
if len(tokens) > 1 and method != 'nocomma': suffixes = set([x.lower() for x in tweaks['author_name_suffixes']])
tokens[0] += ',' suffixes |= set([x+u'.' for x in suffixes])
return ' '.join(tokens)
last = tokens[-1].lower()
suffix = None
if last in suffixes:
suffix = tokens[-1]
tokens = tokens[:-1]
if method == u'comma' and u',' in u''.join(tokens):
return author
atokens = tokens[-1:] + tokens[:-1]
if suffix:
atokens.append(suffix)
if method != u'nocomma' and len(atokens) > 1:
atokens[0] += u','
return u' '.join(atokens)
def authors_to_sort_string(authors): def authors_to_sort_string(authors):
return ' & '.join(map(author_to_author_sort, authors)) return ' & '.join(map(author_to_author_sort, authors))

View File

@ -16,7 +16,7 @@ from lxml.html import soupparser, tostring
from calibre import as_unicode from calibre import as_unicode
from calibre.ebooks.metadata import check_isbn from calibre.ebooks.metadata import check_isbn
from calibre.ebooks.metadata.sources.base import Source from calibre.ebooks.metadata.sources.base import Source, Option
from calibre.utils.cleantext import clean_ascii_chars from calibre.utils.cleantext import clean_ascii_chars
from calibre.ebooks.chardet import xml_to_unicode from calibre.ebooks.chardet import xml_to_unicode
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
@ -37,6 +37,92 @@ class Worker(Thread): # Get details {{{
self.relevance, self.plugin = relevance, plugin self.relevance, self.plugin = relevance, plugin
self.browser = browser.clone_browser() self.browser = browser.clone_browser()
self.cover_url = self.amazon_id = self.isbn = None self.cover_url = self.amazon_id = self.isbn = None
self.domain = self.plugin.domain
months = {
'de': {
1 : ['jän'],
3 : ['märz'],
5 : ['mai'],
6 : ['juni'],
7 : ['juli'],
10: ['okt'],
12: ['dez']
},
'it': {
1: ['enn'],
2: ['febbr'],
5: ['magg'],
6: ['giugno'],
7: ['luglio'],
8: ['ag'],
9: ['sett'],
10: ['ott'],
12: ['dic'],
},
'fr': {
1: ['janv'],
2: ['févr'],
3: ['mars'],
4: ['avril'],
5: ['mai'],
6: ['juin'],
7: ['juil'],
8: ['août'],
9: ['sept'],
12: ['déc'],
},
}
self.english_months = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
self.months = months.get(self.domain, {})
self.pd_xpath = '''
//h2[text()="Product Details" or \
text()="Produktinformation" or \
text()="Dettagli prodotto" or \
text()="Product details" or \
text()="Détails sur le produit"]/../div[@class="content"]
'''
self.publisher_xpath = '''
descendant::*[starts-with(text(), "Publisher:") or \
starts-with(text(), "Verlag:") or \
starts-with(text(), "Editore:") or \
starts-with(text(), "Editeur")]
'''
self.language_xpath = '''
descendant::*[
starts-with(text(), "Language:") \
or text() = "Language" \
or text() = "Sprache:" \
or text() = "Lingua:" \
or starts-with(text(), "Langue") \
]
'''
self.ratings_pat = re.compile(
r'([0-9.]+) (out of|von|su|étoiles sur) (\d+)( (stars|Sternen|stelle)){0,1}')
lm = {
'en': ('English', 'Englisch'),
'fr': ('French', 'Français'),
'it': ('Italian', 'Italiano'),
'de': ('German', 'Deutsch'),
}
self.lang_map = {}
for code, names in lm.iteritems():
for name in names:
self.lang_map[name] = code
def delocalize_datestr(self, raw):
if not self.months:
return raw
ans = raw.lower()
for i, vals in self.months.iteritems():
for x in vals:
ans = ans.replace(x, self.english_months[i])
return ans
def run(self): def run(self):
try: try:
@ -132,7 +218,7 @@ class Worker(Thread): # Get details {{{
self.log.exception('Error parsing cover for url: %r'%self.url) self.log.exception('Error parsing cover for url: %r'%self.url)
mi.has_cover = bool(self.cover_url) mi.has_cover = bool(self.cover_url)
pd = root.xpath('//h2[text()="Product Details"]/../div[@class="content"]') pd = root.xpath(self.pd_xpath)
if pd: if pd:
pd = pd[0] pd = pd[0]
@ -194,23 +280,29 @@ class Worker(Thread): # Get details {{{
def parse_authors(self, root): def parse_authors(self, root):
x = '//h1[@class="parseasinTitle"]/following-sibling::span/*[(name()="a" and @href) or (name()="span" and @class="contributorNameTrigger")]' x = '//h1[@class="parseasinTitle"]/following-sibling::span/*[(name()="a" and @href) or (name()="span" and @class="contributorNameTrigger")]'
aname = root.xpath(x) aname = root.xpath(x)
if not aname:
aname = root.xpath('''
//h1[@class="parseasinTitle"]/following-sibling::*[(name()="a" and @href) or (name()="span" and @class="contributorNameTrigger")]
''')
for x in aname: for x in aname:
x.tail = '' x.tail = ''
authors = [tostring(x, encoding=unicode, method='text').strip() for x authors = [tostring(x, encoding=unicode, method='text').strip() for x
in aname] in aname]
authors = [a for a in authors if a]
return authors return authors
def parse_rating(self, root): def parse_rating(self, root):
ratings = root.xpath('//div[@class="jumpBar"]/descendant::span[@class="asinReviewsSummary"]') ratings = root.xpath('//div[@class="jumpBar"]/descendant::span[@class="asinReviewsSummary"]')
if not ratings: if not ratings:
ratings = root.xpath('//div[@class="buying"]/descendant::span[@class="asinReviewsSummary"]') ratings = root.xpath('//div[@class="buying"]/descendant::span[@class="asinReviewsSummary"]')
pat = re.compile(r'([0-9.]+) out of (\d+) stars') if not ratings:
ratings = root.xpath('//span[@class="crAvgStars"]/descendant::span[@class="asinReviewsSummary"]')
if ratings: if ratings:
for elem in ratings[0].xpath('descendant::*[@title]'): for elem in ratings[0].xpath('descendant::*[@title]'):
t = elem.get('title').strip() t = elem.get('title').strip()
m = pat.match(t) m = self.ratings_pat.match(t)
if m is not None: if m is not None:
return float(m.group(1))/float(m.group(2)) * 5 return float(m.group(1))/float(m.group(3)) * 5
def parse_comments(self, root): def parse_comments(self, root):
desc = root.xpath('//div[@id="productDescription"]/*[@class="content"]') desc = root.xpath('//div[@id="productDescription"]/*[@class="content"]')
@ -264,27 +356,26 @@ class Worker(Thread): # Get details {{{
return ans return ans
def parse_publisher(self, pd): def parse_publisher(self, pd):
for x in reversed(pd.xpath( for x in reversed(pd.xpath(self.publisher_xpath)):
'descendant::*[starts-with(text(), "Publisher:")]')):
if x.tail: if x.tail:
ans = x.tail.partition(';')[0] ans = x.tail.partition(';')[0]
return ans.partition('(')[0].strip() return ans.partition('(')[0].strip()
def parse_pubdate(self, pd): def parse_pubdate(self, pd):
for x in reversed(pd.xpath( for x in reversed(pd.xpath(self.publisher_xpath)):
'descendant::*[starts-with(text(), "Publisher:")]')):
if x.tail: if x.tail:
ans = x.tail ans = x.tail
date = ans.partition('(')[-1].replace(')', '').strip() date = ans.partition('(')[-1].replace(')', '').strip()
date = self.delocalize_datestr(date)
return parse_date(date, assume_utc=True) return parse_date(date, assume_utc=True)
def parse_language(self, pd): def parse_language(self, pd):
for x in reversed(pd.xpath( for x in reversed(pd.xpath(self.language_xpath)):
'descendant::*[starts-with(text(), "Language:")]')):
if x.tail: if x.tail:
ans = x.tail.strip() ans = x.tail.strip()
if ans == 'English': ans = self.lang_map.get(ans, None)
return 'en' if ans:
return ans
# }}} # }}}
class Amazon(Source): class Amazon(Source):
@ -304,8 +395,15 @@ class Amazon(Source):
'fr' : _('France'), 'fr' : _('France'),
'de' : _('Germany'), 'de' : _('Germany'),
'uk' : _('UK'), 'uk' : _('UK'),
'it' : _('Italy'),
} }
options = (
Option('domain', 'choices', 'com', _('Amazon website to use:'),
_('Metadata from Amazon will be fetched using this '
'country\'s Amazon website.'), choices=AMAZON_DOMAINS),
)
def get_book_url(self, identifiers): # {{{ def get_book_url(self, identifiers): # {{{
asin = identifiers.get('amazon', None) asin = identifiers.get('amazon', None)
if asin is None: if asin is None:
@ -314,8 +412,16 @@ class Amazon(Source):
return ('amazon', asin, 'http://amzn.com/%s'%asin) return ('amazon', asin, 'http://amzn.com/%s'%asin)
# }}} # }}}
@property
def domain(self):
domain = self.prefs['domain']
if domain not in self.AMAZON_DOMAINS:
domain = 'com'
return domain
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{ def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
domain = self.prefs.get('domain', 'com') domain = self.domain
# See the amazon detailed search page to get all options # See the amazon detailed search page to get all options
q = { 'search-alias' : 'aps', q = { 'search-alias' : 'aps',
@ -355,6 +461,8 @@ class Amazon(Source):
latin1q = dict([(x.encode('latin1', 'ignore'), y.encode('latin1', latin1q = dict([(x.encode('latin1', 'ignore'), y.encode('latin1',
'ignore')) for x, y in 'ignore')) for x, y in
q.iteritems()]) q.iteritems()])
if domain == 'uk':
domain = 'co.uk'
url = 'http://www.amazon.%s/s/?'%domain + urlencode(latin1q) url = 'http://www.amazon.%s/s/?'%domain + urlencode(latin1q)
return url return url
@ -526,8 +634,7 @@ if __name__ == '__main__': # tests {{{
# src/calibre/ebooks/metadata/sources/amazon.py # src/calibre/ebooks/metadata/sources/amazon.py
from calibre.ebooks.metadata.sources.test import (test_identify_plugin, from calibre.ebooks.metadata.sources.test import (test_identify_plugin,
title_test, authors_test) title_test, authors_test)
test_identify_plugin(Amazon.name, com_tests = [ # {{{
[
( # Description has links ( # Description has links
{'identifiers':{'isbn': '9780671578275'}}, {'identifiers':{'isbn': '9780671578275'}},
@ -575,6 +682,38 @@ if __name__ == '__main__': # tests {{{
), ),
]) ] # }}}
de_tests = [ # {{{
(
{'identifiers':{'isbn': '3548283519'}},
[title_test('Wer Wind sät',
exact=True), authors_test(['Nele Neuhaus'])
]
),
] # }}}
it_tests = [ # {{{
(
{'identifiers':{'isbn': '8838922195'}},
[title_test('La briscola in cinque',
exact=True), authors_test(['Marco Malvaldi'])
]
),
] # }}}
fr_tests = [ # {{{
(
{'identifiers':{'isbn': '2221116798'}},
[title_test('L\'étrange voyage de Monsieur Daldry',
exact=True), authors_test(['Marc Levy'])
]
),
] # }}}
test_identify_plugin(Amazon.name, com_tests)
# }}} # }}}

View File

@ -145,10 +145,13 @@ class Option(object):
:param default: The default value for this option :param default: The default value for this option
:param label: A short (few words) description of this option :param label: A short (few words) description of this option
:param desc: A longer description of this option :param desc: A longer description of this option
:param choices: A list of possible values, used only if type='choices' :param choices: A dict of possible values, used only if type='choices'.
dict is of the form {key:human readable label, ...}
''' '''
self.name, self.type, self.default, self.label, self.desc = (name, self.name, self.type, self.default, self.label, self.desc = (name,
type_, default, label, desc) type_, default, label, desc)
if choices and not isinstance(choices, dict):
choices = dict([(x, x) for x in choices])
self.choices = choices self.choices = choices
class Source(Plugin): class Source(Plugin):

View File

@ -690,6 +690,14 @@ class MobiReader(object):
lm = unit_convert('2em', 12, 500, 166) lm = unit_convert('2em', 12, 500, 166)
lm = self.left_margins.get(tag, lm) lm = self.left_margins.get(tag, lm)
ti = self.text_indents.get(tag, ti) ti = self.text_indents.get(tag, ti)
try:
lm = float(lm)
except:
lm = 0.0
try:
ti = float(ti)
except:
ti = 0.0
return lm + ti return lm + ti
parent = tag parent = tag

View File

@ -13,6 +13,7 @@
#include <math.h> #include <math.h>
#include <iostream> #include <iostream>
#include <wand/MagickWand.h> #include <wand/MagickWand.h>
#include <zlib.h>
#include "images.h" #include "images.h"
#include "utils.h" #include "utils.h"

View File

@ -2,12 +2,8 @@
# jisyo.py # jisyo.py
# #
# Copyright 2011 Hiroshi Miura <miurahr@linux.com> # Copyright 2011 Hiroshi Miura <miurahr@linux.com>
from cPickle import load import cPickle, marshal
import anydbm,marshal
from zlib import decompress from zlib import decompress
import os
import calibre.utils.resources as resources
class jisyo (object): class jisyo (object):
kanwadict = None kanwadict = None
@ -25,16 +21,14 @@ class jisyo (object):
def __init__(self): def __init__(self):
if self.kanwadict is None: if self.kanwadict is None:
dictpath = resources.get_path(os.path.join('localization','pykakasi','kanwadict2.db')) self.kanwadict = cPickle.loads(
self.kanwadict = anydbm.open(dictpath,'r') P('localization/pykakasi/kanwadict2.pickle', data=True))
if self.itaijidict is None: if self.itaijidict is None:
itaijipath = resources.get_path(os.path.join('localization','pykakasi','itaijidict2.pickle')) self.itaijidict = cPickle.loads(
itaiji_pkl = open(itaijipath, 'rb') P('localization/pykakasi/itaijidict2.pickle', data=True))
self.itaijidict = load(itaiji_pkl)
if self.kanadict is None: if self.kanadict is None:
kanadictpath = resources.get_path(os.path.join('localization','pykakasi','kanadict2.pickle')) self.kanadict = cPickle.loads(
kanadict_pkl = open(kanadictpath, 'rb') P('localization/pykakasi/kanadict2.pickle', data=True))
self.kanadict = load(kanadict_pkl)
def load_jisyo(self, char): def load_jisyo(self, char):
try:#python2 try:#python2

View File

@ -625,6 +625,17 @@ class Application(QApplication):
if s is not None: if s is not None:
font.setStretch(s) font.setStretch(s)
QApplication.setFont(font) QApplication.setFont(font)
st = self.style()
if st is not None:
st = unicode(st.objectName()).lower()
if (islinux or isfreebsd) and st in ('windows', 'motif', 'cde'):
from PyQt4.Qt import QStyleFactory
styles = set(map(unicode, QStyleFactory.keys()))
if 'Plastique' in styles and os.environ.get('KDE_FULL_SESSION',
False):
self.setStyle('Plastique')
elif 'Cleanlooks' in styles:
self.setStyle('Cleanlooks')
def _send_file_open_events(self): def _send_file_open_events(self):
with self._file_open_lock: with self._file_open_lock:

View File

@ -19,8 +19,9 @@ class PreferencesAction(InterfaceAction):
def genesis(self): def genesis(self):
pm = QMenu() pm = QMenu()
acname = _('Change calibre behavior') if isosx else _('Preferences') pm.addAction(QIcon(I('config.png')), _('Preferences'), self.do_config)
pm.addAction(QIcon(I('config.png')), acname, self.do_config) if isosx:
pm.addAction(QIcon(I('config.png')), _('Change calibre behavior'), self.do_config)
pm.addAction(QIcon(I('wizard.png')), _('Run welcome wizard'), pm.addAction(QIcon(I('wizard.png')), _('Run welcome wizard'),
self.gui.run_wizard) self.gui.run_wizard)
if not DEBUG: if not DEBUG:

View File

@ -52,16 +52,23 @@ class StoreAction(InterfaceAction):
return rows[0].row() return rows[0].row()
def _get_author(self, row): def _get_author(self, row):
author = '' authors = []
if self.gui.current_view() is self.gui.library_view: if self.gui.current_view() is self.gui.library_view:
author = self.gui.library_view.model().authors(row) a = self.gui.library_view.model().authors(row)
if author: authors = a.split(',')
author = author.replace('|', ' ')
else: else:
mi = self.gui.current_view().model().get_book_display_info(row) mi = self.gui.current_view().model().get_book_display_info(row)
author = ' & '.join(mi.authors) authors = mi.authors
return author corrected_authors = []
for x in authors:
a = x.split('|')
a.reverse()
a = ' '.join(a)
corrected_authors.append(a)
return ' & '.join(corrected_authors).strip()
def search_author(self): def search_author(self):
row = self._get_selected_row() row = self._get_selected_row()
@ -80,7 +87,7 @@ class StoreAction(InterfaceAction):
mi = self.gui.current_view().model().get_book_display_info(row) mi = self.gui.current_view().model().get_book_display_info(row)
title = mi.title title = mi.title
return title return title.strip()
def search_title(self): def search_title(self):
row = self._get_selected_row() row = self._get_selected_row()

316
src/calibre/gui2/bars.py Normal file
View File

@ -0,0 +1,316 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import (QObject, QToolBar, Qt, QSize, QToolButton, QVBoxLayout,
QLabel, QWidget, QAction, QMenuBar, QMenu)
from calibre.constants import isosx
from calibre.gui2 import gprefs
class ToolBar(QToolBar): # {{{
def __init__(self, donate, location_manager, parent):
QToolBar.__init__(self, parent)
self.setContextMenuPolicy(Qt.PreventContextMenu)
self.setMovable(False)
self.setFloatable(False)
self.setOrientation(Qt.Horizontal)
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
self.setStyleSheet('QToolButton:checked { font-weight: bold }')
self.preferred_width = self.sizeHint().width()
self.gui = parent
self.donate_button = donate
self.added_actions = []
self.location_manager = location_manager
donate.setAutoRaise(True)
donate.setCursor(Qt.PointingHandCursor)
self.setAcceptDrops(True)
self.showing_donate = False
def resizeEvent(self, ev):
QToolBar.resizeEvent(self, ev)
style = self.get_text_style()
self.setToolButtonStyle(style)
if hasattr(self, 'd_widget') and hasattr(self.d_widget, 'filler'):
self.d_widget.filler.setVisible(style != Qt.ToolButtonIconOnly)
def get_text_style(self):
style = Qt.ToolButtonTextUnderIcon
s = gprefs['toolbar_icon_size']
if s != 'off':
p = gprefs['toolbar_text']
if p == 'never':
style = Qt.ToolButtonIconOnly
elif p == 'auto' and self.preferred_width > self.width()+35:
style = Qt.ToolButtonIconOnly
return style
def contextMenuEvent(self, *args):
pass
def update_lm_actions(self):
for ac in self.added_actions:
if ac in self.location_manager.all_actions:
ac.setVisible(ac in self.location_manager.available_actions)
def init_bar(self, actions):
self.showing_donate = False
for ac in self.added_actions:
m = ac.menu()
if m is not None:
m.setVisible(False)
self.clear()
self.added_actions = []
bar = self
for what in actions:
if what is None:
bar.addSeparator()
elif what == 'Location Manager':
for ac in self.location_manager.all_actions:
bar.addAction(ac)
bar.added_actions.append(ac)
bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup)
ac.setVisible(False)
elif what == 'Donate':
self.d_widget = QWidget()
self.d_widget.setLayout(QVBoxLayout())
self.d_widget.layout().addWidget(self.donate_button)
if isosx:
self.d_widget.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }')
self.d_widget.layout().setContentsMargins(0,0,0,0)
self.d_widget.setContentsMargins(0,0,0,0)
self.d_widget.filler = QLabel(u'\u00a0')
self.d_widget.layout().addWidget(self.d_widget.filler)
bar.addWidget(self.d_widget)
self.showing_donate = True
elif what in self.gui.iactions:
action = self.gui.iactions[what]
bar.addAction(action.qaction)
self.added_actions.append(action.qaction)
self.setup_tool_button(bar, action.qaction, action.popup_type)
self.preferred_width = self.sizeHint().width()
def setup_tool_button(self, bar, ac, menu_mode=None):
ch = bar.widgetForAction(ac)
if ch is None:
ch = self.child_bar.widgetForAction(ac)
ch.setCursor(Qt.PointingHandCursor)
ch.setAutoRaise(True)
if ac.menu() is not None and menu_mode is not None:
ch.setPopupMode(menu_mode)
return ch
#support drag&drop from/to library from/to reader/card
def dragEnterEvent(self, event):
md = event.mimeData()
if md.hasFormat("application/calibre+from_library") or \
md.hasFormat("application/calibre+from_device"):
event.setDropAction(Qt.CopyAction)
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
allowed = False
md = event.mimeData()
#Drop is only allowed in the location manager widget's different from the selected one
for ac in self.location_manager.available_actions:
w = self.widgetForAction(ac)
if w is not None:
if ( md.hasFormat("application/calibre+from_library") or \
md.hasFormat("application/calibre+from_device") ) and \
w.geometry().contains(event.pos()) and \
isinstance(w, QToolButton) and not w.isChecked():
allowed = True
break
if allowed:
event.acceptProposedAction()
else:
event.ignore()
def dropEvent(self, event):
data = event.mimeData()
mime = 'application/calibre+from_library'
if data.hasFormat(mime):
ids = list(map(int, str(data.data(mime)).split()))
tgt = None
for ac in self.location_manager.available_actions:
w = self.widgetForAction(ac)
if w is not None and w.geometry().contains(event.pos()):
tgt = ac.calibre_name
if tgt is not None:
if tgt == 'main':
tgt = None
self.gui.sync_to_device(tgt, False, send_ids=ids)
event.accept()
mime = 'application/calibre+from_device'
if data.hasFormat(mime):
paths = [unicode(u.toLocalFile()) for u in data.urls()]
if paths:
self.gui.iactions['Add Books'].add_books_from_device(
self.gui.current_view(), paths=paths)
event.accept()
# }}}
class MenuAction(QAction): # {{{
def __init__(self, clone, parent):
QAction.__init__(self, clone.text(), parent)
self.clone = clone
clone.changed.connect(self.clone_changed)
def clone_changed(self):
self.setText(self.clone.text())
# }}}
class MenuBar(QMenuBar): # {{{
def __init__(self, location_manager, parent):
QMenuBar.__init__(self, parent)
self.gui = parent
self.setNativeMenuBar(True)
self.location_manager = location_manager
self.added_actions = []
self.donate_action = QAction(_('Donate'), self)
self.donate_menu = QMenu()
self.donate_menu.addAction(self.gui.donate_action)
self.donate_action.setMenu(self.donate_menu)
def update_lm_actions(self):
for ac in self.added_actions:
if ac in self.location_manager.all_actions:
ac.setVisible(ac in self.location_manager.available_actions)
def init_bar(self, actions):
for ac in self.added_actions:
m = ac.menu()
if m is not None:
m.setVisible(False)
self.clear()
self.added_actions = []
for what in actions:
if what is None:
continue
elif what == 'Location Manager':
for ac in self.location_manager.all_actions:
ac = self.build_menu(ac)
self.addAction(ac)
self.added_actions.append(ac)
ac.setVisible(False)
elif what == 'Donate':
self.addAction(self.donate_action)
elif what in self.gui.iactions:
action = self.gui.iactions[what]
ac = self.build_menu(action.qaction)
self.addAction(ac)
self.added_actions.append(ac)
def build_menu(self, action):
m = action.menu()
ac = MenuAction(action, self)
if m is None:
m = QMenu()
m.addAction(action)
ac.setMenu(m)
return ac
# }}}
class BarsManager(QObject):
def __init__(self, donate_button, location_manager, parent):
QObject.__init__(self, parent)
self.donate_button, self.location_manager = (donate_button,
location_manager)
bars = [ToolBar(donate_button, location_manager, parent) for i in
range(3)]
self.main_bars = tuple(bars[:2])
self.child_bars = tuple(bars[2:])
self.apply_settings()
self.init_bars()
def database_changed(self, db):
pass
@property
def bars(self):
for x in self.main_bars + self.child_bars:
yield x
@property
def showing_donate(self):
for b in self.bars:
if b.isVisible() and b.showing_donate:
return True
return False
def init_bars(self):
self.bar_actions = tuple(
[gprefs['action-layout-toolbar'+x] for x in ('', '-device')] +
[gprefs['action-layout-toolbar-child']] +
[gprefs['action-layout-menubar']] +
[gprefs['action-layout-menubar-device']]
)
for bar, actions in zip(self.bars, self.bar_actions[:3]):
bar.init_bar(actions)
def update_bars(self):
'''
This shows the correct main toolbar and rebuilds the menubar based on
whether a device is connected or not. Note that the toolbars are
explicitly not rebuilt, this is to workaround a Qt limitation iwth
QToolButton's popup menus and modal dialogs. If you want the toolbars
rebuilt, call init_bars().
'''
showing_device = self.location_manager.has_device
main_bar = self.main_bars[1 if showing_device else 0]
child_bar = self.child_bars[0]
for bar in self.bars:
bar.setVisible(False)
bar.update_lm_actions()
if main_bar.added_actions:
main_bar.setVisible(True)
if child_bar.added_actions:
child_bar.setVisible(True)
self.menu_bar = MenuBar(self.location_manager, self.parent())
self.menu_bar.init_bar(self.bar_actions[4 if showing_device else 3])
self.menu_bar.update_lm_actions()
self.menu_bar.setVisible(bool(self.menu_bar.added_actions))
self.parent().setMenuBar(self.menu_bar)
def apply_settings(self):
sz = gprefs['toolbar_icon_size']
sz = {'off':0, 'small':24, 'medium':48, 'large':64}[sz]
style = Qt.ToolButtonTextUnderIcon
if sz > 0 and gprefs['toolbar_text'] == 'never':
style = Qt.ToolButtonIconOnly
for bar in self.bars:
bar.setIconSize(QSize(sz, sz))
bar.setToolButtonStyle(style)
self.donate_button.set_normal_icon_size(sz, sz)

View File

@ -751,6 +751,7 @@ class DeviceMixin(object): # {{{
if self.current_view() != self.library_view: if self.current_view() != self.library_view:
self.book_details.reset_info() self.book_details.reset_info()
self.location_manager.update_devices() self.location_manager.update_devices()
self.bars_manager.update_bars()
self.library_view.set_device_connected(self.device_connected) self.library_view.set_device_connected(self.device_connected)
self.refresh_ondevice() self.refresh_ondevice()
device_signals.device_connection_changed.emit(connected) device_signals.device_connection_changed.emit(connected)
@ -764,6 +765,7 @@ class DeviceMixin(object): # {{{
info, cp, fs = job.result info, cp, fs = job.result
self.location_manager.update_devices(cp, fs, self.location_manager.update_devices(cp, fs,
self.device_manager.device.icon) self.device_manager.device.icon)
self.bars_manager.update_bars()
self.status_bar.device_connected(info[0]) self.status_bar.device_connected(info[0])
self.device_manager.books(Dispatcher(self.metadata_downloaded)) self.device_manager.books(Dispatcher(self.metadata_downloaded))

View File

@ -7,15 +7,15 @@ __docformat__ = 'restructuredtext en'
from functools import partial from functools import partial
from PyQt4.Qt import (QIcon, Qt, QWidget, QToolBar, QSize, from PyQt4.Qt import (QIcon, Qt, QWidget, QSize,
pyqtSignal, QToolButton, QMenu, QMenuBar, QAction, pyqtSignal, QToolButton, QMenu,
QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup) QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup)
from calibre.constants import __appname__, isosx from calibre.constants import __appname__
from calibre.gui2.search_box import SearchBox2, SavedSearchBox from calibre.gui2.search_box import SearchBox2, SavedSearchBox
from calibre.gui2.throbber import ThrobbingButton from calibre.gui2.throbber import ThrobbingButton
from calibre.gui2 import gprefs from calibre.gui2.bars import BarsManager
from calibre.gui2.widgets import ComboBoxWithHelp from calibre.gui2.widgets import ComboBoxWithHelp
from calibre import human_readable from calibre import human_readable
@ -35,6 +35,8 @@ class LocationManager(QObject): # {{{
self._mem = [] self._mem = []
self.tooltips = {} self.tooltips = {}
self.all_actions = []
def ac(name, text, icon, tooltip): def ac(name, text, icon, tooltip):
icon = QIcon(I(icon)) icon = QIcon(I(icon))
ac = self.location_actions.addAction(icon, text) ac = self.location_actions.addAction(icon, text)
@ -59,6 +61,7 @@ class LocationManager(QObject): # {{{
ac.setMenu(m) ac.setMenu(m)
ac.calibre_name = name ac.calibre_name = name
self.all_actions.append(ac)
return ac return ac
self.library_action = ac('library', _('Library'), 'lt.png', self.library_action = ac('library', _('Library'), 'lt.png',
@ -234,259 +237,6 @@ class Spacer(QWidget): # {{{
self.l.addStretch(10) self.l.addStretch(10)
# }}} # }}}
class MenuAction(QAction): # {{{
def __init__(self, clone, parent):
QAction.__init__(self, clone.text(), parent)
self.clone = clone
clone.changed.connect(self.clone_changed)
def clone_changed(self):
self.setText(self.clone.text())
# }}}
class MenuBar(QMenuBar): # {{{
def __init__(self, location_manager, parent):
QMenuBar.__init__(self, parent)
self.gui = parent
self.setNativeMenuBar(True)
self.location_manager = location_manager
self.location_manager.locations_changed.connect(self.build_bar)
self.added_actions = []
self.donate_action = QAction(_('Donate'), self)
self.donate_menu = QMenu()
self.donate_menu.addAction(self.gui.donate_action)
self.donate_action.setMenu(self.donate_menu)
self.build_bar()
def build_bar(self, changed_action=None):
showing_device = self.location_manager.has_device
actions = '-device' if showing_device else ''
actions = gprefs['action-layout-menubar'+actions]
show_main = len(actions) > 0
self.setVisible(show_main)
for ac in self.added_actions:
m = ac.menu()
if m is not None:
m.setVisible(False)
self.clear()
self.added_actions = []
self.action_map = {}
for what in actions:
if what is None:
continue
elif what == 'Location Manager':
for ac in self.location_manager.available_actions:
ac = self.build_menu(ac)
self.addAction(ac)
self.added_actions.append(ac)
elif what == 'Donate':
self.addAction(self.donate_action)
elif what in self.gui.iactions:
action = self.gui.iactions[what]
ac = self.build_menu(action.qaction)
self.addAction(ac)
self.added_actions.append(ac)
def build_menu(self, action):
m = action.menu()
ac = MenuAction(action, self)
if m is None:
m = QMenu()
m.addAction(action)
ac.setMenu(m)
return ac
# }}}
class BaseToolBar(QToolBar): # {{{
def __init__(self, parent):
QToolBar.__init__(self, parent)
self.setContextMenuPolicy(Qt.PreventContextMenu)
self.setMovable(False)
self.setFloatable(False)
self.setOrientation(Qt.Horizontal)
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
self.setStyleSheet('QToolButton:checked { font-weight: bold }')
self.preferred_width = self.sizeHint().width()
def resizeEvent(self, ev):
QToolBar.resizeEvent(self, ev)
style = self.get_text_style()
self.setToolButtonStyle(style)
if hasattr(self, 'd_widget') and hasattr(self.d_widget, 'filler'):
self.d_widget.filler.setVisible(style != Qt.ToolButtonIconOnly)
def get_text_style(self):
style = Qt.ToolButtonTextUnderIcon
s = gprefs['toolbar_icon_size']
if s != 'off':
p = gprefs['toolbar_text']
if p == 'never':
style = Qt.ToolButtonIconOnly
elif p == 'auto' and self.preferred_width > self.width()+35:
style = Qt.ToolButtonIconOnly
return style
def contextMenuEvent(self, *args):
pass
# }}}
class ToolBar(BaseToolBar): # {{{
def __init__(self, donate, location_manager, child_bar, parent):
BaseToolBar.__init__(self, parent)
self.gui = parent
self.child_bar = child_bar
self.donate_button = donate
self.apply_settings()
self.location_manager = location_manager
self.location_manager.locations_changed.connect(self.build_bar)
donate.setAutoRaise(True)
donate.setCursor(Qt.PointingHandCursor)
self.added_actions = []
self.build_bar()
self.setAcceptDrops(True)
def apply_settings(self):
sz = gprefs['toolbar_icon_size']
sz = {'off':0, 'small':24, 'medium':48, 'large':64}[sz]
self.setIconSize(QSize(sz, sz))
self.child_bar.setIconSize(QSize(sz, sz))
style = Qt.ToolButtonTextUnderIcon
if sz > 0 and gprefs['toolbar_text'] == 'never':
style = Qt.ToolButtonIconOnly
self.setToolButtonStyle(style)
self.child_bar.setToolButtonStyle(style)
self.donate_button.set_normal_icon_size(sz, sz)
def build_bar(self):
self.showing_donate = False
showing_device = self.location_manager.has_device
mactions = '-device' if showing_device else ''
mactions = gprefs['action-layout-toolbar'+mactions]
cactions = gprefs['action-layout-toolbar-child']
show_main = len(mactions) > 0
self.setVisible(show_main)
show_child = len(cactions) > 0
self.child_bar.setVisible(show_child)
for ac in self.added_actions:
m = ac.menu()
if m is not None:
m.setVisible(False)
self.clear()
self.child_bar.clear()
self.added_actions = []
for bar, actions in ((self, mactions), (self.child_bar, cactions)):
for what in actions:
if what is None:
bar.addSeparator()
elif what == 'Location Manager':
for ac in self.location_manager.available_actions:
bar.addAction(ac)
bar.added_actions.append(ac)
bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup)
elif what == 'Donate':
self.d_widget = QWidget()
self.d_widget.setLayout(QVBoxLayout())
self.d_widget.layout().addWidget(self.donate_button)
if isosx:
self.d_widget.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }')
self.d_widget.layout().setContentsMargins(0,0,0,0)
self.d_widget.setContentsMargins(0,0,0,0)
self.d_widget.filler = QLabel(u'\u00a0')
self.d_widget.layout().addWidget(self.d_widget.filler)
bar.addWidget(self.d_widget)
self.showing_donate = True
elif what in self.gui.iactions:
action = self.gui.iactions[what]
bar.addAction(action.qaction)
self.added_actions.append(action.qaction)
self.setup_tool_button(bar, action.qaction, action.popup_type)
self.preferred_width = self.sizeHint().width()
self.child_bar.preferred_width = self.child_bar.sizeHint().width()
def setup_tool_button(self, bar, ac, menu_mode=None):
ch = bar.widgetForAction(ac)
if ch is None:
ch = self.child_bar.widgetForAction(ac)
ch.setCursor(Qt.PointingHandCursor)
ch.setAutoRaise(True)
if ac.menu() is not None and menu_mode is not None:
ch.setPopupMode(menu_mode)
return ch
def database_changed(self, db):
pass
#support drag&drop from/to library from/to reader/card
def dragEnterEvent(self, event):
md = event.mimeData()
if md.hasFormat("application/calibre+from_library") or \
md.hasFormat("application/calibre+from_device"):
event.setDropAction(Qt.CopyAction)
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
allowed = False
md = event.mimeData()
#Drop is only allowed in the location manager widget's different from the selected one
for ac in self.location_manager.available_actions:
w = self.widgetForAction(ac)
if w is not None:
if ( md.hasFormat("application/calibre+from_library") or \
md.hasFormat("application/calibre+from_device") ) and \
w.geometry().contains(event.pos()) and \
isinstance(w, QToolButton) and not w.isChecked():
allowed = True
break
if allowed:
event.acceptProposedAction()
else:
event.ignore()
def dropEvent(self, event):
data = event.mimeData()
mime = 'application/calibre+from_library'
if data.hasFormat(mime):
ids = list(map(int, str(data.data(mime)).split()))
tgt = None
for ac in self.location_manager.available_actions:
w = self.widgetForAction(ac)
if w is not None and w.geometry().contains(event.pos()):
tgt = ac.calibre_name
if tgt is not None:
if tgt == 'main':
tgt = None
self.gui.sync_to_device(tgt, False, send_ids=ids)
event.accept()
mime = 'application/calibre+from_device'
if data.hasFormat(mime):
paths = [unicode(u.toLocalFile()) for u in data.urls()]
if paths:
self.gui.iactions['Add Books'].add_books_from_device(
self.gui.current_view(), paths=paths)
event.accept()
# }}}
class MainWindowMixin(object): # {{{ class MainWindowMixin(object): # {{{
@ -507,13 +257,13 @@ class MainWindowMixin(object): # {{{
self.iactions['Fetch News'].init_scheduler(db) self.iactions['Fetch News'].init_scheduler(db)
self.search_bar = SearchBar(self) self.search_bar = SearchBar(self)
self.child_bar = BaseToolBar(self) self.bars_manager = BarsManager(self.donate_button,
self.tool_bar = ToolBar(self.donate_button, self.location_manager, self)
self.location_manager, self.child_bar, self) for bar in self.bars_manager.main_bars:
self.addToolBar(Qt.TopToolBarArea, self.tool_bar) self.addToolBar(Qt.TopToolBarArea, bar)
self.addToolBar(Qt.BottomToolBarArea, self.child_bar) for bar in self.bars_manager.child_bars:
self.menu_bar = MenuBar(self.location_manager, self) self.addToolBar(Qt.BottomToolBarArea, bar)
self.setMenuBar(self.menu_bar) self.bars_manager.update_bars()
self.setUnifiedTitleAndToolBarOnMac(True) self.setUnifiedTitleAndToolBarOnMac(True)
l = self.centralwidget.layout() l = self.centralwidget.layout()

View File

@ -10,7 +10,7 @@ __docformat__ = 'restructuredtext en'
import textwrap import textwrap
from PyQt4.Qt import (QWidget, QGridLayout, QGroupBox, QListView, Qt, QSpinBox, from PyQt4.Qt import (QWidget, QGridLayout, QGroupBox, QListView, Qt, QSpinBox,
QDoubleSpinBox, QCheckBox, QLineEdit, QComboBox, QLabel) QDoubleSpinBox, QCheckBox, QLineEdit, QComboBox, QLabel, QVariant)
from calibre.gui2.preferences.metadata_sources import FieldsModel as FM from calibre.gui2.preferences.metadata_sources import FieldsModel as FM
@ -95,9 +95,9 @@ class ConfigWidget(QWidget):
widget.setChecked(bool(val)) widget.setChecked(bool(val))
elif opt.type == 'choices': elif opt.type == 'choices':
widget = QComboBox(self) widget = QComboBox(self)
for x in opt.choices: for key, label in opt.choices.iteritems():
widget.addItem(x) widget.addItem(label, QVariant(key))
idx = opt.choices.index(val) idx = widget.findData(QVariant(val))
widget.setCurrentIndex(idx) widget.setCurrentIndex(idx)
widget.opt = opt widget.opt = opt
widget.setToolTip(textwrap.fill(opt.desc)) widget.setToolTip(textwrap.fill(opt.desc))
@ -124,7 +124,8 @@ class ConfigWidget(QWidget):
elif isinstance(w, QCheckBox): elif isinstance(w, QCheckBox):
val = w.isChecked() val = w.isChecked()
elif isinstance(w, QComboBox): elif isinstance(w, QComboBox):
val = unicode(w.currentText()) idx = w.currentIndex()
val = unicode(w.itemData(idx).toString())
self.plugin.prefs[w.opt.name] = val self.plugin.prefs[w.opt.name] = val

View File

@ -361,10 +361,9 @@ class Preferences(QMainWindow):
self.gui.tags_view.recount() self.gui.tags_view.recount()
self.gui.create_device_menu() self.gui.create_device_menu()
self.gui.set_device_menu_items_state(bool(self.gui.device_connected)) self.gui.set_device_menu_items_state(bool(self.gui.device_connected))
self.gui.tool_bar.build_bar() self.gui.bars_manager.apply_settings()
self.gui.menu_bar.build_bar() self.gui.bars_manager.update_bars()
self.gui.build_context_menus() self.gui.build_context_menus()
self.gui.tool_bar.apply_settings()
return QMainWindow.closeEvent(self, *args) return QMainWindow.closeEvent(self, *args)

View File

@ -317,6 +317,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
am.restore_defaults() am.restore_defaults()
self.changed_signal.emit() self.changed_signal.emit()
def refresh_gui(self, gui):
gui.bars_manager.init_bars()
gui.bars_manager.update_bars()
if __name__ == '__main__': if __name__ == '__main__':
from PyQt4.Qt import QApplication from PyQt4.Qt import QApplication

View File

@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import urllib
from contextlib import closing
from lxml import html
from PyQt4.Qt import QUrl
from calibre import browser, url_slash_cleaner
from calibre.gui2 import open_url
from calibre.gui2.store import StorePlugin
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog
class ArchiveOrgStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
url = 'http://www.archive.org/details/texts'
if detail_item:
detail_item = url_slash_cleaner('http://www.archive.org' + detail_item)
if external or self.config.get('open_external', False):
open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
else:
d = WebStoreDialog(self.gui, url, parent, detail_item)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
def search(self, query, max_results=10, timeout=60):
query = query + ' AND mediatype:texts'
url = 'http://www.archive.org/search.php?query=' + urllib.quote(query)
br = browser()
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//td[@class="hitCell"]'):
if counter <= 0:
break
id = ''.join(data.xpath('.//a[@class="titleLink"]/@href'))
if not id:
continue
title = ''.join(data.xpath('.//a[@class="titleLink"]//text()'))
authors = data.xpath('.//text()')
if not authors:
continue
author = None
for a in authors:
if '-' in a:
author = a.replace('-', ' ').strip()
if author:
break
if not author:
continue
counter -= 1
s = SearchResult()
s.title = title.strip()
s.author = author.strip()
s.price = '$0.00'
s.detail_item = id.strip()
s.drm = SearchResult.DRM_UNLOCKED
yield s
def get_details(self, search_result, timeout):
url = url_slash_cleaner('http://www.archive.org' + search_result.detail_item)
br = browser()
with closing(br.open(url, timeout=timeout)) as nf:
idata = html.fromstring(nf.read())
formats = ', '.join(idata.xpath('//p[@id="dl" and @class="content"]//a/text()'))
search_result.formats = formats.upper()
return True

View File

@ -26,14 +26,9 @@ class BeWriteStore(BasicStoreConfig, StorePlugin):
url = 'http://www.bewrite.net/mm5/merchant.mvc?Screen=SFNT' url = 'http://www.bewrite.net/mm5/merchant.mvc?Screen=SFNT'
if external or self.config.get('open_external', False): if external or self.config.get('open_external', False):
if detail_item: open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
url = url + detail_item
open_url(QUrl(url_slash_cleaner(url)))
else: else:
detail_url = None d = WebStoreDialog(self.gui, url, parent, detail_item)
if detail_item:
detail_url = url + detail_item
d = WebStoreDialog(self.gui, url, parent, detail_url)
d.setWindowTitle(self.name) d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', '')) d.set_tags(self.config.get('tags', ''))
d.exec_() d.exec_()

View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import urllib
from contextlib import closing
from lxml import html
from PyQt4.Qt import QUrl
from calibre import browser, url_slash_cleaner
from calibre.gui2 import open_url
from calibre.gui2.store import StorePlugin
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog
class GoogleBooksStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
url = 'http://books.google.com/'
if external or self.config.get('open_external', False):
open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
else:
d = WebStoreDialog(self.gui, url, parent, detail_item)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
def search(self, query, max_results=10, timeout=60):
url = 'http://www.google.com/search?tbm=bks&q=' + urllib.quote_plus(query)
br = browser()
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//ol[@id="rso"]/li'):
if counter <= 0:
break
id = ''.join(data.xpath('.//h3/a/@href'))
if not id:
continue
title = ''.join(data.xpath('.//h3/a//text()'))
authors = data.xpath('.//span[@class="gl"]//a//text()')
if authors[-1].strip().lower() == 'preview':
authors = authors[:-1]
else:
continue
author = ', '.join(authors)
counter -= 1
s = SearchResult()
s.title = title.strip()
s.author = author.strip()
s.detail_item = id.strip()
s.drm = SearchResult.DRM_UNKNOWN
yield s
def get_details(self, search_result, timeout):
br = browser()
with closing(br.open(search_result.detail_item, timeout=timeout)) as nf:
doc = html.fromstring(nf.read())
search_result.cover_url = ''.join(doc.xpath('//div[@class="sidebarcover"]//img/@src'))
# Try to get the set price.
price = ''.join(doc.xpath('//div[@class="buy-price-container"]/span[contains(@class, "buy-price")]/text()'))
# Try to get the price inside of a buy button.
if not price.strip():
price = ''.join(doc.xpath('//div[@class="buy-container"]/a/text()'))
price = price.split('-')[-1]
# No price set for this book.
if not price.strip():
price = '$0.00'
search_result.price = price.strip()
search_result.formats = ', '.join(doc.xpath('//div[contains(@class, "download-panel-div")]//a/text()')).upper()
if not search_result.formats:
search_result.formats = _('Unknown')
return True

View File

@ -20,6 +20,7 @@ class MobeReadStoreDialog(QDialog, Ui_Dialog):
self.setupUi(self) self.setupUi(self)
self.plugin = plugin self.plugin = plugin
self.search_query.initialize('store_mobileread_search')
self.adv_search_button.setIcon(QIcon(I('search.png'))) self.adv_search_button.setIcon(QIcon(I('search.png')))

View File

@ -34,7 +34,14 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLineEdit" name="search_query"/> <widget class="HistoryLineEdit" name="search_query">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="search_button"> <widget class="QPushButton" name="search_button">
@ -107,6 +114,13 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>HistoryLineEdit</class>
<extends>QLineEdit</extends>
<header>widgets.h</header>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections> <connections>
<connection> <connection>

View File

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, Tomasz Długosz <tomek3d@gmail.com>'
__docformat__ = 'restructuredtext en'
import re
import urllib
from contextlib import closing
from lxml import html
from PyQt4.Qt import QUrl
from calibre import browser, url_slash_cleaner
from calibre.gui2 import open_url
from calibre.gui2.store import StorePlugin
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog
class NextoStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
pid = '155711'
url = 'http://www.nexto.pl/ebooki_c1015.xml'
detail_url = None
if detail_item:
book_id = re.search(r'p[0-9]*\.xml\Z', detail_item)
book_id = book_id.group(0).replace('.xml','').replace('p','')
if book_id:
detail_url = 'http://www.nexto.pl/rf/pr?p=' + book_id + '&pid=' + pid
if external or self.config.get('open_external', False):
open_url(QUrl(url_slash_cleaner(detail_url if detail_url else url)))
else:
d = WebStoreDialog(self.gui, url, parent, detail_url)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
def search(self, query, max_results=10, timeout=60):
url = 'http://www.nexto.pl/szukaj.xml?search-clause=' + urllib.quote_plus(query.encode('utf-8')) + '&scid=1015'
br = browser()
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//ul[@class="productslist"]/li'):
if counter <= 0:
break
id = ''.join(data.xpath('.//div[@class="cover_container"]/a[1]/@href'))
if not id:
continue
price = ''.join(data.xpath('.//strong[@class="nprice"]/text()'))
cover_url = ''.join(data.xpath('.//img[@class="cover"]/@src'))
title = ''.join(data.xpath('.//a[@class="title"]/text()'))
formats = ', '.join(data.xpath('.//ul[@class="formats_available"]/li//b/text()'))
DrmFree = re.search(r'bez.DRM', formats)
formats = re.sub(r'\(.+\)', '', formats)
author = ''
with closing(br.open('http://www.nexto.pl/' + id.strip(), timeout=timeout/4)) as nf:
idata = html.fromstring(nf.read())
author = ''.join(idata.xpath('//div[@class="basic_data"]/p[1]/b/a/text()'))
counter -= 1
s = SearchResult()
s.cover_url = cover_url
s.title = title.strip()
s.author = author.strip()
s.price = price
s.detail_item = id.strip()
s.drm = SearchResult.DRM_UNLOCKED if DrmFree else SearchResult.DRM_LOCKED
s.formats = formats.upper().strip()
yield s

View File

@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
import re import re
from random import shuffle from random import shuffle
from PyQt4.Qt import (Qt, QDialog, QTimer, QCheckBox, QVBoxLayout, QIcon) from PyQt4.Qt import (Qt, QDialog, QTimer, QCheckBox, QVBoxLayout, QIcon, QWidget)
from calibre.gui2 import JSONConfig, info_dialog from calibre.gui2 import JSONConfig, info_dialog
from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.progress_indicator import ProgressIndicator
@ -29,6 +29,8 @@ class SearchDialog(QDialog, Ui_Dialog):
self.config = JSONConfig('store/search') self.config = JSONConfig('store/search')
self.search_edit.initialize('store_search_search')
# We keep a cache of store plugins and reference them by name. # We keep a cache of store plugins and reference them by name.
self.store_plugins = istores self.store_plugins = istores
self.search_pool = SearchThreadPool(4) self.search_pool = SearchThreadPool(4)
@ -45,14 +47,16 @@ class SearchDialog(QDialog, Ui_Dialog):
# Add check boxes for each store so the user # Add check boxes for each store so the user
# can disable searching specific stores on a # can disable searching specific stores on a
# per search basis. # per search basis.
stores_check_widget = QWidget()
stores_group_layout = QVBoxLayout() stores_group_layout = QVBoxLayout()
self.stores_group.setLayout(stores_group_layout) stores_check_widget.setLayout(stores_group_layout)
for x in sorted(self.store_plugins.keys(), key=lambda x: x.lower()): for x in sorted(self.store_plugins.keys(), key=lambda x: x.lower()):
cbox = QCheckBox(x) cbox = QCheckBox(x)
cbox.setChecked(True) cbox.setChecked(False)
stores_group_layout.addWidget(cbox) stores_group_layout.addWidget(cbox)
setattr(self, 'store_check_' + x, cbox) setattr(self, 'store_check_' + x, cbox)
stores_group_layout.addStretch() stores_group_layout.addStretch()
self.stores_group.setWidget(stores_check_widget)
# Set the search query # Set the search query
self.search_edit.setText(query) self.search_edit.setText(query)

View File

@ -38,7 +38,14 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLineEdit" name="search_edit"/> <widget class="HistoryLineEdit" name="search_edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="search"> <widget class="QPushButton" name="search">
@ -201,6 +208,11 @@
<extends>QTreeView</extends> <extends>QTreeView</extends>
<header>results_view.h</header> <header>results_view.h</header>
</customwidget> </customwidget>
<customwidget>
<class>HistoryLineEdit</class>
<extends>QLineEdit</extends>
<header>widgets.h</header>
</customwidget>
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../../../../resources/images.qrc"/> <include location="../../../../resources/images.qrc"/>

View File

@ -44,40 +44,70 @@ class WizardsTowerBooksStore(BasicStoreConfig, StorePlugin):
counter = max_results counter = max_results
with closing(br.open(url, timeout=timeout)) as f: with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read()) doc = html.fromstring(f.read())
for data in doc.xpath('//table[@class="gridp"]//td'): if 'search.html' in f.geturl():
if counter <= 0: for data in doc.xpath('//table[@class="gridp"]//td'):
break if counter <= 0:
break
id = ''.join(data.xpath('.//span[@class="prti"]/a/@href')) id = ''.join(data.xpath('.//span[@class="prti"]/a/@href'))
id = id.strip() id = id.strip()
if not id: if not id:
continue continue
cover_url = ''.join(data.xpath('.//div[@class="prim"]/a/img/@src')) cover_url = ''.join(data.xpath('.//div[@class="prim"]/a/img/@src'))
cover_url = url_slash_cleaner(self.url + cover_url.strip()) cover_url = url_slash_cleaner(self.url + cover_url.strip())
price = ''.join(data.xpath('.//font[@class="selling_price"]//text()')) price = ''.join(data.xpath('.//font[@class="selling_price"]//text()'))
price = price.strip() price = price.strip()
if not price: if not price:
continue continue
title = ''.join(data.xpath('.//span[@class="prti"]/a/b/text()')) title = ''.join(data.xpath('.//span[@class="prti"]/a/b/text()'))
author = ''.join(data.xpath('.//p[@class="last"]/text()')) author = ''.join(data.xpath('.//p[@class="last"]/text()'))
a, b, author = author.partition(' by ') a, b, author = author.partition(' by ')
counter -= 1 counter -= 1
s = SearchResult()
s.cover_url = cover_url
s.title = title.strip()
s.author = author.strip()
s.price = price.strip()
s.detail_item = id.strip()
s.drm = SearchResult.DRM_UNLOCKED
yield s
# Exact match brought us to the books detail page.
else:
s = SearchResult() s = SearchResult()
s.cover_url = cover_url
s.title = title.strip() cover_url = ''.join(doc.xpath('//div[@id="image"]/a/img[@title="Zoom"]/@src')).strip()
s.author = author.strip() s.cover_url = url_slash_cleaner(self.url + cover_url.strip())
s.price = price.strip()
s.detail_item = id.strip() s.title = ''.join(doc.xpath('//form[@name="details"]/h1/text()')).strip()
authors = doc.xpath('//p[contains(., "Author:")]//text()')
author_index = None
for i, a in enumerate(authors):
if 'author' in a.lower():
author_index = i + 1
break
if author_index is not None and len(authors) > author_index:
a = authors[author_index]
a = a.replace(u'\xa0', '')
s.author = a.strip()
s.price = ''.join(doc.xpath('//span[@id="price_selling"]//text()')).strip()
s.detail_item = f.geturl().replace(self.url, '').strip()
s.formats = ', '.join(doc.xpath('//select[@id="N1_"]//option//text()'))
s.drm = SearchResult.DRM_UNLOCKED s.drm = SearchResult.DRM_UNLOCKED
yield s yield s
def get_details(self, search_result, timeout): def get_details(self, search_result, timeout):
if search_result.formats:
return False
br = browser() br = browser()
with closing(br.open(url_slash_cleaner(self.url + search_result.detail_item), timeout=timeout)) as nf: with closing(br.open(url_slash_cleaner(self.url + search_result.detail_item), timeout=timeout)) as nf:
idata = html.fromstring(nf.read()) idata = html.fromstring(nf.read())

View File

@ -1263,7 +1263,7 @@ class TagsModel(QAbstractItemModel): # {{{
d['last'] = data[key][cat_len-1] d['last'] = data[key][cat_len-1]
name = eval_formatter.safe_format(collapse_template, name = eval_formatter.safe_format(collapse_template,
d, 'TAG_VIEW', None) d, 'TAG_VIEW', None)
self.beginInsertRows(category_index, 999999, 1) #len(data[key])-1) self.beginInsertRows(category_index, 999998, 999999) #len(data[key])-1)
sub_cat = TagTreeItem(parent=category, data = name, sub_cat = TagTreeItem(parent=category, data = name,
tooltip = None, temporary=True, tooltip = None, temporary=True,
category_icon = category_node.icon, category_icon = category_node.icon,
@ -1296,7 +1296,7 @@ class TagsModel(QAbstractItemModel): # {{{
key in ['authors', 'publisher', 'news', 'formats', 'rating'] or key in ['authors', 'publisher', 'news', 'formats', 'rating'] or
key not in self.db.prefs.get('categories_using_hierarchy', []) or key not in self.db.prefs.get('categories_using_hierarchy', []) or
len(components) == 1): len(components) == 1):
self.beginInsertRows(category_index, 999999, 1) self.beginInsertRows(category_index, 999998, 999999)
n = TagTreeItem(parent=node_parent, data=tag, tooltip=tt, n = TagTreeItem(parent=node_parent, data=tag, tooltip=tt,
icon_map=self.icon_state_map) icon_map=self.icon_state_map)
if tag.id_set is not None: if tag.id_set is not None:
@ -1332,7 +1332,7 @@ class TagsModel(QAbstractItemModel): # {{{
t.is_hierarchical = \ t.is_hierarchical = \
'5state' if t.category != 'search' else '3state' '5state' if t.category != 'search' else '3state'
t.name = comp t.name = comp
self.beginInsertRows(category_index, 999999, 1) self.beginInsertRows(category_index, 999998, 999999)
node_parent = TagTreeItem(parent=node_parent, data=t, node_parent = TagTreeItem(parent=node_parent, data=t,
tooltip=tt, icon_map=self.icon_state_map) tooltip=tt, icon_map=self.icon_state_map)
child_map[(comp,tag.category)] = node_parent child_map[(comp,tag.category)] = node_parent

View File

@ -288,8 +288,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.db_images.reset() self.db_images.reset()
self.library_view.model().count_changed() self.library_view.model().count_changed()
self.tool_bar.database_changed(self.library_view.model().db) self.bars_manager.database_changed(self.library_view.model().db)
self.library_view.model().database_changed.connect(self.tool_bar.database_changed, self.library_view.model().database_changed.connect(self.bars_manager.database_changed,
type=Qt.QueuedConnection) type=Qt.QueuedConnection)
########################### Tags Browser ############################## ########################### Tags Browser ##############################
@ -324,7 +324,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.read_settings() self.read_settings()
self.finalize_layout() self.finalize_layout()
if self.tool_bar.showing_donate: if self.bars_manager.showing_donate:
self.donate_button.start_animation() self.donate_button.start_animation()
self.set_window_title() self.set_window_title()

View File

@ -22,7 +22,7 @@ It can convert every input format in the following list, to every output format.
*Input Formats:* CBZ, CBR, CBC, CHM, EPUB, FB2, HTML, HTMLZ, LIT, LRF, MOBI, ODT, PDF, PRC, PDB, PML, RB, RTF, SNB, TCR, TXT, TXTZ *Input Formats:* CBZ, CBR, CBC, CHM, EPUB, FB2, HTML, HTMLZ, LIT, LRF, MOBI, ODT, PDF, PRC, PDB, PML, RB, RTF, SNB, TCR, TXT, TXTZ
*Output Formats:* EPUB, FB2, OEB, LIT, LRF, MOBI, HTMLZ, PDB, PML, RB, PDF, SNB, TCR, TXT, TXTZ *Output Formats:* EPUB, FB2, OEB, LIT, LRF, MOBI, HTMLZ, PDB, PML, RB, PDF, RTF, SNB, TCR, TXT, TXTZ
.. note :: .. note ::

View File

@ -50,6 +50,12 @@ PARALLEL_FUNCS = {
'save_book' : 'save_book' :
('calibre.ebooks.metadata.worker', 'save_book', 'notification'), ('calibre.ebooks.metadata.worker', 'save_book', 'notification'),
'arbitrary' :
('calibre.utils.ipc.worker', 'arbitrary', None),
'arbitrary_n' :
('calibre.utils.ipc.worker', 'arbitrary', 'notification'),
} }
class Progress(Thread): class Progress(Thread):
@ -73,7 +79,55 @@ class Progress(Thread):
except: except:
break break
def arbitrary(module_name, func_name, args, kwargs={}):
'''
An entry point that allows arbitrary functions to be run in a parallel
process. useful for plugin developers that want to run jobs in a parallel
process.
To use this entry point, simply create a ParallelJob with the module and
function names for the real entry point.
Remember that args and kwargs must be serialized so only use basic types
for them.
To use this, you will do something like
from calibre.gui2 import Dispatcher
gui.job_manager.run_job(Dispatcher(job_done), 'arbitrary',
args=('calibre_plugins.myplugin.worker', 'do_work',
('arg1' 'arg2', 'arg3')),
description='Change the world')
The function job_done will be called on completion, see the code in
gui2.actions.catalog for an example of using run_job and Dispatcher.
:param module_name: The fully qualified name of the module that contains
the actual function to be run. For example:
calibre_plugins.myplugin.worker
:param func_name: The name of the function to be run.
:param name: A list (or tuple) of arguments that will be passed to the
function ``func_name``
:param kwargs: A dictionary of keyword arguments to pass to func_name
'''
module = importlib.import_module(module_name)
func = getattr(module, func_name)
return func(*args, **kwargs)
def arbitrary_n(module_name, func_name, args, kwargs={},
notification=lambda x, y: y):
'''
Same as :func:`arbitrary` above, except that func_name must support a
keyword argument "notification". This will be a function that accepts two
arguments. func_name should call it periodically with progress information.
The first argument is a float between 0 and 1 that represent percent
completed and the second is a string with a message (it can be an empty
string).
'''
module = importlib.import_module(module_name)
func = getattr(module, func_name)
kwargs['notification'] = notification
return func(*args, **kwargs)
def get_func(name): def get_func(name):
module, func, notification = PARALLEL_FUNCS[name] module, func, notification = PARALLEL_FUNCS[name]

View File

@ -10,7 +10,6 @@ License: http://www.opensource.org/licenses/mit-license.php
import re import re
from calibre.utils.icu import capitalize from calibre.utils.icu import capitalize
from calibre.utils.config import prefs
__all__ = ['titlecase'] __all__ = ['titlecase']
__version__ = '0.5' __version__ = '0.5'
@ -31,6 +30,17 @@ ALL_CAPS = re.compile(r'^[A-Z\s%s]+$' % PUNCT)
UC_INITIALS = re.compile(r"^(?:[A-Z]{1}\.{1}|[A-Z]{1}\.{1}[A-Z]{1})+$") UC_INITIALS = re.compile(r"^(?:[A-Z]{1}\.{1}|[A-Z]{1}\.{1}[A-Z]{1})+$")
MAC_MC = re.compile(r"^([Mm]a?c)(.+)") MAC_MC = re.compile(r"^([Mm]a?c)(.+)")
_lang = None
def lang():
global _lang
if _lang is None:
from calibre.utils.localization import get_lang
_lang = get_lang().lower()
return _lang
def titlecase(text): def titlecase(text):
""" """
@ -68,7 +78,7 @@ def titlecase(text):
line.append(icu_lower(word)) line.append(icu_lower(word))
continue continue
if prefs['language'].lower().startswith('en'): if lang().startswith('en'):
match = MAC_MC.match(word) match = MAC_MC.match(word)
if match and not match.group(2)[:3] in ('hin', 'ht'): if match and not match.group(2)[:3] in ('hin', 'ht'):
line.append("%s%s" % (capitalize(match.group(1)), line.append("%s%s" % (capitalize(match.group(1)),