Sync to trunk

This commit is contained in:
John Schember 2009-02-07 12:17:28 -05:00
commit 31ba8daf2a
184 changed files with 9140 additions and 1849 deletions

View File

@ -299,7 +299,6 @@ File ::2BCD9281-2CBC-CF0D-0E12-2CE11F6ED758 -name comic2epub.exe.local -parent 8
File ::EDE6F457-C83F-C5FA-9AF4-38FDFF17D929 -name PIL._imagingtk.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::09D0906E-3611-3DB7-32CF-A140585694A7 -name win32pdh.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4C84F0DC-7157-0C90-2062-180139B03E25 -name IM_MOD_RL_rgb_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::F402F507-87C5-BDB1-80AE-AD3FF4A4BCE7 -name bzrlib._patiencediff_c.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A732EDE7-4796-241F-BECA-68E59F88F8AF -name lrs2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::69072379-7D16-B9F7-9F39-3E6403C48267 -name IM_MOD_RL_xbm_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::FBD11D98-D1E7-5DD9-BF02-01CE92518859 -name IM_MOD_RL_otb_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
@ -365,7 +364,6 @@ File ::26741B21-C241-E100-8BB1-8B679BC3E662 -name configure.xml -parent 8E5D85A4
File ::7D491E89-C6D3-1E6E-F4BD-8E55260FE33E -name libexpat.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A4910EB3-0F1C-F6F0-CD2D-16A64BBAA92B -name calibre-fontconfig.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::8711327A-716D-B162-6AC6-2FB4AD071266 -name fb22lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::0FDD3A7A-31F3-8089-CE32-D80EAA6F62B2 -name bzrlib._btree_serializer_c.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::476CB977-5155-D56F-26CA-EB243AEBBA99 -name unrar.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2DA1CC8D-AF5C-3B03-2060-301DFE0356CC -name mobi2oeb.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2E2A9EDA-5386-444E-8479-557386794552 -name IM_MOD_RL_uil_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
@ -487,7 +485,6 @@ File ::AA761ACD-B728-2324-AA75-B20A2A79F125 -name lrf2lrs.exe -parent 8E5D85A4-7
File ::95434C76-22F5-B9CE-6194-6E1B1EE3232D -name IM_MOD_RL_info_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::AAF45D03-322F-5553-63A7-312DB754A20B -name _ctypes.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::C3D351CA-A8D8-AB35-55D9-5AACF8DB37D1 -name python26.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2F90B52F-A728-2CA4-5688-0283674695B7 -name _elementtree.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::B50B66A1-FB65-FAD5-1DD7-E894ACC07464 -name QtSvg4.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::906FF13D-D993-7192-7EA5-6D15A5A24BFB -name CORE_RL_png_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::5D368661-6BF0-D6AF-7C1A-87646864EB4B -name delegates.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
@ -552,7 +549,7 @@ SetupType ::D9ADE41C-B744-690C-2CED-CF826BF03D2E -setup Install -active Yes -pla
InstallComponent 3EA07B17-04D8-6508-B535-96CC7173B49A -setup Install -type pane -conditions D7F585DB-0DEC-A94E-DAB0-94D558D82764 -title {Welcome Screen} -component Welcome -command insert -active Yes -parent StandardInstall
Condition D7F585DB-0DEC-A94E-DAB0-94D558D82764 -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A -title {Execute Script Condition} -component ExecuteScriptCondition -TreeObject::id D7F585DB-0DEC-A94E-DAB0-94D558D82764
InstallComponent 7CCDA4BB-861C-C21E-3011-E93DB58F07D6 -setup Install -type action -conditions ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E -title {Check for Previous Install} -component CheckForPreviousInstall -command reorder -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A
InstallComponent 7CCDA4BB-861C-C21E-3011-E93DB58F07D6 -setup Install -type action -conditions ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E -title {Check for Previous Install} -component CheckForPreviousInstall -command insert -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A
Condition ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E -active Yes -parent 7CCDA4BB-861C-C21E-3011-E93DB58F07D6 -title {Execute Script Condition} -component ExecuteScriptCondition -TreeObject::id ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E
InstallComponent 580ACF2C-517F-5E48-9DEF-7DAEFBA59FDD -setup Install -type action -conditions 6DE3B369-9D6B-6BC1-4EA0-2C54ECE159EB -title {Set Virtual Text} -component SetVirtualText -command insert -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A
Condition 6DE3B369-9D6B-6BC1-4EA0-2C54ECE159EB -active Yes -parent 580ACF2C-517F-5E48-9DEF-7DAEFBA59FDD -title {String Is Condition} -component StringIsCondition -TreeObject::id 6DE3B369-9D6B-6BC1-4EA0-2C54ECE159EB

View File

@ -12,7 +12,7 @@ LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
PDFTOHTML = 'C:\\pdftohtml\\pdftohtml.exe'
IMAGEMAGICK_DIR = 'C:\\ImageMagick'
FONTCONFIG_DIR = 'C:\\fontconfig'
VC90 = r'C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\x86\Microsoft.VC90.CRT'
VC90 = r'C:\VC90.CRT'
import sys, os, py2exe, shutil, zipfile, glob, subprocess, re
from distutils.core import setup

View File

