Sync to trunk.

This commit is contained in:
John Schember 2009-08-16 13:26:02 -04:00
commit d4121a7cfe
61 changed files with 16688 additions and 22636 deletions

View File

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

View File

@ -42,7 +42,6 @@ class Device(DeviceConfig, DevicePlugin):
WINDOWS_MAIN_MEM = None
WINDOWS_CARD_A_MEM = None
WINDOWS_CARD_B_MEM = None
ALLOW_NO_MAIN_MEMORY = False
# The following are used by the check_ioreg_line method and can be either:
# None, a string, a list of strings or a compiled regular expression
@ -267,9 +266,6 @@ class Device(DeviceConfig, DevicePlugin):
drives['cardb'] = self.windows_get_drive_prefix(drive)
elif self.windows_match_device(drive, 'WINDOWS_MAIN_MEM') and not drives.get('main', None):
drives['main'] = self.windows_get_drive_prefix(drive)
if not self.ALLOW_NO_MAIN_MEMORY:
raise DeviceError('Failed to find the drive corresponding'
' to the main memory')
if 'main' in drives.keys() and 'carda' in drives.keys() and \
'cardb' in drives.keys():
@ -277,7 +273,7 @@ class Device(DeviceConfig, DevicePlugin):
drives = self.windows_open_callback(drives)
if 'main' not in drives:
if drives.get('main', None) is None:
raise DeviceError(
_('Unable to detect the %s disk drive. Try rebooting.') %
self.__class__.__name__)

View File

