From 5bce3d10d3c53ab5952c31799bb7eb6fe974eeab Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 16 Jan 2010 21:15:23 -0700 Subject: [PATCH 01/18] New recipe for Joop by kwetal --- resources/images/news/joop.png | Bin 0 -> 395 bytes resources/images/news/nrcnext.png | Bin 0 -> 1723 bytes resources/quick_start.epub | Bin 0 -> 14164 bytes resources/recipes/fokkeensukke.recipe | 83 +++++++++++--------- resources/recipes/joop.recipe | 91 ++++++++++++++++++++++ resources/recipes/ncrnext.recipe | 106 ++++++++++++++------------ 6 files changed, 195 insertions(+), 85 deletions(-) create mode 100644 resources/images/news/joop.png create mode 100644 resources/images/news/nrcnext.png create mode 100644 resources/quick_start.epub create mode 100644 resources/recipes/joop.recipe diff --git a/resources/images/news/joop.png b/resources/images/news/joop.png new file mode 100644 index 0000000000000000000000000000000000000000..0ea5e422e10e371a28e69249c24919b06d651df3 GIT binary patch literal 395 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6T^Rm@ z;DWu&Cj&(|3p^r=85p>QL70(Y)*K0-AbW|YuPgg4Mgd_Bv84~+iU3XF_H=O!u{gbT zvSraB2Ladk{tJ&hBpL0R4zf=4;wqJN>O0iwlW=N^Q!$s5@P-V@i+mcw4GUT{>ntw+ zT~_~JDlqgZ34=Xs%!6+OQw`j6dz3Z@r#uG*dyTVqSa4oVeW+ zwuWuixopuVdg0%1hy7yQx?N76ZNjtPuH1I9$*@hM%j2?0gNmoeBMHHSrLi){cji9y zpSqS|!!@P{yYhCc+Ud5|GaoY8!(XuO0KeSqgxz=Vv0KRS9Tz#3%r&>~vfP4I%Q@?= zWiz-fz8Kmtr+(S4b%KIDhI@E_-B^|MbFT_V(>F1**}RWGO5FcEqyF4_Gjk@Fmb-Pw l3oKH;87{v4_MXoR@d>ATEz_C04gf=n!PC{xWt~$(698N$oH_si literal 0 HcmV?d00001 diff --git a/resources/images/news/nrcnext.png b/resources/images/news/nrcnext.png new file mode 100644 index 0000000000000000000000000000000000000000..13497559257329a06ee962335bc9e47de7668062 GIT binary patch literal 1723 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 literal 0 HcmV?d00001 diff --git a/resources/recipes/fokkeensukke.recipe b/resources/recipes/fokkeensukke.recipe index 3ddbe1cfe5..76a4aa39b9 100644 --- a/resources/recipes/fokkeensukke.recipe +++ b/resources/recipes/fokkeensukke.recipe @@ -1,23 +1,29 @@ -#!/usr/bin/python from calibre.web.feeds.news import BasicNewsRecipe -from calibre.ebooks.BeautifulSoup import Tag +from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag class FokkeEnSukkeRecipe(BasicNewsRecipe) : __license__ = 'GPL v3' __author__ = 'kwetal' language = 'nl' - description = u'Popular Dutch daily cartoon Fokke en Sukke' + country = 'NL' + version = 2 title = u'Fokke en Sukke' - no_stylesheets = True - # For reasons unknown to me the extra css is, on the cartoon pages, inserted in the and not in the . My reader (Sony PRS-600) has a serious issue - # with that: it treats it as content and displays it as is. Setting this property to empty solves this for me. - template_css = '' - INDEX = u'http://foksuk.nl' + publisher = u'Reid, Geleijnse & Van Tol' + category = u'News, Cartoons' + description = u'Popular Dutch daily cartoon Fokke en Sukke' - # This cover is not as nice as it could be, needs some work - #cover_url = 'http://foksuk.nl/content/wysiwyg/simpleimages/image350.gif' + conversion_options = {'comments': description, 'language': language, 'publisher': publisher} + + no_stylesheets = True + extra_css = ''' + body{font-family: verdana, arial, helvetica, geneva, sans-serif ; margin: 0em; padding: 0em;} + div.title {text-align: center; margin-bottom: 1em;} + ''' + + INDEX = u'http://foksuk.nl' + cover_url = 'http://foksuk.nl/content/wysiwyg/simpleimages/image350.gif' keep_only_tags = [dict(name='div', attrs={'class' : 'cartoon'})] @@ -31,15 +37,14 @@ class FokkeEnSukkeRecipe(BasicNewsRecipe) : links = index.findAll('a') maxIndex = len(links) - 1 articles = [] - for i in range(len(links)) : - # The first link does not interest us, as it points to no cartoon. A begin_at parameter in the range() function would be nice. - if i == 0 : - continue - - # There can be more than one cartoon for a given day (currently either one or two). If there's only one, there is just a link with the dayname. - # If there are two, there are three links in sequence: dayname 1 2. In that case we're interested in the last two. + for i in range(1, len(links)) : + # There can be more than one cartoon for a given day (currently either one or two). + # If there's only one, there is just a link with the dayname. + # If there are two, there are three links in sequence: dayname 1 2. + # In that case we're interested in the last two. if links[i].renderContents() in dayNames : - # If the link is not in daynames, we processed it already, but if it is, let's see if the next one has '1' as content + # If the link is not in daynames, we processed it already, but if it is, let's see + # if the next one has '1' as content if (i + 1 <= maxIndex) and (links[i + 1].renderContents() == '1') : # Got you! Add it to the list article = {'title' : links[i].renderContents() + ' 1', 'date' : u'', 'url' : self.INDEX + links[i + 1]['href'], 'description' : ''} @@ -59,29 +64,31 @@ class FokkeEnSukkeRecipe(BasicNewsRecipe) : return [[week, articles]] def preprocess_html(self, soup) : - # This method is called for every page, be it cartoon or TOC. We need to process each in their own way cartoon = soup.find('div', attrs={'class' : 'cartoon'}) - if cartoon : - # It is a cartoon. Extract the title. - title = '' - img = soup.find('img', attrs = {'alt' : True}) - if img : - title = img['alt'] - # Using the 'extra_css' displays it in the and not in the . See comment at the top of this class. Setting the style this way solves that. - tag = Tag(soup, 'div', [('style', 'text-align: center; margin-bottom: 8px')]) - tag.insert(0, title) - cartoon.insert(0, tag) + title = '' + img = soup.find('img', attrs = {'alt' : True}) + if img : + title = img['alt'] - # I have not quite worked out why, but we have to throw out this part of the page. It contains the very same index we processed earlier, - # and Calibre does not like that too much. As far as I can tell it goes into recursion and the result is an empty eBook. - select = cartoon.find('div', attrs={'class' : 'selectcartoon'}) - if select : - select.extract() + tag = Tag(soup, 'div', [('class', 'title')]) + tag.insert(0, title) + cartoon.insert(0, tag) - return cartoon - else : - # It is a TOC. Just return the whole lot. - return soup + # We only want the cartoon, so throw out the index + select = cartoon.find('div', attrs={'class' : 'selectcartoon'}) + if select : + select.extract() + + freshSoup = self.getFreshSoup(soup) + freshSoup.body.append(cartoon) + + return freshSoup + + def getFreshSoup(self, oldSoup): + freshSoup = BeautifulSoup('') + if oldSoup.head.title: + freshSoup.head.title.append(self.tag_to_string(oldSoup.head.title)) + return freshSoup diff --git a/resources/recipes/joop.recipe b/resources/recipes/joop.recipe new file mode 100644 index 0000000000..a913328b9b --- /dev/null +++ b/resources/recipes/joop.recipe @@ -0,0 +1,91 @@ +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import Tag +import re + +class JoopRecipe(BasicNewsRecipe): + __license__ = 'GPL v3' + __author__ = 'kwetal' + language = 'nl' + country = 'NL' + version = 1 + + title = u'Joop' + publisher = u'Vara' + category = u'News, Politics, Discussion' + description = u'Political blog from the Netherlands' + + oldest_article = 7 + max_articles_per_feed = 100 + use_embedded_content = False + + no_stylesheets = True + remove_javascript = True + + keep_only_tags = [] + keep_only_tags.append(dict(name = 'div', attrs = {'class': 'author_head clearfix photo'})) + keep_only_tags.append(dict(name = 'h2', attrs = {'class': 'columnhead smallline'})) + keep_only_tags.append(dict(name = 'div', attrs = {'class': re.compile('article.*')})) + + extra_css = ''' + body {font-family: verdana, arial, helvetica, geneva, sans-serif;} + img {margin-right: 0.4em;} + h3 {font-size: medium; font-style: italic; font-weight: normal;} + h2 {font-size: xx-large; font-weight: bold} + sub {color: #666666; font-size: x-small; font-weight: normal;} + div.joop_byline {font-size: large} + div.joop_byline_job {font-size: small; color: #696969;} + div.joop_date {font-size: x-small; font-style: italic; margin-top: 0.6em} + ''' + + INDEX = 'http://www.joop.nl' + + conversion_options = {'comments': description, 'tags': category, 'language': language, + 'publisher': publisher} + + def parse_index(self): + sections = ['Politiek', 'Wereld', 'Economie', 'Groen', 'Media', 'Leven', 'Show', 'Opinies'] + soup = self.index_to_soup(self.INDEX) + answer = [] + + div = soup.find('div', attrs = {'id': 'footer'}) + for section in sections: + articles = [] + h2 = div.find(lambda tag: tag.name == 'h2' and tag.renderContents() == section) + if h2: + ul = h2.findNextSibling('ul', 'linklist') + if ul: + for li in ul.findAll('li'): + title = self.tag_to_string(li.a) + url = self.INDEX + li.a['href'] + articles.append({'title': title, 'date': None, 'url': url, 'description': ''}) + + answer.append((section, articles)) + + return answer + + def preprocess_html(self, soup): + div = soup.find('div', 'author_head clearfix photo') + if div: + h2 = soup.find('h2') + if h2: + h2.name = 'div' + h2['class'] = 'joop_byline' + span = h2.find('span') + if span: + span.name = 'div' + span['class'] = 'joop_byline_job' + div.replaceWith(h2) + + h2 = soup.find('h2', attrs = {'class': 'columnhead smallline'}) + if h2: + txt = None + span = h2.find('span', 'info') + if span: + txt = span.find(text = True) + div = Tag(soup, 'div', attrs = [('class', 'joop_date')]) + div.append(txt) + h2.replaceWith(div) + + return soup + + diff --git a/resources/recipes/ncrnext.recipe b/resources/recipes/ncrnext.recipe index d8a51e62c8..e03da301fa 100644 --- a/resources/recipes/ncrnext.recipe +++ b/resources/recipes/ncrnext.recipe @@ -1,29 +1,38 @@ from calibre.web.feeds.news import BasicNewsRecipe -from calibre.ebooks.BeautifulSoup import BeautifulSoup +from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag class NrcNextRecipe(BasicNewsRecipe): - __license__ = 'GPL v3' + __license__ = 'GPL v3' __author__ = 'kwetal' - version = 1 language = 'nl' + country = 'NL' + version = 2 + + title = u'nrcnext' + publisher = u'NRC Media' + category = u'News, Opinion, the Netherlands' description = u'Dutch newsblog from the Dutch daily newspaper nrcnext.' - title = u'nrcnext' + + conversion_options = {'comments': description, 'language': language, 'publisher': publisher} no_stylesheets = True - template_css = '' + remove_javascript = True - # I want to do some special processing on the articles. I could not solve it with the 'extra_css' property . So we do it the hard way. keep_only_tags = [dict(name='div', attrs={'id' : 'main'})] - # If that's overkill for you comment out the previous line and uncomment the next. Then get rid of the preprocess_html() method. - #keep_only_tags = [dict(name='div', attrs={'class' : 'post'}), dict(name='div', attrs={'class' : 'vlag'}) ] - remove_tags = [dict(name = 'div', attrs = {'class' : 'meta'}), - dict(name = 'div', attrs = {'class' : 'datumlabel'}), - dict(name = 'ul', attrs = {'class' : 'cats single'}), - dict(name = 'ul', attrs = {'class' : 'cats onderwerpen'}), - dict(name = 'ul', attrs = {'class' : 'cats rubrieken'})] + remove_tags = [] + remove_tags.append(dict(name = 'div', attrs = {'class' : 'meta'})) + remove_tags.append(dict(name = 'div', attrs = {'class' : 'datumlabel'})) + remove_tags.append(dict(name = 'ul', attrs = {'class' : 'cats single'})) + remove_tags.append(dict(name = 'ul', attrs = {'class' : 'cats onderwerpen'})) + remove_tags.append(dict(name = 'ul', attrs = {'class' : 'cats rubrieken'})) - use_embedded_content = False + extra_css = ''' + body {font-family: verdana, arial, helvetica, geneva, sans-serif; text-align: left;} + p.wp-caption-text {font-size: x-small; color: #666666;} + h2.sub_title {font-size: medium; color: #696969;} + h2.vlag {font-size: small; font-weight: bold;} + ''' def parse_index(self) : # Use the wesbite as an index. Their RSS feeds can be out of date. @@ -44,10 +53,11 @@ class NrcNextRecipe(BasicNewsRecipe): # Find the links to the actual articles and rember the location they're pointing to and the title a = post.find('a', attrs={'rel' : 'bookmark'}) href = a['href'] - title = a.renderContents() + title = self.tag_to_string(a) if index == 'columnisten' : - # In this feed/page articles can be written by more than one author. It is nice to see their names in the titles. + # In this feed/page articles can be written by more than one author. + # It is nice to see their names in the titles. flag = post.find('h2', attrs = {'class' : 'vlag'}) author = flag.contents[0].renderContents() completeTitle = u''.join([author, u': ', title]) @@ -71,44 +81,46 @@ class NrcNextRecipe(BasicNewsRecipe): return answer def preprocess_html(self, soup) : - # This method is called for every page, be it cartoon or TOC. We need to process each in their own way - if soup.find('div', attrs = {'id' : 'main', 'class' : 'single'}) : - # It's an article, find the interesting part + if soup.find('div', attrs = {'id' : 'main', 'class' : 'single'}): tag = soup.find('div', attrs = {'class' : 'post'}) - if tag : - # And replace any links with their text, so they don't show up underlined on my reader. - for link in tag.findAll('a') : - link.replaceWith(link.renderContents()) + if tag: + h2 = tag.find('h2', 'vlag') + if h2: + new_h2 = Tag(soup, 'h2', attrs = [('class', 'vlag')]) + new_h2.append(self.tag_to_string(h2)) + h2.replaceWith(new_h2) + else: + h2 = tag.find('h2') + if h2: + new_h2 = Tag(soup, 'h2', attrs = [('class', 'sub_title')]) + new_h2.append(self.tag_to_string(h2)) + h2.replaceWith(new_h2) - # Slows down my Sony reader; feel free to comment out - for movie in tag.findAll('span', attrs = {'class' : 'vvqbox vvqvimeo'}) : + h1 = tag.find('h1') + if h1: + new_h1 = Tag(soup, 'h1') + new_h1.append(self.tag_to_string(h1)) + h1.replaceWith(new_h1) + + # Slows down my reader. + for movie in tag.findAll('span', attrs = {'class' : 'vvqbox vvqvimeo'}): movie.extract() - for movie in tag.findAll('span', attrs = {'class' : 'vvqbox vvqyoutube'}) : + for movie in tag.findAll('span', attrs = {'class' : 'vvqbox vvqyoutube'}): movie.extract() + for iframe in tag.findAll('iframe') : + iframe.extract() - homeMadeSoup = BeautifulSoup('') - body = homeMadeSoup.find('body') - body.append(tag) + fresh_soup = self.getFreshSoup(soup) + fresh_soup.body.append(tag) - return homeMadeSoup - else : + return fresh_soup + else: # This should never happen and other famous last words... return soup - else : - # It's a TOC, return the whole lot. - return soup - - def postproces_html(self, soup) : - # Should not happen, but it does. Slows down my Sony eReader - for img in soup.findAll('img') : - if img['src'].startswith('http://') : - img.extract() - - # Happens for some movies which we are not able to view anyway - for iframe in soup.findAll('iframe') : - if iframe['src'].startswith('http://') : - iframe.extract() - - + def getFreshSoup(self, oldSoup): + freshSoup = BeautifulSoup('') + if oldSoup.head.title: + freshSoup.head.title.append(self.tag_to_string(oldSoup.head.title)) + return freshSoup From ee561de271784f0b9ec103e87f8c7730a2749d99 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 16 Jan 2010 21:17:47 -0700 Subject: [PATCH 02/18] New recipe for Le Devoir by Lorenzo Vigentini --- resources/recipes/ledevoir.recipe | 80 +++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 resources/recipes/ledevoir.recipe diff --git a/resources/recipes/ledevoir.recipe b/resources/recipes/ledevoir.recipe new file mode 100644 index 0000000000..4612beea2e --- /dev/null +++ b/resources/recipes/ledevoir.recipe @@ -0,0 +1,80 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'Lorenzo Vigentini' +__copyright__ = '2009, Lorenzo Vigentini ' +__version__ = 'v1.01' +__date__ = '14, January 2010' +__description__ = 'Canadian Paper ' + +''' +http://www.ledevoir.com/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class ledevoir(BasicNewsRecipe): + author = 'Lorenzo Vigentini' + description = 'Canadian Paper' + + cover_url = 'http://www.ledevoir.com/images/ul/graphiques/logo_devoir.gif' + title = u'Le Devoir' + publisher = 'leDevoir.com' + category = 'News, finance, economy, politics' + + language = 'fr' + encoding = 'utf-8' + timefmt = '[%a, %d %b, %Y]' + + oldest_article = 1 + max_articles_per_feed = 50 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True + + keep_only_tags = [ + dict(name='div', attrs={'id':'article'}), + dict(name='ul', attrs={'id':'ariane'}) + ] + + remove_tags = [ + dict(name='div', attrs={'id':'dialog'}), + dict(name='div', attrs={'class':['interesse_actions','reactions']}), + dict(name='ul', attrs={'class':'mots_cles'}), + dict(name='a', attrs={'class':'haut'}), + dict(name='h5', attrs={'class':'interesse_actions'}) + ] + + feeds = [ + (u'A la une', 'http://www.ledevoir.com/rss/manchettes.xml'), + (u'Edition complete', 'http://feeds2.feedburner.com/fluxdudevoir'), + (u'Opinions', 'http://www.ledevoir.com/rss/opinions.xml'), + (u'Chroniques', 'http://www.ledevoir.com/rss/chroniques.xml'), + (u'Politique', 'http://www.ledevoir.com/rss/section/politique.xml?id=51'), + (u'International', 'http://www.ledevoir.com/rss/section/international.xml?id=76'), + (u'Culture', 'http://www.ledevoir.com/rss/section/culture.xml?id=48'), + (u'Environnement', 'http://www.ledevoir.com/rss/section/environnement.xml?id=78'), + (u'Societe', 'http://www.ledevoir.com/rss/section/societe.xml?id=52'), + (u'Economie', 'http://www.ledevoir.com/rss/section/economie.xml?id=49'), + (u'Sports', 'http://www.ledevoir.com/rss/section/sports.xml?id=85'), + (u'Loisirs', 'http://www.ledevoir.com/rss/section/loisirs.xml?id=50') + ] + + extra_css = ''' + h1 {color:#1C1E7C;font-family:Times,Georgia,serif;font-size:1.85em;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:bold;line-height:1.2em;margin:0 0 5px;} + h2 {color:#333333;font-family:Times,Georgia,serif;font-size:1.5em;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:1.2em;margin:0 0 5px;} + h3 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:15px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px;} + 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:14px; } + 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:14px; text-transform:uppercase;} + .specs {line-height:1em;margin:1px 0;} + .specs span.auteur {font:0.85em/1.1em Arial, Verdana, sans-serif;color:#787878;} + .specs span.auteur a, + .specs span.auteur span {text-transform:uppercase;color:#787878;} + .specs .date {font:0.85em/1.1em Arial, Verdana, sans-serif;color:#787878;} + ul#ariane {list-style-type:none;margin:0;padding:5px 0 8px 0;font:0.85em/1.2em Arial, Verdana, sans-serif;color:#2E2E2E;border-bottom:10px solid #fff;} + ul#ariane li {display:inline;} + ul#ariane a {color:#2E2E2E;text-decoration:underline;} + .credit {color:#787878;font-size:0.71em;line-height:1.1em;font-weight:bold;} + .texte {font-size:1.15em;line-height:1.4em;margin-bottom:17px;} + ''' From e12253ff151c83d749cd17f98bdca10ad6fa4410 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 16 Jan 2010 21:24:58 -0700 Subject: [PATCH 03/18] Add quick start guide on first run --- src/calibre/gui2/__init__.py | 3 ++- src/calibre/gui2/ui.py | 17 +++++++++++++++-- src/calibre/utils/config.py | 28 +++++++++++++++++++++++----- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index db4bb5c754..34f9f57161 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -10,11 +10,12 @@ from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \ ORG_NAME = 'KovidsBrain' APP_UID = 'libprs500' from calibre import islinux, iswindows, isosx -from calibre.utils.config import Config, ConfigProxy, dynamic +from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig from calibre.utils.localization import set_qt_translator from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats from calibre.ebooks.metadata import MetaInformation +gprefs = JSONConfig('gui') NONE = QVariant() #: Null value to return from the data function of item models diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index f85a19da24..6cbae7f7b0 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -31,7 +31,7 @@ from calibre.utils.ipc.server import Server from calibre.gui2 import warning_dialog, choose_files, error_dialog, \ question_dialog,\ pixmap_to_data, choose_dir, \ - Dispatcher, \ + Dispatcher, gprefs, \ available_height, \ max_available_height, config, info_dialog, \ available_width, GetMetadata @@ -518,7 +518,21 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.connect(self.library_view.model(), SIGNAL('count_changed(int)'), self.tags_view.recount) self.connect(self.search, SIGNAL('cleared()'), self.tags_view.clear) + if not gprefs.get('quick_start_guide_added', False): + from calibre.ebooks.metadata import MetaInformation + mi = MetaInformation(_('Calibre Quick Start Guide'), ['John Schember']) + mi.author_sort = 'Schember, John' + mi.comments = "A guide to get you up an running with calibre" + mi.publisher = 'calibre' + self.library_view.model().add_books([P('quick_start.epub')], ['epub'], + [mi]) + gprefs['quick_start_guide_added'] = True + self.library_view.model().books_added(1) + if hasattr(self, 'db_images'): + self.db_images.reset() + self.library_view.model().count_changed() + ########################### Cover Flow ################################ self.cover_flow = None if CoverFlow is not None: @@ -1008,7 +1022,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): return self._add_books(books, to_device) - def _add_books(self, paths, to_device, on_card=None): if on_card is None: on_card = 'carda' if self.stack.currentIndex() == 2 else 'cardb' if self.stack.currentIndex() == 3 else None diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py index 697cfbe388..a0e5632cb7 100644 --- a/src/calibre/utils/config.py +++ b/src/calibre/utils/config.py @@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en' ''' Manage application-wide preferences. ''' -import os, re, cPickle, textwrap, traceback, plistlib +import os, re, cPickle, textwrap, traceback, plistlib, json from copy import deepcopy from functools import partial from optparse import OptionParser as _OptionParser @@ -564,23 +564,31 @@ class XMLConfig(dict): data types. ''' + EXTENSION = '.plist' + def __init__(self, rel_path_to_cf_file): dict.__init__(self) self.file_path = os.path.join(config_dir, *(rel_path_to_cf_file.split('/'))) self.file_path = os.path.abspath(self.file_path) - if not self.file_path.endswith('.plist'): - self.file_path += '.plist' + if not self.file_path.endswith(self.EXTENSION): + self.file_path += self.EXTENSION self.refresh() + def raw_to_object(self, raw): + return plistlib.readPlistFromString(raw) + + def to_raw(self): + return plistlib.writePlistToString(self) + def refresh(self): d = {} if os.path.exists(self.file_path): with ExclusiveFile(self.file_path) as f: raw = f.read() try: - d = plistlib.readPlistFromString(raw) if raw.strip() else {} + d = self.raw_to_object(raw) if raw.strip() else {} except SystemError: pass except: @@ -618,11 +626,21 @@ class XMLConfig(dict): if not os.path.exists(dpath): os.makedirs(dpath, mode=CONFIG_DIR_MODE) with ExclusiveFile(self.file_path) as f: - raw = plistlib.writePlistToString(self) + raw = self.to_raw() f.seek(0) f.truncate() f.write(raw) +class JSONConfig(XMLConfig): + + EXTENSION = '.json' + + def raw_to_object(self, raw): + return json.loads(raw.decode('utf-8')) + + def to_raw(self): + return json.dumps(self, indent=2) + def _prefs(): c = Config('global', 'calibre wide preferences') From 6e52a2ada0ee0220db25045b445e6c41cccd675a Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 17 Jan 2010 09:59:00 -0500 Subject: [PATCH 04/18] Update quick start guide. --- resources/quick_start.epub | Bin 14164 -> 14971 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/quick_start.epub b/resources/quick_start.epub index a70f9f13ecbf52a75d01236e214c625491266b32..d340d409968bcc4190010ff127dd2dfe948db80a 100644 GIT binary patch delta 14517 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 T;@=!#!7l#M4?TJQ)B3*vS5E^L delta 13699 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 From d2a6dc430569552c08ef3c1e98b1e0a6a0c64222 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 17 Jan 2010 09:42:35 -0700 Subject: [PATCH 05/18] Fix main mem and card being swapped in pocketbook detection on OS X --- src/calibre/devices/eb600/driver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/devices/eb600/driver.py b/src/calibre/devices/eb600/driver.py index d84f3c3e77..d3990e95ac 100644 --- a/src/calibre/devices/eb600/driver.py +++ b/src/calibre/devices/eb600/driver.py @@ -14,6 +14,7 @@ Windows PNP strings: 2W00000&1', 3, u'G:\\') ''' +import re from calibre.devices.usbms.driver import USBMS @@ -108,6 +109,7 @@ class POCKETBOOK360(EB600): OSX_MAIN_MEM = 'Philips Mass Storge Media' OSX_CARD_A_MEM = 'Philips Mass Storge Media' + OSX_MAIN_MEM_VOL_PAT = re.compile(r'/Pocket') @classmethod def can_handle(cls, dev, debug=False): From 10565bd06144f6e8ec55ea69b0890f287f1cb81d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 17 Jan 2010 09:57:58 -0700 Subject: [PATCH 06/18] Conversion pipeline: Don't error out if the user sets an invalid chapter detection XPath --- src/calibre/ebooks/oeb/transforms/structure.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/oeb/transforms/structure.py b/src/calibre/ebooks/oeb/transforms/structure.py index 2f52fde371..15e9675aa8 100644 --- a/src/calibre/ebooks/oeb/transforms/structure.py +++ b/src/calibre/ebooks/oeb/transforms/structure.py @@ -90,7 +90,10 @@ class DetectStructure(object): mark = etree.Element(XHTML('div'), style=page_break_after) else: # chapter_mark == 'both': mark = etree.Element(XHTML('hr'), style=page_break_before) - elem.addprevious(mark) + try: + elem.addprevious(mark) + except TypeError: + self.log.exception('Failed to mark chapter') def create_level_based_toc(self): if self.opts.level1_toc is None: From 5c243cda3b2832b11f608323a7465ad996e34471 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 17 Jan 2010 11:09:06 -0700 Subject: [PATCH 07/18] New recipe for Google Reader that downloads unread articles instead of just starred ones, by rollercoaster --- resources/recipes/greader_uber.recipe | 38 +++++++++++++++++++++++++++ resources/recipes/ledevoir.recipe | 1 - 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 resources/recipes/greader_uber.recipe diff --git a/resources/recipes/greader_uber.recipe b/resources/recipes/greader_uber.recipe new file mode 100644 index 0000000000..ee48e7069d --- /dev/null +++ b/resources/recipes/greader_uber.recipe @@ -0,0 +1,38 @@ +import urllib, re, mechanize +from calibre.web.feeds.recipes import BasicNewsRecipe +from calibre import __appname__ + +class GoogleReaderUber(BasicNewsRecipe): + title = 'Google Reader Uber' + description = 'This recipe downloads all unread feedsfrom your Google Reader account.' + needs_subscription = True + __author__ = 'rollercoaster, davec' + base_url = 'http://www.google.com/reader/atom/' + oldest_article = 365 + max_articles_per_feed = 250 + get_options = '?n=%d&xt=user/-/state/com.google/read' % max_articles_per_feed + use_embedded_content = True + + def get_browser(self): + br = BasicNewsRecipe.get_browser() + + if self.username is not None and self.password is not None: + request = urllib.urlencode([('Email', self.username), ('Passwd', self.password), + ('service', 'reader'), ('source', __appname__)]) + response = br.open('https://www.google.com/accounts/ClientLogin', request) + sid = re.search('SID=(\S*)', response.read()).group(1) + + cookies = mechanize.CookieJar() + br = mechanize.build_opener(mechanize.HTTPCookieProcessor(cookies)) + cookies.set_cookie(mechanize.Cookie(None, 'SID', sid, None, False, '.google.com', True, True, '/', True, False, None, True, '', '', None)) + return br + + + def get_feeds(self): + feeds = [] + soup = self.index_to_soup('http://www.google.com/reader/api/0/tag/list') + for id in soup.findAll(True, attrs={'name':['id']}): + url = id.contents[0].replace('broadcast','reading-list') + feeds.append((re.search('/([^/]*)$', url).group(1), + self.base_url + urllib.quote(url.encode('utf-8')) + self.get_options)) + return feeds diff --git a/resources/recipes/ledevoir.recipe b/resources/recipes/ledevoir.recipe index 4612beea2e..c9dbd8c5d7 100644 --- a/resources/recipes/ledevoir.recipe +++ b/resources/recipes/ledevoir.recipe @@ -25,7 +25,6 @@ class ledevoir(BasicNewsRecipe): encoding = 'utf-8' timefmt = '[%a, %d %b, %Y]' - oldest_article = 1 max_articles_per_feed = 50 use_embedded_content = False recursion = 10 From c6692c859ef8b6b8dc06421f22dd122fc200f52e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 17 Jan 2010 19:40:31 -0700 Subject: [PATCH 08/18] Fix multipage articles in The National Post --- resources/recipes/national_post.recipe | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/resources/recipes/national_post.recipe b/resources/recipes/national_post.recipe index d9743d5980..4fe188934c 100644 --- a/resources/recipes/national_post.recipe +++ b/resources/recipes/national_post.recipe @@ -70,11 +70,28 @@ class NYTimes(BasicNewsRecipe): feeds.append((current_section, current_articles)) return feeds + def preprocess_html(self, soup): story = soup.find(name='div', attrs={'class':'triline'}) - #td = heading.findParent(name='td') - #td.extract() + page2_link = soup.find('p','pagenav') + if page2_link: + atag = page2_link.find('a',href=True) + if atag: + page2_url = atag['href'] + if page2_url.startswith('story'): + page2_url = 'http://www.nationalpost.com/todays-paper/'+page2_url + elif page2_url.startswith( '/todays-paper/story.html'): + page2_url = 'http://www.nationalpost.com/'+page2_url + page2_soup = self.index_to_soup(page2_url) + if page2_soup: + page2_content = page2_soup.find('div','story-content') + if page2_content: + full_story = BeautifulSoup('

') + full_story.insert(0,story) + full_story.insert(1,page2_content) + story = full_story soup = BeautifulSoup('t') body = soup.find(name='body') body.insert(0, story) return soup + From 03714a978fc9453bc5df8d18cf12f3ee45c6a1a7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 17 Jan 2010 19:56:45 -0700 Subject: [PATCH 09/18] RTF Input: Support for unicode characters. Fixes #4501 (Unicode escaped RTF to XML problem) --- src/calibre/ebooks/rtf/input.py | 18 +- src/calibre/ebooks/rtf/preprocess.py | 344 +++++++++++++++++++++++++++ 2 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 src/calibre/ebooks/rtf/preprocess.py diff --git a/src/calibre/ebooks/rtf/input.py b/src/calibre/ebooks/rtf/input.py index 55f42ae4d5..ff20793f39 100644 --- a/src/calibre/ebooks/rtf/input.py +++ b/src/calibre/ebooks/rtf/input.py @@ -169,6 +169,21 @@ class RTFInput(InputFormatPlugin): with open('styles.css', 'ab') as f: f.write(css) + def preprocess(self, fname): + self.log('\tPreprocessing to convert unicode characters') + try: + data = open(fname, 'rb').read() + from calibre.ebooks.rtf.preprocess import RtfTokenizer, RtfTokenParser + tokenizer = RtfTokenizer(data) + tokens = RtfTokenParser(tokenizer.tokens) + data = tokens.toRTF() + fname = 'preprocessed.rtf' + with open(fname, 'wb') as f: + f.write(data) + except: + self.log.exception( + 'Failed to preprocess RTF to convert unicode sequences, ignoring...') + return fname def convert(self, stream, options, file_ext, log, accelerators): @@ -177,8 +192,9 @@ class RTFInput(InputFormatPlugin): from calibre.ebooks.rtf2xml.ParseRtf import RtfInvalidCodeException self.log = log self.log('Converting RTF to XML...') + fname = self.preprocess(stream.name) try: - xml = self.generate_xml(stream.name) + xml = self.generate_xml(fname) except RtfInvalidCodeException: raise ValueError(_('This RTF file has a feature calibre does not ' 'support. Convert it to HTML first and then try it.')) diff --git a/src/calibre/ebooks/rtf/preprocess.py b/src/calibre/ebooks/rtf/preprocess.py new file mode 100644 index 0000000000..07e6d41fac --- /dev/null +++ b/src/calibre/ebooks/rtf/preprocess.py @@ -0,0 +1,344 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2010, Gerendi Sandor Attila' +__docformat__ = 'restructuredtext en' + +""" +RTF tokenizer and token parser. v.1.0 (1/17/2010) +Author: Gerendi Sandor Attila + +At this point this will tokenize a RTF file then rebuild it from the tokens. +In the process the UTF8 tokens are altered to be supported by the RTF2XML and also remain RTF specification compilant. +""" + +class tokenDelimitatorStart(): + def __init__(self): + pass + def toRTF(self): + return b'{' + def __repr__(self): + return '{' + +class tokenDelimitatorEnd(): + def __init__(self): + pass + def toRTF(self): + return b'}' + def __repr__(self): + return '}' + +class tokenControlWord(): + def __init__(self, name, separator = ''): + self.name = name + self.separator = separator + def toRTF(self): + return self.name + self.separator + def __repr__(self): + return self.name + self.separator + +class tokenControlWordWithNumericArgument(): + def __init__(self, name, argument, separator = ''): + self.name = name + self.argument = argument + self.separator = separator + def toRTF(self): + return self.name + repr(self.argument) + self.separator + def __repr__(self): + return self.name + repr(self.argument) + self.separator + +class tokenControlSymbol(): + def __init__(self, name): + self.name = name + def toRTF(self): + return self.name + def __repr__(self): + return self.name + +class tokenData(): + def __init__(self, data): + self.data = data + def toRTF(self): + return self.data + def __repr__(self): + return self.data + +class tokenBinN(): + def __init__(self, data, separator = ''): + self.data = data + self.separator = separator + def toRTF(self): + return "\\bin" + repr(len(self.data)) + self.separator + self.data + def __repr__(self): + return "\\bin" + repr(len(self.data)) + self.separator + self.data + +class token8bitChar(): + def __init__(self, data): + self.data = data + def toRTF(self): + return "\\'" + self.data + def __repr__(self): + return "\\'" + self.data + +class tokenUnicode(): + def __init__(self, data, separator = '', current_ucn = 1, eqList = []): + self.data = data + self.separator = separator + self.current_ucn = current_ucn + self.eqList = eqList + def toRTF(self): + result = '\\u' + repr(self.data) + ' ' + ucn = self.current_ucn + if len(self.eqList) < ucn: + ucn = len(self.eqList) + result = tokenControlWordWithNumericArgument('\\uc', ucn).toRTF() + result + i = 0 + for eq in self.eqList: + if i >= ucn: + break + result = result + eq.toRTF() + return result + def __repr__(self): + return '\\u' + repr(self.data) + + +def isAsciiLetter(value): + return ((value >= 'a') and (value <= 'z')) or ((value >= 'A') and (value <= 'Z')) + +def isDigit(value): + return (value >= '0') and (value <= '9') + +def isChar(value, char): + return value == char + +def isString(buffer, string): + return buffer == string + + +class RtfTokenParser(): + def __init__(self, tokens): + self.tokens = tokens + self.process() + self.processUnicode() + + def process(self): + i = 0 + newTokens = [] + while i < len(self.tokens): + if isinstance(self.tokens[i], tokenControlSymbol): + if isString(self.tokens[i].name, "\\'"): + i = i + 1 + if not isinstance(self.tokens[i], tokenData): + raise BaseException('Error: token8bitChar without data.') + if len(self.tokens[i].data) < 2: + raise BaseException('Error: token8bitChar without data.') + newTokens.append(token8bitChar(self.tokens[i].data[0:2])) + if len(self.tokens[i].data) > 2: + newTokens.append(tokenData(self.tokens[i].data[2:])) + i = i + 1 + continue + + newTokens.append(self.tokens[i]) + i = i + 1 + + self.tokens = list(newTokens) + + def processUnicode(self): + i = 0 + newTokens = [] + ucNbStack = [1] + while i < len(self.tokens): + if isinstance(self.tokens[i], tokenDelimitatorStart): + ucNbStack.append(ucNbStack[len(ucNbStack) - 1]) + newTokens.append(self.tokens[i]) + i = i + 1 + continue + if isinstance(self.tokens[i], tokenDelimitatorEnd): + ucNbStack.pop() + newTokens.append(self.tokens[i]) + i = i + 1 + continue + if isinstance(self.tokens[i], tokenControlWordWithNumericArgument): + if isString(self.tokens[i].name, '\\uc'): + ucNbStack[len(ucNbStack) - 1] = self.tokens[i].argument + newTokens.append(self.tokens[i]) + i = i + 1 + continue + if isString(self.tokens[i].name, '\\u'): + x = i + j = 0 + i = i + 1 + replace = [] + partialData = None + ucn = ucNbStack[len(ucNbStack) - 1] + while (i < len(self.tokens)) and (j < ucn): + if isinstance(self.tokens[i], tokenDelimitatorStart): + break + if isinstance(self.tokens[i], tokenDelimitatorEnd): + break + if isinstance(self.tokens[i], tokenData): + if len(self.tokens[i].data) >= ucn - j: + replace.append(tokenData(self.tokens[i].data[0 : ucn - j])) + if len(self.tokens[i].data) > ucn - j: + partialData = tokenData(self.tokens[i].data[ucn - j:]) + i = i + 1 + break + else: + replace.append(self.tokens[i]) + j = j + len(self.tokens[i].data) + i = i + 1 + continue + if isinstance(self.tokens[i], token8bitChar) or isinstance(self.tokens[i], tokenBinN): + replace.append(self.tokens[i]) + i = i + 1 + j = j + 1 + continue + raise BaseException('Error: incorect utf replacement.') + + #calibre rtf2xml does not support utfreplace + replace = [] + + newTokens.append(tokenUnicode(self.tokens[x].argument, self.tokens[x].separator, ucNbStack[len(ucNbStack) - 1], replace)) + if partialData != None: + newTokens.append(partialData) + continue + + newTokens.append(self.tokens[i]) + i = i + 1 + + self.tokens = list(newTokens) + + + def toRTF(self): + result = [] + for token in self.tokens: + result.append(token.toRTF()) + return "".join(result) + + +class RtfTokenizer(): + def __init__(self, rtfData): + self.rtfData = [] + self.tokens = [] + self.rtfData = rtfData + self.tokenize() + + def tokenize(self): + i = 0 + lastDataStart = -1 + while i < len(self.rtfData): + + if isChar(self.rtfData[i], '{'): + if lastDataStart > -1: + self.tokens.append(tokenData(self.rtfData[lastDataStart : i])) + lastDataStart = -1 + self.tokens.append(tokenDelimitatorStart()) + i = i + 1 + continue + + if isChar(self.rtfData[i], '}'): + if lastDataStart > -1: + self.tokens.append(tokenData(self.rtfData[lastDataStart : i])) + lastDataStart = -1 + self.tokens.append(tokenDelimitatorEnd()) + i = i + 1 + continue + + if isChar(self.rtfData[i], '\\'): + if i + 1 >= len(self.rtfData): + raise BaseException('Error: Control character found at the end of the document.') + + if lastDataStart > -1: + self.tokens.append(tokenData(self.rtfData[lastDataStart : i])) + lastDataStart = -1 + + tokenStart = i + i = i + 1 + + #Control Words + if isAsciiLetter(self.rtfData[i]): + #consume + consumed = False + while i < len(self.rtfData): + if not isAsciiLetter(self.rtfData[i]): + tokenEnd = i + consumed = True + break + i = i + 1 + + if not consumed: + raise BaseException('Error (at:%d): Control Word without end.'%(tokenStart)) + + #we have numeric argument before delimiter + if isChar(self.rtfData[i], '-') or isDigit(self.rtfData[i]): + #consume the numeric argument + consumed = False + l = 0 + while i < len(self.rtfData): + if not isDigit(self.rtfData[i]): + consumed = True + break + l = l + 1 + i = i + 1 + if l > 10 : + raise BaseException('Error (at:%d): Too many digits in control word numeric argument.'%[tokenStart]) + + if not consumed: + raise BaseException('Error (at:%d): Control Word without numeric argument end.'%[tokenStart]) + + separator = '' + if isChar(self.rtfData[i], ' '): + separator = ' ' + + controlWord = self.rtfData[tokenStart: tokenEnd] + if tokenEnd < i: + value = int(self.rtfData[tokenEnd: i]) + if isString(controlWord, "\\bin"): + i = i + value + self.tokens.append(tokenBinN(self.rtfData[tokenStart:i], separator)) + else: + self.tokens.append(tokenControlWordWithNumericArgument(controlWord, value, separator)) + else: + self.tokens.append(tokenControlWord(controlWord, separator)) + #space delimiter, we should discard it + if self.rtfData[i] == ' ': + i = i + 1 + + #Control Symbol + else: + self.tokens.append(tokenControlSymbol(self.rtfData[tokenStart : i + 1])) + i = i + 1 + continue + + if lastDataStart < 0: + lastDataStart = i + i = i + 1 + + def toRTF(self): + result = [] + for token in self.tokens: + result.append(token.toRTF()) + return "".join(result) + + +if __name__ == "__main__": + import sys + if len(sys.argv) < 2: + print ("Usage %prog rtfFileToConvert") + sys.exit() + f = open(sys.argv[1], 'rb') + data = f.read() + f.close() + + tokenizer = RtfTokenizer(data) + parsedTokens = RtfTokenParser(tokenizer.tokens) + + data = parsedTokens.toRTF() + + f = open(sys.argv[1], 'w') + f.write(data) + f.close() + + From 96e32a2deff9676570e15ff33a1a6f168cdef16d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 17 Jan 2010 20:04:19 -0700 Subject: [PATCH 10/18] New recipe for drivelry.com by Krittika Goyal --- resources/recipes/drivelry.recipe | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 resources/recipes/drivelry.recipe diff --git a/resources/recipes/drivelry.recipe b/resources/recipes/drivelry.recipe new file mode 100644 index 0000000000..9e001ba530 --- /dev/null +++ b/resources/recipes/drivelry.recipe @@ -0,0 +1,41 @@ +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import BeautifulSoup + +class drivelrycom(BasicNewsRecipe): + title = u'drivelry.com' + language = 'en' + description = 'A blog by Mike Abrahams' + __author__ = 'Krittika Goyal' + oldest_article = 60 #days + max_articles_per_feed = 25 + #encoding = 'latin1' + + remove_stylesheets = True + #remove_tags_before = dict(name='h1', attrs={'class':'heading'}) + remove_tags_after = dict(name='div', attrs={'id':'bookmark'}) + remove_tags = [ + dict(name='iframe'), + dict(name='div', attrs={'class':['sidebar']}), + dict(name='div', attrs={'id':['bookmark']}), + #dict(name='span', attrs={'class':['related_link', 'slideshowcontrols']}), + #dict(name='ul', attrs={'class':'articleTools'}), + ] + + feeds = [ +('drivelry.com', + 'http://feeds.feedburner.com/drivelry'), + +] + + def preprocess_html(self, soup): + story = soup.find(name='div', attrs={'id':'main'}) + #td = heading.findParent(name='td') + #td.extract() + soup = BeautifulSoup(''' +t +

To donate to this blog: click here

+ +''') + body = soup.find(name='body') + body.insert(0, story) + return soup From 547e984accf3e44b8b205ca5759481b9e249c475 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 17 Jan 2010 20:12:54 -0700 Subject: [PATCH 11/18] RTF metadata: Fix reading metadata from very small files --- src/calibre/ebooks/metadata/rtf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/metadata/rtf.py b/src/calibre/ebooks/metadata/rtf.py index 7f418de8d7..d116ec30fb 100644 --- a/src/calibre/ebooks/metadata/rtf.py +++ b/src/calibre/ebooks/metadata/rtf.py @@ -25,12 +25,14 @@ def get_document_info(stream): while not found: prefix = block[-6:] block = prefix + stream.read(block_size) + actual_block_size = len(block) - len(prefix) if len(block) == len(prefix): break idx = block.find(r'{\info') if idx >= 0: found = True - stream.seek(stream.tell() - block_size + idx - len(prefix)) + pos = stream.tell() - actual_block_size + idx - len(prefix) + stream.seek(pos) else: if block.find(r'\sect') > -1: break From b2857225db8c34e21dac25d825770742c83c6633 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 18 Jan 2010 09:25:16 -0700 Subject: [PATCH 12/18] New recipe for The Kitsap Sun by Darko Miletic --- resources/images/news/kitsapun.png | Bin 0 -> 2356 bytes resources/recipes/kitsapun.recipe | 44 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 resources/images/news/kitsapun.png create mode 100644 resources/recipes/kitsapun.recipe diff --git a/resources/images/news/kitsapun.png b/resources/images/news/kitsapun.png new file mode 100644 index 0000000000000000000000000000000000000000..4b7b883d52e86f751173b3f135e782bf56bae334 GIT binary patch literal 2356 zcmV-43Cs40P)VyN(zoyXKKGuP@x#5(?PA39c z+;eB<%>4f|bLGiD-0fKvRmaS5ryC-MnYY(M2to{j7y~gt2muFjRTW&mgw ziXsw1L6kreGb|f;VET-Cj2$0gd~~E!+Bars@(hNCr*rhsE|L^T6vV;IFjKUyN#$gQ zC(mp>2FFJmS*aLg+Kh7m7(YIOBo6G_%)#BS@sXQ|n;+MF1Qd*@xvZ z1_w$+oFq&U$f}4qb)A_q>pYfRaSQIw{(o*@$L2o~sYqqxwW1J; z*r#!GfE2|6Be{{?P&Yy!SS@wDzh;Ucs5;plH85+G&pG!}-uwPr0NDNJi|pL`6e$Hl z43vdXq=x&kJ%9*AhZF={AS5BEAT5O&fO|(r1cXMR*NV9}iAS9|=U&3%<*RUa-dg_{ zhxTlyOpzD^WfF?i8W01t$ib{hL~gkO5(ch-f-7!{_dQ?+mT64rGtbD`Saj(s<}O%{ zo3VYvV;tVQnSmk^0+a=Wyj zK0>L^87kRlX?kfKloh|+jYyeVdVe1SI+u$Bed-{cC` z@Y>s77X(wvDQB?ws;@CHJd@F*dwBhY2MN@qB2WaUj8Ft420@VKVEua1iSmA_#ob8( z?|6?XffE?$gAQPL>X|IL_8Y`vh_R!4d427#2~v?#Bm`%mpml#kF_G4MIW_JfBtq*| z6(iMJHQ>vj?wBcPM%+8~lc%4<`#yXZu^0ki-?r66pP)z)+=w0;PKyu`+!b-~5Sxl> z+V&Fxw~B22kQgC|6KEQZAP__d(vrTNu^b|nL*4!Si*Ld_QP;Vt@s_)3mb)@8rVXgu z6T3CQ?pI*MCW5(OF1Q$mVeVMVnm$@Z1j`fM_2A@LELm|k*%G~8P1dF&R<%-RLsd~T z+*;nZSZI0&>kKrr1Hsz`H^EIh&;!7zMe`)Y+*j zC1=GHOcis*%+PjP+f|u6>KXB{Be*urgysw?t%Wtx_H#`~B#a%|&6elxMe7rum^^bn z%RYJ!>WN;jV!W4`sFbR1`U5jMPuOWwv!kk8f?7aLFcia3b<`bco6Zmd5(G74?8sg= zKl5`ePjt_w&RW1FH+-M$1$C{R5w$uww+j0qQ0;JlJDUB~;9?y`oEmq}e(~0i+v^nQ-`6qseNr|epMl_hnxk=xB*Y~rbyKixHUBD~cYRCao zBS?eNAb}7fAw)z1?hRH_3QQc`!}_Ow(qN+9Gi}a7F1Yem>Rh8{^eUrQE4{W=Q}a_W z;Ot|sxnNQ7T(+*x$cgqw*jR6BQ-N70R&VdMvc3XBoJe4 z(`Q6g5ibb>jv{K5QP}t93*f@#AG@o=(!!-H5eaO5_5o6n7_R~YMQAd7()(lZhr87L z%ImOkJM2AdjMp_;VXT+Pc2WpLh)ww-TW-8k6gW;X5ZSx^&un<+SKa;?0*jWdWcdx> zBAa8L=~WdIb^UOzx$nlW|D`Jrsj6%CydBteFf%w9Da(?#-yUOSn%a5btc4eI_*l)5 z4Kh?l3ah~*B93LH2#Hk$#wKmALjNcUUjzE$5{v;X9FKOv4; z#!MJJ)}yM9bM}HoEI5DhDc=Pn#bs=LV=FVK3G=2W=1$9$1Bs&Th=&Si*Vd=O;i^yG z)4lhei*MK@B9Cl(_MwMw{n2yIX+yqx%Qc=cGz;2OR(|{@KKuDEGdw)`l#OF!qpV%C zn)NTQW9M62IBRyvnR82K4#PmK2r*(=(b_q8=}NBt^!K&^PXLb>MX`yOdF`SxYw`FTKK_r z*MH#``}jZOKU7_5ka2wYAOHXWC3HntbYx+4WjbSWWnpw>05UK!FfA}KEipJ$FgZFh zGdeUgEigAaFffzMOmYAK03~!qSaf7zbY(hiZ)9m^c>ppnF)%GKF)cATR53C-G%z|a aHZ3qWIxsLzr}_o}0000 Date: Mon, 18 Jan 2010 09:33:46 -0700 Subject: [PATCH 13/18] Improved recipe for FTD --- resources/recipes/ftd.recipe | 31 ++++-- resources/recipes/ledevoir.recipe | 158 +++++++++++++++--------------- 2 files changed, 100 insertions(+), 89 deletions(-) diff --git a/resources/recipes/ftd.recipe b/resources/recipes/ftd.recipe index db53a3ed19..d18f9bdc56 100644 --- a/resources/recipes/ftd.recipe +++ b/resources/recipes/ftd.recipe @@ -9,16 +9,16 @@ from calibre.web.feeds.news import BasicNewsRecipe class FTDe(BasicNewsRecipe): - + title = 'FTD' description = 'Financial Times Deutschland' __author__ = 'Oliver Niesner' use_embedded_content = False timefmt = ' [%d %b %Y]' - language = 'de' + language = _('German') max_articles_per_feed = 40 no_stylesheets = True - + remove_tags = [dict(id='navi_top'), dict(id='topbanner'), dict(id='seitenkopf'), @@ -28,8 +28,13 @@ class FTDe(BasicNewsRecipe): dict(id='ADS_Top'), dict(id='spinner'), dict(id='ftd-contentad'), + dict(id='ftd-promo'), dict(id='nava-50009007-1-0'), dict(id='navli-50009007-1-0'), + dict(id='Box5000534-0-0-0'), + dict(id='ExpV-1-0-0-1'), + dict(id='ExpV-1-0-0-0'), + dict(id='PollExpV-2-0-0-0'), dict(id='starRating'), dict(id='saveRating'), dict(id='yLayer'), @@ -44,14 +49,19 @@ class FTDe(BasicNewsRecipe): dict(name='ul', attrs={'class':'nav'}), dict(name='p', attrs={'class':'articleOptionHead'}), dict(name='p', attrs={'class':'articleOptionFoot'}), + dict(name='p', attrs={'class':'moreInfo'}), dict(name='div', attrs={'class':'chartBox'}), dict(name='div', attrs={'class':'ratingOpt starRatingContainer articleOptionFootFrame'}), dict(name='div', attrs={'class':'box boxArticleBasic boxComments boxTransparent'}), - dict(name='div', attrs={'class':'box boxNavTabs '}), + dict(name='div', attrs={'class':'box boxNavTabs'}), + dict(name='div', attrs={'class':'boxMMRgtLow'}), dict(name='span', attrs={'class':'vote_455857'}), dict(name='div', attrs={'class':'relatedhalb'}), dict(name='div', attrs={'class':'box boxListScrollOutline'}), + dict(name='div', attrs={'class':'box boxPhotoshow boxImgWide'}), + dict(name='div', attrs={'class':'box boxTeaser'}), dict(name='div', attrs={'class':'tagCloud'}), + dict(name='div', attrs={'class':'pollView'}), dict(name='div', attrs={'class':'box boxArticleBasic boxNavTabsOutline'}), dict(name='div', attrs={'class':'ftdHpNav'}), dict(name='div', attrs={'class':'ftdHead'}), @@ -67,11 +77,12 @@ class FTDe(BasicNewsRecipe): dict(name='div', attrs={'class':'wertungoben'}), dict(name='div', attrs={'class':'artikelfuss'}), dict(name='a', attrs={'class':'rating'}), + dict(name='a', attrs={'href':'#rt'}), dict(name='div', attrs={'class':'articleOptionFootFrame'}), dict(name='div', attrs={'class':'artikelsplitfaq'})] - remove_tags_after = [dict(name='a', attrs={'class':'more'})] - - feeds = [ ('Finanzen', 'http://www.ftd.de/rss2/finanzen/maerkte'), + #remove_tags_after = [dict(name='a', attrs={'class':'more'})] + + feeds = [ ('Finanzen', 'http://www.ftd.de/rss2/finanzen/maerkte'), ('Meinungshungrige', 'http://www.ftd.de/rss2/meinungshungrige'), ('Unternehmen', 'http://www.ftd.de/rss2/unternehmen'), ('Politik', 'http://www.ftd.de/rss2/politik'), @@ -82,8 +93,8 @@ class FTDe(BasicNewsRecipe): ('Auto', 'http://www.ftd.de/rss2/auto'), ('Lifestyle', 'http://www.ftd.de/rss2/lifestyle') - ] - + ] + def print_version(self, url): - return url + '?mode=print' + return url.replace('.html', '.html?mode=print') diff --git a/resources/recipes/ledevoir.recipe b/resources/recipes/ledevoir.recipe index c9dbd8c5d7..97b33c43a7 100644 --- a/resources/recipes/ledevoir.recipe +++ b/resources/recipes/ledevoir.recipe @@ -1,79 +1,79 @@ -#!/usr/bin/env python -__license__ = 'GPL v3' -__author__ = 'Lorenzo Vigentini' -__copyright__ = '2009, Lorenzo Vigentini ' -__version__ = 'v1.01' -__date__ = '14, January 2010' -__description__ = 'Canadian Paper ' - -''' -http://www.ledevoir.com/ -''' - -from calibre.web.feeds.news import BasicNewsRecipe - -class ledevoir(BasicNewsRecipe): - author = 'Lorenzo Vigentini' - description = 'Canadian Paper' - - cover_url = 'http://www.ledevoir.com/images/ul/graphiques/logo_devoir.gif' - title = u'Le Devoir' - publisher = 'leDevoir.com' - category = 'News, finance, economy, politics' - - language = 'fr' - encoding = 'utf-8' - timefmt = '[%a, %d %b, %Y]' - - max_articles_per_feed = 50 - use_embedded_content = False - recursion = 10 - - remove_javascript = True - no_stylesheets = True - - keep_only_tags = [ - dict(name='div', attrs={'id':'article'}), - dict(name='ul', attrs={'id':'ariane'}) - ] - - remove_tags = [ - dict(name='div', attrs={'id':'dialog'}), - dict(name='div', attrs={'class':['interesse_actions','reactions']}), - dict(name='ul', attrs={'class':'mots_cles'}), - dict(name='a', attrs={'class':'haut'}), - dict(name='h5', attrs={'class':'interesse_actions'}) - ] - - feeds = [ - (u'A la une', 'http://www.ledevoir.com/rss/manchettes.xml'), - (u'Edition complete', 'http://feeds2.feedburner.com/fluxdudevoir'), - (u'Opinions', 'http://www.ledevoir.com/rss/opinions.xml'), - (u'Chroniques', 'http://www.ledevoir.com/rss/chroniques.xml'), - (u'Politique', 'http://www.ledevoir.com/rss/section/politique.xml?id=51'), - (u'International', 'http://www.ledevoir.com/rss/section/international.xml?id=76'), - (u'Culture', 'http://www.ledevoir.com/rss/section/culture.xml?id=48'), - (u'Environnement', 'http://www.ledevoir.com/rss/section/environnement.xml?id=78'), - (u'Societe', 'http://www.ledevoir.com/rss/section/societe.xml?id=52'), - (u'Economie', 'http://www.ledevoir.com/rss/section/economie.xml?id=49'), - (u'Sports', 'http://www.ledevoir.com/rss/section/sports.xml?id=85'), - (u'Loisirs', 'http://www.ledevoir.com/rss/section/loisirs.xml?id=50') - ] - - extra_css = ''' - h1 {color:#1C1E7C;font-family:Times,Georgia,serif;font-size:1.85em;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:bold;line-height:1.2em;margin:0 0 5px;} - h2 {color:#333333;font-family:Times,Georgia,serif;font-size:1.5em;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:1.2em;margin:0 0 5px;} - h3 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:15px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px;} - 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:14px; } - 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:14px; text-transform:uppercase;} - .specs {line-height:1em;margin:1px 0;} - .specs span.auteur {font:0.85em/1.1em Arial, Verdana, sans-serif;color:#787878;} - .specs span.auteur a, - .specs span.auteur span {text-transform:uppercase;color:#787878;} - .specs .date {font:0.85em/1.1em Arial, Verdana, sans-serif;color:#787878;} - ul#ariane {list-style-type:none;margin:0;padding:5px 0 8px 0;font:0.85em/1.2em Arial, Verdana, sans-serif;color:#2E2E2E;border-bottom:10px solid #fff;} - ul#ariane li {display:inline;} - ul#ariane a {color:#2E2E2E;text-decoration:underline;} - .credit {color:#787878;font-size:0.71em;line-height:1.1em;font-weight:bold;} - .texte {font-size:1.15em;line-height:1.4em;margin-bottom:17px;} - ''' +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'Lorenzo Vigentini' +__copyright__ = '2009, Lorenzo Vigentini ' +__version__ = 'v1.01' +__date__ = '14, January 2010' +__description__ = 'Canadian Paper ' + +''' +http://www.ledevoir.com/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class ledevoir(BasicNewsRecipe): + author = 'Lorenzo Vigentini' + description = 'Canadian Paper' + + cover_url = 'http://www.ledevoir.com/images/ul/graphiques/logo_devoir.gif' + title = u'Le Devoir' + publisher = 'leDevoir.com' + category = 'News, finance, economy, politics' + + language = 'fr' + encoding = 'utf-8' + timefmt = '[%a, %d %b, %Y]' + + max_articles_per_feed = 50 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True + + keep_only_tags = [ + dict(name='div', attrs={'id':'article'}), + dict(name='ul', attrs={'id':'ariane'}) + ] + + remove_tags = [ + dict(name='div', attrs={'id':'dialog'}), + dict(name='div', attrs={'class':['interesse_actions','reactions']}), + dict(name='ul', attrs={'class':'mots_cles'}), + dict(name='a', attrs={'class':'haut'}), + dict(name='h5', attrs={'class':'interesse_actions'}) + ] + + feeds = [ + (u'A la une', 'http://www.ledevoir.com/rss/manchettes.xml'), + (u'Edition complete', 'http://feeds2.feedburner.com/fluxdudevoir'), + (u'Opinions', 'http://www.ledevoir.com/rss/opinions.xml'), + (u'Chroniques', 'http://www.ledevoir.com/rss/chroniques.xml'), + (u'Politique', 'http://www.ledevoir.com/rss/section/politique.xml?id=51'), + (u'International', 'http://www.ledevoir.com/rss/section/international.xml?id=76'), + (u'Culture', 'http://www.ledevoir.com/rss/section/culture.xml?id=48'), + (u'Environnement', 'http://www.ledevoir.com/rss/section/environnement.xml?id=78'), + (u'Societe', 'http://www.ledevoir.com/rss/section/societe.xml?id=52'), + (u'Economie', 'http://www.ledevoir.com/rss/section/economie.xml?id=49'), + (u'Sports', 'http://www.ledevoir.com/rss/section/sports.xml?id=85'), + (u'Loisirs', 'http://www.ledevoir.com/rss/section/loisirs.xml?id=50') + ] + + extra_css = ''' + h1 {color:#1C1E7C;font-family:Times,Georgia,serif;font-size:1.85em;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:bold;line-height:1.2em;margin:0 0 5px;} + h2 {color:#333333;font-family:Times,Georgia,serif;font-size:1.5em;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:1.2em;margin:0 0 5px;} + h3 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:15px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px;} + 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:14px; } + 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:14px; text-transform:uppercase;} + .specs {line-height:1em;margin:1px 0;} + .specs span.auteur {font:0.85em/1.1em Arial, Verdana, sans-serif;color:#787878;} + .specs span.auteur a, + .specs span.auteur span {text-transform:uppercase;color:#787878;} + .specs .date {font:0.85em/1.1em Arial, Verdana, sans-serif;color:#787878;} + ul#ariane {list-style-type:none;margin:0;padding:5px 0 8px 0;font:0.85em/1.2em Arial, Verdana, sans-serif;color:#2E2E2E;border-bottom:10px solid #fff;} + ul#ariane li {display:inline;} + ul#ariane a {color:#2E2E2E;text-decoration:underline;} + .credit {color:#787878;font-size:0.71em;line-height:1.1em;font-weight:bold;} + .texte {font-size:1.15em;line-height:1.4em;margin-bottom:17px;} + ''' From 7535f5862712a54e4dd3ae54132f2a3060229306 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 18 Jan 2010 09:46:52 -0700 Subject: [PATCH 14/18] New recipe for The Yemen Times by kwetal --- resources/recipes/yementimes.recipe | 125 ++++++++++++++++++++++++++++ src/calibre/utils/localization.py | 1 + 2 files changed, 126 insertions(+) create mode 100644 resources/recipes/yementimes.recipe diff --git a/resources/recipes/yementimes.recipe b/resources/recipes/yementimes.recipe new file mode 100644 index 0000000000..426c9a748c --- /dev/null +++ b/resources/recipes/yementimes.recipe @@ -0,0 +1,125 @@ +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag + +class YemenTimesRecipe(BasicNewsRecipe): + __license__ = 'GPL v3' + __author__ = 'kwetal' + language = 'en_YE' + country = 'YE' + version = 1 + + title = u'Yemen Times' + publisher = u'yementimes.com' + category = u'News, Opinion, Yemen' + description = u'Award winning weekly from Yemen, promoting press freedom, professional journalism and the defense of human rights.' + + oldest_article = 7 + max_articles_per_feed = 100 + use_embedded_content = False + encoding = 'utf-8' + + remove_empty_feeds = True + no_stylesheets = True + remove_javascript = True + + keep_only_tags = [] + keep_only_tags.append(dict(name = 'div', attrs = {'id': 'ctl00_ContentPlaceHolder1_MAINNEWS0_Panel1', + 'class': 'DMAIN2'})) + remove_attributes = ['style'] + + INDEX = 'http://www.yementimes.com/' + feeds = [] + feeds.append((u'Our Viewpoint', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=6&pnm=OUR%20VIEWPOINT')) + feeds.append((u'Local News', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=3&pnm=Local%20news')) + feeds.append((u'Their News', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=80&pnm=Their%20News')) + feeds.append((u'Report', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=8&pnm=report')) + feeds.append((u'Health', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=51&pnm=health')) + feeds.append((u'Interview', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=77&pnm=interview')) + feeds.append((u'Opinion', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=7&pnm=opinion')) + feeds.append((u'Business', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=5&pnm=business')) + feeds.append((u'Op-Ed', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=81&pnm=Op-Ed')) + feeds.append((u'Culture', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=75&pnm=Culture')) + feeds.append((u'Readers View', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=4&pnm=Readers%20View')) + feeds.append((u'Variety', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=9&pnm=Variety')) + feeds.append((u'Education', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=57&pnm=Education')) + + extra_css = ''' + body {font-family:verdana, arial, helvetica, geneva, sans-serif;} + div.yemen_byline {font-size: medium; font-weight: bold;} + div.yemen_date {font-size: small; color: #666666; margin-bottom: 0.6em;} + .yemen_caption {font-size: x-small; font-style: italic; color: #696969;} + ''' + + conversion_options = {'comments': description, 'tags': category, 'language': 'en', + 'publisher': publisher, 'linearize_tables': True} + + def get_browser(self): + br = BasicNewsRecipe.get_browser() + br.set_handle_gzip(True) + + return br + + def parse_index(self): + answer = [] + for feed_title, feed in self.feeds: + soup = self.index_to_soup(feed) + + newsbox = soup.find('div', 'newsbox') + main = newsbox.findNextSibling('table') + + articles = [] + for li in main.findAll('li'): + title = self.tag_to_string(li.a) + url = self.INDEX + li.a['href'] + articles.append({'title': title, 'date': None, 'url': url, 'description': '
 '}) + + answer.append((feed_title, articles)) + + return answer + + def preprocess_html(self, soup): + freshSoup = self.getFreshSoup(soup) + + headline = soup.find('div', attrs = {'id': 'DVMTIT'}) + if headline: + div = headline.findNext('div', attrs = {'id': 'DVTOP'}) + img = None + if div: + img = div.find('img') + + headline.name = 'h1' + freshSoup.body.append(headline) + if img is not None: + freshSoup.body.append(img) + + byline = soup.find('div', attrs = {'id': 'DVTIT'}) + if byline: + date_el = byline.find('span') + if date_el: + pub_date = self.tag_to_string(date_el) + date = Tag(soup, 'div', attrs = [('class', 'yemen_date')]) + date.append(pub_date) + date_el.extract() + + raw = '
'.join(['%s' % (part) for part in byline.findAll(text = True)]) + author = BeautifulSoup('') + + if date is not None: + freshSoup.body.append(date) + freshSoup.body.append(author) + + story = soup.find('div', attrs = {'id': 'DVDET'}) + if story: + for table in story.findAll('table'): + if table.find('img'): + table['class'] = 'yemen_caption' + + freshSoup.body.append(story) + + return freshSoup + + def getFreshSoup(self, oldSoup): + freshSoup = BeautifulSoup('') + if oldSoup.head.title: + freshSoup.head.title.append(self.tag_to_string(oldSoup.head.title)) + return freshSoup diff --git a/src/calibre/utils/localization.py b/src/calibre/utils/localization.py index 1ade012b1f..90f86a8368 100644 --- a/src/calibre/utils/localization.py +++ b/src/calibre/utils/localization.py @@ -104,6 +104,7 @@ _extra_lang_codes = { 'en_CY' : _('English (Cyprus)'), 'en_PK' : _('English (Pakistan)'), 'en_SG' : _('English (Singapore)'), + 'en_YE' : _('English (Yemen)'), 'de_AT' : _('German (AT)'), 'nl' : _('Dutch (NL)'), 'nl_BE' : _('Dutch (BE)'), From e71b23e5c37a9d90139416772455695859bb8404 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 18 Jan 2010 09:48:12 -0700 Subject: [PATCH 15/18] ebook-meta: Fix setting of series metadata --- src/calibre/ebooks/metadata/cli.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/calibre/ebooks/metadata/cli.py b/src/calibre/ebooks/metadata/cli.py index e4ea1a3931..5de8b76c43 100644 --- a/src/calibre/ebooks/metadata/cli.py +++ b/src/calibre/ebooks/metadata/cli.py @@ -128,6 +128,10 @@ def do_set_metadata(opts, mi, stream, stream_type): mi.title_sort = title_sort(opts.title) if getattr(opts, 'tags', None) is not None: mi.tags = [t.strip() for t in opts.tags.split(',')] + if getattr(opts, 'series', None) is not None: + mi.series = opts.series.strip() + if getattr(opts, 'series_index', None) is not None: + mi.series_index = float(opts.series_index.strip()) if getattr(opts, 'cover', None) is not None: ext = os.path.splitext(opts.cover)[1].replace('.', '').upper() From e8d1e03f737ccbb765276c608f474fe0670a9ea6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Jan 2010 08:31:41 -0700 Subject: [PATCH 16/18] Fix #4607 (Updated recipe for The Amercian Spectator) --- resources/recipes/amspec.recipe | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/resources/recipes/amspec.recipe b/resources/recipes/amspec.recipe index 62bec5ae18..e5a76a4f86 100644 --- a/resources/recipes/amspec.recipe +++ b/resources/recipes/amspec.recipe @@ -1,7 +1,5 @@ -#!/usr/bin/env python - __license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' +__copyright__ = '2009-2010, Darko Miletic ' ''' spectator.org ''' @@ -11,20 +9,22 @@ from calibre.web.feeds.news import BasicNewsRecipe class TheAmericanSpectator(BasicNewsRecipe): title = 'The American Spectator' __author__ = 'Darko Miletic' - language = 'en' - description = 'News from USA' + category = 'news, politics, USA, world' + publisher = 'The American Spectator' oldest_article = 7 max_articles_per_feed = 100 no_stylesheets = True use_embedded_content = False + language = 'en' INDEX = 'http://spectator.org' - html2lrf_options = [ - '--comment' , description - , '--category' , 'news, politics, USA' - , '--publisher' , title - ] + conversion_options = { + 'comments' : description + ,'tags' : category + ,'language' : language + ,'publisher' : publisher + } keep_only_tags = [ dict(name='div', attrs={'class':'post inner'}) @@ -33,13 +33,11 @@ class TheAmericanSpectator(BasicNewsRecipe): remove_tags = [ dict(name='object') - ,dict(name='div', attrs={'class':'col3' }) - ,dict(name='div', attrs={'class':'post-options' }) - ,dict(name='p' , attrs={'class':'letter-editor'}) - ,dict(name='div', attrs={'class':'social' }) + ,dict(name='div', attrs={'class':['col3','post-options','social']}) + ,dict(name='p' , attrs={'class':['letter-editor','meta']}) ] - feeds = [ (u'Articles', u'http://feedproxy.google.com/amspecarticles')] + feeds = [ (u'Articles', u'http://feeds.feedburner.com/amspecarticles')] def get_cover_url(self): cover_url = None @@ -53,3 +51,7 @@ class TheAmericanSpectator(BasicNewsRecipe): def print_version(self, url): return url + '/print' + + def get_article_url(self, article): + return article.get('guid', None) + From 0e0863103a12bed55e75bc48ae30f51ea80bbc48 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Jan 2010 08:43:52 -0700 Subject: [PATCH 17/18] ... --- src/calibre/ebooks/pdf/reflow.py | 13 ++++++++++--- src/calibre/gui2/ui.py | 2 +- src/calibre/library/database2.py | 9 +++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/calibre/ebooks/pdf/reflow.py b/src/calibre/ebooks/pdf/reflow.py index 1b2149cf3a..f4bdb9c7ac 100644 --- a/src/calibre/ebooks/pdf/reflow.py +++ b/src/calibre/ebooks/pdf/reflow.py @@ -20,6 +20,10 @@ class Font(object): class Column(object): + # A column contains an element is the element bulges out to + # the left or the right by at most HFUZZ*col width. + HFUZZ = 0.2 + def __init__(self): self.left = self.right = self.top = self.bottom = 0 self.width = self.height = 0 @@ -41,6 +45,10 @@ class Column(object): for x in self.elements: yield x + def contains(self, elem): + return elem.left > self.left - self.HFUZZ*self.width and \ + elem.right < self.right + self.HFUZZ*self.width + class Element(object): def __eq__(self, other): @@ -238,11 +246,10 @@ class Page(object): return columns def find_elements_in_row_of(self, x): - interval = Interval(x.top - self.YFUZZ * self.average_text_height, + interval = Interval(x.top, x.top + self.YFUZZ*(1+self.average_text_height)) h_interval = Interval(x.left, x.right) - m = max(0, x.idx-15) - for y in self.elements[m:x.idx+15]: + for y in self.elements[x.idx:x.idx+15]: if y is not x: y_interval = Interval(y.top, y.bottom) x_interval = Interval(y.left, y.right) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 6cbae7f7b0..98b416eaa3 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -1361,7 +1361,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): def generate_catalog(self): rows = self.library_view.selectionModel().selectedRows() - if not rows: + if not rows or len(rows) < 3: rows = xrange(self.library_view.model().rowCount(QModelIndex())) ids = map(self.library_view.model().id, rows) dbspec = None diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 84638410c7..db75516292 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1634,13 +1634,15 @@ class LibraryDatabase2(LibraryDatabase): for i in iter(self): yield i[x] - def get_data_as_dict(self, prefix=None, authors_as_string=False): + def get_data_as_dict(self, prefix=None, authors_as_string=False, ids=None): ''' Return all metadata stored in the database as a dict. Includes paths to the cover and each format. :param prefix: The prefix for all paths. By default, the prefix is the absolute path to the library folder. + :param ids: Set of ids to return the data for. If None return data for + all entries in database. ''' if prefix is None: prefix = self.library_path @@ -1649,12 +1651,15 @@ class LibraryDatabase2(LibraryDatabase): 'isbn', 'uuid', 'pubdate']) data = [] for record in self.data: + db_id = record[FIELD_MAP['id']] + if ids is not None and db_id not in ids: + continue if record is None: continue x = {} for field in FIELDS: x[field] = record[FIELD_MAP[field]] data.append(x) - x['id'] = record[FIELD_MAP['id']] + x['id'] = db_id x['formats'] = [] if not x['authors']: x['authors'] = _('Unknown') From c290fc198c013a90dffc4a643e4dedfe53192c16 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Jan 2010 08:51:11 -0700 Subject: [PATCH 18/18] ... --- src/calibre/library/database2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index db75516292..7b0f7a083e 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1651,10 +1651,10 @@ class LibraryDatabase2(LibraryDatabase): 'isbn', 'uuid', 'pubdate']) data = [] for record in self.data: + if record is None: continue db_id = record[FIELD_MAP['id']] if ids is not None and db_id not in ids: continue - if record is None: continue x = {} for field in FIELDS: x[field] = record[FIELD_MAP[field]]