@ -19,8 +19,15 @@ import mechanize
mimetypes.add_type('application/epub+zip', '.epub')
mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs')
mimetypes.add_type('http://www.w3.org/1999/xhtml', '.xhtml')
mimetypes.add_type('image/svg+xml', '.svg')
mimetypes.add_type('application/x-sony-bbeb', '.lrf')
mimetypes.add_type('application/x-dtbncx+xml', '.ncx')
mimetypes.add_type('application/adobe-page-template+xml', '.xpgt')
mimetypes.add_type('application/x-font-opentype', '.otf')
mimetypes.add_type('application/x-font-truetype', '.ttf')
mimetypes.add_type('application/oebps-package+xml', '.opf')
def to_unicode(raw, encoding='utf-8', errors='strict'):
if isinstance(raw, unicode):

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = 'calibre'
__version__ = '0.4.132'
__version__ = '0.4.134'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
'''
Various run time constants.

View File

@ -96,7 +96,7 @@ class PRS500(Device):
# Location of cache.xml on storage card in device
CACHE_XML = "/Sony Reader/database/cache.xml"
# Ordered list of supported formats
FORMATS = ["lrf", "rtf", "pdf", "txt"]
FORMATS = ["lrf", "lrx", "rtf", "pdf", "txt"]
# Height for thumbnails of books/images on the device
THUMBNAIL_HEIGHT = 68
# Directory on card to which books are copied

View File

@ -32,7 +32,7 @@ class PRS505(Device):
BCD = [0x229] #: Needed to disambiguate 505 and 700 on linux
PRODUCT_NAME = 'PRS-505'
VENDOR_NAME = 'SONY'
FORMATS = ['lrf', 'epub', "rtf", "pdf", "txt"]
FORMATS = ['lrf', 'epub', 'lrx', 'rtf', 'pdf', 'txt']
MEDIA_XML = 'database/cache/media.xml'
CACHE_XML = 'Sony Reader/database/cache.xml'
@ -147,6 +147,7 @@ class PRS505(Device):
def open_windows(self):
time.sleep(6)
drives = []
wmi = __import__('wmi', globals(), locals(), [], -1)
c = wmi.WMI()

View File

@ -172,6 +172,7 @@ class Device(_Device):
return prefix
def open_windows(self):
time.sleep(6)
drives = {}
wmi = __import__('wmi', globals(), locals(), [], -1)
c = wmi.WMI()

View File

@ -102,13 +102,23 @@ def config(defaults=None, name='epub'):
c.remove_opt('zip')
c.add_opt('output', ['-o', '--output'], default=None,
help=_('The output EPUB file. If not specified, it is derived from the input file name.'))
help=_('The output EPUB file. If not specified, it is '
'derived from the input file name.'))
c.add_opt('profile', ['--profile'], default='PRS505', choices=list(PROFILES.keys()),
help=_('Profile of the target device this EPUB is meant for. Set to None to create a device independent EPUB. The profile is used for device specific restrictions on the EPUB. Choices are: ')+str(list(PROFILES.keys())))
help=_('Profile of the target device this EPUB is meant for. '
'Set to None to create a device independent EPUB. '
'The profile is used for device specific restrictions '
'on the EPUB. Choices are: ')+str(list(PROFILES.keys())))
c.add_opt('override_css', ['--override-css'], default=None,
help=_('Either the path to a CSS stylesheet or raw CSS. This CSS will override any existing CSS declarations in the source files.'))
structure = c.add_group('structure detection', _('Control auto-detection of document structure.'))
structure('chapter', ['--chapter'], default="//*[re:match(name(), 'h[1-2]') and re:test(., 'chapter|book|section|part', 'i')] | //*[@class = 'chapter']",
help=_('Either the path to a CSS stylesheet or raw CSS. '
'This CSS will override any existing CSS '
'declarations in the source files.'))
structure = c.add_group('structure detection',
_('Control auto-detection of document structure.'))
structure('chapter', ['--chapter'],
default="//*[re:match(name(), 'h[1-2]') and "
"re:test(., 'chapter|book|section|part', 'i')] | "
"//*[@class = 'chapter']",
help=_('''\
An XPath expression to detect chapter titles. The default is to consider <h1> or
<h2> tags that contain the words "chapter","book","section" or "part" as chapter titles as
@ -118,14 +128,39 @@ use the expression "/". See the XPath Tutorial in the calibre User Manual for fu
help on using this feature.
''').replace('\n', ' '))
structure('chapter_mark', ['--chapter-mark'], choices=['pagebreak', 'rule', 'both', 'none'],
default='pagebreak', help=_('Specify how to mark detected chapters. A value of "pagebreak" will insert page breaks before chapters. A value of "rule" will insert a line before chapters. A value of "none" will disable chapter marking and a value of "both" will use both page breaks and lines to mark chapters.'))
default='pagebreak',
help=_('Specify how to mark detected chapters. A value of '
'"pagebreak" will insert page breaks before chapters. '
'A value of "rule" will insert a line before chapters. '
'A value of "none" will disable chapter marking and a '
'value of "both" will use both page breaks and lines '
'to mark chapters.'))
structure('cover', ['--cover'], default=None,
help=_('Path to the cover to be used for this book'))
structure('prefer_metadata_cover', ['--prefer-metadata-cover'], default=False,
action='store_true',
help=_('Use the cover detected from the source file in preference to the specified cover.'))
help=_('Use the cover detected from the source file in preference '
'to the specified cover.'))
structure('remove_first_image', ['--remove-first-image'], default=False,
help=_('Remove the first image from the input ebook. Useful if '
'the first image in the source file is a cover and you '
'are specifying an external cover.'))
structure('dont_split_on_page_breaks', ['--dont-split-on-page-breaks'], default=False,
help=_('Turn off splitting at page breaks. Normally, input files are automatically split at every page break into two files. This gives an output ebook that can be parsed faster and with less resources. However, splitting is slow and if your source file contains a very large number of page breaks, you should turn off splitting on page breaks.'))
help=_('Turn off splitting at page breaks. Normally, input files '
'are automatically split at every page break into '
'two files. This gives an output ebook that can be parsed '
'faster and with less resources. However, splitting is '
'slow and if your source file contains a very large '
'number of page breaks, you should turn off splitting '
'on page breaks.'))
structure('page', ['--page'], default=None,
help=_('XPath expression to detect page boundaries for building '
'a custom pagination map, as used by AdobeDE. Default is '
'not to build an explicit pagination map.'))
structure('page_names', ['--page-names'], default=None,
help=_('XPath expression to find the name of each page in the '
'pagination map relative to its boundary element. '
'Default is to number all pages staring with 1.'))
toc = c.add_group('toc',
_('''\
Control the automatic generation of a Table of Contents. If an OPF file is detected
@ -133,21 +168,36 @@ and it specifies a Table of Contents, then that will be used rather than trying
to auto-generate a Table of Contents.
''').replace('\n', ' '))
toc('max_toc_links', ['--max-toc-links'], default=50,
help=_('Maximum number of links to insert into the TOC. Set to 0 to disable. Default is: %default. Links are only added to the TOC if less than the --toc-threshold number of chapters were detected.'))
help=_('Maximum number of links to insert into the TOC. Set to 0 '
'to disable. Default is: %default. Links are only added to the '
'TOC if less than the --toc-threshold number of chapters were detected.'))
toc('no_chapters_in_toc', ['--no-chapters-in-toc'], default=False,
help=_("Don't add auto-detected chapters to the Table of Contents."))
toc('toc_threshold', ['--toc-threshold'], default=6,
help=_('If fewer than this number of chapters is detected, then links are added to the Table of Contents. Default: %default'))
help=_('If fewer than this number of chapters is detected, then links '
'are added to the Table of Contents. Default: %default'))
toc('level1_toc', ['--level1-toc'], default=None,
help=_('XPath expression that specifies all tags that should be added to the Table of Contents at level one. If this is specified, it takes precedence over other forms of auto-detection.'))
help=_('XPath expression that specifies all tags that should be added '
'to the Table of Contents at level one. If this is specified, '
'it takes precedence over other forms of auto-detection.'))
toc('level2_toc', ['--level2-toc'], default=None,
help=_('XPath expression that specifies all tags that should be added to the Table of Contents at level two. Each entry is added under the previous level one entry.'))
help=_('XPath expression that specifies all tags that should be added '
'to the Table of Contents at level two. Each entry is added '
'under the previous level one entry.'))
toc('level3_toc', ['--level3-toc'], default=None,
help=_('XPath expression that specifies all tags that should be added to the Table of Contents at level three. Each entry is added under the previous level two entry.'))
help=_('XPath expression that specifies all tags that should be added '
'to the Table of Contents at level three. Each entry is added '
'under the previous level two entry.'))
toc('from_ncx', ['--from-ncx'], default=None,
help=_('Path to a .ncx file that contains the table of contents to use for this ebook. The NCX file should contain links relative to the directory it is placed in. See http://www.niso.org/workrooms/daisy/Z39-86-2005.html#NCX for an overview of the NCX format.'))
help=_('Path to a .ncx file that contains the table of contents to use '
'for this ebook. The NCX file should contain links relative to '
'the directory it is placed in. See '
'http://www.niso.org/workrooms/daisy/Z39-86-2005.html#NCX for '
'an overview of the NCX format.'))
toc('use_auto_toc', ['--use-auto-toc'], default=False,
help=_('Normally, if the source file already has a Table of Contents, it is used in preference to the autodetected one. With this option, the autodetected one is always used.'))
help=_('Normally, if the source file already has a Table of Contents, '
'it is used in preference to the autodetected one. '
'With this option, the autodetected one is always used.'))
layout = c.add_group('page layout', _('Control page layout'))
layout('margin_top', ['--margin-top'], default=5.0,
@ -159,18 +209,33 @@ to auto-generate a Table of Contents.
layout('margin_right', ['--margin-right'], default=5.0,
help=_('Set the right margin in pts. Default is %default'))
layout('base_font_size2', ['--base-font-size'], default=12.0,
help=_('The base font size in pts. Default is %defaultpt. Set to 0 to disable rescaling of fonts.'))
help=_('The base font size in pts. Default is %defaultpt. '
'Set to 0 to disable rescaling of fonts.'))
layout('remove_paragraph_spacing', ['--remove-paragraph-spacing'], default=False,
help=_('Remove spacing between paragraphs. Will not work if the source file forces inter-paragraph spacing.'))
help=_('Remove spacing between paragraphs. '
'Also sets a indent on paragraphs of 1.5em. '
'You can override this by adding p {text-indent: 0cm} to '
'--override-css. Spacing removal will not work if the source '
'file forces inter-paragraph spacing.'))
layout('no_justification', ['--no-justification'], default=False,
help=_('Do not force text to be justified in output.'))
layout('linearize_tables', ['--linearize-tables'], default=False,
help=_('Remove table markup, converting it into paragraphs. '
'This is useful if your source file uses a table to manage layout.'))
layout('preserve_tag_structure', ['--preserve-tag-structure'], default=False,
help=_('Preserve the HTML tag structure while splitting large HTML files. This is only neccessary if the HTML files contain CSS that uses sibling selectors. Enabling this greatly slows down processing of large HTML files.'))
help=_('Preserve the HTML tag structure while splitting large HTML files. '
'This is only neccessary if the HTML files contain CSS that '
'uses sibling selectors. Enabling this greatly slows down '
'processing of large HTML files.'))
c.add_opt('show_opf', ['--show-opf'], default=False, group='debug',
help=_('Print generated OPF file to stdout'))
c.add_opt('show_ncx', ['--show-ncx'], default=False, group='debug',
help=_('Print generated NCX file to stdout'))
c.add_opt('keep_intermediate', ['--keep-intermediate-files'], group='debug', default=False,
c.add_opt('keep_intermediate', ['--keep-intermediate-files'], group='debug',
default=False,
help=_('Keep intermediate files during processing by html2epub'))
c.add_opt('extract_to', ['--extract-to'], group='debug', default=None,
help=_('Extract the contents of the produced EPUB file to the specified directory.'))
help=_('Extract the contents of the produced EPUB file to the '
'specified directory.'))
return c

View File

@ -46,6 +46,7 @@ from calibre.ebooks.metadata.toc import TOC
from calibre.ebooks.metadata.opf2 import OPF
from calibre.ebooks.epub import initialize_container, PROFILES
from calibre.ebooks.epub.split import split
from calibre.ebooks.epub.pages import add_page_map
from calibre.ebooks.epub.fonts import Rationalizer
from calibre.constants import preferred_encoding
from calibre.customize.ui import run_plugins_on_postprocess
@ -141,7 +142,7 @@ class HTMLProcessor(Processor, Rationalizer):
p = QPixmap()
p.load(path)
if not p.isNull():
p.save(path+'_calibre_converted.jpg')
p.save(path + '_calibre_converted.jpg')
os.remove(path)
for key, val in self.resource_map.items():
if val == rpath:
@ -194,6 +195,9 @@ class HTMLProcessor(Processor, Rationalizer):
if not tag.text and not tag.get('src', False):
tag.getparent().remove(tag)
if self.opts.linearize_tables:
for tag in self.root.xpath('//table | //tr | //th | //td'):
tag.tag = 'div'
def save(self):
@ -203,6 +207,13 @@ class HTMLProcessor(Processor, Rationalizer):
# self.convert_image(img)
Processor.save(self)
def remove_first_image(self):
images = self.root.xpath('//img')
if images:
images[0].getparent().remove(images[0])
return True
return False
@ -224,10 +235,13 @@ def parse_content(filelist, opts, tdir):
resource_map, stylesheets = {}, {}
toc = TOC(base_path=tdir, type='root')
stylesheet_map = {}
first_image_removed = False
for htmlfile in filelist:
logging.getLogger('html2epub').debug('Processing %s...'%htmlfile)
hp = HTMLProcessor(htmlfile, opts, os.path.join(tdir, 'content'),
resource_map, filelist, stylesheets)
if not first_image_removed and opts.remove_first_image:
first_image_removed = hp.remove_first_image()
hp.populate_toc(toc)
hp.save()
stylesheet_map[os.path.basename(hp.save_path())] = \
@ -425,6 +439,9 @@ def convert(htmlfile, opts, notification=None, create_epub=True,
if opts.show_ncx:
print toc
split(opf_path, opts, stylesheet_map)
if opts.page:
logger.info('\tBuilding page map...')
add_page_map(opf_path, opts)
check_links(opf_path, opts.pretty_print)
opf = OPF(opf_path, tdir)
@ -443,10 +460,7 @@ def convert(htmlfile, opts, notification=None, create_epub=True,
if os.path.exists(cpath):
opf.add_path_to_manifest(cpath, 'image/jpeg')
with open(opf_path, 'wb') as f:
raw = opf.render()
if not raw.startswith('<?xml '):
raw = '<?xml version="1.0" encoding="UTF-8"?>\n'+raw
f.write(raw)
f.write(opf.render())
ncx_path = os.path.join(os.path.dirname(opf_path), 'toc.ncx')
if os.path.exists(ncx_path) and os.stat(ncx_path).st_size > opts.profile.flow_size:
logger.info('Condensing NCX from %d bytes...'%os.stat(ncx_path).st_size)
@ -462,7 +476,7 @@ def convert(htmlfile, opts, notification=None, create_epub=True,
logger.info(_('Output written to ')+opts.output)
if opts.show_opf:
print open(os.path.join(tdir, 'metadata.opf')).read()
print open(opf_path, 'rb').read()
if opts.extract_to is not None:
if os.path.exists(opts.extract_to):

View File

@ -0,0 +1,59 @@
'''
Add page mapping information to an EPUB book.
'''
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
__docformat__ = 'restructuredtext en'
import os, re
from itertools import count, chain
from calibre.ebooks.oeb.base import XHTML, XHTML_NS
from calibre.ebooks.oeb.base import OEBBook, DirWriter
from lxml import etree, html
from lxml.etree import XPath
NSMAP = {'h': XHTML_NS, 'html': XHTML_NS, 'xhtml': XHTML_NS}
PAGE_RE = re.compile(r'page', re.IGNORECASE)
ROMAN_RE = re.compile(r'^[ivxlcdm]+$', re.IGNORECASE)
def filter_name(name):
name = name.strip()
name = PAGE_RE.sub('', name)
for word in name.split():
if word.isdigit() or ROMAN_RE.match(word):
name = word
break
return name
def build_name_for(expr):
if not expr:
counter = count(1)
return lambda elem: str(counter.next())
selector = XPath(expr, namespaces=NSMAP)
def name_for(elem):
results = selector(elem)
if not results:
return ''
name = ' '.join(results)
return filter_name(name)
return name_for
def add_page_map(opfpath, opts):
oeb = OEBBook(opfpath)
selector = XPath(opts.page, namespaces=NSMAP)
name_for = build_name_for(opts.page_names)
idgen = ("calibre-page-%d" % n for n in count(1))
for item in oeb.spine:
data = item.data
for elem in selector(data):
name = name_for(elem)
id = elem.get('id', None)
if id is None:
id = elem.attrib['id'] = idgen.next()
href = '#'.join((item.href, id))
oeb.pages.add(name, href)
writer = DirWriter(version='2.0', page_map=True)
writer.dump(oeb, opfpath)

View File

@ -9,7 +9,7 @@ directory or zip file. All the action starts in :function:`create_dir`.
'''
import sys, re, os, shutil, logging, tempfile, cStringIO, operator, functools
from urlparse import urlparse
from urlparse import urlparse, urlunparse
from urllib import unquote
from lxml import etree
@ -98,7 +98,8 @@ class Link(object):
@classmethod
def url_to_local_path(cls, url, base):
path = url.path
path = urlunparse(('', '', url.path, url.params, url.query, ''))
path = unquote(path)
if os.path.isabs(path):
return path
return os.path.abspath(os.path.join(base, path))
@ -111,11 +112,11 @@ class Link(object):
'''
assert isinstance(url, unicode) and isinstance(base, unicode)
self.url = url
self.parsed_url = urlparse(unquote(self.url))
self.parsed_url = urlparse(self.url)
self.is_local = self.parsed_url.scheme in ('', 'file')
self.is_internal = self.is_local and not bool(self.parsed_url.path)
self.path = None
self.fragment = self.parsed_url.fragment
self.fragment = unquote(self.parsed_url.fragment)
if self.is_local and not self.is_internal:
self.path = self.url_to_local_path(self.parsed_url, base)
@ -594,15 +595,21 @@ class Processor(Parser):
'''
Populate the Table of Contents from detected chapters and links.
'''
class Adder(object):
def add_item(href, fragment, text, target, type='link'):
def __init__(self, toc):
self.next_play_order = max([x.play_order for x in toc.flat()])
def __call__(self, href, fragment, text, target, type='link'):
for entry in toc.flat():
if entry.href == href and entry.fragment == fragment:
return entry
if len(text) > 50:
text = text[:50] + u'\u2026'
return target.add_item(href, fragment, text, type=type)
self.next_play_order += 1
return target.add_item(href, fragment, text, type=type,
play_order=self.next_play_order)
add_item = Adder(toc)
name = self.htmlfile_map[self.htmlfile.path]
href = 'content/'+name
@ -629,13 +636,15 @@ class Processor(Parser):
if self.opts.level1_toc is not None:
level1 = self.opts.level1_toc(self.root)
level1_order = []
if level1:
added = {}
for elem in level1:
text, _href, frag = elem_to_link(elem, href, counter)
counter += 1
if text:
added[elem] = add_item(_href, frag, text, toc, type='chapter')
level1_order.append(add_item(_href, frag, text, toc, type='chapter'))
added[elem] = level1_order[-1]
add_item(_href, frag, 'Top', added[elem], type='chapter')
if self.opts.level2_toc is not None:
added2 = {}
@ -665,6 +674,15 @@ class Processor(Parser):
add_item(_href, frag, text, level2, type='chapter')
if level1_order: # Fix play order
next_play_order = level1_order[0].play_order
for x in level1_order:
for y in x.flat():
y.play_order = next_play_order
next_play_order += 1
if len(toc) > 0:
return
# Add chapters to TOC
@ -867,6 +885,8 @@ class Processor(Parser):
css += '\n\na { color: inherit; text-decoration: inherit; cursor: default; }\na[href] { color: blue; text-decoration: underline; cursor:pointer; }'
if self.opts.remove_paragraph_spacing:
css += '\n\np {text-indent: 1.5em; margin-top:0pt; margin-bottom:0pt; padding:0pt; border:0pt;}'
if not self.opts.no_justification:
css += '\n\nbody {text-align: justify}'
if self.opts.override_css:
css += '\n\n' + self.opts.override_css
self.override_css = self.css_parser.parseString(self.preprocess_css(css))
@ -987,7 +1007,6 @@ def merge_metadata(htmlfile, opf, opts):
mi = get_metadata(open(htmlfile, 'rb'), 'html')
except:
mi = MetaInformation(None, None)
if opts.from_opf is not None and os.access(opts.from_opf, os.R_OK):
mi.smart_update(OPF(open(opts.from_opf, 'rb'), os.path.abspath(os.path.dirname(opts.from_opf))))
for attr in ('title', 'authors', 'publisher', 'tags', 'comments'):

View File

@ -23,7 +23,7 @@ from urllib import unquote as urlunquote
from lxml import etree
from calibre.ebooks.lit.reader import DirectoryEntry
import calibre.ebooks.lit.maps as maps
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES, OEB_CSS_MIME, \
from calibre.ebooks.oeb.base import OEB_DOCS, XHTML_MIME, OEB_STYLES, \
CSS_MIME, OPF_MIME, XML_NS, XML
from calibre.ebooks.oeb.base import namespace, barename, prefixname, \
urlnormalize, xpath
@ -474,7 +474,7 @@ class LitWriter(object):
name = '/data/' + item.id
data = item.data
secnum = 0
if not isinstance(data, basestring):
if isinstance(data, etree._Element):
self._add_folder(name)
rebin = ReBinary(data, item, self._oeb, map=HTML_MAP)
self._add_file(name + '/ahc', rebin.ahc, 0)
@ -483,6 +483,8 @@ class LitWriter(object):
data = rebin.content
name = name + '/content'
secnum = 1
elif isinstance(data, unicode):
data = data.encode('utf-8')
self._add_file(name, data, secnum)
item.size = len(data)
@ -493,7 +495,7 @@ class LitWriter(object):
if item.spine_position is not None:
key = 'linear' if item.linear else 'nonlinear'
manifest[key].append(item)
elif item.media_type == CSS_MIME:
elif item.media_type in OEB_STYLES:
manifest['css'].append(item)
elif item.media_type in LIT_IMAGES:
manifest['images'].append(item)
@ -506,6 +508,11 @@ class LitWriter(object):
data.write(pack('<I', len(items)))
for item in items:
id, media_type = item.id, item.media_type
if media_type in OEB_DOCS:
# Needs to have 'html' in media-type
media_type = XHTML_MIME
elif media_type in OEB_STYLES:
media_type = CSS_MIME
href = urlunquote(item.href)
item.offset = offset \
if state in ('linear', 'nonlinear') else 0
@ -525,7 +532,12 @@ class LitWriter(object):
pb3 = StringIO()
pb3cur = 0
bits = 0
linear = []
nonlinear = []
for item in self._oeb.spine:
dest = linear if item.linear else nonlinear
dest.append(item)
for item in chain(linear, nonlinear):
page_breaks = copy.copy(item.page_breaks)
if not item.linear:
page_breaks.insert(0, (0, []))

View File

@ -247,6 +247,12 @@ class MetaInformation(object):
if getattr(mi, 'cover_data', None) and mi.cover_data[0] is not None:
self.cover_data = mi.cover_data
def format_series_index(self):
try:
x = float(self.series_index)
except ValueError:
x = 1.0
return '%d'%x if int(x) == x else '%.2f'%x
def __unicode__(self):
ans = u''
@ -267,7 +273,7 @@ class MetaInformation(object):
if self.tags:
ans += u'Tags : ' + u', '.join([unicode(t) for t in self.tags]) + '\n'
if self.series:
ans += u'Series : '+unicode(self.series) + ' #%d\n'%self.series_index
ans += u'Series : '+unicode(self.series) + ' #%s\n'%self.format_series_index()
if self.language:
ans += u'Language : ' + unicode(self.language) + u'\n'
return ans.strip()
@ -277,11 +283,11 @@ class MetaInformation(object):
ans += [(_('Author(s)'), (authors_to_string(self.authors) if self.authors else _('Unknown')))]
ans += [(_('Publisher'), unicode(self.publisher))]
ans += [(_('Producer'), unicode(self.book_producer))]
ans += [(_('Category'), unicode(self.category))]
ans += [(_('Comments'), unicode(self.comments))]
ans += [('ISBN', unicode(self.isbn))]
ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))]
ans += [(_('Series'), unicode(self.series))]
if self.series:
ans += [(_('Series'), unicode(self.series))+ ' #%s'%self.format_series_index()]
ans += [(_('Language'), unicode(self.language))]
for i, x in enumerate(ans):
ans[i] = u'<tr><td><b>%s</b></td><td>%s</td></tr>'%x

View File

@ -6,9 +6,9 @@
>
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata">
<dc:title py:with="attrs={'opf:files-as':mi.title_sort}" py:attrs="attrs">${mi.title}</dc:title>
<dc:title py:with="attrs={'opf:file-as':mi.title_sort}" py:attrs="attrs">${mi.title}</dc:title>
<dc:creator opf:role="aut" py:for="i, author in enumerate(mi.authors)" py:attrs="{'opf:file-as':mi.author_sort} if mi.author_sort and i == 0 else {}">${author}</dc:creator>
<dc:contributor opf:role="bkp" py:with="attrs={'opf:files-as':__appname__}" py:attrs="attrs">${'%s (%s)'%(__appname__, __version__)} [http://${__appname__}.kovidgoyal.net]</dc:contributor>
<dc:contributor opf:role="bkp" py:with="attrs={'opf:file-as':__appname__}" py:attrs="attrs">${'%s (%s)'%(__appname__, __version__)} [http://${__appname__}.kovidgoyal.net]</dc:contributor>
<dc:identifier opf:scheme="${__appname__}" id="${__appname__}_id">${mi.application_id}</dc:identifier>
<dc:language>${mi.language if mi.language else 'UND'}</dc:language>

View File

@ -17,7 +17,7 @@ from calibre.ebooks.chardet import xml_to_unicode
from calibre import relpath
from calibre.constants import __appname__, __version__
from calibre.ebooks.metadata.toc import TOC
from calibre.ebooks.metadata import MetaInformation, get_parser
from calibre.ebooks.metadata import MetaInformation
class Resource(object):
@ -414,6 +414,7 @@ class OPF(object):
metadata_path = XPath('descendant::*[re:match(name(), "metadata", "i")]')
metadata_elem_path = XPath('descendant::*[re:match(name(), concat($name, "$"), "i") or (re:match(name(), "meta$", "i") and re:match(@name, concat("^calibre:", $name, "$"), "i"))]')
title_path = XPath('descendant::*[re:match(name(), "title", "i")]')
authors_path = XPath('descendant::*[re:match(name(), "creator", "i") and (@role="aut" or @opf:role="aut" or (not(@role) and not(@opf:role)))]')
bkp_path = XPath('descendant::*[re:match(name(), "contributor", "i") and (@role="bkp" or @opf:role="bkp")]')
tags_path = XPath('descendant::*[re:match(name(), "subject", "i")]')
@ -503,7 +504,7 @@ class OPF(object):
def set_text(self, elem, content):
if elem.tag == self.META:
elem.attib['content'] = content
elem.attrib['content'] = content
else:
elem.text = content
@ -641,6 +642,32 @@ class OPF(object):
def fset(self, val):
matches = self.authors_path(self.metadata)
if matches:
for key in matches[0].attrib:
if key.endswith('file-as'):
matches[0].attrib.pop(key)
matches[0].set('file-as', unicode(val))
return property(fget=fget, fset=fset)
@apply
def title_sort():
def fget(self):
matches = self.title_path(self.metadata)
if matches:
for match in matches:
ans = match.get('{%s}file-as'%self.NAMESPACES['opf'], None)
if not ans:
ans = match.get('file-as', None)
if ans:
return ans
def fset(self, val):
matches = self.title_path(self.metadata)
if matches:
for key in matches[0].attrib:
if key.endswith('file-as'):
matches[0].attrib.pop(key)
matches[0].set('file-as', unicode(val))
return property(fget=fget, fset=fset)
@ -789,12 +816,15 @@ class OPF(object):
return elem
def render(self, encoding='utf-8'):
return etree.tostring(self.root, encoding='utf-8', pretty_print=True)
raw = etree.tostring(self.root, encoding=encoding, pretty_print=True)
if not raw.lstrip().startswith('<?xml '):
raw = '<?xml version="1.0" encoding="%s"?>\n'%encoding.upper()+raw
return raw
def smart_update(self, mi):
for attr in ('author_sort', 'title_sort', 'comments', 'category',
for attr in ('title', 'authors', 'author_sort', 'title_sort',
'publisher', 'series', 'series_index', 'rating',
'isbn', 'language', 'tags', 'title', 'authors'):
'isbn', 'language', 'tags', 'category', 'comments'):
val = getattr(mi, attr, None)
if val is not None and val != [] and val != (None, None):
setattr(self, attr, val)
@ -877,7 +907,8 @@ class OPFCreator(MetaInformation):
self.guide = Guide.from_opf_guide(guide_element, self.base_path)
self.guide.set_basedir(self.base_path)
def render(self, opf_stream, ncx_stream=None, ncx_manifest_entry=None):
def render(self, opf_stream=sys.stdout, ncx_stream=None,
ncx_manifest_entry=None):
from calibre.resources import opf_template
from calibre.utils.genshi.template import MarkupTemplate
template = MarkupTemplate(opf_template)
@ -917,7 +948,7 @@ class OPFTest(unittest.TestCase):
<?xml version="1.0" encoding="UTF-8"?>
<package version="2.0" xmlns="http://www.idpf.org/2007/opf" >
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
<dc:title>A Cool &amp; &copy; &#223; Title</dc:title>
<dc:title opf:file-as="Wow">A Cool &amp; &copy; &#223; Title</dc:title>
<creator opf:role="aut" file-as="Monkey">Monkey Kitchen, Next</creator>
<dc:subject>One</dc:subject><dc:subject>Two</dc:subject>
<dc:identifier scheme="ISBN">123456789</dc:identifier>
@ -933,33 +964,50 @@ class OPFTest(unittest.TestCase):
)
self.opf = OPF(self.stream, os.getcwd())
def testReading(self):
def testReading(self, opf=None):
if opf is None:
opf = self.opf
self.assertEqual(opf.title, u'A Cool & \xa9 \xdf Title')
self.assertEqual(opf.authors, u'Monkey Kitchen,Next'.split(','))
self.assertEqual(opf.author_sort, 'Monkey')
self.assertEqual(opf.title_sort, 'Wow')
self.assertEqual(opf.tags, ['One', 'Two'])
self.assertEqual(opf.isbn, '123456789')
self.assertEqual(opf.series, 'A one book series')
self.assertEqual(opf.series_index, None)
self.assertEqual(opf.series_index, 1)
self.assertEqual(list(opf.itermanifest())[0].get('href'), 'a ~ b')
def testWriting(self):
for test in [('title', 'New & Title'), ('authors', ['One', 'Two']),
('author_sort', "Kitchen"), ('tags', ['Three']),
('isbn', 'a'), ('rating', 3), ('series_index', 1)]:
('isbn', 'a'), ('rating', 3), ('series_index', 1),
('title_sort', 'ts')]:
setattr(self.opf, *test)
self.assertEqual(getattr(self.opf, test[0]), test[1])
attr, val = test
self.assertEqual(getattr(self.opf, attr), val)
self.opf.render()
def testCreator(self):
opf = OPFCreator(os.getcwd(), self.opf)
buf = cStringIO.StringIO()
opf.render(buf)
raw = buf.getvalue()
self.testReading(opf=OPF(cStringIO.StringIO(raw), os.getcwd()))
def testSmartUpdate(self):
self.opf.smart_update(MetaInformation(self.opf))
self.testReading()
def suite():
return unittest.TestLoader().loadTestsFromTestCase(OPFTest)
def test():
unittest.TextTestRunner(verbosity=2).run(suite())
def option_parser():
from calibre.ebooks.metadata import get_parser
parser = get_parser('opf')
parser.add_option('--language', default=None, help=_('Set the dc:language field'))
return parser

View File

@ -138,6 +138,7 @@ class MobiMLizer(object):
return result
def mobimlize_content(self, tag, text, bstate, istates):
if text or tag != 'br':
bstate.content = True
istate = istates[-1]
para = bstate.para
@ -188,11 +189,6 @@ class MobiMLizer(object):
vspace -= 1
if istate.halign != 'auto':
para.attrib['align'] = istate.halign
if istate.ids:
last = bstate.body[-1]
for id in istate.ids:
last.addprevious(etree.Element(XHTML('a'), attrib={'id': id}))
istate.ids.clear()
pstate = bstate.istate
if tag in CONTENT_TAGS:
bstate.inline = para
@ -200,6 +196,11 @@ class MobiMLizer(object):
etree.SubElement(para, XHTML(tag), attrib=istate.attrib)
elif tag in TABLE_TAGS:
para.attrib['valign'] = 'top'
if istate.ids:
last = bstate.body[-1]
for id in istate.ids:
last.addprevious(etree.Element(XHTML('a'), attrib={'id': id}))
istate.ids.clear()
if not text:
return
if not pstate or istate != pstate:

View File

@ -121,6 +121,7 @@ class BookHeader(object):
sublangid = (langcode >> 10) & 0xFF
self.language = main_language.get(langid, 'ENGLISH')
self.sublanguage = sub_language.get(sublangid, 'NEUTRAL')
self.mobi_version = struct.unpack('>I', raw[0x68:0x6c])[0]
self.first_image_index = struct.unpack('>L', raw[0x6c:0x6c+4])[0]
self.exth_flag, = struct.unpack('>L', raw[0x80:0x84])
@ -132,11 +133,8 @@ class BookHeader(object):
class MobiReader(object):
PAGE_BREAK_PAT = re.compile(r'(<[/]{0,1}mbp:pagebreak\s*[/]{0,1}>)+', re.IGNORECASE)
IMAGE_PATS = map(re.compile, (r'\shirecindex=[\'"]{0,1}(\d+)[\'"]{0,1}',
r'\srecindex=[\'"]{0,1}(\d+)[\'"]{0,1}',
r'\slorecindex=[\'"]{0,1}(\d+)[\'"]{0,1}'))
IMAGE_ATTRS = ('lowrecindex', 'recindex', 'hirecindex')
def __init__(self, filename_or_stream, verbose=False):
self.verbose = verbose
@ -247,6 +245,7 @@ class MobiReader(object):
self.processed_html = re.sub(r'<div height="0(pt|px|ex|em|%){0,1}"></div>', '', self.processed_html)
if self.book_header.ancient and '<html' not in self.mobi_html[:300].lower():
self.processed_html = '<html><p>'+self.processed_html.replace('\n\n', '<p>')+'</html>'
self.processed_html = self.processed_html.replace('\r\n', '\n')
self.processed_html = self.processed_html.replace('> <', '>\n<')
for t, c in [('b', 'bold'), ('i', 'italic')]:
self.processed_html = re.sub(r'(?i)<%s>'%t, r'<span class="%s">'%c, self.processed_html)
@ -264,6 +263,7 @@ class MobiReader(object):
'x-large' : '5',
'xx-large' : '6',
}
mobi_version = self.book_header.mobi_version
for tag in root.iter(etree.Element):
if tag.tag in ('country-region', 'place', 'placetype', 'placename',
'state', 'city'):
@ -290,6 +290,11 @@ class MobiReader(object):
align = attrib.pop('align').strip()
if align:
styles.append('text-align: %s' % align)
if mobi_version == 1 and tag.tag == 'hr':
tag.tag = 'div'
styles.append('page-break-before: always')
styles.append('display: block')
styles.append('margin: 0')
if styles:
attrib['style'] = '; '.join(styles)
@ -300,6 +305,17 @@ class MobiReader(object):
except ValueError:
if sz in size_map.keys():
attrib['size'] = size_map[sz]
if 'filepos-id' in attrib:
attrib['id'] = attrib.pop('filepos-id')
if 'filepos' in attrib:
filepos = int(attrib.pop('filepos'))
attrib['href'] = "#filepos%d" % filepos
if tag.tag == 'img':
recindex = None
for attr in self.IMAGE_ATTRS:
recindex = attrib.pop(attr, None) or recindex
if recindex is not None:
attrib['src'] = 'images/%s.jpg' % recindex
def create_opf(self, htmlfile, guide=None):
mi = self.book_header.exth.mi
@ -399,37 +415,39 @@ class MobiReader(object):
def replace_page_breaks(self):
self.processed_html = self.PAGE_BREAK_PAT.sub('<br style="page-break-after:always" />',
self.processed_html = self.PAGE_BREAK_PAT.sub(
'<div style="page-break-after: always; margin: 0; display: block" />',
self.processed_html)
def add_anchors(self):
if self.verbose:
print 'Adding anchors...'
positions = set([])
link_pattern = re.compile(r'<[^<>]+filepos=[\'"]{0,1}(\d+)[^<>]*>', re.IGNORECASE)
link_pattern = re.compile(r'''<[^<>]+filepos=['"]{0,1}(\d+)[^<>]*>''',
re.IGNORECASE)
for match in link_pattern.finditer(self.mobi_html):
positions.add(int(match.group(1)))
positions = list(positions)
positions.sort()
pos = 0
self.processed_html = ''
for end in positions:
end_tag_re = re.compile(r'<\s*/')
for end in sorted(positions):
if end == 0:
continue
oend = end
l = self.mobi_html.find('<', end)
r = self.mobi_html.find('>', end)
if r > -1 and r < l: # Move out of tag
end = r+1
self.processed_html += self.mobi_html[pos:end] + '<a id="filepos%d" name="filepos%d"></a>'%(oend, oend)
anchor = '<a id="filepos%d"></a>'
if r > -1 and (r < l or l == end or l == -1):
p = self.mobi_html.rfind('<', 0, end + 1)
if pos < end and p > -1 and \
not end_tag_re.match(self.mobi_html[p:r]):
anchor = ' filepos-id="filepos%d"'
end = r
else:
end = r + 1
self.processed_html += self.mobi_html[pos:end] + (anchor % oend)
pos = end
self.processed_html += self.mobi_html[pos:]
fpat = re.compile(r'filepos=[\'"]{0,1}(\d+)[\'"]{0,1}', re.IGNORECASE)
def fpos_to_href(match):
return fpat.sub('href="#filepos%d"'%int(match.group(1)), match.group())
self.processed_html = link_pattern.sub(fpos_to_href,
self.processed_html)
def extract_images(self, processed_records, output_dir):
if self.verbose:
@ -455,19 +473,6 @@ class MobiReader(object):
self.image_names.append(os.path.basename(path))
im.convert('RGB').save(open(path, 'wb'), format='JPEG')
def fix_images(match):
tag = match.group()
for pat in self.IMAGE_PATS:
m = pat.search(tag)
if m:
return pat.sub(' src="images/%s.jpg"'%m.group(1), tag)
if hasattr(self, 'processed_html'):
self.processed_html = \
re.compile(r'<img (.*?)>', re.IGNORECASE|re.DOTALL)\
.sub(fix_images, self.processed_html)
def get_metadata(stream):
mr = MobiReader(stream)
if mr.book_header.exth is None:

