mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Start work on ToC editor
This commit is contained in:
parent
7b6db40323
commit
e6a7357e66
@ -157,12 +157,13 @@ class TOC(list):
|
||||
toc = m[0]
|
||||
self.read_ncx_toc(toc)
|
||||
|
||||
def read_ncx_toc(self, toc):
|
||||
def read_ncx_toc(self, toc, root=None):
|
||||
self.base_path = os.path.dirname(toc)
|
||||
raw = xml_to_unicode(open(toc, 'rb').read(), assume_utf8=True,
|
||||
strip_encoding_pats=True)[0]
|
||||
root = etree.fromstring(raw, parser=etree.XMLParser(recover=True,
|
||||
no_network=True))
|
||||
if root is None:
|
||||
raw = xml_to_unicode(open(toc, 'rb').read(), assume_utf8=True,
|
||||
strip_encoding_pats=True)[0]
|
||||
root = etree.fromstring(raw, parser=etree.XMLParser(recover=True,
|
||||
no_network=True))
|
||||
xpn = {'re': 'http://exslt.org/regular-expressions'}
|
||||
XPath = functools.partial(etree.XPath, namespaces=xpn)
|
||||
|
||||
|
@ -8,6 +8,7 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, logging, sys, hashlib, uuid, re
|
||||
from collections import defaultdict
|
||||
from io import BytesIO
|
||||
from urllib import unquote as urlunquote, quote as urlquote
|
||||
from urlparse import urlparse
|
||||
@ -230,6 +231,14 @@ class Container(object):
|
||||
return {item.get('id'):self.href_to_name(item.get('href'), self.opf_name)
|
||||
for item in self.opf_xpath('//opf:manifest/opf:item[@href and @id]')}
|
||||
|
||||
@property
|
||||
def manifest_type_map(self):
|
||||
ans = defaultdict(list)
|
||||
for item in self.opf_xpath('//opf:manifest/opf:item[@href and @media-type]'):
|
||||
ans[item.get('media-type').lower()].append(self.href_to_name(
|
||||
item.get('href'), self.opf_name))
|
||||
return {mt:tuple(v) for mt, v in ans.iteritems()}
|
||||
|
||||
@property
|
||||
def guide_type_map(self):
|
||||
return {item.get('type', ''):self.href_to_name(item.get('href'), self.opf_name)
|
||||
|
83
src/calibre/ebooks/oeb/polish/toc.py
Normal file
83
src/calibre/ebooks/oeb/polish/toc.py
Normal file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from urlparse import urlparse
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from calibre.ebooks.oeb.polish.container import guess_type
|
||||
|
||||
ns = etree.FunctionNamespace('calibre_xpath_extensions')
|
||||
ns.prefix = 'calibre'
|
||||
ns['lower-case'] = lambda c, x: x.lower() if hasattr(x, 'lower') else x
|
||||
|
||||
class TOC(object):
|
||||
|
||||
def __init__(self, title=None, dest=None, frag=None):
|
||||
self.title, self.dest, self.frag = title, dest, frag
|
||||
self.parent = None
|
||||
self.children = []
|
||||
|
||||
def add(self, title, dest, frag=None):
|
||||
c = TOC(title, dest, frag)
|
||||
self.children.append(c)
|
||||
c.parent = self
|
||||
return c
|
||||
|
||||
def __iter__(self):
|
||||
for c in self.children:
|
||||
yield c
|
||||
|
||||
def child_xpath(tag, name):
|
||||
return tag.xpath('./*[calibre:lower-case(local-name()) = "%s"]'%name)
|
||||
|
||||
def add_from_navpoint(container, navpoint, parent, ncx_name):
|
||||
dest = frag = text = None
|
||||
nl = child_xpath(navpoint, 'navlabel')
|
||||
if nl:
|
||||
nl = nl[0]
|
||||
text = ''
|
||||
for txt in child_xpath(nl, 'text'):
|
||||
text += etree.tostring(txt, method='text',
|
||||
encoding=unicode, with_tail=False)
|
||||
content = child_xpath(navpoint, 'content')
|
||||
if content:
|
||||
content = content[0]
|
||||
href = content.get('src', None)
|
||||
if href:
|
||||
dest = container.href_to_name(href, base=ncx_name)
|
||||
frag = urlparse(href).fragment or None
|
||||
return parent.add(text or None, dest or None, frag or None)
|
||||
|
||||
def process_ncx_node(container, node, toc_parent, ncx_name):
|
||||
for navpoint in node.xpath('./*[calibre:lower-case(local-name()) = "navpoint"]'):
|
||||
child = add_from_navpoint(container, navpoint, toc_parent, ncx_name)
|
||||
if child is not None:
|
||||
process_ncx_node(container, navpoint, child, ncx_name)
|
||||
|
||||
def parse_ncx(container, ncx_name):
|
||||
root = container.parsed(ncx_name)
|
||||
toc_root = TOC()
|
||||
navmaps = root.xpath('//*[calibre:lower-case(local-name()) = "navmap"]')
|
||||
if navmaps:
|
||||
process_ncx_node(container, navmaps[0], toc_root, ncx_name)
|
||||
return toc_root
|
||||
|
||||
def get_toc(container):
|
||||
toc = container.opf_xpath('//opf:spine/@toc')
|
||||
if toc:
|
||||
toc = container.manifest_id_map.get(toc[0], None)
|
||||
if not toc:
|
||||
ncx = guess_type('a.ncx')
|
||||
toc = container.manifest_type_map.get(ncx, [None])[0]
|
||||
if not toc:
|
||||
return None
|
||||
return parse_ncx(container, toc)
|
||||
|
||||
|
11
src/calibre/gui2/toc/__init__.py
Normal file
11
src/calibre/gui2/toc/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
|
132
src/calibre/gui2/toc/main.py
Normal file
132
src/calibre/gui2/toc/main.py
Normal file
@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import sys, os
|
||||
from threading import Thread
|
||||
|
||||
from PyQt4.Qt import (QDialog, QVBoxLayout, QDialogButtonBox, QSize,
|
||||
QStackedWidget, QWidget, QLabel, Qt, pyqtSignal, QIcon,
|
||||
QTreeWidget, QHBoxLayout, QTreeWidgetItem)
|
||||
|
||||
from calibre.ebooks.oeb.polish.container import get_container
|
||||
from calibre.ebooks.oeb.polish.toc import get_toc
|
||||
from calibre.gui2 import Application
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||
from calibre.utils.logging import GUILog
|
||||
|
||||
class TOCView(QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
QWidget.__init__(self, parent)
|
||||
l = self.l = QHBoxLayout()
|
||||
self.setLayout(l)
|
||||
self.tocw = t = QTreeWidget(self)
|
||||
t.setHeaderLabel(_('Table of Contents'))
|
||||
icon_size = 16
|
||||
t.setIconSize(QSize(icon_size, icon_size))
|
||||
t.setDragEnabled(True)
|
||||
t.setSelectionMode(t.SingleSelection)
|
||||
t.viewport().setAcceptDrops(True)
|
||||
t.setDropIndicatorShown(True)
|
||||
t.setDragDropMode(t.InternalMove)
|
||||
t.setAutoScroll(True)
|
||||
t.setAutoScrollMargin(icon_size*2)
|
||||
t.setDefaultDropAction(Qt.MoveAction)
|
||||
l.addWidget(t)
|
||||
|
||||
def data_changed(self, top_left, bottom_right):
|
||||
for r in xrange(top_left.row(), bottom_right.row()+1):
|
||||
idx = self.tocw.model().index(r, 0, top_left.parent())
|
||||
new_title = unicode(idx.data(Qt.DisplayRole).toString()).strip()
|
||||
toc = idx.data(Qt.UserRole).toPyObject()
|
||||
toc.title = new_title or _('(Untitled)')
|
||||
|
||||
def __call__(self, ebook):
|
||||
self.ebook = ebook
|
||||
self.toc = get_toc(self.ebook)
|
||||
blank = QIcon(I('blank.png'))
|
||||
|
||||
def process_item(node, parent):
|
||||
for child in node:
|
||||
c = QTreeWidgetItem(parent)
|
||||
c.setData(0, Qt.DisplayRole, child.title or _('(Untitled)'))
|
||||
c.setData(0, Qt.UserRole, child)
|
||||
c.setFlags(Qt.ItemIsDragEnabled|Qt.ItemIsEditable|Qt.ItemIsEnabled|
|
||||
Qt.ItemIsSelectable|Qt.ItemIsDropEnabled)
|
||||
c.setData(0, Qt.DecorationRole, blank)
|
||||
process_item(child, c)
|
||||
|
||||
root = self.tocw.invisibleRootItem()
|
||||
root.setData(0, Qt.UserRole, self.toc)
|
||||
process_item(self.toc, root)
|
||||
self.tocw.model().dataChanged.connect(self.data_changed)
|
||||
|
||||
class TOCEditor(QDialog):
|
||||
|
||||
explode_done = pyqtSignal()
|
||||
|
||||
def __init__(self, pathtobook, title=None, parent=None):
|
||||
QDialog.__init__(self, parent)
|
||||
self.pathtobook = pathtobook
|
||||
|
||||
t = title or os.path.basename(pathtobook)
|
||||
self.setWindowTitle(_('Edit the ToC in %s')%t)
|
||||
self.setWindowIcon(QIcon(I('highlight_only_on.png')))
|
||||
|
||||
l = self.l = QVBoxLayout()
|
||||
self.setLayout(l)
|
||||
|
||||
self.stacks = s = QStackedWidget(self)
|
||||
l.addWidget(s)
|
||||
self.loading_widget = lw = QWidget(self)
|
||||
s.addWidget(lw)
|
||||
ll = QVBoxLayout()
|
||||
lw.setLayout(ll)
|
||||
self.pi = pi = ProgressIndicator()
|
||||
pi.setDisplaySize(200)
|
||||
pi.startAnimation()
|
||||
ll.addWidget(pi, alignment=Qt.AlignHCenter|Qt.AlignCenter)
|
||||
la = QLabel(_('Loading %s, please wait...')%t)
|
||||
la.setStyleSheet('QLabel { font-size: 20pt }')
|
||||
ll.addWidget(la, alignment=Qt.AlignHCenter|Qt.AlignTop)
|
||||
self.toc_view = TOCView(self)
|
||||
s.addWidget(self.toc_view)
|
||||
|
||||
bb = self.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
|
||||
l.addWidget(bb)
|
||||
bb.accepted.connect(self.accept)
|
||||
bb.rejected.connect(self.reject)
|
||||
|
||||
self.explode_done.connect(self.read_toc, type=Qt.QueuedConnection)
|
||||
|
||||
self.resize(950, 630)
|
||||
|
||||
def start(self):
|
||||
t = Thread(target=self.explode)
|
||||
t.daemon = True
|
||||
self.log = GUILog()
|
||||
t.start()
|
||||
|
||||
def explode(self):
|
||||
self.ebook = get_container(self.pathtobook, log=self.log)
|
||||
if not self.isVisible():
|
||||
return
|
||||
self.explode_done.emit()
|
||||
|
||||
def read_toc(self):
|
||||
self.toc_view(self.ebook)
|
||||
self.stacks.setCurrentIndex(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = Application([], force_calibre_style=True)
|
||||
app
|
||||
d = TOCEditor(sys.argv[-1])
|
||||
d.start()
|
||||
d.exec_()
|
||||
|
Loading…
x
Reference in New Issue
Block a user