@ -317,7 +317,13 @@ class MobiReader(object):
if root.xpath('descendant::p/descendant::p'):
from lxml.html import soupparser
self.log.warning('Malformed markup, parsing using BeautifulSoup')
root = soupparser.fromstring(self.processed_html)
try:
root = soupparser.fromstring(self.processed_html)
except Exception, err:
self.log.warning('MOBI markup appears to contain random bytes. Stripping.')
self.processed_html = self.remove_random_bytes(self.processed_html)
root = soupparser.fromstring(self.processed_html)
if root.tag != 'html':
self.log.warn('File does not have opening <html> tag')
@ -457,7 +463,7 @@ class MobiReader(object):
self.processed_html = self.processed_html.replace('<mbp: ', '<mbp:')
def remove_random_bytes(self, html):
return re.sub('\x14|\x15|\x1c|\x1d|\xef|\x12|\x13|\xec',
return re.sub('\x14|\x15|\x19|\x1c|\x1d|\xef|\x12|\x13|\xec|\x08',
'', html)
def ensure_unit(self, raw, unit='px'):
@ -492,11 +498,12 @@ class MobiReader(object):
styles.append(style)
if attrib.has_key('height'):
height = attrib.pop('height').strip()
if height:
if height and '<' not in height and '>' not in height and \
re.search(r'\d+', height):
styles.append('margin-top: %s' % self.ensure_unit(height))
if attrib.has_key('width'):
width = attrib.pop('width').strip()
if width:
if width and re.search(r'\d+', width):
styles.append('text-indent: %s' % self.ensure_unit(width))
if width.startswith('-'):
styles.append('margin-left: %s' % self.ensure_unit(width[1:]))
@ -714,6 +721,9 @@ class MobiReader(object):
self.processed_html += self.mobi_html[pos:end] + (anchor % oend)
pos = end
self.processed_html += self.mobi_html[pos:]
# Remove anchors placed inside entities
self.processed_html = re.sub(r'&([^;]*?)(<a id="filepos\d+"></a>)([^;]*);',
r'&\1\3;\2', self.processed_html)
def extract_images(self, processed_records, output_dir):

View File

@ -14,6 +14,9 @@ class LinearizeTables(object):
for x in root.xpath('//h:table|//h:td|//h:tr|//h:th',
namespaces=XPNSMAP):
x.tag = 'div'
for attr in ('valign', 'colspan', 'rowspan', 'width', 'halign'):
if attr in x.attrib:
del x.attrib[attr]
def __call__(self, oeb, context):
for x in oeb.manifest.items:

View File

@ -1,27 +1,26 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import urllib, re, traceback
import traceback
from PyQt4.QtCore import QThread, SIGNAL
import mechanize
from calibre import __version__, __appname__
from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.constants import __version__
from calibre import browser
URL = 'http://status.calibre-ebook.com/latest'
class CheckForUpdates(QThread):
def run(self):
try:
src = urllib.urlopen('http://pypi.python.org/pypi/'+__appname__).read()
soup = BeautifulSoup(src)
meta = soup.find('link', rel='meta', title='DOAP')
if meta:
src = meta['href']
match = re.search(r'version=(\S+)', src)
if match:
version = match.group(1)
if version != __version__:
self.emit(SIGNAL('update_found(PyQt_PyObject)'), version)
br = browser()
req = mechanize.Request(URL)
req.add_header('CALIBRE_VERSION', __version__)
version = br.open(req).read().strip()
if version and version != __version__:
self.emit(SIGNAL('update_found(PyQt_PyObject)'), version)
except:
traceback.print_exc()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -4,9 +4,9 @@
#
msgid ""
msgstr ""
"Project-Id-Version: calibre 0.6.6\n"
"POT-Creation-Date: 2009-08-13 12:30+MDT\n"
"PO-Revision-Date: 2009-08-13 12:30+MDT\n"
"Project-Id-Version: calibre 0.6.7\n"
"POT-Creation-Date: 2009-08-14 21:48+MDT\n"
"PO-Revision-Date: 2009-08-14 21:48+MDT\n"
"Last-Translator: Automatically generated\n"
"Language-Team: LANGUAGE\n"
"MIME-Version: 1.0\n"
@ -118,7 +118,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/library.py:404
#: /home/kovid/work/calibre/src/calibre/gui2/library.py:874
#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1000
#: /home/kovid/work/calibre/src/calibre/library/cli.py:264
#: /home/kovid/work/calibre/src/calibre/library/cli.py:265
#: /home/kovid/work/calibre/src/calibre/library/database.py:917
#: /home/kovid/work/calibre/src/calibre/library/database2.py:655
#: /home/kovid/work/calibre/src/calibre/library/database2.py:667
@ -127,9 +127,9 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1429
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1431
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1515
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1604
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1632
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1683
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1606
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1634
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1685
#: /home/kovid/work/calibre/src/calibre/library/server.py:309
#: /home/kovid/work/calibre/src/calibre/library/server.py:373
#: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:45
@ -1465,7 +1465,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1308
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:48
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:51
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:168
msgid "Table of Contents"
msgstr ""
@ -2240,7 +2240,7 @@ msgid "&Monospaced font family:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:23
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:142
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:145
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:169
msgid "Metadata"
msgstr ""
@ -3745,7 +3745,7 @@ msgid "Scheduled"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:240
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:218
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:221
msgid "Search"
msgstr ""
@ -4346,7 +4346,7 @@ msgid "<b>No matches</b> for the search phrase <i>%s</i> were found."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:157
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:417
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:420
msgid "No matches found"
msgstr ""
@ -5185,7 +5185,7 @@ msgid "Options to customize the ebook viewer"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:59
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:646
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:649
msgid "Remember last used window size"
msgstr ""
@ -5234,95 +5234,95 @@ msgstr ""
msgid "The standard font type"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:143
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:146
msgid "Book format"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:166
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:169
msgid "Go to..."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:207
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:210
msgid "Position in book"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:208
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:211
msgid "/Unknown"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:213
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:216
msgid "Go to a reference. To get reference numbers, use the reference mode."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:219
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:222
msgid "Search for text in book"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:282
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:285
msgid "Print Preview"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:377
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:380
msgid "Choose ebook"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:378
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:381
msgid "Ebooks"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:397
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:400
msgid "Add bookmark"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:397
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:400
msgid "Enter title for bookmark:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:418
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:421
msgid "No matches found for: %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:458
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:461
msgid "Loading flow..."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:485
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:488
msgid "Laying out %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:514
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:517
msgid "Manage Bookmarks"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:549
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:552
msgid "Loading ebook..."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:557
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:560
msgid "DRM Error"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:558
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:561
msgid "<p>This book is protected by <a href=\"%s\">DRM</a>"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:562
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:565
msgid "Could not open ebook"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:636
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:639
msgid "Options to control the ebook viewer"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:643
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:646
msgid "If specified, viewer window will try to come to the front when started."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:648
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:651
msgid "Print javascript alert and console messages to the console"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:654
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:657
msgid ""
"%prog [options] file\n"
"\n"
@ -5706,64 +5706,64 @@ msgstr ""
msgid "Path to the calibre library. Default is to use the path stored in the settings."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:188
#: /home/kovid/work/calibre/src/calibre/library/cli.py:189
msgid ""
"%prog list [options]\n"
"\n"
"List the books available in the calibre database.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:196
#: /home/kovid/work/calibre/src/calibre/library/cli.py:197
msgid ""
"The fields to display when listing books in the database. Should be a comma separated list of fields.\n"
"Available fields: %s\n"
"Default: %%default. The special field \"all\" can be used to select all fields. Only has effect in the text output format."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:198
#: /home/kovid/work/calibre/src/calibre/library/cli.py:199
msgid ""
"The field by which to sort the results.\n"
"Available fields: %s\n"
"Default: %%default"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:200
#: /home/kovid/work/calibre/src/calibre/library/cli.py:201
msgid "Sort results in ascending order"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:202
#: /home/kovid/work/calibre/src/calibre/library/cli.py:203
msgid "Filter the results by the search query. For the format of the search query, please see the search related documentation in the User Manual. Default is to do no filtering."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:204
#: /home/kovid/work/calibre/src/calibre/library/cli.py:205
msgid "The maximum width of a single line in the output. Defaults to detecting screen size."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:205
#: /home/kovid/work/calibre/src/calibre/library/cli.py:206
msgid "The string used to separate fields. Default is a space."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:206
#: /home/kovid/work/calibre/src/calibre/library/cli.py:207
msgid "The prefix for all file paths. Default is the absolute path to the library folder."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:209
#: /home/kovid/work/calibre/src/calibre/library/cli.py:210
msgid "The format in which to output the data. Available choices: %s. Defaults is text."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:217
#: /home/kovid/work/calibre/src/calibre/library/cli.py:218
msgid "Invalid fields. Available fields:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:224
#: /home/kovid/work/calibre/src/calibre/library/cli.py:225
msgid "Invalid sort field. Available fields:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:295
#: /home/kovid/work/calibre/src/calibre/library/cli.py:296
msgid "The following books were not added as they already exist in the database (see --duplicates option):"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:321
#: /home/kovid/work/calibre/src/calibre/library/cli.py:322
msgid ""
"%prog add [options] file1 file2 file3 ...\n"
"\n"
@ -5771,49 +5771,49 @@ msgid ""
"the directory related options below.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:330
#: /home/kovid/work/calibre/src/calibre/library/cli.py:331
msgid "Assume that each directory has only a single logical book and that all files in it are different e-book formats of that book"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:332
#: /home/kovid/work/calibre/src/calibre/library/cli.py:333
msgid "Process directories recursively"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:334
#: /home/kovid/work/calibre/src/calibre/library/cli.py:335
msgid "Add books to database even if they already exist. Comparison is done based on book titles."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:339
#: /home/kovid/work/calibre/src/calibre/library/cli.py:340
msgid "You must specify at least one file to add"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:357
#: /home/kovid/work/calibre/src/calibre/library/cli.py:358
msgid ""
"%prog remove ids\n"
"\n"
"Remove the books identified by ids from the database. ids should be a comma separated list of id numbers (you can get id numbers by using the list command). For example, 23,34,57-85\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:369
#: /home/kovid/work/calibre/src/calibre/library/cli.py:370
msgid "You must specify at least one book to remove"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:389
#: /home/kovid/work/calibre/src/calibre/library/cli.py:390
msgid ""
"%prog add_format [options] id ebook_file\n"
"\n"
"Add the ebook in ebook_file to the available formats for the logical book identified by id. You can get id by using the list command. If the format already exists, it is replaced.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:400
#: /home/kovid/work/calibre/src/calibre/library/cli.py:401
msgid "You must specify an id and an ebook file"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:405
#: /home/kovid/work/calibre/src/calibre/library/cli.py:406
msgid "ebook file must have an extension"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:413
#: /home/kovid/work/calibre/src/calibre/library/cli.py:414
msgid ""
"\n"
"%prog remove_format [options] id fmt\n"
@ -5821,11 +5821,11 @@ msgid ""
"Remove the format fmt from the logical book identified by id. You can get id by using the list command. fmt should be a file extension like LRF or TXT or EPUB. If the logical book does not have fmt available, do nothing.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:426
#: /home/kovid/work/calibre/src/calibre/library/cli.py:427
msgid "You must specify an id and a format"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:444
#: /home/kovid/work/calibre/src/calibre/library/cli.py:445
msgid ""
"\n"
"%prog show_metadata [options] id\n"
@ -5834,15 +5834,15 @@ msgid ""
"id is an id number from the list command.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:452
#: /home/kovid/work/calibre/src/calibre/library/cli.py:453
msgid "Print metadata in OPF form (XML)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:457
#: /home/kovid/work/calibre/src/calibre/library/cli.py:458
msgid "You must specify an id"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:471
#: /home/kovid/work/calibre/src/calibre/library/cli.py:472
msgid ""
"\n"
"%prog set_metadata [options] id /path/to/metadata.opf\n"
@ -5853,11 +5853,11 @@ msgid ""
"show_metadata command.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:484
#: /home/kovid/work/calibre/src/calibre/library/cli.py:485
msgid "You must specify an id and a metadata file"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:496
#: /home/kovid/work/calibre/src/calibre/library/cli.py:497
msgid ""
"%prog export [options] ids\n"
"\n"
@ -5866,27 +5866,27 @@ msgid ""
"an opf file). You can get id numbers from the list command.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:504
#: /home/kovid/work/calibre/src/calibre/library/cli.py:505
msgid "Export all books in database, ignoring the list of ids."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:506
#: /home/kovid/work/calibre/src/calibre/library/cli.py:507
msgid "Export books to the specified directory. Default is"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:508
#: /home/kovid/work/calibre/src/calibre/library/cli.py:509
msgid "Export all books into a single directory"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:510
#: /home/kovid/work/calibre/src/calibre/library/cli.py:511
msgid "Create file names as author - title instead of title - author"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:515
#: /home/kovid/work/calibre/src/calibre/library/cli.py:516
msgid "You must specify some ids or the %s option"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:525
#: /home/kovid/work/calibre/src/calibre/library/cli.py:526
msgid ""
"%%prog command [options] [arguments]\n"
"\n"
@ -5898,15 +5898,15 @@ msgid ""
"For help on an individual command: %%prog command --help\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1539
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1541
msgid "<p>Migrating old database to ebook library in %s<br><center>"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1568
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1570
msgid "Copying <b>%s</b>"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1585
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1587
msgid "Compacting database"
msgstr ""
@ -6257,6 +6257,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_nspm_int.py:17
#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_nytimes.py:17
#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_nytimes_sub.py:17
#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_ourdailybread.py:16
#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_outlook_india.py:17
#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_phd_comics.py:16
#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_physics_today.py:11

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -83,11 +83,12 @@ else:
if _lib is None:
_lib = util.find_library('Wand')
_magick = _magick_error = None
_magick = None
_magick_error = None
try:
_magick = ctypes.CDLL(_lib)
except Exception, err:
global _magick_error
#global _magick_error
_magick_error = str(err)
_initialized = False

View File

@ -210,6 +210,12 @@ class BasicNewsRecipe(Recipe):
#: tags before the first element with `id="content"`.
remove_tags_before = None
#: List of attributes to remove from all tags
#: For example::
#:
#: remove_attributes = ['style', 'font']
remove_attributes = []
#: Keep only the specified tags and their children.
#: For the format for specifying a tag see :attr:`BasicNewsRecipe.remove_tags`.
#: If this list is not empty, then the `<body>` tag will be emptied and re-filled with
@ -562,6 +568,9 @@ class BasicNewsRecipe(Recipe):
script.extract()
for script in list(soup.findAll('noscript')):
script.extract()
for attr in self.remove_attributes:
for x in soup.findAll(attrs={attr:True}):
del x[attr]
return self.postprocess_html(soup, first_fetch)

View File

@ -11,14 +11,28 @@ from calibre.web.feeds.news import BasicNewsRecipe
class TheAtlantic(BasicNewsRecipe):
title = 'The Atlantic'
__author__ = 'Kovid Goyal'
__author__ = 'Kovid Goyal and Sujata Raman'
description = 'Current affairs and politics focussed on the US'
INDEX = 'http://www.theatlantic.com/doc/current'
language = _('English')
remove_tags_before = dict(name='div', id='storytop')
remove_tags = [dict(name='div', id=['seealso', 'storybottom', 'footer', 'ad_banner_top', 'sidebar'])]
remove_tags = [
dict(name='div', id=['seealso','storybottom', 'footer', 'ad_banner_top', 'sidebar','articletoolstop','subcontent',]),
dict(name='p', attrs={'id':["pagination"]}),
dict(name='table',attrs={'class':"tools"}),
dict(name='a', href='/a/newsletters.mhtml')
]
no_stylesheets = True
extra_css = '''
#timestamp{font-family:Arial,Helvetica,sans-serif; color:#666666 ;font-size:x-small}
#storytype{font-family:Arial,Helvetica,sans-serif; color:#D52B1E ;font-weight:bold; font-size:x-small}
h2{font-family:georgia,serif; font-style:italic;font-size:x-small;font-weight:normal;}
h1{font-family:georgia,serif; font-weight:bold; font-size:large}
#byline{font-family:georgia,serif; font-weight:bold; font-size:x-small}
#topgraf{font-family:Arial,Helvetica,sans-serif;font-size:x-small;font-weight:bold;}
.artsans{{font-family:Arial,Helvetica,sans-serif;font-size:x-small;}
'''
def parse_index(self):
articles = []
@ -35,18 +49,21 @@ class TheAtlantic(BasicNewsRecipe):
for item in soup.findAll('div', attrs={'class':'item'}):
a = item.find('a')
if a and a.has_key('href'):
url = a['href'].replace('/doc', 'doc/print')
url = a['href']#.replace('/doc', 'doc/print')
if not url.startswith('http://'):
url = 'http://www.theatlantic.com/'+url
title = self.tag_to_string(a)
byline = item.find(attrs={'class':'byline'})
date = self.tag_to_string(byline) if byline else ''
description = ''
articles.append({
'title':title,
'date':date,
'url':url,
'description':description
})
})
return [('Current Issue', articles)]

View File