File diff suppressed because it is too large Load Diff

View File

@ -109,6 +109,7 @@ class Stylizer(object):
STYLESHEETS = {}
def __init__(self, tree, path, oeb, profile=PROFILES['PRS505']):
self.oeb = oeb
self.profile = profile
self.logger = oeb.logger
item = oeb.manifest.hrefs[path]
@ -117,7 +118,7 @@ class Stylizer(object):
stylesheets = [HTML_CSS_STYLESHEET]
head = xpath(tree, '/h:html/h:head')[0]
parser = cssutils.CSSParser()
parser.setFetcher(lambda path: ('utf-8', oeb.container.read(path)))
parser.setFetcher(self._fetch_css_file)
for elem in head:
if elem.tag == XHTML('style') and elem.text \
and elem.get('type', CSS_MIME) in OEB_STYLES:
@ -138,8 +139,7 @@ class Stylizer(object):
if path in self.STYLESHEETS:
stylesheet = self.STYLESHEETS[path]
else:
data = XHTML_CSS_NAMESPACE
data += oeb.manifest.hrefs[path].data
data = self._fetch_css_file(path)[1]
stylesheet = parser.parseString(data, href=path)
stylesheet.namespaces['h'] = XHTML_NS
self.STYLESHEETS[path] = stylesheet
@ -167,6 +167,14 @@ class Stylizer(object):
for elem in xpath(tree, '//h:*[@style]'):
self.style(elem)._apply_style_attr()
def _fetch_css_file(self, path):
hrefs = self.oeb.manifest.hrefs
if path not in hrefs:
return (None, None)
data = hrefs[path].data
data = XHTML_CSS_NAMESPACE + data
return ('utf-8', data)
def flatten_rule(self, rule, href, index):
results = []
if isinstance(rule, CSSStyleRule):

