Browse Source

Merge branch 'refs/heads/develop'

pull/1683/merge
Ruud 12 years ago
parent
commit
7a5588d5de
  1. 6
      couchpotato/core/_base/_core/main.py
  2. 13
      couchpotato/core/_base/clientscript/main.py
  3. 20
      couchpotato/core/_base/updater/main.py
  4. 4
      couchpotato/core/_base/updater/static/updater.js
  5. 6
      couchpotato/core/downloaders/nzbget/__init__.py
  6. 38
      couchpotato/core/downloaders/nzbget/main.py
  7. 40
      couchpotato/core/event.py
  8. 14
      couchpotato/core/helpers/variable.py
  9. 1
      couchpotato/core/notifications/base.py
  10. 28
      couchpotato/core/notifications/core/main.py
  11. 67
      couchpotato/core/notifications/core/static/notification.js
  12. 2
      couchpotato/core/plugins/file/main.py
  13. 19
      couchpotato/core/plugins/log/static/log.css
  14. 2
      couchpotato/core/plugins/manage/main.py
  15. 15
      couchpotato/core/plugins/movie/main.py
  16. 170
      couchpotato/core/plugins/movie/static/list.js
  17. 349
      couchpotato/core/plugins/movie/static/movie.css
  18. 14
      couchpotato/core/plugins/movie/static/movie.js
  19. 229
      couchpotato/core/plugins/movie/static/search.css
  20. 79
      couchpotato/core/plugins/movie/static/search.js
  21. 16
      couchpotato/core/plugins/profile/static/profile.css
  22. 28
      couchpotato/core/plugins/release/main.py
  23. 4
      couchpotato/core/plugins/renamer/__init__.py
  24. 113
      couchpotato/core/plugins/renamer/main.py
  25. 3
      couchpotato/core/plugins/searcher/__init__.py
  26. 19
      couchpotato/core/plugins/searcher/main.py
  27. 45
      couchpotato/core/plugins/status/main.py
  28. 2
      couchpotato/core/plugins/trailer/__init__.py
  29. 9
      couchpotato/core/plugins/trailer/main.py
  30. 24
      couchpotato/core/plugins/userscript/static/userscript.css
  31. 19
      couchpotato/core/plugins/userscript/static/userscript.js
  32. 5
      couchpotato/core/plugins/userscript/template.js
  33. 61
      couchpotato/core/plugins/wizard/static/wizard.css
  34. 39
      couchpotato/core/plugins/wizard/static/wizard.js
  35. 3
      couchpotato/core/providers/movie/_modifier/main.py
  36. 23
      couchpotato/core/providers/movie/couchpotatoapi/main.py
  37. 2
      couchpotato/core/providers/nzb/newznab/__init__.py
  38. 3
      couchpotato/core/providers/torrent/torrentleech/main.py
  39. 6
      couchpotato/core/providers/userscript/criticker/__init__.py
  40. 6
      couchpotato/core/providers/userscript/criticker/main.py
  41. 3
      couchpotato/runner.py
  42. BIN
      couchpotato/static/fonts/Elusive-Icons.eot
  43. 298
      couchpotato/static/fonts/Elusive-Icons.svg
  44. BIN
      couchpotato/static/fonts/Elusive-Icons.ttf
  45. BIN
      couchpotato/static/fonts/Elusive-Icons.woff
  46. BIN
      couchpotato/static/fonts/OpenSans-Bold-webfont.eot
  47. 146
      couchpotato/static/fonts/OpenSans-Bold-webfont.svg
  48. BIN
      couchpotato/static/fonts/OpenSans-Bold-webfont.ttf
  49. BIN
      couchpotato/static/fonts/OpenSans-Bold-webfont.woff
  50. BIN
      couchpotato/static/fonts/OpenSans-BoldItalic-webfont.eot
  51. 146
      couchpotato/static/fonts/OpenSans-BoldItalic-webfont.svg
  52. BIN
      couchpotato/static/fonts/OpenSans-BoldItalic-webfont.ttf
  53. BIN
      couchpotato/static/fonts/OpenSans-BoldItalic-webfont.woff
  54. BIN
      couchpotato/static/fonts/OpenSans-Italic-webfont.eot
  55. 146
      couchpotato/static/fonts/OpenSans-Italic-webfont.svg
  56. BIN
      couchpotato/static/fonts/OpenSans-Italic-webfont.ttf
  57. BIN
      couchpotato/static/fonts/OpenSans-Italic-webfont.woff
  58. BIN
      couchpotato/static/fonts/OpenSans-Regular-webfont.eot
  59. 146
      couchpotato/static/fonts/OpenSans-Regular-webfont.svg
  60. BIN
      couchpotato/static/fonts/OpenSans-Regular-webfont.ttf
  61. BIN
      couchpotato/static/fonts/OpenSans-Regular-webfont.woff
  62. BIN
      couchpotato/static/images/sprite.png
  63. 2
      couchpotato/static/scripts/block/menu.js
  64. 45
      couchpotato/static/scripts/block/navigation.js
  65. 18
      couchpotato/static/scripts/couchpotato.js
  66. 487
      couchpotato/static/scripts/library/prefix_free.js
  67. 28
      couchpotato/static/scripts/library/question.js
  68. 34
      couchpotato/static/scripts/page/home.js
  69. 2
      couchpotato/static/scripts/page/wanted.js
  70. 618
      couchpotato/static/style/main.css
  71. 48
      couchpotato/static/style/settings.css
  72. 14
      couchpotato/templates/index.html
  73. 20
      libs/cssprefixer/__init__.py
  74. 117
      libs/cssprefixer/engine.py
  75. 271
      libs/cssprefixer/rules.py
  76. 385
      libs/cssutils/__init__.py
  77. 584
      libs/cssutils/_codec2.py
  78. 608
      libs/cssutils/_codec3.py
  79. 44
      libs/cssutils/_fetch.py
  80. 68
      libs/cssutils/_fetchgae.py
  81. 16
      libs/cssutils/codec.py
  82. 80
      libs/cssutils/css/__init__.py
  83. 184
      libs/cssutils/css/colors.py
  84. 159
      libs/cssutils/css/csscharsetrule.py
  85. 87
      libs/cssutils/css/csscomment.py
  86. 184
      libs/cssutils/css/cssfontfacerule.py
  87. 396
      libs/cssutils/css/cssimportrule.py
  88. 302
      libs/cssutils/css/cssmediarule.py
  89. 295
      libs/cssutils/css/cssnamespacerule.py
  90. 436
      libs/cssutils/css/csspagerule.py
  91. 122
      libs/cssutils/css/cssproperties.py
  92. 304
      libs/cssutils/css/cssrule.py
  93. 53
      libs/cssutils/css/cssrulelist.py
  94. 697
      libs/cssutils/css/cssstyledeclaration.py
  95. 234
      libs/cssutils/css/cssstylerule.py
  96. 804
      libs/cssutils/css/cssstylesheet.py
  97. 209
      libs/cssutils/css/cssunknownrule.py
  98. 1251
      libs/cssutils/css/cssvalue.py
  99. 330
      libs/cssutils/css/cssvariablesdeclaration.py
  100. 198
      libs/cssutils/css/cssvariablesrule.py

6
couchpotato/core/_base/_core/main.py

@ -79,7 +79,7 @@ class Core(Plugin):
def shutdown():
self.initShutdown()
IOLoop.instance().add_callback(shutdown)
IOLoop.current().add_callback(shutdown)
return 'shutdown'
@ -89,7 +89,7 @@ class Core(Plugin):
def restart():
self.initShutdown(restart = True)
IOLoop.instance().add_callback(restart)
IOLoop.current().add_callback(restart)
return 'restarting'
@ -128,7 +128,7 @@ class Core(Plugin):
log.debug('Save to shutdown/restart')
try:
IOLoop.instance().stop()
IOLoop.current().stop()
except RuntimeError:
pass
except:

13
couchpotato/core/_base/clientscript/main.py

@ -1,10 +1,11 @@
from couchpotato.core.event import addEvent
from couchpotato.core.helpers.encoding import ss
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env
from minify.cssmin import cssmin
from minify.jsmin import jsmin
import cssprefixer
import os
import traceback
@ -23,7 +24,6 @@ class ClientScript(Plugin):
'script': [
'scripts/library/mootools.js',
'scripts/library/mootools_more.js',
'scripts/library/prefix_free.js',
'scripts/library/uniform.js',
'scripts/library/form_replacement/form_check.js',
'scripts/library/form_replacement/form_radio.js',
@ -69,7 +69,8 @@ class ClientScript(Plugin):
addEvent('clientscript.get_styles', self.getStyles)
addEvent('clientscript.get_scripts', self.getScripts)
addEvent('app.load', self.minify)
if not Env.get('dev'):
addEvent('app.load', self.minify)
self.addCore()
@ -108,8 +109,10 @@ class ClientScript(Plugin):
if file_type == 'script':
data = jsmin(f)
else:
data = cssmin(f)
data = cssprefixer.process(f, debug = False, minify = True)
data = data.replace('../images/', '../static/images/')
data = data.replace('../fonts/', '../static/fonts/')
data = data.replace('../../static/', '../static/') # Replace inside plugins
raw.append({'file': file_path, 'date': int(os.path.getmtime(file_path)), 'data': data})
@ -119,7 +122,7 @@ class ClientScript(Plugin):
data += self.comment.get(file_type) % (r.get('file'), r.get('date'))
data += r.get('data') + '\n\n'
self.createFile(out, data.strip())
self.createFile(out, ss(data.strip()))
if not self.minified.get(file_type):
self.minified[file_type] = {}

20
couchpotato/core/_base/updater/main.py

@ -15,6 +15,7 @@ import tarfile
import time
import traceback
import version
import zipfile
log = CPLog(__name__)
@ -263,11 +264,11 @@ class SourceUpdater(BaseUpdater):
def doUpdate(self):
try:
url = 'https://github.com/%s/%s/tarball/%s' % (self.repo_user, self.repo_name, self.branch)
destination = os.path.join(Env.get('cache_dir'), self.update_version.get('hash') + '.tar.gz')
extracted_path = os.path.join(Env.get('cache_dir'), 'temp_updater')
download_data = fireEvent('cp.source_url', repo = self.repo_user, repo_name = self.repo_name, branch = self.branch, single = True)
destination = os.path.join(Env.get('cache_dir'), self.update_version.get('hash')) + '.' + download_data.get('type')
destination = fireEvent('file.download', url = url, dest = destination, single = True)
extracted_path = os.path.join(Env.get('cache_dir'), 'temp_updater')
destination = fireEvent('file.download', url = download_data.get('url'), dest = destination, single = True)
# Cleanup leftover from last time
if os.path.isdir(extracted_path):
@ -275,9 +276,14 @@ class SourceUpdater(BaseUpdater):
self.makeDir(extracted_path)
# Extract
tar = tarfile.open(destination)
tar.extractall(path = extracted_path)
tar.close()
if download_data.get('type') == 'zip':
zip = zipfile.ZipFile(destination)
zip.extractall(extracted_path)
else:
tar = tarfile.open(destination)
tar.extractall(path = extracted_path)
tar.close()
os.remove(destination)
if self.replaceWith(os.path.join(extracted_path, os.listdir(extracted_path)[0])):

4
couchpotato/core/_base/updater/static/updater.js

@ -5,7 +5,7 @@ var UpdaterBase = new Class({
initialize: function(){
var self = this;
App.addEvent('load', self.info.bind(self, 1000))
App.addEvent('load', self.info.bind(self, 2000))
App.addEvent('unload', function(){
if(self.timer)
clearTimeout(self.timer);
@ -84,7 +84,7 @@ var UpdaterBase = new Class({
'click': self.doUpdate.bind(self)
}
})
).inject($(document.body).getElement('.header'))
).inject(document.body)
},
doUpdate: function(){

6
couchpotato/core/downloaders/nzbget/__init__.py

@ -25,6 +25,12 @@ config = [{
'description': 'Hostname with port. Usually <strong>localhost:6789</strong>',
},
{
'name': 'username',
'default': 'nzbget',
'advanced': True,
'description': 'Set a different username to connect. Default: nzbget',
},
{
'name': 'password',
'type': 'password',
'description': 'Default NZBGet password is <i>tegbzn6789</i>',

38
couchpotato/core/downloaders/nzbget/main.py

@ -1,7 +1,7 @@
from base64 import standard_b64encode
from couchpotato.core.downloaders.base import Downloader, StatusList
from couchpotato.core.helpers.encoding import ss
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.helpers.variable import tryInt, md5
from couchpotato.core.logger import CPLog
from datetime import timedelta
import re
@ -16,7 +16,7 @@ class NZBGet(Downloader):
type = ['nzb']
url = 'http://nzbget:%(password)s@%(host)s/xmlrpc'
url = 'http://%(username)s:%(password)s@%(host)s/xmlrpc'
def download(self, data = {}, movie = {}, filedata = None):
@ -26,7 +26,7 @@ class NZBGet(Downloader):
log.info('Sending "%s" to NZBGet.', data.get('name'))
url = self.url % {'host': self.conf('host'), 'password': self.conf('password')}
url = self.url % {'host': self.conf('host'), 'username': self.conf('username'), 'password': self.conf('password')}
nzb_name = ss('%s.nzb' % self.createNzbName(data, movie))
rpc = xmlrpclib.ServerProxy(url)
@ -52,7 +52,14 @@ class NZBGet(Downloader):
if xml_response:
log.info('NZB sent successfully to NZBGet')
return True
nzb_id = md5(data['url']) # about as unique as they come ;)
couchpotato_id = "couchpotato=" + nzb_id
groups = rpc.listgroups()
file_id = [item['LastID'] for item in groups if item['NZBFilename'] == nzb_name]
confirmed = rpc.editqueue("GroupSetParameter", 0, couchpotato_id, file_id)
if confirmed:
log.debug('couchpotato parameter set in nzbget download')
return self.downloadReturnId(nzb_id)
else:
log.error('NZBGet could not add %s to the queue.', nzb_name)
return False
@ -61,7 +68,7 @@ class NZBGet(Downloader):
log.debug('Checking NZBGet download status.')
url = self.url % {'host': self.conf('host'), 'password': self.conf('password')}
url = self.url % {'host': self.conf('host'), 'username': self.conf('username'), 'password': self.conf('password')}
rpc = xmlrpclib.ServerProxy(url)
try:
@ -93,15 +100,19 @@ class NZBGet(Downloader):
for item in groups:
log.debug('Found %s in NZBGet download queue', item['NZBFilename'])
try:
nzb_id = [param['Value'] for param in item['Parameters'] if param['Name'] == 'couchpotato'][0]
except:
nzb_id = item['NZBID']
statuses.append({
'id': item['NZBID'],
'id': nzb_id,
'name': item['NZBFilename'],
'original_status': 'DOWNLOADING' if item['ActiveDownloads'] > 0 else 'QUEUED',
# Seems to have no native API function for time left. This will return the time left after NZBGet started downloading this item
'timeleft': str(timedelta(seconds = item['RemainingSizeMB'] / status['DownloadRate'] * 2 ^ 20)) if item['ActiveDownloads'] > 0 and not (status['DownloadPaused'] or status['Download2Paused']) else -1,
})
for item in queue:
for item in queue: # 'Parameters' is not passed in rpc.postqueue
log.debug('Found %s in NZBGet postprocessing queue', item['NZBFilename'])
statuses.append({
'id': item['NZBID'],
@ -112,8 +123,12 @@ class NZBGet(Downloader):
for item in history:
log.debug('Found %s in NZBGet history. ParStatus: %s, ScriptStatus: %s, Log: %s', (item['NZBFilename'] , item['ParStatus'], item['ScriptStatus'] , item['Log']))
try:
nzb_id = [param['Value'] for param in item['Parameters'] if param['Name'] == 'couchpotato'][0]
except:
nzb_id = item['NZBID']
statuses.append({
'id': item['NZBID'],
'id': nzb_id,
'name': item['NZBFilename'],
'status': 'completed' if item['ParStatus'] == 'SUCCESS' and item['ScriptStatus'] == 'SUCCESS' else 'failed',
'original_status': item['ParStatus'] + ', ' + item['ScriptStatus'],
@ -147,8 +162,11 @@ class NZBGet(Downloader):
try:
history = rpc.history()
if rpc.editqueue('HistoryDelete', 0, "", [tryInt(item['id'])]):
path = [hist['DestDir'] for hist in history if hist['NZBID'] == item['id']][0]
for hist in history:
if hist['Parameters'] and hist['Parameters']['couchpotato'] and hist['Parameters']['couchpotato'] == item['id']:
nzb_id = hist['ID']
path = hist['DestDir']
if rpc.editqueue('HistoryDelete', 0, "", [tryInt(nzb_id)]):
shutil.rmtree(path, True)
except:
log.error('Failed deleting: %s', traceback.format_exc(0))

40
couchpotato/core/event.py

@ -16,10 +16,8 @@ def runHandler(name, handler, *args, **kwargs):
def addEvent(name, handler, priority = 100):
if events.get(name):
e = events[name]
else:
e = events[name] = Event(name = name, threads = 10, exc_info = True, traceback = True, lock = threading.RLock())
if not events.get(name):
events[name] = []
def createHandle(*args, **kwargs):
@ -35,7 +33,10 @@ def addEvent(name, handler, priority = 100):
return h
e.handle(createHandle, priority = priority)
events[name].append({
'handler': createHandle,
'priority': priority,
})
def removeEvent(name, handler):
e = events[name]
@ -43,6 +44,12 @@ def removeEvent(name, handler):
def fireEvent(name, *args, **kwargs):
if not events.get(name): return
e = Event(name = name, threads = 10, asynch = kwargs.get('async', False), exc_info = True, traceback = True, lock = threading.RLock())
for event in events[name]:
e.handle(event['handler'], priority = event['priority'])
#log.debug('Firing event %s', name)
try:
@ -52,6 +59,7 @@ def fireEvent(name, *args, **kwargs):
'single': False, # Return single handler
'merge': False, # Merge items
'in_order': False, # Fire them in specific order, waits for the other to finish
'async': False
}
# Do options
@ -62,13 +70,6 @@ def fireEvent(name, *args, **kwargs):
options[x] = val
except: pass
e = events[name]
# Lock this event
e.lock.acquire()
e.asynchronous = False
# Make sure only 1 event is fired at a time when order is wanted
kwargs['event_order_lock'] = threading.RLock() if options['in_order'] or options['single'] else None
kwargs['event_return_on_result'] = options['single']
@ -76,9 +77,6 @@ def fireEvent(name, *args, **kwargs):
# Fire
result = e(*args, **kwargs)
# Release lock for this event
e.lock.release()
if options['single'] and not options['merge']:
results = None
@ -104,10 +102,11 @@ def fireEvent(name, *args, **kwargs):
# Merge
if options['merge'] and len(results) > 0:
results.reverse() # Priority 1 is higher then 100
# Dict
if isinstance(results[0], dict):
results.reverse()
merged = {}
for result in results:
merged = mergeDicts(merged, result, prepend_list = True)
@ -140,13 +139,8 @@ def fireEvent(name, *args, **kwargs):
log.error('%s: %s', (name, traceback.format_exc()))
def fireEventAsync(*args, **kwargs):
try:
my_thread = threading.Thread(target = fireEvent, args = args, kwargs = kwargs)
my_thread.setDaemon(True)
my_thread.start()
return True
except Exception, e:
log.error('%s: %s', (args[0], e))
kwargs['async'] = True
fireEvent(*args, **kwargs)
def errorHandler(error):
etype, value, tb = error

14
couchpotato/core/helpers/variable.py

@ -10,6 +10,20 @@ import sys
log = CPLog(__name__)
def link(src, dst):
if os.name == 'nt':
import ctypes
if ctypes.windll.kernel32.CreateHardLinkW(unicode(dst), unicode(src), 0) == 0: raise ctypes.WinError()
else:
os.link(src, dst)
def symlink(src, dst):
if os.name == 'nt':
import ctypes
if ctypes.windll.kernel32.CreateSymbolicLinkW(unicode(dst), unicode(src), 1 if os.path.isdir(src) else 0) in [0, 1280]: raise ctypes.WinError()
else:
os.symlink(src, dst)
def getUserDir():
try:
import pwd

1
couchpotato/core/notifications/base.py

@ -18,6 +18,7 @@ class Notification(Provider):
listen_to = [
'renamer.after', 'movie.snatched',
'updater.available', 'updater.updated',
'core.message',
]
dont_listen_to = []

28
couchpotato/core/notifications/core/main.py

@ -1,12 +1,13 @@
from couchpotato import get_session
from couchpotato.api import addApiView, addNonBlockApiView
from couchpotato.core.event import addEvent
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.request import jsonified, getParam
from couchpotato.core.helpers.variable import tryInt, splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
from couchpotato.core.settings.model import Notification as Notif
from couchpotato.environment import Env
from sqlalchemy.sql.expression import or_
import threading
import time
@ -21,11 +22,6 @@ class CoreNotifier(Notification):
messages = []
listeners = []
listen_to = [
'renamer.after', 'movie.snatched',
'updater.available', 'updater.updated',
]
def __init__(self):
super(CoreNotifier, self).__init__()
@ -54,7 +50,10 @@ class CoreNotifier(Notification):
addNonBlockApiView('notification.listener', (self.addListener, self.removeListener))
addApiView('notification.listener', self.listener)
fireEvent('schedule.interval', 'core.check_messages', self.checkMessages, hours = 12, single = True)
addEvent('app.load', self.clean)
addEvent('app.load', self.checkMessages)
def clean(self):
@ -112,6 +111,23 @@ class CoreNotifier(Notification):
'notifications': notifications
})
def checkMessages(self):
prop_name = 'messages.last_check'
last_check = tryInt(Env.prop(prop_name, default = 0))
messages = fireEvent('cp.messages', last_check = last_check, single = True)
last_time = 0
for message in messages:
if message.get('time') > last_check:
fireEvent('core.message', message = message.get('message'), data = message)
if last_time < message.get('time'):
last_time = message.get('time')
Env.prop(prop_name, value = last_time)
def notify(self, message = '', data = {}, listener = None):
db = get_session()

67
couchpotato/core/notifications/core/static/notification.js

@ -21,20 +21,17 @@ var NotificationBase = new Class({
App.addEvent('load', function(){
App.block.notification = new Block.Menu(self, {
'button_class': 'icon2.eye-open',
'class': 'notification_menu',
'onOpen': self.markAsRead.bind(self)
})
$(App.block.notification).inject(App.getBlock('search'), 'after');
self.badge = new Element('div.badge').inject(App.block.notification, 'top').hide();
/* App.getBlock('notification').addLink(new Element('a.more', {
'href': App.createUrl('notifications'),
'text': 'Show older notifications'
})); */
});
window.addEvent('load', function(){
self.startInterval.delay(Browser.safari ? 100 : 0, self)
self.startInterval.delay(2000, self)
});
},
@ -47,14 +44,19 @@ var NotificationBase = new Class({
result.el = App.getBlock('notification').addLink(
new Element('span.'+(result.read ? 'read' : '' )).adopt(
new Element('span.message', {'text': result.message}),
new Element('span.message', {'html': result.message}),
new Element('span.added', {'text': added.timeDiffInWords(), 'title': added})
)
, 'top');
self.notifications.include(result);
if(!result.read)
if(result.data.important !== undefined && !result.read){
var sticky = true
App.fireEvent('message', [result.message, sticky, result])
}
else if(!result.read){
self.setBadge(self.notifications.filter(function(n){ return !n.read}).length)
}
},
@ -64,20 +66,26 @@ var NotificationBase = new Class({
self.badge[value ? 'show' : 'hide']()
},
markAsRead: function(){
var self = this;
markAsRead: function(force_ids){
var self = this,
ids = force_ids;
var rn = self.notifications.filter(function(n){
return !n.read
})
if(!force_ids) {
var rn = self.notifications.filter(function(n){
return !n.read && n.data.important === undefined
})
var ids = []
rn.each(function(n){
ids.include(n.id)
})
var ids = []
rn.each(function(n){
ids.include(n.id)
})
}
if(ids.length > 0)
Api.request('notification.markread', {
'data': {
'ids': ids.join(',')
},
'onSuccess': function(){
self.setBadge('')
}
@ -143,26 +151,41 @@ var NotificationBase = new Class({
self.startPoll()
},
showMessage: function(message){
showMessage: function(message, sticky, data){
var self = this;
if(!self.message_container)
self.message_container = new Element('div.messages').inject(document.body);
var new_message = new Element('div.message', {
'text': message
}).inject(self.message_container);
var new_message = new Element('div', {
'class': 'message' + (sticky ? ' sticky' : ''),
'html': message
}).inject(self.message_container, 'top');
setTimeout(function(){
new_message.addClass('show')
}, 10);
setTimeout(function(){
var hide_message = function(){
new_message.addClass('hide')
setTimeout(function(){
new_message.destroy();
}, 1000);
}, 4000);
}
if(sticky)
new_message.grab(
new Element('a.close.icon2', {
'events': {
'click': function(){
self.markAsRead([data.id]);
hide_message();
}
}
})
);
else
setTimeout(hide_message, 4000);
},

2
couchpotato/core/plugins/file/main.py

@ -73,7 +73,7 @@ class FileManager(Plugin):
db = get_session()
for root, dirs, walk_files in os.walk(Env.get('cache_dir')):
for filename in walk_files:
if root == python_cache or 'minified' in filename: continue
if root == python_cache or 'minified' in filename or 'version' in filename: continue
file_path = os.path.join(root, filename)
f = db.query(File).filter(File.path == toUnicode(file_path)).first()
if not f:

19
couchpotato/core/plugins/log/static/log.css

@ -1,17 +1,18 @@
.page.log .nav {
display: block;
text-align: center;
padding: 20px 0;
padding: 0 0 30px;
margin: 0;
font-size: 20px;
position: fixed;
width: 960px;
width: 100%;
bottom: 0;
left: 0;
background: #4E5969;
}
.page.log .nav li {
display: inline;
display: inline-block;
padding: 5px 10px;
margin: 0;
cursor: pointer;
@ -24,7 +25,17 @@
.page.log .nav li.active {
font-weight: bold;
cursor: default;
font-size: 30px;
background: rgba(255,255,255,.1);
}
@media all and (max-width: 480px) {
.page.log .nav {
font-size: 14px;
}
.page.log .nav li {
padding: 5px;
}
}
.page.log .loading {

2
couchpotato/core/plugins/manage/main.py

@ -185,6 +185,8 @@ class Manage(Plugin):
# Add it to release and update the info
fireEvent('release.add', group = group)
fireEventAsync('library.update', identifier = identifier, on_complete = self.createAfterUpdate(folder, identifier))
else:
self.in_progress[folder]['to_go'] = self.in_progress[folder]['to_go'] - 1
return addToLibrary

15
couchpotato/core/plugins/movie/main.py

@ -108,9 +108,8 @@ class MoviePlugin(Plugin):
now = time.time()
week = 262080
done_status = fireEvent('status.get', 'done', single = True)
available_status = fireEvent('status.get', 'available', single = True)
snatched_status = fireEvent('status.get', 'snatched', single = True)
done_status, available_status, snatched_status = \
fireEvent('status.get', ['done', 'available', 'snatched'], single = True)
db = get_session()
@ -367,11 +366,8 @@ class MoviePlugin(Plugin):
library = fireEvent('library.add', single = True, attrs = params, update_after = update_library)
# Status
status_active = fireEvent('status.add', 'active', single = True)
snatched_status = fireEvent('status.add', 'snatched', single = True)
ignored_status = fireEvent('status.add', 'ignored', single = True)
done_status = fireEvent('status.add', 'done', single = True)
downloaded_status = fireEvent('status.add', 'downloaded', single = True)
status_active, snatched_status, ignored_status, done_status, downloaded_status = \
fireEvent('status.get', ['active', 'snatched', 'ignored', 'done', 'downloaded'], single = True)
default_profile = fireEvent('profile.default', single = True)
@ -549,8 +545,7 @@ class MoviePlugin(Plugin):
def restatus(self, movie_id):
active_status = fireEvent('status.get', 'active', single = True)
done_status = fireEvent('status.get', 'done', single = True)
active_status, done_status = fireEvent('status.get', ['active', 'done'], single = True)
db = get_session()

170
couchpotato/core/plugins/movie/static/list.js

@ -1,6 +1,6 @@
var MovieList = new Class({
Implements: [Options],
Implements: [Events, Options],
options: {
navigation: true,
@ -8,7 +8,8 @@ var MovieList = new Class({
load_more: true,
loader: true,
menu: [],
add_new: false
add_new: false,
force_view: false
},
movies: [],
@ -43,8 +44,11 @@ var MovieList = new Class({
}) : null
);
self.changeView(self.getSavedView() || self.options.view || 'details');
if($(window).getSize().x <= 480 && !self.options.force_view)
self.changeView('list');
else
self.changeView(self.getSavedView() || self.options.view || 'details');
self.getMovies();
App.addEvent('movie.added', self.movieAdded.bind(self))
@ -121,7 +125,7 @@ var MovieList = new Class({
if(!self.navigation_counter) return;
self.navigation_counter.set('text', (count || 0));
self.navigation_counter.set('text', (count || 0) + ' movies');
},
@ -145,65 +149,67 @@ var MovieList = new Class({
var self = this;
var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ';
self.current_view = self.getSavedView() || 'details';
self.el.addClass(self.current_view+'_list')
self.el.addClass('with_navigation')
self.navigation = new Element('div.alph_nav').adopt(
self.navigation_actions = new Element('ul.inlay.actions.reversed'),
self.navigation_counter = new Element('span.counter[title=Total]'),
self.navigation_alpha = new Element('ul.numbers', {
'events': {
'click:relay(li)': function(e, el){
self.movie_list.empty()
self.activateLetter(el.get('data-letter'))
self.getMovies()
}
}
}),
self.navigation_search_input = new Element('input.inlay', {
'placeholder': 'Search',
'events': {
'keyup': self.search.bind(self),
'change': self.search.bind(self)
}
}),
self.navigation_menu = new Block.Menu(self),
self.mass_edit_form = new Element('div.mass_edit_form').adopt(
new Element('span.select').adopt(
self.mass_edit_select = new Element('input[type=checkbox].inlay', {
'events': {
'change': self.massEditToggleAll.bind(self)
}
}),
self.mass_edit_selected = new Element('span.count', {'text': 0}),
self.mass_edit_selected_label = new Element('span', {'text': 'selected'})
),
new Element('div.quality').adopt(
self.mass_edit_quality = new Element('select'),
new Element('a.button.orange', {
'text': 'Change quality',
'events': {
'click': self.changeQualitySelected.bind(self)
}
})
),
new Element('div.delete').adopt(
new Element('span[text=or]'),
new Element('a.button.red', {
'text': 'Delete',
'events': {
'click': self.deleteSelected.bind(self)
}
})
),
new Element('div.refresh').adopt(
new Element('span[text=or]'),
new Element('a.button.green', {
'text': 'Refresh',
'events': {
'click': self.refreshSelected.bind(self)
self.navigation = new Element('div.alph_nav').grab(
new Element('div').adopt(
self.navigation_alpha = new Element('ul.numbers', {
'events': {
'click:relay(li)': function(e, el){
self.movie_list.empty()
self.activateLetter(el.get('data-letter'))
self.getMovies()
}
})
}
}),
self.navigation_counter = new Element('span.counter[title=Total]'),
self.navigation_actions = new Element('ul.inlay.actions.reversed'),
self.navigation_search_input = new Element('input.search.inlay', {
'title': 'Search through ' + self.options.identifier,
'placeholder': 'Search through ' + self.options.identifier,
'events': {
'keyup': self.search.bind(self),
'change': self.search.bind(self)
}
}),
self.navigation_menu = new Block.Menu(self),
self.mass_edit_form = new Element('div.mass_edit_form').adopt(
new Element('span.select').adopt(
self.mass_edit_select = new Element('input[type=checkbox].inlay', {
'events': {
'change': self.massEditToggleAll.bind(self)
}
}),
self.mass_edit_selected = new Element('span.count', {'text': 0}),
self.mass_edit_selected_label = new Element('span', {'text': 'selected'})
),
new Element('div.quality').adopt(
self.mass_edit_quality = new Element('select'),
new Element('a.button.orange', {
'text': 'Change quality',
'events': {
'click': self.changeQualitySelected.bind(self)
}
})
),
new Element('div.delete').adopt(
new Element('span[text=or]'),
new Element('a.button.red', {
'text': 'Delete',
'events': {
'click': self.deleteSelected.bind(self)
}
})
),
new Element('div.refresh').adopt(
new Element('span[text=or]'),
new Element('a.button.green', {
'text': 'Refresh',
'events': {
'click': self.refreshSelected.bind(self)
}
})
)
)
)
).inject(self.el, 'top');
@ -248,18 +254,19 @@ var MovieList = new Class({
});
// Get available chars and highlight
Api.request('movie.available_chars', {
'data': Object.merge({
'status': self.options.status
}, self.filter),
'onSuccess': function(json){
json.chars.split('').each(function(c){
self.letters[c.capitalize()].addClass('available')
})
}
});
if(self.navigation.isDisplayed() || self.navigation.isVisible())
Api.request('movie.available_chars', {
'data': Object.merge({
'status': self.options.status
}, self.filter),
'onSuccess': function(json){
json.chars.split('').each(function(c){
self.letters[c.capitalize()].addClass('available')
})
}
});
// Add menu or hide
if (self.options.menu.length > 0)
@ -267,17 +274,7 @@ var MovieList = new Class({
self.navigation_menu.addLink(menu_item);
})
else
self.navigation_menu.hide()
self.nav_scrollspy = new ScrollSpy({
min: 10,
onEnter: function(){
self.navigation.addClass('float')
},
onLeave: function(){
self.navigation.removeClass('float')
}
});
self.navigation_menu.hide();
},
@ -517,6 +514,7 @@ var MovieList = new Class({
}
self.checkIfEmpty();
self.fireEvent('loaded');
}
});
},
@ -543,7 +541,7 @@ var MovieList = new Class({
self.title[is_empty ? 'hide' : 'show']()
if(self.description)
self.description[is_empty ? 'hide' : 'show']()
self.description.setStyle('display', [is_empty ? 'none' : ''])
if(is_empty && self.options.on_empty_element){
self.options.on_empty_element.inject(self.loader_first || self.title || self.movie_list, 'after');

349
couchpotato/core/plugins/movie/static/movie.css

@ -1,50 +1,73 @@
.movies {
padding: 60px 0 20px;
padding: 10px 0 20px;
position: relative;
z-index: 3;
width: 100%;
}
.movies .loading {
display: block;
padding: 20px 0 0 0;
width: 100%;
z-index: 3;
transition: all .4s cubic-bezier(0.9,0,0.1,1);
height: 40px;
opacity: 1;
position: absolute;
.movies > div {
clear: both;
}
.movies.thumbs_list > div:not(.description) {
margin-right: -4px;
text-align: center;
}
.movies .loading.hide {
height: 0;
.movies .loading {
display: block;
padding: 20px 0 0 0;
opacity: 0;
margin-top: -20px;
}
.movies .loading .spinner {
display: inline-block;
}
.movies .loading .message {
margin: 0 20px;
width: 100%;
z-index: 3;
transition: all .4s cubic-bezier(0.9,0,0.1,1);
height: 40px;
opacity: 1;
position: absolute;
text-align: center;
}
.movies .loading.hide {
height: 0;
padding: 0;
opacity: 0;
margin-top: -20px;
overflow: hidden;
}
.movies .loading .spinner {
display: inline-block;
}
.movies .loading .message {
margin: 0 20px;
}
.movies > h2 {
.movies h2 {
margin-bottom: 20px;
}
@media all and (max-width: 480px) {
.movies h2 {
font-size: 25px;
margin-bottom: 10px;
}
}
.movies > .description {
position: absolute;
top: 30px;
right: 0;
font-style: italic;
text-shadow: none;
opacity: 0.8;
}
.movies:hover > .description {
opacity: 1;
}
@media all and (max-width: 860px) {
.movies > .description {
display: none;
}
}
.movies.thumbs_list {
padding: 20px 0 20px;
@ -53,21 +76,21 @@
.home .movies {
padding-top: 6px;
}
.movies.mass_edit_list {
padding-top: 90px;
}
.movies .movie {
position: relative;
border-radius: 4px;
margin: 10px 0;
padding-left: 20px;
overflow: hidden;
width: 100%;
height: 180px;
transition: all 0.6s cubic-bezier(0.9,0,0.1,1);
}
.movies.details_list .movie {
padding-left: 120px;
}
.movies.list_list .movie:not(.details_view),
.movies.mass_edit_list .movie {
@ -75,13 +98,21 @@
}
.movies.thumbs_list .movie {
width: 153px;
height: 230px;
width: 16.66667%;
height: auto;
display: inline-block;
margin: 0 8px 0 0;
margin: 0;
padding: 0;
vertical-align: top;
border-radius: 0;
box-shadow: none;
border: 0;
}
.movies.thumbs_list .movie:nth-child(6n+6) {
margin: 0;
@media all and (max-width: 800px) {
.movies.thumbs_list .movie {
width: 25%;
}
}
.movies .movie .mask {
@ -109,8 +140,8 @@
.movies .data {
padding: 20px;
height: 100%;
width: 840px;
position: absolute;
width: 100%;
position: relative;
right: 0;
border-radius: 0;
transition: all .6s cubic-bezier(0.9,0,0.1,1);
@ -119,14 +150,15 @@
.movies.mass_edit_list .movie .data {
height: 30px;
padding: 3px 0 3px 10px;
width: 938px;
box-shadow: none;
border: 0;
background: #4e5969;
}
.movies.thumbs_list .data {
position: absolute;
left: 0;
top: 0;
width: 100%;
padding: 10px;
height: 100%;
@ -171,8 +203,11 @@
height: 100%;
border-radius: 4px 0 0 4px;
transition: all .6s cubic-bezier(0.9,0,0.1,1);
}
.movies.thumbs_list .poster {
position: relative;
border-radius: 0;
}
.movies.list_list .movie:not(.details_view) .poster,
.movies.mass_edit_list .poster {
width: 20px;
@ -186,38 +221,72 @@
.movies.thumbs_list .poster {
width: 100%;
height: 100%;
transition: none;
}
.movies .poster img,
.options .poster img {
width: 101%;
height: 101%;
width: 100%;
height: 100%;
}
.movies.thumbs_list .poster img {
height: auto;
width: 100%;
top: 0;
bottom: 0;
border-radius: 0;
}
.movies .info {
position: relative;
height: 100%;
width: 100%;
}
.movies .info .title {
display: inline;
position: absolute;
font-size: 28px;
font-weight: bold;
margin-bottom: 10px;
margin-top: 2px;
left: 0;
top: 0;
width: 90%;
width: 100%;
padding-right: 80px;
transition: all 0.2s linear;
}
.movies .info .title span {
display: block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
height: 30px;
line-height: 30px;
top: -5px;
position: relative;
}
.movies.thumbs_list .info .title span {
white-space: normal;
overflow: auto;
height: auto;
text-align: left;
}
@media all and (max-width: 480px) {
.movies.thumbs_list .movie .info .title span,
.movies.thumbs_list .movie .info .year {
font-size: 15px;
line-height: 15px;
overflow: hidden;
}
}
.movies.list_list .movie:not(.details_view) .info .title,
.movies.mass_edit_list .info .title {
font-size: 16px;
font-weight: normal;
text-overflow: ellipsis;
width: auto;
overflow: hidden;
}
.movies.thumbs_list .movie:not(.no_thumbnail) .info {
@ -229,25 +298,22 @@
.movies.thumbs_list .info .title {
font-size: 21px;
text-shadow: 0 0 10px #000;
word-wrap: break-word;
padding: 0;
}
.movies .info .year {
position: absolute;
font-size: 30px;
margin-bottom: 10px;
color: #bbb;
width: 10%;
right: 0;
top: 0;
top: 1px;
text-align: right;
transition: all 0.2s linear;
font-weight: normal;
}
.movies.list_list .movie:not(.details_view) .info .year,
.movies.mass_edit_list .info .year {
font-size: 16px;
width: 6%;
font-size: 1.25em;
right: 10px;
}
@ -259,16 +325,14 @@
top: auto;
right: auto;
color: #FFF;
text-shadow: none;
text-shadow: 0 0 6px #000;
}
.movies .info .description {
position: absolute;
top: 30px;
clear: both;
height: 80px;
bottom: 30px;
overflow: hidden;
position: absolute;
}
.movies .data:hover .description {
overflow: auto;
@ -281,12 +345,17 @@
.movies .data .quality {
position: absolute;
bottom: 0;
bottom: 2px;
display: block;
min-height: 20px;
vertical-align: mid;
}
@media all and (max-width: 480px) {
.movies .data .quality {
display: none;
}
}
.movies .status_suggest .data .quality,
.movies.thumbs_list .data .quality {
display: none;
@ -302,9 +371,8 @@
vertical-align: middle;
display: inline-block;
text-transform: uppercase;
text-shadow: none;
font-weight: normal;
margin: 0 2px;
margin: 0 4px 0 0;
border-radius: 2px;
background-color: rgba(255,255,255,0.1);
}
@ -312,7 +380,7 @@
.movies.mass_edit_list .data .quality {
text-align: right;
right: 0;
margin-right: 50px;
margin-right: 60px;
z-index: 1;
}
@ -342,18 +410,40 @@
bottom: 20px;
right: 20px;
line-height: 0;
margin-top: -25px;
top: 0;
opacity: 0;
display: none;
width: 0;
}
@media all and (max-width: 480px) {
.movies .data .actions {
display: none !important;
}
}
.movies .movie:hover .data .actions {
opacity: 1;
display: inline-block;
width: auto;
}
.movies.details_list .data .actions {
top: auto;
bottom: 18px;
}
.movies .movie:hover .actions {
opacity: 1;
display: inline-block;
}
.movies.thumbs_list .data .actions {
bottom: 8px;
bottom: 2px;
right: 10px;
top: auto;
}
.movies .movie:hover .action { opacity: 0.6; }
.movies .movie:hover .action:hover { opacity: 1; }
.movies.mass_edit_list .data .actions {
display: none;
}
.movies .data .action {
background-repeat: no-repeat;
@ -362,7 +452,10 @@
width: 26px;
height: 26px;
padding: 3px;
opacity: 0;
}
.movies.mass_edit_list .movie .data .actions {
display: none;
}
.movies.list_list .movie:not(.details_view):hover .actions,
@ -381,7 +474,8 @@
font-size: 20px;
position: absolute;
padding: 70px 0 0;
width: 100%;
left: 120px;
right: 0;
}
.movies .delete_container .cancel {
}
@ -399,8 +493,8 @@
.movies .options {
position: absolute;
margin-left: 120px;
width: 840px;
right: 0;
left: 120px;
}
.movies .options .form {
@ -423,7 +517,6 @@
.movies .options .table .item.ignored span {
text-decoration: line-through;
color: rgba(255,255,255,0.4);
text-shadow: none;
}
.movies .options .table .item.ignored .delete {
background-image: url('../images/icon.undo.png');
@ -457,7 +550,7 @@
overflow: hidden;
}
.movies .options .table .name {
width: 350px;
width: 340px;
overflow: hidden;
text-align: left;
padding: 0 10px;
@ -491,6 +584,9 @@
text-align: center;
transition: all .6s cubic-bezier(0.9,0,0.1,1);
overflow: hidden;
left: 0;
position: absolute;
z-index: 10;
}
.movies .movie .trailer_container.hide {
height: 0 !important;
@ -507,6 +603,7 @@
background: #4e5969;
border-radius: 0 0 2px 2px;
transition: all .2s cubic-bezier(0.9,0,0.1,1) .2s;
z-index: 11;
}
.movies .movie .hide_trailer.hide {
top: -30px;
@ -543,11 +640,20 @@
right: 135px;
z-index: 2;
opacity: 0;
text-shadow: none;
background: #4e5969;
min-width: 300px;
text-align: right;
height: 100%;
padding: 3px 0;
top: 0;
}
@media all and (max-width: 480px) {
.movies .movie .trynext {
display: none;
}
}
.movies.mass_edit_list .trynext { display: none; }
.wanted .movies .movie .trynext {
padding-right: 50px;
}
@ -555,6 +661,14 @@
opacity: 1;
}
.movies.details_list .movie .trynext {
background: #47515f;
padding: 0;
right: 0;
bottom: 35px;
height: auto;
}
.movies .movie .trynext a {
background-position: 5px center;
padding: 0 5px 0 25px;
@ -562,6 +676,9 @@
color: #FFF;
border-radius: 2px;
}
.movies .movie .trynext a:last-child {
margin: 0;
}
.movies .movie .trynext a:hover {
background-color: #369545;
}
@ -578,24 +695,32 @@
.movies .alph_nav {
transition: box-shadow .4s linear;
position: fixed;
position: relative;
z-index: 4;
top: 0;
padding: 100px 60px 7px;
width: 1080px;
margin: 0 -60px;
box-shadow: 0 20px 20px -22px rgba(0,0,0,0.1);
background: #4e5969;
top: 0px;
right: 0;
margin: 0 auto;
width: 100%;
padding: 10px 0;
}
.movies .alph_nav.float {
box-shadow: 0 30px 30px -32px rgba(0,0,0,0.5);
border-radius: 0;
@media all and (max-width: 480px) {
.movies .alph_nav {
display: none;
}
}
.movies .alph_nav ul.numbers,
.movies .alph_nav > div {
position: relative;
max-width: 980px;
margin: 0 auto;
padding: 0;
min-height: 24px;
}
.movies .alph_nav .numbers,
.movies .alph_nav .counter,
.movies .alph_nav ul.actions {
.movies .alph_nav .actions {
list-style: none;
padding: 0 0 1px;
margin: 0;
@ -604,49 +729,62 @@
}
.movies .alph_nav .counter {
width: 60px;
text-align: center;
text-align: right;
position: absolute;
right: 270px;
background: #4e5969;
padding: 4px 10px;
}
.movies .alph_nav .numbers li,
.movies .alph_nav .actions li {
display: inline-block;
vertical-align: top;
width: 20px;
height: 24px;
line-height: 26px;
line-height: 23px;
text-align: center;
cursor: pointer;
color: rgba(255,255,255,0.2);
border: 1px solid transparent;
transition: all 0.1s ease-in-out;
text-shadow: none;
}
.movies .alph_nav .numbers li:first-child {
width: 43px;
}
@media all and (max-width: 900px) {
.movies .alph_nav .numbers {
display: none;
}
}
.movies .alph_nav .numbers li {
width: auto;
padding: 0 4px;
}
.movies .alph_nav li.available {
color: rgba(255,255,255,0.8);
color: #FFF;
font-weight: bolder;
}
.movies .alph_nav li.active.available, .movies .alph_nav li.available:hover {
color: #fff;
font-size: 20px;
line-height: 20px;
.movies .alph_nav li.active.available,
.movies .alph_nav li.available:hover {
background: rgba(255,255,255,.1);
}
.movies .alph_nav input {
.movies .alph_nav .search {
padding: 6px 5px;
margin: 0 0 0 6px;
float: left;
width: 155px;
margin: 0 0 0 20px;
position: absolute;
right: 30px;
width: 154px;
height: 25px;
transition: all 0.6s cubic-bezier(0.9,0,0.1,1);
}
.movies .alph_nav .actions {
margin: 0 6px 0 0;
-moz-user-select: none;
position: absolute;
right: 183px;
}
.movies .alph_nav .actions li {
border-radius: 1px;
@ -730,10 +868,12 @@
}
.movies .alph_nav .more_menu {
margin-left: 48px;
right: 0;
position: absolute;
}
.movies .alph_nav .more_menu > a {
background-color: #4e5969;
background-position: center -158px;
}
@ -790,7 +930,6 @@
font-weight: bold;
display: inline-block;
text-transform: uppercase;
text-shadow: none;
font-weight: normal;
font-size: 20px;
border-left: 1px solid rgba(255, 255, 255, .2);

14
couchpotato/core/plugins/movie/static/movie.js

@ -126,12 +126,14 @@ var Movie = new Class({
self.thumbnail = File.Select.single('poster', self.data.library.files),
self.data_container = new Element('div.data.inlay.light').adopt(
self.info_container = new Element('div.info').adopt(
self.title = new Element('div.title', {
'text': self.getTitle() || 'n/a'
}),
self.year = new Element('div.year', {
'text': self.data.library.year || 'n/a'
}),
new Element('div.title').adopt(
self.title = new Element('span', {
'text': self.getTitle() || 'n/a'
}),
self.year = new Element('div.year', {
'text': self.data.library.year || 'n/a'
})
),
self.rating = new Element('div.rating.icon', {
'text': self.data.library.rating
}),

229
couchpotato/core/plugins/movie/static/search.css

@ -1,105 +1,143 @@
.search_form {
display: inline-block;
vertical-align: middle;
width: 25%;
position: absolute;
right: 105px;
top: 0;
text-align: right;
height: 100%;
border-bottom: 4px solid transparent;
transition: all .4s cubic-bezier(0.9,0,0.1,1);
position: absolute;
z-index: 20;
border: 1px solid transparent;
border-width: 0 0 4px;
}
.search_form:hover {
border-color: #047792;
}
.search_form input {
padding: 4px 20px 4px 4px;
margin: 0;
font-size: 14px;
width: 100%;
height: 24px;
@media all and (max-width: 480px) {
.search_form {
right: 44px;
}
}
.search_form.focused,
.search_form.shown {
border-color: #04bce6;
}
.search_form input:focus {
padding-right: 83px;
.search_form .input {
height: 100%;
overflow: hidden;
width: 45px;
transition: all .4s cubic-bezier(0.9,0,0.1,1);
}
.search_form.focused .input,
.search_form.shown .input {
width: 380px;
background: #4e5969;
}
.search_form .input .enter {
background: #369545 url('../images/sprite.png') right -188px no-repeat;
padding: 0 20px 0 4px;
border-radius: 2px;
text-transform: uppercase;
font-size: 10px;
margin-left: -78px;
display: inline-block;
opacity: 0;
position: relative;
top: -2px;
cursor: pointer;
vertical-align: middle;
visibility: hidden;
}
.search_form.focused .input .enter {
visibility: visible;
.search_form .input input {
border-radius: 0;
display: block;
width: 100%;
border: 0;
background: rgba(255,255,255,.08);
color: #FFF;
font-size: 25px;
height: 100%;
padding: 10px;
width: 100%;
opacity: 0;
padding: 0 40px 0 10px;
transition: all .4s ease-in-out .2s;
}
.search_form.focused.filled .input .enter {
opacity: 1;
.search_form.focused .input input,
.search_form.shown .input input {
opacity: 1;
}
@media all and (max-width: 480px) {
.search_form .input input {
font-size: 15px;
}
.search_form.focused .input,
.search_form.shown .input {
width: 277px;
}
}
.search_form .input a {
width: 17px;
height: 20px;
display: inline-block;
margin: -2px 0 0 2px;
top: 4px;
right: 5px;
background: url('../images/sprite.png') left -37px no-repeat;
cursor: pointer;
opacity: 0;
transition: all 0.2s ease-in-out;
vertical-align: middle;
.search_form .input a {
position: absolute;
top: 0;
right: 0;
width: 44px;
height: 100%;
cursor: pointer;
vertical-align: middle;
text-align: center;
line-height: 66px;
font-size: 15px;
color: #FFF;
}
.search_form .input a:after {
content: "\e03e";
}
.search_form.filled .input a {
opacity: 1;
.search_form.shown.filled .input a:after {
content: "\e04e";
}
@media all and (max-width: 480px) {
.search_form .input a {
line-height: 44px;
}
}
.search_form .results_container {
text-align: left;
position: absolute;
background: #5c697b;
margin: 6px 0 0 -230px;
margin: 4px 0 0;
width: 470px;
min-height: 140px;
border-radius: 3px;
box-shadow: 0 20px 20px -10px rgba(0,0,0,0.55);
display: none;
}
.search_form.shown.filled .results_container {
display: block;
@media all and (max-width: 480px) {
.search_form .results_container {
width: 320px;
}
}
.search_form .results_container:before {
content: ' ';
height: 0;
position: relative;
width: 0;
border: 10px solid transparent;
border-bottom-color: #5c697b;
.search_form.focused.filled .results_container,
.search_form.shown.filled .results_container {
display: block;
top: -20px;
left: 346px;
}
.search_form .results {
max-height: 570px;
overflow-x: hidden;
padding: 10px 0;
margin-top: -18px;
}
.movie_result {
overflow: hidden;
height: 140px;
height: 50px;
position: relative;
}
.movie_result .options {
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
left: 30px;
right: 0;
padding: 13px;
border: 1px solid transparent;
border-width: 1px 0;
border-radius: 0;
@ -107,7 +145,6 @@
}
.movie_result .options > div {
padding: 0 15px;
border: 0;
}
@ -122,6 +159,13 @@
}
.movie_result .options select[name=title] { width: 180px; }
.movie_result .options select[name=profile] { width: 90px; }
@media all and (max-width: 480px) {
.movie_result .options select[name=title] { width: 90px; }
.movie_result .options select[name=profile] { width: 60px; }
}
.movie_result .options .button {
vertical-align: middle;
@ -130,25 +174,21 @@
.movie_result .options .message {
height: 100%;
line-height: 140px;
font-size: 20px;
text-align: center;
color: #fff;
line-height: 20px;
}
.movie_result .data {
padding: 0 15px;
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
left: 30px;
right: 0;
background: #5c697b;
cursor: pointer;
border-bottom: 1px solid #333;
border-top: 1px solid rgba(255,255,255, 0.15);
transition: all .6s cubic-bezier(0.9,0,0.1,1);
border-top: 1px solid rgba(255,255,255, 0.08);
transition: all .4s cubic-bezier(0.9,0,0.1,1);
}
.movie_result .data.open {
left: 100%;
@ -162,49 +202,40 @@
}
.movie_result .thumbnail {
width: 17%;
display: inline-block;
margin: 15px 3% 15px 0;
width: 34px;
min-height: 100%;
display: block;
margin: 0;
vertical-align: top;
border-radius: 3px;
box-shadow: 0 0 3px rgba(0,0,0,0.35);
}
.movie_result .info {
width: 80%;
display: inline-block;
vertical-align: top;
padding: 15px 0;
height: 120px;
overflow: hidden;
}
.movie_result .info .tagline {
max-height: 70px;
overflow: hidden;
display: inline-block;
}
.movie_result .add +.info {
margin-left: 20%;
position: absolute;
top: 20%;
left: 15px;
right: 60px;
vertical-align: middle;
}
.movie_result .info h2 {
font-weight: normal;
font-size: 20px;
display: block;
margin: 0;
font-size: 17px;
line-height: 20px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
}
.movie_result .info h2 span {
padding: 0 5px;
position: absolute;
right: -60px;
}
.movie_result .info h2 span:before { content: "("; }
.movie_result .info h2 span:after { content: ")"; }
.search_form .mask,
.movie_result .mask {
border-radius: 3px;
position: absolute;
height: 100%;
width: 100%;

79
couchpotato/core/plugins/movie/static/search.js

@ -7,33 +7,30 @@ Block.Search = new Class({
create: function(){
var self = this;
var focus_timer = 0;
self.el = new Element('div.search_form').adopt(
new Element('div.input').adopt(
self.input = new Element('input.inlay', {
self.input = new Element('input', {
'placeholder': 'Search & add a new movie',
'events': {
'keyup': self.keyup.bind(self),
'focus': function(){
if(focus_timer) clearTimeout(focus_timer);
self.el.addClass('focused')
if(this.get('value'))
self.hideResults(false)
},
'blur': function(){
(function(){
focus_timer = (function(){
self.el.removeClass('focused')
}).delay(2000);
}).delay(100);
}
}
}),
new Element('span.enter', {
new Element('a.icon2', {
'events': {
'click': self.keyup.bind(self)
},
'text':'Enter'
}),
new Element('a', {
'events': {
'click': self.clear.bind(self)
'click': self.clear.bind(self),
'touchend': self.clear.bind(self)
}
})
),
@ -59,13 +56,21 @@ Block.Search = new Class({
var self = this;
(e).preventDefault();
self.last_q = '';
self.input.set('value', '');
self.input.focus()
if(self.last_q === ''){
self.input.blur()
self.last_q = null;
}
else {
self.last_q = '';
self.input.set('value', '');
self.input.focus()
self.movies = []
self.results.empty()
self.el.removeClass('filled')
self.movies = []
self.results.empty()
self.el.removeClass('filled')
}
},
hideResults: function(bool){
@ -92,8 +97,10 @@ Block.Search = new Class({
self.el[self.q() ? 'addClass' : 'removeClass']('filled')
if(self.q() != self.last_q && (['enter'].indexOf(e.key) > -1 || e.type == 'click'))
self.autocomplete()
if(self.q() != self.last_q){
if(self.autocomplete_timer) clearTimeout(self.autocomplete_timer)
self.autocomplete_timer = self.autocomplete.delay(300, self)
}
},
@ -197,6 +204,11 @@ Block.Search.Item = new Class({
self.el = new Element('div.movie_result', {
'id': info.imdb
}).adopt(
self.thumbnail = info.images && info.images.poster.length > 0 ? new Element('img.thumbnail', {
'src': info.images.poster[0],
'height': null,
'width': null
}) : null,
self.options_el = new Element('div.options.inlay'),
self.data_container = new Element('div.data', {
'tween': {
@ -207,11 +219,6 @@ Block.Search.Item = new Class({
'click': self.showOptions.bind(self)
}
}).adopt(
self.thumbnail = info.images && info.images.poster.length > 0 ? new Element('img.thumbnail', {
'src': info.images.poster[0],
'height': null,
'width': null
}) : null,
new Element('div.info').adopt(
self.title = new Element('h2', {
'text': info.titles[0]
@ -219,28 +226,11 @@ Block.Search.Item = new Class({
self.year = info.year ? new Element('span.year', {
'text': info.year
}) : null
),
self.tagline = new Element('span.tagline', {
'text': info.tagline ? info.tagline : info.plot,
'title': info.tagline ? info.tagline : info.plot
}),
self.director = self.info.director ? new Element('span.director', {
'text': 'Director:' + info.director
}) : null,
self.starring = info.actors ? new Element('span.actors', {
'text': 'Starring:'
}) : null
)
)
)
)
if(info.actors){
Object.each(info.actors, function(actor){
new Element('span', {
'text': actor
}).inject(self.starring)
})
}
info.titles.each(function(title){
self.alternativeTitle({
@ -320,11 +310,6 @@ Block.Search.Item = new Class({
self.options_el.grab(
new Element('div').adopt(
self.thumbnail = (info.images && info.images.poster.length > 0) ? new Element('img.thumbnail', {
'src': info.images.poster[0],
'height': null,
'width': null
}) : null,
self.info.in_wanted && self.info.in_wanted.profile ? new Element('span.in_wanted', {
'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label
}) : (in_library ? new Element('span.in_library', {

16
couchpotato/core/plugins/profile/static/profile.css

@ -61,6 +61,10 @@
margin-right: 10px;
}
.profile .type .check {
margin-top: -1px;
}
.profile .quality_type select {
width: 186px;
margin-left: -1px;
@ -71,13 +75,13 @@
}
.profile .types .type .handle {
background: url('./handle.png') center;
background: url('../../static/profile_plugin/handle.png') center;
display: inline-block;
height: 20px;
width: 20px;
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
margin: 0;
}
@ -105,9 +109,9 @@
}
#profile_ordering li {
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
border-bottom: 1px solid rgba(255,255,255,0.2);
padding: 0 5px;
}
@ -126,7 +130,7 @@
}
#profile_ordering li .handle {
background: url('./handle.png') center;
background: url('../../static/profile_plugin/handle.png') center;
width: 20px;
float: right;
}

28
couchpotato/core/plugins/release/main.py

@ -41,12 +41,15 @@ class Release(Plugin):
addEvent('release.clean', self.clean)
def add(self, group):
db = get_session()
identifier = '%s.%s.%s' % (group['library']['identifier'], group['meta_data'].get('audio', 'unknown'), group['meta_data']['quality']['identifier'])
done_status, snatched_status = fireEvent('status.get', ['done', 'snatched'], single = True)
# Add movie
done_status = fireEvent('status.get', 'done', single = True)
movie = db.query(Movie).filter_by(library_id = group['library'].get('id')).first()
if not movie:
movie = Movie(
@ -58,7 +61,6 @@ class Release(Plugin):
db.commit()
# Add Release
snatched_status = fireEvent('status.get', 'snatched', single = True)
rel = db.query(Relea).filter(
or_(
Relea.identifier == identifier,
@ -76,15 +78,19 @@ class Release(Plugin):
db.commit()
# Add each file type
added_files = []
for type in group['files']:
for cur_file in group['files'][type]:
added_file = self.saveFile(cur_file, type = type, include_media_info = type is 'movie')
try:
added_file = db.query(File).filter_by(id = added_file.get('id')).one()
rel.files.append(added_file)
db.commit()
except Exception, e:
log.debug('Failed to attach "%s" to release: %s', (cur_file, e))
added_files.append(added_file.get('id'))
# Add the release files in batch
try:
added_files = db.query(File).filter(or_(*[File.id == x for x in added_files])).all()
rel.files.append(added_files)
db.commit()
except Exception, e:
log.debug('Failed to attach "%s" to release: %s', (cur_file, e))
fireEvent('movie.restatus', movie.id)
@ -147,8 +153,7 @@ class Release(Plugin):
rel = db.query(Relea).filter_by(id = id).first()
if rel:
ignored_status = fireEvent('status.get', 'ignored', single = True)
available_status = fireEvent('status.get', 'available', single = True)
ignored_status, available_status = fireEvent('status.get', ['ignored', 'available'], single = True)
rel.status_id = available_status.get('id') if rel.status_id is ignored_status.get('id') else ignored_status.get('id')
db.commit()
@ -161,8 +166,7 @@ class Release(Plugin):
db = get_session()
id = getParam('id')
snatched_status = fireEvent('status.add', 'snatched', single = True)
done_status = fireEvent('status.get', 'done', single = True)
snatched_status, done_status = fireEvent('status.get', ['snatched', 'done'], single = True)
rel = db.query(Relea).filter_by(id = id).first()
if rel:

4
couchpotato/core/plugins/renamer/__init__.py

@ -114,11 +114,11 @@ config = [{
},
{
'name': 'file_action',
'label': 'File Action',
'label': 'Torrent File Action',
'default': 'move',
'type': 'dropdown',
'values': [('Move', 'move'), ('Copy', 'copy'), ('Hard link', 'hardlink'), ('Sym link', 'symlink'), ('Move & Sym link', 'move_symlink')],
'description': 'Define which kind of file operation you want to use. Before you start using <a href="http://en.wikipedia.org/wiki/Hard_link">hard links</a> or <a href="http://en.wikipedia.org/wiki/Sym_link">sym links</a>, PLEASE read about their possible drawbacks.',
'description': 'Define which kind of file operation you want to use for torrents. Before you start using <a href="http://en.wikipedia.org/wiki/Hard_link">hard links</a> or <a href="http://en.wikipedia.org/wiki/Sym_link">sym links</a>, PLEASE read about their possible drawbacks.',
'advanced': True,
},
{

113
couchpotato/core/plugins/renamer/main.py

@ -4,13 +4,12 @@ from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
from couchpotato.core.helpers.encoding import toUnicode, ss
from couchpotato.core.helpers.request import getParams, jsonified
from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle, \
getImdb
getImdb, link, symlink
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Library, File, Profile, Release, \
ReleaseInfo
from couchpotato.environment import Env
from linktastic.linktastic import link, symlink
import errno
import os
import re
@ -20,7 +19,6 @@ import traceback
log = CPLog(__name__)
class Renamer(Plugin):
renaming_started = False
@ -70,15 +68,14 @@ class Renamer(Plugin):
fireEventAsync('renamer.scan',
movie_folder = movie_folder,
downloader = downloader,
download_id = download_id
download_info = {'id': download_id, 'downloader': downloader} if download_id else None
)
return jsonified({
'success': True
})
def scan(self, movie_folder = None, downloader = None, download_id = None):
def scan(self, movie_folder = None, download_info = None):
if self.isDisabled():
return
@ -103,7 +100,7 @@ class Renamer(Plugin):
# make sure the movie folder name is included in the search
folder = None
movie_files = []
files = []
if movie_folder:
log.info('Scanning movie folder %s...', movie_folder)
movie_folder = movie_folder.rstrip(os.path.sep)
@ -112,37 +109,17 @@ class Renamer(Plugin):
# Get all files from the specified folder
try:
for root, folders, names in os.walk(movie_folder):
movie_files.extend([os.path.join(root, name) for name in names])
files.extend([os.path.join(root, name) for name in names])
except:
log.error('Failed getting files from %s: %s', (movie_folder, traceback.format_exc()))
db = get_session()
# Get the release with the downloader ID that was downloded by the downloader
download_info = None
if download_id and downloader:
rls = None
rlsnfo_dwnlds = db.query(ReleaseInfo).filter_by(identifier = 'download_downloader', value = downloader).all()
rlsnfo_ids = db.query(ReleaseInfo).filter_by(identifier = 'download_id', value = download_id).all()
for rlsnfo_dwnld in rlsnfo_dwnlds:
for rlsnfo_id in rlsnfo_ids:
if rlsnfo_id.release == rlsnfo_dwnld.release:
rls = rlsnfo_id.release
break
if rls: break
if rls:
download_info = {
'imdb_id': rls.movie.library.identifier,
'quality': rls.quality.identifier,
}
else:
log.error('Download ID %s from downloader %s not found in releases', (download_id, downloader))
# Extend the download info with info stored in the downloaded release
download_info = self.extendDownloadInfo(download_info)
groups = fireEvent('scanner.scan', folder = folder if folder else self.conf('from'),
files = movie_files, download_info = download_info, return_ignored = False, single = True)
files = files, download_info = download_info, return_ignored = False, single = True)
destination = self.conf('to')
folder_name = self.conf('folder_name')
@ -152,10 +129,8 @@ class Renamer(Plugin):
separator = self.conf('separator')
# Statusses
done_status = fireEvent('status.get', 'done', single = True)
active_status = fireEvent('status.get', 'active', single = True)
downloaded_status = fireEvent('status.get', 'downloaded', single = True)
snatched_status = fireEvent('status.get', 'snatched', single = True)
done_status, active_status, downloaded_status, snatched_status = \
fireEvent('status.get', ['done', 'active', 'downloaded', 'snatched'], single = True)
for group_identifier in groups:
@ -375,7 +350,7 @@ class Renamer(Plugin):
else:
log.info('Better quality release already exists for %s, with quality %s', (movie.library.titles[0].title, release.quality.label))
# Add _EXISTS_ to the parent dir
# Add exists tag to the .ignore file
self.tagDir(group, 'exists')
# Notify on rename fail
@ -396,7 +371,8 @@ class Renamer(Plugin):
db.commit()
# Remove leftover files
if self.conf('cleanup') and not self.conf('move_leftover') and remove_leftovers:
if self.conf('cleanup') and not self.conf('move_leftover') and remove_leftovers and \
not (self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info)):
log.debug('Removing leftover files')
for current_file in group['files']['leftover']:
remove_files.append(current_file)
@ -421,7 +397,7 @@ class Renamer(Plugin):
os.remove(src)
parent_dir = os.path.normpath(os.path.dirname(src))
if delete_folders.count(parent_dir) == 0 and os.path.isdir(parent_dir) and destination != parent_dir:
if delete_folders.count(parent_dir) == 0 and os.path.isdir(parent_dir) and not parent_dir in [destination, movie_folder] and not self.conf('from') in parent_dir:
delete_folders.append(parent_dir)
except:
@ -446,13 +422,13 @@ class Renamer(Plugin):
self.makeDir(os.path.dirname(dst))
try:
self.moveFile(src, dst)
self.moveFile(src, dst, forcemove = not self.downloadIsTorrent(download_info))
group['renamed_files'].append(dst)
except:
log.error('Failed moving the file "%s" : %s', (os.path.basename(src), traceback.format_exc()))
self.tagDir(group, 'failed_rename')
if self.conf('file_action') != 'move':
if self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info):
self.tagDir(group, 'renamed already')
# Remove matching releases
@ -518,10 +494,12 @@ Remove it if you want it to be renamed (again, or at least let it try again)
self.createFile(ignore_file, text)
def moveFile(self, old, dest):
def moveFile(self, old, dest, forcemove = False):
dest = ss(dest)
try:
if self.conf('file_action') == 'hardlink':
if forcemove:
shutil.move(old, dest)
elif self.conf('file_action') == 'hardlink':
link(old, dest)
elif self.conf('file_action') == 'symlink':
symlink(old, dest)
@ -606,11 +584,8 @@ Remove it if you want it to be renamed (again, or at least let it try again)
self.checking_snatched = True
snatched_status = fireEvent('status.get', 'snatched', single = True)
ignored_status = fireEvent('status.get', 'ignored', single = True)
failed_status = fireEvent('status.get', 'failed', single = True)
done_status = fireEvent('status.get', 'done', single = True)
snatched_status, ignored_status, failed_status, done_status = \
fireEvent('status.get', ['snatched', 'ignored', 'failed', 'done'], single = True)
db = get_session()
rels = db.query(Release).filter_by(status_id = snatched_status.get('id')).all()
@ -665,17 +640,16 @@ Remove it if you want it to be renamed (again, or at least let it try again)
pass
elif item['status'] == 'failed':
fireEvent('download.remove_failed', item, single = True)
rel.status_id = failed_status.get('id')
rel.last_edit = int(time.time())
db.commit()
if self.conf('next_on_failed'):
fireEvent('searcher.try_next_release', movie_id = rel.movie_id)
else:
rel.status_id = failed_status.get('id')
rel.last_edit = int(time.time())
db.commit()
elif item['status'] == 'completed':
log.info('Download of %s completed!', item['name'])
if item['id'] and item['downloader'] and item['folder']:
fireEventAsync('renamer.scan', movie_folder = item['folder'], downloader = item['downloader'], download_id = item['id'])
fireEventAsync('renamer.scan', movie_folder = item['folder'], download_info = item)
else:
scan_required = True
@ -694,3 +668,38 @@ Remove it if you want it to be renamed (again, or at least let it try again)
self.checking_snatched = False
return True
def extendDownloadInfo(self, download_info):
rls = None
if download_info and download_info.get('id') and download_info.get('downloader'):
db = get_session()
rlsnfo_dwnlds = db.query(ReleaseInfo).filter_by(identifier = 'download_downloader', value = download_info.get('downloader')).all()
rlsnfo_ids = db.query(ReleaseInfo).filter_by(identifier = 'download_id', value = download_info.get('id')).all()
for rlsnfo_dwnld in rlsnfo_dwnlds:
for rlsnfo_id in rlsnfo_ids:
if rlsnfo_id.release == rlsnfo_dwnld.release:
rls = rlsnfo_id.release
break
if rls: break
if not rls:
log.error('Download ID %s from downloader %s not found in releases', (download_info.get('id'), download_info.get('downloader')))
if rls:
rls_dict = rls.to_dict({'info':{}})
download_info.update({
'imdb_id': rls.movie.library.identifier,
'quality': rls.quality.identifier,
'type': rls_dict.get('info', {}).get('type')
})
return download_info
def downloadIsTorrent(self, download_info):
return download_info and download_info.get('type') in ['torrent', 'torrent_magnet']

3
couchpotato/core/plugins/searcher/__init__.py

@ -25,12 +25,13 @@ config = [{
'label': 'Required words',
'default': '',
'placeholder': 'Example: DTS, AC3 & English',
'description': 'Ignore releases that don\'t contain at least one set of words. Sets are separated by "," and each word within a set must be separated with "&"'
'description': 'A release should contain at least one set of words. Sets are separated by "," and each word within a set must be separated with "&"'
},
{
'name': 'ignored_words',
'label': 'Ignored words',
'default': 'german, dutch, french, truefrench, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub, dksubs',
'description': 'Ignores releases that match any of these sets. (Works like explained above)'
},
{
'name': 'preferred_method',

19
couchpotato/core/plugins/searcher/main.py

@ -146,8 +146,7 @@ class Searcher(Plugin):
pre_releases = fireEvent('quality.pre_releases', single = True)
release_dates = fireEvent('library.update_release_date', identifier = movie['library']['identifier'], merge = True)
available_status = fireEvent('status.get', 'available', single = True)
ignored_status = fireEvent('status.get', 'ignored', single = True)
available_status, ignored_status = fireEvent('status.get', ['available', 'ignored'], single = True)
found_releases = []
@ -384,8 +383,9 @@ class Searcher(Plugin):
movie_words = re.split('\W+', simplifyString(movie_name))
nzb_name = simplifyString(nzb['name'])
nzb_words = re.split('\W+', nzb_name)
required_words = splitString(self.conf('required_words').lower())
# Make sure it has required words
required_words = splitString(self.conf('required_words').lower())
req_match = 0
for req_set in required_words:
req = splitString(req_set, '&')
@ -395,19 +395,24 @@ class Searcher(Plugin):
log.info2("Wrong: Required word missing: %s" % nzb['name'])
return False
# Ignore releases
ignored_words = splitString(self.conf('ignored_words').lower())
blacklisted = list(set(nzb_words) & set(ignored_words) - set(movie_words))
if self.conf('ignored_words') and blacklisted:
log.info2("Wrong: '%s' blacklisted words: %s" % (nzb['name'], ", ".join(blacklisted)))
ignored_match = 0
for ignored_set in ignored_words:
ignored = splitString(ignored_set, '&')
ignored_match += len(list(set(nzb_words) & set(ignored))) == len(ignored)
if self.conf('ignored_words') and ignored_match:
log.info2("Wrong: '%s' contains 'ignored words'" % (nzb['name']))
return False
# Ignore porn stuff
pron_tags = ['xxx', 'sex', 'anal', 'tits', 'fuck', 'porn', 'orgy', 'milf', 'boobs', 'erotica', 'erotic']
pron_words = list(set(nzb_words) & set(pron_tags) - set(movie_words))
if pron_words:
log.info('Wrong: %s, probably pr0n', (nzb['name']))
return False
#qualities = fireEvent('quality.all', single = True)
preferred_quality = fireEvent('quality.single', identifier = quality['identifier'], single = True)
# Contains lower quality string

45
couchpotato/core/plugins/status/main.py

@ -25,13 +25,14 @@ class StatusPlugin(Plugin):
'available': 'Available',
'suggest': 'Suggest',
}
status_cached = {}
def __init__(self):
addEvent('status.add', self.add)
addEvent('status.get', self.add) # Alias for .add
addEvent('status.get', self.get)
addEvent('status.get_by_id', self.getById)
addEvent('status.all', self.all)
addEvent('app.initialize', self.fill)
addEvent('app.load', self.all) # Cache all statuses
addApiView('status.list', self.list, docs = {
'desc': 'Check for available update',
@ -67,26 +68,40 @@ class StatusPlugin(Plugin):
s = status.to_dict()
temp.append(s)
#db.close()
# Update cache
self.status_cached[status.identifier] = s
return temp
def add(self, identifier):
def get(self, identifiers):
if not isinstance(identifiers, (list)):
identifiers = [identifiers]
db = get_session()
return_list = []
s = db.query(Status).filter_by(identifier = identifier).first()
if not s:
s = Status(
identifier = identifier,
label = toUnicode(identifier.capitalize())
)
db.add(s)
db.commit()
for identifier in identifiers:
status_dict = s.to_dict()
if self.status_cached.get(identifier):
return_list.append(self.status_cached.get(identifier))
continue
#db.close()
return status_dict
s = db.query(Status).filter_by(identifier = identifier).first()
if not s:
s = Status(
identifier = identifier,
label = toUnicode(identifier.capitalize())
)
db.add(s)
db.commit()
status_dict = s.to_dict()
self.status_cached[identifier] = status_dict
return_list.append(status_dict)
return return_list if len(identifiers) > 1 else return_list[0]
def fill(self):

2
couchpotato/core/plugins/trailer/__init__.py

@ -29,7 +29,7 @@ config = [{
'label': 'Naming',
'default': '<filename>-trailer',
'advanced': True,
'description': 'Use <filename> to use above settings.'
'description': 'Use <strong>&lt;filename&gt;</strong> to use above settings.'
},
],
},

9
couchpotato/core/plugins/trailer/main.py

@ -22,10 +22,15 @@ class Trailer(Plugin):
return False
for trailer in trailers.get(self.conf('quality'), []):
filename = self.conf('name').replace('<filename>', group['filename']) + ('.%s' % getExt(trailer))
ext = getExt(trailer)
filename = self.conf('name').replace('<filename>', group['filename']) + ('.%s' % ('mp4' if len(ext) > 5 else ext))
destination = os.path.join(group['destination_dir'], filename)
if not os.path.isfile(destination):
fireEvent('file.download', url = trailer, dest = destination, urlopen_kwargs = {'headers': {'User-Agent': 'Quicktime'}}, single = True)
trailer_file = fireEvent('file.download', url = trailer, dest = destination, urlopen_kwargs = {'headers': {'User-Agent': 'Quicktime'}}, single = True)
if os.path.getsize(trailer_file) < (1024 * 1024): # Don't trust small trailers (1MB), try next one
os.unlink(trailer_file)
continue
else:
log.debug('Trailer already exists: %s', destination)

24
couchpotato/core/plugins/userscript/static/userscript.css

@ -5,6 +5,7 @@
bottom: 0;
left: 0;
right: 0;
padding: 0;
}
.page.userscript .frame.loading {
@ -12,3 +13,26 @@
font-size: 20px;
padding: 20px;
}
.page.userscript .movie_result {
height: 140px;
}
.page.userscript .movie_result .thumbnail {
width: 90px;
}
.page.userscript .movie_result .options {
left: 90px;
padding: 54px 15px;
}
.page.userscript .movie_result .year {
display: none;
}
.page.userscript .movie_result .options select[name="title"] {
width: 190px;
}
.page.userscript .movie_result .options select[name="profile"] {
width: 70px;
}

19
couchpotato/core/plugins/userscript/static/userscript.js

@ -63,28 +63,19 @@ var UserscriptSettingTab = new Class({
self.settings = App.getPage('Settings')
self.settings.addEvent('create', function(){
// See if userscript can be installed
var userscript = false;
try {
if(Components.interfaces.gmIGreasemonkeyService)
userscript = true
}
catch(e){
userscript = Browser.chrome === true;
}
var host_url = window.location.protocol + '//' + window.location.host;
self.settings.createGroup({
'name': 'userscript',
'label': 'Install the bookmarklet' + (userscript ? ' or userscript' : ''),
'label': 'Install the bookmarklet or userscript',
'description': 'Easily add movies via imdb.com, appletrailers and more'
}).inject(self.settings.tabs.automation.content, 'top').adopt(
(userscript ? [new Element('a.userscript.button', {
new Element('a.userscript.button', {
'text': 'Install userscript',
'href': Api.createUrl('userscript.get')+randomString()+'/couchpotato.user.js',
'target': '_self'
}), new Element('span.or[text=or]')] : null),
'target': '_blank'
}),
new Element('span.or[text=or]'),
new Element('span.bookmarklet').adopt(
new Element('a.button.green', {
'text': '+CouchPotato',

5
couchpotato/core/plugins/userscript/template.js

@ -1,4 +1,9 @@
// ==UserScript==
//
// If you can read this, you need to enable or install the Greasemonkey add-on for firefox
// If you are using Chrome, download this file and drag it to the extensions tab
// Other browsers, use the bookmarklet
//
// @name CouchPotato UserScript
// @description Add movies like a real CouchPotato
// @grant none

61
couchpotato/core/plugins/wizard/static/wizard.css

@ -1,49 +1,60 @@
.page.wizard .uniForm {
width: 80%;
margin: 0 auto 30px;
margin: 0 0 30px;
width: 83%;
}
.page.wizard h1 {
padding: 10px 30px;
margin: 0;
padding: 10px 0;
margin: 0 5px;
display: block;
font-size: 30px;
margin-top: 80px;
}
.page.wizard .description {
padding: 10px 30px;
font-size: 18px;
padding: 10px 5px;
font-size: 1.45em;
line-height: 1.4em;
display: block;
}
.page.wizard .tab_wrapper {
background: #5c697b;
padding: 10px 0;
font-size: 18px;
height: 65px;
font-size: 1.75em;
position: fixed;
top: 0;
margin: 0;
width: 100%;
min-width: 960px;
left: 0;
z-index: 2;
box-shadow: 0 0 50px rgba(0,0,0,0.55);
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.page.wizard .tab_wrapper .tabs {
text-align: center;
padding: 0;
margin: 0;
margin: 0 auto;
display: block;
height: 100%;
width: 100%;
max-width: 960px;
}
.page.wizard .tabs li {
display: inline-block;
height: 100%;
}
.page.wizard .tabs li a {
padding: 20px 10px;
height: 100%;
display: block;
color: #FFF;
font-weight: normal;
border-bottom: 4px solid transparent;
}
.page.wizard .tabs li:hover a { border-color: #047792; }
.page.wizard .tabs li.done a { border-color: #04bce6; }
.page.wizard .tab_wrapper .pointer {
border-right: 10px solid transparent;
@ -61,27 +72,13 @@
.page.wizard form > div {
min-height: 300px;
}
.page.wizard .wgroup_finish {
height: 300px;
}
.page.wizard .wgroup_finish h1 {
text-align: center;
}
.page.wizard .wgroup_finish .wizard_support,
.page.wizard .wgroup_finish .description {
font-size: 25px;
line-height: 120%;
margin: 20px 0;
text-align: center;
}
.page.wizard .button.green {
padding: 20px;
font-size: 25px;
margin: 10px 30px 80px;
display: block;
text-align: center;
}
.page.wizard .button.green {
padding: 20px;
font-size: 25px;
margin: 10px 0 80px;
display: block;
}
.page.wizard .tab_nzb_providers {
margin: 20px 0 0 0;

39
couchpotato/core/plugins/wizard/static/wizard.js

@ -9,27 +9,12 @@ Page.Wizard = new Class({
headers: {
'welcome': {
'title': 'Welcome to the new CouchPotato',
'description': 'To get started, fill in each of the following settings as much as you can. <br />Maybe first start with importing your movies from the previous CouchPotato',
'description': 'To get started, fill in each of the following settings as much as you can.',
'content': new Element('div', {
'styles': {
'margin': '0 0 0 30px'
}
}).adopt(
new Element('div', {
'html': 'Select the <strong>data.db</strong>. It should be in your CouchPotato root directory.'
}),
self.import_iframe = new Element('iframe', {
'styles': {
'height': 40,
'width': 300,
'border': 0,
'overflow': 'hidden'
}
})
),
'event': function(){
self.import_iframe.set('src', Api.createUrl('v1.import'))
}
})
},
'general': {
'title': 'General',
@ -178,7 +163,7 @@ Page.Wizard = new Class({
'href': App.createUrl('wizard/'+group),
'text': (self.headers[group].label || group).capitalize()
})
).inject(tabs);
).inject(tabs)
}
else
@ -214,13 +199,7 @@ Page.Wizard = new Class({
self.el.getElement('.t_searcher').hide();
// Add pointer
new Element('.tab_wrapper').wraps(tabs).adopt(
self.pointer = new Element('.pointer', {
'tween': {
'transition': 'quint:in:out'
}
})
);
new Element('.tab_wrapper').wraps(tabs);
// Add nav
var minimum = self.el.getSize().y-window.getSize().y;
@ -232,16 +211,18 @@ Page.Wizard = new Class({
if(!t) return;
var func = function(){
var ct = t.getCoordinates();
self.pointer.tween('left', ct.left+(ct.width/2)-(self.pointer.getWidth()/2));
// Activate all previous ones
self.groups.each(function(groups2, nr2){
var t2 = self.el.getElement('.t_'+groups2);
t2[nr2 > nr ? 'removeClass' : 'addClass' ]('done');
})
g.tween('opacity', 1);
}
if(nr == 0)
func();
var ss = new ScrollSpy( {
new ScrollSpy( {
min: function(){
var c = g.getCoordinates();
var top = c.top-(window.getSize().y/2);

3
couchpotato/core/providers/movie/_modifier/main.py

@ -52,8 +52,7 @@ class MovieResultModifier(Plugin):
if l:
# Statuses
active_status = fireEvent('status.get', 'active', single = True)
done_status = fireEvent('status.get', 'done', single = True)
active_status, done_status = fireEvent('status.get', ['active', 'done'], single = True)
for movie in l.movies:
if movie.status_id == active_status['id']:

23
couchpotato/core/providers/movie/couchpotatoapi/main.py

@ -2,6 +2,7 @@ from couchpotato import get_session
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.helpers.request import jsonified, getParams
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.movie.base import MovieProvider
from couchpotato.core.settings.model import Movie
@ -19,19 +20,37 @@ class CouchPotatoApi(MovieProvider):
'is_movie': 'https://couchpota.to/api/ismovie/%s/',
'eta': 'https://couchpota.to/api/eta/%s/',
'suggest': 'https://couchpota.to/api/suggest/',
'updater': 'https://couchpota.to/api/updater/?%s',
'messages': 'https://couchpota.to/api/messages/?%s',
}
http_time_between_calls = 0
api_version = 1
def __init__(self):
#addApiView('movie.suggest', self.suggestView)
addEvent('movie.info', self.getInfo, priority = 1)
addEvent('movie.search', self.search, priority = 1)
addEvent('movie.release_date', self.getReleaseDate)
addEvent('movie.suggest', self.suggest)
addEvent('movie.is_movie', self.isMovie)
addEvent('cp.source_url', self.getSourceUrl)
addEvent('cp.messages', self.getMessages)
def getMessages(self, last_check = 0):
data = self.getJsonData(self.urls['messages'] % tryUrlencode({
'last_check': last_check,
}), headers = self.getRequestHeaders(), cache_timeout = 10)
return data
def getSourceUrl(self, repo = None, repo_name = None, branch = None):
return self.getJsonData(self.urls['updater'] % tryUrlencode({
'repo': repo,
'name': repo_name,
'branch': branch,
}), headers = self.getRequestHeaders())
def search(self, q, limit = 12):
return self.getJsonData(self.urls['search'] % tryUrlencode(q), headers = self.getRequestHeaders())

2
couchpotato/core/providers/nzb/newznab/__init__.py

@ -40,7 +40,7 @@ config = [{
},
{
'name': 'api_key',
'default': ',,,',
'default': ',,,,,',
'label': 'Api Key',
'description': 'Can be found on your profile page',
'type': 'combined',

3
couchpotato/core/providers/torrent/torrentleech/main.py

@ -74,3 +74,6 @@ class TorrentLeech(TorrentProvider):
'remember_me': 'on',
'login': 'submit',
})
def loginSuccess(self, output):
return '/user/account/logout' in output.lower() or 'welcome back' in output.lower()

6
couchpotato/core/providers/userscript/criticker/__init__.py

@ -0,0 +1,6 @@
from .main import Criticker
def start():
return Criticker()
config = []

6
couchpotato/core/providers/userscript/criticker/main.py

@ -0,0 +1,6 @@
from couchpotato.core.providers.userscript.base import UserscriptBase
class Criticker(UserscriptBase):
includes = ['http://www.criticker.com/film/*']

3
couchpotato/runner.py

@ -241,7 +241,8 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
from tornado.ioloop import IOLoop
web_container = WSGIContainer(app)
web_container._log = _log
loop = IOLoop.instance()
loop = IOLoop.current()
application = Application([
(r'%s/api/%s/nonblock/(.*)/' % (url_base, api_key), NonBlockHandler),

BIN
couchpotato/static/fonts/Elusive-Icons.eot

Binary file not shown.

298
couchpotato/static/fonts/Elusive-Icons.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 213 KiB

BIN
couchpotato/static/fonts/Elusive-Icons.ttf

Binary file not shown.

BIN
couchpotato/static/fonts/Elusive-Icons.woff

Binary file not shown.

BIN
couchpotato/static/fonts/OpenSans-Bold-webfont.eot

Binary file not shown.

146
couchpotato/static/fonts/OpenSans-Bold-webfont.svg

@ -0,0 +1,146 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
This is a custom SVG webfont generated by Font Squirrel.
Copyright : Digitized data copyright 20102011 Google Corporation
Foundry : Ascender Corporation
Foundry URL : httpwwwascendercorpcom
</metadata>
<defs>
<font id="OpenSansBold" horiz-adv-x="1169" >
<font-face units-per-em="2048" ascent="1638" descent="-410" />
<missing-glyph horiz-adv-x="532" />
<glyph unicode=" " horiz-adv-x="532" />
<glyph unicode="&#x09;" horiz-adv-x="532" />
<glyph unicode="&#xa0;" horiz-adv-x="532" />
<glyph unicode="!" horiz-adv-x="586" d="M117 143q0 84 45 127t131 43q83 0 128.5 -44t45.5 -126q0 -79 -46 -124.5t-128 -45.5q-84 0 -130 44.5t-46 125.5zM121 1462h346l-51 -977h-244z" />
<glyph unicode="&#x22;" horiz-adv-x="967" d="M133 1462h279l-41 -528h-197zM555 1462h279l-41 -528h-197z" />
<glyph unicode="#" horiz-adv-x="1323" d="M45 406v206h277l47 232h-252v209h289l77 407h219l-77 -407h198l78 407h215l-78 -407h240v-209h-279l-47 -232h258v-206h-297l-77 -406h-220l78 406h-194l-76 -406h-215l74 406h-238zM539 612h196l47 232h-196z" />
<glyph unicode="$" d="M88 1049q0 145 113.5 238.5t316.5 113.5v153h137v-149q229 -10 414 -92l-94 -234q-156 64 -320 78v-295q195 -75 277.5 -130t121 -121t38.5 -154q0 -159 -115 -255.5t-322 -115.5v-205h-137v201q-244 5 -428 86v264q87 -43 209.5 -76t218.5 -39v310l-67 26 q-198 78 -280.5 169.5t-82.5 226.5zM389 1049q0 -44 30.5 -72.5t98.5 -58.5v235q-129 -19 -129 -104zM655 324q136 23 136 118q0 42 -34 71t-102 60v-249z" />
<glyph unicode="%" horiz-adv-x="1845" d="M63 1026q0 457 345 457q169 0 259.5 -118.5t90.5 -338.5q0 -230 -89 -345.5t-261 -115.5q-165 0 -255 118.5t-90 342.5zM315 1024q0 -127 22.5 -189.5t72.5 -62.5q96 0 96 252q0 250 -96 250q-50 0 -72.5 -61.5t-22.5 -188.5zM395 0l811 1462h240l-811 -1462h-240z M1087 442q0 457 345 457q169 0 259.5 -118.5t90.5 -338.5q0 -229 -89 -344.5t-261 -115.5q-165 0 -255 118.5t-90 341.5zM1339 440q0 -127 22.5 -189.5t72.5 -62.5q96 0 96 252q0 250 -96 250q-50 0 -72.5 -61.5t-22.5 -188.5z" />
<glyph unicode="&#x26;" horiz-adv-x="1536" d="M82 395q0 137 60.5 233.5t207.5 180.5q-75 86 -109 164.5t-34 171.5q0 152 116.5 245t311.5 93q186 0 297.5 -86.5t111.5 -231.5q0 -119 -69 -217.5t-223 -187.5l284 -277q71 117 123 301h318q-36 -135 -99 -263.5t-143 -227.5l301 -293h-377l-115 113 q-191 -133 -432 -133q-244 0 -387 112t-143 303zM403 424q0 -86 64.5 -137t165.5 -51q126 0 227 61l-332 330q-58 -44 -91.5 -92t-33.5 -111zM489 1124q0 -88 95 -194q86 48 132 94.5t46 108.5q0 53 -36 83.5t-93 30.5q-67 0 -105.5 -32t-38.5 -91z" />
<glyph unicode="'" horiz-adv-x="545" d="M133 1462h279l-41 -528h-197z" />
<glyph unicode="(" horiz-adv-x="694" d="M82 561q0 265 77.5 496t223.5 405h250q-141 -193 -213 -424t-72 -475q0 -245 73.5 -473.5t209.5 -413.5h-248q-147 170 -224 397t-77 488z" />
<glyph unicode=")" horiz-adv-x="694" d="M61 1462h250q147 -175 224 -406.5t77 -494.5t-77.5 -490t-223.5 -395h-248q135 184 209 412.5t74 474.5q0 244 -72 475t-213 424z" />
<glyph unicode="*" horiz-adv-x="1116" d="M63 1042l39 250l365 -104l-41 368h262l-41 -368l373 104l33 -252l-340 -24l223 -297l-227 -121l-156 313l-137 -311l-236 119l221 297z" />
<glyph unicode="+" d="M88 612v219h387v390h219v-390h387v-219h-387v-385h-219v385h-387z" />
<glyph unicode="," horiz-adv-x="594" d="M63 -264q65 266 101 502h280l15 -23q-52 -202 -176 -479h-220z" />
<glyph unicode="-" horiz-adv-x="659" d="M61 424v250h537v-250h-537z" />
<glyph unicode="." horiz-adv-x="584" d="M117 143q0 84 45 127t131 43q83 0 128.5 -44t45.5 -126q0 -79 -46 -124.5t-128 -45.5q-84 0 -130 44.5t-46 125.5z" />
<glyph unicode="/" horiz-adv-x="846" d="M14 0l545 1462h277l-545 -1462h-277z" />
<glyph unicode="0" d="M74 731q0 387 125 570.5t385 183.5q253 0 382.5 -192t129.5 -562q0 -383 -125.5 -567t-386.5 -184q-253 0 -381.5 190t-128.5 561zM381 731q0 -269 46.5 -385.5t156.5 -116.5q108 0 156 118t48 384q0 269 -48.5 386.5t-155.5 117.5q-109 0 -156 -117.5t-47 -386.5z" />
<glyph unicode="1" d="M121 1087l471 375h254v-1462h-309v846l3 139l5 152q-77 -77 -107 -101l-168 -135z" />
<glyph unicode="2" d="M78 1274q108 92 179 130t155 58.5t188 20.5q137 0 242 -50t163 -140t58 -206q0 -101 -35.5 -189.5t-110 -181.5t-262.5 -265l-188 -177v-14h637v-260h-1022v215l367 371q163 167 213 231.5t72 119.5t22 114q0 88 -48.5 131t-129.5 43q-85 0 -165 -39t-167 -111z" />
<glyph unicode="3" d="M78 59v263q85 -43 187 -70t202 -27q153 0 226 52t73 167q0 103 -84 146t-268 43h-111v237h113q170 0 248.5 44.5t78.5 152.5q0 166 -208 166q-72 0 -146.5 -24t-165.5 -83l-143 213q200 144 477 144q227 0 358.5 -92t131.5 -256q0 -137 -83 -233t-233 -132v-6 q177 -22 268 -107.5t91 -230.5q0 -211 -153 -328.5t-437 -117.5q-238 0 -422 79z" />
<glyph unicode="4" d="M35 303v215l641 944h285v-919h176v-240h-176v-303h-302v303h-624zM307 543h352v248q0 62 5 180t8 137h-8q-37 -82 -89 -160z" />
<glyph unicode="5" d="M100 59v267q79 -42 184 -68.5t199 -26.5q283 0 283 232q0 221 -293 221q-53 0 -117 -10.5t-104 -22.5l-123 66l55 745h793v-262h-522l-27 -287l35 7q61 14 151 14q212 0 337.5 -119t125.5 -326q0 -245 -151 -377t-432 -132q-244 0 -394 79z" />
<glyph unicode="6" d="M72 621q0 434 183.5 646t549.5 212q125 0 196 -15v-247q-89 20 -176 20q-159 0 -259.5 -48t-150.5 -142t-59 -267h13q99 170 317 170q196 0 307 -123t111 -340q0 -234 -132 -370.5t-366 -136.5q-162 0 -282.5 75t-186 219t-65.5 347zM379 510q0 -119 62.5 -201t158.5 -82 q99 0 152 66.5t53 189.5q0 107 -49.5 168.5t-149.5 61.5q-94 0 -160.5 -61t-66.5 -142z" />
<glyph unicode="7" d="M55 1200v260h1049v-194l-553 -1266h-324l549 1200h-721z" />
<glyph unicode="8" d="M72 371q0 125 66.5 222t213.5 171q-125 79 -180 169t-55 197q0 157 130 254t339 97q210 0 338.5 -95.5t128.5 -257.5q0 -112 -62 -199.5t-200 -156.5q164 -88 235.5 -183.5t71.5 -209.5q0 -180 -141 -289.5t-371 -109.5q-240 0 -377 102t-137 289zM358 389q0 -86 60 -134 t164 -48q115 0 172 49.5t57 130.5q0 67 -56.5 125.5t-183.5 124.5q-213 -98 -213 -248zM408 1106q0 -60 38.5 -107.5t139.5 -97.5q98 46 137 94t39 111q0 69 -50 109t-128 40q-79 0 -127.5 -40.5t-48.5 -108.5z" />
<glyph unicode="9" d="M66 971q0 235 133.5 371.5t363.5 136.5q162 0 283.5 -76t186.5 -220.5t65 -344.5q0 -432 -182 -645t-551 -213q-130 0 -197 14v248q84 -21 176 -21q155 0 255 45.5t153 143t61 268.5h-12q-58 -94 -134 -132t-190 -38q-191 0 -301 122.5t-110 340.5zM365 975 q0 -106 49 -168t149 -62q94 0 161 61.5t67 141.5q0 119 -62.5 201t-159.5 82q-96 0 -150 -66t-54 -190z" />
<glyph unicode=":" horiz-adv-x="584" d="M117 143q0 84 45 127t131 43q83 0 128.5 -44t45.5 -126q0 -79 -46 -124.5t-128 -45.5q-84 0 -130 44.5t-46 125.5zM117 969q0 84 45 127t131 43q83 0 128.5 -44t45.5 -126q0 -81 -46.5 -125.5t-127.5 -44.5q-84 0 -130 44t-46 126z" />
<glyph unicode=";" horiz-adv-x="594" d="M63 -264q65 266 101 502h280l15 -23q-52 -202 -176 -479h-220zM117 969q0 84 45 127t131 43q83 0 128.5 -44t45.5 -126q0 -81 -46.5 -125.5t-127.5 -44.5q-84 0 -130 44t-46 126z" />
<glyph unicode="&#x3c;" d="M88 641v143l993 496v-240l-684 -317l684 -281v-239z" />
<glyph unicode="=" d="M88 418v219h993v-219h-993zM88 805v219h993v-219h-993z" />
<glyph unicode="&#x3e;" d="M88 203v239l684 281l-684 317v240l993 -496v-143z" />
<glyph unicode="?" horiz-adv-x="977" d="M6 1358q223 125 473 125q206 0 327.5 -99t121.5 -264q0 -110 -50 -190t-190 -180q-96 -71 -121.5 -108t-25.5 -97v-60h-265v74q0 96 41 167t150 151q105 75 138.5 122t33.5 105q0 65 -48 99t-134 34q-150 0 -342 -98zM244 143q0 84 45 127t131 43q83 0 128.5 -44 t45.5 -126q0 -79 -46 -124.5t-128 -45.5q-84 0 -130 44.5t-46 125.5z" />
<glyph unicode="@" horiz-adv-x="1837" d="M102 602q0 247 108.5 448.5t309 316t461.5 114.5q220 0 393 -90t267 -256t94 -383q0 -144 -46 -263.5t-130 -187.5t-195 -68q-74 0 -131 35.5t-82 93.5h-16q-108 -129 -275 -129q-177 0 -279 106.5t-102 291.5q0 211 134 340t350 129q86 0 189.5 -16.5t170.5 -39.5 l-23 -489q0 -139 76 -139q64 0 102 93.5t38 244.5q0 161 -67 284.5t-188.5 188.5t-277.5 65q-202 0 -351 -83t-228.5 -239.5t-79.5 -361.5q0 -276 147.5 -423.5t427.5 -147.5q106 0 233 23.5t250 68.5v-192q-214 -91 -475 -91q-380 0 -592.5 200t-212.5 556zM711 627 q0 -211 172 -211q90 0 137 63.5t57 206.5l13 221q-51 11 -115 11q-125 0 -194.5 -78t-69.5 -213z" />
<glyph unicode="A" horiz-adv-x="1413" d="M0 0l516 1468h379l518 -1468h-334l-106 348h-533l-106 -348h-334zM518 608h381q-147 473 -165.5 535t-26.5 98q-33 -128 -189 -633z" />
<glyph unicode="B" horiz-adv-x="1376" d="M184 0v1462h455q311 0 451.5 -88.5t140.5 -281.5q0 -131 -61.5 -215t-163.5 -101v-10q139 -31 200.5 -116t61.5 -226q0 -200 -144.5 -312t-392.5 -112h-547zM494 256h202q128 0 189 49t61 150q0 182 -260 182h-192v-381zM494 883h180q126 0 182.5 39t56.5 129 q0 84 -61.5 120.5t-194.5 36.5h-163v-325z" />
<glyph unicode="C" horiz-adv-x="1305" d="M119 729q0 228 83 399.5t238.5 263t364.5 91.5q213 0 428 -103l-100 -252q-82 39 -165 68t-163 29q-175 0 -271 -131.5t-96 -366.5q0 -489 367 -489q154 0 373 77v-260q-180 -75 -402 -75q-319 0 -488 193.5t-169 555.5z" />
<glyph unicode="D" horiz-adv-x="1516" d="M184 0v1462h459q358 0 556 -189t198 -528q0 -361 -205.5 -553t-593.5 -192h-414zM494 256h133q448 0 448 481q0 471 -416 471h-165v-952z" />
<glyph unicode="E" horiz-adv-x="1147" d="M184 0v1462h842v-254h-532v-321h495v-254h-495v-377h532v-256h-842z" />
<glyph unicode="F" horiz-adv-x="1124" d="M184 0v1462h838v-254h-533v-377h496v-253h-496v-578h-305z" />
<glyph unicode="G" horiz-adv-x="1483" d="M119 733q0 354 202.5 552t561.5 198q225 0 434 -90l-103 -248q-160 80 -333 80q-201 0 -322 -135t-121 -363q0 -238 97.5 -363.5t283.5 -125.5q97 0 197 20v305h-277v258h580v-758q-141 -46 -265.5 -64.5t-254.5 -18.5q-331 0 -505.5 194.5t-174.5 558.5z" />
<glyph unicode="H" horiz-adv-x="1567" d="M184 0v1462h310v-573h579v573h309v-1462h-309v631h-579v-631h-310z" />
<glyph unicode="I" horiz-adv-x="678" d="M184 0v1462h310v-1462h-310z" />
<glyph unicode="J" horiz-adv-x="678" d="M-152 -150q80 -20 146 -20q102 0 146 63.5t44 198.5v1370h310v-1368q0 -256 -117 -390t-346 -134q-105 0 -183 22v258z" />
<glyph unicode="K" horiz-adv-x="1360" d="M184 0v1462h310v-669l122 172l396 497h344l-510 -647l514 -815h-352l-383 616l-131 -94v-522h-310z" />
<glyph unicode="L" horiz-adv-x="1157" d="M184 0v1462h310v-1206h593v-256h-903z" />
<glyph unicode="M" horiz-adv-x="1931" d="M184 0v1462h422l346 -1118h6l367 1118h422v-1462h-289v692q0 49 1.5 113t13.5 340h-9l-377 -1145h-284l-352 1147h-9q19 -350 19 -467v-680h-277z" />
<glyph unicode="N" horiz-adv-x="1665" d="M184 0v1462h391l635 -1095h7q-15 285 -15 403v692h279v-1462h-394l-636 1106h-9q19 -293 19 -418v-688h-277z" />
<glyph unicode="O" horiz-adv-x="1630" d="M119 735q0 365 180.5 557.5t517.5 192.5t515.5 -194t178.5 -558q0 -363 -180 -558t-516 -195t-516 195t-180 560zM444 733q0 -245 93 -369t278 -124q371 0 371 493q0 494 -369 494q-185 0 -279 -124.5t-94 -369.5z" />
<glyph unicode="P" horiz-adv-x="1286" d="M184 0v1462h467q266 0 404.5 -114.5t138.5 -341.5q0 -236 -147.5 -361t-419.5 -125h-133v-520h-310zM494 774h102q143 0 214 56.5t71 164.5q0 109 -59.5 161t-186.5 52h-141v-434z" />
<glyph unicode="Q" horiz-adv-x="1630" d="M119 735q0 365 180.5 557.5t517.5 192.5t515.5 -194t178.5 -558q0 -258 -91.5 -432.5t-268.5 -255.5l352 -393h-397l-268 328h-23q-336 0 -516 195t-180 560zM444 733q0 -245 93 -369t278 -124q371 0 371 493q0 494 -369 494q-185 0 -279 -124.5t-94 -369.5z" />
<glyph unicode="R" horiz-adv-x="1352" d="M184 0v1462h426q298 0 441 -108.5t143 -329.5q0 -129 -71 -229.5t-201 -157.5q330 -493 430 -637h-344l-349 561h-165v-561h-310zM494 813h100q147 0 217 49t70 154q0 104 -71.5 148t-221.5 44h-94v-395z" />
<glyph unicode="S" horiz-adv-x="1128" d="M94 68v288q148 -66 250.5 -93t187.5 -27q102 0 156.5 39t54.5 116q0 43 -24 76.5t-70.5 64.5t-189.5 99q-134 63 -201 121t-107 135t-40 180q0 194 131.5 305t363.5 111q114 0 217.5 -27t216.5 -76l-100 -241q-117 48 -193.5 67t-150.5 19q-88 0 -135 -41t-47 -107 q0 -41 19 -71.5t60.5 -59t196.5 -102.5q205 -98 281 -196.5t76 -241.5q0 -198 -142.5 -312t-396.5 -114q-234 0 -414 88z" />
<glyph unicode="T" horiz-adv-x="1186" d="M41 1204v258h1104v-258h-397v-1204h-310v1204h-397z" />
<glyph unicode="U" horiz-adv-x="1548" d="M174 520v942h309v-895q0 -169 68 -248t225 -79q152 0 220.5 79.5t68.5 249.5v893h309v-946q0 -162 -72.5 -284t-209.5 -187t-324 -65q-282 0 -438 144.5t-156 395.5z" />
<glyph unicode="V" horiz-adv-x="1331" d="M0 1462h313l275 -870q23 -77 47.5 -179.5t30.5 -142.5q11 92 75 322l277 870h313l-497 -1462h-338z" />
<glyph unicode="W" horiz-adv-x="1980" d="M0 1462h305l187 -798q49 -221 71 -383q6 57 27.5 176.5t40.5 185.5l213 819h293l213 -819q14 -55 35 -168t32 -194q10 78 32 194.5t40 188.5l186 798h305l-372 -1462h-353l-198 768q-11 41 -37.5 169.5t-30.5 172.5q-6 -54 -30 -173.5t-37 -170.5l-197 -766h-352z" />
<glyph unicode="X" horiz-adv-x="1366" d="M0 0l485 754l-454 708h342l315 -526l309 526h334l-459 -725l494 -737h-354l-340 553l-340 -553h-332z" />
<glyph unicode="Y" horiz-adv-x="1278" d="M0 1462h336l303 -602l305 602h334l-485 -893v-569h-308v559z" />
<glyph unicode="Z" horiz-adv-x="1186" d="M49 0v201l701 1005h-682v256h1050v-200l-700 -1006h719v-256h-1088z" />
<glyph unicode="[" horiz-adv-x="678" d="M143 -324v1786h484v-211h-224v-1364h224v-211h-484z" />
<glyph unicode="\" horiz-adv-x="846" d="M12 1462h277l545 -1462h-277z" />
<glyph unicode="]" horiz-adv-x="678" d="M51 -113h223v1364h-223v211h484v-1786h-484v211z" />
<glyph unicode="^" horiz-adv-x="1090" d="M8 520l438 950h144l495 -950h-239l-322 643l-280 -643h-236z" />
<glyph unicode="_" horiz-adv-x="842" d="M-4 -184h850v-140h-850v140z" />
<glyph unicode="`" horiz-adv-x="1243" d="M332 1548v21h342q63 -101 235 -301v-27h-202q-63 44 -185 142.5t-190 164.5z" />
<glyph unicode="a" horiz-adv-x="1237" d="M86 334q0 178 124.5 262.5t375.5 93.5l194 6v49q0 170 -174 170q-134 0 -315 -81l-101 206q193 101 428 101q225 0 345 -98t120 -298v-745h-213l-59 152h-8q-77 -97 -158.5 -134.5t-212.5 -37.5q-161 0 -253.5 92t-92.5 262zM399 332q0 -129 148 -129q106 0 169.5 61 t63.5 162v92l-118 -4q-133 -4 -198 -48t-65 -134z" />
<glyph unicode="b" horiz-adv-x="1296" d="M160 0v1556h305v-362q0 -69 -12 -221h12q107 166 317 166q198 0 310 -154.5t112 -423.5q0 -277 -115.5 -429t-314.5 -152q-197 0 -309 143h-21l-51 -123h-233zM465 563q0 -180 53.5 -258t169.5 -78q94 0 149.5 86.5t55.5 251.5t-56 247.5t-153 82.5q-113 0 -165 -69.5 t-54 -229.5v-33z" />
<glyph unicode="c" horiz-adv-x="1053" d="M92 553q0 285 142 435.5t407 150.5q194 0 348 -76l-90 -236q-72 29 -134 47.5t-124 18.5q-238 0 -238 -338q0 -328 238 -328q88 0 163 23.5t150 73.5v-261q-74 -47 -149.5 -65t-190.5 -18q-522 0 -522 573z" />
<glyph unicode="d" horiz-adv-x="1296" d="M92 557q0 275 114.5 428.5t315.5 153.5q211 0 322 -164h10q-23 125 -23 223v358h306v-1556h-234l-59 145h-13q-104 -165 -317 -165q-197 0 -309.5 153t-112.5 424zM401 553q0 -165 57 -247.5t163 -82.5q117 0 171.5 68t59.5 231v33q0 180 -55.5 258t-180.5 78 q-102 0 -158.5 -86.5t-56.5 -251.5z" />
<glyph unicode="e" horiz-adv-x="1210" d="M92 551q0 281 140.5 434.5t388.5 153.5q237 0 369 -135t132 -373v-148h-721q5 -130 77 -203t202 -73q101 0 191 21t188 67v-236q-80 -40 -171 -59.5t-222 -19.5q-270 0 -422 149t-152 422zM408 686h428q-2 113 -59 174.5t-154 61.5t-152 -61.5t-63 -174.5z" />
<glyph unicode="f" horiz-adv-x="793" d="M41 889v147l168 82v82q0 191 94 279t301 88q158 0 281 -47l-78 -224q-92 29 -170 29q-65 0 -94 -38.5t-29 -98.5v-70h264v-229h-264v-889h-305v889h-168z" />
<glyph unicode="g" horiz-adv-x="1157" d="M6 -182q0 101 63 169t185 97q-47 20 -82 65.5t-35 96.5q0 64 37 106.5t107 83.5q-88 38 -139.5 122t-51.5 198q0 183 119 283t340 100q47 0 111.5 -8.5t82.5 -12.5h390v-155l-175 -45q48 -75 48 -168q0 -180 -125.5 -280.5t-348.5 -100.5l-55 3l-45 5q-47 -36 -47 -80 q0 -66 168 -66h190q184 0 280.5 -79t96.5 -232q0 -196 -163.5 -304t-469.5 -108q-234 0 -357.5 81.5t-123.5 228.5zM270 -158q0 -63 60.5 -99t169.5 -36q164 0 257 45t93 123q0 63 -55 87t-170 24h-158q-84 0 -140.5 -39.5t-56.5 -104.5zM381 752q0 -91 41.5 -144t126.5 -53 q86 0 126 53t40 144q0 202 -166 202q-168 0 -168 -202z" />
<glyph unicode="h" horiz-adv-x="1346" d="M160 0v1556h305v-317q0 -37 -7 -174l-7 -90h16q102 164 324 164q197 0 299 -106t102 -304v-729h-305v653q0 242 -180 242q-128 0 -185 -87t-57 -282v-526h-305z" />
<glyph unicode="i" horiz-adv-x="625" d="M147 1407q0 149 166 149t166 -149q0 -71 -41.5 -110.5t-124.5 -39.5q-166 0 -166 150zM160 0v1118h305v-1118h-305z" />
<glyph unicode="j" horiz-adv-x="625" d="M-131 -227q70 -19 143 -19q77 0 112.5 43t35.5 127v1194h305v-1239q0 -178 -103 -274.5t-292 -96.5q-117 0 -201 25v240zM147 1407q0 149 166 149t166 -149q0 -71 -41.5 -110.5t-124.5 -39.5q-166 0 -166 150z" />
<glyph unicode="k" horiz-adv-x="1270" d="M160 0v1556h305v-694l-16 -254h4l133 170l313 340h344l-444 -485l471 -633h-352l-322 453l-131 -105v-348h-305z" />
<glyph unicode="l" horiz-adv-x="625" d="M160 0v1556h305v-1556h-305z" />
<glyph unicode="m" horiz-adv-x="2011" d="M160 0v1118h233l41 -143h17q45 77 130 120.5t195 43.5q251 0 340 -164h27q45 78 132.5 121t197.5 43q190 0 287.5 -97.5t97.5 -312.5v-729h-306v653q0 121 -40.5 181.5t-127.5 60.5q-112 0 -167.5 -80t-55.5 -254v-561h-305v653q0 121 -40.5 181.5t-127.5 60.5 q-117 0 -170 -86t-53 -283v-526h-305z" />
<glyph unicode="n" horiz-adv-x="1346" d="M160 0v1118h233l41 -143h17q51 81 140.5 122.5t203.5 41.5q195 0 296 -105.5t101 -304.5v-729h-305v653q0 121 -43 181.5t-137 60.5q-128 0 -185 -85.5t-57 -283.5v-526h-305z" />
<glyph unicode="o" horiz-adv-x="1268" d="M92 561q0 274 143 426t402 152q161 0 284 -70t189 -201t66 -307q0 -273 -144 -427t-401 -154q-161 0 -284 70.5t-189 202.5t-66 308zM403 561q0 -166 54.5 -251t177.5 -85q122 0 175.5 84.5t53.5 251.5q0 166 -54 249t-177 83q-122 0 -176 -82.5t-54 -249.5z" />
<glyph unicode="p" horiz-adv-x="1296" d="M160 -492v1610h248l43 -145h14q107 166 317 166q198 0 310 -153t112 -425q0 -179 -52.5 -311t-149.5 -201t-228 -69q-197 0 -309 143h-16q16 -140 16 -162v-453h-305zM465 563q0 -180 53.5 -258t169.5 -78q205 0 205 338q0 165 -50.5 247.5t-158.5 82.5 q-113 0 -165 -69.5t-54 -229.5v-33z" />
<glyph unicode="q" horiz-adv-x="1296" d="M92 557q0 274 114.5 428t313.5 154q106 0 185 -40t139 -124h8l27 143h258v-1610h-306v469q0 61 13 168h-13q-49 -81 -130 -123t-187 -42q-198 0 -310 152.5t-112 424.5zM403 553q0 -168 53.5 -251t166.5 -83q116 0 170 66.5t59 232.5v37q0 180 -55.5 258t-178.5 78 q-215 0 -215 -338z" />
<glyph unicode="r" horiz-adv-x="930" d="M160 0v1118h231l45 -188h15q52 94 140.5 151.5t192.5 57.5q62 0 103 -9l-23 -286q-37 10 -90 10q-146 0 -227.5 -75t-81.5 -210v-569h-305z" />
<glyph unicode="s" horiz-adv-x="1018" d="M92 827q0 149 115.5 230.5t327.5 81.5q202 0 393 -88l-92 -220q-84 36 -157 59t-149 23q-135 0 -135 -73q0 -41 43.5 -71t190.5 -89q131 -53 192 -99t90 -106t29 -143q0 -172 -119.5 -262t-357.5 -90q-122 0 -208 16.5t-161 48.5v252q85 -40 191.5 -67t187.5 -27 q166 0 166 96q0 36 -22 58.5t-76 51t-144 66.5q-129 54 -189.5 100t-88 105.5t-27.5 146.5z" />
<glyph unicode="t" horiz-adv-x="889" d="M47 889v129l168 102l88 236h195v-238h313v-229h-313v-539q0 -65 36.5 -96t96.5 -31q80 0 192 35v-227q-114 -51 -280 -51q-183 0 -266.5 92.5t-83.5 277.5v539h-146z" />
<glyph unicode="u" horiz-adv-x="1346" d="M154 389v729h305v-653q0 -121 43 -181.5t137 -60.5q128 0 185 85.5t57 283.5v526h305v-1118h-234l-41 143h-16q-49 -78 -139 -120.5t-205 -42.5q-197 0 -297 105.5t-100 303.5z" />
<glyph unicode="v" horiz-adv-x="1165" d="M0 1118h319l216 -637q36 -121 45 -229h6q5 96 45 229l215 637h319l-426 -1118h-313z" />
<glyph unicode="w" horiz-adv-x="1753" d="M20 1118h304l129 -495q31 -133 63 -367h6q4 76 35 241l16 85l138 536h336l131 -536q4 -22 12.5 -65t16.5 -91.5t14.5 -95t7.5 -74.5h6q9 72 32 197.5t33 169.5l134 495h299l-322 -1118h-332l-86 391l-116 494h-7l-204 -885h-328z" />
<glyph unicode="x" horiz-adv-x="1184" d="M10 0l379 571l-360 547h346l217 -356l219 356h346l-364 -547l381 -571h-347l-235 383l-236 -383h-346z" />
<glyph unicode="y" horiz-adv-x="1165" d="M0 1118h334l211 -629q27 -82 37 -194h6q11 103 43 194l207 629h327l-473 -1261q-65 -175 -185.5 -262t-281.5 -87q-79 0 -155 17v242q55 -13 120 -13q81 0 141.5 49.5t94.5 149.5l18 55z" />
<glyph unicode="z" horiz-adv-x="999" d="M55 0v180l518 705h-487v233h834v-198l-504 -687h522v-233h-883z" />
<glyph unicode="{" horiz-adv-x="807" d="M31 449v239q126 0 191 44t65 126v8v318q0 153 97 215.5t341 62.5v-225q-99 -3 -136.5 -38t-37.5 -103v-299q-6 -188 -234 -222v-12q234 -35 234 -212v-9v-299q0 -68 37 -103t137 -38v-226q-244 0 -341 62.5t-97 216.5v315q0 87 -65.5 133t-190.5 46z" />
<glyph unicode="|" horiz-adv-x="1128" d="M455 -465v2015h219v-2015h-219z" />
<glyph unicode="}" horiz-adv-x="807" d="M82 -98q99 2 136.5 36t37.5 105v299v11q0 86 59 139.5t174 70.5v12q-227 34 -233 222v299q0 70 -37 104t-137 37v225q167 0 262 -26.5t135.5 -84t40.5 -167.5v-318v-10q0 -84 61.5 -126t194.5 -42v-239q-125 0 -190.5 -41t-65.5 -138v-315q0 -112 -41 -169t-135.5 -83.5 t-261.5 -26.5v226z" />
<glyph unicode="~" d="M88 551v231q103 109 256 109q73 0 137.5 -16t139.5 -48q129 -55 227 -55q53 0 116 32t117 89v-231q-101 -109 -256 -109q-66 0 -126 13t-150 50q-131 56 -227 56q-55 0 -117.5 -33.5t-116.5 -87.5z" />
<glyph unicode="&#xa2;" d="M143 741q0 261 104.5 403t315.5 173v166h178v-158q166 -9 299 -74l-90 -235q-72 29 -134 47t-124 18q-121 0 -179 -83.5t-58 -254.5q0 -327 237 -327q82 0 148 15.5t166 60.5v-254q-127 -61 -265 -70v-188h-178v196q-420 59 -420 565z" />
<glyph unicode="&#xa3;" d="M82 0v248q103 44 141.5 101t38.5 157v145h-178v219h178v195q0 201 114.5 309.5t323.5 108.5q195 0 390 -82l-93 -230q-157 64 -272 64q-78 0 -120 -44.5t-42 -127.5v-193h375v-219h-375v-143q0 -170 -151 -248h718v-260h-1048z" />
<glyph unicode="&#xa5;" d="M6 1462h316l262 -602l264 602h313l-383 -747h195v-178h-246v-138h246v-178h-246v-221h-287v221h-247v178h247v138h-247v178h190z" />
<glyph unicode="&#xa9;" horiz-adv-x="1704" d="M100 731q0 200 100 375t275 276t377 101q200 0 375 -100t276 -275t101 -377q0 -197 -97 -370t-272 -277t-383 -104q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM242 731q0 -164 82 -305.5t224 -223t304 -81.5q164 0 305.5 82t223 224t81.5 304q0 164 -82 305.5t-224 223 t-304 81.5q-164 0 -305.5 -82t-223 -224t-81.5 -304zM461 733q0 220 110.5 342.5t309.5 122.5q149 0 305 -78l-74 -168q-113 58 -217 58q-97 0 -150 -74t-53 -205q0 -280 203 -280q57 0 123 15t123 44v-191q-120 -57 -252 -57q-204 0 -316 125t-112 346z" />
<glyph unicode="&#xad;" horiz-adv-x="659" d="M61 424v250h537v-250h-537z" />
<glyph unicode="&#xae;" horiz-adv-x="1704" d="M100 731q0 200 100 375t275 276t377 101q200 0 375 -100t276 -275t101 -377q0 -197 -97 -370t-272 -277t-383 -104q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM242 731q0 -164 82 -305.5t224 -223t304 -81.5q164 0 305.5 82t223 224t81.5 304q0 164 -82 305.5t-224 223 t-304 81.5q-164 0 -305.5 -82t-223 -224t-81.5 -304zM543 272v916h264q181 0 265.5 -70t84.5 -213q0 -170 -143 -233l237 -400h-254l-178 338h-47v-338h-229zM772 778h31q66 0 94.5 28.5t28.5 94.5q0 65 -28 92t-97 27h-29v-242z" />
<glyph unicode="&#xb4;" horiz-adv-x="1243" d="M332 1241v27q172 200 235 301h342v-21q-52 -52 -177.5 -154.5t-196.5 -152.5h-203z" />
<glyph unicode="&#x2000;" horiz-adv-x="784" />
<glyph unicode="&#x2001;" horiz-adv-x="1569" />
<glyph unicode="&#x2002;" horiz-adv-x="784" />
<glyph unicode="&#x2003;" horiz-adv-x="1569" />
<glyph unicode="&#x2004;" horiz-adv-x="523" />
<glyph unicode="&#x2005;" horiz-adv-x="392" />
<glyph unicode="&#x2006;" horiz-adv-x="261" />
<glyph unicode="&#x2007;" horiz-adv-x="261" />
<glyph unicode="&#x2008;" horiz-adv-x="196" />
<glyph unicode="&#x2009;" horiz-adv-x="313" />
<glyph unicode="&#x200a;" horiz-adv-x="87" />
<glyph unicode="&#x2010;" horiz-adv-x="659" d="M61 424v250h537v-250h-537z" />
<glyph unicode="&#x2011;" horiz-adv-x="659" d="M61 424v250h537v-250h-537z" />
<glyph unicode="&#x2012;" horiz-adv-x="659" d="M61 424v250h537v-250h-537z" />
<glyph unicode="&#x2013;" horiz-adv-x="1024" d="M82 436v230h860v-230h-860z" />
<glyph unicode="&#x2014;" horiz-adv-x="2048" d="M82 436v230h1884v-230h-1884z" />
<glyph unicode="&#x2018;" horiz-adv-x="444" d="M25 983q22 91 72.5 228.5t103.5 250.5h219q-66 -267 -101 -501h-280z" />
<glyph unicode="&#x2019;" horiz-adv-x="444" d="M25 961q69 296 100 501h281l14 -22q-50 -197 -176 -479h-219z" />
<glyph unicode="&#x201c;" horiz-adv-x="911" d="M25 983q22 91 72.5 228.5t103.5 250.5h219q-66 -267 -101 -501h-280zM492 983q22 91 72.5 228.5t103.5 250.5h219q-66 -267 -101 -501h-280z" />
<glyph unicode="&#x201d;" horiz-adv-x="911" d="M25 961q69 296 100 501h281l14 -22q-50 -197 -176 -479h-219zM492 961q69 296 100 501h280l15 -22q-50 -197 -176 -479h-219z" />
<glyph unicode="&#x2022;" horiz-adv-x="770" d="M98 748q0 154 74 235.5t213 81.5q137 0 212 -82t75 -235q0 -152 -75.5 -235t-211.5 -83q-138 0 -212.5 83t-74.5 235z" />
<glyph unicode="&#x2026;" horiz-adv-x="1751" d="M117 143q0 84 45 127t131 43q83 0 128.5 -44t45.5 -126q0 -79 -46 -124.5t-128 -45.5q-84 0 -130 44.5t-46 125.5zM700 143q0 84 45 127t132 43q83 0 128.5 -44t45.5 -126q0 -79 -46 -124.5t-128 -45.5q-85 0 -131 44.5t-46 125.5zM1284 143q0 84 45 127t131 43 q83 0 128.5 -44t45.5 -126q0 -79 -46 -124.5t-128 -45.5q-84 0 -130 44.5t-46 125.5z" />
<glyph unicode="&#x202f;" horiz-adv-x="313" />
<glyph unicode="&#x205f;" horiz-adv-x="392" />
<glyph unicode="&#x20ac;" d="M66 481v178h118q-4 23 -4 62l2 53h-116v176h133q37 242 199 382.5t405 140.5q188 0 352 -82l-98 -232q-69 31 -129 48.5t-125 17.5q-122 0 -201 -70.5t-102 -204.5h403v-176h-418l-2 -35v-47l2 -33h355v-178h-338q51 -243 321 -243q143 0 275 57v-256q-116 -59 -293 -59 q-245 0 -403 133t-199 368h-137z" />
<glyph unicode="&#x2122;" horiz-adv-x="1534" d="M16 1313v149h564v-149h-199v-572h-168v572h-197zM625 741v721h247l160 -510l170 510h240v-721h-168v408l4 121h-6l-174 -529h-142l-165 529h-7l4 -111v-418h-163z" />
<glyph unicode="&#xe000;" horiz-adv-x="1120" d="M0 1120h1120v-1120h-1120v1120z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 25 KiB

BIN
couchpotato/static/fonts/OpenSans-Bold-webfont.ttf

Binary file not shown.

BIN
couchpotato/static/fonts/OpenSans-Bold-webfont.woff

Binary file not shown.

BIN
couchpotato/static/fonts/OpenSans-BoldItalic-webfont.eot

Binary file not shown.

146
couchpotato/static/fonts/OpenSans-BoldItalic-webfont.svg

@ -0,0 +1,146 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
This is a custom SVG webfont generated by Font Squirrel.
Copyright : Digitized data copyright 20102011 Google Corporation
Foundry : Ascender Corporation
Foundry URL : httpwwwascendercorpcom
</metadata>
<defs>
<font id="OpenSansBoldItalic" horiz-adv-x="1128" >
<font-face units-per-em="2048" ascent="1638" descent="-410" />
<missing-glyph horiz-adv-x="532" />
<glyph unicode=" " horiz-adv-x="532" />
<glyph unicode="&#x09;" horiz-adv-x="532" />
<glyph unicode="&#xa0;" horiz-adv-x="532" />
<glyph unicode="!" horiz-adv-x="586" d="M25 115q0 90 53.5 144t150.5 54q68 0 109 -38t41 -107q0 -87 -55 -141t-144 -54q-73 0 -114 37.5t-41 104.5zM150 485l157 977h340l-256 -977h-241z" />
<glyph unicode="&#x22;" horiz-adv-x="928" d="M201 934l71 528h277l-152 -528h-196zM604 934l74 528h276l-151 -528h-199z" />
<glyph unicode="#" horiz-adv-x="1323" d="M41 408l18 206h277l70 232h-252l18 209h289l119 407h217l-117 -407h199l116 407h215l-116 -407h239l-18 -209h-279l-69 -232h258l-19 -206h-297l-116 -408h-220l117 408h-194l-115 -408h-215l113 408h-238zM553 614h197l69 232h-196z" />
<glyph unicode="$" d="M51 168v266q198 -107 404 -117l71 322q-163 61 -241 151t-78 214q0 173 127 279.5t350 121.5l35 151h139l-33 -151q166 -22 295 -90l-106 -232q-132 65 -242 74l-63 -299q131 -51 195 -99.5t97 -113t33 -149.5q0 -184 -125.5 -291.5t-367.5 -124.5l-39 -199h-140l44 201 q-209 12 -355 86zM502 1022q0 -79 80 -111l51 246q-62 -7 -96.5 -41t-34.5 -94zM594 322q63 9 102 45t39 98q0 46 -24.5 75.5t-59.5 43.5z" />
<glyph unicode="%" horiz-adv-x="1753" d="M115 885q0 169 55.5 311.5t148.5 214.5t216 72q137 0 211.5 -80t74.5 -238q0 -166 -56 -310t-151 -217t-217 -73q-139 0 -210.5 83.5t-71.5 236.5zM231 0l1088 1462h235l-1083 -1462h-240zM360 868q0 -96 56 -96q65 0 112 131t47 275q0 96 -57 96q-63 0 -110.5 -128.5 t-47.5 -277.5zM973 283q0 177 53 322.5t148 219.5t219 74q137 0 211.5 -78.5t74.5 -230.5q0 -167 -54 -313.5t-148 -220.5t-215 -74q-144 0 -216.5 78.5t-72.5 222.5zM1219 285q0 -97 55 -97q41 0 77 55t59.5 154.5t23.5 196.5q0 96 -58 96q-39 0 -75 -56t-59 -154t-23 -195 z" />
<glyph unicode="&#x26;" horiz-adv-x="1450" d="M68 358q0 145 78.5 248.5t273.5 200.5q-76 130 -76 258q0 195 117.5 307.5t316.5 112.5q169 0 266 -82.5t97 -224.5q0 -280 -365 -426l195 -263q44 57 80.5 121.5t78.5 173.5h300q-133 -313 -310 -497l205 -287h-350l-72 98q-175 -118 -403 -118q-209 0 -320.5 97.5 t-111.5 280.5zM383 387q0 -65 45.5 -108t116.5 -43q115 0 221 59l-225 328q-88 -51 -123 -104.5t-35 -131.5zM621 1085q0 -46 12 -92t29 -73q113 59 155.5 111t42.5 112q0 57 -30 82.5t-70 25.5q-66 0 -102.5 -46.5t-36.5 -119.5z" />
<glyph unicode="'" horiz-adv-x="522" d="M201 934l71 528h277l-152 -528h-196z" />
<glyph unicode="(" horiz-adv-x="694" d="M74 281q0 339 122.5 626.5t381.5 554.5h262q-255 -278 -377.5 -573.5t-122.5 -618.5q0 -308 117 -594h-234q-149 266 -149 605z" />
<glyph unicode=")" horiz-adv-x="694" d="M-147 -324q499 545 499 1192q0 307 -116 594h233q149 -264 149 -604q0 -342 -124 -630.5t-379 -551.5h-262z" />
<glyph unicode="*" horiz-adv-x="1116" d="M172 1141l86 237l338 -174l33 369l256 -51l-113 -353l387 29l-18 -254l-338 43l160 -336l-246 -73l-90 337l-197 -278l-207 164l275 248z" />
<glyph unicode="+" d="M109 612v219h366v369h219v-369h367v-219h-367v-364h-219v364h-366z" />
<glyph unicode="," horiz-adv-x="569" d="M-102 -264q74 167 194 502h285l8 -23q-118 -255 -262 -479h-225z" />
<glyph unicode="-" horiz-adv-x="659" d="M41 424l53 250h524l-53 -250h-524z" />
<glyph unicode="." horiz-adv-x="584" d="M25 115q0 90 53.5 144t150.5 54q68 0 109 -38t41 -107q0 -87 -55 -141t-144 -54q-73 0 -114 37.5t-41 104.5z" />
<glyph unicode="/" horiz-adv-x="862" d="M-90 0l809 1462h295l-809 -1462h-295z" />
<glyph unicode="0" d="M66 467q0 297 84 537t228 360.5t333 120.5q399 0 399 -473q0 -470 -168.5 -751t-472.5 -281q-198 0 -300.5 122t-102.5 365zM369 461q0 -115 27.5 -173.5t97.5 -58.5q81 0 150.5 106t116 301t46.5 386q0 111 -30.5 162t-92.5 51q-80 0 -149.5 -104t-117.5 -302t-48 -368z " />
<glyph unicode="1" d="M182 1114l566 348h249l-309 -1462h-305l180 829q35 152 76 287q-9 -8 -61.5 -47t-262.5 -170z" />
<glyph unicode="2" d="M-49 0l43 213l477 424q180 159 248.5 254.5t68.5 179.5q0 75 -41 114.5t-110 39.5q-66 0 -135.5 -33.5t-171.5 -118.5l-146 203q132 112 252 159.5t250 47.5q190 0 301 -98t111 -259q0 -107 -41 -201t-122.5 -188t-266.5 -245l-269 -222v-10h568l-54 -260h-962z" />
<glyph unicode="3" d="M14 59v267q84 -50 182 -75.5t191 -25.5q158 0 243 63.5t85 176.5q0 172 -258 172h-138l46 221h73q167 0 263 62t96 172q0 67 -43 104t-121 37q-134 0 -287 -100l-127 204q124 81 232.5 113.5t246.5 32.5q190 0 298 -90.5t108 -243.5q0 -156 -94.5 -262t-261.5 -135v-4 q131 -26 198.5 -106.5t67.5 -201.5q0 -133 -74 -238t-212 -163.5t-327 -58.5q-239 0 -387 79z" />
<glyph unicode="4" d="M-25 303l48 234l770 925h311l-195 -919h170l-51 -240h-170l-63 -303h-293l63 303h-590zM305 543h311l58 248q12 58 40 164t42 141h-6q-35 -63 -132 -181z" />
<glyph unicode="5" d="M27 61v269q174 -99 352 -99q154 0 241 71t87 194q0 94 -57.5 141t-166.5 47q-102 0 -213 -33l-104 78l207 733h755l-55 -262h-489l-88 -293q72 15 127 15q183 0 289 -103t106 -287q0 -167 -71.5 -292t-208.5 -192.5t-330 -67.5q-117 0 -218.5 23t-162.5 58z" />
<glyph unicode="6" d="M88 469q0 202 61 395.5t167.5 335t256.5 213.5t357 72q125 0 223 -27l-51 -246q-84 25 -191 25q-194 0 -313.5 -108t-185.5 -345h4q115 166 311 166q157 0 242.5 -97t85.5 -273q0 -169 -71 -313.5t-190.5 -215.5t-277.5 -71q-212 0 -320 127t-108 362zM383 422 q0 -91 40 -143t107 -52q99 0 161.5 94t62.5 236q0 71 -33.5 113.5t-102.5 42.5q-60 0 -114.5 -35.5t-87.5 -95.5t-33 -160z" />
<glyph unicode="7" d="M78 0l737 1202h-629l56 260h975l-41 -194l-752 -1268h-346z" />
<glyph unicode="8" d="M55 350q0 298 348 426q-165 132 -165 299q0 119 58 212.5t168 145.5t257 52q123 0 215.5 -42t141 -118t48.5 -174q0 -134 -80.5 -233.5t-230.5 -151.5q217 -141 217 -365q0 -122 -63.5 -218.5t-181 -149.5t-273.5 -53q-214 0 -336.5 100t-122.5 270zM352 383 q0 -81 50 -128.5t135 -47.5q93 0 147.5 53.5t54.5 138.5q0 73 -36.5 131.5t-120.5 112.5q-116 -45 -173 -107t-57 -153zM528 1094q0 -132 123 -201q185 72 185 221q0 68 -39.5 107t-102.5 39q-76 0 -121 -46.5t-45 -119.5z" />
<glyph unicode="9" d="M86 12v256q111 -41 227 -41q121 0 207.5 49t144 138.5t99.5 257.5h-4q-111 -158 -295 -158q-163 0 -252.5 103.5t-89.5 285.5q0 166 73 305.5t196 208t286 68.5q203 0 308.5 -123t105.5 -361q0 -280 -99 -533t-264 -370.5t-403 -117.5q-128 0 -240 32zM424 928 q0 -87 37.5 -131.5t105.5 -44.5q60 0 111.5 36.5t82 100t30.5 158.5q0 84 -35.5 137t-110.5 53q-65 0 -115.5 -42t-78 -114t-27.5 -153z" />
<glyph unicode=":" horiz-adv-x="584" d="M25 115q0 90 53.5 144t150.5 54q68 0 109 -38t41 -107q0 -87 -55 -141t-144 -54q-73 0 -114 37.5t-41 104.5zM207 940q0 92 55.5 145.5t149.5 53.5q68 0 108.5 -38.5t40.5 -107.5q0 -86 -54.5 -140t-144.5 -54q-72 0 -113.5 36.5t-41.5 104.5z" />
<glyph unicode=";" horiz-adv-x="584" d="M-102 -264q74 167 194 502h285l8 -23q-118 -255 -262 -479h-225zM207 940q0 92 55.5 145.5t149.5 53.5q68 0 108.5 -38.5t40.5 -107.5q0 -86 -54.5 -140t-144.5 -54q-72 0 -113.5 36.5t-41.5 104.5z" />
<glyph unicode="&#x3c;" d="M109 641v143l952 496v-240l-643 -317l643 -281v-239z" />
<glyph unicode="=" d="M109 418v219h952v-219h-952zM109 807v217h952v-217h-952z" />
<glyph unicode="&#x3e;" d="M109 203v239l643 281l-643 317v240l952 -496v-143z" />
<glyph unicode="?" horiz-adv-x="940" d="M166 115q0 91 55 144.5t150 53.5q68 0 108.5 -38t40.5 -107q0 -87 -55 -141t-143 -54q-74 0 -115 38t-41 104zM178 1358q230 125 445 125q177 0 280 -87.5t103 -244.5q0 -83 -28.5 -149.5t-82.5 -123t-190 -147.5q-64 -43 -96.5 -73t-52.5 -64.5t-38 -108.5h-258l14 78 q19 103 73.5 177t172.5 155q124 84 157.5 127t33.5 96q0 119 -133 119q-50 0 -106.5 -16t-201.5 -84z" />
<glyph unicode="@" horiz-adv-x="1753" d="M92 500q0 279 120.5 497t343 341.5t497.5 123.5q318 0 499 -163.5t181 -458.5q0 -173 -64 -321t-177.5 -231t-254.5 -83q-88 0 -144.5 38.5t-72.5 108.5h-6q-50 -77 -113 -112t-147 -35q-127 0 -198 79.5t-71 229.5q0 147 67.5 276.5t187.5 205t268 75.5q185 0 327 -55 l-106 -420q-11 -44 -19 -76.5t-8 -64.5q0 -68 58 -68q66 0 124 64t92.5 171t34.5 214q0 213 -123.5 325.5t-359.5 112.5q-203 0 -366.5 -94t-255 -266t-91.5 -392q0 -243 134 -380.5t376 -137.5q117 0 219.5 20t221.5 66v-186q-230 -90 -465 -90q-217 0 -378 85.5 t-246 241.5t-85 359zM713 526q0 -65 24.5 -102t69.5 -37q141 0 213 270l57 222q-36 10 -82 10q-82 0 -145.5 -51.5t-100 -137t-36.5 -174.5z" />
<glyph unicode="A" horiz-adv-x="1286" d="M-123 0l766 1468h373l147 -1468h-297l-24 348h-473l-172 -348h-320zM494 608h333l-26 350q-10 131 -10 253v36q-44 -120 -109 -254z" />
<glyph unicode="B" horiz-adv-x="1270" d="M53 0l309 1462h426q229 0 346 -81.5t117 -243.5q0 -150 -83 -247.5t-236 -129.5v-6q100 -26 159.5 -96.5t59.5 -180.5q0 -229 -153 -353t-423 -124h-522zM412 256h180q117 0 183.5 58t66.5 161q0 162 -183 162h-165zM545 883h149q121 0 181.5 48.5t60.5 139.5 q0 137 -170 137h-152z" />
<glyph unicode="C" horiz-adv-x="1253" d="M123 553q0 262 104 482.5t278 335t400 114.5q125 0 222 -22.5t208 -82.5l-118 -250q-106 59 -175 78t-137 19q-132 0 -237.5 -81t-169.5 -238.5t-64 -338.5q0 -167 68.5 -248t218.5 -81q146 0 338 77v-260q-199 -77 -400 -77q-254 0 -395 149.5t-141 423.5z" />
<glyph unicode="D" horiz-adv-x="1386" d="M53 0l309 1462h396q270 0 417.5 -143t147.5 -410q0 -280 -98 -486.5t-283.5 -314.5t-437.5 -108h-451zM412 256h106q148 0 258 76t172 223.5t62 337.5q0 154 -72.5 234.5t-208.5 80.5h-115z" />
<glyph unicode="E" horiz-adv-x="1110" d="M53 0l309 1462h818l-54 -254h-512l-67 -321h477l-55 -254h-477l-80 -377h512l-54 -256h-817z" />
<glyph unicode="F" horiz-adv-x="1087" d="M53 0l309 1462h814l-54 -254h-508l-79 -377h473l-56 -253h-473l-121 -578h-305z" />
<glyph unicode="G" horiz-adv-x="1413" d="M123 549q0 268 107 484.5t301 334t448 117.5q218 0 410 -99l-115 -251q-74 40 -148 64t-161 24q-153 0 -273.5 -83t-189 -236.5t-68.5 -330.5q0 -172 72.5 -252.5t222.5 -80.5q76 0 170 24l66 299h-267l56 258h563l-162 -762q-134 -46 -248.5 -62.5t-242.5 -16.5 q-259 0 -400 147t-141 422z" />
<glyph unicode="H" horiz-adv-x="1434" d="M53 0l309 1462h306l-121 -573h471l121 573h305l-309 -1462h-306l134 631h-471l-134 -631h-305z" />
<glyph unicode="I" horiz-adv-x="659" d="M53 0l312 1462h305l-312 -1462h-305z" />
<glyph unicode="J" horiz-adv-x="678" d="M-322 -150q88 -20 164 -20q99 0 160.5 60.5t89.5 191.5l293 1380h305l-303 -1423q-52 -245 -175.5 -357t-346.5 -112q-94 0 -187 27v253z" />
<glyph unicode="K" horiz-adv-x="1255" d="M53 0l309 1462h306l-152 -702l158 205l409 497h361l-594 -700l291 -762h-338l-211 592l-125 -70l-109 -522h-305z" />
<glyph unicode="L" horiz-adv-x="1061" d="M53 0l309 1462h306l-256 -1206h512l-54 -256h-817z" />
<glyph unicode="M" horiz-adv-x="1802" d="M53 0l309 1462h404l68 -1093h4l551 1093h423l-309 -1462h-280l145 692q53 247 105 441h-5l-569 -1133h-281l-61 1133h-4q-11 -88 -38 -231t-187 -902h-275z" />
<glyph unicode="N" horiz-adv-x="1546" d="M53 0l309 1462h357l340 -1077h4q12 76 39 217t180 860h274l-309 -1462h-342l-356 1106h-6l-4 -32q-32 -216 -66 -386l-145 -688h-275z" />
<glyph unicode="O" horiz-adv-x="1495" d="M123 537q0 265 99 487.5t273 341.5t402 119q255 0 395 -144t140 -403q0 -283 -99 -506.5t-271 -337.5t-396 -114q-256 0 -399.5 147.5t-143.5 409.5zM434 537q0 -147 66.5 -222t187.5 -75t220.5 87t155.5 246t56 357q0 142 -65 219.5t-183 77.5q-121 0 -222 -91.5 t-158.5 -251.5t-57.5 -347z" />
<glyph unicode="P" horiz-adv-x="1188" d="M53 0l309 1462h338q242 0 366 -106.5t124 -319.5q0 -241 -169.5 -378.5t-467.5 -137.5h-86l-109 -520h-305zM522 774h56q142 0 223.5 69t81.5 185q0 180 -195 180h-74z" />
<glyph unicode="Q" horiz-adv-x="1495" d="M123 537q0 265 99 487.5t273 341.5t402 119q255 0 395 -144t140 -403q0 -316 -122.5 -555.5t-334.5 -337.5l254 -393h-359l-178 328h-26q-256 0 -399.5 147.5t-143.5 409.5zM434 537q0 -147 66.5 -222t187.5 -75t220.5 87t155.5 246t56 357q0 142 -65 219.5t-183 77.5 q-121 0 -222 -91.5t-158.5 -251.5t-57.5 -347z" />
<glyph unicode="R" horiz-adv-x="1247" d="M53 0l309 1462h359q237 0 356 -102t119 -299q0 -158 -83 -271.5t-239 -168.5l261 -621h-332l-207 561h-119l-119 -561h-305zM530 813h78q131 0 204 57t73 174q0 82 -47.5 123t-149.5 41h-74z" />
<glyph unicode="S" horiz-adv-x="1085" d="M41 70v274q193 -108 358 -108q112 0 175 42.5t63 116.5q0 43 -13.5 75.5t-38.5 60.5t-124 102q-138 99 -194 196t-56 209q0 129 62 230.5t176.5 158t263.5 56.5q217 0 397 -99l-109 -233q-156 74 -288 74q-83 0 -136 -45t-53 -119q0 -61 33 -106.5t148 -120.5 q121 -80 181 -176.5t60 -225.5q0 -209 -148 -330.5t-401 -121.5q-221 0 -356 90z" />
<glyph unicode="T" horiz-adv-x="1087" d="M168 1204l55 258h1010l-55 -258h-353l-254 -1204h-305l254 1204h-352z" />
<glyph unicode="U" horiz-adv-x="1415" d="M141 401q0 72 15 138l196 923h305l-194 -919q-17 -74 -17 -125q0 -178 189 -178q123 0 195 76.5t104 228.5l194 917h306l-201 -946q-57 -266 -218 -401t-419 -135q-212 0 -333.5 113.5t-121.5 307.5z" />
<glyph unicode="V" horiz-adv-x="1208" d="M184 1462h295l51 -880q4 -45 4 -133q-2 -103 -6 -150h7q78 221 110 283l432 880h316l-748 -1462h-334z" />
<glyph unicode="W" horiz-adv-x="1831" d="M184 1462h287l6 -798q0 -52 -4 -173t-10 -174h6q22 64 67 180.5t60 145.5l369 819h270l21 -873q0 -146 -9 -272h6q43 129 131 349l330 796h309l-647 -1462h-346l-22 721l-2 139q0 88 4 158h-4q-46 -146 -115 -299l-324 -719h-338z" />
<glyph unicode="X" horiz-adv-x="1241" d="M-117 0l576 764l-238 698h320l153 -518l363 518h344l-545 -725l268 -737h-331l-172 543l-396 -543h-342z" />
<glyph unicode="Y" horiz-adv-x="1155" d="M186 1462h312l129 -592l374 592h342l-618 -903l-119 -559h-303l119 559z" />
<glyph unicode="Z" horiz-adv-x="1098" d="M-61 0l38 201l777 1005h-543l53 256h936l-41 -202l-782 -1004h596l-53 -256h-981z" />
<glyph unicode="[" horiz-adv-x="678" d="M-37 -324l381 1786h473l-45 -211h-215l-291 -1364h215l-45 -211h-473z" />
<glyph unicode="\" horiz-adv-x="862" d="M221 1462h260l224 -1462h-267z" />
<glyph unicode="]" horiz-adv-x="678" d="M-137 -324l45 211h213l291 1364h-215l45 211h473l-381 -1786h-471z" />
<glyph unicode="^" horiz-adv-x="1081" d="M20 520l619 950h147l277 -950h-223l-174 633l-402 -633h-244z" />
<glyph unicode="_" horiz-adv-x="819" d="M-186 -324l30 140h822l-31 -140h-821z" />
<glyph unicode="`" horiz-adv-x="1135" d="M508 1548v21h311q36 -148 115 -303v-25h-184q-71 69 -138.5 153.5t-103.5 153.5z" />
<glyph unicode="a" horiz-adv-x="1217" d="M90 385q0 198 72 377.5t189 278t257 98.5q97 0 167.5 -42t109.5 -122h8l57 143h232l-238 -1118h-229l14 145h-4q-134 -165 -319 -165q-147 0 -231.5 106.5t-84.5 298.5zM395 399q0 -88 33.5 -132t95.5 -44q69 0 133 67t103 181.5t39 259.5q0 71 -38.5 117.5t-101.5 46.5 q-68 0 -129.5 -72t-98 -190t-36.5 -234z" />
<glyph unicode="b" horiz-adv-x="1219" d="M37 0l330 1556h301l-62 -288q-41 -182 -84 -299h8q78 98 142.5 134t140.5 36q146 0 230.5 -108t84.5 -298t-68 -367.5t-187 -281.5t-263 -104q-194 0 -276 163h-8l-58 -143h-231zM420 399q0 -80 37 -128t102 -48q67 0 128 69t98.5 189.5t37.5 237.5q0 176 -131 176 q-68 0 -130 -65t-102 -180.5t-40 -250.5z" />
<glyph unicode="c" horiz-adv-x="989" d="M90 391q0 212 74.5 385.5t209.5 268t308 94.5q182 0 328 -72l-92 -229q-54 23 -106 40t-118 17q-85 0 -153.5 -64t-107 -175.5t-38.5 -239.5q0 -96 45.5 -144.5t126.5 -48.5q76 0 141 23.5t134 58.5v-246q-152 -79 -336 -79q-201 0 -308.5 107.5t-107.5 303.5z" />
<glyph unicode="d" horiz-adv-x="1217" d="M90 387q0 196 71.5 374.5t188.5 278t258 99.5q82 0 141.5 -37t112.5 -127h8l2 28q6 110 25 195l76 358h301l-330 -1556h-229l14 145h-4q-71 -87 -148.5 -126t-170.5 -39q-147 0 -231.5 107t-84.5 300zM395 399q0 -176 137 -176q66 0 128.5 68.5t100.5 182.5t38 245 q0 80 -37.5 128t-102.5 48q-68 0 -129.5 -72t-98 -190t-36.5 -234z" />
<glyph unicode="e" horiz-adv-x="1141" d="M90 412q0 207 82.5 377.5t223.5 260t319 89.5q177 0 276 -81.5t99 -223.5q0 -187 -167 -288.5t-477 -101.5h-51l-2 -21v-20q0 -91 51.5 -143.5t147.5 -52.5q87 0 158 19t172 67v-227q-172 -86 -390 -86q-210 0 -326 113t-116 319zM428 647h45q155 0 241.5 48.5 t86.5 131.5q0 95 -105 95q-88 0 -166 -80t-102 -195z" />
<glyph unicode="f" horiz-adv-x="764" d="M-219 -225q61 -21 115 -21q61 0 107 40t65 130l204 965h-163l30 145l183 84l18 84q41 190 138.5 277.5t273.5 87.5q131 0 235 -49l-80 -224q-69 31 -133 31q-57 0 -92 -40t-47 -105l-12 -62h219l-49 -229h-220l-215 -1010q-77 -371 -403 -371q-104 0 -174 25v242z" />
<glyph unicode="g" horiz-adv-x="1108" d="M-115 -209q0 102 68.5 175.5t214.5 121.5q-74 47 -74 133q0 71 44.5 122.5t146.5 98.5q-65 49 -96 112t-31 153q0 199 125.5 315.5t341.5 116.5q83 0 166 -23h395l-35 -166l-174 -41q16 -52 16 -118q0 -195 -121 -308.5t-329 -113.5q-59 0 -99 10q-84 -27 -84 -78 q0 -34 30 -49t89 -23l137 -18q163 -21 237.5 -84.5t74.5 -183.5q0 -211 -156 -323t-446 -112q-208 0 -324.5 75.5t-116.5 207.5zM150 -172q0 -115 194 -115q151 0 228 45t77 127q0 39 -32.5 60t-137.5 35l-114 14q-106 -14 -160.5 -57t-54.5 -109zM442 680q0 -119 103 -119 q75 0 121.5 76.5t46.5 193.5t-99 117q-77 0 -124.5 -76.5t-47.5 -191.5z" />
<glyph unicode="h" horiz-adv-x="1237" d="M37 0l330 1556h301q-39 -181 -60 -278t-86 -309h8q62 77 138 123.5t176 46.5q138 0 213.5 -83.5t75.5 -238.5q0 -73 -23 -180l-133 -637h-301l137 653q16 68 16 119q0 123 -108 123q-92 0 -167 -114t-118 -318l-98 -463h-301z" />
<glyph unicode="i" horiz-adv-x="608" d="M37 0l237 1118h301l-237 -1118h-301zM322 1380q0 87 47.5 131.5t134.5 44.5q73 0 111 -31t38 -89q0 -80 -44 -129.5t-136 -49.5q-151 0 -151 123z" />
<glyph unicode="j" horiz-adv-x="608" d="M-264 -225q61 -21 114 -21q137 0 173 170l253 1194h302l-265 -1239q-77 -371 -403 -371q-104 0 -174 25v242zM324 1380q0 87 47.5 131.5t134.5 44.5q73 0 111 -31t38 -89q0 -80 -44 -129.5t-136 -49.5q-151 0 -151 123z" />
<glyph unicode="k" horiz-adv-x="1163" d="M37 0l330 1556h301l-148 -694q-8 -41 -29 -117l-28 -102h4l453 475h344l-498 -504l285 -614h-336l-183 420l-120 -72l-74 -348h-301z" />
<glyph unicode="l" horiz-adv-x="608" d="M37 0l330 1556h301l-330 -1556h-301z" />
<glyph unicode="m" horiz-adv-x="1853" d="M37 0l237 1118h230l-21 -207h6q146 228 355 228q219 0 262 -228h6q68 110 160.5 169t197.5 59q136 0 207.5 -85t71.5 -237q0 -76 -23 -180l-133 -637h-301l138 653q16 68 16 119q0 123 -98 123q-92 0 -166.5 -112t-118.5 -318l-96 -465h-301l137 653q16 68 16 119 q0 123 -98 123q-92 0 -167 -114t-118 -318l-98 -463h-301z" />
<glyph unicode="n" horiz-adv-x="1237" d="M37 0l237 1118h230l-21 -207h6q146 228 355 228q138 0 213.5 -83.5t75.5 -238.5q0 -73 -23 -180l-133 -637h-301l137 653q16 68 16 119q0 123 -108 123q-92 0 -167 -114t-118 -318l-98 -463h-301z" />
<glyph unicode="o" horiz-adv-x="1198" d="M90 410q0 213 71.5 379.5t206.5 258t316 91.5q196 0 310 -118t114 -325q0 -211 -70.5 -374t-203.5 -252.5t-316 -89.5q-195 0 -311.5 117.5t-116.5 312.5zM393 410q0 -185 150 -185q75 0 135 61.5t93.5 171t33.5 238.5q0 197 -143 197q-75 0 -134.5 -61t-97 -179 t-37.5 -243z" />
<glyph unicode="p" horiz-adv-x="1219" d="M-68 -492l342 1610h230l-17 -170h9q138 191 317 191q146 0 230.5 -107.5t84.5 -300.5q0 -191 -68.5 -367.5t-187.5 -280t-262 -103.5q-83 0 -143 37t-111 126h-8q-12 -159 -43 -295l-72 -340h-301zM420 399q0 -80 37 -128t102 -48q67 0 128 69t98.5 189.5t37.5 237.5 q0 176 -131 176q-68 0 -131.5 -67.5t-102 -180t-38.5 -248.5z" />
<glyph unicode="q" horiz-adv-x="1217" d="M90 385q0 198 72 377.5t189 278t257 98.5q86 0 152.5 -37.5t124.5 -126.5h8l57 143h232l-342 -1610h-301q47 218 73 337.5t84 304.5h-8q-72 -94 -143 -132t-154 -38q-88 0 -156 47.5t-106.5 138.5t-38.5 219zM395 399q0 -88 36.5 -132t103.5 -44q64 0 127.5 70t100 181 t36.5 245q0 80 -37.5 128t-102.5 48q-68 0 -129.5 -72t-98 -190t-36.5 -234z" />
<glyph unicode="r" horiz-adv-x="862" d="M37 0l237 1118h230l-21 -207h6q147 228 353 228q59 0 96 -11l-66 -290q-45 16 -100 16q-116 0 -203.5 -91.5t-124.5 -262.5l-106 -500h-301z" />
<glyph unicode="s" horiz-adv-x="969" d="M23 45v248q157 -90 319 -90q80 0 131 32.5t51 88.5q0 43 -37 77t-131 86q-121 68 -169 135.5t-48 159.5q0 170 110.5 263.5t315.5 93.5q201 0 363 -95l-99 -215q-140 84 -258 84q-57 0 -92 -25.5t-35 -68.5q0 -39 32 -68.5t120 -74.5q123 -63 178 -137t55 -170 q0 -188 -124.5 -288.5t-346.5 -100.5q-107 0 -186.5 15t-148.5 50z" />
<glyph unicode="t" horiz-adv-x="840" d="M94 889l29 147l196 84l132 236h194l-49 -238h283l-50 -229h-282l-115 -539q-6 -30 -6 -53q0 -74 88 -74q65 0 162 35v-225q-111 -53 -266 -53q-150 0 -220.5 63t-70.5 195q0 50 12 112l115 539h-152z" />
<glyph unicode="u" horiz-adv-x="1237" d="M111 301q0 93 24 213l127 604h301l-137 -653q-16 -68 -16 -119q0 -123 108 -123q92 0 167 114t118 318l98 463h301l-237 -1118h-230l21 207h-6q-145 -227 -355 -227q-138 0 -211 82.5t-73 238.5z" />
<glyph unicode="v" horiz-adv-x="1049" d="M102 1118h295l45 -586q7 -133 7 -231h6q55 153 92 223l297 594h323l-604 -1118h-323z" />
<glyph unicode="w" horiz-adv-x="1614" d="M125 1118h281l4 -495l-4 -167l-7 -171h4q6 20 14 41.5t51 136.5t46 119l231 536h328v-536q0 -142 -10 -297h6l28 80q73 208 95 258l219 495h307l-530 -1118h-330l-6 520q0 155 10 340h-6q-62 -178 -123 -319l-233 -541h-324z" />
<glyph unicode="x" horiz-adv-x="1087" d="M-100 0l479 573l-225 545h321l115 -334l244 334h354l-467 -561l244 -557h-326l-125 342l-264 -342h-350z" />
<glyph unicode="y" horiz-adv-x="1063" d="M-141 -233q68 -13 116 -13q84 0 147.5 48t117.5 149l26 49l-164 1118h295l56 -518q14 -122 14 -293h6q20 51 44 119.5t65 153.5l260 538h327l-680 -1278q-177 -332 -483 -332q-90 0 -147 19v240z" />
<glyph unicode="z" horiz-adv-x="932" d="M-47 0l35 180l575 705h-397l51 233h750l-43 -200l-566 -685h439l-49 -233h-795z" />
<glyph unicode="{" horiz-adv-x="727" d="M-8 459l45 229q122 0 192.5 41.5t92.5 138.5l61 285q38 170 131 239.5t270 69.5h84l-49 -225q-90 -2 -130.5 -34.5t-55.5 -106.5l-66 -297q-45 -207 -276 -236v-8q85 -26 126.5 -82.5t41.5 -134.5q0 -44 -15 -113l-36 -178q-7 -28 -7 -51q0 -54 33.5 -74t91.5 -20v-226 h-53q-167 0 -253.5 63.5t-86.5 184.5q0 57 14 125l39 184q15 69 15 86q0 140 -209 140z" />
<glyph unicode="|" d="M455 -465v2015h219v-2015h-219z" />
<glyph unicode="}" horiz-adv-x="727" d="M-100 -98q93 3 137 35.5t59 105.5l66 297q25 111 95 166t181 69v9q-168 51 -168 217q0 43 15 112l37 179q6 30 6 51q0 54 -36.5 74t-109.5 20l41 225h33q340 0 340 -248q0 -56 -14 -124l-39 -185q-15 -69 -15 -86q0 -139 209 -139l-45 -229q-122 0 -192.5 -42t-91.5 -139 l-62 -284q-37 -170 -130.5 -240t-270.5 -70h-45v226z" />
<glyph unicode="~" d="M109 551v231q101 109 256 109q64 0 117 -14t139 -50q64 -27 111 -41t95 -14q51 0 112 30.5t122 90.5v-231q-103 -109 -256 -109q-59 0 -109 11.5t-147 51.5q-89 38 -127 47t-80 9q-54 0 -116.5 -33t-116.5 -88z" />
<glyph unicode="&#xa2;" d="M164 584q0 193 62.5 355t178 262.5t267.5 123.5l33 158h188l-35 -158q118 -14 225 -65l-92 -230q-53 23 -105 40t-118 17q-133 0 -216 -143t-83 -336q0 -96 45 -144t127 -48q75 0 140 23.5t134 58.5v-246q-136 -71 -299 -80l-41 -192h-188l49 210q-134 36 -203 136 t-69 258z" />
<glyph unicode="&#xa3;" d="M-12 0l49 246q196 48 244 264l22 104h-192l45 220h192l49 247q41 197 162 300.5t313 103.5q195 0 369 -86l-113 -232q-141 68 -237 68q-75 0 -123 -39.5t-68 -132.5l-47 -229h299l-45 -220h-299l-18 -84q-42 -195 -209 -270h655l-55 -260h-993z" />
<glyph unicode="&#xa5;" d="M88 221l37 178h252l29 138h-252l39 178h196l-192 747h297l114 -590l371 590h311l-506 -747h203l-39 -178h-252l-28 -138h252l-37 -178h-252l-47 -221h-291l47 221h-252z" />
<glyph unicode="&#xa9;" horiz-adv-x="1704" d="M125 731q0 200 100 375t275 276t377 101q199 0 373.5 -99t276 -275.5t101.5 -377.5q0 -199 -98.5 -373t-272.5 -276t-380 -102q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM266 731q0 -164 81.5 -305t224 -223t305.5 -82q167 0 308 83t221.5 223.5t80.5 303.5 t-80.5 303.5t-222 223.5t-307.5 83q-164 0 -306.5 -82.5t-223.5 -223.5t-81 -304zM485 721q0 225 117.5 351t325.5 126q142 0 284 -72l-75 -174q-114 58 -205 58q-111 0 -163 -73t-52 -214q0 -134 55.5 -203t159.5 -69q43 0 108.5 15.5t124.5 43.5v-191q-131 -57 -262 -57 q-196 0 -307 122.5t-111 336.5z" />
<glyph unicode="&#xad;" horiz-adv-x="659" d="M41 424l53 250h524l-53 -250h-524z" />
<glyph unicode="&#xae;" horiz-adv-x="1704" d="M125 731q0 200 100 375t275 276t377 101q199 0 373.5 -99t276 -275.5t101.5 -377.5q0 -199 -98.5 -373t-272.5 -276t-380 -102q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM266 731q0 -164 81.5 -305t224 -223t305.5 -82q167 0 308 83t221.5 223.5t80.5 303.5 t-80.5 303.5t-222 223.5t-307.5 83q-164 0 -306.5 -82.5t-223.5 -223.5t-81 -304zM571 293v874h308q173 0 265.5 -67.5t92.5 -200.5q0 -86 -44 -149.5t-130 -96.5l197 -360h-254l-138 297h-67v-297h-230zM801 758h51q72 0 113 31t41 92q0 59 -35.5 88.5t-116.5 29.5h-53 v-241z" />
<glyph unicode="&#xb4;" horiz-adv-x="1135" d="M483 1241v25q79 88 222 303h335v-17q-46 -56 -154 -152.5t-194 -158.5h-209z" />
<glyph unicode="&#x2000;" horiz-adv-x="786" />
<glyph unicode="&#x2001;" horiz-adv-x="1573" />
<glyph unicode="&#x2002;" horiz-adv-x="786" />
<glyph unicode="&#x2003;" horiz-adv-x="1573" />
<glyph unicode="&#x2004;" horiz-adv-x="524" />
<glyph unicode="&#x2005;" horiz-adv-x="393" />
<glyph unicode="&#x2006;" horiz-adv-x="262" />
<glyph unicode="&#x2007;" horiz-adv-x="262" />
<glyph unicode="&#x2008;" horiz-adv-x="196" />
<glyph unicode="&#x2009;" horiz-adv-x="314" />
<glyph unicode="&#x200a;" horiz-adv-x="87" />
<glyph unicode="&#x2010;" horiz-adv-x="659" d="M41 424l53 250h524l-53 -250h-524z" />
<glyph unicode="&#x2011;" horiz-adv-x="659" d="M41 424l53 250h524l-53 -250h-524z" />
<glyph unicode="&#x2012;" horiz-adv-x="659" d="M41 424l53 250h524l-53 -250h-524z" />
<glyph unicode="&#x2013;" horiz-adv-x="983" d="M41 436l49 230h852l-49 -230h-852z" />
<glyph unicode="&#x2014;" horiz-adv-x="1966" d="M41 436l49 230h1835l-49 -230h-1835z" />
<glyph unicode="&#x2018;" horiz-adv-x="440" d="M115 983q103 227 262 479h225q-91 -213 -194 -501h-285z" />
<glyph unicode="&#x2019;" horiz-adv-x="440" d="M106 961q89 206 195 501h285l8 -22q-103 -227 -262 -479h-226z" />
<glyph unicode="&#x201c;" horiz-adv-x="887" d="M115 983q103 227 262 479h225q-91 -213 -194 -501h-285zM561 983q103 227 262 479h226q-97 -227 -195 -501h-285z" />
<glyph unicode="&#x201d;" horiz-adv-x="887" d="M106 961q89 206 195 501h285l8 -22q-103 -227 -262 -479h-226zM553 961q23 53 46.5 111t148.5 390h284l8 -22q-103 -227 -262 -479h-225z" />
<glyph unicode="&#x2022;" horiz-adv-x="739" d="M104 686q0 106 42.5 194t120 136.5t182.5 48.5q120 0 182.5 -67t62.5 -191q0 -177 -91.5 -277t-248.5 -100q-117 0 -183.5 67t-66.5 189z" />
<glyph unicode="&#x2026;" horiz-adv-x="1706" d="M25 115q0 90 53.5 144t150.5 54q68 0 109 -38t41 -107q0 -87 -55 -141t-144 -54q-73 0 -114 37.5t-41 104.5zM586 115q0 90 53.5 144t150.5 54q68 0 109 -38t41 -107q0 -87 -55 -141t-144 -54q-73 0 -114 37.5t-41 104.5zM1147 115q0 90 53.5 144t150.5 54q68 0 109 -38 t41 -107q0 -87 -55 -141t-144 -54q-73 0 -114 37.5t-41 104.5z" />
<glyph unicode="&#x202f;" horiz-adv-x="314" />
<glyph unicode="&#x205f;" horiz-adv-x="393" />
<glyph unicode="&#x20ac;" d="M41 481l37 178h127q9 67 22 115h-125l39 176h135q87 252 250.5 393.5t374.5 141.5q100 0 179 -23t165 -80l-125 -223q-87 49 -131 63.5t-90 14.5q-97 0 -176 -74.5t-135 -212.5h348l-39 -176h-360q-11 -34 -25 -115h299l-37 -178h-280q0 -120 44.5 -181.5t147.5 -61.5 q133 0 283 63v-258q-126 -63 -330 -63q-446 0 -446 501h-152z" />
<glyph unicode="&#x2122;" horiz-adv-x="1534" d="M106 1313v149h564v-149h-199v-572h-168v572h-197zM715 741v721h248l159 -510l170 510h240v-721h-168v408l4 121h-6l-174 -529h-141l-166 529h-7l5 -111v-418h-164z" />
<glyph unicode="&#xe000;" horiz-adv-x="1120" d="M0 1120h1120v-1120h-1120v1120z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 26 KiB

BIN
couchpotato/static/fonts/OpenSans-BoldItalic-webfont.ttf

Binary file not shown.

BIN
couchpotato/static/fonts/OpenSans-BoldItalic-webfont.woff

Binary file not shown.

BIN
couchpotato/static/fonts/OpenSans-Italic-webfont.eot

Binary file not shown.

146
couchpotato/static/fonts/OpenSans-Italic-webfont.svg

@ -0,0 +1,146 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
This is a custom SVG webfont generated by Font Squirrel.
Copyright : Digitized data copyright 20102011 Google Corporation
Foundry : Ascender Corporation
Foundry URL : httpwwwascendercorpcom
</metadata>
<defs>
<font id="OpenSansItalic" horiz-adv-x="1128" >
<font-face units-per-em="2048" ascent="1638" descent="-410" />
<missing-glyph horiz-adv-x="532" />
<glyph unicode=" " horiz-adv-x="532" />
<glyph unicode="&#x09;" horiz-adv-x="532" />
<glyph unicode="&#xa0;" horiz-adv-x="532" />
<glyph unicode="!" horiz-adv-x="530" d="M43 78q0 76 39.5 120t107.5 44q45 0 73 -27.5t28 -81.5q0 -68 -39 -115t-105 -47q-49 0 -76.5 28t-27.5 79zM172 403q49 307 176 1059h207l-274 -1059h-109z" />
<glyph unicode="&#x22;" horiz-adv-x="791" d="M225 934l72 528h188l-153 -528h-107zM573 934l72 528h189l-154 -528h-107z" />
<glyph unicode="#" horiz-adv-x="1323" d="M63 430l13 129h284l101 340h-277l13 127h301l123 436h139l-125 -436h305l127 436h133l-125 -436h264l-12 -127h-291l-98 -340h285l-13 -129h-309l-125 -430h-139l129 430h-303l-127 -430h-133l121 430h-261zM500 559h303l96 340h-303z" />
<glyph unicode="$" d="M72 176v154q82 -41 175.5 -63.5t166.5 -22.5l98 452q-139 49 -201.5 123.5t-62.5 188.5q0 159 108 255t299 113l39 176h133l-39 -178q159 -12 283 -76l-63 -135q-121 63 -248 72l-94 -440q149 -55 212.5 -125t63.5 -178q0 -162 -112.5 -263t-309.5 -123l-49 -225h-133 l49 223q-195 14 -315 72zM401 1010q0 -53 34.5 -97.5t107.5 -70.5l84 393q-108 -11 -167 -69t-59 -156zM549 250q107 13 170 75t63 154q0 54 -33 96t-114 74z" />
<glyph unicode="%" horiz-adv-x="1624" d="M168 860q0 166 50.5 318.5t136.5 228.5t200 76q116 0 176 -72t60 -205q0 -108 -32 -237.5t-82.5 -217.5t-120.5 -137t-157 -49q-109 0 -170 75t-61 220zM231 0l1086 1462h151l-1085 -1462h-152zM307 864q0 -172 107 -172q52 0 94 39.5t73.5 114t50.5 175t19 171.5 q0 166 -108 166q-66 0 -119 -63t-85 -187.5t-32 -243.5zM909 274q0 166 50.5 318.5t136.5 228.5t200 76q116 0 176 -71.5t60 -204.5q0 -107 -31.5 -236t-82 -217.5t-121 -138t-156.5 -49.5q-110 0 -171 74.5t-61 219.5zM1049 279q0 -173 106 -173q65 0 117 65t86.5 198.5 t34.5 236.5q0 166 -109 166q-67 0 -119.5 -64.5t-84 -188.5t-31.5 -240z" />
<glyph unicode="&#x26;" horiz-adv-x="1372" d="M66 342q0 148 90 257.5t303 211.5q-103 165 -103 309q0 164 106 264.5t281 100.5q149 0 236.5 -79t87.5 -212q0 -78 -32.5 -137t-87.5 -108t-127.5 -90t-153.5 -83l278 -389q127 110 199 295h168q-101 -236 -283 -412l203 -270h-201l-117 166q-120 -100 -230 -143 t-247 -43q-168 0 -269 96t-101 266zM229 354q0 -106 66.5 -170.5t175.5 -64.5q87 0 168 33t195 124l-306 433q-128 -67 -184 -116t-85.5 -107.5t-29.5 -131.5zM516 1118q0 -120 82 -235q139 71 191 110t83 85t31 104q0 77 -42.5 121.5t-123.5 44.5q-105 0 -163 -60t-58 -170 z" />
<glyph unicode="'" horiz-adv-x="444" d="M225 934l72 528h188l-153 -528h-107z" />
<glyph unicode="(" horiz-adv-x="584" d="M82 272q0 339 120 627t384 563h157q-246 -270 -371.5 -570t-125.5 -618q0 -339 114 -598h-131q-147 266 -147 596z" />
<glyph unicode=")" horiz-adv-x="584" d="M-160 -324q496 551 496 1188q0 341 -113 598h131q146 -269 146 -598q0 -341 -121.5 -629.5t-382.5 -558.5h-156z" />
<glyph unicode="*" horiz-adv-x="1130" d="M215 1194l55 154l371 -185l41 400l172 -35l-123 -383l422 18l-8 -157l-393 47l180 -383l-166 -52l-113 406l-258 -344l-116 121l309 284z" />
<glyph unicode="+" d="M127 651v142h389v391h141v-391h390v-142h-390v-387h-141v387h-389z" />
<glyph unicode="," horiz-adv-x="492" d="M-100 -264q126 286 204 502h187l8 -23q-113 -235 -270 -479h-129z" />
<glyph unicode="-" horiz-adv-x="639" d="M55 469l35 158h479l-34 -158h-480z" />
<glyph unicode="." horiz-adv-x="518" d="M43 74q0 77 40.5 122.5t111.5 45.5q43 0 69.5 -26t26.5 -79q0 -71 -40 -118.5t-108 -47.5q-46 0 -73 26t-27 77z" />
<glyph unicode="/" horiz-adv-x="717" d="M-94 0l813 1462h174l-813 -1462h-174z" />
<glyph unicode="0" d="M121 477q0 270 82 514.5t216.5 369t307.5 124.5q365 0 365 -471q0 -295 -78.5 -539t-214 -369.5t-314.5 -125.5q-176 0 -270 127.5t-94 369.5zM293 479q0 -172 50 -264t161 -92q115 0 209 114t150.5 328t56.5 453q0 323 -203 323q-113 0 -209 -115.5t-155.5 -323 t-59.5 -423.5z" />
<glyph unicode="1" d="M303 1178l449 284h149l-313 -1462h-172l196 913q59 261 88 359q-50 -53 -139 -111l-178 -110z" />
<glyph unicode="2" d="M12 0l31 147l465 420q102 93 176.5 163.5t123 133t72 124t23.5 136.5q0 99 -60 157t-163 58q-77 0 -150.5 -28.5t-162.5 -96.5l-82 115q191 154 413 154q176 0 278.5 -88.5t102.5 -243.5q0 -111 -39.5 -204t-131 -197t-294.5 -281l-352 -307v-8h678l-29 -154h-899z" />
<glyph unicode="3" d="M47 59v164q94 -49 199 -75.5t190 -26.5q162 0 252 79.5t90 217.5q0 131 -79 198.5t-220 67.5h-131l31 143h139q165 0 274 87t109 227q0 92 -58 146t-157 54q-80 0 -157 -27t-175 -93l-80 118q195 144 424 144q179 0 277 -87t98 -237q0 -156 -101 -264.5t-280 -140.5v-9 q124 -23 195 -106.5t71 -208.5q0 -133 -62 -234.5t-181 -158.5t-283 -57q-210 0 -385 79z" />
<glyph unicode="4" d="M16 334l29 158l834 978h196l-207 -983h232l-33 -153h-233l-72 -334h-164l74 334h-656zM219 487h486q46 220 78 373t116 445h-8q-17 -29 -66.5 -96.5t-72.5 -96.5z" />
<glyph unicode="5" d="M80 59v164q164 -102 334 -102q191 0 298 96t107 268q0 126 -73.5 199.5t-204.5 73.5q-48 0 -97 -6.5t-139 -30.5l-74 57l197 684h668l-33 -153h-522l-127 -439q87 23 184 23q182 0 289.5 -104.5t107.5 -282.5q0 -161 -73 -283t-204 -182.5t-308 -60.5q-193 0 -330 79z " />
<glyph unicode="6" d="M133 424q0 209 60.5 415t163.5 351.5t246 219t327 73.5q111 0 184 -23l-35 -145q-68 22 -170 22q-212 0 -356.5 -149t-212.5 -443h8q59 79 146.5 126t193.5 47q154 0 244 -98.5t90 -270.5q0 -161 -66.5 -294.5t-180.5 -204t-261 -70.5q-182 0 -281.5 115t-99.5 329z M299 416q0 -137 60.5 -216t172.5 -79q94 0 167.5 54t114 149t40.5 208q0 248 -221 248q-66 0 -128 -28.5t-110 -76t-72 -104.5t-24 -155z" />
<glyph unicode="7" d="M174 0l768 1313h-719l31 149h891l-27 -139l-764 -1323h-180z" />
<glyph unicode="8" d="M96 346q0 148 95 256t296 184q-95 69 -135.5 144.5t-40.5 171.5q0 111 54.5 198.5t153.5 136t222 48.5q174 0 271.5 -86.5t97.5 -235.5q0 -129 -78 -225t-266 -176q127 -78 180 -165t53 -202q0 -122 -60 -217.5t-172.5 -146.5t-264.5 -51q-190 0 -298 98.5t-108 267.5z M270 354q0 -107 69 -170t181 -63q139 0 222 74t83 196q0 99 -52 174t-165 135q-185 -60 -261.5 -143.5t-76.5 -202.5zM479 1100q0 -82 39 -144t127 -116q161 60 228 131.5t67 173.5q0 90 -57.5 143t-153.5 53q-114 0 -182 -65.5t-68 -175.5z" />
<glyph unicode="9" d="M98 14v158q134 -47 246 -47q202 0 327 141t189 441h-10q-51 -75 -132.5 -118.5t-180.5 -43.5q-169 0 -261 98.5t-92 288.5q0 153 64.5 280.5t180 199t259.5 71.5q180 0 279.5 -114.5t99.5 -334.5q0 -194 -56 -406.5t-147.5 -360t-221.5 -217.5t-302 -70q-136 0 -242 34z M350 938q0 -124 54.5 -190t162.5 -66q76 0 140 28.5t108.5 81.5t65 114t20.5 151q0 131 -59 207.5t-160 76.5q-150 0 -241 -113t-91 -290z" />
<glyph unicode=":" horiz-adv-x="518" d="M43 74q0 77 40.5 122.5t111.5 45.5q43 0 69.5 -26t26.5 -79q0 -71 -40 -118.5t-108 -47.5q-46 0 -73 26t-27 77zM203 956q0 77 40 122.5t111 45.5q97 0 97 -104q0 -73 -41.5 -119.5t-106.5 -46.5q-46 0 -73 26.5t-27 75.5z" />
<glyph unicode=";" horiz-adv-x="518" d="M-100 -264q126 286 204 502h187l8 -23q-113 -235 -270 -479h-129zM203 956q0 77 40 122.5t111 45.5q97 0 97 -104q0 -73 -41.5 -119.5t-106.5 -46.5q-46 0 -73 26.5t-27 75.5z" />
<glyph unicode="&#x3c;" d="M121 664v98l919 479v-149l-747 -371l747 -328v-151z" />
<glyph unicode="=" d="M127 444v142h920v-142h-920zM127 858v139h920v-139h-920z" />
<glyph unicode="&#x3e;" d="M121 242v151l745 328l-745 371v149l919 -479v-98z" />
<glyph unicode="?" horiz-adv-x="874" d="M158 74q0 77 40 122.5t111 45.5q44 0 70.5 -26t26.5 -79q0 -73 -41.5 -119.5t-106.5 -46.5q-46 0 -73 26t-27 77zM197 1382q92 51 192 76t182 25q167 0 259 -84t92 -238q0 -123 -65.5 -226.5t-225.5 -223.5q-125 -91 -169 -147.5t-67 -160.5h-135q22 130 72.5 213.5 t165.5 174.5q128 100 168 144t63 94t23 112q0 93 -51.5 143.5t-147.5 50.5q-81 0 -155 -25.5t-140 -56.5z" />
<glyph unicode="@" horiz-adv-x="1735" d="M111 504q0 261 126.5 485.5t343.5 347.5t486 123q191 0 329 -75.5t210.5 -213.5t72.5 -319q0 -179 -55 -324t-155 -227t-222 -82q-197 0 -213 184h-8q-111 -184 -291 -184q-115 0 -180.5 75.5t-65.5 209.5q0 157 68 284t188.5 199t260.5 72q65 0 127.5 -12t150.5 -48 q-64 -242 -98 -368t-31 -172q0 -117 102 -117q78 0 141.5 67t100.5 183.5t37 243.5q0 239 -128 367t-370 128q-228 0 -406.5 -107t-277 -295.5t-98.5 -416.5q0 -270 143.5 -418.5t409.5 -148.5q197 0 420 86v-127q-219 -90 -443 -90q-314 0 -494.5 184.5t-180.5 505.5z M639 518q0 -93 33 -134.5t98 -41.5q187 0 272 315l70 258q-63 23 -127 23q-94 0 -174 -55t-126 -153t-46 -212z" />
<glyph unicode="A" horiz-adv-x="1137" d="M-117 0l799 1462h174l184 -1462h-170l-57 465h-496l-245 -465h-189zM401 621h394l-35 299q-24 179 -29 350q-37 -88 -80.5 -175t-249.5 -474z" />
<glyph unicode="B" horiz-adv-x="1225" d="M86 0l309 1462h375q432 0 432 -336q0 -141 -87 -238t-245 -126v-10q115 -32 176.5 -110.5t61.5 -188.5q0 -212 -152 -332.5t-407 -120.5h-463zM287 145h266q181 0 278 80.5t97 227.5q0 116 -74.5 177.5t-214.5 61.5h-236zM434 836h248q156 0 249 73t93 199 q0 104 -66.5 155.5t-209.5 51.5h-211z" />
<glyph unicode="C" horiz-adv-x="1198" d="M150 537q0 261 105.5 485.5t283.5 342.5t403 118q197 0 348 -80l-69 -141q-138 69 -279 69q-174 0 -311.5 -97t-218 -284.5t-80.5 -408.5q0 -187 97.5 -298.5t268.5 -111.5q139 0 322 57v-149q-86 -31 -164 -45t-188 -14q-242 0 -380 149.5t-138 407.5z" />
<glyph unicode="D" horiz-adv-x="1364" d="M86 0l309 1462h342q276 0 419.5 -149.5t143.5 -435.5q0 -261 -105 -461t-300 -308t-457 -108h-352zM287 147h162q202 0 355 91.5t234.5 258.5t81.5 382t-103 325.5t-302 110.5h-178z" />
<glyph unicode="E" horiz-adv-x="1047" d="M86 0l309 1462h735l-32 -153h-566l-98 -469h527l-29 -152h-529l-114 -536h565l-33 -152h-735z" />
<glyph unicode="F" horiz-adv-x="967" d="M86 0l309 1462h735l-30 -153h-568l-110 -533h528l-32 -153h-529l-131 -623h-172z" />
<glyph unicode="G" horiz-adv-x="1386" d="M150 528q0 269 101.5 489.5t281.5 343t399 122.5q117 0 219.5 -20t206.5 -64l-66 -152q-77 34 -165.5 59t-194.5 25q-169 0 -307.5 -101.5t-215.5 -283.5t-77 -407q0 -190 102.5 -299t286.5 -109q154 0 260 39l96 444h-289l33 152h459l-154 -711q-216 -75 -419 -75 q-264 0 -410.5 144.5t-146.5 403.5z" />
<glyph unicode="H" horiz-adv-x="1389" d="M86 0l309 1462h170l-131 -622h660l133 622h168l-310 -1462h-167l143 688h-660l-145 -688h-170z" />
<glyph unicode="I" horiz-adv-x="559" d="M86 0l311 1462h168l-311 -1462h-168z" />
<glyph unicode="J" horiz-adv-x="547" d="M-319 -360l6 147q69 -20 145 -20q100 0 165.5 62.5t90.5 182.5l307 1450h170l-309 -1468q-79 -379 -422 -379q-105 0 -153 25z" />
<glyph unicode="K" horiz-adv-x="1141" d="M86 0l309 1462h170l-151 -710l700 710h209l-639 -637l350 -825h-186q-72 181 -146.5 359.5t-146.5 361.5l-174 -131l-125 -590h-170z" />
<glyph unicode="L" horiz-adv-x="971" d="M86 0l309 1462h170l-276 -1308h565l-33 -154h-735z" />
<glyph unicode="M" horiz-adv-x="1714" d="M84 0l309 1462h244l149 -1204h9l659 1204h266l-303 -1462h-174q126 590 193 905.5t94 392.5h-6l-717 -1298h-131l-166 1296h-8q-7 -72 -28.5 -197.5t-37.5 -199.5l-190 -899h-162z" />
<glyph unicode="N" horiz-adv-x="1438" d="M84 0l309 1462h180l459 -1220h6q30 224 72 405l174 815h164l-309 -1462h-181l-460 1223h-6q-32 -221 -74 -418l-172 -805h-162z" />
<glyph unicode="O" horiz-adv-x="1475" d="M150 549q0 264 96 482t263.5 336t377.5 118q244 0 384 -154t140 -424q0 -269 -88 -481.5t-252 -329t-379 -116.5q-256 0 -399 149.5t-143 419.5zM332 553q0 -199 98 -310.5t266 -111.5q152 0 272.5 97.5t190.5 279.5t70 403q0 199 -94 310.5t-261 111.5q-157 0 -281 -101 t-192.5 -281t-68.5 -398z" />
<glyph unicode="P" horiz-adv-x="1159" d="M86 0l309 1462h330q214 0 324 -94.5t110 -282.5q0 -248 -164 -379t-481 -131h-135l-123 -575h-170zM410 721h133q216 0 328 91t112 267q0 125 -69.5 180.5t-213.5 55.5h-163z" />
<glyph unicode="Q" horiz-adv-x="1475" d="M150 549q0 264 96 482t263.5 336t377.5 118q244 0 384 -154t140 -424q0 -333 -139 -576t-375 -321l274 -358h-219l-227 330l-17 -2h-16q-256 0 -399 149.5t-143 419.5zM332 553q0 -199 98 -310.5t266 -111.5q158 0 279 100t187.5 280.5t66.5 399.5q0 199 -94 310.5 t-261 111.5q-157 0 -281 -101t-192.5 -281t-68.5 -398z" />
<glyph unicode="R" horiz-adv-x="1165" d="M86 0l309 1462h320q446 0 446 -366q0 -348 -368 -449l239 -647h-186l-209 608h-252l-129 -608h-170zM416 754h168q193 0 297 85t104 244q0 121 -67.5 175.5t-219.5 54.5h-166q-102 -494 -116 -559z" />
<glyph unicode="S" horiz-adv-x="1028" d="M39 43v170q162 -84 340 -84q162 0 257 75.5t95 207.5q0 78 -52.5 137.5t-195.5 140.5q-151 85 -209.5 170t-58.5 201q0 187 132 304.5t347 117.5q99 0 184.5 -19t180.5 -65l-66 -150q-66 38 -148 60t-151 22q-134 0 -215.5 -69.5t-81.5 -188.5q0 -54 17 -92.5t54 -72.5 t142 -95q147 -88 198.5 -138t78 -110.5t26.5 -140.5q0 -211 -140.5 -327.5t-395.5 -116.5q-106 0 -186.5 14.5t-151.5 48.5z" />
<glyph unicode="T" horiz-adv-x="1020" d="M186 1311l33 151h985l-30 -151h-408l-279 -1311h-172l277 1311h-406z" />
<glyph unicode="U" horiz-adv-x="1384" d="M164 383q0 81 24 201l189 878h170l-191 -891q-22 -106 -22 -188q0 -117 73 -184.5t218 -67.5q172 0 267.5 87.5t139.5 289.5l205 954h170l-205 -966q-55 -263 -197.5 -389.5t-388.5 -126.5q-230 0 -341 104t-111 299z" />
<glyph unicode="V" horiz-adv-x="1122" d="M188 1462h170l97 -930q20 -196 20 -335h4q61 144 162 338l479 927h191l-781 -1462h-180z" />
<glyph unicode="W" horiz-adv-x="1745" d="M223 1462h170l31 -901l2 -88q0 -98 -10 -258h6q89 243 156 383l405 864h178l43 -860q9 -153 9 -304l-1 -83h9q75 224 131 354l387 893h182l-664 -1462h-170l-49 965q-8 136 -8 282h-6q-25 -72 -61 -154.5t-504 -1092.5h-174z" />
<glyph unicode="X" horiz-adv-x="1063" d="M-104 0l596 776l-263 686h172l203 -563l443 563h186l-555 -694l278 -768h-180l-213 641l-481 -641h-186z" />
<glyph unicode="Y" horiz-adv-x="1030" d="M188 1462h170l179 -747l489 747h193l-627 -921l-113 -541h-172l119 549z" />
<glyph unicode="Z" horiz-adv-x="1087" d="M-16 0l28 137l924 1170h-655l32 155h858l-26 -139l-924 -1169h697l-33 -154h-901z" />
<glyph unicode="[" horiz-adv-x="586" d="M-16 -324l381 1786h387l-31 -141h-227l-318 -1503h227l-32 -142h-387z" />
<glyph unicode="\" horiz-adv-x="717" d="M221 1462h154l217 -1462h-154z" />
<glyph unicode="]" horiz-adv-x="586" d="M-150 -324l31 142h225l320 1503h-227l30 141h389l-380 -1786h-388z" />
<glyph unicode="^" horiz-adv-x="1059" d="M53 553l598 920h109l266 -920h-145l-201 747l-467 -747h-160z" />
<glyph unicode="_" horiz-adv-x="807" d="M-188 -324l30 140h811l-30 -140h-811z" />
<glyph unicode="`" horiz-adv-x="1135" d="M575 1548v21h181q43 -136 147 -303v-25h-104q-61 61 -128.5 154t-95.5 153z" />
<glyph unicode="a" horiz-adv-x="1157" d="M98 350q0 208 71 386t196 279t274 101q92 0 164 -49.5t112 -142.5h11l67 172h127l-233 -1096h-133l26 209h-8q-179 -229 -377 -229q-139 0 -218 99t-79 271zM270 346q0 -114 47 -170.5t132 -56.5q97 0 193 92.5t156 241t60 297.5q0 103 -56 164t-147 61 q-104 0 -193.5 -86t-140.5 -233t-51 -310z" />
<glyph unicode="b" horiz-adv-x="1182" d="M59 0l330 1556h168q-51 -242 -78.5 -370.5t-75.5 -300.5h9q93 118 183.5 173.5t186.5 55.5q141 0 220 -99t79 -272q0 -209 -68.5 -386.5t-191 -277t-276.5 -99.5q-97 0 -170.5 51t-110.5 139h-10l-70 -170h-125zM319 346q0 -110 55.5 -168.5t160.5 -58.5q99 0 184.5 81 t137.5 230.5t52 317.5q0 227 -178 227q-96 0 -195.5 -95t-158 -239t-58.5 -295z" />
<glyph unicode="c" horiz-adv-x="922" d="M98 389q0 200 74 369t204.5 263.5t293.5 94.5q137 0 268 -51l-47 -141q-120 51 -219 51q-112 0 -204.5 -76.5t-145 -213t-52.5 -296.5q0 -128 66.5 -199t183.5 -71q72 0 136 20t126 47v-143q-124 -63 -276 -63q-194 0 -301 107t-107 302z" />
<glyph unicode="d" horiz-adv-x="1182" d="M98 350q0 214 72 392t194.5 275t274.5 97q194 0 281 -190h10q17 155 45 274l78 358h166l-330 -1556h-139l22 209h-8q-101 -125 -189 -177t-182 -52q-139 0 -217 98t-78 272zM270 346q0 -227 179 -227q94 0 194 93.5t158.5 239t58.5 296.5q0 111 -54 169t-157 58 q-101 0 -187.5 -82.5t-139 -232t-52.5 -314.5z" />
<glyph unicode="e" horiz-adv-x="1010" d="M98 391q0 188 74.5 360.5t197.5 268.5t271 96q153 0 230 -66.5t77 -185.5q0 -180 -166 -282.5t-475 -102.5h-33l-4 -80q0 -131 61.5 -204.5t190.5 -73.5q63 0 129.5 18t165.5 66v-146q-94 -44 -166 -61.5t-159 -17.5q-184 0 -289 109t-105 302zM299 618h12 q228 0 349.5 59.5t121.5 172.5q0 53 -36.5 88t-114.5 35q-103 0 -193.5 -94t-138.5 -261z" />
<glyph unicode="f" horiz-adv-x="641" d="M-229 -330q64 -22 112 -22q76 0 117 62t66 177l227 1082h-193l13 67l206 66l23 100q46 200 127.5 282.5t241.5 82.5q40 0 98 -11.5t90 -25.5l-43 -129q-76 29 -137 29q-87 0 -133.5 -48.5t-75.5 -177.5l-25 -108h238l-25 -127h-237l-232 -1098q-39 -189 -120 -276 t-213 -87q-69 0 -125 21v141z" />
<glyph unicode="g" horiz-adv-x="1026" d="M-127 -211q0 105 72 182t233 131q-78 41 -78 121q0 69 51 118.5t142 92.5q-63 32 -103 94.5t-40 145.5q0 194 119.5 318t305.5 124q78 0 154 -20h371l-25 -107l-211 -24q41 -62 41 -158q0 -191 -116.5 -304.5t-311.5 -113.5q-55 0 -84 8q-139 -53 -139 -131 q0 -41 33 -54.5t96 -21.5l117 -14q181 -22 262.5 -88t81.5 -194q0 -184 -146 -285t-411 -101q-194 0 -304 73.5t-110 207.5zM35 -195q0 -77 65 -122t193 -45q182 0 284.5 63.5t102.5 179.5q0 62 -54 98t-184 50l-159 16q-120 -25 -184 -88t-64 -152zM313 680 q0 -85 45 -129.5t125 -44.5q79 0 138 42t90.5 115.5t31.5 159.5q0 82 -44 125t-126 43q-78 0 -136.5 -40.5t-91 -113t-32.5 -157.5z" />
<glyph unicode="h" horiz-adv-x="1182" d="M59 0l330 1556h168q-18 -82 -34.5 -159t-34 -156.5t-38 -166.5t-47.5 -189h11q94 123 185.5 176t191.5 53q131 0 202.5 -72t71.5 -204q0 -62 -23 -166q-39 -193 -145 -672h-168l148 692q18 94 18 135q0 148 -147 148q-89 0 -173.5 -59t-149 -171.5t-97.5 -271.5 l-101 -473h-168z" />
<glyph unicode="i" horiz-adv-x="520" d="M59 0l234 1096h168l-234 -1096h-168zM340 1376q0 56 32 91.5t83 35.5q88 0 88 -90q0 -55 -33.5 -93t-77.5 -38q-40 0 -66 24.5t-26 69.5z" />
<glyph unicode="j" horiz-adv-x="520" d="M-258 -330q61 -22 119 -22q125 0 168 205l264 1243h166l-266 -1258q-36 -171 -114.5 -250.5t-213.5 -79.5q-69 0 -123 21v141zM340 1376q0 56 32 91.5t83 35.5q86 0 86 -90q0 -55 -33.5 -93t-77.5 -38q-38 0 -64 24.5t-26 69.5z" />
<glyph unicode="k" horiz-adv-x="999" d="M57 0l330 1556h170l-129 -602q-57 -266 -102 -395h4l526 537h201l-469 -467l295 -629h-187l-235 524l-152 -123l-82 -401h-170z" />
<glyph unicode="l" horiz-adv-x="520" d="M57 0l332 1556h168l-332 -1556h-168z" />
<glyph unicode="m" horiz-adv-x="1786" d="M59 0l234 1096h139l-22 -203h10q87 119 173.5 171t178.5 52q113 0 174 -65t72 -181h8q86 125 183 185.5t196 60.5q127 0 196.5 -68t69.5 -198q0 -68 -22 -178l-144 -672h-170l148 692q20 104 20 146q0 62 -34.5 99.5t-108.5 37.5q-81 0 -160 -58t-138.5 -164.5 t-90.5 -252.5l-107 -500h-168l148 692q18 94 18 135q0 70 -31 109t-106 39q-84 0 -163.5 -60t-140 -171.5t-93.5 -268.5l-101 -475h-168z" />
<glyph unicode="n" horiz-adv-x="1182" d="M59 0l234 1096h139l-22 -203h10q96 122 185.5 172.5t185.5 50.5q127 0 200.5 -69.5t73.5 -194.5q0 -79 -23 -180l-143 -672h-170l148 692q20 104 20 144q0 63 -35.5 101t-113.5 38q-89 0 -173.5 -60t-149 -171t-97.5 -269l-101 -475h-168z" />
<glyph unicode="o" horiz-adv-x="1149" d="M98 406q0 190 73 357.5t197 257t275 89.5q190 0 300 -112.5t110 -309.5q0 -188 -72 -355t-195 -258t-278 -91q-192 0 -301 113t-109 309zM270 397q0 -131 63.5 -202.5t182.5 -71.5q104 0 187 73t129.5 207.5t46.5 307.5q0 115 -62.5 186.5t-169.5 71.5q-109 0 -195.5 -74 t-134 -205.5t-47.5 -292.5z" />
<glyph unicode="p" horiz-adv-x="1182" d="M-43 -492l336 1588h139l-26 -209h8q179 227 372 227q137 0 216 -97.5t79 -273.5q0 -212 -69 -389t-191 -275.5t-276 -98.5q-97 0 -170 50t-113 140h-10l-4 -38q-3 -25 -10.5 -70t-114.5 -554h-166zM319 346q0 -110 55.5 -168.5t160.5 -58.5q99 0 184.5 81t137.5 230.5 t52 317.5q0 227 -178 227q-96 0 -195.5 -95t-158 -239t-58.5 -295z" />
<glyph unicode="q" horiz-adv-x="1182" d="M98 350q0 212 72.5 392t196 277t274.5 97q94 0 165.5 -50.5t108.5 -141.5h13l67 172h125l-336 -1588h-166l101 480q9 45 57 221h-8q-95 -121 -185 -175t-186 -54q-140 0 -219.5 97.5t-79.5 272.5zM270 346q0 -227 179 -227q92 0 190 92t158.5 237t60.5 300 q0 105 -54.5 166t-152.5 61q-101 0 -189 -84.5t-140 -233t-52 -311.5z" />
<glyph unicode="r" horiz-adv-x="811" d="M59 0l234 1096h139l-22 -203h10q72 95 119 136.5t98.5 64t114.5 22.5q69 0 120 -14l-36 -150q-53 13 -105 13q-91 0 -170.5 -60t-139 -166.5t-87.5 -236.5l-107 -502h-168z" />
<glyph unicode="s" horiz-adv-x="877" d="M8 49v158q70 -42 151 -65t150 -23q126 0 190 50t64 128q0 57 -35 96t-151 107q-130 73 -184 143t-54 166q0 138 101 222.5t266 84.5q171 0 330 -74l-54 -137l-56 25q-101 43 -220 43q-93 0 -146 -43.5t-53 -112.5q0 -56 35.5 -96t146.5 -103q107 -60 153.5 -103 t69.5 -92.5t23 -111.5q0 -156 -110.5 -243.5t-311.5 -87.5q-169 0 -305 69z" />
<glyph unicode="t" horiz-adv-x="664" d="M90 969l14 73l185 78l125 228h98l-55 -252h274l-26 -127h-273l-129 -604q-18 -87 -18 -132q0 -56 29 -86t81 -30q55 0 144 26v-129q-34 -14 -84 -24t-80 -10q-125 0 -191.5 59.5t-66.5 177.5q0 66 18 150l127 602h-172z" />
<glyph unicode="u" horiz-adv-x="1182" d="M113 248q0 62 22 172l146 676h170l-150 -695q-18 -89 -18 -139q0 -143 147 -143q88 0 173 60t150 172t99 270l100 475h166l-231 -1096h-139l22 203h-12q-98 -125 -187 -174t-184 -49q-128 0 -201 69.5t-73 198.5z" />
<glyph unicode="v" horiz-adv-x="946" d="M98 1096h168l64 -613q24 -258 24 -362h6q127 275 179 371l325 604h178l-591 -1096h-228z" />
<glyph unicode="w" horiz-adv-x="1468" d="M117 1096h164l18 -594v-88q0 -147 -8 -269h6q47 124 137 322l295 629h182l37 -594q6 -168 6 -262v-53l-2 -42h6q28 86 83 218.5t323 732.5h178l-506 -1096h-205l-32 602q-4 94 -4 172v156h-9l-50 -118l-83 -189l-291 -623h-202z" />
<glyph unicode="x" horiz-adv-x="979" d="M-74 0l475 565l-239 531h170l174 -412l330 412h194l-455 -539l252 -557h-168l-192 434l-346 -434h-195z" />
<glyph unicode="y" horiz-adv-x="946" d="M-197 -336q63 -18 131 -18q82 0 140.5 50.5t113.5 149.5l76 136l-166 1114h168l74 -545q10 -69 19.5 -203.5t9.5 -216.5h6q35 87 87 200t77 156l325 609h178l-696 -1282q-93 -172 -184 -239t-219 -67q-72 0 -140 21v135z" />
<glyph unicode="z" horiz-adv-x="909" d="M-29 0l23 117l694 854h-479l27 125h657l-29 -140l-680 -831h531l-25 -125h-719z" />
<glyph unicode="{" horiz-adv-x="715" d="M27 514l32 143q118 0 189.5 43.5t93.5 147.5l68 326q34 160 117.5 224t254.5 64h33l-31 -141q-105 0 -151 -36.5t-66 -123.5l-71 -321q-28 -123 -91 -184t-167 -78v-5q151 -41 151 -213q0 -59 -18 -131l-47 -211q-15 -58 -15 -98q0 -53 36.5 -77.5t119.5 -24.5v-142h-23 q-141 0 -216.5 52.5t-75.5 171.5q0 52 20 141q33 146 51.5 227.5t14.5 102.5q0 143 -209 143z" />
<glyph unicode="|" d="M541 -496v2052h139v-2052h-139z" />
<glyph unicode="}" horiz-adv-x="715" d="M-74 -182q115 0 167 36t71 123l72 322q25 117 88 179.5t170 80.5v6q-150 42 -150 211q0 59 18 131l50 213q14 65 14 99q0 53 -40.5 77.5t-139.5 24.5l28 141h11q144 0 220.5 -52.5t76.5 -170.5q0 -48 -21 -141l-49 -219q-16 -68 -16 -111q0 -143 209 -143l-33 -144 q-119 0 -190 -43t-93 -147l-67 -326q-36 -164 -119 -226.5t-264 -62.5h-13v142z" />
<glyph unicode="~" d="M115 592v151q98 109 243 109q69 0 127 -14.5t144 -51.5q64 -27 112.5 -41t98.5 -14q55 0 119.5 33t115.5 88v-150q-100 -110 -244 -110q-72 0 -135 16.5t-135 48.5q-75 32 -120 44t-93 12q-54 0 -118.5 -34.5t-114.5 -86.5z" />
<glyph unicode="&#xa2;" d="M225 590q0 185 63.5 344t178.5 258.5t260 120.5l35 170h123l-37 -168q119 -9 217 -49l-47 -142q-109 52 -219 52q-112 0 -204.5 -76.5t-145 -213t-52.5 -296.5q0 -125 66 -198t184 -73q72 0 136 20t126 48v-143q-123 -62 -286 -66l-41 -198h-125l43 215 q-132 34 -203.5 137.5t-71.5 257.5z" />
<glyph unicode="&#xa3;" d="M-23 0l27 141q205 46 258 289l47 221h-200l26 127h201l76 350q75 353 430 353q184 0 336 -86l-66 -133q-146 79 -278 79q-213 0 -263 -237l-69 -326h370l-26 -127h-371l-47 -219q-22 -98 -66 -166.5t-124 -111.5h725l-33 -154h-953z" />
<glyph unicode="&#xa5;" d="M127 266l29 133h290l33 160h-291l29 133h225l-202 770h163l179 -747l491 747h187l-533 -770h231l-28 -133h-297l-33 -160h297l-29 -133h-295l-57 -266h-154l56 266h-291z" />
<glyph unicode="&#xa9;" horiz-adv-x="1704" d="M139 731q0 200 100 375t275 276t377 101q197 0 370 -97t277 -272t104 -383q0 -204 -100.5 -376.5t-273 -273.5t-377.5 -101q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM244 731q0 -173 87 -323.5t237.5 -237t322.5 -86.5q174 0 323 87t236.5 235.5t87.5 324.5 q0 174 -87 323t-235.5 236.5t-324.5 87.5q-174 0 -323 -87t-236.5 -235.5t-87.5 -324.5zM520 733q0 208 110 330.5t300 122.5q130 0 248 -60l-60 -120q-106 53 -190 53q-125 0 -191.5 -87t-66.5 -241q0 -169 65 -249.5t193 -80.5q82 0 211 43v-122q-66 -28 -113 -38 t-104 -10q-192 0 -297 119.5t-105 339.5z" />
<glyph unicode="&#xad;" horiz-adv-x="639" d="M55 469l35 158h479l-34 -158h-480z" />
<glyph unicode="&#xae;" horiz-adv-x="1704" d="M139 731q0 200 100 375t275 276t377 101q197 0 370 -97t277 -272t104 -383q0 -204 -100.5 -376.5t-273 -273.5t-377.5 -101q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM244 731q0 -173 87 -323.5t237.5 -237t322.5 -86.5q174 0 323 87t236.5 235.5t87.5 324.5 q0 174 -87 323t-235.5 236.5t-324.5 87.5q-174 0 -323 -87t-236.5 -235.5t-87.5 -324.5zM645 291v880h229q163 0 241.5 -63t78.5 -193q0 -78 -47.5 -141t-132.5 -98l227 -385h-149l-207 352h-113v-352h-127zM772 762h92q195 0 195 149q0 76 -47.5 107t-149.5 31h-90v-287z " />
<glyph unicode="&#xb4;" horiz-adv-x="1135" d="M532 1241v27q56 60 125.5 151.5t106.5 149.5h190v-21q-38 -49 -140 -151t-177 -156h-105z" />
<glyph unicode="&#x2000;" horiz-adv-x="784" />
<glyph unicode="&#x2001;" horiz-adv-x="1569" />
<glyph unicode="&#x2002;" horiz-adv-x="784" />
<glyph unicode="&#x2003;" horiz-adv-x="1569" />
<glyph unicode="&#x2004;" horiz-adv-x="523" />
<glyph unicode="&#x2005;" horiz-adv-x="392" />
<glyph unicode="&#x2006;" horiz-adv-x="261" />
<glyph unicode="&#x2007;" horiz-adv-x="261" />
<glyph unicode="&#x2008;" horiz-adv-x="196" />
<glyph unicode="&#x2009;" horiz-adv-x="313" />
<glyph unicode="&#x200a;" horiz-adv-x="87" />
<glyph unicode="&#x2010;" horiz-adv-x="639" d="M55 469l35 158h479l-34 -158h-480z" />
<glyph unicode="&#x2011;" horiz-adv-x="639" d="M55 469l35 158h479l-34 -158h-480z" />
<glyph unicode="&#x2012;" horiz-adv-x="639" d="M55 469l35 158h479l-34 -158h-480z" />
<glyph unicode="&#x2013;" horiz-adv-x="983" d="M55 469l35 160h823l-34 -160h-824z" />
<glyph unicode="&#x2014;" horiz-adv-x="1966" d="M55 469l35 160h1806l-34 -160h-1807z" />
<glyph unicode="&#x2018;" horiz-adv-x="348" d="M123 983q98 211 270 479h127q-147 -345 -203 -501h-188z" />
<glyph unicode="&#x2019;" horiz-adv-x="348" d="M125 961q134 298 203 501h188l8 -22q-40 -91 -111 -218.5t-159 -260.5h-129z" />
<glyph unicode="&#x201c;" horiz-adv-x="719" d="M123 983q98 211 270 479h127q-147 -345 -203 -501h-188zM492 983q80 181 272 479h127q-162 -379 -203 -501h-188z" />
<glyph unicode="&#x201d;" horiz-adv-x="719" d="M125 961q134 298 203 501h188l8 -22q-40 -91 -111 -218.5t-159 -260.5h-129zM494 961q57 126 115.5 272.5t86.5 228.5h189l10 -22q-94 -206 -274 -479h-127z" />
<glyph unicode="&#x2022;" horiz-adv-x="774" d="M199 684q0 145 73.5 231t198.5 86q92 0 139 -49t47 -141q0 -141 -74 -230t-202 -89q-89 0 -135.5 49.5t-46.5 142.5z" />
<glyph unicode="&#x2026;" horiz-adv-x="1563" d="M563 74q0 77 40.5 122.5t111.5 45.5q43 0 69.5 -26t26.5 -79q0 -71 -40 -118.5t-108 -47.5q-46 0 -73 26t-27 77zM1085 74q0 77 40.5 122.5t111.5 45.5q43 0 69.5 -26t26.5 -79q0 -71 -40 -118.5t-108 -47.5q-46 0 -73 26t-27 77zM43 74q0 77 40.5 122.5t111.5 45.5 q43 0 69.5 -26t26.5 -79q0 -71 -40 -118.5t-108 -47.5q-46 0 -73 26t-27 77z" />
<glyph unicode="&#x202f;" horiz-adv-x="313" />
<glyph unicode="&#x205f;" horiz-adv-x="392" />
<glyph unicode="&#x20ac;" d="M63 504l27 131h154q8 80 30 164h-151l27 133h159q97 267 259.5 408t369.5 141q89 0 160 -21.5t141 -70.5l-80 -138q-113 78 -231 78q-140 0 -254 -99t-189 -298h426l-26 -133h-441q-21 -65 -32 -164h381l-29 -131h-361q0 -373 297 -373q123 0 256 55v-147 q-127 -59 -278 -59q-212 0 -328.5 133.5t-116.5 378.5v12h-170z" />
<glyph unicode="&#x2122;" horiz-adv-x="1534" d="M121 1358v104h516v-104h-199v-617h-121v617h-196zM705 741v721h180l182 -557l193 557h170v-721h-121v430q0 73 4 121h-6l-197 -551h-96l-189 551h-6q4 -52 4 -121v-430h-118z" />
<glyph unicode="&#xe000;" horiz-adv-x="1095" d="M0 1095h1095v-1095h-1095v1095z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 27 KiB

BIN
couchpotato/static/fonts/OpenSans-Italic-webfont.ttf

Binary file not shown.

BIN
couchpotato/static/fonts/OpenSans-Italic-webfont.woff

Binary file not shown.

BIN
couchpotato/static/fonts/OpenSans-Regular-webfont.eot

Binary file not shown.

146
couchpotato/static/fonts/OpenSans-Regular-webfont.svg

@ -0,0 +1,146 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
This is a custom SVG webfont generated by Font Squirrel.
Copyright : Digitized data copyright 20102011 Google Corporation
Foundry : Ascender Corporation
Foundry URL : httpwwwascendercorpcom
</metadata>
<defs>
<font id="OpenSansRegular" horiz-adv-x="1171" >
<font-face units-per-em="2048" ascent="1638" descent="-410" />
<missing-glyph horiz-adv-x="532" />
<glyph unicode=" " horiz-adv-x="532" />
<glyph unicode="&#x09;" horiz-adv-x="532" />
<glyph unicode="&#xa0;" horiz-adv-x="532" />
<glyph unicode="!" horiz-adv-x="547" d="M152 106q0 136 120 136q58 0 89.5 -35t31.5 -101q0 -64 -32 -99.5t-89 -35.5q-52 0 -86 31.5t-34 103.5zM170 1462h207l-51 -1059h-105z" />
<glyph unicode="&#x22;" horiz-adv-x="821" d="M133 1462h186l-40 -528h-105zM502 1462h186l-41 -528h-104z" />
<glyph unicode="#" horiz-adv-x="1323" d="M51 430v129h287l68 340h-277v127h299l82 436h139l-82 -436h305l84 436h134l-84 -436h264v-127h-289l-66 -340h283v-129h-307l-84 -430h-137l84 430h-303l-82 -430h-136l80 430h-262zM475 559h303l66 340h-303z" />
<glyph unicode="$" d="M131 170v156q83 -37 191.5 -60.5t197.5 -23.5v440q-205 65 -287.5 151t-82.5 222q0 131 101.5 215t268.5 102v182h129v-180q184 -5 355 -74l-52 -131q-149 59 -303 70v-434q157 -50 235 -97.5t115 -109t37 -149.5q0 -136 -102 -224.5t-285 -111.5v-232h-129v223 q-112 0 -217 17.5t-172 48.5zM319 1057q0 -76 45 -122t156 -87v387q-99 -16 -150 -62.5t-51 -115.5zM649 252q217 30 217 184q0 72 -44.5 116.5t-172.5 88.5v-389z" />
<glyph unicode="%" horiz-adv-x="1686" d="M104 1026q0 227 74.5 342t220.5 115q145 0 223 -119t78 -338q0 -228 -76.5 -344.5t-224.5 -116.5q-140 0 -217.5 119t-77.5 342zM242 1026q0 -170 37 -255t120 -85q164 0 164 340q0 338 -164 338q-83 0 -120 -84t-37 -254zM365 0l811 1462h147l-811 -1462h-147zM985 440 q0 227 74.5 342t220.5 115q142 0 221.5 -117.5t79.5 -339.5q0 -227 -76.5 -343.5t-224.5 -116.5q-142 0 -218.5 119t-76.5 341zM1122 440q0 -171 37 -255.5t121 -84.5t124 83.5t40 256.5q0 171 -40 253.5t-124 82.5t-121 -82.5t-37 -253.5z" />
<glyph unicode="&#x26;" horiz-adv-x="1495" d="M113 379q0 130 69.5 230t249.5 202q-85 95 -115.5 144t-48.5 102t-18 110q0 150 98 234t273 84q162 0 255 -83.5t93 -232.5q0 -107 -68 -197.5t-225 -183.5l407 -391q56 62 89.5 145.5t56.5 182.5h168q-68 -286 -205 -434l299 -291h-229l-185 178q-118 -106 -240 -152 t-272 -46q-215 0 -333.5 106t-118.5 293zM285 383q0 -117 77.5 -185.5t206.5 -68.5q241 0 400 154l-437 424q-111 -68 -157 -112.5t-68 -95.5t-22 -116zM414 1171q0 -69 36 -131.5t123 -150.5q129 75 179.5 138.5t50.5 146.5q0 77 -51.5 125.5t-137.5 48.5q-89 0 -144.5 -48 t-55.5 -129z" />
<glyph unicode="'" horiz-adv-x="453" d="M133 1462h186l-40 -528h-105z" />
<glyph unicode="(" horiz-adv-x="606" d="M82 561q0 265 77.5 496t223.5 405h162q-144 -193 -216.5 -424t-72.5 -475q0 -240 74 -469t213 -418h-160q-147 170 -224 397t-77 488z" />
<glyph unicode=")" horiz-adv-x="606" d="M61 1462h162q147 -175 224 -406.5t77 -494.5t-77.5 -490t-223.5 -395h-160q139 188 213 417.5t74 469.5q0 244 -72.5 475t-216.5 424z" />
<glyph unicode="*" horiz-adv-x="1130" d="M86 1090l29 182l391 -111l-43 395h194l-43 -395l398 111l26 -182l-381 -31l248 -326l-172 -94l-176 362l-160 -362l-176 94l242 326z" />
<glyph unicode="+" d="M104 653v138h410v428h139v-428h412v-138h-412v-426h-139v426h-410z" />
<glyph unicode="," horiz-adv-x="502" d="M63 -264q27 104 59.5 257t45.5 245h182l15 -23q-26 -100 -75 -232.5t-102 -246.5h-125z" />
<glyph unicode="-" horiz-adv-x="659" d="M84 473v152h491v-152h-491z" />
<glyph unicode="." horiz-adv-x="545" d="M152 106q0 67 30.5 101.5t87.5 34.5q58 0 90.5 -34.5t32.5 -101.5q0 -65 -33 -100t-90 -35q-51 0 -84.5 31.5t-33.5 103.5z" />
<glyph unicode="/" horiz-adv-x="752" d="M20 0l545 1462h166l-545 -1462h-166z" />
<glyph unicode="0" d="M102 733q0 382 119 567t363 185q238 0 361.5 -193t123.5 -559q0 -379 -119.5 -566t-365.5 -187q-236 0 -359 191.5t-123 561.5zM270 733q0 -319 75 -464.5t239 -145.5q166 0 240.5 147.5t74.5 462.5t-74.5 461.5t-240.5 146.5q-164 0 -239 -144.5t-75 -463.5z" />
<glyph unicode="1" d="M188 1163l387 299h140v-1462h-162v1042q0 130 8 246q-21 -21 -47 -44t-238 -195z" />
<glyph unicode="2" d="M100 0v143l385 387q176 178 232 254t84 148t28 155q0 117 -71 185.5t-197 68.5q-91 0 -172.5 -30t-181.5 -109l-88 113q202 168 440 168q206 0 323 -105.5t117 -283.5q0 -139 -78 -275t-292 -344l-320 -313v-8h752v-154h-961z" />
<glyph unicode="3" d="M94 59v158q95 -47 202.5 -71.5t203.5 -24.5q379 0 379 297q0 266 -418 266h-144v143h146q171 0 271 75.5t100 209.5q0 107 -73.5 168t-199.5 61q-96 0 -181 -26t-194 -96l-84 112q90 71 207.5 111.5t247.5 40.5q213 0 331 -97.5t118 -267.5q0 -140 -78.5 -229 t-222.5 -119v-8q176 -22 261 -112t85 -236q0 -209 -145 -321.5t-412 -112.5q-116 0 -212.5 17.5t-187.5 61.5z" />
<glyph unicode="4" d="M43 336v145l694 989h176v-983h217v-151h-217v-336h-159v336h-711zM209 487h545v486q0 143 10 323h-8q-48 -96 -90 -159z" />
<glyph unicode="5" d="M133 59v160q70 -45 174 -70.5t205 -25.5q176 0 273.5 83t97.5 240q0 306 -375 306q-95 0 -254 -29l-86 55l55 684h727v-153h-585l-37 -439q115 23 229 23q231 0 363.5 -114.5t132.5 -313.5q0 -227 -144.5 -356t-398.5 -129q-247 0 -377 79z" />
<glyph unicode="6" d="M117 625q0 431 167.5 644.5t495.5 213.5q113 0 178 -19v-143q-77 25 -176 25q-235 0 -359 -146.5t-136 -460.5h12q110 172 348 172q197 0 310.5 -119t113.5 -323q0 -228 -124.5 -358.5t-336.5 -130.5q-227 0 -360 170.5t-133 474.5zM287 506q0 -103 40 -192t113.5 -141 t167.5 -52q142 0 220.5 89.5t78.5 258.5q0 145 -73 228t-218 83q-90 0 -165 -37t-119.5 -102t-44.5 -135z" />
<glyph unicode="7" d="M94 1309v153h973v-133l-598 -1329h-184l606 1309h-797z" />
<glyph unicode="8" d="M104 373q0 251 306 391q-138 78 -198 168.5t-60 202.5q0 159 117.5 253.5t314.5 94.5q200 0 317 -93t117 -257q0 -108 -67 -197t-214 -162q178 -85 253 -178.5t75 -216.5q0 -182 -127 -290.5t-348 -108.5q-234 0 -360 102.5t-126 290.5zM268 369q0 -120 83.5 -187 t234.5 -67q149 0 232 70t83 192q0 97 -78 172.5t-272 146.5q-149 -64 -216 -141.5t-67 -185.5zM315 1128q0 -92 59 -158t218 -132q143 60 202.5 129t59.5 161q0 101 -72.5 160.5t-199.5 59.5q-125 0 -196 -60t-71 -160z" />
<glyph unicode="9" d="M106 991q0 228 127.5 360t335.5 132q149 0 260.5 -76.5t171.5 -223t60 -345.5q0 -858 -664 -858q-116 0 -184 20v143q80 -26 182 -26q240 0 362.5 148.5t133.5 455.5h-12q-55 -83 -146 -126.5t-205 -43.5q-194 0 -308 116t-114 324zM270 993q0 -144 72 -226.5t219 -82.5 q91 0 167.5 37t120.5 101t44 134q0 105 -41 194t-114.5 140t-168.5 51q-143 0 -221 -92t-78 -256z" />
<glyph unicode=":" horiz-adv-x="545" d="M152 106q0 67 30.5 101.5t87.5 34.5q58 0 90.5 -34.5t32.5 -101.5q0 -65 -33 -100t-90 -35q-51 0 -84.5 31.5t-33.5 103.5zM152 989q0 135 118 135q123 0 123 -135q0 -65 -33 -100t-90 -35q-51 0 -84.5 31.5t-33.5 103.5z" />
<glyph unicode=";" horiz-adv-x="545" d="M63 -264q27 104 59.5 257t45.5 245h182l15 -23q-26 -100 -75 -232.5t-102 -246.5h-125zM147 989q0 135 119 135q123 0 123 -135q0 -65 -33 -100t-90 -35q-58 0 -88.5 35t-30.5 100z" />
<glyph unicode="&#x3c;" d="M104 664v98l961 479v-149l-782 -371l782 -328v-151z" />
<glyph unicode="=" d="M119 449v137h930v-137h-930zM119 858v137h930v-137h-930z" />
<glyph unicode="&#x3e;" d="M104 242v151l783 326l-783 373v149l961 -479v-98z" />
<glyph unicode="?" horiz-adv-x="879" d="M27 1384q189 99 395 99q191 0 297 -94t106 -265q0 -73 -19.5 -128.5t-57.5 -105t-164 -159.5q-101 -86 -133.5 -143t-32.5 -152v-33h-129v54q0 117 36 192.5t134 159.5q136 115 171.5 173t35.5 140q0 102 -65.5 157.5t-188.5 55.5q-79 0 -154 -18.5t-172 -67.5zM240 106 q0 136 120 136q58 0 89.5 -35t31.5 -101q0 -64 -32 -99.5t-89 -35.5q-52 0 -86 31.5t-34 103.5z" />
<glyph unicode="@" horiz-adv-x="1841" d="M121 571q0 260 107 463t305 314.5t454 111.5q215 0 382.5 -90.5t259 -257t91.5 -383.5q0 -142 -44 -260t-124 -183t-184 -65q-86 0 -145 52t-70 133h-8q-40 -87 -114.5 -136t-176.5 -49q-150 0 -234.5 102.5t-84.5 278.5q0 204 118 331.5t310 127.5q68 0 154 -12.5 t155 -34.5l-25 -470v-22q0 -178 133 -178q91 0 148 107.5t57 279.5q0 181 -74 317t-210.5 209.5t-313.5 73.5q-223 0 -388 -92.5t-252 -264t-87 -396.5q0 -305 161 -469t464 -164q210 0 436 86v-133q-192 -84 -436 -84q-363 0 -563.5 199.5t-200.5 557.5zM686 598 q0 -254 195 -254q207 0 225 313l14 261q-72 20 -157 20q-130 0 -203.5 -90t-73.5 -250z" />
<glyph unicode="A" horiz-adv-x="1296" d="M0 0l578 1468h143l575 -1468h-176l-182 465h-586l-180 -465h-172zM412 618h473l-170 453q-33 86 -68 211q-22 -96 -63 -211z" />
<glyph unicode="B" horiz-adv-x="1327" d="M201 0v1462h413q291 0 421 -87t130 -275q0 -130 -72.5 -214.5t-211.5 -109.5v-10q333 -57 333 -350q0 -196 -132.5 -306t-370.5 -110h-510zM371 145h305q177 0 266.5 68.5t89.5 214.5q0 136 -91.5 200t-278.5 64h-291v-547zM371 836h280q180 0 259 56.5t79 190.5 q0 123 -88 177.5t-280 54.5h-250v-479z" />
<glyph unicode="C" horiz-adv-x="1292" d="M125 733q0 226 84.5 396t244 262t375.5 92q230 0 402 -84l-72 -146q-166 78 -332 78q-241 0 -380.5 -160.5t-139.5 -439.5q0 -287 134.5 -443.5t383.5 -156.5q153 0 349 55v-149q-152 -57 -375 -57q-323 0 -498.5 196t-175.5 557z" />
<glyph unicode="D" horiz-adv-x="1493" d="M201 0v1462h448q341 0 530 -189t189 -528q0 -362 -196.5 -553.5t-565.5 -191.5h-405zM371 147h207q304 0 457 149.5t153 442.5q0 286 -143.5 431t-426.5 145h-247v-1168z" />
<glyph unicode="E" horiz-adv-x="1139" d="M201 0v1462h815v-151h-645v-471h606v-150h-606v-538h645v-152h-815z" />
<glyph unicode="F" horiz-adv-x="1057" d="M201 0v1462h815v-151h-645v-535h606v-151h-606v-625h-170z" />
<glyph unicode="G" horiz-adv-x="1491" d="M125 731q0 228 91.5 399.5t263.5 262t403 90.5q234 0 436 -86l-66 -150q-198 84 -381 84q-267 0 -417 -159t-150 -441q0 -296 144.5 -449t424.5 -153q152 0 297 35v450h-327v152h497v-711q-116 -37 -236 -56t-278 -19q-332 0 -517 197.5t-185 553.5z" />
<glyph unicode="H" horiz-adv-x="1511" d="M201 0v1462h170v-622h770v622h170v-1462h-170v688h-770v-688h-170z" />
<glyph unicode="I" horiz-adv-x="571" d="M201 0v1462h170v-1462h-170z" />
<glyph unicode="J" horiz-adv-x="547" d="M-160 -213q71 -20 148 -20q99 0 150.5 60t51.5 173v1462h170v-1448q0 -190 -96 -294.5t-276 -104.5q-94 0 -148 27v145z" />
<glyph unicode="K" horiz-adv-x="1257" d="M201 0v1462h170v-725l663 725h201l-588 -635l610 -827h-200l-533 709l-153 -136v-573h-170z" />
<glyph unicode="L" horiz-adv-x="1063" d="M201 0v1462h170v-1308h645v-154h-815z" />
<glyph unicode="M" horiz-adv-x="1849" d="M201 0v1462h256l463 -1206h8l467 1206h254v-1462h-170v942q0 162 14 352h-8l-500 -1294h-137l-496 1296h-8q14 -154 14 -366v-930h-157z" />
<glyph unicode="N" horiz-adv-x="1544" d="M201 0v1462h192l797 -1222h8q-2 28 -9 174q-5 114 -5 177v32v839h159v-1462h-194l-799 1227h-8q16 -216 16 -396v-831h-157z" />
<glyph unicode="O" horiz-adv-x="1595" d="M125 735q0 357 176 553.5t500 196.5q315 0 492 -200t177 -552q0 -351 -177.5 -552t-493.5 -201q-323 0 -498.5 197.5t-175.5 557.5zM305 733q0 -297 126.5 -450.5t367.5 -153.5q243 0 367 153t124 451q0 295 -123.5 447.5t-365.5 152.5q-243 0 -369.5 -153.5 t-126.5 -446.5z" />
<glyph unicode="P" horiz-adv-x="1233" d="M201 0v1462h379q548 0 548 -426q0 -222 -151.5 -341.5t-433.5 -119.5h-172v-575h-170zM371 721h153q226 0 327 73t101 234q0 145 -95 216t-296 71h-190v-594z" />
<glyph unicode="Q" horiz-adv-x="1595" d="M125 735q0 357 176 553.5t500 196.5q315 0 492 -200t177 -552q0 -281 -113 -467t-319 -252l348 -362h-247l-285 330l-55 -2q-323 0 -498.5 197.5t-175.5 557.5zM305 733q0 -297 126.5 -450.5t367.5 -153.5q243 0 367 153t124 451q0 295 -123.5 447.5t-365.5 152.5 q-243 0 -369.5 -153.5t-126.5 -446.5z" />
<glyph unicode="R" horiz-adv-x="1266" d="M201 0v1462h401q269 0 397.5 -103t128.5 -310q0 -290 -294 -392l397 -657h-201l-354 608h-305v-608h-170zM371 754h233q180 0 264 71.5t84 214.5q0 145 -85.5 209t-274.5 64h-221v-559z" />
<glyph unicode="S" horiz-adv-x="1124" d="M106 47v164q90 -38 196 -60t210 -22q170 0 256 64.5t86 179.5q0 76 -30.5 124.5t-102 89.5t-217.5 93q-204 73 -291.5 173t-87.5 261q0 169 127 269t336 100q218 0 401 -80l-53 -148q-181 76 -352 76q-135 0 -211 -58t-76 -161q0 -76 28 -124.5t94.5 -89t203.5 -89.5 q230 -82 316.5 -176t86.5 -244q0 -193 -140 -301t-380 -108q-260 0 -400 67z" />
<glyph unicode="T" horiz-adv-x="1133" d="M18 1311v151h1096v-151h-463v-1311h-170v1311h-463z" />
<glyph unicode="U" horiz-adv-x="1491" d="M186 520v942h170v-954q0 -183 100 -281t294 -98q185 0 285 98.5t100 282.5v952h170v-946q0 -250 -151 -393t-415 -143t-408.5 144t-144.5 396z" />
<glyph unicode="V" horiz-adv-x="1219" d="M0 1462h180l336 -946q58 -163 92 -317q36 162 94 323l334 940h183l-527 -1462h-168z" />
<glyph unicode="W" horiz-adv-x="1896" d="M27 1462h180l231 -903q48 -190 70 -344q27 183 80 358l262 889h180l275 -897q48 -155 81 -350q19 142 72 346l230 901h180l-391 -1462h-168l-295 979q-21 65 -47 164t-27 119q-22 -132 -70 -289l-286 -973h-168z" />
<glyph unicode="X" horiz-adv-x="1182" d="M8 0l486 764l-453 698h188l363 -579l366 579h181l-453 -692l488 -770h-193l-393 643l-400 -643h-180z" />
<glyph unicode="Y" horiz-adv-x="1147" d="M0 1462h186l387 -731l390 731h184l-488 -895v-567h-172v559z" />
<glyph unicode="Z" horiz-adv-x="1169" d="M82 0v133l776 1176h-752v153h959v-133l-776 -1175h798v-154h-1005z" />
<glyph unicode="[" horiz-adv-x="674" d="M166 -324v1786h457v-141h-289v-1503h289v-142h-457z" />
<glyph unicode="\" horiz-adv-x="752" d="M23 1462h163l547 -1462h-166z" />
<glyph unicode="]" horiz-adv-x="674" d="M51 -182h289v1503h-289v141h457v-1786h-457v142z" />
<glyph unicode="^" horiz-adv-x="1110" d="M49 551l434 922h99l477 -922h-152l-372 745l-334 -745h-152z" />
<glyph unicode="_" horiz-adv-x="918" d="M-4 -184h926v-131h-926v131z" />
<glyph unicode="`" horiz-adv-x="1182" d="M393 1548v21h203q32 -69 89 -159.5t101 -143.5v-25h-110q-65 52 -154 148t-129 159z" />
<glyph unicode="a" horiz-adv-x="1139" d="M94 303q0 332 531 348l186 6v68q0 129 -55.5 190.5t-177.5 61.5q-137 0 -310 -84l-51 127q81 44 177.5 69t193.5 25q196 0 290.5 -87t94.5 -279v-748h-123l-33 156h-8q-82 -103 -163.5 -139.5t-203.5 -36.5q-163 0 -255.5 84t-92.5 239zM268 301q0 -90 54.5 -137 t152.5 -47q155 0 243.5 85t88.5 238v99l-166 -7q-198 -7 -285.5 -61.5t-87.5 -169.5z" />
<glyph unicode="b" horiz-adv-x="1255" d="M176 0v1556h166v-378q0 -127 -8 -228h8q116 164 344 164q216 0 335.5 -147.5t119.5 -417.5t-120.5 -419.5t-334.5 -149.5q-107 0 -195.5 39.5t-148.5 121.5h-12l-35 -141h-119zM342 549q0 -231 77 -330.5t247 -99.5q153 0 228 111.5t75 320.5q0 214 -75 319t-232 105 q-170 0 -245 -97.5t-75 -328.5z" />
<glyph unicode="c" horiz-adv-x="975" d="M115 541q0 275 132.5 425t377.5 150q79 0 158 -17t124 -40l-51 -141q-55 22 -120 36.5t-115 14.5q-334 0 -334 -426q0 -202 81.5 -310t241.5 -108q137 0 281 59v-147q-110 -57 -277 -57q-238 0 -368.5 146.5t-130.5 414.5z" />
<glyph unicode="d" horiz-adv-x="1255" d="M115 545q0 271 120 421t334 150q223 0 342 -162h13l-7 79l-4 77v446h166v-1556h-135l-22 147h-9q-115 -167 -344 -167q-215 0 -334.5 147t-119.5 418zM287 543q0 -210 77 -317t226 -107q170 0 246.5 92.5t76.5 298.5v35q0 233 -77.5 332.5t-247.5 99.5 q-146 0 -223.5 -113.5t-77.5 -320.5z" />
<glyph unicode="e" horiz-adv-x="1149" d="M115 539q0 265 130.5 421t350.5 156q206 0 326 -135.5t120 -357.5v-105h-755q5 -193 97.5 -293t260.5 -100q177 0 350 74v-148q-88 -38 -166.5 -54.5t-189.5 -16.5q-243 0 -383.5 148t-140.5 411zM291 653h573q0 157 -70 240.5t-200 83.5q-132 0 -210.5 -86t-92.5 -238z " />
<glyph unicode="f" horiz-adv-x="694" d="M29 967v75l196 60v61q0 404 353 404q87 0 204 -35l-43 -133q-96 31 -164 31q-94 0 -139 -62.5t-45 -200.5v-71h279v-129h-279v-967h-166v967h-196z" />
<glyph unicode="g" horiz-adv-x="1122" d="M39 -186q0 100 64 173t180 99q-42 19 -70.5 59t-28.5 93q0 60 32 105t101 87q-85 35 -138.5 119t-53.5 192q0 180 108 277.5t306 97.5q86 0 155 -20h379v-105l-203 -24q28 -35 50 -91.5t22 -127.5q0 -161 -110 -257t-302 -96q-49 0 -92 8q-106 -56 -106 -141 q0 -45 37 -66.5t127 -21.5h194q178 0 273.5 -75t95.5 -218q0 -182 -146 -277.5t-426 -95.5q-215 0 -331.5 80t-116.5 226zM199 -184q0 -89 75 -135t215 -46q209 0 309.5 62.5t100.5 169.5q0 89 -55 123.5t-207 34.5h-199q-113 0 -176 -54t-63 -155zM289 745q0 -115 65 -174 t181 -59q243 0 243 236q0 247 -246 247q-117 0 -180 -63t-63 -187z" />
<glyph unicode="h" horiz-adv-x="1257" d="M176 0v1556h166v-471q0 -85 -8 -141h10q49 79 139.5 124.5t206.5 45.5q201 0 301.5 -95.5t100.5 -303.5v-715h-166v709q0 134 -61 200t-191 66q-173 0 -252.5 -94t-79.5 -308v-573h-166z" />
<glyph unicode="i" horiz-adv-x="518" d="M162 1393q0 57 28 83.5t70 26.5q40 0 69 -27t29 -83t-29 -83.5t-69 -27.5q-42 0 -70 27.5t-28 83.5zM176 0v1096h166v-1096h-166z" />
<glyph unicode="j" horiz-adv-x="518" d="M-111 -332q69 -20 136 -20q78 0 114.5 42.5t36.5 129.5v1276h166v-1264q0 -324 -299 -324q-95 0 -154 25v135zM162 1393q0 57 28 83.5t70 26.5q40 0 69 -27t29 -83t-29 -83.5t-69 -27.5q-42 0 -70 27.5t-28 83.5z" />
<glyph unicode="k" horiz-adv-x="1075" d="M176 0v1556h164v-825q0 -55 -8 -170h8q43 61 131 160l354 375h197l-444 -467l475 -629h-201l-387 518l-125 -108v-410h-164z" />
<glyph unicode="l" horiz-adv-x="518" d="M176 0v1556h166v-1556h-166z" />
<glyph unicode="m" horiz-adv-x="1905" d="M176 0v1096h135l27 -150h8q47 80 132.5 125t191.5 45q257 0 336 -186h8q49 86 142 136t212 50q186 0 278.5 -95.5t92.5 -305.5v-715h-166v713q0 131 -56 196.5t-174 65.5q-155 0 -229 -89t-74 -274v-612h-166v713q0 131 -56 196.5t-175 65.5q-156 0 -228.5 -93.5 t-72.5 -306.5v-575h-166z" />
<glyph unicode="n" horiz-adv-x="1257" d="M176 0v1096h135l27 -150h8q51 81 143 125.5t205 44.5q198 0 298 -95.5t100 -305.5v-715h-166v709q0 134 -61 200t-191 66q-172 0 -252 -93t-80 -307v-575h-166z" />
<glyph unicode="o" horiz-adv-x="1237" d="M115 549q0 268 134 417.5t372 149.5q230 0 365.5 -153t135.5 -414q0 -268 -135 -418.5t-373 -150.5q-147 0 -261 69t-176 198t-62 302zM287 549q0 -210 84 -320t247 -110t247.5 109.5t84.5 320.5q0 209 -84.5 317.5t-249.5 108.5q-163 0 -246 -107t-83 -319z" />
<glyph unicode="p" horiz-adv-x="1255" d="M176 -492v1588h135l23 -150h8q64 90 149 130t195 40q218 0 336.5 -149t118.5 -418q0 -270 -120.5 -419.5t-334.5 -149.5q-107 0 -195.5 39.5t-148.5 121.5h-12q12 -96 12 -182v-451h-166zM342 549q0 -231 77 -330.5t247 -99.5q142 0 222.5 115t80.5 317 q0 205 -80.5 314.5t-226.5 109.5q-168 0 -243 -93t-77 -296v-37z" />
<glyph unicode="q" horiz-adv-x="1255" d="M115 545q0 269 120 420t334 151q225 0 346 -170h9l24 150h131v-1588h-166v469q0 100 11 170h-13q-115 -167 -346 -167q-212 0 -331 149t-119 416zM287 543q0 -207 76.5 -315.5t226.5 -108.5q166 0 242 89t81 300v37q0 230 -78 331t-247 101q-146 0 -223.5 -113.5 t-77.5 -320.5z" />
<glyph unicode="r" horiz-adv-x="836" d="M176 0v1096h137l19 -203h8q61 107 147 165t189 58q73 0 131 -12l-23 -154q-68 15 -120 15q-133 0 -227.5 -108t-94.5 -269v-588h-166z" />
<glyph unicode="s" horiz-adv-x="977" d="M106 827q0 134 109 211.5t299 77.5q177 0 346 -72l-59 -135q-165 68 -299 68q-118 0 -178 -37t-60 -102q0 -44 22.5 -75t72.5 -59t192 -81q195 -71 263.5 -143t68.5 -181q0 -153 -114 -236t-320 -83q-218 0 -340 69v154q79 -40 169.5 -63t174.5 -23q130 0 200 41.5 t70 126.5q0 64 -55.5 109.5t-216.5 107.5q-153 57 -217.5 99.5t-96 96.5t-31.5 129z" />
<glyph unicode="t" horiz-adv-x="723" d="M31 967v80l157 69l70 234h96v-254h318v-129h-318v-645q0 -99 47 -152t129 -53q44 0 85 6.5t65 13.5v-127q-27 -13 -79.5 -21.5t-94.5 -8.5q-318 0 -318 335v652h-157z" />
<glyph unicode="u" horiz-adv-x="1257" d="M164 379v717h168v-711q0 -134 61 -200t191 -66q172 0 251.5 94t79.5 307v576h166v-1096h-137l-24 147h-9q-51 -81 -141.5 -124t-206.5 -43q-200 0 -299.5 95t-99.5 304z" />
<glyph unicode="v" horiz-adv-x="1026" d="M0 1096h178l236 -650q80 -228 94 -296h8q11 53 69.5 219.5t262.5 726.5h178l-416 -1096h-194z" />
<glyph unicode="w" horiz-adv-x="1593" d="M23 1096h174q106 -413 161.5 -629t63.5 -291h8q11 57 35.5 147.5t42.5 143.5l201 629h180l196 -629q56 -172 76 -289h8q4 36 21.5 111t208.5 807h172l-303 -1096h-197l-201 643q-19 59 -71 268h-8q-40 -175 -70 -270l-207 -641h-192z" />
<glyph unicode="x" horiz-adv-x="1073" d="M39 0l401 561l-381 535h189l289 -420l288 420h187l-381 -535l401 -561h-188l-307 444l-310 -444h-188z" />
<glyph unicode="y" horiz-adv-x="1032" d="M2 1096h178l240 -625q79 -214 98 -309h8q13 51 54.5 174.5t271.5 759.5h178l-471 -1248q-70 -185 -163.5 -262.5t-229.5 -77.5q-76 0 -150 17v133q55 -12 123 -12q171 0 244 192l61 156z" />
<glyph unicode="z" horiz-adv-x="958" d="M82 0v113l598 854h-561v129h743v-129l-590 -838h605v-129h-795z" />
<glyph unicode="{" horiz-adv-x="776" d="M61 498v141q130 2 188 48t58 142v306q0 155 108 241t290 86v-139q-230 -6 -230 -199v-295q0 -215 -223 -254v-12q223 -39 223 -254v-297q0 -102 58.5 -148t171.5 -48v-140q-190 2 -294 87t-104 239v303q0 104 -63 148.5t-183 44.5z" />
<glyph unicode="|" horiz-adv-x="1128" d="M494 -496v2052h141v-2052h-141z" />
<glyph unicode="}" horiz-adv-x="776" d="M72 -184q111 2 169 48t58 148v297q0 114 55 174t168 80v12q-223 39 -223 254v295q0 193 -227 199v139q184 0 289.5 -87t105.5 -240v-306q0 -97 59 -142.5t189 -47.5v-141q-122 0 -185 -44.5t-63 -148.5v-303q0 -153 -102.5 -238.5t-292.5 -87.5v140z" />
<glyph unicode="~" d="M104 592v151q100 109 244 109q68 0 124.5 -14t145.5 -52q66 -28 115 -41.5t96 -13.5q54 0 118 32t118 89v-150q-102 -110 -244 -110q-72 0 -135 16.5t-135 48.5q-75 32 -120 44t-93 12q-53 0 -116.5 -33.5t-117.5 -87.5z" />
<glyph unicode="&#xa2;" d="M190 741q0 508 396 570v172h135v-164q75 -3 146 -19.5t120 -39.5l-49 -140q-133 51 -242 51q-172 0 -253 -105.5t-81 -322.5q0 -212 79.5 -313.5t246.5 -101.5q141 0 283 59v-147q-105 -54 -252 -60v-200h-133v206q-203 32 -299.5 168.5t-96.5 386.5z" />
<glyph unicode="&#xa3;" d="M63 0v141q205 47 205 291v223h-198v127h198v316q0 178 112 280.5t302 102.5t360 -84l-61 -133q-154 77 -297 77q-123 0 -185.5 -62t-62.5 -202v-295h422v-127h-422v-221q0 -100 -32.5 -168t-106.5 -112h795v-154h-1029z" />
<glyph unicode="&#xa5;" d="M31 1462h178l375 -727l379 727h174l-416 -770h262v-127h-317v-170h317v-127h-317v-268h-164v268h-316v127h316v170h-316v127h256z" />
<glyph unicode="&#xa9;" horiz-adv-x="1704" d="M100 731q0 200 100 375t275 276t377 101q200 0 375 -100t276 -275t101 -377q0 -197 -97 -370t-272 -277t-383 -104q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM205 731q0 -173 87 -323.5t237.5 -237t322.5 -86.5q174 0 323 87t236.5 235.5t87.5 324.5q0 174 -87 323 t-235.5 236.5t-324.5 87.5q-174 0 -323 -87t-236.5 -235.5t-87.5 -324.5zM481 731q0 209 110.5 332t301.5 123q128 0 246 -60l-58 -118q-108 51 -188 51q-125 0 -192.5 -87t-67.5 -241q0 -168 63.5 -249t194.5 -81q86 0 211 45v-124q-48 -20 -98.5 -34t-120.5 -14 q-194 0 -298 120.5t-104 336.5z" />
<glyph unicode="&#xad;" horiz-adv-x="659" d="M84 473v152h491v-152h-491z" />
<glyph unicode="&#xae;" horiz-adv-x="1704" d="M100 731q0 200 100 375t275 276t377 101q200 0 375 -100t276 -275t101 -377q0 -197 -97 -370t-272 -277t-383 -104q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM205 731q0 -173 87 -323.5t237.5 -237t322.5 -86.5q174 0 323 87t236.5 235.5t87.5 324.5q0 174 -87 323 t-235.5 236.5t-324.5 87.5q-174 0 -323 -87t-236.5 -235.5t-87.5 -324.5zM575 285v891h261q166 0 243.5 -65t77.5 -198q0 -80 -42.5 -141.5t-119.5 -91.5l238 -395h-168l-207 354h-135v-354h-148zM723 762h108q80 0 128.5 41.5t48.5 105.5q0 75 -43 107.5t-136 32.5h-106 v-287z" />
<glyph unicode="&#xb4;" horiz-adv-x="1182" d="M393 1241v25q48 62 103.5 150t87.5 153h202v-21q-44 -65 -131 -160t-151 -147h-111z" />
<glyph unicode="&#x2000;" horiz-adv-x="784" />
<glyph unicode="&#x2001;" horiz-adv-x="1569" />
<glyph unicode="&#x2002;" horiz-adv-x="784" />
<glyph unicode="&#x2003;" horiz-adv-x="1569" />
<glyph unicode="&#x2004;" horiz-adv-x="523" />
<glyph unicode="&#x2005;" horiz-adv-x="392" />
<glyph unicode="&#x2006;" horiz-adv-x="261" />
<glyph unicode="&#x2007;" horiz-adv-x="261" />
<glyph unicode="&#x2008;" horiz-adv-x="196" />
<glyph unicode="&#x2009;" horiz-adv-x="313" />
<glyph unicode="&#x200a;" horiz-adv-x="87" />
<glyph unicode="&#x2010;" horiz-adv-x="659" d="M84 473v152h491v-152h-491z" />
<glyph unicode="&#x2011;" horiz-adv-x="659" d="M84 473v152h491v-152h-491z" />
<glyph unicode="&#x2012;" horiz-adv-x="659" d="M84 473v152h491v-152h-491z" />
<glyph unicode="&#x2013;" horiz-adv-x="1024" d="M82 473v152h860v-152h-860z" />
<glyph unicode="&#x2014;" horiz-adv-x="2048" d="M82 473v152h1884v-152h-1884z" />
<glyph unicode="&#x2018;" horiz-adv-x="348" d="M25 983q22 90 71 224t105 255h123q-66 -254 -103 -501h-184z" />
<glyph unicode="&#x2019;" horiz-adv-x="348" d="M25 961q70 285 102 501h182l15 -22q-26 -100 -75 -232.5t-102 -246.5h-122z" />
<glyph unicode="&#x201c;" horiz-adv-x="717" d="M25 983q22 90 71 224t105 255h123q-66 -254 -103 -501h-184zM391 983q56 215 178 479h123q-30 -115 -59.5 -259.5t-42.5 -241.5h-184z" />
<glyph unicode="&#x201d;" horiz-adv-x="717" d="M25 961q70 285 102 501h182l15 -22q-26 -100 -75 -232.5t-102 -246.5h-122zM391 961q26 100 59 254t46 247h182l14 -22q-24 -91 -72 -224t-104 -255h-125z" />
<glyph unicode="&#x2022;" horiz-adv-x="770" d="M164 748q0 121 56.5 184t164.5 63q105 0 163 -62t58 -185q0 -119 -57.5 -183.5t-163.5 -64.5q-107 0 -164 65.5t-57 182.5z" />
<glyph unicode="&#x2026;" horiz-adv-x="1606" d="M152 106q0 67 30.5 101.5t87.5 34.5q58 0 90.5 -34.5t32.5 -101.5q0 -65 -33 -100t-90 -35q-51 0 -84.5 31.5t-33.5 103.5zM682 106q0 67 30.5 101.5t87.5 34.5q58 0 90.5 -34.5t32.5 -101.5q0 -65 -33 -100t-90 -35q-51 0 -84.5 31.5t-33.5 103.5zM1213 106 q0 67 30.5 101.5t87.5 34.5q58 0 90.5 -34.5t32.5 -101.5q0 -65 -33 -100t-90 -35q-51 0 -84.5 31.5t-33.5 103.5z" />
<glyph unicode="&#x202f;" horiz-adv-x="313" />
<glyph unicode="&#x205f;" horiz-adv-x="392" />
<glyph unicode="&#x20ac;" horiz-adv-x="1208" d="M63 506v129h152l-2 42v44l2 80h-152v129h164q39 261 185 407t383 146q201 0 366 -97l-71 -139q-166 86 -295 86q-319 0 -398 -403h510v-129h-524l-2 -57v-64l2 -45h463v-129h-447q37 -180 138.5 -278.5t271.5 -98.5q156 0 309 66v-150q-146 -65 -317 -65 q-237 0 -381.5 134.5t-190.5 391.5h-166z" />
<glyph unicode="&#x2122;" horiz-adv-x="1589" d="M37 1356v106h543v-106h-211v-615h-123v615h-209zM647 741v721h187l196 -559l203 559h180v-721h-127v420l6 137h-8l-211 -557h-104l-201 559h-8l6 -129v-430h-119z" />
<glyph unicode="&#xe000;" horiz-adv-x="1095" d="M0 1095h1095v-1095h-1095v1095z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 25 KiB

BIN
couchpotato/static/fonts/OpenSans-Regular-webfont.ttf

Binary file not shown.

BIN
couchpotato/static/fonts/OpenSans-Regular-webfont.woff

Binary file not shown.

BIN
couchpotato/static/images/sprite.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

2
couchpotato/static/scripts/block/menu.js

@ -15,7 +15,7 @@ Block.Menu = new Class({
self.wrapper = new Element('div.wrapper').adopt(
self.more_option_ul = new Element('ul')
),
new Element('a.button.onlay', {
new Element('a.button.onlay' + (self.options.button_class ? '.' + self.options.button_class : ''), {
'events': {
'click': function(){
self.el.toggleClass('show')

45
couchpotato/static/scripts/block/navigation.js

@ -5,7 +5,17 @@ Block.Navigation = new Class({
create: function(){
var self = this;
var settings_added = false;
self.el = new Element('div.navigation').adopt(
self.foldout = new Element('a.foldout.icon2.menu', {
'events': {
'click': self.toggleMenu.bind(self)
}
}).grab(new Element('span.overlay')),
self.logo = new Element('a.logo', {
'text': 'CouchPotato',
'href': App.createUrl('')
}),
self.nav = new Element('ul'),
self.backtotop = new Element('a.backtotop', {
'text': 'back to top',
@ -28,19 +38,50 @@ Block.Navigation = new Class({
onEnter: function(){
self.backtotop.fade('in')
}
});
self.nav.addEvents({
'click:relay(a)': function(){
if($(document.body).getParent().hasClass('menu_shown'))
self.toggleMenu();
}
})
},
addTab: function(name, tab){
var self = this
var self = this;
return new Element('li.tab_'+(name || 'unknown')).adopt(
return new Element('li.tab_'+(name || 'unknown')).grab(
new Element('a', tab)
).inject(self.nav)
},
toggleMenu: function(e){
var self = this,
body = $(document.body),
html = body.getParent();
// Copy over settings menu
if(!self.added){
new Element('li.separator').inject(self.nav);
body.getElements('.header .more_menu.menu li a').each(function(el, nr){
if([0, 1, 2, 5].indexOf(nr) > -1){
self.nav.grab(
new Element('li').grab(el.clone().cloneEvents(el))
);
}
});
self.added = true;
}
html.toggleClass('menu_shown');
},
activate: function(name){
var self = this;

18
couchpotato/static/scripts/couchpotato.js

@ -61,7 +61,7 @@ var CouchPotato = new Class({
new Element('div').adopt(
self.block.navigation = new Block.Navigation(self, {}),
self.block.search = new Block.Search(self, {}),
self.block.more = new Block.Menu(self, {})
self.block.more = new Block.Menu(self, {'button_class': 'icon2.cog'})
)
),
self.content = new Element('div.content'),
@ -285,23 +285,15 @@ var CouchPotato = new Class({
createUserscriptButtons: function(){
var userscript = false;
try {
if(Components.interfaces.gmIGreasemonkeyService)
userscript = true
}
catch(e){
userscript = Browser.chrome === true;
}
var host_url = window.location.protocol + '//' + window.location.host;
return new Element('div.group_userscript').adopt(
(userscript ? [new Element('a.userscript.button', {
new Element('a.userscript.button', {
'text': 'Install userscript',
'href': Api.createUrl('userscript.get')+randomString()+'/couchpotato.user.js',
'target': '_self'
}), new Element('span.or[text=or]')] : null),
'target': '_blank'
}),
new Element('span.or[text=or]'),
new Element('span.bookmarklet').adopt(
new Element('a.button.orange', {
'text': '+CouchPotato',

487
couchpotato/static/scripts/library/prefix_free.js

@ -1,487 +0,0 @@
/**
* StyleFix 1.0.3 & PrefixFree 1.0.7
* @author Lea Verou
* MIT license
*/
(function(){
if(!window.addEventListener) {
return;
}
var self = window.StyleFix = {
link: function(link) {
try {
// Ignore stylesheets with data-noprefix attribute as well as alternate stylesheets
if(link.rel !== 'stylesheet' || link.hasAttribute('data-noprefix')) {
return;
}
}
catch(e) {
return;
}
var url = link.href || link.getAttribute('data-href'),
base = url.replace(/[^\/]+$/, ''),
base_scheme = (/^[a-z]{3,10}:/.exec(base) || [''])[0],
base_domain = (/^[a-z]{3,10}:\/\/[^\/]+/.exec(base) || [''])[0],
base_query = /^([^?]*)\??/.exec(url)[1],
parent = link.parentNode,
xhr = new XMLHttpRequest(),
process;
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
process();
}
};
process = function() {
var css = xhr.responseText;
if(css && link.parentNode && (!xhr.status || xhr.status < 400 || xhr.status > 600)) {
css = self.fix(css, true, link);
// Convert relative URLs to absolute, if needed
if(base) {
css = css.replace(/url\(\s*?((?:"|')?)(.+?)\1\s*?\)/gi, function($0, quote, url) {
if(/^([a-z]{3,10}:|#)/i.test(url)) { // Absolute & or hash-relative
return $0;
}
else if(/^\/\//.test(url)) { // Scheme-relative
// May contain sequences like /../ and /./ but those DO work
return 'url("' + base_scheme + url + '")';
}
else if(/^\//.test(url)) { // Domain-relative
return 'url("' + base_domain + url + '")';
}
else if(/^\?/.test(url)) { // Query-relative
return 'url("' + base_query + url + '")';
}
else {
// Path-relative
return 'url("' + base + url + '")';
}
});
// behavior URLs shoudn’t be converted (Issue #19)
// base should be escaped before added to RegExp (Issue #81)
var escaped_base = base.replace(/([\\\^\$*+[\]?{}.=!:(|)])/g,"\\$1");
css = css.replace(RegExp('\\b(behavior:\\s*?url\\(\'?"?)' + escaped_base, 'gi'), '$1');
}
var style = document.createElement('style');
style.textContent = css;
style.media = link.media;
style.disabled = link.disabled;
style.setAttribute('data-href', link.getAttribute('href'));
parent.insertBefore(style, link);
parent.removeChild(link);
style.media = link.media; // Duplicate is intentional. See issue #31
}
};
try {
xhr.open('GET', url);
xhr.send(null);
} catch (e) {
// Fallback to XDomainRequest if available
if (typeof XDomainRequest != "undefined") {
xhr = new XDomainRequest();
xhr.onerror = xhr.onprogress = function() {};
xhr.onload = process;
xhr.open("GET", url);
xhr.send(null);
}
}
link.setAttribute('data-inprogress', '');
},
styleElement: function(style) {
if (style.hasAttribute('data-noprefix')) {
return;
}
var disabled = style.disabled;
style.textContent = self.fix(style.textContent, true, style);
style.disabled = disabled;
},
styleAttribute: function(element) {
var css = element.getAttribute('style');
css = self.fix(css, false, element);
element.setAttribute('style', css);
},
process: function() {
// Linked stylesheets
$('link[rel="stylesheet"]:not([data-inprogress])').forEach(StyleFix.link);
// Inline stylesheets
$('style').forEach(StyleFix.styleElement);
// Inline styles
$('[style]').forEach(StyleFix.styleAttribute);
},
register: function(fixer, index) {
(self.fixers = self.fixers || [])
.splice(index === undefined? self.fixers.length : index, 0, fixer);
},
fix: function(css, raw, element) {
for(var i=0; i<self.fixers.length; i++) {
css = self.fixers[i](css, raw, element) || css;
}
return css;
},
camelCase: function(str) {
return str.replace(/-([a-z])/g, function($0, $1) { return $1.toUpperCase(); }).replace('-','');
},
deCamelCase: function(str) {
return str.replace(/[A-Z]/g, function($0) { return '-' + $0.toLowerCase() });
}
};
/**************************************
* Process styles
**************************************/
(function(){
setTimeout(function(){
$('link[rel="stylesheet"]').forEach(StyleFix.link);
}, 10);
document.addEventListener('DOMContentLoaded', StyleFix.process, false);
})();
function $(expr, con) {
return [].slice.call((con || document).querySelectorAll(expr));
}
})();
/**
* PrefixFree
*/
(function(root){
if(!window.StyleFix || !window.getComputedStyle) {
return;
}
// Private helper
function fix(what, before, after, replacement, css) {
what = self[what];
if(what.length) {
var regex = RegExp(before + '(' + what.join('|') + ')' + after, 'gi');
css = css.replace(regex, replacement);
}
return css;
}
var self = window.PrefixFree = {
prefixCSS: function(css, raw, element) {
var prefix = self.prefix;
// Gradient angles hotfix
if(self.functions.indexOf('linear-gradient') > -1) {
// Gradients are supported with a prefix, convert angles to legacy
css = css.replace(/(\s|:|,)(repeating-)?linear-gradient\(\s*(-?\d*\.?\d*)deg/ig, function ($0, delim, repeating, deg) {
return delim + (repeating || '') + 'linear-gradient(' + (90-deg) + 'deg';
});
}
css = fix('functions', '(\\s|:|,)', '\\s*\\(', '$1' + prefix + '$2(', css);
css = fix('keywords', '(\\s|:)', '(\\s|;|\\}|$)', '$1' + prefix + '$2$3', css);
css = fix('properties', '(^|\\{|\\s|;)', '\\s*:', '$1' + prefix + '$2:', css);
// Prefix properties *inside* values (issue #8)
if (self.properties.length) {
var regex = RegExp('\\b(' + self.properties.join('|') + ')(?!:)', 'gi');
css = fix('valueProperties', '\\b', ':(.+?);', function($0) {
return $0.replace(regex, prefix + "$1")
}, css);
}
if(raw) {
css = fix('selectors', '', '\\b', self.prefixSelector, css);
css = fix('atrules', '@', '\\b', '@' + prefix + '$1', css);
}
// Fix double prefixing
css = css.replace(RegExp('-' + prefix, 'g'), '-');
// Prefix wildcard
css = css.replace(/-\*-(?=[a-z]+)/gi, self.prefix);
return css;
},
property: function(property) {
return (self.properties.indexOf(property)? self.prefix : '') + property;
},
value: function(value, property) {
value = fix('functions', '(^|\\s|,)', '\\s*\\(', '$1' + self.prefix + '$2(', value);
value = fix('keywords', '(^|\\s)', '(\\s|$)', '$1' + self.prefix + '$2$3', value);
// TODO properties inside values
return value;
},
// Warning: Prefixes no matter what, even if the selector is supported prefix-less
prefixSelector: function(selector) {
return selector.replace(/^:{1,2}/, function($0) { return $0 + self.prefix })
},
// Warning: Prefixes no matter what, even if the property is supported prefix-less
prefixProperty: function(property, camelCase) {
var prefixed = self.prefix + property;
return camelCase? StyleFix.camelCase(prefixed) : prefixed;
}
};
/**************************************
* Properties
**************************************/
(function() {
var prefixes = {},
properties = [],
shorthands = {},
style = getComputedStyle(document.documentElement, null),
dummy = document.createElement('div').style;
// Why are we doing this instead of iterating over properties in a .style object? Cause Webkit won't iterate over those.
var iterate = function(property) {
if(property.charAt(0) === '-') {
properties.push(property);
var parts = property.split('-'),
prefix = parts[1];
// Count prefix uses
prefixes[prefix] = ++prefixes[prefix] || 1;
// This helps determining shorthands
while(parts.length > 3) {
parts.pop();
var shorthand = parts.join('-');
if(supported(shorthand) && properties.indexOf(shorthand) === -1) {
properties.push(shorthand);
}
}
}
},
supported = function(property) {
return StyleFix.camelCase(property) in dummy;
}
// Some browsers have numerical indices for the properties, some don't
if(style.length > 0) {
for(var i=0; i<style.length; i++) {
iterate(style[i])
}
}
else {
for(var property in style) {
iterate(StyleFix.deCamelCase(property));
}
}
// Find most frequently used prefix
var highest = {uses:0};
for(var prefix in prefixes) {
var uses = prefixes[prefix];
if(highest.uses < uses) {
highest = {prefix: prefix, uses: uses};
}
}
self.prefix = '-' + highest.prefix + '-';
self.Prefix = StyleFix.camelCase(self.prefix);
self.properties = [];
// Get properties ONLY supported with a prefix
for(var i=0; i<properties.length; i++) {
var property = properties[i];
if(property.indexOf(self.prefix) === 0) { // we might have multiple prefixes, like Opera
var unprefixed = property.slice(self.prefix.length);
if(!supported(unprefixed)) {
self.properties.push(unprefixed);
}
}
}
// IE fix
if(self.Prefix == 'Ms'
&& !('transform' in dummy)
&& !('MsTransform' in dummy)
&& ('msTransform' in dummy)) {
self.properties.push('transform', 'transform-origin');
}
self.properties.sort();
})();
/**************************************
* Values
**************************************/
(function() {
// Values that might need prefixing
var functions = {
'linear-gradient': {
property: 'backgroundImage',
params: 'red, teal'
},
'calc': {
property: 'width',
params: '1px + 5%'
},
'element': {
property: 'backgroundImage',
params: '#foo'
},
'cross-fade': {
property: 'backgroundImage',
params: 'url(a.png), url(b.png), 50%'
}
};
functions['repeating-linear-gradient'] =
functions['repeating-radial-gradient'] =
functions['radial-gradient'] =
functions['linear-gradient'];
var keywords = {
'initial': 'color',
'zoom-in': 'cursor',
'zoom-out': 'cursor',
'box': 'display',
'flexbox': 'display',
'inline-flexbox': 'display',
'flex': 'display',
'inline-flex': 'display'
};
self.functions = [];
self.keywords = [];
var style = document.createElement('div').style;
function supported(value, property) {
style[property] = '';
style[property] = value;
return !!style[property];
}
for (var func in functions) {
var test = functions[func],
property = test.property,
value = func + '(' + test.params + ')';
if (!supported(value, property)
&& supported(self.prefix + value, property)) {
// It's supported, but with a prefix
self.functions.push(func);
}
}
for (var keyword in keywords) {
var property = keywords[keyword];
if (!supported(keyword, property)
&& supported(self.prefix + keyword, property)) {
// It's supported, but with a prefix
self.keywords.push(keyword);
}
}
})();
/**************************************
* Selectors and @-rules
**************************************/
(function() {
var
selectors = {
':read-only': null,
':read-write': null,
':any-link': null,
'::selection': null
},
atrules = {
'keyframes': 'name',
'viewport': null,
'document': 'regexp(".")'
};
self.selectors = [];
self.atrules = [];
var style = root.appendChild(document.createElement('style'));
function supported(selector) {
style.textContent = selector + '{}'; // Safari 4 has issues with style.innerHTML
return !!style.sheet.cssRules.length;
}
for(var selector in selectors) {
var test = selector + (selectors[selector]? '(' + selectors[selector] + ')' : '');
if(!supported(test) && supported(self.prefixSelector(test))) {
self.selectors.push(selector);
}
}
for(var atrule in atrules) {
var test = atrule + ' ' + (atrules[atrule] || '');
if(!supported('@' + test) && supported('@' + self.prefix + test)) {
self.atrules.push(atrule);
}
}
root.removeChild(style);
})();
// Properties that accept properties as their value
self.valueProperties = [
'transition',
'transition-property'
]
// Add class for current prefix
root.className += ' ' + self.prefix;
StyleFix.register(self.prefixCSS);
})(document.documentElement);

28
couchpotato/static/scripts/library/question.js

@ -7,36 +7,24 @@ var Question = new Class( {
self.hint = hint
self.answers = answers
self.createQuestion()
self.createQuestion();
self.answers.each(function(answer) {
self.createAnswer(answer)
})
self.createMask()
},
createMask : function() {
var self = this
self.mask = new Element('div.mask').fade('hide').inject(document.body).fade('in');
},
createQuestion : function() {
var self = this;
this.container = new Element('div', {
'class' : 'question'
}).adopt(
self.container = new Element('div.mask.question').adopt(
new Element('h3', {
'html': this.question
}),
new Element('div.hint', {
'html': this.hint
})
).inject(document.body)
this.container.position( {
'position' : 'center'
});
).fade('hide').inject(document.body).fade('in')
},
@ -59,17 +47,15 @@ var Question = new Class( {
(options.onComplete || function(){})()
self.close();
}
})).send();
})).send();
});
}
},
close : function() {
var self = this;
self.mask.fade('out');
(function(){self.mask.destroy()}).delay(1000);
this.container.destroy();
self.container.fade('out');
(function(){self.container.destroy()}).delay(1000);
},
toElement : function() {

34
couchpotato/static/scripts/page/home.js

@ -46,7 +46,7 @@ Page.Home = new Class({
self.soon_list = new MovieList({
'navigation': false,
'identifier': 'soon',
'limit': 18,
'limit': 12,
'title': 'Available soon',
'description': 'These are being searched for and should be available soon as they will be released on DVD in the next few weeks.',
'on_empty_element': new Element('div').adopt(
@ -59,9 +59,41 @@ Page.Home = new Class({
'actions': [MA.IMDB, MA.Refresh],
'load_more': false,
'view': 'thumbs',
'force_view': true,
'api_call': 'dashboard.soon'
});
// Make all thumbnails the same size
self.soon_list.addEvent('loaded', function(){
var images = $(self.soon_list).getElements('img'),
timer,
lowest = null;
images.addEvent('load', function(){
var height = this.getSize().y;
if(!lowest || lowest > height){
lowest = height;
if(timer) clearTimeout(timer);
timer = (function(){
images.getParent().setStyle('height', lowest);
}).delay(300)
}
});
$(window).addEvent('resize', function(){
if(timer) clearTimeout(timer);
timer = (function(){
var lowest;
images.each(function(img){
var height = img.getSize().y;
if(!lowest || lowest > height)
lowest = height;
});
images.getParent().setStyle('height', lowest);
}).delay(300);
});
});
// Still not available
self.late_list = new MovieList({
'navigation': false,

2
couchpotato/static/scripts/page/wanted.js

@ -30,7 +30,7 @@ Page.Wanted = new Class({
$(self.wanted).inject(self.el);
// Check if search is in progress
self.startProgressInterval();
self.startProgressInterval.delay(4000, self);
}
},

618
couchpotato/static/style/main.css

@ -1,19 +1,14 @@
html {
body, html {
color: #fff;
font-size: 12px;
line-height: 1.5;
font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;
font-family: OpenSans, "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;
height: 100%;
text-shadow: 0 1px 0 #000;
}
body {
margin: 0;
padding: 0;
background: #4e5969;
overflow-y: scroll;
height: 100%;
}
body { overflow-y: scroll; }
body.noscroll { overflow: hidden; }
#clean {
@ -32,14 +27,16 @@ pre {
}
input, textarea {
font-size: 12px;
font-size: 1em;
font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;
}
input:focus, textarea:focus {
outline: none;
}
input:-moz-placeholder, textarea:-moz-placeholder {
color: rgba(255, 255, 255, 0.6);
}
::-webkit-input-placeholder, ::-webkit-textarea-placeholder {
color: rgba(255, 255, 255, 0.6);
}
@ -59,55 +56,32 @@ a:hover { color: #f3f3f3; }
.page {
display: none;
width: 960px;
width: 100%;
max-width: 980px;
margin: 0 auto;
line-height: 24px;
padding: 0 0 20px;
line-height: 1.5em;
padding: 0 15px 20px;
}
.page.active { display: block; }
.page .noticeMe {
background-color: lightgoldenrodyellow;
display: block;
padding: 20px 10px;
margin: 0 -10px 40px;
font-size: 19px;
text-align: center;
}
.content {
clear:both;
padding: 80px 0 10px;
padding: 65px 0 10px;
background: #4e5969;
}
@media all and (max-width: 480px) {
.content {
padding-top: 40px;
}
}
h2 {
font-size: 30px;
font-size: 2.5em;
padding: 0;
margin: 20px 0 0 0;
}
.footer {
text-align:center;
padding: 50px 0 0 0;
color: #999;
font-size: 10px;
clear: both;
}
.footer .check {
color: #333;
}
#toTop {
background: black;
position: fixed;
bottom: 0;
right: 0;
padding: 10px 10px 10px 40px;
background: #f7f7f7 url('../images/toTop.gif') no-repeat 10px center;
border-radius: 5px 0 0 0;
}
form {
padding:0;
margin:0;
@ -126,6 +100,12 @@ body > .spinner, .mask{
width: 100%;
padding: 200px;
}
@media all and (max-width: 480px) {
body > .mask {
padding: 20px;
}
}
.button {
background: #5082bc url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAyCAYAAACd+7GKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAClJREFUeNpi/v//vwMTAwPDfzjBgMpFI/7hFSOT9Y8qRuF3JLoHAQIMAHYtMmRA+CugAAAAAElFTkSuQmCC") repeat-x;
@ -135,8 +115,6 @@ body > .spinner, .mask{
font-weight: bold;
line-height: 1;
border-radius: 2px;
box-shadow: 0 1px 2px rgba(0,0,0,0.3);
text-shadow: 0 -1px 1px rgba(0,0,0,0.25);
cursor: pointer;
}
.button.red { background-color: #ff0000; }
@ -164,132 +142,302 @@ body > .spinner, .mask{
.icon.spinner { background-image: url('../images/icon.spinner.gif'); }
.icon.attention { background-image: url('../images/icon.attention.png'); }
.icon2 {
display: inline-block;
background: center no-repeat;
font-family: 'Elusive-Icons';
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
-webkit-font-smoothing: antialiased;
font-size: 15px;
}
.icon2.cog:before { content: "\e109"; }
.icon2.eye-open:before { content: "\e09d"; }
.icon2.search:before { content: "\e03e"; }
.icon2.return-key:before { content: "\e111"; }
.icon2.close:before { content: "\e04e"; }
.icon2.menu:before {
content: "\e076 \e076 \e076";
line-height: 6px;
transform: scaleX(2);
width: 20px;
font-size: 10px;
display: inline-block;
vertical-align: middle;
}
/*** Navigation ***/
.header {
background: #4e5969;
padding: 10px 0;
height: 80px;
height: 66px;
position: fixed;
margin: 0;
width: 100%;
z-index: 5;
box-shadow: 0 20px 30px -30px rgba(0,0,0,0.05);
transition: box-shadow .4s cubic-bezier(0.9,0,0.1,1);
background: #4e5969;
box-shadow: 0 0 10px rgba(0,0,0,.1);
transition: all .4s ease-in-out;
}
.header.with_shadow {
box-shadow: 0 20px 30px -30px rgba(0,0,0,0.3);
background-color: #46505e;
}
@media all and (max-width: 480px) {
.header {
height: 44px;
}
}
.header > div {
width: 960px;
width: 100%;
max-width: 980px;
margin: 0 auto;
overflow: hidden;
position: relative;
height: 100%;
padding: 0 15px;
}
.header .navigation {
display: inline-block;
vertical-align: middle;
width: 67.2%;
}
.header .navigation ul {
margin: 0;
padding: 0;
}
.header .navigation li {
color: #fff;
display: inline-block;
font-size:20px;
font-weight: bold;
margin: 0;
text-align: center;
position: absolute;
height: 100%;
left: 0;
bottom: 0;
}
.header .navigation li a {
display: block;
padding: 15px;
position: relative;
.header .foldout {
width: 44px;
height: 100%;
text-align: center;
border-right: 1px solid rgba(255,255,255,.07);
display: none;
vertical-align: top;
line-height: 42px;
color: #FFF;
}
.header .navigation li:first-child a { padding-left: 10px; }
.header .navigation li span {
display: block;
margin-top: 5px;
}
.header .navigation li a:after {
content: '';
.header .logo {
display: inline-block;
height: 2px;
width: 76%;
left: 12%;
position: absolute;
top: 46px;
background-color: #46505e;
outline: none;
box-shadow: inset 0 1px 8px rgba(0,0,0,0.05), 0 1px 0px rgba(255,255,255,0.15);
transition: all .4s cubic-bezier(0.9,0,0.1,1);
font-size: 1.75em;
padding: 15px 30px 0 15px;
height: 100%;
vertical-align: middle;
border-right: 1px solid rgba(255,255,255,.07);
color: #FFF;
font-weight: normal;
vertical-align: top;
}
.header .navigation li:hover a:after { background-color: #047792; }
.header .navigation li.active a:after { background-color: #04bce6; }
.header .navigation li.disabled { color: #e5e5e5; }
.header .navigation li a { color: #fff; }
.header .navigation .backtotop {
opacity: 0;
display: block;
width: 80px;
left: 50%;
position: absolute;
text-align: center;
margin: -10px 0 0 -40px;
background: #4e5969;
padding: 5px 0;
border-radius: 0 0 5px 5px;
color: rgba(255,255,255,.4);
text-shadow: none;
font-weight: normal;
@media all and (max-width: 480px) {
.header .foldout {
display: inline-block;
}
.header .logo {
padding-top: 7px;
border: 0;
}
}
@media all and (min-width: 481px) and (max-width: 640px) {
.header .logo {
display: none;
}
}
.header .navigation ul {
display: inline-block;
margin: 0;
padding: 0;
height: 100%;
}
.header .navigation li {
color: #fff;
display: inline-block;
font-size: 1.75em;
margin: 0;
text-align: center;
height: 100%;
border: 1px solid rgba(255,255,255,.07);
border-width: 0 0 0 1px;
}
.header .navigation li:first-child {
border: none;
}
.header .navigation li a {
display: block;
padding: 15px;
position: relative;
height: 100%;
border: 1px solid transparent;
border-width: 0 0 4px 0;
font-weight: normal;
}
.header .navigation li:hover a { border-color: #047792; }
.header .navigation li.active a { border-color: #04bce6; }
.header .navigation li.disabled { color: #e5e5e5; }
.header .navigation li a { color: #fff; }
.header .navigation .backtotop {
opacity: 0;
display: block;
width: 80px;
left: 50%;
position: fixed;
bottom: 0;
text-align: center;
margin: -10px 0 0 -40px;
background: #4e5969;
padding: 5px 0;
color: rgba(255,255,255,.4);
font-weight: normal;
}
.header:hover .navigation .backtotop { color: #fff; }
@media all and (max-width: 480px) {
body {
position: absolute;
width: 100%;
transition: all .5s cubic-bezier(0.9,0,0.1,1);
left: 0;
}
.menu_shown body {
left: 160px;
}
.header .navigation {
height: 100%;
}
.menu_shown .header .navigation .overlay {
position: fixed;
right: 0;
top: 0;
bottom: 0;
left: 160px;
}
.header .navigation ul {
width: 160px;
position: fixed;
left: -160px;
background: rgba(0,0,0,.5);
transition: all .5s cubic-bezier(0.9,0,0.1,1);
}
.menu_shown .header .navigation ul {
left: 0;
}
.header .navigation ul li {
display: block;
text-align: left;
border-width: 1px 0 0 0;
height: 44px;
}
.header .navigation ul li a {
border-width: 0 4px 0 0;
padding: 5px 20px;
}
.header .navigation ul li.separator {
background-color: rgba(255,255,255, .07);
height: 5px;
}
}
.header:hover .navigation .backtotop { color: #fff; }
.header .more_menu {
margin-left: 12px;
position: absolute;
right: 15px;
height: 100%;
border-left: 1px solid rgba(255,255,255,.07);
}
@media all and (max-width: 480px) {
.header .more_menu {
display: none;
}
}
.header .more_menu .button {
height: 100%;
width: 44px;
border: 0;
box-shadow: none;
border-radius: 0;
background: none;
line-height: 66px;
text-align: center;
padding: 0;
border: 1px solid transparent;
border-width: 0 0 4px;
}
.header .more_menu .button:hover {
background: none;
border-color: #047792;
}
.header .more_menu .wrapper {
width: 150px;
margin-left: -110px;
}
.header .more_menu .wrapper:before {
margin-left: -34px;
margin-left: -106px;
margin-top: 66px;
}
@media all and (max-width: 480px) {
.header .more_menu .button {
line-height: 44px;
}
.header .more_menu .wrapper {
margin-top: 44px;
}
}
.header .more_menu .red { color: red; }
.header .more_menu .orange { color: orange; }
.badge {
position: absolute;
width: 14px;
height: 14px;
width: 20px;
height: 20px;
text-align: center;
line-height: 14px;
border-radius: 50%;
font-size: 8px;
margin: -5px 0 0 15px;
box-shadow: inset 0 1px 0 rgba(255,255,255,.6), 0 0 3px rgba(0,0,0,.7);
line-height: 20px;
margin: 0;
background-color: #1b79b8;
text-shadow: none;
background-image: -*-linear-gradient(0deg, rgba(255,255,255,.3) 0%, rgba(255,255,255,.1) 100%);
top: 0;
right: 0;
}
.header .notification_menu {
right: 60px;
display: block;
}
@media all and (max-width: 480px) {
.header .notification_menu {
right: 0;
}
}
.header .notification_menu .wrapper {
width: 300px;
margin-left: -260px;
margin-left: -255px;
text-align: left;
}
.header .notification_menu .wrapper:before {
left: 296px;
}
.header .notification_menu ul {
max-height: 300px;
overflow: auto;
@ -309,7 +457,7 @@ body > .spinner, .mask{
.header .notification_menu li:last-child > span { border: 0; }
.header .notification_menu li .added {
display: block;
font-size: 10px;
font-size: .85em;
color: #aaa;
text-align: ;
}
@ -318,25 +466,27 @@ body > .spinner, .mask{
text-align: center;
}
.header .message.update {
.message.update {
text-align: center;
position: relative;
top: -70px;
padding: 2px 0;
position: fixed;
padding: 10px;
background: #ff6134;
font-size: 12px;
border-radius: 0 0 5px 5px;
box-shadow: 0 2px 1px rgba(0,0,0, 0.3);
font-size: 15px;
bottom: 0;
left: 0;
width: 100%;
z-index: 19;
}
.header .message a {
padding: 0 10px;
.message.update a {
padding: 0 5px;
}
/*** Global Styles ***/
.check {
display: inline-block;
vertical-align: middle;
vertical-align: top;
margin-top: 4px;
height: 16px;
width: 16px;
cursor: pointer;
@ -361,8 +511,8 @@ body > .spinner, .mask{
border-radius:30px;
box-shadow: 0 1px 1px rgba(0,0,0,0.35), inset 0 1px 0px rgba(255,255,255,0.20);
background: url('../images/sprite.png') no-repeat 94% -53px, -*-linear-gradient(
270deg,
background: url('../images/sprite.png') no-repeat 94% -53px, linear-gradient(
180deg,
#5b9bd1 0%,
#406db8 100%
);
@ -437,8 +587,8 @@ body > .spinner, .mask{
border: 1px solid #252930;
box-shadow: inset 0 1px 0px rgba(255,255,255,0.20), 0 0 3px rgba(0,0,0, 0.2);
background: rgb(55,62,74);
background-image: -*-linear-gradient(
90deg,
background-image: linear-gradient(
0,
rgb(55,62,74) 0%,
rgb(73,83,98) 100%
);
@ -454,13 +604,9 @@ body > .spinner, .mask{
display: block;
width: 600px;
padding: 20px;
background: #f5f5f5;
position:fixed;
z-index:101;
z-index: 101;
text-align: center;
background: #5c697b;
border-radius: 3px;
box-shadow: 0 0 50px rgba(0,0,0,0.55);
}
.question h3 {
@ -472,7 +618,6 @@ body > .spinner, .mask{
.question .hint {
font-size: 14px;
color: #ccc;
text-shadow: none;
}
.question .answer {
@ -495,10 +640,11 @@ body > .spinner, .mask{
background-color: #4c5766;
}
.more_menu {
display: inline-block;
vertical-align: middle;
}
.more_menu {
display: inline-block;
vertical-align: middle;
overflow: visible;
}
.more_menu > a {
display: block;
@ -508,57 +654,45 @@ body > .spinner, .mask{
border: 1px solid rgba(0,0,0,0.3);
transition: all 0.3s ease-in-out;
}
.more_menu.show > a:not(:active), .more_menu > a:hover:not(:active) {
background-color: #406db8;
}
.more_menu.show > a:not(:active), .more_menu > a:hover:not(:active) {
background-color: #406db8;
}
.more_menu .wrapper {
display: none;
border: 1px solid #333;
background: rgba(255,255,255,0.98);
border-radius: 3px;
padding: 4px !important;
top: 0;
right: 0;
padding: 4px;
margin: 26px 0 0 0;
position: absolute;
z-index: 9;
margin: 32px 0 0 -145px;
z-index: 90;
width: 185px;
box-shadow: 0 10px 10px -5px rgba(0,0,0,0.4);
box-shadow: 0 20px 20px -5px rgba(0,0,0,0.1);
text-align: center;
color: #000;
text-shadow: none;
background-image: -*-linear-gradient(
45deg,
background-image: linear-gradient(
-45deg,
rgb(200,200,200) 0%,
rgb(255,255,255) 100%
);
}
.more_menu .wrapper:before {
content: ' ';
height: 0;
position: relative;
width: 0;
border: 6px solid transparent;
border-bottom-color: #fff;
display: block;
top: -16px;
left: 146px;
}
.more_menu.show .wrapper {
display: block;
}
.more_menu ul {
padding: 0;
margin: -12px 0 0 0;
margin: 0;
list-style: none;
}
.more_menu .wrapper li {
width: 100%;
height: auto;
}
.more_menu .wrapper li a {
display: block;
border-bottom: 1px solid rgba(255,255,255,0.2);
@ -570,7 +704,7 @@ body > .spinner, .mask{
padding: 3px 0;
color: #000;
}
.more_menu .wrapper li:last-child a {
border: none;
}
@ -582,41 +716,109 @@ body > .spinner, .mask{
position: fixed;
right: 0;
bottom: 0;
padding: 2px;
width: 240px;
width: 320px;
z-index: 20;
overflow: hidden;
font-size: 14px;
font-weight: bold;
}
@media all and (max-width: 480px) {
.messages {
width: 100%;
}
}
.messages .message {
text-align: center;
border-radius: 2px;
margin: 2px 0 0 0;
height: 0;
overflow: hidden;
transition: all .6s cubic-bezier(0.9,0,0.1,1);
box-shadow: 0 1px 1px rgba(0,0,0,0.35), inset 0 1px 0px rgba(255,255,255,0.20);
background-image: -*-linear-gradient(
270deg,
#5b9bd1 0%,
#406db8 100%
);
background: #5b9bd1;
width: 100%;
padding: 0 5px;
visibility: hidden;
position: relative;
margin: 1px 0 0;
max-height: 0;
padding: 0 30px 0 20px;
font-size: 1.1em;
font-weight: normal;
transform: scale(0);
}
.messages .message.sticky {
background-color: #c84040;
}
.messages .message.show {
visibility: visible;
height: auto;
padding-top: 3px;
padding-bottom: 3px;
min-height: 1px;
max-height: 400px;
max-height: 100px;
padding: 15px 30px 15px 20px;
transform: scale(1);
}
.messages .message.hide {
margin-left: 240px;
opacity: 0;
}
max-height: 0;
padding: 0 20px;
margin: 0;
transform: scale(0);
}
.messages .close {
position: absolute;
padding: 10px 8px;
top: 0;
right: 0;
color: #FFF;
}
/* Fonts */
@font-face {
font-family: 'Elusive-Icons';
src:url('../fonts/Elusive-Icons.eot');
src:url('../fonts/Elusive-Icons.eot?#iefix') format('embedded-opentype'),
url('../fonts/Elusive-Icons.woff') format('woff'),
url('../fonts/Elusive-Icons.ttf') format('truetype'),
url('../fonts/Elusive-Icons.svg#Elusive-Icons') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'OpenSans';
src: url('../fonts/OpenSans-Regular-webfont.eot');
src: url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-Regular-webfont.woff') format('woff'),
url('../fonts/OpenSans-Regular-webfont.ttf') format('truetype'),
url('../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'OpenSans';
src: url('../fonts/OpenSans-Italic-webfont.eot');
src: url('../fonts/OpenSans-Italic-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-Italic-webfont.woff') format('woff'),
url('../fonts/OpenSans-Italic-webfont.ttf') format('truetype'),
url('../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic') format('svg');
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: 'OpenSans';
src: url('../fonts/OpenSans-Bold-webfont.eot');
src: url('../fonts/OpenSans-Bold-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-Bold-webfont.woff') format('woff'),
url('../fonts/OpenSans-Bold-webfont.ttf') format('truetype'),
url('../fonts/OpenSans-Bold-webfont.svg#OpenSansBold') format('svg');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'OpenSans';
src: url('../fonts/OpenSans-BoldItalic-webfont.eot');
src: url('../fonts/OpenSans-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-BoldItalic-webfont.woff') format('woff'),
url('../fonts/OpenSans-BoldItalic-webfont.ttf') format('truetype'),
url('../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic') format('svg');
font-weight: bold;
font-style: italic;
}

48
couchpotato/static/style/settings.css

@ -1,5 +1,9 @@
.page.settings {
min-width: 960px;
}
.page.settings:after {
content: ".";
content: "";
display: block;
clear: both;
visibility: hidden;
@ -9,15 +13,15 @@
.page.settings .tabs {
float: left;
width: 20%;
font-size: 20px;
width: 14.7%;
font-size: 17px;
text-align: right;
list-style: none;
padding: 40px 0;
padding: 35px 0;
margin: 0;
min-height: 470px;
background-image: -*-linear-gradient(
20deg,
background-image: linear-gradient(
76deg,
rgba(0,0,0,0) 50%,
rgba(0,0,0,0.3) 100%
);
@ -60,9 +64,9 @@
.page.settings .containers {
width: 80%;
width: 84%;
float: left;
padding: 20px 2%;
padding: 40px 2%;
min-height: 300px;
}
@ -135,6 +139,10 @@
padding-left: 2%;
line-height: 14px;
}
.page .check {
margin-top: 6px;
}
.page .check + .formHint {
float: none;
@ -163,6 +171,10 @@
margin-bottom: 20px;
}
.page .option_list .check {
margin-top: 5px;
}
.page .option_list .enabler {
padding: 0;
margin-left: 5px !important;
@ -240,8 +252,11 @@
display: block;
text-align: right;
height: 20px;
margin: 0;
margin: 0 0 -37px;
}
.page .advanced_toggle .check {
margin: 0;
}
.page .advanced_toggle span { padding: 0 5px; }
.page.show_advanced .advanced_toggle {
color: #edc07f;
@ -439,16 +454,16 @@
border-radius: 2px;
}
.page .tag_input > ul:hover > li.choice {
background: -*-linear-gradient(
270deg,
background: linear-gradient(
180deg,
rgba(255,255,255,0.3) 0%,
rgba(255,255,255,0.1) 100%
);
}
.page .tag_input > ul > li.choice:hover,
.page .tag_input > ul > li.choice.selected {
background: -*-linear-gradient(
270deg,
background: linear-gradient(
180deg,
#5b9bd1 0%,
#406db8 100%
);
@ -486,8 +501,8 @@
margin: -9px 0 0 -16px;
border-radius: 30px 30px 0 0;
cursor: pointer;
background: url('../images/icon.delete.png') no-repeat center 2px, -*-linear-gradient(
270deg,
background: url('../images/icon.delete.png') no-repeat center 2px, linear-gradient(
180deg,
#5b9bd1 0%,
#5b9bd1 100%
);
@ -540,6 +555,9 @@
.page .combined_table .ctrlHolder > * {
margin: 0 10px 0 0;
}
.page .combined_table .ctrlHolder > .check {
margin-top: 6px;
}
.page .combined_table .ctrlHolder .delete {
display: none;

14
couchpotato/templates/index.html

@ -1,6 +1,9 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=no;"/>
<meta name="apple-mobile-web-app-capable" content="yes">
{% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'front', single = True) %}
<link rel="stylesheet" href="{{ url_for('web.index') }}{{ url }}" type="text/css">{% endfor %}
{% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'front', single = True) %}
@ -17,6 +20,17 @@
<script type="text/javascript" src="https://www.youtube.com/player_api" defer="defer"></script>
<script type="text/javascript">
if($(window).getSize().x <= 480)
window.addEvent('load', function() {
setTimeout(function(){
window.scrollTo(0, 1);
window.scrollTo(0, 0);
}, 100);
});
window.addEvent('domready', function() {
new Uniform();

20
libs/cssprefixer/__init__.py

@ -0,0 +1,20 @@
# CSSPrefixer
# Copyright 2010-2012 Greg V. <floatboth@me.com>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import engine
import rules
from engine import process
__all__ = ('process', 'engine', 'rules')

117
libs/cssprefixer/engine.py

@ -0,0 +1,117 @@
# CSSPrefixer
# Copyright 2010-2012 Greg V. <floatboth@me.com>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import cssutils
import re
from rules import rules as tr_rules
from rules import prefixRegex
keyframesRegex = re.compile(r'@keyframes\s?\w+\s?{(.*)}')
blockRegex = re.compile(r'\w+\s?\{(.*)\}')
def magic(ruleset, debug, minify, filt, parser):
if isinstance(ruleset, cssutils.css.CSSUnknownRule):
if ruleset.cssText.startswith('@keyframes'):
inner = parser.parseString(keyframesRegex.split(ruleset.cssText.replace('\n', ''))[1])
# BUG: doesn't work when minified
s = '' if minify else '\n'
return '@-webkit-keyframes {' + s + \
''.join([magic(rs, debug, minify, ['webkit'], parser) for rs in inner]) \
+ '}' + s + '@-moz-keyframes {' + s + \
''.join([magic(rs, debug, minify, ['moz'], parser) for rs in inner]) \
+ '}' + s + ruleset.cssText
elif ruleset.cssText.startswith('from') or ruleset.cssText.startswith('to'):
return ''.join([magic(rs, debug, minify, filt, parser)
for rs in parser.parseString(blockRegex.sub(r'\1', ruleset.cssText.replace('\n', ''))[1])])
else:
return
elif hasattr(ruleset, 'style'): # Comments don't
ruleSet = set()
rules = list()
children = list(ruleset.style.children())
ruleset.style = cssutils.css.CSSStyleDeclaration() # clear out the styles that were there
for rule in children:
if not hasattr(rule, 'name'): # comments don't have name
rules.append(rule)
continue
name = prefixRegex.sub('', rule.name)
if name in tr_rules:
rule.name = name
if rule.cssText in ruleSet:
continue
ruleSet.add(rule.cssText)
rules.append(rule)
ruleset.style.seq._readonly = False
for rule in rules:
if not hasattr(rule, 'name'):
ruleset.style.seq.append(rule, 'Comment')
continue
processor = None
try: # try except so if anything goes wrong we don't lose the original property
if rule.name in tr_rules:
processor = tr_rules[rule.name](rule)
[ruleset.style.seq.append(prop, 'Property') for prop in processor.get_prefixed_props(filt) if prop]
# always add the original rule
if processor and hasattr(processor, 'get_base_prop'):
ruleset.style.seq.append(processor.get_base_prop(), 'Property')
else:
ruleset.style.seq.append(rule, 'Property')
except:
if debug:
print 'warning with ' + str(rule)
ruleset.style.seq.append(rule, 'Property')
ruleset.style.seq._readonly = True
elif hasattr(ruleset, 'cssRules'):
for subruleset in ruleset:
magic(subruleset, debug, minify, filt, parser)
cssText = ruleset.cssText
if not cssText: # blank rules return None so return an empty string
return
if minify or not hasattr(ruleset, 'style'):
return unicode(cssText)
return unicode(cssText) + '\n'
def process(string, debug = False, minify = False, filt = ['webkit', 'moz', 'o', 'ms'], **prefs):
loglevel = 'DEBUG' if debug else 'ERROR'
parser = cssutils.CSSParser(loglevel = 'CRITICAL')
if minify:
cssutils.ser.prefs.useMinified()
else:
cssutils.ser.prefs.useDefaults()
# use the passed in prefs
for key, value in prefs.iteritems():
if hasattr(cssutils.ser.prefs, key):
cssutils.ser.prefs.__dict__[key] = value
results = []
sheet = parser.parseString(string)
for ruleset in sheet.cssRules:
cssText = magic(ruleset, debug, minify, filt, parser)
if cssText:
results.append(cssText)
# format with newlines based on minify
joinStr = '' if minify else '\n'
# Not using sheet.cssText - it's buggy:
# it skips some prefixed properties.
return joinStr.join(results).rstrip()
__all__ = ['process']

271
libs/cssprefixer/rules.py

@ -0,0 +1,271 @@
# CSSPrefixer
# Copyright 2010-2012 Greg V. <floatboth@me.com>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
import cssutils
prefixRegex = re.compile('^(-o-|-ms-|-moz-|-webkit-)')
class BaseReplacementRule(object):
vendor_prefixes = ['moz', 'webkit']
def __init__(self, prop):
self.prop = prop
def get_prefixed_props(self, filt):
for prefix in [p for p in self.vendor_prefixes if p in filt]:
yield cssutils.css.Property(
name='-%s-%s' % (prefix, self.prop.name),
value=self.prop.value,
priority=self.prop.priority
)
@staticmethod
def should_prefix():
return True
class FullReplacementRule(BaseReplacementRule):
vendor_prefixes = sorted(BaseReplacementRule.vendor_prefixes + ['o', 'ms'])
class BaseAndIEReplacementRule(BaseReplacementRule):
vendor_prefixes = sorted(BaseReplacementRule.vendor_prefixes + ['ms'])
class BaseAndOperaReplacementRule(BaseReplacementRule):
vendor_prefixes = sorted(BaseReplacementRule.vendor_prefixes + ['o'])
class WebkitReplacementRule(BaseReplacementRule):
vendor_prefixes = ['webkit']
class OperaAndIEReplacementRule(BaseReplacementRule):
vendor_prefixes = ['ms', 'o']
class MozReplacementRule(BaseReplacementRule):
vendor_prefixes = ['moz']
class BorderRadiusReplacementRule(BaseReplacementRule):
"""
Mozilla's Gecko engine uses different syntax for rounded corners.
"""
vendor_prefixes = ['webkit']
def get_prefixed_props(self, filt):
for prop in BaseReplacementRule.get_prefixed_props(self, filt):
yield prop
if 'moz' in filt:
name = '-moz-' + self.prop.name.replace('top-left-radius', 'radius-topleft') \
.replace('top-right-radius', 'radius-topright') \
.replace('bottom-right-radius', 'radius-bottomright') \
.replace('bottom-left-radius', 'radius-bottomleft')
yield cssutils.css.Property(
name=name,
value=self.prop.value,
priority=self.prop.priority
)
class DisplayReplacementRule(BaseReplacementRule):
"""
Flexible Box Model stuff.
CSSUtils parser doesn't support duplicate properties, so that's dirty.
"""
def get_prefixed_props(self, filt):
if self.prop.value == 'box': # only add prefixes if the value is box
for prefix in [p for p in self.vendor_prefixes if p in filt]:
yield cssutils.css.Property(
name='display',
value='-%s-box' % prefix,
priority=self.prop.priority
)
class TransitionReplacementRule(BaseReplacementRule):
vendor_prefixes = ['moz', 'o', 'webkit']
def __get_prefixed_prop(self, prefix=None):
name = self.prop.name
if prefix:
name = '-%s-%s' % (prefix, self.prop.name)
newValues = []
for value in self.prop.value.split(','):
parts = value.strip().split(' ')
parts[0] = prefixRegex.sub('', parts[0])
if parts[0] in rules and prefix and rules[parts[0]].should_prefix():
parts[0] = '-%s-%s' % (prefix, parts[0])
newValues.append(' '.join(parts))
return cssutils.css.Property(
name=name,
value=', '.join(newValues),
priority=self.prop.priority
)
def get_prefixed_props(self, filt):
for prefix in [p for p in self.vendor_prefixes if p in filt]:
yield self.__get_prefixed_prop(prefix)
def get_base_prop(self):
return self.__get_prefixed_prop()
class GradientReplacementRule(BaseReplacementRule):
vendor_prefixes = ['moz', 'o', 'webkit']
def __iter_values(self):
valueSplit = self.prop.value.split(',')
index = 0
# currentString = ''
while(True):
if index >= len(valueSplit):
break
rawValue = valueSplit[index].strip()
snip = prefixRegex.sub('', rawValue)
if snip.startswith('linear-gradient'):
values = [re.sub('^linear-gradient\(', '', snip)]
if valueSplit[index + 1].strip().endswith(')'):
values.append(re.sub('\)+$', '', valueSplit[index + 1].strip()))
else:
values.append(valueSplit[index + 1].strip())
values.append(re.sub('\)+$', '', valueSplit[index + 2].strip()))
if len(values) == 2:
yield {
'start': values[0],
'end': values[1]
}
else:
yield {
'pos': values[0],
'start': values[1],
'end': values[2]
}
index += len(values)
elif snip.startswith('gradient'):
yield {
'start': re.sub('\)+$', '', valueSplit[index + 4].strip()),
'end': re.sub('\)+$', '', valueSplit[index + 6].strip()),
}
index += 7
else:
# not a gradient so just yield the raw string
yield rawValue
index += 1
def __get_prefixed_prop(self, values, prefix=None):
gradientName = 'linear-gradient'
if prefix:
gradientName = '-%s-%s' % (prefix, gradientName)
newValues = []
for value in values:
if isinstance(value, dict):
if 'pos' in value:
newValues.append(gradientName + '(%(pos)s, %(start)s, %(end)s)' % value)
else:
newValues.append(gradientName + '(%(start)s, %(end)s)' % value)
else:
newValues.append(value)
return cssutils.css.Property(
name=self.prop.name,
value=', '.join(newValues),
priority=self.prop.priority
)
def get_prefixed_props(self, filt):
values = list(self.__iter_values())
needPrefix = False
for value in values: # check if there are any gradients
if isinstance(value, dict):
needPrefix = True
break
if needPrefix:
for prefix in [p for p in self.vendor_prefixes if p in filt]:
yield self.__get_prefixed_prop(values, prefix)
if prefix == 'webkit':
newValues = []
for value in values:
if isinstance(value, dict):
newValues.append('-webkit-gradient(linear, left top, left bottom, color-stop(0, %(start)s), color-stop(1, %(end)s))' % value)
else:
newValues.append(value)
yield cssutils.css.Property(
name=self.prop.name,
value=', '.join(newValues),
priority=self.prop.priority
)
else:
yield None
def get_base_prop(self):
values = self.__iter_values()
return self.__get_prefixed_prop(values)
rules = {
'border-radius': BaseReplacementRule,
'border-top-left-radius': BorderRadiusReplacementRule,
'border-top-right-radius': BorderRadiusReplacementRule,
'border-bottom-right-radius': BorderRadiusReplacementRule,
'border-bottom-left-radius': BorderRadiusReplacementRule,
'border-image': FullReplacementRule,
'box-shadow': BaseReplacementRule,
'box-sizing': MozReplacementRule,
'box-orient': BaseAndIEReplacementRule,
'box-direction': BaseAndIEReplacementRule,
'box-ordinal-group': BaseAndIEReplacementRule,
'box-align': BaseAndIEReplacementRule,
'box-flex': BaseAndIEReplacementRule,
'box-flex-group': BaseReplacementRule,
'box-pack': BaseAndIEReplacementRule,
'box-lines': BaseAndIEReplacementRule,
'user-select': BaseReplacementRule,
'user-modify': BaseReplacementRule,
'margin-start': BaseReplacementRule,
'margin-end': BaseReplacementRule,
'padding-start': BaseReplacementRule,
'padding-end': BaseReplacementRule,
'column-count': BaseReplacementRule,
'column-gap': BaseReplacementRule,
'column-rule': BaseReplacementRule,
'column-rule-color': BaseReplacementRule,
'column-rule-style': BaseReplacementRule,
'column-rule-width': BaseReplacementRule,
'column-span': WebkitReplacementRule,
'column-width': BaseReplacementRule,
'columns': WebkitReplacementRule,
'background-clip': WebkitReplacementRule,
'background-origin': WebkitReplacementRule,
'background-size': WebkitReplacementRule,
'background-image': GradientReplacementRule,
'background': GradientReplacementRule,
'text-overflow': OperaAndIEReplacementRule,
'transition': TransitionReplacementRule,
'transition-delay': BaseAndOperaReplacementRule,
'transition-duration': BaseAndOperaReplacementRule,
'transition-property': TransitionReplacementRule,
'transition-timing-function': BaseAndOperaReplacementRule,
'transform': FullReplacementRule,
'transform-origin': FullReplacementRule,
'display': DisplayReplacementRule,
'appearance': WebkitReplacementRule,
'hyphens': BaseReplacementRule,
}

385
libs/cssutils/__init__.py

@ -0,0 +1,385 @@
#!/usr/bin/env python
"""cssutils - CSS Cascading Style Sheets library for Python
Copyright (C) 2004-2013 Christof Hoeke
cssutils is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
A Python package to parse and build CSS Cascading Style Sheets. DOM only, not
any rendering facilities!
Based upon and partly implementing the following specifications :
`CSS 2.1 <http://www.w3.org/TR/CSS2/>`__
General CSS rules and properties are defined here
`CSS 2.1 Errata <http://www.w3.org/Style/css2-updates/CR-CSS21-20070719-errata.html>`__
A few errata, mainly the definition of CHARSET_SYM tokens
`CSS3 Module: Syntax <http://www.w3.org/TR/css3-syntax/>`__
Used in parts since cssutils 0.9.4. cssutils tries to use the features from
CSS 2.1 and CSS 3 with preference to CSS3 but as this is not final yet some
parts are from CSS 2.1
`MediaQueries <http://www.w3.org/TR/css3-mediaqueries/>`__
MediaQueries are part of ``stylesheets.MediaList`` since v0.9.4, used in
@import and @media rules.
`Namespaces <http://dev.w3.org/csswg/css3-namespace/>`__
Added in v0.9.1, updated to definition in CSSOM in v0.9.4, updated in 0.9.5
for dev version
`CSS3 Module: Pages Media <http://www.w3.org/TR/css3-page/>`__
Most properties of this spec are implemented including MarginRules
`Selectors <http://www.w3.org/TR/css3-selectors/>`__
The selector syntax defined here (and not in CSS 2.1) should be parsable
with cssutils (*should* mind though ;) )
`DOM Level 2 Style CSS <http://www.w3.org/TR/DOM-Level-2-Style/css.html>`__
DOM for package css. 0.9.8 removes support for CSSValue and related API,
see PropertyValue and Value API for now
`DOM Level 2 Style Stylesheets <http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html>`__
DOM for package stylesheets
`CSSOM <http://dev.w3.org/csswg/cssom/>`__
A few details (mainly the NamespaceRule DOM) is taken from here. Plan is
to move implementation to the stuff defined here which is newer but still
no REC so might change anytime...
The cssutils tokenizer is a customized implementation of `CSS3 Module: Syntax
(W3C Working Draft 13 August 2003) <http://www.w3.org/TR/css3-syntax/>`__ which
itself is based on the CSS 2.1 tokenizer. It tries to be as compliant as
possible but uses some (helpful) parts of the CSS 2.1 tokenizer.
I guess cssutils is neither CSS 2.1 nor CSS 3 compliant but tries to at least
be able to parse both grammars including some more real world cases (some CSS
hacks are actually parsed and serialized). Both official grammars are not final
nor bugfree but still feasible. cssutils aim is not to be fully compliant to
any CSS specification (the specifications seem to be in a constant flow anyway)
but cssutils *should* be able to read and write as many as possible CSS
stylesheets "in the wild" while at the same time implement the official APIs
which are well documented. Some minor extensions are provided as well.
Please visit http://cthedot.de/cssutils/ for more details.
Tested with Python 2.7.3 and 3.3 on Windows 8 64bit.
This library may be used ``from cssutils import *`` which
import subpackages ``css`` and ``stylesheets``, CSSParser and
CSSSerializer classes only.
Usage may be::
>>> from cssutils import *
>>> parser = CSSParser()
>>> sheet = parser.parseString(u'a { color: red}')
>>> print sheet.cssText
a {
color: red
}
"""
__all__ = ['css', 'stylesheets', 'CSSParser', 'CSSSerializer']
__docformat__ = 'restructuredtext'
__author__ = 'Christof Hoeke with contributions by Walter Doerwald'
__date__ = '$LastChangedDate:: $:'
VERSION = '0.9.10'
__version__ = '%s $Id$' % VERSION
import sys
if sys.version_info < (2,6):
bytes = str
import codec
import os.path
import urllib
import urlparse
import xml.dom
# order of imports is important (partly circular)
from . import util
import errorhandler
log = errorhandler.ErrorHandler()
import css
import stylesheets
from parse import CSSParser
from serialize import CSSSerializer
ser = CSSSerializer()
from profiles import Profiles
profile = Profiles(log=log)
# used by Selector defining namespace prefix '*'
_ANYNS = -1
class DOMImplementationCSS(object):
"""This interface allows the DOM user to create a CSSStyleSheet
outside the context of a document. There is no way to associate
the new CSSStyleSheet with a document in DOM Level 2.
This class is its *own factory*, as it is given to
xml.dom.registerDOMImplementation which simply calls it and receives
an instance of this class then.
"""
_features = [
('css', '1.0'),
('css', '2.0'),
('stylesheets', '1.0'),
('stylesheets', '2.0')
]
def createCSSStyleSheet(self, title, media):
"""
Creates a new CSSStyleSheet.
title of type DOMString
The advisory title. See also the Style Sheet Interfaces
section.
media of type DOMString
The comma-separated list of media associated with the new style
sheet. See also the Style Sheet Interfaces section.
returns
CSSStyleSheet: A new CSS style sheet.
TODO: DOMException
SYNTAX_ERR: Raised if the specified media string value has a
syntax error and is unparsable.
"""
return css.CSSStyleSheet(title=title, media=media)
def createDocument(self, *args):
# not needed to HTML, also not for CSS?
raise NotImplementedError
def createDocumentType(self, *args):
# not needed to HTML, also not for CSS?
raise NotImplementedError
def hasFeature(self, feature, version):
return (feature.lower(), unicode(version)) in self._features
xml.dom.registerDOMImplementation('cssutils', DOMImplementationCSS)
def parseString(*a, **k):
return CSSParser().parseString(*a, **k)
parseString.__doc__ = CSSParser.parseString.__doc__
def parseFile(*a, **k):
return CSSParser().parseFile(*a, **k)
parseFile.__doc__ = CSSParser.parseFile.__doc__
def parseUrl(*a, **k):
return CSSParser().parseUrl(*a, **k)
parseUrl.__doc__ = CSSParser.parseUrl.__doc__
def parseStyle(*a, **k):
return CSSParser().parseStyle(*a, **k)
parseStyle.__doc__ = CSSParser.parseStyle.__doc__
# set "ser", default serializer
def setSerializer(serializer):
"""Set the global serializer used by all class in cssutils."""
global ser
ser = serializer
def getUrls(sheet):
"""Retrieve all ``url(urlstring)`` values (in e.g.
:class:`cssutils.css.CSSImportRule` or :class:`cssutils.css.CSSValue`
objects of given `sheet`.
:param sheet:
:class:`cssutils.css.CSSStyleSheet` object whose URLs are yielded
This function is a generator. The generated URL values exclude ``url(`` and
``)`` and surrounding single or double quotes.
"""
for importrule in (r for r in sheet if r.type == r.IMPORT_RULE):
yield importrule.href
def styleDeclarations(base):
"recursive generator to find all CSSStyleDeclarations"
if hasattr(base, 'cssRules'):
for rule in base.cssRules:
for s in styleDeclarations(rule):
yield s
elif hasattr(base, 'style'):
yield base.style
for style in styleDeclarations(sheet):
for p in style.getProperties(all=True):
for v in p.propertyValue:
if v.type == 'URI':
yield v.uri
def replaceUrls(sheetOrStyle, replacer, ignoreImportRules=False):
"""Replace all URLs in :class:`cssutils.css.CSSImportRule` or
:class:`cssutils.css.CSSValue` objects of given `sheetOrStyle`.
:param sheetOrStyle:
a :class:`cssutils.css.CSSStyleSheet` or a
:class:`cssutils.css.CSSStyleDeclaration` which is changed in place
:param replacer:
a function which is called with a single argument `url` which
is the current value of each url() excluding ``url(``, ``)`` and
surrounding (single or double) quotes.
:param ignoreImportRules:
if ``True`` does not call `replacer` with URLs from @import rules.
"""
if not ignoreImportRules and not isinstance(sheetOrStyle,
css.CSSStyleDeclaration):
for importrule in (r for r in sheetOrStyle if r.type == r.IMPORT_RULE):
importrule.href = replacer(importrule.href)
def styleDeclarations(base):
"recursive generator to find all CSSStyleDeclarations"
if hasattr(base, 'cssRules'):
for rule in base.cssRules:
for s in styleDeclarations(rule):
yield s
elif hasattr(base, 'style'):
yield base.style
elif isinstance(sheetOrStyle, css.CSSStyleDeclaration):
# base is a style already
yield base
for style in styleDeclarations(sheetOrStyle):
for p in style.getProperties(all=True):
for v in p.propertyValue:
if v.type == v.URI:
v.uri = replacer(v.uri)
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 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
resolved and added to a resulting *flat* sheet.
:param target:
A :class:`cssutils.css.CSSStyleSheet` object which will be the
resulting *flat* sheet if given
:returns: given `target` or a new :class:`cssutils.css.CSSStyleSheet`
object
"""
if not target:
target = css.CSSStyleSheet(href=sheet.href,
media=sheet.media,
title=sheet.title)
def getReplacer(targetbase):
"Return a replacer which uses base to return adjusted URLs"
basesch, baseloc, basepath, basequery, basefrag = urlparse.urlsplit(targetbase)
basepath, basepathfilename = os.path.split(basepath)
def replacer(uri):
scheme, location, path, query, fragment = urlparse.urlsplit(uri)
if not scheme and not location and not path.startswith(u'/'):
# relative
path, filename = os.path.split(path)
combined = os.path.normpath(os.path.join(basepath, path, filename))
return urllib.pathname2url(combined)
else:
# keep anything absolute
return uri
return replacer
for rule in sheet.cssRules:
if rule.type == rule.CHARSET_RULE:
pass
elif rule.type == rule.IMPORT_RULE:
log.info(u'Processing @import %r' % rule.href, neverraise=True)
if rule.hrefFound:
# add all rules of @import to current sheet
target.add(css.CSSComment(cssText=u'/* START @import "%s" */'
% rule.href))
try:
# nested imports
importedSheet = resolveImports(rule.styleSheet)
except xml.dom.HierarchyRequestErr, e:
log.warn(u'@import: Cannot resolve target, keeping rule: %s'
% e, neverraise=True)
target.add(rule)
else:
# adjust relative URI references
log.info(u'@import: Adjusting paths for %r' % rule.href,
neverraise=True)
replaceUrls(importedSheet,
getReplacer(rule.href),
ignoreImportRules=True)
# might have to wrap rules in @media if media given
if rule.media.mediaText == u'all':
mediaproxy = None
else:
keepimport = False
for r in importedSheet:
# check if rules present which may not be
# combined with media
if r.type not in (r.COMMENT,
r.STYLE_RULE,
r.IMPORT_RULE):
keepimport = True
break
if keepimport:
log.warn(u'Cannot combine imported sheet with'
u' given media as other rules then'
u' comments or stylerules found %r,'
u' keeping %r' % (r,
rule.cssText),
neverraise=True)
target.add(rule)
continue
# wrap in @media if media is not `all`
log.info(u'@import: Wrapping some rules in @media '
u' to keep media: %s'
% rule.media.mediaText, neverraise=True)
mediaproxy = css.CSSMediaRule(rule.media.mediaText)
for r in importedSheet:
if mediaproxy:
mediaproxy.add(r)
else:
# add to top sheet directly but are difficult anyway
target.add(r)
if mediaproxy:
target.add(mediaproxy)
else:
# keep @import as it is
log.error(u'Cannot get referenced stylesheet %r, keeping rule'
% rule.href, neverraise=True)
target.add(rule)
else:
target.add(rule)
return target
if __name__ == '__main__':
print __doc__

584
libs/cssutils/_codec2.py

@ -0,0 +1,584 @@
#!/usr/bin/env python
"""Python codec for CSS."""
__docformat__ = 'restructuredtext'
__author__ = 'Walter Doerwald'
__version__ = '$Id: util.py 1114 2008-03-05 13:22:59Z cthedot $'
import codecs
import marshal
# We're using bits to store all possible candidate encodings (or variants, i.e.
# we have two bits for the variants of UTF-16 and two for the
# variants of UTF-32).
#
# Prefixes for various CSS encodings
# UTF-8-SIG xEF xBB xBF
# UTF-16 (LE) xFF xFE ~x00|~x00
# UTF-16 (BE) xFE xFF
# UTF-16-LE @ x00 @ x00
# UTF-16-BE x00 @
# UTF-32 (LE) xFF xFE x00 x00
# UTF-32 (BE) x00 x00 xFE xFF
# UTF-32-LE @ x00 x00 x00
# UTF-32-BE x00 x00 x00 @
# CHARSET @ c h a ...
def detectencoding_str(input, final=False):
"""
Detect the encoding of the byte string ``input``, which contains the
beginning of a CSS file. This function returns the detected encoding (or
``None`` if it hasn't got enough data), and a flag that indicates whether
that encoding has been detected explicitely or implicitely. To detect the
encoding the first few bytes are used (or if ``input`` is ASCII compatible
and starts with a charset rule the encoding name from the rule). "Explicit"
detection means that the bytes start with a BOM or a charset rule.
If the encoding can't be detected yet, ``None`` is returned as the encoding.
``final`` specifies whether more data will be available in later calls or
not. If ``final`` is true, ``detectencoding_str()`` will never return
``None`` as the encoding.
"""
# A bit for every candidate
CANDIDATE_UTF_8_SIG = 1
CANDIDATE_UTF_16_AS_LE = 2
CANDIDATE_UTF_16_AS_BE = 4
CANDIDATE_UTF_16_LE = 8
CANDIDATE_UTF_16_BE = 16
CANDIDATE_UTF_32_AS_LE = 32
CANDIDATE_UTF_32_AS_BE = 64
CANDIDATE_UTF_32_LE = 128
CANDIDATE_UTF_32_BE = 256
CANDIDATE_CHARSET = 512
candidates = 1023 # all candidates
li = len(input)
if li>=1:
# Check first byte
c = input[0]
if c != "\xef":
candidates &= ~CANDIDATE_UTF_8_SIG
if c != "\xff":
candidates &= ~(CANDIDATE_UTF_32_AS_LE|CANDIDATE_UTF_16_AS_LE)
if c != "\xfe":
candidates &= ~CANDIDATE_UTF_16_AS_BE
if c != "@":
candidates &= ~(CANDIDATE_UTF_32_LE|CANDIDATE_UTF_16_LE|CANDIDATE_CHARSET)
if c != "\x00":
candidates &= ~(CANDIDATE_UTF_32_AS_BE|CANDIDATE_UTF_32_BE|CANDIDATE_UTF_16_BE)
if li>=2:
# Check second byte
c = input[1]
if c != "\xbb":
candidates &= ~CANDIDATE_UTF_8_SIG
if c != "\xfe":
candidates &= ~(CANDIDATE_UTF_16_AS_LE|CANDIDATE_UTF_32_AS_LE)
if c != "\xff":
candidates &= ~CANDIDATE_UTF_16_AS_BE
if c != "\x00":
candidates &= ~(CANDIDATE_UTF_16_LE|CANDIDATE_UTF_32_AS_BE|CANDIDATE_UTF_32_LE|CANDIDATE_UTF_32_BE)
if c != "@":
candidates &= ~CANDIDATE_UTF_16_BE
if c != "c":
candidates &= ~CANDIDATE_CHARSET
if li>=3:
# Check third byte
c = input[2]
if c != "\xbf":
candidates &= ~CANDIDATE_UTF_8_SIG
if c != "c":
candidates &= ~CANDIDATE_UTF_16_LE
if c != "\x00":
candidates &= ~(CANDIDATE_UTF_32_AS_LE|CANDIDATE_UTF_32_LE|CANDIDATE_UTF_32_BE)
if c != "\xfe":
candidates &= ~CANDIDATE_UTF_32_AS_BE
if c != "h":
candidates &= ~CANDIDATE_CHARSET
if li>=4:
# Check fourth byte
c = input[3]
if input[2:4] == "\x00\x00":
candidates &= ~CANDIDATE_UTF_16_AS_LE
if c != "\x00":
candidates &= ~(CANDIDATE_UTF_16_LE|CANDIDATE_UTF_32_AS_LE|CANDIDATE_UTF_32_LE)
if c != "\xff":
candidates &= ~CANDIDATE_UTF_32_AS_BE
if c != "@":
candidates &= ~CANDIDATE_UTF_32_BE
if c != "a":
candidates &= ~CANDIDATE_CHARSET
if candidates == 0:
return ("utf-8", False)
if not (candidates & (candidates-1)): # only one candidate remaining
if candidates == CANDIDATE_UTF_8_SIG and li >= 3:
return ("utf-8-sig", True)
elif candidates == CANDIDATE_UTF_16_AS_LE and li >= 2:
return ("utf-16", True)
elif candidates == CANDIDATE_UTF_16_AS_BE and li >= 2:
return ("utf-16", True)
elif candidates == CANDIDATE_UTF_16_LE and li >= 4:
return ("utf-16-le", False)
elif candidates == CANDIDATE_UTF_16_BE and li >= 2:
return ("utf-16-be", False)
elif candidates == CANDIDATE_UTF_32_AS_LE and li >= 4:
return ("utf-32", True)
elif candidates == CANDIDATE_UTF_32_AS_BE and li >= 4:
return ("utf-32", True)
elif candidates == CANDIDATE_UTF_32_LE and li >= 4:
return ("utf-32-le", False)
elif candidates == CANDIDATE_UTF_32_BE and li >= 4:
return ("utf-32-be", False)
elif candidates == CANDIDATE_CHARSET and li >= 4:
prefix = '@charset "'
if input[:len(prefix)] == prefix:
pos = input.find('"', len(prefix))
if pos >= 0:
return (input[len(prefix):pos], True)
# if this is the last call, and we haven't determined an encoding yet,
# we default to UTF-8
if final:
return ("utf-8", False)
return (None, False) # dont' know yet
def detectencoding_unicode(input, final=False):
"""
Detect the encoding of the unicode string ``input``, which contains the
beginning of a CSS file. The encoding is detected from the charset rule
at the beginning of ``input``. If there is no charset rule, ``"utf-8"``
will be returned.
If the encoding can't be detected yet, ``None`` is returned. ``final``
specifies whether more data will be available in later calls or not. If
``final`` is true, ``detectencoding_unicode()`` will never return ``None``.
"""
prefix = u'@charset "'
if input.startswith(prefix):
pos = input.find(u'"', len(prefix))
if pos >= 0:
return (input[len(prefix):pos], True)
elif final or not prefix.startswith(input):
# if this is the last call, and we haven't determined an encoding yet,
# (or the string definitely doesn't start with prefix) we default to UTF-8
return ("utf-8", False)
return (None, False) # don't know yet
def _fixencoding(input, encoding, final=False):
"""
Replace the name of the encoding in the charset rule at the beginning of
``input`` with ``encoding``. If ``input`` doesn't starts with a charset
rule, ``input`` will be returned unmodified.
If the encoding can't be found yet, ``None`` is returned. ``final``
specifies whether more data will be available in later calls or not.
If ``final`` is true, ``_fixencoding()`` will never return ``None``.
"""
prefix = u'@charset "'
if len(input) > len(prefix):
if input.startswith(prefix):
pos = input.find(u'"', len(prefix))
if pos >= 0:
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = u"utf-8"
return prefix + encoding + input[pos:]
# we haven't seen the end of the encoding name yet => fall through
else:
return input # doesn't start with prefix, so nothing to fix
elif not prefix.startswith(input) or final:
# can't turn out to be a @charset rule later (or there is no "later")
return input
if final:
return input
return None # don't know yet
def decode(input, errors="strict", encoding=None, force=True):
if encoding is None or not force:
(_encoding, explicit) = detectencoding_str(input, True)
if _encoding == "css":
raise ValueError("css not allowed as encoding name")
if (explicit and not force) or encoding is None: # Take the encoding from the input
encoding = _encoding
(input, consumed) = codecs.getdecoder(encoding)(input, errors)
return (_fixencoding(input, unicode(encoding), True), consumed)
def encode(input, errors="strict", encoding=None):
consumed = len(input)
if encoding is None:
encoding = detectencoding_unicode(input, True)[0]
if encoding.replace("_", "-").lower() == "utf-8-sig":
input = _fixencoding(input, u"utf-8", True)
else:
input = _fixencoding(input, unicode(encoding), True)
if encoding == "css":
raise ValueError("css not allowed as encoding name")
encoder = codecs.getencoder(encoding)
return (encoder(input, errors)[0], consumed)
def _bytes2int(bytes):
# Helper: convert an 8 bit string into an ``int``.
i = 0
for byte in bytes:
i = (i<<8) + ord(byte)
return i
def _int2bytes(i):
# Helper: convert an ``int`` into an 8-bit string.
v = []
while i:
v.insert(0, chr(i&0xff))
i >>= 8
return "".join(v)
if hasattr(codecs, "IncrementalDecoder"):
class IncrementalDecoder(codecs.IncrementalDecoder):
def __init__(self, errors="strict", encoding=None, force=True):
self.decoder = None
self.encoding = encoding
self.force = force
codecs.IncrementalDecoder.__init__(self, errors)
# Store ``errors`` somewhere else,
# because we have to hide it in a property
self._errors = errors
self.buffer = u"".encode()
self.headerfixed = False
def iterdecode(self, input):
for part in input:
result = self.decode(part, False)
if result:
yield result
result = self.decode("", True)
if result:
yield result
def decode(self, input, final=False):
# We're doing basically the same as a ``BufferedIncrementalDecoder``,
# but since the buffer is only relevant until the encoding has been
# detected (in which case the buffer of the underlying codec might
# kick in), we're implementing buffering ourselves to avoid some
# overhead.
if self.decoder is None:
input = self.buffer + input
# Do we have to detect the encoding from the input?
if self.encoding is None or not self.force:
(encoding, explicit) = detectencoding_str(input, final)
if encoding is None: # no encoding determined yet
self.buffer = input # retry the complete input on the next call
return u"" # no encoding determined yet, so no output
elif encoding == "css":
raise ValueError("css not allowed as encoding name")
if (explicit and not self.force) or self.encoding is None: # Take the encoding from the input
self.encoding = encoding
self.buffer = "" # drop buffer, as the decoder might keep its own
decoder = codecs.getincrementaldecoder(self.encoding)
self.decoder = decoder(self._errors)
if self.headerfixed:
return self.decoder.decode(input, final)
# If we haven't fixed the header yet,
# the content of ``self.buffer`` is a ``unicode`` object
output = self.buffer + self.decoder.decode(input, final)
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newoutput = _fixencoding(output, unicode(encoding), final)
if newoutput is None:
# retry fixing the @charset rule (but keep the decoded stuff)
self.buffer = output
return u""
self.headerfixed = True
return newoutput
def reset(self):
codecs.IncrementalDecoder.reset(self)
self.decoder = None
self.buffer = u"".encode()
self.headerfixed = False
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors`` must be done on the real decoder too
if self.decoder is not None:
self.decoder.errors = errors
self._errors = errors
errors = property(_geterrors, _seterrors)
def getstate(self):
if self.decoder is not None:
state = (self.encoding, self.buffer, self.headerfixed, True, self.decoder.getstate())
else:
state = (self.encoding, self.buffer, self.headerfixed, False, None)
return ("", _bytes2int(marshal.dumps(state)))
def setstate(self, state):
state = _int2bytes(marshal.loads(state[1])) # ignore buffered input
self.encoding = state[0]
self.buffer = state[1]
self.headerfixed = state[2]
if state[3] is not None:
self.decoder = codecs.getincrementaldecoder(self.encoding)(self._errors)
self.decoder.setstate(state[4])
else:
self.decoder = None
if hasattr(codecs, "IncrementalEncoder"):
class IncrementalEncoder(codecs.IncrementalEncoder):
def __init__(self, errors="strict", encoding=None):
self.encoder = None
self.encoding = encoding
codecs.IncrementalEncoder.__init__(self, errors)
# Store ``errors`` somewhere else,
# because we have to hide it in a property
self._errors = errors
self.buffer = u""
def iterencode(self, input):
for part in input:
result = self.encode(part, False)
if result:
yield result
result = self.encode(u"", True)
if result:
yield result
def encode(self, input, final=False):
if self.encoder is None:
input = self.buffer + input
if self.encoding is not None:
# Replace encoding in the @charset rule with the specified one
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newinput = _fixencoding(input, unicode(encoding), final)
if newinput is None: # @charset rule incomplete => Retry next time
self.buffer = input
return ""
input = newinput
else:
# Use encoding from the @charset declaration
self.encoding = detectencoding_unicode(input, final)[0]
if self.encoding is not None:
if self.encoding == "css":
raise ValueError("css not allowed as encoding name")
info = codecs.lookup(self.encoding)
encoding = self.encoding
if self.encoding.replace("_", "-").lower() == "utf-8-sig":
input = _fixencoding(input, u"utf-8", True)
self.encoder = info.incrementalencoder(self._errors)
self.buffer = u""
else:
self.buffer = input
return ""
return self.encoder.encode(input, final)
def reset(self):
codecs.IncrementalEncoder.reset(self)
self.encoder = None
self.buffer = u""
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors ``must be done on the real encoder too
if self.encoder is not None:
self.encoder.errors = errors
self._errors = errors
errors = property(_geterrors, _seterrors)
def getstate(self):
if self.encoder is not None:
state = (self.encoding, self.buffer, True, self.encoder.getstate())
else:
state = (self.encoding, self.buffer, False, None)
return _bytes2int(marshal.dumps(state))
def setstate(self, state):
state = _int2bytes(marshal.loads(state))
self.encoding = state[0]
self.buffer = state[1]
if state[2] is not None:
self.encoder = codecs.getincrementalencoder(self.encoding)(self._errors)
self.encoder.setstate(state[4])
else:
self.encoder = None
class StreamWriter(codecs.StreamWriter):
def __init__(self, stream, errors="strict", encoding=None, header=False):
codecs.StreamWriter.__init__(self, stream, errors)
self.streamwriter = None
self.encoding = encoding
self._errors = errors
self.buffer = u""
def encode(self, input, errors='strict'):
li = len(input)
if self.streamwriter is None:
input = self.buffer + input
li = len(input)
if self.encoding is not None:
# Replace encoding in the @charset rule with the specified one
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newinput = _fixencoding(input, unicode(encoding), False)
if newinput is None: # @charset rule incomplete => Retry next time
self.buffer = input
return ("", 0)
input = newinput
else:
# Use encoding from the @charset declaration
self.encoding = detectencoding_unicode(input, False)[0]
if self.encoding is not None:
if self.encoding == "css":
raise ValueError("css not allowed as encoding name")
self.streamwriter = codecs.getwriter(self.encoding)(self.stream, self._errors)
encoding = self.encoding
if self.encoding.replace("_", "-").lower() == "utf-8-sig":
input = _fixencoding(input, u"utf-8", True)
self.buffer = u""
else:
self.buffer = input
return ("", 0)
return (self.streamwriter.encode(input, errors)[0], li)
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors`` must be done on the streamwriter too
if self.streamwriter is not None:
self.streamwriter.errors = errors
self._errors = errors
errors = property(_geterrors, _seterrors)
class StreamReader(codecs.StreamReader):
def __init__(self, stream, errors="strict", encoding=None, force=True):
codecs.StreamReader.__init__(self, stream, errors)
self.streamreader = None
self.encoding = encoding
self.force = force
self._errors = errors
def decode(self, input, errors='strict'):
if self.streamreader is None:
if self.encoding is None or not self.force:
(encoding, explicit) = detectencoding_str(input, False)
if encoding is None: # no encoding determined yet
return (u"", 0) # no encoding determined yet, so no output
elif encoding == "css":
raise ValueError("css not allowed as encoding name")
if (explicit and not self.force) or self.encoding is None: # Take the encoding from the input
self.encoding = encoding
streamreader = codecs.getreader(self.encoding)
streamreader = streamreader(self.stream, self._errors)
(output, consumed) = streamreader.decode(input, errors)
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newoutput = _fixencoding(output, unicode(encoding), False)
if newoutput is not None:
self.streamreader = streamreader
return (newoutput, consumed)
return (u"", 0) # we will create a new streamreader on the next call
return self.streamreader.decode(input, errors)
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors`` must be done on the streamreader too
if self.streamreader is not None:
self.streamreader.errors = errors
self._errors = errors
errors = property(_geterrors, _seterrors)
if hasattr(codecs, "CodecInfo"):
# We're running on Python 2.5 or better
def search_function(name):
if name == "css":
return codecs.CodecInfo(
name="css",
encode=encode,
decode=decode,
incrementalencoder=IncrementalEncoder,
incrementaldecoder=IncrementalDecoder,
streamwriter=StreamWriter,
streamreader=StreamReader,
)
else:
# If we're running on Python 2.4, define the utf-8-sig codec here
def utf8sig_encode(input, errors='strict'):
return (codecs.BOM_UTF8 + codecs.utf_8_encode(input, errors)[0], len(input))
def utf8sig_decode(input, errors='strict'):
prefix = 0
if input[:3] == codecs.BOM_UTF8:
input = input[3:]
prefix = 3
(output, consumed) = codecs.utf_8_decode(input, errors, True)
return (output, consumed+prefix)
class UTF8SigStreamWriter(codecs.StreamWriter):
def reset(self):
codecs.StreamWriter.reset(self)
try:
del self.encode
except AttributeError:
pass
def encode(self, input, errors='strict'):
self.encode = codecs.utf_8_encode
return utf8sig_encode(input, errors)
class UTF8SigStreamReader(codecs.StreamReader):
def reset(self):
codecs.StreamReader.reset(self)
try:
del self.decode
except AttributeError:
pass
def decode(self, input, errors='strict'):
if len(input) < 3 and codecs.BOM_UTF8.startswith(input):
# not enough data to decide if this is a BOM
# => try again on the next call
return (u"", 0)
self.decode = codecs.utf_8_decode
return utf8sig_decode(input, errors)
def search_function(name):
import encodings
name = encodings.normalize_encoding(name)
if name == "css":
return (encode, decode, StreamReader, StreamWriter)
elif name == "utf_8_sig":
return (utf8sig_encode, utf8sig_decode, UTF8SigStreamReader, UTF8SigStreamWriter)
codecs.register(search_function)
# Error handler for CSS escaping
def cssescape(exc):
if not isinstance(exc, UnicodeEncodeError):
raise TypeError("don't know how to handle %r" % exc)
return (u"".join(u"\\%06x" % ord(c) for c in exc.object[exc.start:exc.end]), exc.end)
codecs.register_error("cssescape", cssescape)

608
libs/cssutils/_codec3.py

@ -0,0 +1,608 @@
#!/usr/bin/env python
"""Python codec for CSS."""
__docformat__ = 'restructuredtext'
__author__ = 'Walter Doerwald'
__version__ = '$Id: util.py 1114 2008-03-05 13:22:59Z cthedot $'
import sys
import codecs
import marshal
# We're using bits to store all possible candidate encodings (or variants, i.e.
# we have two bits for the variants of UTF-16 and two for the
# variants of UTF-32).
#
# Prefixes for various CSS encodings
# UTF-8-SIG xEF xBB xBF
# UTF-16 (LE) xFF xFE ~x00|~x00
# UTF-16 (BE) xFE xFF
# UTF-16-LE @ x00 @ x00
# UTF-16-BE x00 @
# UTF-32 (LE) xFF xFE x00 x00
# UTF-32 (BE) x00 x00 xFE xFF
# UTF-32-LE @ x00 x00 x00
# UTF-32-BE x00 x00 x00 @
# CHARSET @ c h a ...
def chars(bytestring):
return ''.join(chr(byte) for byte in bytestring)
def detectencoding_str(input, final=False):
"""
Detect the encoding of the byte string ``input``, which contains the
beginning of a CSS file. This function returns the detected encoding (or
``None`` if it hasn't got enough data), and a flag that indicates whether
that encoding has been detected explicitely or implicitely. To detect the
encoding the first few bytes are used (or if ``input`` is ASCII compatible
and starts with a charset rule the encoding name from the rule). "Explicit"
detection means that the bytes start with a BOM or a charset rule.
If the encoding can't be detected yet, ``None`` is returned as the encoding.
``final`` specifies whether more data will be available in later calls or
not. If ``final`` is true, ``detectencoding_str()`` will never return
``None`` as the encoding.
"""
# A bit for every candidate
CANDIDATE_UTF_8_SIG = 1
CANDIDATE_UTF_16_AS_LE = 2
CANDIDATE_UTF_16_AS_BE = 4
CANDIDATE_UTF_16_LE = 8
CANDIDATE_UTF_16_BE = 16
CANDIDATE_UTF_32_AS_LE = 32
CANDIDATE_UTF_32_AS_BE = 64
CANDIDATE_UTF_32_LE = 128
CANDIDATE_UTF_32_BE = 256
CANDIDATE_CHARSET = 512
candidates = 1023 # all candidates
#input = chars(input)
li = len(input)
if li>=1:
# Check first byte
c = input[0]
if c != b"\xef"[0]:
candidates &= ~CANDIDATE_UTF_8_SIG
if c != b"\xff"[0]:
candidates &= ~(CANDIDATE_UTF_32_AS_LE|CANDIDATE_UTF_16_AS_LE)
if c != b"\xfe"[0]:
candidates &= ~CANDIDATE_UTF_16_AS_BE
if c != b"@"[0]:
candidates &= ~(CANDIDATE_UTF_32_LE|CANDIDATE_UTF_16_LE|CANDIDATE_CHARSET)
if c != b"\x00"[0]:
candidates &= ~(CANDIDATE_UTF_32_AS_BE|CANDIDATE_UTF_32_BE|CANDIDATE_UTF_16_BE)
if li>=2:
# Check second byte
c = input[1]
if c != b"\xbb"[0]:
candidates &= ~CANDIDATE_UTF_8_SIG
if c != b"\xfe"[0]:
candidates &= ~(CANDIDATE_UTF_16_AS_LE|CANDIDATE_UTF_32_AS_LE)
if c != b"\xff"[0]:
candidates &= ~CANDIDATE_UTF_16_AS_BE
if c != b"\x00"[0]:
candidates &= ~(CANDIDATE_UTF_16_LE|CANDIDATE_UTF_32_AS_BE|CANDIDATE_UTF_32_LE|CANDIDATE_UTF_32_BE)
if c != b"@"[0]:
candidates &= ~CANDIDATE_UTF_16_BE
if c != b"c"[0]:
candidates &= ~CANDIDATE_CHARSET
if li>=3:
# Check third byte
c = input[2]
if c != b"\xbf"[0]:
candidates &= ~CANDIDATE_UTF_8_SIG
if c != b"c"[0]:
candidates &= ~CANDIDATE_UTF_16_LE
if c != b"\x00"[0]:
candidates &= ~(CANDIDATE_UTF_32_AS_LE|CANDIDATE_UTF_32_LE|CANDIDATE_UTF_32_BE)
if c != b"\xfe"[0]:
candidates &= ~CANDIDATE_UTF_32_AS_BE
if c != b"h"[0]:
candidates &= ~CANDIDATE_CHARSET
if li>=4:
# Check fourth byte
c = input[3]
if input[2:4] == b"\x00\x00"[0:2]:
candidates &= ~CANDIDATE_UTF_16_AS_LE
if c != b"\x00"[0]:
candidates &= ~(CANDIDATE_UTF_16_LE|CANDIDATE_UTF_32_AS_LE|CANDIDATE_UTF_32_LE)
if c != b"\xff"[0]:
candidates &= ~CANDIDATE_UTF_32_AS_BE
if c != b"@"[0]:
candidates &= ~CANDIDATE_UTF_32_BE
if c != b"a"[0]:
candidates &= ~CANDIDATE_CHARSET
if candidates == 0:
return ("utf-8", False)
if not (candidates & (candidates-1)): # only one candidate remaining
if candidates == CANDIDATE_UTF_8_SIG and li >= 3:
return ("utf-8-sig", True)
elif candidates == CANDIDATE_UTF_16_AS_LE and li >= 2:
return ("utf-16", True)
elif candidates == CANDIDATE_UTF_16_AS_BE and li >= 2:
return ("utf-16", True)
elif candidates == CANDIDATE_UTF_16_LE and li >= 4:
return ("utf-16-le", False)
elif candidates == CANDIDATE_UTF_16_BE and li >= 2:
return ("utf-16-be", False)
elif candidates == CANDIDATE_UTF_32_AS_LE and li >= 4:
return ("utf-32", True)
elif candidates == CANDIDATE_UTF_32_AS_BE and li >= 4:
return ("utf-32", True)
elif candidates == CANDIDATE_UTF_32_LE and li >= 4:
return ("utf-32-le", False)
elif candidates == CANDIDATE_UTF_32_BE and li >= 4:
return ("utf-32-be", False)
elif candidates == CANDIDATE_CHARSET and li >= 4:
prefix = '@charset "'
charsinput = chars(input)
if charsinput[:len(prefix)] == prefix:
pos = charsinput.find('"', len(prefix))
if pos >= 0:
# TODO: return str and not bytes!
return (charsinput[len(prefix):pos], True)
# if this is the last call, and we haven't determined an encoding yet,
# we default to UTF-8
if final:
return ("utf-8", False)
return (None, False) # dont' know yet
def detectencoding_unicode(input, final=False):
"""
Detect the encoding of the unicode string ``input``, which contains the
beginning of a CSS file. The encoding is detected from the charset rule
at the beginning of ``input``. If there is no charset rule, ``"utf-8"``
will be returned.
If the encoding can't be detected yet, ``None`` is returned. ``final``
specifies whether more data will be available in later calls or not. If
``final`` is true, ``detectencoding_unicode()`` will never return ``None``.
"""
prefix = '@charset "'
if input.startswith(prefix):
pos = input.find('"', len(prefix))
if pos >= 0:
return (input[len(prefix):pos], True)
elif final or not prefix.startswith(input):
# if this is the last call, and we haven't determined an encoding yet,
# (or the string definitely doesn't start with prefix) we default to UTF-8
return ("utf-8", False)
return (None, False) # don't know yet
def _fixencoding(input, encoding, final=False):
"""
Replace the name of the encoding in the charset rule at the beginning of
``input`` with ``encoding``. If ``input`` doesn't starts with a charset
rule, ``input`` will be returned unmodified.
If the encoding can't be found yet, ``None`` is returned. ``final``
specifies whether more data will be available in later calls or not.
If ``final`` is true, ``_fixencoding()`` will never return ``None``.
"""
prefix = '@charset "'
if len(input) > len(prefix):
if input.startswith(prefix):
pos = input.find('"', len(prefix))
if pos >= 0:
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
return prefix + encoding + input[pos:]
# we haven't seen the end of the encoding name yet => fall through
else:
return input # doesn't start with prefix, so nothing to fix
elif not prefix.startswith(input) or final:
# can't turn out to be a @charset rule later (or there is no "later")
return input
if final:
return input
return None # don't know yet
def decode(input, errors="strict", encoding=None, force=True):
try:
# py 3 only, memory?! object to bytes
input = input.tobytes()
except AttributeError as e:
pass
if encoding is None or not force:
(_encoding, explicit) = detectencoding_str(input, True)
if _encoding == "css":
raise ValueError("css not allowed as encoding name")
if (explicit and not force) or encoding is None: # Take the encoding from the input
encoding = _encoding
# NEEDS: change in parse.py (str to bytes!)
(input, consumed) = codecs.getdecoder(encoding)(input, errors)
return (_fixencoding(input, str(encoding), True), consumed)
def encode(input, errors="strict", encoding=None):
consumed = len(input)
if encoding is None:
encoding = detectencoding_unicode(input, True)[0]
if encoding.replace("_", "-").lower() == "utf-8-sig":
input = _fixencoding(input, "utf-8", True)
else:
input = _fixencoding(input, str(encoding), True)
if encoding == "css":
raise ValueError("css not allowed as encoding name")
encoder = codecs.getencoder(encoding)
return (encoder(input, errors)[0], consumed)
def _bytes2int(bytes):
# Helper: convert an 8 bit string into an ``int``.
i = 0
for byte in bytes:
i = (i<<8) + ord(byte)
return i
def _int2bytes(i):
# Helper: convert an ``int`` into an 8-bit string.
v = []
while i:
v.insert(0, chr(i&0xff))
i >>= 8
return "".join(v)
if hasattr(codecs, "IncrementalDecoder"):
class IncrementalDecoder(codecs.IncrementalDecoder):
def __init__(self, errors="strict", encoding=None, force=True):
self.decoder = None
self.encoding = encoding
self.force = force
codecs.IncrementalDecoder.__init__(self, errors)
# Store ``errors`` somewhere else,
# because we have to hide it in a property
self._errors = errors
self.buffer = b""
self.headerfixed = False
def iterdecode(self, input):
for part in input:
result = self.decode(part, False)
if result:
yield result
result = self.decode("", True)
if result:
yield result
def decode(self, input, final=False):
# We're doing basically the same as a ``BufferedIncrementalDecoder``,
# but since the buffer is only relevant until the encoding has been
# detected (in which case the buffer of the underlying codec might
# kick in), we're implementing buffering ourselves to avoid some
# overhead.
if self.decoder is None:
input = self.buffer + input
# Do we have to detect the encoding from the input?
if self.encoding is None or not self.force:
(encoding, explicit) = detectencoding_str(input, final)
if encoding is None: # no encoding determined yet
self.buffer = input # retry the complete input on the next call
return "" # no encoding determined yet, so no output
elif encoding == "css":
raise ValueError("css not allowed as encoding name")
if (explicit and not self.force) or self.encoding is None: # Take the encoding from the input
self.encoding = encoding
self.buffer = "" # drop buffer, as the decoder might keep its own
decoder = codecs.getincrementaldecoder(self.encoding)
self.decoder = decoder(self._errors)
if self.headerfixed:
return self.decoder.decode(input, final)
# If we haven't fixed the header yet,
# the content of ``self.buffer`` is a ``unicode`` object
output = self.buffer + self.decoder.decode(input, final)
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newoutput = _fixencoding(output, str(encoding), final)
if newoutput is None:
# retry fixing the @charset rule (but keep the decoded stuff)
self.buffer = output
return ""
self.headerfixed = True
return newoutput
def reset(self):
codecs.IncrementalDecoder.reset(self)
self.decoder = None
self.buffer = b""
self.headerfixed = False
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors`` must be done on the real decoder too
if self.decoder is not None:
self.decoder.errors = errors
self._errors = errors
errors = property(_geterrors, _seterrors)
def getstate(self):
if self.decoder is not None:
state = (self.encoding, self.buffer, self.headerfixed, True, self.decoder.getstate())
else:
state = (self.encoding, self.buffer, self.headerfixed, False, None)
return ("", _bytes2int(marshal.dumps(state)))
def setstate(self, state):
state = _int2bytes(marshal.loads(state[1])) # ignore buffered input
self.encoding = state[0]
self.buffer = state[1]
self.headerfixed = state[2]
if state[3] is not None:
self.decoder = codecs.getincrementaldecoder(self.encoding)(self._errors)
self.decoder.setstate(state[4])
else:
self.decoder = None
if hasattr(codecs, "IncrementalEncoder"):
class IncrementalEncoder(codecs.IncrementalEncoder):
def __init__(self, errors="strict", encoding=None):
self.encoder = None
self.encoding = encoding
codecs.IncrementalEncoder.__init__(self, errors)
# Store ``errors`` somewhere else,
# because we have to hide it in a property
self._errors = errors
self.buffer = ""
def iterencode(self, input):
for part in input:
result = self.encode(part, False)
if result:
yield result
result = self.encode("", True)
if result:
yield result
def encode(self, input, final=False):
if self.encoder is None:
input = self.buffer + input
if self.encoding is not None:
# Replace encoding in the @charset rule with the specified one
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newinput = _fixencoding(input, str(encoding), final)
if newinput is None: # @charset rule incomplete => Retry next time
self.buffer = input
return ""
input = newinput
else:
# Use encoding from the @charset declaration
self.encoding = detectencoding_unicode(input, final)[0]
if self.encoding is not None:
if self.encoding == "css":
raise ValueError("css not allowed as encoding name")
info = codecs.lookup(self.encoding)
encoding = self.encoding
if self.encoding.replace("_", "-").lower() == "utf-8-sig":
input = _fixencoding(input, "utf-8", True)
self.encoder = info.incrementalencoder(self._errors)
self.buffer = ""
else:
self.buffer = input
return ""
return self.encoder.encode(input, final)
def reset(self):
codecs.IncrementalEncoder.reset(self)
self.encoder = None
self.buffer = ""
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors ``must be done on the real encoder too
if self.encoder is not None:
self.encoder.errors = errors
self._errors = errors
errors = property(_geterrors, _seterrors)
def getstate(self):
if self.encoder is not None:
state = (self.encoding, self.buffer, True, self.encoder.getstate())
else:
state = (self.encoding, self.buffer, False, None)
return _bytes2int(marshal.dumps(state))
def setstate(self, state):
state = _int2bytes(marshal.loads(state))
self.encoding = state[0]
self.buffer = state[1]
if state[2] is not None:
self.encoder = codecs.getincrementalencoder(self.encoding)(self._errors)
self.encoder.setstate(state[4])
else:
self.encoder = None
class StreamWriter(codecs.StreamWriter):
def __init__(self, stream, errors="strict", encoding=None, header=False):
codecs.StreamWriter.__init__(self, stream, errors)
self.streamwriter = None
self.encoding = encoding
self._errors = errors
self.buffer = ""
def encode(self, input, errors='strict'):
li = len(input)
if self.streamwriter is None:
input = self.buffer + input
li = len(input)
if self.encoding is not None:
# Replace encoding in the @charset rule with the specified one
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newinput = _fixencoding(input, str(encoding), False)
if newinput is None: # @charset rule incomplete => Retry next time
self.buffer = input
return ("", 0)
input = newinput
else:
# Use encoding from the @charset declaration
self.encoding = detectencoding_unicode(input, False)[0]
if self.encoding is not None:
if self.encoding == "css":
raise ValueError("css not allowed as encoding name")
self.streamwriter = codecs.getwriter(self.encoding)(self.stream, self._errors)
encoding = self.encoding
if self.encoding.replace("_", "-").lower() == "utf-8-sig":
input = _fixencoding(input, "utf-8", True)
self.buffer = ""
else:
self.buffer = input
return ("", 0)
return (self.streamwriter.encode(input, errors)[0], li)
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors`` must be done on the streamwriter too
try:
if self.streamwriter is not None:
self.streamwriter.errors = errors
except AttributeError as e:
# TODO: py3 only exception?
pass
self._errors = errors
errors = property(_geterrors, _seterrors)
class StreamReader(codecs.StreamReader):
def __init__(self, stream, errors="strict", encoding=None, force=True):
codecs.StreamReader.__init__(self, stream, errors)
self.streamreader = None
self.encoding = encoding
self.force = force
self._errors = errors
def decode(self, input, errors='strict'):
if self.streamreader is None:
if self.encoding is None or not self.force:
(encoding, explicit) = detectencoding_str(input, False)
if encoding is None: # no encoding determined yet
return ("", 0) # no encoding determined yet, so no output
elif encoding == "css":
raise ValueError("css not allowed as encoding name")
if (explicit and not self.force) or self.encoding is None: # Take the encoding from the input
self.encoding = encoding
streamreader = codecs.getreader(self.encoding)
streamreader = streamreader(self.stream, self._errors)
(output, consumed) = streamreader.decode(input, errors)
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newoutput = _fixencoding(output, str(encoding), False)
if newoutput is not None:
self.streamreader = streamreader
return (newoutput, consumed)
return ("", 0) # we will create a new streamreader on the next call
return self.streamreader.decode(input, errors)
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors`` must be done on the streamreader too
try:
if self.streamreader is not None:
self.streamreader.errors = errors
except AttributeError as e:
# TODO: py3 only exception?
pass
self._errors = errors
errors = property(_geterrors, _seterrors)
if hasattr(codecs, "CodecInfo"):
# We're running on Python 2.5 or better
def search_function(name):
if name == "css":
return codecs.CodecInfo(
name="css",
encode=encode,
decode=decode,
incrementalencoder=IncrementalEncoder,
incrementaldecoder=IncrementalDecoder,
streamwriter=StreamWriter,
streamreader=StreamReader,
)
else:
# If we're running on Python 2.4, define the utf-8-sig codec here
def utf8sig_encode(input, errors='strict'):
return (codecs.BOM_UTF8 + codecs.utf_8_encode(input, errors)[0], len(input))
def utf8sig_decode(input, errors='strict'):
prefix = 0
if input[:3] == codecs.BOM_UTF8:
input = input[3:]
prefix = 3
(output, consumed) = codecs.utf_8_decode(input, errors, True)
return (output, consumed+prefix)
class UTF8SigStreamWriter(codecs.StreamWriter):
def reset(self):
codecs.StreamWriter.reset(self)
try:
del self.encode
except AttributeError:
pass
def encode(self, input, errors='strict'):
self.encode = codecs.utf_8_encode
return utf8sig_encode(input, errors)
class UTF8SigStreamReader(codecs.StreamReader):
def reset(self):
codecs.StreamReader.reset(self)
try:
del self.decode
except AttributeError:
pass
def decode(self, input, errors='strict'):
if len(input) < 3 and codecs.BOM_UTF8.startswith(input):
# not enough data to decide if this is a BOM
# => try again on the next call
return ("", 0)
self.decode = codecs.utf_8_decode
return utf8sig_decode(input, errors)
def search_function(name):
import encodings
name = encodings.normalize_encoding(name)
if name == "css":
return (encode, decode, StreamReader, StreamWriter)
elif name == "utf_8_sig":
return (utf8sig_encode, utf8sig_decode, UTF8SigStreamReader, UTF8SigStreamWriter)
codecs.register(search_function)
# Error handler for CSS escaping
def cssescape(exc):
if not isinstance(exc, UnicodeEncodeError):
raise TypeError("don't know how to handle %r" % exc)
return ("".join("\\%06x" % ord(c) for c in exc.object[exc.start:exc.end]), exc.end)
codecs.register_error("cssescape", cssescape)

44
libs/cssutils/_fetch.py

@ -0,0 +1,44 @@
"""Default URL reading functions"""
__all__ = ['_defaultFetcher']
__docformat__ = 'restructuredtext'
__version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $'
import cssutils
from cssutils import VERSION
import encutils
import errorhandler
import urllib2
log = errorhandler.ErrorHandler()
def _defaultFetcher(url):
"""Retrieve data from ``url``. cssutils default implementation of fetch
URL function.
Returns ``(encoding, string)`` or ``None``
"""
try:
request = urllib2.Request(url)
request.add_header('User-agent',
'cssutils %s (http://www.cthedot.de/cssutils/)' % VERSION)
res = urllib2.urlopen(request)
except OSError, e:
# e.g if file URL and not found
log.warn(e, error=OSError)
except (OSError, ValueError), e:
# invalid url, e.g. "1"
log.warn(u'ValueError, %s' % e.args[0], error=ValueError)
except urllib2.HTTPError, e:
# http error, e.g. 404, e can be raised
log.warn(u'HTTPError opening url=%s: %s %s' %
(url, e.code, e.msg), error=e)
except urllib2.URLError, e:
# URLError like mailto: or other IO errors, e can be raised
log.warn(u'URLError, %s' % e.reason, error=e)
else:
if res:
mimeType, encoding = encutils.getHTTPInfo(res)
if mimeType != u'text/css':
log.error(u'Expected "text/css" mime type for url=%r but found: %r' %
(url, mimeType), error=ValueError)
return encoding, res.read()

68
libs/cssutils/_fetchgae.py

@ -0,0 +1,68 @@
"""GAE specific URL reading functions"""
__all__ = ['_defaultFetcher']
__docformat__ = 'restructuredtext'
__version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $'
# raises ImportError of not on GAE
from google.appengine.api import urlfetch
import cgi
import errorhandler
import util
log = errorhandler.ErrorHandler()
def _defaultFetcher(url):
"""
uses GoogleAppEngine (GAE)
fetch(url, payload=None, method=GET, headers={}, allow_truncated=False)
Response
content
The body content of the response.
content_was_truncated
True if the allow_truncated parameter to fetch() was True and
the response exceeded the maximum response size. In this case,
the content attribute contains the truncated response.
status_code
The HTTP status code.
headers
The HTTP response headers, as a mapping of names to values.
Exceptions
exception InvalidURLError()
The URL of the request was not a valid URL, or it used an
unsupported method. Only http and https URLs are supported.
exception DownloadError()
There was an error retrieving the data.
This exception is not raised if the server returns an HTTP
error code: In that case, the response data comes back intact,
including the error code.
exception ResponseTooLargeError()
The response data exceeded the maximum allowed size, and the
allow_truncated parameter passed to fetch() was False.
"""
#from google.appengine.api import urlfetch
try:
r = urlfetch.fetch(url, method=urlfetch.GET)
except urlfetch.Error, e:
log.warn(u'Error opening url=%r: %s' % (url, e),
error=IOError)
else:
if r.status_code == 200:
# find mimetype and encoding
mimetype = 'application/octet-stream'
try:
mimetype, params = cgi.parse_header(r.headers['content-type'])
encoding = params['charset']
except KeyError:
encoding = None
if mimetype != u'text/css':
log.error(u'Expected "text/css" mime type for url %r but found: %r' %
(url, mimetype), error=ValueError)
return encoding, r.content
else:
# TODO: 301 etc
log.warn(u'Error opening url=%r: HTTP status %s' %
(url, r.status_code), error=IOError)

16
libs/cssutils/codec.py

@ -0,0 +1,16 @@
#!/usr/bin/env python
"""Python codec for CSS."""
__docformat__ = 'restructuredtext'
__author__ = 'Walter Doerwald'
__version__ = '$Id: util.py 1114 2008-03-05 13:22:59Z cthedot $'
import sys
if sys.version_info < (3,):
from _codec2 import *
# for tests
from _codec2 import _fixencoding
else:
from _codec3 import *
# for tests
from _codec3 import _fixencoding

80
libs/cssutils/css/__init__.py

@ -0,0 +1,80 @@
"""Implements Document Object Model Level 2 CSS
http://www.w3.org/TR/2000/PR-DOM-Level-2-Style-20000927/css.html
currently implemented
- CSSStyleSheet
- CSSRuleList
- CSSRule
- CSSComment (cssutils addon)
- CSSCharsetRule
- CSSFontFaceRule
- CSSImportRule
- CSSMediaRule
- CSSNamespaceRule (WD)
- CSSPageRule
- CSSStyleRule
- CSSUnkownRule
- Selector and SelectorList
- CSSStyleDeclaration
- CSS2Properties
- CSSValue
- CSSPrimitiveValue
- CSSValueList
- CSSVariablesRule
- CSSVariablesDeclaration
todo
- RGBColor, Rect, Counter
"""
__all__ = [
'CSSStyleSheet',
'CSSRuleList',
'CSSRule',
'CSSComment',
'CSSCharsetRule',
'CSSFontFaceRule'
'CSSImportRule',
'CSSMediaRule',
'CSSNamespaceRule',
'CSSPageRule',
'MarginRule',
'CSSStyleRule',
'CSSUnknownRule',
'CSSVariablesRule',
'CSSVariablesDeclaration',
'Selector', 'SelectorList',
'CSSStyleDeclaration', 'Property',
#'CSSValue', 'CSSPrimitiveValue', 'CSSValueList'
'PropertyValue',
'Value',
'ColorValue',
'DimensionValue',
'URIValue',
'CSSFunction',
'CSSVariable',
'MSValue'
]
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssstylesheet import *
from cssrulelist import *
from cssrule import *
from csscomment import *
from csscharsetrule import *
from cssfontfacerule import *
from cssimportrule import *
from cssmediarule import *
from cssnamespacerule import *
from csspagerule import *
from marginrule 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 *
from value import *

184
libs/cssutils/css/colors.py

@ -0,0 +1,184 @@
# -*- coding: utf-8 -*-
"""
Built from something like this:
print [
(
row[2].text_content().strip(),
eval(row[4].text_content().strip())
)
for row in lxml.html.parse('http://www.w3.org/TR/css3-color/')
.xpath("//*[@class='colortable']//tr[position()>1]")
]
by Simon Sapin
"""
COLORS = {
'transparent': (0, 0, 0, 0.0),
'black': (0, 0, 0, 1.0),
'silver': (192, 192, 192, 1.0),
'gray': (128, 128, 128, 1.0),
'white': (255, 255, 255, 1.0),
'maroon': (128, 0, 0, 1.0),
'red': (255, 0, 0, 1.0),
'purple': (128, 0, 128, 1.0),
'fuchsia': (255, 0, 255, 1.0),
'green': (0, 128, 0, 1.0),
'lime': (0, 255, 0, 1.0),
'olive': (128, 128, 0, 1.0),
'yellow': (255, 255, 0, 1.0),
'navy': (0, 0, 128, 1.0),
'blue': (0, 0, 255, 1.0),
'teal': (0, 128, 128, 1.0),
'aqua': (0, 255, 255, 1.0),
'aliceblue': (240, 248, 255, 1.0),
'antiquewhite': (250, 235, 215, 1.0),
'aqua': (0, 255, 255, 1.0),
'aquamarine': (127, 255, 212, 1.0),
'azure': (240, 255, 255, 1.0),
'beige': (245, 245, 220, 1.0),
'bisque': (255, 228, 196, 1.0),
'black': (0, 0, 0, 1.0),
'blanchedalmond': (255, 235, 205, 1.0),
'blue': (0, 0, 255, 1.0),
'blueviolet': (138, 43, 226, 1.0),
'brown': (165, 42, 42, 1.0),
'burlywood': (222, 184, 135, 1.0),
'cadetblue': (95, 158, 160, 1.0),
'chartreuse': (127, 255, 0, 1.0),
'chocolate': (210, 105, 30, 1.0),
'coral': (255, 127, 80, 1.0),
'cornflowerblue': (100, 149, 237, 1.0),
'cornsilk': (255, 248, 220, 1.0),
'crimson': (220, 20, 60, 1.0),
'cyan': (0, 255, 255, 1.0),
'darkblue': (0, 0, 139, 1.0),
'darkcyan': (0, 139, 139, 1.0),
'darkgoldenrod': (184, 134, 11, 1.0),
'darkgray': (169, 169, 169, 1.0),
'darkgreen': (0, 100, 0, 1.0),
'darkgrey': (169, 169, 169, 1.0),
'darkkhaki': (189, 183, 107, 1.0),
'darkmagenta': (139, 0, 139, 1.0),
'darkolivegreen': (85, 107, 47, 1.0),
'darkorange': (255, 140, 0, 1.0),
'darkorchid': (153, 50, 204, 1.0),
'darkred': (139, 0, 0, 1.0),
'darksalmon': (233, 150, 122, 1.0),
'darkseagreen': (143, 188, 143, 1.0),
'darkslateblue': (72, 61, 139, 1.0),
'darkslategray': (47, 79, 79, 1.0),
'darkslategrey': (47, 79, 79, 1.0),
'darkturquoise': (0, 206, 209, 1.0),
'darkviolet': (148, 0, 211, 1.0),
'deeppink': (255, 20, 147, 1.0),
'deepskyblue': (0, 191, 255, 1.0),
'dimgray': (105, 105, 105, 1.0),
'dimgrey': (105, 105, 105, 1.0),
'dodgerblue': (30, 144, 255, 1.0),
'firebrick': (178, 34, 34, 1.0),
'floralwhite': (255, 250, 240, 1.0),
'forestgreen': (34, 139, 34, 1.0),
'fuchsia': (255, 0, 255, 1.0),
'gainsboro': (220, 220, 220, 1.0),
'ghostwhite': (248, 248, 255, 1.0),
'gold': (255, 215, 0, 1.0),
'goldenrod': (218, 165, 32, 1.0),
'gray': (128, 128, 128, 1.0),
'green': (0, 128, 0, 1.0),
'greenyellow': (173, 255, 47, 1.0),
'grey': (128, 128, 128, 1.0),
'honeydew': (240, 255, 240, 1.0),
'hotpink': (255, 105, 180, 1.0),
'indianred': (205, 92, 92, 1.0),
'indigo': (75, 0, 130, 1.0),
'ivory': (255, 255, 240, 1.0),
'khaki': (240, 230, 140, 1.0),
'lavender': (230, 230, 250, 1.0),
'lavenderblush': (255, 240, 245, 1.0),
'lawngreen': (124, 252, 0, 1.0),
'lemonchiffon': (255, 250, 205, 1.0),
'lightblue': (173, 216, 230, 1.0),
'lightcoral': (240, 128, 128, 1.0),
'lightcyan': (224, 255, 255, 1.0),
'lightgoldenrodyellow': (250, 250, 210, 1.0),
'lightgray': (211, 211, 211, 1.0),
'lightgreen': (144, 238, 144, 1.0),
'lightgrey': (211, 211, 211, 1.0),
'lightpink': (255, 182, 193, 1.0),
'lightsalmon': (255, 160, 122, 1.0),
'lightseagreen': (32, 178, 170, 1.0),
'lightskyblue': (135, 206, 250, 1.0),
'lightslategray': (119, 136, 153, 1.0),
'lightslategrey': (119, 136, 153, 1.0),
'lightsteelblue': (176, 196, 222, 1.0),
'lightyellow': (255, 255, 224, 1.0),
'lime': (0, 255, 0, 1.0),
'limegreen': (50, 205, 50, 1.0),
'linen': (250, 240, 230, 1.0),
'magenta': (255, 0, 255, 1.0),
'maroon': (128, 0, 0, 1.0),
'mediumaquamarine': (102, 205, 170, 1.0),
'mediumblue': (0, 0, 205, 1.0),
'mediumorchid': (186, 85, 211, 1.0),
'mediumpurple': (147, 112, 219, 1.0),
'mediumseagreen': (60, 179, 113, 1.0),
'mediumslateblue': (123, 104, 238, 1.0),
'mediumspringgreen': (0, 250, 154, 1.0),
'mediumturquoise': (72, 209, 204, 1.0),
'mediumvioletred': (199, 21, 133, 1.0),
'midnightblue': (25, 25, 112, 1.0),
'mintcream': (245, 255, 250, 1.0),
'mistyrose': (255, 228, 225, 1.0),
'moccasin': (255, 228, 181, 1.0),
'navajowhite': (255, 222, 173, 1.0),
'navy': (0, 0, 128, 1.0),
'oldlace': (253, 245, 230, 1.0),
'olive': (128, 128, 0, 1.0),
'olivedrab': (107, 142, 35, 1.0),
'orange': (255, 165, 0, 1.0),
'orangered': (255, 69, 0, 1.0),
'orchid': (218, 112, 214, 1.0),
'palegoldenrod': (238, 232, 170, 1.0),
'palegreen': (152, 251, 152, 1.0),
'paleturquoise': (175, 238, 238, 1.0),
'palevioletred': (219, 112, 147, 1.0),
'papayawhip': (255, 239, 213, 1.0),
'peachpuff': (255, 218, 185, 1.0),
'peru': (205, 133, 63, 1.0),
'pink': (255, 192, 203, 1.0),
'plum': (221, 160, 221, 1.0),
'powderblue': (176, 224, 230, 1.0),
'purple': (128, 0, 128, 1.0),
'red': (255, 0, 0, 1.0),
'rosybrown': (188, 143, 143, 1.0),
'royalblue': (65, 105, 225, 1.0),
'saddlebrown': (139, 69, 19, 1.0),
'salmon': (250, 128, 114, 1.0),
'sandybrown': (244, 164, 96, 1.0),
'seagreen': (46, 139, 87, 1.0),
'seashell': (255, 245, 238, 1.0),
'sienna': (160, 82, 45, 1.0),
'silver': (192, 192, 192, 1.0),
'skyblue': (135, 206, 235, 1.0),
'slateblue': (106, 90, 205, 1.0),
'slategray': (112, 128, 144, 1.0),
'slategrey': (112, 128, 144, 1.0),
'snow': (255, 250, 250, 1.0),
'springgreen': (0, 255, 127, 1.0),
'steelblue': (70, 130, 180, 1.0),
'tan': (210, 180, 140, 1.0),
'teal': (0, 128, 128, 1.0),
'thistle': (216, 191, 216, 1.0),
'tomato': (255, 99, 71, 1.0),
'turquoise': (64, 224, 208, 1.0),
'violet': (238, 130, 238, 1.0),
'wheat': (245, 222, 179, 1.0),
'white': (255, 255, 255, 1.0),
'whitesmoke': (245, 245, 245, 1.0),
'yellow': (255, 255, 0, 1.0),
'yellowgreen': (154, 205, 50, 1.0),
}

159
libs/cssutils/css/csscharsetrule.py

@ -0,0 +1,159 @@
"""CSSCharsetRule implements DOM Level 2 CSS CSSCharsetRule."""
__all__ = ['CSSCharsetRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import codecs
import cssrule
import cssutils
import xml.dom
class CSSCharsetRule(cssrule.CSSRule):
"""
The CSSCharsetRule interface represents an @charset rule in a CSS style
sheet. The value of the encoding attribute does not affect the encoding
of text data in the DOM objects; this encoding is always UTF-16
(also in Python?). After a stylesheet is loaded, the value of the
encoding attribute is the value found in the @charset rule. If there
was no @charset in the original document, then no CSSCharsetRule is
created. The value of the encoding attribute may also be used as a hint
for the encoding used on serialization of the style sheet.
The value of the @charset rule (and therefore of the CSSCharsetRule)
may not correspond to the encoding the document actually came in;
character encoding information e.g. in an HTTP header, has priority
(see CSS document representation) but this is not reflected in the
CSSCharsetRule.
This rule is not really needed anymore as setting
:attr:`CSSStyleSheet.encoding` is much easier.
Format::
charsetrule:
CHARSET_SYM S* STRING S* ';'
BUT: Only valid format is (single space, double quotes!)::
@charset "ENCODING";
"""
def __init__(self, encoding=None, parentRule=None,
parentStyleSheet=None, readonly=False):
"""
:param encoding:
a valid character encoding
:param readonly:
defaults to False, not used yet
"""
super(CSSCharsetRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = '@charset'
if encoding:
self.encoding = encoding
else:
self._encoding = None
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(encoding=%r)" % (
self.__class__.__name__,
self.encoding)
def __str__(self):
return u"<cssutils.css.%s object encoding=%r at 0x%x>" % (
self.__class__.__name__,
self.encoding,
id(self))
def _getCssText(self):
"""The parsable textual representation."""
return cssutils.ser.do_CSSCharsetRule(self)
def _setCssText(self, cssText):
"""
:param cssText:
A parsable DOMString.
: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.
"""
super(CSSCharsetRule, self)._setCssText(cssText)
wellformed = True
tokenizer = self._tokenize2(cssText)
if self._type(self._nexttoken(tokenizer)) != self._prods.CHARSET_SYM:
wellformed = False
self._log.error(u'CSSCharsetRule must start with "@charset "',
error=xml.dom.InvalidModificationErr)
encodingtoken = self._nexttoken(tokenizer)
encodingtype = self._type(encodingtoken)
encoding = self._stringtokenvalue(encodingtoken)
if self._prods.STRING != encodingtype or not encoding:
wellformed = False
self._log.error(u'CSSCharsetRule: no encoding found; %r.' %
self._valuestr(cssText))
semicolon = self._tokenvalue(self._nexttoken(tokenizer))
EOFtype = self._type(self._nexttoken(tokenizer))
if u';' != semicolon or EOFtype not in ('EOF', None):
wellformed = False
self._log.error(u'CSSCharsetRule: Syntax Error: %r.' %
self._valuestr(cssText))
if wellformed:
self.encoding = encoding
cssText = property(fget=_getCssText, fset=_setCssText,
doc=u"(DOM) The parsable textual representation.")
def _setEncoding(self, encoding):
"""
:param encoding:
a valid encoding to be used. Currently only valid Python encodings
are allowed.
:exceptions:
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this encoding rule is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified encoding value has a syntax error and
is unparsable.
"""
self._checkReadonly()
tokenizer = self._tokenize2(encoding)
encodingtoken = self._nexttoken(tokenizer)
unexpected = self._nexttoken(tokenizer)
if not encodingtoken or unexpected or\
self._prods.IDENT != self._type(encodingtoken):
self._log.error(u'CSSCharsetRule: Syntax Error in encoding value '
u'%r.' % encoding)
else:
try:
codecs.lookup(encoding)
except LookupError:
self._log.error(u'CSSCharsetRule: Unknown (Python) encoding %r.'
% encoding)
else:
self._encoding = encoding.lower()
encoding = property(lambda self: self._encoding, _setEncoding,
doc=u"(DOM)The encoding information used in this @charset rule.")
type = property(lambda self: self.CHARSET_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
wellformed = property(lambda self: bool(self.encoding))

87
libs/cssutils/css/csscomment.py

@ -0,0 +1,87 @@
"""CSSComment is not defined in DOM Level 2 at all but a cssutils defined
class only.
Implements CSSRule which is also extended for a CSSComment rule type.
"""
__all__ = ['CSSComment']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssrule
import cssutils
import xml.dom
class CSSComment(cssrule.CSSRule):
"""
Represents a CSS comment (cssutils only).
Format::
/*...*/
"""
def __init__(self, cssText=None, parentRule=None,
parentStyleSheet=None, readonly=False):
super(CSSComment, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._cssText = None
if cssText:
self._setCssText(cssText)
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(cssText=%r)" % (
self.__class__.__name__,
self.cssText)
def __str__(self):
return u"<cssutils.css.%s object cssText=%r at 0x%x>" % (
self.__class__.__name__,
self.cssText,
id(self))
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSComment(self)
def _setCssText(self, cssText):
"""
:param cssText:
textual text to set or tokenlist which is not tokenized
anymore. May also be a single token for this rule
: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.NoModificationAllowedErr`:
Raised if the rule is readonly.
"""
super(CSSComment, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
commenttoken = self._nexttoken(tokenizer)
unexpected = self._nexttoken(tokenizer)
if not commenttoken or\
self._type(commenttoken) != self._prods.COMMENT or\
unexpected:
self._log.error(u'CSSComment: Not a CSSComment: %r' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
self._cssText = self._tokenvalue(commenttoken)
cssText = property(_getCssText, _setCssText,
doc=u"The parsable textual representation of this rule.")
type = property(lambda self: self.COMMENT,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
# constant but needed:
wellformed = property(lambda self: True)

184
libs/cssutils/css/cssfontfacerule.py

@ -0,0 +1,184 @@
"""CSSFontFaceRule implements DOM Level 2 CSS CSSFontFaceRule.
From cssutils 0.9.6 additions from CSS Fonts Module Level 3 are
added http://www.w3.org/TR/css3-fonts/.
"""
__all__ = ['CSSFontFaceRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssstyledeclaration import CSSStyleDeclaration
import cssrule
import cssutils
import xml.dom
class CSSFontFaceRule(cssrule.CSSRule):
"""
The CSSFontFaceRule interface represents a @font-face rule in a CSS
style sheet. The @font-face rule is used to hold a set of font
descriptions.
Format::
font_face
: FONT_FACE_SYM S*
'{' S* declaration [ ';' S* declaration ]* '}' S*
;
cssutils uses a :class:`~cssutils.css.CSSStyleDeclaration` to
represent the font descriptions. For validation a specific profile
is used though were some properties have other valid values than
when used in e.g. a :class:`~cssutils.css.CSSStyleRule`.
"""
def __init__(self, style=None, parentRule=None,
parentStyleSheet=None, readonly=False):
"""
If readonly allows setting of properties in constructor only.
:param style:
CSSStyleDeclaration used to hold any font descriptions
for this CSSFontFaceRule
"""
super(CSSFontFaceRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@font-face'
if style:
self.style = style
else:
self.style = CSSStyleDeclaration()
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(style=%r)" % (
self.__class__.__name__,
self.style.cssText)
def __str__(self):
return u"<cssutils.css.%s object style=%r valid=%r at 0x%x>" % (
self.__class__.__name__,
self.style.cssText,
self.valid,
id(self))
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSFontFaceRule(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.
"""
super(CSSFontFaceRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.FONT_FACE_SYM:
self._log.error(u'CSSFontFaceRule: No CSSFontFaceRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
newStyle = CSSStyleDeclaration(parentRule=self)
ok = True
beforetokens, brace = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
if self._tokenvalue(brace) != u'{':
ok = False
self._log.error(u'CSSFontFaceRule: No start { of style '
u'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']
styletokens, braceorEOFtoken = self._tokensupto2(tokenizer,
blockendonly=True,
separateEnd=True)
val, type_ = self._tokenvalue(braceorEOFtoken),\
self._type(braceorEOFtoken)
if val != u'}' and type_ != 'EOF':
ok = False
self._log.error(u'CSSFontFaceRule: No "}" after style '
u'declaration found: %r'
% self._valuestr(cssText))
nonetoken = self._nexttoken(tokenizer)
if nonetoken:
ok = False
self._log.error(u'CSSFontFaceRule: Trailing content found.',
token=nonetoken)
if 'EOF' == type_:
# add again as style needs it
styletokens.append(braceorEOFtoken)
# SET, may raise:
newStyle.cssText = styletokens
if ok:
# contains probably comments only (upto ``{``)
self._setSeq(newseq)
self.style = newStyle
cssText = property(_getCssText, _setCssText,
doc=u"(DOM) The parsable textual representation of this "
u"rule.")
def _setStyle(self, style):
"""
:param style:
a CSSStyleDeclaration or string
"""
self._checkReadonly()
if isinstance(style, basestring):
self._style = CSSStyleDeclaration(cssText=style, parentRule=self)
else:
style._parentRule = self
self._style = style
style = property(lambda self: self._style, _setStyle,
doc=u"(DOM) The declaration-block of this rule set, "
u"a :class:`~cssutils.css.CSSStyleDeclaration`.")
type = property(lambda self: self.FONT_FACE_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
def _getValid(self):
needed = ['font-family', 'src']
for p in self.style.getProperties(all=True):
if not p.valid:
return False
try:
needed.remove(p.name)
except ValueError:
pass
return not bool(needed)
valid = property(_getValid,
doc=u"CSSFontFace is valid if properties `font-family` "
u"and `src` are set and all properties are valid.")
# constant but needed:
wellformed = property(lambda self: True)

396
libs/cssutils/css/cssimportrule.py

@ -0,0 +1,396 @@
"""CSSImportRule implements DOM Level 2 CSS CSSImportRule plus the
``name`` property from http://www.w3.org/TR/css3-cascade/#cascading."""
__all__ = ['CSSImportRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssrule
import cssutils
import os
import urlparse
import xml.dom
class CSSImportRule(cssrule.CSSRule):
"""
Represents an @import rule within a CSS style sheet. The @import rule
is used to import style rules from other style sheets.
Format::
import
: IMPORT_SYM S*
[STRING|URI] S* [ medium [ COMMA S* medium]* ]? S* STRING? S* ';' S*
;
"""
def __init__(self, href=None, mediaText=None, name=None,
parentRule=None, parentStyleSheet=None, readonly=False):
"""
If readonly allows setting of properties in constructor only
:param href:
location of the style sheet to be imported.
:param mediaText:
A list of media types for which this style sheet may be used
as a string
:param name:
Additional name of imported style sheet
"""
super(CSSImportRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@import'
self._styleSheet = None
# string or uri used for reserialization
self.hreftype = None
# prepare seq
seq = self._tempSeq()
seq.append(None, 'href')
#seq.append(None, 'media')
seq.append(None, 'name')
self._setSeq(seq)
# 1. media
if mediaText:
self.media = mediaText
else:
# must be all for @import
self.media = cssutils.stylesheets.MediaList(mediaText=u'all')
# 2. name
self.name = name
# 3. href and styleSheet
self.href = href
self._readonly = readonly
def __repr__(self):
if self._usemedia:
mediaText = self.media.mediaText
else:
mediaText = None
return u"cssutils.css.%s(href=%r, mediaText=%r, name=%r)" % (
self.__class__.__name__,
self.href,
self.media.mediaText,
self.name)
def __str__(self):
if self._usemedia:
mediaText = self.media.mediaText
else:
mediaText = None
return u"<cssutils.css.%s object href=%r mediaText=%r name=%r at 0x%x>"\
% (self.__class__.__name__,
self.href,
mediaText,
self.name,
id(self))
_usemedia = property(lambda self: self.media.mediaText not in (u'', u'all'),
doc="if self.media is used (or simply empty)")
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSImportRule(self)
def _setCssText(self, cssText):
"""
:exceptions:
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
"""
super(CSSImportRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.IMPORT_SYM:
self._log.error(u'CSSImportRule: No CSSImportRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# for closures: must be a mutable
new = {'keyword': self._tokenvalue(attoken),
'href': None,
'hreftype': None,
'media': None,
'name': None,
'wellformed': True
}
def __doname(seq, token):
# called by _string or _ident
new['name'] = self._stringtokenvalue(token)
seq.append(new['name'], 'name')
return ';'
def _string(expected, seq, token, tokenizer=None):
if 'href' == expected:
# href
new['href'] = self._stringtokenvalue(token)
new['hreftype'] = 'string'
seq.append(new['href'], 'href')
return 'media name ;'
elif 'name' in expected:
# name
return __doname(seq, token)
else:
new['wellformed'] = False
self._log.error(
u'CSSImportRule: Unexpected string.', token)
return expected
def _uri(expected, seq, token, tokenizer=None):
# href
if 'href' == expected:
uri = self._uritokenvalue(token)
new['hreftype'] = 'uri'
new['href'] = uri
seq.append(new['href'], 'href')
return 'media name ;'
else:
new['wellformed'] = False
self._log.error(
u'CSSImportRule: Unexpected URI.', token)
return expected
def _ident(expected, seq, token, tokenizer=None):
# medialist ending with ; which is checked upon too
if expected.startswith('media'):
mediatokens = self._tokensupto2(
tokenizer, importmediaqueryendonly=True)
mediatokens.insert(0, token) # push found token
last = mediatokens.pop() # retrieve ;
lastval, lasttyp = self._tokenvalue(last), self._type(last)
if lastval != u';' and lasttyp not in ('EOF',
self._prods.STRING):
new['wellformed'] = False
self._log.error(u'CSSImportRule: No ";" found: %s' %
self._valuestr(cssText), token=token)
newMedia = cssutils.stylesheets.MediaList(parentRule=self)
newMedia.mediaText = mediatokens
if newMedia.wellformed:
new['media'] = newMedia
seq.append(newMedia, 'media')
else:
new['wellformed'] = False
self._log.error(u'CSSImportRule: Invalid MediaList: %s' %
self._valuestr(cssText), token=token)
if lasttyp == self._prods.STRING:
# name
return __doname(seq, last)
else:
return 'EOF' # ';' is token "last"
else:
new['wellformed'] = False
self._log.error(u'CSSImportRule: Unexpected ident.', token)
return expected
def _char(expected, seq, token, tokenizer=None):
# final ;
val = self._tokenvalue(token)
if expected.endswith(';') and u';' == val:
return 'EOF'
else:
new['wellformed'] = False
self._log.error(
u'CSSImportRule: Unexpected char.', token)
return expected
# import : IMPORT_SYM S* [STRING|URI]
# S* [ medium [ ',' S* medium]* ]? ';' S*
# STRING? # see http://www.w3.org/TR/css3-cascade/#cascading
# ;
newseq = self._tempSeq()
wellformed, expected = self._parse(expected='href',
seq=newseq, tokenizer=tokenizer,
productions={'STRING': _string,
'URI': _uri,
'IDENT': _ident,
'CHAR': _char},
new=new)
# wellformed set by parse
ok = wellformed and new['wellformed']
# post conditions
if not new['href']:
ok = False
self._log.error(u'CSSImportRule: No href found: %s' %
self._valuestr(cssText))
if expected != 'EOF':
ok = False
self._log.error(u'CSSImportRule: No ";" found: %s' %
self._valuestr(cssText))
# set all
if ok:
self._setSeq(newseq)
self.atkeyword = new['keyword']
self.hreftype = new['hreftype']
self.name = new['name']
if new['media']:
self.media = new['media']
else:
# must be all for @import
self.media = cssutils.stylesheets.MediaList(mediaText=u'all')
# needs new self.media
self.href = new['href']
cssText = property(fget=_getCssText, fset=_setCssText,
doc="(DOM) The parsable textual representation of this rule.")
def _setHref(self, href):
# set new href
self._href = href
# update seq
for i, item in enumerate(self.seq):
val, type_ = item.value, item.type
if 'href' == type_:
self._seq[i] = (href, type_, item.line, item.col)
break
importedSheet = cssutils.css.CSSStyleSheet(media=self.media,
ownerRule=self,
title=self.name)
self.hrefFound = False
# set styleSheet
if href and self.parentStyleSheet:
# loading errors are all catched!
# relative href
parentHref = self.parentStyleSheet.href
if parentHref is None:
# use cwd instead
parentHref = cssutils.helper.path2url(os.getcwd()) + '/'
fullhref = urlparse.urljoin(parentHref, self.href)
# all possible exceptions are ignored
try:
usedEncoding, enctype, cssText = \
self.parentStyleSheet._resolveImport(fullhref)
if cssText is None:
# catched in next except below!
raise IOError('Cannot read Stylesheet.')
# contentEncoding with parentStyleSheet.overrideEncoding,
# HTTP or parent
encodingOverride, encoding = None, None
if enctype == 0:
encodingOverride = usedEncoding
elif 0 < enctype < 5:
encoding = usedEncoding
# inherit fetcher for @imports in styleSheet
importedSheet._href = fullhref
importedSheet._setFetcher(self.parentStyleSheet._fetcher)
importedSheet._setCssTextWithEncodingOverride(
cssText,
encodingOverride=encodingOverride,
encoding=encoding)
except (OSError, IOError, ValueError), e:
self._log.warn(u'CSSImportRule: While processing imported '
u'style sheet href=%s: %r'
% (self.href, e), neverraise=True)
else:
# used by resolveImports if to keep unprocessed href
self.hrefFound = True
self._styleSheet = importedSheet
_href = None # needs to be set
href = property(lambda self: self._href, _setHref,
doc=u"Location of the style sheet to be imported.")
def _setMedia(self, media):
"""
:param media:
a :class:`~cssutils.stylesheets.MediaList` or string
"""
self._checkReadonly()
if isinstance(media, basestring):
self._media = cssutils.stylesheets.MediaList(mediaText=media,
parentRule=self)
else:
media._parentRule = self
self._media = media
# update seq
ihref = 0
for i, item in enumerate(self.seq):
if item.type == 'href':
ihref = i
elif item.type == 'media':
self.seq[i] = (self._media, 'media', None, None)
break
else:
# if no media until now add after href
self.seq.insert(ihref+1,
self._media, 'media', None, None)
media = property(lambda self: self._media, _setMedia,
doc=u"(DOM) A list of media types for this rule "
u"of type :class:`~cssutils.stylesheets.MediaList`.")
def _setName(self, name=u''):
"""Raises xml.dom.SyntaxErr if name is not a string."""
if name is None or isinstance(name, basestring):
# "" or '' handled as None
if not name:
name = None
# save name
self._name = name
# update seq
for i, item in enumerate(self.seq):
val, typ = item.value, item.type
if 'name' == typ:
self._seq[i] = (name, typ, item.line, item.col)
break
# set title of imported sheet
if self.styleSheet:
self.styleSheet.title = name
else:
self._log.error(u'CSSImportRule: Not a valid name: %s' % name)
name = property(lambda self: self._name, _setName,
doc=u"An optional name for the imported sheet.")
styleSheet = property(lambda self: self._styleSheet,
doc=u"(readonly) The style sheet referred to by this "
u"rule.")
type = property(lambda self: self.IMPORT_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
def _getWellformed(self):
"Depending on if media is used at all."
if self._usemedia:
return bool(self.href and self.media.wellformed)
else:
return bool(self.href)
wellformed = property(_getWellformed)

302
libs/cssutils/css/cssmediarule.py

@ -0,0 +1,302 @@
"""CSSMediaRule implements DOM Level 2 CSS CSSMediaRule."""
__all__ = ['CSSMediaRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssrule
import cssutils
import xml.dom
class CSSMediaRule(cssrule.CSSRuleRules):
"""
Objects implementing the CSSMediaRule interface can be identified by the
MEDIA_RULE constant. On these objects the type attribute must return the
value of that constant.
Format::
: MEDIA_SYM S* medium [ COMMA S* medium ]*
STRING? # the name
LBRACE S* ruleset* '}' S*;
``cssRules``
All Rules in this media rule, a :class:`~cssutils.css.CSSRuleList`.
"""
def __init__(self, mediaText='all', name=None,
parentRule=None, parentStyleSheet=None, readonly=False):
"""constructor"""
super(CSSMediaRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@media'
# 1. media
if mediaText:
self.media = mediaText
else:
self.media = cssutils.stylesheets.MediaList()
self.name = name
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(mediaText=%r)" % (
self.__class__.__name__,
self.media.mediaText)
def __str__(self):
return u"<cssutils.css.%s object mediaText=%r at 0x%x>" % (
self.__class__.__name__,
self.media.mediaText,
id(self))
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSMediaRule(self)
def _setCssText(self, cssText):
"""
:param cssText:
a parseable string or a tuple of (cssText, dict-of-namespaces)
:Exceptions:
- :exc:`~xml.dom.NamespaceErr`:
Raised if a specified selector uses an unknown namespace
prefix.
- :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.
"""
# media "name"? { cssRules }
super(CSSMediaRule, self)._setCssText(cssText)
# might be (cssText, namespaces)
cssText, namespaces = self._splitNamespacesOff(cssText)
try:
# use parent style sheet ones if available
namespaces = self.parentStyleSheet.namespaces
except AttributeError:
pass
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.MEDIA_SYM:
self._log.error(u'CSSMediaRule: No CSSMediaRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# save if parse goes wrong
oldMedia = self._media
oldName = self._name
oldCssRules = self._cssRules
ok = True
# media
mediatokens, end = self._tokensupto2(tokenizer,
mediaqueryendonly=True,
separateEnd=True)
if u'{' == self._tokenvalue(end)\
or self._prods.STRING == self._type(end):
self.media = cssutils.stylesheets.MediaList(parentRule=self)
# TODO: remove special case
self.media.mediaText = mediatokens
ok = ok and self.media.wellformed
else:
ok = False
# name (optional)
name = None
nameseq = self._tempSeq()
if self._prods.STRING == self._type(end):
name = self._stringtokenvalue(end)
# TODO: for now comments are lost after name
nametokens, end = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
wellformed, expected = self._parse(None,
nameseq,
nametokens,
{})
if not wellformed:
ok = False
self._log.error(u'CSSMediaRule: Syntax Error: %s' %
self._valuestr(cssText))
# check for {
if u'{' != self._tokenvalue(end):
self._log.error(u'CSSMediaRule: No "{" found: %s' %
self._valuestr(cssText))
return
# cssRules
cssrulestokens, braceOrEOF = self._tokensupto2(tokenizer,
mediaendonly=True,
separateEnd=True)
nonetoken = self._nexttoken(tokenizer, None)
if 'EOF' == self._type(braceOrEOF):
# HACK!!!
# TODO: Not complete, add EOF to rule and } to @media
cssrulestokens.append(braceOrEOF)
braceOrEOF = ('CHAR', '}', 0, 0)
self._log.debug(u'CSSMediaRule: Incomplete, adding "}".',
token=braceOrEOF, neverraise=True)
if u'}' != self._tokenvalue(braceOrEOF):
self._log.error(u'CSSMediaRule: No "}" found.',
token=braceOrEOF)
elif nonetoken:
self._log.error(u'CSSMediaRule: Trailing content found.',
token=nonetoken)
else:
# for closures: must be a mutable
new = {'wellformed': True }
def COMMENT(expected, seq, token, tokenizer=None):
self.insertRule(cssutils.css.CSSComment([token],
parentRule=self,
parentStyleSheet=self.parentStyleSheet))
return expected
def ruleset(expected, seq, token, tokenizer):
rule = cssutils.css.CSSStyleRule(parentRule=self,
parentStyleSheet=self.parentStyleSheet)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
self.insertRule(rule)
return expected
def atrule(expected, seq, token, tokenizer):
# TODO: get complete rule!
tokens = self._tokensupto2(tokenizer, token)
atval = self._tokenvalue(token)
if atval in ('@charset ', '@font-face', '@import',
'@namespace', '@page', '@media', '@variables'):
self._log.error(u'CSSMediaRule: This rule is not '
u'allowed in CSSMediaRule - ignored: '
u'%s.' % self._valuestr(tokens),
token = token,
error=xml.dom.HierarchyRequestErr)
else:
rule = cssutils.css.CSSUnknownRule(tokens,
parentRule=self,
parentStyleSheet=self.parentStyleSheet)
if rule.wellformed:
self.insertRule(rule)
return expected
# save for possible reset
oldCssRules = self.cssRules
self.cssRules = cssutils.css.CSSRuleList()
seq = [] # not used really
tokenizer = iter(cssrulestokens)
wellformed, expected = self._parse(braceOrEOF,
seq,
tokenizer, {
'COMMENT': COMMENT,
'CHARSET_SYM': atrule,
'FONT_FACE_SYM': atrule,
'IMPORT_SYM': atrule,
'NAMESPACE_SYM': atrule,
'PAGE_SYM': atrule,
'MEDIA_SYM': atrule,
'ATKEYWORD': atrule
},
default=ruleset,
new=new)
ok = ok and wellformed
if ok:
self.name = name
self._setSeq(nameseq)
else:
self._media = oldMedia
self._cssRules = oldCssRules
cssText = property(_getCssText, _setCssText,
doc=u"(DOM) The parsable textual representation of this "
u"rule.")
def _setName(self, name):
if isinstance(name, basestring) or name is None:
# "" or ''
if not name:
name = None
self._name = name
else:
self._log.error(u'CSSImportRule: Not a valid name: %s' % name)
name = property(lambda self: self._name, _setName,
doc=u"An optional name for this media rule.")
def _setMedia(self, media):
"""
:param media:
a :class:`~cssutils.stylesheets.MediaList` or string
"""
self._checkReadonly()
if isinstance(media, basestring):
self._media = cssutils.stylesheets.MediaList(mediaText=media,
parentRule=self)
else:
media._parentRule = self
self._media = media
# NOT IN @media seq at all?!
# # update seq
# for i, item in enumerate(self.seq):
# if item.type == 'media':
# self._seq[i] = (self._media, 'media', None, None)
# break
# else:
# # insert after @media if not in seq at all
# self.seq.insert(0,
# self._media, 'media', None, None)
media = property(lambda self: self._media, _setMedia,
doc=u"(DOM) A list of media types for this rule "
u"of type :class:`~cssutils.stylesheets.MediaList`.")
def insertRule(self, rule, index=None):
"""Implements base ``insertRule``."""
rule, index = self._prepareInsertRule(rule, index)
if rule is False or rule is True:
# done or error
return
# check hierarchy
if isinstance(rule, cssutils.css.CSSCharsetRule) or \
isinstance(rule, cssutils.css.CSSFontFaceRule) or \
isinstance(rule, cssutils.css.CSSImportRule) or \
isinstance(rule, cssutils.css.CSSNamespaceRule) or \
isinstance(rule, cssutils.css.CSSPageRule) or \
isinstance(rule, cssutils.css.MarginRule) or \
isinstance(rule, CSSMediaRule):
self._log.error(u'%s: This type of rule is not allowed here: %s'
% (self.__class__.__name__, rule.cssText),
error=xml.dom.HierarchyRequestErr)
return
return self._finishInsertRule(rule, index)
type = property(lambda self: self.MEDIA_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
wellformed = property(lambda self: self.media.wellformed)

295
libs/cssutils/css/cssnamespacerule.py

@ -0,0 +1,295 @@
"""CSSNamespaceRule currently implements http://dev.w3.org/csswg/css3-namespace/
"""
__all__ = ['CSSNamespaceRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssrule
import cssutils
import xml.dom
class CSSNamespaceRule(cssrule.CSSRule):
"""
Represents an @namespace rule within a CSS style sheet.
The @namespace at-rule declares a namespace prefix and associates
it with a given namespace (a string). This namespace prefix can then be
used in namespace-qualified names such as those described in the
Selectors Module [SELECT] or the Values and Units module [CSS3VAL].
Dealing with these rules directly is not needed anymore, easier is
the use of :attr:`cssutils.css.CSSStyleSheet.namespaces`.
Format::
namespace
: NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
;
namespace_prefix
: IDENT
;
"""
def __init__(self, namespaceURI=None, prefix=None, cssText=None,
parentRule=None, parentStyleSheet=None, readonly=False):
"""
:Parameters:
namespaceURI
The namespace URI (a simple string!) which is bound to the
given prefix. If no prefix is set
(``CSSNamespaceRule.prefix==''``) the namespace defined by
namespaceURI is set as the default namespace
prefix
The prefix used in the stylesheet for the given
``CSSNamespaceRule.uri``.
cssText
if no namespaceURI is given cssText must be given to set
a namespaceURI as this is readonly later on
parentStyleSheet
sheet where this rule belongs to
Do not use as positional but as keyword parameters only!
If readonly allows setting of properties in constructor only
format namespace::
namespace
: NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
;
namespace_prefix
: IDENT
;
"""
super(CSSNamespaceRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@namespace'
self._prefix = u''
self._namespaceURI = None
if namespaceURI:
self.namespaceURI = namespaceURI
self.prefix = prefix
tempseq = self._tempSeq()
tempseq.append(self.prefix, 'prefix')
tempseq.append(self.namespaceURI, 'namespaceURI')
self._setSeq(tempseq)
elif cssText is not None:
self.cssText = cssText
if parentStyleSheet:
self._parentStyleSheet = parentStyleSheet
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(namespaceURI=%r, prefix=%r)" % (
self.__class__.__name__,
self.namespaceURI,
self.prefix)
def __str__(self):
return u"<cssutils.css.%s object namespaceURI=%r prefix=%r at 0x%x>" % (
self.__class__.__name__,
self.namespaceURI,
self.prefix,
id(self))
def _getCssText(self):
"""Return serialized property cssText"""
return cssutils.ser.do_CSSNamespaceRule(self)
def _setCssText(self, cssText):
"""
:param cssText: initial value for this rules cssText which is parsed
:exceptions:
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
"""
super(CSSNamespaceRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.NAMESPACE_SYM:
self._log.error(u'CSSNamespaceRule: No CSSNamespaceRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# for closures: must be a mutable
new = {'keyword': self._tokenvalue(attoken),
'prefix': u'',
'uri': None,
'wellformed': True
}
def _ident(expected, seq, token, tokenizer=None):
# the namespace prefix, optional
if 'prefix or uri' == expected:
new['prefix'] = self._tokenvalue(token)
seq.append(new['prefix'], 'prefix')
return 'uri'
else:
new['wellformed'] = False
self._log.error(
u'CSSNamespaceRule: Unexpected ident.', token)
return expected
def _string(expected, seq, token, tokenizer=None):
# the namespace URI as a STRING
if expected.endswith('uri'):
new['uri'] = self._stringtokenvalue(token)
seq.append(new['uri'], 'namespaceURI')
return ';'
else:
new['wellformed'] = False
self._log.error(
u'CSSNamespaceRule: Unexpected string.', token)
return expected
def _uri(expected, seq, token, tokenizer=None):
# the namespace URI as URI which is DEPRECATED
if expected.endswith('uri'):
uri = self._uritokenvalue(token)
new['uri'] = uri
seq.append(new['uri'], 'namespaceURI')
return ';'
else:
new['wellformed'] = False
self._log.error(
u'CSSNamespaceRule: Unexpected URI.', token)
return expected
def _char(expected, seq, token, tokenizer=None):
# final ;
val = self._tokenvalue(token)
if ';' == expected and u';' == val:
return 'EOF'
else:
new['wellformed'] = False
self._log.error(
u'CSSNamespaceRule: Unexpected char.', token)
return expected
# "NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*"
newseq = self._tempSeq()
wellformed, expected = self._parse(expected='prefix or uri',
seq=newseq, tokenizer=tokenizer,
productions={'IDENT': _ident,
'STRING': _string,
'URI': _uri,
'CHAR': _char},
new=new)
# wellformed set by parse
wellformed = wellformed and new['wellformed']
# post conditions
if new['uri'] is None:
wellformed = False
self._log.error(u'CSSNamespaceRule: No namespace URI found: %s'
% self._valuestr(cssText))
if expected != 'EOF':
wellformed = False
self._log.error(u'CSSNamespaceRule: No ";" found: %s' %
self._valuestr(cssText))
# set all
if wellformed:
self.atkeyword = new['keyword']
self._prefix = new['prefix']
self.namespaceURI = new['uri']
self._setSeq(newseq)
cssText = property(fget=_getCssText, fset=_setCssText,
doc=u"(DOM) The parsable textual representation of this "
u"rule.")
def _setNamespaceURI(self, namespaceURI):
"""
:param namespaceURI: the initial value for this rules namespaceURI
:exceptions:
- :exc:`~xml.dom.NoModificationAllowedErr`:
(CSSRule) Raised if this rule is readonly or a namespaceURI is
already set in this rule.
"""
self._checkReadonly()
if not self._namespaceURI:
# initial setting
self._namespaceURI = namespaceURI
tempseq = self._tempSeq()
tempseq.append(namespaceURI, 'namespaceURI')
self._setSeq(tempseq) # makes seq readonly!
elif self._namespaceURI != namespaceURI:
self._log.error(u'CSSNamespaceRule: namespaceURI is readonly.',
error=xml.dom.NoModificationAllowedErr)
namespaceURI = property(lambda self: self._namespaceURI, _setNamespaceURI,
doc="URI (handled as simple string) of the defined namespace.")
def _replaceNamespaceURI(self, namespaceURI):
"""Used during parse of new sheet only!
:param namespaceURI: the new value for this rules namespaceURI
"""
self._namespaceURI = namespaceURI
for i, x in enumerate(self._seq):
if 'namespaceURI' == x.type:
self._seq._readonly = False
self._seq.replace(i, namespaceURI, 'namespaceURI')
self._seq._readonly = True
break
def _setPrefix(self, prefix=None):
"""
:param prefix: the new prefix
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this rule is readonly.
"""
self._checkReadonly()
if not prefix:
prefix = u''
else:
tokenizer = self._tokenize2(prefix)
prefixtoken = self._nexttoken(tokenizer, None)
if not prefixtoken or self._type(prefixtoken) != self._prods.IDENT:
self._log.error(u'CSSNamespaceRule: No valid prefix "%s".' %
self._valuestr(prefix),
error=xml.dom.SyntaxErr)
return
else:
prefix = self._tokenvalue(prefixtoken)
# update seq
for i, x in enumerate(self._seq):
if x == self._prefix:
self._seq[i] = (prefix, 'prefix', None, None)
break
else:
# put prefix at the beginning!
self._seq[0] = (prefix, 'prefix', None, None)
# set new prefix
self._prefix = prefix
prefix = property(lambda self: self._prefix, _setPrefix,
doc=u"Prefix used for the defined namespace.")
type = property(lambda self: self.NAMESPACE_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
wellformed = property(lambda self: self.namespaceURI is not None)

436
libs/cssutils/css/csspagerule.py

@ -0,0 +1,436 @@
"""CSSPageRule implements DOM Level 2 CSS CSSPageRule."""
__all__ = ['CSSPageRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from itertools import chain
from cssstyledeclaration import CSSStyleDeclaration
from marginrule import MarginRule
import cssrule
import cssutils
import xml.dom
class CSSPageRule(cssrule.CSSRuleRules):
"""
The CSSPageRule interface represents a @page rule within a CSS style
sheet. The @page rule is used to specify the dimensions, orientation,
margins, etc. of a page box for paged media.
Format::
page :
PAGE_SYM S* IDENT? pseudo_page? S*
'{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S*
;
pseudo_page :
':' [ "left" | "right" | "first" ]
;
margin :
margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
;
margin_sym :
TOPLEFTCORNER_SYM |
TOPLEFT_SYM |
TOPCENTER_SYM |
TOPRIGHT_SYM |
TOPRIGHTCORNER_SYM |
BOTTOMLEFTCORNER_SYM |
BOTTOMLEFT_SYM |
BOTTOMCENTER_SYM |
BOTTOMRIGHT_SYM |
BOTTOMRIGHTCORNER_SYM |
LEFTTOP_SYM |
LEFTMIDDLE_SYM |
LEFTBOTTOM_SYM |
RIGHTTOP_SYM |
RIGHTMIDDLE_SYM |
RIGHTBOTTOM_SYM
;
`cssRules` contains a list of `MarginRule` objects.
"""
def __init__(self, selectorText=None, style=None, parentRule=None,
parentStyleSheet=None, readonly=False):
"""
If readonly allows setting of properties in constructor only.
:param selectorText:
type string
:param style:
CSSStyleDeclaration for this CSSStyleRule
"""
super(CSSPageRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@page'
self._specificity = (0, 0, 0)
tempseq = self._tempSeq()
if selectorText:
self.selectorText = selectorText
tempseq.append(self.selectorText, 'selectorText')
else:
self._selectorText = self._tempSeq()
if style:
self.style = style
else:
self.style = CSSStyleDeclaration()
tempseq.append(self.style, 'style')
self._setSeq(tempseq)
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(selectorText=%r, style=%r)" % (
self.__class__.__name__,
self.selectorText,
self.style.cssText)
def __str__(self):
return (u"<cssutils.css.%s object selectorText=%r specificity=%r "+
u"style=%r cssRules=%r at 0x%x>") % (
self.__class__.__name__,
self.selectorText,
self.specificity,
self.style.cssText,
len(self.cssRules),
id(self))
def __contains__(self, margin):
"""Check if margin is set in the rule."""
return margin in self.keys()
def keys(self):
"Return list of all set margins (MarginRule)."
return list(r.margin for r in self.cssRules)
def __getitem__(self, margin):
"""Retrieve the style (of MarginRule)
for `margin` (which must be normalized).
"""
for r in self.cssRules:
if r.margin == margin:
return r.style
def __setitem__(self, margin, style):
"""Set the style (of MarginRule)
for `margin` (which must be normalized).
"""
for i, r in enumerate(self.cssRules):
if r.margin == margin:
r.style = style
return i
else:
return self.add(MarginRule(margin, style))
def __delitem__(self, margin):
"""Delete the style (the MarginRule)
for `margin` (which must be normalized).
"""
for r in self.cssRules:
if r.margin == margin:
self.deleteRule(r)
def __parseSelectorText(self, selectorText):
"""
Parse `selectorText` which may also be a list of tokens
and returns (selectorText, seq).
see _setSelectorText for details
"""
# for closures: must be a mutable
new = {'wellformed': True, 'last-S': False,
'name': 0, 'first': 0, 'lr': 0}
specificity = (0, 0, 0)
def _char(expected, seq, token, tokenizer=None):
# pseudo_page, :left, :right or :first
val = self._tokenvalue(token)
if not new['last-S'] and expected in ['page', ': or EOF']\
and u':' == val:
try:
identtoken = tokenizer.next()
except StopIteration:
self._log.error(
u'CSSPageRule selectorText: No IDENT found.', token)
else:
ival, ityp = self._tokenvalue(identtoken),\
self._type(identtoken)
if self._prods.IDENT != ityp:
self._log.error(u'CSSPageRule selectorText: Expected '
u'IDENT but found: %r' % ival, token)
else:
if not ival in (u'first', u'left', u'right'):
self._log.warn(u'CSSPageRule: Unknown @page '
u'selector: %r'
% (u':'+ival,), neverraise=True)
if ival == u'first':
new['first'] = 1
else:
new['lr'] = 1
seq.append(val + ival, 'pseudo')
return 'EOF'
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSPageRule selectorText: Unexpected CHAR: %r'
% val, token)
return expected
def S(expected, seq, token, tokenizer=None):
"Does not raise if EOF is found."
if expected == ': or EOF':
# pseudo must directly follow IDENT if given
new['last-S'] = True
return expected
def IDENT(expected, seq, token, tokenizer=None):
""
val = self._tokenvalue(token)
if 'page' == expected:
if self._normalize(val) == u'auto':
self._log.error(u'CSSPageRule selectorText: Invalid pagename.',
token)
else:
new['name'] = 1
seq.append(val, 'IDENT')
return ': or EOF'
else:
new['wellformed'] = False
self._log.error(u'CSSPageRule selectorText: Unexpected IDENT: '
u'%r' % val, token)
return expected
def COMMENT(expected, seq, token, tokenizer=None):
"Does not raise if EOF is found."
seq.append(cssutils.css.CSSComment([token]), 'COMMENT')
return expected
newseq = self._tempSeq()
wellformed, expected = self._parse(expected='page',
seq=newseq, tokenizer=self._tokenize2(selectorText),
productions={'CHAR': _char,
'IDENT': IDENT,
'COMMENT': COMMENT,
'S': S},
new=new)
wellformed = wellformed and new['wellformed']
# post conditions
if expected == 'ident':
self._log.error(
u'CSSPageRule selectorText: No valid selector: %r' %
self._valuestr(selectorText))
return wellformed, newseq, (new['name'], new['first'], new['lr'])
def __parseMarginAndStyle(self, tokens):
"tokens is a list, no generator (yet)"
g = iter(tokens)
styletokens = []
# new rules until parse done
cssRules = []
for token in g:
if token[0] == 'ATKEYWORD' and \
self._normalize(token[1]) in MarginRule.margins:
# MarginRule
m = MarginRule(parentRule=self,
parentStyleSheet=self.parentStyleSheet)
m.cssText = chain([token], g)
# merge if margin set more than once
for r in cssRules:
if r.margin == m.margin:
for p in m.style:
r.style.setProperty(p, replace=False)
break
else:
cssRules.append(m)
continue
# TODO: Properties?
styletokens.append(token)
return cssRules, styletokens
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSPageRule(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.
"""
super(CSSPageRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
if self._type(self._nexttoken(tokenizer)) != self._prods.PAGE_SYM:
self._log.error(u'CSSPageRule: No CSSPageRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
newStyle = CSSStyleDeclaration(parentRule=self)
ok = True
selectortokens, startbrace = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
styletokens, braceorEOFtoken = self._tokensupto2(tokenizer,
blockendonly=True,
separateEnd=True)
nonetoken = self._nexttoken(tokenizer)
if self._tokenvalue(startbrace) != u'{':
ok = False
self._log.error(u'CSSPageRule: No start { of style declaration '
u'found: %r' %
self._valuestr(cssText), startbrace)
elif nonetoken:
ok = False
self._log.error(u'CSSPageRule: Trailing content found.',
token=nonetoken)
selok, newselseq, specificity = self.__parseSelectorText(selectortokens)
ok = ok and selok
val, type_ = self._tokenvalue(braceorEOFtoken),\
self._type(braceorEOFtoken)
if val != u'}' and type_ != 'EOF':
ok = False
self._log.error(
u'CSSPageRule: No "}" after style declaration found: %r' %
self._valuestr(cssText))
else:
if 'EOF' == type_:
# add again as style needs it
styletokens.append(braceorEOFtoken)
# filter pagemargin rules out first
cssRules, styletokens = self.__parseMarginAndStyle(styletokens)
# SET, may raise:
newStyle.cssText = styletokens
if ok:
self._selectorText = newselseq
self._specificity = specificity
self.style = newStyle
self.cssRules = cssutils.css.CSSRuleList()
for r in cssRules:
self.cssRules.append(r)
cssText = property(_getCssText, _setCssText,
doc=u"(DOM) The parsable textual representation of this rule.")
def _getSelectorText(self):
"""Wrapper for cssutils Selector object."""
return cssutils.ser.do_CSSPageRuleSelector(self._selectorText)
def _setSelectorText(self, selectorText):
"""Wrapper for cssutils Selector object.
:param selectorText:
DOM String, in CSS 2.1 one of
- :first
- :left
- :right
- empty
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error
and is unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this rule is readonly.
"""
self._checkReadonly()
# may raise SYNTAX_ERR
wellformed, newseq, specificity = self.__parseSelectorText(selectorText)
if wellformed:
self._selectorText = newseq
self._specificity = specificity
selectorText = property(_getSelectorText, _setSelectorText,
doc=u"(DOM) The parsable textual representation of "
u"the page selector for the rule.")
def _setStyle(self, style):
"""
:param style:
a CSSStyleDeclaration or string
"""
self._checkReadonly()
if isinstance(style, basestring):
self._style = CSSStyleDeclaration(cssText=style, parentRule=self)
else:
style._parentRule = self
self._style = style
style = property(lambda self: self._style, _setStyle,
doc=u"(DOM) The declaration-block of this rule set, "
u"a :class:`~cssutils.css.CSSStyleDeclaration`.")
def insertRule(self, rule, index=None):
"""Implements base ``insertRule``."""
rule, index = self._prepareInsertRule(rule, index)
if rule is False or rule is True:
# done or error
return
# check hierarchy
if isinstance(rule, cssutils.css.CSSCharsetRule) or \
isinstance(rule, cssutils.css.CSSFontFaceRule) or \
isinstance(rule, cssutils.css.CSSImportRule) or \
isinstance(rule, cssutils.css.CSSNamespaceRule) or \
isinstance(rule, CSSPageRule) or \
isinstance(rule, cssutils.css.CSSMediaRule):
self._log.error(u'%s: This type of rule is not allowed here: %s'
% (self.__class__.__name__, rule.cssText),
error=xml.dom.HierarchyRequestErr)
return
return self._finishInsertRule(rule, index)
specificity = property(lambda self: self._specificity,
doc=u"""Specificity of this page rule (READONLY).
Tuple of (f, g, h) where:
- if the page selector has a named page, f=1; else f=0
- if the page selector has a ':first' pseudo-class, g=1; else g=0
- if the page selector has a ':left' or ':right' pseudo-class, h=1; else h=0
""")
type = property(lambda self: self.PAGE_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
# constant but needed:
wellformed = property(lambda self: True)

122
libs/cssutils/css/cssproperties.py

@ -0,0 +1,122 @@
"""CSS2Properties (partly!) implements DOM Level 2 CSS CSS2Properties used
by CSSStyleDeclaration
TODO: CSS2Properties
If an implementation does implement this interface, it is expected to
understand the specific syntax of the shorthand properties, and apply
their semantics; when the margin property is set, for example, the
marginTop, marginRight, marginBottom and marginLeft properties are
actually being set by the underlying implementation.
When dealing with CSS "shorthand" properties, the shorthand properties
should be decomposed into their component longhand properties as
appropriate, and when querying for their value, the form returned
should be the shortest form exactly equivalent to the declarations made
in the ruleset. However, if there is no shorthand declaration that
could be added to the ruleset without changing in any way the rules
already declared in the ruleset (i.e., by adding longhand rules that
were previously not declared in the ruleset), then the empty string
should be returned for the shorthand property.
For example, querying for the font property should not return
"normal normal normal 14pt/normal Arial, sans-serif", when
"14pt Arial, sans-serif" suffices. (The normals are initial values, and
are implied by use of the longhand property.)
If the values for all the longhand properties that compose a particular
string are the initial values, then a string consisting of all the
initial values should be returned (e.g. a border-width value of
"medium" should be returned as such, not as "").
For some shorthand properties that take missing values from other
sides, such as the margin, padding, and border-[width|style|color]
properties, the minimum number of sides possible should be used; i.e.,
"0px 10px" will be returned instead of "0px 10px 0px 10px".
If the value of a shorthand property can not be decomposed into its
component longhand properties, as is the case for the font property
with a value of "menu", querying for the values of the component
longhand properties should return the empty string.
TODO: CSS2Properties DOMImplementation
The interface found within this section are not mandatory. A DOM
application can use the hasFeature method of the DOMImplementation
interface to determine whether it is supported or not. The feature
string for this extended interface listed in this section is "CSS2"
and the version is "2.0".
"""
__all__ = ['CSS2Properties']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssutils.profiles
import re
class CSS2Properties(object):
"""The CSS2Properties interface represents a convenience mechanism
for retrieving and setting properties within a CSSStyleDeclaration.
The attributes of this interface correspond to all the properties
specified in CSS2. Getting an attribute of this interface is
equivalent to calling the getPropertyValue method of the
CSSStyleDeclaration interface. Setting an attribute of this
interface is equivalent to calling the setProperty method of the
CSSStyleDeclaration interface.
cssutils actually also allows usage of ``del`` to remove a CSS property
from a CSSStyleDeclaration.
This is an abstract class, the following functions need to be present
in inheriting class:
- ``_getP``
- ``_setP``
- ``_delP``
"""
# actual properties are set after the class definition!
def _getP(self, CSSname): pass
def _setP(self, CSSname, value): pass
def _delP(self, CSSname): pass
_reCSStoDOMname = re.compile('-[a-z]', re.I)
def _toDOMname(CSSname):
"""Returns DOMname for given CSSname e.g. for CSSname 'font-style' returns
'fontStyle'.
"""
def _doCSStoDOMname2(m): return m.group(0)[1].capitalize()
return _reCSStoDOMname.sub(_doCSStoDOMname2, CSSname)
_reDOMtoCSSname = re.compile('([A-Z])[a-z]+')
def _toCSSname(DOMname):
"""Return CSSname for given DOMname e.g. for DOMname 'fontStyle' returns
'font-style'.
"""
def _doDOMtoCSSname2(m): return '-' + m.group(0).lower()
return _reDOMtoCSSname.sub(_doDOMtoCSSname2, DOMname)
# add list of DOMname properties to CSS2Properties
# used for CSSStyleDeclaration to check if allowed properties
# but somehow doubled, any better way?
CSS2Properties._properties = []
for group in cssutils.profiles.properties:
for name in cssutils.profiles.properties[group]:
CSS2Properties._properties.append(_toDOMname(name))
# add CSS2Properties to CSSStyleDeclaration:
def __named_property_def(DOMname):
"""
Closure to keep name known in each properties accessor function
DOMname is converted to CSSname here, so actual calls use CSSname.
"""
CSSname = _toCSSname(DOMname)
def _get(self): return self._getP(CSSname)
def _set(self, value): self._setP(CSSname, value)
def _del(self): self._delP(CSSname)
return _get, _set, _del
# add all CSS2Properties to CSSStyleDeclaration
for DOMname in CSS2Properties._properties:
setattr(CSS2Properties, DOMname,
property(*__named_property_def(DOMname)))

304
libs/cssutils/css/cssrule.py

@ -0,0 +1,304 @@
"""CSSRule implements DOM Level 2 CSS CSSRule."""
__all__ = ['CSSRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssutils
import xml.dom
class CSSRule(cssutils.util.Base2):
"""Abstract base interface for any type of CSS statement. This includes
both rule sets and at-rules. An implementation is expected to preserve
all rules specified in a CSS style sheet, even if the rule is not
recognized by the parser. Unrecognized rules are represented using the
:class:`CSSUnknownRule` interface.
"""
"""
CSSRule type constants.
An integer indicating which type of rule this is.
"""
UNKNOWN_RULE = 0
":class:`cssutils.css.CSSUnknownRule` (not used in CSSOM anymore)"
STYLE_RULE = 1
":class:`cssutils.css.CSSStyleRule`"
CHARSET_RULE = 2
":class:`cssutils.css.CSSCharsetRule` (not used in CSSOM anymore)"
IMPORT_RULE = 3
":class:`cssutils.css.CSSImportRule`"
MEDIA_RULE = 4
":class:`cssutils.css.CSSMediaRule`"
FONT_FACE_RULE = 5
":class:`cssutils.css.CSSFontFaceRule`"
PAGE_RULE = 6
":class:`cssutils.css.CSSPageRule`"
NAMESPACE_RULE = 10
""":class:`cssutils.css.CSSNamespaceRule`,
Value has changed in 0.9.7a3 due to a change in the CSSOM spec."""
COMMENT = 1001 # was -1, cssutils only
""":class:`cssutils.css.CSSComment` - not in the offical spec,
Value has changed in 0.9.7a3"""
VARIABLES_RULE = 1008
""":class:`cssutils.css.CSSVariablesRule` - experimental rule
not in the offical spec"""
MARGIN_RULE = 1006
""":class:`cssutils.css.MarginRule` - experimental rule
not in the offical spec"""
_typestrings = {UNKNOWN_RULE: u'UNKNOWN_RULE',
STYLE_RULE: u'STYLE_RULE',
CHARSET_RULE: u'CHARSET_RULE',
IMPORT_RULE: u'IMPORT_RULE',
MEDIA_RULE: u'MEDIA_RULE',
FONT_FACE_RULE: u'FONT_FACE_RULE',
PAGE_RULE: u'PAGE_RULE',
NAMESPACE_RULE: u'NAMESPACE_RULE',
COMMENT: u'COMMENT',
VARIABLES_RULE: u'VARIABLES_RULE',
MARGIN_RULE: u'MARGIN_RULE'
}
def __init__(self, parentRule=None, parentStyleSheet=None, readonly=False):
"""Set common attributes for all rules."""
super(CSSRule, self).__init__()
self._parent = parentRule
self._parentRule = parentRule
self._parentStyleSheet = parentStyleSheet
self._setSeq(self._tempSeq())
#self._atkeyword = None
# must be set after initialization of #inheriting rule is done
self._readonly = False
def _setAtkeyword(self, keyword):
"""Check if new keyword fits the rule it is used for."""
atkeyword = self._normalize(keyword)
if not self.atkeyword or (self.atkeyword == atkeyword):
self._atkeyword = atkeyword
self._keyword = keyword
else:
self._log.error(u'%s: Invalid atkeyword for this rule: %r' %
(self.atkeyword, keyword),
error=xml.dom.InvalidModificationErr)
atkeyword = property(lambda self: self._atkeyword, _setAtkeyword,
doc=u"Normalized keyword of an @rule (e.g. ``@import``).")
def _setCssText(self, cssText):
"""
:param cssText:
A parsable DOMString.
: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.
"""
self._checkReadonly()
cssText = property(lambda self: u'', _setCssText,
doc=u"(DOM) The parsable textual representation of the "
u"rule. This reflects the current state of the rule "
u"and not its initial value.")
parent = property(lambda self: self._parent,
doc=u"The Parent Node of this CSSRule or None.")
parentRule = property(lambda self: self._parentRule,
doc=u"If this rule is contained inside another rule "
u"(e.g. a style rule inside an @media block), this "
u"is the containing rule. If this rule is not nested "
u"inside any other rules, this returns None.")
def _getParentStyleSheet(self):
# rules contained in other rules (@media) use that rules parent
if (self.parentRule):
return self.parentRule._parentStyleSheet
else:
return self._parentStyleSheet
parentStyleSheet = property(_getParentStyleSheet,
doc=u"The style sheet that contains this rule.")
type = property(lambda self: self.UNKNOWN_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
typeString = property(lambda self: CSSRule._typestrings[self.type],
doc=u"Descriptive name of this rule's type.")
wellformed = property(lambda self: False,
doc=u"If the rule is wellformed.")
class CSSRuleRules(CSSRule):
"""Abstract base interface for rules that contain other rules
like @media or @page. Methods may be overwritten if a rule
has specific stuff to do like checking the order of insertion like
@media does.
"""
def __init__(self, parentRule=None, parentStyleSheet=None):
super(CSSRuleRules, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self.cssRules = cssutils.css.CSSRuleList()
def __iter__(self):
"""Generator iterating over these rule's cssRules."""
for rule in self._cssRules:
yield rule
def _setCssRules(self, cssRules):
"Set new cssRules and update contained rules refs."
cssRules.append = self.insertRule
cssRules.extend = self.insertRule
cssRules.__delitem__ == self.deleteRule
for rule in cssRules:
rule._parentRule = self
rule._parentStyleSheet = None
self._cssRules = cssRules
cssRules = property(lambda self: self._cssRules, _setCssRules,
"All Rules in this style sheet, a "
":class:`~cssutils.css.CSSRuleList`.")
def deleteRule(self, index):
"""
Delete the rule at `index` from rules ``cssRules``.
:param index:
The `index` of the rule to be removed from the rules cssRules
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.
:Exceptions:
- :exc:`~xml.dom.IndexSizeErr`:
Raised if the specified index does not correspond to a rule in
the media rule list.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this media rule is readonly.
"""
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"%s: Not a rule in "
u"this rule'a cssRules list: %s"
% (self.__class__.__name__, index))
try:
# detach
self._cssRules[index]._parentRule = None
del self._cssRules[index]
except IndexError:
raise xml.dom.IndexSizeErr(u'%s: %s is not a valid index '
u'in the rulelist of length %i'
% (self.__class__.__name__,
index, self._cssRules.length))
def _prepareInsertRule(self, rule, index=None):
"return checked `index` and optional parsed `rule`"
self._checkReadonly()
# check index
if index is None:
index = len(self._cssRules)
elif index < 0 or index > self._cssRules.length:
raise xml.dom.IndexSizeErr(u'%s: Invalid index %s for '
u'CSSRuleList with a length of %s.'
% (self.__class__.__name__,
index, self._cssRules.length))
# check and optionally parse rule
if isinstance(rule, basestring):
tempsheet = cssutils.css.CSSStyleSheet()
tempsheet.cssText = rule
if len(tempsheet.cssRules) != 1 or (tempsheet.cssRules and
not isinstance(tempsheet.cssRules[0], cssutils.css.CSSRule)):
self._log.error(u'%s: Invalid Rule: %s' % (self.__class__.__name__,
rule))
return False, False
rule = tempsheet.cssRules[0]
elif isinstance(rule, cssutils.css.CSSRuleList):
# insert all rules
for i, r in enumerate(rule):
self.insertRule(r, index + i)
return True, True
elif not isinstance(rule, cssutils.css.CSSRule):
self._log.error(u'%s: Not a CSSRule: %s' % (rule,
self.__class__.__name__))
return False, False
return rule, index
def _finishInsertRule(self, rule, index):
"add `rule` at `index`"
rule._parentRule = self
rule._parentStyleSheet = None
self._cssRules.insert(index, rule)
return index
def add(self, rule):
"""Add `rule` to page rule. Same as ``insertRule(rule)``."""
return self.insertRule(rule)
def insertRule(self, rule, index=None):
"""
Insert `rule` into the rules ``cssRules``.
:param rule:
the parsable text representing the `rule` to be inserted. For rule
sets this contains both the selector and the style declaration.
For at-rules, this specifies both the at-identifier and the rule
content.
cssutils also allows rule to be a valid
:class:`~cssutils.css.CSSRule` object.
:param index:
before the `index` the specified `rule` will be inserted.
If the specified `index` is equal to the length of the rules
rule collection, the rule will be added to the end of the rule.
If index is not given or None rule will be appended to rule
list.
:returns:
the index of the newly inserted rule.
:exceptions:
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the `rule` cannot be inserted at the specified `index`,
e.g., if an @import rule is inserted after a standard rule set
or other at-rule.
- :exc:`~xml.dom.IndexSizeErr`:
Raised if the specified `index` is not a valid insertion point.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this rule is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified `rule` has a syntax error and is
unparsable.
"""
return self._prepareInsertRule(rule, index)

53
libs/cssutils/css/cssrulelist.py

@ -0,0 +1,53 @@
"""CSSRuleList implements DOM Level 2 CSS CSSRuleList.
Partly also http://dev.w3.org/csswg/cssom/#the-cssrulelist."""
__all__ = ['CSSRuleList']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
class CSSRuleList(list):
"""The CSSRuleList object represents an (ordered) list of statements.
The items in the CSSRuleList are accessible via an integral index,
starting from 0.
Subclasses a standard Python list so theoretically all standard list
methods are available. Setting methods like ``__init__``, ``append``,
``extend`` or ``__setslice__`` are added later on instances of this
class if so desired.
E.g. CSSStyleSheet adds ``append`` which is not available in a simple
instance of this class!
"""
def __init__(self, *ignored):
"Nothing is set as this must also be defined later."
pass
def __notimplemented(self, *ignored):
"Implemented in class using a CSSRuleList only."
raise NotImplementedError(
'Must be implemented by class using an instance of this class.')
append = extend = __setitem__ = __setslice__ = __notimplemented
def item(self, index):
"""(DOM) Retrieve a CSS rule by ordinal `index`. The order in this
collection represents the order of the rules in the CSS style
sheet. If index is greater than or equal to the number of rules in
the list, this returns None.
Returns CSSRule, the style rule at the index position in the
CSSRuleList, or None if that is not a valid index.
"""
try:
return self[index]
except IndexError:
return None
length = property(lambda self: len(self),
doc=u"(DOM) The number of CSSRules in the list.")
def rulesOfType(self, type):
"""Yield the rules which have the given `type` only, one of the
constants defined in :class:`cssutils.css.CSSRule`."""
for r in self:
if r.type == type:
yield r

697
libs/cssutils/css/cssstyledeclaration.py

@ -0,0 +1,697 @@
"""CSSStyleDeclaration implements DOM Level 2 CSS CSSStyleDeclaration and
extends CSS2Properties
see
http://www.w3.org/TR/1998/REC-CSS2-19980512/syndata.html#parsing-errors
Unknown properties
------------------
User agents must ignore a declaration with an unknown property.
For example, if the style sheet is::
H1 { color: red; rotation: 70minutes }
the user agent will treat this as if the style sheet had been::
H1 { color: red }
Cssutils gives a message about any unknown properties but
keeps any property (if syntactically correct).
Illegal values
--------------
User agents must ignore a declaration with an illegal value. For example::
IMG { float: left } /* correct CSS2 */
IMG { float: left here } /* "here" is not a value of 'float' */
IMG { background: "red" } /* keywords cannot be quoted in CSS2 */
IMG { border-width: 3 } /* a unit must be specified for length values */
A CSS2 parser would honor the first rule and ignore the rest, as if the
style sheet had been::
IMG { float: left }
IMG { }
IMG { }
IMG { }
Cssutils again will issue a message (WARNING in this case) about invalid
CSS2 property values.
TODO:
This interface is also used to provide a read-only access to the
computed values of an element. See also the ViewCSS interface.
- return computed values and not literal values
- simplify unit pairs/triples/quadruples
2px 2px 2px 2px -> 2px for border/padding...
- normalize compound properties like:
background: no-repeat left url() #fff
-> background: #fff url() no-repeat left
"""
__all__ = ['CSSStyleDeclaration', 'Property']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssproperties import CSS2Properties
from property import Property
import cssutils
import xml.dom
class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
"""The CSSStyleDeclaration class represents a single CSS declaration
block. This class may be used to determine the style properties
currently set in a block or to set style properties explicitly
within the block.
While an implementation may not recognize all CSS properties within
a CSS declaration block, it is expected to provide access to all
specified properties in the style sheet through the
CSSStyleDeclaration interface.
Furthermore, implementations that support a specific level of CSS
should correctly handle CSS shorthand properties for that level. For
a further discussion of shorthand properties, see the CSS2Properties
interface.
Additionally the CSS2Properties interface is implemented.
$css2propertyname
All properties defined in the CSS2Properties class are available
as direct properties of CSSStyleDeclaration with their respective
DOM name, so e.g. ``fontStyle`` for property 'font-style'.
These may be used as::
>>> style = CSSStyleDeclaration(cssText='color: red')
>>> style.color = 'green'
>>> print style.color
green
>>> del style.color
>>> print style.color
<BLANKLINE>
Format::
[Property: Value Priority?;]* [Property: Value Priority?]?
"""
def __init__(self, cssText=u'', parentRule=None, readonly=False,
validating=None):
"""
:param cssText:
Shortcut, sets CSSStyleDeclaration.cssText
:param parentRule:
The CSS rule that contains this declaration block or
None if this CSSStyleDeclaration is not attached to a CSSRule.
:param readonly:
defaults to False
:param validating:
a flag defining if this sheet should be validated on change.
Defaults to None, which means defer to the parent stylesheet.
"""
super(CSSStyleDeclaration, self).__init__()
self._parentRule = parentRule
self.validating = validating
self.cssText = cssText
self._readonly = readonly
def __contains__(self, nameOrProperty):
"""Check if a property (or a property with given name) is in style.
:param name:
a string or Property, uses normalized name and not literalname
"""
if isinstance(nameOrProperty, Property):
name = nameOrProperty.name
else:
name = self._normalize(nameOrProperty)
return name in self.__nnames()
def __iter__(self):
"""Iterator of set Property objects with different normalized names."""
def properties():
for name in self.__nnames():
yield self.getProperty(name)
return properties()
def keys(self):
"""Analoguous to standard dict returns property names which are set in
this declaration."""
return list(self.__nnames())
def __getitem__(self, CSSName):
"""Retrieve the value of property ``CSSName`` from this declaration.
``CSSName`` will be always normalized.
"""
return self.getPropertyValue(CSSName)
def __setitem__(self, CSSName, value):
"""Set value of property ``CSSName``. ``value`` may also be a tuple of
(value, priority), e.g. style['color'] = ('red', 'important')
``CSSName`` will be always normalized.
"""
priority = None
if isinstance(value, tuple):
value, priority = value
return self.setProperty(CSSName, value, priority)
def __delitem__(self, CSSName):
"""Delete property ``CSSName`` from this declaration.
If property is not in this declaration return u'' just like
removeProperty.
``CSSName`` will be always normalized.
"""
return self.removeProperty(CSSName)
def __setattr__(self, n, v):
"""Prevent setting of unknown properties on CSSStyleDeclaration
which would not work anyway. For these
``CSSStyleDeclaration.setProperty`` MUST be called explicitly!
TODO:
implementation of known is not really nice, any alternative?
"""
known = ['_tokenizer', '_log', '_ttypes',
'_seq', 'seq', 'parentRule', '_parentRule', 'cssText',
'valid', 'wellformed', 'validating',
'_readonly', '_profiles', '_validating']
known.extend(CSS2Properties._properties)
if n in known:
super(CSSStyleDeclaration, self).__setattr__(n, v)
else:
raise AttributeError(u'Unknown CSS Property, '
u'``CSSStyleDeclaration.setProperty("%s", '
u'...)`` MUST be used.' % n)
def __repr__(self):
return u"cssutils.css.%s(cssText=%r)" % (
self.__class__.__name__,
self.getCssText(separator=u' '))
def __str__(self):
return u"<cssutils.css.%s object length=%r (all: %r) at 0x%x>" % (
self.__class__.__name__,
self.length,
len(self.getProperties(all=True)),
id(self))
def __nnames(self):
"""Return iterator for all different names in order as set
if names are set twice the last one is used (double reverse!)
"""
names = []
for item in reversed(self.seq):
val = item.value
if isinstance(val, Property) and not val.name in names:
names.append(val.name)
return reversed(names)
# overwritten accessor functions for CSS2Properties' properties
def _getP(self, CSSName):
"""(DOM CSS2Properties) Overwritten here and effectively the same as
``self.getPropertyValue(CSSname)``.
Parameter is in CSSname format ('font-style'), see CSS2Properties.
Example::
>>> style = CSSStyleDeclaration(cssText='font-style:italic;')
>>> print style.fontStyle
italic
"""
return self.getPropertyValue(CSSName)
def _setP(self, CSSName, value):
"""(DOM CSS2Properties) Overwritten here and effectively the same as
``self.setProperty(CSSname, value)``.
Only known CSS2Properties may be set this way, otherwise an
AttributeError is raised.
For these unknown properties ``setPropertyValue(CSSname, value)``
has to be called explicitly.
Also setting the priority of properties needs to be done with a
call like ``setPropertyValue(CSSname, value, priority)``.
Example::
>>> style = CSSStyleDeclaration()
>>> style.fontStyle = 'italic'
>>> # or
>>> style.setProperty('font-style', 'italic', '!important')
"""
self.setProperty(CSSName, value)
# TODO: Shorthand ones
def _delP(self, CSSName):
"""(cssutils only) Overwritten here and effectively the same as
``self.removeProperty(CSSname)``.
Example::
>>> style = CSSStyleDeclaration(cssText='font-style:italic;')
>>> del style.fontStyle
>>> print style.fontStyle
<BLANKLINE>
"""
self.removeProperty(CSSName)
def children(self):
"""Generator yielding any known child in this declaration including
*all* properties, comments or CSSUnknownrules.
"""
for item in self._seq:
yield item.value
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_css_CSSStyleDeclaration(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.
"""
self._checkReadonly()
tokenizer = self._tokenize2(cssText)
# for closures: must be a mutable
new = {'wellformed': True}
def ident(expected, seq, token, tokenizer=None):
# a property
tokens = self._tokensupto2(tokenizer, starttoken=token,
semicolon=True)
if self._tokenvalue(tokens[-1]) == u';':
tokens.pop()
property = Property(parent=self)
property.cssText = tokens
if property.wellformed:
seq.append(property, 'Property')
else:
self._log.error(u'CSSStyleDeclaration: Syntax Error in '
u'Property: %s' % self._valuestr(tokens))
# does not matter in this case
return expected
def unexpected(expected, seq, token, tokenizer=None):
# error, find next ; or } to omit upto next property
ignored = self._tokenvalue(token) + self._valuestr(
self._tokensupto2(tokenizer,
propertyvalueendonly=True))
self._log.error(u'CSSStyleDeclaration: Unexpected token, ignoring '
'upto %r.' % ignored,token)
# does not matter in this case
return expected
def char(expected, seq, token, tokenizer=None):
# a standalone ; or error...
if self._tokenvalue(token) == u';':
self._log.info(u'CSSStyleDeclaration: Stripped standalone semicolon'
u': %s' % self._valuestr([token]), neverraise=True)
return expected
else:
return unexpected(expected, seq, token, tokenizer)
# [Property: Value;]* Property: Value?
newseq = self._tempSeq()
wellformed, expected = self._parse(expected=None,
seq=newseq, tokenizer=tokenizer,
productions={'IDENT': ident, 'CHAR': char},
default=unexpected)
# wellformed set by parse
for item in newseq:
item.value._parent = self
# do not check wellformed as invalid things are removed anyway
self._setSeq(newseq)
cssText = property(_getCssText, _setCssText,
doc=u"(DOM) A parsable textual representation of the "
u"declaration block excluding the surrounding curly "
u"braces.")
def getCssText(self, separator=None):
"""
:returns:
serialized property cssText, each property separated by
given `separator` which may e.g. be ``u''`` to be able to use
cssText directly in an HTML style attribute. ``;`` is part of
each property (except the last one) and **cannot** be set with
separator!
"""
return cssutils.ser.do_css_CSSStyleDeclaration(self, separator)
def _setParentRule(self, parentRule):
self._parentRule = parentRule
# for x in self.children():
# x.parent = self
parentRule = property(lambda self: self._parentRule, _setParentRule,
doc="(DOM) The CSS rule that contains this declaration block or "
"None if this CSSStyleDeclaration is not attached to a CSSRule.")
def getProperties(self, name=None, all=False):
"""
:param name:
optional `name` of properties which are requested.
Only properties with this **always normalized** `name` are returned.
If `name` is ``None`` all properties are returned (at least one for
each set name depending on parameter `all`).
:param all:
if ``False`` (DEFAULT) only the effective properties are returned.
If name is given a list with only one property is returned.
if ``True`` all properties including properties set multiple times
with different values or priorities for different UAs are returned.
The order of the properties is fully kept as in the original
stylesheet.
:returns:
a list of :class:`~cssutils.css.Property` objects set in
this declaration.
"""
if name and not all:
# single prop but list
p = self.getProperty(name)
if p:
return [p]
else:
return []
elif not all:
# effective Properties in name order
return [self.getProperty(name) for name in self.__nnames()]
else:
# all properties or all with this name
nname = self._normalize(name)
properties = []
for item in self.seq:
val = item.value
if isinstance(val, Property) and (
(bool(nname) == False) or (val.name == nname)):
properties.append(val)
return properties
def getProperty(self, name, normalize=True):
"""
:param name:
of the CSS property, always lowercase (even if not normalized)
:param normalize:
if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
escapes) so "color", "COLOR" or "C\olor" will all be equivalent
If ``False`` may return **NOT** the effective value but the
effective for the unnormalized name.
:returns:
the effective :class:`~cssutils.css.Property` object.
"""
nname = self._normalize(name)
found = None
for item in reversed(self.seq):
val = item.value
if isinstance(val, Property):
if (normalize and nname == val.name) or name == val.literalname:
if val.priority:
return val
elif not found:
found = val
return found
def getPropertyCSSValue(self, name, normalize=True):
"""
:param name:
of the CSS property, always lowercase (even if not normalized)
:param normalize:
if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
escapes) so "color", "COLOR" or "C\olor" will all be equivalent
If ``False`` may return **NOT** the effective value but the
effective for the unnormalized name.
:returns:
:class:`~cssutils.css.CSSValue`, the value of the effective
property if it has been explicitly set for this declaration block.
(DOM)
Used to retrieve the object representation of the value of a CSS
property if it has been explicitly set within this declaration
block. Returns None if the property has not been set.
(This method returns None if the property is a shorthand
property. Shorthand property values can only be accessed and
modified as strings, using the getPropertyValue and setProperty
methods.)
**cssutils currently always returns a CSSValue if the property is
set.**
for more on shorthand properties see
http://www.dustindiaz.com/css-shorthand/
"""
nname = self._normalize(name)
if nname in self._SHORTHANDPROPERTIES:
self._log.info(u'CSSValue for shorthand property "%s" should be '
u'None, this may be implemented later.' %
nname, neverraise=True)
p = self.getProperty(name, normalize)
if p:
return p.cssValue
else:
return None
def getPropertyValue(self, name, normalize=True):
"""
:param name:
of the CSS property, always lowercase (even if not normalized)
:param normalize:
if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
escapes) so "color", "COLOR" or "C\olor" will all be equivalent
If ``False`` may return **NOT** the effective value but the
effective for the unnormalized name.
:returns:
the value of the effective property if it has been explicitly set
for this declaration block. Returns the empty string if the
property has not been set.
"""
p = self.getProperty(name, normalize)
if p:
return p.value
else:
return u''
def getPropertyPriority(self, name, normalize=True):
"""
:param name:
of the CSS property, always lowercase (even if not normalized)
:param normalize:
if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
escapes) so "color", "COLOR" or "C\olor" will all be equivalent
If ``False`` may return **NOT** the effective value but the
effective for the unnormalized name.
:returns:
the priority of the effective CSS property (e.g. the
"important" qualifier) if the property has been explicitly set in
this declaration block. The empty string if none exists.
"""
p = self.getProperty(name, normalize)
if p:
return p.priority
else:
return u''
def removeProperty(self, name, normalize=True):
"""
(DOM)
Used to remove a CSS property if it has been explicitly set within
this declaration block.
:param name:
of the CSS property
:param normalize:
if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
escapes) so "color", "COLOR" or "C\olor" will all be equivalent.
The effective Property value is returned and *all* Properties
with ``Property.name == name`` are removed.
If ``False`` may return **NOT** the effective value but the
effective for the unnormalized `name` only. Also only the
Properties with the literal name `name` are removed.
:returns:
the value of the property if it has been explicitly set for
this declaration block. Returns the empty string if the property
has not been set or the property name does not correspond to a
known CSS property
:exceptions:
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this declaration is readonly or the property is
readonly.
"""
self._checkReadonly()
r = self.getPropertyValue(name, normalize=normalize)
newseq = self._tempSeq()
if normalize:
# remove all properties with name == nname
nname = self._normalize(name)
for item in self.seq:
if not (isinstance(item.value, Property)
and item.value.name == nname):
newseq.appendItem(item)
else:
# remove all properties with literalname == name
for item in self.seq:
if not (isinstance(item.value, Property)
and item.value.literalname == name):
newseq.appendItem(item)
self._setSeq(newseq)
return r
def setProperty(self, name, value=None, priority=u'',
normalize=True, replace=True):
"""(DOM) Set a property value and priority within this declaration
block.
:param name:
of the CSS property to set (in W3C DOM the parameter is called
"propertyName"), always lowercase (even if not normalized)
If a property with this `name` is present it will be reset.
cssutils also allowed `name` to be a
:class:`~cssutils.css.Property` object, all other
parameter are ignored in this case
:param value:
the new value of the property, ignored if `name` is a Property.
:param priority:
the optional priority of the property (e.g. "important"),
ignored if `name` is a Property.
:param normalize:
if True (DEFAULT) `name` will be normalized (lowercase, no simple
escapes) so "color", "COLOR" or "C\olor" will all be equivalent
:param replace:
if True (DEFAULT) the given property will replace a present
property. If False a new property will be added always.
The difference to `normalize` is that two or more properties with
the same name may be set, useful for e.g. stuff like::
background: red;
background: rgba(255, 0, 0, 0.5);
which defines the same property but only capable UAs use the last
property value, older ones use the first value.
: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()
if isinstance(name, Property):
newp = name
name = newp.literalname
elif not value:
# empty string or None effectively removed property
return self.removeProperty(name)
else:
newp = Property(name, value, priority)
if newp.wellformed:
if replace:
# check if update
nname = self._normalize(name)
properties = self.getProperties(name, all=(not normalize))
for property in reversed(properties):
if normalize and property.name == nname:
property.cssValue = newp.cssValue.cssText
property.priority = newp.priority
return
elif property.literalname == name:
property.cssValue = newp.cssValue.cssText
property.priority = newp.priority
return
# not yet set or forced omit replace
newp.parent = self
self.seq._readonly = False
self.seq.append(newp, 'Property')
self.seq._readonly = True
else:
self._log.warn(u'Invalid Property: %s: %s %s'
% (name, value, priority))
def item(self, index):
"""(DOM) Retrieve the properties that have been explicitly set in
this declaration block. The order of the properties 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 properties in this
declaration block.
:param index:
of the property to retrieve, negative values behave like
negative indexes on Python lists, so -1 is the last element
:returns:
the name of the property at this ordinal position. The
empty string if no property exists at this position.
**ATTENTION:**
Only properties with different names are counted. If two
properties with the same name are present in this declaration
only the effective one is included.
:meth:`item` and :attr:`length` work on the same set here.
"""
names = list(self.__nnames())
try:
return names[index]
except IndexError:
return u''
length = property(lambda self: len(list(self.__nnames())),
doc=u"(DOM) The number of distinct properties that have "
u"been explicitly in this declaration block. The "
u"range of valid indices is 0 to length-1 inclusive. "
u"These are properties with a different ``name`` "
u"only. :meth:`item` and :attr:`length` work on the "
u"same set here.")
def _getValidating(self):
try:
# CSSParser.parseX() sets validating of stylesheet
return self.parentRule.parentStyleSheet.validating
except AttributeError:
# CSSParser.parseStyle() sets validating of declaration
if self._validating is not None:
return self._validating
# default
return True
def _setValidating(self, validating):
self._validating = validating
validating = property(_getValidating, _setValidating,
doc=u"If ``True`` this declaration validates "
u"contained properties. The parent StyleSheet "
u"validation setting does *always* win though so "
u"even if validating is True it may not validate "
u"if the StyleSheet defines else!")

234
libs/cssutils/css/cssstylerule.py

@ -0,0 +1,234 @@
"""CSSStyleRule implements DOM Level 2 CSS CSSStyleRule."""
__all__ = ['CSSStyleRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssstyledeclaration import CSSStyleDeclaration
from selectorlist import SelectorList
import cssrule
import cssutils
import xml.dom
class CSSStyleRule(cssrule.CSSRule):
"""The CSSStyleRule object represents a ruleset specified (if any) in a CSS
style sheet. It provides access to a declaration block as well as to the
associated group of selectors.
Format::
: selector [ COMMA S* selector ]*
LBRACE S* declaration [ ';' S* declaration ]* '}' S*
;
"""
def __init__(self, selectorText=None, style=None, parentRule=None,
parentStyleSheet=None, readonly=False):
"""
:Parameters:
selectorText
string parsed into selectorList
style
string parsed into CSSStyleDeclaration for this CSSStyleRule
readonly
if True allows setting of properties in constructor only
"""
super(CSSStyleRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self.selectorList = SelectorList()
if selectorText:
self.selectorText = selectorText
if style:
self.style = style
else:
self.style = CSSStyleDeclaration()
self._readonly = readonly
def __repr__(self):
if self._namespaces:
st = (self.selectorText, self._namespaces)
else:
st = self.selectorText
return u"cssutils.css.%s(selectorText=%r, style=%r)" % (
self.__class__.__name__, st, self.style.cssText)
def __str__(self):
return u"<cssutils.css.%s object selectorText=%r style=%r _namespaces=%r "\
u"at 0x%x>" % (self.__class__.__name__,
self.selectorText,
self.style.cssText,
self._namespaces,
id(self))
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSStyleRule(self)
def _setCssText(self, cssText):
"""
:param cssText:
a parseable string or a tuple of (cssText, dict-of-namespaces)
:exceptions:
- :exc:`~xml.dom.NamespaceErr`:
Raised if the specified selector uses an unknown namespace
prefix.
- :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.
"""
super(CSSStyleRule, self)._setCssText(cssText)
# might be (cssText, namespaces)
cssText, namespaces = self._splitNamespacesOff(cssText)
try:
# use parent style sheet ones if available
namespaces = self.parentStyleSheet.namespaces
except AttributeError:
pass
tokenizer = self._tokenize2(cssText)
selectortokens = self._tokensupto2(tokenizer, blockstartonly=True)
styletokens = self._tokensupto2(tokenizer, blockendonly=True)
trail = self._nexttoken(tokenizer)
if trail:
self._log.error(u'CSSStyleRule: Trailing content: %s' %
self._valuestr(cssText), token=trail)
elif not selectortokens:
self._log.error(u'CSSStyleRule: No selector found: %r' %
self._valuestr(cssText))
elif self._tokenvalue(selectortokens[0]).startswith(u'@'):
self._log.error(u'CSSStyleRule: No style rule: %r' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
newSelectorList = SelectorList(parentRule=self)
newStyle = CSSStyleDeclaration(parentRule=self)
ok = True
bracetoken = selectortokens.pop()
if self._tokenvalue(bracetoken) != u'{':
ok = False
self._log.error(
u'CSSStyleRule: No start { of style declaration found: %r' %
self._valuestr(cssText), bracetoken)
elif not selectortokens:
ok = False
self._log.error(u'CSSStyleRule: No selector found: %r.' %
self._valuestr(cssText), bracetoken)
# SET
newSelectorList.selectorText = (selectortokens,
namespaces)
if not styletokens:
ok = False
self._log.error(
u'CSSStyleRule: No style declaration or "}" found: %r' %
self._valuestr(cssText))
else:
braceorEOFtoken = styletokens.pop()
val, typ = self._tokenvalue(braceorEOFtoken),\
self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF':
ok = False
self._log.error(u'CSSStyleRule: No "}" after style '
u'declaration found: %r'
% self._valuestr(cssText))
else:
if 'EOF' == typ:
# add again as style needs it
styletokens.append(braceorEOFtoken)
# SET, may raise:
newStyle.cssText = styletokens
if ok:
self.selectorList = newSelectorList
self.style = newStyle
cssText = property(_getCssText, _setCssText,
doc=u"(DOM) The parsable textual representation of this "
u"rule.")
def __getNamespaces(self):
"""Uses children namespaces if not attached to a sheet, else the sheet's
ones."""
try:
return self.parentStyleSheet.namespaces
except AttributeError:
return self.selectorList._namespaces
_namespaces = property(__getNamespaces,
doc=u"If this Rule is attached to a CSSStyleSheet "
u"the namespaces of that sheet are mirrored "
u"here. While the Rule is not attached the "
u"namespaces of selectorList are used.""")
def _setSelectorList(self, selectorList):
"""
:param selectorList: A SelectorList which replaces the current
selectorList object
"""
self._checkReadonly()
selectorList._parentRule = self
self._selectorList = selectorList
_selectorList = None
selectorList = property(lambda self: self._selectorList, _setSelectorList,
doc=u"The SelectorList of this rule.")
def _setSelectorText(self, selectorText):
"""
wrapper for cssutils SelectorList object
:param selectorText:
of type string, might also be a comma separated list
of selectors
:exceptions:
- :exc:`~xml.dom.NamespaceErr`:
Raised if the specified selector uses an unknown namespace
prefix.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error
and is unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this rule is readonly.
"""
self._checkReadonly()
sl = SelectorList(selectorText=selectorText, parentRule=self)
if sl.wellformed:
self._selectorList = sl
selectorText = property(lambda self: self._selectorList.selectorText,
_setSelectorText,
doc=u"(DOM) The textual representation of the "
u"selector for the rule set.")
def _setStyle(self, style):
"""
:param style: A string or CSSStyleDeclaration which replaces the
current style object.
"""
self._checkReadonly()
if isinstance(style, basestring):
self._style = CSSStyleDeclaration(cssText=style, parentRule=self)
else:
style._parentRule = self
self._style = style
style = property(lambda self: self._style, _setStyle,
doc=u"(DOM) The declaration-block of this rule set.")
type = property(lambda self: self.STYLE_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
"type constant.")
wellformed = property(lambda self: self.selectorList.wellformed)

804
libs/cssutils/css/cssstylesheet.py

@ -0,0 +1,804 @@
"""CSSStyleSheet implements DOM Level 2 CSS CSSStyleSheet.
Partly also:
- http://dev.w3.org/csswg/cssom/#the-cssstylesheet
- http://www.w3.org/TR/2006/WD-css3-namespace-20060828/
TODO:
- ownerRule and ownerNode
"""
__all__ = ['CSSStyleSheet']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssutils.helper import Deprecated
from cssutils.util import _Namespaces, _SimpleNamespaces, _readUrl
from cssrule import CSSRule
from cssvariablesdeclaration import CSSVariablesDeclaration
import cssutils.stylesheets
import xml.dom
class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
"""CSSStyleSheet represents a CSS style sheet.
Format::
stylesheet
: [ CHARSET_SYM S* STRING S* ';' ]?
[S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
[ namespace [S|CDO|CDC]* ]* # according to @namespace WD
[ [ ruleset | media | page ] [S|CDO|CDC]* ]*
``cssRules``
All Rules in this style sheet, a :class:`~cssutils.css.CSSRuleList`.
"""
def __init__(self, href=None, media=None, title=u'', disabled=None,
ownerNode=None, parentStyleSheet=None, readonly=False,
ownerRule=None,
validating=True):
"""
For parameters see :class:`~cssutils.stylesheets.StyleSheet`
"""
super(CSSStyleSheet, self).__init__(
'text/css', href, media, title, disabled,
ownerNode, parentStyleSheet,
validating=validating)
self._ownerRule = ownerRule
self.cssRules = cssutils.css.CSSRuleList()
self._namespaces = _Namespaces(parentStyleSheet=self, log=self._log)
self._variables = CSSVariablesDeclaration()
self._readonly = readonly
# used only during setting cssText by parse*()
self.__encodingOverride = None
self._fetcher = None
def __iter__(self):
"Generator which iterates over cssRules."
for rule in self._cssRules:
yield rule
def __repr__(self):
if self.media:
mediaText = self.media.mediaText
else:
mediaText = None
return "cssutils.css.%s(href=%r, media=%r, title=%r)" % (
self.__class__.__name__,
self.href, mediaText, self.title)
def __str__(self):
if self.media:
mediaText = self.media.mediaText
else:
mediaText = None
return "<cssutils.css.%s object encoding=%r href=%r "\
"media=%r title=%r namespaces=%r at 0x%x>" % (
self.__class__.__name__, self.encoding, self.href,
mediaText, self.title, self.namespaces.namespaces,
id(self))
def _cleanNamespaces(self):
"Remove all namespace rules with same namespaceURI but last."
rules = self.cssRules
namespaceitems = self.namespaces.items()
i = 0
while i < len(rules):
rule = rules[i]
if rule.type == rule.NAMESPACE_RULE and \
(rule.prefix, rule.namespaceURI) not in namespaceitems:
self.deleteRule(i)
else:
i += 1
def _getUsedURIs(self):
"Return set of URIs used in the sheet."
useduris = set()
for r1 in self:
if r1.STYLE_RULE == r1.type:
useduris.update(r1.selectorList._getUsedUris())
elif r1.MEDIA_RULE == r1.type:
for r2 in r1:
if r2.type == r2.STYLE_RULE:
useduris.update(r2.selectorList._getUsedUris())
return useduris
def _setCssRules(self, cssRules):
"Set new cssRules and update contained rules refs."
cssRules.append = self.insertRule
cssRules.extend = self.insertRule
cssRules.__delitem__ = self.deleteRule
for rule in cssRules:
rule._parentStyleSheet = self
self._cssRules = cssRules
cssRules = property(lambda self: self._cssRules, _setCssRules,
u"All Rules in this style sheet, a "
u":class:`~cssutils.css.CSSRuleList`.")
def _getCssText(self):
"Textual representation of the stylesheet (a byte string)."
return cssutils.ser.do_CSSStyleSheet(self)
def _setCssText(self, cssText):
"""Parse `cssText` and overwrites the whole stylesheet.
:param cssText:
a parseable string or a tuple of (cssText, dict-of-namespaces)
:exceptions:
- :exc:`~xml.dom.NamespaceErr`:
If a namespace prefix is found which is not declared.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
"""
self._checkReadonly()
cssText, namespaces = self._splitNamespacesOff(cssText)
tokenizer = self._tokenize2(cssText)
def S(expected, seq, token, tokenizer=None):
# @charset must be at absolute beginning of style sheet
# or 0 for py3
return max(1, expected or 0)
def COMMENT(expected, seq, token, tokenizer=None):
"special: sets parent*"
self.insertRule(cssutils.css.CSSComment([token],
parentStyleSheet=self))
# or 0 for py3
return max(1, expected or 0)
def charsetrule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSCharsetRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if expected > 0:
self._log.error(u'CSSStylesheet: CSSCharsetRule only allowed '
u'at beginning of stylesheet.',
token, xml.dom.HierarchyRequestErr)
return expected
elif rule.wellformed:
self.insertRule(rule)
return 1
def importrule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSImportRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if expected > 1:
self._log.error(u'CSSStylesheet: CSSImportRule not allowed '
u'here.', token, xml.dom.HierarchyRequestErr)
return expected
elif rule.wellformed:
self.insertRule(rule)
return 1
def namespacerule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSNamespaceRule(cssText=self._tokensupto2(tokenizer,
token),
parentStyleSheet=self)
if expected > 2:
self._log.error(u'CSSStylesheet: CSSNamespaceRule not allowed '
u'here.', token, xml.dom.HierarchyRequestErr)
return expected
elif rule.wellformed:
if rule.prefix not in self.namespaces:
# add new if not same prefix
self.insertRule(rule, _clean=False)
else:
# same prefix => replace namespaceURI
for r in self.cssRules.rulesOfType(rule.NAMESPACE_RULE):
if r.prefix == rule.prefix:
r._replaceNamespaceURI(rule.namespaceURI)
self._namespaces[rule.prefix] = rule.namespaceURI
return 2
def variablesrule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSVariablesRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if expected > 2:
self._log.error(u'CSSStylesheet: CSSVariablesRule not allowed '
u'here.', token, xml.dom.HierarchyRequestErr)
return expected
elif rule.wellformed:
self.insertRule(rule)
self._updateVariables()
return 2
def fontfacerule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSFontFaceRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
self.insertRule(rule)
return 3
def mediarule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSMediaRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
self.insertRule(rule)
return 3
def pagerule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSPageRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
self.insertRule(rule)
return 3
def unknownrule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
if token[1] in cssutils.css.MarginRule.margins:
self._log.error(u'CSSStylesheet: MarginRule out CSSPageRule.',
token, neverraise=True)
rule = cssutils.css.MarginRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
else:
self._log.warn(u'CSSStylesheet: Unknown @rule found.',
token, neverraise=True)
rule = cssutils.css.CSSUnknownRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
self.insertRule(rule)
# or 0 for py3
return max(1, expected or 0)
def ruleset(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSStyleRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
self.insertRule(rule)
return 3
# save for possible reset
oldCssRules = self.cssRules
oldNamespaces = self._namespaces
self.cssRules = cssutils.css.CSSRuleList()
# simple during parse
self._namespaces = namespaces
self._variables = CSSVariablesDeclaration()
# not used?!
newseq = []
# ['CHARSET', 'IMPORT', ('VAR', NAMESPACE'), ('PAGE', 'MEDIA', ruleset)]
wellformed, expected = self._parse(0, newseq, tokenizer,
{'S': S,
'COMMENT': COMMENT,
'CDO': lambda *ignored: None,
'CDC': lambda *ignored: None,
'CHARSET_SYM': charsetrule,
'FONT_FACE_SYM': fontfacerule,
'IMPORT_SYM': importrule,
'NAMESPACE_SYM': namespacerule,
'PAGE_SYM': pagerule,
'MEDIA_SYM': mediarule,
'VARIABLES_SYM': variablesrule,
'ATKEYWORD': unknownrule
},
default=ruleset)
if wellformed:
# use proper namespace object
self._namespaces = _Namespaces(parentStyleSheet=self, log=self._log)
self._cleanNamespaces()
else:
# reset
self._cssRules = oldCssRules
self._namespaces = oldNamespaces
self._updateVariables()
self._cleanNamespaces()
cssText = property(_getCssText, _setCssText,
"Textual representation of the stylesheet (a byte string)")
def _resolveImport(self, url):
"""Read (encoding, enctype, decodedContent) from `url` for @import
sheets."""
try:
# only available during parsing of a complete sheet
parentEncoding = self.__newEncoding
except AttributeError:
try:
# explicit @charset
parentEncoding = self._cssRules[0].encoding
except (IndexError, AttributeError):
# default not UTF-8 but None!
parentEncoding = None
return _readUrl(url, fetcher=self._fetcher,
overrideEncoding=self.__encodingOverride,
parentEncoding=parentEncoding)
def _setCssTextWithEncodingOverride(self, cssText, encodingOverride=None,
encoding=None):
"""Set `cssText` but use `encodingOverride` to overwrite detected
encoding. This is used by parse and @import during setting of cssText.
If `encoding` is given use this but do not save as `encodingOverride`.
"""
if encodingOverride:
# encoding during resolving of @import
self.__encodingOverride = encodingOverride
if encoding:
# save for nested @import
self.__newEncoding = encoding
self.cssText = cssText
if encodingOverride:
# set encodingOverride explicit again!
self.encoding = self.__encodingOverride
# del?
self.__encodingOverride = None
elif encoding:
# may e.g. be httpEncoding
self.encoding = encoding
try:
del self.__newEncoding
except AttributeError, e:
pass
def _setFetcher(self, fetcher=None):
"""Set @import URL loader, if None the default is used."""
self._fetcher = fetcher
def _getEncoding(self):
"""Encoding set in :class:`~cssutils.css.CSSCharsetRule` or if ``None``
resulting in default ``utf-8`` encoding being used."""
try:
return self._cssRules[0].encoding
except (IndexError, AttributeError):
return 'utf-8'
def _setEncoding(self, encoding):
"""Set `encoding` of charset rule if present in sheet or insert a new
:class:`~cssutils.css.CSSCharsetRule` with given `encoding`.
If `encoding` is None removes charsetrule if present resulting in
default encoding of utf-8.
"""
try:
rule = self._cssRules[0]
except IndexError:
rule = None
if rule and rule.CHARSET_RULE == rule.type:
if encoding:
rule.encoding = encoding
else:
self.deleteRule(0)
elif encoding:
self.insertRule(cssutils.css.CSSCharsetRule(encoding=encoding), 0)
encoding = property(_getEncoding, _setEncoding,
"(cssutils) Reflect encoding of an @charset rule or 'utf-8' "
"(default) if set to ``None``")
namespaces = property(lambda self: self._namespaces,
doc="All Namespaces used in this CSSStyleSheet.")
def _updateVariables(self):
"""Updates self._variables, called when @import or @variables rules
is added to sheet.
"""
for r in self.cssRules.rulesOfType(CSSRule.IMPORT_RULE):
s = r.styleSheet
if s:
for var in s.variables:
self._variables.setVariable(var, s.variables[var])
# for r in self.cssRules.rulesOfType(CSSRule.IMPORT_RULE):
# for vr in r.styleSheet.cssRules.rulesOfType(CSSRule.VARIABLES_RULE):
# for var in vr.variables:
# self._variables.setVariable(var, vr.variables[var])
for vr in self.cssRules.rulesOfType(CSSRule.VARIABLES_RULE):
for var in vr.variables:
self._variables.setVariable(var, vr.variables[var])
variables = property(lambda self: self._variables,
doc=u"A :class:`cssutils.css.CSSVariablesDeclaration` "
u"containing all available variables in this "
u"CSSStyleSheet including the ones defined in "
u"imported sheets.")
def add(self, rule):
"""Add `rule` to style sheet at appropriate position.
Same as ``insertRule(rule, inOrder=True)``.
"""
return self.insertRule(rule, index=None, inOrder=True)
def deleteRule(self, index):
"""Delete rule at `index` from the style sheet.
:param index:
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
the style sheet's rule list.
- :exc:`~xml.dom.NamespaceErr`:
Raised if removing this rule would result in an invalid StyleSheet
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this style sheet is readonly.
"""
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:
raise xml.dom.IndexSizeErr(
u'CSSStyleSheet: %s is not a valid index in the rulelist of '
u'length %i' % (index, self._cssRules.length))
else:
if rule.type == rule.NAMESPACE_RULE:
# check all namespacerules if used
uris = [r.namespaceURI for r in self
if r.type == r.NAMESPACE_RULE]
useduris = self._getUsedURIs()
if rule.namespaceURI in useduris and\
uris.count(rule.namespaceURI) == 1:
raise xml.dom.NoModificationAllowedErr(
u'CSSStyleSheet: NamespaceURI defined in this rule is '
u'used, cannot remove.')
return
rule._parentStyleSheet = None # detach
del self._cssRules[index] # delete from StyleSheet
def insertRule(self, rule, index=None, inOrder=False, _clean=True):
"""
Used to insert a new rule into the style sheet. The new rule now
becomes part of the cascade.
:param rule:
a parsable DOMString, in cssutils also a
:class:`~cssutils.css.CSSRule` or :class:`~cssutils.css.CSSRuleList`
:param index:
of the rule before the new rule will be inserted.
If the specified `index` is equal to the length of the
StyleSheet's rule collection, the rule will be added to the end
of the style sheet.
If `index` is not given or ``None`` rule will be appended to rule
list.
:param inOrder:
if ``True`` the rule will be put to a proper location while
ignoring `index` and without raising
:exc:`~xml.dom.HierarchyRequestErr`.
The resulting index is returned nevertheless.
:returns: The index within the style sheet's rule collection
:Exceptions:
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at the specified `index`
e.g. if an @import rule is inserted after a standard rule set
or other at-rule.
- :exc:`~xml.dom.IndexSizeErr`:
Raised if the specified `index` is not a valid insertion point.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this style sheet is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified rule has a syntax error and is
unparsable.
"""
self._checkReadonly()
# check position
if index is None:
index = len(self._cssRules)
elif index < 0 or index > self._cssRules.length:
raise xml.dom.IndexSizeErr(
u'CSSStyleSheet: Invalid index %s for CSSRuleList with a '
u'length of %s.' % (index, self._cssRules.length))
return
if isinstance(rule, basestring):
# init a temp sheet which has the same properties as self
tempsheet = CSSStyleSheet(href=self.href,
media=self.media,
title=self.title,
parentStyleSheet=self.parentStyleSheet,
ownerRule=self.ownerRule)
tempsheet._ownerNode = self.ownerNode
tempsheet._fetcher = self._fetcher
# prepend encoding if in this sheet to be able to use it in
# @import rules encoding resolution
# do not add if new rule startswith "@charset" (which is exact!)
if not rule.startswith(u'@charset') and (self._cssRules and
self._cssRules[0].type == self._cssRules[0].CHARSET_RULE):
# rule 0 is @charset!
newrulescount, newruleindex = 2, 1
rule = self._cssRules[0].cssText + rule
else:
newrulescount, newruleindex = 1, 0
# parse the new rule(s)
tempsheet.cssText = (rule, self._namespaces)
if len(tempsheet.cssRules) != newrulescount or (not isinstance(
tempsheet.cssRules[newruleindex], cssutils.css.CSSRule)):
self._log.error(u'CSSStyleSheet: Not a CSSRule: %s' % rule)
return
rule = tempsheet.cssRules[newruleindex]
rule._parentStyleSheet = None # done later?
# TODO:
#tempsheet._namespaces = self._namespaces
#variables?
elif isinstance(rule, cssutils.css.CSSRuleList):
# insert all rules
for i, r in enumerate(rule):
self.insertRule(r, index + i)
return index
if not rule.wellformed:
self._log.error(u'CSSStyleSheet: Invalid rules cannot be added.')
return
# CHECK HIERARCHY
# @charset
if rule.type == rule.CHARSET_RULE:
if inOrder:
index = 0
# always first and only
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.',
error=xml.dom.HierarchyRequestErr)
return
else:
self._cssRules.insert(index, rule)
# @unknown or comment
elif rule.type in (rule.UNKNOWN_RULE, rule.COMMENT) and not inOrder:
if index == 0 and self._cssRules and\
self._cssRules[0].type == rule.CHARSET_RULE:
self._log.error(
u'CSSStylesheet: @charset must be the first rule.',
error=xml.dom.HierarchyRequestErr)
return
else:
self._cssRules.insert(index, rule)
# @import
elif rule.type == rule.IMPORT_RULE:
if inOrder:
# automatic order
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
if self._cssRules and\
self._cssRules[0].type in (rule.CHARSET_RULE,
rule.COMMENT):
index = 1
else:
index = 0
else:
# after @charset
if index == 0 and self._cssRules and\
self._cssRules[0].type == rule.CHARSET_RULE:
self._log.error(
u'CSSStylesheet: Found @charset at index 0.',
error=xml.dom.HierarchyRequestErr)
return
# before @namespace @variables @page @font-face @media stylerule
for r in self._cssRules[:index]:
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, @variables, @media, @page or'
' CSSStyleRule before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
self._cssRules.insert(index, rule)
self._updateVariables()
# @namespace
elif rule.type == rule.NAMESPACE_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.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:
# after @charset and @import
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.' %
index,
error=xml.dom.HierarchyRequestErr)
return
# before @variables @media @page @font-face and stylerule
for r in self._cssRules[:index]:
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 @variables, @media, @page or CSSStyleRule'
' before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
if not (rule.prefix in self.namespaces and
self.namespaces[rule.prefix] == rule.namespaceURI):
# no doublettes
self._cssRules.insert(index, rule)
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)
self._updateVariables()
# all other where order is not important
else:
if inOrder:
# simply add to end as no specific order
self._cssRules.append(rule)
index = len(self._cssRules) - 1
else:
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 rule here, found '
u'@charset, @import or @namespace before index %s.'
% index, error=xml.dom.HierarchyRequestErr)
return
self._cssRules.insert(index, rule)
# post settings
rule._parentStyleSheet = self
if rule.IMPORT_RULE == rule.type and not rule.hrefFound:
# try loading the imported sheet which has new relative href now
rule.href = rule.href
return index
ownerRule = property(lambda self: self._ownerRule,
doc=u'A ref to an @import rule if it is imported, '
u'else ``None``.')
@Deprecated(u'Use ``cssutils.setSerializer(serializer)`` instead.')
def setSerializer(self, cssserializer):
"""Set the cssutils global Serializer used for all output."""
if isinstance(cssserializer, cssutils.CSSSerializer):
cssutils.ser = cssserializer
else:
raise ValueError(u'Serializer must be an instance of '
u'cssutils.CSSSerializer.')
@Deprecated(u'Set pref in ``cssutils.ser.prefs`` instead.')
def setSerializerPref(self, pref, value):
"""Set a Preference of CSSSerializer used for output.
See :class:`cssutils.serialize.Preferences` for possible
preferences to be set.
"""
cssutils.ser.prefs.__setattr__(pref, value)

209
libs/cssutils/css/cssunknownrule.py

@ -0,0 +1,209 @@
"""CSSUnknownRule implements DOM Level 2 CSS CSSUnknownRule."""
__all__ = ['CSSUnknownRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssrule
import cssutils
import xml.dom
class CSSUnknownRule(cssrule.CSSRule):
"""
Represents an at-rule not supported by this user agent, so in
effect all other at-rules not defined in cssutils.
Format::
@xxx until ';' or block {...}
"""
def __init__(self, cssText=u'', parentRule=None,
parentStyleSheet=None, readonly=False):
"""
:param cssText:
of type string
"""
super(CSSUnknownRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = None
if cssText:
self.cssText = cssText
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(cssText=%r)" % (
self.__class__.__name__,
self.cssText)
def __str__(self):
return u"<cssutils.css.%s object cssText=%r at 0x%x>" % (
self.__class__.__name__,
self.cssText,
id(self))
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSUnknownRule(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.
"""
super(CSSUnknownRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if not attoken or self._type(attoken) != self._prods.ATKEYWORD:
self._log.error(u'CSSUnknownRule: No CSSUnknownRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# for closures: must be a mutable
new = {'nesting': [], # {} [] or ()
'wellformed': True
}
def CHAR(expected, seq, token, tokenizer=None):
type_, val, line, col = token
if expected != 'EOF':
if val in u'{[(':
new['nesting'].append(val)
elif val in u'}])':
opening = {u'}': u'{', u']': u'[', u')': u'('}[val]
try:
if new['nesting'][-1] == opening:
new['nesting'].pop()
else:
raise IndexError()
except IndexError:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Wrong nesting of '
u'{, [ or (.', token=token)
if val in u'};' and not new['nesting']:
expected = 'EOF'
seq.append(val, type_, line=line, col=col)
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Expected end of rule.',
token=token)
return expected
def FUNCTION(expected, seq, token, tokenizer=None):
# handled as opening (
type_, val, line, col = token
val = self._tokenvalue(token)
if expected != 'EOF':
new['nesting'].append(u'(')
seq.append(val, type_, line=line, col=col)
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Expected end of rule.',
token=token)
return expected
def EOF(expected, seq, token, tokenizer=None):
"close all blocks and return 'EOF'"
for x in reversed(new['nesting']):
closing = {u'{': u'}', u'[': u']', u'(': u')'}[x]
seq.append(closing, closing)
new['nesting'] = []
return 'EOF'
def INVALID(expected, seq, token, tokenizer=None):
# makes rule invalid
self._log.error(u'CSSUnknownRule: Bad syntax.',
token=token, error=xml.dom.SyntaxErr)
new['wellformed'] = False
return expected
def STRING(expected, seq, token, tokenizer=None):
type_, val, line, col = token
val = self._stringtokenvalue(token)
if expected != 'EOF':
seq.append(val, type_, line=line, col=col)
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Expected end of rule.',
token=token)
return expected
def URI(expected, seq, token, tokenizer=None):
type_, val, line, col = token
val = self._uritokenvalue(token)
if expected != 'EOF':
seq.append(val, type_, line=line, col=col)
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Expected end of rule.',
token=token)
return expected
def default(expected, seq, token, tokenizer=None):
type_, val, line, col = token
if expected != 'EOF':
seq.append(val, type_, line=line, col=col)
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Expected end of rule.',
token=token)
return expected
# unknown : ATKEYWORD S* ... ; | }
newseq = self._tempSeq()
wellformed, expected = self._parse(expected=None,
seq=newseq, tokenizer=tokenizer,
productions={'CHAR': CHAR,
'EOF': EOF,
'FUNCTION': FUNCTION,
'INVALID': INVALID,
'STRING': STRING,
'URI': URI,
'S': default # overwrite default default!
},
default=default,
new=new)
# wellformed set by parse
wellformed = wellformed and new['wellformed']
# post conditions
if expected != 'EOF':
wellformed = False
self._log.error(u'CSSUnknownRule: No ending ";" or "}" found: '
u'%r' % self._valuestr(cssText))
elif new['nesting']:
wellformed = False
self._log.error(u'CSSUnknownRule: Unclosed "{", "[" or "(": %r'
% self._valuestr(cssText))
# set all
if wellformed:
self.atkeyword = self._tokenvalue(attoken)
self._setSeq(newseq)
cssText = property(fget=_getCssText, fset=_setCssText,
doc=u"(DOM) The parsable textual representation.")
type = property(lambda self: self.UNKNOWN_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
wellformed = property(lambda self: bool(self.atkeyword))

1251
libs/cssutils/css/cssvalue.py

File diff suppressed because it is too large

330
libs/cssutils/css/cssvariablesdeclaration.py

@ -0,0 +1,330 @@
"""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 cssutils.helper import normalize
from value import PropertyValue
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
Format::
variableset
: vardeclaration [ ';' S* vardeclaration ]* S*
;
vardeclaration
: varname ':' S* term
;
varname
: IDENT S*
;
"""
super(CSSVariablesDeclaration, self).__init__()
self._parentRule = parentRule
self._vars = {}
if cssText:
self.cssText = cssText
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(cssText=%r)" % (self.__class__.__name__,
self.cssText)
def __str__(self):
return u"<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 normalize(variableName) in self.keys()
def __getitem__(self, variableName):
"""Retrieve the value of variable ``variableName`` from this
declaration.
"""
return self.getVariableValue(variableName)
def __setitem__(self, variableName, value):
self.setVariable(variableName, value)
def __delitem__(self, variableName):
return self.removeVariable(variableName)
def __iter__(self):
"""Iterator of names of set variables."""
for name in self.keys():
yield name
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',
PropertyValue(itertools.chain([t],
tokens),
parent=self)
)
)
)
prods = Sequence(vardeclaration,
Sequence(PreDef.S(optional=True),
PreDef.char(u';', u';', toSeq=False),
PreDef.S(optional=True),
vardeclaration,
minmax=lambda: (0, None)),
PreDef.S(optional=True),
PreDef.char(u';', u';', toSeq=False, optional=True)
)
# parse
wellformed, seq, store, notused = \
ProdParser().parse(cssText,
u'CSSVariableDeclaration',
prods)
if wellformed:
newseq = self._tempSeq()
newvars = {}
# seq contains only name: value pairs plus comments etc
nameitem = None
for item in seq:
if u'IDENT' == item.type:
nameitem = item
elif u'value' == item.type:
nname = normalize(nameitem.value)
if nname in newvars:
# replace var with same name
for i, it in enumerate(newseq):
if normalize(it.value[0]) == nname:
newseq.replace(i,
(nameitem.value, item.value),
'var',
nameitem.line, nameitem.col)
else:
# saved non normalized name for reserialization
newseq.append((nameitem.value, item.value),
'var',
nameitem.line, nameitem.col)
# newseq.append((nameitem.value, item.value),
# 'var',
# nameitem.line, nameitem.col)
newvars[nname] = item.value
else:
newseq.appendItem(item)
self._setSeq(newseq)
self._vars = newvars
self.wellformed = True
cssText = property(_getCssText, _setCssText,
doc=u"(DOM) A parsable textual representation of the declaration "
u"block excluding the surrounding curly braces.")
def _setParentRule(self, parentRule):
self._parentRule = parentRule
parentRule = property(lambda self: self._parentRule, _setParentRule,
doc=u"(DOM) The CSS rule that contains this"
u" declaration block or None if this block"
u" 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[normalize(variableName)].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.
"""
normalname = variableName
try:
r = self._vars[normalname]
except KeyError, e:
return u''
else:
self.seq._readonly = False
if normalname 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[normalname]
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 PropertyValue 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(normalize(variableName),
u'variableName',
Sequence(PreDef.ident()))
if not wellformed:
self._log.error(u'Invalid variableName: %r: %r'
% (variableName, value))
else:
# check value
if isinstance(value, PropertyValue):
v = value
else:
v = PropertyValue(cssText=value, parent=self)
if not v.wellformed:
self._log.error(u'Invalid variable value: %r: %r'
% (variableName, value))
else:
# update seq
self.seq._readonly = False
variableName = normalize(variableName)
if variableName in self._vars:
for i, x in enumerate(self.seq):
if x.value[0] == variableName:
self.seq.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=u"The number of variables that have been explicitly set in this"
u" variable declaration block. The range of valid indices is 0"
u" to length-1 inclusive.")

198
libs/cssutils/css/cssvariablesrule.py

@ -0,0 +1,198 @@
"""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.
Format::
variables
VARIABLES_SYM S* medium [ COMMA S* medium ]* LBRACE S*
variableset* '}' S*
;
for variableset see :class:`cssutils.css.CSSVariablesDeclaration`
**Media are not implemented. Reason is that cssutils is using CSS
variables in a kind of preprocessing and therefor no media information
is available at this stage. For now do not use media!**
Example::
@variables {
CorporateLogoBGColor: #fe8d12;
}
div.logoContainer {
background-color: var(CorporateLogoBGColor);
}
"""
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'
# dummy
self._media = cssutils.stylesheets.MediaList(mediaText,
readonly=readonly)
if variables:
self.variables = variables
else:
self.variables = CSSVariablesDeclaration(parentRule=self)
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(mediaText=%r, variables=%r)" % (
self.__class__.__name__,
self._media.mediaText,
self.variables.cssText)
def __str__(self):
return u"<cssutils.css.%s object mediaText=%r variables=%r valid=%r " \
u"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:
newVariables = CSSVariablesDeclaration(parentRule=self)
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 '
u'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, type_ = self._tokenvalue(braceorEOFtoken), \
self._type(braceorEOFtoken)
if val != u'}' and type_ != 'EOF':
ok = False
self._log.error(u'CSSVariablesRule: No "}" after variables '
u'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' == type_:
# add again as variables needs it
variablestokens.append(braceorEOFtoken)
# SET but may raise:
newVariables.cssText = variablestokens
if ok:
# contains probably comments only upto {
self._setSeq(newseq)
self.variables = newVariables
cssText = property(_getCssText, _setCssText,
doc=u"(DOM) The parsable textual representation of this "
u"rule.")
media = property(doc=u"NOT IMPLEMENTED! As cssutils resolves variables "\
u"during serializing media information is lost.")
def _setVariables(self, variables):
"""
:param variables:
a CSSVariablesDeclaration or string
"""
self._checkReadonly()
if isinstance(variables, basestring):
self._variables = CSSVariablesDeclaration(cssText=variables,
parentRule=self)
else:
variables._parentRule = self
self._variables = variables
variables = property(lambda self: self._variables, _setVariables,
doc=u"(DOM) The variables of this rule set, a "
u":class:`cssutils.css.CSSVariablesDeclaration`.")
type = property(lambda self: self.VARIABLES_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
valid = property(lambda self: True, doc='NOT IMPLEMTED REALLY (TODO)')
# constant but needed:
wellformed = property(lambda self: True)

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save