Sync to trunk.

This commit is contained in:
John Schember 2009-11-09 19:11:14 -05:00
commit 3cf7e789c1
74 changed files with 26952 additions and 3892 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

View File

@ -0,0 +1,12 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1257775999(BasicNewsRecipe):
title = u'An Druma M\xf3r'
__author__ = "David O'Calaghan"
oldest_article = 7
max_articles_per_feed = 100
language = 'ga'
use_embedded_content = True
feeds = [(u'Nuacht Laeth\xfail', u'http://feeds.feedburner.com/NuachtLneLaethilArAnDrumaMr')]

View File

@ -16,44 +16,60 @@ class CNN(BasicNewsRecipe):
no_stylesheets = True
use_embedded_content = False
oldest_article = 15
recursions = 1
match_regexps = [r'http://sportsillustrated.cnn.com/.*/[1-9].html']
extra_css = '''
h1{font-family :Arial,Helvetica,sans-serif; font-size:large}
.cnn_strycntntlft{font-family :Arial,Helvetica,sans-serif;}
h2{font-family :Arial,Helvetica,sans-serif; font-size:x-small}
.cnnTxtCmpnt{font-family :Arial,Helvetica,sans-serif; font-size:x-small}
.cnnTMcontent{font-family :Arial,Helvetica,sans-serif; font-size:xx-small;color:#575757}
.storytext{font-family :Arial,Helvetica,sans-serif; font-size:x-small}
.storybyline{font-family :Arial,Helvetica,sans-serif; font-size:xx-small; color:#575757}
.cnnTMcontent{font-family :Arial,Helvetica,sans-serif; font-size:x-small;color:#575757}
.storytext{font-family :Arial,Helvetica,sans-serif; font-size:small}
.storybyline{font-family :Arial,Helvetica,sans-serif; font-size:x-small; color:#575757}
.credit{font-family :Arial,Helvetica,sans-serif; font-size:xx-small; color:#575757}
.storyBrandingBanner{font-family :Arial,Helvetica,sans-serif; font-size:xx-small; color:#575757}
.storytimestamp{font-family :Arial,Helvetica,sans-serif; font-size:xx-small; color:#575757}
.timestamp{font-family :Arial,Helvetica,sans-serif; font-size:xx-small; color:#575757}
.subhead p{font-family :Arial,Helvetica,sans-serif; font-size:xx-small;}
.cnnStoryContent{font-family :Arial,Helvetica,sans-serif; font-size:xx-small}
.cnnContentContainer{font-family :Arial,Helvetica,sans-serif; font-size:xx-small}
.storyBrandingBanner{font-family :Arial,Helvetica,sans-serif; font-size:x-small; color:#575757}
.storytimestamp{font-family :Arial,Helvetica,sans-serif; font-size:x-small; color:#575757}
.timestamp{font-family :Arial,Helvetica,sans-serif; font-size:x-small; color:#575757}
.cnn_strytmstmp{font-family :Arial,Helvetica,sans-serif; font-size:x-small; color:#666666;}
.cnn_stryimg640caption{font-family :Arial,Helvetica,sans-serif; font-size:x-small; color:#666666;}
.cnn_strylccimg300cntr{font-family :Arial,Helvetica,sans-serif; font-size:x-small; color:#666666;}
.cnn_stryichgfcpt{font-family :Arial,Helvetica,sans-serif; font-size:x-small; color:#666666;}
.cnnByline{font-family :Arial,Helvetica,sans-serif; font-size:x-small; color:#666666;}
.cnn_bulletbin cnnStryHghLght{ font-size:xx-small;}
.subhead p{font-family :Arial,Helvetica,sans-serif; font-size:x-small;}
.cnnStoryContent{font-family :Arial,Helvetica,sans-serif; font-size:x-small}
.cnnContentContainer{font-family :Arial,Helvetica,sans-serif; font-size:x-small}
.col1{font-family :Arial,Helvetica,sans-serif; font-size:x-small; color:#666666;}
.col3{color:#333333; font-family :Arial,Helvetica,sans-serif; font-size:x-small;font-weight:bold;}
.cnnInlineT1Caption{font-family :Arial,Helvetica,sans-serif; font-size:xx-small;font-weight:bold;}
.cnnInlineT1Credit{font-family :Arial,Helvetica,sans-serif; font-size:xx-small;color:#333333;}
.col10{color:#5A637E}
.cnnTimeStamp{font-family :Arial,Helvetica,sans-serif; font-size:xx-small;color:#333333;}
.galleryhedDek{font-family :Arial,Helvetica,sans-serif; font-size:xx-small;color:#575757;}
.galleryWidgetHeader{font-family :Arial,Helvetica,sans-serif; font-size:xx-small;color:#004276;}
.article-content{font-family :Arial,Helvetica,sans-serif; font-size:xx-small}
.cnnRecapStory{font-family :Arial,Helvetica,sans-serif; font-size:xx-small}
.cnnInlineT1Caption{font-family :Arial,Helvetica,sans-serif; font-size:x-small;font-weight:bold;}
.cnnInlineT1Credit{font-family :Arial,Helvetica,sans-serif; font-size:x-small;color:#333333;}
.col10{color:#5A637E;}
.cnnInlineRailBulletList{color:black;}
.cnnLine0{font-family :Arial,Helvetica,sans-serif; color:#666666;font-weight:bold;}
.cnnTimeStamp{font-family :Arial,Helvetica,sans-serif; font-size:x-small;color:#333333;}
.galleryhedDek{font-family :Arial,Helvetica,sans-serif; font-size:x-small;color:#575757;}
.galleryWidgetHeader{font-family :Arial,Helvetica,sans-serif; font-size:x-small;color:#004276;}
.article-content{font-family :Arial,Helvetica,sans-serif; font-size:x-small}
.cnnRecapStory{font-family :Arial,Helvetica,sans-serif; font-size:x-small}
h1{font-family :Arial,Helvetica,sans-serif; font-size:x-large}
.captionname{font-family :Arial,Helvetica,sans-serif; font-size:x-small;color:#575757;}
inStoryIE{{font-family :Arial,Helvetica,sans-serif; font-size:x-small;}
'''
keep_only_tags = [
dict(name='div', attrs={'class':["cnnWCBoxContent","cnnContent","cnnMainBodySecs"]}),
dict(name='div', attrs={'class':["cnnWCBoxContent","cnnContent","cnnMainBodySecs","cnn_storyarea","cnn_strycntntlft","cnn_strytmstmp"]}),
dict(name='div', attrs={'id':["contentBody","content"]}),
dict(name='td', attrs={'id':["cnnRecapStory"]}),]
dict(name='td', attrs={'id':["cnnRecapStory"]}),
dict(name='td', attrs={'class':["cnnBigSideLeft"]}),
]
remove_tags = [
dict(name='div', attrs={'class':["storyLink","article-tools clearfix","widget video related-video vList","cnnFooterBox","scrollArrows","boxHeading","cnnInlineMailbag","mainCol_lastBlock","cnn_bookmarks","cnnFooterBox","cnnEndOfStory","cnnInlineSL","cnnStoryHighlights","cnnFooterClick","cnnSnapShotHeader","cnnStoryToolsFooter","cnnWsnr","cnnUGCBox","cnnTopNewsModule","cnnStoryElementBox","cnnStoryPhotoBoxNavigation"]}),
dict(name='div', attrs={'class':["cnnInlineRailSelectList","cnn_strycntntrgt","cnn_strybtntools","cnn_strylctcntr cnn_strylctcqrelt","cnn_strylceclbtn","cnn_stryftsbttm","cnn_strybtmmorebx","storyLink","article-tools clearfix","widget video related-video vList","cnnFooterBox","scrollArrows","boxHeading","cnnInlineMailbag","mainCol_lastBlock","cnn_bookmarks","cnnFooterBox","cnnEndOfStory","cnnInlineSL","cnnStoryHighlights","cnnFooterClick","cnnSnapShotHeader","cnnStoryToolsFooter","cnnWsnr","cnnUGCBox","cnnTopNewsModule","cnnStoryElementBox","cnnStoryPhotoBoxNavigation"]}),
dict(name='span', attrs={'class':["cnnEmbeddedMosLnk"]}),
dict(name='div', attrs={'id':["cnnIncldHlder","articleCommentsContainer","featuredContent","superstarsWidget","shareMenuContainer","rssMenuContainer","storyBrandingBanner","cnnRightCol","siteFeatures","quigo628","rightColumn","clickIncludeBox","cnnHeaderRightCol","cnnSCFontLabel","cnnSnapShotBottomRight","cnnSCFontButtons","rightColumn"]}),
dict(name='p', attrs={'class':["cnnTopics"]}),
dict(name='td', attrs={'class':["cnnRightRail"]}),
dict(name='table', attrs={'class':["cnnTMbox"]}),
dict(name='ul', attrs={'id':["cnnTopNav","cnnBotNav","cnnSBNav"]}),
dict(name='div', attrs={'id':["cnn_ftrcntnt"]})
]
# def print_version(self, url):
@ -75,3 +91,10 @@ class CNN(BasicNewsRecipe):
('Offbeat', 'http://rss.cnn.com/rss/cnn_offbeat.rss'),
('Most Popular', 'http://rss.cnn.com/rss/cnn_mostpopular.rss')
]
def preprocess_html(self, soup):
for tag in soup.findAll(name=['ul','li']):
tag.name = 'div'
return soup

View File

@ -1,24 +1,56 @@
import re
from calibre.web.feeds.news import BasicNewsRecipe
class Cyberpresse(BasicNewsRecipe):
title = u'Cyberpresse'
__author__ = 'balok'
__author__ = 'balok and Sujata Raman'
description = 'Canadian news in French'
language = 'fr'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
remove_javascript = True
html2lrf_options = ['--left-margin=0','--right-margin=0','--top-margin=0','--bottom-margin=0']
preprocess_regexps = [
(re.compile(r'<body.*?<!-- END .centerbar -->', re.IGNORECASE | re.DOTALL), lambda match : '<BODY>'),
(re.compile(r'<!-- END .entry -->.*?</body>', re.IGNORECASE | re.DOTALL), lambda match : '</BODY>'),
(re.compile(r'<strong>Agrandir.*?</strong>', re.IGNORECASE | re.DOTALL), lambda match : '<br>'),
]
encoding = 'utf-8'
feeds = [(u'Manchettes', u'http://www.cyberpresse.ca/rss/225.xml'),(u'Capitale nationale', u'http://www.cyberpresse.ca/rss/501.xml'),(u'Opinions', u'http://www.cyberpresse.ca/rss/977.xml'),(u'Insolite', u'http://www.cyberpresse.ca/rss/279.xml')]
keep_only_tags = [dict(name='div', attrs={'class':'article-page'}),
dict(name='div', attrs={'id':'articlePage'}),
]
extra_css = '''
.photodata{font-family:Arial,Helvetica,Verdana,sans-serif;color: #999999; font-size: 90%; }
h1{font-family:Georgia,Times,serif ; font-size: large; }
.amorce{font-family:Arial,Helvetica,Verdana,sans-serif; font-weight:bold;}
.article-page{font-family:Arial,Helvetica,Verdana,sans-serif; font-size: x-small;}
#articlePage{font-family:Arial,Helvetica,Verdana,sans-serif; font-size: x-small;}
.auteur{font-family:Georgia,Times,sans-serif; font-size: 90%; color:#006699 ;}
.bodyText{font-family:Arial,Helvetica,Verdana,sans-serif; font-size: x-small;}
.byLine{font-family:Arial,Helvetica,Verdana,sans-serif; font-size: 90%;}
.entry{font-family:Arial,Helvetica,Verdana,sans-serif; font-size: x-small;}
.minithumb-auteurs{font-family:Arial,Helvetica,Verdana,sans-serif; font-size: 90%; }
a{color:#003399; font-weight:bold; }
'''
remove_tags = [
dict(name='div', attrs={'class':['centerbar','colspan','share-module']}),
dict(name='p', attrs={'class':['zoom']}),
dict(name='ul', attrs={'class':['stories']}),
dict(name='h4', attrs={'class':['general-cat']}),
]
feeds = [(u'Manchettes', u'http://www.cyberpresse.ca/rss/225.xml'),
(u'Capitale nationale', u'http://www.cyberpresse.ca/rss/501.xml'),
(u'Opinions', u'http://www.cyberpresse.ca/rss/977.xml'),
(u'Insolite', u'http://www.cyberpresse.ca/rss/279.xml')
]
def postprocess_html(self, soup, first):
for tag in soup.findAll(name=['i','strong']):
tag.name = 'div'
return soup

View File

@ -16,9 +16,11 @@ class Economist(BasicNewsRecipe):
language = 'en'
__author__ = "Kovid Goyal"
description = 'Global news and current affairs from a European perspective'
oldest_article = 7.0
INDEX = 'http://www.economist.com/printedition'
description = ('Global news and current affairs from a European perspective.'
' Needs a subscription from ')+INDEX
oldest_article = 7.0
cover_url = 'http://www.economist.com/images/covers/currentcovereu_large.jpg'
remove_tags = [dict(name=['script', 'noscript', 'title'])]
remove_tags_before = dict(name=lambda tag: tag.name=='title' and tag.parent.name=='body')

View File

@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en'
'''
www.guardian.co.uk
'''
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class Guardian(BasicNewsRecipe):
@ -15,8 +15,8 @@ class Guardian(BasicNewsRecipe):
__author__ = 'Seabound and Sujata Raman'
language = 'en_GB'
oldest_article = 7
max_articles_per_feed = 20
#oldest_article = 7
#max_articles_per_feed = 100
remove_javascript = True
timefmt = ' [%a, %d %b %Y]'
@ -43,29 +43,97 @@ class Guardian(BasicNewsRecipe):
#match-stats-summary{font-size:small; font-family:Arial,Helvetica,sans-serif;font-weight:normal;}
'''
def parse_index(self):
soup = self.index_to_soup('http://www.guardian.co.uk/theguardian')
# find cover pic
img = soup.find( 'img',attrs ={'alt':'Guardian digital edition'})
if img is None: return None
else:
self.cover_url = img['src']
# end find cover pic
sections = []
ans = []
for li in soup.findAll( 'li'):
section = ''
articles = []
if li.a and li.a.has_key('href'):
url = li.a['href']
if 'mainsection' in url:
section = self.tag_to_string(url)
i = len(section)
index1 = section.rfind('/',0,i)
section = section[index1+1:i]
sections.append(section)
#find the articles in the Main Section start
soup = self.index_to_soup(url)
date = strftime('%a, %d %b')
descl = []
for desclist in soup.findAll(name='div',attrs={'class':"trailtext"}):
descl.append(self.tag_to_string(desclist).strip())
t = -1
for tag in soup.findAll('h3'):
t = t+1
for a in tag.findAll('a'):
if t < len(descl):
desc = descl[t]
else:
desc = ''
if a and a.has_key('href'):
url2 = a['href']
else:
url2 =''
title = self.tag_to_string(a)
if len(articles) == 0: #First article
articles.append({
'title':title,
'date':date,
'url':url2,
'description':desc,
})
else:
#eliminate duplicates start
if {'title':title,'date':date,'url':url2,'description':desc} in articles :
url2 = ''
#eliminate duplicates end
else:
if 'http://jobs.guardian.co.uk/' in url2:
url2 = ''
else:
articles.append({
'title':title,
'date':date,
'url':url2,
'description':desc,
})
#find the articles in the Main Section end
ans.append( articles)
else:
url =''
feeds = [
('Front Page', 'http://www.guardian.co.uk/rss'),
('Business', 'http://www.guardian.co.uk/business/rss'),
('Sport', 'http://www.guardian.co.uk/sport/rss'),
('Culture', 'http://www.guardian.co.uk/culture/rss'),
('Money', 'http://www.guardian.co.uk/money/rss'),
('Life & Style', 'http://www.guardian.co.uk/lifeandstyle/rss'),
('Travel', 'http://www.guardian.co.uk/travel/rss'),
('Environment', 'http://www.guardian.co.uk/environment/rss'),
('Comment','http://www.guardian.co.uk/commentisfree/rss'),
]
titles = map(self.find_title, sections)
ans1 = list(zip(titles,ans))
def get_article_url(self, article):
url = article.get('guid', None)
if '/video/' in url or '/flyer/' in url or '/quiz/' in url or \
'/gallery/' in url or 'ivebeenthere' in url or \
'pickthescore' in url or 'audioslideshow' in url :
url = None
return url
return ans1[2:]
def find_title(self, section):
d = {'topstories':'Top Stories', 'international':'International', 'editorialsandreply':'Editorials and Reply',
'commentanddebate':'Comment and Debate','uknews':'UK News','saturday':'Saturday','sunday':'Sunday',
'reviews':'Reviews', 'obituaries':'Obituaries'}
return d.get(section, section)
def preprocess_html(self, soup):

View File

@ -1,5 +1,5 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Derry FitzGerald. 2009 Modified by Ray Kinsella'
__copyright__ = "2008, Derry FitzGerald. 2009 Modified by Ray Kinsella and David O'Callaghan"
'''
irishtimes.com
'''
@ -9,18 +9,21 @@ from calibre.web.feeds.news import BasicNewsRecipe
class IrishTimes(BasicNewsRecipe):
title = u'The Irish Times'
__author__ = 'Derry FitzGerald and Ray Kinsella'
__author__ = "Derry FitzGerald, Ray Kinsella and David O'Callaghan"
language = 'en'
timefmt = ' (%A, %B %e, %Y)'
oldest_article = 3
no_stylesheets = True
simultaneous_downloads= 1
r = re.compile('.*(?P<url>http:\/\/www.irishtimes.com\/.*\.html).*')
r = re.compile('.*(?P<url>http:\/\/(www.irishtimes.com)|(rss.feedsportal.com\/c)\/.*\.html?).*')
remove_tags = [dict(name='div', attrs={'class':'footer'})]
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'
feeds = [
('Frontpage', 'http://www.irishtimes.com/feeds/rss/newspaper/index.rss'),
('Frontpage', 'http://www.irishtimes.com/feeds/rss/newspaper/index.rss'),
('Ireland', 'http://www.irishtimes.com/feeds/rss/newspaper/ireland.rss'),
('World', 'http://www.irishtimes.com/feeds/rss/newspaper/world.rss'),
('Finance', 'http://www.irishtimes.com/feeds/rss/newspaper/finance.rss'),
@ -29,12 +32,14 @@ class IrishTimes(BasicNewsRecipe):
('Opinion', 'http://www.irishtimes.com/feeds/rss/newspaper/opinion.rss'),
('Letters', 'http://www.irishtimes.com/feeds/rss/newspaper/letters.rss'),
]
def print_version(self, url):
return url.replace('.html', '_pf.html')
if url.count('rss.feedsportal.com'):
u = url.replace('0Bhtml/story01.htm','_pf0Bhtml/story01.htm')
else:
u = url.replace('.html','_pf.html')
return u
def get_article_url(self, article):
m = self.r.match(article.get('description', None))
print m.group('url')
return m.group('url')
return article.link

View File

@ -0,0 +1,55 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''
lwn.net
'''
from calibre.web.feeds.news import BasicNewsRecipe
class LWN(BasicNewsRecipe):
title = u'lwn'
__author__ = 'Oliver Niesner'
description = 'Linux Weekly News'
oldest_article = 54
language = _('English')
max_articles_per_feed = 100
needs_subscription = True
language = 'en'
remove_javascript = True
simultaneous_downloads= 1
delay = 1
LOGIN = 'https://lwn.net/login'
def get_browser(self):
br = BasicNewsRecipe.get_browser()
if self.username is not None and self.password is not None:
br.open(self.LOGIN)
br.select_form(name='loginform')
br['Username'] = self.username
br['Password'] = self.password
br.submit()
return br
remove_tags = [
dict(name='td', attrs={'class':'LeftColumn'}),
dict(name='td', attrs={'class':'NavLink'}),
dict(name='div', attrs={'class':'FormattedComment'}),
dict(name='td', attrs={'class':'MCTopBanner'}),
dict(name='div', attrs={'class':'CommentBox'})
]
feeds = [
(u'lwn' , u'http://lwn.net/headlines/newrss' ),
]
def postprocess_html(self, soup, first):
for tag in soup.findAll(name=['table', 'tr', 'td']):
tag.name = 'div'
return soup
def print_version(self, url):
return url.replace ('rss', '?format=printable')