View File

@ -13,13 +13,9 @@ from urlparse import urldefrag
from lxml import etree
import cssutils
from calibre.ebooks.oeb.base import XPNSMAP, CSS_MIME, OEB_DOCS
from calibre.ebooks.oeb.base import LINK_SELECTORS, CSSURL_RE
from calibre.ebooks.oeb.base import urlnormalize
LINK_SELECTORS = []
for expr in ('//h:link/@href', '//h:img/@src', '//h:object/@data',
'//*/@xl:href'):
LINK_SELECTORS.append(etree.XPath(expr, namespaces=XPNSMAP))
class ManifestTrimmer(object):
def transform(self, oeb, context):
oeb.logger.info('Trimming unused files from manifest...')
@ -53,15 +49,13 @@ class ManifestTrimmer(object):
if found not in used:
new.add(found)
elif item.media_type == CSS_MIME:
def replacer(uri):
absuri = item.abshref(urlnormalize(uri))
if absuri in oeb.manifest.hrefs:
for match in CSSURL_RE.finditer(item.data):
href = match.group('url')
href = item.abshref(urlnormalize(href))
if href in oeb.manifest.hrefs:
found = oeb.manifest.hrefs[href]
if found not in used:
new.add(found)
return uri
sheet = cssutils.parseString(item.data, href=item.href)
cssutils.replaceUrls(sheet, replacer)
used.update(new)
unchecked = new
for item in oeb.manifest.values():

View File

@ -18,7 +18,7 @@ from calibre.gui2 import error_dialog, choose_images, pixmap_to_data, ResizableD
from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config as epubconfig
from calibre.ebooks.metadata import MetaInformation
from calibre.ptempfile import PersistentTemporaryFile
from calibre.ebooks.metadata.opf import OPFCreator
from calibre.ebooks.metadata.opf2 import OPFCreator
from calibre.ebooks.metadata import authors_to_string, string_to_authors
@ -224,6 +224,7 @@ class Config(ResizableDialog, Ui_Dialog):
g.setValue(val)
elif isinstance(g, (QLineEdit, QTextEdit)):
getattr(g, 'setPlainText', g.setText)(val)
getattr(g, 'setCursorPosition', lambda x: x)(0)
elif isinstance(g, QComboBox):
for value in pref.choices:
g.addItem(value)
@ -253,7 +254,8 @@ class Config(ResizableDialog, Ui_Dialog):
self.source_format = d.format()
def accept(self):
for opt in ('chapter', 'level1_toc', 'level2_toc', 'level3_toc'):
for opt in ('chapter', 'level1_toc', 'level2_toc', 'level3_toc', 'page',
'page_names'):
text = unicode(getattr(self, 'opt_'+opt).text())
if text:
try:

