KG changes

This commit is contained in:
GRiker 2010-05-29 03:20:41 -06:00
commit 9f55353a50
51 changed files with 2852 additions and 11468 deletions

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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:

View File

@ -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 &amp; &copy; &#223; 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()

View File

@ -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)

View File

@ -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)

View File

@ -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')

View File

@ -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)))

View File

@ -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'

View File

@ -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

View File

@ -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."

View File

@ -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 ""

View File

@ -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)"

View File

@ -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

View File

@ -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)"

View File

@ -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)"

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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.

View File

@ -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

View File

@ -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 &lt; 2"/>
>>> print Element('span', title='"baz"')
<span title="&#34;baz&#34;"/>
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 &lt; 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`
"""

View File

@ -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'&#34;1 &lt; 2&#34;'>
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 &lt; 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('&', '&amp;') \
.replace('<', '&lt;') \
.replace('>', '&gt;')
if quotes:
text = text.replace('"', '&#34;')
return cls(text)
escape = classmethod(escape)
def unescape(self):
"""Reverse-escapes &, <, >, and \" and returns a `unicode` object.
>>> Markup('1 &lt; 2').unescape()
u'1 < 2'
:return: the unescaped string
:rtype: `unicode`
:see: `genshi.core.unescape`
"""
if not self:
return u''
return unicode(self).replace('&#34;', '"') \
.replace('&gt;', '>') \
.replace('&lt;', '<') \
.replace('&amp;', '&')
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 (``&amp;``, ``&apos;``, ``&gt;``, ``&lt;`` and
``&quot;``) 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 &lt; 2'))
u'1 < 2'
If the provided `text` object is not a `Markup` instance, it is returned
unchanged.
>>> unescape('1 &lt; 2')
'1 &lt; 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('{'))

View File

@ -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'

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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">&lt;Hello!&gt;</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 &amp; Bye!</a><br/>'))
>>> print elem.generate().render(TextSerializer)
<a href="foo">Hello &amp; 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

View File

@ -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'

View File

@ -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

View File

@ -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__)

View File

@ -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])
])

View File

@ -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:]

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 &lt; 2</b>')
u'1 < 2'
The `keeplinebreaks` parameter can be set to ``False`` to replace any line
breaks by simple spaces:
>>> plaintext('''<b>1
... &lt;
... 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 &lt; 2')
u'1 < 2'
>>> stripentities('more &hellip;')
u'more \u2026'
>>> stripentities('&#8230;')
u'\u2026'
>>> stripentities('&#x2026;')
u'\u2026'
If the `keepxmlentities` parameter is provided and is a truth value, the
core XML entities (&amp;, &apos;, &gt;, &lt; and &quot;) are left intact.
>>> stripentities('1 &lt; 2 &hellip;', keepxmlentities=True)
u'1 &lt; 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'&amp;%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)