1 # -*- coding: utf-8 -*-
3 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
4 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
7 from copy import deepcopy
8 from tempfile import NamedTemporaryFile
10 from lxml import etree
11 from librarian import OPFNS, NCXNS, XHTMLNS
12 from librarian import core
13 from librarian.formats import Format
14 from librarian.formats.cover.wolnelektury import WLCover
15 from librarian.output import OutputFile
16 from librarian.renderers import Register, TreeRenderer, UnknownElement
17 from librarian.utils import Context, get_resource, extend_element
20 class EpubFormat(Format):
25 renderers = Register()
27 def __init__(self, doc, cover=None, with_fonts=True):
28 super(EpubFormat, self).__init__(doc)
29 self.with_fonts = with_fonts
34 opf = etree.parse(get_resource('formats/epub/res/content.opf'))
35 manifest = opf.find(OPFNS('manifest'))
36 guide = opf.find(OPFNS('guide'))
37 spine = opf.find(OPFNS('spine'))
39 output_file = NamedTemporaryFile(prefix='librarian', suffix='.epub', delete=False)
40 zip = zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED)
42 mime = zipfile.ZipInfo()
43 mime.filename = 'mimetype'
44 mime.compress_type = zipfile.ZIP_STORED
46 zip.writestr(mime, 'application/epub+zip')
47 zip.writestr('META-INF/container.xml', '<?xml version="1.0" ?><container version="1.0" '
48 'xmlns="urn:oasis:names:tc:opendocument:xmlns:container">'
49 '<rootfiles><rootfile full-path="OPS/content.opf" '
50 'media-type="application/oebps-package+xml" />'
51 '</rootfiles></container>')
53 toc_file = etree.fromstring('<?xml version="1.0" encoding="utf-8"?><!DOCTYPE ncx PUBLIC '
54 '"-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">'
55 '<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" xml:lang="pl" '
56 'version="2005-1"><head></head><docTitle></docTitle><navMap>'
58 # nav_map = toc_file[-1]
60 if self.cover is not None:
61 cover = self.cover(self.doc)
62 cover_output = cover.build()
63 cover_name = 'cover.%s' % cover.format_ext
64 zip.writestr(os.path.join('OPS', cover_name), cover_output.get_string())
67 cover_tree = etree.parse(get_resource('formats/epub/res/cover.html'))
68 cover_tree.find('//' + XHTMLNS('img')).set('src', cover_name)
69 zip.writestr('OPS/cover.html', etree.tostring(
70 cover_tree, method="html", pretty_print=True))
72 if cover.uses_dc_cover:
73 if self.doc.meta.get_one('cover_by'):
74 self.doc.edoc.getroot().set('data-cover-by', self.doc.meta.get_one('cover_by'))
75 if self.doc.meta.get_one('cover_source'):
76 self.doc.edoc.getroot().set('data-cover-source', self.doc.meta.get_one('cover_source'))
78 manifest.append(etree.fromstring(
79 '<item id="cover" href="cover.html" media-type="application/xhtml+xml" />'))
80 manifest.append(etree.fromstring(
81 '<item id="cover-image" href="%s" media-type="%s" />' % (cover_name, cover.mime_type())))
82 spine.insert(0, etree.fromstring('<itemref idref="cover" linear="no" />'))
83 opf.getroot()[0].append(etree.fromstring('<meta name="cover" content="cover-image"/>'))
84 guide.append(etree.fromstring('<reference href="cover.html" type="cover" title="Okładka"/>'))
86 ctx = Context(format=self)
89 ctx.footnotes = Footnotes()
92 wrap_tmpl = etree.parse(get_resource('formats/epub/res/chapter.html'))
93 for e in self.render(self.doc.edoc.getroot(), ctx):
94 if not len(e) and not e.text.strip():
96 wrap = deepcopy(wrap_tmpl)
97 extend_element(wrap.find('//*[@id="book-text"]'), e)
99 partstr = 'part%d' % int(e.get('part_no'))
100 manifest.append(manifest.makeelement(OPFNS('item'), attrib={
102 'href': partstr + ".html",
103 'media-type': 'application/xhtml+xml',
105 spine.append(spine.makeelement(OPFNS('itemref'), attrib={
108 zip.writestr('OPS/%s.html' % partstr, etree.tostring(wrap, method='html'))
110 if len(ctx.footnotes.output):
111 ctx.toc.add("Przypisy", "footnotes.html")
112 manifest.append(etree.Element(
113 OPFNS('item'), id='footnotes', href='footnotes.html',
114 **{'media-type': "application/xhtml+xml"}))
115 spine.append(etree.Element('itemref', idref='footnotes'))
116 wrap = etree.parse(get_resource('formats/epub/res/footnotes.html'))
117 extend_element(wrap.find('//*[@id="footnotes"]'), ctx.footnotes.output)
119 # chars = chars.union(used_chars(html_tree.getroot()))
120 zip.writestr('OPS/footnotes.html', etree.tostring(
121 wrap, method="html", pretty_print=True))
123 zip.writestr('OPS/content.opf', etree.tostring(opf, pretty_print=True))
124 ctx.toc.render(toc_file[-1])
125 zip.writestr('OPS/toc.ncx', etree.tostring(toc_file, pretty_print=True))
127 return OutputFile.from_filename(output_file.name)
129 def render(self, element, ctx):
130 return self.renderers.get_for(element).render(element, ctx)
135 class EpubRenderer(TreeRenderer):
136 """ Renders insides as XML in a <_/> container. """
137 def container(self, ctx):
138 root, inner = super(EpubRenderer, self).container()
139 root.set("part_no", str(ctx.part_no))
142 def render(self, element, ctx):
143 subctx = self.subcontext(element, ctx)
144 wrapper, inside = self.container(ctx)
146 extend_element(inside, self.render_text(element.text, ctx))
147 for child in element:
149 child_renderer = ctx.format.renderers.get_for(child)
150 except UnknownElement:
153 if getattr(child_renderer, 'epub_separate', False):
156 for child_part in child_renderer.render(child, subctx):
158 wrapper, inside = self.container(ctx)
160 child_parts = list(child_renderer.render(child, subctx))
161 extend_element(inside, child_parts[0])
162 if len(child_parts) > 1:
164 for child_part in child_parts[1:-1]:
166 wrapper, inside = self.container(ctx)
167 extend_element(inside, child_parts[-1])
170 extend_element(inside, self.render_text(child.tail, ctx))
174 class Footnotes(object):
177 self.output = etree.Element("_")
179 def append(self, items):
182 "a", href="part%d.html#footnote-anchor-%d" % (int(items[0].get('part_no')), self.counter),
183 id="footnote-%d" % self.counter,
184 style="float:left;margin-right:1em")
185 e.text = "[%d]" % self.counter
187 self.output.append(e)
189 extend_element(self.output, item)
190 anchor = etree.Element(
191 "a", href="footnotes.html#footnote-%d" % self.counter, id="footnote-anchor-%d" % self.counter)
192 anchor.text = "[%d]" % self.counter
197 def __init__(self, title=None, href="", root=None):
205 self.href = href.format(counter=self.root.counter)
206 self.number = self.root.counter
207 self.root.counter += 1
209 def add(self, title, href):
210 subtoc = type(self)(title, href, root=self.root)
211 self.children.append(subtoc)
214 def render(self, nav_map):
215 for child in self.children:
216 nav_point = etree.Element(NCXNS('navPoint'))
217 nav_point.set('id', 'NavPoint-%d' % child.number)
218 nav_point.set('playOrder', str(child.number))
220 nav_label = etree.Element(NCXNS('navLabel'))
221 text = etree.Element(NCXNS('text'))
222 text.text = child.title
223 nav_label.append(text)
224 nav_point.append(nav_label)
226 content = etree.Element(NCXNS('content'))
227 content.set('src', child.href)
228 nav_point.append(content)
229 nav_map.append(nav_point)
230 child.render(nav_point)
235 class AsideR(EpubRenderer):
236 def render(self, element, ctx):
237 outputs = list(super(AsideR, self).render(element, ctx))
238 anchor = ctx.footnotes.append(outputs)
239 wrapper, inside = self.text_container() # etree.Element('_', part_no=str(ctx.part_no))
240 inside.append(anchor)
242 EpubFormat.renderers.register(core.Aside, None, AsideR('div'))
245 class DivR(EpubRenderer):
246 def container(self, ctx):
247 root, inner = super(DivR, self).container(ctx)
248 if getattr(ctx, 'inline', False):
250 inner.set('style', 'display: block;')
252 EpubFormat.renderers.register(core.Div, None, DivR('div'))
255 class HeaderR(EpubRenderer):
256 def subcontext(self, element, ctx):
257 return Context(ctx, inline=True)
258 EpubFormat.renderers.register(core.Header, None, HeaderR('h1'))
261 class SectionR(EpubRenderer):
264 def render(self, element, ctx):
266 if element.getparent() is not None:
267 tocitem = ctx.toc.add(element.meta.title(), 'part%d.html' % ctx.part_no)
268 ctx = Context(ctx, toc=tocitem)
269 return super(SectionR, self).render(element, ctx)
270 EpubFormat.renderers.register(core.Section, None, SectionR())
273 class SpanR(EpubRenderer):
275 EpubFormat.renderers.register(core.Span, None, SpanR('span'))