View File

@ -493,6 +493,20 @@
</property>
</widget>
</item>
<item row="7" column="0" >
<widget class="QCheckBox" name="opt_no_justification" >
<property name="text" >
<string>No text &amp;justification</string>
</property>
</widget>
</item>
<item row="8" column="0" >
<widget class="QCheckBox" name="opt_linearize_tables" >
<property name="text" >
<string>&amp;Linearize tables</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
@ -510,7 +524,7 @@
</layout>
</widget>
<widget class="QWidget" name="pagesetup_page" >
<layout class="QGridLayout" name="_13" >
<layout class="QGridLayout" name="gridLayout_7" >
<item row="0" column="0" >
<widget class="QLabel" name="profile_label" >
<property name="text" >
@ -531,6 +545,32 @@
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="source_profile_label" >
<property name="text" >
<string>&amp;Source profile:</string>
</property>
<property name="buddy" >
<cstring>opt_source_profile</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QComboBox" name="opt_source_profile" />
</item>
<item row="2" column="0" >
<widget class="QLabel" name="dest_profile_label" >
<property name="text" >
<string>&amp;Destination profile:</string>
</property>
<property name="buddy" >
<cstring>opt_dest_profile</cstring>
</property>
</widget>
</item>
<item row="2" column="1" >
<widget class="QComboBox" name="opt_dest_profile" />
</item>
<item row="3" column="0" >
<widget class="QLabel" name="label_12" >
<property name="text" >
@ -630,31 +670,72 @@
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="source_profile_label" >
<item row="8" column="0" colspan="2" >
<widget class="QGroupBox" name="page_map_box" >
<property name="title" >
<string>&amp;Page map</string>
</property>
<layout class="QGridLayout" name="gridLayout" >
<item rowspan="2" row="0" column="0" colspan="4" >
<widget class="QLabel" name="label_23" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Minimum" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" >
<string>&amp;Source profile:</string>
<string>&lt;p>You can control how calibre detects page boundaries using a XPath expression. To learn how to use XPath expressions see the &lt;a href="http://calibre.kovidgoyal.net/user_manual/xpath.html">XPath tutorial&lt;/a>. The page boundaries are useful only if you want a mapping from pages in a paper book, to locations in the e-book. This controls where Adobe Digital Editions displays the page numbers in the right margin.&lt;/p></string>
</property>
<property name="wordWrap" >
<bool>true</bool>
</property>
<property name="openExternalLinks" >
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_21" >
<property name="text" >
<string>&amp;Boundary XPath:</string>
</property>
<property name="buddy" >
<cstring>opt_source_profile</cstring>
<cstring>opt_page</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QComboBox" name="opt_source_profile" />
<widget class="QLineEdit" name="opt_page" />
</item>
<item row="2" column="0" >
<widget class="QLabel" name="dest_profile_label" >
<item row="1" column="2" >
<widget class="QLabel" name="label_22" >
<property name="text" >
<string>&amp;Destination profile:</string>
<string>&amp;Name XPath:</string>
</property>
<property name="buddy" >
<cstring>opt_dest_profile</cstring>
<cstring>opt_page_names</cstring>
</property>
</widget>
</item>
<item row="2" column="1" >
<widget class="QComboBox" name="opt_dest_profile" />
<item row="1" column="3" >
<widget class="QLineEdit" name="opt_page_names" />
</item>
</layout>
</widget>
</item>
<item row="9" column="0" >
<spacer name="verticalSpacer" >
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
@ -665,8 +746,26 @@
<property name="title" >
<string>Automatic &amp;chapter detection</string>
</property>
<layout class="QGridLayout" name="gridLayout" >
<item row="1" column="0" >
<layout class="QVBoxLayout" name="verticalLayout_4" >
<item>
<widget class="QLabel" name="label_8" >
<property name="text" >
<string>&lt;p>You can control how calibre detects chapters using a XPath expression. To learn how to use XPath expressions see the &lt;a href="http://calibre.kovidgoyal.net/user_manual/xpath.html">XPath tutorial&lt;/a>&lt;/p></string>
</property>
<property name="textFormat" >
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap" >
<bool>true</bool>
</property>
<property name="openExternalLinks" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3" >
<item>
<widget class="QLabel" name="label_17" >
<property name="text" >
<string>&amp;XPath:</string>
@ -676,30 +775,10 @@
</property>
</widget>
</item>
<item row="1" column="1" >
<item>
<widget class="QLineEdit" name="opt_chapter" />
</item>
<item row="0" column="0" colspan="2" >
<widget class="QLabel" name="label_8" >
<property name="text" >
<string>&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
&lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css">
p, li { white-space: pre-wrap; }
&lt;/style>&lt;/head>&lt;body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;">
&lt;p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">You can control how calibre detects chapters using a XPath expression. To learn how to use XPath expressions see the &lt;a href="https://calibre.kovidgoyal.net/user_manual/xpath.html">&lt;span style=" text-decoration: underline; color:#0000ff;">XPath tutorial&lt;/span>&lt;/a>&lt;/p>&lt;/body>&lt;/html></string>
</property>
<property name="textFormat" >
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap" >
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1" >
<widget class="QComboBox" name="opt_chapter_mark" />
</item>
<item row="2" column="0" >
<item>
<widget class="QLabel" name="label_9" >
<property name="text" >
<string>Chapter &amp;mark:</string>
@ -709,6 +788,11 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="opt_chapter_mark" />
</item>
</layout>
</item>
</layout>
</widget>
</item>
@ -834,7 +918,7 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
<item row="2" column="0" colspan="2" >
<item row="3" column="0" colspan="2" >
<widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>

View File

@ -18,3 +18,6 @@ class Config(_Config):
self.opt_profile.setVisible(False)
self.opt_dont_split_on_page_breaks.setVisible(False)
self.opt_preserve_tag_structure.setVisible(False)
self.opt_linearize_tables.setVisible(False)
self.opt_no_justification.setVisible(False)
self.page_map_box.setVisible(False)

View File

@ -1,16 +0,0 @@
__license__ = 'GPL v3'
__copyright__ = '2009, John Schember john@nachtimwald.com'
'''
List View for showing recipies. Allows for keyboad events when selecting new
items.
'''
from PyQt4.Qt import QListView, SIGNAL
class RecipeListView(QListView):
def __init__(self, *args):
QListView.__init__(self, *args)
def selectionChanged(self, selected, deselected):
self.emit(SIGNAL('itemChanged(QModelIndex)'), selected.indexes()[0])

View File

@ -10,8 +10,8 @@ Scheduler for automated recipe downloads
import sys, copy, time
from datetime import datetime, timedelta, date
from PyQt4.Qt import QDialog, QApplication, QLineEdit, QPalette, SIGNAL, QBrush, \
QColor, QAbstractListModel, Qt, QVariant, QFont, QIcon, \
QFile, QObject, QTimer, QMutex, QMenu, QAction, QTime
QColor, QAbstractItemModel, Qt, QVariant, QFont, QIcon, \
QFile, QObject, QTimer, QMutex, QMenu, QAction, QTime, QModelIndex
from calibre import english_sort
from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog
@ -30,6 +30,7 @@ class Recipe(object):
self.id = id
self.title = getattr(recipe_class, 'title', None)
self.description = getattr(recipe_class, 'description', None)
self.language = getattr(recipe_class, 'language', _('Unknown'))
self.last_downloaded = datetime.fromordinal(1)
self.downloading = False
self.builtin = builtin
@ -86,12 +87,12 @@ def load_recipes():
recipes.append(r)
return recipes
class RecipeModel(QAbstractListModel, SearchQueryParser):
class RecipeModel(QAbstractItemModel, SearchQueryParser):
LOCATIONS = ['all']
def __init__(self, db, *args):
QAbstractListModel.__init__(self, *args)
QAbstractItemModel.__init__(self, *args)
SearchQueryParser.__init__(self)
self.default_icon = QIcon(':/images/news.svg')
self.custom_icon = QIcon(':/images/user_profile.svg')
@ -100,7 +101,10 @@ class RecipeModel(QAbstractListModel, SearchQueryParser):
recipe = compile_recipe(x[1])
self.recipes.append(Recipe(x[0], recipe, False))
self.refresh()
self._map = list(range(len(self.recipes)))
self.bold_font = QFont()
self.bold_font.setBold(True)
self.bold_font = QVariant(self.bold_font)
def refresh(self):
sr = load_recipes()
@ -110,6 +114,34 @@ class RecipeModel(QAbstractListModel, SearchQueryParser):
recipe.last_downloaded = sr[sr.index(recipe)].last_downloaded
self.recipes.sort()
self.num_of_recipes = len(self.recipes)
self.category_map = {}
for r in self.recipes:
category = getattr(r, 'language', _('Unknown'))
if not r.builtin:
category = _('Custom')
if r.schedule is not None:
category = _('Scheduled')
if category not in self.category_map.keys():
self.category_map[category] = []
self.category_map[category].append(r)
self.categories = sorted(self.category_map.keys(), cmp=self.sort_categories)
self._map = dict(self.category_map)
def sort_categories(self, x, y):
def decorate(x):
if x == _('Scheduled'):
x = '0' + x
elif x == _('Custom'):
x = '1' + x
else:
x = '2' + x
return x
return cmp(decorate(x), decorate(y))
def universal_set(self):
@ -129,36 +161,46 @@ class RecipeModel(QAbstractListModel, SearchQueryParser):
try:
results = self.parse(unicode(query))
except ParseException:
self._map = list(range(len(self.recipes)))
self._map = dict(self.category_map)
else:
self._map = []
for i, recipe in enumerate(self.recipes):
self._map = {}
for category in self.categories:
self._map[category] = []
for recipe in self.category_map[category]:
if recipe in results:
self._map.append(i)
self._map[category].append(recipe)
self.reset()
def resort(self):
self.recipes.sort()
self.reset()
def columnCount(self, *args):
return 1
def index(self, row, column, parent):
return self.createIndex(row, column, parent.row() if parent.isValid() else -1)
def rowCount(self, *args):
return len(self._map)
def parent(self, index):
if index.internalId() == -1:
return QModelIndex()
return self.createIndex(index.internalId(), 0, -1)
def columnCount(self, parent):
if not parent.isValid() or not parent.parent().isValid():
return 1
return 0
def rowCount(self, parent):
if not parent.isValid():
return len(self.categories)
if not parent.parent().isValid():
category = self.categories[parent.row()]
return len(self._map[category])
return 0
def data(self, index, role):
recipe = self.recipes[self._map[index.row()]]
if role == Qt.FontRole:
if recipe.schedule is not None:
font = QFont()
font.setBold(True)
return QVariant(font)
if not recipe.builtin:
font = QFont()
font.setItalic(True)
return QVariant(font)
elif role == Qt.DisplayRole:
if index.parent().isValid():
category = self.categories[index.parent().row()]
recipe = self._map[category][index.row()]
if role == Qt.DisplayRole:
return QVariant(recipe.title)
elif role == Qt.UserRole:
return recipe
@ -170,7 +212,13 @@ class RecipeModel(QAbstractListModel, SearchQueryParser):
elif QFile().exists(icon_path):
icon = QIcon(icon_path)
return QVariant(icon)
else:
category = self.categories[index.row()]
if role == Qt.DisplayRole:
num = len(self._map[category])
return QVariant(category + ' [%d]'%num)
elif role == Qt.FontRole:
return self.bold_font
return NONE
def update_recipe_schedule(self, recipe):
@ -241,7 +289,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self._model = RecipeModel(db)
self.current_recipe = None
self.recipes.setModel(self._model)
self.connect(self.recipes, SIGNAL('itemChanged(QModelIndex)'), self.show_recipe)
self.recipes.currentChanged = self.currentChanged
self.connect(self.username, SIGNAL('textEdited(QString)'), self.set_account_info)
self.connect(self.password, SIGNAL('textEdited(QString)'), self.set_account_info)
self.connect(self.schedule, SIGNAL('stateChanged(int)'), self.do_schedule)
@ -257,11 +305,15 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.connect(self.download, SIGNAL('clicked()'), self.download_now)
self.search.setFocus(Qt.OtherFocusReason)
self.old_news.setValue(gconf['oldest_news'])
self.rnumber.setText(_('%d recipes')%self._model.rowCount(None))
self.rnumber.setText(_('%d recipes')%self._model.num_of_recipes)
for day in (_('day'), _('Monday'), _('Tuesday'), _('Wednesday'),
_('Thursday'), _('Friday'), _('Saturday'), _('Sunday')):
self.day.addItem(day)
def currentChanged(self, current, previous):
if current.parent().isValid():
self.show_recipe(current)
def download_now(self):
recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole)
self.emit(SIGNAL('download_now(PyQt_PyObject)'), recipe)
@ -304,6 +356,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
hour, minute = t.hour(), t.minute()
recipe.schedule = encode_schedule(day_of_week, hour, minute)
else:
recipe.schedule = None
if recipe in recipes:
recipes.remove(recipe)
save_recipes(recipes)
@ -432,7 +485,7 @@ class Scheduler(QObject):
day_matches = day > 6 or day == now.tm_wday
tnow = now.tm_hour*60 + now.tm_min
matches = day_matches and (hour*60+minute) < tnow
if matches and nowt.toordinal() < date.today().toordinal():
if matches and recipe.last_downloaded.toordinal() < date.today().toordinal():
needs_downloading.add(recipe)
self.debug('Needs downloading:', needs_downloading)
@ -464,7 +517,7 @@ class Scheduler(QObject):
recipe = self.recipes[self.recipes.index(recipe)]
now = datetime.utcnow()
d = now - recipe.last_downloaded
if recipe.schedule is not None:
if recipe.schedule is not None and recipe.schedule < 1e4:
interval = timedelta(days=recipe.schedule)
if abs(d - interval) < timedelta(hours=1):
recipe.last_downloaded += interval

