mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
KG changes
This commit is contained in:
commit
9f55353a50
12
COPYRIGHT
12
COPYRIGHT
@ -30,6 +30,12 @@ License: BSD
|
||||
The full text of the BSD license is distributed as in
|
||||
/usr/share/common-licenses/BSD on Debian systems.
|
||||
|
||||
Files: /src/routes/*
|
||||
Copyright: Copyright (c) 2005-2008 Ben Bangert <ben@groovie.org>
|
||||
License: BSD
|
||||
The full text of the BSD license is distributed as in
|
||||
/usr/share/common-licenses/BSD on Debian systems.
|
||||
|
||||
Files: src/odf/*
|
||||
Copyright: Copyright (C) 2006-2008 Søren Roug, European Environment Agency
|
||||
License: LGPL2.1+
|
||||
@ -50,12 +56,6 @@ License: BSD
|
||||
The full text of the BSD license is distributed as in
|
||||
/usr/share/common-licenses/BSD on Debian systems.
|
||||
|
||||
Files: src/calibre/utils/genshi/*
|
||||
Copyright: Copyright (C) 2006-2008 Edgewall Software
|
||||
License: BSD
|
||||
The full text of the BSD license is distributed as in
|
||||
/usr/share/common-licenses/BSD on Debian systems.
|
||||
|
||||
Files: src/calibre/utils/lzx/*
|
||||
Copyright: Copyright (C) 2002, Matthew T. Russotto
|
||||
Copyright: Copyright (C) 2008, Marshall T. Vandegrift <llasram@gmail.com>
|
||||
|
@ -4,6 +4,60 @@
|
||||
# for important features/bug fixes.
|
||||
# Also, each release can have new and improved recipes.
|
||||
|
||||
- version: 0.6.55
|
||||
date: 2010-05-28
|
||||
|
||||
new features:
|
||||
- title: "Support for the Nokia E71X"
|
||||
|
||||
- title: "EPUB Output: Generate a default one entry TOC if no TOC is present. This allows the EPUB to pass epubcheck and work on the Kobo"
|
||||
|
||||
- title: "Kobo driver: Add support for storage card"
|
||||
|
||||
- title: "PDF Output: Improved cover and comic handling"
|
||||
|
||||
- title: "EPUB metadata: When setting authors, always move the new dc:creator element to the top so broken implementations don't get confused"
|
||||
|
||||
bug fixes:
|
||||
- title: "Make the HTML shown in the regex builder closer to that actually processed by the conversion pipeline."
|
||||
tickets: [5549]
|
||||
|
||||
- title: "Fix tab ordering in Bulk edit meta information dialog"
|
||||
tickets: [5624]
|
||||
|
||||
- title: "EPUB Input: Ignore __MACOSX directories inside the EPUB file"
|
||||
|
||||
- title: "EPUB Input: Raise an appropriate error for DTBook EPUB files"
|
||||
|
||||
- title: "EPUB Output: Use correct SVG code when not preserving aspect ratio for covers"
|
||||
|
||||
- title: "Use PNP drive number based sorting on windows when the device has identical main memory and card ids"
|
||||
|
||||
new recipes:
|
||||
- title: Infomotori
|
||||
author: Gabriele Marini
|
||||
|
||||
- title: Las Vegas Review
|
||||
author: Joel
|
||||
|
||||
- title: Troitskiy variant
|
||||
author: Vadim Dyadkin
|
||||
|
||||
- title: American Thinker
|
||||
author: Walt Anthony
|
||||
|
||||
- title: The Observer
|
||||
author: jbambridge
|
||||
|
||||
improved recipes:
|
||||
- The BBC
|
||||
- The New York Times
|
||||
- Wired
|
||||
- Corriere della Serra
|
||||
- Leggo
|
||||
- darknet
|
||||
- Freakonomics Blog
|
||||
|
||||
- version: 0.6.54
|
||||
date: 2010-05-21
|
||||
|
||||
|
@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?python
|
||||
from uuid import uuid4
|
||||
import re
|
||||
?>
|
||||
<ncx version="2005-1"
|
||||
xml:lang="en"
|
||||
xmlns="http://www.daisy.org/z3986/2005/ncx/"
|
||||
xmlns:py="http://genshi.edgewall.org/"
|
||||
xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata"
|
||||
>
|
||||
<head>
|
||||
<meta name="dtb:uid" content="${uid}"/>
|
||||
<meta name="dtb:depth" content="${toc.depth()}"/>
|
||||
<meta name="dtb:generator" content="${__appname__}"/>
|
||||
<meta name="dtb:totalPageCount" content="0"/>
|
||||
<meta name="dtb:maxPageNumber" content="0"/>
|
||||
</head>
|
||||
<docTitle><text>Table of Contents</text></docTitle>
|
||||
|
||||
<py:def function="navpoint(np, level)">
|
||||
${'%*s'%(4*level,'')}<navPoint id="${str(uuid4())}" playOrder="${str(np.play_order)}">
|
||||
${'%*s'%(4*level,'')}<navLabel>
|
||||
${'%*s'%(4*level,'')}<text>${re.sub(r'\s+', ' ', np.text)}</text>
|
||||
${'%*s'%(4*level,'')}</navLabel>
|
||||
${'%*s'%(4*level,'')}<content src="${unicode(np.href)+(('#' + unicode(np.fragment)) if np.fragment else '')}" />
|
||||
${'%*s'%(4*level,'')}<calibre:meta py:if="np.author" name="author">${np.author}</calibre:meta>
|
||||
${'%*s'%(4*level,'')}<calibre:meta py:if="np.description" name="description">${np.description}</calibre:meta>
|
||||
<py:for each="np2 in np">${navpoint(np2, level+1)}</py:for>
|
||||
${'%*s'%(4*level,'')}</navPoint>
|
||||
</py:def>
|
||||
<navMap>
|
||||
<py:for each="np in toc">${navpoint(np, 0)}</py:for>
|
||||
</navMap>
|
||||
</ncx>
|
@ -1,49 +0,0 @@
|
||||
<package version="2.0"
|
||||
xmlns="http://www.idpf.org/2007/opf"
|
||||
xmlns:py="http://genshi.edgewall.org/"
|
||||
unique-identifier="${__appname__}_id"
|
||||
|
||||
>
|
||||
<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: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: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:date py:if="getattr(mi, 'pubdate', None) is not None">${mi.pubdate.isoformat()}</dc:date>
|
||||
<dc:language>${mi.language if mi.language else 'UND'}</dc:language>
|
||||
<dc:type py:if="getattr(mi, 'category', False)">${mi.category}</dc:type>
|
||||
<dc:description py:if="mi.comments">${mi.comments}</dc:description>
|
||||
<dc:publisher py:if="mi.publisher">${mi.publisher}</dc:publisher>
|
||||
<dc:identifier opf:scheme="ISBN" py:if="mi.isbn">${mi.isbn}</dc:identifier>
|
||||
<dc:rights py:if="mi.rights">${mi.rights}</dc:rights>
|
||||
<meta py:if="mi.series is not None" name="calibre:series" content="${mi.series}"/>
|
||||
<meta py:if="mi.series_index is not None" name="calibre:series_index" content="${mi.format_series_index()}"/>
|
||||
<meta py:if="mi.rating is not None" name="calibre:rating" content="${mi.rating}"/>
|
||||
<meta py:if="mi.timestamp is not None" name="calibre:timestamp" content="${mi.timestamp.isoformat()}"/>
|
||||
<meta py:if="mi.publication_type is not None" name="calibre:publication_type" content="${mi.publication_type}" />
|
||||
<py:for each="tag in mi.tags">
|
||||
<dc:subject py:if="mi.tags is not None">${tag}</dc:subject>
|
||||
</py:for>
|
||||
</metadata>
|
||||
|
||||
<manifest py:if="getattr(mi, 'manifest', None)">
|
||||
<py:for each="ref in mi.manifest">
|
||||
<item id="${ref.id}" href="${ref.href()}" media-type="${ref.mime_type}" />
|
||||
</py:for>
|
||||
</manifest>
|
||||
|
||||
<guide py:if="getattr(mi, 'guide', None)">
|
||||
<py:for each="ref in mi.guide">
|
||||
<reference type="${ref.type}" href="${ref.href()}" py:with="attrs={'title': ref.title if ref.title else None}" py:attrs="attrs" />
|
||||
</py:for>
|
||||
</guide>
|
||||
|
||||
<spine py:if="getattr(mi, 'spine', None)"
|
||||
py:with="attrs={'toc':'ncx' if mi.toc else None}" py:attrs="attrs">
|
||||
<py:for each="resource in mi.spine">
|
||||
<itemref idref="${resource.id}" />
|
||||
</py:for>
|
||||
</spine>
|
||||
|
||||
|
||||
</package>
|
@ -257,7 +257,7 @@ def process_pages(pages, opts, update, tdir):
|
||||
ans, failures = [], []
|
||||
|
||||
for job in jobs:
|
||||
if job.failed:
|
||||
if job.failed or job.result is None:
|
||||
raise Exception(_('Failed to process comic: \n\n%s')%
|
||||
job.log_file.read())
|
||||
pages, failures_ = job.result
|
||||
|
@ -252,7 +252,7 @@ class HTMLPreProcessor(object):
|
||||
end_rules = []
|
||||
if getattr(self.extra_opts, 'remove_header', None):
|
||||
try:
|
||||
end_rules.append(
|
||||
rules.insert(0,
|
||||
(re.compile(self.extra_opts.header_regex), lambda match : '')
|
||||
)
|
||||
except:
|
||||
@ -262,7 +262,7 @@ class HTMLPreProcessor(object):
|
||||
|
||||
if getattr(self.extra_opts, 'remove_footer', None):
|
||||
try:
|
||||
end_rules.append(
|
||||
rules.insert(0
|
||||
(re.compile(self.extra_opts.footer_regex), lambda match : '')
|
||||
)
|
||||
except:
|
||||
|
@ -451,8 +451,6 @@ class OPF(object):
|
||||
guide_path = XPath('descendant::*[re:match(name(), "guide", "i")]/*[re:match(name(), "reference", "i")]')
|
||||
|
||||
title = MetadataField('title', formatter=lambda x: re.sub(r'\s+', ' ', x))
|
||||
title_sort = MetadataField('title_sort', formatter=lambda x:
|
||||
re.sub(r'\s+', ' ', x), is_dc=False)
|
||||
publisher = MetadataField('publisher')
|
||||
language = MetadataField('language')
|
||||
comments = MetadataField('description')
|
||||
@ -708,6 +706,30 @@ class OPF(object):
|
||||
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
@dynamic_property
|
||||
def title_sort(self):
|
||||
|
||||
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('{%s}file-as'%self.NAMESPACES['opf'], unicode(val))
|
||||
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
|
||||
@dynamic_property
|
||||
def tags(self):
|
||||
|
||||
@ -981,11 +1003,8 @@ class OPFCreator(MetaInformation):
|
||||
|
||||
def render(self, opf_stream=sys.stdout, ncx_stream=None,
|
||||
ncx_manifest_entry=None, encoding=None):
|
||||
from calibre.utils.genshi.template import MarkupTemplate
|
||||
opf_template = open(P('templates/opf.xml'), 'rb').read()
|
||||
if encoding is None:
|
||||
encoding = 'utf-8'
|
||||
template = MarkupTemplate(opf_template)
|
||||
toc = getattr(self, 'toc', None)
|
||||
if self.manifest:
|
||||
self.manifest.set_basedir(self.base_path)
|
||||
@ -1006,12 +1025,101 @@ class OPFCreator(MetaInformation):
|
||||
cover = os.path.abspath(os.path.join(self.base_path, cover))
|
||||
self.guide.set_cover(cover)
|
||||
self.guide.set_basedir(self.base_path)
|
||||
opf = template.generate(
|
||||
__appname__=__appname__, mi=self,
|
||||
__version__=__version__).render('xml', encoding=encoding)
|
||||
opf_stream.write('<?xml version="1.0" encoding="%s" ?>\n'
|
||||
%encoding.upper())
|
||||
opf_stream.write(opf)
|
||||
|
||||
# Actual rendering
|
||||
from lxml.builder import ElementMaker
|
||||
from calibre.ebooks.oeb.base import OPF2_NS, DC11_NS, CALIBRE_NS
|
||||
DNS = OPF2_NS+'___xx___'
|
||||
E = ElementMaker(namespace=DNS, nsmap={None:DNS})
|
||||
M = ElementMaker(namespace=DNS,
|
||||
nsmap={'dc':DC11_NS, 'calibre':CALIBRE_NS, 'opf':OPF2_NS})
|
||||
DC = ElementMaker(namespace=DC11_NS)
|
||||
|
||||
def DC_ELEM(tag, text, dc_attrs={}, opf_attrs={}):
|
||||
if text:
|
||||
elem = getattr(DC, tag)(text, **dc_attrs)
|
||||
else:
|
||||
elem = getattr(DC, tag)(**dc_attrs)
|
||||
for k, v in opf_attrs.items():
|
||||
elem.set('{%s}%s'%(OPF2_NS, k), v)
|
||||
return elem
|
||||
|
||||
def CAL_ELEM(name, content):
|
||||
return M.meta(name=name, content=content)
|
||||
|
||||
metadata = M.metadata()
|
||||
a = metadata.append
|
||||
role = {}
|
||||
if self.title_sort:
|
||||
role = {'file-as':self.title_sort}
|
||||
a(DC_ELEM('title', self.title if self.title else _('Unknown'),
|
||||
opf_attrs=role))
|
||||
for i, author in enumerate(self.authors):
|
||||
fa = {'role':'aut'}
|
||||
if i == 0 and self.author_sort:
|
||||
fa['file-as'] = self.author_sort
|
||||
a(DC_ELEM('creator', author, opf_attrs=fa))
|
||||
a(DC_ELEM('contributor', '%s (%s) [%s]'%(__appname__, __version__,
|
||||
'http://calibre-ebook.com'), opf_attrs={'role':'bkp',
|
||||
'file-as':__appname__}))
|
||||
a(DC_ELEM('identifier', str(self.application_id),
|
||||
opf_attrs={'scheme':__appname__},
|
||||
dc_attrs={'id':__appname__+'_id'}))
|
||||
if getattr(self, 'pubdate', None) is not None:
|
||||
a(DC_ELEM('date', self.pubdate.isoformat()))
|
||||
a(DC_ELEM('language', self.language if self.language else 'UND'))
|
||||
if self.comments:
|
||||
a(DC_ELEM('description', self.comments))
|
||||
if self.publisher:
|
||||
a(DC_ELEM('publisher', self.publisher))
|
||||
if self.isbn:
|
||||
a(DC_ELEM('identifier', self.isbn, opf_attrs={'scheme':'ISBN'}))
|
||||
if self.rights:
|
||||
a(DC_ELEM('rights', self.rights))
|
||||
if self.tags:
|
||||
for tag in self.tags:
|
||||
a(DC_ELEM('subject', tag))
|
||||
if self.series:
|
||||
a(CAL_ELEM('calibre:series', self.series))
|
||||
if self.series_index is not None:
|
||||
a(CAL_ELEM('calibre:series_index', self.format_series_index()))
|
||||
if self.rating is not None:
|
||||
a(CAL_ELEM('calibre:rating', str(self.rating)))
|
||||
if self.timestamp is not None:
|
||||
a(CAL_ELEM('calibre:timestamp', self.timestamp.isoformat()))
|
||||
if self.publication_type is not None:
|
||||
a(CAL_ELEM('calibre:publication_type', self.publication_type))
|
||||
manifest = E.manifest()
|
||||
if self.manifest is not None:
|
||||
for ref in self.manifest:
|
||||
item = E.item(id=str(ref.id), href=ref.href())
|
||||
item.set('media-type', ref.mime_type)
|
||||
manifest.append(item)
|
||||
spine = E.spine()
|
||||
if self.toc is not None:
|
||||
spine.set('toc', 'ncx')
|
||||
if self.spine is not None:
|
||||
for ref in self.spine:
|
||||
spine.append(E.itemref(idref=ref.id))
|
||||
guide = E.guide()
|
||||
if self.guide is not None:
|
||||
for ref in self.guide:
|
||||
item = E.reference(type=ref.type, href=ref.href())
|
||||
if ref.title:
|
||||
item.set('title', ref.title)
|
||||
guide.append(item)
|
||||
|
||||
root = E.package(
|
||||
metadata,
|
||||
manifest,
|
||||
spine,
|
||||
guide
|
||||
)
|
||||
root.set('unique-identifier', __appname__+'_id')
|
||||
raw = etree.tostring(root, pretty_print=True, xml_declaration=True,
|
||||
encoding=encoding)
|
||||
raw = raw.replace(DNS, OPF2_NS)
|
||||
opf_stream.write(raw)
|
||||
opf_stream.flush()
|
||||
if toc is not None and ncx_stream is not None:
|
||||
toc.render(ncx_stream, self.application_id)
|
||||
@ -1159,12 +1267,14 @@ class OPFTest(unittest.TestCase):
|
||||
<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 opf:file-as="Wow">A Cool & © ß Title</dc:title>
|
||||
<creator opf:role="aut" file-as="Monkey">Monkey Kitchen, Next</creator>
|
||||
<creator opf:role="aut" file-as="Monkey">Monkey Kitchen</creator>
|
||||
<creator opf:role="aut">Next</creator>
|
||||
<dc:subject>One</dc:subject><dc:subject>Two</dc:subject>
|
||||
<dc:identifier scheme="ISBN">123456789</dc:identifier>
|
||||
<x-metadata>
|
||||
<series>A one book series</series>
|
||||
</x-metadata>
|
||||
<meta name="calibre:series" content="A one book series" />
|
||||
<meta name="calibre:rating" content="4"/>
|
||||
<meta name="calibre:publication_type" content="test"/>
|
||||
<meta name="calibre:series_index" content="2.5" />
|
||||
</metadata>
|
||||
<manifest>
|
||||
<item id="1" href="a%20%7E%20b" media-type="text/txt" />
|
||||
@ -1184,7 +1294,9 @@ class OPFTest(unittest.TestCase):
|
||||
self.assertEqual(opf.tags, ['One', 'Two'])
|
||||
self.assertEqual(opf.isbn, '123456789')
|
||||
self.assertEqual(opf.series, 'A one book series')
|
||||
self.assertEqual(opf.series_index, 1)
|
||||
self.assertEqual(opf.series_index, 2.5)
|
||||
self.assertEqual(opf.rating, 4)
|
||||
self.assertEqual(opf.publication_type, 'test')
|
||||
self.assertEqual(list(opf.itermanifest())[0].get('href'), 'a ~ b')
|
||||
|
||||
def testWriting(self):
|
||||
@ -1214,3 +1326,6 @@ def suite():
|
||||
|
||||
def test():
|
||||
unittest.TextTestRunner(verbosity=2).run(suite())
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
||||
|
@ -1,14 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import os, glob, re
|
||||
from urlparse import urlparse
|
||||
from urllib import unquote
|
||||
from uuid import uuid4
|
||||
|
||||
from calibre import __appname__
|
||||
from lxml import etree
|
||||
from lxml.builder import ElementMaker
|
||||
|
||||
from calibre.constants import __appname__, __version__
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, BeautifulSoup
|
||||
from calibre.ebooks.chardet import xml_to_unicode
|
||||
|
||||
NCX_NS = "http://www.daisy.org/z3986/2005/ncx/"
|
||||
CALIBRE_NS = "http://calibre.kovidgoyal.net/2009/metadata"
|
||||
NSMAP = {
|
||||
None: NCX_NS,
|
||||
'calibre':CALIBRE_NS
|
||||
}
|
||||
|
||||
|
||||
E = ElementMaker(namespace=NCX_NS, nsmap=NSMAP)
|
||||
|
||||
C = ElementMaker(namespace=CALIBRE_NS, nsmap=NSMAP)
|
||||
|
||||
class NCXSoup(BeautifulStoneSoup):
|
||||
|
||||
NESTABLE_TAGS = {'navpoint':[]}
|
||||
@ -208,10 +225,46 @@ class TOC(list):
|
||||
self.add_item(href, fragment, txt)
|
||||
|
||||
def render(self, stream, uid):
|
||||
from calibre.utils.genshi.template import MarkupTemplate
|
||||
ncx_template = open(P('templates/ncx.xml'), 'rb').read()
|
||||
doctype = ('ncx', "-//NISO//DTD ncx 2005-1//EN", "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd")
|
||||
template = MarkupTemplate(ncx_template)
|
||||
raw = template.generate(uid=uid, toc=self, __appname__=__appname__)
|
||||
raw = raw.render(doctype=doctype)
|
||||
root = E.ncx(
|
||||
E.head(
|
||||
E.meta(name='dtb:uid', content=str(uid)),
|
||||
E.meta(name='dtb:depth', content=str(self.depth())),
|
||||
E.meta(name='dtb:generator', content='%s (%s)'%(__appname__,
|
||||
__version__)),
|
||||
E.meta(name='dtb:totalPageCount', content='0'),
|
||||
E.meta(name='dtb:maxPageNumber', content='0'),
|
||||
),
|
||||
E.docTitle(E.text('Table of Contents')),
|
||||
)
|
||||
navmap = E.navMap()
|
||||
root.append(navmap)
|
||||
root.set('{http://www.w3.org/XML/1998/namespace}lang', 'en')
|
||||
|
||||
def navpoint(parent, np):
|
||||
text = np.text
|
||||
if not text:
|
||||
text = ''
|
||||
elem = E.navPoint(
|
||||
E.navLabel(E.text(re.sub(r'\s+', ' ', text))),
|
||||
E.content(src=unicode(np.href)+(('#' + unicode(np.fragment))
|
||||
if np.fragment else '')),
|
||||
id=str(uuid4()),
|
||||
playOrder=str(np.play_order)
|
||||
)
|
||||
au = getattr(np, 'author', None)
|
||||
if au:
|
||||
au = re.sub(r'\s+', ' ', au)
|
||||
elem.append(C.meta(au, name='author'))
|
||||
desc = getattr(np, 'description', None)
|
||||
if desc:
|
||||
desc = re.sub(r'\s+', ' ', desc)
|
||||
elem.append(C.meta(desc, name='description'))
|
||||
parent.append(elem)
|
||||
for np2 in np:
|
||||
navpoint(elem, np2)
|
||||
|
||||
for np in self:
|
||||
navpoint(navmap, np)
|
||||
raw = etree.tostring(root, encoding='utf-8', xml_declaration=True,
|
||||
pretty_print=True)
|
||||
stream.write(raw)
|
||||
|
@ -166,7 +166,7 @@ class EbookIterator(object):
|
||||
f.truncate()
|
||||
f.write(ncss.encode(enc))
|
||||
|
||||
def __enter__(self, processed=False):
|
||||
def __enter__(self, processed=False, only_input_plugin=False):
|
||||
self.delete_on_exit = []
|
||||
self._tdir = TemporaryDirectory('_ebook_iter')
|
||||
self.base = self._tdir.__enter__()
|
||||
@ -184,12 +184,14 @@ class EbookIterator(object):
|
||||
plumber.opts, plumber.input_fmt, self.log,
|
||||
{}, self.base)
|
||||
|
||||
if not only_input_plugin:
|
||||
if processed or plumber.input_fmt.lower() in ('pdb', 'pdf', 'rb') and \
|
||||
not hasattr(self.pathtoopf, 'manifest'):
|
||||
if hasattr(self.pathtoopf, 'manifest'):
|
||||
self.pathtoopf = write_oebbook(self.pathtoopf, self.base)
|
||||
self.pathtoopf = create_oebbook(self.log, self.pathtoopf, plumber.opts,
|
||||
plumber.input_plugin)
|
||||
|
||||
if hasattr(self.pathtoopf, 'manifest'):
|
||||
self.pathtoopf = write_oebbook(self.pathtoopf, self.base)
|
||||
|
||||
|
@ -85,7 +85,7 @@ class RegexBuilder(QDialog, Ui_RegexBuilder):
|
||||
|
||||
def open_book(self, pathtoebook):
|
||||
self.iterator = EbookIterator(pathtoebook)
|
||||
self.iterator.__enter__(processed=True)
|
||||
self.iterator.__enter__(only_input_plugin=True)
|
||||
text = [u'']
|
||||
for path in self.iterator.spine:
|
||||
html = open(path, 'rb').read().decode('utf-8', 'replace')
|
||||
|
@ -966,6 +966,7 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
return len(self.map)
|
||||
|
||||
def set_database(self, db):
|
||||
self.custom_columns = {}
|
||||
self.db = db
|
||||
self.map = list(range(0, len(db)))
|
||||
|
||||
|
@ -127,120 +127,60 @@ class CSV_XML(CatalogPlugin):
|
||||
|
||||
elif self.fmt == 'xml':
|
||||
from lxml import etree
|
||||
from lxml.builder import E
|
||||
|
||||
from calibre.utils.genshi.template import MarkupTemplate
|
||||
root = E.calibredb()
|
||||
for r in data:
|
||||
record = E.record()
|
||||
root.append(record)
|
||||
|
||||
PY_NAMESPACE = "http://genshi.edgewall.org/"
|
||||
PY = "{%s}" % PY_NAMESPACE
|
||||
NSMAP = {'py' : PY_NAMESPACE}
|
||||
root = etree.Element('calibredb', nsmap=NSMAP)
|
||||
py_for = etree.SubElement(root, PY + 'for', each="record in data")
|
||||
record = etree.SubElement(py_for, 'record')
|
||||
|
||||
if 'id' in fields:
|
||||
record_child = etree.SubElement(record, 'id')
|
||||
record_child.set(PY + "if", "record['id']")
|
||||
record_child.text = "${record['id']}"
|
||||
|
||||
if 'uuid' in fields:
|
||||
record_child = etree.SubElement(record, 'uuid')
|
||||
record_child.set(PY + "if", "record['uuid']")
|
||||
record_child.text = "${record['uuid']}"
|
||||
|
||||
if 'title' in fields:
|
||||
record_child = etree.SubElement(record, 'title')
|
||||
record_child.set(PY + "if", "record['title']")
|
||||
record_child.text = "${record['title']}"
|
||||
for field in ('id', 'uuid', 'title', 'publisher', 'rating', 'size',
|
||||
'isbn'):
|
||||
if field in fields:
|
||||
val = r[field]
|
||||
if val is None:
|
||||
continue
|
||||
if not isinstance(val, (str, unicode)):
|
||||
val = unicode(val)
|
||||
item = getattr(E, field)(val)
|
||||
record.append(item)
|
||||
|
||||
if 'authors' in fields:
|
||||
record_child = etree.SubElement(record, 'authors', sort="${record['author_sort']}")
|
||||
record_subchild = etree.SubElement(record_child, PY + 'for', each="author in record['authors']")
|
||||
record_subsubchild = etree.SubElement(record_subchild, 'author')
|
||||
record_subsubchild.text = '$author'
|
||||
aus = E.authors(sort=r['author_sort'])
|
||||
for au in r['authors']:
|
||||
aus.append(E.author(au))
|
||||
record.append(aus)
|
||||
|
||||
if 'publisher' in fields:
|
||||
record_child = etree.SubElement(record, 'publisher')
|
||||
record_child.set(PY + "if", "record['publisher']")
|
||||
record_child.text = "${record['publisher']}"
|
||||
for field in ('timestamp', 'pubdate'):
|
||||
if field in fields:
|
||||
record.append(getattr(E, field)(r[field].isoformat()))
|
||||
|
||||
if 'rating' in fields:
|
||||
record_child = etree.SubElement(record, 'rating')
|
||||
record_child.set(PY + "if", "record['rating']")
|
||||
record_child.text = "${record['rating']}"
|
||||
if 'tags' in fields and r['tags']:
|
||||
tags = E.tags()
|
||||
for tag in r['tags']:
|
||||
tags.append(E.tag(tag))
|
||||
record.append(tags)
|
||||
|
||||
if 'date' in fields:
|
||||
record_child = etree.SubElement(record, 'date')
|
||||
record_child.set(PY + "if", "record['date']")
|
||||
record_child.text = "${record['date'].isoformat()}"
|
||||
if 'comments' in fields and r['comments']:
|
||||
record.append(E.comments(r['comments']))
|
||||
|
||||
if 'pubdate' in fields:
|
||||
record_child = etree.SubElement(record, 'pubdate')
|
||||
record_child.set(PY + "if", "record['pubdate']")
|
||||
record_child.text = "${record['pubdate'].isoformat()}"
|
||||
if 'series' in fields and r['series']:
|
||||
record.append(E.series(r['series'],
|
||||
index=str(r['series_index'])))
|
||||
|
||||
if 'size' in fields:
|
||||
record_child = etree.SubElement(record, 'size')
|
||||
record_child.set(PY + "if", "record['size']")
|
||||
record_child.text = "${record['size']}"
|
||||
if 'cover' in fields and r['cover']:
|
||||
record.append(E.cover(r['cover'].replace(os.sep, '/')))
|
||||
|
||||
if 'tags' in fields:
|
||||
# <tags py:if="record['tags']">
|
||||
# <py:for each="tag in record['tags']">
|
||||
# <tag>$tag</tag>
|
||||
# </py:for>
|
||||
# </tags>
|
||||
record_child = etree.SubElement(record, 'tags')
|
||||
record_child.set(PY + "if", "record['tags']")
|
||||
record_subchild = etree.SubElement(record_child, PY + 'for', each="tag in record['tags']")
|
||||
record_subsubchild = etree.SubElement(record_subchild, 'tag')
|
||||
record_subsubchild.text = '$tag'
|
||||
if 'formats' in fields and r['formats']:
|
||||
fmt = E.formats()
|
||||
for f in r['formats']:
|
||||
fmt.append(E.format(f.replace(os.sep, '/')))
|
||||
record.append(fmt)
|
||||
|
||||
if 'comments' in fields:
|
||||
record_child = etree.SubElement(record, 'comments')
|
||||
record_child.set(PY + "if", "record['comments']")
|
||||
record_child.text = "${record['comments']}"
|
||||
with open(path_to_output, 'w') as f:
|
||||
f.write(etree.tostring(root, encoding='utf-8',
|
||||
xml_declaration=True, pretty_print=True))
|
||||
|
||||
if 'series' in fields:
|
||||
# <series py:if="record['series']" index="${record['series_index']}">
|
||||
# ${record['series']}
|
||||
# </series>
|
||||
record_child = etree.SubElement(record, 'series')
|
||||
record_child.set(PY + "if", "record['series']")
|
||||
record_child.set('index', "${record['series_index']}")
|
||||
record_child.text = "${record['series']}"
|
||||
|
||||
if 'isbn' in fields:
|
||||
record_child = etree.SubElement(record, 'isbn')
|
||||
record_child.set(PY + "if", "record['isbn']")
|
||||
record_child.text = "${record['isbn']}"
|
||||
|
||||
if 'cover' in fields:
|
||||
# <cover py:if="record['cover']">
|
||||
# ${record['cover'].replace(os.sep, '/')}
|
||||
# </cover>
|
||||
record_child = etree.SubElement(record, 'cover')
|
||||
record_child.set(PY + "if", "record['cover']")
|
||||
record_child.text = "${record['cover']}"
|
||||
|
||||
if 'formats' in fields:
|
||||
# <formats py:if="record['formats']">
|
||||
# <py:for each="path in record['formats']">
|
||||
# <format>${path.replace(os.sep, '/')}</format>
|
||||
# </py:for>
|
||||
# </formats>
|
||||
record_child = etree.SubElement(record, 'formats')
|
||||
record_child.set(PY + "if", "record['formats']")
|
||||
record_subchild = etree.SubElement(record_child, PY + 'for', each="path in record['formats']")
|
||||
record_subsubchild = etree.SubElement(record_subchild, 'format')
|
||||
record_subsubchild.text = "${path.replace(os.sep, '/')}"
|
||||
|
||||
outfile = open(path_to_output, 'w')
|
||||
template = MarkupTemplate(etree.tostring(root, xml_declaration=True,
|
||||
encoding="UTF-8", pretty_print=True))
|
||||
outfile.write(template.generate(data=data, os=os).render('xml'))
|
||||
outfile.close()
|
||||
|
||||
return None
|
||||
|
||||
class EPUB_MOBI(CatalogPlugin):
|
||||
'ePub catalog generator'
|
||||
|
@ -8,13 +8,13 @@ msgstr ""
|
||||
"Project-Id-Version: calibre\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 07:25+0000\n"
|
||||
"PO-Revision-Date: 2010-05-22 17:09+0000\n"
|
||||
"Last-Translator: Kovid Goyal <Unknown>\n"
|
||||
"Language-Team: Arabic <ar@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-22 03:55+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-23 03:54+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
|
||||
|
@ -7,13 +7,13 @@ msgstr ""
|
||||
"Project-Id-Version: calibre 0.4.51\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 15:08+0000\n"
|
||||
"PO-Revision-Date: 2010-05-24 19:01+0000\n"
|
||||
"Last-Translator: D Iordanov <Unknown>\n"
|
||||
"Language-Team: bg\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-22 03:55+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-25 03:41+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
@ -384,6 +384,8 @@ msgid ""
|
||||
"This profile is intended for the SONY PRS line. The 500/505/700 etc, in "
|
||||
"landscape mode. Mainly useful for comics."
|
||||
msgstr ""
|
||||
"Този профил е предназначен за SONY PRS линия продукти. Модел 500/505/700 и "
|
||||
"т.н., landscape формат. Основно използван за комикси."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:408
|
||||
msgid "This profile is intended for the Amazon Kindle DX."
|
||||
@ -395,19 +397,19 @@ msgstr "Инсталирани приставки"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:32
|
||||
msgid "Mapping for filetype plugins"
|
||||
msgstr ""
|
||||
msgstr "Съответствия за плъгини за вида файл"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:33
|
||||
msgid "Local plugin customization"
|
||||
msgstr ""
|
||||
msgstr "Настройка на локалните плъгини."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:34
|
||||
msgid "Disabled plugins"
|
||||
msgstr "Изключени приставки"
|
||||
msgstr "Изключени плъгини"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:77
|
||||
msgid "No valid plugin found in "
|
||||
msgstr ""
|
||||
msgstr "Валидни плъгини не са открити в "
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:278
|
||||
msgid "Initialization of plugin %s failed with traceback:"
|
||||
@ -441,15 +443,15 @@ msgstr "Списък на инсталираните приставки"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:447
|
||||
msgid "Enable the named plugin"
|
||||
msgstr ""
|
||||
msgstr "Активирай избрания плъгин"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:449
|
||||
msgid "Disable the named plugin"
|
||||
msgstr ""
|
||||
msgstr "Дективирай избрания плъгин"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:13
|
||||
msgid "Communicate with Android phones."
|
||||
msgstr ""
|
||||
msgstr "Комуникирай с Android устройства"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:39
|
||||
msgid ""
|
||||
@ -459,15 +461,15 @@ msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:67
|
||||
msgid "Communicate with S60 phones."
|
||||
msgstr ""
|
||||
msgstr "Комуникирай със S60 устройства"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/binatone/driver.py:17
|
||||
msgid "Communicate with the Binatone Readme eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Комуникирай с Binatone Readme eBook устройство"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:13
|
||||
msgid "Communicate with the Blackberry smart phone."
|
||||
msgstr ""
|
||||
msgstr "Комуникирай Blackberry устройство"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:14
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/nuut2/driver.py:18
|
||||
@ -477,15 +479,15 @@ msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/cybook/driver.py:22
|
||||
msgid "Communicate with the Cybook Gen 3 / Opus eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Комуникирай с Cybook Gen 3 / Opus eBook устройство"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:24
|
||||
msgid "Communicate with the EB600 eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Комуникирай с EB600 eBook устройство"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/edge/driver.py:17
|
||||
msgid "Entourage Edge"
|
||||
msgstr ""
|
||||
msgstr "Entourage Edge"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/edge/driver.py:18
|
||||
msgid "Communicate with the Entourage Edge."
|
||||
@ -493,15 +495,15 @@ msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/eslick/driver.py:16
|
||||
msgid "Communicate with the ESlick eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Комуникирай с ESlick eBook устройство"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:19
|
||||
msgid "Communicate with Hanlin V3 eBook readers."
|
||||
msgstr ""
|
||||
msgstr "Комуникирай с Hanlin V3 eBook устройство"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:95
|
||||
msgid "Communicate with Hanlin V5 eBook readers."
|
||||
msgstr ""
|
||||
msgstr "Комуникирай с Hanlin V5 eBook устройство"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:114
|
||||
msgid "Communicate with the BOOX eBook reader."
|
||||
|
@ -4,9 +4,9 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: calibre 0.6.54\n"
|
||||
"POT-Creation-Date: 2010-05-21 15:44+MDT\n"
|
||||
"PO-Revision-Date: 2010-05-21 15:44+MDT\n"
|
||||
"Project-Id-Version: calibre 0.6.55\n"
|
||||
"POT-Creation-Date: 2010-05-28 16:38+MDT\n"
|
||||
"PO-Revision-Date: 2010-05-28 16:38+MDT\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: LANGUAGE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -53,7 +53,7 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:120
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:329
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:444
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:912
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:921
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdb.py:39
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdf.py:28
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pml.py:23
|
||||
@ -95,8 +95,8 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/rotate.py:63
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:81
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:82
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:87
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:88
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:97
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:98
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:233
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:235
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:279
|
||||
@ -453,11 +453,11 @@ msgstr ""
|
||||
msgid "Communicate with Hanlin V3 eBook readers."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:95
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:78
|
||||
msgid "Communicate with Hanlin V5 eBook readers."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:114
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:97
|
||||
msgid "Communicate with the BOOX eBook reader."
|
||||
msgstr ""
|
||||
|
||||
@ -465,15 +465,19 @@ msgstr ""
|
||||
msgid "Communicate with the Hanvon N520 eBook reader."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:41
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:40
|
||||
msgid "Communicate with The Book reader."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:51
|
||||
msgid "Communicate with the SpringDesign Alex eBook reader."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:57
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:67
|
||||
msgid "Communicate with the Azbooka"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:70
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:80
|
||||
msgid "Communicate with the Elonex EB 511 eBook reader."
|
||||
msgstr ""
|
||||
|
||||
@ -519,15 +523,15 @@ msgstr ""
|
||||
msgid "Communicate with the Kindle DX eBook reader."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:14
|
||||
msgid "Communicate with the Kobo Reader"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/misc.py:15
|
||||
msgid "Communicate with the Palm Pre"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/misc.py:35
|
||||
msgid "Communicate with the Kobo Reader"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/misc.py:56
|
||||
msgid "Communicate with the Booq Avant"
|
||||
msgstr ""
|
||||
|
||||
@ -608,59 +612,59 @@ msgstr ""
|
||||
msgid "Communicate with the Teclast K3 reader."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:45
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:37
|
||||
msgid "Communicate with the Newsmy reader."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:60
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:49
|
||||
msgid "Communicate with the iPapyrus reader."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:252
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:246
|
||||
msgid "Unable to detect the %s disk drive. Try rebooting."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:425
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:426
|
||||
msgid "Unable to detect the %s mount point. Try rebooting."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:490
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:491
|
||||
msgid "Unable to detect the %s disk drive."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:583
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:584
|
||||
msgid "Could not find mount helper: %s."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:595
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:596
|
||||
msgid "Unable to detect the %s disk drive. Your kernel is probably exporting a deprecated version of SYSFS."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:603
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:604
|
||||
msgid "Unable to mount main memory (Error code: %d)"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:740
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:742
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:741
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:743
|
||||
msgid "The reader has no storage card in this slot."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:744
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:745
|
||||
msgid "Selected slot: %s is not supported."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:777
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:778
|
||||
msgid "There is insufficient free space in main memory"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:779
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:781
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:780
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:782
|
||||
msgid "There is insufficient free space on the storage card"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:811
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:817
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:842
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:812
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:818
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:843
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:240
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:151
|
||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:589
|
||||
@ -1186,30 +1190,34 @@ msgstr ""
|
||||
msgid "Creating"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:205
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:56
|
||||
msgid "Extract the contents of the generated EPUB file to the specified directory. The contents of the directory are first deleted, so be careful."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:211
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:62
|
||||
msgid "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."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:222
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:73
|
||||
msgid "Split all HTML files larger than this size (in KB). This is necessary as most EPUB readers cannot handle large file sizes. The default of %defaultKB is the size required for Adobe Digital Editions."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:229
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:80
|
||||
msgid "Normally, if the input file has no cover and you don't specify one, a default cover is generated with the title, authors, etc. This option disables the generation of this cover."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:235
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:86
|
||||
msgid "Do not use SVG for the book cover. Use this option if your EPUB is going to be used ona device that does not support SVG, like the iPhone or the JetBook Lite. Without this option, such devices will display the cover as a blank page."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:243
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:94
|
||||
msgid "When using an SVG cover, this option will cause the cover to scale to cover the available screen area, but still preserve its aspect ratio (ratio of width to height). That means there may be white borders at the sides or top and bottom of the image, but the image will never be distorted. Without this option the image may be slightly distorted, but there will be no borders."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:169
|
||||
msgid "Start"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/fb2ml.py:144
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/rb/rbml.py:102
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/txt/txtml.py:77
|
||||
@ -1699,7 +1707,7 @@ msgid ""
|
||||
"Fetch a cover image for the book identified by ISBN from LibraryThing.com\n"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1103
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1112
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1372
|
||||
msgid "Cover"
|
||||
msgstr ""
|
||||
@ -1804,7 +1812,7 @@ msgstr ""
|
||||
msgid "Main Text"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/iterator.py:39
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/iterator.py:41
|
||||
msgid "%s format books are not supported"
|
||||
msgstr ""
|
||||
|
||||
@ -2046,22 +2054,26 @@ msgstr ""
|
||||
msgid "Split Options:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:59
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:31
|
||||
msgid "The unit of measure. Default is inch. Choices are %s Note: This does not override the unit for margins!"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:64
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:36
|
||||
msgid "The size of the paper. This size will be overridden when an output profile is used. Default is letter. Choices are %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:68
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:40
|
||||
msgid "Custom size of the document. Use the form widthxheight EG. `123x321` to specify the width and height. This overrides any specified paper-size."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:73
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:45
|
||||
msgid "The orientation of the page. Default is portrait. Choices are %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:49
|
||||
msgid "Preserve the aspect ratio of the cover, instead of stretching it to fill the ull first page of the generated pdf."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftohtml.py:55
|
||||
msgid "Could not find pdftohtml, check it is in your PATH"
|
||||
msgstr ""
|
||||
@ -2209,7 +2221,7 @@ msgid "Limit max simultaneous jobs to number of CPUs"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:135
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:475
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:482
|
||||
msgid "Copied"
|
||||
msgstr ""
|
||||
|
||||
@ -2338,7 +2350,7 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_input_ui.py:31
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output_ui.py:35
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_input_ui.py:38
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:39
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:42
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output_ui.py:28
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:59
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/toc_ui.py:62
|
||||
@ -3048,14 +3060,18 @@ msgstr ""
|
||||
msgid "PDF Output"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:40
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:43
|
||||
msgid "&Paper Size:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:41
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:44
|
||||
msgid "&Orientation:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:45
|
||||
msgid "Preserve &aspect ratio of cover"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output.py:14
|
||||
msgid "RB Output"
|
||||
msgstr ""
|
||||
@ -3811,7 +3827,7 @@ msgid "Failed to start content server"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:715
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:586
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:593
|
||||
msgid "Select location for books"
|
||||
msgstr ""
|
||||
|
||||
@ -5440,7 +5456,7 @@ msgid "The database repair failed. Starting with a new empty library."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:150
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:594
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:601
|
||||
msgid "Calibre Library"
|
||||
msgstr ""
|
||||
|
||||
@ -6905,40 +6921,40 @@ msgstr ""
|
||||
msgid "Title Case"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:366
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:373
|
||||
msgid "If you use the WordPlayer e-book app on your Android phone, you can access your calibre book collection directly on the device. To do this you have to turn on the content server."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:370
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:377
|
||||
msgid "Remember to leave calibre running as the server only runs as long as calibre is running."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:372
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:379
|
||||
msgid "You have to add the URL http://myhostname:8080 as your calibre library in WordPlayer. Here myhostname should be the fully qualified hostname or the IP address of the computer calibre is running on."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:449
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:456
|
||||
msgid "Moving library..."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:465
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:466
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:472
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:473
|
||||
msgid "Failed to move library"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:520
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:527
|
||||
msgid "Invalid database"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:521
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:528
|
||||
msgid "<p>An invalid library already exists at %s, delete it before trying to move the existing library.<br>Error: %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:532
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:539
|
||||
msgid "Could not move library"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:661
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:668
|
||||
msgid "welcome wizard"
|
||||
msgstr ""
|
||||
|
||||
|
@ -8,13 +8,13 @@ msgstr ""
|
||||
"Project-Id-Version: de\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 07:32+0000\n"
|
||||
"Last-Translator: Kovid Goyal <Unknown>\n"
|
||||
"PO-Revision-Date: 2010-05-25 19:29+0000\n"
|
||||
"Last-Translator: S. Dorscht <Unknown>\n"
|
||||
"Language-Team: American English <kde-i18n-doc@lists.kde.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-22 03:56+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-26 03:46+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
@ -371,6 +371,7 @@ msgstr ""
|
||||
msgid ""
|
||||
"Intended for the iPad and similar devices with a resolution of 768x1024"
|
||||
msgstr ""
|
||||
"Geeignet für das iPad und ähnliche Geräte mit einer Auflösung von 768 x1024"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:271
|
||||
msgid "This profile is intended for the Kobo Reader."
|
||||
@ -536,7 +537,7 @@ msgstr "Kommunikation mit dem SpringDesign Alex eBook Reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:57
|
||||
msgid "Communicate with the Azbooka"
|
||||
msgstr ""
|
||||
msgstr "kommuniziere mit Azooka"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:70
|
||||
msgid "Communicate with the Elonex EB 511 eBook reader."
|
||||
@ -594,7 +595,7 @@ msgstr "Kommunikation mit dem Kobo Reader"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/misc.py:56
|
||||
msgid "Communicate with the Booq Avant"
|
||||
msgstr ""
|
||||
msgstr "Kommunikation mit dem Booq Avant"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/nokia/driver.py:17
|
||||
msgid "Communicate with the Nokia 770 internet tablet."
|
||||
@ -679,11 +680,11 @@ msgstr "Kommunikation mit dem Teclast K3 Reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:45
|
||||
msgid "Communicate with the Newsmy reader."
|
||||
msgstr ""
|
||||
msgstr "Kommunikation mit dem Newsmy Reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:60
|
||||
msgid "Communicate with the iPapyrus reader."
|
||||
msgstr ""
|
||||
msgstr "Kommunikation mit dem iPapyrus Reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:252
|
||||
msgid "Unable to detect the %s disk drive. Try rebooting."
|
||||
@ -1461,6 +1462,12 @@ msgid ""
|
||||
"corresponding pair of normal characters. This option will preserve them "
|
||||
"instead."
|
||||
msgstr ""
|
||||
"Ligaturen im Eingabe-Dokument erhalten. Eine Ligatur ist eine besondere Form "
|
||||
"von einem Zeichenpaar wie ff, fi, fl, usw. Die meisten Lesegeräte haben "
|
||||
"keine Unterstützung für Ligaturen in ihren Standard-Schriftarten, so dass "
|
||||
"sie sie kaum korrekt wiedergeben. Standardmäßig wird Calibre eine Ligatur in "
|
||||
"das entsprechende normale Zeichenpaar verwandeln. Diese Einstellung ist dazu "
|
||||
"da, sie stattdessen zu erhalten."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:428
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:38
|
||||
@ -1634,6 +1641,13 @@ msgid ""
|
||||
"and bottom of the image, but the image will never be distorted. Without this "
|
||||
"option the image may be slightly distorted, but there will be no borders."
|
||||
msgstr ""
|
||||
"Bei Verwendung eines SVG Umschlagbildes führt diese Einstellung dazu, dass "
|
||||
"das Umschlagbild auf die verfügbare Bildschirmgröße skaliert wird, aber "
|
||||
"dennoch sein Seitenverhältnis (Verhältnis von Breite zu Höhe) erhalten "
|
||||
"bleibt. Das heißt, es können weiße Ränder an den Seiten oder oben und unten "
|
||||
"auf dem Bild sein, aber das Bild wird nie verzerrt werden. Ohne diese "
|
||||
"Einstellung kann das Bild leicht verzerrt sein, aber es gibt dafür keine "
|
||||
"Ränder."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/fb2ml.py:144
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/rb/rbml.py:102
|
||||
@ -2287,7 +2301,7 @@ msgstr "Komprimierung der Datei-Inhalte ausschalten."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:40
|
||||
msgid "Tag marking book to be filed with Personal Docs"
|
||||
msgstr ""
|
||||
msgstr "Etikett, das ein Buch markiert, welches persönliche Daten enthält"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:108
|
||||
msgid "All articles"
|
||||
@ -3340,7 +3354,7 @@ msgstr "Kein &SVG Umschlagbild"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:52
|
||||
msgid "Preserve cover &aspect ratio"
|
||||
msgstr ""
|
||||
msgstr "Seitenverhältnis des Umschl&agbildes beibehalten"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:53
|
||||
msgid "Split files &larger than:"
|
||||
@ -3462,15 +3476,15 @@ msgstr "Kontrolle des Layouts der Ausgabe"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:30
|
||||
msgid "Original"
|
||||
msgstr ""
|
||||
msgstr "Original"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:31
|
||||
msgid "Left align"
|
||||
msgstr ""
|
||||
msgstr "Linksbündig"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:32
|
||||
msgid "Justify text"
|
||||
msgstr ""
|
||||
msgstr "Text ausrichten"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:120
|
||||
msgid "&Disable font size rescaling"
|
||||
@ -3531,7 +3545,7 @@ msgstr "Extra &CSS"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:136
|
||||
msgid "&Transliterate unicode characters to ASCII"
|
||||
msgstr ""
|
||||
msgstr "Unicode Schriftzeichen in ASCII umse&tzen"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:137
|
||||
msgid "Insert &blank line"
|
||||
@ -3539,7 +3553,7 @@ msgstr "&Leerzeile einfügen"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:138
|
||||
msgid "Keep &ligatures"
|
||||
msgstr ""
|
||||
msgstr "&Ligaturen behalten"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:19
|
||||
msgid "LRF Output"
|
||||
@ -4570,7 +4584,7 @@ msgstr "Neue eMail-Adresse"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:472
|
||||
msgid "System port selected"
|
||||
msgstr ""
|
||||
msgstr "System-Port ausgewählt"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:473
|
||||
msgid ""
|
||||
@ -4578,6 +4592,9 @@ msgid ""
|
||||
"port. You operating system <b>may</b> not allow the server to run on this "
|
||||
"port. To be safe choose a port number larger than 1024."
|
||||
msgstr ""
|
||||
"Der für den Content-Server gewählte Port <b>%d</b> ist ein System-Port. Ihr "
|
||||
"Betriebssystem <b>könnte</b> nicht zulassen, dass der Server auf diesem Port "
|
||||
"läuft. Um sicher zu gehen, wählen Sie eine Port-Nummer größer als 1024."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:492
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:837
|
||||
@ -5218,7 +5235,7 @@ msgstr "Datum"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:138
|
||||
msgid "Tag on book"
|
||||
msgstr ""
|
||||
msgstr "Etikett (Tag) auf Buch"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:139
|
||||
msgid "Explanation text added in create_ct_column.py"
|
||||
@ -5728,7 +5745,7 @@ msgstr "Neue individuelle Nachrichtenquelle hinzufügen"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:225
|
||||
msgid "Download all scheduled new sources"
|
||||
msgstr ""
|
||||
msgstr "Alle geplanten Nachrichtenquellen laden"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:322
|
||||
msgid "No internet connection"
|
||||
@ -6503,23 +6520,25 @@ msgstr "Nicht nach Updates suchen"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:65
|
||||
msgid "Choose a location for your calibre e-book library"
|
||||
msgstr ""
|
||||
msgstr "Wählen Sie einen Ort für Ihre Calibre eBook Bibliothek"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:74
|
||||
msgid "Failed to create library"
|
||||
msgstr ""
|
||||
msgstr "Das Erstellen der Bibliothek schlug fehl"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:75
|
||||
msgid "Failed to create calibre library at: %r. Aborting."
|
||||
msgstr ""
|
||||
msgstr "Das Erstellen der Bibliothek schlug fehl in: %r. Abbruch."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:145
|
||||
msgid "Repairing failed"
|
||||
msgstr ""
|
||||
msgstr "Reparatur schlug fehl"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:146
|
||||
msgid "The database repair failed. Starting with a new empty library."
|
||||
msgstr ""
|
||||
"Die Reparatur der Datenbank schlug fehl. Es erfolgt ein Start mit einer "
|
||||
"neuen, leeren Bibliothek."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:150
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:594
|
||||
@ -6528,7 +6547,7 @@ msgstr "Calibre Bibliothek"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:163
|
||||
msgid "Choose a location for your new calibre e-book library"
|
||||
msgstr ""
|
||||
msgstr "Wählen Sie einen Ort für Ihre neue Calibre eBook Bibliothek"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:173
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:206
|
||||
@ -6537,11 +6556,11 @@ msgstr "Schlechter Datenbank Standort"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:174
|
||||
msgid "Bad database location %r. calibre will now quit."
|
||||
msgstr ""
|
||||
msgstr "Ungültiger Datenbank-Ort %r. Calibre beendet sich jetzt."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:187
|
||||
msgid "Corrupted database"
|
||||
msgstr ""
|
||||
msgstr "Beschädigte Datenbank"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:188
|
||||
msgid ""
|
||||
@ -6549,16 +6568,23 @@ msgid ""
|
||||
"and repair it automatically? If you say No, a new empty calibre library will "
|
||||
"be created."
|
||||
msgstr ""
|
||||
"Ihre Calibre Datenbank scheint beschädigt zu sein. Soll Calibre versuchen, "
|
||||
"es automatisch zu reparieren? Wenn Sie Nein sagen, wird eine neue, leere "
|
||||
"Calibre Bibliothek erstellt werden."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:194
|
||||
msgid ""
|
||||
"Repairing database. This can take a very long time for a large collection"
|
||||
msgstr ""
|
||||
"Repariere Datenbank. Dies kann für eine große Büchersammlung einige Zeit "
|
||||
"dauern"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:207
|
||||
msgid ""
|
||||
"Bad database location %r. Will start with a new, empty calibre library"
|
||||
msgstr ""
|
||||
"Ungültiger Datenbank-Ort %r. Starte mit einer neuen, leeren Calibre "
|
||||
"Bibliothek"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:250
|
||||
msgid "If you are sure it is not running"
|
||||
@ -7990,7 +8016,7 @@ msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:184
|
||||
msgid "E-book Viewer"
|
||||
msgstr ""
|
||||
msgstr "E-book Viewer"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:185
|
||||
msgid "Close dictionary"
|
||||
@ -8022,11 +8048,11 @@ msgstr "Weitersuchen"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:198
|
||||
msgid "Find next occurrence"
|
||||
msgstr ""
|
||||
msgstr "Finde nächste Stelle"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:199
|
||||
msgid "F3"
|
||||
msgstr ""
|
||||
msgstr "F3"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:200
|
||||
msgid "Copy to clipboard"
|
||||
@ -8050,15 +8076,15 @@ msgstr "Drucken"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:206
|
||||
msgid "Find previous"
|
||||
msgstr ""
|
||||
msgstr "Finde vorherige"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:207
|
||||
msgid "Find previous occurrence"
|
||||
msgstr ""
|
||||
msgstr "Finde vorherige Stelle"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:208
|
||||
msgid "Shift+F3"
|
||||
msgstr ""
|
||||
msgstr "Shift+F3"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/printing.py:114
|
||||
msgid "Print eBook"
|
||||
@ -8793,19 +8819,19 @@ msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:380
|
||||
msgid "Add an empty book (a book with no formats)"
|
||||
msgstr ""
|
||||
msgstr "Ein leeres Buch hinzufügen (ein Buch ohne Formate)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:382
|
||||
msgid "Set the title of the added empty book"
|
||||
msgstr ""
|
||||
msgstr "Titel des hinzugefügten leeren Buches angeben"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:384
|
||||
msgid "Set the authors of the added empty book"
|
||||
msgstr ""
|
||||
msgstr "Autoren des hinzugefügten leeren Buches angeben"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:386
|
||||
msgid "Set the ISBN of the added empty book"
|
||||
msgstr ""
|
||||
msgstr "ISBN des hinzugefügten leeren Buches angeben"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:411
|
||||
msgid "You must specify at least one file to add"
|
||||
@ -8974,22 +9000,33 @@ msgid ""
|
||||
"column.\n"
|
||||
"datatype is one of: {0}\n"
|
||||
msgstr ""
|
||||
"%prog add_custom_column [options] Beschriftung Name Datentyp\n"
|
||||
"\n"
|
||||
"Erstellt eine benutzerdefinierte Spalte. Beschriftung ist der "
|
||||
"maschinefreundliche Name der Spalte. Sollte\n"
|
||||
"keine Leerzeichen oder Doppelpunkte enthalten. Name ist der "
|
||||
"benutzerfreundliche Name der Spalte.\n"
|
||||
"Datentyp ist einer von : {0}\n"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:654
|
||||
msgid ""
|
||||
"This column stores tag like data (i.e. multiple comma separated values). "
|
||||
"Only applies if datatype is text."
|
||||
msgstr ""
|
||||
"Diese Spalte speichert Etiketten wie Daten (z.B. mehrere durch Kommata "
|
||||
"getrennte Werte). Gilt nur, wenn der Datentyp Text ist."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:658
|
||||
msgid ""
|
||||
"A dictionary of options to customize how the data in this column will be "
|
||||
"interpreted."
|
||||
msgstr ""
|
||||
"Ein Wörterbuch von Einstellungen zum Anpassen, wie die Daten in dieser "
|
||||
"Spalte interpretiert werden."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:671
|
||||
msgid "You must specify label, name and datatype"
|
||||
msgstr ""
|
||||
msgstr "Sie müssen Beschriftung, Name und Datentyp angeben"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:729
|
||||
msgid ""
|
||||
@ -9050,16 +9087,28 @@ msgid ""
|
||||
" command.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" %prog set_custom [options] column id value\n"
|
||||
"\n"
|
||||
" Geben Sie den Wert einer benutzerdefinierten Spalte für das durch die ID "
|
||||
"identifizierte Buch an.\n"
|
||||
" Sie erhalten eine Liste der IDs mit Hilfe des list Befehls.\n"
|
||||
" Sie erhalten eine Liste der Namen von benutzerdefinierten Spalten mit "
|
||||
"Hilfe des custom_columns\n"
|
||||
" Befehls.\n"
|
||||
" "
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:797
|
||||
msgid ""
|
||||
"If the column stores multiple values, append the specified values to the "
|
||||
"existing ones, instead of replacing them."
|
||||
msgstr ""
|
||||
"Wenn die Spalte mehrere Werte speichert, sollen die angegebenen Werte zu den "
|
||||
"bestehenden hinzugefügt werden, anstatt sie zu ersetzen."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:808
|
||||
msgid "Error: You must specify a field name, id and value"
|
||||
msgstr ""
|
||||
msgstr "Fehler: Sie müssen einen Feldnamen, eine ID und einen Wert angeben"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:827
|
||||
msgid ""
|
||||
@ -9069,6 +9118,12 @@ msgid ""
|
||||
" List available custom columns. Shows column labels and ids.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" %prog custom_columns [options]\n"
|
||||
"\n"
|
||||
" Listet verfügbare benutzerdefinierte Spalten auf. Zeigt "
|
||||
"Spaltenbeschriftung und IDs.\n"
|
||||
" "
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:834
|
||||
msgid "Show details for each column."
|
||||
@ -9092,6 +9147,13 @@ msgid ""
|
||||
" columns with the custom_columns command.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" %prog remove_custom_column [options] Beschriftung\n"
|
||||
"\n"
|
||||
" Entfernt die durch die Beschriftung identifizierten benutzerdefinierten "
|
||||
"Spalten. Sie können die verfügbaren\n"
|
||||
" Spalten mit dem custom_columns Befehl anzeigen lassen.\n"
|
||||
" "
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:862
|
||||
msgid "Do not ask for confirmation"
|
||||
@ -9099,7 +9161,7 @@ msgstr "Nicht nach einer Bestätigung fragen"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:872
|
||||
msgid "Error: You must specify a column label"
|
||||
msgstr ""
|
||||
msgstr "Fehler: Sie müssen eine Spaltenbeschriftung angeben"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:885
|
||||
msgid ""
|
||||
@ -9471,7 +9533,7 @@ msgstr "Englisch"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:109
|
||||
msgid "English (China)"
|
||||
msgstr ""
|
||||
msgstr "Englisch (China)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:110
|
||||
msgid "Spanish (Paraguay)"
|
||||
|
@ -8,13 +8,13 @@ msgstr ""
|
||||
"Project-Id-Version: calibre\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 09:40+0000\n"
|
||||
"Last-Translator: pontios <Unknown>\n"
|
||||
"PO-Revision-Date: 2010-05-22 17:30+0000\n"
|
||||
"Last-Translator: MasterCom7 <Unknown>\n"
|
||||
"Language-Team: Greek <el@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: 2010-05-22 03:56+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-23 03:55+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
|
||||
@ -181,6 +181,8 @@ msgid ""
|
||||
"Character encoding for the input HTML files. Common choices include: cp1252, "
|
||||
"latin1, iso-8859-1 and utf-8."
|
||||
msgstr ""
|
||||
"Κωδικοποίηση χαρακτήρων για τα εισαγόμενα αρχεία HTML. Συνήθεις επιλογές "
|
||||
"συμπεριλαμβάνουν : cp1252, latin1, iso-8859-1 και utf-8"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:57
|
||||
msgid ""
|
||||
@ -188,10 +190,12 @@ msgid ""
|
||||
"directory pmlname_img or images. This plugin is run every time you add a PML "
|
||||
"file to the library."
|
||||
msgstr ""
|
||||
"Δημιουργία μιας αρχειοθήκης PMLZ που περιέχει το αρχείο PML και όλες τις "
|
||||
"εικόνες στον κατάλογο pmlname_img ή images"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:89
|
||||
msgid "Extract cover from comic files"
|
||||
msgstr ""
|
||||
msgstr "Εξαγωγή εξωφύλλου από αρχεία κόμικς"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:116
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:127
|
||||
@ -212,15 +216,15 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:296
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:306
|
||||
msgid "Read metadata from %s files"
|
||||
msgstr ""
|
||||
msgstr "Ανάγνωση συνδεδομένων από αρχεία %s"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:265
|
||||
msgid "Read metadata from ebooks in RAR archives"
|
||||
msgstr ""
|
||||
msgstr "Ανάγνωση συνδεδομένων από ηλεκτρονικά βιβλία μέσα σε αρχειοθήκες RAR"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:317
|
||||
msgid "Read metadata from ebooks in ZIP archives"
|
||||
msgstr ""
|
||||
msgstr "Ανάγνωση συνδεδομένων από ηλεκτρονικά βιβλία μέσα σε αρχειοθήκες ZIP"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:328
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:338
|
||||
@ -229,15 +233,15 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:381
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:391
|
||||
msgid "Set metadata in %s files"
|
||||
msgstr ""
|
||||
msgstr "Καθορισμός συνδεδομένων σε αρχεία %s"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:359
|
||||
msgid "Set metadata from %s files"
|
||||
msgstr ""
|
||||
msgstr "Καθορισμός συνδεδομένων από αρχεία %s"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:102
|
||||
msgid "Conversion Input"
|
||||
msgstr ""
|
||||
msgstr "Είσοδος μετατροπής"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:125
|
||||
msgid ""
|
||||
@ -246,100 +250,113 @@ msgid ""
|
||||
"useful for documents that do not declare an encoding or that have erroneous "
|
||||
"encoding declarations."
|
||||
msgstr ""
|
||||
"Προσδιορισμός κωδικοποίησης χαρακτήρων του εισαγόμενου εγγράφου. Αυτή η "
|
||||
"επιλογή, αν καθορισθεί, θα έχει προτεραιότητα έναντι οποιασδήποτε άλλης "
|
||||
"κωδικοποίησης που έχει δηλωθεί από το ίδιο το έγγραφο. Ιδιαιτέρως χρήσιμο "
|
||||
"για έγγραφα που δεν δηλώνουν κωδικοποίηση ή διαθέτουν εσφαλμένη δήλωση "
|
||||
"κωδικοποίησης."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:241
|
||||
msgid "Conversion Output"
|
||||
msgstr ""
|
||||
msgstr "Έξοδος μετατροπής"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:255
|
||||
msgid ""
|
||||
"If specified, the output plugin will try to create output that is as human "
|
||||
"readable as possible. May not have any effect for some output plugins."
|
||||
msgstr ""
|
||||
"Αν προσδιορισθεί, το πρόσθετο εξόδου θα προσπαθήσει να δημιουργήσει "
|
||||
"εξαγόμενα όσο το δυνατόν ανθρωπίνως αναγνώσιμα. Για ορισμένα πρόσθετα εξόδου "
|
||||
"μπορεί να μην έχει κανένα αποτέλεσμα."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:45
|
||||
msgid "Input profile"
|
||||
msgstr ""
|
||||
msgstr "Περίγραμμα εισόδου"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:49
|
||||
msgid ""
|
||||
"This profile tries to provide sane defaults and is useful if you know "
|
||||
"nothing about the input document."
|
||||
msgstr ""
|
||||
"Αυτό το περίγραμμα προσπαθεί να παρέχει λογικά προτερόθετα (αρχικές τιμές) "
|
||||
"και είναι χρήσιμο αν δε γνωρίζετε τίποτα για το εισαγόμενο έγγραφο."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:57
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:258
|
||||
msgid ""
|
||||
"This profile is intended for the SONY PRS line. The 500/505/600/700 etc."
|
||||
msgstr ""
|
||||
"Αυτό το περίγραμμα προορίζεται για τη σειρά SONY PRS. Τα 500/505/600/700 κλπ."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:69
|
||||
msgid "This profile is intended for the SONY PRS 300."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για το SONY PRS 300."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:78
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:292
|
||||
msgid "This profile is intended for the SONY PRS-900."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για το SONY PRS-900."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:86
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:322
|
||||
msgid "This profile is intended for the Microsoft Reader."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για το Microsoft Reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:97
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:333
|
||||
msgid "This profile is intended for the Mobipocket books."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για τα βιβλία Mobipocket."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:110
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:346
|
||||
msgid "This profile is intended for the Hanlin V3 and its clones."
|
||||
msgstr ""
|
||||
"Αυτό το περίγραμμα προορίζεται για το Hanlin V3 και τους κλώνους του."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:122
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:358
|
||||
msgid "This profile is intended for the Hanlin V5 and its clones."
|
||||
msgstr ""
|
||||
"Αυτό το περίγραμμα προορίζεται για το Hanlin V5 και τους κλώνους του."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:132
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:366
|
||||
msgid "This profile is intended for the Cybook G3."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για το Cybook G3."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:145
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:379
|
||||
msgid "This profile is intended for the Cybook Opus."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για το Cybook Opus."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:157
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:390
|
||||
msgid "This profile is intended for the Amazon Kindle."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για το Amazon Kindle."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:169
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:425
|
||||
msgid "This profile is intended for the Irex Illiad."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για το Irex Illiad."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:181
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:438
|
||||
msgid "This profile is intended for the IRex Digital Reader 1000."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για το IRex Digital Reader 1000."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:194
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:452
|
||||
msgid "This profile is intended for the IRex Digital Reader 800."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για το IRex Digital Reader 800."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:206
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:466
|
||||
msgid "This profile is intended for the B&N Nook."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για το B&N Nook."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:226
|
||||
msgid "Output profile"
|
||||
msgstr ""
|
||||
msgstr "Περίγραμμα εξόδου"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:230
|
||||
msgid ""
|
||||
@ -347,57 +364,62 @@ msgid ""
|
||||
"produce a document intended to be read at a computer or on a range of "
|
||||
"devices."
|
||||
msgstr ""
|
||||
"Αυτό το περίγραμμα προσπαθεί να παρέχει λογικά προτερόθετα (αρχικές τιμές) "
|
||||
"και είναι χρήσιμο για την παραγωγή εγγράφων που προορίζονται να διαβαστούν "
|
||||
"σε Η/Υ ή σε μια ποικιλία συσκευών."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:248
|
||||
msgid ""
|
||||
"Intended for the iPad and similar devices with a resolution of 768x1024"
|
||||
msgstr ""
|
||||
msgstr "Προορίζεται για το iPad και παρόμοιες συσκευές με ανάλυση 768x1024"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:271
|
||||
msgid "This profile is intended for the Kobo Reader."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για το Kobo Reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:283
|
||||
msgid "This profile is intended for the SONY PRS-300."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για το SONY PRS-300."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:301
|
||||
msgid "This profile is intended for the 5-inch JetBook."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για το JetBook 5 ιντσών."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:310
|
||||
msgid ""
|
||||
"This profile is intended for the SONY PRS line. The 500/505/700 etc, in "
|
||||
"landscape mode. Mainly useful for comics."
|
||||
msgstr ""
|
||||
"Αυτό το περίγραμμα προορίζεται για τη σειρά SONY PRS. Τα 500/505/700 κλπ., "
|
||||
"σε οριζόντια διάταξη (landscape). Χρήσιμο κυρίως για κόμικς."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:408
|
||||
msgid "This profile is intended for the Amazon Kindle DX."
|
||||
msgstr ""
|
||||
msgstr "Αυτό το περίγραμμα προορίζεται για το Amazon Kindle DX."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:31
|
||||
msgid "Installed plugins"
|
||||
msgstr ""
|
||||
msgstr "Εγκατεστημένα πρόσθετα"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:32
|
||||
msgid "Mapping for filetype plugins"
|
||||
msgstr ""
|
||||
msgstr "Απεικόνιση για πρόσθετα αρχειοτύπων"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:33
|
||||
msgid "Local plugin customization"
|
||||
msgstr ""
|
||||
msgstr "Τοπική εξατομίκευση προσθέτων"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:34
|
||||
msgid "Disabled plugins"
|
||||
msgstr ""
|
||||
msgstr "Απενεργοποιημένα πρόσθετα"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:77
|
||||
msgid "No valid plugin found in "
|
||||
msgstr ""
|
||||
msgstr "Δεν βρέθηκε έγκυρο πρόσθετο "
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:278
|
||||
msgid "Initialization of plugin %s failed with traceback:"
|
||||
msgstr ""
|
||||
msgstr "Η αρχικοποίηση του πρόσθετου %s απέτυχε με traceback:"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:433
|
||||
msgid ""
|
||||
@ -406,20 +428,30 @@ msgid ""
|
||||
" Customize calibre by loading external plugins.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
" Επιλογές %prog\n"
|
||||
"\n"
|
||||
" Εξατομίκευση του calibre με φόρτωση εξωτερικών προσθέτων.\n"
|
||||
" "
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:439
|
||||
msgid "Add a plugin by specifying the path to the zip file containing it."
|
||||
msgstr ""
|
||||
"Προσθήκη ενός προσθέτου με προσδιορισμό της διεύθυνσης (path) του αρχείου "
|
||||
"zip που το περιέχει."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:441
|
||||
msgid "Remove a custom plugin by name. Has no effect on builtin plugins"
|
||||
msgstr ""
|
||||
"Αφαίρεση εξατομικευμένων προσθέτων, ονομαστικά. Δεν επηρεάζει τα "
|
||||
"ενσωματωμένα πρόσθετα"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:443
|
||||
msgid ""
|
||||
"Customize plugin. Specify name of plugin and customization string separated "
|
||||
"by a comma."
|
||||
msgstr ""
|
||||
"Εξατομίκευση προσθέτου. Προσδιόρισε όνομα προσθέτου και στοιχειοσειρά "
|
||||
"εξατομίκευσης χωρισμένα με κόμμα."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:445
|
||||
msgid "List all installed plugins"
|
||||
@ -427,33 +459,35 @@ msgstr "Εμφάνιση όλων των εγκατεστημένων πρόσθ
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:447
|
||||
msgid "Enable the named plugin"
|
||||
msgstr ""
|
||||
msgstr "Ενεργοποίηση του ονομαζόμενου προσθέτου"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:449
|
||||
msgid "Disable the named plugin"
|
||||
msgstr ""
|
||||
msgstr "Απενεργοποίηση του ονομαζόμενου προσθέτου"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:13
|
||||
msgid "Communicate with Android phones."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με τηλέφωνα Android."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:39
|
||||
msgid ""
|
||||
"Comma separated list of directories to send e-books to on the device. The "
|
||||
"first one that exists will be used"
|
||||
msgstr ""
|
||||
"Σειρά καταλόγων στη συσκευή, χωρισμένων με κόμμα, προς αποστολή ηλεκτρονικών "
|
||||
"βιβλίων. Ο πρώτος στη σειρά θα χρησιμοποιηθεί."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:67
|
||||
msgid "Communicate with S60 phones."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με τηλέφωνα S60."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/binatone/driver.py:17
|
||||
msgid "Communicate with the Binatone Readme eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Binatone Readme eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:13
|
||||
msgid "Communicate with the Blackberry smart phone."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το «έξυπνο» τηλέφωνο Blackberry."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:14
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/nuut2/driver.py:18
|
||||
@ -463,113 +497,113 @@ msgstr "Kovid Goyal"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/cybook/driver.py:22
|
||||
msgid "Communicate with the Cybook Gen 3 / Opus eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Cybook Gen 3 / Opus eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:24
|
||||
msgid "Communicate with the EB600 eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με τον ηλ.αναγνώστη EB600."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/edge/driver.py:17
|
||||
msgid "Entourage Edge"
|
||||
msgstr ""
|
||||
msgstr "Entourage Edge"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/edge/driver.py:18
|
||||
msgid "Communicate with the Entourage Edge."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Entourage Edge."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/eslick/driver.py:16
|
||||
msgid "Communicate with the ESlick eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με τον ηλ.αναγνώστη ESlick."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:19
|
||||
msgid "Communicate with Hanlin V3 eBook readers."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με τους ηλ.αναγνώστες Hanlin V3."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:95
|
||||
msgid "Communicate with Hanlin V5 eBook readers."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με τους ηλ.αναγνώστες Hanlin V5."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:114
|
||||
msgid "Communicate with the BOOX eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με τον ηλ.αναγνώστη BOOX."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:18
|
||||
msgid "Communicate with the Hanvon N520 eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με τον ηλ.αναγνώστη Hanvon N520."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:41
|
||||
msgid "Communicate with the SpringDesign Alex eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με τον ηλ.αναγνώστη SpringDesign Alex."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:57
|
||||
msgid "Communicate with the Azbooka"
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Azbooka"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:70
|
||||
msgid "Communicate with the Elonex EB 511 eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Elonex EB 511 eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/iliad/driver.py:16
|
||||
msgid "Communicate with the IRex Iliad eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το IRex Iliad eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/iliad/driver.py:17
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/irexdr/driver.py:18
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:29
|
||||
msgid "John Schember"
|
||||
msgstr ""
|
||||
msgstr "John Schember"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/interface.py:23
|
||||
msgid "Device Interface"
|
||||
msgstr ""
|
||||
msgstr "Διεπαφή συσκευής"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/irexdr/driver.py:16
|
||||
msgid "Communicate with the IRex Digital Reader 1000 eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το ηλ.αναγνωστήριο IRex Digital Reader 1000."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/irexdr/driver.py:42
|
||||
msgid "Communicate with the IRex Digital Reader 800"
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το IRex Digital Reader 800"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/iriver/driver.py:15
|
||||
msgid "Communicate with the Iriver Story reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Iriver Story reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:20
|
||||
msgid "Communicate with the JetBook eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το JetBook eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:21
|
||||
msgid "Communicate with the Kindle eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Kindle eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:152
|
||||
msgid "Communicate with the Kindle 2 eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Kindle 2 eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:162
|
||||
msgid "Communicate with the Kindle DX eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Kindle DX eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/misc.py:15
|
||||
msgid "Communicate with the Palm Pre"
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Palm Pre"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/misc.py:35
|
||||
msgid "Communicate with the Kobo Reader"
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Kobo Reader"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/misc.py:56
|
||||
msgid "Communicate with the Booq Avant"
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Booq Avant"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/nokia/driver.py:17
|
||||
msgid "Communicate with the Nokia 770 internet tablet."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Nokia 770 internet tablet."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/nokia/driver.py:40
|
||||
msgid "Communicate with the Nokia 810 internet tablet."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Nokia 810 internet tablet."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:20
|
||||
msgid "The Nook"
|
||||
@ -577,15 +611,15 @@ msgstr "The Nook"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:21
|
||||
msgid "Communicate with the Nook eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Nook eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/nuut2/driver.py:17
|
||||
msgid "Communicate with the Nuut2 eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Nuut2 eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs500/driver.py:89
|
||||
msgid "Communicate with the Sony PRS-500 eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Sony PRS-500 eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/books.py:150
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:104
|
||||
@ -599,24 +633,26 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:80
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:89
|
||||
msgid "Getting list of books on device..."
|
||||
msgstr "Λήψη λίστας βιβλίων στη συσκευή..."
|
||||
msgstr "Λήψη καταλόγου βιβλίων στη συσκευή..."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:26
|
||||
msgid "Communicate with the Sony PRS-300/505/500 eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Sony PRS-300/505/500 eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:58
|
||||
msgid ""
|
||||
"Comma separated list of metadata fields to turn into collections on the "
|
||||
"device. Possibilities include: "
|
||||
msgstr ""
|
||||
"Κατάλογος πεδίων συνδεδομένων, χωρισμένων με κόμμα, στη συσκευή, προς "
|
||||
"μετατροπή σε συλλογές. Οι πιθανότητες συμπεριλαμβάνουν: "
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:149
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:151
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:115
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:117
|
||||
msgid "Transferring books to device..."
|
||||
msgstr "Μεταφορά βιβλίων στη συεκυή..."
|
||||
msgstr "Μεταφορά βιβλίων στη συσκευή..."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:189
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:196
|
||||
@ -628,71 +664,74 @@ msgstr "Αφαίρεση βιβλίων από τη συσκευή..."
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:224
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:176
|
||||
msgid "Sending metadata to device..."
|
||||
msgstr ""
|
||||
msgstr "Αποστολή συνδεδομένων στη συσκευή..."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:230
|
||||
msgid "Communicate with the Sony PRS-600/700/900 eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Sony PRS-600/700/900 eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/sne/driver.py:17
|
||||
msgid "Communicate with the Samsung SNE eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Samsung SNE eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:11
|
||||
msgid "Communicate with the Teclast K3 reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Teclast K3 reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:45
|
||||
msgid "Communicate with the Newsmy reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το Newsmy reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:60
|
||||
msgid "Communicate with the iPapyrus reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με το iPapyrus reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:252
|
||||
msgid "Unable to detect the %s disk drive. Try rebooting."
|
||||
msgstr ""
|
||||
msgstr "Αδύνατον να εντοπιστεί ο σκληρός δίσκος %s. Δοκιμάστε επανεκκίνηση."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:425
|
||||
msgid "Unable to detect the %s mount point. Try rebooting."
|
||||
msgstr ""
|
||||
"Αδύνατον να εντοπιστεί το σημείο εφαρμογής %s. Δοκιμάστε επανεκκίνηση."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:490
|
||||
msgid "Unable to detect the %s disk drive."
|
||||
msgstr ""
|
||||
msgstr "Αδύνατον να εντοπιστεί ο σκληρός δίσκος %s."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:583
|
||||
msgid "Could not find mount helper: %s."
|
||||
msgstr ""
|
||||
msgstr "Δεν βρέθηκε ο βοηθός εφαρμογής: %s."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:595
|
||||
msgid ""
|
||||
"Unable to detect the %s disk drive. Your kernel is probably exporting a "
|
||||
"deprecated version of SYSFS."
|
||||
msgstr ""
|
||||
"Αδύνατον να εντοπιστεί ο σκληρός δίσκος %s. Είναι πιθανό ο πυρήνας σας "
|
||||
"(kernel) να εξαγάγει μια παρωχημένη έκδοση του SYSFS."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:603
|
||||
msgid "Unable to mount main memory (Error code: %d)"
|
||||
msgstr ""
|
||||
msgstr "Αδύνατον να εφαρμοστεί η κύρια μνήμη (Κώδικας σφάλματος : %d)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:740
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:742
|
||||
msgid "The reader has no storage card in this slot."
|
||||
msgstr ""
|
||||
msgstr "Δεν υπάρχει κάρτα αποθήκευσης στην υποδοχή του αναγνώστη."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:744
|
||||
msgid "Selected slot: %s is not supported."
|
||||
msgstr ""
|
||||
msgstr "Η επιλεγμένη υποδοχή: %s δεν υποστηρίζεται."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:777
|
||||
msgid "There is insufficient free space in main memory"
|
||||
msgstr ""
|
||||
msgstr "Δεν υπάρχει αρκετός χώρος στην κύρια μνήμη."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:779
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:781
|
||||
msgid "There is insufficient free space on the storage card"
|
||||
msgstr ""
|
||||
msgstr "Δεν υπάρχει αρκετός χώρος στην κάρτα αποθήκευσης"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:811
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:817
|
||||
@ -710,114 +749,116 @@ msgstr "Ρύθμιση συσκευής"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/deviceconfig.py:28
|
||||
msgid "settings for device drivers"
|
||||
msgstr ""
|
||||
msgstr "Ρυθμίσεις για οδηγούς συσκευών"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/deviceconfig.py:30
|
||||
msgid "Ordered list of formats the device will accept"
|
||||
msgstr ""
|
||||
"Ταξινομημένος κατάλογος των μορφοτύπων (format) που δέχεται η συσκευή"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/deviceconfig.py:32
|
||||
msgid "Place files in sub directories if the device supports them"
|
||||
msgstr ""
|
||||
msgstr "Βάλε τα αρχεία σε υποφακέλους, αν υποστηρίζονται από τη συσκευή"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/deviceconfig.py:34
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:78
|
||||
msgid "Read metadata from files on device"
|
||||
msgstr ""
|
||||
msgstr "Ανάγνωση συνδεδομένων από τα αρχεία της συσκευής"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/deviceconfig.py:36
|
||||
msgid "Template to control how books are saved"
|
||||
msgstr ""
|
||||
msgstr "Σχεδιότυπο που ελέγχει πως αποθηκεύονται τα βιβλία"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/deviceconfig.py:39
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:80
|
||||
msgid "Extra customization"
|
||||
msgstr ""
|
||||
msgstr "Πρόσθετη εξατομίκευση"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:28
|
||||
msgid "Communicate with an eBook reader."
|
||||
msgstr ""
|
||||
msgstr "Επικοινωνία με έναν ηλ.αναγνώστη"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:36
|
||||
msgid "Get device information..."
|
||||
msgstr "Λήψη πληροφοριών συσκευής..."
|
||||
msgstr "Λήψη στοιχείων συσκευής"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:132
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:140
|
||||
msgid "Adding books to device metadata listing..."
|
||||
msgstr ""
|
||||
msgstr "Προσθήκη βιβλίων στον κατάλογο συνδεδομένων της συσκευής..."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:165
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:170
|
||||
msgid "Removing books from device metadata listing..."
|
||||
msgstr ""
|
||||
msgstr "Αφαίρεση βιβλίων από τον κατάλογο συνδεδομένων της συσκευής..."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:41
|
||||
msgid "%prog [options] mybook.chm"
|
||||
msgstr ""
|
||||
msgstr "%prog [επιλογές] mybook.chm"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:42
|
||||
msgid "Output directory. Defaults to current directory"
|
||||
msgstr ""
|
||||
msgstr "Κατάλογος εξόδου. Αρχικά έχει οριστεί ο τρέχων κατάλογος"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:45
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:589
|
||||
msgid "Set the book title"
|
||||
msgstr ""
|
||||
msgstr "Καθορισμός τίτλου βιβλίου"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:47
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:591
|
||||
msgid "Set sort key for the title"
|
||||
msgstr ""
|
||||
msgstr "Καθορισμός κλειδιού ταξινόμησης για τον τίτλο"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:49
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:593
|
||||
msgid "Set the author"
|
||||
msgstr ""
|
||||
msgstr "Καθορισμός συγγραφέα"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:51
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:595
|
||||
msgid "Set sort key for the author"
|
||||
msgstr ""
|
||||
msgstr "Καθορισμός κλειδιού ταξινόμησης για τον συγγραφέα"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:53
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:597
|
||||
msgid "The category this book belongs to. E.g.: History"
|
||||
msgstr ""
|
||||
msgstr "Κατηγορία στην οποία ανήκει αυτό το βιβλίο. π.χ.: Ιστορία"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:56
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:600
|
||||
msgid "Path to a graphic that will be set as this files' thumbnail"
|
||||
msgstr ""
|
||||
"Διεύθυνση εικόνας που θα χρησιμοποιηθεί ως εικονίδιο αυτού του αρχείου"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:59
|
||||
msgid "Path to a txt file containing a comment."
|
||||
msgstr ""
|
||||
msgstr "Διεύθυνση αρχείοου txt που περιέχει κάποιο σχόλιο"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:62
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:607
|
||||
msgid "Extract thumbnail from LRF file"
|
||||
msgstr ""
|
||||
msgstr "Εξαγωγή εικονιδίου από αρχείο LRF"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:63
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:608
|
||||
msgid "Set the publisher"
|
||||
msgstr ""
|
||||
msgstr "Καθορισμός εκδότη"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:64
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:609
|
||||
msgid "Set the book classification"
|
||||
msgstr ""
|
||||
msgstr "Καθορισμός κατηγοριοποίησης βιβλίου"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:65
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:610
|
||||
msgid "Set the book creator"
|
||||
msgstr ""
|
||||
msgstr "Καθορισμός δημιουργού του βιβλίου"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:66
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:611
|
||||
msgid "Set the book producer"
|
||||
msgstr ""
|
||||
msgstr "Καθορισμός παραγωγού του βιβλίου"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:68
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:613
|
||||
@ -825,19 +866,22 @@ msgid ""
|
||||
"Extract cover from LRF file. Note that the LRF format has no defined cover, "
|
||||
"so we use some heuristics to guess the cover."
|
||||
msgstr ""
|
||||
"Εξαγωγή εξωφύλλου από αρχείο LRF. Σημειώστε ότι τα αρχεία LRF δεν έχουν "
|
||||
"προκαθορισμένο εξώφυλλο, γι'αυτό χρησιμοποιούμε ευρετικές μεθόδους "
|
||||
"(heuristics) για να μαντέψουμε το εξώφυλλο."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:70
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:615
|
||||
msgid "Set book ID"
|
||||
msgstr ""
|
||||
msgstr "Καθορισμός ταυτότητας (ID) του βιβλίου"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:72
|
||||
msgid "Set font delta"
|
||||
msgstr ""
|
||||
msgstr "Καθορισμός του δέλτα της γραμματοσειράς"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:201
|
||||
msgid "Rendered %s"
|
||||
msgstr ""
|
||||
msgstr "%s επεξεργάσθηκε"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:204
|
||||
msgid "Failed %s"
|
||||
@ -849,6 +893,9 @@ msgid ""
|
||||
"\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
"Αποτυχία στην επεξεργασία του κόμικ: \n"
|
||||
"\n"
|
||||
"%s"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:279
|
||||
msgid ""
|
||||
@ -856,53 +903,74 @@ msgid ""
|
||||
"of less than 256 may result in blurred text on your device if you are "
|
||||
"creating your comics in EPUB format."
|
||||
msgstr ""
|
||||
"Αριθμός χρωμάτων για μετατροπή α/μ εικόνας (grayscale) . Αρχική τιμή: "
|
||||
"%default. Αν οι τιμή είναι μικρότερη από 256 μπορεί το κείμενο στη συσκευή "
|
||||
"σας να εμφανίζεται θολό αν δημιουργείτε τα κόμικς σας σε μορφότυπο EPUB."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:283
|
||||
msgid ""
|
||||
"Disable normalize (improve contrast) color range for pictures. Default: False"
|
||||
msgstr ""
|
||||
"Απενεργοποίηση κανονικοποίησης (βελτίωση της αντίθεσης) της χρωματικής "
|
||||
"κλίμακας των εικόνων. Αρχική τιμή: Μη Αληθές"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:286
|
||||
msgid "Maintain picture aspect ratio. Default is to fill the screen."
|
||||
msgstr ""
|
||||
"Διατήρηση των αναλογιών της εικόνας. Αρχική τιμή: να καταλαμβάνει όλη την "
|
||||
"οθόνη"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:288
|
||||
msgid "Disable sharpening."
|
||||
msgstr ""
|
||||
msgstr "Απενεργοποίηση όξυνσης"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:290
|
||||
msgid ""
|
||||
"Disable trimming of comic pages. For some comics, trimming might remove "
|
||||
"content as well as borders."
|
||||
msgstr ""
|
||||
"Απενεργοποίηση ψαλιδίσματος των σελίδων κόμικς. Σε κάποια κόμικς το "
|
||||
"ψαλίδισμα ενδέχεται να αφαιρέσει μέρος του περιεχομένου μαζί με τα περιθώρια"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:293
|
||||
msgid "Don't split landscape images into two portrait images"
|
||||
msgstr ""
|
||||
"Να μη διασπώνται οι οριζόντιες εικόνες (landscape) σε δύο κάθετες εικόνες "
|
||||
"(portrait)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:295
|
||||
msgid ""
|
||||
"Keep aspect ratio and scale image using screen height as image width for "
|
||||
"viewing in landscape mode."
|
||||
msgstr ""
|
||||
"Διατήρηση των αναλογιών των εικόνων και κλιμάκωση τους χρησιμοποιώντας για "
|
||||
"πλάτος της εικόνας το ύψος της οθόνης, όταν η θέαση είναι οριζόντια "
|
||||
"(landscape)."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:298
|
||||
msgid ""
|
||||
"Used for right-to-left publications like manga. Causes landscape pages to be "
|
||||
"split into portrait pages from right to left."
|
||||
msgstr ""
|
||||
"Χρησιμοποιείται για έντυπα όπου η ανάγνωση γίνεται από δεξιά προς αριστερά, "
|
||||
"όπως τα μάνγκα. Προκαλεί διάσπαση των οριζόντιων σελίδων (landscape) σε "
|
||||
"κάθετες σελίδες (portrait), από δεξιά προς αριστερά."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:302
|
||||
msgid ""
|
||||
"Enable Despeckle. Reduces speckle noise. May greatly increase processing "
|
||||
"time."
|
||||
msgstr ""
|
||||
"Ενεργοποίηση Αποκηλίδωσης. Μειώνει το θόρυβο από μικροκηλίδες. Ο χρόνος "
|
||||
"επεξεργασίας μπορεί να αυξηθεί κατά πολύ."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:305
|
||||
msgid ""
|
||||
"Don't sort the files found in the comic alphabetically by name. Instead use "
|
||||
"the order they were added to the comic."
|
||||
msgstr ""
|
||||
"Να μην ταξινομούνται ονομαστικά τα αρχεία που βρίσκονται σε ένα κόμικ. "
|
||||
"Αντ'αυτού να χρησιμοποιείται η σειρά με την οποία προστέθηκαν στο κόμικ."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:309
|
||||
msgid ""
|
||||
@ -910,14 +978,17 @@ msgid ""
|
||||
"experiment to see which format gives you optimal size and look on your "
|
||||
"device."
|
||||
msgstr ""
|
||||
"Το μορφότυπο (format) στο οποίο μετατρέπονται οι εικόνες στο δημιουργηθέν "
|
||||
"ηλ.βιβλίο. Μπορείτε να πειραματιστείτε για το ποιό μορφότυπο αποδίδει το "
|
||||
"βέλτιστο μέγεθος και εμφάνιση στη συσκευή σας."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:313
|
||||
msgid "Apply no processing to the image"
|
||||
msgstr ""
|
||||
msgstr "Να μην επεξεργαστεί η εικόνα"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:315
|
||||
msgid "Do not convert the image to grayscale (black and white)"
|
||||
msgstr ""
|
||||
msgstr "Να μην μετατραπεί η εικόνα σε α/μ (grayscale)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:452
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:463
|
||||
|
@ -11,15 +11,31 @@ msgstr ""
|
||||
"Project-Id-Version: es\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 07:09+0000\n"
|
||||
"Last-Translator: Kovid Goyal <Unknown>\n"
|
||||
"PO-Revision-Date: 2010-05-23 00:32+0000\n"
|
||||
"Last-Translator: errotaburu <Unknown>\n"
|
||||
"Language-Team: Spanish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-22 03:58+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-24 03:45+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:310
|
||||
msgid ""
|
||||
"This profile is intended for the SONY PRS line. The 500/505/700 etc, in "
|
||||
"landscape mode. Mainly useful for comics."
|
||||
msgstr ""
|
||||
"Este perfil está pensado para la línea PRS de SONY. Los 500/505/700, etc., "
|
||||
"en modo apaisado. Útil principalmente para cómics."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:408
|
||||
msgid "This profile is intended for the Amazon Kindle DX."
|
||||
msgstr "Este perfil está pensado para el Kindle DX de Amazon."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:31
|
||||
msgid "Installed plugins"
|
||||
msgstr "Complementos instalados"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
|
||||
msgid "Does absolutely nothing"
|
||||
msgstr "No hace absolutamente nada"
|
||||
@ -374,10 +390,11 @@ msgstr ""
|
||||
msgid ""
|
||||
"Intended for the iPad and similar devices with a resolution of 768x1024"
|
||||
msgstr ""
|
||||
"Pensado para el Ipad y dispositivos similares con una resolución de 768x1024"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:271
|
||||
msgid "This profile is intended for the Kobo Reader."
|
||||
msgstr ""
|
||||
msgstr "Este perfil está pensado para el lector Kobo"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:283
|
||||
msgid "This profile is intended for the SONY PRS-300."
|
||||
@ -387,22 +404,6 @@ msgstr "Este perfil está pensado para el SONY PRS-300."
|
||||
msgid "This profile is intended for the 5-inch JetBook."
|
||||
msgstr "Este perfil está pensado para el JetBook de 5 pulgadas."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:310
|
||||
msgid ""
|
||||
"This profile is intended for the SONY PRS line. The 500/505/700 etc, in "
|
||||
"landscape mode. Mainly useful for comics."
|
||||
msgstr ""
|
||||
"Este perfil está pensado para la línea PRS de SONY. Los 500/505/700, etc., "
|
||||
"en modo apaisado. Útil principalmente para cómics."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:408
|
||||
msgid "This profile is intended for the Amazon Kindle DX."
|
||||
msgstr "Este perfil está pensado para el Kindle DX de Amazon."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:31
|
||||
msgid "Installed plugins"
|
||||
msgstr "Complementos instalados"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/ui.py:32
|
||||
msgid "Mapping for filetype plugins"
|
||||
msgstr "Asociaciones para complementos por tipos de archivo"
|
||||
@ -539,7 +540,7 @@ msgstr "Comunicar con el lector Alex de SpringDesign."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:57
|
||||
msgid "Communicate with the Azbooka"
|
||||
msgstr ""
|
||||
msgstr "Comunicarse con el Azbooka"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:70
|
||||
msgid "Communicate with the Elonex EB 511 eBook reader."
|
||||
@ -593,11 +594,11 @@ msgstr "Comunicar con Palm Pre."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/misc.py:35
|
||||
msgid "Communicate with the Kobo Reader"
|
||||
msgstr ""
|
||||
msgstr "Comunicarse con el Kobo Reader"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/misc.py:56
|
||||
msgid "Communicate with the Booq Avant"
|
||||
msgstr ""
|
||||
msgstr "Comunicarse con el Booq Avant"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/nokia/driver.py:17
|
||||
msgid "Communicate with the Nokia 770 internet tablet."
|
||||
@ -682,11 +683,11 @@ msgstr "Comunicar con el lector Teclast K3."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:45
|
||||
msgid "Communicate with the Newsmy reader."
|
||||
msgstr ""
|
||||
msgstr "Comunicarse con el lector Newsmy"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:60
|
||||
msgid "Communicate with the iPapyrus reader."
|
||||
msgstr ""
|
||||
msgstr "Comunicarse con el lector iPapyrus"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:252
|
||||
msgid "Unable to detect the %s disk drive. Try rebooting."
|
||||
@ -1618,6 +1619,12 @@ msgid ""
|
||||
"and bottom of the image, but the image will never be distorted. Without this "
|
||||
"option the image may be slightly distorted, but there will be no borders."
|
||||
msgstr ""
|
||||
"Cuando se use una portada SVG esta opción podrá causar que la portada se "
|
||||
"escale para cubrir el área disponible de pantalla, pero conservara su "
|
||||
"relación de aspecto (la relación entre la anchura y la altura). Esto supone "
|
||||
"que puede haber margenes blancos a los lados o arriba y abajo de la imagen, "
|
||||
"pero la imagen no sera distorsionada. Sin esta opción la imagen puede estar "
|
||||
"ligeramente distorsionada pero no tendrá margenes en blanco."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/fb2ml.py:144
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/rb/rbml.py:102
|
||||
@ -3308,7 +3315,7 @@ msgstr "&Sin portada SVG"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:52
|
||||
msgid "Preserve cover &aspect ratio"
|
||||
msgstr ""
|
||||
msgstr "Conservar la portada y la proporción de aspecto."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:53
|
||||
msgid "Split files &larger than:"
|
||||
@ -3430,15 +3437,15 @@ msgstr "Controlar la apariencia de la salida"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:30
|
||||
msgid "Original"
|
||||
msgstr ""
|
||||
msgstr "Original"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:31
|
||||
msgid "Left align"
|
||||
msgstr ""
|
||||
msgstr "Alineación izquierda"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:32
|
||||
msgid "Justify text"
|
||||
msgstr ""
|
||||
msgstr "Justifiar texto"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:120
|
||||
msgid "&Disable font size rescaling"
|
||||
@ -3497,7 +3504,7 @@ msgstr "C&SS adicional"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:136
|
||||
msgid "&Transliterate unicode characters to ASCII"
|
||||
msgstr ""
|
||||
msgstr "&Transliterar los caracteres unicode mediante ASCII"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:137
|
||||
msgid "Insert &blank line"
|
||||
@ -3505,7 +3512,7 @@ msgstr "Insertar línea en &blanco"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:138
|
||||
msgid "Keep &ligatures"
|
||||
msgstr ""
|
||||
msgstr "Mantener &ligaduras"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:19
|
||||
msgid "LRF Output"
|
||||
@ -4541,7 +4548,7 @@ msgstr "nueva dirección de correo electrónico"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:472
|
||||
msgid "System port selected"
|
||||
msgstr ""
|
||||
msgstr "Puerto de sistema seleccionado"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:473
|
||||
msgid ""
|
||||
@ -4549,6 +4556,10 @@ msgid ""
|
||||
"port. You operating system <b>may</b> not allow the server to run on this "
|
||||
"port. To be safe choose a port number larger than 1024."
|
||||
msgstr ""
|
||||
"El valor <b>%d</b> que ha escogido para el puerto del servidor de contenidos "
|
||||
"es un puerto de sistema. Su sistema operativo <b>podría</b> no permitir al "
|
||||
"servidor utilizar ese puerto. Para estar seguros, elija un número de puerto "
|
||||
"superior a 1024."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:492
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:837
|
||||
@ -6465,15 +6476,15 @@ msgstr "No comprobar actualizaciones"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:65
|
||||
msgid "Choose a location for your calibre e-book library"
|
||||
msgstr ""
|
||||
msgstr "Escoja una ubicación para su biblioteca de libros de calibre"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:74
|
||||
msgid "Failed to create library"
|
||||
msgstr ""
|
||||
msgstr "Error en la creación de la biblioteca"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:75
|
||||
msgid "Failed to create calibre library at: %r. Aborting."
|
||||
msgstr ""
|
||||
msgstr "Error en la creación de la biblioteca de calibre en: %r. Cancelando."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:145
|
||||
msgid "Repairing failed"
|
||||
@ -6482,6 +6493,8 @@ msgstr "Reparación fallida"
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:146
|
||||
msgid "The database repair failed. Starting with a new empty library."
|
||||
msgstr ""
|
||||
"La reparación de la base de datos falló. Comenzando con una nueva biblioteca "
|
||||
"vacía."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:150
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:594
|
||||
@ -6490,7 +6503,7 @@ msgstr "Biblioteca de calibre"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:163
|
||||
msgid "Choose a location for your new calibre e-book library"
|
||||
msgstr ""
|
||||
msgstr "Escoja una ubicación para su nueva biblioteca de libros de calibre"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:173
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:206
|
||||
@ -6500,10 +6513,11 @@ msgstr "Ubicación de la base de datos incorrecta"
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:174
|
||||
msgid "Bad database location %r. calibre will now quit."
|
||||
msgstr ""
|
||||
"Ubicación de la base de datos %r errónea. Calibre se cerrará a continuación."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:187
|
||||
msgid "Corrupted database"
|
||||
msgstr ""
|
||||
msgstr "Base de datos corrupta"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:188
|
||||
msgid ""
|
||||
@ -6511,16 +6525,23 @@ msgid ""
|
||||
"and repair it automatically? If you say No, a new empty calibre library will "
|
||||
"be created."
|
||||
msgstr ""
|
||||
"Su base de datos de calibre parece estar corrompida. ¿Quiere que calibre "
|
||||
"intente repararla automáticamente? Si escoge \"No\", se creará una nueva "
|
||||
"biblioteca de calibre vacía"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:194
|
||||
msgid ""
|
||||
"Repairing database. This can take a very long time for a large collection"
|
||||
msgstr ""
|
||||
"Reparando la base de datos. Esto puede requerir mucho tiempo si la colección "
|
||||
"es grande."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:207
|
||||
msgid ""
|
||||
"Bad database location %r. Will start with a new, empty calibre library"
|
||||
msgstr ""
|
||||
"Ubicación de la base de datos %r errónea. Se comenzará con una biblioteca de "
|
||||
"calibre nueva y vacía"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:250
|
||||
msgid "If you are sure it is not running"
|
||||
@ -7950,7 +7971,7 @@ msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:184
|
||||
msgid "E-book Viewer"
|
||||
msgstr ""
|
||||
msgstr "Visor de libros electrónicos"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:185
|
||||
msgid "Close dictionary"
|
||||
@ -8014,7 +8035,7 @@ msgstr "Buscar anterior"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:207
|
||||
msgid "Find previous occurrence"
|
||||
msgstr ""
|
||||
msgstr "Encontrar incidencia anterior"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:208
|
||||
msgid "Shift+F3"
|
||||
@ -8749,19 +8770,19 @@ msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:380
|
||||
msgid "Add an empty book (a book with no formats)"
|
||||
msgstr ""
|
||||
msgstr "Añadir libro en blanco (sin formato)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:382
|
||||
msgid "Set the title of the added empty book"
|
||||
msgstr ""
|
||||
msgstr "Introduzca el título del libro en blanco añadido"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:384
|
||||
msgid "Set the authors of the added empty book"
|
||||
msgstr ""
|
||||
msgstr "Introduzca el autor del libro en blanco añadido"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:386
|
||||
msgid "Set the ISBN of the added empty book"
|
||||
msgstr ""
|
||||
msgstr "Introduzca el ISBN del libro en blanco añadido"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:411
|
||||
msgid "You must specify at least one file to add"
|
||||
@ -9461,7 +9482,7 @@ msgstr "Inglés (Irlanda)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:109
|
||||
msgid "English (China)"
|
||||
msgstr ""
|
||||
msgstr "Ingles (Chino)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:110
|
||||
msgid "Spanish (Paraguay)"
|
||||
|
@ -7,13 +7,13 @@ msgstr ""
|
||||
"Project-Id-Version: calibre 0.4.22\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 07:14+0000\n"
|
||||
"Last-Translator: Kovid Goyal <Unknown>\n"
|
||||
"PO-Revision-Date: 2010-05-24 18:37+0000\n"
|
||||
"Last-Translator: sengian <Unknown>\n"
|
||||
"Language-Team: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-22 03:56+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-25 03:41+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
@ -369,6 +369,7 @@ msgstr ""
|
||||
msgid ""
|
||||
"Intended for the iPad and similar devices with a resolution of 768x1024"
|
||||
msgstr ""
|
||||
"Prévu pour l'iPad et les appareils semblables avec une résolution de 768x1024"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:271
|
||||
msgid "This profile is intended for the Kobo Reader."
|
||||
@ -676,11 +677,11 @@ msgstr "Communiquer avec le lecteur Teclast K3."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:45
|
||||
msgid "Communicate with the Newsmy reader."
|
||||
msgstr ""
|
||||
msgstr "Communiquer avec le lecteur Newsmy"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:60
|
||||
msgid "Communicate with the iPapyrus reader."
|
||||
msgstr ""
|
||||
msgstr "Communiquer avec le lecteur iPapyrus"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:252
|
||||
msgid "Unable to detect the %s disk drive. Try rebooting."
|
||||
@ -914,7 +915,7 @@ msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:286
|
||||
msgid "Maintain picture aspect ratio. Default is to fill the screen."
|
||||
msgstr "Maintient le ratio pour l'image. Par défaut : Plein écran."
|
||||
msgstr "Maintient les proportions de l'image. Par défaut : Plein écran."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:288
|
||||
msgid "Disable sharpening."
|
||||
@ -938,8 +939,8 @@ msgid ""
|
||||
"Keep aspect ratio and scale image using screen height as image width for "
|
||||
"viewing in landscape mode."
|
||||
msgstr ""
|
||||
"Garde la proportion d'image et redimensionne en utilisant la hauteur d'écran "
|
||||
"comme largeur d'image en mode paysage."
|
||||
"Garde les proportions de l'image et la redimensionne en utilisant la hauteur "
|
||||
"de l'écran comme largeur d'image pour une visualisation en mode paysage."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:298
|
||||
msgid ""
|
||||
@ -1463,6 +1464,12 @@ msgid ""
|
||||
"corresponding pair of normal characters. This option will preserve them "
|
||||
"instead."
|
||||
msgstr ""
|
||||
"Conserver les ligatures présentes dans le document d'entrée. Une ligature "
|
||||
"est d'une paire de caractères comme oe, ae et caetera. La plupart des fontes "
|
||||
"par défaut des lecteurs ne supportent pas les ligatures, aussi un rendu "
|
||||
"correct de celles-ci semble improbable sur le lecteur. Par défaut, calibre "
|
||||
"va transformer une ligature en la paire de caractères correspondants. A "
|
||||
"l'opposé, cette option va conserver la ligature."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:428
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:38
|
||||
@ -1631,6 +1638,13 @@ msgid ""
|
||||
"and bottom of the image, but the image will never be distorted. Without this "
|
||||
"option the image may be slightly distorted, but there will be no borders."
|
||||
msgstr ""
|
||||
"Lors de l'utilisation d'une image SVG en couverture, cette option va "
|
||||
"entrainer une mise à l'échelle permettant de couvrir tout l'écran, mais va "
|
||||
"toujours garder les proportions (ratio hauteur/largeur) de l'image "
|
||||
"d'origine. Ceci signifie qu'il peut y avoir des bordures blanches sur les "
|
||||
"cotés, en haut ou en bas de l'image, mais que celle-ci ne sera jamais "
|
||||
"distordue. Sans cette option l'image peut être légèrement distordue, mais il "
|
||||
"n'y aura pas de bordures."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/fb2ml.py:144
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/rb/rbml.py:102
|
||||
@ -2705,9 +2719,9 @@ msgid ""
|
||||
"Custom size of the document. Use the form widthxheight EG. `123x321` to "
|
||||
"specify the width and height. This overrides any specified paper-size."
|
||||
msgstr ""
|
||||
"Taille personnalisée de document. Utiliser le format largeurxhauteur, par "
|
||||
"exemple `123x321` pour spécifier la largeur et la hauteur. Cela écrasera "
|
||||
"n'importe quelle taille de papier spécifiée."
|
||||
"Taille de document personnalisée. Utiliser le format largeur x hauteur, c.-à-"
|
||||
"d. `123x321` pour spécifier la largeur et la hauteur. Ceci outrepassera "
|
||||
"toute taille de papier spécifiée."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:73
|
||||
msgid "The orientation of the page. Default is portrait. Choices are %s"
|
||||
@ -3332,7 +3346,7 @@ msgstr "Pas de couverture &SVG"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:52
|
||||
msgid "Preserve cover &aspect ratio"
|
||||
msgstr ""
|
||||
msgstr "Conserver les &proportions de la couverture"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:53
|
||||
msgid "Split files &larger than:"
|
||||
@ -3406,7 +3420,7 @@ msgstr "Taille de &base de la police:"
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/font_key_ui.py:105
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:123
|
||||
msgid "Font size &key:"
|
||||
msgstr "Taille de la police &key:"
|
||||
msgstr "Taille de la police &clé:"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/font_key_ui.py:106
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/font_key_ui.py:110
|
||||
@ -3453,15 +3467,15 @@ msgstr "Contrôler l'apparence de la sortie"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:30
|
||||
msgid "Original"
|
||||
msgstr ""
|
||||
msgstr "Original"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:31
|
||||
msgid "Left align"
|
||||
msgstr ""
|
||||
msgstr "Aligner à gauche"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:32
|
||||
msgid "Justify text"
|
||||
msgstr ""
|
||||
msgstr "Justifier le texte"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:120
|
||||
msgid "&Disable font size rescaling"
|
||||
@ -3519,7 +3533,7 @@ msgstr "&CSS complémentaire"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:136
|
||||
msgid "&Transliterate unicode characters to ASCII"
|
||||
msgstr ""
|
||||
msgstr "&Translittérer les caractères unicode en représentation ASCII"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:137
|
||||
msgid "Insert &blank line"
|
||||
@ -3527,7 +3541,7 @@ msgstr "Insérer une ligne blanche"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:138
|
||||
msgid "Keep &ligatures"
|
||||
msgstr ""
|
||||
msgstr "Conserver les ligatures"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:19
|
||||
msgid "LRF Output"
|
||||
@ -9534,7 +9548,7 @@ msgstr "Anglais (Irlande)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:109
|
||||
msgid "English (China)"
|
||||
msgstr ""
|
||||
msgstr "Anglais (Chine)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:110
|
||||
msgid "Spanish (Paraguay)"
|
||||
|
@ -8,13 +8,13 @@ msgstr ""
|
||||
"Project-Id-Version: calibre\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 07:23+0000\n"
|
||||
"PO-Revision-Date: 2010-05-22 16:54+0000\n"
|
||||
"Last-Translator: Antón Méixome <meixome@gmail.com>\n"
|
||||
"Language-Team: Galician <gl@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: 2010-05-22 03:56+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-23 03:55+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
|
||||
|
@ -8,13 +8,13 @@ msgstr ""
|
||||
"Project-Id-Version: calibre\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 07:09+0000\n"
|
||||
"Last-Translator: Kovid Goyal <Unknown>\n"
|
||||
"PO-Revision-Date: 2010-05-24 14:04+0000\n"
|
||||
"Last-Translator: Uriel <Unknown>\n"
|
||||
"Language-Team: Hebrew <he@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: 2010-05-22 03:56+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-25 03:41+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
|
||||
@ -257,18 +257,20 @@ msgid ""
|
||||
"If specified, the output plugin will try to create output that is as human "
|
||||
"readable as possible. May not have any effect for some output plugins."
|
||||
msgstr ""
|
||||
"אם מוגדר ,רכיב ההמרה ינסה ליצור קובץ קריא ככל האפשר.עלול לא להשפיע כלל עבור "
|
||||
"רכיבים מסויימים."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:45
|
||||
msgid "Input profile"
|
||||
msgstr ""
|
||||
msgstr "פרופיל קלט"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:49
|
||||
msgid ""
|
||||
"This profile tries to provide sane defaults and is useful if you know "
|
||||
"nothing about the input document."
|
||||
msgstr ""
|
||||
"פרופיל זה מנסה להגדיר הגדרות תקינות והוא יעיל עם אינך יודע דבר אודות מקור "
|
||||
"המסמך."
|
||||
"פרופיל זה מנסה להגדיר ברירות מחדל סבירות והוא יעיל אם אינך יודע דבר אודות "
|
||||
"מקור המסמך."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:57
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:258
|
||||
@ -333,7 +335,7 @@ msgstr "פרופיל זה מיועד עבור IRex Digital Reader 1000."
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:194
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:452
|
||||
msgid "This profile is intended for the IRex Digital Reader 800."
|
||||
msgstr ""
|
||||
msgstr "פרופיל זה מיועד עבור IRex Digital Reader 800"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:206
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:466
|
||||
@ -342,7 +344,7 @@ msgstr "פרופיל זה מיועד עבור B&N Nook."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:226
|
||||
msgid "Output profile"
|
||||
msgstr ""
|
||||
msgstr "פרופיל פלט"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:230
|
||||
msgid ""
|
||||
@ -350,17 +352,17 @@ msgid ""
|
||||
"produce a document intended to be read at a computer or on a range of "
|
||||
"devices."
|
||||
msgstr ""
|
||||
"פרופיל זה מנסה לבצע המרה תקינה ויעיל במידה ואתה רוצה להפיק מסמך שנועד להקרא "
|
||||
"במחשב או על מגוון מכשירים."
|
||||
"פרופיל זה מנסה לת ברירות מחדל סבירות והוא יעיל במידה ואתה רוצה להפיק מסמך "
|
||||
"שנועד להקרא במחשב או על מגוון מכשירים."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:248
|
||||
msgid ""
|
||||
"Intended for the iPad and similar devices with a resolution of 768x1024"
|
||||
msgstr ""
|
||||
msgstr "מיועד ל-iPad ומכשירים דומים עם רזולוציה של 768x1024"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:271
|
||||
msgid "This profile is intended for the Kobo Reader."
|
||||
msgstr ""
|
||||
msgstr "פרופיל זה מיועד ל-Kobo Reader"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:283
|
||||
msgid "This profile is intended for the SONY PRS-300."
|
||||
@ -478,11 +480,11 @@ msgstr "מחליף נתונים עם EB600 eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/edge/driver.py:17
|
||||
msgid "Entourage Edge"
|
||||
msgstr ""
|
||||
msgstr "Entourage Edge"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/edge/driver.py:18
|
||||
msgid "Communicate with the Entourage Edge."
|
||||
msgstr ""
|
||||
msgstr "מתקשר עם מכשיר Entourage Edge"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/eslick/driver.py:16
|
||||
msgid "Communicate with the ESlick eBook reader."
|
||||
@ -506,11 +508,11 @@ msgstr "מחליף נתונים עם Hanvon N520 eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:41
|
||||
msgid "Communicate with the SpringDesign Alex eBook reader."
|
||||
msgstr ""
|
||||
msgstr "מתקשר עם SpringDesign Alex eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:57
|
||||
msgid "Communicate with the Azbooka"
|
||||
msgstr ""
|
||||
msgstr "מתקשר עם מכשיר Azbooka"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:70
|
||||
msgid "Communicate with the Elonex EB 511 eBook reader."
|
||||
@ -528,11 +530,11 @@ msgstr "John Schember"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/interface.py:23
|
||||
msgid "Device Interface"
|
||||
msgstr "מימשק המכשיר"
|
||||
msgstr "ממשק המכשיר"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/irexdr/driver.py:16
|
||||
msgid "Communicate with the IRex Digital Reader 1000 eBook reader."
|
||||
msgstr ""
|
||||
msgstr "מתקשר עם - IRex Digital Reader 1000 eBook reader"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/irexdr/driver.py:42
|
||||
msgid "Communicate with the IRex Digital Reader 800"
|
||||
@ -580,15 +582,15 @@ msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:20
|
||||
msgid "The Nook"
|
||||
msgstr ""
|
||||
msgstr "מכשיר ה-Nook"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:21
|
||||
msgid "Communicate with the Nook eBook reader."
|
||||
msgstr ""
|
||||
msgstr "מתקשר עם Nook eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/nuut2/driver.py:17
|
||||
msgid "Communicate with the Nuut2 eBook reader."
|
||||
msgstr ""
|
||||
msgstr "מתקשר עם Nuut2 eBook reader"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs500/driver.py:89
|
||||
msgid "Communicate with the Sony PRS-500 eBook reader."
|
||||
@ -606,11 +608,11 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:80
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:89
|
||||
msgid "Getting list of books on device..."
|
||||
msgstr ""
|
||||
msgstr "קורא את רשימת הספרים מההתקן..."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:26
|
||||
msgid "Communicate with the Sony PRS-300/505/500 eBook reader."
|
||||
msgstr ""
|
||||
msgstr "מתקשר עם Sony PRS-300/505/500 eBook reader."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:58
|
||||
msgid ""
|
||||
@ -681,7 +683,7 @@ msgstr "לא מצליח למצוא את כונן %s. המעבד מיצא גרס
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:603
|
||||
msgid "Unable to mount main memory (Error code: %d)"
|
||||
msgstr ""
|
||||
msgstr "לא מצליח להעלות זכרון ראשי (קוד שגיאה: %d)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:740
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:742
|
||||
@ -717,11 +719,11 @@ msgstr "קבע תצורת מכשיר"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/deviceconfig.py:28
|
||||
msgid "settings for device drivers"
|
||||
msgstr ""
|
||||
msgstr "הגדרות דרייברים למכשיר."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/deviceconfig.py:30
|
||||
msgid "Ordered list of formats the device will accept"
|
||||
msgstr "רשימת סוגי קבצים שהמכשיר יקבל."
|
||||
msgstr "רשימת סוגי קבצים שהמכשיר מסוגל לקבל."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/deviceconfig.py:32
|
||||
msgid "Place files in sub directories if the device supports them"
|
||||
@ -743,7 +745,7 @@ msgstr "הגדרות נוספות"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:28
|
||||
msgid "Communicate with an eBook reader."
|
||||
msgstr ""
|
||||
msgstr "מחליף נתונים עם eBook reader"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:36
|
||||
msgid "Get device information..."
|
||||
@ -765,17 +767,17 @@ msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:42
|
||||
msgid "Output directory. Defaults to current directory"
|
||||
msgstr ""
|
||||
msgstr "תיקיה לפלט (ברירת מחדל - תיקייה נוכחית)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:45
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:589
|
||||
msgid "Set the book title"
|
||||
msgstr "כתוב את כותרת הספר"
|
||||
msgstr "קבע את כותרת הספר"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:47
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:591
|
||||
msgid "Set sort key for the title"
|
||||
msgstr ""
|
||||
msgstr "הגדר מפתח למיון הכותרות"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:49
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:593
|
||||
@ -785,7 +787,7 @@ msgstr "כתוב את שם ההמחבר"
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:51
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:595
|
||||
msgid "Set sort key for the author"
|
||||
msgstr "הגדר מקש מיון למחבר"
|
||||
msgstr "הגדר מפתח מיון למחבר"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:53
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:597
|
||||
@ -795,7 +797,7 @@ msgstr "הקטגוריה אליה הספר שייך. לדוגמה : היסטור
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:56
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:600
|
||||
msgid "Path to a graphic that will be set as this files' thumbnail"
|
||||
msgstr "חלק מהתמונה שישמר כתמונה ממוזערת"
|
||||
msgstr "כתובת לקובץ גראפי שישמש כתמונה מוקטנת"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:59
|
||||
msgid "Path to a txt file containing a comment."
|
||||
@ -832,6 +834,7 @@ msgid ""
|
||||
"Extract cover from LRF file. Note that the LRF format has no defined cover, "
|
||||
"so we use some heuristics to guess the cover."
|
||||
msgstr ""
|
||||
"חלץ את העטיפה מתוך קובץ ה-LRF. (משתמש בכללי אצבע לקביעת תמונת העטיפה)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/reader.py:70
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:615
|
||||
@ -848,7 +851,7 @@ msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:204
|
||||
msgid "Failed %s"
|
||||
msgstr ""
|
||||
msgstr "%s נכשל"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:261
|
||||
msgid ""
|
||||
@ -863,29 +866,31 @@ msgid ""
|
||||
"of less than 256 may result in blurred text on your device if you are "
|
||||
"creating your comics in EPUB format."
|
||||
msgstr ""
|
||||
"מספר גווני אפור להמרת התמונה. ברירת מחדל: %default. ערכים קטנים מ-256 עלולים "
|
||||
"לגרום למריחה בקומיקס בפורמט EPUB."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:283
|
||||
msgid ""
|
||||
"Disable normalize (improve contrast) color range for pictures. Default: False"
|
||||
msgstr ""
|
||||
msgstr "בטל נורמליזציה של תחום הצבעים (שיפור ניגודיות). ברירת מחדל: לא"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:286
|
||||
msgid "Maintain picture aspect ratio. Default is to fill the screen."
|
||||
msgstr ""
|
||||
msgstr "שמור על יחסי מידות התמונה. ברירת מחדל: מילוי המסך."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:288
|
||||
msgid "Disable sharpening."
|
||||
msgstr ""
|
||||
msgstr "בטל חידוד התמונה."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:290
|
||||
msgid ""
|
||||
"Disable trimming of comic pages. For some comics, trimming might remove "
|
||||
"content as well as borders."
|
||||
msgstr ""
|
||||
msgstr "ביטול של קיצוץ קצות עמודי קומיקס. עלול לקצוץ תוכן מהתמונה."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:293
|
||||
msgid "Don't split landscape images into two portrait images"
|
||||
msgstr ""
|
||||
msgstr "אל תפצל תמונת \"נוף\" לשתי תמונות \"פורטרט\""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:295
|
||||
msgid ""
|
||||
@ -897,19 +902,19 @@ msgstr ""
|
||||
msgid ""
|
||||
"Used for right-to-left publications like manga. Causes landscape pages to be "
|
||||
"split into portrait pages from right to left."
|
||||
msgstr ""
|
||||
msgstr "פיצול של תמונה לשתי תמונות מימין לשמאל."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:302
|
||||
msgid ""
|
||||
"Enable Despeckle. Reduces speckle noise. May greatly increase processing "
|
||||
"time."
|
||||
msgstr ""
|
||||
msgstr "אפשר הורדת רעש בתמונה. עלול להגדיל בהרבה את זמן העיבוד."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:305
|
||||
msgid ""
|
||||
"Don't sort the files found in the comic alphabetically by name. Instead use "
|
||||
"the order they were added to the comic."
|
||||
msgstr ""
|
||||
msgstr "מיין תמונות קומיקס לפי סדר ההוספה שלהם ולא לפי שם."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:309
|
||||
msgid ""
|
||||
@ -917,14 +922,15 @@ msgid ""
|
||||
"experiment to see which format gives you optimal size and look on your "
|
||||
"device."
|
||||
msgstr ""
|
||||
"הפורמט אליו יומרו התמונות ב-eBook. ניתן לבדוק פורמטים שונים לתוצאה אופטימלית."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:313
|
||||
msgid "Apply no processing to the image"
|
||||
msgstr ""
|
||||
msgstr "אל תפעיל עיבוד על התמונה."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:315
|
||||
msgid "Do not convert the image to grayscale (black and white)"
|
||||
msgstr ""
|
||||
msgstr "לא להמיר לגווני אפור (המרה לשחור לבן)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:452
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:463
|
||||
@ -8648,13 +8654,14 @@ msgstr ""
|
||||
msgid ""
|
||||
"Minimum interval in seconds between consecutive fetches. Default is %default "
|
||||
"s"
|
||||
msgstr ""
|
||||
msgstr "פרק הזמן בין הורדות. ברירת המחדל היא %default שניות."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:487
|
||||
msgid ""
|
||||
"The character encoding for the websites you are trying to download. The "
|
||||
"default is to try and guess the encoding."
|
||||
msgstr ""
|
||||
"קידוד האותיות של האתר להורדה. ברירת המחדל תנסה לנחש את הקידוד המתאים."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:489
|
||||
msgid ""
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,13 +8,13 @@ msgstr ""
|
||||
"Project-Id-Version: calibre\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 07:28+0000\n"
|
||||
"PO-Revision-Date: 2010-05-22 16:59+0000\n"
|
||||
"Last-Translator: Kovid Goyal <Unknown>\n"
|
||||
"Language-Team: Latvian <ivars_a@inbox.lv>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-22 03:57+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-23 03:55+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
"X-Poedit-Country: LATVIA\n"
|
||||
"X-Poedit-Language: Latvian\n"
|
||||
|
@ -8,13 +8,13 @@ msgstr ""
|
||||
"Project-Id-Version: calibre\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 07:32+0000\n"
|
||||
"PO-Revision-Date: 2010-05-22 16:56+0000\n"
|
||||
"Last-Translator: Bartosz Kaszubowski <gosimek@gmail.com>\n"
|
||||
"Language-Team: Polish <pl@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: 2010-05-22 03:57+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-23 03:55+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
|
||||
|
@ -7,13 +7,13 @@ msgstr ""
|
||||
"Project-Id-Version: calibre 0.4.55\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 07:21+0000\n"
|
||||
"PO-Revision-Date: 2010-05-22 17:13+0000\n"
|
||||
"Last-Translator: Ilya Telegin <devi29rus@gmail.com>\n"
|
||||
"Language-Team: American English <kde-i18n-doc@lists.kde.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-22 03:58+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-23 03:56+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
"X-Poedit-Country: RUSSIAN FEDERATION\n"
|
||||
"X-Poedit-Language: Russian\n"
|
||||
|
@ -8,13 +8,13 @@ msgstr ""
|
||||
"Project-Id-Version: calibre\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 07:33+0000\n"
|
||||
"PO-Revision-Date: 2010-05-22 16:52+0000\n"
|
||||
"Last-Translator: Besnik <besnik@programeshqip.org>\n"
|
||||
"Language-Team: Albanian <sq@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: 2010-05-22 03:55+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-23 03:54+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
|
||||
|
@ -8,13 +8,13 @@ msgstr ""
|
||||
"Project-Id-Version: calibre\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 07:13+0000\n"
|
||||
"Last-Translator: Kovid Goyal <Unknown>\n"
|
||||
"PO-Revision-Date: 2010-05-22 09:40+0000\n"
|
||||
"Last-Translator: Vladimir Oka <Unknown>\n"
|
||||
"Language-Team: Serbian <sr@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: 2010-05-22 03:58+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-23 03:56+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
|
||||
@ -366,7 +366,7 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:248
|
||||
msgid ""
|
||||
"Intended for the iPad and similar devices with a resolution of 768x1024"
|
||||
msgstr ""
|
||||
msgstr "Namenjeno za iPad i slične uređaje sa rezolucijom 768x1024"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:271
|
||||
msgid "This profile is intended for the Kobo Reader."
|
||||
@ -673,11 +673,11 @@ msgstr "Uspostavi komunikaciju s Teclast K3 čitačem."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:45
|
||||
msgid "Communicate with the Newsmy reader."
|
||||
msgstr ""
|
||||
msgstr "Uspostavi komunikaciju s Newsmy čitačem"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:60
|
||||
msgid "Communicate with the iPapyrus reader."
|
||||
msgstr ""
|
||||
msgstr "Uspostavi komunikaciju s iPapyrus čitačem"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:252
|
||||
msgid "Unable to detect the %s disk drive. Try rebooting."
|
||||
@ -1437,6 +1437,11 @@ msgid ""
|
||||
"corresponding pair of normal characters. This option will preserve them "
|
||||
"instead."
|
||||
msgstr ""
|
||||
"Sačuvaj ligature u ulaznom dokumentu. Ligatura je poseban način za "
|
||||
"prikazivanje parova slova kao što su ff, fi, fl, itd. Većina čitača ne "
|
||||
"podržava ligature u podrazumevanoj vrsti slova i malo je verovatno da će ih "
|
||||
"ispravno prikazati. U podrazumevanom stanju calibre će pretvoriti ligature u "
|
||||
"parove običnih slova. Ova opcija će ih sačuvati nepromenjene."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:428
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:38
|
||||
@ -1601,6 +1606,11 @@ msgid ""
|
||||
"and bottom of the image, but the image will never be distorted. Without this "
|
||||
"option the image may be slightly distorted, but there will be no borders."
|
||||
msgstr ""
|
||||
"Kada se koristi SVG omot ova opcija će mu promeniti veličinu srazmerno "
|
||||
"raspoloživoj veličini ekrana, čuvajući originalne razmere (odnos širine i "
|
||||
"visine). Ovo znači da će se možda pojaviti beline u vrhu, ili na dnu strane, "
|
||||
"ali slika neće biti izobličena. Bez ove opcije slika može biti delimično "
|
||||
"izibličena, ali neće biti belina po ivicama."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/fb2ml.py:144
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/rb/rbml.py:102
|
||||
@ -3264,7 +3274,7 @@ msgstr "Bez &SVG omota"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:52
|
||||
msgid "Preserve cover &aspect ratio"
|
||||
msgstr ""
|
||||
msgstr "Sačuvaj r&azmere omota"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:53
|
||||
msgid "Split files &larger than:"
|
||||
@ -3384,15 +3394,15 @@ msgstr "Kontrola izgleda izlaznog dokumenta"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:30
|
||||
msgid "Original"
|
||||
msgstr ""
|
||||
msgstr "Originalno"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:31
|
||||
msgid "Left align"
|
||||
msgstr ""
|
||||
msgstr "Levo poravnanje"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:32
|
||||
msgid "Justify text"
|
||||
msgstr ""
|
||||
msgstr "Poravnanje sa obe strane"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:120
|
||||
msgid "&Disable font size rescaling"
|
||||
@ -3451,7 +3461,7 @@ msgstr "Dodatni &CSS"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:136
|
||||
msgid "&Transliterate unicode characters to ASCII"
|
||||
msgstr ""
|
||||
msgstr "&Prevedi UNICODE znake u ASCII"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:137
|
||||
msgid "Insert &blank line"
|
||||
@ -3459,7 +3469,7 @@ msgstr "Ubaci &prazan red"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:138
|
||||
msgid "Keep &ligatures"
|
||||
msgstr ""
|
||||
msgstr "Sačuvaj &ligature"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:19
|
||||
msgid "LRF Output"
|
||||
@ -9362,7 +9372,7 @@ msgstr "Engleski (Irska)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:109
|
||||
msgid "English (China)"
|
||||
msgstr ""
|
||||
msgstr "Engleski (Kina)"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:110
|
||||
msgid "Spanish (Paraguay)"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,13 +8,13 @@ msgstr ""
|
||||
"Project-Id-Version: calibre\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 13:18+0000\n"
|
||||
"PO-Revision-Date: 2010-05-22 17:13+0000\n"
|
||||
"Last-Translator: Thruth Wang <wanglihao@gmail.com>\n"
|
||||
"Language-Team: Simplified Chinese <wanglihao@gmail.com>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-22 03:59+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-23 03:56+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
"X-Poedit-Country: CHINA\n"
|
||||
"X-Poedit-Language: Chinese\n"
|
||||
|
@ -8,13 +8,13 @@ msgstr ""
|
||||
"Project-Id-Version: calibre\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2010-05-21 22:47+0000\n"
|
||||
"PO-Revision-Date: 2010-05-21 07:16+0000\n"
|
||||
"PO-Revision-Date: 2010-05-22 16:49+0000\n"
|
||||
"Last-Translator: Chao-Hsiung Liao <j_h_liau@yahoo.com.tw>\n"
|
||||
"Language-Team: Chinese (traditional)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-22 03:58+0000\n"
|
||||
"X-Launchpad-Export-Date: 2010-05-23 03:56+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
"Language: zh_TW\n"
|
||||
|
||||
|
@ -1,28 +0,0 @@
|
||||
Copyright © 2006-2007 Edgewall Software[[BR]]
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
3. The name of the author may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR “AS IS” AND ANY EXPRESS
|
||||
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -1,26 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2007 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""This package provides various means for generating and processing web markup
|
||||
(XML or HTML).
|
||||
|
||||
The design is centered around the concept of streams of markup events (similar
|
||||
in concept to SAX parsing events) which can be processed in a uniform manner
|
||||
independently of where or how they are produced.
|
||||
"""
|
||||
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__version__ = '0.5.0'
|
||||
|
||||
from calibre.utils.genshi.core import *
|
||||
from calibre.utils.genshi.input import ParseError, XML, HTML
|
@ -1,362 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Support for programmatically generating markup streams from Python code using
|
||||
a very simple syntax. The main entry point to this module is the `tag` object
|
||||
(which is actually an instance of the ``ElementFactory`` class). You should
|
||||
rarely (if ever) need to directly import and use any of the other classes in
|
||||
this module.
|
||||
|
||||
Elements can be created using the `tag` object using attribute access. For
|
||||
example:
|
||||
|
||||
>>> doc = tag.p('Some text and ', tag.a('a link', href='http://example.org/'), '.')
|
||||
>>> doc
|
||||
<Element "p">
|
||||
|
||||
This produces an `Element` instance which can be further modified to add child
|
||||
nodes and attributes. This is done by "calling" the element: positional
|
||||
arguments are added as child nodes (alternatively, the `Element.append` method
|
||||
can be used for that purpose), whereas keywords arguments are added as
|
||||
attributes:
|
||||
|
||||
>>> doc(tag.br)
|
||||
<Element "p">
|
||||
>>> print doc
|
||||
<p>Some text and <a href="http://example.org/">a link</a>.<br/></p>
|
||||
|
||||
If an attribute name collides with a Python keyword, simply append an underscore
|
||||
to the name:
|
||||
|
||||
>>> doc(class_='intro')
|
||||
<Element "p">
|
||||
>>> print doc
|
||||
<p class="intro">Some text and <a href="http://example.org/">a link</a>.<br/></p>
|
||||
|
||||
As shown above, an `Element` can easily be directly rendered to XML text by
|
||||
printing it or using the Python ``str()`` function. This is basically a
|
||||
shortcut for converting the `Element` to a stream and serializing that
|
||||
stream:
|
||||
|
||||
>>> stream = doc.generate()
|
||||
>>> stream #doctest: +ELLIPSIS
|
||||
<genshi.core.Stream object at ...>
|
||||
>>> print stream
|
||||
<p class="intro">Some text and <a href="http://example.org/">a link</a>.<br/></p>
|
||||
|
||||
|
||||
The `tag` object also allows creating "fragments", which are basically lists
|
||||
of nodes (elements or text) that don't have a parent element. This can be useful
|
||||
for creating snippets of markup that are attached to a parent element later (for
|
||||
example in a template). Fragments are created by calling the `tag` object, which
|
||||
returns an object of type `Fragment`:
|
||||
|
||||
>>> fragment = tag('Hello, ', tag.em('world'), '!')
|
||||
>>> fragment
|
||||
<Fragment>
|
||||
>>> print fragment
|
||||
Hello, <em>world</em>!
|
||||
"""
|
||||
|
||||
try:
|
||||
set
|
||||
except NameError:
|
||||
from sets import Set as set
|
||||
|
||||
from calibre.utils.genshi.core import Attrs, Markup, Namespace, QName, Stream, \
|
||||
START, END, TEXT
|
||||
|
||||
__all__ = ['Fragment', 'Element', 'ElementFactory', 'tag']
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
class Fragment(object):
|
||||
"""Represents a markup fragment, which is basically just a list of element
|
||||
or text nodes.
|
||||
"""
|
||||
__slots__ = ['children']
|
||||
|
||||
def __init__(self):
|
||||
"""Create a new fragment."""
|
||||
self.children = []
|
||||
|
||||
def __add__(self, other):
|
||||
return Fragment()(self, other)
|
||||
|
||||
def __call__(self, *args):
|
||||
"""Append any positional arguments as child nodes.
|
||||
|
||||
:see: `append`
|
||||
"""
|
||||
map(self.append, args)
|
||||
return self
|
||||
|
||||
def __iter__(self):
|
||||
return self._generate()
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s>' % self.__class__.__name__
|
||||
|
||||
def __str__(self):
|
||||
return str(self.generate())
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.generate())
|
||||
|
||||
def __html__(self):
|
||||
return Markup(self.generate())
|
||||
|
||||
def append(self, node):
|
||||
"""Append an element or string as child node.
|
||||
|
||||
:param node: the node to append; can be an `Element`, `Fragment`, or a
|
||||
`Stream`, or a Python string or number
|
||||
"""
|
||||
if isinstance(node, (Stream, Element, basestring, int, float, long)):
|
||||
# For objects of a known/primitive type, we avoid the check for
|
||||
# whether it is iterable for better performance
|
||||
self.children.append(node)
|
||||
elif isinstance(node, Fragment):
|
||||
self.children.extend(node.children)
|
||||
elif node is not None:
|
||||
try:
|
||||
map(self.append, iter(node))
|
||||
except TypeError:
|
||||
self.children.append(node)
|
||||
|
||||
def _generate(self):
|
||||
for child in self.children:
|
||||
if isinstance(child, Fragment):
|
||||
for event in child._generate():
|
||||
yield event
|
||||
elif isinstance(child, Stream):
|
||||
for event in child:
|
||||
yield event
|
||||
else:
|
||||
if not isinstance(child, basestring):
|
||||
child = unicode(child)
|
||||
yield TEXT, child, (None, -1, -1)
|
||||
|
||||
def generate(self):
|
||||
"""Return a markup event stream for the fragment.
|
||||
|
||||
:rtype: `Stream`
|
||||
"""
|
||||
return Stream(self._generate())
|
||||
|
||||
|
||||
def _kwargs_to_attrs(kwargs):
|
||||
attrs = []
|
||||
names = set()
|
||||
for name, value in kwargs.items():
|
||||
name = name.rstrip('_').replace('_', '-')
|
||||
if value is not None and name not in names:
|
||||
attrs.append((QName(name), unicode(value)))
|
||||
names.add(name)
|
||||
return Attrs(attrs)
|
||||
|
||||
|
||||
class Element(Fragment):
|
||||
"""Simple XML output generator based on the builder pattern.
|
||||
|
||||
Construct XML elements by passing the tag name to the constructor:
|
||||
|
||||
>>> print Element('strong')
|
||||
<strong/>
|
||||
|
||||
Attributes can be specified using keyword arguments. The values of the
|
||||
arguments will be converted to strings and any special XML characters
|
||||
escaped:
|
||||
|
||||
>>> print Element('textarea', rows=10, cols=60)
|
||||
<textarea rows="10" cols="60"/>
|
||||
>>> print Element('span', title='1 < 2')
|
||||
<span title="1 < 2"/>
|
||||
>>> print Element('span', title='"baz"')
|
||||
<span title=""baz""/>
|
||||
|
||||
The " character is escaped using a numerical entity.
|
||||
The order in which attributes are rendered is undefined.
|
||||
|
||||
If an attribute value evaluates to `None`, that attribute is not included
|
||||
in the output:
|
||||
|
||||
>>> print Element('a', name=None)
|
||||
<a/>
|
||||
|
||||
Attribute names that conflict with Python keywords can be specified by
|
||||
appending an underscore:
|
||||
|
||||
>>> print Element('div', class_='warning')
|
||||
<div class="warning"/>
|
||||
|
||||
Nested elements can be added to an element using item access notation.
|
||||
The call notation can also be used for this and for adding attributes
|
||||
using keyword arguments, as one would do in the constructor.
|
||||
|
||||
>>> print Element('ul')(Element('li'), Element('li'))
|
||||
<ul><li/><li/></ul>
|
||||
>>> print Element('a')('Label')
|
||||
<a>Label</a>
|
||||
>>> print Element('a')('Label', href="target")
|
||||
<a href="target">Label</a>
|
||||
|
||||
Text nodes can be nested in an element by adding strings instead of
|
||||
elements. Any special characters in the strings are escaped automatically:
|
||||
|
||||
>>> print Element('em')('Hello world')
|
||||
<em>Hello world</em>
|
||||
>>> print Element('em')(42)
|
||||
<em>42</em>
|
||||
>>> print Element('em')('1 < 2')
|
||||
<em>1 < 2</em>
|
||||
|
||||
This technique also allows mixed content:
|
||||
|
||||
>>> print Element('p')('Hello ', Element('b')('world'))
|
||||
<p>Hello <b>world</b></p>
|
||||
|
||||
Quotes are not escaped inside text nodes:
|
||||
>>> print Element('p')('"Hello"')
|
||||
<p>"Hello"</p>
|
||||
|
||||
Elements can also be combined with other elements or strings using the
|
||||
addition operator, which results in a `Fragment` object that contains the
|
||||
operands:
|
||||
|
||||
>>> print Element('br') + 'some text' + Element('br')
|
||||
<br/>some text<br/>
|
||||
|
||||
Elements with a namespace can be generated using the `Namespace` and/or
|
||||
`QName` classes:
|
||||
|
||||
>>> from genshi.core import Namespace
|
||||
>>> xhtml = Namespace('http://www.w3.org/1999/xhtml')
|
||||
>>> print Element(xhtml.html, lang='en')
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en"/>
|
||||
"""
|
||||
__slots__ = ['tag', 'attrib']
|
||||
|
||||
def __init__(self, tag_, **attrib):
|
||||
Fragment.__init__(self)
|
||||
self.tag = QName(tag_)
|
||||
self.attrib = _kwargs_to_attrs(attrib)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""Append any positional arguments as child nodes, and keyword arguments
|
||||
as attributes.
|
||||
|
||||
:return: the element itself so that calls can be chained
|
||||
:rtype: `Element`
|
||||
:see: `Fragment.append`
|
||||
"""
|
||||
self.attrib |= _kwargs_to_attrs(kwargs)
|
||||
Fragment.__call__(self, *args)
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s "%s">' % (self.__class__.__name__, self.tag)
|
||||
|
||||
def _generate(self):
|
||||
yield START, (self.tag, self.attrib), (None, -1, -1)
|
||||
for kind, data, pos in Fragment._generate(self):
|
||||
yield kind, data, pos
|
||||
yield END, self.tag, (None, -1, -1)
|
||||
|
||||
def generate(self):
|
||||
"""Return a markup event stream for the fragment.
|
||||
|
||||
:rtype: `Stream`
|
||||
"""
|
||||
return Stream(self._generate())
|
||||
|
||||
|
||||
class ElementFactory(object):
|
||||
"""Factory for `Element` objects.
|
||||
|
||||
A new element is created simply by accessing a correspondingly named
|
||||
attribute of the factory object:
|
||||
|
||||
>>> factory = ElementFactory()
|
||||
>>> print factory.foo
|
||||
<foo/>
|
||||
>>> print factory.foo(id=2)
|
||||
<foo id="2"/>
|
||||
|
||||
Markup fragments (lists of nodes without a parent element) can be created
|
||||
by calling the factory:
|
||||
|
||||
>>> print factory('Hello, ', factory.em('world'), '!')
|
||||
Hello, <em>world</em>!
|
||||
|
||||
A factory can also be bound to a specific namespace:
|
||||
|
||||
>>> factory = ElementFactory('http://www.w3.org/1999/xhtml')
|
||||
>>> print factory.html(lang="en")
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en"/>
|
||||
|
||||
The namespace for a specific element can be altered on an existing factory
|
||||
by specifying the new namespace using item access:
|
||||
|
||||
>>> factory = ElementFactory()
|
||||
>>> print factory.html(factory['http://www.w3.org/2000/svg'].g(id=3))
|
||||
<html><g xmlns="http://www.w3.org/2000/svg" id="3"/></html>
|
||||
|
||||
Usually, the `ElementFactory` class is not be used directly. Rather, the
|
||||
`tag` instance should be used to create elements.
|
||||
"""
|
||||
|
||||
def __init__(self, namespace=None):
|
||||
"""Create the factory, optionally bound to the given namespace.
|
||||
|
||||
:param namespace: the namespace URI for any created elements, or `None`
|
||||
for no namespace
|
||||
"""
|
||||
if namespace and not isinstance(namespace, Namespace):
|
||||
namespace = Namespace(namespace)
|
||||
self.namespace = namespace
|
||||
|
||||
def __call__(self, *args):
|
||||
"""Create a fragment that has the given positional arguments as child
|
||||
nodes.
|
||||
|
||||
:return: the created `Fragment`
|
||||
:rtype: `Fragment`
|
||||
"""
|
||||
return Fragment()(*args)
|
||||
|
||||
def __getitem__(self, namespace):
|
||||
"""Return a new factory that is bound to the specified namespace.
|
||||
|
||||
:param namespace: the namespace URI or `Namespace` object
|
||||
:return: an `ElementFactory` that produces elements bound to the given
|
||||
namespace
|
||||
:rtype: `ElementFactory`
|
||||
"""
|
||||
return ElementFactory(namespace)
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Create an `Element` with the given name.
|
||||
|
||||
:param name: the tag name of the element to create
|
||||
:return: an `Element` with the specified name
|
||||
:rtype: `Element`
|
||||
"""
|
||||
return Element(self.namespace and self.namespace[name] or name)
|
||||
|
||||
|
||||
tag = ElementFactory()
|
||||
"""Global `ElementFactory` bound to the default namespace.
|
||||
|
||||
:type: `ElementFactory`
|
||||
"""
|
@ -1,707 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Core classes for markup processing."""
|
||||
|
||||
from itertools import chain
|
||||
import operator
|
||||
|
||||
from calibre.utils.genshi.util import plaintext, stripentities, striptags
|
||||
|
||||
__all__ = ['Stream', 'Markup', 'escape', 'unescape', 'Attrs', 'Namespace',
|
||||
'QName']
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
class StreamEventKind(str):
|
||||
"""A kind of event on a markup stream."""
|
||||
__slots__ = []
|
||||
_instances = {}
|
||||
|
||||
def __new__(cls, val):
|
||||
return cls._instances.setdefault(val, str.__new__(cls, val))
|
||||
|
||||
|
||||
class Stream(object):
|
||||
"""Represents a stream of markup events.
|
||||
|
||||
This class is basically an iterator over the events.
|
||||
|
||||
Stream events are tuples of the form::
|
||||
|
||||
(kind, data, position)
|
||||
|
||||
where ``kind`` is the event kind (such as `START`, `END`, `TEXT`, etc),
|
||||
``data`` depends on the kind of event, and ``position`` is a
|
||||
``(filename, line, offset)`` tuple that contains the location of the
|
||||
original element or text in the input. If the original location is unknown,
|
||||
``position`` is ``(None, -1, -1)``.
|
||||
|
||||
Also provided are ways to serialize the stream to text. The `serialize()`
|
||||
method will return an iterator over generated strings, while `render()`
|
||||
returns the complete generated text at once. Both accept various parameters
|
||||
that impact the way the stream is serialized.
|
||||
"""
|
||||
__slots__ = ['events', 'serializer']
|
||||
|
||||
START = StreamEventKind('START') #: a start tag
|
||||
END = StreamEventKind('END') #: an end tag
|
||||
TEXT = StreamEventKind('TEXT') #: literal text
|
||||
XML_DECL = StreamEventKind('XML_DECL') #: XML declaration
|
||||
DOCTYPE = StreamEventKind('DOCTYPE') #: doctype declaration
|
||||
START_NS = StreamEventKind('START_NS') #: start namespace mapping
|
||||
END_NS = StreamEventKind('END_NS') #: end namespace mapping
|
||||
START_CDATA = StreamEventKind('START_CDATA') #: start CDATA section
|
||||
END_CDATA = StreamEventKind('END_CDATA') #: end CDATA section
|
||||
PI = StreamEventKind('PI') #: processing instruction
|
||||
COMMENT = StreamEventKind('COMMENT') #: comment
|
||||
|
||||
def __init__(self, events, serializer=None):
|
||||
"""Initialize the stream with a sequence of markup events.
|
||||
|
||||
:param events: a sequence or iterable providing the events
|
||||
:param serializer: the default serialization method to use for this
|
||||
stream
|
||||
|
||||
:note: Changed in 0.5: added the `serializer` argument
|
||||
"""
|
||||
self.events = events #: The underlying iterable producing the events
|
||||
self.serializer = serializer #: The default serializion method
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.events)
|
||||
|
||||
def __or__(self, function):
|
||||
"""Override the "bitwise or" operator to apply filters or serializers
|
||||
to the stream, providing a syntax similar to pipes on Unix shells.
|
||||
|
||||
Assume the following stream produced by the `HTML` function:
|
||||
|
||||
>>> from genshi.input import HTML
|
||||
>>> html = HTML('''<p onclick="alert('Whoa')">Hello, world!</p>''')
|
||||
>>> print html
|
||||
<p onclick="alert('Whoa')">Hello, world!</p>
|
||||
|
||||
A filter such as the HTML sanitizer can be applied to that stream using
|
||||
the pipe notation as follows:
|
||||
|
||||
>>> from genshi.filters import HTMLSanitizer
|
||||
>>> sanitizer = HTMLSanitizer()
|
||||
>>> print html | sanitizer
|
||||
<p>Hello, world!</p>
|
||||
|
||||
Filters can be any function that accepts and produces a stream (where
|
||||
a stream is anything that iterates over events):
|
||||
|
||||
>>> def uppercase(stream):
|
||||
... for kind, data, pos in stream:
|
||||
... if kind is TEXT:
|
||||
... data = data.upper()
|
||||
... yield kind, data, pos
|
||||
>>> print html | sanitizer | uppercase
|
||||
<p>HELLO, WORLD!</p>
|
||||
|
||||
Serializers can also be used with this notation:
|
||||
|
||||
>>> from genshi.output import TextSerializer
|
||||
>>> output = TextSerializer()
|
||||
>>> print html | sanitizer | uppercase | output
|
||||
HELLO, WORLD!
|
||||
|
||||
Commonly, serializers should be used at the end of the "pipeline";
|
||||
using them somewhere in the middle may produce unexpected results.
|
||||
|
||||
:param function: the callable object that should be applied as a filter
|
||||
:return: the filtered stream
|
||||
:rtype: `Stream`
|
||||
"""
|
||||
return Stream(_ensure(function(self)), serializer=self.serializer)
|
||||
|
||||
def filter(self, *filters):
|
||||
"""Apply filters to the stream.
|
||||
|
||||
This method returns a new stream with the given filters applied. The
|
||||
filters must be callables that accept the stream object as parameter,
|
||||
and return the filtered stream.
|
||||
|
||||
The call::
|
||||
|
||||
stream.filter(filter1, filter2)
|
||||
|
||||
is equivalent to::
|
||||
|
||||
stream | filter1 | filter2
|
||||
|
||||
:param filters: one or more callable objects that should be applied as
|
||||
filters
|
||||
:return: the filtered stream
|
||||
:rtype: `Stream`
|
||||
"""
|
||||
return reduce(operator.or_, (self,) + filters)
|
||||
|
||||
def render(self, method=None, encoding='utf-8', out=None, **kwargs):
|
||||
"""Return a string representation of the stream.
|
||||
|
||||
Any additional keyword arguments are passed to the serializer, and thus
|
||||
depend on the `method` parameter value.
|
||||
|
||||
:param method: determines how the stream is serialized; can be either
|
||||
"xml", "xhtml", "html", "text", or a custom serializer
|
||||
class; if `None`, the default serialization method of
|
||||
the stream is used
|
||||
:param encoding: how the output string should be encoded; if set to
|
||||
`None`, this method returns a `unicode` object
|
||||
:param out: a file-like object that the output should be written to
|
||||
instead of being returned as one big string; note that if
|
||||
this is a file or socket (or similar), the `encoding` must
|
||||
not be `None` (that is, the output must be encoded)
|
||||
:return: a `str` or `unicode` object (depending on the `encoding`
|
||||
parameter), or `None` if the `out` parameter is provided
|
||||
:rtype: `basestring`
|
||||
|
||||
:see: XMLSerializer, XHTMLSerializer, HTMLSerializer, TextSerializer
|
||||
:note: Changed in 0.5: added the `out` parameter
|
||||
"""
|
||||
from calibre.utils.genshi.output import encode
|
||||
if method is None:
|
||||
method = self.serializer or 'xml'
|
||||
generator = self.serialize(method=method, **kwargs)
|
||||
return encode(generator, method=method, encoding=encoding, out=out)
|
||||
|
||||
def select(self, path, namespaces=None, variables=None):
|
||||
"""Return a new stream that contains the events matching the given
|
||||
XPath expression.
|
||||
|
||||
>>> from genshi import HTML
|
||||
>>> stream = HTML('<doc><elem>foo</elem><elem>bar</elem></doc>')
|
||||
>>> print stream.select('elem')
|
||||
<elem>foo</elem><elem>bar</elem>
|
||||
>>> print stream.select('elem/text()')
|
||||
foobar
|
||||
|
||||
Note that the outermost element of the stream becomes the *context
|
||||
node* for the XPath test. That means that the expression "doc" would
|
||||
not match anything in the example above, because it only tests against
|
||||
child elements of the outermost element:
|
||||
|
||||
>>> print stream.select('doc')
|
||||
<BLANKLINE>
|
||||
|
||||
You can use the "." expression to match the context node itself
|
||||
(although that usually makes little sense):
|
||||
|
||||
>>> print stream.select('.')
|
||||
<doc><elem>foo</elem><elem>bar</elem></doc>
|
||||
|
||||
:param path: a string containing the XPath expression
|
||||
:param namespaces: mapping of namespace prefixes used in the path
|
||||
:param variables: mapping of variable names to values
|
||||
:return: the selected substream
|
||||
:rtype: `Stream`
|
||||
:raises PathSyntaxError: if the given path expression is invalid or not
|
||||
supported
|
||||
"""
|
||||
from genshi.path import Path
|
||||
return Path(path).select(self, namespaces, variables)
|
||||
|
||||
def serialize(self, method='xml', **kwargs):
|
||||
"""Generate strings corresponding to a specific serialization of the
|
||||
stream.
|
||||
|
||||
Unlike the `render()` method, this method is a generator that returns
|
||||
the serialized output incrementally, as opposed to returning a single
|
||||
string.
|
||||
|
||||
Any additional keyword arguments are passed to the serializer, and thus
|
||||
depend on the `method` parameter value.
|
||||
|
||||
:param method: determines how the stream is serialized; can be either
|
||||
"xml", "xhtml", "html", "text", or a custom serializer
|
||||
class; if `None`, the default serialization method of
|
||||
the stream is used
|
||||
:return: an iterator over the serialization results (`Markup` or
|
||||
`unicode` objects, depending on the serialization method)
|
||||
:rtype: ``iterator``
|
||||
:see: XMLSerializer, XHTMLSerializer, HTMLSerializer, TextSerializer
|
||||
"""
|
||||
from calibre.utils.genshi.output import get_serializer
|
||||
if method is None:
|
||||
method = self.serializer or 'xml'
|
||||
return get_serializer(method, **kwargs)(_ensure(self))
|
||||
|
||||
def __str__(self):
|
||||
return self.render()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.render(encoding=None)
|
||||
|
||||
def __html__(self):
|
||||
return self
|
||||
|
||||
|
||||
START = Stream.START
|
||||
END = Stream.END
|
||||
TEXT = Stream.TEXT
|
||||
XML_DECL = Stream.XML_DECL
|
||||
DOCTYPE = Stream.DOCTYPE
|
||||
START_NS = Stream.START_NS
|
||||
END_NS = Stream.END_NS
|
||||
START_CDATA = Stream.START_CDATA
|
||||
END_CDATA = Stream.END_CDATA
|
||||
PI = Stream.PI
|
||||
COMMENT = Stream.COMMENT
|
||||
|
||||
def _ensure(stream):
|
||||
"""Ensure that every item on the stream is actually a markup event."""
|
||||
stream = iter(stream)
|
||||
event = stream.next()
|
||||
|
||||
# Check whether the iterable is a real markup event stream by examining the
|
||||
# first item it yields; if it's not we'll need to do some conversion
|
||||
if type(event) is not tuple or len(event) != 3:
|
||||
for event in chain([event], stream):
|
||||
if hasattr(event, 'totuple'):
|
||||
event = event.totuple()
|
||||
else:
|
||||
event = TEXT, unicode(event), (None, -1, -1)
|
||||
yield event
|
||||
return
|
||||
|
||||
# This looks like a markup event stream, so we'll just pass it through
|
||||
# unchanged
|
||||
yield event
|
||||
for event in stream:
|
||||
yield event
|
||||
|
||||
|
||||
class Attrs(tuple):
|
||||
"""Immutable sequence type that stores the attributes of an element.
|
||||
|
||||
Ordering of the attributes is preserved, while access by name is also
|
||||
supported.
|
||||
|
||||
>>> attrs = Attrs([('href', '#'), ('title', 'Foo')])
|
||||
>>> attrs
|
||||
Attrs([('href', '#'), ('title', 'Foo')])
|
||||
|
||||
>>> 'href' in attrs
|
||||
True
|
||||
>>> 'tabindex' in attrs
|
||||
False
|
||||
>>> attrs.get('title')
|
||||
'Foo'
|
||||
|
||||
Instances may not be manipulated directly. Instead, the operators ``|`` and
|
||||
``-`` can be used to produce new instances that have specific attributes
|
||||
added, replaced or removed.
|
||||
|
||||
To remove an attribute, use the ``-`` operator. The right hand side can be
|
||||
either a string or a set/sequence of strings, identifying the name(s) of
|
||||
the attribute(s) to remove:
|
||||
|
||||
>>> attrs - 'title'
|
||||
Attrs([('href', '#')])
|
||||
>>> attrs - ('title', 'href')
|
||||
Attrs()
|
||||
|
||||
The original instance is not modified, but the operator can of course be
|
||||
used with an assignment:
|
||||
|
||||
>>> attrs
|
||||
Attrs([('href', '#'), ('title', 'Foo')])
|
||||
>>> attrs -= 'title'
|
||||
>>> attrs
|
||||
Attrs([('href', '#')])
|
||||
|
||||
To add a new attribute, use the ``|`` operator, where the right hand value
|
||||
is a sequence of ``(name, value)`` tuples (which includes `Attrs`
|
||||
instances):
|
||||
|
||||
>>> attrs | [('title', 'Bar')]
|
||||
Attrs([('href', '#'), ('title', 'Bar')])
|
||||
|
||||
If the attributes already contain an attribute with a given name, the value
|
||||
of that attribute is replaced:
|
||||
|
||||
>>> attrs | [('href', 'http://example.org/')]
|
||||
Attrs([('href', 'http://example.org/')])
|
||||
"""
|
||||
__slots__ = []
|
||||
|
||||
def __contains__(self, name):
|
||||
"""Return whether the list includes an attribute with the specified
|
||||
name.
|
||||
|
||||
:return: `True` if the list includes the attribute
|
||||
:rtype: `bool`
|
||||
"""
|
||||
for attr, _ in self:
|
||||
if attr == name:
|
||||
return True
|
||||
|
||||
def __getslice__(self, i, j):
|
||||
"""Return a slice of the attributes list.
|
||||
|
||||
>>> attrs = Attrs([('href', '#'), ('title', 'Foo')])
|
||||
>>> attrs[1:]
|
||||
Attrs([('title', 'Foo')])
|
||||
"""
|
||||
return Attrs(tuple.__getslice__(self, i, j))
|
||||
|
||||
def __or__(self, attrs):
|
||||
"""Return a new instance that contains the attributes in `attrs` in
|
||||
addition to any already existing attributes.
|
||||
|
||||
:return: a new instance with the merged attributes
|
||||
:rtype: `Attrs`
|
||||
"""
|
||||
repl = dict([(an, av) for an, av in attrs if an in self])
|
||||
return Attrs([(sn, repl.get(sn, sv)) for sn, sv in self] +
|
||||
[(an, av) for an, av in attrs if an not in self])
|
||||
|
||||
def __repr__(self):
|
||||
if not self:
|
||||
return 'Attrs()'
|
||||
return 'Attrs([%s])' % ', '.join([repr(item) for item in self])
|
||||
|
||||
def __sub__(self, names):
|
||||
"""Return a new instance with all attributes with a name in `names` are
|
||||
removed.
|
||||
|
||||
:param names: the names of the attributes to remove
|
||||
:return: a new instance with the attribute removed
|
||||
:rtype: `Attrs`
|
||||
"""
|
||||
if isinstance(names, basestring):
|
||||
names = (names,)
|
||||
return Attrs([(name, val) for name, val in self if name not in names])
|
||||
|
||||
def get(self, name, default=None):
|
||||
"""Return the value of the attribute with the specified name, or the
|
||||
value of the `default` parameter if no such attribute is found.
|
||||
|
||||
:param name: the name of the attribute
|
||||
:param default: the value to return when the attribute does not exist
|
||||
:return: the attribute value, or the `default` value if that attribute
|
||||
does not exist
|
||||
:rtype: `object`
|
||||
"""
|
||||
for attr, value in self:
|
||||
if attr == name:
|
||||
return value
|
||||
return default
|
||||
|
||||
def totuple(self):
|
||||
"""Return the attributes as a markup event.
|
||||
|
||||
The returned event is a `TEXT` event, the data is the value of all
|
||||
attributes joined together.
|
||||
|
||||
>>> Attrs([('href', '#'), ('title', 'Foo')]).totuple()
|
||||
('TEXT', u'#Foo', (None, -1, -1))
|
||||
|
||||
:return: a `TEXT` event
|
||||
:rtype: `tuple`
|
||||
"""
|
||||
return TEXT, u''.join([x[1] for x in self]), (None, -1, -1)
|
||||
|
||||
|
||||
class Markup(unicode):
|
||||
"""Marks a string as being safe for inclusion in HTML/XML output without
|
||||
needing to be escaped.
|
||||
"""
|
||||
__slots__ = []
|
||||
|
||||
def __add__(self, other):
|
||||
return Markup(unicode(self) + unicode(escape(other)))
|
||||
|
||||
def __radd__(self, other):
|
||||
return Markup(unicode(escape(other)) + unicode(self))
|
||||
|
||||
def __mod__(self, args):
|
||||
if isinstance(args, dict):
|
||||
args = dict(zip(args.keys(), map(escape, args.values())))
|
||||
elif isinstance(args, (list, tuple)):
|
||||
args = tuple(map(escape, args))
|
||||
else:
|
||||
args = escape(args)
|
||||
return Markup(unicode.__mod__(self, args))
|
||||
|
||||
def __mul__(self, num):
|
||||
return Markup(unicode(self) * num)
|
||||
|
||||
def __rmul__(self, num):
|
||||
return Markup(num * unicode(self))
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r>' % (self.__class__.__name__, unicode(self))
|
||||
|
||||
def join(self, seq, escape_quotes=True):
|
||||
"""Return a `Markup` object which is the concatenation of the strings
|
||||
in the given sequence, where this `Markup` object is the separator
|
||||
between the joined elements.
|
||||
|
||||
Any element in the sequence that is not a `Markup` instance is
|
||||
automatically escaped.
|
||||
|
||||
:param seq: the sequence of strings to join
|
||||
:param escape_quotes: whether double quote characters in the elements
|
||||
should be escaped
|
||||
:return: the joined `Markup` object
|
||||
:rtype: `Markup`
|
||||
:see: `escape`
|
||||
"""
|
||||
return Markup(unicode(self).join([escape(item, quotes=escape_quotes)
|
||||
for item in seq]))
|
||||
|
||||
def escape(cls, text, quotes=True):
|
||||
"""Create a Markup instance from a string and escape special characters
|
||||
it may contain (<, >, & and \").
|
||||
|
||||
>>> escape('"1 < 2"')
|
||||
<Markup u'"1 < 2"'>
|
||||
|
||||
If the `quotes` parameter is set to `False`, the \" character is left
|
||||
as is. Escaping quotes is generally only required for strings that are
|
||||
to be used in attribute values.
|
||||
|
||||
>>> escape('"1 < 2"', quotes=False)
|
||||
<Markup u'"1 < 2"'>
|
||||
|
||||
:param text: the text to escape
|
||||
:param quotes: if ``True``, double quote characters are escaped in
|
||||
addition to the other special characters
|
||||
:return: the escaped `Markup` string
|
||||
:rtype: `Markup`
|
||||
"""
|
||||
if not text:
|
||||
return cls()
|
||||
if type(text) is cls:
|
||||
return text
|
||||
if hasattr(text, '__html__'):
|
||||
return Markup(text.__html__())
|
||||
|
||||
if isinstance(text, str):
|
||||
text = text.decode('utf-8', 'replace')
|
||||
text = text.replace('&', '&') \
|
||||
.replace('<', '<') \
|
||||
.replace('>', '>')
|
||||
if quotes:
|
||||
text = text.replace('"', '"')
|
||||
return cls(text)
|
||||
escape = classmethod(escape)
|
||||
|
||||
def unescape(self):
|
||||
"""Reverse-escapes &, <, >, and \" and returns a `unicode` object.
|
||||
|
||||
>>> Markup('1 < 2').unescape()
|
||||
u'1 < 2'
|
||||
|
||||
:return: the unescaped string
|
||||
:rtype: `unicode`
|
||||
:see: `genshi.core.unescape`
|
||||
"""
|
||||
if not self:
|
||||
return u''
|
||||
return unicode(self).replace('"', '"') \
|
||||
.replace('>', '>') \
|
||||
.replace('<', '<') \
|
||||
.replace('&', '&')
|
||||
|
||||
def stripentities(self, keepxmlentities=False):
|
||||
"""Return a copy of the text with any character or numeric entities
|
||||
replaced by the equivalent UTF-8 characters.
|
||||
|
||||
If the `keepxmlentities` parameter is provided and evaluates to `True`,
|
||||
the core XML entities (``&``, ``'``, ``>``, ``<`` and
|
||||
``"``) are not stripped.
|
||||
|
||||
:return: a `Markup` instance with entities removed
|
||||
:rtype: `Markup`
|
||||
:see: `genshi.util.stripentities`
|
||||
"""
|
||||
return Markup(stripentities(self, keepxmlentities=keepxmlentities))
|
||||
|
||||
def striptags(self):
|
||||
"""Return a copy of the text with all XML/HTML tags removed.
|
||||
|
||||
:return: a `Markup` instance with all tags removed
|
||||
:rtype: `Markup`
|
||||
:see: `genshi.util.striptags`
|
||||
"""
|
||||
return Markup(striptags(self))
|
||||
|
||||
|
||||
try:
|
||||
from calibre.utils.genshi._speedups import Markup
|
||||
except ImportError:
|
||||
pass # just use the Python implementation
|
||||
|
||||
escape = Markup.escape
|
||||
|
||||
def unescape(text):
|
||||
"""Reverse-escapes &, <, >, and \" and returns a `unicode` object.
|
||||
|
||||
>>> unescape(Markup('1 < 2'))
|
||||
u'1 < 2'
|
||||
|
||||
If the provided `text` object is not a `Markup` instance, it is returned
|
||||
unchanged.
|
||||
|
||||
>>> unescape('1 < 2')
|
||||
'1 < 2'
|
||||
|
||||
:param text: the text to unescape
|
||||
:return: the unescsaped string
|
||||
:rtype: `unicode`
|
||||
"""
|
||||
if not isinstance(text, Markup):
|
||||
return text
|
||||
return text.unescape()
|
||||
|
||||
|
||||
class Namespace(object):
|
||||
"""Utility class creating and testing elements with a namespace.
|
||||
|
||||
Internally, namespace URIs are encoded in the `QName` of any element or
|
||||
attribute, the namespace URI being enclosed in curly braces. This class
|
||||
helps create and test these strings.
|
||||
|
||||
A `Namespace` object is instantiated with the namespace URI.
|
||||
|
||||
>>> html = Namespace('http://www.w3.org/1999/xhtml')
|
||||
>>> html
|
||||
<Namespace "http://www.w3.org/1999/xhtml">
|
||||
>>> html.uri
|
||||
u'http://www.w3.org/1999/xhtml'
|
||||
|
||||
The `Namespace` object can than be used to generate `QName` objects with
|
||||
that namespace:
|
||||
|
||||
>>> html.body
|
||||
QName(u'http://www.w3.org/1999/xhtml}body')
|
||||
>>> html.body.localname
|
||||
u'body'
|
||||
>>> html.body.namespace
|
||||
u'http://www.w3.org/1999/xhtml'
|
||||
|
||||
The same works using item access notation, which is useful for element or
|
||||
attribute names that are not valid Python identifiers:
|
||||
|
||||
>>> html['body']
|
||||
QName(u'http://www.w3.org/1999/xhtml}body')
|
||||
|
||||
A `Namespace` object can also be used to test whether a specific `QName`
|
||||
belongs to that namespace using the ``in`` operator:
|
||||
|
||||
>>> qname = html.body
|
||||
>>> qname in html
|
||||
True
|
||||
>>> qname in Namespace('http://www.w3.org/2002/06/xhtml2')
|
||||
False
|
||||
"""
|
||||
def __new__(cls, uri):
|
||||
if type(uri) is cls:
|
||||
return uri
|
||||
return object.__new__(cls)
|
||||
|
||||
def __getnewargs__(self):
|
||||
return (self.uri,)
|
||||
|
||||
def __getstate__(self):
|
||||
return self.uri
|
||||
|
||||
def __setstate__(self, uri):
|
||||
self.uri = uri
|
||||
|
||||
def __init__(self, uri):
|
||||
self.uri = unicode(uri)
|
||||
|
||||
def __contains__(self, qname):
|
||||
return qname.namespace == self.uri
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Namespace):
|
||||
return self.uri == other.uri
|
||||
return self.uri == other
|
||||
|
||||
def __getitem__(self, name):
|
||||
return QName(self.uri + u'}' + name)
|
||||
__getattr__ = __getitem__
|
||||
|
||||
def __repr__(self):
|
||||
return '<Namespace "%s">' % self.uri
|
||||
|
||||
def __str__(self):
|
||||
return self.uri.encode('utf-8')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.uri
|
||||
|
||||
|
||||
# The namespace used by attributes such as xml:lang and xml:space
|
||||
XML_NAMESPACE = Namespace('http://www.w3.org/XML/1998/namespace')
|
||||
|
||||
|
||||
class QName(unicode):
|
||||
"""A qualified element or attribute name.
|
||||
|
||||
The unicode value of instances of this class contains the qualified name of
|
||||
the element or attribute, in the form ``{namespace-uri}local-name``. The
|
||||
namespace URI can be obtained through the additional `namespace` attribute,
|
||||
while the local name can be accessed through the `localname` attribute.
|
||||
|
||||
>>> qname = QName('foo')
|
||||
>>> qname
|
||||
QName(u'foo')
|
||||
>>> qname.localname
|
||||
u'foo'
|
||||
>>> qname.namespace
|
||||
|
||||
>>> qname = QName('http://www.w3.org/1999/xhtml}body')
|
||||
>>> qname
|
||||
QName(u'http://www.w3.org/1999/xhtml}body')
|
||||
>>> qname.localname
|
||||
u'body'
|
||||
>>> qname.namespace
|
||||
u'http://www.w3.org/1999/xhtml'
|
||||
"""
|
||||
__slots__ = ['namespace', 'localname']
|
||||
|
||||
def __new__(cls, qname):
|
||||
"""Create the `QName` instance.
|
||||
|
||||
:param qname: the qualified name as a string of the form
|
||||
``{namespace-uri}local-name``, where the leading curly
|
||||
brace is optional
|
||||
"""
|
||||
if type(qname) is cls:
|
||||
return qname
|
||||
|
||||
parts = qname.lstrip(u'{').split(u'}', 1)
|
||||
if len(parts) > 1:
|
||||
self = unicode.__new__(cls, u'{%s' % qname)
|
||||
self.namespace, self.localname = map(unicode, parts)
|
||||
else:
|
||||
self = unicode.__new__(cls, qname)
|
||||
self.namespace, self.localname = None, unicode(qname)
|
||||
return self
|
||||
|
||||
def __getnewargs__(self):
|
||||
return (self.lstrip('{'),)
|
||||
|
||||
def __repr__(self):
|
||||
return 'QName(%s)' % unicode.__repr__(self.lstrip('{'))
|
@ -1,20 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2007 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Implementation of a number of stream filters."""
|
||||
|
||||
from calibre.utils.genshi.filters.html import HTMLFormFiller, HTMLSanitizer
|
||||
from calibre.utils.genshi.filters.i18n import Translator
|
||||
from calibre.utils.genshi.filters.transform import Transformer
|
||||
|
||||
__docformat__ = 'restructuredtext en'
|
@ -1,397 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Implementation of a number of stream filters."""
|
||||
|
||||
try:
|
||||
set
|
||||
except NameError:
|
||||
from sets import ImmutableSet as frozenset
|
||||
from sets import Set as set
|
||||
import re
|
||||
|
||||
from calibre.utils.genshi.core import Attrs, QName, stripentities
|
||||
from calibre.utils.genshi.core import END, START, TEXT, COMMENT
|
||||
|
||||
__all__ = ['HTMLFormFiller', 'HTMLSanitizer']
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
class HTMLFormFiller(object):
|
||||
"""A stream filter that can populate HTML forms from a dictionary of values.
|
||||
|
||||
>>> from genshi.input import HTML
|
||||
>>> html = HTML('''<form>
|
||||
... <p><input type="text" name="foo" /></p>
|
||||
... </form>''')
|
||||
>>> filler = HTMLFormFiller(data={'foo': 'bar'})
|
||||
>>> print html | filler
|
||||
<form>
|
||||
<p><input type="text" name="foo" value="bar"/></p>
|
||||
</form>
|
||||
"""
|
||||
# TODO: only select the first radio button, and the first select option
|
||||
# (if not in a multiple-select)
|
||||
# TODO: only apply to elements in the XHTML namespace (or no namespace)?
|
||||
|
||||
def __init__(self, name=None, id=None, data=None):
|
||||
"""Create the filter.
|
||||
|
||||
:param name: The name of the form that should be populated. If this
|
||||
parameter is given, only forms where the ``name`` attribute
|
||||
value matches the parameter are processed.
|
||||
:param id: The ID of the form that should be populated. If this
|
||||
parameter is given, only forms where the ``id`` attribute
|
||||
value matches the parameter are processed.
|
||||
:param data: The dictionary of form values, where the keys are the names
|
||||
of the form fields, and the values are the values to fill
|
||||
in.
|
||||
"""
|
||||
self.name = name
|
||||
self.id = id
|
||||
if data is None:
|
||||
data = {}
|
||||
self.data = data
|
||||
|
||||
def __call__(self, stream):
|
||||
"""Apply the filter to the given stream.
|
||||
|
||||
:param stream: the markup event stream to filter
|
||||
"""
|
||||
in_form = in_select = in_option = in_textarea = False
|
||||
select_value = option_value = textarea_value = None
|
||||
option_start = None
|
||||
option_text = []
|
||||
no_option_value = False
|
||||
|
||||
for kind, data, pos in stream:
|
||||
|
||||
if kind is START:
|
||||
tag, attrs = data
|
||||
tagname = tag.localname
|
||||
|
||||
if tagname == 'form' and (
|
||||
self.name and attrs.get('name') == self.name or
|
||||
self.id and attrs.get('id') == self.id or
|
||||
not (self.id or self.name)):
|
||||
in_form = True
|
||||
|
||||
elif in_form:
|
||||
if tagname == 'input':
|
||||
type = attrs.get('type')
|
||||
if type in ('checkbox', 'radio'):
|
||||
name = attrs.get('name')
|
||||
if name and name in self.data:
|
||||
value = self.data[name]
|
||||
declval = attrs.get('value')
|
||||
checked = False
|
||||
if isinstance(value, (list, tuple)):
|
||||
if declval:
|
||||
checked = declval in [unicode(v) for v
|
||||
in value]
|
||||
else:
|
||||
checked = bool(filter(None, value))
|
||||
else:
|
||||
if declval:
|
||||
checked = declval == unicode(value)
|
||||
elif type == 'checkbox':
|
||||
checked = bool(value)
|
||||
if checked:
|
||||
attrs |= [(QName('checked'), 'checked')]
|
||||
elif 'checked' in attrs:
|
||||
attrs -= 'checked'
|
||||
elif type in (None, 'hidden', 'text'):
|
||||
name = attrs.get('name')
|
||||
if name and name in self.data:
|
||||
value = self.data[name]
|
||||
if isinstance(value, (list, tuple)):
|
||||
value = value[0]
|
||||
if value is not None:
|
||||
attrs |= [(QName('value'), unicode(value))]
|
||||
elif tagname == 'select':
|
||||
name = attrs.get('name')
|
||||
if name in self.data:
|
||||
select_value = self.data[name]
|
||||
in_select = True
|
||||
elif tagname == 'textarea':
|
||||
name = attrs.get('name')
|
||||
if name in self.data:
|
||||
textarea_value = self.data.get(name)
|
||||
if isinstance(textarea_value, (list, tuple)):
|
||||
textarea_value = textarea_value[0]
|
||||
in_textarea = True
|
||||
elif in_select and tagname == 'option':
|
||||
option_start = kind, data, pos
|
||||
option_value = attrs.get('value')
|
||||
if option_value is None:
|
||||
no_option_value = True
|
||||
option_value = ''
|
||||
in_option = True
|
||||
continue
|
||||
yield kind, (tag, attrs), pos
|
||||
|
||||
elif in_form and kind is TEXT:
|
||||
if in_select and in_option:
|
||||
if no_option_value:
|
||||
option_value += data
|
||||
option_text.append((kind, data, pos))
|
||||
continue
|
||||
elif in_textarea:
|
||||
continue
|
||||
yield kind, data, pos
|
||||
|
||||
elif in_form and kind is END:
|
||||
tagname = data.localname
|
||||
if tagname == 'form':
|
||||
in_form = False
|
||||
elif tagname == 'select':
|
||||
in_select = False
|
||||
select_value = None
|
||||
elif in_select and tagname == 'option':
|
||||
if isinstance(select_value, (tuple, list)):
|
||||
selected = option_value in [unicode(v) for v
|
||||
in select_value]
|
||||
else:
|
||||
selected = option_value == unicode(select_value)
|
||||
okind, (tag, attrs), opos = option_start
|
||||
if selected:
|
||||
attrs |= [(QName('selected'), 'selected')]
|
||||
elif 'selected' in attrs:
|
||||
attrs -= 'selected'
|
||||
yield okind, (tag, attrs), opos
|
||||
if option_text:
|
||||
for event in option_text:
|
||||
yield event
|
||||
in_option = False
|
||||
no_option_value = False
|
||||
option_start = option_value = None
|
||||
option_text = []
|
||||
elif tagname == 'textarea':
|
||||
if textarea_value:
|
||||
yield TEXT, unicode(textarea_value), pos
|
||||
in_textarea = False
|
||||
yield kind, data, pos
|
||||
|
||||
else:
|
||||
yield kind, data, pos
|
||||
|
||||
|
||||
class HTMLSanitizer(object):
|
||||
"""A filter that removes potentially dangerous HTML tags and attributes
|
||||
from the stream.
|
||||
|
||||
>>> from genshi import HTML
|
||||
>>> html = HTML('<div><script>alert(document.cookie)</script></div>')
|
||||
>>> print html | HTMLSanitizer()
|
||||
<div/>
|
||||
|
||||
The default set of safe tags and attributes can be modified when the filter
|
||||
is instantiated. For example, to allow inline ``style`` attributes, the
|
||||
following instantation would work:
|
||||
|
||||
>>> html = HTML('<div style="background: #000"></div>')
|
||||
>>> sanitizer = HTMLSanitizer(safe_attrs=HTMLSanitizer.SAFE_ATTRS | set(['style']))
|
||||
>>> print html | sanitizer
|
||||
<div style="background: #000"/>
|
||||
|
||||
Note that even in this case, the filter *does* attempt to remove dangerous
|
||||
constructs from style attributes:
|
||||
|
||||
>>> html = HTML('<div style="background: url(javascript:void); color: #000"></div>')
|
||||
>>> print html | sanitizer
|
||||
<div style="color: #000"/>
|
||||
|
||||
This handles HTML entities, unicode escapes in CSS and Javascript text, as
|
||||
well as a lot of other things. However, the style tag is still excluded by
|
||||
default because it is very hard for such sanitizing to be completely safe,
|
||||
especially considering how much error recovery current web browsers perform.
|
||||
|
||||
:warn: Note that this special processing of CSS is currently only applied to
|
||||
style attributes, **not** style elements.
|
||||
"""
|
||||
|
||||
SAFE_TAGS = frozenset(['a', 'abbr', 'acronym', 'address', 'area', 'b',
|
||||
'big', 'blockquote', 'br', 'button', 'caption', 'center', 'cite',
|
||||
'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt',
|
||||
'em', 'fieldset', 'font', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||
'hr', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'map',
|
||||
'menu', 'ol', 'optgroup', 'option', 'p', 'pre', 'q', 's', 'samp',
|
||||
'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table',
|
||||
'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u',
|
||||
'ul', 'var'])
|
||||
|
||||
SAFE_ATTRS = frozenset(['abbr', 'accept', 'accept-charset', 'accesskey',
|
||||
'action', 'align', 'alt', 'axis', 'bgcolor', 'border', 'cellpadding',
|
||||
'cellspacing', 'char', 'charoff', 'charset', 'checked', 'cite', 'class',
|
||||
'clear', 'cols', 'colspan', 'color', 'compact', 'coords', 'datetime',
|
||||
'dir', 'disabled', 'enctype', 'for', 'frame', 'headers', 'height',
|
||||
'href', 'hreflang', 'hspace', 'id', 'ismap', 'label', 'lang',
|
||||
'longdesc', 'maxlength', 'media', 'method', 'multiple', 'name',
|
||||
'nohref', 'noshade', 'nowrap', 'prompt', 'readonly', 'rel', 'rev',
|
||||
'rows', 'rowspan', 'rules', 'scope', 'selected', 'shape', 'size',
|
||||
'span', 'src', 'start', 'summary', 'tabindex', 'target', 'title',
|
||||
'type', 'usemap', 'valign', 'value', 'vspace', 'width'])
|
||||
|
||||
SAFE_SCHEMES = frozenset(['file', 'ftp', 'http', 'https', 'mailto', None])
|
||||
|
||||
URI_ATTRS = frozenset(['action', 'background', 'dynsrc', 'href', 'lowsrc',
|
||||
'src'])
|
||||
|
||||
def __init__(self, safe_tags=SAFE_TAGS, safe_attrs=SAFE_ATTRS,
|
||||
safe_schemes=SAFE_SCHEMES, uri_attrs=URI_ATTRS):
|
||||
"""Create the sanitizer.
|
||||
|
||||
The exact set of allowed elements and attributes can be configured.
|
||||
|
||||
:param safe_tags: a set of tag names that are considered safe
|
||||
:param safe_attrs: a set of attribute names that are considered safe
|
||||
:param safe_schemes: a set of URI schemes that are considered safe
|
||||
:param uri_attrs: a set of names of attributes that contain URIs
|
||||
"""
|
||||
self.safe_tags = safe_tags
|
||||
"The set of tag names that are considered safe."
|
||||
self.safe_attrs = safe_attrs
|
||||
"The set of attribute names that are considered safe."
|
||||
self.uri_attrs = uri_attrs
|
||||
"The set of names of attributes that may contain URIs."
|
||||
self.safe_schemes = safe_schemes
|
||||
"The set of URI schemes that are considered safe."
|
||||
|
||||
def __call__(self, stream):
|
||||
"""Apply the filter to the given stream.
|
||||
|
||||
:param stream: the markup event stream to filter
|
||||
"""
|
||||
waiting_for = None
|
||||
|
||||
for kind, data, pos in stream:
|
||||
if kind is START:
|
||||
if waiting_for:
|
||||
continue
|
||||
tag, attrs = data
|
||||
if tag not in self.safe_tags:
|
||||
waiting_for = tag
|
||||
continue
|
||||
|
||||
new_attrs = []
|
||||
for attr, value in attrs:
|
||||
value = stripentities(value)
|
||||
if attr not in self.safe_attrs:
|
||||
continue
|
||||
elif attr in self.uri_attrs:
|
||||
# Don't allow URI schemes such as "javascript:"
|
||||
if not self.is_safe_uri(value):
|
||||
continue
|
||||
elif attr == 'style':
|
||||
# Remove dangerous CSS declarations from inline styles
|
||||
decls = self.sanitize_css(value)
|
||||
if not decls:
|
||||
continue
|
||||
value = '; '.join(decls)
|
||||
new_attrs.append((attr, value))
|
||||
|
||||
yield kind, (tag, Attrs(new_attrs)), pos
|
||||
|
||||
elif kind is END:
|
||||
tag = data
|
||||
if waiting_for:
|
||||
if waiting_for == tag:
|
||||
waiting_for = None
|
||||
else:
|
||||
yield kind, data, pos
|
||||
|
||||
elif kind is not COMMENT:
|
||||
if not waiting_for:
|
||||
yield kind, data, pos
|
||||
|
||||
def is_safe_uri(self, uri):
|
||||
"""Determine whether the given URI is to be considered safe for
|
||||
inclusion in the output.
|
||||
|
||||
The default implementation checks whether the scheme of the URI is in
|
||||
the set of allowed URIs (`safe_schemes`).
|
||||
|
||||
>>> sanitizer = HTMLSanitizer()
|
||||
>>> sanitizer.is_safe_uri('http://example.org/')
|
||||
True
|
||||
>>> sanitizer.is_safe_uri('javascript:alert(document.cookie)')
|
||||
False
|
||||
|
||||
:param uri: the URI to check
|
||||
:return: `True` if the URI can be considered safe, `False` otherwise
|
||||
:rtype: `bool`
|
||||
:since: version 0.4.3
|
||||
"""
|
||||
if ':' not in uri:
|
||||
return True # This is a relative URI
|
||||
chars = [char for char in uri.split(':', 1)[0] if char.isalnum()]
|
||||
return ''.join(chars).lower() in self.safe_schemes
|
||||
|
||||
def sanitize_css(self, text):
|
||||
"""Remove potentially dangerous property declarations from CSS code.
|
||||
|
||||
In particular, properties using the CSS ``url()`` function with a scheme
|
||||
that is not considered safe are removed:
|
||||
|
||||
>>> sanitizer = HTMLSanitizer()
|
||||
>>> sanitizer.sanitize_css(u'''
|
||||
... background: url(javascript:alert("foo"));
|
||||
... color: #000;
|
||||
... ''')
|
||||
[u'color: #000']
|
||||
|
||||
Also, the proprietary Internet Explorer function ``expression()`` is
|
||||
always stripped:
|
||||
|
||||
>>> sanitizer.sanitize_css(u'''
|
||||
... background: #fff;
|
||||
... color: #000;
|
||||
... width: e/**/xpression(alert("foo"));
|
||||
... ''')
|
||||
[u'background: #fff', u'color: #000']
|
||||
|
||||
:param text: the CSS text; this is expected to be `unicode` and to not
|
||||
contain any character or numeric references
|
||||
:return: a list of declarations that are considered safe
|
||||
:rtype: `list`
|
||||
:since: version 0.4.3
|
||||
"""
|
||||
decls = []
|
||||
text = self._strip_css_comments(self._replace_unicode_escapes(text))
|
||||
for decl in filter(None, text.split(';')):
|
||||
decl = decl.strip()
|
||||
if not decl:
|
||||
continue
|
||||
is_evil = False
|
||||
if 'expression' in decl:
|
||||
is_evil = True
|
||||
for match in re.finditer(r'url\s*\(([^)]+)', decl):
|
||||
if not self.is_safe_uri(match.group(1)):
|
||||
is_evil = True
|
||||
break
|
||||
if not is_evil:
|
||||
decls.append(decl.strip())
|
||||
return decls
|
||||
|
||||
_NORMALIZE_NEWLINES = re.compile(r'\r\n').sub
|
||||
_UNICODE_ESCAPE = re.compile(r'\\([0-9a-fA-F]{1,6})\s?').sub
|
||||
|
||||
def _replace_unicode_escapes(self, text):
|
||||
def _repl(match):
|
||||
return unichr(int(match.group(1), 16))
|
||||
return self._UNICODE_ESCAPE(_repl, self._NORMALIZE_NEWLINES('\n', text))
|
||||
|
||||
_CSS_COMMENTS = re.compile(r'/\*.*?\*/').sub
|
||||
|
||||
def _strip_css_comments(self, text):
|
||||
return self._CSS_COMMENTS('', text)
|
@ -1,528 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2007 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Utilities for internationalization and localization of templates.
|
||||
|
||||
:since: version 0.4
|
||||
"""
|
||||
|
||||
from compiler import ast
|
||||
try:
|
||||
frozenset
|
||||
except NameError:
|
||||
from sets import ImmutableSet as frozenset
|
||||
from gettext import gettext
|
||||
import re
|
||||
|
||||
from calibre.utils.genshi.core import Attrs, Namespace, QName, START, END, TEXT, START_NS, \
|
||||
END_NS, XML_NAMESPACE, _ensure
|
||||
from calibre.utils.genshi.template.base import Template, EXPR, SUB
|
||||
from calibre.utils.genshi.template.markup import MarkupTemplate, EXEC
|
||||
|
||||
__all__ = ['Translator', 'extract']
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
I18N_NAMESPACE = Namespace('http://genshi.edgewall.org/i18n')
|
||||
|
||||
|
||||
class Translator(object):
|
||||
"""Can extract and translate localizable strings from markup streams and
|
||||
templates.
|
||||
|
||||
For example, assume the followng template:
|
||||
|
||||
>>> from genshi.template import MarkupTemplate
|
||||
>>>
|
||||
>>> tmpl = MarkupTemplate('''<html xmlns:py="http://genshi.edgewall.org/">
|
||||
... <head>
|
||||
... <title>Example</title>
|
||||
... </head>
|
||||
... <body>
|
||||
... <h1>Example</h1>
|
||||
... <p>${_("Hello, %(name)s") % dict(name=username)}</p>
|
||||
... </body>
|
||||
... </html>''', filename='example.html')
|
||||
|
||||
For demonstration, we define a dummy ``gettext``-style function with a
|
||||
hard-coded translation table, and pass that to the `Translator` initializer:
|
||||
|
||||
>>> def pseudo_gettext(string):
|
||||
... return {
|
||||
... 'Example': 'Beispiel',
|
||||
... 'Hello, %(name)s': 'Hallo, %(name)s'
|
||||
... }[string]
|
||||
>>>
|
||||
>>> translator = Translator(pseudo_gettext)
|
||||
|
||||
Next, the translator needs to be prepended to any already defined filters
|
||||
on the template:
|
||||
|
||||
>>> tmpl.filters.insert(0, translator)
|
||||
|
||||
When generating the template output, our hard-coded translations should be
|
||||
applied as expected:
|
||||
|
||||
>>> print tmpl.generate(username='Hans', _=pseudo_gettext)
|
||||
<html>
|
||||
<head>
|
||||
<title>Beispiel</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Beispiel</h1>
|
||||
<p>Hallo, Hans</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Note that elements defining ``xml:lang`` attributes that do not contain
|
||||
variable expressions are ignored by this filter. That can be used to
|
||||
exclude specific parts of a template from being extracted and translated.
|
||||
"""
|
||||
|
||||
IGNORE_TAGS = frozenset([
|
||||
QName('script'), QName('http://www.w3.org/1999/xhtml}script'),
|
||||
QName('style'), QName('http://www.w3.org/1999/xhtml}style')
|
||||
])
|
||||
INCLUDE_ATTRS = frozenset(['abbr', 'alt', 'label', 'prompt', 'standby',
|
||||
'summary', 'title'])
|
||||
|
||||
def __init__(self, translate=gettext, ignore_tags=IGNORE_TAGS,
|
||||
include_attrs=INCLUDE_ATTRS, extract_text=True):
|
||||
"""Initialize the translator.
|
||||
|
||||
:param translate: the translation function, for example ``gettext`` or
|
||||
``ugettext``.
|
||||
:param ignore_tags: a set of tag names that should not be localized
|
||||
:param include_attrs: a set of attribute names should be localized
|
||||
:param extract_text: whether the content of text nodes should be
|
||||
extracted, or only text in explicit ``gettext``
|
||||
function calls
|
||||
"""
|
||||
self.translate = translate
|
||||
self.ignore_tags = ignore_tags
|
||||
self.include_attrs = include_attrs
|
||||
self.extract_text = extract_text
|
||||
|
||||
def __call__(self, stream, ctxt=None, search_text=True, msgbuf=None):
|
||||
"""Translate any localizable strings in the given stream.
|
||||
|
||||
This function shouldn't be called directly. Instead, an instance of
|
||||
the `Translator` class should be registered as a filter with the
|
||||
`Template` or the `TemplateLoader`, or applied as a regular stream
|
||||
filter. If used as a template filter, it should be inserted in front of
|
||||
all the default filters.
|
||||
|
||||
:param stream: the markup event stream
|
||||
:param ctxt: the template context (not used)
|
||||
:param search_text: whether text nodes should be translated (used
|
||||
internally)
|
||||
:param msgbuf: a `MessageBuffer` object or `None` (used internally)
|
||||
:return: the localized stream
|
||||
"""
|
||||
ignore_tags = self.ignore_tags
|
||||
include_attrs = self.include_attrs
|
||||
translate = self.translate
|
||||
if not self.extract_text:
|
||||
search_text = False
|
||||
skip = 0
|
||||
i18n_msg = I18N_NAMESPACE['msg']
|
||||
ns_prefixes = []
|
||||
xml_lang = XML_NAMESPACE['lang']
|
||||
|
||||
for kind, data, pos in stream:
|
||||
|
||||
# skip chunks that should not be localized
|
||||
if skip:
|
||||
if kind is START:
|
||||
skip += 1
|
||||
elif kind is END:
|
||||
skip -= 1
|
||||
yield kind, data, pos
|
||||
continue
|
||||
|
||||
# handle different events that can be localized
|
||||
if kind is START:
|
||||
tag, attrs = data
|
||||
if tag in self.ignore_tags or \
|
||||
isinstance(attrs.get(xml_lang), basestring):
|
||||
skip += 1
|
||||
yield kind, data, pos
|
||||
continue
|
||||
|
||||
new_attrs = []
|
||||
changed = False
|
||||
for name, value in attrs:
|
||||
newval = value
|
||||
if search_text and isinstance(value, basestring):
|
||||
if name in include_attrs:
|
||||
newval = self.translate(value)
|
||||
else:
|
||||
newval = list(self(_ensure(value), ctxt,
|
||||
search_text=False, msgbuf=msgbuf)
|
||||
)
|
||||
if newval != value:
|
||||
value = newval
|
||||
changed = True
|
||||
new_attrs.append((name, value))
|
||||
if changed:
|
||||
attrs = Attrs(new_attrs)
|
||||
|
||||
if msgbuf:
|
||||
msgbuf.append(kind, data, pos)
|
||||
continue
|
||||
elif i18n_msg in attrs:
|
||||
msgbuf = MessageBuffer()
|
||||
attrs -= i18n_msg
|
||||
|
||||
yield kind, (tag, attrs), pos
|
||||
|
||||
elif search_text and kind is TEXT:
|
||||
if not msgbuf:
|
||||
text = data.strip()
|
||||
if text:
|
||||
data = data.replace(text, translate(text))
|
||||
yield kind, data, pos
|
||||
else:
|
||||
msgbuf.append(kind, data, pos)
|
||||
|
||||
elif not skip and msgbuf and kind is END:
|
||||
msgbuf.append(kind, data, pos)
|
||||
if not msgbuf.depth:
|
||||
for event in msgbuf.translate(translate(msgbuf.format())):
|
||||
yield event
|
||||
msgbuf = None
|
||||
yield kind, data, pos
|
||||
|
||||
elif kind is SUB:
|
||||
subkind, substream = data
|
||||
new_substream = list(self(substream, ctxt, msgbuf=msgbuf))
|
||||
yield kind, (subkind, new_substream), pos
|
||||
|
||||
elif kind is START_NS and data[1] == I18N_NAMESPACE:
|
||||
ns_prefixes.append(data[0])
|
||||
|
||||
elif kind is END_NS and data in ns_prefixes:
|
||||
ns_prefixes.remove(data)
|
||||
|
||||
else:
|
||||
yield kind, data, pos
|
||||
|
||||
GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext', 'dgettext', 'dngettext',
|
||||
'ugettext', 'ungettext')
|
||||
|
||||
def extract(self, stream, gettext_functions=GETTEXT_FUNCTIONS,
|
||||
search_text=True, msgbuf=None):
|
||||
"""Extract localizable strings from the given template stream.
|
||||
|
||||
For every string found, this function yields a ``(lineno, function,
|
||||
message)`` tuple, where:
|
||||
|
||||
* ``lineno`` is the number of the line on which the string was found,
|
||||
* ``function`` is the name of the ``gettext`` function used (if the
|
||||
string was extracted from embedded Python code), and
|
||||
* ``message`` is the string itself (a ``unicode`` object, or a tuple
|
||||
of ``unicode`` objects for functions with multiple string arguments).
|
||||
|
||||
>>> from genshi.template import MarkupTemplate
|
||||
>>>
|
||||
>>> tmpl = MarkupTemplate('''<html xmlns:py="http://genshi.edgewall.org/">
|
||||
... <head>
|
||||
... <title>Example</title>
|
||||
... </head>
|
||||
... <body>
|
||||
... <h1>Example</h1>
|
||||
... <p>${_("Hello, %(name)s") % dict(name=username)}</p>
|
||||
... <p>${ngettext("You have %d item", "You have %d items", num)}</p>
|
||||
... </body>
|
||||
... </html>''', filename='example.html')
|
||||
>>>
|
||||
>>> for lineno, funcname, message in Translator().extract(tmpl.stream):
|
||||
... print "%d, %r, %r" % (lineno, funcname, message)
|
||||
3, None, u'Example'
|
||||
6, None, u'Example'
|
||||
7, '_', u'Hello, %(name)s'
|
||||
8, 'ngettext', (u'You have %d item', u'You have %d items', None)
|
||||
|
||||
:param stream: the event stream to extract strings from; can be a
|
||||
regular stream or a template stream
|
||||
:param gettext_functions: a sequence of function names that should be
|
||||
treated as gettext-style localization
|
||||
functions
|
||||
:param search_text: whether the content of text nodes should be
|
||||
extracted (used internally)
|
||||
|
||||
:note: Changed in 0.4.1: For a function with multiple string arguments
|
||||
(such as ``ngettext``), a single item with a tuple of strings is
|
||||
yielded, instead an item for each string argument.
|
||||
"""
|
||||
if not self.extract_text:
|
||||
search_text = False
|
||||
skip = 0
|
||||
i18n_msg = I18N_NAMESPACE['msg']
|
||||
xml_lang = XML_NAMESPACE['lang']
|
||||
|
||||
for kind, data, pos in stream:
|
||||
|
||||
if skip:
|
||||
if kind is START:
|
||||
skip += 1
|
||||
if kind is END:
|
||||
skip -= 1
|
||||
|
||||
if kind is START and not skip:
|
||||
tag, attrs = data
|
||||
|
||||
if tag in self.ignore_tags or \
|
||||
isinstance(attrs.get(xml_lang), basestring):
|
||||
skip += 1
|
||||
continue
|
||||
|
||||
for name, value in attrs:
|
||||
if search_text and isinstance(value, basestring):
|
||||
if name in self.include_attrs:
|
||||
text = value.strip()
|
||||
if text:
|
||||
yield pos[1], None, text
|
||||
else:
|
||||
for lineno, funcname, text in self.extract(
|
||||
_ensure(value), gettext_functions,
|
||||
search_text=False):
|
||||
yield lineno, funcname, text
|
||||
|
||||
if msgbuf:
|
||||
msgbuf.append(kind, data, pos)
|
||||
elif i18n_msg in attrs:
|
||||
msgbuf = MessageBuffer(pos[1])
|
||||
|
||||
elif not skip and search_text and kind is TEXT:
|
||||
if not msgbuf:
|
||||
text = data.strip()
|
||||
if text and filter(None, [ch.isalpha() for ch in text]):
|
||||
yield pos[1], None, text
|
||||
else:
|
||||
msgbuf.append(kind, data, pos)
|
||||
|
||||
elif not skip and msgbuf and kind is END:
|
||||
msgbuf.append(kind, data, pos)
|
||||
if not msgbuf.depth:
|
||||
yield msgbuf.lineno, None, msgbuf.format()
|
||||
msgbuf = None
|
||||
|
||||
elif kind is EXPR or kind is EXEC:
|
||||
for funcname, strings in extract_from_code(data,
|
||||
gettext_functions):
|
||||
yield pos[1], funcname, strings
|
||||
|
||||
elif kind is SUB:
|
||||
subkind, substream = data
|
||||
messages = self.extract(substream, gettext_functions,
|
||||
search_text=search_text and not skip,
|
||||
msgbuf=msgbuf)
|
||||
for lineno, funcname, text in messages:
|
||||
yield lineno, funcname, text
|
||||
|
||||
|
||||
class MessageBuffer(object):
|
||||
"""Helper class for managing internationalized mixed content.
|
||||
|
||||
:since: version 0.5
|
||||
"""
|
||||
|
||||
def __init__(self, lineno=-1):
|
||||
"""Initialize the message buffer.
|
||||
|
||||
:param lineno: the line number on which the first stream event
|
||||
belonging to the message was found
|
||||
"""
|
||||
self.lineno = lineno
|
||||
self.string = []
|
||||
self.events = {}
|
||||
self.depth = 1
|
||||
self.order = 1
|
||||
self.stack = [0]
|
||||
|
||||
def append(self, kind, data, pos):
|
||||
"""Append a stream event to the buffer.
|
||||
|
||||
:param kind: the stream event kind
|
||||
:param data: the event data
|
||||
:param pos: the position of the event in the source
|
||||
"""
|
||||
if kind is TEXT:
|
||||
self.string.append(data)
|
||||
self.events.setdefault(self.stack[-1], []).append(None)
|
||||
else:
|
||||
if kind is START:
|
||||
self.string.append(u'[%d:' % self.order)
|
||||
self.events.setdefault(self.order, []).append((kind, data, pos))
|
||||
self.stack.append(self.order)
|
||||
self.depth += 1
|
||||
self.order += 1
|
||||
elif kind is END:
|
||||
self.depth -= 1
|
||||
if self.depth:
|
||||
self.events[self.stack[-1]].append((kind, data, pos))
|
||||
self.string.append(u']')
|
||||
self.stack.pop()
|
||||
|
||||
def format(self):
|
||||
"""Return a message identifier representing the content in the
|
||||
buffer.
|
||||
"""
|
||||
return u''.join(self.string).strip()
|
||||
|
||||
def translate(self, string):
|
||||
"""Interpolate the given message translation with the events in the
|
||||
buffer and return the translated stream.
|
||||
|
||||
:param string: the translated message string
|
||||
"""
|
||||
parts = parse_msg(string)
|
||||
for order, string in parts:
|
||||
events = self.events[order]
|
||||
while events:
|
||||
event = self.events[order].pop(0)
|
||||
if not event:
|
||||
if not string:
|
||||
break
|
||||
yield TEXT, string, (None, -1, -1)
|
||||
if not self.events[order] or not self.events[order][0]:
|
||||
break
|
||||
else:
|
||||
yield event
|
||||
|
||||
|
||||
def parse_msg(string, regex=re.compile(r'(?:\[(\d+)\:)|\]')):
|
||||
"""Parse a translated message using Genshi mixed content message
|
||||
formatting.
|
||||
|
||||
>>> parse_msg("See [1:Help].")
|
||||
[(0, 'See '), (1, 'Help'), (0, '.')]
|
||||
|
||||
>>> parse_msg("See [1:our [2:Help] page] for details.")
|
||||
[(0, 'See '), (1, 'our '), (2, 'Help'), (1, ' page'), (0, ' for details.')]
|
||||
|
||||
>>> parse_msg("[2:Details] finden Sie in [1:Hilfe].")
|
||||
[(2, 'Details'), (0, ' finden Sie in '), (1, 'Hilfe'), (0, '.')]
|
||||
|
||||
>>> parse_msg("[1:] Bilder pro Seite anzeigen.")
|
||||
[(1, ''), (0, ' Bilder pro Seite anzeigen.')]
|
||||
|
||||
:param string: the translated message string
|
||||
:return: a list of ``(order, string)`` tuples
|
||||
:rtype: `list`
|
||||
"""
|
||||
parts = []
|
||||
stack = [0]
|
||||
while True:
|
||||
mo = regex.search(string)
|
||||
if not mo:
|
||||
break
|
||||
|
||||
if mo.start() or stack[-1]:
|
||||
parts.append((stack[-1], string[:mo.start()]))
|
||||
string = string[mo.end():]
|
||||
|
||||
orderno = mo.group(1)
|
||||
if orderno is not None:
|
||||
stack.append(int(orderno))
|
||||
else:
|
||||
stack.pop()
|
||||
if not stack:
|
||||
break
|
||||
|
||||
if string:
|
||||
parts.append((stack[-1], string))
|
||||
|
||||
return parts
|
||||
|
||||
def extract_from_code(code, gettext_functions):
|
||||
"""Extract strings from Python bytecode.
|
||||
|
||||
>>> from genshi.template.eval import Expression
|
||||
|
||||
>>> expr = Expression('_("Hello")')
|
||||
>>> list(extract_from_code(expr, Translator.GETTEXT_FUNCTIONS))
|
||||
[('_', u'Hello')]
|
||||
|
||||
>>> expr = Expression('ngettext("You have %(num)s item", '
|
||||
... '"You have %(num)s items", num)')
|
||||
>>> list(extract_from_code(expr, Translator.GETTEXT_FUNCTIONS))
|
||||
[('ngettext', (u'You have %(num)s item', u'You have %(num)s items', None))]
|
||||
|
||||
:param code: the `Code` object
|
||||
:type code: `genshi.template.eval.Code`
|
||||
:param gettext_functions: a sequence of function names
|
||||
:since: version 0.5
|
||||
"""
|
||||
def _walk(node):
|
||||
if isinstance(node, ast.CallFunc) and isinstance(node.node, ast.Name) \
|
||||
and node.node.name in gettext_functions:
|
||||
strings = []
|
||||
def _add(arg):
|
||||
if isinstance(arg, ast.Const) \
|
||||
and isinstance(arg.value, basestring):
|
||||
strings.append(unicode(arg.value, 'utf-8'))
|
||||
elif arg and not isinstance(arg, ast.Keyword):
|
||||
strings.append(None)
|
||||
[_add(arg) for arg in node.args]
|
||||
_add(node.star_args)
|
||||
_add(node.dstar_args)
|
||||
if len(strings) == 1:
|
||||
strings = strings[0]
|
||||
else:
|
||||
strings = tuple(strings)
|
||||
yield node.node.name, strings
|
||||
else:
|
||||
for child in node.getChildNodes():
|
||||
for funcname, strings in _walk(child):
|
||||
yield funcname, strings
|
||||
return _walk(code.ast)
|
||||
|
||||
def extract(fileobj, keywords, comment_tags, options):
|
||||
"""Babel extraction method for Genshi templates.
|
||||
|
||||
:param fileobj: the file-like object the messages should be extracted from
|
||||
:param keywords: a list of keywords (i.e. function names) that should be
|
||||
recognized as translation functions
|
||||
:param comment_tags: a list of translator tags to search for and include
|
||||
in the results
|
||||
:param options: a dictionary of additional options (optional)
|
||||
:return: an iterator over ``(lineno, funcname, message, comments)`` tuples
|
||||
:rtype: ``iterator``
|
||||
"""
|
||||
template_class = options.get('template_class', MarkupTemplate)
|
||||
if isinstance(template_class, basestring):
|
||||
module, clsname = template_class.split(':', 1)
|
||||
template_class = getattr(__import__(module, {}, {}, [clsname]), clsname)
|
||||
encoding = options.get('encoding', None)
|
||||
|
||||
extract_text = options.get('extract_text', True)
|
||||
if isinstance(extract_text, basestring):
|
||||
extract_text = extract_text.lower() in ('1', 'on', 'yes', 'true')
|
||||
|
||||
ignore_tags = options.get('ignore_tags', Translator.IGNORE_TAGS)
|
||||
if isinstance(ignore_tags, basestring):
|
||||
ignore_tags = ignore_tags.split()
|
||||
ignore_tags = [QName(tag) for tag in ignore_tags]
|
||||
|
||||
include_attrs = options.get('include_attrs', Translator.INCLUDE_ATTRS)
|
||||
if isinstance(include_attrs, basestring):
|
||||
include_attrs = include_attrs.split()
|
||||
include_attrs = [QName(attr) for attr in include_attrs]
|
||||
|
||||
tmpl = template_class(fileobj, filename=getattr(fileobj, 'name', None),
|
||||
encoding=encoding)
|
||||
translator = Translator(None, ignore_tags, include_attrs, extract_text)
|
||||
for lineno, func, message in translator.extract(tmpl.stream,
|
||||
gettext_functions=keywords):
|
||||
yield lineno, func, message, []
|
File diff suppressed because it is too large
Load Diff
@ -1,449 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2007 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Support for constructing markup streams from files, strings, or other
|
||||
sources.
|
||||
"""
|
||||
|
||||
from itertools import chain
|
||||
from xml.parsers import expat
|
||||
try:
|
||||
frozenset
|
||||
except NameError:
|
||||
from sets import ImmutableSet as frozenset
|
||||
import HTMLParser as html
|
||||
import htmlentitydefs
|
||||
from StringIO import StringIO
|
||||
|
||||
from calibre.utils.genshi.core import Attrs, QName, Stream, stripentities
|
||||
from calibre.utils.genshi.core import START, END, XML_DECL, DOCTYPE, TEXT, START_NS, END_NS, \
|
||||
START_CDATA, END_CDATA, PI, COMMENT
|
||||
|
||||
__all__ = ['ET', 'ParseError', 'XMLParser', 'XML', 'HTMLParser', 'HTML']
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
def ET(element):
|
||||
"""Convert a given ElementTree element to a markup stream.
|
||||
|
||||
:param element: an ElementTree element
|
||||
:return: a markup stream
|
||||
"""
|
||||
tag_name = QName(element.tag.lstrip('{'))
|
||||
attrs = Attrs([(QName(attr.lstrip('{')), value)
|
||||
for attr, value in element.items()])
|
||||
|
||||
yield START, (tag_name, attrs), (None, -1, -1)
|
||||
if element.text:
|
||||
yield TEXT, element.text, (None, -1, -1)
|
||||
for child in element.getchildren():
|
||||
for item in ET(child):
|
||||
yield item
|
||||
yield END, tag_name, (None, -1, -1)
|
||||
if element.tail:
|
||||
yield TEXT, element.tail, (None, -1, -1)
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
"""Exception raised when fatal syntax errors are found in the input being
|
||||
parsed.
|
||||
"""
|
||||
|
||||
def __init__(self, message, filename=None, lineno=-1, offset=-1):
|
||||
"""Exception initializer.
|
||||
|
||||
:param message: the error message from the parser
|
||||
:param filename: the path to the file that was parsed
|
||||
:param lineno: the number of the line on which the error was encountered
|
||||
:param offset: the column number where the error was encountered
|
||||
"""
|
||||
self.msg = message
|
||||
if filename:
|
||||
message += ', in ' + filename
|
||||
Exception.__init__(self, message)
|
||||
self.filename = filename or '<string>'
|
||||
self.lineno = lineno
|
||||
self.offset = offset
|
||||
|
||||
|
||||
class XMLParser(object):
|
||||
"""Generator-based XML parser based on roughly equivalent code in
|
||||
Kid/ElementTree.
|
||||
|
||||
The parsing is initiated by iterating over the parser object:
|
||||
|
||||
>>> parser = XMLParser(StringIO('<root id="2"><child>Foo</child></root>'))
|
||||
>>> for kind, data, pos in parser:
|
||||
... print kind, data
|
||||
START (QName(u'root'), Attrs([(QName(u'id'), u'2')]))
|
||||
START (QName(u'child'), Attrs())
|
||||
TEXT Foo
|
||||
END child
|
||||
END root
|
||||
"""
|
||||
|
||||
_entitydefs = ['<!ENTITY %s "&#%d;">' % (name, value) for name, value in
|
||||
htmlentitydefs.name2codepoint.items()]
|
||||
_external_dtd = '\n'.join(_entitydefs)
|
||||
|
||||
def __init__(self, source, filename=None, encoding=None):
|
||||
"""Initialize the parser for the given XML input.
|
||||
|
||||
:param source: the XML text as a file-like object
|
||||
:param filename: the name of the file, if appropriate
|
||||
:param encoding: the encoding of the file; if not specified, the
|
||||
encoding is assumed to be ASCII, UTF-8, or UTF-16, or
|
||||
whatever the encoding specified in the XML declaration
|
||||
(if any)
|
||||
"""
|
||||
self.source = source
|
||||
self.filename = filename
|
||||
|
||||
# Setup the Expat parser
|
||||
parser = expat.ParserCreate(encoding, '}')
|
||||
parser.buffer_text = True
|
||||
parser.returns_unicode = True
|
||||
parser.ordered_attributes = True
|
||||
|
||||
parser.StartElementHandler = self._handle_start
|
||||
parser.EndElementHandler = self._handle_end
|
||||
parser.CharacterDataHandler = self._handle_data
|
||||
parser.StartDoctypeDeclHandler = self._handle_doctype
|
||||
parser.StartNamespaceDeclHandler = self._handle_start_ns
|
||||
parser.EndNamespaceDeclHandler = self._handle_end_ns
|
||||
parser.StartCdataSectionHandler = self._handle_start_cdata
|
||||
parser.EndCdataSectionHandler = self._handle_end_cdata
|
||||
parser.ProcessingInstructionHandler = self._handle_pi
|
||||
parser.XmlDeclHandler = self._handle_xml_decl
|
||||
parser.CommentHandler = self._handle_comment
|
||||
|
||||
# Tell Expat that we'll handle non-XML entities ourselves
|
||||
# (in _handle_other)
|
||||
parser.DefaultHandler = self._handle_other
|
||||
parser.SetParamEntityParsing(expat.XML_PARAM_ENTITY_PARSING_ALWAYS)
|
||||
parser.UseForeignDTD()
|
||||
parser.ExternalEntityRefHandler = self._build_foreign
|
||||
|
||||
# Location reporting is only support in Python >= 2.4
|
||||
if not hasattr(parser, 'CurrentLineNumber'):
|
||||
self._getpos = self._getpos_unknown
|
||||
|
||||
self.expat = parser
|
||||
self._queue = []
|
||||
|
||||
def parse(self):
|
||||
"""Generator that parses the XML source, yielding markup events.
|
||||
|
||||
:return: a markup event stream
|
||||
:raises ParseError: if the XML text is not well formed
|
||||
"""
|
||||
def _generate():
|
||||
try:
|
||||
bufsize = 4 * 1024 # 4K
|
||||
done = False
|
||||
while 1:
|
||||
while not done and len(self._queue) == 0:
|
||||
data = self.source.read(bufsize)
|
||||
if data == '': # end of data
|
||||
if hasattr(self, 'expat'):
|
||||
self.expat.Parse('', True)
|
||||
del self.expat # get rid of circular references
|
||||
done = True
|
||||
else:
|
||||
if isinstance(data, unicode):
|
||||
data = data.encode('utf-8')
|
||||
self.expat.Parse(data, False)
|
||||
for event in self._queue:
|
||||
yield event
|
||||
self._queue = []
|
||||
if done:
|
||||
break
|
||||
except expat.ExpatError, e:
|
||||
msg = str(e)
|
||||
raise ParseError(msg, self.filename, e.lineno, e.offset)
|
||||
return Stream(_generate()).filter(_coalesce)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.parse())
|
||||
|
||||
def _build_foreign(self, context, base, sysid, pubid):
|
||||
parser = self.expat.ExternalEntityParserCreate(context)
|
||||
parser.ParseFile(StringIO(self._external_dtd))
|
||||
return 1
|
||||
|
||||
def _enqueue(self, kind, data=None, pos=None):
|
||||
if pos is None:
|
||||
pos = self._getpos()
|
||||
if kind is TEXT:
|
||||
# Expat reports the *end* of the text event as current position. We
|
||||
# try to fix that up here as much as possible. Unfortunately, the
|
||||
# offset is only valid for single-line text. For multi-line text,
|
||||
# it is apparently not possible to determine at what offset it
|
||||
# started
|
||||
if '\n' in data:
|
||||
lines = data.splitlines()
|
||||
lineno = pos[1] - len(lines) + 1
|
||||
offset = -1
|
||||
else:
|
||||
lineno = pos[1]
|
||||
offset = pos[2] - len(data)
|
||||
pos = (pos[0], lineno, offset)
|
||||
self._queue.append((kind, data, pos))
|
||||
|
||||
def _getpos_unknown(self):
|
||||
return (self.filename, -1, -1)
|
||||
|
||||
def _getpos(self):
|
||||
return (self.filename, self.expat.CurrentLineNumber,
|
||||
self.expat.CurrentColumnNumber)
|
||||
|
||||
def _handle_start(self, tag, attrib):
|
||||
attrs = Attrs([(QName(name), value) for name, value in
|
||||
zip(*[iter(attrib)] * 2)])
|
||||
self._enqueue(START, (QName(tag), attrs))
|
||||
|
||||
def _handle_end(self, tag):
|
||||
self._enqueue(END, QName(tag))
|
||||
|
||||
def _handle_data(self, text):
|
||||
self._enqueue(TEXT, text)
|
||||
|
||||
def _handle_xml_decl(self, version, encoding, standalone):
|
||||
self._enqueue(XML_DECL, (version, encoding, standalone))
|
||||
|
||||
def _handle_doctype(self, name, sysid, pubid, has_internal_subset):
|
||||
self._enqueue(DOCTYPE, (name, pubid, sysid))
|
||||
|
||||
def _handle_start_ns(self, prefix, uri):
|
||||
self._enqueue(START_NS, (prefix or '', uri))
|
||||
|
||||
def _handle_end_ns(self, prefix):
|
||||
self._enqueue(END_NS, prefix or '')
|
||||
|
||||
def _handle_start_cdata(self):
|
||||
self._enqueue(START_CDATA)
|
||||
|
||||
def _handle_end_cdata(self):
|
||||
self._enqueue(END_CDATA)
|
||||
|
||||
def _handle_pi(self, target, data):
|
||||
self._enqueue(PI, (target, data))
|
||||
|
||||
def _handle_comment(self, text):
|
||||
self._enqueue(COMMENT, text)
|
||||
|
||||
def _handle_other(self, text):
|
||||
if text.startswith('&'):
|
||||
# deal with undefined entities
|
||||
try:
|
||||
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
|
||||
self._enqueue(TEXT, text)
|
||||
except KeyError:
|
||||
filename, lineno, offset = self._getpos()
|
||||
error = expat.error('undefined entity "%s": line %d, column %d'
|
||||
% (text, lineno, offset))
|
||||
error.code = expat.errors.XML_ERROR_UNDEFINED_ENTITY
|
||||
error.lineno = lineno
|
||||
error.offset = offset
|
||||
raise error
|
||||
|
||||
|
||||
def XML(text):
|
||||
"""Parse the given XML source and return a markup stream.
|
||||
|
||||
Unlike with `XMLParser`, the returned stream is reusable, meaning it can be
|
||||
iterated over multiple times:
|
||||
|
||||
>>> xml = XML('<doc><elem>Foo</elem><elem>Bar</elem></doc>')
|
||||
>>> print xml
|
||||
<doc><elem>Foo</elem><elem>Bar</elem></doc>
|
||||
>>> print xml.select('elem')
|
||||
<elem>Foo</elem><elem>Bar</elem>
|
||||
>>> print xml.select('elem/text()')
|
||||
FooBar
|
||||
|
||||
:param text: the XML source
|
||||
:return: the parsed XML event stream
|
||||
:raises ParseError: if the XML text is not well-formed
|
||||
"""
|
||||
return Stream(list(XMLParser(StringIO(text))))
|
||||
|
||||
|
||||
class HTMLParser(html.HTMLParser, object):
|
||||
"""Parser for HTML input based on the Python `HTMLParser` module.
|
||||
|
||||
This class provides the same interface for generating stream events as
|
||||
`XMLParser`, and attempts to automatically balance tags.
|
||||
|
||||
The parsing is initiated by iterating over the parser object:
|
||||
|
||||
>>> parser = HTMLParser(StringIO('<UL compact><LI>Foo</UL>'))
|
||||
>>> for kind, data, pos in parser:
|
||||
... print kind, data
|
||||
START (QName(u'ul'), Attrs([(QName(u'compact'), u'compact')]))
|
||||
START (QName(u'li'), Attrs())
|
||||
TEXT Foo
|
||||
END li
|
||||
END ul
|
||||
"""
|
||||
|
||||
_EMPTY_ELEMS = frozenset(['area', 'base', 'basefont', 'br', 'col', 'frame',
|
||||
'hr', 'img', 'input', 'isindex', 'link', 'meta',
|
||||
'param'])
|
||||
|
||||
def __init__(self, source, filename=None, encoding='utf-8'):
|
||||
"""Initialize the parser for the given HTML input.
|
||||
|
||||
:param source: the HTML text as a file-like object
|
||||
:param filename: the name of the file, if known
|
||||
:param filename: encoding of the file; ignored if the input is unicode
|
||||
"""
|
||||
html.HTMLParser.__init__(self)
|
||||
self.source = source
|
||||
self.filename = filename
|
||||
self.encoding = encoding
|
||||
self._queue = []
|
||||
self._open_tags = []
|
||||
|
||||
def parse(self):
|
||||
"""Generator that parses the HTML source, yielding markup events.
|
||||
|
||||
:return: a markup event stream
|
||||
:raises ParseError: if the HTML text is not well formed
|
||||
"""
|
||||
def _generate():
|
||||
try:
|
||||
bufsize = 4 * 1024 # 4K
|
||||
done = False
|
||||
while 1:
|
||||
while not done and len(self._queue) == 0:
|
||||
data = self.source.read(bufsize)
|
||||
if data == '': # end of data
|
||||
self.close()
|
||||
done = True
|
||||
else:
|
||||
self.feed(data)
|
||||
for kind, data, pos in self._queue:
|
||||
yield kind, data, pos
|
||||
self._queue = []
|
||||
if done:
|
||||
open_tags = self._open_tags
|
||||
open_tags.reverse()
|
||||
for tag in open_tags:
|
||||
yield END, QName(tag), pos
|
||||
break
|
||||
except html.HTMLParseError, e:
|
||||
msg = '%s: line %d, column %d' % (e.msg, e.lineno, e.offset)
|
||||
raise ParseError(msg, self.filename, e.lineno, e.offset)
|
||||
return Stream(_generate()).filter(_coalesce)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.parse())
|
||||
|
||||
def _enqueue(self, kind, data, pos=None):
|
||||
if pos is None:
|
||||
pos = self._getpos()
|
||||
self._queue.append((kind, data, pos))
|
||||
|
||||
def _getpos(self):
|
||||
lineno, column = self.getpos()
|
||||
return (self.filename, lineno, column)
|
||||
|
||||
def handle_starttag(self, tag, attrib):
|
||||
fixed_attrib = []
|
||||
for name, value in attrib: # Fixup minimized attributes
|
||||
if value is None:
|
||||
value = unicode(name)
|
||||
elif not isinstance(value, unicode):
|
||||
value = value.decode(self.encoding, 'replace')
|
||||
fixed_attrib.append((QName(name), stripentities(value)))
|
||||
|
||||
self._enqueue(START, (QName(tag), Attrs(fixed_attrib)))
|
||||
if tag in self._EMPTY_ELEMS:
|
||||
self._enqueue(END, QName(tag))
|
||||
else:
|
||||
self._open_tags.append(tag)
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag not in self._EMPTY_ELEMS:
|
||||
while self._open_tags:
|
||||
open_tag = self._open_tags.pop()
|
||||
self._enqueue(END, QName(open_tag))
|
||||
if open_tag.lower() == tag.lower():
|
||||
break
|
||||
|
||||
def handle_data(self, text):
|
||||
if not isinstance(text, unicode):
|
||||
text = text.decode(self.encoding, 'replace')
|
||||
self._enqueue(TEXT, text)
|
||||
|
||||
def handle_charref(self, name):
|
||||
if name.lower().startswith('x'):
|
||||
text = unichr(int(name[1:], 16))
|
||||
else:
|
||||
text = unichr(int(name))
|
||||
self._enqueue(TEXT, text)
|
||||
|
||||
def handle_entityref(self, name):
|
||||
try:
|
||||
text = unichr(htmlentitydefs.name2codepoint[name])
|
||||
except KeyError:
|
||||
text = '&%s;' % name
|
||||
self._enqueue(TEXT, text)
|
||||
|
||||
def handle_pi(self, data):
|
||||
target, data = data.split(None, 1)
|
||||
if data.endswith('?'):
|
||||
data = data[:-1]
|
||||
self._enqueue(PI, (target.strip(), data.strip()))
|
||||
|
||||
def handle_comment(self, text):
|
||||
self._enqueue(COMMENT, text)
|
||||
|
||||
|
||||
def HTML(text, encoding='utf-8'):
|
||||
"""Parse the given HTML source and return a markup stream.
|
||||
|
||||
Unlike with `HTMLParser`, the returned stream is reusable, meaning it can be
|
||||
iterated over multiple times:
|
||||
|
||||
>>> html = HTML('<body><h1>Foo</h1></body>')
|
||||
>>> print html
|
||||
<body><h1>Foo</h1></body>
|
||||
>>> print html.select('h1')
|
||||
<h1>Foo</h1>
|
||||
>>> print html.select('h1/text()')
|
||||
Foo
|
||||
|
||||
:param text: the HTML source
|
||||
:return: the parsed XML event stream
|
||||
:raises ParseError: if the HTML text is not well-formed, and error recovery
|
||||
fails
|
||||
"""
|
||||
return Stream(list(HTMLParser(StringIO(text), encoding=encoding)))
|
||||
|
||||
def _coalesce(stream):
|
||||
"""Coalesces adjacent TEXT events into a single event."""
|
||||
textbuf = []
|
||||
textpos = None
|
||||
for kind, data, pos in chain(stream, [(None, None, None)]):
|
||||
if kind is TEXT:
|
||||
textbuf.append(data)
|
||||
if textpos is None:
|
||||
textpos = pos
|
||||
else:
|
||||
if textbuf:
|
||||
yield TEXT, u''.join(textbuf), textpos
|
||||
del textbuf[:]
|
||||
textpos = None
|
||||
if kind:
|
||||
yield kind, data, pos
|
@ -1,773 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""This module provides different kinds of serialization methods for XML event
|
||||
streams.
|
||||
"""
|
||||
|
||||
from itertools import chain
|
||||
try:
|
||||
frozenset
|
||||
except NameError:
|
||||
from sets import ImmutableSet as frozenset
|
||||
import re
|
||||
|
||||
from calibre.utils.genshi.core import escape, Attrs, Markup, Namespace, QName, StreamEventKind
|
||||
from calibre.utils.genshi.core import START, END, TEXT, XML_DECL, DOCTYPE, START_NS, END_NS, \
|
||||
START_CDATA, END_CDATA, PI, COMMENT, XML_NAMESPACE
|
||||
|
||||
__all__ = ['encode', 'get_serializer', 'DocType', 'XMLSerializer',
|
||||
'XHTMLSerializer', 'HTMLSerializer', 'TextSerializer']
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
def encode(iterator, method='xml', encoding='utf-8', out=None):
|
||||
"""Encode serializer output into a string.
|
||||
|
||||
:param iterator: the iterator returned from serializing a stream (basically
|
||||
any iterator that yields unicode objects)
|
||||
:param method: the serialization method; determines how characters not
|
||||
representable in the specified encoding are treated
|
||||
:param encoding: how the output string should be encoded; if set to `None`,
|
||||
this method returns a `unicode` object
|
||||
:param out: a file-like object that the output should be written to
|
||||
instead of being returned as one big string; note that if
|
||||
this is a file or socket (or similar), the `encoding` must
|
||||
not be `None` (that is, the output must be encoded)
|
||||
:return: a `str` or `unicode` object (depending on the `encoding`
|
||||
parameter), or `None` if the `out` parameter is provided
|
||||
|
||||
:since: version 0.4.1
|
||||
:note: Changed in 0.5: added the `out` parameter
|
||||
"""
|
||||
if encoding is not None:
|
||||
errors = 'replace'
|
||||
if method != 'text' and not isinstance(method, TextSerializer):
|
||||
errors = 'xmlcharrefreplace'
|
||||
_encode = lambda string: string.encode(encoding, errors)
|
||||
else:
|
||||
_encode = lambda string: string
|
||||
if out is None:
|
||||
return _encode(u''.join(list(iterator)))
|
||||
for chunk in iterator:
|
||||
out.write(_encode(chunk))
|
||||
|
||||
def get_serializer(method='xml', **kwargs):
|
||||
"""Return a serializer object for the given method.
|
||||
|
||||
:param method: the serialization method; can be either "xml", "xhtml",
|
||||
"html", "text", or a custom serializer class
|
||||
|
||||
Any additional keyword arguments are passed to the serializer, and thus
|
||||
depend on the `method` parameter value.
|
||||
|
||||
:see: `XMLSerializer`, `XHTMLSerializer`, `HTMLSerializer`, `TextSerializer`
|
||||
:since: version 0.4.1
|
||||
"""
|
||||
if isinstance(method, basestring):
|
||||
method = {'xml': XMLSerializer,
|
||||
'xhtml': XHTMLSerializer,
|
||||
'html': HTMLSerializer,
|
||||
'text': TextSerializer}[method.lower()]
|
||||
return method(**kwargs)
|
||||
|
||||
|
||||
class DocType(object):
|
||||
"""Defines a number of commonly used DOCTYPE declarations as constants."""
|
||||
|
||||
HTML_STRICT = (
|
||||
'html', '-//W3C//DTD HTML 4.01//EN',
|
||||
'http://www.w3.org/TR/html4/strict.dtd'
|
||||
)
|
||||
HTML_TRANSITIONAL = (
|
||||
'html', '-//W3C//DTD HTML 4.01 Transitional//EN',
|
||||
'http://www.w3.org/TR/html4/loose.dtd'
|
||||
)
|
||||
HTML_FRAMESET = (
|
||||
'html', '-//W3C//DTD HTML 4.01 Frameset//EN',
|
||||
'http://www.w3.org/TR/html4/frameset.dtd'
|
||||
)
|
||||
HTML = HTML_STRICT
|
||||
|
||||
HTML5 = ('html', None, None)
|
||||
|
||||
XHTML_STRICT = (
|
||||
'html', '-//W3C//DTD XHTML 1.0 Strict//EN',
|
||||
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
|
||||
)
|
||||
XHTML_TRANSITIONAL = (
|
||||
'html', '-//W3C//DTD XHTML 1.0 Transitional//EN',
|
||||
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
|
||||
)
|
||||
XHTML_FRAMESET = (
|
||||
'html', '-//W3C//DTD XHTML 1.0 Frameset//EN',
|
||||
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd'
|
||||
)
|
||||
XHTML = XHTML_STRICT
|
||||
|
||||
XHTML11 = (
|
||||
'html', '-//W3C//DTD XHTML 1.1//EN',
|
||||
'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
|
||||
)
|
||||
|
||||
SVG_FULL = (
|
||||
'svg', '-//W3C//DTD SVG 1.1//EN',
|
||||
'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'
|
||||
)
|
||||
SVG_BASIC = (
|
||||
'svg', '-//W3C//DTD SVG Basic 1.1//EN',
|
||||
'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd'
|
||||
)
|
||||
SVG_TINY = (
|
||||
'svg', '-//W3C//DTD SVG Tiny 1.1//EN',
|
||||
'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd'
|
||||
)
|
||||
SVG = SVG_FULL
|
||||
|
||||
def get(cls, name):
|
||||
"""Return the ``(name, pubid, sysid)`` tuple of the ``DOCTYPE``
|
||||
declaration for the specified name.
|
||||
|
||||
The following names are recognized in this version:
|
||||
* "html" or "html-strict" for the HTML 4.01 strict DTD
|
||||
* "html-transitional" for the HTML 4.01 transitional DTD
|
||||
* "html-frameset" for the HTML 4.01 frameset DTD
|
||||
* "html5" for the ``DOCTYPE`` proposed for HTML5
|
||||
* "xhtml" or "xhtml-strict" for the XHTML 1.0 strict DTD
|
||||
* "xhtml-transitional" for the XHTML 1.0 transitional DTD
|
||||
* "xhtml-frameset" for the XHTML 1.0 frameset DTD
|
||||
* "xhtml11" for the XHTML 1.1 DTD
|
||||
* "svg" or "svg-full" for the SVG 1.1 DTD
|
||||
* "svg-basic" for the SVG Basic 1.1 DTD
|
||||
* "svg-tiny" for the SVG Tiny 1.1 DTD
|
||||
|
||||
:param name: the name of the ``DOCTYPE``
|
||||
:return: the ``(name, pubid, sysid)`` tuple for the requested
|
||||
``DOCTYPE``, or ``None`` if the name is not recognized
|
||||
:since: version 0.4.1
|
||||
"""
|
||||
return {
|
||||
'html': cls.HTML, 'html-strict': cls.HTML_STRICT,
|
||||
'html-transitional': DocType.HTML_TRANSITIONAL,
|
||||
'html-frameset': DocType.HTML_FRAMESET,
|
||||
'html5': cls.HTML5,
|
||||
'xhtml': cls.XHTML, 'xhtml-strict': cls.XHTML_STRICT,
|
||||
'xhtml-transitional': cls.XHTML_TRANSITIONAL,
|
||||
'xhtml-frameset': cls.XHTML_FRAMESET,
|
||||
'xhtml11': cls.XHTML11,
|
||||
'svg': cls.SVG, 'svg-full': cls.SVG_FULL,
|
||||
'svg-basic': cls.SVG_BASIC,
|
||||
'svg-tiny': cls.SVG_TINY
|
||||
}.get(name.lower())
|
||||
get = classmethod(get)
|
||||
|
||||
|
||||
class XMLSerializer(object):
|
||||
"""Produces XML text from an event stream.
|
||||
|
||||
>>> from genshi.builder import tag
|
||||
>>> elem = tag.div(tag.a(href='foo'), tag.br, tag.hr(noshade=True))
|
||||
>>> print ''.join(XMLSerializer()(elem.generate()))
|
||||
<div><a href="foo"/><br/><hr noshade="True"/></div>
|
||||
"""
|
||||
|
||||
_PRESERVE_SPACE = frozenset()
|
||||
|
||||
def __init__(self, doctype=None, strip_whitespace=True,
|
||||
namespace_prefixes=None):
|
||||
"""Initialize the XML serializer.
|
||||
|
||||
:param doctype: a ``(name, pubid, sysid)`` tuple that represents the
|
||||
DOCTYPE declaration that should be included at the top
|
||||
of the generated output, or the name of a DOCTYPE as
|
||||
defined in `DocType.get`
|
||||
:param strip_whitespace: whether extraneous whitespace should be
|
||||
stripped from the output
|
||||
:note: Changed in 0.4.2: The `doctype` parameter can now be a string.
|
||||
"""
|
||||
self.filters = [EmptyTagFilter()]
|
||||
if strip_whitespace:
|
||||
self.filters.append(WhitespaceFilter(self._PRESERVE_SPACE))
|
||||
self.filters.append(NamespaceFlattener(prefixes=namespace_prefixes))
|
||||
if doctype:
|
||||
self.filters.append(DocTypeInserter(doctype))
|
||||
|
||||
def __call__(self, stream):
|
||||
have_decl = have_doctype = False
|
||||
in_cdata = False
|
||||
|
||||
for filter_ in self.filters:
|
||||
stream = filter_(stream)
|
||||
for kind, data, pos in stream:
|
||||
|
||||
if kind is START or kind is EMPTY:
|
||||
tag, attrib = data
|
||||
buf = ['<', tag]
|
||||
for attr, value in attrib:
|
||||
buf += [' ', attr, '="', escape(value), '"']
|
||||
buf.append(kind is EMPTY and '/>' or '>')
|
||||
yield Markup(u''.join(buf))
|
||||
|
||||
elif kind is END:
|
||||
yield Markup('</%s>' % data)
|
||||
|
||||
elif kind is TEXT:
|
||||
if in_cdata:
|
||||
yield data
|
||||
else:
|
||||
yield escape(data, quotes=False)
|
||||
|
||||
elif kind is COMMENT:
|
||||
yield Markup('<!--%s-->' % data)
|
||||
|
||||
elif kind is XML_DECL and not have_decl:
|
||||
version, encoding, standalone = data
|
||||
buf = ['<?xml version="%s"' % version]
|
||||
if encoding:
|
||||
buf.append(' encoding="%s"' % encoding)
|
||||
if standalone != -1:
|
||||
standalone = standalone and 'yes' or 'no'
|
||||
buf.append(' standalone="%s"' % standalone)
|
||||
buf.append('?>\n')
|
||||
yield Markup(u''.join(buf))
|
||||
have_decl = True
|
||||
|
||||
elif kind is DOCTYPE and not have_doctype:
|
||||
name, pubid, sysid = data
|
||||
buf = ['<!DOCTYPE %s']
|
||||
if pubid:
|
||||
buf.append(' PUBLIC "%s"')
|
||||
elif sysid:
|
||||
buf.append(' SYSTEM')
|
||||
if sysid:
|
||||
buf.append(' "%s"')
|
||||
buf.append('>\n')
|
||||
yield Markup(u''.join(buf)) % filter(None, data)
|
||||
have_doctype = True
|
||||
|
||||
elif kind is START_CDATA:
|
||||
yield Markup('<![CDATA[')
|
||||
in_cdata = True
|
||||
|
||||
elif kind is END_CDATA:
|
||||
yield Markup(']]>')
|
||||
in_cdata = False
|
||||
|
||||
elif kind is PI:
|
||||
yield Markup('<?%s %s?>' % data)
|
||||
|
||||
|
||||
class XHTMLSerializer(XMLSerializer):
|
||||
"""Produces XHTML text from an event stream.
|
||||
|
||||
>>> from genshi.builder import tag
|
||||
>>> elem = tag.div(tag.a(href='foo'), tag.br, tag.hr(noshade=True))
|
||||
>>> print ''.join(XHTMLSerializer()(elem.generate()))
|
||||
<div><a href="foo"></a><br /><hr noshade="noshade" /></div>
|
||||
"""
|
||||
|
||||
_EMPTY_ELEMS = frozenset(['area', 'base', 'basefont', 'br', 'col', 'frame',
|
||||
'hr', 'img', 'input', 'isindex', 'link', 'meta',
|
||||
'param'])
|
||||
_BOOLEAN_ATTRS = frozenset(['selected', 'checked', 'compact', 'declare',
|
||||
'defer', 'disabled', 'ismap', 'multiple',
|
||||
'nohref', 'noresize', 'noshade', 'nowrap'])
|
||||
_PRESERVE_SPACE = frozenset([
|
||||
QName('pre'), QName('http://www.w3.org/1999/xhtml}pre'),
|
||||
QName('textarea'), QName('http://www.w3.org/1999/xhtml}textarea')
|
||||
])
|
||||
|
||||
def __init__(self, doctype=None, strip_whitespace=True,
|
||||
namespace_prefixes=None, drop_xml_decl=True):
|
||||
super(XHTMLSerializer, self).__init__(doctype, False)
|
||||
self.filters = [EmptyTagFilter()]
|
||||
if strip_whitespace:
|
||||
self.filters.append(WhitespaceFilter(self._PRESERVE_SPACE))
|
||||
namespace_prefixes = namespace_prefixes or {}
|
||||
namespace_prefixes['http://www.w3.org/1999/xhtml'] = ''
|
||||
self.filters.append(NamespaceFlattener(prefixes=namespace_prefixes))
|
||||
if doctype:
|
||||
self.filters.append(DocTypeInserter(doctype))
|
||||
self.drop_xml_decl = drop_xml_decl
|
||||
|
||||
def __call__(self, stream):
|
||||
boolean_attrs = self._BOOLEAN_ATTRS
|
||||
empty_elems = self._EMPTY_ELEMS
|
||||
drop_xml_decl = self.drop_xml_decl
|
||||
have_decl = have_doctype = False
|
||||
in_cdata = False
|
||||
|
||||
for filter_ in self.filters:
|
||||
stream = filter_(stream)
|
||||
for kind, data, pos in stream:
|
||||
|
||||
if kind is START or kind is EMPTY:
|
||||
tag, attrib = data
|
||||
buf = ['<', tag]
|
||||
for attr, value in attrib:
|
||||
if attr in boolean_attrs:
|
||||
value = attr
|
||||
elif attr == u'xml:lang' and u'lang' not in attrib:
|
||||
buf += [' lang="', escape(value), '"']
|
||||
elif attr == u'xml:space':
|
||||
continue
|
||||
buf += [' ', attr, '="', escape(value), '"']
|
||||
if kind is EMPTY:
|
||||
if tag in empty_elems:
|
||||
buf.append(' />')
|
||||
else:
|
||||
buf.append('></%s>' % tag)
|
||||
else:
|
||||
buf.append('>')
|
||||
yield Markup(u''.join(buf))
|
||||
|
||||
elif kind is END:
|
||||
yield Markup('</%s>' % data)
|
||||
|
||||
elif kind is TEXT:
|
||||
if in_cdata:
|
||||
yield data
|
||||
else:
|
||||
yield escape(data, quotes=False)
|
||||
|
||||
elif kind is COMMENT:
|
||||
yield Markup('<!--%s-->' % data)
|
||||
|
||||
elif kind is DOCTYPE and not have_doctype:
|
||||
name, pubid, sysid = data
|
||||
buf = ['<!DOCTYPE %s']
|
||||
if pubid:
|
||||
buf.append(' PUBLIC "%s"')
|
||||
elif sysid:
|
||||
buf.append(' SYSTEM')
|
||||
if sysid:
|
||||
buf.append(' "%s"')
|
||||
buf.append('>\n')
|
||||
yield Markup(u''.join(buf)) % filter(None, data)
|
||||
have_doctype = True
|
||||
|
||||
elif kind is XML_DECL and not have_decl and not drop_xml_decl:
|
||||
version, encoding, standalone = data
|
||||
buf = ['<?xml version="%s"' % version]
|
||||
if encoding:
|
||||
buf.append(' encoding="%s"' % encoding)
|
||||
if standalone != -1:
|
||||
standalone = standalone and 'yes' or 'no'
|
||||
buf.append(' standalone="%s"' % standalone)
|
||||
buf.append('?>\n')
|
||||
yield Markup(u''.join(buf))
|
||||
have_decl = True
|
||||
|
||||
elif kind is START_CDATA:
|
||||
yield Markup('<![CDATA[')
|
||||
in_cdata = True
|
||||
|
||||
elif kind is END_CDATA:
|
||||
yield Markup(']]>')
|
||||
in_cdata = False
|
||||
|
||||
elif kind is PI:
|
||||
yield Markup('<?%s %s?>' % data)
|
||||
|
||||
|
||||
class HTMLSerializer(XHTMLSerializer):
|
||||
"""Produces HTML text from an event stream.
|
||||
|
||||
>>> from genshi.builder import tag
|
||||
>>> elem = tag.div(tag.a(href='foo'), tag.br, tag.hr(noshade=True))
|
||||
>>> print ''.join(HTMLSerializer()(elem.generate()))
|
||||
<div><a href="foo"></a><br><hr noshade></div>
|
||||
"""
|
||||
|
||||
_NOESCAPE_ELEMS = frozenset([
|
||||
QName('script'), QName('http://www.w3.org/1999/xhtml}script'),
|
||||
QName('style'), QName('http://www.w3.org/1999/xhtml}style')
|
||||
])
|
||||
|
||||
def __init__(self, doctype=None, strip_whitespace=True):
|
||||
"""Initialize the HTML serializer.
|
||||
|
||||
:param doctype: a ``(name, pubid, sysid)`` tuple that represents the
|
||||
DOCTYPE declaration that should be included at the top
|
||||
of the generated output
|
||||
:param strip_whitespace: whether extraneous whitespace should be
|
||||
stripped from the output
|
||||
"""
|
||||
super(HTMLSerializer, self).__init__(doctype, False)
|
||||
self.filters = [EmptyTagFilter()]
|
||||
if strip_whitespace:
|
||||
self.filters.append(WhitespaceFilter(self._PRESERVE_SPACE,
|
||||
self._NOESCAPE_ELEMS))
|
||||
self.filters.append(NamespaceFlattener(prefixes={
|
||||
'http://www.w3.org/1999/xhtml': ''
|
||||
}))
|
||||
if doctype:
|
||||
self.filters.append(DocTypeInserter(doctype))
|
||||
|
||||
def __call__(self, stream):
|
||||
boolean_attrs = self._BOOLEAN_ATTRS
|
||||
empty_elems = self._EMPTY_ELEMS
|
||||
noescape_elems = self._NOESCAPE_ELEMS
|
||||
have_doctype = False
|
||||
noescape = False
|
||||
|
||||
for filter_ in self.filters:
|
||||
stream = filter_(stream)
|
||||
for kind, data, pos in stream:
|
||||
|
||||
if kind is START or kind is EMPTY:
|
||||
tag, attrib = data
|
||||
buf = ['<', tag]
|
||||
for attr, value in attrib:
|
||||
if attr in boolean_attrs:
|
||||
if value:
|
||||
buf += [' ', attr]
|
||||
elif ':' in attr:
|
||||
if attr == 'xml:lang' and u'lang' not in attrib:
|
||||
buf += [' lang="', escape(value), '"']
|
||||
elif attr != 'xmlns':
|
||||
buf += [' ', attr, '="', escape(value), '"']
|
||||
buf.append('>')
|
||||
if kind is EMPTY:
|
||||
if tag not in empty_elems:
|
||||
buf.append('</%s>' % tag)
|
||||
yield Markup(u''.join(buf))
|
||||
if tag in noescape_elems:
|
||||
noescape = True
|
||||
|
||||
elif kind is END:
|
||||
yield Markup('</%s>' % data)
|
||||
noescape = False
|
||||
|
||||
elif kind is TEXT:
|
||||
if noescape:
|
||||
yield data
|
||||
else:
|
||||
yield escape(data, quotes=False)
|
||||
|
||||
elif kind is COMMENT:
|
||||
yield Markup('<!--%s-->' % data)
|
||||
|
||||
elif kind is DOCTYPE and not have_doctype:
|
||||
name, pubid, sysid = data
|
||||
buf = ['<!DOCTYPE %s']
|
||||
if pubid:
|
||||
buf.append(' PUBLIC "%s"')
|
||||
elif sysid:
|
||||
buf.append(' SYSTEM')
|
||||
if sysid:
|
||||
buf.append(' "%s"')
|
||||
buf.append('>\n')
|
||||
yield Markup(u''.join(buf)) % filter(None, data)
|
||||
have_doctype = True
|
||||
|
||||
elif kind is PI:
|
||||
yield Markup('<?%s %s?>' % data)
|
||||
|
||||
|
||||
class TextSerializer(object):
|
||||
"""Produces plain text from an event stream.
|
||||
|
||||
Only text events are included in the output. Unlike the other serializer,
|
||||
special XML characters are not escaped:
|
||||
|
||||
>>> from genshi.builder import tag
|
||||
>>> elem = tag.div(tag.a('<Hello!>', href='foo'), tag.br)
|
||||
>>> print elem
|
||||
<div><a href="foo"><Hello!></a><br/></div>
|
||||
>>> print ''.join(TextSerializer()(elem.generate()))
|
||||
<Hello!>
|
||||
|
||||
If text events contain literal markup (instances of the `Markup` class),
|
||||
that markup is by default passed through unchanged:
|
||||
|
||||
>>> elem = tag.div(Markup('<a href="foo">Hello & Bye!</a><br/>'))
|
||||
>>> print elem.generate().render(TextSerializer)
|
||||
<a href="foo">Hello & Bye!</a><br/>
|
||||
|
||||
You can use the ``strip_markup`` to change this behavior, so that tags and
|
||||
entities are stripped from the output (or in the case of entities,
|
||||
replaced with the equivalent character):
|
||||
|
||||
>>> print elem.generate().render(TextSerializer, strip_markup=True)
|
||||
Hello & Bye!
|
||||
"""
|
||||
|
||||
def __init__(self, strip_markup=False):
|
||||
"""Create the serializer.
|
||||
|
||||
:param strip_markup: whether markup (tags and encoded characters) found
|
||||
in the text should be removed
|
||||
"""
|
||||
self.strip_markup = strip_markup
|
||||
|
||||
def __call__(self, stream):
|
||||
strip_markup = self.strip_markup
|
||||
for event in stream:
|
||||
if event[0] is TEXT:
|
||||
data = event[1]
|
||||
if strip_markup and type(data) is Markup:
|
||||
data = data.striptags().stripentities()
|
||||
yield unicode(data)
|
||||
|
||||
|
||||
class EmptyTagFilter(object):
|
||||
"""Combines `START` and `STOP` events into `EMPTY` events for elements that
|
||||
have no contents.
|
||||
"""
|
||||
|
||||
EMPTY = StreamEventKind('EMPTY')
|
||||
|
||||
def __call__(self, stream):
|
||||
prev = (None, None, None)
|
||||
for ev in stream:
|
||||
if prev[0] is START:
|
||||
if ev[0] is END:
|
||||
prev = EMPTY, prev[1], prev[2]
|
||||
yield prev
|
||||
continue
|
||||
else:
|
||||
yield prev
|
||||
if ev[0] is not START:
|
||||
yield ev
|
||||
prev = ev
|
||||
|
||||
|
||||
EMPTY = EmptyTagFilter.EMPTY
|
||||
|
||||
|
||||
class NamespaceFlattener(object):
|
||||
r"""Output stream filter that removes namespace information from the stream,
|
||||
instead adding namespace attributes and prefixes as needed.
|
||||
|
||||
:param prefixes: optional mapping of namespace URIs to prefixes
|
||||
|
||||
>>> from genshi.input import XML
|
||||
>>> xml = XML('''<doc xmlns="NS1" xmlns:two="NS2">
|
||||
... <two:item/>
|
||||
... </doc>''')
|
||||
>>> for kind, data, pos in NamespaceFlattener()(xml):
|
||||
... print kind, repr(data)
|
||||
START (u'doc', Attrs([(u'xmlns', u'NS1'), (u'xmlns:two', u'NS2')]))
|
||||
TEXT u'\n '
|
||||
START (u'two:item', Attrs())
|
||||
END u'two:item'
|
||||
TEXT u'\n'
|
||||
END u'doc'
|
||||
"""
|
||||
|
||||
def __init__(self, prefixes=None):
|
||||
self.prefixes = {XML_NAMESPACE.uri: 'xml'}
|
||||
if prefixes is not None:
|
||||
self.prefixes.update(prefixes)
|
||||
|
||||
def __call__(self, stream):
|
||||
prefixes = dict([(v, [k]) for k, v in self.prefixes.items()])
|
||||
namespaces = {XML_NAMESPACE.uri: ['xml']}
|
||||
default = prefixes.get('', [''])
|
||||
def _push_ns(prefix, uri):
|
||||
namespaces.setdefault(uri, []).append(prefix)
|
||||
prefixes.setdefault(prefix, []).append(uri)
|
||||
|
||||
ns_attrs = []
|
||||
_push_ns_attr = ns_attrs.append
|
||||
def _make_ns_attr(prefix, uri):
|
||||
return u'xmlns%s' % (prefix and ':%s' % prefix or ''), uri
|
||||
|
||||
def _gen_prefix():
|
||||
val = 0
|
||||
while 1:
|
||||
val += 1
|
||||
yield 'ns%d' % val
|
||||
_gen_prefix = _gen_prefix().next
|
||||
|
||||
for kind, data, pos in stream:
|
||||
|
||||
if kind is START or kind is EMPTY:
|
||||
tag, attrs = data
|
||||
|
||||
tagname = tag.localname
|
||||
tagns = tag.namespace
|
||||
if tagns and tagns != default[-1]:
|
||||
if tagns in namespaces:
|
||||
prefix = namespaces[tagns][-1]
|
||||
if prefix:
|
||||
tagname = u'%s:%s' % (prefix, tagname)
|
||||
else:
|
||||
_push_ns_attr((u'xmlns', tagns))
|
||||
default.push(tagns)
|
||||
|
||||
new_attrs = []
|
||||
for attr, value in attrs:
|
||||
attrname = attr.localname
|
||||
attrns = attr.namespace
|
||||
if attrns:
|
||||
if attrns not in namespaces:
|
||||
prefix = _gen_prefix()
|
||||
_push_ns(prefix, attrns)
|
||||
_push_ns_attr(('xmlns:%s' % prefix, attrns))
|
||||
else:
|
||||
prefix = namespaces[attrns][-1]
|
||||
if prefix:
|
||||
attrname = u'%s:%s' % (prefix, attrname)
|
||||
new_attrs.append((attrname, value))
|
||||
|
||||
yield kind, (tagname, Attrs(ns_attrs + new_attrs)), pos
|
||||
del ns_attrs[:]
|
||||
|
||||
elif kind is END:
|
||||
tagname = data.localname
|
||||
tagns = data.namespace
|
||||
if tagns and tagns != default[-1]:
|
||||
prefix = namespaces[tagns][-1]
|
||||
if prefix:
|
||||
tagname = u'%s:%s' % (prefix, tagname)
|
||||
yield kind, tagname, pos
|
||||
|
||||
elif kind is START_NS:
|
||||
prefix, uri = data
|
||||
push_attr = False
|
||||
if prefix is '' and default[-1] != uri:
|
||||
default.append(uri)
|
||||
_push_ns_attr(_make_ns_attr(prefix, uri))
|
||||
elif uri not in namespaces:
|
||||
prefix = namespaces.get(uri, [prefix])[-1]
|
||||
_push_ns_attr(_make_ns_attr(prefix, uri))
|
||||
if prefix is not '':
|
||||
_push_ns(prefix, uri)
|
||||
|
||||
elif kind is END_NS:
|
||||
if data is '':
|
||||
default.pop()
|
||||
if data in prefixes and prefixes.get(data):
|
||||
uris = prefixes.get(data)
|
||||
uri = uris.pop()
|
||||
if not uris:
|
||||
del prefixes[data]
|
||||
if uri not in uris or uri != uris[-1]:
|
||||
uri_prefixes = namespaces[uri]
|
||||
uri_prefixes.pop()
|
||||
if not uri_prefixes:
|
||||
del namespaces[uri]
|
||||
if ns_attrs:
|
||||
attr = _make_ns_attr(data, uri)
|
||||
if attr in ns_attrs:
|
||||
ns_attrs.remove(attr)
|
||||
|
||||
else:
|
||||
yield kind, data, pos
|
||||
|
||||
|
||||
class WhitespaceFilter(object):
|
||||
"""A filter that removes extraneous ignorable white space from the
|
||||
stream.
|
||||
"""
|
||||
|
||||
def __init__(self, preserve=None, noescape=None):
|
||||
"""Initialize the filter.
|
||||
|
||||
:param preserve: a set or sequence of tag names for which white-space
|
||||
should be preserved
|
||||
:param noescape: a set or sequence of tag names for which text content
|
||||
should not be escaped
|
||||
|
||||
The `noescape` set is expected to refer to elements that cannot contain
|
||||
further child elements (such as ``<style>`` or ``<script>`` in HTML
|
||||
documents).
|
||||
"""
|
||||
if preserve is None:
|
||||
preserve = []
|
||||
self.preserve = frozenset(preserve)
|
||||
if noescape is None:
|
||||
noescape = []
|
||||
self.noescape = frozenset(noescape)
|
||||
|
||||
def __call__(self, stream, ctxt=None, space=XML_NAMESPACE['space'],
|
||||
trim_trailing_space=re.compile('[ \t]+(?=\n)').sub,
|
||||
collapse_lines=re.compile('\n{2,}').sub):
|
||||
mjoin = Markup('').join
|
||||
preserve_elems = self.preserve
|
||||
preserve = 0
|
||||
noescape_elems = self.noescape
|
||||
noescape = False
|
||||
|
||||
textbuf = []
|
||||
push_text = textbuf.append
|
||||
pop_text = textbuf.pop
|
||||
for kind, data, pos in chain(stream, [(None, None, None)]):
|
||||
|
||||
if kind is TEXT:
|
||||
if noescape:
|
||||
data = Markup(data)
|
||||
push_text(data)
|
||||
else:
|
||||
if textbuf:
|
||||
if len(textbuf) > 1:
|
||||
text = mjoin(textbuf, escape_quotes=False)
|
||||
del textbuf[:]
|
||||
else:
|
||||
text = escape(pop_text(), quotes=False)
|
||||
if not preserve:
|
||||
text = collapse_lines('\n', trim_trailing_space('', text))
|
||||
yield TEXT, Markup(text), pos
|
||||
|
||||
if kind is START:
|
||||
tag, attrs = data
|
||||
if preserve or (tag in preserve_elems or
|
||||
attrs.get(space) == 'preserve'):
|
||||
preserve += 1
|
||||
if not noescape and tag in noescape_elems:
|
||||
noescape = True
|
||||
|
||||
elif kind is END:
|
||||
noescape = False
|
||||
if preserve:
|
||||
preserve -= 1
|
||||
|
||||
elif kind is START_CDATA:
|
||||
noescape = True
|
||||
|
||||
elif kind is END_CDATA:
|
||||
noescape = False
|
||||
|
||||
if kind:
|
||||
yield kind, data, pos
|
||||
|
||||
|
||||
class DocTypeInserter(object):
|
||||
"""A filter that inserts the DOCTYPE declaration in the correct location,
|
||||
after the XML declaration.
|
||||
"""
|
||||
def __init__(self, doctype):
|
||||
"""Initialize the filter.
|
||||
|
||||
:param doctype: DOCTYPE as a string or DocType object.
|
||||
"""
|
||||
if isinstance(doctype, basestring):
|
||||
doctype = DocType.get(doctype)
|
||||
self.doctype_event = (DOCTYPE, doctype, (None, -1, -1))
|
||||
|
||||
def __call__(self, stream):
|
||||
doctype_inserted = False
|
||||
for kind, data, pos in stream:
|
||||
if not doctype_inserted:
|
||||
doctype_inserted = True
|
||||
if kind is XML_DECL:
|
||||
yield (kind, data, pos)
|
||||
yield self.doctype_event
|
||||
continue
|
||||
yield self.doctype_event
|
||||
|
||||
yield (kind, data, pos)
|
||||
|
||||
if not doctype_inserted:
|
||||
yield self.doctype_event
|
File diff suppressed because it is too large
Load Diff
@ -1,23 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2007 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Implementation of the template engine."""
|
||||
|
||||
from calibre.utils.genshi.template.base import Context, Template, TemplateError, \
|
||||
TemplateRuntimeError, TemplateSyntaxError, \
|
||||
BadDirectiveError
|
||||
from calibre.utils.genshi.template.loader import TemplateLoader, TemplateNotFound
|
||||
from calibre.utils.genshi.template.markup import MarkupTemplate
|
||||
from calibre.utils.genshi.template.text import TextTemplate, OldTextTemplate, NewTextTemplate
|
||||
|
||||
__docformat__ = 'restructuredtext en'
|
@ -1,601 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Basic templating functionality."""
|
||||
|
||||
try:
|
||||
from collections import deque
|
||||
except ImportError:
|
||||
class deque(list):
|
||||
def appendleft(self, x): self.insert(0, x)
|
||||
def popleft(self): return self.pop(0)
|
||||
import os
|
||||
from StringIO import StringIO
|
||||
import sys
|
||||
|
||||
from calibre.utils.genshi.core import Attrs, Stream, StreamEventKind, START, TEXT, _ensure
|
||||
from calibre.utils.genshi.input import ParseError
|
||||
|
||||
__all__ = ['Context', 'Template', 'TemplateError', 'TemplateRuntimeError',
|
||||
'TemplateSyntaxError', 'BadDirectiveError']
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
if sys.version_info < (2, 4):
|
||||
_ctxt2dict = lambda ctxt: ctxt.frames[0]
|
||||
else:
|
||||
_ctxt2dict = lambda ctxt: ctxt
|
||||
|
||||
|
||||
class TemplateError(Exception):
|
||||
"""Base exception class for errors related to template processing."""
|
||||
|
||||
def __init__(self, message, filename=None, lineno=-1, offset=-1):
|
||||
"""Create the exception.
|
||||
|
||||
:param message: the error message
|
||||
:param filename: the filename of the template
|
||||
:param lineno: the number of line in the template at which the error
|
||||
occurred
|
||||
:param offset: the column number at which the error occurred
|
||||
"""
|
||||
if filename is None:
|
||||
filename = '<string>'
|
||||
self.msg = message #: the error message string
|
||||
if filename != '<string>' or lineno >= 0:
|
||||
message = '%s (%s, line %d)' % (self.msg, filename, lineno)
|
||||
Exception.__init__(self, message)
|
||||
self.filename = filename #: the name of the template file
|
||||
self.lineno = lineno #: the number of the line containing the error
|
||||
self.offset = offset #: the offset on the line
|
||||
|
||||
|
||||
class TemplateSyntaxError(TemplateError):
|
||||
"""Exception raised when an expression in a template causes a Python syntax
|
||||
error, or the template is not well-formed.
|
||||
"""
|
||||
|
||||
def __init__(self, message, filename=None, lineno=-1, offset=-1):
|
||||
"""Create the exception
|
||||
|
||||
:param message: the error message
|
||||
:param filename: the filename of the template
|
||||
:param lineno: the number of line in the template at which the error
|
||||
occurred
|
||||
:param offset: the column number at which the error occurred
|
||||
"""
|
||||
if isinstance(message, SyntaxError) and message.lineno is not None:
|
||||
message = str(message).replace(' (line %d)' % message.lineno, '')
|
||||
TemplateError.__init__(self, message, filename, lineno)
|
||||
|
||||
|
||||
class BadDirectiveError(TemplateSyntaxError):
|
||||
"""Exception raised when an unknown directive is encountered when parsing
|
||||
a template.
|
||||
|
||||
An unknown directive is any attribute using the namespace for directives,
|
||||
with a local name that doesn't match any registered directive.
|
||||
"""
|
||||
|
||||
def __init__(self, name, filename=None, lineno=-1):
|
||||
"""Create the exception
|
||||
|
||||
:param name: the name of the directive
|
||||
:param filename: the filename of the template
|
||||
:param lineno: the number of line in the template at which the error
|
||||
occurred
|
||||
"""
|
||||
TemplateSyntaxError.__init__(self, 'bad directive "%s"' % name,
|
||||
filename, lineno)
|
||||
|
||||
|
||||
class TemplateRuntimeError(TemplateError):
|
||||
"""Exception raised when an the evaluation of a Python expression in a
|
||||
template causes an error.
|
||||
"""
|
||||
|
||||
|
||||
class Context(object):
|
||||
"""Container for template input data.
|
||||
|
||||
A context provides a stack of scopes (represented by dictionaries).
|
||||
|
||||
Template directives such as loops can push a new scope on the stack with
|
||||
data that should only be available inside the loop. When the loop
|
||||
terminates, that scope can get popped off the stack again.
|
||||
|
||||
>>> ctxt = Context(one='foo', other=1)
|
||||
>>> ctxt.get('one')
|
||||
'foo'
|
||||
>>> ctxt.get('other')
|
||||
1
|
||||
>>> ctxt.push(dict(one='frost'))
|
||||
>>> ctxt.get('one')
|
||||
'frost'
|
||||
>>> ctxt.get('other')
|
||||
1
|
||||
>>> ctxt.pop()
|
||||
{'one': 'frost'}
|
||||
>>> ctxt.get('one')
|
||||
'foo'
|
||||
"""
|
||||
|
||||
def __init__(self, **data):
|
||||
"""Initialize the template context with the given keyword arguments as
|
||||
data.
|
||||
"""
|
||||
self.frames = deque([data])
|
||||
self.pop = self.frames.popleft
|
||||
self.push = self.frames.appendleft
|
||||
self._match_templates = []
|
||||
self._choice_stack = []
|
||||
|
||||
# Helper functions for use in expressions
|
||||
def defined(name):
|
||||
"""Return whether a variable with the specified name exists in the
|
||||
expression scope."""
|
||||
return name in self
|
||||
def value_of(name, default=None):
|
||||
"""If a variable of the specified name is defined, return its value.
|
||||
Otherwise, return the provided default value, or ``None``."""
|
||||
return self.get(name, default)
|
||||
data.setdefault('defined', defined)
|
||||
data.setdefault('value_of', value_of)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(list(self.frames))
|
||||
|
||||
def __contains__(self, key):
|
||||
"""Return whether a variable exists in any of the scopes.
|
||||
|
||||
:param key: the name of the variable
|
||||
"""
|
||||
return self._find(key)[1] is not None
|
||||
has_key = __contains__
|
||||
|
||||
def __delitem__(self, key):
|
||||
"""Remove a variable from all scopes.
|
||||
|
||||
:param key: the name of the variable
|
||||
"""
|
||||
for frame in self.frames:
|
||||
if key in frame:
|
||||
del frame[key]
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Get a variables's value, starting at the current scope and going
|
||||
upward.
|
||||
|
||||
:param key: the name of the variable
|
||||
:return: the variable value
|
||||
:raises KeyError: if the requested variable wasn't found in any scope
|
||||
"""
|
||||
value, frame = self._find(key)
|
||||
if frame is None:
|
||||
raise KeyError(key)
|
||||
return value
|
||||
|
||||
def __len__(self):
|
||||
"""Return the number of distinctly named variables in the context.
|
||||
|
||||
:return: the number of variables in the context
|
||||
"""
|
||||
return len(self.items())
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Set a variable in the current scope.
|
||||
|
||||
:param key: the name of the variable
|
||||
:param value: the variable value
|
||||
"""
|
||||
self.frames[0][key] = value
|
||||
|
||||
def _find(self, key, default=None):
|
||||
"""Retrieve a given variable's value and the frame it was found in.
|
||||
|
||||
Intended primarily for internal use by directives.
|
||||
|
||||
:param key: the name of the variable
|
||||
:param default: the default value to return when the variable is not
|
||||
found
|
||||
"""
|
||||
for frame in self.frames:
|
||||
if key in frame:
|
||||
return frame[key], frame
|
||||
return default, None
|
||||
|
||||
def get(self, key, default=None):
|
||||
"""Get a variable's value, starting at the current scope and going
|
||||
upward.
|
||||
|
||||
:param key: the name of the variable
|
||||
:param default: the default value to return when the variable is not
|
||||
found
|
||||
"""
|
||||
for frame in self.frames:
|
||||
if key in frame:
|
||||
return frame[key]
|
||||
return default
|
||||
|
||||
def keys(self):
|
||||
"""Return the name of all variables in the context.
|
||||
|
||||
:return: a list of variable names
|
||||
"""
|
||||
keys = []
|
||||
for frame in self.frames:
|
||||
keys += [key for key in frame if key not in keys]
|
||||
return keys
|
||||
|
||||
def items(self):
|
||||
"""Return a list of ``(name, value)`` tuples for all variables in the
|
||||
context.
|
||||
|
||||
:return: a list of variables
|
||||
"""
|
||||
return [(key, self.get(key)) for key in self.keys()]
|
||||
|
||||
def update(self, mapping):
|
||||
"""Update the context from the mapping provided."""
|
||||
self.frames[0].update(mapping)
|
||||
|
||||
def push(self, data):
|
||||
"""Push a new scope on the stack.
|
||||
|
||||
:param data: the data dictionary to push on the context stack.
|
||||
"""
|
||||
|
||||
def pop(self):
|
||||
"""Pop the top-most scope from the stack."""
|
||||
|
||||
|
||||
def _apply_directives(stream, directives, ctxt, **vars):
|
||||
"""Apply the given directives to the stream.
|
||||
|
||||
:param stream: the stream the directives should be applied to
|
||||
:param directives: the list of directives to apply
|
||||
:param ctxt: the `Context`
|
||||
:param vars: additional variables that should be available when Python
|
||||
code is executed
|
||||
:return: the stream with the given directives applied
|
||||
"""
|
||||
if directives:
|
||||
stream = directives[0](iter(stream), directives[1:], ctxt, **vars)
|
||||
return stream
|
||||
|
||||
def _eval_expr(expr, ctxt, **vars):
|
||||
"""Evaluate the given `Expression` object.
|
||||
|
||||
:param expr: the expression to evaluate
|
||||
:param ctxt: the `Context`
|
||||
:param vars: additional variables that should be available to the
|
||||
expression
|
||||
:return: the result of the evaluation
|
||||
"""
|
||||
if vars:
|
||||
ctxt.push(vars)
|
||||
retval = expr.evaluate(ctxt)
|
||||
if vars:
|
||||
ctxt.pop()
|
||||
return retval
|
||||
|
||||
def _exec_suite(suite, ctxt, **vars):
|
||||
"""Execute the given `Suite` object.
|
||||
|
||||
:param suite: the code suite to execute
|
||||
:param ctxt: the `Context`
|
||||
:param vars: additional variables that should be available to the
|
||||
code
|
||||
"""
|
||||
if vars:
|
||||
ctxt.push(vars)
|
||||
ctxt.push({})
|
||||
suite.execute(_ctxt2dict(ctxt))
|
||||
if vars:
|
||||
top = ctxt.pop()
|
||||
ctxt.pop()
|
||||
ctxt.frames[0].update(top)
|
||||
|
||||
|
||||
class TemplateMeta(type):
|
||||
"""Meta class for templates."""
|
||||
|
||||
def __new__(cls, name, bases, d):
|
||||
if 'directives' in d:
|
||||
d['_dir_by_name'] = dict(d['directives'])
|
||||
d['_dir_order'] = [directive[1] for directive in d['directives']]
|
||||
|
||||
return type.__new__(cls, name, bases, d)
|
||||
|
||||
|
||||
class Template(object):
|
||||
"""Abstract template base class.
|
||||
|
||||
This class implements most of the template processing model, but does not
|
||||
specify the syntax of templates.
|
||||
"""
|
||||
__metaclass__ = TemplateMeta
|
||||
|
||||
EXEC = StreamEventKind('EXEC')
|
||||
"""Stream event kind representing a Python code suite to execute."""
|
||||
|
||||
EXPR = StreamEventKind('EXPR')
|
||||
"""Stream event kind representing a Python expression."""
|
||||
|
||||
INCLUDE = StreamEventKind('INCLUDE')
|
||||
"""Stream event kind representing the inclusion of another template."""
|
||||
|
||||
SUB = StreamEventKind('SUB')
|
||||
"""Stream event kind representing a nested stream to which one or more
|
||||
directives should be applied.
|
||||
"""
|
||||
|
||||
serializer = None
|
||||
_number_conv = unicode # function used to convert numbers to event data
|
||||
|
||||
def __init__(self, source, filepath=None, filename=None, loader=None,
|
||||
encoding=None, lookup='strict', allow_exec=True):
|
||||
"""Initialize a template from either a string, a file-like object, or
|
||||
an already parsed markup stream.
|
||||
|
||||
:param source: a string, file-like object, or markup stream to read the
|
||||
template from
|
||||
:param filepath: the absolute path to the template file
|
||||
:param filename: the path to the template file relative to the search
|
||||
path
|
||||
:param loader: the `TemplateLoader` to use for loading included
|
||||
templates
|
||||
:param encoding: the encoding of the `source`
|
||||
:param lookup: the variable lookup mechanism; either "strict" (the
|
||||
default), "lenient", or a custom lookup class
|
||||
:param allow_exec: whether Python code blocks in templates should be
|
||||
allowed
|
||||
|
||||
:note: Changed in 0.5: Added the `allow_exec` argument
|
||||
"""
|
||||
self.filepath = filepath or filename
|
||||
self.filename = filename
|
||||
self.loader = loader
|
||||
self.lookup = lookup
|
||||
self.allow_exec = allow_exec
|
||||
self._init_filters()
|
||||
|
||||
if isinstance(source, basestring):
|
||||
source = StringIO(source)
|
||||
else:
|
||||
source = source
|
||||
try:
|
||||
self.stream = list(self._prepare(self._parse(source, encoding)))
|
||||
except ParseError, e:
|
||||
raise TemplateSyntaxError(e.msg, self.filepath, e.lineno, e.offset)
|
||||
|
||||
def __getstate__(self):
|
||||
state = self.__dict__.copy()
|
||||
state['filters'] = []
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__ = state
|
||||
self._init_filters()
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s "%s">' % (self.__class__.__name__, self.filename)
|
||||
|
||||
def _init_filters(self):
|
||||
self.filters = [self._flatten, self._eval, self._exec]
|
||||
if self.loader:
|
||||
self.filters.append(self._include)
|
||||
|
||||
def _parse(self, source, encoding):
|
||||
"""Parse the template.
|
||||
|
||||
The parsing stage parses the template and constructs a list of
|
||||
directives that will be executed in the render stage. The input is
|
||||
split up into literal output (text that does not depend on the context
|
||||
data) and directives or expressions.
|
||||
|
||||
:param source: a file-like object containing the XML source of the
|
||||
template, or an XML event stream
|
||||
:param encoding: the encoding of the `source`
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _prepare(self, stream):
|
||||
"""Call the `attach` method of every directive found in the template.
|
||||
|
||||
:param stream: the event stream of the template
|
||||
"""
|
||||
from calibre.utils.genshi.template.loader import TemplateNotFound
|
||||
|
||||
for kind, data, pos in stream:
|
||||
if kind is SUB:
|
||||
directives = []
|
||||
substream = data[1]
|
||||
for cls, value, namespaces, pos in data[0]:
|
||||
directive, substream = cls.attach(self, substream, value,
|
||||
namespaces, pos)
|
||||
if directive:
|
||||
directives.append(directive)
|
||||
substream = self._prepare(substream)
|
||||
if directives:
|
||||
yield kind, (directives, list(substream)), pos
|
||||
else:
|
||||
for event in substream:
|
||||
yield event
|
||||
else:
|
||||
if kind is INCLUDE:
|
||||
href, cls, fallback = data
|
||||
if isinstance(href, basestring) and \
|
||||
not getattr(self.loader, 'auto_reload', True):
|
||||
# If the path to the included template is static, and
|
||||
# auto-reloading is disabled on the template loader,
|
||||
# the template is inlined into the stream
|
||||
try:
|
||||
tmpl = self.loader.load(href, relative_to=pos[0],
|
||||
cls=cls or self.__class__)
|
||||
for event in tmpl.stream:
|
||||
yield event
|
||||
except TemplateNotFound:
|
||||
if fallback is None:
|
||||
raise
|
||||
for event in self._prepare(fallback):
|
||||
yield event
|
||||
continue
|
||||
elif fallback:
|
||||
# Otherwise the include is performed at run time
|
||||
data = href, cls, list(self._prepare(fallback))
|
||||
|
||||
yield kind, data, pos
|
||||
|
||||
def generate(self, *args, **kwargs):
|
||||
"""Apply the template to the given context data.
|
||||
|
||||
Any keyword arguments are made available to the template as context
|
||||
data.
|
||||
|
||||
Only one positional argument is accepted: if it is provided, it must be
|
||||
an instance of the `Context` class, and keyword arguments are ignored.
|
||||
This calling style is used for internal processing.
|
||||
|
||||
:return: a markup event stream representing the result of applying
|
||||
the template to the context data.
|
||||
"""
|
||||
vars = {}
|
||||
if args:
|
||||
assert len(args) == 1
|
||||
ctxt = args[0]
|
||||
if ctxt is None:
|
||||
ctxt = Context(**kwargs)
|
||||
else:
|
||||
vars = kwargs
|
||||
assert isinstance(ctxt, Context)
|
||||
else:
|
||||
ctxt = Context(**kwargs)
|
||||
|
||||
stream = self.stream
|
||||
for filter_ in self.filters:
|
||||
stream = filter_(iter(stream), ctxt, **vars)
|
||||
return Stream(stream, self.serializer)
|
||||
|
||||
def _eval(self, stream, ctxt, **vars):
|
||||
"""Internal stream filter that evaluates any expressions in `START` and
|
||||
`TEXT` events.
|
||||
"""
|
||||
filters = (self._flatten, self._eval)
|
||||
number_conv = self._number_conv
|
||||
|
||||
for kind, data, pos in stream:
|
||||
|
||||
if kind is START and data[1]:
|
||||
# Attributes may still contain expressions in start tags at
|
||||
# this point, so do some evaluation
|
||||
tag, attrs = data
|
||||
new_attrs = []
|
||||
for name, substream in attrs:
|
||||
if isinstance(substream, basestring):
|
||||
value = substream
|
||||
else:
|
||||
values = []
|
||||
for subkind, subdata, subpos in self._eval(substream,
|
||||
ctxt,
|
||||
**vars):
|
||||
if subkind is TEXT:
|
||||
values.append(subdata)
|
||||
value = [x for x in values if x is not None]
|
||||
if not value:
|
||||
continue
|
||||
for i, v in enumerate(value):
|
||||
if isinstance(v, str):
|
||||
value[i] = v.decode('utf-8')
|
||||
new_attrs.append((name, u''.join(value)))
|
||||
yield kind, (tag, Attrs(new_attrs)), pos
|
||||
|
||||
elif kind is EXPR:
|
||||
result = _eval_expr(data, ctxt, **vars)
|
||||
if result is not None:
|
||||
# First check for a string, otherwise the iterable test
|
||||
# below succeeds, and the string will be chopped up into
|
||||
# individual characters
|
||||
if isinstance(result, basestring):
|
||||
yield TEXT, result, pos
|
||||
elif isinstance(result, (int, float, long)):
|
||||
yield TEXT, number_conv(result), pos
|
||||
elif hasattr(result, '__iter__'):
|
||||
substream = _ensure(result)
|
||||
for filter_ in filters:
|
||||
substream = filter_(substream, ctxt, **vars)
|
||||
for event in substream:
|
||||
yield event
|
||||
else:
|
||||
yield TEXT, unicode(result), pos
|
||||
|
||||
else:
|
||||
yield kind, data, pos
|
||||
|
||||
def _exec(self, stream, ctxt, **vars):
|
||||
"""Internal stream filter that executes Python code blocks."""
|
||||
for event in stream:
|
||||
if event[0] is EXEC:
|
||||
_exec_suite(event[1], ctxt, **vars)
|
||||
else:
|
||||
yield event
|
||||
|
||||
def _flatten(self, stream, ctxt, **vars):
|
||||
"""Internal stream filter that expands `SUB` events in the stream."""
|
||||
for event in stream:
|
||||
if event[0] is SUB:
|
||||
# This event is a list of directives and a list of nested
|
||||
# events to which those directives should be applied
|
||||
directives, substream = event[1]
|
||||
substream = _apply_directives(substream, directives, ctxt,
|
||||
**vars)
|
||||
for event in self._flatten(substream, ctxt, **vars):
|
||||
yield event
|
||||
else:
|
||||
yield event
|
||||
|
||||
def _include(self, stream, ctxt, **vars):
|
||||
"""Internal stream filter that performs inclusion of external
|
||||
template files.
|
||||
"""
|
||||
from calibre.utils.genshi.template.loader import TemplateNotFound
|
||||
|
||||
for event in stream:
|
||||
if event[0] is INCLUDE:
|
||||
href, cls, fallback = event[1]
|
||||
if not isinstance(href, basestring):
|
||||
parts = []
|
||||
for subkind, subdata, subpos in self._eval(href, ctxt,
|
||||
**vars):
|
||||
if subkind is TEXT:
|
||||
parts.append(subdata)
|
||||
href = u''.join([x for x in parts if x is not None])
|
||||
try:
|
||||
tmpl = self.loader.load(href, relative_to=event[2][0],
|
||||
cls=cls or self.__class__)
|
||||
for event in tmpl.generate(ctxt, **vars):
|
||||
yield event
|
||||
except TemplateNotFound:
|
||||
if fallback is None:
|
||||
raise
|
||||
for filter_ in self.filters:
|
||||
fallback = filter_(iter(fallback), ctxt, **vars)
|
||||
for event in fallback:
|
||||
yield event
|
||||
else:
|
||||
yield event
|
||||
|
||||
|
||||
EXEC = Template.EXEC
|
||||
EXPR = Template.EXPR
|
||||
INCLUDE = Template.INCLUDE
|
||||
SUB = Template.SUB
|
@ -1,745 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Implementation of the various template directives."""
|
||||
|
||||
import compiler
|
||||
try:
|
||||
frozenset
|
||||
except NameError:
|
||||
from sets import ImmutableSet as frozenset
|
||||
|
||||
from calibre.utils.genshi.core import QName, Stream
|
||||
from calibre.utils.genshi.path import Path
|
||||
from calibre.utils.genshi.template.base import TemplateRuntimeError, TemplateSyntaxError, \
|
||||
EXPR, _apply_directives, _eval_expr, \
|
||||
_exec_suite
|
||||
from calibre.utils.genshi.template.eval import Expression, ExpressionASTTransformer, _parse
|
||||
|
||||
__all__ = ['AttrsDirective', 'ChooseDirective', 'ContentDirective',
|
||||
'DefDirective', 'ForDirective', 'IfDirective', 'MatchDirective',
|
||||
'OtherwiseDirective', 'ReplaceDirective', 'StripDirective',
|
||||
'WhenDirective', 'WithDirective']
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
class DirectiveMeta(type):
|
||||
"""Meta class for template directives."""
|
||||
|
||||
def __new__(cls, name, bases, d):
|
||||
d['tagname'] = name.lower().replace('directive', '')
|
||||
return type.__new__(cls, name, bases, d)
|
||||
|
||||
|
||||
class Directive(object):
|
||||
"""Abstract base class for template directives.
|
||||
|
||||
A directive is basically a callable that takes three positional arguments:
|
||||
``ctxt`` is the template data context, ``stream`` is an iterable over the
|
||||
events that the directive applies to, and ``directives`` is is a list of
|
||||
other directives on the same stream that need to be applied.
|
||||
|
||||
Directives can be "anonymous" or "registered". Registered directives can be
|
||||
applied by the template author using an XML attribute with the
|
||||
corresponding name in the template. Such directives should be subclasses of
|
||||
this base class that can be instantiated with the value of the directive
|
||||
attribute as parameter.
|
||||
|
||||
Anonymous directives are simply functions conforming to the protocol
|
||||
described above, and can only be applied programmatically (for example by
|
||||
template filters).
|
||||
"""
|
||||
__metaclass__ = DirectiveMeta
|
||||
__slots__ = ['expr']
|
||||
|
||||
def __init__(self, value, template=None, namespaces=None, lineno=-1,
|
||||
offset=-1):
|
||||
self.expr = self._parse_expr(value, template, lineno, offset)
|
||||
|
||||
def attach(cls, template, stream, value, namespaces, pos):
|
||||
"""Called after the template stream has been completely parsed.
|
||||
|
||||
:param template: the `Template` object
|
||||
:param stream: the event stream associated with the directive
|
||||
:param value: the argument value for the directive; if the directive was
|
||||
specified as an element, this will be an `Attrs` instance
|
||||
with all specified attributes, otherwise it will be a
|
||||
`unicode` object with just the attribute value
|
||||
:param namespaces: a mapping of namespace URIs to prefixes
|
||||
:param pos: a ``(filename, lineno, offset)`` tuple describing the
|
||||
location where the directive was found in the source
|
||||
|
||||
This class method should return a ``(directive, stream)`` tuple. If
|
||||
``directive`` is not ``None``, it should be an instance of the `Directive`
|
||||
class, and gets added to the list of directives applied to the substream
|
||||
at runtime. `stream` is an event stream that replaces the original
|
||||
stream associated with the directive.
|
||||
"""
|
||||
return cls(value, template, namespaces, *pos[1:]), stream
|
||||
attach = classmethod(attach)
|
||||
|
||||
def __call__(self, stream, directives, ctxt, **vars):
|
||||
"""Apply the directive to the given stream.
|
||||
|
||||
:param stream: the event stream
|
||||
:param directives: a list of the remaining directives that should
|
||||
process the stream
|
||||
:param ctxt: the context data
|
||||
:param vars: additional variables that should be made available when
|
||||
Python code is executed
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __repr__(self):
|
||||
expr = ''
|
||||
if getattr(self, 'expr', None) is not None:
|
||||
expr = ' "%s"' % self.expr.source
|
||||
return '<%s%s>' % (self.__class__.__name__, expr)
|
||||
|
||||
def _parse_expr(cls, expr, template, lineno=-1, offset=-1):
|
||||
"""Parses the given expression, raising a useful error message when a
|
||||
syntax error is encountered.
|
||||
"""
|
||||
try:
|
||||
return expr and Expression(expr, template.filepath, lineno,
|
||||
lookup=template.lookup) or None
|
||||
except SyntaxError, err:
|
||||
err.msg += ' in expression "%s" of "%s" directive' % (expr,
|
||||
cls.tagname)
|
||||
raise TemplateSyntaxError(err, template.filepath, lineno,
|
||||
offset + (err.offset or 0))
|
||||
_parse_expr = classmethod(_parse_expr)
|
||||
|
||||
|
||||
def _assignment(ast):
|
||||
"""Takes the AST representation of an assignment, and returns a function
|
||||
that applies the assignment of a given value to a dictionary.
|
||||
"""
|
||||
def _names(node):
|
||||
if isinstance(node, (compiler.ast.AssTuple, compiler.ast.Tuple)):
|
||||
return tuple([_names(child) for child in node.nodes])
|
||||
elif isinstance(node, (compiler.ast.AssName, compiler.ast.Name)):
|
||||
return node.name
|
||||
def _assign(data, value, names=_names(ast)):
|
||||
if type(names) is tuple:
|
||||
for idx in range(len(names)):
|
||||
_assign(data, value[idx], names[idx])
|
||||
else:
|
||||
data[names] = value
|
||||
return _assign
|
||||
|
||||
|
||||
class AttrsDirective(Directive):
|
||||
"""Implementation of the ``py:attrs`` template directive.
|
||||
|
||||
The value of the ``py:attrs`` attribute should be a dictionary or a sequence
|
||||
of ``(name, value)`` tuples. The items in that dictionary or sequence are
|
||||
added as attributes to the element:
|
||||
|
||||
>>> from genshi.template import MarkupTemplate
|
||||
>>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/">
|
||||
... <li py:attrs="foo">Bar</li>
|
||||
... </ul>''')
|
||||
>>> print tmpl.generate(foo={'class': 'collapse'})
|
||||
<ul>
|
||||
<li class="collapse">Bar</li>
|
||||
</ul>
|
||||
>>> print tmpl.generate(foo=[('class', 'collapse')])
|
||||
<ul>
|
||||
<li class="collapse">Bar</li>
|
||||
</ul>
|
||||
|
||||
If the value evaluates to ``None`` (or any other non-truth value), no
|
||||
attributes are added:
|
||||
|
||||
>>> print tmpl.generate(foo=None)
|
||||
<ul>
|
||||
<li>Bar</li>
|
||||
</ul>
|
||||
"""
|
||||
__slots__ = []
|
||||
|
||||
def __call__(self, stream, directives, ctxt, **vars):
|
||||
def _generate():
|
||||
kind, (tag, attrib), pos = stream.next()
|
||||
attrs = _eval_expr(self.expr, ctxt, **vars)
|
||||
if attrs:
|
||||
if isinstance(attrs, Stream):
|
||||
try:
|
||||
attrs = iter(attrs).next()
|
||||
except StopIteration:
|
||||
attrs = []
|
||||
elif not isinstance(attrs, list): # assume it's a dict
|
||||
attrs = attrs.items()
|
||||
attrib -= [name for name, val in attrs if val is None]
|
||||
attrib |= [(QName(name), unicode(val).strip()) for name, val
|
||||
in attrs if val is not None]
|
||||
yield kind, (tag, attrib), pos
|
||||
for event in stream:
|
||||
yield event
|
||||
|
||||
return _apply_directives(_generate(), directives, ctxt, **vars)
|
||||
|
||||
|
||||
class ContentDirective(Directive):
|
||||
"""Implementation of the ``py:content`` template directive.
|
||||
|
||||
This directive replaces the content of the element with the result of
|
||||
evaluating the value of the ``py:content`` attribute:
|
||||
|
||||
>>> from genshi.template import MarkupTemplate
|
||||
>>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/">
|
||||
... <li py:content="bar">Hello</li>
|
||||
... </ul>''')
|
||||
>>> print tmpl.generate(bar='Bye')
|
||||
<ul>
|
||||
<li>Bye</li>
|
||||
</ul>
|
||||
"""
|
||||
__slots__ = []
|
||||
|
||||
def attach(cls, template, stream, value, namespaces, pos):
|
||||
if type(value) is dict:
|
||||
raise TemplateSyntaxError('The content directive can not be used '
|
||||
'as an element', template.filepath,
|
||||
*pos[1:])
|
||||
expr = cls._parse_expr(value, template, *pos[1:])
|
||||
return None, [stream[0], (EXPR, expr, pos), stream[-1]]
|
||||
attach = classmethod(attach)
|
||||
|
||||
|
||||
class DefDirective(Directive):
|
||||
"""Implementation of the ``py:def`` template directive.
|
||||
|
||||
This directive can be used to create "Named Template Functions", which
|
||||
are template snippets that are not actually output during normal
|
||||
processing, but rather can be expanded from expressions in other places
|
||||
in the template.
|
||||
|
||||
A named template function can be used just like a normal Python function
|
||||
from template expressions:
|
||||
|
||||
>>> from genshi.template import MarkupTemplate
|
||||
>>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
|
||||
... <p py:def="echo(greeting, name='world')" class="message">
|
||||
... ${greeting}, ${name}!
|
||||
... </p>
|
||||
... ${echo('Hi', name='you')}
|
||||
... </div>''')
|
||||
>>> print tmpl.generate(bar='Bye')
|
||||
<div>
|
||||
<p class="message">
|
||||
Hi, you!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
If a function does not require parameters, the parenthesis can be omitted
|
||||
in the definition:
|
||||
|
||||
>>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
|
||||
... <p py:def="helloworld" class="message">
|
||||
... Hello, world!
|
||||
... </p>
|
||||
... ${helloworld()}
|
||||
... </div>''')
|
||||
>>> print tmpl.generate(bar='Bye')
|
||||
<div>
|
||||
<p class="message">
|
||||
Hello, world!
|
||||
</p>
|
||||
</div>
|
||||
"""
|
||||
__slots__ = ['name', 'args', 'star_args', 'dstar_args', 'defaults']
|
||||
|
||||
def __init__(self, args, template, namespaces=None, lineno=-1, offset=-1):
|
||||
Directive.__init__(self, None, template, namespaces, lineno, offset)
|
||||
ast = _parse(args).node
|
||||
self.args = []
|
||||
self.star_args = None
|
||||
self.dstar_args = None
|
||||
self.defaults = {}
|
||||
if isinstance(ast, compiler.ast.CallFunc):
|
||||
self.name = ast.node.name
|
||||
for arg in ast.args:
|
||||
if isinstance(arg, compiler.ast.Keyword):
|
||||
self.args.append(arg.name)
|
||||
self.defaults[arg.name] = Expression(arg.expr,
|
||||
template.filepath,
|
||||
lineno,
|
||||
lookup=template.lookup)
|
||||
else:
|
||||
self.args.append(arg.name)
|
||||
if ast.star_args:
|
||||
self.star_args = ast.star_args.name
|
||||
if ast.dstar_args:
|
||||
self.dstar_args = ast.dstar_args.name
|
||||
else:
|
||||
self.name = ast.name
|
||||
|
||||
def attach(cls, template, stream, value, namespaces, pos):
|
||||
if type(value) is dict:
|
||||
value = value.get('function')
|
||||
return super(DefDirective, cls).attach(template, stream, value,
|
||||
namespaces, pos)
|
||||
attach = classmethod(attach)
|
||||
|
||||
def __call__(self, stream, directives, ctxt, **vars):
|
||||
stream = list(stream)
|
||||
|
||||
def function(*args, **kwargs):
|
||||
scope = {}
|
||||
args = list(args) # make mutable
|
||||
for name in self.args:
|
||||
if args:
|
||||
scope[name] = args.pop(0)
|
||||
else:
|
||||
if name in kwargs:
|
||||
val = kwargs.pop(name)
|
||||
else:
|
||||
val = _eval_expr(self.defaults.get(name), ctxt, **vars)
|
||||
scope[name] = val
|
||||
if not self.star_args is None:
|
||||
scope[self.star_args] = args
|
||||
if not self.dstar_args is None:
|
||||
scope[self.dstar_args] = kwargs
|
||||
ctxt.push(scope)
|
||||
for event in _apply_directives(stream, directives, ctxt, **vars):
|
||||
yield event
|
||||
ctxt.pop()
|
||||
try:
|
||||
function.__name__ = self.name
|
||||
except TypeError:
|
||||
# Function name can't be set in Python 2.3
|
||||
pass
|
||||
|
||||
# Store the function reference in the bottom context frame so that it
|
||||
# doesn't get popped off before processing the template has finished
|
||||
# FIXME: this makes context data mutable as a side-effect
|
||||
ctxt.frames[-1][self.name] = function
|
||||
|
||||
return []
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s "%s">' % (self.__class__.__name__, self.name)
|
||||
|
||||
|
||||
class ForDirective(Directive):
|
||||
"""Implementation of the ``py:for`` template directive for repeating an
|
||||
element based on an iterable in the context data.
|
||||
|
||||
>>> from genshi.template import MarkupTemplate
|
||||
>>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/">
|
||||
... <li py:for="item in items">${item}</li>
|
||||
... </ul>''')
|
||||
>>> print tmpl.generate(items=[1, 2, 3])
|
||||
<ul>
|
||||
<li>1</li><li>2</li><li>3</li>
|
||||
</ul>
|
||||
"""
|
||||
__slots__ = ['assign', 'filename']
|
||||
|
||||
def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
|
||||
if ' in ' not in value:
|
||||
raise TemplateSyntaxError('"in" keyword missing in "for" directive',
|
||||
template.filepath, lineno, offset)
|
||||
assign, value = value.split(' in ', 1)
|
||||
ast = _parse(assign, 'exec')
|
||||
value = 'iter(%s)' % value.strip()
|
||||
self.assign = _assignment(ast.node.nodes[0].expr)
|
||||
self.filename = template.filepath
|
||||
Directive.__init__(self, value, template, namespaces, lineno, offset)
|
||||
|
||||
def attach(cls, template, stream, value, namespaces, pos):
|
||||
if type(value) is dict:
|
||||
value = value.get('each')
|
||||
return super(ForDirective, cls).attach(template, stream, value,
|
||||
namespaces, pos)
|
||||
attach = classmethod(attach)
|
||||
|
||||
def __call__(self, stream, directives, ctxt, **vars):
|
||||
iterable = _eval_expr(self.expr, ctxt, **vars)
|
||||
if iterable is None:
|
||||
return
|
||||
|
||||
assign = self.assign
|
||||
scope = {}
|
||||
stream = list(stream)
|
||||
for item in iterable:
|
||||
assign(scope, item)
|
||||
ctxt.push(scope)
|
||||
for event in _apply_directives(stream, directives, ctxt, **vars):
|
||||
yield event
|
||||
ctxt.pop()
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s>' % self.__class__.__name__
|
||||
|
||||
|
||||
class IfDirective(Directive):
|
||||
"""Implementation of the ``py:if`` template directive for conditionally
|
||||
excluding elements from being output.
|
||||
|
||||
>>> from genshi.template import MarkupTemplate
|
||||
>>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
|
||||
... <b py:if="foo">${bar}</b>
|
||||
... </div>''')
|
||||
>>> print tmpl.generate(foo=True, bar='Hello')
|
||||
<div>
|
||||
<b>Hello</b>
|
||||
</div>
|
||||
"""
|
||||
__slots__ = []
|
||||
|
||||
def attach(cls, template, stream, value, namespaces, pos):
|
||||
if type(value) is dict:
|
||||
value = value.get('test')
|
||||
return super(IfDirective, cls).attach(template, stream, value,
|
||||
namespaces, pos)
|
||||
attach = classmethod(attach)
|
||||
|
||||
def __call__(self, stream, directives, ctxt, **vars):
|
||||
value = _eval_expr(self.expr, ctxt, **vars)
|
||||
if value:
|
||||
return _apply_directives(stream, directives, ctxt, **vars)
|
||||
return []
|
||||
|
||||
|
||||
class MatchDirective(Directive):
|
||||
"""Implementation of the ``py:match`` template directive.
|
||||
|
||||
>>> from genshi.template import MarkupTemplate
|
||||
>>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
|
||||
... <span py:match="greeting">
|
||||
... Hello ${select('@name')}
|
||||
... </span>
|
||||
... <greeting name="Dude" />
|
||||
... </div>''')
|
||||
>>> print tmpl.generate()
|
||||
<div>
|
||||
<span>
|
||||
Hello Dude
|
||||
</span>
|
||||
</div>
|
||||
"""
|
||||
__slots__ = ['path', 'namespaces', 'hints']
|
||||
|
||||
def __init__(self, value, template, hints=None, namespaces=None,
|
||||
lineno=-1, offset=-1):
|
||||
Directive.__init__(self, None, template, namespaces, lineno, offset)
|
||||
self.path = Path(value, template.filepath, lineno)
|
||||
self.namespaces = namespaces or {}
|
||||
self.hints = hints or ()
|
||||
|
||||
def attach(cls, template, stream, value, namespaces, pos):
|
||||
hints = []
|
||||
if type(value) is dict:
|
||||
if value.get('buffer', '').lower() == 'false':
|
||||
hints.append('not_buffered')
|
||||
if value.get('once', '').lower() == 'true':
|
||||
hints.append('match_once')
|
||||
if value.get('recursive', '').lower() == 'false':
|
||||
hints.append('not_recursive')
|
||||
value = value.get('path')
|
||||
return cls(value, template, frozenset(hints), namespaces, *pos[1:]), \
|
||||
stream
|
||||
attach = classmethod(attach)
|
||||
|
||||
def __call__(self, stream, directives, ctxt, **vars):
|
||||
ctxt._match_templates.append((self.path.test(ignore_context=True),
|
||||
self.path, list(stream), self.hints,
|
||||
self.namespaces, directives))
|
||||
return []
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s "%s">' % (self.__class__.__name__, self.path.source)
|
||||
|
||||
|
||||
class ReplaceDirective(Directive):
|
||||
"""Implementation of the ``py:replace`` template directive.
|
||||
|
||||
This directive replaces the element with the result of evaluating the
|
||||
value of the ``py:replace`` attribute:
|
||||
|
||||
>>> from genshi.template import MarkupTemplate
|
||||
>>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
|
||||
... <span py:replace="bar">Hello</span>
|
||||
... </div>''')
|
||||
>>> print tmpl.generate(bar='Bye')
|
||||
<div>
|
||||
Bye
|
||||
</div>
|
||||
|
||||
This directive is equivalent to ``py:content`` combined with ``py:strip``,
|
||||
providing a less verbose way to achieve the same effect:
|
||||
|
||||
>>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
|
||||
... <span py:content="bar" py:strip="">Hello</span>
|
||||
... </div>''')
|
||||
>>> print tmpl.generate(bar='Bye')
|
||||
<div>
|
||||
Bye
|
||||
</div>
|
||||
"""
|
||||
__slots__ = []
|
||||
|
||||
def attach(cls, template, stream, value, namespaces, pos):
|
||||
if type(value) is dict:
|
||||
value = value.get('value')
|
||||
if not value:
|
||||
raise TemplateSyntaxError('missing value for "replace" directive',
|
||||
template.filepath, *pos[1:])
|
||||
expr = cls._parse_expr(value, template, *pos[1:])
|
||||
return None, [(EXPR, expr, pos)]
|
||||
attach = classmethod(attach)
|
||||
|
||||
|
||||
class StripDirective(Directive):
|
||||
"""Implementation of the ``py:strip`` template directive.
|
||||
|
||||
When the value of the ``py:strip`` attribute evaluates to ``True``, the
|
||||
element is stripped from the output
|
||||
|
||||
>>> from genshi.template import MarkupTemplate
|
||||
>>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
|
||||
... <div py:strip="True"><b>foo</b></div>
|
||||
... </div>''')
|
||||
>>> print tmpl.generate()
|
||||
<div>
|
||||
<b>foo</b>
|
||||
</div>
|
||||
|
||||
Leaving the attribute value empty is equivalent to a truth value.
|
||||
|
||||
This directive is particulary interesting for named template functions or
|
||||
match templates that do not generate a top-level element:
|
||||
|
||||
>>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
|
||||
... <div py:def="echo(what)" py:strip="">
|
||||
... <b>${what}</b>
|
||||
... </div>
|
||||
... ${echo('foo')}
|
||||
... </div>''')
|
||||
>>> print tmpl.generate()
|
||||
<div>
|
||||
<b>foo</b>
|
||||
</div>
|
||||
"""
|
||||
__slots__ = []
|
||||
|
||||
def __call__(self, stream, directives, ctxt, **vars):
|
||||
def _generate():
|
||||
if _eval_expr(self.expr, ctxt, **vars):
|
||||
stream.next() # skip start tag
|
||||
previous = stream.next()
|
||||
for event in stream:
|
||||
yield previous
|
||||
previous = event
|
||||
else:
|
||||
for event in stream:
|
||||
yield event
|
||||
return _apply_directives(_generate(), directives, ctxt, **vars)
|
||||
|
||||
def attach(cls, template, stream, value, namespaces, pos):
|
||||
if not value:
|
||||
return None, stream[1:-1]
|
||||
return super(StripDirective, cls).attach(template, stream, value,
|
||||
namespaces, pos)
|
||||
attach = classmethod(attach)
|
||||
|
||||
|
||||
class ChooseDirective(Directive):
|
||||
"""Implementation of the ``py:choose`` directive for conditionally selecting
|
||||
one of several body elements to display.
|
||||
|
||||
If the ``py:choose`` expression is empty the expressions of nested
|
||||
``py:when`` directives are tested for truth. The first true ``py:when``
|
||||
body is output. If no ``py:when`` directive is matched then the fallback
|
||||
directive ``py:otherwise`` will be used.
|
||||
|
||||
>>> from genshi.template import MarkupTemplate
|
||||
>>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"
|
||||
... py:choose="">
|
||||
... <span py:when="0 == 1">0</span>
|
||||
... <span py:when="1 == 1">1</span>
|
||||
... <span py:otherwise="">2</span>
|
||||
... </div>''')
|
||||
>>> print tmpl.generate()
|
||||
<div>
|
||||
<span>1</span>
|
||||
</div>
|
||||
|
||||
If the ``py:choose`` directive contains an expression, the nested
|
||||
``py:when`` directives are tested for equality to the ``py:choose``
|
||||
expression:
|
||||
|
||||
>>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"
|
||||
... py:choose="2">
|
||||
... <span py:when="1">1</span>
|
||||
... <span py:when="2">2</span>
|
||||
... </div>''')
|
||||
>>> print tmpl.generate()
|
||||
<div>
|
||||
<span>2</span>
|
||||
</div>
|
||||
|
||||
Behavior is undefined if a ``py:choose`` block contains content outside a
|
||||
``py:when`` or ``py:otherwise`` block. Behavior is also undefined if a
|
||||
``py:otherwise`` occurs before ``py:when`` blocks.
|
||||
"""
|
||||
__slots__ = ['matched', 'value']
|
||||
|
||||
def attach(cls, template, stream, value, namespaces, pos):
|
||||
if type(value) is dict:
|
||||
value = value.get('test')
|
||||
return super(ChooseDirective, cls).attach(template, stream, value,
|
||||
namespaces, pos)
|
||||
attach = classmethod(attach)
|
||||
|
||||
def __call__(self, stream, directives, ctxt, **vars):
|
||||
info = [False, bool(self.expr), None]
|
||||
if self.expr:
|
||||
info[2] = _eval_expr(self.expr, ctxt, **vars)
|
||||
ctxt._choice_stack.append(info)
|
||||
for event in _apply_directives(stream, directives, ctxt, **vars):
|
||||
yield event
|
||||
ctxt._choice_stack.pop()
|
||||
|
||||
|
||||
class WhenDirective(Directive):
|
||||
"""Implementation of the ``py:when`` directive for nesting in a parent with
|
||||
the ``py:choose`` directive.
|
||||
|
||||
See the documentation of the `ChooseDirective` for usage.
|
||||
"""
|
||||
__slots__ = ['filename']
|
||||
|
||||
def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
|
||||
Directive.__init__(self, value, template, namespaces, lineno, offset)
|
||||
self.filename = template.filepath
|
||||
|
||||
def attach(cls, template, stream, value, namespaces, pos):
|
||||
if type(value) is dict:
|
||||
value = value.get('test')
|
||||
return super(WhenDirective, cls).attach(template, stream, value,
|
||||
namespaces, pos)
|
||||
attach = classmethod(attach)
|
||||
|
||||
def __call__(self, stream, directives, ctxt, **vars):
|
||||
info = ctxt._choice_stack and ctxt._choice_stack[-1]
|
||||
if not info:
|
||||
raise TemplateRuntimeError('"when" directives can only be used '
|
||||
'inside a "choose" directive',
|
||||
self.filename, *stream.next()[2][1:])
|
||||
if info[0]:
|
||||
return []
|
||||
if not self.expr and not info[1]:
|
||||
raise TemplateRuntimeError('either "choose" or "when" directive '
|
||||
'must have a test expression',
|
||||
self.filename, *stream.next()[2][1:])
|
||||
if info[1]:
|
||||
value = info[2]
|
||||
if self.expr:
|
||||
matched = value == _eval_expr(self.expr, ctxt, **vars)
|
||||
else:
|
||||
matched = bool(value)
|
||||
else:
|
||||
matched = bool(_eval_expr(self.expr, ctxt, **vars))
|
||||
info[0] = matched
|
||||
if not matched:
|
||||
return []
|
||||
|
||||
return _apply_directives(stream, directives, ctxt, **vars)
|
||||
|
||||
|
||||
class OtherwiseDirective(Directive):
|
||||
"""Implementation of the ``py:otherwise`` directive for nesting in a parent
|
||||
with the ``py:choose`` directive.
|
||||
|
||||
See the documentation of `ChooseDirective` for usage.
|
||||
"""
|
||||
__slots__ = ['filename']
|
||||
|
||||
def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
|
||||
Directive.__init__(self, None, template, namespaces, lineno, offset)
|
||||
self.filename = template.filepath
|
||||
|
||||
def __call__(self, stream, directives, ctxt, **vars):
|
||||
info = ctxt._choice_stack and ctxt._choice_stack[-1]
|
||||
if not info:
|
||||
raise TemplateRuntimeError('an "otherwise" directive can only be '
|
||||
'used inside a "choose" directive',
|
||||
self.filename, *stream.next()[2][1:])
|
||||
if info[0]:
|
||||
return []
|
||||
info[0] = True
|
||||
|
||||
return _apply_directives(stream, directives, ctxt, **vars)
|
||||
|
||||
|
||||
class WithDirective(Directive):
|
||||
"""Implementation of the ``py:with`` template directive, which allows
|
||||
shorthand access to variables and expressions.
|
||||
|
||||
>>> from genshi.template import MarkupTemplate
|
||||
>>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
|
||||
... <span py:with="y=7; z=x+10">$x $y $z</span>
|
||||
... </div>''')
|
||||
>>> print tmpl.generate(x=42)
|
||||
<div>
|
||||
<span>42 7 52</span>
|
||||
</div>
|
||||
"""
|
||||
__slots__ = ['vars']
|
||||
|
||||
def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
|
||||
Directive.__init__(self, None, template, namespaces, lineno, offset)
|
||||
self.vars = []
|
||||
value = value.strip()
|
||||
try:
|
||||
ast = _parse(value, 'exec').node
|
||||
for node in ast.nodes:
|
||||
if isinstance(node, compiler.ast.Discard):
|
||||
continue
|
||||
elif not isinstance(node, compiler.ast.Assign):
|
||||
raise TemplateSyntaxError('only assignment allowed in '
|
||||
'value of the "with" directive',
|
||||
template.filepath, lineno, offset)
|
||||
self.vars.append(([_assignment(n) for n in node.nodes],
|
||||
Expression(node.expr, template.filepath,
|
||||
lineno, lookup=template.lookup)))
|
||||
except SyntaxError, err:
|
||||
err.msg += ' in expression "%s" of "%s" directive' % (value,
|
||||
self.tagname)
|
||||
raise TemplateSyntaxError(err, template.filepath, lineno,
|
||||
offset + (err.offset or 0))
|
||||
|
||||
def attach(cls, template, stream, value, namespaces, pos):
|
||||
if type(value) is dict:
|
||||
value = value.get('vars')
|
||||
return super(WithDirective, cls).attach(template, stream, value,
|
||||
namespaces, pos)
|
||||
attach = classmethod(attach)
|
||||
|
||||
def __call__(self, stream, directives, ctxt, **vars):
|
||||
frame = {}
|
||||
ctxt.push(frame)
|
||||
for targets, expr in self.vars:
|
||||
value = _eval_expr(expr, ctxt, **vars)
|
||||
for assign in targets:
|
||||
assign(frame, value)
|
||||
for event in _apply_directives(stream, directives, ctxt, **vars):
|
||||
yield event
|
||||
ctxt.pop()
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s>' % (self.__class__.__name__)
|
@ -1,826 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Support for "safe" evaluation of Python expressions."""
|
||||
|
||||
import __builtin__
|
||||
from compiler import ast, parse
|
||||
from compiler.pycodegen import ExpressionCodeGenerator, ModuleCodeGenerator
|
||||
import new
|
||||
try:
|
||||
set
|
||||
except NameError:
|
||||
from sets import ImmutableSet as frozenset
|
||||
from sets import Set as set
|
||||
from textwrap import dedent
|
||||
|
||||
from calibre.utils.genshi.core import Markup
|
||||
from calibre.utils.genshi.template.base import TemplateRuntimeError
|
||||
from calibre.utils.genshi.util import flatten
|
||||
|
||||
__all__ = ['Code', 'Expression', 'Suite', 'LenientLookup', 'StrictLookup',
|
||||
'Undefined', 'UndefinedError']
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
# Check for a Python 2.4 bug in the eval loop
|
||||
has_star_import_bug = False
|
||||
try:
|
||||
class _FakeMapping(object):
|
||||
__getitem__ = __setitem__ = lambda *a: None
|
||||
exec 'from sys import *' in {}, _FakeMapping()
|
||||
except SystemError:
|
||||
has_star_import_bug = True
|
||||
except TypeError:
|
||||
pass # Python 2.3
|
||||
del _FakeMapping
|
||||
|
||||
def _star_import_patch(mapping, modname):
|
||||
"""This function is used as helper if a Python version with a broken
|
||||
star-import opcode is in use.
|
||||
"""
|
||||
module = __import__(modname, None, None, ['__all__'])
|
||||
if hasattr(module, '__all__'):
|
||||
members = module.__all__
|
||||
else:
|
||||
members = [x for x in module.__dict__ if not x.startswith('_')]
|
||||
mapping.update([(name, getattr(module, name)) for name in members])
|
||||
|
||||
|
||||
class Code(object):
|
||||
"""Abstract base class for the `Expression` and `Suite` classes."""
|
||||
__slots__ = ['source', 'code', 'ast', '_globals']
|
||||
|
||||
def __init__(self, source, filename=None, lineno=-1, lookup='strict',
|
||||
xform=None):
|
||||
"""Create the code object, either from a string, or from an AST node.
|
||||
|
||||
:param source: either a string containing the source code, or an AST
|
||||
node
|
||||
:param filename: the (preferably absolute) name of the file containing
|
||||
the code
|
||||
:param lineno: the number of the line on which the code was found
|
||||
:param lookup: the lookup class that defines how variables are looked
|
||||
up in the context; can be either "strict" (the default),
|
||||
"lenient", or a custom lookup class
|
||||
:param xform: the AST transformer that should be applied to the code;
|
||||
if `None`, the appropriate transformation is chosen
|
||||
depending on the mode
|
||||
"""
|
||||
if isinstance(source, basestring):
|
||||
self.source = source
|
||||
node = _parse(source, mode=self.mode)
|
||||
else:
|
||||
assert isinstance(source, ast.Node), \
|
||||
'Expected string or AST node, but got %r' % source
|
||||
self.source = '?'
|
||||
if self.mode == 'eval':
|
||||
node = ast.Expression(source)
|
||||
else:
|
||||
node = ast.Module(None, source)
|
||||
|
||||
self.ast = node
|
||||
self.code = _compile(node, self.source, mode=self.mode,
|
||||
filename=filename, lineno=lineno, xform=xform)
|
||||
if lookup is None:
|
||||
lookup = LenientLookup
|
||||
elif isinstance(lookup, basestring):
|
||||
lookup = {'lenient': LenientLookup, 'strict': StrictLookup}[lookup]
|
||||
self._globals = lookup.globals
|
||||
|
||||
def __getstate__(self):
|
||||
state = {'source': self.source, 'ast': self.ast,
|
||||
'lookup': self._globals.im_self}
|
||||
c = self.code
|
||||
state['code'] = (c.co_nlocals, c.co_stacksize, c.co_flags, c.co_code,
|
||||
c.co_consts, c.co_names, c.co_varnames, c.co_filename,
|
||||
c.co_name, c.co_firstlineno, c.co_lnotab, (), ())
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.source = state['source']
|
||||
self.ast = state['ast']
|
||||
self.code = new.code(0, *state['code'])
|
||||
self._globals = state['lookup'].globals
|
||||
|
||||
def __eq__(self, other):
|
||||
return (type(other) == type(self)) and (self.code == other.code)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.code)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r)' % (self.__class__.__name__, self.source)
|
||||
|
||||
|
||||
class Expression(Code):
|
||||
"""Evaluates Python expressions used in templates.
|
||||
|
||||
>>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'})
|
||||
>>> Expression('test').evaluate(data)
|
||||
'Foo'
|
||||
|
||||
>>> Expression('items[0]').evaluate(data)
|
||||
1
|
||||
>>> Expression('items[-1]').evaluate(data)
|
||||
3
|
||||
>>> Expression('dict["some"]').evaluate(data)
|
||||
'thing'
|
||||
|
||||
Similar to e.g. Javascript, expressions in templates can use the dot
|
||||
notation for attribute access to access items in mappings:
|
||||
|
||||
>>> Expression('dict.some').evaluate(data)
|
||||
'thing'
|
||||
|
||||
This also works the other way around: item access can be used to access
|
||||
any object attribute:
|
||||
|
||||
>>> class MyClass(object):
|
||||
... myattr = 'Bar'
|
||||
>>> data = dict(mine=MyClass(), key='myattr')
|
||||
>>> Expression('mine.myattr').evaluate(data)
|
||||
'Bar'
|
||||
>>> Expression('mine["myattr"]').evaluate(data)
|
||||
'Bar'
|
||||
>>> Expression('mine[key]').evaluate(data)
|
||||
'Bar'
|
||||
|
||||
All of the standard Python operators are available to template expressions.
|
||||
Built-in functions such as ``len()`` are also available in template
|
||||
expressions:
|
||||
|
||||
>>> data = dict(items=[1, 2, 3])
|
||||
>>> Expression('len(items)').evaluate(data)
|
||||
3
|
||||
"""
|
||||
__slots__ = []
|
||||
mode = 'eval'
|
||||
|
||||
def evaluate(self, data):
|
||||
"""Evaluate the expression against the given data dictionary.
|
||||
|
||||
:param data: a mapping containing the data to evaluate against
|
||||
:return: the result of the evaluation
|
||||
"""
|
||||
__traceback_hide__ = 'before_and_this'
|
||||
_globals = self._globals(data)
|
||||
code = self.code
|
||||
if not isinstance(code, unicode) and isinstance(code, basestring):
|
||||
code = code.decode('utf-8', 'replace')
|
||||
return eval(code, _globals, {'__data__': data})
|
||||
|
||||
|
||||
class Suite(Code):
|
||||
"""Executes Python statements used in templates.
|
||||
|
||||
>>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'})
|
||||
>>> Suite("foo = dict['some']").execute(data)
|
||||
>>> data['foo']
|
||||
'thing'
|
||||
"""
|
||||
__slots__ = []
|
||||
mode = 'exec'
|
||||
|
||||
def execute(self, data):
|
||||
"""Execute the suite in the given data dictionary.
|
||||
|
||||
:param data: a mapping containing the data to execute in
|
||||
"""
|
||||
__traceback_hide__ = 'before_and_this'
|
||||
_globals = self._globals(data)
|
||||
exec self.code in _globals, data
|
||||
|
||||
|
||||
UNDEFINED = object()
|
||||
|
||||
|
||||
class UndefinedError(TemplateRuntimeError):
|
||||
"""Exception thrown when a template expression attempts to access a variable
|
||||
not defined in the context.
|
||||
|
||||
:see: `LenientLookup`, `StrictLookup`
|
||||
"""
|
||||
def __init__(self, name, owner=UNDEFINED):
|
||||
if owner is not UNDEFINED:
|
||||
message = '%s has no member named "%s"' % (repr(owner), name)
|
||||
else:
|
||||
message = '"%s" not defined' % name
|
||||
TemplateRuntimeError.__init__(self, message)
|
||||
|
||||
|
||||
class Undefined(object):
|
||||
"""Represents a reference to an undefined variable.
|
||||
|
||||
Unlike the Python runtime, template expressions can refer to an undefined
|
||||
variable without causing a `NameError` to be raised. The result will be an
|
||||
instance of the `Undefined` class, which is treated the same as ``False`` in
|
||||
conditions, but raise an exception on any other operation:
|
||||
|
||||
>>> foo = Undefined('foo')
|
||||
>>> bool(foo)
|
||||
False
|
||||
>>> list(foo)
|
||||
[]
|
||||
>>> print foo
|
||||
undefined
|
||||
|
||||
However, calling an undefined variable, or trying to access an attribute
|
||||
of that variable, will raise an exception that includes the name used to
|
||||
reference that undefined variable.
|
||||
|
||||
>>> foo('bar')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
UndefinedError: "foo" not defined
|
||||
|
||||
>>> foo.bar
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
UndefinedError: "foo" not defined
|
||||
|
||||
:see: `LenientLookup`
|
||||
"""
|
||||
__slots__ = ['_name', '_owner']
|
||||
|
||||
def __init__(self, name, owner=UNDEFINED):
|
||||
"""Initialize the object.
|
||||
|
||||
:param name: the name of the reference
|
||||
:param owner: the owning object, if the variable is accessed as a member
|
||||
"""
|
||||
self._name = name
|
||||
self._owner = owner
|
||||
|
||||
def __iter__(self):
|
||||
return iter([])
|
||||
|
||||
def __nonzero__(self):
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r>' % (self.__class__.__name__, self._name)
|
||||
|
||||
def __str__(self):
|
||||
return 'undefined'
|
||||
|
||||
def _die(self, *args, **kwargs):
|
||||
"""Raise an `UndefinedError`."""
|
||||
__traceback_hide__ = True
|
||||
raise UndefinedError(self._name, self._owner)
|
||||
__call__ = __getattr__ = __getitem__ = _die
|
||||
|
||||
|
||||
class LookupBase(object):
|
||||
"""Abstract base class for variable lookup implementations."""
|
||||
|
||||
def globals(cls, data):
|
||||
"""Construct the globals dictionary to use as the execution context for
|
||||
the expression or suite.
|
||||
"""
|
||||
return {
|
||||
'__data__': data,
|
||||
'_lookup_name': cls.lookup_name,
|
||||
'_lookup_attr': cls.lookup_attr,
|
||||
'_lookup_item': cls.lookup_item,
|
||||
'_star_import_patch': _star_import_patch,
|
||||
'UndefinedError': UndefinedError,
|
||||
}
|
||||
globals = classmethod(globals)
|
||||
|
||||
def lookup_name(cls, data, name):
|
||||
__traceback_hide__ = True
|
||||
val = data.get(name, UNDEFINED)
|
||||
if val is UNDEFINED:
|
||||
val = BUILTINS.get(name, val)
|
||||
if val is UNDEFINED:
|
||||
val = cls.undefined(name)
|
||||
return val
|
||||
lookup_name = classmethod(lookup_name)
|
||||
|
||||
def lookup_attr(cls, obj, key):
|
||||
__traceback_hide__ = True
|
||||
try:
|
||||
val = getattr(obj, key)
|
||||
except AttributeError:
|
||||
if hasattr(obj.__class__, key):
|
||||
raise
|
||||
else:
|
||||
try:
|
||||
val = obj[key]
|
||||
except (KeyError, TypeError):
|
||||
val = cls.undefined(key, owner=obj)
|
||||
return val
|
||||
lookup_attr = classmethod(lookup_attr)
|
||||
|
||||
def lookup_item(cls, obj, key):
|
||||
__traceback_hide__ = True
|
||||
if len(key) == 1:
|
||||
key = key[0]
|
||||
try:
|
||||
return obj[key]
|
||||
except (AttributeError, KeyError, IndexError, TypeError), e:
|
||||
if isinstance(key, basestring):
|
||||
val = getattr(obj, key, UNDEFINED)
|
||||
if val is UNDEFINED:
|
||||
val = cls.undefined(key, owner=obj)
|
||||
return val
|
||||
raise
|
||||
lookup_item = classmethod(lookup_item)
|
||||
|
||||
def undefined(cls, key, owner=UNDEFINED):
|
||||
"""Can be overridden by subclasses to specify behavior when undefined
|
||||
variables are accessed.
|
||||
|
||||
:param key: the name of the variable
|
||||
:param owner: the owning object, if the variable is accessed as a member
|
||||
"""
|
||||
raise NotImplementedError
|
||||
undefined = classmethod(undefined)
|
||||
|
||||
|
||||
class LenientLookup(LookupBase):
|
||||
"""Default variable lookup mechanism for expressions.
|
||||
|
||||
When an undefined variable is referenced using this lookup style, the
|
||||
reference evaluates to an instance of the `Undefined` class:
|
||||
|
||||
>>> expr = Expression('nothing', lookup='lenient')
|
||||
>>> undef = expr.evaluate({})
|
||||
>>> undef
|
||||
<Undefined 'nothing'>
|
||||
|
||||
The same will happen when a non-existing attribute or item is accessed on
|
||||
an existing object:
|
||||
|
||||
>>> expr = Expression('something.nil', lookup='lenient')
|
||||
>>> expr.evaluate({'something': dict()})
|
||||
<Undefined 'nil'>
|
||||
|
||||
See the documentation of the `Undefined` class for details on the behavior
|
||||
of such objects.
|
||||
|
||||
:see: `StrictLookup`
|
||||
"""
|
||||
def undefined(cls, key, owner=UNDEFINED):
|
||||
"""Return an ``Undefined`` object."""
|
||||
__traceback_hide__ = True
|
||||
return Undefined(key, owner=owner)
|
||||
undefined = classmethod(undefined)
|
||||
|
||||
|
||||
class StrictLookup(LookupBase):
|
||||
"""Strict variable lookup mechanism for expressions.
|
||||
|
||||
Referencing an undefined variable using this lookup style will immediately
|
||||
raise an ``UndefinedError``:
|
||||
|
||||
>>> expr = Expression('nothing', lookup='strict')
|
||||
>>> expr.evaluate({})
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
UndefinedError: "nothing" not defined
|
||||
|
||||
The same happens when a non-existing attribute or item is accessed on an
|
||||
existing object:
|
||||
|
||||
>>> expr = Expression('something.nil', lookup='strict')
|
||||
>>> expr.evaluate({'something': dict()})
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
UndefinedError: {} has no member named "nil"
|
||||
"""
|
||||
def undefined(cls, key, owner=UNDEFINED):
|
||||
"""Raise an ``UndefinedError`` immediately."""
|
||||
__traceback_hide__ = True
|
||||
raise UndefinedError(key, owner=owner)
|
||||
undefined = classmethod(undefined)
|
||||
|
||||
|
||||
def _parse(source, mode='eval'):
|
||||
source = source.strip()
|
||||
if mode == 'exec':
|
||||
lines = [line.expandtabs() for line in source.splitlines()]
|
||||
if lines:
|
||||
first = lines[0]
|
||||
rest = dedent('\n'.join(lines[1:])).rstrip()
|
||||
if first.rstrip().endswith(':') and not rest[0].isspace():
|
||||
rest = '\n'.join([' %s' % line for line in rest.splitlines()])
|
||||
source = '\n'.join([first, rest])
|
||||
if isinstance(source, unicode):
|
||||
source = '\xef\xbb\xbf' + source.encode('utf-8')
|
||||
return parse(source, mode)
|
||||
|
||||
def _compile(node, source=None, mode='eval', filename=None, lineno=-1,
|
||||
xform=None):
|
||||
if xform is None:
|
||||
xform = {'eval': ExpressionASTTransformer}.get(mode,
|
||||
TemplateASTTransformer)
|
||||
tree = xform().visit(node)
|
||||
if isinstance(filename, unicode):
|
||||
# unicode file names not allowed for code objects
|
||||
filename = filename.encode('utf-8', 'replace')
|
||||
elif not filename:
|
||||
filename = '<string>'
|
||||
tree.filename = filename
|
||||
if lineno <= 0:
|
||||
lineno = 1
|
||||
|
||||
if mode == 'eval':
|
||||
gen = ExpressionCodeGenerator(tree)
|
||||
name = '<Expression %r>' % (source or '?')
|
||||
else:
|
||||
gen = ModuleCodeGenerator(tree)
|
||||
lines = source.splitlines()
|
||||
if not lines:
|
||||
extract = ''
|
||||
else:
|
||||
extract = lines[0]
|
||||
if len(lines) > 1:
|
||||
extract += ' ...'
|
||||
name = '<Suite %r>' % (extract)
|
||||
gen.optimized = True
|
||||
code = gen.getCode()
|
||||
|
||||
# We'd like to just set co_firstlineno, but it's readonly. So we need to
|
||||
# clone the code object while adjusting the line number
|
||||
return new.code(0, code.co_nlocals, code.co_stacksize,
|
||||
code.co_flags | 0x0040, code.co_code, code.co_consts,
|
||||
code.co_names, code.co_varnames, filename, name, lineno,
|
||||
code.co_lnotab, (), ())
|
||||
|
||||
BUILTINS = __builtin__.__dict__.copy()
|
||||
BUILTINS.update({'Markup': Markup, 'Undefined': Undefined})
|
||||
CONSTANTS = frozenset(['False', 'True', 'None', 'NotImplemented', 'Ellipsis'])
|
||||
|
||||
|
||||
class ASTTransformer(object):
|
||||
"""General purpose base class for AST transformations.
|
||||
|
||||
Every visitor method can be overridden to return an AST node that has been
|
||||
altered or replaced in some way.
|
||||
"""
|
||||
|
||||
def visit(self, node):
|
||||
if node is None:
|
||||
return None
|
||||
if type(node) is tuple:
|
||||
return tuple([self.visit(n) for n in node])
|
||||
visitor = getattr(self, 'visit%s' % node.__class__.__name__,
|
||||
self._visitDefault)
|
||||
return visitor(node)
|
||||
|
||||
def _clone(self, node, *args):
|
||||
lineno = getattr(node, 'lineno', None)
|
||||
node = node.__class__(*args)
|
||||
if lineno is not None:
|
||||
node.lineno = lineno
|
||||
if isinstance(node, (ast.Class, ast.Function, ast.Lambda)) or \
|
||||
hasattr(ast, 'GenExpr') and isinstance(node, ast.GenExpr):
|
||||
node.filename = '<string>' # workaround for bug in pycodegen
|
||||
return node
|
||||
|
||||
def _visitDefault(self, node):
|
||||
return node
|
||||
|
||||
def visitExpression(self, node):
|
||||
return self._clone(node, self.visit(node.node))
|
||||
|
||||
def visitModule(self, node):
|
||||
return self._clone(node, node.doc, self.visit(node.node))
|
||||
|
||||
def visitStmt(self, node):
|
||||
return self._clone(node, [self.visit(x) for x in node.nodes])
|
||||
|
||||
# Classes, Functions & Accessors
|
||||
|
||||
def visitCallFunc(self, node):
|
||||
return self._clone(node, self.visit(node.node),
|
||||
[self.visit(x) for x in node.args],
|
||||
node.star_args and self.visit(node.star_args) or None,
|
||||
node.dstar_args and self.visit(node.dstar_args) or None
|
||||
)
|
||||
|
||||
def visitClass(self, node):
|
||||
return self._clone(node, node.name, [self.visit(x) for x in node.bases],
|
||||
node.doc, self.visit(node.code)
|
||||
)
|
||||
|
||||
def visitFrom(self, node):
|
||||
if not has_star_import_bug or node.names != [('*', None)]:
|
||||
# This is a Python 2.4 bug. Only if we have a broken Python
|
||||
# version we have to apply the hack
|
||||
return node
|
||||
new_node = ast.Discard(ast.CallFunc(
|
||||
ast.Name('_star_import_patch'),
|
||||
[ast.Name('__data__'), ast.Const(node.modname)], None, None
|
||||
))
|
||||
if hasattr(node, 'lineno'): # No lineno in Python 2.3
|
||||
new_node.lineno = node.lineno
|
||||
return new_node
|
||||
|
||||
def visitFunction(self, node):
|
||||
args = []
|
||||
if hasattr(node, 'decorators'):
|
||||
args.append(self.visit(node.decorators))
|
||||
return self._clone(node, *args + [
|
||||
node.name,
|
||||
node.argnames,
|
||||
[self.visit(x) for x in node.defaults],
|
||||
node.flags,
|
||||
node.doc,
|
||||
self.visit(node.code)
|
||||
])
|
||||
|
||||
def visitGetattr(self, node):
|
||||
return self._clone(node, self.visit(node.expr), node.attrname)
|
||||
|
||||
def visitLambda(self, node):
|
||||
node = self._clone(node, node.argnames,
|
||||
[self.visit(x) for x in node.defaults], node.flags,
|
||||
self.visit(node.code)
|
||||
)
|
||||
return node
|
||||
|
||||
def visitSubscript(self, node):
|
||||
return self._clone(node, self.visit(node.expr), node.flags,
|
||||
[self.visit(x) for x in node.subs]
|
||||
)
|
||||
|
||||
# Statements
|
||||
|
||||
def visitAssert(self, node):
|
||||
return self._clone(node, self.visit(node.test), self.visit(node.fail))
|
||||
|
||||
def visitAssign(self, node):
|
||||
return self._clone(node, [self.visit(x) for x in node.nodes],
|
||||
self.visit(node.expr)
|
||||
)
|
||||
|
||||
def visitAssAttr(self, node):
|
||||
return self._clone(node, self.visit(node.expr), node.attrname,
|
||||
node.flags
|
||||
)
|
||||
|
||||
def visitAugAssign(self, node):
|
||||
return self._clone(node, self.visit(node.node), node.op,
|
||||
self.visit(node.expr)
|
||||
)
|
||||
|
||||
def visitDecorators(self, node):
|
||||
return self._clone(node, [self.visit(x) for x in node.nodes])
|
||||
|
||||
def visitExec(self, node):
|
||||
return self._clone(node, self.visit(node.expr), self.visit(node.locals),
|
||||
self.visit(node.globals)
|
||||
)
|
||||
|
||||
def visitFor(self, node):
|
||||
return self._clone(node, self.visit(node.assign), self.visit(node.list),
|
||||
self.visit(node.body), self.visit(node.else_)
|
||||
)
|
||||
|
||||
def visitIf(self, node):
|
||||
return self._clone(node, [self.visit(x) for x in node.tests],
|
||||
self.visit(node.else_)
|
||||
)
|
||||
|
||||
def _visitPrint(self, node):
|
||||
return self._clone(node, [self.visit(x) for x in node.nodes],
|
||||
self.visit(node.dest)
|
||||
)
|
||||
visitPrint = visitPrintnl = _visitPrint
|
||||
|
||||
def visitRaise(self, node):
|
||||
return self._clone(node, self.visit(node.expr1), self.visit(node.expr2),
|
||||
self.visit(node.expr3)
|
||||
)
|
||||
|
||||
def visitReturn(self, node):
|
||||
return self._clone(node, self.visit(node.value))
|
||||
|
||||
def visitTryExcept(self, node):
|
||||
return self._clone(node, self.visit(node.body), self.visit(node.handlers),
|
||||
self.visit(node.else_)
|
||||
)
|
||||
|
||||
def visitTryFinally(self, node):
|
||||
return self._clone(node, self.visit(node.body), self.visit(node.final))
|
||||
|
||||
def visitWhile(self, node):
|
||||
return self._clone(node, self.visit(node.test), self.visit(node.body),
|
||||
self.visit(node.else_)
|
||||
)
|
||||
|
||||
def visitWith(self, node):
|
||||
return self._clone(node, self.visit(node.expr),
|
||||
[self.visit(x) for x in node.vars], self.visit(node.body)
|
||||
)
|
||||
|
||||
def visitYield(self, node):
|
||||
return self._clone(node, self.visit(node.value))
|
||||
|
||||
# Operators
|
||||
|
||||
def _visitBoolOp(self, node):
|
||||
return self._clone(node, [self.visit(x) for x in node.nodes])
|
||||
visitAnd = visitOr = visitBitand = visitBitor = visitBitxor = _visitBoolOp
|
||||
visitAssTuple = visitAssList = _visitBoolOp
|
||||
|
||||
def _visitBinOp(self, node):
|
||||
return self._clone(node,
|
||||
(self.visit(node.left), self.visit(node.right))
|
||||
)
|
||||
visitAdd = visitSub = _visitBinOp
|
||||
visitDiv = visitFloorDiv = visitMod = visitMul = visitPower = _visitBinOp
|
||||
visitLeftShift = visitRightShift = _visitBinOp
|
||||
|
||||
def visitCompare(self, node):
|
||||
return self._clone(node, self.visit(node.expr),
|
||||
[(op, self.visit(n)) for op, n in node.ops]
|
||||
)
|
||||
|
||||
def _visitUnaryOp(self, node):
|
||||
return self._clone(node, self.visit(node.expr))
|
||||
visitUnaryAdd = visitUnarySub = visitNot = visitInvert = _visitUnaryOp
|
||||
visitBackquote = visitDiscard = _visitUnaryOp
|
||||
|
||||
def visitIfExp(self, node):
|
||||
return self._clone(node, self.visit(node.test), self.visit(node.then),
|
||||
self.visit(node.else_)
|
||||
)
|
||||
|
||||
# Identifiers, Literals and Comprehensions
|
||||
|
||||
def visitDict(self, node):
|
||||
return self._clone(node,
|
||||
[(self.visit(k), self.visit(v)) for k, v in node.items]
|
||||
)
|
||||
|
||||
def visitGenExpr(self, node):
|
||||
return self._clone(node, self.visit(node.code))
|
||||
|
||||
def visitGenExprFor(self, node):
|
||||
return self._clone(node, self.visit(node.assign), self.visit(node.iter),
|
||||
[self.visit(x) for x in node.ifs]
|
||||
)
|
||||
|
||||
def visitGenExprIf(self, node):
|
||||
return self._clone(node, self.visit(node.test))
|
||||
|
||||
def visitGenExprInner(self, node):
|
||||
quals = [self.visit(x) for x in node.quals]
|
||||
return self._clone(node, self.visit(node.expr), quals)
|
||||
|
||||
def visitKeyword(self, node):
|
||||
return self._clone(node, node.name, self.visit(node.expr))
|
||||
|
||||
def visitList(self, node):
|
||||
return self._clone(node, [self.visit(n) for n in node.nodes])
|
||||
|
||||
def visitListComp(self, node):
|
||||
quals = [self.visit(x) for x in node.quals]
|
||||
return self._clone(node, self.visit(node.expr), quals)
|
||||
|
||||
def visitListCompFor(self, node):
|
||||
return self._clone(node, self.visit(node.assign), self.visit(node.list),
|
||||
[self.visit(x) for x in node.ifs]
|
||||
)
|
||||
|
||||
def visitListCompIf(self, node):
|
||||
return self._clone(node, self.visit(node.test))
|
||||
|
||||
def visitSlice(self, node):
|
||||
return self._clone(node, self.visit(node.expr), node.flags,
|
||||
node.lower and self.visit(node.lower) or None,
|
||||
node.upper and self.visit(node.upper) or None
|
||||
)
|
||||
|
||||
def visitSliceobj(self, node):
|
||||
return self._clone(node, [self.visit(x) for x in node.nodes])
|
||||
|
||||
def visitTuple(self, node):
|
||||
return self._clone(node, [self.visit(n) for n in node.nodes])
|
||||
|
||||
|
||||
class TemplateASTTransformer(ASTTransformer):
|
||||
"""Concrete AST transformer that implements the AST transformations needed
|
||||
for code embedded in templates.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.locals = [CONSTANTS]
|
||||
|
||||
def visitConst(self, node):
|
||||
if isinstance(node.value, str):
|
||||
try: # If the string is ASCII, return a `str` object
|
||||
node.value.decode('ascii')
|
||||
except ValueError: # Otherwise return a `unicode` object
|
||||
return ast.Const(node.value.decode('utf-8'))
|
||||
return node
|
||||
|
||||
def visitAssName(self, node):
|
||||
if len(self.locals) > 1:
|
||||
self.locals[-1].add(node.name)
|
||||
return node
|
||||
|
||||
def visitAugAssign(self, node):
|
||||
if isinstance(node.node, ast.Name) \
|
||||
and node.node.name not in flatten(self.locals):
|
||||
name = node.node.name
|
||||
node.node = ast.Subscript(ast.Name('__data__'), 'OP_APPLY',
|
||||
[ast.Const(name)])
|
||||
node.expr = self.visit(node.expr)
|
||||
return ast.If([
|
||||
(ast.Compare(ast.Const(name), [('in', ast.Name('__data__'))]),
|
||||
ast.Stmt([node]))],
|
||||
ast.Stmt([ast.Raise(ast.CallFunc(ast.Name('UndefinedError'),
|
||||
[ast.Const(name)]),
|
||||
None, None)]))
|
||||
else:
|
||||
return ASTTransformer.visitAugAssign(self, node)
|
||||
|
||||
def visitClass(self, node):
|
||||
if len(self.locals) > 1:
|
||||
self.locals[-1].add(node.name)
|
||||
self.locals.append(set())
|
||||
try:
|
||||
return ASTTransformer.visitClass(self, node)
|
||||
finally:
|
||||
self.locals.pop()
|
||||
|
||||
def visitFor(self, node):
|
||||
self.locals.append(set())
|
||||
try:
|
||||
return ASTTransformer.visitFor(self, node)
|
||||
finally:
|
||||
self.locals.pop()
|
||||
|
||||
def visitFunction(self, node):
|
||||
if len(self.locals) > 1:
|
||||
self.locals[-1].add(node.name)
|
||||
self.locals.append(set(node.argnames))
|
||||
try:
|
||||
return ASTTransformer.visitFunction(self, node)
|
||||
finally:
|
||||
self.locals.pop()
|
||||
|
||||
def visitGenExpr(self, node):
|
||||
self.locals.append(set())
|
||||
try:
|
||||
return ASTTransformer.visitGenExpr(self, node)
|
||||
finally:
|
||||
self.locals.pop()
|
||||
|
||||
def visitLambda(self, node):
|
||||
self.locals.append(set(flatten(node.argnames)))
|
||||
try:
|
||||
return ASTTransformer.visitLambda(self, node)
|
||||
finally:
|
||||
self.locals.pop()
|
||||
|
||||
def visitListComp(self, node):
|
||||
self.locals.append(set())
|
||||
try:
|
||||
return ASTTransformer.visitListComp(self, node)
|
||||
finally:
|
||||
self.locals.pop()
|
||||
|
||||
def visitName(self, node):
|
||||
# If the name refers to a local inside a lambda, list comprehension, or
|
||||
# generator expression, leave it alone
|
||||
if node.name not in flatten(self.locals):
|
||||
# Otherwise, translate the name ref into a context lookup
|
||||
func_args = [ast.Name('__data__'), ast.Const(node.name)]
|
||||
node = ast.CallFunc(ast.Name('_lookup_name'), func_args)
|
||||
return node
|
||||
|
||||
|
||||
class ExpressionASTTransformer(TemplateASTTransformer):
|
||||
"""Concrete AST transformer that implements the AST transformations needed
|
||||
for code embedded in templates.
|
||||
"""
|
||||
|
||||
def visitGetattr(self, node):
|
||||
return ast.CallFunc(ast.Name('_lookup_attr'), [
|
||||
self.visit(node.expr),
|
||||
ast.Const(node.attrname)
|
||||
])
|
||||
|
||||
def visitSubscript(self, node):
|
||||
return ast.CallFunc(ast.Name('_lookup_item'), [
|
||||
self.visit(node.expr),
|
||||
ast.Tuple([self.visit(sub) for sub in node.subs])
|
||||
])
|
@ -1,151 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2007-2008 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""String interpolation routines, i.e. the splitting up a given text into some
|
||||
parts that are literal strings, and others that are Python expressions.
|
||||
"""
|
||||
|
||||
from itertools import chain
|
||||
import os
|
||||
import re
|
||||
from tokenize import PseudoToken
|
||||
|
||||
from calibre.utils.genshi.core import TEXT
|
||||
from calibre.utils.genshi.template.base import TemplateSyntaxError, EXPR
|
||||
from calibre.utils.genshi.template.eval import Expression
|
||||
|
||||
__all__ = ['interpolate']
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
NAMESTART = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
|
||||
NAMECHARS = NAMESTART + '.0123456789'
|
||||
PREFIX = '$'
|
||||
|
||||
token_re = re.compile('%s|%s(?s)' % (
|
||||
r'[uU]?[rR]?("""|\'\'\')((?<!\\)\\\1|.)*?\1',
|
||||
PseudoToken
|
||||
))
|
||||
|
||||
def interpolate(text, filepath=None, lineno=-1, offset=0, lookup='strict'):
|
||||
"""Parse the given string and extract expressions.
|
||||
|
||||
This function is a generator that yields `TEXT` events for literal strings,
|
||||
and `EXPR` events for expressions, depending on the results of parsing the
|
||||
string.
|
||||
|
||||
>>> for kind, data, pos in interpolate("hey ${foo}bar"):
|
||||
... print kind, `data`
|
||||
TEXT u'hey '
|
||||
EXPR Expression('foo')
|
||||
TEXT u'bar'
|
||||
|
||||
:param text: the text to parse
|
||||
:param filepath: absolute path to the file in which the text was found
|
||||
(optional)
|
||||
:param lineno: the line number at which the text was found (optional)
|
||||
:param offset: the column number at which the text starts in the source
|
||||
(optional)
|
||||
:param lookup: the variable lookup mechanism; either "lenient" (the
|
||||
default), "strict", or a custom lookup class
|
||||
:return: a list of `TEXT` and `EXPR` events
|
||||
:raise TemplateSyntaxError: when a syntax error in an expression is
|
||||
encountered
|
||||
"""
|
||||
pos = [filepath, lineno, offset]
|
||||
|
||||
textbuf = []
|
||||
textpos = None
|
||||
for is_expr, chunk in chain(lex(text, pos, filepath), [(True, '')]):
|
||||
if is_expr:
|
||||
if textbuf:
|
||||
yield TEXT, u''.join(textbuf), textpos
|
||||
del textbuf[:]
|
||||
textpos = None
|
||||
if chunk:
|
||||
try:
|
||||
expr = Expression(chunk.strip(), pos[0], pos[1],
|
||||
lookup=lookup)
|
||||
yield EXPR, expr, tuple(pos)
|
||||
except SyntaxError, err:
|
||||
raise TemplateSyntaxError(err, filepath, pos[1],
|
||||
pos[2] + (err.offset or 0))
|
||||
else:
|
||||
textbuf.append(chunk)
|
||||
if textpos is None:
|
||||
textpos = tuple(pos)
|
||||
|
||||
if '\n' in chunk:
|
||||
lines = chunk.splitlines()
|
||||
pos[1] += len(lines) - 1
|
||||
pos[2] += len(lines[-1])
|
||||
else:
|
||||
pos[2] += len(chunk)
|
||||
|
||||
def lex(text, textpos, filepath):
|
||||
offset = pos = 0
|
||||
end = len(text)
|
||||
escaped = False
|
||||
|
||||
while 1:
|
||||
if escaped:
|
||||
offset = text.find(PREFIX, offset + 2)
|
||||
escaped = False
|
||||
else:
|
||||
offset = text.find(PREFIX, pos)
|
||||
if offset < 0 or offset == end - 1:
|
||||
break
|
||||
next = text[offset + 1]
|
||||
|
||||
if next == '{':
|
||||
if offset > pos:
|
||||
yield False, text[pos:offset]
|
||||
pos = offset + 2
|
||||
level = 1
|
||||
while level:
|
||||
match = token_re.match(text, pos)
|
||||
if match is None:
|
||||
raise TemplateSyntaxError('invalid syntax', filepath,
|
||||
*textpos[1:])
|
||||
pos = match.end()
|
||||
tstart, tend = match.regs[3]
|
||||
token = text[tstart:tend]
|
||||
if token == '{':
|
||||
level += 1
|
||||
elif token == '}':
|
||||
level -= 1
|
||||
yield True, text[offset + 2:pos - 1]
|
||||
|
||||
elif next in NAMESTART:
|
||||
if offset > pos:
|
||||
yield False, text[pos:offset]
|
||||
pos = offset
|
||||
pos += 1
|
||||
while pos < end:
|
||||
char = text[pos]
|
||||
if char not in NAMECHARS:
|
||||
break
|
||||
pos += 1
|
||||
yield True, text[offset + 1:pos].strip()
|
||||
|
||||
elif not escaped and next == PREFIX:
|
||||
if offset > pos:
|
||||
yield False, text[pos:offset]
|
||||
escaped = True
|
||||
pos = offset + 1
|
||||
|
||||
else:
|
||||
yield False, text[pos:offset + 1]
|
||||
pos = offset + 1
|
||||
|
||||
if pos < end:
|
||||
yield False, text[pos:]
|
@ -1,328 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Template loading and caching."""
|
||||
|
||||
import os
|
||||
try:
|
||||
import threading
|
||||
except ImportError:
|
||||
import dummy_threading as threading
|
||||
|
||||
from calibre.utils.genshi.template.base import TemplateError
|
||||
from calibre.utils.genshi.util import LRUCache
|
||||
|
||||
__all__ = ['TemplateLoader', 'TemplateNotFound']
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
class TemplateNotFound(TemplateError):
|
||||
"""Exception raised when a specific template file could not be found."""
|
||||
|
||||
def __init__(self, name, search_path):
|
||||
"""Create the exception.
|
||||
|
||||
:param name: the filename of the template
|
||||
:param search_path: the search path used to lookup the template
|
||||
"""
|
||||
TemplateError.__init__(self, 'Template "%s" not found' % name)
|
||||
self.search_path = search_path
|
||||
|
||||
|
||||
class TemplateLoader(object):
|
||||
"""Responsible for loading templates from files on the specified search
|
||||
path.
|
||||
|
||||
>>> import tempfile
|
||||
>>> fd, path = tempfile.mkstemp(suffix='.html', prefix='template')
|
||||
>>> os.write(fd, '<p>$var</p>')
|
||||
11
|
||||
>>> os.close(fd)
|
||||
|
||||
The template loader accepts a list of directory paths that are then used
|
||||
when searching for template files, in the given order:
|
||||
|
||||
>>> loader = TemplateLoader([os.path.dirname(path)])
|
||||
|
||||
The `load()` method first checks the template cache whether the requested
|
||||
template has already been loaded. If not, it attempts to locate the
|
||||
template file, and returns the corresponding `Template` object:
|
||||
|
||||
>>> from genshi.template import MarkupTemplate
|
||||
>>> template = loader.load(os.path.basename(path))
|
||||
>>> isinstance(template, MarkupTemplate)
|
||||
True
|
||||
|
||||
Template instances are cached: requesting a template with the same name
|
||||
results in the same instance being returned:
|
||||
|
||||
>>> loader.load(os.path.basename(path)) is template
|
||||
True
|
||||
|
||||
The `auto_reload` option can be used to control whether a template should
|
||||
be automatically reloaded when the file it was loaded from has been
|
||||
changed. Disable this automatic reloading to improve performance.
|
||||
|
||||
>>> os.remove(path)
|
||||
"""
|
||||
def __init__(self, search_path=None, auto_reload=False,
|
||||
default_encoding=None, max_cache_size=25, default_class=None,
|
||||
variable_lookup='strict', allow_exec=True, callback=None):
|
||||
"""Create the template laoder.
|
||||
|
||||
:param search_path: a list of absolute path names that should be
|
||||
searched for template files, or a string containing
|
||||
a single absolute path; alternatively, any item on
|
||||
the list may be a ''load function'' that is passed
|
||||
a filename and returns a file-like object and some
|
||||
metadata
|
||||
:param auto_reload: whether to check the last modification time of
|
||||
template files, and reload them if they have changed
|
||||
:param default_encoding: the default encoding to assume when loading
|
||||
templates; defaults to UTF-8
|
||||
:param max_cache_size: the maximum number of templates to keep in the
|
||||
cache
|
||||
:param default_class: the default `Template` subclass to use when
|
||||
instantiating templates
|
||||
:param variable_lookup: the variable lookup mechanism; either "strict"
|
||||
(the default), "lenient", or a custom lookup
|
||||
class
|
||||
:param allow_exec: whether to allow Python code blocks in templates
|
||||
:param callback: (optional) a callback function that is invoked after a
|
||||
template was initialized by this loader; the function
|
||||
is passed the template object as only argument. This
|
||||
callback can be used for example to add any desired
|
||||
filters to the template
|
||||
:see: `LenientLookup`, `StrictLookup`
|
||||
|
||||
:note: Changed in 0.5: Added the `allow_exec` argument
|
||||
"""
|
||||
from calibre.utils.genshi.template.markup import MarkupTemplate
|
||||
|
||||
self.search_path = search_path
|
||||
if self.search_path is None:
|
||||
self.search_path = []
|
||||
elif not isinstance(self.search_path, (list, tuple)):
|
||||
self.search_path = [self.search_path]
|
||||
|
||||
self.auto_reload = auto_reload
|
||||
"""Whether templates should be reloaded when the underlying file is
|
||||
changed"""
|
||||
|
||||
self.default_encoding = default_encoding
|
||||
self.default_class = default_class or MarkupTemplate
|
||||
self.variable_lookup = variable_lookup
|
||||
self.allow_exec = allow_exec
|
||||
if callback is not None and not callable(callback):
|
||||
raise TypeError('The "callback" parameter needs to be callable')
|
||||
self.callback = callback
|
||||
self._cache = LRUCache(max_cache_size)
|
||||
self._uptodate = {}
|
||||
self._lock = threading.RLock()
|
||||
|
||||
def load(self, filename, relative_to=None, cls=None, encoding=None):
|
||||
"""Load the template with the given name.
|
||||
|
||||
If the `filename` parameter is relative, this method searches the
|
||||
search path trying to locate a template matching the given name. If the
|
||||
file name is an absolute path, the search path is ignored.
|
||||
|
||||
If the requested template is not found, a `TemplateNotFound` exception
|
||||
is raised. Otherwise, a `Template` object is returned that represents
|
||||
the parsed template.
|
||||
|
||||
Template instances are cached to avoid having to parse the same
|
||||
template file more than once. Thus, subsequent calls of this method
|
||||
with the same template file name will return the same `Template`
|
||||
object (unless the ``auto_reload`` option is enabled and the file was
|
||||
changed since the last parse.)
|
||||
|
||||
If the `relative_to` parameter is provided, the `filename` is
|
||||
interpreted as being relative to that path.
|
||||
|
||||
:param filename: the relative path of the template file to load
|
||||
:param relative_to: the filename of the template from which the new
|
||||
template is being loaded, or ``None`` if the
|
||||
template is being loaded directly
|
||||
:param cls: the class of the template object to instantiate
|
||||
:param encoding: the encoding of the template to load; defaults to the
|
||||
``default_encoding`` of the loader instance
|
||||
:return: the loaded `Template` instance
|
||||
:raises TemplateNotFound: if a template with the given name could not
|
||||
be found
|
||||
"""
|
||||
if cls is None:
|
||||
cls = self.default_class
|
||||
if relative_to and not os.path.isabs(relative_to):
|
||||
filename = os.path.join(os.path.dirname(relative_to), filename)
|
||||
filename = os.path.normpath(filename)
|
||||
cachekey = filename
|
||||
|
||||
self._lock.acquire()
|
||||
try:
|
||||
# First check the cache to avoid reparsing the same file
|
||||
try:
|
||||
tmpl = self._cache[cachekey]
|
||||
if not self.auto_reload:
|
||||
return tmpl
|
||||
uptodate = self._uptodate[cachekey]
|
||||
if uptodate is not None and uptodate():
|
||||
return tmpl
|
||||
except (KeyError, OSError):
|
||||
pass
|
||||
|
||||
search_path = self.search_path
|
||||
isabs = False
|
||||
|
||||
if os.path.isabs(filename):
|
||||
# Bypass the search path if the requested filename is absolute
|
||||
search_path = [os.path.dirname(filename)]
|
||||
isabs = True
|
||||
|
||||
elif relative_to and os.path.isabs(relative_to):
|
||||
# Make sure that the directory containing the including
|
||||
# template is on the search path
|
||||
dirname = os.path.dirname(relative_to)
|
||||
if dirname not in search_path:
|
||||
search_path = list(search_path) + [dirname]
|
||||
isabs = True
|
||||
|
||||
elif not search_path:
|
||||
# Uh oh, don't know where to look for the template
|
||||
raise TemplateError('Search path for templates not configured')
|
||||
|
||||
for loadfunc in search_path:
|
||||
if isinstance(loadfunc, basestring):
|
||||
loadfunc = directory(loadfunc)
|
||||
try:
|
||||
filepath, filename, fileobj, uptodate = loadfunc(filename)
|
||||
except IOError:
|
||||
continue
|
||||
else:
|
||||
try:
|
||||
if isabs:
|
||||
# If the filename of either the included or the
|
||||
# including template is absolute, make sure the
|
||||
# included template gets an absolute path, too,
|
||||
# so that nested includes work properly without a
|
||||
# search path
|
||||
filename = filepath
|
||||
tmpl = self._instantiate(cls, fileobj, filepath,
|
||||
filename, encoding=encoding)
|
||||
if self.callback:
|
||||
self.callback(tmpl)
|
||||
self._cache[cachekey] = tmpl
|
||||
self._uptodate[cachekey] = uptodate
|
||||
finally:
|
||||
if hasattr(fileobj, 'close'):
|
||||
fileobj.close()
|
||||
return tmpl
|
||||
|
||||
raise TemplateNotFound(filename, search_path)
|
||||
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def _instantiate(self, cls, fileobj, filepath, filename, encoding=None):
|
||||
"""Instantiate and return the `Template` object based on the given
|
||||
class and parameters.
|
||||
|
||||
This function is intended for subclasses to override if they need to
|
||||
implement special template instantiation logic. Code that just uses
|
||||
the `TemplateLoader` should use the `load` method instead.
|
||||
|
||||
:param cls: the class of the template object to instantiate
|
||||
:param fileobj: a readable file-like object containing the template
|
||||
source
|
||||
:param filepath: the absolute path to the template file
|
||||
:param filename: the path to the template file relative to the search
|
||||
path
|
||||
:param encoding: the encoding of the template to load; defaults to the
|
||||
``default_encoding`` of the loader instance
|
||||
:return: the loaded `Template` instance
|
||||
:rtype: `Template`
|
||||
"""
|
||||
if encoding is None:
|
||||
encoding = self.default_encoding
|
||||
return cls(fileobj, filepath=filepath, filename=filename, loader=self,
|
||||
encoding=encoding, lookup=self.variable_lookup,
|
||||
allow_exec=self.allow_exec)
|
||||
|
||||
def directory(path):
|
||||
"""Loader factory for loading templates from a local directory.
|
||||
|
||||
:param path: the path to the local directory containing the templates
|
||||
:return: the loader function to load templates from the given directory
|
||||
:rtype: ``function``
|
||||
"""
|
||||
def _load_from_directory(filename):
|
||||
filepath = os.path.join(path, filename)
|
||||
fileobj = open(filepath, 'U')
|
||||
mtime = os.path.getmtime(filepath)
|
||||
def _uptodate():
|
||||
return mtime == os.path.getmtime(filepath)
|
||||
return filepath, filename, fileobj, _uptodate
|
||||
return _load_from_directory
|
||||
directory = staticmethod(directory)
|
||||
|
||||
def package(name, path):
|
||||
"""Loader factory for loading templates from egg package data.
|
||||
|
||||
:param name: the name of the package containing the resources
|
||||
:param path: the path inside the package data
|
||||
:return: the loader function to load templates from the given package
|
||||
:rtype: ``function``
|
||||
"""
|
||||
from pkg_resources import resource_stream
|
||||
def _load_from_package(filename):
|
||||
filepath = os.path.join(path, filename)
|
||||
return filepath, filename, resource_stream(name, filepath), None
|
||||
return _load_from_package
|
||||
package = staticmethod(package)
|
||||
|
||||
def prefixed(**delegates):
|
||||
"""Factory for a load function that delegates to other loaders
|
||||
depending on the prefix of the requested template path.
|
||||
|
||||
The prefix is stripped from the filename when passing on the load
|
||||
request to the delegate.
|
||||
|
||||
>>> load = prefixed(
|
||||
... app1 = lambda filename: ('app1', filename, None, None),
|
||||
... app2 = lambda filename: ('app2', filename, None, None)
|
||||
... )
|
||||
>>> print load('app1/foo.html')
|
||||
('app1', 'app1/foo.html', None, None)
|
||||
>>> print load('app2/bar.html')
|
||||
('app2', 'app2/bar.html', None, None)
|
||||
|
||||
:param delegates: mapping of path prefixes to loader functions
|
||||
:return: the loader function
|
||||
:rtype: ``function``
|
||||
"""
|
||||
def _dispatch_by_prefix(filename):
|
||||
for prefix, delegate in delegates.items():
|
||||
if filename.startswith(prefix):
|
||||
if isinstance(delegate, basestring):
|
||||
delegate = directory(delegate)
|
||||
filepath, _, fileobj, uptodate = delegate(
|
||||
filename[len(prefix):].lstrip('/\\')
|
||||
)
|
||||
return filepath, filename, fileobj, uptodate
|
||||
raise TemplateNotFound(filename, delegates.keys())
|
||||
return _dispatch_by_prefix
|
||||
prefixed = staticmethod(prefixed)
|
||||
|
||||
directory = TemplateLoader.directory
|
||||
package = TemplateLoader.package
|
||||
prefixed = TemplateLoader.prefixed
|
@ -1,305 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Markup templating engine."""
|
||||
|
||||
from itertools import chain
|
||||
|
||||
from calibre.utils.genshi.core import Attrs, Markup, Namespace, Stream, StreamEventKind
|
||||
from calibre.utils.genshi.core import START, END, START_NS, END_NS, TEXT, PI, COMMENT
|
||||
from calibre.utils.genshi.input import XMLParser
|
||||
from calibre.utils.genshi.template.base import BadDirectiveError, Template, \
|
||||
TemplateSyntaxError, _apply_directives, \
|
||||
EXEC, INCLUDE, SUB
|
||||
from calibre.utils.genshi.template.eval import Suite
|
||||
from calibre.utils.genshi.template.interpolation import interpolate
|
||||
from calibre.utils.genshi.template.directives import *
|
||||
from calibre.utils.genshi.template.text import NewTextTemplate
|
||||
|
||||
__all__ = ['MarkupTemplate']
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
class MarkupTemplate(Template):
|
||||
"""Implementation of the template language for XML-based templates.
|
||||
|
||||
>>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/">
|
||||
... <li py:for="item in items">${item}</li>
|
||||
... </ul>''')
|
||||
>>> print tmpl.generate(items=[1, 2, 3])
|
||||
<ul>
|
||||
<li>1</li><li>2</li><li>3</li>
|
||||
</ul>
|
||||
"""
|
||||
|
||||
DIRECTIVE_NAMESPACE = Namespace('http://genshi.edgewall.org/')
|
||||
XINCLUDE_NAMESPACE = Namespace('http://www.w3.org/2001/XInclude')
|
||||
|
||||
directives = [('def', DefDirective),
|
||||
('match', MatchDirective),
|
||||
('when', WhenDirective),
|
||||
('otherwise', OtherwiseDirective),
|
||||
('for', ForDirective),
|
||||
('if', IfDirective),
|
||||
('choose', ChooseDirective),
|
||||
('with', WithDirective),
|
||||
('replace', ReplaceDirective),
|
||||
('content', ContentDirective),
|
||||
('attrs', AttrsDirective),
|
||||
('strip', StripDirective)]
|
||||
serializer = 'xml'
|
||||
_number_conv = Markup
|
||||
|
||||
def _init_filters(self):
|
||||
Template._init_filters(self)
|
||||
# Make sure the include filter comes after the match filter
|
||||
if self.loader:
|
||||
self.filters.remove(self._include)
|
||||
self.filters += [self._match]
|
||||
if self.loader:
|
||||
self.filters.append(self._include)
|
||||
|
||||
def _parse(self, source, encoding):
|
||||
streams = [[]] # stacked lists of events of the "compiled" template
|
||||
dirmap = {} # temporary mapping of directives to elements
|
||||
ns_prefix = {}
|
||||
depth = 0
|
||||
fallbacks = []
|
||||
includes = []
|
||||
|
||||
if not isinstance(source, Stream):
|
||||
source = XMLParser(source, filename=self.filename,
|
||||
encoding=encoding)
|
||||
|
||||
for kind, data, pos in source:
|
||||
stream = streams[-1]
|
||||
|
||||
if kind is START_NS:
|
||||
# Strip out the namespace declaration for template directives
|
||||
prefix, uri = data
|
||||
ns_prefix[prefix] = uri
|
||||
if uri not in (self.DIRECTIVE_NAMESPACE,
|
||||
self.XINCLUDE_NAMESPACE):
|
||||
stream.append((kind, data, pos))
|
||||
|
||||
elif kind is END_NS:
|
||||
uri = ns_prefix.pop(data, None)
|
||||
if uri and uri not in (self.DIRECTIVE_NAMESPACE,
|
||||
self.XINCLUDE_NAMESPACE):
|
||||
stream.append((kind, data, pos))
|
||||
|
||||
elif kind is START:
|
||||
# Record any directive attributes in start tags
|
||||
tag, attrs = data
|
||||
directives = []
|
||||
strip = False
|
||||
|
||||
if tag in self.DIRECTIVE_NAMESPACE:
|
||||
cls = self._dir_by_name.get(tag.localname)
|
||||
if cls is None:
|
||||
raise BadDirectiveError(tag.localname, self.filepath,
|
||||
pos[1])
|
||||
args = dict([(name.localname, value) for name, value
|
||||
in attrs if not name.namespace])
|
||||
directives.append((cls, args, ns_prefix.copy(), pos))
|
||||
strip = True
|
||||
|
||||
new_attrs = []
|
||||
for name, value in attrs:
|
||||
if name in self.DIRECTIVE_NAMESPACE:
|
||||
cls = self._dir_by_name.get(name.localname)
|
||||
if cls is None:
|
||||
raise BadDirectiveError(name.localname,
|
||||
self.filepath, pos[1])
|
||||
directives.append((cls, value, ns_prefix.copy(), pos))
|
||||
else:
|
||||
if value:
|
||||
value = list(interpolate(value, self.filepath,
|
||||
pos[1], pos[2],
|
||||
lookup=self.lookup))
|
||||
if len(value) == 1 and value[0][0] is TEXT:
|
||||
value = value[0][1]
|
||||
else:
|
||||
value = [(TEXT, u'', pos)]
|
||||
new_attrs.append((name, value))
|
||||
new_attrs = Attrs(new_attrs)
|
||||
|
||||
if directives:
|
||||
index = self._dir_order.index
|
||||
directives.sort(lambda a, b: cmp(index(a[0]), index(b[0])))
|
||||
dirmap[(depth, tag)] = (directives, len(stream), strip)
|
||||
|
||||
if tag in self.XINCLUDE_NAMESPACE:
|
||||
if tag.localname == 'include':
|
||||
include_href = new_attrs.get('href')
|
||||
if not include_href:
|
||||
raise TemplateSyntaxError('Include misses required '
|
||||
'attribute "href"',
|
||||
self.filepath, *pos[1:])
|
||||
includes.append((include_href, new_attrs.get('parse')))
|
||||
streams.append([])
|
||||
elif tag.localname == 'fallback':
|
||||
streams.append([])
|
||||
fallbacks.append(streams[-1])
|
||||
|
||||
else:
|
||||
stream.append((kind, (tag, new_attrs), pos))
|
||||
|
||||
depth += 1
|
||||
|
||||
elif kind is END:
|
||||
depth -= 1
|
||||
|
||||
if fallbacks and data == self.XINCLUDE_NAMESPACE['fallback']:
|
||||
assert streams.pop() is fallbacks[-1]
|
||||
elif data == self.XINCLUDE_NAMESPACE['include']:
|
||||
fallback = None
|
||||
if len(fallbacks) == len(includes):
|
||||
fallback = fallbacks.pop()
|
||||
streams.pop() # discard anything between the include tags
|
||||
# and the fallback element
|
||||
stream = streams[-1]
|
||||
href, parse = includes.pop()
|
||||
try:
|
||||
cls = {
|
||||
'xml': MarkupTemplate,
|
||||
'text': NewTextTemplate
|
||||
}[parse or 'xml']
|
||||
except KeyError:
|
||||
raise TemplateSyntaxError('Invalid value for "parse" '
|
||||
'attribute of include',
|
||||
self.filepath, *pos[1:])
|
||||
stream.append((INCLUDE, (href, cls, fallback), pos))
|
||||
else:
|
||||
stream.append((kind, data, pos))
|
||||
|
||||
# If there have have directive attributes with the corresponding
|
||||
# start tag, move the events inbetween into a "subprogram"
|
||||
if (depth, data) in dirmap:
|
||||
directives, start_offset, strip = dirmap.pop((depth, data))
|
||||
substream = stream[start_offset:]
|
||||
if strip:
|
||||
substream = substream[1:-1]
|
||||
stream[start_offset:] = [(SUB, (directives, substream),
|
||||
pos)]
|
||||
|
||||
elif kind is PI and data[0] == 'python':
|
||||
if not self.allow_exec:
|
||||
raise TemplateSyntaxError('Python code blocks not allowed',
|
||||
self.filepath, *pos[1:])
|
||||
try:
|
||||
suite = Suite(data[1], self.filepath, pos[1],
|
||||
lookup=self.lookup)
|
||||
except SyntaxError, err:
|
||||
raise TemplateSyntaxError(err, self.filepath,
|
||||
pos[1] + (err.lineno or 1) - 1,
|
||||
pos[2] + (err.offset or 0))
|
||||
stream.append((EXEC, suite, pos))
|
||||
|
||||
elif kind is TEXT:
|
||||
for kind, data, pos in interpolate(data, self.filepath, pos[1],
|
||||
pos[2], lookup=self.lookup):
|
||||
stream.append((kind, data, pos))
|
||||
|
||||
elif kind is COMMENT:
|
||||
if not data.lstrip().startswith('!'):
|
||||
stream.append((kind, data, pos))
|
||||
|
||||
else:
|
||||
stream.append((kind, data, pos))
|
||||
|
||||
assert len(streams) == 1
|
||||
return streams[0]
|
||||
|
||||
def _match(self, stream, ctxt, match_templates=None, **vars):
|
||||
"""Internal stream filter that applies any defined match templates
|
||||
to the stream.
|
||||
"""
|
||||
if match_templates is None:
|
||||
match_templates = ctxt._match_templates
|
||||
|
||||
tail = []
|
||||
def _strip(stream):
|
||||
depth = 1
|
||||
while 1:
|
||||
event = stream.next()
|
||||
if event[0] is START:
|
||||
depth += 1
|
||||
elif event[0] is END:
|
||||
depth -= 1
|
||||
if depth > 0:
|
||||
yield event
|
||||
else:
|
||||
tail[:] = [event]
|
||||
break
|
||||
|
||||
for event in stream:
|
||||
|
||||
# We (currently) only care about start and end events for matching
|
||||
# We might care about namespace events in the future, though
|
||||
if not match_templates or (event[0] is not START and
|
||||
event[0] is not END):
|
||||
yield event
|
||||
continue
|
||||
|
||||
for idx, (test, path, template, hints, namespaces, directives) \
|
||||
in enumerate(match_templates):
|
||||
|
||||
if test(event, namespaces, ctxt) is True:
|
||||
if 'match_once' in hints:
|
||||
del match_templates[idx]
|
||||
idx -= 1
|
||||
|
||||
# Let the remaining match templates know about the event so
|
||||
# they get a chance to update their internal state
|
||||
for test in [mt[0] for mt in match_templates[idx + 1:]]:
|
||||
test(event, namespaces, ctxt, updateonly=True)
|
||||
|
||||
# Consume and store all events until an end event
|
||||
# corresponding to this start event is encountered
|
||||
pre_match_templates = match_templates[:idx + 1]
|
||||
if 'match_once' not in hints and 'not_recursive' in hints:
|
||||
pre_match_templates.pop()
|
||||
inner = _strip(stream)
|
||||
if pre_match_templates:
|
||||
inner = self._match(inner, ctxt, pre_match_templates)
|
||||
content = self._include(chain([event], inner, tail), ctxt)
|
||||
if 'not_buffered' not in hints:
|
||||
content = list(content)
|
||||
|
||||
if tail:
|
||||
for test in [mt[0] for mt in match_templates]:
|
||||
test(tail[0], namespaces, ctxt, updateonly=True)
|
||||
|
||||
# Make the select() function available in the body of the
|
||||
# match template
|
||||
def select(path):
|
||||
return Stream(content).select(path, namespaces, ctxt)
|
||||
vars = dict(select=select)
|
||||
|
||||
# Recursively process the output
|
||||
template = _apply_directives(template, directives, ctxt,
|
||||
**vars)
|
||||
for event in self._match(
|
||||
self._exec(
|
||||
self._eval(
|
||||
self._flatten(template, ctxt, **vars),
|
||||
ctxt, **vars),
|
||||
ctxt, **vars),
|
||||
ctxt, match_templates[idx + 1:], **vars):
|
||||
yield event
|
||||
|
||||
break
|
||||
|
||||
else: # no matches
|
||||
yield event
|
@ -1,176 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2007 Edgewall Software
|
||||
# Copyright (C) 2006 Matthew Good
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Basic support for the template engine plugin API used by TurboGears and
|
||||
CherryPy/Buffet.
|
||||
"""
|
||||
|
||||
from pkg_resources import resource_filename
|
||||
|
||||
from calibre.utils.genshi.input import ET, HTML, XML
|
||||
from calibre.utils.genshi.output import DocType
|
||||
from calibre.utils.genshi.template.base import Template
|
||||
from calibre.utils.genshi.template.loader import TemplateLoader
|
||||
from calibre.utils.genshi.template.markup import MarkupTemplate
|
||||
from calibre.utils.genshi.template.text import TextTemplate, NewTextTemplate
|
||||
|
||||
__all__ = ['ConfigurationError', 'AbstractTemplateEnginePlugin',
|
||||
'MarkupTemplateEnginePlugin', 'TextTemplateEnginePlugin']
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
class ConfigurationError(ValueError):
|
||||
"""Exception raised when invalid plugin options are encountered."""
|
||||
|
||||
|
||||
class AbstractTemplateEnginePlugin(object):
|
||||
"""Implementation of the plugin API."""
|
||||
|
||||
template_class = None
|
||||
extension = None
|
||||
|
||||
def __init__(self, extra_vars_func=None, options=None):
|
||||
self.get_extra_vars = extra_vars_func
|
||||
if options is None:
|
||||
options = {}
|
||||
self.options = options
|
||||
|
||||
self.default_encoding = options.get('genshi.default_encoding', 'utf-8')
|
||||
auto_reload = options.get('genshi.auto_reload', '1')
|
||||
if isinstance(auto_reload, basestring):
|
||||
auto_reload = auto_reload.lower() in ('1', 'on', 'yes', 'true')
|
||||
search_path = filter(None, options.get('genshi.search_path', '').split(':'))
|
||||
self.use_package_naming = not search_path
|
||||
try:
|
||||
max_cache_size = int(options.get('genshi.max_cache_size', 25))
|
||||
except ValueError:
|
||||
raise ConfigurationError('Invalid value for max_cache_size: "%s"' %
|
||||
options.get('genshi.max_cache_size'))
|
||||
|
||||
loader_callback = options.get('genshi.loader_callback', None)
|
||||
if loader_callback and not callable(loader_callback):
|
||||
raise ConfigurationError('loader callback must be a function')
|
||||
|
||||
lookup_errors = options.get('genshi.lookup_errors', 'strict')
|
||||
if lookup_errors not in ('lenient', 'strict'):
|
||||
raise ConfigurationError('Unknown lookup errors mode "%s"' %
|
||||
lookup_errors)
|
||||
|
||||
try:
|
||||
allow_exec = bool(options.get('genshi.allow_exec', True))
|
||||
except ValueError:
|
||||
raise ConfigurationError('Invalid value for allow_exec "%s"' %
|
||||
options.get('genshi.allow_exec'))
|
||||
|
||||
self.loader = TemplateLoader(filter(None, search_path),
|
||||
auto_reload=auto_reload,
|
||||
max_cache_size=max_cache_size,
|
||||
default_class=self.template_class,
|
||||
variable_lookup=lookup_errors,
|
||||
allow_exec=allow_exec,
|
||||
callback=loader_callback)
|
||||
|
||||
def load_template(self, templatename, template_string=None):
|
||||
"""Find a template specified in python 'dot' notation, or load one from
|
||||
a string.
|
||||
"""
|
||||
if template_string is not None:
|
||||
return self.template_class(template_string)
|
||||
|
||||
if self.use_package_naming:
|
||||
divider = templatename.rfind('.')
|
||||
if divider >= 0:
|
||||
package = templatename[:divider]
|
||||
basename = templatename[divider + 1:] + self.extension
|
||||
templatename = resource_filename(package, basename)
|
||||
|
||||
return self.loader.load(templatename)
|
||||
|
||||
def _get_render_options(self, format=None, fragment=False):
|
||||
if format is None:
|
||||
format = self.default_format
|
||||
kwargs = {'method': format}
|
||||
if self.default_encoding:
|
||||
kwargs['encoding'] = self.default_encoding
|
||||
return kwargs
|
||||
|
||||
def render(self, info, format=None, fragment=False, template=None):
|
||||
"""Render the template to a string using the provided info."""
|
||||
kwargs = self._get_render_options(format=format, fragment=fragment)
|
||||
return self.transform(info, template).render(**kwargs)
|
||||
|
||||
def transform(self, info, template):
|
||||
"""Render the output to an event stream."""
|
||||
if not isinstance(template, Template):
|
||||
template = self.load_template(template)
|
||||
return template.generate(**info)
|
||||
|
||||
|
||||
class MarkupTemplateEnginePlugin(AbstractTemplateEnginePlugin):
|
||||
"""Implementation of the plugin API for markup templates."""
|
||||
|
||||
template_class = MarkupTemplate
|
||||
extension = '.html'
|
||||
|
||||
def __init__(self, extra_vars_func=None, options=None):
|
||||
AbstractTemplateEnginePlugin.__init__(self, extra_vars_func, options)
|
||||
|
||||
default_doctype = self.options.get('genshi.default_doctype')
|
||||
if default_doctype:
|
||||
doctype = DocType.get(default_doctype)
|
||||
if doctype is None:
|
||||
raise ConfigurationError('Unknown doctype %r' % default_doctype)
|
||||
self.default_doctype = doctype
|
||||
else:
|
||||
self.default_doctype = None
|
||||
|
||||
format = self.options.get('genshi.default_format', 'html').lower()
|
||||
if format not in ('html', 'xhtml', 'xml', 'text'):
|
||||
raise ConfigurationError('Unknown output format %r' % format)
|
||||
self.default_format = format
|
||||
|
||||
def _get_render_options(self, format=None, fragment=False):
|
||||
kwargs = super(MarkupTemplateEnginePlugin,
|
||||
self)._get_render_options(format, fragment)
|
||||
if self.default_doctype and not fragment:
|
||||
kwargs['doctype'] = self.default_doctype
|
||||
return kwargs
|
||||
|
||||
def transform(self, info, template):
|
||||
"""Render the output to an event stream."""
|
||||
data = {'ET': ET, 'HTML': HTML, 'XML': XML}
|
||||
if self.get_extra_vars:
|
||||
data.update(self.get_extra_vars())
|
||||
data.update(info)
|
||||
return super(MarkupTemplateEnginePlugin, self).transform(data, template)
|
||||
|
||||
|
||||
class TextTemplateEnginePlugin(AbstractTemplateEnginePlugin):
|
||||
"""Implementation of the plugin API for text templates."""
|
||||
|
||||
template_class = TextTemplate
|
||||
extension = '.txt'
|
||||
default_format = 'text'
|
||||
|
||||
def __init__(self, extra_vars_func=None, options=None):
|
||||
if options is None:
|
||||
options = {}
|
||||
|
||||
new_syntax = options.get('genshi.new_text_syntax')
|
||||
if isinstance(new_syntax, basestring):
|
||||
new_syntax = new_syntax.lower() in ('1', 'on', 'yes', 'true')
|
||||
if new_syntax:
|
||||
self.template_class = NewTextTemplate
|
||||
|
||||
AbstractTemplateEnginePlugin.__init__(self, extra_vars_func, options)
|
@ -1,333 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Plain text templating engine.
|
||||
|
||||
This module implements two template language syntaxes, at least for a certain
|
||||
transitional period. `OldTextTemplate` (aliased to just `TextTemplate`) defines
|
||||
a syntax that was inspired by Cheetah/Velocity. `NewTextTemplate` on the other
|
||||
hand is inspired by the syntax of the Django template language, which has more
|
||||
explicit delimiting of directives, and is more flexible with regards to
|
||||
white space and line breaks.
|
||||
|
||||
In a future release, `OldTextTemplate` will be phased out in favor of
|
||||
`NewTextTemplate`, as the names imply. Therefore the new syntax is strongly
|
||||
recommended for new projects, and existing projects may want to migrate to the
|
||||
new syntax to remain compatible with future Genshi releases.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from calibre.utils.genshi.core import TEXT
|
||||
from calibre.utils.genshi.template.base import BadDirectiveError, Template, \
|
||||
TemplateSyntaxError, EXEC, INCLUDE, SUB
|
||||
from calibre.utils.genshi.template.eval import Suite
|
||||
from calibre.utils.genshi.template.directives import *
|
||||
from calibre.utils.genshi.template.directives import Directive
|
||||
from calibre.utils.genshi.template.interpolation import interpolate
|
||||
|
||||
__all__ = ['NewTextTemplate', 'OldTextTemplate', 'TextTemplate']
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
class NewTextTemplate(Template):
|
||||
r"""Implementation of a simple text-based template engine. This class will
|
||||
replace `OldTextTemplate` in a future release.
|
||||
|
||||
It uses a more explicit delimiting style for directives: instead of the old
|
||||
style which required putting directives on separate lines that were prefixed
|
||||
with a ``#`` sign, directives and commenbtsr are enclosed in delimiter pairs
|
||||
(by default ``{% ... %}`` and ``{# ... #}``, respectively).
|
||||
|
||||
Variable substitution uses the same interpolation syntax as for markup
|
||||
languages: simple references are prefixed with a dollar sign, more complex
|
||||
expression enclosed in curly braces.
|
||||
|
||||
>>> tmpl = NewTextTemplate('''Dear $name,
|
||||
...
|
||||
... {# This is a comment #}
|
||||
... We have the following items for you:
|
||||
... {% for item in items %}
|
||||
... * ${'Item %d' % item}
|
||||
... {% end %}
|
||||
... ''')
|
||||
>>> print tmpl.generate(name='Joe', items=[1, 2, 3]).render()
|
||||
Dear Joe,
|
||||
<BLANKLINE>
|
||||
<BLANKLINE>
|
||||
We have the following items for you:
|
||||
<BLANKLINE>
|
||||
* Item 1
|
||||
<BLANKLINE>
|
||||
* Item 2
|
||||
<BLANKLINE>
|
||||
* Item 3
|
||||
<BLANKLINE>
|
||||
<BLANKLINE>
|
||||
|
||||
By default, no spaces or line breaks are removed. If a line break should
|
||||
not be included in the output, prefix it with a backslash:
|
||||
|
||||
>>> tmpl = NewTextTemplate('''Dear $name,
|
||||
...
|
||||
... {# This is a comment #}\
|
||||
... We have the following items for you:
|
||||
... {% for item in items %}\
|
||||
... * $item
|
||||
... {% end %}\
|
||||
... ''')
|
||||
>>> print tmpl.generate(name='Joe', items=[1, 2, 3]).render()
|
||||
Dear Joe,
|
||||
<BLANKLINE>
|
||||
We have the following items for you:
|
||||
* 1
|
||||
* 2
|
||||
* 3
|
||||
<BLANKLINE>
|
||||
|
||||
Backslashes are also used to escape the start delimiter of directives and
|
||||
comments:
|
||||
|
||||
>>> tmpl = NewTextTemplate('''Dear $name,
|
||||
...
|
||||
... \{# This is a comment #}
|
||||
... We have the following items for you:
|
||||
... {% for item in items %}\
|
||||
... * $item
|
||||
... {% end %}\
|
||||
... ''')
|
||||
>>> print tmpl.generate(name='Joe', items=[1, 2, 3]).render()
|
||||
Dear Joe,
|
||||
<BLANKLINE>
|
||||
{# This is a comment #}
|
||||
We have the following items for you:
|
||||
* 1
|
||||
* 2
|
||||
* 3
|
||||
<BLANKLINE>
|
||||
|
||||
:since: version 0.5
|
||||
"""
|
||||
directives = [('def', DefDirective),
|
||||
('when', WhenDirective),
|
||||
('otherwise', OtherwiseDirective),
|
||||
('for', ForDirective),
|
||||
('if', IfDirective),
|
||||
('choose', ChooseDirective),
|
||||
('with', WithDirective)]
|
||||
serializer = 'text'
|
||||
|
||||
_DIRECTIVE_RE = r'((?<!\\)%s\s*(\w+)\s*(.*?)\s*%s|(?<!\\)%s.*?%s)'
|
||||
_ESCAPE_RE = r'\\\n|\\(\\)|\\(%s)|\\(%s)'
|
||||
|
||||
def __init__(self, source, filepath=None, filename=None, loader=None,
|
||||
encoding=None, lookup='strict', allow_exec=False,
|
||||
delims=('{%', '%}', '{#', '#}')):
|
||||
self.delimiters = delims
|
||||
Template.__init__(self, source, filepath=filepath, filename=filename,
|
||||
loader=loader, encoding=encoding, lookup=lookup)
|
||||
|
||||
def _get_delims(self):
|
||||
return self._delims
|
||||
def _set_delims(self, delims):
|
||||
if len(delims) != 4:
|
||||
raise ValueError('delimiers tuple must have exactly four elements')
|
||||
self._delims = delims
|
||||
self._directive_re = re.compile(self._DIRECTIVE_RE % tuple(
|
||||
map(re.escape, delims)
|
||||
), re.DOTALL)
|
||||
self._escape_re = re.compile(self._ESCAPE_RE % tuple(
|
||||
map(re.escape, delims[::2])
|
||||
))
|
||||
delimiters = property(_get_delims, _set_delims, """\
|
||||
The delimiters for directives and comments. This should be a four item tuple
|
||||
of the form ``(directive_start, directive_end, comment_start,
|
||||
comment_end)``, where each item is a string.
|
||||
""")
|
||||
|
||||
def _parse(self, source, encoding):
|
||||
"""Parse the template from text input."""
|
||||
stream = [] # list of events of the "compiled" template
|
||||
dirmap = {} # temporary mapping of directives to elements
|
||||
depth = 0
|
||||
|
||||
source = source.read()
|
||||
if isinstance(source, str):
|
||||
source = source.decode(encoding or 'utf-8', 'replace')
|
||||
offset = 0
|
||||
lineno = 1
|
||||
|
||||
_escape_sub = self._escape_re.sub
|
||||
def _escape_repl(mo):
|
||||
groups = filter(None, mo.groups())
|
||||
if not groups:
|
||||
return ''
|
||||
return groups[0]
|
||||
|
||||
for idx, mo in enumerate(self._directive_re.finditer(source)):
|
||||
start, end = mo.span(1)
|
||||
if start > offset:
|
||||
text = _escape_sub(_escape_repl, source[offset:start])
|
||||
for kind, data, pos in interpolate(text, self.filepath, lineno,
|
||||
lookup=self.lookup):
|
||||
stream.append((kind, data, pos))
|
||||
lineno += len(text.splitlines())
|
||||
|
||||
lineno += len(source[start:end].splitlines())
|
||||
command, value = mo.group(2, 3)
|
||||
|
||||
if command == 'include':
|
||||
pos = (self.filename, lineno, 0)
|
||||
value = list(interpolate(value, self.filepath, lineno, 0,
|
||||
lookup=self.lookup))
|
||||
if len(value) == 1 and value[0][0] is TEXT:
|
||||
value = value[0][1]
|
||||
stream.append((INCLUDE, (value, None, []), pos))
|
||||
|
||||
elif command == 'python':
|
||||
if not self.allow_exec:
|
||||
raise TemplateSyntaxError('Python code blocks not allowed',
|
||||
self.filepath, lineno)
|
||||
try:
|
||||
suite = Suite(value, self.filepath, lineno,
|
||||
lookup=self.lookup)
|
||||
except SyntaxError, err:
|
||||
raise TemplateSyntaxError(err, self.filepath,
|
||||
lineno + (err.lineno or 1) - 1)
|
||||
pos = (self.filename, lineno, 0)
|
||||
stream.append((EXEC, suite, pos))
|
||||
|
||||
elif command == 'end':
|
||||
depth -= 1
|
||||
if depth in dirmap:
|
||||
directive, start_offset = dirmap.pop(depth)
|
||||
substream = stream[start_offset:]
|
||||
stream[start_offset:] = [(SUB, ([directive], substream),
|
||||
(self.filepath, lineno, 0))]
|
||||
|
||||
elif command:
|
||||
cls = self._dir_by_name.get(command)
|
||||
if cls is None:
|
||||
raise BadDirectiveError(command)
|
||||
directive = cls, value, None, (self.filepath, lineno, 0)
|
||||
dirmap[depth] = (directive, len(stream))
|
||||
depth += 1
|
||||
|
||||
offset = end
|
||||
|
||||
if offset < len(source):
|
||||
text = _escape_sub(_escape_repl, source[offset:])
|
||||
for kind, data, pos in interpolate(text, self.filepath, lineno,
|
||||
lookup=self.lookup):
|
||||
stream.append((kind, data, pos))
|
||||
|
||||
return stream
|
||||
|
||||
|
||||
class OldTextTemplate(Template):
|
||||
"""Legacy implementation of the old syntax text-based templates. This class
|
||||
is provided in a transition phase for backwards compatibility. New code
|
||||
should use the `NewTextTemplate` class and the improved syntax it provides.
|
||||
|
||||
>>> tmpl = OldTextTemplate('''Dear $name,
|
||||
...
|
||||
... We have the following items for you:
|
||||
... #for item in items
|
||||
... * $item
|
||||
... #end
|
||||
...
|
||||
... All the best,
|
||||
... Foobar''')
|
||||
>>> print tmpl.generate(name='Joe', items=[1, 2, 3]).render()
|
||||
Dear Joe,
|
||||
<BLANKLINE>
|
||||
We have the following items for you:
|
||||
* 1
|
||||
* 2
|
||||
* 3
|
||||
<BLANKLINE>
|
||||
All the best,
|
||||
Foobar
|
||||
"""
|
||||
directives = [('def', DefDirective),
|
||||
('when', WhenDirective),
|
||||
('otherwise', OtherwiseDirective),
|
||||
('for', ForDirective),
|
||||
('if', IfDirective),
|
||||
('choose', ChooseDirective),
|
||||
('with', WithDirective)]
|
||||
serializer = 'text'
|
||||
|
||||
_DIRECTIVE_RE = re.compile(r'(?:^[ \t]*(?<!\\)#(end).*\n?)|'
|
||||
r'(?:^[ \t]*(?<!\\)#((?:\w+|#).*)\n?)',
|
||||
re.MULTILINE)
|
||||
|
||||
def _parse(self, source, encoding):
|
||||
"""Parse the template from text input."""
|
||||
stream = [] # list of events of the "compiled" template
|
||||
dirmap = {} # temporary mapping of directives to elements
|
||||
depth = 0
|
||||
|
||||
source = source.read()
|
||||
if isinstance(source, str):
|
||||
source = source.decode(encoding or 'utf-8', 'replace')
|
||||
offset = 0
|
||||
lineno = 1
|
||||
|
||||
for idx, mo in enumerate(self._DIRECTIVE_RE.finditer(source)):
|
||||
start, end = mo.span()
|
||||
if start > offset:
|
||||
text = source[offset:start]
|
||||
for kind, data, pos in interpolate(text, self.filepath, lineno,
|
||||
lookup=self.lookup):
|
||||
stream.append((kind, data, pos))
|
||||
lineno += len(text.splitlines())
|
||||
|
||||
text = source[start:end].lstrip()[1:]
|
||||
lineno += len(text.splitlines())
|
||||
directive = text.split(None, 1)
|
||||
if len(directive) > 1:
|
||||
command, value = directive
|
||||
else:
|
||||
command, value = directive[0], None
|
||||
|
||||
if command == 'end':
|
||||
depth -= 1
|
||||
if depth in dirmap:
|
||||
directive, start_offset = dirmap.pop(depth)
|
||||
substream = stream[start_offset:]
|
||||
stream[start_offset:] = [(SUB, ([directive], substream),
|
||||
(self.filepath, lineno, 0))]
|
||||
elif command == 'include':
|
||||
pos = (self.filename, lineno, 0)
|
||||
stream.append((INCLUDE, (value.strip(), None, []), pos))
|
||||
elif command != '#':
|
||||
cls = self._dir_by_name.get(command)
|
||||
if cls is None:
|
||||
raise BadDirectiveError(command)
|
||||
directive = cls, value, None, (self.filepath, lineno, 0)
|
||||
dirmap[depth] = (directive, len(stream))
|
||||
depth += 1
|
||||
|
||||
offset = end
|
||||
|
||||
if offset < len(source):
|
||||
text = source[offset:].replace('\\#', '#')
|
||||
for kind, data, pos in interpolate(text, self.filepath, lineno,
|
||||
lookup=self.lookup):
|
||||
stream.append((kind, data, pos))
|
||||
|
||||
return stream
|
||||
|
||||
|
||||
TextTemplate = OldTextTemplate
|
@ -1,250 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2006-2007 Edgewall Software
|
||||
# All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://genshi.edgewall.org/wiki/License.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For the exact contribution history, see the revision
|
||||
# history and logs, available at http://genshi.edgewall.org/log/.
|
||||
|
||||
"""Various utility classes and functions."""
|
||||
|
||||
import htmlentitydefs
|
||||
import re
|
||||
try:
|
||||
set
|
||||
except NameError:
|
||||
from sets import ImmutableSet as frozenset
|
||||
from sets import Set as set
|
||||
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
class LRUCache(dict):
|
||||
"""A dictionary-like object that stores only a certain number of items, and
|
||||
discards its least recently used item when full.
|
||||
|
||||
>>> cache = LRUCache(3)
|
||||
>>> cache['A'] = 0
|
||||
>>> cache['B'] = 1
|
||||
>>> cache['C'] = 2
|
||||
>>> len(cache)
|
||||
3
|
||||
|
||||
>>> cache['A']
|
||||
0
|
||||
|
||||
Adding new items to the cache does not increase its size. Instead, the least
|
||||
recently used item is dropped:
|
||||
|
||||
>>> cache['D'] = 3
|
||||
>>> len(cache)
|
||||
3
|
||||
>>> 'B' in cache
|
||||
False
|
||||
|
||||
Iterating over the cache returns the keys, starting with the most recently
|
||||
used:
|
||||
|
||||
>>> for key in cache:
|
||||
... print key
|
||||
D
|
||||
A
|
||||
C
|
||||
|
||||
This code is based on the LRUCache class from ``myghtyutils.util``, written
|
||||
by Mike Bayer and released under the MIT license. See:
|
||||
|
||||
http://svn.myghty.org/myghtyutils/trunk/lib/myghtyutils/util.py
|
||||
"""
|
||||
|
||||
class _Item(object):
|
||||
def __init__(self, key, value):
|
||||
self.previous = self.next = None
|
||||
self.key = key
|
||||
self.value = value
|
||||
def __repr__(self):
|
||||
return repr(self.value)
|
||||
|
||||
def __init__(self, capacity):
|
||||
self._dict = dict()
|
||||
self.capacity = capacity
|
||||
self.head = None
|
||||
self.tail = None
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._dict
|
||||
|
||||
def __iter__(self):
|
||||
cur = self.head
|
||||
while cur:
|
||||
yield cur.key
|
||||
cur = cur.next
|
||||
|
||||
def __len__(self):
|
||||
return len(self._dict)
|
||||
|
||||
def __getitem__(self, key):
|
||||
item = self._dict[key]
|
||||
self._update_item(item)
|
||||
return item.value
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
item = self._dict.get(key)
|
||||
if item is None:
|
||||
item = self._Item(key, value)
|
||||
self._dict[key] = item
|
||||
self._insert_item(item)
|
||||
else:
|
||||
item.value = value
|
||||
self._update_item(item)
|
||||
self._manage_size()
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._dict)
|
||||
|
||||
def _insert_item(self, item):
|
||||
item.previous = None
|
||||
item.next = self.head
|
||||
if self.head is not None:
|
||||
self.head.previous = item
|
||||
else:
|
||||
self.tail = item
|
||||
self.head = item
|
||||
self._manage_size()
|
||||
|
||||
def _manage_size(self):
|
||||
while len(self._dict) > self.capacity:
|
||||
olditem = self._dict[self.tail.key]
|
||||
del self._dict[self.tail.key]
|
||||
if self.tail != self.head:
|
||||
self.tail = self.tail.previous
|
||||
self.tail.next = None
|
||||
else:
|
||||
self.head = self.tail = None
|
||||
|
||||
def _update_item(self, item):
|
||||
if self.head == item:
|
||||
return
|
||||
|
||||
previous = item.previous
|
||||
previous.next = item.next
|
||||
if item.next is not None:
|
||||
item.next.previous = previous
|
||||
else:
|
||||
self.tail = previous
|
||||
|
||||
item.previous = None
|
||||
item.next = self.head
|
||||
self.head.previous = self.head = item
|
||||
|
||||
|
||||
def flatten(items):
|
||||
"""Flattens a potentially nested sequence into a flat list.
|
||||
|
||||
:param items: the sequence to flatten
|
||||
|
||||
>>> flatten((1, 2))
|
||||
[1, 2]
|
||||
>>> flatten([1, (2, 3), 4])
|
||||
[1, 2, 3, 4]
|
||||
>>> flatten([1, (2, [3, 4]), 5])
|
||||
[1, 2, 3, 4, 5]
|
||||
"""
|
||||
retval = []
|
||||
for item in items:
|
||||
if isinstance(item, (frozenset, list, set, tuple)):
|
||||
retval += flatten(item)
|
||||
else:
|
||||
retval.append(item)
|
||||
return retval
|
||||
|
||||
def plaintext(text, keeplinebreaks=True):
|
||||
"""Returns the text as a `unicode` string with all entities and tags
|
||||
removed.
|
||||
|
||||
>>> plaintext('<b>1 < 2</b>')
|
||||
u'1 < 2'
|
||||
|
||||
The `keeplinebreaks` parameter can be set to ``False`` to replace any line
|
||||
breaks by simple spaces:
|
||||
|
||||
>>> plaintext('''<b>1
|
||||
... <
|
||||
... 2</b>''', keeplinebreaks=False)
|
||||
u'1 < 2'
|
||||
|
||||
:param text: the text to convert to plain text
|
||||
:param keeplinebreaks: whether line breaks in the text should be kept intact
|
||||
:return: the text with tags and entities removed
|
||||
"""
|
||||
text = stripentities(striptags(text))
|
||||
if not keeplinebreaks:
|
||||
text = text.replace(u'\n', u' ')
|
||||
return text
|
||||
|
||||
_STRIPENTITIES_RE = re.compile(r'&(?:#((?:\d+)|(?:[xX][0-9a-fA-F]+));?|(\w+);)')
|
||||
def stripentities(text, keepxmlentities=False):
|
||||
"""Return a copy of the given text with any character or numeric entities
|
||||
replaced by the equivalent UTF-8 characters.
|
||||
|
||||
>>> stripentities('1 < 2')
|
||||
u'1 < 2'
|
||||
>>> stripentities('more …')
|
||||
u'more \u2026'
|
||||
>>> stripentities('…')
|
||||
u'\u2026'
|
||||
>>> stripentities('…')
|
||||
u'\u2026'
|
||||
|
||||
If the `keepxmlentities` parameter is provided and is a truth value, the
|
||||
core XML entities (&, ', >, < and ") are left intact.
|
||||
|
||||
>>> stripentities('1 < 2 …', keepxmlentities=True)
|
||||
u'1 < 2 \u2026'
|
||||
"""
|
||||
def _replace_entity(match):
|
||||
if match.group(1): # numeric entity
|
||||
ref = match.group(1)
|
||||
if ref.startswith('x'):
|
||||
ref = int(ref[1:], 16)
|
||||
else:
|
||||
ref = int(ref, 10)
|
||||
return unichr(ref)
|
||||
else: # character entity
|
||||
ref = match.group(2)
|
||||
if keepxmlentities and ref in ('amp', 'apos', 'gt', 'lt', 'quot'):
|
||||
return u'&%s;' % ref
|
||||
try:
|
||||
return unichr(htmlentitydefs.name2codepoint[ref])
|
||||
except KeyError:
|
||||
if keepxmlentities:
|
||||
return u'&%s;' % ref
|
||||
else:
|
||||
return ref
|
||||
return _STRIPENTITIES_RE.sub(_replace_entity, text)
|
||||
|
||||
_STRIPTAGS_RE = re.compile(r'(<!--.*?-->|<[^>]*>)')
|
||||
def striptags(text):
|
||||
"""Return a copy of the text with any XML/HTML tags removed.
|
||||
|
||||
>>> striptags('<span>Foo</span> bar')
|
||||
'Foo bar'
|
||||
>>> striptags('<span class="bar">Foo</span>')
|
||||
'Foo'
|
||||
>>> striptags('Foo<br />')
|
||||
'Foo'
|
||||
|
||||
HTML/XML comments are stripped, too:
|
||||
|
||||
>>> striptags('<!-- <blub>hehe</blah> -->test')
|
||||
'test'
|
||||
|
||||
:param text: the string to remove tags from
|
||||
:return: the text with tags removed
|
||||
"""
|
||||
return _STRIPTAGS_RE.sub('', text)
|
Loading…
x
Reference in New Issue
Block a user