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