View File

@ -24,8 +24,20 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout" >
<item>
<widget class="RecipeListView" name="recipes" >
<property name="alternatingRowColors" >
<widget class="QTreeView" name="recipes" >
<property name="showDropIndicator" stdset="0" >
<bool>false</bool>
</property>
<property name="iconSize" >
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="animated" >
<bool>true</bool>
</property>
<property name="headerHidden" >
<bool>true</bool>
</property>
</widget>
@ -321,13 +333,6 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>RecipeListView</class>
<extends>QListView</extends>
<header>recipelistview.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../images.qrc" />
</resources>

View File

@ -114,6 +114,9 @@
<property name="text" >
<string>See the &lt;a href="http://calibre.kovidgoyal.net/user_manual/gui.html#the-search-interface">User Manual&lt;/a> for more help</string>
</property>
<property name="openExternalLinks" >
<bool>true</bool>
</property>
</widget>
</item>
<item>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

View File

@ -115,16 +115,11 @@ class Main(MainWindow, Ui_MainWindow):
self.connect(self.donate_action, SIGNAL('triggered(bool)'), self.donate)
self.connect(self.restore_action, SIGNAL('triggered(bool)'), lambda c : self.show())
self.connect(self.action_show_book_details, SIGNAL('triggered(bool)'), self.show_book_info)
def restart_app(c):
self.quit(None, restart=True)
self.connect(self.action_restart, SIGNAL('triggered(bool)'), restart_app)
def sta(r):
if r == QSystemTrayIcon.Trigger:
self.hide() if self.isVisible() else self.show()
self.connect(self.system_tray_icon, SIGNAL('activated(QSystemTrayIcon::ActivationReason)'), sta)
def tcme(self, *args):
pass
self.tool_bar.contextMenuEvent = tcme
self.connect(self.action_restart, SIGNAL('triggered(bool)'),
lambda c : self.quit(None, restart=True))
self.connect(self.system_tray_icon, SIGNAL('activated(QSystemTrayIcon::ActivationReason)'),
self.system_tray_icon_activated)
self.tool_bar.contextMenuEvent = self.no_op
####################### Location View ########################
QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'),
self.location_selected)
@ -165,15 +160,11 @@ class Main(MainWindow, Ui_MainWindow):
sm.addSeparator()
sm.addAction(_('Send to storage card by default'))
sm.actions()[-1].setCheckable(True)
def default_sync(checked):
config.set('send_to_storage_card_by_default', bool(checked))
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_main_memory)
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_card)
QObject.connect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_card if checked else self.sync_to_main_memory)
QObject.connect(sm.actions()[-1], SIGNAL('toggled(bool)'), default_sync)
QObject.connect(sm.actions()[-1], SIGNAL('toggled(bool)'),
self.do_default_sync)
sm.actions()[-1].setChecked(config.get('send_to_storage_card_by_default'))
default_sync(sm.actions()[-1].isChecked())
self.do_default_sync(sm.actions()[-1].isChecked())
self.sync_menu = sm # Needed
md = QMenu()
md.addAction(_('Edit metadata individually'))
@ -294,7 +285,7 @@ class Main(MainWindow, Ui_MainWindow):
self.stack.setCurrentIndex(0)
try:
db = LibraryDatabase2(self.library_path)
except OSError, err:
except Exception, err:
error_dialog(self, _('Bad database location'), unicode(err)).exec_()
dir = unicode(QFileDialog.getExistingDirectory(self,
_('Choose a location for your ebook library.'), os.path.expanduser('~')))
@ -371,6 +362,32 @@ class Main(MainWindow, Ui_MainWindow):
self.connect(self.action_news, SIGNAL('triggered(bool)'), self.scheduler.show_dialog)
self.location_view.setCurrentIndex(self.location_view.model().index(0))
def no_op(self, *args):
pass
def system_tray_icon_activated(self, r):
if r == QSystemTrayIcon.Trigger:
if self.isVisible():
for window in QApplication.topLevelWidgets():
if isinstance(window, (MainWindow, QDialog)) and window.isVisible():
window.hide()
setattr(window, '__systray_minimized', True)
else:
for window in QApplication.topLevelWidgets():
if getattr(window, '__systray_minimized', False):
window.show()
setattr(window, '__systray_minimized', False)
def do_default_sync(self, checked):
config.set('send_to_storage_card_by_default', bool(checked))
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"),
self.sync_to_main_memory)
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"),
self.sync_to_card)
QObject.connect(self.action_sync, SIGNAL("triggered(bool)"),
self.sync_to_card if checked else self.sync_to_main_memory)
def change_output_format(self, x):
of = unicode(x).strip()
if of != prefs['output_format']:
@ -1426,10 +1443,10 @@ class Main(MainWindow, Ui_MainWindow):
def donate(self, *args):
BUTTON = '''
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_s-xclick">
<input type="hidden" name="hosted_button_id" value="1335186">
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="">
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="hidden" name="hosted_button_id" value="3029467" />
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="Donate to support calibre development" />
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
</form>
'''
MSG = _('is the result of the efforts of many volunteers from all over the world. If you find it useful, please consider donating to support its development.')

View File

@ -27,15 +27,6 @@ from calibre.customize.ui import run_plugins_on_import
from calibre import sanitize_file_name
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
iscaseinsensitive = iswindows or isosx
def normpath(x):
# The builtin os.path.normcase doesn't work on OS X
x = os.path.abspath(x)
if iscaseinsensitive:
x = x.lower()
return x
FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5,
'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10,
@ -355,6 +346,8 @@ class LibraryDatabase2(LibraryDatabase):
if isinstance(self.dbpath, unicode):
self.dbpath = self.dbpath.encode(filesystem_encoding)
self.connect()
self.is_case_sensitive = not iswindows and not isosx and \
not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB'))
# Upgrade database
while True:
meth = getattr(self, 'upgrade_version_%d'%self.user_version, None)
@ -488,6 +481,16 @@ class LibraryDatabase2(LibraryDatabase):
name = title + ' - ' + author
return name
def rmtree(self, path):
if not self.normpath(self.library_path).startswith(self.normpath(path)):
shutil.rmtree(path)
def normpath(self, path):
path = os.path.abspath(os.path.realpath(path))
if not self.is_case_sensitive:
path = path.lower()
return path
def set_path(self, index, index_is_id=False):
'''
Set the path to the directory containing this books files based on its
@ -531,11 +534,11 @@ class LibraryDatabase2(LibraryDatabase):
self.data.set(id, FIELD_MAP['path'], path, row_is_id=True)
# Delete not needed directories
if current_path and os.path.exists(spath):
if normpath(spath) != normpath(tpath):
shutil.rmtree(spath)
if self.normpath(spath) != self.normpath(tpath):
self.rmtree(spath)
parent = os.path.dirname(spath)
if len(os.listdir(parent)) == 0:
shutil.rmtree(parent)
self.rmtree(parent)
def add_listener(self, listener):
'''
@ -698,10 +701,10 @@ class LibraryDatabase2(LibraryDatabase):
path = os.path.join(self.library_path, self.path(id, index_is_id=True))
self.data.remove(id)
if os.path.exists(path):
shutil.rmtree(path)
self.rmtree(path)
parent = os.path.dirname(path)
if len(os.listdir(parent)) == 0:
shutil.rmtree(parent)
self.rmtree(parent)
self.conn.execute('DELETE FROM books WHERE id=?', (id,))
self.conn.commit()
self.clean()

View File

@ -102,7 +102,7 @@ Device Integration
What devices does |app| support?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
At the moment |app| has full support for the SONY PRS 500/505/700, Cybook Gen 3 as well as the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk.
At the moment |app| has full support for the SONY PRS 500/505/700, Cybook Gen 3, Amazon Kindle as well as the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk.
I used |app| to transfer some books to my reader, and now the SONY software hangs every time I connect the reader?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -161,7 +161,7 @@ class WorkerMother(object):
self.executable = self.gui_executable = sys.executable
self.prefix = ''
if isfrozen:
fd = getattr(sys, 'frameworks_dir')
fd = os.path.realpath(getattr(sys, 'frameworks_dir'))
contents = os.path.dirname(fd)
self.gui_executable = os.path.join(contents, 'MacOS',
os.path.basename(sys.executable))

View File

@ -196,7 +196,7 @@ class Server(object):
def calculate_month_trend(self, days=31):
stats = self.get_slice(date.today()-timedelta(days=days-1), date.today())
fig = plt.figure(2, (8, 3), 96)#, facecolor, edgecolor, frameon, FigureClass)
fig = plt.figure(2, (12, 4), 96)#, facecolor, edgecolor, frameon, FigureClass)
ax = fig.add_subplot(111)
x = list(range(days-1, -1, -1))
y = stats.daily_totals
@ -205,6 +205,17 @@ class Server(object):
ax.set_ylabel('Income ($)')
ax.hlines([stats.daily_average], 0, days-1)
ax.set_xlim([0, days-1])
text = u'''\
Total: $%(total).2f
Daily average: $%(da).2f \u00b1 %(dd).2f
Average contribution: $%(ac).2f \u00b1 %(ad).2f
Donors per day: %(dpd).2f
'''%dict(total=stats.total, da=stats.daily_average,
dd=stats.daily_deviation, ac=stats.average,
ad=stats.average_deviation,
dpd=len(stats.totals)/float(stats.period.days),
)
text = ax.annotate(text, (0.6, 0.65), textcoords='axes fraction')
fig.savefig(self.MONTH_TRENDS)
def calculate_trend(self):
@ -223,7 +234,7 @@ class Server(object):
x = [m.min for m in _months]
y = [m.total for m in _months]
ml = mdates.MonthLocator() # every month
fig = plt.figure(1, (8, 3), 96)#, facecolor, edgecolor, frameon, FigureClass)
fig = plt.figure(1, (8, 4), 96)#, facecolor, edgecolor, frameon, FigureClass)
ax = fig.add_subplot(111)
ax.bar(x, y, align='center', width=20, color='g')
ax.xaxis.set_major_locator(ml)

View File

@ -25,13 +25,12 @@
<div>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!" />
<input type="hidden" name="hosted_button_id" value="3029289" />
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="Donate to support calibre development" />
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----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-----END PKCS7-----" />
</form>
</div>
<br />
</div>
<h2>Note</h2>
<div class="note">$note</div>