View File

@ -12,8 +12,7 @@ class NewScientist(BasicNewsRecipe):
title = 'New Scientist - Online News'
__author__ = 'Darko Miletic'
description = 'Science news and science articles from New Scientist.'
language = 'en'
language = 'en'
publisher = 'New Scientist'
category = 'science news, science articles, science jobs, drugs, cancer, depression, computer software, sex'
delay = 3
@ -21,16 +20,14 @@ class NewScientist(BasicNewsRecipe):
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
remove_javascript = True
encoding = 'utf-8'
html2lrf_options = [
'--comment', description
, '--category', category
, '--publisher', publisher
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
keep_only_tags = [dict(name='div', attrs={'id':['pgtop','maincol']})]
@ -53,18 +50,9 @@ class NewScientist(BasicNewsRecipe):
]
def get_article_url(self, article):
url = article.get('link', None)
raw = article.get('description', None)
rsoup = self.index_to_soup(raw)
atags = rsoup.findAll('a',href=True)
for atag in atags:
if atag['href'].startswith('http://res.feedsportal.com/viral/sendemail2.html?'):
st, sep, rest = atag['href'].partition('&link=')
real_url, sep2, drest = rest.partition('" target=')
return real_url
url = article.get('guid', None)
return url
def print_version(self, url):
rawurl, sep, params = url.partition('?')
return rawurl + '?full=true&print=true'
return url + '?full=true&print=true'

View File

@ -12,14 +12,42 @@ from lxml import html
from calibre.constants import preferred_encoding
class NewYorkReviewOfBooks(BasicNewsRecipe):
title = u'New York Review of Books (no subscription)'
description = u'Book reviews'
language = 'en'
__author__ = 'Kovid Goyal'
__author__ = 'Kovid Goyal and Sujata Raman'
no_stylesheets = True
no_javascript = True
remove_tags_before = {'id':'container'}
remove_tags = [{'class':['noprint', 'ad', 'footer']}, {'id':'right-content'}]
remove_tags = [{'class':['noprint', 'ad', 'footer']}, {'id':'right-content'},
dict(name='img', attrs={'src':"/images/1x1-clear.gif"}),
]
extra_css = '''
p{font-family:"Times New Roman",Georgia,serif; font-size: 60%;}
.caption{ font-family:"Times New Roman",Georgia,serif; font-size:40%;}
h2{font-family:"Times New Roman",Georgia,serif; font-size:90%;}
a{ color:#003399; }
.reviewed-title{font-family:"Times New Roman",Georgia,serif;font-size : 50%; font-style:italic;}
.reviewed-author{font-family:"Times New Roman",Georgia,serif;font-size : 50%;}
.reviewed-info{font-family:"Times New Roman",Georgia,serif;font-size : 50%;}
h5{font-family:"Times New Roman",Georgia,serif;font-size : 50%;}
.date{font-family:"Times New Roman",Georgia,serif;font-variant:small-caps;font-size : 50%;}
h4{font-family:"Times New Roman",Georgia,serif;font-size : 50%;}
'''
def preprocess_html(self, soup):
for tag in soup.findAll(name=['span',]):
tag.name = 'div'
for tag in soup.findAll(name=['blockquote',]):
tag.name = 'p'
return soup
def parse_index(self):
root = html.fromstring(self.browser.open('http://www.nybooks.com/current-issue').read())
@ -40,6 +68,6 @@ class NewYorkReviewOfBooks(BasicNewsRecipe):
'description': '',
}
articles.append(article)
return [('Current Issue', articles)]

View File

@ -0,0 +1,71 @@
/*
* bookmarks management
* Copyright 2008 Kovid Goyal
* License: GNU GPL v3
*/
function selector_in_parent(elem) {
var num = elem.prevAll().length;
var sel = " > *:eq("+num+") ";
return sel;
}
function selector(elem) {
var obj = elem;
var sel = "";
while (obj[0] != document) {
sel = selector_in_parent(obj) + sel;
obj = obj.parent();
}
return sel;
}
function find_closest_enclosing_block(top) {
var START = top-1000;
var STOP = top;
var matches = [];
var elem, temp;
var width = 1000;
for (y = START; y < STOP; y += 20) {
for ( x = 0; x < width; x += 20) {
elem = document.elementFromPoint(x, y);
try {
elem = $(elem);
temp = elem.offset().top
matches.push(elem);
if (Math.abs(temp - START) < 25) { y = STOP; break}
} catch(error) {}
}
}
var miny = Math.abs(matches[0].offset().top - START), min_elem = matches[0];
for (i = 1; i < matches.length; i++) {
elem = matches[i];
temp = Math.abs(elem.offset().top - START);
if ( temp < miny ) { miny = temp; min_elem = elem; }
}
return min_elem;
}
function calculate_bookmark(y) {
var elem = find_closest_enclosing_block(y);
var sel = selector(elem);
var ratio = (y - elem.offset().top)/elem.height();
if (ratio > 1) { ratio = 1; }
if (ratio < 0) { ratio = 0; }
return sel + "|" + ratio;
}
function animated_scrolling_done() {
window.py_bridge.animated_scroll_done();
}
function scroll_to_bookmark(bookmark) {
bm = bookmark.split("|");
var ratio = 0.7 * parseFloat(bm[1]);
$.scrollTo($(bm[0]), 1000,
{over:ratio, onAfter:function(){window.py_bridge.animated_scroll_done()}});
}

View File

@ -0,0 +1,26 @@
/*
* bookmarks management
* Copyright 2008 Kovid Goyal
* License: GNU GPL v3
*/
function init_hyphenate() {
window.py_bridge.init_hyphenate();
}
document.addEventListener("DOMContentLoaded", init_hyphenate, false);
function do_hyphenation(lang) {
Hyphenator.config(
{
'minwordlength' : 6,
//'hyphenchar' : '|',
'displaytogglebox' : false,
'remoteloading' : false,
'onerrorhandler' : function (e) {
window.py_bridge.debug(e);
}
});
Hyphenator.hyphenate(document.body, lang);
}

View File

@ -0,0 +1,62 @@
/*
* reference management
* Copyright 2008 Kovid Goyal
* License: GNU GPL v3
*/
var reference_old_bgcol = "transparent";
var reference_prefix = "1.";
function show_reference_panel(ref) {
panel = $("#calibre_reference_panel");
if (panel.length < 1) {
$(document.body).append('<div id="calibre_reference_panel" style="top:20px; left:20px; padding-left:30px; padding-right:30px; font:monospace normal;text-align:center; z-index:10000; background: beige; border:red ridge 2px; position:absolute;"><h5>Paragraph</h5><p style="text-indent:0pt">None</p></div>')
panel = $("#calibre_reference_panel");
}
$("> p", panel).text(ref);
panel.css({top:(window.pageYOffset+20)+"px"});
panel.fadeIn(500);
}
function toggle_reference(e) {
p = $(this);
if (e.type == "mouseenter") {
reference_old_bgcol = p.css("background-color");
p.css({backgroundColor:"beige"});
var i = 0;
var paras = $("p");
for (j = 0; j < paras.length; j++,i++) {
if (paras[j] == p[0]) break;
}
show_reference_panel(reference_prefix+(i+1) );
} else {
p.css({backgroundColor:reference_old_bgcol});
panel = $("#calibre_reference_panel").hide();
}
return false;
}
function enter_reference_mode() {
$("p").bind("mouseenter mouseleave", toggle_reference);
}
function leave_reference_mode() {
$("p").unbind("mouseenter mouseleave", toggle_reference);
}
function goto_reference(ref) {
var tokens = ref.split(".");
if (tokens.length != 2) {alert("Invalid reference: "+ref); return;}
var num = parseInt(tokens[1]);
if (isNaN(num)) {alert("Invalid reference: "+ref); return;}
num -= 1;
if (num < 0) {alert("Invalid reference: "+ref); return;}
var p = $("p");
if (num >= p.length) {alert("Reference not found: "+ref); return;}
$.scrollTo($(p[num]), 1000,
{onAfter:function(){window.py_bridge.animated_scroll_done()}});
}

View File

