1#!/usr/local/bin/python3.8
2# vim:fileencoding=utf-8
3
4
5__license__ = 'GPL v3'
6__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
7
8import sys, os
9
10from lxml import etree
11
12from calibre import prepare_string_for_xml, CurrentDir
13from calibre.ptempfile import TemporaryDirectory
14from calibre.ebooks.oeb.base import serialize
15from calibre.ebooks.metadata import authors_to_string
16from calibre.ebooks.metadata.opf2 import metadata_to_opf
17from calibre.ebooks.oeb.polish.parsing import parse
18from calibre.ebooks.oeb.polish.container import OPF_NAMESPACES, opf_to_azw3, Container
19from calibre.ebooks.oeb.polish.utils import guess_type
20from calibre.ebooks.oeb.polish.pretty import pretty_xml_tree, pretty_html_tree
21from calibre.ebooks.oeb.polish.toc import TOC, create_ncx
22from calibre.utils.localization import lang_as_iso639_1
23from calibre.utils.logging import DevNull
24from calibre.utils.zipfile import ZipFile, ZIP_STORED
25from polyglot.builtins import as_bytes
26
27valid_empty_formats = {'epub', 'txt', 'docx', 'azw3', 'md'}
28
29
30def create_toc(mi, opf, html_name, lang):
31    uuid = ''
32    for u in opf.xpath('//*[@id="uuid_id"]'):
33        uuid = u.text
34    toc = TOC()
35    toc.add(_('Start'), html_name)
36    return create_ncx(toc, lambda x:x, mi.title, lang, uuid)
37
38
39def create_book(mi, path, fmt='epub', opf_name='metadata.opf', html_name='start.xhtml', toc_name='toc.ncx'):
40    ''' Create an empty book in the specified format at the specified location. '''
41    if fmt not in valid_empty_formats:
42        raise ValueError('Cannot create empty book in the %s format' % fmt)
43    if fmt == 'txt':
44        with open(path, 'wb') as f:
45            if not mi.is_null('title'):
46                f.write(as_bytes(mi.title))
47        return
48    if fmt == 'md':
49        with open(path, 'w', encoding='utf-8') as f:
50            if not mi.is_null('title'):
51                print('#', mi.title, file=f)
52        return
53    if fmt == 'docx':
54        from calibre.ebooks.conversion.plumber import Plumber
55        from calibre.ebooks.docx.writer.container import DOCX
56        from calibre.utils.logging import default_log
57        p = Plumber('a.docx', 'b.docx', default_log)
58        p.setup_options()
59        # Use the word default of one inch page margins
60        for x in 'left right top bottom'.split():
61            setattr(p.opts, 'margin_' + x, 72)
62        DOCX(p.opts, default_log).write(path, mi, create_empty_document=True)
63        return
64    path = os.path.abspath(path)
65    lang = 'und'
66    opf = metadata_to_opf(mi, as_string=False)
67    for l in opf.xpath('//*[local-name()="language"]'):
68        if l.text:
69            lang = l.text
70            break
71    lang = lang_as_iso639_1(lang) or lang
72
73    opfns = OPF_NAMESPACES['opf']
74    m = opf.makeelement('{%s}manifest' % opfns)
75    opf.insert(1, m)
76    i = m.makeelement('{%s}item' % opfns, href=html_name, id='start')
77    i.set('media-type', guess_type('a.xhtml'))
78    m.append(i)
79    i = m.makeelement('{%s}item' % opfns, href=toc_name, id='ncx')
80    i.set('media-type', guess_type(toc_name))
81    m.append(i)
82    s = opf.makeelement('{%s}spine' % opfns, toc="ncx")
83    opf.insert(2, s)
84    i = s.makeelement('{%s}itemref' % opfns, idref='start')
85    s.append(i)
86    CONTAINER = '''\
87<?xml version="1.0"?>
88<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
89   <rootfiles>
90      <rootfile full-path="{}" media-type="application/oebps-package+xml"/>
91   </rootfiles>
92</container>
93    '''.format(prepare_string_for_xml(opf_name, True)).encode('utf-8')
94    HTML = P('templates/new_book.html', data=True).decode('utf-8').replace(
95        '_LANGUAGE_', prepare_string_for_xml(lang, True)
96    ).replace(
97        '_TITLE_', prepare_string_for_xml(mi.title)
98    ).replace(
99        '_AUTHORS_', prepare_string_for_xml(authors_to_string(mi.authors))
100    ).encode('utf-8')
101    h = parse(HTML)
102    pretty_html_tree(None, h)
103    HTML = serialize(h, 'text/html')
104    ncx = etree.tostring(create_toc(mi, opf, html_name, lang), encoding='utf-8', xml_declaration=True, pretty_print=True)
105    pretty_xml_tree(opf)
106    opf = etree.tostring(opf, encoding='utf-8', xml_declaration=True, pretty_print=True)
107    if fmt == 'azw3':
108        with TemporaryDirectory('create-azw3') as tdir, CurrentDir(tdir):
109            for name, data in ((opf_name, opf), (html_name, HTML), (toc_name, ncx)):
110                with open(name, 'wb') as f:
111                    f.write(data)
112            c = Container(os.path.dirname(os.path.abspath(opf_name)), opf_name, DevNull())
113            opf_to_azw3(opf_name, path, c)
114    else:
115        with ZipFile(path, 'w', compression=ZIP_STORED) as zf:
116            zf.writestr('mimetype', b'application/epub+zip', compression=ZIP_STORED)
117            zf.writestr('META-INF/', b'', 0o755)
118            zf.writestr('META-INF/container.xml', CONTAINER)
119            zf.writestr(opf_name, opf)
120            zf.writestr(html_name, HTML)
121            zf.writestr(toc_name, ncx)
122
123
124if __name__ == '__main__':
125    from calibre.ebooks.metadata.book.base import Metadata
126    mi = Metadata('Test book', authors=('Kovid Goyal',))
127    path = sys.argv[-1]
128    ext = path.rpartition('.')[-1].lower()
129    if ext not in valid_empty_formats:
130        print('Unsupported format:', ext)
131        raise SystemExit(1)
132    create_book(mi, path, fmt=ext)
133