View File

@ -58,9 +58,9 @@ python setup.py build &amp;&amp; sudo python setup.py install
<div>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!" />
<input type="hidden" name="hosted_button_id" value="3029289" />
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="Donate to support calibre development" />
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----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-----END PKCS7-----" />
</form>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
"Generated-By: pygettext.py 1.5\n"

View File

@ -17,7 +17,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
"Generated-By: pygettext.py 1.5\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -17,7 +17,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -13,7 +13,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
"Generated-By: pygettext.py 1.5\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -8,13 +8,13 @@ msgstr ""
"Project-Id-Version: calibre\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2009-01-27 01:54+0000\n"
"PO-Revision-Date: 2009-01-23 21:22+0000\n"
"Last-Translator: Molnár Gábor <csirkus@gmail.com>\n"
"PO-Revision-Date: 2009-02-04 20:39+0000\n"
"Last-Translator: Kovid Goyal <Unknown>\n"
"Language-Team: Hungarian <hu@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
@ -142,7 +142,7 @@ msgstr "Tömörített könyvek metaadatait is olvassa be"
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:196
msgid "Read metadata from ebooks in RAR archives"
msgstr ""
msgstr "Metaadatok olvasása a RAR-ral tömörített könyvekből is"
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:207
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:217
@ -274,7 +274,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:110
msgid "Control auto-detection of document structure."
msgstr ""
msgstr "Dokumentum-struktúra automatikus felismerése."
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:112
msgid ""
@ -832,6 +832,9 @@ msgid ""
"FONT_DELTA pts. FONT_DELTA can be a fraction.If FONT_DELTA is negative, the "
"font size is decreased."
msgstr ""
"A betűméret nővelése 2*FONT_DELTA ponttal, és a sortávolság növelése "
"FONT_DELTA ponttal. A FONT_DELTA érték törtszám is lehet, valamint ha "
"negatív az értéke, a betűméret csökkenni fog."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:125
msgid ""
@ -1086,6 +1089,8 @@ msgstr "Szerző a fájl metaadataiban. Alapértelmezés: %default"
msgid ""
"Path to output file. By default a file is created in the current directory."
msgstr ""
"A kimeneti fájl útvonala. Alapértelmezés szerint az aktuális könyvtárba "
"kerül a fájl."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:299
msgid "Number of colors for grayscale image conversion. Default: %default"
@ -1134,6 +1139,8 @@ msgid ""
"Don't sort the files found in the comic alphabetically by name. Instead use "
"the order they were added to the comic."
msgstr ""
"A képregény csomagban talált fájlokat a képregényhez való hozzáadás "
"sorrendje alapján rendezze a névsorrend helyett."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:317
msgid ""
@ -1185,6 +1192,10 @@ msgid ""
" \n"
"%prog converts mybook.epub to mybook.lrf"
msgstr ""
"Használat: %prog [beállítások] konyv.epub\n"
" \n"
" \n"
"A %prog a konyv.epub fájlt lrf formátumba konvertálja."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:23
msgid ""
@ -1205,7 +1216,7 @@ msgstr "A létrehozott HTML kiírása a kimenetre, majd kilépés."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:30
msgid "Keep generated HTML files after completing conversion to LRF."
msgstr ""
msgstr "Ne törölje a konvertálásnál keletkező átmeneti HTML fájlokat."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/feeds/convert_from.py:20
msgid "Options to control the behavior of feeds2disk"
@ -1225,7 +1236,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:319
msgid "\tParsing HTML..."
msgstr ""
msgstr "\tHTML beolvasása..."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:342
msgid "\tBaen file detected. Re-parsing..."
@ -1241,71 +1252,79 @@ msgstr "Feldolgozás: %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:390
msgid "\tConverting to BBeB..."
msgstr ""
msgstr "\tKonvertálás BBeB formátumba..."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:536
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:549
msgid "Could not parse file: %s"
msgstr ""
msgstr "Nem tudtam feldolgozni a fájlt: %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:541
msgid "%s is an empty file"
msgstr ""
msgstr "A %s fájl üres!"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:561
msgid "Failed to parse link %s %s"
msgstr ""
msgstr "A link feldolgozása nem sikerült: %s %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:605
msgid "Cannot add link %s to TOC"
msgstr ""
msgstr "Nem tudtam a linket hozzáadni a tartalomjegyzékhez: %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:957
msgid "Unable to process image %s. Error: %s"
msgstr ""
msgstr "Hiba a \"%s\" kép feldolgozása közben: %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:995
msgid "Unable to process interlaced PNG %s"
msgstr ""
msgstr "Nem tudtam feldolgozni a PNG képet: %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1010
msgid ""
"Could not process image: %s\n"
"%s"
msgstr ""
"Nem tudtam feldolgozni a képet: %s\n"
"%s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1763
msgid ""
"An error occurred while processing a table: %s. Ignoring table markup."
msgstr ""
"Hiba történt a táblázat feldolgozása közben: %s. A táblázat formázást "
"figyelmen kívül hagyom."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1765
msgid ""
"Bad table:\n"
"%s"
msgstr ""
"Hibás táblázat:\n"
"%s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1787
msgid "Table has cell that is too large"
msgstr ""
msgstr "A táblázatban olyan cellák vannak, amelyek túl nagyok."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1817
msgid ""
"You have to save the website %s as an html file first and then run html2lrf "
"on it."
msgstr ""
"Mentsd le a %s weboldalt egy mappába, majd a html fájlon futtasd a html2lrf "
"konvertáló programot."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1860
msgid "Could not read cover image: %s"
msgstr ""
msgstr "Nem tudtam a borító képet olvasni: %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1863
msgid "Cannot read from: %s"
msgstr ""
msgstr "Hiba olvasás közben: %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1988
msgid "Failed to process opf file"
msgstr ""
msgstr "Hiba az opf fájl feldolgozása közben"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1994
msgid ""
@ -1317,6 +1336,13 @@ msgid ""
"to local files recursively. Thus, you can use it to \n"
"convert a whole tree of HTML files."
msgstr ""
"Használat: %prog [beállítások] konyv.html\n"
"\n"
"\n"
"A %prog a konyv.html fájlt LRF formátumba konvertálja. \n"
"A %prog minden linket rekurzívan követ ami helyi \n"
"html fájlokra hivatkozik, tehát lehetséges sok összefüggő \n"
"html fájl konvertálása is."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lit/convert_from.py:15
msgid ""
@ -1325,16 +1351,22 @@ msgid ""
"\n"
"%prog converts mybook.lit to mybook.lrf"
msgstr ""
"Használat: %prog [beállítások] konyv.lit\n"
"\n"
"\n"
"A %prog a konyv.lit fájlt LRF formátumba konvertálja"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:136
msgid ""
"%prog book.lrf\n"
"Convert an LRF file into an LRS (XML UTF-8 encoded) file"
msgstr ""
"%prog konyv.lrf\n"
"A prog a konyv.lrf fájlt LRS formátumba (UTF-8 kódolású XML) konvertálja."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:137
msgid "Output LRS file"
msgstr ""
msgstr "Kimeneti LRS fájl"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:139
msgid "Do not save embedded image and font files to disk"
@ -1342,42 +1374,44 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:158
msgid "Parsing LRF..."
msgstr ""
msgstr "LRF fájl beolvasása..."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:161
msgid "Creating XML..."
msgstr ""
msgstr "XML létrehozása..."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:163
msgid "LRS written to "
msgstr ""
msgstr "Az LRS fájl helye: "
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:249
msgid "Could not read from thumbnail file:"
msgstr ""
msgstr "Nem tudtam az ikon fájlt olvasni:"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:269
msgid ""
"%prog [options] file.lrs\n"
"Compile an LRS file into an LRF file."
msgstr ""
"%prog [beállítások] konyv.lrf\n"
"LRS fájlt lefordítása LRF formátumba."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:270
msgid "Path to output file"
msgstr ""
msgstr "A célfájl elérési útja"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:272
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:115
msgid "Verbose processing"
msgstr ""
msgstr "Informatívabb üzenetek feldolgozásnál"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:274
msgid "Convert LRS to LRS, useful for debugging."
msgstr ""
msgstr "LRS fájl konvertálása LRS formátumba (hibakeresésnél hasznos)."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:455
msgid "Invalid LRF file. Could not set metadata."
msgstr ""
msgstr "Érvénytelen LRF fájl. Nem tudtam a metadatokat beállítani."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:580
msgid ""
@ -1391,42 +1425,44 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:587
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:43
msgid "Set the book title"
msgstr ""
msgstr "A könyv címe"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:589
msgid "Set sort key for the title"
msgstr ""
msgstr "Rendezési kulcs a címhez"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:591
msgid "Set the author"
msgstr ""
msgstr "Szerző"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:593
msgid "Set sort key for the author"
msgstr ""
msgstr "Rendezési kulcs a szerzőhöz"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:595
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:47
msgid "The category this book belongs to. E.g.: History"
msgstr ""
msgstr "A kategória, amibe a könyv tartozik. Pl.: történelem"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:598
msgid "Path to a graphic that will be set as this files' thumbnail"
msgstr ""
msgstr "A fájlhoz használandó ikon elérési útja"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:601
msgid ""
"Path to a txt file containing the comment to be stored in the lrf file."
msgstr ""
"A txt fájl útvonala, amelynek tartalma az lrf fájlhoz lesz csatolva "
"megjegyzésként."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:605
msgid "Extract thumbnail from LRF file"
msgstr ""
msgstr "Ikon kinyerése az LRF fájlból"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:606
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/mobi.py:182
msgid "Set the publisher"
msgstr ""
msgstr "Kiadó"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:607
msgid "Set the book classification"

View File

@ -15,7 +15,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
"Generated-By: pygettext.py 1.5\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
"Generated-By: pygettext.py 1.5\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -13,7 +13,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
"X-Poedit-Country: RUSSIAN FEDERATION\n"
"X-Poedit-Language: Russian\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -13,7 +13,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
"Generated-By: pygettext.py 1.5\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

File diff suppressed because it is too large Load Diff

View File

@ -47,6 +47,9 @@ class BasicNewsRecipe(object, LoggingInterface):
#: The author of this recipe
__author__ = __appname__
#: The language that the news is in
language = _('Unknown')
#: Maximum number of articles to download from each feed. This is primarily
#: useful for feeds that don't have article dates. For most feeds, you should
#: use :attr:`BasicNewsRecipe.oldest_article`

View File

@ -24,7 +24,9 @@ recipe_modules = ['recipe_' + r for r in (
'joelonsoftware', 'telepolis', 'common_dreams', 'nin', 'tomshardware_de',
'pagina12', 'infobae', 'ambito', 'elargentino', 'sueddeutsche', 'the_age',
'laprensa', 'amspec', 'freakonomics', 'criticadigital', 'elcronista',
'shacknews', 'teleread',
'shacknews', 'teleread', 'granma', 'juventudrebelde', 'juventudrebelde_english',
'la_tercera', 'el_mercurio_chile', 'la_cuarta', 'lanacion_chile', 'la_segunda',
'jb_online', 'estadao', 'o_globo', 'vijesti', 'elmundo', 'the_oz', 'exiled',
)]
import re, imp, inspect, time, os

View File