@ -9,6 +9,7 @@ from setup import __version__ as VERSION, __appname__ as APPNAME, SRC, Command,
try:
from setuptools import setup
setup
except:
class setup:
pass
@ -385,7 +386,7 @@ def main():
{
'optimize' : 2,
'dist_dir' : 'build/py2app',
'argv_emulation' : False,
'argv_emulation' : True,
'iconfile' : icon,
'frameworks': ['libusb.dylib', 'libunrar.dylib'],
'includes' : ['sip', 'pkg_resources', 'PyQt4.QtXml',

View File

@ -303,9 +303,6 @@ class UploadInstallers(Command):
installers.append(installer_name('tar.bz2', is64bit=True))
map(self.upload_installer, installers)
check_call('''ssh divok echo %s \\> %s/latest_version'''\
%(__version__, DOWNLOADS), shell=True)
class UploadUserManual(Command):
description = 'Build and upload the User Manual'
sub_commands = ['manual']
@ -349,6 +346,8 @@ class UploadToServer(Command):
shell=True)
check_call('ssh divok bzr update /usr/local/calibre',
shell=True)
check_call('''ssh divok echo %s \\> %s/latest_version'''\
%(__version__, DOWNLOADS), shell=True)
check_call('ssh divok /etc/init.d/apache2 graceful',
shell=True)

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = 'calibre'
__version__ = '0.6.20'
__version__ = '0.6.21'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re

View File

@ -372,6 +372,7 @@ from calibre.devices.prs700.driver import PRS700
from calibre.devices.android.driver import ANDROID
from calibre.devices.eslick.driver import ESLICK
from calibre.devices.nuut2.driver import NUUT2
from calibre.devices.iriver.driver import IRIVER_STORY
plugins = [HTML2ZIP]
plugins += [
@ -427,7 +428,8 @@ plugins += [
COOL_ER,
SHINEBOOK,
ESLICK,
NUUT2
NUUT2,
IRIVER_STORY
]
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
x.__name__.endswith('MetadataReader')]

View File

@ -250,7 +250,7 @@ class HanlinV3Output(OutputProfile):
name = 'Hanlin V3'
short_name = 'hanlinv3'
description = _('This profile is intended for the Hanlin V3 and its clones.')
description = _('This profile is intended for the Hanlin V3/V5 and its clones.')
# Screen size is a best guess
screen_size = (584, 754)

View File

@ -276,8 +276,10 @@ def input_format_plugins():
yield plugin
def plugin_for_input_format(fmt):
customization = config['plugin_customization']
for plugin in input_format_plugins():
if fmt.lower() in plugin.file_types:
plugin.site_customization = customization.get(plugin.name, None)
return plugin
def all_input_formats():
@ -302,8 +304,10 @@ def output_format_plugins():
yield plugin
def plugin_for_output_format(fmt):
customization = config['plugin_customization']
for plugin in output_format_plugins():
if fmt.lower() == plugin.file_type:
plugin.site_customization = customization.get(plugin.name, None)
return plugin
def available_output_formats():

View File

@ -0,0 +1,10 @@
#!/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__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

View File

@ -0,0 +1,46 @@
#!/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__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from calibre.devices.usbms.driver import USBMS
class IRIVER_STORY(USBMS):
name = 'Iriver Story Device Interface'
gui_name = 'Iriver Story'
description = _('Communicate with the Iriver Story reader.')
author = _('Kovid Goyal')
supported_platforms = ['windows', 'osx', 'linux']
# Ordered list of supported formats
FORMATS = ['epub', 'pdf', 'txt']
VENDOR_ID = [0x1006]
PRODUCT_ID = [0x4023]
BCD = [0x0323]
VENDOR_NAME = 'IRIVER'
WINDOWS_MAIN_MEM = 'STORY'
WINDOWS_CARD_A_MEM = 'STORY'
#OSX_MAIN_MEM = 'Kindle Internal Storage Media'
#OSX_CARD_A_MEM = 'Kindle Card Storage Media'
MAIN_MEMORY_VOLUME_LABEL = 'Story Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'Story Storage Card'
SUPPORTS_SUB_DIRS = True
def windows_sort_drives(self, drives):
main = drives.get('main', None)
card = drives.get('carda', None)
if card and main and card < main:
drives['main'] = card
drives['carda'] = main
return drives

View File

@ -19,7 +19,7 @@ class NUUT2(USBMS):
supported_platforms = ['windows', 'osx', 'linux']
# Ordered list of supported formats
FORMATS = ['epub', 'pdft', 'txt']
FORMATS = ['epub', 'pdf', 'txt']
DRM_FORMATS = ['epub']
VENDOR_ID = [0x140e]

View File

@ -34,6 +34,7 @@ def metadata_from_formats(formats):
mi = metadata_from_filename(list(iter(formats))[0])
if not mi.authors:
mi.authors = [_('Unknown')]
return mi
def _metadata_from_formats(formats):
mi = MetaInformation(None, None)

View File

@ -60,9 +60,11 @@ class FormatState(object):
self.valign = 'baseline'
self.italic = False
self.bold = False
self.strikethrough = False
self.preserve = False
self.family = 'serif'
self.bgcolor = 'transparent'
self.fgcolor = 'black'
self.href = None
self.list_num = 0
self.attrib = {}
@ -75,7 +77,9 @@ class FormatState(object):
and self.valign == other.valign \
and self.preserve == other.preserve \
and self.family == other.family \
and self.bgcolor == other.bgcolor
and self.bgcolor == other.bgcolor \
and self.fgcolor == other.fgcolor \
and self.strikethrough == other.strikethrough
def __ne__(self, other):
return not self.__eq__(other)
@ -239,6 +243,11 @@ class MobiMLizer(object):
if istate.bgcolor is not None and istate.bgcolor != 'transparent' :
inline = etree.SubElement(inline, XHTML('span'),
bgcolor=istate.bgcolor)
if istate.fgcolor != 'black':
inline = etree.SubElement(inline, XHTML('font'),
color=istate.fgcolor)
if istate.strikethrough:
inline = etree.SubElement(inline, XHTML('s'))
bstate.inline = inline
bstate.istate = istate
inline = bstate.inline
@ -316,6 +325,8 @@ class MobiMLizer(object):
istate.bold = weight in ('bold', 'bolder') or asfloat(weight) > 400
istate.preserve = (style['white-space'] in ('pre', 'pre-wrap'))
istate.bgcolor = style['background-color']
istate.fgcolor = style['color']
istate.strikethrough = style['text-decoration'] == 'line-through'
if 'monospace' in style['font-family']:
istate.family = 'monospace'
elif 'sans-serif' in style['font-family']:

View File

@ -709,6 +709,10 @@ class MobiWriter(object):
if self.opts.verbose > 3 :self._oeb.logger.info(" node: %03d %-10.10s %-15.15s... spans HTML records %03d-%03d \t offset: 0x%06X length: 0x%06X" % \
(myIndex, self._ctoc_map[i]['klass'], child.title if child.title.strip() > "" else "(missing)", thisRecord, interimSpanRecord, offset, length) )
elif thisRecord == numberOfHTMLRecords-1:
# Check for short terminating record (GR provisional)
if self._HTMLRecords[thisRecord].continuingNode == -1:
self._HTMLRecords[thisRecord].continuingNode = self._HTMLRecords[thisRecord].openingNode - 1
else :
if self.opts.verbose > 3 : self._oeb.logger.info(" node: %03d %-10.10s %-15.15s... spans HTML records %03d-%03d \t offset: 0x%06X length: 0x%06X" % \
(myIndex, self._ctoc_map[i]['klass'], child.title if child.title.strip() > "" else "(missing)", thisRecord, thisRecord, offset, length) )

View File

@ -19,7 +19,7 @@ from calibre.utils.zipfile import safe_replace, ZipFile
from calibre.utils.config import DynamicConfig
from calibre.utils.logging import Log
from calibre.ebooks.epub.output import EPUBOutput
from calibre import guess_type
from calibre import guess_type, prints
TITLEPAGE = EPUBOutput.TITLEPAGE_COVER.decode('utf-8')
@ -99,29 +99,63 @@ class EbookIterator(object):
if text in open(path, 'rb').read().decode(path.encoding).lower():
return i
def find_missing_css_files(self):
for x in os.walk(os.path.dirname(self.pathtoopf)):
for f in x[-1]:
if f.endswith('.css'):
yield os.path.join(x[0], f)
def find_declared_css_files(self):
for item in self.opf.manifest:
if item.mime_type and 'css' in item.mime_type.lower():
yield item.path
def find_embedded_fonts(self):
'''
This will become unnecessary once Qt WebKit supports the @font-face rule.
'''
for item in self.opf.manifest:
if item.mime_type and 'css' in item.mime_type.lower():
css = open(item.path, 'rb').read().decode('utf-8', 'replace')
for match in re.compile(r'@font-face\s*{([^}]+)}').finditer(css):
block = match.group(1)
family = re.compile(r'font-family\s*:\s*([^;]+)').search(block)
url = re.compile(r'url\s*\([\'"]*(.+?)[\'"]*\)', re.DOTALL).search(block)
if url:
path = url.group(1).split('/')
path = os.path.join(os.path.dirname(item.path), *path)
id = QFontDatabase.addApplicationFont(path)
if id != -1:
families = [unicode(f) for f in QFontDatabase.applicationFontFamilies(id)]
if family:
family = family.group(1).strip().replace('"', '')
if family not in families:
print 'WARNING: Family aliasing not supported:', block
else:
print 'Loaded embedded font:', repr(family)
css_files = set(self.find_declared_css_files())
if not css_files:
css_files = set(self.find_missing_css_files())
bad_map = {}
font_family_pat = re.compile(r'font-family\s*:\s*([^;]+)')
for csspath in css_files:
css = open(csspath, 'rb').read().decode('utf-8', 'replace')
for match in re.compile(r'@font-face\s*{([^}]+)}').finditer(css):
block = match.group(1)
family = font_family_pat.search(block)
url = re.compile(r'url\s*\([\'"]*(.+?)[\'"]*\)', re.DOTALL).search(block)
if url:
path = url.group(1).split('/')
path = os.path.join(os.path.dirname(csspath), *path)
if not os.access(path, os.R_OK):
continue
id = QFontDatabase.addApplicationFont(path)
if id != -1:
families = [unicode(f) for f in QFontDatabase.applicationFontFamilies(id)]
if family:
family = family.group(1).strip().replace('"', '')
bad_map[family] = families[0]
if family not in families:
prints('WARNING: Family aliasing not fully supported.')
prints('\tDeclared family: %s not in actual families: %s'
% (family, families))
else:
prints('Loaded embedded font:', repr(family))
if bad_map:
def prepend_embedded_font(match):
for bad, good in bad_map.items():
if bad in match.group(1):
prints('Substituting font family: %s -> %s'%(bad, good))
return match.group().replace(bad, '"%s"'%good)
for csspath in css_files:
with open(csspath, 'r+b') as f:
css = f.read()
css = font_family_pat.sub(prepend_embedded_font, css)
f.seek(0)
f.truncate()
f.write(css)
def __enter__(self, processed=False):
self.delete_on_exit = []

View File

@ -274,6 +274,7 @@ class GetMetadata(QObject):
self.emit(SIGNAL('metadata(PyQt_PyObject, PyQt_PyObject)'), id, mi)
class TableView(QTableView):
def __init__(self, parent):
QTableView.__init__(self, parent)
self.read_settings()
@ -585,8 +586,11 @@ def build_forms(srcdir, info=None):
if form.endswith('viewer%smain.ui'%os.sep):
info('\t\tPromoting WebView')
dat = dat.replace('self.view = QtWebKit.QWebView(', 'self.view = DocumentView(')
dat = dat.replace('from PyQt4 import QtWebKit', '')
if iswindows:
dat = dat.replace('self.view = QWebView(', 'self.view = DocumentView(')
dat = dat.replace('from QtWebKit.QWebView import QWebView', '')
dat += '\n\nfrom calibre.gui2.viewer.documentview import DocumentView'
dat += '\nQtWebKit'
open(compiled_form, 'wb').write(dat)

View File

@ -157,7 +157,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.formats_changed = True
added = True
if bad_perms:
error_dialog(self.window, _('You do not have '
error_dialog(self.window, _('No permission'),
_('You do not have '
'permission to read the following files:'),
det_msg='\n'.join(bad_perms), show=True)

View File

@ -742,6 +742,16 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
########################## Connect to device ##############################
def save_device_view_settings(self):
model = self.location_view.model()
self.memory_view.write_settings()
for x in range(model.rowCount()):
if x > 1:
if model.location_for_row(x) == 'carda':
self.card_a_view.write_settings()
elif model.location_for_row(x) == 'cardb':
self.card_b_view.write_settings()
def device_detected(self, connected):
'''
Called when a device is connected to the computer.
@ -757,6 +767,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.device_connected = True
self._sync_menu.enable_device_actions(True, self.device_manager.device.card_prefix())
else:
self.save_device_view_settings()
self.device_connected = False
self._sync_menu.enable_device_actions(False)
self.location_view.model().update_devices()
@ -765,7 +776,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.device_info = ' '
if self.current_view() != self.library_view:
self.status_bar.reset_info()
self.location_selected('library')
self.location_view.setCurrentIndex(self.location_view.model().index(0))
def info_read(self, job):
'''
@ -807,6 +818,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.card_b_view.set_editable(self.device_manager.device_class.CAN_SET_METADATA)
for view in (self.memory_view, self.card_a_view, self.card_b_view):
view.sortByColumn(3, Qt.DescendingOrder)
view.read_settings()
if not view.restore_column_widths():
view.resizeColumnsToContents()
view.resizeRowsToContents()
@ -1662,7 +1674,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
dynamic.set('sort_column', self.library_view.model().sorted_on)
self.library_view.write_settings()
if self.device_connected:
self.memory_view.write_settings()
self.save_device_view_settings()
def restart(self):
self.quit(restart=True)

View File

@ -15,11 +15,12 @@ from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
from calibre.utils.config import Config, StringConfig
from calibre.utils.localization import get_language
from calibre.gui2.viewer.config_ui import Ui_Dialog
from calibre.gui2.viewer.js import bookmarks, referencing, hyphenation
from calibre.ptempfile import PersistentTemporaryFile
from calibre.constants import iswindows
from calibre import prints, guess_type
bookmarks = referencing = hyphenation = jquery = jquery_scrollTo = hyphenator = None
def load_builtin_fonts():
base = P('fonts/liberation/*.ttf')
for f in glob.glob(base):
@ -192,15 +193,24 @@ class Document(QWebPage):
self.hyphenate_default_lang = opts.hyphenate_default_lang
def load_javascript_libraries(self):
global bookmarks, referencing, hyphenation, jquery, jquery_scrollTo, hyphenator
self.mainFrame().addToJavaScriptWindowObject("py_bridge", self)
jquery = open(P('content_server/jquery.js'), 'rb').read()
jquery_scrollTo = open(P('viewer/jquery_scrollTo.js'), 'rb').read()
hyphenator = open(P('viewer/hyphenate/Hyphenator.js'),
'rb').read().decode('utf-8')
if jquery is None:
jquery = P('content_server/jquery.js', data=True)
if jquery_scrollTo is None:
jquery_scrollTo = P('viewer/jquery_scrollTo.js', data=True)
if hyphenator is None:
hyphenator = P('viewer/hyphenate/Hyphenator.js', data=True).decode('utf-8')
self.javascript(jquery)
self.javascript(jquery_scrollTo)
if bookmarks is None:
bookmarks = P('viewer/bookmarks.js', data=True)
self.javascript(bookmarks)
if referencing is None:
referencing = P('viewer/referencing.js', data=True)
self.javascript(referencing)
if hyphenation is None:
hyphenation = P('viewer/hyphenation.js', data=True)
self.javascript(hyphenation)
default_lang = self.hyphenate_default_lang
lang = self.current_language
@ -333,6 +343,7 @@ class Document(QWebPage):
def width(self):
return self.mainFrame().contentsSize().width() # offsetWidth gives inaccurate results
class EntityDeclarationProcessor(object):
def __init__(self, html):
@ -447,6 +458,12 @@ class DocumentView(QWebView):
def path(self):
return os.path.abspath(unicode(self.url().toLocalFile()))
def self_closing_sub(self, match):
tag = match.group(1)
if tag.lower().strip() == 'br':
return match.group()
return '<%s %s></%s>'%(match.group(1), match.group(2), match.group(1))
def load_path(self, path, pos=0.0):
self.initial_pos = pos
mt = getattr(path, 'mime_type', None)
@ -455,7 +472,7 @@ class DocumentView(QWebView):
html = open(path, 'rb').read().decode(path.encoding, 'replace')
html = EntityDeclarationProcessor(html).processed_html
if 'xhtml' in mt:
html = self.self_closing_pat.sub(r'<\1 \2></\1>', html)
html = self.self_closing_pat.sub(self.self_closing_sub, html)
#self.setContent(QByteArray(html.encode(path.encoding)), mt, QUrl.fromLocalFile(path))
self.setHtml(html, QUrl.fromLocalFile(path))
self.turn_off_internal_scrollbars()
@ -508,6 +525,7 @@ class DocumentView(QWebView):
@classmethod
def test_line(cls, img, y):
'Test if line contains pixels of exactly the same color'
start = img.pixel(0, y)
for i in range(1, img.width()):
if img.pixel(i, y) != start:
@ -517,6 +535,7 @@ class DocumentView(QWebView):
def find_next_blank_line(self, overlap):
img = QImage(self.width(), overlap, QImage.Format_ARGB32)
painter = QPainter(img)
# Render a region of width x overlap pixels atthe bottom of the current viewport
self.document.mainFrame().render(painter, QRegion(0, 0, self.width(), overlap))
painter.end()
for i in range(overlap-1, -1, -1):
@ -542,18 +561,20 @@ class DocumentView(QWebView):
self.manager.scrolled(self.scroll_fraction)
def next_page(self):
delta_y = self.document.window_height - 25
window_height = self.document.window_height
delta_y = window_height - 25
if self.document.at_bottom:
if self.manager is not None:
self.manager.next_document()
else:
opos = self.document.ypos
lower_limit = opos + delta_y
max_y = self.document.height - self.document.window_height
max_y = self.document.height - window_height
lower_limit = min(max_y, lower_limit)
if lower_limit > opos:
self.document.scroll_to(self.document.xpos, lower_limit)
self.find_next_blank_line( self.height() - (self.document.ypos-opos) )
actually_scrolled = self.document.ypos - opos
self.find_next_blank_line(window_height - actually_scrolled)
if self.manager is not None:
self.manager.scrolled(self.scroll_fraction)

View File

@ -1,156 +0,0 @@
bookmarks = '''
function selector_in_parent(elem) {
var num = elem.prevAll().length;
var sel = " > *:eq("+num+") ";
return sel;
}
function selector(elem) {
var obj = elem;
var sel = "";
while (obj[0] != document) {
sel = selector_in_parent(obj) + sel;
obj = obj.parent();
}
return sel;
}
function find_closest_enclosing_block(top) {
var START = top-1000;
var STOP = top;
var matches = [];
var elem, temp;
var width = 1000;
for (y = START; y < STOP; y += 20) {
for ( x = 0; x < width; x += 20) {
elem = document.elementFromPoint(x, y);
try {
elem = $(elem);
temp = elem.offset().top
matches.push(elem);
if (Math.abs(temp - START) < 25) { y = STOP; break}
} catch(error) {}
}
}
var miny = Math.abs(matches[0].offset().top - START), min_elem = matches[0];
for (i = 1; i < matches.length; i++) {
elem = matches[i];
temp = Math.abs(elem.offset().top - START);
if ( temp < miny ) { miny = temp; min_elem = elem; }
}
return min_elem;
}
function calculate_bookmark(y) {
var elem = find_closest_enclosing_block(y);
var sel = selector(elem);
var ratio = (y - elem.offset().top)/elem.height();
if (ratio > 1) { ratio = 1; }
if (ratio < 0) { ratio = 0; }
return sel + "|" + ratio;
}
function animated_scrolling_done() {
window.py_bridge.animated_scroll_done();
}
function scroll_to_bookmark(bookmark) {
bm = bookmark.split("|");
var ratio = 0.7 * parseFloat(bm[1]);
$.scrollTo($(bm[0]), 1000,
{over:ratio, onAfter:function(){window.py_bridge.animated_scroll_done()}});
}
'''
referencing = '''
var reference_old_bgcol = "transparent";
var reference_prefix = "1.";
function show_reference_panel(ref) {
panel = $("#calibre_reference_panel");
if (panel.length < 1) {
$(document.body).append('<div id="calibre_reference_panel" style="top:20px; left:20px; padding-left:30px; padding-right:30px; font:monospace normal;text-align:center; z-index:10000; background: beige; border:red ridge 2px; position:absolute;"><h5>Paragraph</h5><p style="text-indent:0pt">None</p></div>')
panel = $("#calibre_reference_panel");
}
$("> p", panel).text(ref);
panel.css({top:(window.pageYOffset+20)+"px"});
panel.fadeIn(500);
}
function toggle_reference(e) {
p = $(this);
if (e.type == "mouseenter") {
reference_old_bgcol = p.css("background-color");
p.css({backgroundColor:"beige"});
var i = 0;
var paras = $("p");
for (j = 0; j < paras.length; j++,i++) {
if (paras[j] == p[0]) break;
}
show_reference_panel(reference_prefix+(i+1) );
} else {
p.css({backgroundColor:reference_old_bgcol});
panel = $("#calibre_reference_panel").hide();
}
return false;
}
function enter_reference_mode() {
$("p").bind("mouseenter mouseleave", toggle_reference);
}
function leave_reference_mode() {
$("p").unbind("mouseenter mouseleave", toggle_reference);
}
function goto_reference(ref) {
var tokens = ref.split(".");
if (tokens.length != 2) {alert("Invalid reference: "+ref); return;}
var num = parseInt(tokens[1]);
if (isNaN(num)) {alert("Invalid reference: "+ref); return;}
num -= 1;
if (num < 0) {alert("Invalid reference: "+ref); return;}
var p = $("p");
if (num >= p.length) {alert("Reference not found: "+ref); return;}
$.scrollTo($(p[num]), 1000,
{onAfter:function(){window.py_bridge.animated_scroll_done()}});
}
'''
test = '''
$(document.body).click(function(e) {
bm = calculate_bookmark(e.pageY);
scroll_to_bookmark(bm);
});
$(document).ready(enter_reference_mode);
'''
hyphenation = '''
function init_hyphenate() {
window.py_bridge.init_hyphenate();
}
document.addEventListener("DOMContentLoaded", init_hyphenate, false);
function do_hyphenation(lang) {
Hyphenator.config(
{
'minwordlength' : 6,
//'hyphenchar' : '|',
'displaytogglebox' : false,
'remoteloading' : false,
'onerrorhandler' : function (e) {
window.py_bridge.debug(e);
}
});
Hyphenator.hyphenate(document.body, lang);
}
'''

View File

@ -468,6 +468,11 @@ class LibraryPage(QWizardPage, LibraryUI):
self.init_languages()
self.connect(self.language, SIGNAL('currentIndexChanged(int)'),
self.change_language)
self.connect(self.location, SIGNAL('textChanged(QString)'),
self.location_text_changed)
def location_text_changed(self, newtext):
self.emit(SIGNAL('completeChanged()'))
def init_languages(self):
self.language.blockSignals(True)
@ -525,9 +530,13 @@ class LibraryPage(QWizardPage, LibraryUI):
self.location.setText(lp)
def isComplete(self):
lp = unicode(self.location.text())
return lp and os.path.exists(lp) and os.path.isdir(lp) and os.access(lp,
os.W_OK)
try:
lp = unicode(self.location.text())
ans = bool(lp) and os.path.exists(lp) and os.path.isdir(lp) and os.access(lp,
os.W_OK)
except:
ans = False
return ans
def commit(self, completed):
oldloc = prefs['library_path']

View File

@ -20,7 +20,7 @@ from calibre.utils.genshi.template import MarkupTemplate
FIELDS = set(['title', 'authors', 'author_sort', 'publisher', 'rating',
'timestamp', 'size', 'tags', 'comments', 'series', 'series_index',
'formats', 'isbn', 'uuid', 'cover'])
'formats', 'isbn', 'uuid', 'pubdate', 'cover'])
XML_TEMPLATE = '''\
<?xml version="1.0" encoding="UTF-8"?>
@ -38,6 +38,7 @@ XML_TEMPLATE = '''\
<publisher>${record['publisher']}</publisher>
<rating>${record['rating']}</rating>
<date>${record['timestamp']}</date>
<pubdate>${record['pubdate']}</pubdate>
<size>${record['size']}</size>
<tags py:if="record['tags']">
<py:for each="tag in record['tags']">

View File

@ -1584,7 +1584,7 @@ class LibraryDatabase2(LibraryDatabase):
prefix = self.library_path
FIELDS = set(['title', 'authors', 'author_sort', 'publisher', 'rating',
'timestamp', 'size', 'tags', 'comments', 'series', 'series_index',
'isbn', 'uuid'])
'isbn', 'uuid', 'pubdate'])
data = []
for record in self.data:
if record is None: continue

View File