@ -70,11 +70,11 @@ Usage may be::
__all__ = ['css', 'stylesheets', 'CSSParser', 'CSSSerializer']
__docformat__ = 'restructuredtext'
__author__ = 'Christof Hoeke with contributions by Walter Doerwald'
__date__ = '$LastChangedDate:: 2009-05-09 13:59:54 -0700 #$:'
__date__ = '$LastChangedDate:: 2009-08-01 16:10:11 -0600 #$:'
VERSION = '0.9.6a5'
VERSION = '0.9.6b3'
__version__ = '%s $Id: __init__.py 1747 2009-05-09 20:59:54Z cthedot $' % VERSION
__version__ = '%s $Id: __init__.py 1832 2009-08-01 22:10:11Z cthedot $' % VERSION
import codec
import xml.dom
@ -165,6 +165,23 @@ def parse(*a, **k):
return parseFile(*a, **k)
parse.__doc__ = CSSParser.parse.__doc__
def parseStyle(cssText, encoding='utf-8'):
"""Parse given `cssText` which is assumed to be the content of
a HTML style attribute.
:param cssText:
CSS string to parse
:param encoding:
It will be used to decode `cssText` if given as a (byte)
string.
:returns:
:class:`~cssutils.css.CSSStyleDeclaration`
"""
if isinstance(cssText, str):
cssText = cssText.decode(encoding)
style = css.CSSStyleDeclaration()
style.cssText = cssText
return style
# set "ser", default serializer
def setSerializer(serializer):
@ -172,7 +189,6 @@ def setSerializer(serializer):
global ser
ser = serializer
def getUrls(sheet):
"""Retrieve all ``url(urlstring)`` values (in e.g.
:class:`cssutils.css.CSSImportRule` or :class:`cssutils.css.CSSValue`
@ -284,5 +300,6 @@ def resolveImports(sheet, target=None):
target.add(rule)
return target
if __name__ == '__main__':
print __doc__

View File

@ -1,8 +1,9 @@
"""Default URL reading functions"""
__all__ = ['_defaultFetcher', '_readUrl']
__all__ = ['_defaultFetcher']
__docformat__ = 'restructuredtext'
__version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $'
from cssutils import VERSION
import encutils
import errorhandler
import urllib2
@ -16,8 +17,11 @@ def _defaultFetcher(url):
Returns ``(encoding, string)`` or ``None``
"""
request = urllib2.Request(url)
request.add_header('User-agent',
'cssutils %s (http://www.cthedot.de/cssutils/)' % VERSION)
try:
res = urllib2.urlopen(url)
res = urllib2.urlopen(request)
except OSError, e:
# e.g if file URL and not found
log.warn(e, error=OSError)

View File

@ -1,5 +1,5 @@
"""GAE specific URL reading functions"""
__all__ = ['_defaultFetcher', '_readUrl']
__all__ = ['_defaultFetcher']
__docformat__ = 'restructuredtext'
__version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $'

View File

@ -1,8 +1,11 @@
"""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: cssfontfacerule.py 1638 2009-01-13 20:39:33Z cthedot $'
__version__ = '$Id: cssfontfacerule.py 1818 2009-07-30 21:39:00Z cthedot $'
from cssstyledeclaration import CSSStyleDeclaration
import cssrule
@ -21,6 +24,11 @@ class CSSFontFaceRule(cssrule.CSSRule):
: 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):
@ -28,15 +36,15 @@ class CSSFontFaceRule(cssrule.CSSRule):
If readonly allows setting of properties in constructor only.
:param style:
CSSStyleDeclaration for this CSSStyleRule
CSSStyleDeclaration used to hold any font descriptions
for this CSSFontFaceRule
"""
super(CSSFontFaceRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@font-face'
self._style = CSSStyleDeclaration(parentRule=self)
if style:
self.style = style
else:
self._style = CSSStyleDeclaration(parentRule=self)
self._readonly = readonly
@ -45,8 +53,9 @@ class CSSFontFaceRule(cssrule.CSSRule):
self.__class__.__name__, self.style.cssText)
def __str__(self):
return "<cssutils.css.%s object style=%r at 0x%x>" % (
self.__class__.__name__, self.style.cssText, id(self))
return "<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."""
@ -112,15 +121,22 @@ class CSSFontFaceRule(cssrule.CSSRule):
self._log.error(u'CSSFontFaceRule: Trailing content found.',
token=nonetoken)
newstyle = CSSStyleDeclaration()
teststyle = CSSStyleDeclaration(parentRule=self)
if 'EOF' == typ:
# add again as style needs it
styletokens.append(braceorEOFtoken)
newstyle.cssText = styletokens
# may raise:
teststyle.cssText = styletokens
if wellformed:
self.style = newstyle
self._setSeq(newseq) # contains (probably comments) upto { only
# contains probably comments only upto {
self._setSeq(newseq)
# known as correct from before
cssutils.log.enabled = False
self.style.cssText = styletokens
cssutils.log.enabled = True
cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.")
@ -132,9 +148,10 @@ class CSSFontFaceRule(cssrule.CSSRule):
"""
self._checkReadonly()
if isinstance(style, basestring):
self._style = CSSStyleDeclaration(parentRule=self, cssText=style)
self._style.cssText = style
else:
self._style._seq = style.seq
self._style = style
self._style.parentRule = self
style = property(lambda self: self._style, _setStyle,
doc="(DOM) The declaration-block of this rule set, "
@ -144,5 +161,20 @@ class CSSFontFaceRule(cssrule.CSSRule):
doc="The type of this rule, as defined by a CSSRule "
"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='CSSFontFace is valid if properties '
'`font-family` and `src` are set and all properties are '
'valid.')
# constant but needed:
wellformed = property(lambda self: True)

View File

@ -1,10 +1,8 @@
"""CSSImportRule implements DOM Level 2 CSS CSSImportRule plus the
``name`` property from http://www.w3.org/TR/css3-cascade/#cascading.
"""
``name`` property from http://www.w3.org/TR/css3-cascade/#cascading."""
__all__ = ['CSSImportRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssimportrule.py 1638 2009-01-13 20:39:33Z cthedot $'
__version__ = '$Id: cssimportrule.py 1824 2009-08-01 21:00:34Z cthedot $'
import cssrule
import cssutils
@ -320,7 +318,8 @@ class CSSImportRule(cssrule.CSSRule):
parentHref = self.parentStyleSheet.href
if parentHref is None:
# use cwd instead
parentHref = u'file:' + urllib.pathname2url(os.getcwd()) + '/'
#parentHref = u'file:' + urllib.pathname2url(os.getcwd()) + '/'
parentHref = cssutils.helper.path2url(os.getcwd()) + '/'
href = urlparse.urljoin(parentHref, self.href)
# all possible exceptions are ignored (styleSheet is None then)

View File

@ -1,7 +1,7 @@
"""CSSMediaRule implements DOM Level 2 CSS CSSMediaRule."""
__all__ = ['CSSMediaRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssmediarule.py 1743 2009-05-09 20:33:15Z cthedot $'
__version__ = '$Id: cssmediarule.py 1820 2009-08-01 20:53:08Z cthedot $'
import cssrule
import cssutils
@ -34,15 +34,11 @@ class CSSMediaRule(cssrule.CSSRule):
readonly=readonly)
self.name = name
self.cssRules = cssutils.css.cssrulelist.CSSRuleList()
self.cssRules.append = self.insertRule
self.cssRules.extend = self.insertRule
self.cssRules.__delitem__ == self.deleteRule
self._readonly = readonly
def __iter__(self):
"""Generator iterating over these rule's cssRules."""
for rule in self.cssRules:
for rule in self._cssRules:
yield rule
def __repr__(self):
@ -53,6 +49,20 @@ class CSSMediaRule(cssrule.CSSRule):
return "<cssutils.css.%s object mediaText=%r at 0x%x>" % (
self.__class__.__name__, self.media.mediaText, id(self))
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.parentStyleSheet
rule._parentRule = self
self._cssRules = cssRules
cssRules = property(lambda self: self._cssRules, _setCssRules,
"All Rules in this style sheet, a "
":class:`~cssutils.css.CSSRuleList`.")
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSMediaRule(self)
@ -204,9 +214,9 @@ class CSSMediaRule(cssrule.CSSRule):
self._media.mediaText = newmedia.mediaText
self.name = name
self._setSeq(nameseq)
del self.cssRules[:]
del self._cssRules[:]
for r in newcssrules:
self.cssRules.append(r)
self._cssRules.append(r)
cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.")
@ -245,12 +255,12 @@ class CSSMediaRule(cssrule.CSSRule):
self._checkReadonly()
try:
self.cssRules[index]._parentRule = None # detach
del self.cssRules[index] # remove from @media
self._cssRules[index]._parentRule = None # detach
del self._cssRules[index] # remove from @media
except IndexError:
raise xml.dom.IndexSizeErr(
u'CSSMediaRule: %s is not a valid index in the rulelist of length %i' % (
index, self.cssRules.length))
index, self._cssRules.length))
def add(self, rule):
"""Add `rule` to end of this mediarule.
@ -300,11 +310,11 @@ class CSSMediaRule(cssrule.CSSRule):
# check position
if index is None:
index = len(self.cssRules)
elif index < 0 or index > self.cssRules.length:
index = len(self._cssRules)
elif index < 0 or index > self._cssRules.length:
raise xml.dom.IndexSizeErr(
u'CSSMediaRule: Invalid index %s for CSSRuleList with a length of %s.' % (
index, self.cssRules.length))
index, self._cssRules.length))
# parse
if isinstance(rule, basestring):
@ -315,6 +325,13 @@ class CSSMediaRule(cssrule.CSSRule):
self._log.error(u'CSSMediaRule: Invalid Rule: %s' % rule)
return
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 index
elif not isinstance(rule, cssutils.css.CSSRule):
self._log.error(u'CSSMediaRule: Not a CSSRule: %s' % rule)
return
@ -332,7 +349,7 @@ class CSSMediaRule(cssrule.CSSRule):
error=xml.dom.HierarchyRequestErr)
return
self.cssRules.insert(index, rule)
self._cssRules.insert(index, rule)
rule._parentRule = self
rule._parentStyleSheet = self.parentStyleSheet
return index

View File

@ -1,8 +1,7 @@
"""CSSPageRule implements DOM Level 2 CSS CSSPageRule.
"""
"""CSSPageRule implements DOM Level 2 CSS CSSPageRule."""
__all__ = ['CSSPageRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: csspagerule.py 1658 2009-02-07 18:24:40Z cthedot $'
__version__ = '$Id: csspagerule.py 1824 2009-08-01 21:00:34Z cthedot $'
from cssstyledeclaration import CSSStyleDeclaration
from selectorlist import SelectorList
@ -45,11 +44,12 @@ class CSSPageRule(cssrule.CSSRule):
tempseq.append(self.selectorText, 'selectorText')
else:
self._selectorText = self._tempSeq()
self._style = CSSStyleDeclaration(parentRule=self)
if style:
self.style = style
tempseq.append(self.style, 'style')
else:
self._style = CSSStyleDeclaration(parentRule=self)
self._setSeq(tempseq)
self._readonly = readonly
@ -192,7 +192,7 @@ class CSSPageRule(cssrule.CSSRule):
wellformed, newselectorseq = self.__parseSelectorText(selectortokens)
newstyle = CSSStyleDeclaration()
teststyle = CSSStyleDeclaration(parentRule=self)
val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF':
wellformed = False
@ -203,12 +203,14 @@ class CSSPageRule(cssrule.CSSRule):
if 'EOF' == typ:
# add again as style needs it
styletokens.append(braceorEOFtoken)
newstyle.cssText = styletokens
teststyle.cssText = styletokens
if wellformed:
self._selectorText = newselectorseq # already parsed
self.style = newstyle
self._setSeq(newselectorseq) # contains upto style only
# known as correct from before
cssutils.log.enabled = False
self._selectorText = newselectorseq # TODO: TEST and REFS
self.style.cssText = styletokens
cssutils.log.enabled = True
cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.")
@ -239,7 +241,7 @@ class CSSPageRule(cssrule.CSSRule):
# may raise SYNTAX_ERR
wellformed, newseq = self.__parseSelectorText(selectorText)
if wellformed and newseq:
if wellformed:
self._selectorText = newseq
selectorText = property(_getSelectorText, _setSelectorText,
@ -251,19 +253,16 @@ class CSSPageRule(cssrule.CSSRule):
a CSSStyleDeclaration or string
"""
self._checkReadonly()
if isinstance(style, basestring):
self._style.cssText = style
else:
# cssText would be serialized with optional preferences
# so use seq!
self._style._seq = style.seq
self._style = style
self._style.parentRule = self
style = property(lambda self: self._style, _setStyle,
doc="(DOM) The declaration-block of this rule set, "
"a :class:`~cssutils.css.CSSStyleDeclaration`.")
type = property(lambda self: self.PAGE_RULE,
doc="The type of this rule, as defined by a CSSRule "
"type constant.")

