1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
4
5# Copyright (c) 2015-2020 Kevin B. Hendricks, and Doug Massay
6# Copyright (c) 2014      Kevin B. Hendricks, John Schember, and Doug Massay
7# All rights reserved.
8#
9# Redistribution and use in source and binary forms, with or without modification,
10# are permitted provided that the following conditions are met:
11#
12# 1. Redistributions of source code must retain the above copyright notice, this list of
13# conditions and the following disclaimer.
14#
15# 2. Redistributions in binary form must reproduce the above copyright notice, this list
16# of conditions and the following disclaimer in the documentation and/or other materials
17# provided with the distribution.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
22# SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
24# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
27# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29from quickparser import QuickXHTMLParser
30from preferences import JSONPrefs
31from pluginhunspell import HunspellChecker
32
33class ContainerException(Exception):
34    pass
35
36class BookContainer(object):
37
38    def __init__(self, wrapper, debug=False):
39        self._debug = debug
40        self._w = wrapper
41        self.qp = QuickXHTMLParser()
42        self.hspell = HunspellChecker(wrapper.get_hunspell_path())
43        self.dictionary_dirs = wrapper.get_dictionary_dirs()
44        self._prefs_store = JSONPrefs(wrapper.plugin_dir, wrapper.plugin_name)
45
46    def getPrefs(self):
47        return self._prefs_store
48
49    def savePrefs(self, user_copy):
50        self._prefs_store = user_copy
51        self._prefs_store._commit()
52
53    def launcher_version(self):
54        return self._w.getversion()
55
56    def epub_version(self):
57        return self._w.getepubversion()
58
59    def epub_is_standard(self):
60        return self._w.epub_is_standard()
61
62    @property
63    def sigil_ui_lang(self):
64        if self._w.sigil_ui_lang is None:
65            return 'en'
66        return self._w.sigil_ui_lang
67
68    @property
69    def sigil_spellcheck_lang(self):
70        if self._w.sigil_spellcheck_lang is None:
71            return 'en_US'
72        return self._w.sigil_spellcheck_lang
73
74# OPF Acess and Manipulation Routines
75
76# toc and pagemap access routines
77
78    def gettocid(self):
79        return self._w.gettocid()
80
81    def getpagemapid(self):
82        return self._w.getpagemapid()
83
84# nav access routines
85
86    def getnavid(self):
87        return self._w.getnavid()
88
89# spine get/set and access routines
90
91    def getspine(self):
92        # spine is an ordered list of tuples (id, linear)
93        return self._w.getspine()
94
95    def setspine(self, new_spine):
96        # new_spine must be an ordered list of tuples (id, linear)
97        self._w.setspine(new_spine)
98
99    # New for epub3
100    def getspine_epub3(self):
101        # spine is an ordered list of tuples (id, linear, properties)
102        return self._w.getspine_epub3()
103
104    # New for epub3
105    def setspine_epub3(self, new_spine):
106        # new_spine must be an ordered list of tuples (id, linear, properties (or None))
107        self._w.setspine_epub3(new_spine)
108
109    # Modified for epub3
110    # Note: for prepend, set pos = 0
111    #       for append, set pos = -1 or pos >= current length of spine
112    def spine_insert_before(self, pos, spid, linear, properties=None):
113        self._w.spine_insert_before(pos, spid, linear, properties)
114
115    def getspine_ppd(self):
116        # spine_ppd is utf-8 string of page direction (rtl, ltr, None)
117        return self._w.getspine_ppd()
118
119    def setspine_ppd(self, ppd):
120        # new pagedirection string
121        self._w.setspine_ppd(ppd)
122
123    # New for epub3
124    def setspine_idref_epub3_attributes(self, idref, linear, properties):
125        self._w.setspine_idref_attributes(idref, linear, properties)
126
127
128# guide get/set
129
130    def getguide(self):
131        # guide is an ordered list of tuples (type, title, href)
132        return self._w.guide
133
134    def setguide(self, new_guide):
135        # new_guide must be an ordered list of tupes (type, title, href)
136        self._w.setguide(new_guide)
137
138
139# bindings get/set access routines
140
141    # New for epub3
142    def getbindings_epub3(self):
143        # bindings is an ordered list of tuples (media-type, handler)
144        return self._w.getbindings_epub3()
145
146    # New for epub3
147    def setbindings_epub3(self, new_bindings):
148        # new_bindings is an ordered list of tuples (media-type, handler)
149        self._w.setbindings_epub3(new_bindings)
150
151# metadata get/set
152
153    def getmetadataxml(self):
154        # returns a utf-8 encoded metadata xml fragement
155        return self._w.getmetadataxml()
156
157    def setmetadataxml(self, new_metadata):
158        # new_metadata must be a metadata xml fragmment
159        self._w.setmetadataxml(new_metadata)
160
161# package tag get/set
162
163    def getpackagetag(self):
164        # returns a utf-8 encoded metadata xml fragement
165        return self._w.getpackagetag()
166
167    def setpackagetag(self, new_tag):
168        # new_tag must be a xml package tag
169        self._w.setpackagetag(new_tag)
170
171
172# reading / writing / adding / deleting files in the opf manifest
173
174    def readfile(self, id):
175        # returns the contents of the file with manifest id  (text files are utf-8 encoded)
176        return self._w.readfile(id)
177
178    def writefile(self, id, data):
179        # writes data to a currently existing file pointed to by the manifest id
180        self._w.writefile(id, data)
181
182    # Modified for epub3
183    def addfile(self, uniqueid, basename, data, mime=None, properties=None, fallback=None, overlay=None):
184        # creates a new file in the manifest with unique manifest id, basename, data, and mimetype
185        self._w.addfile(uniqueid, basename, data, mime, properties, fallback, overlay)
186
187    def deletefile(self, id):
188        # removes the file associated with that manifest id, removes any existing spine entries as well
189        self._w.deletefile(id)
190
191    # New for epub3
192    def set_manifest_epub3_attributes(self, id, properties=None, fallback=None, overlay=None):
193        # sets the epub3 manifest attrobutes for this manifest id
194        self._w.set_manifest_epub3_attributes(id, properties, fallback, overlay)
195
196# reading / writing / adding / deleting other ebook files that DO NOT exist in the opf manifest
197
198    def readotherfile(self, book_href):
199        # returns the contents of the file pointed to by the ebook href
200        return self._w.readotherfile(book_href)
201
202    def writeotherfile(self, book_href, data):
203        # writes data to a currently existing file pointed to by the ebook href
204        self._w.writeotherfile(book_href, data)
205
206    def addotherfile(self, book_href, data):
207        # creates a new file with desired ebook href
208        self._w.addotherfile(book_href, data)
209
210    def deleteotherfile(self, book_href):
211        # removes file pointed to by the ebook href
212        self._w.deleteotherfile(book_href)
213
214
215# iterators
216
217    def text_iter(self):
218        # yields manifest id, href in spine order plus any non-spine items
219        text_set = set([k for k, v in self._w.id_to_mime.items() if v == 'application/xhtml+xml'])
220        for (id, linear, properties) in self._w.spine:
221            if id in text_set:
222                text_set -= set([id])
223                href = self._w.id_to_href[id]
224                yield id, href
225        for id in text_set:
226            href = self._w.id_to_href[id]
227            yield id, href
228
229    def css_iter(self):
230        # yields manifest id, href
231        for id in sorted(self._w.id_to_mime):
232            mime = self._w.id_to_mime[id]
233            if mime == 'text/css':
234                href = self._w.id_to_href[id]
235                yield id, href
236
237    def image_iter(self):
238        # yields manifest id, href, and mimetype
239        for id in sorted(self._w.id_to_mime):
240            mime = self._w.id_to_mime[id]
241            if mime.startswith('image'):
242                href = self._w.id_to_href[id]
243                yield id, href, mime
244
245    def font_iter(self):
246        # yields manifest id, href, and mimetype
247        for id in sorted(self._w.id_to_mime):
248            mime = self._w.id_to_mime[id]
249            if 'font-' in mime or 'truetype' in mime or 'opentype' in mime or mime.startswith('font/'):
250                href = self._w.id_to_href[id]
251                yield id, href, mime
252
253    def manifest_iter(self):
254        # yields manifest id, href, and mimetype
255        for id in sorted(self._w.id_to_mime):
256            mime = self._w.id_to_mime[id]
257            href = self._w.id_to_href[id]
258            yield id, href, mime
259
260    # New for epub3
261    def manifest_epub3_iter(self):
262        # yields manifest id, href, mimetype, properties, fallback, media-overlay
263        for id in sorted(self._w.id_to_mime):
264            mime = self._w.id_to_mime[id]
265            href = self._w.id_to_href[id]
266            properties = self._w.id_to_props[id]
267            fallback = self._w.id_to_fall[id]
268            overlay = self._w.id_to_over[id]
269            yield id, href, mime, properties, fallback, overlay
270
271    def spine_iter(self):
272        # yields spine idref, linear(yes,no,None), href in spine order
273        for (id, linear, properties) in self._w.spine:
274            href = self._w.id_to_href[id]
275            yield id, linear, href
276
277    # New for epub3
278    def spine_epub3_iter(self):
279        # yields spine idref, linear(yes,no,None), properties, href in spine order
280        for (id, linear, properties) in self._w.spine:
281            href = self._w.id_to_href[id]
282            yield id, linear, properties, href
283
284
285    def guide_iter(self):
286        # yields guide reference type, title, href, and manifest id of href
287        for (type, title, href) in self._w.guide:
288            thref = href.split('#')[0]
289            id = self._w.href_to_id.get(thref, None)
290            yield type, title, href, id
291
292    # New for epub3
293    def bindings_epub3_iter(self):
294        # yields media-type handler in bindings order
295        for (mtype, handler) in self._w.bindings:
296            handler_href = self._w.id_to_href[handler]
297            yield mtype, handler, handler_href
298
299
300    def media_iter(self):
301        # yields manifest, title, href, and manifest id of href
302        for id in sorted(self._w.id_to_mime):
303            mime = self._w.id_to_mime[id]
304            if mime.startswith('audio') or mime.startswith('video'):
305                href = self._w.id_to_href[id]
306                yield id, href, mime
307
308    def other_iter(self):
309        # yields otherid for each file not in the manifest
310        for book_href in self._w.other:
311            yield book_href
312
313    def selected_iter(self):
314        # yields id type ('other' or 'manifest') and id/otherid for each file selected in the BookBrowser
315        for book_href in self._w.selected:
316            id_type = 'other'
317            id = book_href
318            if book_href in self._w.bookpath_to_id:
319                id_type = 'manifest'
320                id = self._w.bookpath_to_id[book_href]
321            yield id_type, id
322
323
324    # miscellaneous routines
325
326    # build the current opf incorporating all changes to date and return it
327    def get_opf(self):
328        return self._w.build_opf()
329
330    # create your own current copy of all ebook contents in destintation directory
331    def copy_book_contents_to(self, destdir):
332        self._w.copy_book_contents_to(destdir)
333
334    # get path to hunspell dll / library
335    def get_hunspell_library_path(self):
336        return self._w.get_hunspell_path()
337
338    # get a list of the directories that contain Sigil's hunspell dictionaries
339    def get_dictionary_dirs(self):
340        return self._w.get_dictionary_dirs()
341
342    # get status of epub file open inside of Sigil
343    def get_epub_is_modified(self):
344        return self._w.epub_isDirty
345
346    # get path to currently open epub or an inside Sigil or empty string if unsaved
347    def get_epub_filepath(self):
348        return self._w.epub_filepath
349
350
351    # functions for converting from  manifest id to href, basename, mimetype etc
352    def href_to_id(self, href, ow=None):
353        return self._w.map_href_to_id(href, ow)
354
355    def id_to_mime(self, id, ow=None):
356        return self._w.map_id_to_mime(id, ow)
357
358    def basename_to_id(self, basename, ow=None):
359        return self._w.map_basename_to_id(basename, ow)
360
361    def id_to_href(self, id, ow=None):
362        return self._w.map_id_to_href(id, ow)
363
364    def href_to_basename(self, href, ow=None):
365        if href is not None:
366            return href.split('/')[-1]
367        return ow
368
369    # New for epub3
370    def id_to_properties(self, id, ow=None):
371        return self._w.map_id_to_properties(id, ow)
372
373    def id_to_fallback(self, id, ow=None):
374        return self._w.map_id_to_fallback(id, ow)
375
376    def id_to_overlay(self, id, ow=None):
377        return self._w.map_id_to_overlay(id, ow)
378
379
380    # New in Sigil 1.1
381    # ----------------
382
383    # returns "light" or "dark"
384    def colorMode(self):
385        return self._w.colorMode()
386
387    # returns color as css or javascript hex color string #xxxxxx
388    # acccepts the following color roles in a case insensitive manner:
389    #    "Window", "Base", "Text", "Highlight", "HighlightedText"
390    def color(self, role):
391        return self._w.color(role)
392
393
394    # New in Sigil 1.0
395    # ----------------
396
397    # A book path (aka bookpath) is a unique relative path from the
398    # ebook root to a specific file in the epub.  As a relative path meant
399    # to be used in an href or src "link", it only uses forward slashes "/"
400    # as path separators.  Since all files exist inside the
401    # epub root (folder the epub was unzipped into), bookpaths will NEVER
402    # have or use "./" or "../" ie they are in always in canonical form
403
404    # For example under Sigil pre 1.0, all epubs were put into a standard
405    # structure.  Under this standard structure book paths would look like
406    # the following:
407    #   OEBPS/content.opf
408    #   OEBPS/toc.ncx
409    #   OEBPS/Text/Section0001.xhtml
410    #   OEBPS/Images/cover.jpg
411    #
412
413    # and src and hrefs always looked like the following:
414    #    from Section0001.xhtml to Section0002.xhtml: ../Text/Section0002.xhtml
415    #    from Section0001.xhtml to cover.jpg:         ../Images/cover.jpg
416    #    from content.opf to Section0001.xhtml        Text/Section0001.xhtml
417    #    from toc.ncx to Section0001.xhtml            Text/Section0001.xhtml
418
419    # Under Sigil 1.0 and later, the original epub structure can be preserved
420    # meaning that files like content.opf could be named package.opf, and be placed
421    # almost anyplace inside the epub.  This is true for almost all files.
422
423    # So to uniquely identify a file, you need to know the bookpath of the OPF
424    # and the manifest href to the specific file, or the path from the epub
425    # root to the file itself (ie. its bookpath)
426
427    # so the Sigil plugin interface for Sigil 1.0 has been extended to allow
428    # the plugin developer to more easily work with bookpaths, create links
429    # between bookpaths, etc.
430
431    # we will use the terms book_href (or bookhref) interchangeably
432    # with bookpath with the following convention:
433    #    - use book_href when working with "other" files outside the manifest
434    #    - use bookpath when working with files in the opf manifest
435    #    - use either when working with the OPF file as it is at the intersection
436
437    # returns the bookpath/book_href to the opf file
438    def get_opfbookpath(self):
439        return self._w.get_opfbookpath()
440
441    # returns the book path of the folder containing this bookpath
442    def get_startingdir(self, bookpath):
443        return self._w.get_startingdir(bookpath)
444
445    # return a bookpath for the file pointed to by the href
446    # from the specified bookpath starting directory
447    def build_bookpath(self, href, starting_dir):
448        return self._w.build_bookpath(href, starting_dir)
449
450    # returns the href relative path from source bookpath to target bookpath
451    def get_relativepath(self, from_bookpath, to_bookpath):
452        return self._w.get_relativepath(from_bookpath, to_bookpath)
453
454    # adds a new file to the *manifest* with the stated bookpath with the provided
455    # uniqueid, data, (and mediatype if specified)
456    def addbookpath(self, uniqueid, bookpath, data, mime=None):
457        return self._w.addbookpath(uniqueid, bookpath, data, mime)
458
459    # functions for converting from  manifest id to bookpath and back
460    def bookpath_to_id(self, bookpath, ow=None):
461        return self._w.map_bookpath_to_id(bookpath, ow)
462
463    def id_to_bookpath(self, id, ow=None):
464        return self._w.map_id_to_bookpath(id, ow)
465
466    # valid groups: Text, Styles, Images, Fonts, Audio, Video, ncx, opf, Misc
467    # returns a sorted folder list of ebook paths for a group
468    def group_to_folders(self, group, ow=None):
469        return self._w.map_group_to_folders(group, ow)
470
471    def mediatype_to_group(self, mediatype, ow=None):
472        return self._w.map_mediatype_to_group(mediatype, ow)
473