@ -108,7 +108,7 @@ will appear in the next release of |app|.
Can I use both |app| and the SONY software to manage my reader?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Yes, you can use both, provided you don not run them at the same time. That is, you should use the following sequence:
Yes, you can use both, provided you do not run them at the same time. That is, you should use the following sequence:
Connect reader->Use one of the programs->Disconnect reader. Reconnect reader->Use the other program->disconnect reader.
The underlying reason is that the Reader uses a single file to keep track
@ -122,7 +122,7 @@ other via the computers hard disk.
If you do need to reset your metadata due to problems caused by using both
at the same time, then just delete the media.xml file on the Reader using
your PC's file explorer and it'll be recreated after disconnection.
your PC's file explorer and it will be recreated after disconnection.
Can I use the collections feature of the SONY reader?
@ -149,6 +149,21 @@ How do I use |app| with my Android phone?
First install the WordPlayer e-book reading app from the Android Marketplace onto you phone. Then simply plug your phone into the computer with a USB cable. |app| should automatically detect the phone and then you can transfer books to it by clicking the Send to Device button. |app| does not have support for every single androind device out there, so if you would like to have support for your device added, follow the instructions above for getting your device supported in |app|.
Can I access my |app| books using the web browser in my Kindle or other reading device?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|app| has a *Content Server* that exports the books in |app| as a web page. You can turn it on under
Preferences->Content Server. Then just point the web browser on your device to the computer running
the Content Server and you will be able to browse your book collection. For example, if the computer running
the server has IP address 63.45.128.5, in the browser, you would type::
http://63.45.128.5:8080
Some devices, like the Kindle, do not allow you to access port 8080 (the default port on which the content
server runs. In that case, change the port in the |app| Preferences to 80. (On some operating systems,
you may not be able to run the server on a port number less than 1024 because of security settings. In
this case the simplest solution is to adjust your router to forward requests on port 80 to port 8080).
I get the error message "Failed to start content server: Port 8080 not free on '0.0.0.0'"?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

File diff suppressed because it is too large Load Diff

View File

@ -8,13 +8,13 @@ msgstr ""
"Project-Id-Version: calibre\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2009-10-26 17:36+0000\n"
"PO-Revision-Date: 2009-10-29 20:45+0000\n"
"PO-Revision-Date: 2009-10-31 00:18+0000\n"
"Last-Translator: Kovid Goyal <Unknown>\n"
"Language-Team: Arabic <ar@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-10-30 08:26+0000\n"
"X-Launchpad-Export-Date: 2009-10-31 08:35+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

File diff suppressed because it is too large Load Diff

View File

@ -4,9 +4,9 @@
#
msgid ""
msgstr ""
"Project-Id-Version: calibre 0.6.20\n"
"POT-Creation-Date: 2009-10-30 16:50+MDT\n"
"PO-Revision-Date: 2009-10-30 16:50+MDT\n"
"Project-Id-Version: calibre 0.6.21\n"
"POT-Creation-Date: 2009-11-09 08:59+MST\n"
"PO-Revision-Date: 2009-11-09 08:59+MST\n"
"Last-Translator: Automatically generated\n"
"Language-Team: LANGUAGE\n"
"MIME-Version: 1.0\n"
@ -43,10 +43,10 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:45
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:46
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:36
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:61
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:63
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:103
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:105
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:62
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:64
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:104
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:106
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/mobi.py:171
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:329
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:444
@ -109,14 +109,14 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:106
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:139
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:431
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:432
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:170
#: /home/kovid/work/calibre/src/calibre/gui2/library.py:391
#: /home/kovid/work/calibre/src/calibre/gui2/library.py:404
#: /home/kovid/work/calibre/src/calibre/gui2/library.py:876
#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1002
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:211
#: /home/kovid/work/calibre/src/calibre/library/cli.py:280
#: /home/kovid/work/calibre/src/calibre/library/cli.py:281
#: /home/kovid/work/calibre/src/calibre/library/database.py:913
#: /home/kovid/work/calibre/src/calibre/library/database2.py:702
#: /home/kovid/work/calibre/src/calibre/library/database2.py:714
@ -244,7 +244,6 @@ msgid "This profile is intended for the Mobipocket books."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:93
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:253
msgid "This profile is intended for the Hanlin V3 and its clones."
msgstr ""
@ -289,6 +288,10 @@ msgstr ""
msgid "This profile is intended for the SONY PRS line. The 500/505/700 etc, in landscape mode. Mainly useful for comics."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:253
msgid "This profile is intended for the Hanlin V3/V5 and its clones."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:306
msgid "This profile is intended for the Amazon Kindle DX."
msgstr ""
@ -317,7 +320,7 @@ msgstr ""
msgid "Initialization of plugin %s failed with traceback:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:363
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:367
msgid ""
" %prog options\n"
"\n"
@ -325,27 +328,27 @@ msgid ""
" "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:369
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:373
msgid "Add a plugin by specifying the path to the zip file containing it."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:371
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:375
msgid "Remove a custom plugin by name. Has no effect on builtin plugins"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:373
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:377
msgid "Customize plugin. Specify name of plugin and customization string separated by a comma."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:375
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:379
msgid "List all installed plugins"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:377
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:381
msgid "Enable the named plugin"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:379
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:383
msgid "Disable the named plugin"
msgstr ""
@ -368,6 +371,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:13
#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:24
#: /home/kovid/work/calibre/src/calibre/devices/eslick/driver.py:17
#: /home/kovid/work/calibre/src/calibre/devices/iriver/driver.py:16
#: /home/kovid/work/calibre/src/calibre/devices/nuut2/driver.py:18
#: /home/kovid/work/calibre/src/calibre/devices/prs500/driver.py:88
msgid "Kovid Goyal"
@ -423,6 +427,10 @@ msgstr ""
msgid "Communicate with the IRex Digital Reader 1000 eBook reader."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/iriver/driver.py:15
msgid "Communicate with the Iriver Story reader."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:22
msgid "Communicate with the JetBook eBook reader."
msgstr ""
@ -956,9 +964,9 @@ msgstr ""
msgid "Normally, if the input file has no cover and you don't specify one, a default cover is generated with the title, authors, etc. This option disables the generation of this cover."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/fb2ml.py:123
#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/fb2ml.py:126
#: /home/kovid/work/calibre/src/calibre/ebooks/pml/pmlml.py:113
#: /home/kovid/work/calibre/src/calibre/ebooks/rb/rbml.py:98
#: /home/kovid/work/calibre/src/calibre/ebooks/rb/rbml.py:101
#: /home/kovid/work/calibre/src/calibre/ebooks/txt/txtml.py:77
msgid "Table of Contents:"
msgstr ""
@ -1970,7 +1978,7 @@ msgstr ""
msgid "Copy to Clipboard"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:386
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:387
msgid "Choose Files"
msgstr ""
@ -2668,7 +2676,7 @@ msgid "RB Output"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:77
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1386
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1398
msgid "Choose the format to view"
msgstr ""
@ -3272,7 +3280,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:467
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:797
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:152
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1060
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1072
#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:53
msgid "Error"
msgstr ""
@ -3347,7 +3355,7 @@ msgid "Failed to start content server"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:678
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:503
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:508
msgid "Select location for books"
msgstr ""
@ -3892,88 +3900,92 @@ msgid "Choose formats for "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:134
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:900
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:912
msgid "Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:160
msgid "No permission"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:161
msgid "You do not have permission to read the following files:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:183
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:184
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:185
msgid "No format selected"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:195
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:196
msgid "Could not read metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:196
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:197
msgid "Could not read metadata from %s format"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:241
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:247
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:242
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:248
msgid "Could not read cover"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:242
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:243
msgid "Could not read cover from %s format"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:248
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:249
msgid "The cover in the %s format is invalid"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:285
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:286
msgid "Abort the editing of all remaining books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:488
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:489
msgid "Downloading cover..."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:500
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:505
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:511
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:501
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:506
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:512
msgid "Cannot fetch cover"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:501
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:512
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:502
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:513
msgid "<b>Could not fetch cover.</b><br/>"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:502
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:503
msgid "The download timed out."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:506
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:507
msgid "Could not find cover for this book. Try specifying the ISBN first."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:518
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:519
msgid "Bad cover"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:519
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:520
msgid "The cover is not a valid picture"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:559
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:560
msgid "Cannot fetch metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:560
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:561
msgid "You must specify at least one of ISBN, Title, Authors or Publisher"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:612
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:613
msgid "Permission denied"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:613
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:614
msgid "Could not open %s. Is it being used by another program?"
msgstr ""
@ -4793,7 +4805,7 @@ msgid "Save to disk in a single directory"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:297
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1488
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1500
msgid "Save only %s format to disk"
msgstr ""
@ -4828,12 +4840,12 @@ msgid "Bad database location"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:445
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:511
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:516
msgid "Calibre Library"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:455
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1631
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1643
msgid "Choose a location for your ebook library."
msgstr ""
@ -4841,23 +4853,23 @@ msgstr ""
msgid "Browse by covers"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:754
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:764
msgid "Device: "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:756
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:766
msgid " detected."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:778
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:789
msgid "Connected "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:790
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:801
msgid "Device database corrupted"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:791
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:802
msgid ""
"\n"
" <p>The database of books on the reader is corrupted. Try the following:\n"
@ -4868,297 +4880,297 @@ msgid ""
" "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:892
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:935
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:904
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:947
msgid "Uploading books to device."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:901
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:913
msgid "EPUB Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:902
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:914
msgid "LRF Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:903
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:915
msgid "HTML Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:904
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:916
msgid "LIT Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:905
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:917
msgid "MOBI Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:906
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:918
msgid "Text books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:907
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:919
msgid "PDF Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:908
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:920
msgid "Comics"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:909
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:921
msgid "Archives"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:944
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:956
msgid "Failed to read metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:945
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:957
msgid "Failed to read metadata from the following"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:964
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:976
msgid "The selected books will be <b>permanently deleted</b> and the files removed from your computer. Are you sure?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:991
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1003
msgid "Deleting books from device."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1022
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1034
msgid "Cannot download metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1023
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1071
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1104
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1129
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1241
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1035
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1083
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1116
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1141
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1253
msgid "No books selected"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1032
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1044
msgid "covers"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1032
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1044
msgid "metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1034
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1046
msgid "Downloading %s for %d book(s)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1055
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1067
msgid "Failed to download some metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1056
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1068
msgid "Failed to download metadata for the following:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1059
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1071
msgid "Failed to download metadata:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1070
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1103
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1082
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1115
msgid "Cannot edit metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1128
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1140
msgid "Cannot save to disk"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1131
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1143
msgid "Choose destination directory"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1158
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1170
msgid "Error while saving"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1159
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1171
msgid "There was an error while saving."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1166
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1167
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1178
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1179
msgid "Could not save some books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1168
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1180
msgid "Click the show details button to see which ones."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1187
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1199
msgid "Fetching news from "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1200
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1212
msgid " fetched."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1240
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1252
msgid "Cannot convert"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1269
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1281
msgid "Starting conversion of %d book(s)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1380
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1399
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1392
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1411
msgid "No book selected"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1380
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1430
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1392
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1442
msgid "Cannot view"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1398
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1410
msgid "Cannot open folder"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1415
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1427
msgid "Multiple Books Selected"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1416
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1428
msgid "You are attempting to open %d books. Opening too many books at once can be slow and have a negative effect on the responsiveness of your computer. Once started the process cannot be stopped until complete. Do you wish to continue?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1431
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1443
msgid "%s has no available formats."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1472
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1484
msgid "Cannot configure"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1473
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1485
msgid "Cannot configure while there are running jobs."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1516
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1528
msgid "No detailed info available"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1517
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1529
msgid "No detailed information is available for books on the device."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1569
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1581
msgid "Error talking to device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1570
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1582
msgid "There was a temporary error talking to the device. Please unplug and reconnect the device and or reboot."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1593
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1611
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1605
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1623
msgid "Conversion Error"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1594
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1606
msgid "<p>Could not convert: %s<p>It is a <a href=\"%s\">DRM</a>ed book. You must first remove the DRM using third party tools."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1612
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1624
msgid "<b>Failed</b>"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1640
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1652
msgid "Invalid library location"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1641
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1653
msgid "Could not access %s. Using %s as the library."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1689
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1701
msgid "is the result of the efforts of many volunteers from all over the world. If you find it useful, please consider donating to support its development."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1713
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1725
msgid "There are active jobs. Are you sure you want to quit?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1716
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1728
msgid ""
" is communicating with the device!<br>\n"
" Quitting may cause corruption on the device.<br>\n"
" Are you sure you want to quit?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1720
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1732
msgid "WARNING: Active jobs"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1771
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1783
msgid "will keep running in the system tray. To close it, choose <b>Quit</b> in the context menu of the system tray."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1790
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1802
msgid "<span style=\"color:red; font-weight:bold\">Latest version: <a href=\"%s\">%s</a></span>"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1798
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1810
msgid "Update available"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1799
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1811
msgid "%s has been updated to version %s. See the <a href=\"http://calibre.kovidgoyal.net/wiki/Changelog\">new features</a>. Visit the download page?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1817
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1829
msgid "Use the library located at the specified path."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1819
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1831
msgid "Start minimized to system tray."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1821
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1833
msgid "Log debugging information to console"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1823
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1835
msgid "Do not check for updates"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1871
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1883
msgid "If you are sure it is not running"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1873
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1885
msgid "Cannot Start "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1874
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1886
msgid "%s is already running."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1877
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1889
msgid "may be running in the system tray, in the"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1879
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1891
msgid "upper right region of the screen."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1881
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1893
msgid "lower right region of the screen."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1884
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1896
msgid "try rebooting your computer."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1886
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1898
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1910
msgid "try deleting the file"
msgstr ""
@ -5509,57 +5521,57 @@ msgstr ""
msgid "&User stylesheet"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:30
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:31
msgid "Options to customize the ebook viewer"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:37
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:38
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:650
msgid "Remember last used window size"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:39
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:88
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:40
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:89
msgid "Set the user CSS stylesheet. This can be used to customize the look of all books."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:41
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:42
msgid "Maximum width of the viewer window, in pixels."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:42
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:43
msgid "Hyphenate text"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:44
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:45
msgid "Default language for hyphenation rules"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:46
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:47
msgid "Font options"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:49
msgid "The serif font family"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:50
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:51
msgid "The sans-serif font family"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:52
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:53
msgid "The monospaced font family"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:53
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:54
msgid "The standard font size in px"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:54
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:55
msgid "The monospaced font size in px"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:55
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:56
msgid "The standard font type"
msgstr ""
@ -5803,7 +5815,7 @@ msgstr ""
msgid "Could not move library"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:574
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:583
msgid "welcome wizard"
msgstr ""
@ -6028,68 +6040,68 @@ msgstr ""
msgid "The maximum number of matches to return per OPDS query. This affects Stanza, WordPlayer, etc. integration."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:120
#: /home/kovid/work/calibre/src/calibre/library/cli.py:121
msgid "Path to the calibre library. Default is to use the path stored in the settings."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:199
#: /home/kovid/work/calibre/src/calibre/library/cli.py:200
msgid ""
"%prog list [options]\n"
"\n"
"List the books available in the calibre database.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:207
#: /home/kovid/work/calibre/src/calibre/library/cli.py:208
msgid ""
"The fields to display when listing books in the database. Should be a comma separated list of fields.\n"
"Available fields: %s\n"
"Default: %%default. The special field \"all\" can be used to select all fields. Only has effect in the text output format."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:209
#: /home/kovid/work/calibre/src/calibre/library/cli.py:210
msgid ""
"The field by which to sort the results.\n"
"Available fields: %s\n"
"Default: %%default"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:211
#: /home/kovid/work/calibre/src/calibre/library/cli.py:212
msgid "Sort results in ascending order"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:213
#: /home/kovid/work/calibre/src/calibre/library/cli.py:214
msgid "Filter the results by the search query. For the format of the search query, please see the search related documentation in the User Manual. Default is to do no filtering."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:215
#: /home/kovid/work/calibre/src/calibre/library/cli.py:216
msgid "The maximum width of a single line in the output. Defaults to detecting screen size."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:216
#: /home/kovid/work/calibre/src/calibre/library/cli.py:217
msgid "The string used to separate fields. Default is a space."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:217
#: /home/kovid/work/calibre/src/calibre/library/cli.py:218
msgid "The prefix for all file paths. Default is the absolute path to the library folder."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:220
#: /home/kovid/work/calibre/src/calibre/library/cli.py:221
msgid "The format in which to output the data. Available choices: %s. Defaults is text."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:233
#: /home/kovid/work/calibre/src/calibre/library/cli.py:234
msgid "Invalid fields. Available fields:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:240
#: /home/kovid/work/calibre/src/calibre/library/cli.py:241
msgid "Invalid sort field. Available fields:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:311
#: /home/kovid/work/calibre/src/calibre/library/cli.py:312
msgid "The following books were not added as they already exist in the database (see --duplicates option):"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:334
#: /home/kovid/work/calibre/src/calibre/library/cli.py:335
msgid ""
"%prog add [options] file1 file2 file3 ...\n"
"\n"
@ -6097,49 +6109,49 @@ msgid ""
"the directory related options below.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:343
#: /home/kovid/work/calibre/src/calibre/library/cli.py:344
msgid "Assume that each directory has only a single logical book and that all files in it are different e-book formats of that book"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:345
#: /home/kovid/work/calibre/src/calibre/library/cli.py:346
msgid "Process directories recursively"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:347
#: /home/kovid/work/calibre/src/calibre/library/cli.py:348
msgid "Add books to database even if they already exist. Comparison is done based on book titles."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:357
#: /home/kovid/work/calibre/src/calibre/library/cli.py:358
msgid "You must specify at least one file to add"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:373
#: /home/kovid/work/calibre/src/calibre/library/cli.py:374
msgid ""
"%prog remove ids\n"
"\n"
"Remove the books identified by ids from the database. ids should be a comma separated list of id numbers (you can get id numbers by using the list command). For example, 23,34,57-85\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:388
#: /home/kovid/work/calibre/src/calibre/library/cli.py:389
msgid "You must specify at least one book to remove"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:407
#: /home/kovid/work/calibre/src/calibre/library/cli.py:408
msgid ""
"%prog add_format [options] id ebook_file\n"
"\n"
"Add the ebook in ebook_file to the available formats for the logical book identified by id. You can get id by using the list command. If the format already exists, it is replaced.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:422
#: /home/kovid/work/calibre/src/calibre/library/cli.py:423
msgid "You must specify an id and an ebook file"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:427
#: /home/kovid/work/calibre/src/calibre/library/cli.py:428
msgid "ebook file must have an extension"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:435
#: /home/kovid/work/calibre/src/calibre/library/cli.py:436
msgid ""
"\n"
"%prog remove_format [options] id fmt\n"
@ -6147,11 +6159,11 @@ msgid ""
"Remove the format fmt from the logical book identified by id. You can get id by using the list command. fmt should be a file extension like LRF or TXT or EPUB. If the logical book does not have fmt available, do nothing.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:452
#: /home/kovid/work/calibre/src/calibre/library/cli.py:453
msgid "You must specify an id and a format"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:470
#: /home/kovid/work/calibre/src/calibre/library/cli.py:471
msgid ""
"\n"
"%prog show_metadata [options] id\n"
@ -6160,15 +6172,15 @@ msgid ""
"id is an id number from the list command.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:478
#: /home/kovid/work/calibre/src/calibre/library/cli.py:479
msgid "Print metadata in OPF form (XML)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:487
#: /home/kovid/work/calibre/src/calibre/library/cli.py:488
msgid "You must specify an id"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:500
#: /home/kovid/work/calibre/src/calibre/library/cli.py:501
msgid ""
"\n"
"%prog set_metadata [options] id /path/to/metadata.opf\n"
@ -6179,11 +6191,11 @@ msgid ""
"show_metadata command.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:516
#: /home/kovid/work/calibre/src/calibre/library/cli.py:517
msgid "You must specify an id and a metadata file"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:536
#: /home/kovid/work/calibre/src/calibre/library/cli.py:537
msgid ""
"%prog export [options] ids\n"
"\n"
@ -6192,27 +6204,27 @@ msgid ""
"an opf file). You can get id numbers from the list command.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:544
#: /home/kovid/work/calibre/src/calibre/library/cli.py:545
msgid "Export all books in database, ignoring the list of ids."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:546
#: /home/kovid/work/calibre/src/calibre/library/cli.py:547
msgid "Export books to the specified directory. Default is"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:548
#: /home/kovid/work/calibre/src/calibre/library/cli.py:549
msgid "Export all books into a single directory"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:555
#: /home/kovid/work/calibre/src/calibre/library/cli.py:556
msgid "Specifying this switch will turn this behavior off."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:578
#: /home/kovid/work/calibre/src/calibre/library/cli.py:579
msgid "You must specify some ids or the %s option"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:590
#: /home/kovid/work/calibre/src/calibre/library/cli.py:591
msgid ""
"%%prog command [options] [arguments]\n"
"\n"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -70,11 +70,11 @@ Usage may be::
__all__ = ['css', 'stylesheets', 'CSSParser', 'CSSSerializer']
__docformat__ = 'restructuredtext'
__author__ = 'Christof Hoeke with contributions by Walter Doerwald'
__date__ = '$LastChangedDate:: 2009-08-01 16:10:11 -0600 #$:'
__date__ = '$LastChangedDate:: 2009-10-17 15:12:28 -0600 #$:'
VERSION = '0.9.6b3'
VERSION = '0.9.7a1'
__version__ = '%s $Id: __init__.py 1832 2009-08-01 22:10:11Z cthedot $' % VERSION
__version__ = '%s $Id: __init__.py 1877 2009-10-17 21:12:28Z cthedot $' % VERSION
import codec
import xml.dom
@ -270,6 +270,12 @@ def replaceUrls(sheet, replacer):
def resolveImports(sheet, target=None):
"""Recurcively combine all rules in given `sheet` into a `target` sheet.
@import rules which use media information are tried to be wrapped into
@media rules so keeping the media information. This may not work in
all instances (if e.g. an @import rule itself contains an @import rule
with different media infos or if it is contains rules which may not be
used inside an @media block like @namespace rules.). In these cases
the @import rule is kept as in the original sheet and a WARNING is issued.
:param sheet:
in this given :class:`cssutils.css.CSSStyleSheet` all import rules are
@ -290,8 +296,22 @@ def resolveImports(sheet, target=None):
log.info(u'Processing @import %r' % rule.href, neverraise=True)
if rule.styleSheet:
target.add(css.CSSComment(cssText=u'/* START @import "%s" */' % rule.href))
resolveImports(rule.styleSheet, target)
target.add(css.CSSComment(cssText=u'/* END "%s" */' % rule.href))
if rule.media.mediaText == 'all':
t = target
else:
log.info(u'Replacing @import media with @media: %s' %
rule.media.mediaText, neverraise=True)
t = css.CSSMediaRule(rule.media.mediaText)
try:
resolveImports(rule.styleSheet, t)
except xml.dom.HierarchyRequestErr, e:
log.warn(u'Cannot resolve @import: %s' %
e, neverraise=True)
target.add(rule)
else:
if t != target:
target.add(t)
t.add(css.CSSComment(cssText=u'/* END "%s" */' % rule.href))
else:
log.error(u'Cannot get referenced stylesheet %r' %
rule.href, neverraise=True)

View File

@ -37,12 +37,13 @@ __all__ = [
'CSSPageRule',
'CSSStyleRule',
'CSSUnknownRule',
'CSSVariablesRule'
'Selector', 'SelectorList',
'CSSStyleDeclaration', 'Property',
'CSSValue', 'CSSPrimitiveValue', 'CSSValueList'
]
__docformat__ = 'restructuredtext'
__version__ = '$Id: __init__.py 1610 2009-01-03 21:07:57Z cthedot $'
__version__ = '$Id: __init__.py 1859 2009-10-10 21:50:27Z cthedot $'
from cssstylesheet import *
from cssrulelist import *
@ -55,9 +56,11 @@ from cssmediarule import *
from cssnamespacerule import *
from csspagerule import *
from cssstylerule import *
from cssvariablesrule import *
from cssunknownrule import *
from selector import *
from selectorlist import *
from cssstyledeclaration import *
from cssvariablesdeclaration import *
from property import *
from cssvalue import *

View File

@ -5,7 +5,7 @@ added http://www.w3.org/TR/css3-fonts/.
"""
__all__ = ['CSSFontFaceRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssfontfacerule.py 1818 2009-07-30 21:39:00Z cthedot $'
__version__ = '$Id: cssfontfacerule.py 1868 2009-10-17 19:36:54Z cthedot $'
from cssstyledeclaration import CSSStyleDeclaration
import cssrule
@ -85,12 +85,16 @@ class CSSFontFaceRule(cssrule.CSSRule):
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
wellformed = True
# save if parse goes wrong
oldstyle = CSSStyleDeclaration()
oldstyle._absorb(self.style)
ok = True
beforetokens, brace = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
if self._tokenvalue(brace) != u'{':
wellformed = False
ok = False
self._log.error(
u'CSSFontFaceRule: No start { of style declaration found: %r' %
self._valuestr(cssText), brace)
@ -102,7 +106,7 @@ class CSSFontFaceRule(cssrule.CSSRule):
beforewellformed, expected = self._parse(expected=':',
seq=newseq, tokenizer=self._tokenize2(beforetokens),
productions={})
wellformed = wellformed and beforewellformed and new['wellformed']
ok = ok and beforewellformed and new['wellformed']
styletokens, braceorEOFtoken = self._tokensupto2(tokenizer,
blockendonly=True,
@ -110,32 +114,30 @@ class CSSFontFaceRule(cssrule.CSSRule):
val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF':
wellformed = False
ok = False
self._log.error(
u'CSSFontFaceRule: No "}" after style declaration found: %r' %
self._valuestr(cssText))
nonetoken = self._nexttoken(tokenizer)
if nonetoken:
wellformed = False
ok = False
self._log.error(u'CSSFontFaceRule: Trailing content found.',
token=nonetoken)
teststyle = CSSStyleDeclaration(parentRule=self)
if 'EOF' == typ:
# add again as style needs it
styletokens.append(braceorEOFtoken)
# may raise:
teststyle.cssText = styletokens
if wellformed:
# contains probably comments only upto {
self._setSeq(newseq)
# known as correct from before
cssutils.log.enabled = False
self.style.cssText = styletokens
cssutils.log.enabled = True
# SET, may raise:
self.style.cssText = styletokens
if ok:
# contains probably comments only (upto ``{``)
self._setSeq(newseq)
else:
# RESET
self.style._absorb(oldstyle)
cssText = property(_getCssText, _setCssText,

View File

@ -2,7 +2,7 @@
``name`` property from http://www.w3.org/TR/css3-cascade/#cascading."""
__all__ = ['CSSImportRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssimportrule.py 1824 2009-08-01 21:00:34Z cthedot $'
__version__ = '$Id: cssimportrule.py 1871 2009-10-17 19:57:37Z cthedot $'
import cssrule
import cssutils
@ -105,6 +105,10 @@ class CSSImportRule(cssrule.CSSRule):
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# save if parse goes wrong
oldmedia = cssutils.stylesheets.MediaList()
oldmedia._absorb(self.media)
# for closures: must be a mutable
new = {'keyword': self._tokenvalue(attoken),
'href': None,
@ -164,12 +168,14 @@ class CSSImportRule(cssrule.CSSRule):
self._log.error(u'CSSImportRule: No ";" found: %s' %
self._valuestr(cssText), token=token)
media = cssutils.stylesheets.MediaList()
media.mediaText = mediatokens
if media.wellformed:
new['media'] = media
seq.append(media, 'media')
#media = cssutils.stylesheets.MediaList()
self.media.mediaText = mediatokens
if self.media.wellformed:
new['media'] = self.media
seq.append(self.media, 'media')
else:
# RESET
self.media._absorb(oldmedia)
new['wellformed'] = False
self._log.error(u'CSSImportRule: Invalid MediaList: %s' %
self._valuestr(cssText), token=token)
@ -227,17 +233,8 @@ class CSSImportRule(cssrule.CSSRule):
if wellformed:
self.atkeyword = new['keyword']
self.hreftype = new['hreftype']
if new['media']:
# use same object
self.media.mediaText = new['media'].mediaText
# put it in newseq too
for index, x in enumerate(newseq):
if x.type == 'media':
newseq.replace(index, self.media,
x.type, x.line, x.col)
break
else:
# reset media
if not new['media']:
# reset media to base media
self.media.mediaText = u'all'
newseq.append(self.media, 'media')
self.name = new['name']

View File

@ -1,7 +1,7 @@
"""CSSMediaRule implements DOM Level 2 CSS CSSMediaRule."""
__all__ = ['CSSMediaRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssmediarule.py 1820 2009-08-01 20:53:08Z cthedot $'
__version__ = '$Id: cssmediarule.py 1871 2009-10-17 19:57:37Z cthedot $'
import cssrule
import cssutils
@ -87,6 +87,7 @@ class CSSMediaRule(cssrule.CSSRule):
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
"""
# media "name"? { cssRules }
super(CSSMediaRule, self)._setCssText(cssText)
# might be (cssText, namespaces)
@ -104,7 +105,9 @@ class CSSMediaRule(cssrule.CSSRule):
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# media "name"? { cssRules }
# save if parse goes wrong
oldmedia = cssutils.stylesheets.MediaList()
oldmedia._absorb(self.media)
# media
wellformed = True
@ -112,8 +115,7 @@ class CSSMediaRule(cssrule.CSSRule):
mediaqueryendonly=True,
separateEnd=True)
if u'{' == self._tokenvalue(end) or self._prods.STRING == self._type(end):
newmedia = cssutils.stylesheets.MediaList()
newmedia.mediaText = mediatokens
self.media.mediaText = mediatokens
# name (optional)
name = None
@ -209,14 +211,16 @@ class CSSMediaRule(cssrule.CSSRule):
new=new)
# no post condition
if newmedia.wellformed and wellformed:
# keep reference
self._media.mediaText = newmedia.mediaText
if self.media.wellformed and wellformed:
self.name = name
self._setSeq(nameseq)
del self._cssRules[:]
for r in newcssrules:
self._cssRules.append(r)
else:
# RESET
self.media._absorb(oldmedia)
cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.")
@ -243,7 +247,13 @@ class CSSMediaRule(cssrule.CSSRule):
Delete the rule at `index` from the media block.
:param index:
of the rule to remove within the media block's rule collection
The `index` of the rule to be removed from the media block's rule
list. For an `index` < 0 **no** :exc:`~xml.dom.IndexSizeErr` is
raised but rules for normal Python lists are used. E.g.
``deleteRule(-1)`` removes the last rule in cssRules.
`index` may also be a CSSRule object which will then be removed
from the media block.
:Exceptions:
- :exc:`~xml.dom.IndexSizeErr`:
@ -254,6 +264,16 @@ class CSSMediaRule(cssrule.CSSRule):
"""
self._checkReadonly()
if isinstance(index, cssrule.CSSRule):
for i, r in enumerate(self.cssRules):
if index == r:
index = i
break
else:
raise xml.dom.IndexSizeErr(u"CSSMediaRule: Not a rule in"
" this rule'a cssRules list: %s"
% index)
try:
self._cssRules[index]._parentRule = None # detach
del self._cssRules[index] # remove from @media

View File

@ -1,7 +1,7 @@
"""CSSPageRule implements DOM Level 2 CSS CSSPageRule."""
__all__ = ['CSSPageRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: csspagerule.py 1824 2009-08-01 21:00:34Z cthedot $'
__version__ = '$Id: csspagerule.py 1868 2009-10-17 19:36:54Z cthedot $'
from cssstyledeclaration import CSSStyleDeclaration
from selectorlist import SelectorList
@ -141,7 +141,6 @@ class CSSPageRule(cssrule.CSSRule):
# if not newselector in (None, u':first', u':left', u':right'):
# self._log.warn(u'CSSPageRule: Unknown CSS 2.1 @page selector: %r' %
# newselector, neverraise=True)
return wellformed, newseq
def _getCssText(self):
@ -171,7 +170,11 @@ class CSSPageRule(cssrule.CSSRule):
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
wellformed = True
# save if parse goes wrong
oldstyle = CSSStyleDeclaration()
oldstyle._absorb(self.style)
ok = True
selectortokens, startbrace = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
@ -180,22 +183,21 @@ class CSSPageRule(cssrule.CSSRule):
separateEnd=True)
nonetoken = self._nexttoken(tokenizer)
if self._tokenvalue(startbrace) != u'{':
wellformed = False
ok = False
self._log.error(
u'CSSPageRule: No start { of style declaration found: %r' %
self._valuestr(cssText), startbrace)
elif nonetoken:
wellformed = False
ok = False
self._log.error(
u'CSSPageRule: Trailing content found.', token=nonetoken)
wellformed, newselectorseq = self.__parseSelectorText(selectortokens)
selok, newselectorseq = self.__parseSelectorText(selectortokens)
ok = ok and selok
teststyle = CSSStyleDeclaration(parentRule=self)
val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF':
wellformed = False
ok = False
self._log.error(
u'CSSPageRule: No "}" after style declaration found: %r' %
self._valuestr(cssText))
@ -203,14 +205,14 @@ class CSSPageRule(cssrule.CSSRule):
if 'EOF' == typ:
# add again as style needs it
styletokens.append(braceorEOFtoken)
teststyle.cssText = styletokens
if wellformed:
# known as correct from before
cssutils.log.enabled = False
self._selectorText = newselectorseq # TODO: TEST and REFS
self.style.cssText = styletokens
cssutils.log.enabled = True
if ok:
# TODO: TEST and REFS
self._selectorText = newselectorseq
else:
# RESET
self.style._absorb(oldstyle)
cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.")

View File

@ -1,7 +1,7 @@
"""CSSRule implements DOM Level 2 CSS CSSRule."""
__all__ = ['CSSRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssrule.py 1808 2009-07-29 13:09:36Z cthedot $'
__version__ = '$Id: cssrule.py 1855 2009-10-07 17:03:19Z cthedot $'
import cssutils
import xml.dom
@ -27,9 +27,11 @@ class CSSRule(cssutils.util.Base2):
FONT_FACE_RULE = 5 #f
PAGE_RULE = 6 #p
NAMESPACE_RULE = 7 # CSSOM
VARIABLES_RULE = 8 # CSS Variables
_typestrings = ['UNKNOWN_RULE', 'STYLE_RULE', 'CHARSET_RULE', 'IMPORT_RULE',
'MEDIA_RULE', 'FONT_FACE_RULE', 'PAGE_RULE', 'NAMESPACE_RULE',
'VARIABLES_RULE',
'COMMENT']
def __init__(self, parentRule=None, parentStyleSheet=None, readonly=False):

View File

@ -51,7 +51,7 @@ TODO:
"""
__all__ = ['CSSStyleDeclaration', 'Property']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstyledeclaration.py 1819 2009-08-01 20:52:43Z cthedot $'
__version__ = '$Id: cssstyledeclaration.py 1870 2009-10-17 19:56:59Z cthedot $'
from cssproperties import CSS2Properties
from property import Property
@ -201,6 +201,12 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
names.append(val.name)
return reversed(names)
def _absorb(self, other):
"""Replace all own data with data from other object."""
self._parentRule = other._parentRule
self.seq.absorb(other.seq)
self._readonly = other._readonly
# overwritten accessor functions for CSS2Properties' properties
def _getP(self, CSSName):
"""(DOM CSS2Properties) Overwritten here and effectively the same as

View File

@ -1,7 +1,7 @@
"""CSSStyleRule implements DOM Level 2 CSS CSSStyleRule."""
__all__ = ['CSSStyleRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstylerule.py 1815 2009-07-29 16:51:58Z cthedot $'
__version__ = '$Id: cssstylerule.py 1868 2009-10-17 19:36:54Z cthedot $'
from cssstyledeclaration import CSSStyleDeclaration
from selectorlist import SelectorList
@ -104,28 +104,30 @@ class CSSStyleRule(cssrule.CSSRule):
self._log.error(u'CSSStyleRule: No style rule: %r' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
wellformed = True
testselectorlist, teststyle = None, None
else:
# save if parse goes wrong
oldstyle = CSSStyleDeclaration()
oldstyle._absorb(self.style)
oldselector = SelectorList()
oldselector._absorb(self.selectorList)
ok = True
bracetoken = selectortokens.pop()
if self._tokenvalue(bracetoken) != u'{':
wellformed = False
ok = False
self._log.error(
u'CSSStyleRule: No start { of style declaration found: %r' %
self._valuestr(cssText), bracetoken)
elif not selectortokens:
wellformed = False
ok = False
self._log.error(u'CSSStyleRule: No selector found: %r.' %
self._valuestr(cssText), bracetoken)
testselectorlist = SelectorList(selectorText=(selectortokens,
namespaces),
parentRule=self)
# SET
self.selectorList.selectorText = (selectortokens,
namespaces)
if not styletokens:
wellformed = False
ok = False
self._log.error(
u'CSSStyleRule: No style declaration or "}" found: %r' %
self._valuestr(cssText))
@ -133,7 +135,7 @@ class CSSStyleRule(cssrule.CSSRule):
braceorEOFtoken = styletokens.pop()
val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF':
wellformed = False
ok = False
self._log.error(
u'CSSStyleRule: No "}" after style declaration found: %r' %
self._valuestr(cssText))
@ -141,14 +143,19 @@ class CSSStyleRule(cssrule.CSSRule):
if 'EOF' == typ:
# add again as style needs it
styletokens.append(braceorEOFtoken)
teststyle = CSSStyleDeclaration(styletokens, parentRule=self)
# SET
try:
self.style.cssText = styletokens
except:
# reset in case of error
self.selectorList._absorb(oldselector)
raise
if wellformed and testselectorlist and teststyle:
# known as correct from before
cssutils.log.enabled = False
self.style.cssText = styletokens
self.selectorList.selectorText=(selectortokens, namespaces)
cssutils.log.enabled = True
if not ok or not self.wellformed:
# reset as not ok
self.selectorList._absorb(oldselector)
self.style._absorb(oldstyle)
cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.")

View File

@ -9,10 +9,11 @@ TODO:
"""
__all__ = ['CSSStyleSheet']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstylesheet.py 1820 2009-08-01 20:53:08Z cthedot $'
__version__ = '$Id: cssstylesheet.py 1857 2009-10-10 21:49:33Z cthedot $'
from cssutils.helper import Deprecated
from cssutils.util import _Namespaces, _SimpleNamespaces, _readUrl
from cssrule import CSSRule
import cssutils.stylesheets
import xml.dom
@ -204,11 +205,26 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
token, xml.dom.HierarchyRequestErr)
else:
if rule.wellformed:
for i, r in enumerate(seq):
if r.type == r.NAMESPACE_RULE and r.prefix == rule.prefix:
# replace as doubled:
seq[i] = rule
self._log.info(
u'CSSStylesheet: CSSNamespaceRule with same prefix found, replacing: %r'
% r.cssText,
token, neverraise=True)
seq.append(rule)
# temporary namespaces given to CSSStyleRule and @media
new['namespaces'][rule.prefix] = rule.namespaceURI
return 2
def variablesrule(expected, seq, token, tokenizer):
rule = cssutils.css.CSSVariablesRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
seq.append(rule)
return 2
def fontfacerule(expected, seq, token, tokenizer):
rule = cssutils.css.CSSFontFaceRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
@ -266,6 +282,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
'NAMESPACE_SYM': namespacerule,
'PAGE_SYM': pagerule,
'MEDIA_SYM': mediarule,
'VARIABLES_SYM': variablesrule,
'ATKEYWORD': unknownrule
},
default=ruleset)
@ -366,10 +383,14 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
"""Delete rule at `index` from the style sheet.
:param index:
of the rule to remove in the StyleSheet's rule list. For an
`index` < 0 **no** :exc:`~xml.dom.IndexSizeErr` is raised but
rules for normal Python lists are used. E.g. ``deleteRule(-1)``
removes the last rule in cssRules.
The `index` of the rule to be removed from the StyleSheet's rule
list. For an `index` < 0 **no** :exc:`~xml.dom.IndexSizeErr` is
raised but rules for normal Python lists are used. E.g.
``deleteRule(-1)`` removes the last rule in cssRules.
`index` may also be a CSSRule object which will then be removed
from the StyleSheet.
:exceptions:
- :exc:`~xml.dom.IndexSizeErr`:
Raised if the specified index does not correspond to a rule in
@ -381,6 +402,16 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
"""
self._checkReadonly()
if isinstance(index, CSSRule):
for i, r in enumerate(self.cssRules):
if index == r:
index = i
break
else:
raise xml.dom.IndexSizeErr(u"CSSStyleSheet: Not a rule in"
" this sheets'a cssRules list: %s"
% index)
try:
rule = self._cssRules[index]
except IndexError:
@ -495,14 +526,16 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
if inOrder:
index = 0
# always first and only
if (self._cssRules and self._cssRules[0].type == rule.CHARSET_RULE):
if (self._cssRules
and self._cssRules[0].type == rule.CHARSET_RULE):
self._cssRules[0].encoding = rule.encoding
else:
self._cssRules.insert(0, rule)
elif index != 0 or (self._cssRules and
self._cssRules[0].type == rule.CHARSET_RULE):
self._log.error(
u'CSSStylesheet: @charset only allowed once at the beginning of a stylesheet.',
u'CSSStylesheet: @charset only allowed once at the'
' beginning of a stylesheet.',
error=xml.dom.HierarchyRequestErr)
return
else:
@ -532,7 +565,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
else:
# find first point to insert
if self._cssRules and self._cssRules[0].type in (rule.CHARSET_RULE,
rule.COMMENT):
rule.COMMENT):
index = 1
else:
index = 0
@ -544,12 +577,18 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
u'CSSStylesheet: Found @charset at index 0.',
error=xml.dom.HierarchyRequestErr)
return
# before @namespace, @page, @font-face, @media and stylerule
# before @namespace @variables @page @font-face @media stylerule
for r in self._cssRules[:index]:
if r.type in (r.NAMESPACE_RULE, r.MEDIA_RULE, r.PAGE_RULE,
r.STYLE_RULE, r.FONT_FACE_RULE):
if r.type in (r.NAMESPACE_RULE,
r.VARIABLES_RULE,
r.MEDIA_RULE,
r.PAGE_RULE,
r.STYLE_RULE,
r.FONT_FACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @import here, found @namespace, @media, @page or CSSStyleRule before index %s.' %
u'CSSStylesheet: Cannot insert @import here,'
' found @namespace, @variables, @media, @page or'
' CSSStyleRule before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
@ -567,8 +606,10 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
else:
# find first point to insert
for i, r in enumerate(self._cssRules):
if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE,
r.FONT_FACE_RULE, r.UNKNOWN_RULE, r.COMMENT):
if r.type in (r.VARIABLES_RULE, r.MEDIA_RULE,
r.PAGE_RULE, r.STYLE_RULE,
r.FONT_FACE_RULE, r.UNKNOWN_RULE,
r.COMMENT):
index = i # before these
break
else:
@ -576,16 +617,22 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
for r in self._cssRules[index:]:
if r.type in (r.CHARSET_RULE, r.IMPORT_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @namespace here, found @charset or @import after index %s.' %
u'CSSStylesheet: Cannot insert @namespace here,'
' found @charset or @import after index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
# before @media and stylerule
# before @variables @media @page @font-face and stylerule
for r in self._cssRules[:index]:
if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE,
if r.type in (r.VARIABLES_RULE,
r.MEDIA_RULE,
r.PAGE_RULE,
r.STYLE_RULE,
r.FONT_FACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @namespace here, found @media, @page or CSSStyleRule before index %s.' %
u'CSSStylesheet: Cannot insert @namespace here,'
' found @variables, @media, @page or CSSStyleRule'
' before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
@ -597,6 +644,56 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
if _clean:
self._cleanNamespaces()
# @variables
elif rule.type == rule.VARIABLES_RULE:
if inOrder:
if rule.type in (r.type for r in self):
# find last of this type
for i, r in enumerate(reversed(self._cssRules)):
if r.type == rule.type:
index = len(self._cssRules) - i
break
else:
# find first point to insert
for i, r in enumerate(self._cssRules):
if r.type in (r.MEDIA_RULE,
r.PAGE_RULE,
r.STYLE_RULE,
r.FONT_FACE_RULE,
r.UNKNOWN_RULE,
r.COMMENT):
index = i # before these
break
else:
# after @charset @import @namespace
for r in self._cssRules[index:]:
if r.type in (r.CHARSET_RULE,
r.IMPORT_RULE,
r.NAMESPACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @variables here,'
' found @charset, @import or @namespace after'
' index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
# before @media @page @font-face and stylerule
for r in self._cssRules[:index]:
if r.type in (r.MEDIA_RULE,
r.PAGE_RULE,
r.STYLE_RULE,
r.FONT_FACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @variables here,'
' found @media, @page or CSSStyleRule'
' before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
self._cssRules.insert(index, rule)
# all other where order is not important
else:
if inOrder:

View File

@ -5,9 +5,10 @@
- CSSValueList implements DOM Level 2 CSS CSSValueList
"""
__all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList', 'RGBColor']
__all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList',
'CSSVariable', 'RGBColor']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssvalue.py 1834 2009-08-02 12:20:21Z cthedot $'
__version__ = '$Id: cssvalue.py 1864 2009-10-11 15:11:39Z cthedot $'
from cssutils.prodparser import *
import cssutils
@ -29,13 +30,16 @@ class CSSValue(cssutils.util._NewBase):
CSS_VALUE_LIST = 2
# The value is a custom value.
CSS_CUSTOM = 3
# The value is a CSSVariable.
CSS_VARIABLE = 4
_typestrings = {0: 'CSS_INHERIT' ,
_typestrings = {0: 'CSS_INHERIT' ,
1: 'CSS_PRIMITIVE_VALUE',
2: 'CSS_VALUE_LIST',
3: 'CSS_CUSTOM'}
3: 'CSS_CUSTOM',
4: 'CSS_VARIABLE'}
def __init__(self, cssText=None, readonly=False):
def __init__(self, cssText=None, parent=None, readonly=False):
"""
:param cssText:
the parsable cssText of the value
@ -46,6 +50,7 @@ class CSSValue(cssutils.util._NewBase):
self._cssValueType = None
self.wellformed = False
self.parent = parent
if cssText is not None: # may be 0
if type(cssText) in (int, float):
@ -109,9 +114,9 @@ class CSSValue(cssutils.util._NewBase):
# used as operator is , / or S
nextSor = u',/'
term = Choice(Sequence(PreDef.unary(),
Choice(PreDef.number(nextSor=nextSor),
term = Choice(Sequence(PreDef.unary(),
Choice(PreDef.number(nextSor=nextSor),
PreDef.percentage(nextSor=nextSor),
PreDef.dimension(nextSor=nextSor))),
PreDef.string(nextSor=nextSor),
@ -120,33 +125,55 @@ class CSSValue(cssutils.util._NewBase):
PreDef.hexcolor(nextSor=nextSor),
PreDef.unicode_range(nextSor=nextSor),
# special case IE only expression
Prod(name='expression',
Prod(name='expression',
match=lambda t, v: t == self._prods.FUNCTION and (
cssutils.helper.normalize(v) in (u'expression(',
u'alpha(') or
v.startswith(u'progid:DXImageTransform.Microsoft.') ),
cssutils.helper.normalize(v) in (u'expression(',
u'alpha(') or
v.startswith(u'progid:DXImageTransform.Microsoft.')
),
nextSor=nextSor,
toSeq=lambda t, tokens: (ExpressionValue.name,
ExpressionValue(cssutils.helper.pushtoken(t,
tokens)))
toSeq=lambda t, tokens: (ExpressionValue._functionName,
ExpressionValue(cssutils.helper.pushtoken(t,
tokens)))
),
# CSS Variable var(
PreDef.variable(nextSor=nextSor,
toSeq=lambda t, tokens: ('CSSVariable',
CSSVariable(
cssutils.helper.pushtoken(t, tokens))
)
),
# other functions like rgb( etc
PreDef.function(nextSor=nextSor,
toSeq=lambda t, tokens: ('FUNCTION',
CSSFunction(cssutils.helper.pushtoken(t,
tokens)))))
toSeq=lambda t, tokens: ('FUNCTION',
CSSFunction(
cssutils.helper.pushtoken(t, tokens))
)
)
)
operator = Choice(PreDef.S(),
PreDef.char('comma', ',', toSeq=lambda t, tokens: ('operator', t[1])),
PreDef.char('slash', '/', toSeq=lambda t, tokens: ('operator', t[1])),
PreDef.char('comma', ',',
toSeq=lambda t, tokens: ('operator', t[1])),
PreDef.char('slash', '/',
toSeq=lambda t, tokens: ('operator', t[1])),
optional=True)
# CSSValue PRODUCTIONS
valueprods = Sequence(term,
valueprods = Sequence(term,
# TODO: only when setting via other class
PreDef.char('END', ';',
stopAndKeep=True,
optional=True),
Sequence(operator, # mayEnd this Sequence if whitespace
term,
PreDef.char('END', ';',
stopAndKeep=True,
optional=True),
minmax=lambda: (0, None)))
# parse
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
u'CSSValue',
valueprods)
wellformed, seq, store, notused = ProdParser().parse(cssText,
u'CSSValue',
valueprods,
keepS=True)
if wellformed:
# - count actual values and set firstvalue which is used later on
# - combine comma separated list, e.g. font-family to a single item
@ -183,7 +210,7 @@ class CSSValue(cssutils.util._NewBase):
break
newval = item.value + next.value
newseq.append(newval, next.type,
newseq.append(newval, next.type,
item.line, item.col)
if not firstvalue:
firstvalue = (newval, next.type)
@ -202,7 +229,7 @@ class CSSValue(cssutils.util._NewBase):
if not firstvalue:
self._log.error(
u'CSSValue: Unknown syntax or no value: %r.' %
u'CSSValue: Unknown syntax or no value: %r.' %
self._valuestr(cssText))
else:
# ok and set
@ -214,16 +241,24 @@ class CSSValue(cssutils.util._NewBase):
del self._value
if count == 1:
# inherit, primitive or variable
if isinstance(firstvalue[0], basestring) and\
u'inherit' == cssutils.helper.normalize(firstvalue[0]):
self.__class__ = CSSValue
self._cssValueType = CSSValue.CSS_INHERIT
self._cssValueType = CSSValue.CSS_INHERIT
elif 'CSSVariable' == firstvalue[1]:
self.__class__ = CSSVariable
self._value = firstvalue
# TODO: remove major hack!
self._name = firstvalue[0]._name
else:
self.__class__ = CSSPrimitiveValue
self._value = firstvalue
elif count > 1:
# valuelist
self.__class__ = CSSValueList
# change items in list to specific type (primitive etc)
newseq = self._tempSeq()
commalist = []
@ -235,7 +270,8 @@ class CSSValue(cssutils.util._NewBase):
return cssutils.helper.string(item.value)
elif self._prods.URI == item.type:
return cssutils.helper.uri(item.value)
elif self._prods.FUNCTION == item.type:
elif self._prods.FUNCTION == item.type or\
'CSSVariable' == item.type:
return item.value.cssText
else:
return item.value
@ -246,9 +282,9 @@ class CSSValue(cssutils.util._NewBase):
if anything in there
"""
if commalist:
newseq.replace(-1,
newseq.replace(-1,
CSSPrimitiveValue(cssText=u''.join(
commalist)),
commalist)),
CSSPrimitiveValue,
newseq[-1].line,
newseq[-1].col)
@ -263,7 +299,8 @@ class CSSValue(cssutils.util._NewBase):
self._prods.PERCENTAGE,
self._prods.STRING,
self._prods.URI,
self._prods.UNICODE_RANGE):
self._prods.UNICODE_RANGE,
'CSSVariable'):
if nexttocommalist:
# wait until complete
commalist.append(itemValue(item))
@ -271,13 +308,13 @@ class CSSValue(cssutils.util._NewBase):
saveifcommalist(commalist, newseq)
# append new item
if hasattr(item.value, 'cssText'):
newseq.append(item.value,
item.value.__class__,
newseq.append(item.value,
item.value.__class__,
item.line, item.col)
else:
newseq.append(CSSPrimitiveValue(itemValue(item)),
CSSPrimitiveValue,
newseq.append(CSSPrimitiveValue(itemValue(item)),
CSSPrimitiveValue,
item.line, item.col)
nexttocommalist = False
@ -285,7 +322,7 @@ class CSSValue(cssutils.util._NewBase):
elif u',' == item.value:
if not commalist:
# save last item to commalist
commalist.append(itemValue(self._seq[i-1]))
commalist.append(itemValue(self._seq[i - 1]))
commalist.append(u',')
nexttocommalist = True
@ -297,12 +334,13 @@ class CSSValue(cssutils.util._NewBase):
saveifcommalist(commalist, newseq)
self._setSeq(newseq)
else:
# should not happen...
self.__class__ = CSSValue
self._cssValueType = CSSValue.CSS_CUSTOM
cssText = property(lambda self: cssutils.ser.do_css_CSSValue(self),
cssText = property(lambda self: cssutils.ser.do_css_CSSValue(self),
_setCssText,
doc="A string representation of the current value.")
@ -373,7 +411,7 @@ class CSSPrimitiveValue(CSSValue):
_countertypes = (CSS_COUNTER,)
_recttypes = (CSS_RECT,)
_rbgtypes = (CSS_RGBCOLOR, CSS_RGBACOLOR)
_lengthtypes = (CSS_NUMBER, CSS_EMS, CSS_EXS,
_lengthtypes = (CSS_NUMBER, CSS_EMS, CSS_EXS,
CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC)
# oldtype: newType: converterfunc
@ -436,14 +474,14 @@ class CSSPrimitiveValue(CSSValue):
'CSS_DEG', 'CSS_RAD', 'CSS_GRAD',
'CSS_MS', 'CSS_S',
'CSS_HZ', 'CSS_KHZ',
'CSS_DIMENSION',
'CSS_DIMENSION',
'CSS_STRING', 'CSS_URI', 'CSS_IDENT',
'CSS_ATTR', 'CSS_COUNTER', 'CSS_RECT',
'CSS_RGBCOLOR', 'CSS_RGBACOLOR',
'CSS_UNICODE_RANGE'
]
_reNumDim = re.compile(ur'([+-]?\d*\.\d+|[+-]?\d+)(.*)$', re.I| re.U|re.X)
_reNumDim = re.compile(ur'([+-]?\d*\.\d+|[+-]?\d+)(.*)$', re.I | re.U | re.X)
def _unitDIMENSION(value):
"""Check val for dimension name."""
@ -467,8 +505,8 @@ class CSSPrimitiveValue(CSSValue):
'rgb(': 'CSS_RGBCOLOR',
'rgba(': 'CSS_RGBACOLOR',
}
return units.get(re.findall(ur'^(.*?\()',
cssutils.helper.normalize(value.cssText),
return units.get(re.findall(ur'^(.*?\()',
cssutils.helper.normalize(value.cssText),
re.U)[0],
'CSS_UNKNOWN')
@ -601,13 +639,13 @@ class CSSPrimitiveValue(CSSValue):
self._checkReadonly()
if unitType not in self._floattypes:
raise xml.dom.InvalidAccessErr(
u'CSSPrimitiveValue: unitType %r is not a float type' %
u'CSSPrimitiveValue: unitType %r is not a float type' %
self._getCSSPrimitiveTypeString(unitType))
try:
val = float(floatValue)
except ValueError, e:
raise xml.dom.InvalidAccessErr(
u'CSSPrimitiveValue: floatValue %r is not a float' %
u'CSSPrimitiveValue: floatValue %r is not a float' %
floatValue)
oldval, dim = self._getNumDim()
@ -761,17 +799,17 @@ class CSSValueList(CSSValue):
"""
cssValueType = CSSValue.CSS_VALUE_LIST
def __init__(self, cssText=None, readonly=False):
def __init__(self, cssText=None, parent=None, readonly=False):
"""Init a new CSSValueList"""
super(CSSValueList, self).__init__(cssText=cssText, readonly=readonly)
super(CSSValueList, self).__init__(cssText=cssText,
parent=parent,
readonly=readonly)
self._items = []
def __iter__(self):
"CSSValueList is iterable."
def itemsiter():
for i in range (0, self.length):
yield self.item(i)
return itemsiter()
for item in self.__items():
yield item.value
def __str__(self):
return "<cssutils.css.%s object cssValueType=%r cssText=%r length=%r at 0x%x>" % (
@ -799,7 +837,7 @@ class CSSValueList(CSSValue):
class CSSFunction(CSSPrimitiveValue):
"""A CSS function value like rect() etc."""
name = u'CSSFunction'
_functionName = u'CSSFunction'
primitiveType = CSSPrimitiveValue.CSS_UNKNOWN
def __init__(self, cssText=None, readonly=False):
@ -812,46 +850,37 @@ class CSSFunction(CSSPrimitiveValue):
defaults to False
"""
super(CSSFunction, self).__init__()
self._funcType = None
self.valid = False
self.wellformed = False
if cssText is not None:
self.cssText = cssText
self._funcType = None
self._readonly = readonly
def __repr__(self):
return "cssutils.css.%s(%r)" % (self.__class__.__name__, self.cssText)
def __str__(self):
return "<cssutils.css.%s object primitiveType=%s cssText=%r at 0x%x>" % (
self.__class__.__name__, self.primitiveTypeString, self.cssText,
id(self))
def _productiondefinition(self):
"""Return defintion used for parsing."""
types = self._prods # rename!
valueProd = Prod(name='PrimitiveValue',
match=lambda t, v: t in (types.DIMENSION,
types.IDENT,
types.NUMBER,
valueProd = Prod(name='PrimitiveValue',
match=lambda t, v: t in (types.DIMENSION,
types.IDENT,
types.NUMBER,
types.PERCENTAGE,
types.STRING),
toSeq=lambda t, tokens: (t[0], CSSPrimitiveValue(t[1])))
funcProds = Sequence(Prod(name='FUNC',
match=lambda t, v: t == types.FUNCTION,
funcProds = Sequence(Prod(name='FUNC',
match=lambda t, v: t == types.FUNCTION,
toSeq=lambda t, tokens: (t[0], cssutils.helper.normalize(t[1]))),
Choice(Sequence(PreDef.unary(),
Choice(Sequence(PreDef.unary(),
valueProd,
# more values starting with Comma
# should use store where colorType is saved to
# define min and may, closure?
Sequence(PreDef.comma(),
PreDef.unary(),
valueProd,
minmax=lambda: (0, 3)),
Sequence(PreDef.comma(),
PreDef.unary(),
valueProd,
minmax=lambda: (0, 3)),
PreDef.funcEnd(stop=True)),
PreDef.funcEnd(stop=True))
)
@ -861,8 +890,9 @@ class CSSFunction(CSSPrimitiveValue):
self._checkReadonly()
# store: colorType, parts
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
self.name,
self._productiondefinition())
self._functionName,
self._productiondefinition(),
keepS=True)
if wellformed:
# combine +/- and following CSSPrimitiveValue, remove S
newseq = self._tempSeq()
@ -876,9 +906,9 @@ class CSSFunction(CSSPrimitiveValue):
next = seq[i]
newval = next.value
if isinstance(newval, CSSPrimitiveValue):
newval.setFloatValue(newval.primitiveType,
newval.setFloatValue(newval.primitiveType,
float(item.value + str(newval.getFloatValue())))
newseq.append(newval, next.type,
newseq.append(newval, next.type,
item.line, item.col)
else:
# expressions only?
@ -893,7 +923,7 @@ class CSSFunction(CSSPrimitiveValue):
self._setSeq(newseq)
self._funcType = newseq[0].value
cssText = property(lambda self: cssutils.ser.do_css_RGBColor(self),
cssText = property(lambda self: cssutils.ser.do_css_RGBColor(self),
_setCssText)
funcType = property(lambda self: self._funcType)
@ -930,37 +960,38 @@ class RGBColor(CSSPrimitiveValue):
def _setCssText(self, cssText):
self._checkReadonly()
types = self._prods # rename!
valueProd = Prod(name='value',
match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE),
valueProd = Prod(name='value',
match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE),
toSeq=lambda t, v: (CSSPrimitiveValue, CSSPrimitiveValue(v)),
toStore='parts'
)
# COLOR PRODUCTION
funccolor = Sequence(Prod(name='FUNC',
funccolor = Sequence(Prod(name='FUNC',
match=lambda t, v: self._normalize(v) in ('rgb(', 'rgba(', 'hsl(', 'hsla(') and t == types.FUNCTION,
toSeq=lambda t, v: (t, self._normalize(v)),
toStore='colorType' ),
PreDef.unary(),
toSeq=lambda t, v: (t, self._normalize(v)),
toStore='colorType'),
PreDef.unary(),
valueProd,
# 2 or 3 more values starting with Comma
Sequence(PreDef.comma(),
PreDef.unary(),
valueProd,
minmax=lambda: (2, 3)),
Sequence(PreDef.comma(),
PreDef.unary(),
valueProd,
minmax=lambda: (2, 3)),
PreDef.funcEnd()
)
colorprods = Choice(funccolor,
PreDef.hexcolor('colorType'),
Prod(name='named color',
Prod(name='named color',
match=lambda t, v: t == types.IDENT,
toStore='colorType'
)
)
# store: colorType, parts
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
u'RGBColor',
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
u'RGBColor',
colorprods,
{'parts': []})
keepS=True,
store={'parts': []})
if wellformed:
self.wellformed = True
@ -973,16 +1004,16 @@ class RGBColor(CSSPrimitiveValue):
self._setSeq(seq)
cssText = property(lambda self: cssutils.ser.do_css_RGBColor(self),
cssText = property(lambda self: cssutils.ser.do_css_RGBColor(self),
_setCssText)
colorType = property(lambda self: self._colorType)
class ExpressionValue(CSSFunction):
"""Special IE only CSSFunction which may contain *anything*.
Used for expressions and ``alpha(opacity=100)`` currently."""
name = u'Expression (IE only)'
_functionName = u'Expression (IE only)'
def _productiondefinition(self):
"""Return defintion used for parsing."""
@ -992,19 +1023,19 @@ class ExpressionValue(CSSFunction):
"Do not normalize function name!"
return t[0], t[1]
funcProds = Sequence(Prod(name='expression',
match=lambda t, v: t == types.FUNCTION,
funcProds = Sequence(Prod(name='expression',
match=lambda t, v: t == types.FUNCTION,
toSeq=toSeq
),
Sequence(Choice(Prod(name='nested function',
Sequence(Choice(Prod(name='nested function',
match=lambda t, v: t == self._prods.FUNCTION,
toSeq=lambda t, tokens: (CSSFunction.name,
CSSFunction(cssutils.helper.pushtoken(t,
toSeq=lambda t, tokens: (CSSFunction._functionName,
CSSFunction(cssutils.helper.pushtoken(t,
tokens)))
),
Prod(name='part',
Prod(name='part',
match=lambda t, v: v != u')',
toSeq=lambda t, tokens: (t[0], t[1])),
toSeq=lambda t, tokens: (t[0], t[1])),
),
minmax=lambda: (0, None)),
PreDef.funcEnd(stop=True))
@ -1018,3 +1049,80 @@ class ExpressionValue(CSSFunction):
cssText = property(_getCssText, _setCssText,
doc="A string representation of the current value.")
class CSSVariable(CSSValue):
"""The CSSVariable represents a call to CSS Variable."""
def __init__(self, cssText=None, readonly=False):
"""Init a new CSSVariable.
:param cssText:
the parsable cssText of the value, e.g. ``var(x)``
:param readonly:
defaults to False
"""
self._name = None
super(CSSVariable, self).__init__(cssText=cssText,
readonly=readonly)
def __repr__(self):
return "cssutils.css.%s(%r)" % (self.__class__.__name__, self.cssText)
def __str__(self):
return "<cssutils.css.%s object name=%r value=%r at 0x%x>" % (
self.__class__.__name__, self.name, self.value,
id(self))
def _setCssText(self, cssText):
self._checkReadonly()
types = self._prods # rename!
funcProds = Sequence(Prod(name='var',
match=lambda t, v: t == types.FUNCTION
),
PreDef.ident(toStore='ident'),
PreDef.funcEnd(stop=True))
# store: name of variable
store = {'ident': None}
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
u'CSSVariable',
funcProds,
keepS=True)
if wellformed:
self._name = store['ident'].value
self._setSeq(seq)
self.wellformed = True
cssText = property(lambda self: cssutils.ser.do_css_CSSVariable(self),
_setCssText,
doc="A string representation of the current variable.")
cssValueType = CSSValue.CSS_VARIABLE
# TODO: writable? check if var (value) available?
name = property(lambda self: self._name)
def _getValue(self):
"Find contained sheet and @variables there"
# TODO: imports!
# property:
if self.parent:
# styleDeclaration:
if self.parent.parent:
# styleRule:
if self.parent.parent.parentRule:
# stylesheet
if self.parent.parent.parentRule.parentStyleSheet:
sheet = self.parent.parent.parentRule.parentStyleSheet
for r in sheet.cssRules:
if r.VARIABLES_RULE == r.type and r.variables:
try:
return r.variables[self.name]
except KeyError:
return None
value = property(_getValue)

View File

@ -0,0 +1,292 @@
"""CSSVariablesDeclaration
http://disruptive-innovations.com/zoo/cssvariables/#mozTocId496530
"""
__all__ = ['CSSVariablesDeclaration']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstyledeclaration.py 1819 2009-08-01 20:52:43Z cthedot $'
from cssutils.prodparser import *
from cssvalue import CSSValue
import cssutils
import itertools
import xml.dom
class CSSVariablesDeclaration(cssutils.util._NewBase):
"""The CSSVariablesDeclaration interface represents a single block of
variable declarations.
"""
def __init__(self, cssText=u'', parentRule=None, readonly=False):
"""
:param cssText:
Shortcut, sets CSSVariablesDeclaration.cssText
:param parentRule:
The CSS rule that contains this declaration block or
None if this CSSVariablesDeclaration is not attached to a CSSRule.
:param readonly:
defaults to False
"""
super(CSSVariablesDeclaration, self).__init__()
self._parentRule = parentRule
self._vars = {}
if cssText:
self.cssText = cssText
self._readonly = readonly
def __repr__(self):
return "cssutils.css.%s(cssText=%r)" % (
self.__class__.__name__, self.cssText)
def __str__(self):
return "<cssutils.css.%s object length=%r at 0x%x>" % (
self.__class__.__name__, self.length, id(self))
def __contains__(self, variableName):
"""Check if a variable is in variable declaration block.
:param variableName:
a string
"""
return variableName.lower() in self.keys()
def __getitem__(self, variableName):
"""Retrieve the value of variable ``variableName`` from this
declaration.
"""
return self.getVariableValue(variableName.lower())
def __setitem__(self, variableName, value):
self.setVariable(variableName.lower(), value)
def __delitem__(self, variableName):
return self.removeVariable(variableName.lower())
def __iter__(self):
"""Iterator of names of set variables."""
for name in self.keys():
yield name
def _absorb(self, other):
"""Replace all own data with data from other object."""
self._parentRule = other._parentRule
self.seq.absorb(other.seq)
self._readonly = other._readonly
def keys(self):
"""Analoguous to standard dict returns variable names which are set in
this declaration."""
return self._vars.keys()
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_css_CSSVariablesDeclaration(self)
def _setCssText(self, cssText):
"""Setting this attribute will result in the parsing of the new value
and resetting of all the properties in the declaration block
including the removal or addition of properties.
:exceptions:
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this declaration is readonly or a property is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
Format::
variableset
: vardeclaration [ ';' S* vardeclaration ]*
;
vardeclaration
: varname ':' S* term
;
varname
: IDENT S*
;
expr
: [ VARCALL | term ] [ operator [ VARCALL | term ] ]*
;
"""
self._checkReadonly()
vardeclaration = Sequence(
PreDef.ident(),
PreDef.char(u':', u':', toSeq=False),
#PreDef.S(toSeq=False, optional=True),
Prod(name=u'term', match=lambda t, v: True,
toSeq=lambda t, tokens: (u'value',
CSSValue(itertools.chain([t],
tokens))
)
),
PreDef.char(u';', u';', toSeq=False, optional=True),
)
prods = Sequence(vardeclaration, minmax=lambda: (0, None))
# parse
wellformed, seq, store, notused = \
ProdParser().parse(cssText,
u'CSSVariableDeclaration',
prods)
if wellformed:
newseq = self._tempSeq()
# seq contains only name: value pairs plus comments etc
lastname = None
for item in seq:
if u'IDENT' == item.type:
lastname = item
self._vars[lastname.value.lower()] = None
elif u'value' == item.type:
self._vars[lastname.value.lower()] = item.value
newseq.append((lastname.value, item.value),
'var',
lastname.line, lastname.col)
else:
newseq.appendItem(item)
self._setSeq(newseq)
self.wellformed = True
cssText = property(_getCssText, _setCssText,
doc="(DOM) A parsable textual representation of the declaration\
block excluding the surrounding curly braces.")
def _setParentRule(self, parentRule):
self._parentRule = parentRule
parentRule = property(lambda self: self._parentRule, _setParentRule,
doc="(DOM) The CSS rule that contains this"
" declaration block or None if this block"
" is not attached to a CSSRule.")
def getVariableValue(self, variableName):
"""Used to retrieve the value of a variable if it has been explicitly
set within this variable declaration block.
:param variableName:
The name of the variable.
:returns:
the value of the variable if it has been explicitly set in this
variable declaration block. Returns the empty string if the
variable has not been set.
"""
try:
return self._vars[variableName.lower()].cssText
except KeyError, e:
return u''
def removeVariable(self, variableName):
"""Used to remove a variable if it has been explicitly set within this
variable declaration block.
:param variableName:
The name of the variable.
:returns:
the value of the variable if it has been explicitly set for this
variable declaration block. Returns the empty string if the
variable has not been set.
:exceptions:
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this declaration is readonly is readonly.
"""
try:
r = self._vars[variableName.lower()]
except KeyError, e:
return u''
else:
self.seq._readonly = False
if variableName in self._vars:
for i, x in enumerate(self.seq):
if x.value[0] == variableName:
del self.seq[i]
self.seq._readonly = True
del self._vars[variableName.lower()]
return r.cssText
def setVariable(self, variableName, value):
"""Used to set a variable value within this variable declaration block.
:param variableName:
The name of the CSS variable.
:param value:
The new value of the variable, may also be a CSSValue object.
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified value has a syntax error and is
unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this declaration is readonly or the property is
readonly.
"""
self._checkReadonly()
# check name
wellformed, seq, store, unused = ProdParser().parse(variableName.lower(),
u'variableName',
Sequence(PreDef.ident()
))
if not wellformed:
self._log.error(u'Invalid variableName: %r: %r'
% (variableName, value))
else:
# check value
if isinstance(value, CSSValue):
v = value
else:
v = CSSValue(cssText=value)
if not v.wellformed:
self._log.error(u'Invalid variable value: %r: %r'
% (variableName, value))
else:
# update seq
self.seq._readonly = False
if variableName in self._vars:
for i, x in enumerate(self.seq):
if x.value[0] == variableName:
x.replace(i,
[variableName, v],
x.type,
x.line,
x.col)
break
else:
self.seq.append([variableName, v], 'var')
self.seq._readonly = True
self._vars[variableName] = v
def item(self, index):
"""Used to retrieve the variables that have been explicitly set in
this variable declaration block. The order of the variables
retrieved using this method does not have to be the order in which
they were set. This method can be used to iterate over all variables
in this variable declaration block.
:param index:
of the variable name to retrieve, negative values behave like
negative indexes on Python lists, so -1 is the last element
:returns:
The name of the variable at this ordinal position. The empty
string if no variable exists at this position.
"""
try:
return self.keys()[index]
except IndexError:
return u''
length = property(lambda self: len(self._vars),
doc="The number of variables that have been explicitly set in this"
" variable declaration block. The range of valid indices is 0"
" to length-1 inclusive.")

View File

@ -0,0 +1,164 @@
"""CSSVariables implements (and only partly) experimental
`CSS Variables <http://disruptive-innovations.com/zoo/cssvariables/>`_
"""
__all__ = ['CSSVariablesRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssfontfacerule.py 1818 2009-07-30 21:39:00Z cthedot $'
from cssvariablesdeclaration import CSSVariablesDeclaration
import cssrule
import cssutils
import xml.dom
class CSSVariablesRule(cssrule.CSSRule):
"""
The CSSVariablesRule interface represents a @variables rule within a CSS
style sheet. The @variables rule is used to specify variables.
cssutils uses a :class:`~cssutils.css.CSSVariablesDeclaration` to
represent the variables.
"""
def __init__(self, mediaText=None, variables=None, parentRule=None,
parentStyleSheet=None, readonly=False):
"""
If readonly allows setting of properties in constructor only.
"""
super(CSSVariablesRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@variables'
self._media = cssutils.stylesheets.MediaList(mediaText,
readonly=readonly)
self._variables = CSSVariablesDeclaration(parentRule=self)
if variables:
self.variables = variables
self._readonly = readonly
def __repr__(self):
return "cssutils.css.%s(mediaText=%r, variables=%r)" % (
self.__class__.__name__,
self._media.mediaText, self.variables.cssText)
def __str__(self):
return "<cssutils.css.%s object mediaText=%r variables=%r valid=%r at 0x%x>" % (
self.__class__.__name__, self._media.mediaText,
self.variables.cssText, self.valid, id(self))
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSVariablesRule(self)
def _setCssText(self, cssText):
"""
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
Format::
variables
: VARIABLES_SYM S* medium [ COMMA S* medium ]* LBRACE S* variableset* '}' S*
;
variableset
: LBRACE S* vardeclaration [ ';' S* vardeclaration ]* '}' S*
;
"""
super(CSSVariablesRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.VARIABLES_SYM:
self._log.error(u'CSSVariablesRule: No CSSVariablesRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# save if parse goes wrong
oldvariables = CSSVariablesDeclaration()
oldvariables._absorb(self.variables)
ok = True
beforetokens, brace = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
if self._tokenvalue(brace) != u'{':
ok = False
self._log.error(
u'CSSVariablesRule: No start { of variable declaration found: %r' %
self._valuestr(cssText), brace)
# parse stuff before { which should be comments and S only
new = {'wellformed': True}
newseq = self._tempSeq()#[]
beforewellformed, expected = self._parse(expected=':',
seq=newseq, tokenizer=self._tokenize2(beforetokens),
productions={})
ok = ok and beforewellformed and new['wellformed']
variablestokens, braceorEOFtoken = self._tokensupto2(tokenizer,
blockendonly=True,
separateEnd=True)
val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF':
ok = False
self._log.error(
u'CSSVariablesRule: No "}" after variables declaration found: %r' %
self._valuestr(cssText))
nonetoken = self._nexttoken(tokenizer)
if nonetoken:
ok = False
self._log.error(u'CSSVariablesRule: Trailing content found.',
token=nonetoken)
if 'EOF' == typ:
# add again as variables needs it
variablestokens.append(braceorEOFtoken)
# may raise:
self.variables.cssText = variablestokens
if ok:
# contains probably comments only upto {
self._setSeq(newseq)
else:
# RESET
self.variables._absorb(oldvariables)
cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.")
def _setVariables(self, variables):
"""
:param variables:
a CSSVariablesDeclaration or string
"""
self._checkReadonly()
if isinstance(variables, basestring):
self._variables.cssText = variables
else:
self._variables = variables
self._variables.parentRule = self
variables = property(lambda self: self._variables, _setVariables,
doc="(DOM) The variables of this rule set, "
"a :class:`~cssutils.css.CSSVariablesDeclaration`.")
type = property(lambda self: self.VARIABLES_RULE,
doc="The type of this rule, as defined by a CSSRule "
"type constant.")
valid = property(lambda self: True, doc='TODO')
# constant but needed:
wellformed = property(lambda self: True)

View File

@ -1,7 +1,7 @@
"""Property is a single CSS property in a CSSStyleDeclaration."""
__all__ = ['Property']
__docformat__ = 'restructuredtext'
__version__ = '$Id: property.py 1811 2009-07-29 13:11:15Z cthedot $'
__version__ = '$Id: property.py 1868 2009-10-17 19:36:54Z cthedot $'
from cssutils.helper import Deprecated
from cssvalue import CSSValue
@ -44,7 +44,7 @@ class Property(cssutils.util.Base):
"""
def __init__(self, name=None, value=None, priority=u'',
_mediaQuery=False, parent=None, parentStyle=None):
_mediaQuery=False, parent=None):
"""
:param name:
a property name string (will be normalized)
@ -58,8 +58,6 @@ class Property(cssutils.util.Base):
:param parent:
the parent object, normally a
:class:`cssutils.css.CSSStyleDeclaration`
:param parentStyle:
DEPRECATED: Use ``parent`` instead
"""
super(Property, self).__init__()
self.seqs = [[], None, []]
@ -76,7 +74,7 @@ class Property(cssutils.util.Base):
if value:
self.cssValue = value
else:
self.seqs[1] = CSSValue()
self.seqs[1] = CSSValue(parent=self)
self._priority = u''
self._literalpriority = u''
@ -246,31 +244,28 @@ class Property(cssutils.util.Base):
type of values than the values allowed by the CSS property.
"""
if self._mediaQuery and not cssText:
self.seqs[1] = CSSValue()
self.seqs[1] = CSSValue(parent=self)
else:
if not self.seqs[1]:
self.seqs[1] = CSSValue()
#if not self.seqs[1]:
# self.seqs[1] = CSSValue(parent=self)
cssvalue = self.seqs[1]
cssvalue.cssText = cssText
if cssvalue.wellformed: #cssvalue._value and
self.seqs[1] = cssvalue
self.wellformed = self.wellformed and cssvalue.wellformed
self.seqs[1] = CSSValue(parent=self)
self.seqs[1].cssText = cssText
self.wellformed = self.wellformed and self.seqs[1].wellformed
# self.valid = self.valid and self.cssValue.valid
cssValue = property(_getCSSValue, _setCSSValue,
doc="(cssutils) CSSValue object of this property")
def _getValue(self):
if self.cssValue:
return self.cssValue.cssText # _value # [0]
return self.cssValue.cssText
else:
return u''
def _setValue(self, value):
self.cssValue.cssText = value
# self.valid = self.valid and self.cssValue.valid
self.wellformed = self.wellformed and self.cssValue.wellformed
self._setCSSValue(value)
value = property(_getValue, _setValue,
doc="The textual value of this Properties cssValue.")
@ -483,12 +478,3 @@ class Property(cssutils.util.Base):
valid = property(validate, doc="Check if value of this property is valid "
"in the properties context.")
@Deprecated('Use ``parent`` attribute instead.')
def _getParentStyle(self):
return self._parent
parentStyle = property(_getParentStyle, _setParent,
doc="DEPRECATED: Use ``parent`` instead")

View File

@ -7,9 +7,10 @@ TODO
"""
__all__ = ['Selector']
__docformat__ = 'restructuredtext'
__version__ = '$Id: selector.py 1741 2009-05-09 18:20:20Z cthedot $'
__version__ = '$Id: selector.py 1868 2009-10-17 19:36:54Z cthedot $'
from cssutils.util import _SimpleNamespaces
from cssutils.helper import Deprecated
import cssutils
import xml.dom
@ -98,13 +99,13 @@ class Selector(cssutils.util.Base2):
;
"""
def __init__(self, selectorText=None, parentList=None,
def __init__(self, selectorText=None, parent=None,
readonly=False):
"""
:Parameters:
selectorText
initial value of this selector
parentList
parent
a SelectorList
readonly
default to False
@ -113,7 +114,7 @@ class Selector(cssutils.util.Base2):
self.__namespaces = _SimpleNamespaces(log=self._log)
self._element = None
self._parent = parentList
self._parent = parent
self._specificity = (0, 0, 0, 0)
if selectorText:
@ -169,10 +170,10 @@ class Selector(cssutils.util.Base2):
element = property(lambda self: self._element,
doc=u"Effective element target of this selector.")
parentList = property(lambda self: self._parent,
parent = property(lambda self: self._parent,
doc="(DOM) The SelectorList that contains this Selector or\
None if this Selector is not attached to a SelectorList.")
def _getSelectorText(self):
"""Return serialized format."""
return cssutils.ser.do_css_Selector(self)
@ -201,7 +202,7 @@ class Selector(cssutils.util.Base2):
try:
# uses parent stylesheets namespaces if available, otherwise given ones
namespaces = self.parentList.parentRule.parentStyleSheet.namespaces
namespaces = self.parent.parentRule.parentStyleSheet.namespaces
except AttributeError:
pass
tokenizer = self._tokenize2(selectorText)
@ -787,3 +788,11 @@ class Selector(cssutils.util.Base2):
""")
wellformed = property(lambda self: bool(len(self.seq)))
@Deprecated('Use property parent instead')
def _getParentList(self):
return self.parent
parentList = property(_getParentList,
doc="DEPRECATED, see property parent instead")

View File

@ -17,7 +17,7 @@ TODO
"""
__all__ = ['SelectorList']
__docformat__ = 'restructuredtext'
__version__ = '$Id: selectorlist.py 1638 2009-01-13 20:39:33Z cthedot $'
__version__ = '$Id: selectorlist.py 1868 2009-10-17 19:36:54Z cthedot $'
from selector import Selector
import cssutils
@ -73,7 +73,7 @@ class SelectorList(cssutils.util.Base, cssutils.util.ListSeq):
self._checkReadonly()
if not isinstance(newSelector, Selector):
newSelector = Selector((newSelector, namespaces),
parentList=self)
parent=self)
if newSelector.wellformed:
newSelector._parent = self # maybe set twice but must be!
return newSelector
@ -88,6 +88,12 @@ class SelectorList(cssutils.util.Base, cssutils.util.ListSeq):
namespaces.update(selector._namespaces)
return namespaces
def _absorb(self, other):
"""Replace all own data with data from other object."""
self._parentRule = other._parentRule
self.seq[:] = other.seq[:]
self._readonly = other._readonly
def _getUsedUris(self):
"Used by CSSStyleSheet to check if @namespace rules are needed"
uris = set()
@ -191,7 +197,7 @@ class SelectorList(cssutils.util.Base, cssutils.util.ListSeq):
expected = None
selector = Selector((selectortokens, namespaces),
parentList=self)
parent=self)
if selector.wellformed:
newseq.append(selector)
else:
@ -212,8 +218,6 @@ class SelectorList(cssutils.util.Base, cssutils.util.ListSeq):
self._valuestr(selectorText))
if wellformed:
self.seq = newseq
# for selector in newseq:
# self.appendSelector(selector)
selectorText = property(_getSelectorText, _setSelectorText,
doc="""(cssutils) The textual representation of the selector for

View File

@ -12,7 +12,7 @@ open issues
"""
__all__ = ['CSSProductions', 'MACROS', 'PRODUCTIONS']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssproductions.py 1835 2009-08-02 16:47:27Z cthedot $'
__version__ = '$Id: cssproductions.py 1855 2009-10-07 17:03:19Z cthedot $'
# a complete list of css3 macros
MACROS = {
@ -41,6 +41,7 @@ MACROS = {
'nl': r'\n|\r\n|\r|\f',
'A': r'A|a|\\0{0,4}(?:41|61)(?:\r\n|[ \t\r\n\f])?',
'B': r'B|b|\\0{0,4}(?:42|62)(?:\r\n|[ \t\r\n\f])?',
'C': r'C|c|\\0{0,4}(?:43|63)(?:\r\n|[ \t\r\n\f])?',
'D': r'D|d|\\0{0,4}(?:44|64)(?:\r\n|[ \t\r\n\f])?',
'E': r'E|e|\\0{0,4}(?:45|65)(?:\r\n|[ \t\r\n\f])?',
@ -58,6 +59,7 @@ MACROS = {
'S': r'S|s|\\0{0,4}(?:53|73)(?:\r\n|[ \t\r\n\f])?|\\S|\\s',
'T': r'T|t|\\0{0,4}(?:54|74)(?:\r\n|[ \t\r\n\f])?|\\T|\\t',
'U': r'U|u|\\0{0,4}(?:55|75)(?:\r\n|[ \t\r\n\f])?|\\U|\\u',
'V': r'V|v|\\0{0,4}(?:56|76)(?:\r\n|[ \t\r\n\f])?|\\V|\\v',
'X': r'X|x|\\0{0,4}(?:58|78)(?:\r\n|[ \t\r\n\f])?|\\X|\\x',
'Z': r'Z|z|\\0{0,4}(?:5a|7a)(?:\r\n|[ \t\r\n\f])?|\\Z|\\z',
}
@ -107,6 +109,7 @@ class CSSProductions(object):
IMPORT_SYM = 'IMPORT_SYM'
NAMESPACE_SYM = 'NAMESPACE_SYM'
PAGE_SYM = 'PAGE_SYM'
VARIABLES_SYM = 'VARIABLES_SYM'
for i, t in enumerate(PRODUCTIONS):
setattr(CSSProductions, t[0].replace('-', '_'), t[0])

View File

@ -60,8 +60,8 @@ def pushtoken(token, tokens):
``tokens``"""
# TODO: may use itertools.chain?
yield token
for x in tokens:
yield x
for t in tokens:
yield t
def string(value):
"""

View File

@ -219,22 +219,31 @@ class Prod(object):
"""Single Prod in Sequence or Choice."""
def __init__(self, name, match, optional=False,
toSeq=None, toStore=None,
stop=False, nextSor=False, mayEnd=False):
stop=False, stopAndKeep=False,
nextSor=False, mayEnd=False):
"""
name
name used for error reporting
match callback
function called with parameters tokentype and tokenvalue
returning True, False or raising ParseError
toSeq callback (optional)
toSeq callback (optional) or False
calling toSeq(token, tokens) returns (type_, val) == (token[0], token[1])
to be appended to seq else simply unaltered (type_, val)
if False nothing is added
toStore (optional)
key to save util.Item to store or callback(store, util.Item)
optional = False
wether Prod is optional or not
stop = False
if True stop parsing of tokens here
stopAndKeep
if True stop parsing of tokens here but return stopping
token in unused tokens
nextSor=False
next is S or other like , or / (CSSValue)
mayEnd = False
no token must follow even defined by Sequence.
Used for operator ',/ ' currently only
@ -243,6 +252,7 @@ class Prod(object):
self.match = match
self.optional = optional
self.stop = stop
self.stopAndKeep = stopAndKeep
self.nextSor = nextSor
self.mayEnd = mayEnd
@ -256,7 +266,7 @@ class Prod(object):
store[key] = item
return toStore
if toSeq:
if toSeq or toSeq is False:
# called: seq.append(toSeq(value))
self.toSeq = toSeq
else:
@ -288,22 +298,27 @@ class Prod(object):
self.__class__.__name__, self._name, id(self))
# global tokenizer as there is only one!
tokenizer = cssutils.tokenize2.Tokenizer()
class ProdParser(object):
"""Productions parser."""
def __init__(self):
def __init__(self, clear=True):
self.types = cssutils.cssproductions.CSSProductions
self._log = cssutils.log
self._tokenizer = cssutils.tokenize2.Tokenizer()
if clear:
tokenizer.clear()
def _texttotokens(self, text):
"""Build a generator which is the only thing that is parsed!
old classes may use lists etc
"""
if isinstance(text, basestring):
# to tokenize strip space
tokens = self._tokenizer.tokenize(text.strip())
# DEFAULT, to tokenize strip space
return tokenizer.tokenize(text.strip())
elif isinstance(text, tuple):
# (token, tokens) or a single token
# OLD: (token, tokens) or a single token
if len(text) == 2:
# (token, tokens)
def gen(token, tokens):
@ -312,19 +327,19 @@ class ProdParser(object):
for t in tokens:
yield t
tokens = (t for t in gen(*text))
return (t for t in gen(*text))
else:
# single token
tokens = (t for t in [text])
return (t for t in [text])
elif isinstance(text, list):
# generator from list
tokens = (t for t in text)
# OLD: generator from list
return (t for t in text)
else:
# already tokenized, assume generator
tokens = text
return tokens
# DEFAULT, already tokenized, assume generator
return text
def _SorTokens(self, tokens, until=',/'):
"""New tokens generator which has S tokens removed,
@ -359,7 +374,7 @@ class ProdParser(object):
return (token for token in removedS(tokens))
def parse(self, text, name, productions, store=None):
def parse(self, text, name, productions, keepS=False, store=None):
"""
text (or token generator)
to parse, will be tokenized if not a generator yet
@ -374,6 +389,8 @@ class ProdParser(object):
used for logging
productions
used to parse tokens
keepS
if WS should be added to Seq or just be ignored
store UPDATED
If a Prod defines ``toStore`` the key defined there
is a key in store to be set or if store[key] is a list
@ -389,7 +406,7 @@ class ProdParser(object):
:store: filled keys defined by Prod.toStore
:unusedtokens: token generator containing tokens not used yet
"""
tokens = self._texttotokens(text)
tokens = self._texttotokens(text)
if not tokens:
self._log.error(u'No content to parse.')
# TODO: return???
@ -411,7 +428,7 @@ class ProdParser(object):
except StopIteration:
break
type_, val, line, col = token
# default productions
if type_ == self.types.COMMENT:
# always append COMMENT
@ -419,10 +436,10 @@ class ProdParser(object):
cssutils.css.CSSComment, line, col)
elif defaultS and type_ == self.types.S:
# append S (but ignore starting ones)
if started:
seq.append(val, type_, line, col)
else:
if not keepS or not started:
continue
else:
seq.append(val, type_, line, col)
# elif type_ == self.types.ATKEYWORD:
# # @rule
# r = cssutils.css.CSSUnknownRule(cssText=val)
@ -465,15 +482,23 @@ class ProdParser(object):
break
else:
# process prod
if prod.toSeq:
if prod.toSeq and not prod.stopAndKeep:
type_, val = prod.toSeq(token, tokens)
if val is not None:
seq.append(val, type_, line, col)
if prod.toStore:
prod.toStore(store, seq[-1])
if val is not None:
seq.append(val, type_, line, col)
if prod.toStore:
prod.toStore(store, seq[-1])
if prod.stop: # EOF?
# stop here and ignore following tokens
break
if prod.stopAndKeep: # e.g. ;
# stop here and ignore following tokens
# but keep this token for next run
tokenizer.push(token)
break
if prod.nextSor:
# following is S or other token (e.g. ",")?
# remove S if
@ -533,12 +558,12 @@ class PreDef(object):
types = cssutils.cssproductions.CSSProductions
@staticmethod
def char(name='char', char=u',', toSeq=None, stop=False,
nextSor=False):
def char(name='char', char=u',', toSeq=None,
stop=False, stopAndKeep=False,
optional=True, nextSor=False):
"any CHAR"
return Prod(name=name, match=lambda t, v: v == char,
toSeq=toSeq,
stop=stop,
return Prod(name=name, match=lambda t, v: v == char, toSeq=toSeq,
stop=stop, stopAndKeep=stopAndKeep, optional=optional,
nextSor=nextSor)
@staticmethod
@ -566,9 +591,10 @@ class PreDef(object):
stop=stop)
@staticmethod
def ident(nextSor=False):
def ident(toStore=None, nextSor=False):
return Prod(name=u'ident',
match=lambda t, v: t == PreDef.types.IDENT,
toStore=toStore,
nextSor=nextSor)
@staticmethod
@ -592,9 +618,11 @@ class PreDef(object):
nextSor=nextSor)
@staticmethod
def S():
def S(toSeq=None, optional=False):
return Prod(name=u'whitespace',
match=lambda t, v: t == PreDef.types.S,
toSeq=toSeq,
optional=optional,
mayEnd=True)
@staticmethod
@ -628,3 +656,11 @@ class PreDef(object):
toSeq=lambda t, tokens: (t[0], t[1].lower()),
nextSor=nextSor
)
@staticmethod
def variable(toSeq=None, nextSor=False):
return Prod(name=u'variable',
match=lambda t, v: u'var(' == cssutils.helper.normalize(v),
toSeq=toSeq,
nextSor=nextSor)

View File

@ -60,7 +60,7 @@ class Profiles(object):
'int': r'[-]?\d+',
'nmchar': r'[\w-]|{nonascii}|{escape}',
'num': r'[-]?\d+|[-]?\d*\.\d+',
'positivenum': r'\d+|[-]?\d*\.\d+',
'positivenum': r'\d+|\d*\.\d+',
'number': r'{num}',
'string': r'{string1}|{string2}',
'string1': r'"(\\\"|[^\"])*"',

View File

@ -3,7 +3,7 @@
"""cssutils serializer"""
__all__ = ['CSSSerializer', 'Preferences']
__docformat__ = 'restructuredtext'
__version__ = '$Id: serialize.py 1741 2009-05-09 18:20:20Z cthedot $'
__version__ = '$Id: serialize.py 1872 2009-10-17 21:00:40Z cthedot $'
import codecs
import cssutils
@ -384,6 +384,32 @@ class CSSSerializer(object):
else:
return u''
def do_CSSVariablesRule(self, rule):
"""
serializes CSSVariablesRule
media
TODO
variables
CSSStyleDeclaration
+ CSSComments
"""
variablesText = rule.variables.cssText
if variablesText and rule.wellformed:
out = Out(self)
out.append(self._atkeyword(rule, u'@variables'))
for item in rule.seq:
# assume comments {
out.append(item.value, item.type)
out.append(u'{')
out.append(u'%s%s}' % (variablesText, self.prefs.lineSeparator),
indent=1)
return out.value()
else:
return u''
def do_CSSFontFaceRule(self, rule):
"""
serializes CSSFontFaceRule
@ -712,11 +738,40 @@ class CSSSerializer(object):
else:
return u''
def do_css_CSSVariablesDeclaration(self, variables):
"""Variables of CSSVariableRule."""
if len(variables.seq) > 0:
out = Out(self)
lastitem = len(variables.seq) - 1
for i, item in enumerate(variables.seq):
type_, val = item.type, item.value
if u'var' == type_:
name, cssvalue = val
out.append(name)
out.append(u':')
out.append(cssvalue.cssText)
if i < lastitem or not self.prefs.omitLastSemicolon:
out.append(u';')
elif isinstance(val, cssutils.css.CSSComment):
# CSSComment
out.append(val, 'COMMENT')
out.append(self.prefs.lineSeparator)
else:
out.append(val.cssText, type_)
out.append(self.prefs.lineSeparator)
return out.value().strip()
else:
return u''
def do_css_CSSStyleDeclaration(self, style, separator=None):
"""
Style declaration of CSSStyleRule
"""
# # TODO: use Out()
# TODO: use Out()
# may be comments only
if len(style.seq) > 0:
@ -867,7 +922,19 @@ class CSSSerializer(object):
out.append(val, type_)
return out.value()
def do_css_CSSVariable(self, variable):
"""Serializes a CSSVariable"""
if not variable:
return u''
else:
out = Out(self)
for item in variable.seq:
type_, val = item.type, item.value
out.append(val, type_)
return out.value()
def do_css_RGBColor(self, cssvalue):
"""Serialize a RGBColor value"""
if not cssvalue:
@ -877,17 +944,6 @@ class CSSSerializer(object):
unary = None
for item in cssvalue.seq:
type_, val = item.type, item.value
# # prepare
# if 'CHAR' == type_ and val in u'+-':
# # save - for next round
# if u'-' == val:
# # omit +
# unary = val
# continue
# elif unary:
# val = unary + val.cssText
# unary = None
out.append(val, type_)

View File

@ -6,7 +6,7 @@ TODO:
"""
__all__ = ['MediaList']
__docformat__ = 'restructuredtext'
__version__ = '$Id: medialist.py 1605 2009-01-03 18:27:32Z cthedot $'
__version__ = '$Id: medialist.py 1871 2009-10-17 19:57:37Z cthedot $'
from cssutils.css import csscomment
from mediaquery import MediaQuery
@ -56,6 +56,13 @@ class MediaList(cssutils.util.Base, cssutils.util.ListSeq):
return "<cssutils.stylesheets.%s object mediaText=%r at 0x%x>" % (
self.__class__.__name__, self.mediaText, id(self))
def _absorb(self, other):
"""Replace all own data with data from other object."""
#self._parentRule = other._parentRule
self.seq[:] = other.seq[:]
self._readonly = other._readonly
length = property(lambda self: len(self),
doc="The number of media in the list (DOM readonly).")

View File

@ -4,10 +4,11 @@
"""
__all__ = ['Tokenizer', 'CSSProductions']
__docformat__ = 'restructuredtext'
__version__ = '$Id: tokenize2.py 1834 2009-08-02 12:20:21Z cthedot $'
__version__ = '$Id: tokenize2.py 1865 2009-10-11 15:23:11Z cthedot $'
from cssproductions import *
from helper import normalize
import itertools
import re
class Tokenizer(object):
@ -20,7 +21,8 @@ class Tokenizer(object):
u'@import': CSSProductions.IMPORT_SYM,
u'@media': CSSProductions.MEDIA_SYM,
u'@namespace': CSSProductions.NAMESPACE_SYM,
u'@page': CSSProductions.PAGE_SYM
u'@page': CSSProductions.PAGE_SYM,
u'@variables': CSSProductions.VARIABLES_SYM
}
_linesep = u'\n'
unicodesub = re.compile(r'\\[0-9a-fA-F]{1,6}(?:\r\n|[\t|\r|\n|\f|\x20])?').sub
@ -40,6 +42,8 @@ class Tokenizer(object):
productions))
self.commentmatcher = [x[1] for x in self.tokenmatches if x[0] == 'COMMENT'][0]
self.urimatcher = [x[1] for x in self.tokenmatches if x[0] == 'URI'][0]
self._pushed = []
def _expand_macros(self, macros, productions):
"""returns macro expanded productions, order of productions is kept"""
@ -60,6 +64,13 @@ class Tokenizer(object):
compiled.append((key, re.compile('^(?:%s)' % value, re.U).match))
return compiled
def push(self, *tokens):
"""Push back tokens which have been pulled but not processed."""
self._pushed = itertools.chain(tokens, self._pushed)
def clear(self):
self._pushed = []
def tokenize(self, text, fullsheet=False):
"""Generator: Tokenize text and yield tokens, each token is a tuple
of::
@ -107,6 +118,11 @@ class Tokenizer(object):
col += len(found)
while text:
for pushed in self._pushed:
# do pushed tokens before new ones
yield pushed
# speed test for most used CHARs
c = text[0]
if c in '{}:;,':

View File

@ -2,7 +2,7 @@
"""
__all__ = []
__docformat__ = 'restructuredtext'
__version__ = '$Id: util.py 1781 2009-07-19 12:30:49Z cthedot $'
__version__ = '$Id: util.py 1872 2009-10-17 21:00:40Z cthedot $'
from helper import normalize
from itertools import ifilter
@ -488,6 +488,27 @@ class Seq(object):
self._seq = []
self._readonly = readonly
def __repr__(self):
"returns a repr same as a list of tuples of (value, type)"
return u'cssutils.%s.%s([\n %s], readonly=%r)' % (self.__module__,
self.__class__.__name__,
u',\n '.join([u'%r' % item for item in self._seq]
), self._readonly)
def __str__(self):
vals = []
for v in self:
if isinstance(v.value, basestring):
vals.append(v.value)
elif type(v) == tuple:
vals.append(v.value[1])
else:
vals.append(str(v))
return "<cssutils.%s.%s object length=%r values=%r readonly=%r at 0x%x>" % (
self.__module__, self.__class__.__name__, len(self),
u', '.join(vals), self._readonly, id(self))
def __delitem__(self, i):
del self._seq[i]
@ -503,8 +524,12 @@ class Seq(object):
def __len__(self):
return len(self._seq)
def absorb(self, other):
"Replace own data with data from other seq"
self._seq = other._seq
def append(self, val, typ, line=None, col=None):
"if not readonly add new Item()"
"If not readonly add new Item()"
if self._readonly:
raise AttributeError('Seq is readonly.')
else:
@ -517,7 +542,7 @@ class Seq(object):
else:
self._seq.append(item)
def replace(self, index= - 1, val=None, typ=None, line=None, col=None):
def replace(self, index=-1, val=None, typ=None, line=None, col=None):
"""
if not readonly replace Item at index with new Item or
simply replace value or type
@ -544,26 +569,6 @@ class Seq(object):
self._seq[index] = Item(old.value + val, old.type,
old.line, old.col)
def __repr__(self):
"returns a repr same as a list of tuples of (value, type)"
return u'cssutils.%s.%s([\n %s], readonly=%r)' % (self.__module__,
self.__class__.__name__,
u',\n '.join([u'%r' % item for item in self._seq]
), self._readonly)
def __str__(self):
vals = []
for v in self:
if isinstance(v.value, basestring):
vals.append(v.value)
elif type(v) == tuple:
vals.append(v.value[1])
else:
vals.append(str(v))
return "<cssutils.%s.%s object length=%r values=%r readonly=%r at 0x%x>" % (
self.__module__, self.__class__.__name__, len(self),
u''.join(vals), self._readonly, id(self))
class Item(object):
"""