View File

@ -1,7 +1,7 @@
"""CSSRule implements DOM Level 2 CSS CSSRule."""
__all__ = ['CSSRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssrule.py 1638 2009-01-13 20:39:33Z cthedot $'
__version__ = '$Id: cssrule.py 1808 2009-07-29 13:09:36Z cthedot $'
import cssutils
import xml.dom
@ -35,6 +35,7 @@ class CSSRule(cssutils.util.Base2):
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())
@ -78,6 +79,10 @@ class CSSRule(cssutils.util.Base2):
"rule. This reflects the current state of the rule "
"and not its initial value.")
parent = property(lambda self: self._parent,
doc="The Parent Node of this CSSRule (currently if a "
"CSSStyleDeclaration only!) or None.")
parentRule = property(lambda self: self._parentRule,
doc="If this rule is contained inside "
"another rule (e.g. a style rule inside "

View File

@ -1,9 +1,8 @@
"""CSSRuleList implements DOM Level 2 CSS CSSRuleList.
Partly also http://dev.w3.org/csswg/cssom/#the-cssrulelist
"""
Partly also http://dev.w3.org/csswg/cssom/#the-cssrulelist."""
__all__ = ['CSSRuleList']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssrulelist.py 1641 2009-01-13 21:05:37Z cthedot $'
__version__ = '$Id: cssrulelist.py 1824 2009-08-01 21:00:34Z cthedot $'
class CSSRuleList(list):
"""The CSSRuleList object represents an (ordered) list of statements.

View File

@ -51,7 +51,7 @@ TODO:
"""
__all__ = ['CSSStyleDeclaration', 'Property']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstyledeclaration.py 1710 2009-04-18 15:46:20Z cthedot $'
__version__ = '$Id: cssstyledeclaration.py 1819 2009-08-01 20:52:43Z cthedot $'
from cssproperties import CSS2Properties
from property import Property
@ -128,6 +128,11 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
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.
@ -247,6 +252,13 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
"""
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)
@ -275,7 +287,7 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
semicolon=True)
if self._tokenvalue(tokens[-1]) == u';':
tokens.pop()
property = Property()
property = Property(parent=self)
property.cssText = tokens
if property.wellformed:
seq.append(property, 'Property')
@ -301,10 +313,11 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
productions={'IDENT': ident},#, 'CHAR': char},
default=unexpected)
# wellformed set by parse
# post conditions
for item in newseq:
item.value._parent = self
# do not check wellformed as invalid things are removed anyway
#if wellformed:
self._setSeq(newseq)
cssText = property(_getCssText, _setCssText,
@ -322,13 +335,12 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
"""
return cssutils.ser.do_css_CSSStyleDeclaration(self, separator)
def _getParentRule(self):
return self._parentRule
def _setParentRule(self, parentRule):
self._parentRule = parentRule
for x in self.children():
x.parent = self
parentRule = property(_getParentRule, _setParentRule,
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.")
@ -581,6 +593,7 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
property.priority = newp.priority
break
else:
newp.parent = self
self.seq._readonly = False
self.seq.append(newp, 'Property')
self.seq._readonly = True

View File

@ -1,7 +1,7 @@
"""CSSStyleRule implements DOM Level 2 CSS CSSStyleRule."""
__all__ = ['CSSStyleRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstylerule.py 1638 2009-01-13 20:39:33Z cthedot $'
__version__ = '$Id: cssstylerule.py 1815 2009-07-29 16:51:58Z cthedot $'
from cssstyledeclaration import CSSStyleDeclaration
from selectorlist import SelectorList
@ -107,6 +107,8 @@ class CSSStyleRule(cssrule.CSSRule):
else:
wellformed = True
testselectorlist, teststyle = None, None
bracetoken = selectortokens.pop()
if self._tokenvalue(bracetoken) != u'{':
wellformed = False
@ -117,11 +119,11 @@ class CSSStyleRule(cssrule.CSSRule):
wellformed = False
self._log.error(u'CSSStyleRule: No selector found: %r.' %
self._valuestr(cssText), bracetoken)
newselectorlist = SelectorList(selectorText=(selectortokens,
testselectorlist = SelectorList(selectorText=(selectortokens,
namespaces),
parentRule=self)
newstyle = CSSStyleDeclaration()
if not styletokens:
wellformed = False
self._log.error(
@ -139,11 +141,14 @@ class CSSStyleRule(cssrule.CSSRule):
if 'EOF' == typ:
# add again as style needs it
styletokens.append(braceorEOFtoken)
newstyle.cssText = styletokens
teststyle = CSSStyleDeclaration(styletokens, parentRule=self)
if wellformed:
self._selectorList = newselectorlist
self.style = newstyle
if wellformed and testselectorlist and teststyle:
# known as correct from before
cssutils.log.enabled = False
self.style.cssText = styletokens
self.selectorList.selectorText=(selectortokens, namespaces)
cssutils.log.enabled = True
cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.")
@ -164,11 +169,12 @@ class CSSStyleRule(cssrule.CSSRule):
def _setSelectorList(self, selectorList):
"""
:param selectorList: selectorList, only content is used, not the actual
object
:param selectorList: A SelectorList which replaces the current
selectorList object
"""
self._checkReadonly()
self.selectorText = selectorList.selectorText
selectorList._parentRule = self
self._selectorList = selectorList
selectorList = property(lambda self: self._selectorList, _setSelectorList,
doc="The SelectorList of this rule.")
@ -200,16 +206,15 @@ class CSSStyleRule(cssrule.CSSRule):
def _setStyle(self, style):
"""
:param style: CSSStyleDeclaration or string, only the cssText of a
declaration is used, not the actual object
:param style: A string or CSSStyleDeclaration which replaces the
current style object.
"""
self._checkReadonly()
if isinstance(style, basestring):
self._style.cssText = style
else:
# cssText would be serialized with optional preferences
# so use _seq!
self._style._seq = style._seq
style._parentRule = self
self._style = style
style = property(lambda self: self._style, _setStyle,
doc="(DOM) The declaration-block of this rule set.")

View File

@ -9,7 +9,7 @@ TODO:
"""
__all__ = ['CSSStyleSheet']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstylesheet.py 1641 2009-01-13 21:05:37Z cthedot $'
__version__ = '$Id: cssstylesheet.py 1820 2009-08-01 20:53:08Z cthedot $'
from cssutils.helper import Deprecated
from cssutils.util import _Namespaces, _SimpleNamespaces, _readUrl
@ -42,8 +42,6 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
self._ownerRule = ownerRule
self.cssRules = cssutils.css.CSSRuleList()
self.cssRules.append = self.insertRule
self.cssRules.extend = self.insertRule
self._namespaces = _Namespaces(parentStyleSheet=self, log=self._log)
self._readonly = readonly
@ -53,7 +51,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
def __iter__(self):
"Generator which iterates over cssRules."
for rule in self.cssRules:
for rule in self._cssRules:
yield rule
def __repr__(self):
@ -78,7 +76,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
def _cleanNamespaces(self):
"Remove all namespace rules with same namespaceURI but last one set."
rules = self.cssRules
rules = self._cssRules
namespaceitems = self.namespaces.items()
i = 0
while i < len(rules):
@ -101,6 +99,19 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
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,
"All Rules in this style sheet, a "
":class:`~cssutils.css.CSSRuleList`.")
def _getCssText(self):
"Textual representation of the stylesheet (a byte string)."
return cssutils.ser.do_CSSStyleSheet(self)
@ -260,7 +271,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
default=ruleset)
if wellformed:
del self.cssRules[:]
del self._cssRules[:]
for rule in newseq:
self.insertRule(rule, _clean=False)
self._cleanNamespaces()
@ -277,7 +288,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
except AttributeError:
try:
# explicit @charset
selfAsParentEncoding = self.cssRules[0].encoding
selfAsParentEncoding = self._cssRules[0].encoding
except (IndexError, AttributeError):
# default not UTF-8 but None!
selfAsParentEncoding = None
@ -312,6 +323,14 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
"""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`.
@ -319,7 +338,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
default encoding of utf-8.
"""
try:
rule = self.cssRules[0]
rule = self._cssRules[0]
except IndexError:
rule = None
if rule and rule.CHARSET_RULE == rule.type:
@ -330,14 +349,6 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
elif encoding:
self.insertRule(cssutils.css.CSSCharsetRule(encoding=encoding), 0)
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'
encoding = property(_getEncoding, _setEncoding,
"(cssutils) Reflect encoding of an @charset rule or 'utf-8' "
"(default) if set to ``None``")
@ -371,11 +382,11 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
self._checkReadonly()
try:
rule = self.cssRules[index]
rule = self._cssRules[index]
except IndexError:
raise xml.dom.IndexSizeErr(
u'CSSStyleSheet: %s is not a valid index in the rulelist of length %i' % (
index, self.cssRules.length))
index, self._cssRules.length))
else:
if rule.type == rule.NAMESPACE_RULE:
# check all namespacerules if used
@ -388,7 +399,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
return
rule._parentStyleSheet = None # detach
del self.cssRules[index] # delete from StyleSheet
del self._cssRules[index] # delete from StyleSheet
def insertRule(self, rule, index=None, inOrder=False, _clean=True):
"""
@ -427,11 +438,11 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
# check position
if index is None:
index = len(self.cssRules)
elif index < 0 or index > self.cssRules.length:
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 length of %s.' % (
index, self.cssRules.length))
index, self._cssRules.length))
return
if isinstance(rule, basestring):
@ -447,11 +458,11 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
# 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):
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
rule = self._cssRules[0].cssText + rule
else:
newrulescount, newruleindex = 1, 0
@ -484,29 +495,29 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
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
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._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)
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:
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)
self._cssRules.insert(index, rule)
# @import
elif rule.type == rule.IMPORT_RULE:
@ -514,27 +525,27 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
# 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)):
for i, r in enumerate(reversed(self._cssRules)):
if r.type == rule.type:
index = len(self.cssRules) - i
index = len(self._cssRules) - i
break
else:
# find first point to insert
if self.cssRules and self.cssRules[0].type in (rule.CHARSET_RULE,
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:
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, @page, @font-face, @media and stylerule
for r in self.cssRules[:index]:
for r in self._cssRules[:index]:
if r.type in (r.NAMESPACE_RULE, r.MEDIA_RULE, r.PAGE_RULE,
r.STYLE_RULE, r.FONT_FACE_RULE):
self._log.error(
@ -542,27 +553,27 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
index,
error=xml.dom.HierarchyRequestErr)
return
self.cssRules.insert(index, rule)
self._cssRules.insert(index, rule)
# @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)):
for i, r in enumerate(reversed(self._cssRules)):
if r.type == rule.type:
index = len(self.cssRules) - i
index = len(self._cssRules) - i
break
else:
# find first point to insert
for i, r in enumerate(self.cssRules):
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 and @import
for r in self.cssRules[index:]:
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.' %
@ -570,7 +581,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
error=xml.dom.HierarchyRequestErr)
return
# before @media and stylerule
for r in self.cssRules[:index]:
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(
@ -582,7 +593,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
if not (rule.prefix in self.namespaces and
self.namespaces[rule.prefix] == rule.namespaceURI):
# no doublettes
self.cssRules.insert(index, rule)
self._cssRules.insert(index, rule)
if _clean:
self._cleanNamespaces()
@ -590,17 +601,17 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
else:
if inOrder:
# simply add to end as no specific order
self.cssRules.append(rule)
index = len(self.cssRules) - 1
self._cssRules.append(rule)
index = len(self._cssRules) - 1
else:
for r in self.cssRules[index:]:
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 @charset, @import or @namespace before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
self.cssRules.insert(index, rule)
self._cssRules.insert(index, rule)
# post settings, TODO: for other rules which contain @rules
rule._parentStyleSheet = self

View File

@ -7,7 +7,7 @@
"""
__all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList', 'RGBColor']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssvalue.py 1684 2009-03-01 18:26:21Z cthedot $'
__version__ = '$Id: cssvalue.py 1834 2009-08-02 12:20:21Z cthedot $'
from cssutils.prodparser import *
import cssutils
@ -81,6 +81,7 @@ class CSSValue(cssutils.util._NewBase):
[ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
TIME S* | FREQ S* ]
| STRING S* | IDENT S* | URI S* | hexcolor | function
| UNICODE-RANGE S*
;
function
: FUNCTION S* expr ')' S*
@ -117,22 +118,25 @@ class CSSValue(cssutils.util._NewBase):
PreDef.ident(nextSor=nextSor),
PreDef.uri(nextSor=nextSor),
PreDef.hexcolor(nextSor=nextSor),
PreDef.unicode_range(nextSor=nextSor),
# special case IE only expression
Prod(name='expression',
match=lambda t, v: t == self._prods.FUNCTION and
match=lambda t, v: t == self._prods.FUNCTION and (
cssutils.helper.normalize(v) in (u'expression(',
u'alpha('),
u'alpha(') or
v.startswith(u'progid:DXImageTransform.Microsoft.') ),
nextSor=nextSor,
toSeq=lambda t, tokens: (ExpressionValue.name,
ExpressionValue(cssutils.helper.pushtoken(t,
tokens)))),
tokens)))
),
PreDef.function(nextSor=nextSor,
toSeq=lambda t, tokens: ('FUNCTION',
CSSFunction(cssutils.helper.pushtoken(t,
tokens)))))
operator = Choice(PreDef.S(optional=False, mayEnd=True),
PreDef.CHAR('comma', ',', toSeq=lambda t, tokens: ('operator', t[1])),
PreDef.CHAR('slash', '/', toSeq=lambda t, tokens: ('operator', t[1])),
operator = Choice(PreDef.S(),
PreDef.char('comma', ',', toSeq=lambda t, tokens: ('operator', t[1])),
PreDef.char('slash', '/', toSeq=lambda t, tokens: ('operator', t[1])),
optional=True)
# CSSValue PRODUCTIONS
valueprods = Sequence(term,
@ -154,19 +158,22 @@ class CSSValue(cssutils.util._NewBase):
item = seq[i]
if item.type == self._prods.S:
pass
elif item.value == u',' and item.type not in (self._prods.URI, self._prods.STRING):
# counts as a single one
elif (item.value, item.type) == (u',', 'operator'):
# , separared counts as a single STRING for now
# URI or STRING value might be a single CHAR too!
newseq.appendItem(item)
if firstvalue:
# may be IDENT or STRING but with , it is always STRING
firstvalue = firstvalue[0], 'STRING'
# each comma separated list counts as a single one only
count -= 1
if firstvalue:
# list of IDENTs is handled as STRING!
if firstvalue[1] == self._prods.IDENT:
firstvalue = firstvalue[0], 'STRING'
elif item.value == u'/':
# counts as a single one
# / separated items count as one
newseq.appendItem(item)
elif item.value == u'+' or item.value == u'-':
elif item.value == u'-' or item.value == u'+':
# combine +- and following number or other
i += 1
try:
@ -228,6 +235,8 @@ class CSSValue(cssutils.util._NewBase):
return cssutils.helper.string(item.value)
elif self._prods.URI == item.type:
return cssutils.helper.uri(item.value)
elif self._prods.FUNCTION == item.type:
return item.value.cssText
else:
return item.value
@ -253,7 +262,8 @@ class CSSValue(cssutils.util._NewBase):
self._prods.NUMBER,
self._prods.PERCENTAGE,
self._prods.STRING,
self._prods.URI):
self._prods.URI,
self._prods.UNICODE_RANGE):
if nexttocommalist:
# wait until complete
commalist.append(itemValue(item))
@ -353,6 +363,7 @@ class CSSPrimitiveValue(CSSValue):
CSS_RGBCOLOR = 25
# NOT OFFICIAL:
CSS_RGBACOLOR = 26
CSS_UNICODE_RANGE = 27
_floattypes = (CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS,
CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC,
@ -429,6 +440,7 @@ class CSSPrimitiveValue(CSSValue):
'CSS_STRING', 'CSS_URI', 'CSS_IDENT',
'CSS_ATTR', 'CSS_COUNTER', 'CSS_RECT',
'CSS_RGBCOLOR', 'CSS_RGBACOLOR',
'CSS_UNICODE_RANGE'
]
_reNumDim = re.compile(ur'([+-]?\d*\.\d+|[+-]?\d+)(.*)$', re.I| re.U|re.X)
@ -464,6 +476,7 @@ class CSSPrimitiveValue(CSSValue):
__types.NUMBER: 'CSS_NUMBER',
__types.PERCENTAGE: 'CSS_PERCENTAGE',
__types.STRING: 'CSS_STRING',
__types.UNICODE_RANGE: 'CSS_UNICODE_RANGE',
__types.URI: 'CSS_URI',
__types.IDENT: 'CSS_IDENT',
__types.HASH: 'CSS_RGBCOLOR',
@ -474,7 +487,6 @@ class CSSPrimitiveValue(CSSValue):
def __set_primitiveType(self):
"""primitiveType is readonly but is set lazy if accessed"""
# TODO: check unary and font-family STRING a, b, "c"
val, type_ = self._value
# try get by type_
pt = self.__unitbytype.get(type_, 'CSS_UNKNOWN')
@ -969,23 +981,31 @@ class RGBColor(CSSPrimitiveValue):
class ExpressionValue(CSSFunction):
"""Special IE only CSSFunction which may contain *anything*.
Used for expressions and ``alpha(opacity=100)`` currently"""
Used for expressions and ``alpha(opacity=100)`` currently."""
name = u'Expression (IE only)'
def _productiondefinition(self):
"""Return defintion used for parsing."""
types = self._prods # rename!
def toSeq(t, tokens):
"Do not normalize function name!"
return t[0], t[1]
funcProds = Sequence(Prod(name='expression',
match=lambda t, v: t == types.FUNCTION,
toSeq=lambda t, tokens: (t[0], cssutils.helper.normalize(t[1]))),
toSeq=toSeq
),
Sequence(Choice(Prod(name='nested function',
match=lambda t, v: t == self._prods.FUNCTION,
toSeq=lambda t, tokens: (CSSFunction.name,
CSSFunction(cssutils.helper.pushtoken(t,
tokens)))),
tokens)))
),
Prod(name='part',
match=lambda t, v: v != u')',
toSeq=lambda t, tokens: (t[0], t[1])), ),
toSeq=lambda t, tokens: (t[0], t[1])),
),
minmax=lambda: (0, None)),
PreDef.funcEnd(stop=True))
return funcProds

View File

@ -1,7 +1,7 @@
"""Property is a single CSS property in a CSSStyleDeclaration."""
__all__ = ['Property']
__docformat__ = 'restructuredtext'
__version__ = '$Id: property.py 1685 2009-03-01 18:26:48Z cthedot $'
__version__ = '$Id: property.py 1811 2009-07-29 13:11:15Z cthedot $'
from cssutils.helper import Deprecated
from cssvalue import CSSValue
@ -44,7 +44,7 @@ class Property(cssutils.util.Base):
"""
def __init__(self, name=None, value=None, priority=u'',
_mediaQuery=False, _parent=None):
_mediaQuery=False, parent=None, parentStyle=None):
"""
:param name:
a property name string (will be normalized)
@ -55,16 +55,17 @@ class Property(cssutils.util.Base):
u'!important' or u'important'
:param _mediaQuery:
if ``True`` value is optional (used by MediaQuery)
:param _parent:
:param parent:
the parent object, normally a
:class:`cssutils.css.CSSStyleDeclaration`
:param parentStyle:
DEPRECATED: Use ``parent`` instead
"""
super(Property, self).__init__()
self.seqs = [[], None, []]
self.wellformed = False
self._mediaQuery = _mediaQuery
self._parent = _parent
self.parent = parent
self.__nametoken = None
self._name = u''
@ -363,12 +364,17 @@ class Property(cssutils.util.Base):
literalpriority = property(lambda self: self._literalpriority,
doc="Readonly literal (not normalized) priority of this property")
def validate(self, profiles=None):
"""Validate value against `profiles`.
def _setParent(self, parent):
self._parent = parent
:param profiles:
A list of profile names used for validating. If no `profiles`
is given ``cssutils.profile.defaultProfiles`` is used
parent = property(lambda self: self._parent, _setParent,
doc="The Parent Node (normally a CSSStyledeclaration) of this "
"Property")
def validate(self):
"""Validate value against `profiles` which are checked dynamically.
properties in e.g. @font-face rules are checked against
``cssutils.profile.CSS3_FONT_FACE`` only.
For each of the following cases a message is reported:
@ -424,6 +430,16 @@ class Property(cssutils.util.Base):
"""
valid = False
profiles = None
try:
# if @font-face use that profile
rule = self.parent.parentRule
if rule.type == rule.FONT_FACE_RULE:
profiles = [cssutils.profile.CSS3_FONT_FACE]
#TODO: same for @page
except AttributeError:
pass
if self.name and self.value:
if self.name in cssutils.profile.knownNames:
@ -467,3 +483,12 @@ class Property(cssutils.util.Base):
valid = property(validate, doc="Check if value of this property is valid "
"in the properties context.")
@Deprecated('Use ``parent`` attribute instead.')
def _getParentStyle(self):
return self._parent
parentStyle = property(_getParentStyle, _setParent,
doc="DEPRECATED: Use ``parent`` instead")

View File

@ -1,62 +0,0 @@
"""productions for CSS 3
CSS3_MACROS and CSS3_PRODUCTIONS are from http://www.w3.org/TR/css3-syntax
"""
__all__ = ['CSSProductions', 'MACROS', 'PRODUCTIONS']
__docformat__ = 'restructuredtext'
__version__ = '$Id: css3productions.py 1116 2008-03-05 13:52:23Z cthedot $'
# a complete list of css3 macros
MACROS = {
'ident': r'[-]?{nmstart}{nmchar}*',
'name': r'{nmchar}+',
'nmstart': r'[_a-zA-Z]|{nonascii}|{escape}',
'nonascii': r'[^\0-\177]',
'unicode': r'\\[0-9a-f]{1,6}{wc}?',
'escape': r'{unicode}|\\[ -~\200-\777]',
# 'escape': r'{unicode}|\\[ -~\200-\4177777]',
'nmchar': r'[-_a-zA-Z0-9]|{nonascii}|{escape}',
# CHANGED TO SPEC: added "-?"
'num': r'-?[0-9]*\.[0-9]+|[0-9]+', #r'[-]?\d+|[-]?\d*\.\d+',
'string': r'''\'({stringchar}|\")*\'|\"({stringchar}|\')*\"''',
'stringchar': r'{urlchar}| |\\{nl}',
'urlchar': r'[\x09\x21\x23-\x26\x27-\x7E]|{nonascii}|{escape}',
# what if \r\n, \n matches first?
'nl': r'\n|\r\n|\r|\f',
'w': r'{wc}*',
'wc': r'\t|\r|\n|\f|\x20'
}
# The following productions are the complete list of tokens in CSS3, the productions are **ordered**:
PRODUCTIONS = [
('BOM', r'\xFEFF'),
('URI', r'url\({w}({string}|{urlchar}*){w}\)'),
('FUNCTION', r'{ident}\('),
('ATKEYWORD', r'\@{ident}'),
('IDENT', r'{ident}'),
('STRING', r'{string}'),
('HASH', r'\#{name}'),
('PERCENTAGE', r'{num}\%'),
('DIMENSION', r'{num}{ident}'),
('NUMBER', r'{num}'),
#???
('UNICODE-RANGE', ur'[0-9A-F?]{1,6}(\-[0-9A-F]{1,6})?'),
('CDO', r'\<\!\-\-'),
('CDC', r'\-\-\>'),
('S', r'{wc}+'),
('INCLUDES', '\~\='),
('DASHMATCH', r'\|\='),
('PREFIXMATCH', r'\^\='),
('SUFFIXMATCH', r'\$\='),
('SUBSTRINGMATCH', r'\*\='),
('COMMENT', r'\/\*[^*]*\*+([^/][^*]*\*+)*\/'),
('CHAR', r'[^"\']'),
]
class CSSProductions(object):
"has attributes for all PRODUCTIONS"
pass
for i, t in enumerate(PRODUCTIONS):
setattr(CSSProductions, t[0].replace('-', '_'), t[0])

View File

@ -12,12 +12,12 @@ open issues
"""
__all__ = ['CSSProductions', 'MACROS', 'PRODUCTIONS']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssproductions.py 1738 2009-05-02 13:03:28Z cthedot $'
__version__ = '$Id: cssproductions.py 1835 2009-08-02 16:47:27Z cthedot $'
# a complete list of css3 macros
MACROS = {
'nonascii': r'[^\0-\177]',
'unicode': r'\\[0-9a-f]{1,6}(?:{nl}|{s})?',
'unicode': r'\\[0-9A-Fa-f]{1,6}(?:{nl}|{s})?',
#'escape': r'{unicode}|\\[ -~\200-\777]',
'escape': r'{unicode}|\\[^\n\r\f0-9a-f]',
'nmstart': r'[_a-zA-Z]|{nonascii}|{escape}',
@ -71,6 +71,7 @@ PRODUCTIONS = [
('S', r'{s}+'), # 1st in list of general productions
('URI', r'{U}{R}{L}\({w}({string}|{url}*){w}\)'),
('FUNCTION', r'{ident}\('),
('UNICODE-RANGE', r'{U}\+[0-9A-Fa-f?]{1,6}(\-[0-9A-Fa-f]{1,6})?'),
('IDENT', r'{ident}'),
('STRING', r'{string}'),
('INVALID', r'{invalid}'), # from CSS2.1
@ -80,8 +81,9 @@ PRODUCTIONS = [
('NUMBER', r'{num}'),
# valid ony at start so not checked everytime
#('CHARSET_SYM', r'@charset '), # from Errata includes ending space!
# checked specially if fullsheet is parsed
('COMMENT', r'{comment}'), #r'\/\*[^*]*\*+([^/][^*]*\*+)*\/'),
('ATKEYWORD', r'@{ident}'), # other keywords are done in the tokenizer
#('UNICODE-RANGE', r'[0-9A-F?]{1,6}(\-[0-9A-F]{1,6})?'), #???
('CDO', r'\<\!\-\-'),
('CDC', r'\-\-\>'),
('INCLUDES', '\~\='),
@ -89,8 +91,6 @@ PRODUCTIONS = [
('PREFIXMATCH', r'\^\='),
('SUFFIXMATCH', r'\$\='),
('SUBSTRINGMATCH', r'\*\='),
# checked specially if fullsheet is parsed
('COMMENT', r'{comment}'), #r'\/\*[^*]*\*+([^/][^*]*\*+)*\/'),
('CHAR', r'[^"\']') # MUST always be last
]
@ -110,3 +110,9 @@ class CSSProductions(object):
for i, t in enumerate(PRODUCTIONS):
setattr(CSSProductions, t[0].replace('-', '_'), t[0])
# may be enabled by settings.set
_DXImageTransform = ('FUNCTION',
r'progid\:DXImageTransform\.Microsoft\..+\('
)

View File

@ -16,7 +16,7 @@ log
"""
__all__ = ['ErrorHandler']
__docformat__ = 'restructuredtext'
__version__ = '$Id: errorhandler.py 1728 2009-05-01 20:35:25Z cthedot $'
__version__ = '$Id: errorhandler.py 1812 2009-07-29 13:11:49Z cthedot $'
from helper import Deprecated
import logging
@ -41,6 +41,9 @@ class _ErrorHandler(object):
- False: Errors will be written to the log, this is the
default behaviour when parsing
"""
# may be disabled during setting of known valid items
self.enabled = True
if log:
self._log = log
else:
@ -74,26 +77,27 @@ class _ErrorHandler(object):
handles all calls
logs or raises exception
"""
line, col = None, None
if token:
if isinstance(token, tuple):
value, line, col = token[1], token[2], token[3]
else:
value, line, col = token.value, token.line, token.col
msg = u'%s [%s:%s: %s]' % (
msg, line, col, value)
if self.enabled:
line, col = None, None
if token:
if isinstance(token, tuple):
value, line, col = token[1], token[2], token[3]
else:
value, line, col = token.value, token.line, token.col
msg = u'%s [%s:%s: %s]' % (
msg, line, col, value)
if error and self.raiseExceptions and not neverraise:
if isinstance(error, urllib2.HTTPError) or isinstance(error, urllib2.URLError):
raise
elif issubclass(error, xml.dom.DOMException):
error.line = line
error.col = col
# raise error(msg, line, col)
# else:
raise error(msg)
else:
self._logcall(msg)
if error and self.raiseExceptions and not neverraise:
if isinstance(error, urllib2.HTTPError) or isinstance(error, urllib2.URLError):
raise
elif issubclass(error, xml.dom.DOMException):
error.line = line
error.col = col
# raise error(msg, line, col)
# else:
raise error(msg)
else:
self._logcall(msg)
def setLog(self, log):
"""set log of errorhandler's log"""

View File

@ -3,7 +3,10 @@
__docformat__ = 'restructuredtext'
__version__ = '$Id: errorhandler.py 1234 2008-05-22 20:26:12Z cthedot $'
import os
import re
import sys
import urllib
class Deprecated(object):
"""This is a decorator which can be used to mark functions
@ -48,6 +51,10 @@ def normalize(x):
else:
return x
def path2url(path):
"""Return file URL of `path`"""
return u'file:' + urllib.pathname2url(os.path.abspath(path))
def pushtoken(token, tokens):
"""Return new generator starting with token followed by all tokens in
``tokens``"""
@ -107,24 +114,24 @@ def urivalue(uri):
else:
return uri
def normalnumber(num):
"""
Return normalized number as string.
"""
sign = ''
if num.startswith('-'):
sign = '-'
num = num[1:]
elif num.startswith('+'):
num = num[1:]
if float(num) == 0.0:
return '0'
else:
if num.find('.') == -1:
return sign + str(int(num))
else:
a, b = num.split('.')
if not a:
a = '0'
return '%s%s.%s' % (sign, int(a), b)
#def normalnumber(num):
# """
# Return normalized number as string.
# """
# sign = ''
# if num.startswith('-'):
# sign = '-'
# num = num[1:]
# elif num.startswith('+'):
# num = num[1:]
#
# if float(num) == 0.0:
# return '0'
# else:
# if num.find('.') == -1:
# return sign + str(int(num))
# else:
# a, b = num.split('.')
# if not a:
# a = '0'
# return '%s%s.%s' % (sign, int(a), b)

View File

@ -2,9 +2,9 @@
"""A validating CSSParser"""
__all__ = ['CSSParser']
__docformat__ = 'restructuredtext'
__version__ = '$Id: parse.py 1656 2009-02-03 20:31:06Z cthedot $'
__version__ = '$Id: parse.py 1754 2009-05-30 14:50:13Z cthedot $'
from helper import Deprecated
from helper import Deprecated, path2url
import codecs
import cssutils
import os
@ -124,7 +124,8 @@ class CSSParser(object):
"""
if not href:
# prepend // for file URL, urllib does not do this?
href = u'file:' + urllib.pathname2url(os.path.abspath(filename))
#href = u'file:' + urllib.pathname2url(os.path.abspath(filename))
href = path2url(filename)
return self.parseString(open(filename, 'rb').read(),
encoding=encoding, # read returns a str

View File

@ -533,105 +533,98 @@ class PreDef(object):
types = cssutils.cssproductions.CSSProductions
@staticmethod
def CHAR(name='char', char=u',', toSeq=None, toStore=None, stop=False,
def char(name='char', char=u',', toSeq=None, stop=False,
nextSor=False):
"any CHAR"
return Prod(name=name, match=lambda t, v: v == char,
toSeq=toSeq,
toStore=toStore,
stop=stop,
nextSor=nextSor)
@staticmethod
def comma(toStore=None):
return PreDef.CHAR(u'comma', u',', toStore=toStore)
def comma():
return PreDef.char(u'comma', u',')
@staticmethod
def dimension(toStore=None, nextSor=False):
def dimension(nextSor=False):
return Prod(name=u'dimension',
match=lambda t, v: t == PreDef.types.DIMENSION,
toStore=toStore,
toSeq=lambda t, tokens: (t[0], cssutils.helper.normalize(t[1])),
nextSor=nextSor)
@staticmethod
def function(toSeq=None, toStore=None, nextSor=False):
def function(toSeq=None, nextSor=False):
return Prod(name=u'function',
match=lambda t, v: t == PreDef.types.FUNCTION,
toSeq=toSeq,
toStore=toStore,
nextSor=nextSor)
@staticmethod
def funcEnd(toStore=None, stop=False, nextSor=False):
def funcEnd(stop=False):
")"
return PreDef.CHAR(u'end FUNC ")"', u')',
toStore=toStore,
stop=stop,
nextSor=nextSor)
return PreDef.char(u'end FUNC ")"', u')',
stop=stop)
@staticmethod
def ident(toStore=None, nextSor=False):
def ident(nextSor=False):
return Prod(name=u'ident',
match=lambda t, v: t == PreDef.types.IDENT,
toStore=toStore,
nextSor=nextSor)
@staticmethod
def number(toStore=None, nextSor=False):
def number(nextSor=False):
return Prod(name=u'number',
match=lambda t, v: t == PreDef.types.NUMBER,
toStore=toStore,
nextSor=nextSor)
@staticmethod
def string(toStore=None, nextSor=False):
def string(nextSor=False):
"string delimiters are removed by default"
return Prod(name=u'string',
match=lambda t, v: t == PreDef.types.STRING,
toStore=toStore,
toSeq=lambda t, tokens: (t[0], cssutils.helper.stringvalue(t[1])),
nextSor=nextSor)
@staticmethod
def percentage(toStore=None, nextSor=False):
def percentage(nextSor=False):
return Prod(name=u'percentage',
match=lambda t, v: t == PreDef.types.PERCENTAGE,
toStore=toStore,
nextSor=nextSor)
@staticmethod
def S(name=u'whitespace', optional=True, toSeq=None, toStore=None, nextSor=False,
mayEnd=False):
return Prod(name=name,
def S():
return Prod(name=u'whitespace',
match=lambda t, v: t == PreDef.types.S,
optional=optional,
toSeq=toSeq,
toStore=toStore,
mayEnd=mayEnd)
mayEnd=True)
@staticmethod
def unary(optional=True, toStore=None):
def unary():
"+ or -"
return Prod(name=u'unary +-', match=lambda t, v: v in (u'+', u'-'),
optional=optional,
toStore=toStore)
optional=True)
@staticmethod
def uri(toStore=None, nextSor=False):
def uri(nextSor=False):
"'url(' and ')' are removed and URI is stripped"
return Prod(name=u'URI',
match=lambda t, v: t == PreDef.types.URI,
toStore=toStore,
toSeq=lambda t, tokens: (t[0], cssutils.helper.urivalue(t[1])),
nextSor=nextSor)
@staticmethod
def hexcolor(toStore=None, toSeq=None, nextSor=False):
def hexcolor(nextSor=False):
"#123456"
return Prod(name='HEX color',
match=lambda t, v: t == PreDef.types.HASH and
len(v) == 4 or len(v) == 7,
toStore=toStore,
toSeq=toSeq,
match=lambda t, v: t == PreDef.types.HASH and (
len(v) == 4 or len(v) == 7),
nextSor=nextSor
)
@staticmethod
def unicode_range(nextSor=False):
"u+123456-abc normalized to lower `u`"
return Prod(name='unicode-range',
match=lambda t, v: t == PreDef.types.UNICODE_RANGE,
toSeq=lambda t, tokens: (t[0], t[1].lower()),
nextSor=nextSor
)

View File

@ -43,8 +43,10 @@ class Profiles(object):
macros.
"""
CSS_LEVEL_2 = 'CSS Level 2.1'
CSS3_COLOR = CSS_COLOR_LEVEL_3 = 'CSS Color Module Level 3'
CSS3_BOX = CSS_BOX_LEVEL_3 = 'CSS Box Module Level 3'
CSS3_COLOR = CSS_COLOR_LEVEL_3 = 'CSS Color Module Level 3'
CSS3_FONTS = 'CSS Fonts Module Level 3'
CSS3_FONT_FACE = 'CSS Fonts Module Level 3 @font-face properties'
CSS3_PAGED_MEDIA = 'CSS3 Paged Media Module'
_TOKEN_MACROS = {
@ -58,6 +60,7 @@ class Profiles(object):
'int': r'[-]?\d+',
'nmchar': r'[\w-]|{nonascii}|{escape}',
'num': r'[-]?\d+|[-]?\d*\.\d+',
'positivenum': r'\d+|[-]?\d*\.\d+',
'number': r'{num}',
'string': r'{string1}|{string2}',
'string1': r'"(\\\"|[^\"])*"',
@ -75,6 +78,7 @@ class Profiles(object):
#'color': r'(maroon|red|orange|yellow|olive|purple|fuchsia|white|lime|green|navy|blue|aqua|teal|black|silver|gray|ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)|#[0-9a-f]{3}|#[0-9a-f]{6}|rgb\({w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgb\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w}\)',
'integer': r'{int}',
'length': r'0|{num}(em|ex|px|in|cm|mm|pt|pc)',
'positivelength': r'0|{positivenum}(em|ex|px|in|cm|mm|pt|pc)',
'angle': r'0|{num}(deg|grad|rad)',
'time': r'0|{num}m?s',
'frequency': r'0|{num}k?Hz',
@ -97,6 +101,16 @@ class Profiles(object):
self.addProfile(self.CSS3_COLOR,
properties[self.CSS3_COLOR],
macros[self.CSS3_COLOR])
self.addProfile(self.CSS3_FONTS,
properties[self.CSS3_FONTS],
macros[self.CSS3_FONTS])
# new object for font-face only?
self.addProfile(self.CSS3_FONT_FACE,
properties[self.CSS3_FONT_FACE],
macros[self.CSS3_FONTS]) # same
self.addProfile(self.CSS3_PAGED_MEDIA,
properties[self.CSS3_PAGED_MEDIA],
macros[self.CSS3_PAGED_MEDIA])
@ -132,7 +146,7 @@ class Profiles(object):
def _getDefaultProfiles(self):
"If not explicitly set same as Profiles.profiles but in reverse order."
if not self._defaultProfiles:
return self.profiles#list(reversed(self.profiles))
return self.profiles
else:
return self._defaultProfiles
@ -338,12 +352,12 @@ macros[Profiles.CSS_LEVEL_2] = {
'shape': r'rect\(({w}({length}|auto}){w},){3}{w}({length}|auto){w}\)',
'counter': r'counter\({w}{identifier}{w}(?:,{w}{list-style-type}{w})?\)',
'identifier': r'{ident}',
'family-name': r'{string}|{identifier}',
'family-name': r'{string}|{identifier}({w}{identifier})*',
'generic-family': r'serif|sans-serif|cursive|fantasy|monospace',
'absolute-size': r'(x?x-)?(small|large)|medium',
'relative-size': r'smaller|larger',
'font-family': r'(({family-name}|{generic-family}){w},{w})*({family-name}|{generic-family})|inherit',
'font-size': r'{absolute-size}|{relative-size}|{length}|{percentage}|inherit',
'font-size': r'{absolute-size}|{relative-size}|{positivelength}|{percentage}|inherit',
'font-style': r'normal|italic|oblique|inherit',
'font-variant': r'normal|small-caps|inherit',
'font-weight': r'normal|bold|bolder|lighter|[1-9]00|inherit',
@ -495,7 +509,7 @@ macros[Profiles.CSS3_BOX] = {
'overflow': macros[Profiles.CSS_LEVEL_2]['overflow']
}
properties[Profiles.CSS3_BOX] = {
'overflow': '{overflow}\s?{overflow}?|inherit',
'overflow': '{overflow}{w}{overflow}?|inherit',
'overflow-x': '{overflow}|inherit',
'overflow-y': '{overflow}|inherit'
}
@ -515,6 +529,28 @@ properties[Profiles.CSS3_COLOR] = {
'opacity': r'{num}|inherit'
}
# CSS Fonts Module Level 3 http://www.w3.org/TR/css3-fonts/
macros[Profiles.CSS3_FONTS] = {
'family-name': r'{string}|{ident}', # but STRING is effectively an IDENT???
'font-face-name': 'local\({w}{ident}{w}\)',
'font-stretch-names': r'(ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded)',
'unicode-range': r'[uU]\+[0-9A-Fa-f?]{1,6}(\-[0-9A-Fa-f]{1,6})?'
}
properties[Profiles.CSS3_FONTS] = {
'font-size-adjust': r'{number}|none|inherit',
'font-stretch': r'normal|wider|narrower|{font-stretch-names}|inherit'
}
properties[Profiles.CSS3_FONT_FACE] = {
'font-family': '{family-name}',
'font-stretch': r'{font-stretch-names}',
'font-style': r'normal|italic|oblique',
'font-weight': r'normal|bold|[1-9]00',
'src': r'({uri}{w}(format\({w}{string}{w}(\,{w}{string}{w})*\))?|{font-face-name})({w},{w}({uri}{w}(format\({w}{string}{w}(\,{w}{string}{w})*\))?|{font-face-name}))*',
'unicode-range': '{unicode-range}({w},{w}{unicode-range})*'
}
# CSS3 Paged Media
macros[Profiles.CSS3_PAGED_MEDIA] = {
'pagesize': 'a5|a4|a3|b5|b4|letter|legal|ledger',

13
src/cssutils/settings.py Normal file
View File

@ -0,0 +1,13 @@
"""Experimental settings for special stuff."""
def set(key, value):
"""Call to enable special settings:
('DXImageTransform.Microsoft', True)
enable support for parsing special MS only filter values
"""
if key == 'DXImageTransform.Microsoft' and value == True:
import cssproductions
cssproductions.PRODUCTIONS.insert(1, cssproductions._DXImageTransform)

View File

@ -4,7 +4,7 @@
"""
__all__ = ['Tokenizer', 'CSSProductions']
__docformat__ = 'restructuredtext'
__version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $'
__version__ = '$Id: tokenize2.py 1834 2009-08-02 12:20:21Z cthedot $'
from cssproductions import *
from helper import normalize
@ -147,7 +147,8 @@ class Tokenizer(object):
break
if name in ('DIMENSION', 'IDENT', 'STRING', 'URI',
'HASH', 'COMMENT', 'FUNCTION', 'INVALID'):
'HASH', 'COMMENT', 'FUNCTION', 'INVALID',
'UNICODE-RANGE'):
# may contain unicode escape, replace with normal char
# but do not _normalize (?)
value = self.unicodesub(_repl, found)
@ -166,7 +167,6 @@ class Tokenizer(object):
name = self._atkeywords.get(_normalize(found), 'ATKEYWORD')
value = found # should not contain unicode escape (?)
yield (name, value, line, col)
text = text[len(found):]
nls = found.count(self._linesep)

View File

@ -2,7 +2,7 @@
"""
__all__ = []
__docformat__ = 'restructuredtext'
__version__ = '$Id: util.py 1743 2009-05-09 20:33:15Z cthedot $'
__version__ = '$Id: util.py 1781 2009-07-19 12:30:49Z cthedot $'
from helper import normalize
from itertools import ifilter
@ -663,6 +663,9 @@ class _Namespaces(object):
self.parentStyleSheet = parentStyleSheet
self._log = log
def __repr__(self):
return "%r" % self.namespaces
def __contains__(self, prefix):
return prefix in self.namespaces