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                     document.edoc.getroot().set('data-cover-by', self.doc.meta.get_one('cover_by'))
 
  75                 if self.doc.meta.get_one('cover_source'):
 
  76                     document.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"/>'))
 
  87         ctx = Context(format=self)
 
  90         ctx.footnotes = Footnotes()
 
  93         wrap_tmpl = etree.parse(get_resource('formats/epub/res/chapter.html'))
 
  94         for e in self.render(self.doc.edoc.getroot(), ctx):
 
  95             if not len(e) and not e.text.strip():
 
  97             wrap = deepcopy(wrap_tmpl)
 
  98             extend_element(wrap.find('//*[@id="book-text"]'), e)
 
 100             partstr = 'part%d' % int(e.get('part_no'))
 
 101             manifest.append(manifest.makeelement(OPFNS('item'), attrib={
 
 103                                  'href': partstr + ".html",
 
 104                                  'media-type': 'application/xhtml+xml',
 
 106             spine.append(spine.makeelement(OPFNS('itemref'), attrib={
 
 109             zip.writestr('OPS/%s.html' % partstr, etree.tostring(wrap, method='html'))
 
 111         if len(ctx.footnotes.output):
 
 112             ctx.toc.add("Przypisy", "footnotes.html")
 
 113             manifest.append(etree.Element(OPFNS('item'),
 
 114                     id='footnotes', href='footnotes.html',
 
 115                     **{'media-type': "application/xhtml+xml"}))
 
 116             spine.append(etree.Element('itemref', idref='footnotes'))
 
 117             wrap = etree.parse(get_resource('formats/epub/res/footnotes.html'))
 
 118             extend_element(wrap.find('//*[@id="footnotes"]'), ctx.footnotes.output)
 
 120             #chars = chars.union(used_chars(html_tree.getroot()))
 
 121             zip.writestr('OPS/footnotes.html', etree.tostring(
 
 122                                 wrap, method="html", pretty_print=True))
 
 125         zip.writestr('OPS/content.opf', etree.tostring(opf, pretty_print=True))
 
 126         ctx.toc.render(toc_file[-1])
 
 127         zip.writestr('OPS/toc.ncx', etree.tostring(toc_file, pretty_print=True))
 
 129         return OutputFile.from_filename(output_file.name)
 
 131     def render(self, element, ctx):
 
 132         return self.renderers.get_for(element).render(element, ctx)
 
 137 class EpubRenderer(TreeRenderer):
 
 138     """ Renders insides as XML in a <_/> container. """
 
 139     def container(self, ctx):
 
 140         root, inner = super(EpubRenderer, self).container()
 
 141         root.set("part_no", str(ctx.part_no))
 
 144     def render(self, element, ctx):
 
 145         subctx = self.subcontext(element, ctx)
 
 146         wrapper, inside = self.container(ctx)
 
 148             extend_element(inside, self.render_text(element.text, ctx))
 
 149         for child in element:
 
 151                 child_renderer = ctx.format.renderers.get_for(child)
 
 152             except UnknownElement:
 
 155                 if getattr(child_renderer, 'epub_separate', False):
 
 158                     for child_part in child_renderer.render(child, subctx):
 
 160                     wrapper, inside = self.container(ctx)
 
 162                     child_parts = list(child_renderer.render(child, subctx))
 
 163                     extend_element(inside, child_parts[0])
 
 164                     if len(child_parts) > 1:
 
 166                         for child_part in child_parts[1:-1]:
 
 168                         wrapper, inside = self.container(ctx)
 
 169                         extend_element(inside, child_parts[-1])
 
 172                     extend_element(inside, self.render_text(child.tail, ctx))
 
 176 class Footnotes(object):
 
 179         self.output = etree.Element("_")
 
 181     def append(self, items):
 
 183         e = etree.Element("a",
 
 184             href="part%d.html#footnote-anchor-%d" % (int(items[0].get('part_no')), self.counter),
 
 185             id="footnote-%d" % self.counter,
 
 186             style="float:left;margin-right:1em")
 
 187         e.text = "[%d]" % self.counter
 
 189         self.output.append(e)
 
 191             extend_element(self.output, item)
 
 192         anchor = etree.Element("a",
 
 193             id="footnote-anchor-%d" % self.counter,
 
 194             href="footnotes.html#footnote-%d" % self.counter)
 
 195         anchor.text = "[%d]" % self.counter
 
 200     def __init__(self, title=None, href="", root=None):
 
 208         self.href = href.format(counter=self.root.counter)
 
 209         self.number = self.root.counter
 
 210         self.root.counter += 1
 
 212     def add(self, title, href):
 
 213         subtoc = type(self)(title, href, root=self.root)
 
 214         self.children.append(subtoc)
 
 217     def render(self, nav_map):
 
 218         for child in self.children:
 
 219             nav_point = etree.Element(NCXNS('navPoint'))
 
 220             nav_point.set('id', 'NavPoint-%d' % child.number)
 
 221             nav_point.set('playOrder', str(child.number))
 
 223             nav_label = etree.Element(NCXNS('navLabel'))
 
 224             text = etree.Element(NCXNS('text'))
 
 225             text.text = child.title
 
 226             nav_label.append(text)
 
 227             nav_point.append(nav_label)
 
 229             content = etree.Element(NCXNS('content'))
 
 230             content.set('src', child.href)
 
 231             nav_point.append(content)
 
 232             nav_map.append(nav_point)
 
 233             child.render(nav_map)
 
 238 class AsideR(EpubRenderer):
 
 239     def render(self, element, ctx):
 
 240         outputs = list(super(AsideR, self).render(element, ctx))
 
 241         anchor = ctx.footnotes.append(outputs)
 
 242         wrapper, inside = self.text_container()  #etree.Element('_', part_no=str(ctx.part_no))
 
 243         inside.append(anchor)
 
 245 EpubFormat.renderers.register(core.Aside, None, AsideR('div'))
 
 248 class DivR(EpubRenderer):
 
 249     def container(self, ctx):
 
 250         root, inner = super(DivR, self).container(ctx)
 
 251         if getattr(ctx, 'inline', False):
 
 253             inner.set('style', 'display: block;')
 
 255 EpubFormat.renderers.register(core.Div, None, DivR('div'))
 
 258 class HeaderR(EpubRenderer):
 
 259     def subcontext(self, element, ctx):
 
 260         return Context(ctx, inline=True)
 
 261 EpubFormat.renderers.register(core.Header, None, HeaderR('h1'))
 
 264 class SectionR(EpubRenderer):
 
 267     def render(self, element, ctx):
 
 269         if element.getparent() is not None:
 
 270             tocitem = ctx.toc.add(element.meta.title(), 'part%d.html' % ctx.part_no)
 
 271             ctx = Context(ctx, toc=tocitem)
 
 272         return super(SectionR, self).render(element, ctx)
 
 273 EpubFormat.renderers.register(core.Section, None, SectionR())
 
 276 class SpanR(EpubRenderer):
 
 278 EpubFormat.renderers.register(core.Span, None, SpanR('span'))