@ -18,6 +18,7 @@ class Ambito(BasicNewsRecipe):
no_stylesheets = True
use_embedded_content = False
encoding = 'iso--8859-1'
language = _('Spanish')
cover_url = 'http://www.ambito.com/img/logo_.jpg'
html2lrf_options = [

View File

@ -11,7 +11,8 @@ from calibre.web.feeds.news import BasicNewsRecipe
class TheAmericanSpectator(BasicNewsRecipe):
title = 'The American Spectator'
__author__ = 'Darko Miletic'
description = 'news from USA'
language = _('English')
description = 'News from USA'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True

View File

@ -8,6 +8,7 @@ class AssociatedPress(BasicNewsRecipe):
description = 'Global news'
__author__ = 'Kovid Goyal'
use_embedded_content = False
language = _('English')
max_articles_per_feed = 15
html2lrf_options = ['--force-page-break-before-tag="chapter"']

View File

@ -13,6 +13,7 @@ class ArsTechnica(BasicNewsRecipe):
title = 'Ars Technica'
description = 'The art of technology'
oldest_article = 7
language = _('English')
no_stylesheets = True
__author__ = 'Michael Warner'
max_articles_per_feed = 100

View File

@ -14,7 +14,7 @@ class TheAtlantic(BasicNewsRecipe):
__author__ = 'Kovid Goyal'
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'])]
no_stylesheets = True

View File

@ -6,12 +6,13 @@ __copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
b92.net
'''
import string,re
import re
from calibre.web.feeds.news import BasicNewsRecipe
class B92(BasicNewsRecipe):
title = u'B92'
__author__ = 'Darko Miletic'
language = _('Serbian')
description = 'Dnevne vesti iz Srbije i sveta'
oldest_article = 7
max_articles_per_feed = 100

View File

@ -15,6 +15,7 @@ class Barrons(BasicNewsRecipe):
title = 'Barron\'s'
max_articles_per_feed = 50
needs_subscription = True
language = _('English')
__author__ = 'Kovid Goyal'
description = 'Weekly publication for investors from the publisher of the Wall Street Journal'
timefmt = ' [%a, %b %d, %Y]'

View File

@ -13,6 +13,7 @@ class BBC(BasicNewsRecipe):
__author__ = 'Kovid Goyal'
description = 'Global news and current affairs from the British Broadcasting Corporation'
no_stylesheets = True
language = _('English')
remove_tags = [dict(name='div', attrs={'class':'footer'})]
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'

View File

@ -13,6 +13,7 @@ class BusinessWeek(BasicNewsRecipe):
title = 'Business Week'
description = 'Business News, Stock Market and Financial Advice'
__author__ = 'ChuckEggDotCom'
language = _('English')
oldest_article = 7
max_articles_per_feed = 10

View File

@ -8,6 +8,7 @@ class ChristianScienceMonitor(BasicNewsRecipe):
description = 'Providing context and clarity on national and international news, peoples and cultures'
max_articles_per_feed = 20
__author__ = 'Kovid Goyal'
language = _('English')
no_stylesheets = True
use_embedded_content = False

View File

@ -15,6 +15,7 @@ class Clarin(BasicNewsRecipe):
description = 'Noticias de Argentina y mundo'
oldest_article = 2
max_articles_per_feed = 100
language = _('Spanish')
use_embedded_content = False
no_stylesheets = True
cover_url = strftime('http://www.clarin.com/diario/%Y/%m/%d/portada.jpg')

View File

@ -12,6 +12,7 @@ class CNN(BasicNewsRecipe):
description = 'Global news'
timefmt = ' [%d %b %Y]'
__author__ = 'Kovid Goyal'
language = _('English')
no_stylesheets = True
use_embedded_content = False
oldest_article = 15

View File

@ -5,6 +5,7 @@ class CommonDreams(BasicNewsRecipe):
title = u'Common Dreams'
description = u'Progressive news and views'
__author__ = u'XanthanGum'
language = _('English')
oldest_article = 7
max_articles_per_feed = 100

View File

@ -14,6 +14,7 @@ class CriticaDigital(BasicNewsRecipe):
description = 'Noticias de Argentina'
oldest_article = 2
max_articles_per_feed = 100
language = _('Spanish')
no_stylesheets = True
use_embedded_content = False
encoding = 'cp1252'

View File

@ -6,6 +6,7 @@ class Cyberpresse(BasicNewsRecipe):
title = u'Cyberpresse'
__author__ = 'balok'
description = 'Canadian news in French'
language = _('French')
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True

View File

@ -12,6 +12,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
class DailyTelegraph(BasicNewsRecipe):
title = u'Daily Telegraph'
__author__ = u'AprilHare'
language = _('English')
description = u'News from down under'
oldest_article = 2
max_articles_per_feed = 10

View File

@ -9,6 +9,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
class DeStandaard(BasicNewsRecipe):
title = u'De Standaard'
__author__ = u'Darko Miletic'
language = _('French')
description = u'News from Belgium'
oldest_article = 7
max_articles_per_feed = 100

View File

@ -14,6 +14,7 @@ class DiscoverMagazine(BasicNewsRecipe):
description = u'Science, Technology and the Future'
__author__ = 'Mike Diaz'
oldest_article = 33
language = _('English')
max_articles_per_feed = 20
feeds = [
(u'Technology', u'http://discovermagazine.com/topics/technology/rss.xml'),

View File

@ -14,6 +14,7 @@ from urllib2 import quote
class Economist(BasicNewsRecipe):
title = 'The Economist'
language = _('English')
__author__ = "Kovid Goyal"
description = 'Global news and current affairs from a European perspective'
oldest_article = 7.0

View File

@ -0,0 +1,48 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
emol.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class ElMercurio(BasicNewsRecipe):
title = 'El Mercurio online'
language = _('Spanish')
__author__ = 'Darko Miletic'
description = 'El sitio de noticias online de Chile'
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'cp1252'
cover_url = 'http://www.emol.com/especiales/logo_emol/logo_emol.gif'
html2lrf_options = [
'--comment' , description
, '--category' , 'news, Chile'
, '--publisher' , title
]
keep_only_tags = [
dict(name='div', attrs={'class':'despliegue-txt_750px'})
,dict(name='div', attrs={'id':'div_cuerpo_participa'})
]
remove_tags = [
dict(name='div', attrs={'class':'contenedor_despliegue-col-left300'})
,dict(name='div', attrs={'id':['div_centro_dn_opc','div_cabezera','div_secciones','div_contenidos','div_pie','nav']})
]
feeds = [
(u'Noticias de ultima hora', u'http://www.emol.com/rss20/rss.asp?canal=0')
,(u'Nacional', u'http://www.emol.com/rss20/rss.asp?canal=1')
,(u'Mundo', u'http://www.emol.com/rss20/rss.asp?canal=2')
,(u'Deportes', u'http://www.emol.com/rss20/rss.asp?canal=4')
,(u'Magazine', u'http://www.emol.com/rss20/rss.asp?canal=6')
,(u'Tecnologia', u'http://www.emol.com/rss20/rss.asp?canal=5')
,(u'La Musica', u'http://www.emol.com/rss20/rss.asp?canal=7')
]

View File

@ -11,6 +11,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
class ElPais(BasicNewsRecipe):
title = u'EL PAIS'
language = _('Spanish')
oldest_article = 7
max_articles_per_feed = 100

View File

@ -12,6 +12,7 @@ class ElArgentino(BasicNewsRecipe):
title = 'ElArgentino.com'
__author__ = 'Darko Miletic'
description = 'Informacion Libre las 24 horas'
language = _('Spanish')
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True

View File

@ -13,6 +13,7 @@ class ElCronista(BasicNewsRecipe):
__author__ = 'Darko Miletic'
description = 'Noticias de Argentina'
oldest_article = 2
language = _('Spanish')
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False

View File

@ -0,0 +1,46 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
elmundo.es
'''
from calibre.web.feeds.news import BasicNewsRecipe
class ElMundo(BasicNewsRecipe):
title = 'El Mundo'
__author__ = 'Darko Miletic'
description = 'News from Spain'
language = _('Spanish')
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'iso8859_15'
cover_url = 'http://estaticos02.cache.el-mundo.net/papel/imagenes/v2.0/logoverde.gif'
html2lrf_options = [
'--comment' , description
, '--category' , 'news, Spain'
, '--publisher' , title
]
keep_only_tags = [dict(name='div', attrs={'class':'noticia'})]
remove_tags = [
dict(name='div', attrs={'class':['herramientas','publicidad_google','video','herramientasarriba','contenido_noticia_02']})
,dict(name='div', attrs={'id':'modulo_multimedia' })
,dict(name=['object','script','link', 'a'])
,dict(name='ul', attrs={'class':'herramientas'})
]
feeds = [
(u'Portada' , u'http://rss.elmundo.es/rss/descarga.htm?data2=4' )
,(u'Television' , u'http://rss.elmundo.es/rss/descarga.htm?data2=76')
,(u'Espana' , u'http://rss.elmundo.es/rss/descarga.htm?data2=8' )
,(u'Internacional' , u'http://rss.elmundo.es/rss/descarga.htm?data2=9' )
,(u'Cultura' , u'http://rss.elmundo.es/rss/descarga.htm?data2=6' )
,(u'Ciencia/Ecologia', u'http://rss.elmundo.es/rss/descarga.htm?data2=5' )
,(u'Comunicacion' , u'http://rss.elmundo.es/rss/descarga.htm?data2=26')
]

View File

@ -13,6 +13,7 @@ class Engadget(BasicNewsRecipe):
title = u'Engadget'
__author__ = 'Darko Miletic'
description = 'Tech news'
language = _('English')
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True

View File

@ -14,6 +14,7 @@ class ESPN(BasicNewsRecipe):
title = 'ESPN'
description = 'Sports news'
__author__ = 'Kovid Goyal'
language = _('English')
needs_subscription = True
remove_tags = [dict(name='font', attrs={'class':'footer'}), dict(name='hr', noshade='noshade')]

View File

@ -0,0 +1,55 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
estadao.com.br
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Estadao(BasicNewsRecipe):
title = 'O Estado de S. Paulo'
__author__ = 'Darko Miletic'
description = 'News from Brasil'
language = _('Spanish')
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'utf8'
cover_url = 'http://www.estadao.com.br/img/logo_estadao.png'
html2lrf_options = [
'--comment' , description
, '--category' , 'news, Brasil'
, '--publisher' , title
]
keep_only_tags = [dict(name='div', attrs={'id':'c1'})]
remove_tags = [
dict(name=['script','object','form','ul'])
,dict(name='div', attrs={'id':['votacao','estadaohoje']})
,dict(name='p', attrs={'id':'ctrl_texto'})
,dict(name='p', attrs={'class':'texto'})
]
feeds = [
(u'Manchetes Estadao', u'http://www.estadao.com.br/rss/manchetes.xml')
,(u'Ultimas noticias', u'http://www.estadao.com.br/rss/ultimas.xml')
,(u'Nacional', u'http://www.estadao.com.br/rss/nacional.xml')
,(u'Internacional', u'http://www.estadao.com.br/rss/internacional.xml')
,(u'Cidades', u'http://www.estadao.com.br/rss/cidades.xml')
,(u'Esportes', u'http://www.estadao.com.br/rss/esportes.xml')
,(u'Arte & Lazer', u'http://www.estadao.com.br/rss/arteelazer.xml')
,(u'Economia', u'http://www.estadao.com.br/rss/economia.xml')
,(u'Vida &', u'http://www.estadao.com.br/rss/vidae.xml')
]
def preprocess_html(self, soup):
ifr = soup.find('iframe')
if ifr:
ifr.extract()
return soup

View File

@ -0,0 +1,51 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
exiledonline.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Exiled(BasicNewsRecipe):
title = 'Exiled Online'
__author__ = 'Darko Miletic'
description = "Mankind's only alternative since 1997 - Formerly known as The eXile"
publisher = 'Exiled Online'
language = _('English')
category = 'news, politics, international'
oldest_article = 15
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'utf8'
remove_javascript = True
cover_url = 'http://exiledonline.com/wp-content/themes/exiledonline_theme/images/header-sm.gif'
html2lrf_options = [
'--comment' , description
, '--category' , category
, '--publisher' , publisher
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
keep_only_tags = [dict(name='div', attrs={'id':'main'})]
remove_tags = [
dict(name=['object','link'])
,dict(name='div', attrs={'class':'info'})
,dict(name='div', attrs={'id':['comments','navig']})
]
feeds = [(u'Articles', u'http://exiledonline.com/feed/' )]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
mtag = '\n<meta http-equiv="Content-Language" content="en"/>\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n'
soup.head.insert(0,mtag)
return soup

View File

@ -11,8 +11,9 @@ class FazNet(BasicNewsRecipe):
title = 'FAZ NET'
__author__ = 'Kovid Goyal'
description = '"Frankfurter Allgemeine Zeitung'
description = 'Frankfurter Allgemeine Zeitung'
use_embedded_content = False
language = _('German')
max_articles_per_feed = 30
preprocess_regexps = [

View File

@ -13,6 +13,7 @@ class FinancialTimes(BasicNewsRecipe):
__author__ = 'Darko Miletic'
description = 'Financial world news'
oldest_article = 2
language = _('English')
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False

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