mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
3cf7e789c1
BIN
resources/images/news/guardian.png
Normal file
BIN
resources/images/news/guardian.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 548 B |
BIN
resources/images/news/lwn.png
Normal file
BIN
resources/images/news/lwn.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 387 B |
12
resources/recipes/an_druma_mor.recipe
Normal file
12
resources/recipes/an_druma_mor.recipe
Normal 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')]
|
||||
|
@ -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
|
||||
|
@ -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']
|
||||
encoding = 'utf-8'
|
||||
|
||||
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>'),
|
||||
|
||||
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
|
||||
|
||||
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')]
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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,13 +9,16 @@ 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 }'
|
||||
|
||||
@ -32,9 +35,11 @@ class IrishTimes(BasicNewsRecipe):
|
||||
|
||||
|
||||
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
|
||||
|
55
resources/recipes/lwn.recipe
Normal file
55
resources/recipes/lwn.recipe
Normal 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')
|
@ -13,7 +13,6 @@ class NewScientist(BasicNewsRecipe):
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Science news and science articles from New Scientist.'
|
||||
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'
|
||||
|
||||
|
@ -17,9 +17,37 @@ class NewYorkReviewOfBooks(BasicNewsRecipe):
|
||||
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())
|
||||
|
71
resources/viewer/bookmarks.js
Normal file
71
resources/viewer/bookmarks.js
Normal 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()}});
|
||||
}
|
||||
|
26
resources/viewer/hyphenation.js
Normal file
26
resources/viewer/hyphenation.js
Normal 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);
|
||||
}
|
||||
|
62
resources/viewer/referencing.js
Normal file
62
resources/viewer/referencing.js
Normal 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()}});
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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')]
|
||||
|
@ -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)
|
||||
|
@ -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():
|
||||
|
10
src/calibre/devices/iriver/__init__.py
Normal file
10
src/calibre/devices/iriver/__init__.py
Normal 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'
|
||||
|
||||
|
||||
|
46
src/calibre/devices/iriver/driver.py
Normal file
46
src/calibre/devices/iriver/driver.py
Normal 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
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -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']:
|
||||
|
@ -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) )
|
||||
|
@ -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')
|
||||
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 = re.compile(r'font-family\s*:\s*([^;]+)').search(block)
|
||||
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(item.path), *path)
|
||||
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:
|
||||
print 'WARNING: Family aliasing not supported:', block
|
||||
prints('WARNING: Family aliasing not fully supported.')
|
||||
prints('\tDeclared family: %s not in actual families: %s'
|
||||
% (family, families))
|
||||
else:
|
||||
print 'Loaded embedded font:', repr(family)
|
||||
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 = []
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
'''
|
@ -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):
|
||||
try:
|
||||
lp = unicode(self.location.text())
|
||||
return lp and os.path.exists(lp) and os.path.isdir(lp) and os.access(lp,
|
||||
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']
|
||||
|
@ -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']">
|
||||
|
@ -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
|
||||
|
@ -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'"?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
7157
src/calibre/translations/af.po
Normal file
7157
src/calibre/translations/af.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
@ -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
7152
src/calibre/translations/ko.po
Normal file
7152
src/calibre/translations/ko.po
Normal file
File diff suppressed because it is too large
Load Diff
7152
src/calibre/translations/lv.po
Normal file
7152
src/calibre/translations/lv.po
Normal file
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
@ -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)
|
||||
|
@ -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 *
|
||||
|
@ -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
|
||||
# SET, may raise:
|
||||
self.style.cssText = styletokens
|
||||
cssutils.log.enabled = True
|
||||
|
||||
if ok:
|
||||
# contains probably comments only (upto ``{``)
|
||||
self._setSeq(newseq)
|
||||
else:
|
||||
# RESET
|
||||
self.style._absorb(oldstyle)
|
||||
|
||||
|
||||
cssText = property(_getCssText, _setCssText,
|
||||
|
@ -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']
|
||||
|
@ -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,15 +211,17 @@ 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
|
||||
|
@ -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)
|
||||
|
||||
selok, newselectorseq = self.__parseSelectorText(selectortokens)
|
||||
ok = ok and selok
|
||||
|
||||
wellformed, newselectorseq = self.__parseSelectorText(selectortokens)
|
||||
|
||||
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.")
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
@ -105,27 +105,29 @@ class CSSStyleRule(cssrule.CSSRule):
|
||||
self._valuestr(cssText),
|
||||
error=xml.dom.InvalidModificationErr)
|
||||
else:
|
||||
wellformed = True
|
||||
# save if parse goes wrong
|
||||
oldstyle = CSSStyleDeclaration()
|
||||
oldstyle._absorb(self.style)
|
||||
oldselector = SelectorList()
|
||||
oldselector._absorb(self.selectorList)
|
||||
|
||||
testselectorlist, teststyle = None, None
|
||||
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)
|
||||
|
||||
if wellformed and testselectorlist and teststyle:
|
||||
# known as correct from before
|
||||
cssutils.log.enabled = False
|
||||
# SET
|
||||
try:
|
||||
self.style.cssText = styletokens
|
||||
self.selectorList.selectorText=(selectortokens, namespaces)
|
||||
cssutils.log.enabled = True
|
||||
except:
|
||||
# reset in case of error
|
||||
self.selectorList._absorb(oldselector)
|
||||
raise
|
||||
|
||||
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.")
|
||||
|
@ -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:
|
||||
@ -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:
|
||||
|
@ -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' ,
|
||||
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):
|
||||
@ -124,29 +129,51 @@ class CSSValue(cssutils.util._NewBase):
|
||||
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.') ),
|
||||
v.startswith(u'progid:DXImageTransform.Microsoft.')
|
||||
),
|
||||
nextSor=nextSor,
|
||||
toSeq=lambda t, tokens: (ExpressionValue.name,
|
||||
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)))))
|
||||
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,
|
||||
# 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,
|
||||
wellformed, seq, store, notused = ProdParser().parse(cssText,
|
||||
u'CSSValue',
|
||||
valueprods)
|
||||
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
|
||||
@ -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
|
||||
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
|
||||
@ -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))
|
||||
@ -297,6 +334,7 @@ class CSSValue(cssutils.util._NewBase):
|
||||
|
||||
saveifcommalist(commalist, newseq)
|
||||
self._setSeq(newseq)
|
||||
|
||||
else:
|
||||
# should not happen...
|
||||
self.__class__ = CSSValue
|
||||
@ -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,23 +850,14 @@ 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!
|
||||
@ -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()
|
||||
@ -960,7 +990,8 @@ class RGBColor(CSSPrimitiveValue):
|
||||
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
|
||||
u'RGBColor',
|
||||
colorprods,
|
||||
{'parts': []})
|
||||
keepS=True,
|
||||
store={'parts': []})
|
||||
|
||||
if wellformed:
|
||||
self.wellformed = True
|
||||
@ -982,7 +1013,7 @@ class RGBColor(CSSPrimitiveValue):
|
||||
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."""
|
||||
@ -998,7 +1029,7 @@ class ExpressionValue(CSSFunction):
|
||||
),
|
||||
Sequence(Choice(Prod(name='nested function',
|
||||
match=lambda t, v: t == self._prods.FUNCTION,
|
||||
toSeq=lambda t, tokens: (CSSFunction.name,
|
||||
toSeq=lambda t, tokens: (CSSFunction._functionName,
|
||||
CSSFunction(cssutils.helper.pushtoken(t,
|
||||
tokens)))
|
||||
),
|
||||
@ -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)
|
||||
|
292
src/cssutils/css/cssvariablesdeclaration.py
Normal file
292
src/cssutils/css/cssvariablesdeclaration.py
Normal 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.")
|
164
src/cssutils/css/cssvariablesrule.py
Normal file
164
src/cssutils/css/cssvariablesrule.py
Normal 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)
|
@ -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")
|
||||
|
||||
|
@ -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,7 +170,7 @@ 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.")
|
||||
|
||||
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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])
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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])
|
||||
elif isinstance(text, list):
|
||||
# generator from list
|
||||
tokens = (t for t in text)
|
||||
else:
|
||||
# already tokenized, assume generator
|
||||
tokens = text
|
||||
return (t for t in [text])
|
||||
|
||||
return tokens
|
||||
elif isinstance(text, list):
|
||||
# OLD: generator from list
|
||||
return (t for t in text)
|
||||
|
||||
else:
|
||||
# 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
|
||||
@ -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 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)
|
||||
|
||||
|
@ -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'"(\\\"|[^\"])*"',
|
||||
|
@ -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:
|
||||
@ -868,6 +923,18 @@ class CSSSerializer(object):
|
||||
|
||||
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:
|
||||
@ -878,17 +945,6 @@ class CSSSerializer(object):
|
||||
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_)
|
||||
|
||||
return out.value()
|
||||
|
@ -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).")
|
||||
|
||||
|
@ -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
|
||||
@ -41,6 +43,8 @@ class Tokenizer(object):
|
||||
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"""
|
||||
def macro_value(m):
|
||||
@ -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 '{}:;,':
|
||||
|
@ -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:
|
||||
@ -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):
|
||||
"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user