diff --git a/resources/templates/opf.xml b/resources/templates/opf.xml
deleted file mode 100644
index 4e0f8d94be..0000000000
--- a/resources/templates/opf.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
- ${mi.title}
- ${author}
- ${'%s (%s)'%(__appname__, __version__)} [http://${__appname__}.kovidgoyal.net]
- ${mi.application_id}
- ${mi.pubdate.isoformat()}
- ${mi.language if mi.language else 'UND'}
- ${mi.category}
- ${mi.comments}
- ${mi.publisher}
- ${mi.isbn}
- ${mi.rights}
-
-
-
-
-
-
- ${tag}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py
index 2f61b5b8fe..1f321568f5 100644
--- a/src/calibre/ebooks/metadata/opf2.py
+++ b/src/calibre/ebooks/metadata/opf2.py
@@ -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('\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):
A Cool & © ß Title
- Monkey Kitchen, Next
+ Monkey Kitchen
+ Next
OneTwo
123456789
-
- A one book series
-
+
+
+
+
@@ -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()