mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Auto verify ToC destinations
This commit is contained in:
parent
8585c580c7
commit
d4a18a2a19
@ -11,6 +11,7 @@ from urlparse import urlparse
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from calibre.ebooks.oeb.base import XPath
|
||||
from calibre.ebooks.oeb.polish.container import guess_type
|
||||
|
||||
ns = etree.FunctionNamespace('calibre_xpath_extensions')
|
||||
@ -21,6 +22,7 @@ class TOC(object):
|
||||
|
||||
def __init__(self, title=None, dest=None, frag=None):
|
||||
self.title, self.dest, self.frag = title, dest, frag
|
||||
self.dest_exists = self.dest_error = None
|
||||
if self.title: self.title = self.title.strip()
|
||||
self.parent = None
|
||||
self.children = []
|
||||
@ -35,6 +37,12 @@ class TOC(object):
|
||||
for c in self.children:
|
||||
yield c
|
||||
|
||||
def iterdescendants(self):
|
||||
for child in self:
|
||||
yield child
|
||||
for gc in child.iterdescendants():
|
||||
yield gc
|
||||
|
||||
def child_xpath(tag, name):
|
||||
return tag.xpath('./*[calibre:lower-case(local-name()) = "%s"]'%name)
|
||||
|
||||
@ -70,7 +78,36 @@ def parse_ncx(container, ncx_name):
|
||||
process_ncx_node(container, navmaps[0], toc_root, ncx_name)
|
||||
return toc_root
|
||||
|
||||
def get_toc(container):
|
||||
def verify_toc_destinations(container, toc):
|
||||
anchor_map = {}
|
||||
anchor_xpath = XPath('//*/@id|//h:a/@name')
|
||||
for item in toc.iterdescendants():
|
||||
name = item.dest
|
||||
if not name:
|
||||
item.dest_exists = False
|
||||
item.dest_error = _('No file named %s exists')%name
|
||||
continue
|
||||
try:
|
||||
root = container.parsed(name)
|
||||
except KeyError:
|
||||
item.dest_exists = False
|
||||
item.dest_error = _('No file named %s exists')%name
|
||||
continue
|
||||
if not hasattr(root, 'xpath'):
|
||||
item.dest_exists = False
|
||||
item.dest_error = _('No HTML file named %s exists')%name
|
||||
continue
|
||||
if not item.frag:
|
||||
item.dest_exists = True
|
||||
continue
|
||||
if name not in anchor_map:
|
||||
anchor_map[name] = frozenset(anchor_xpath(root))
|
||||
item.dest_exists = item.frag in anchor_map[name]
|
||||
if not item.dest_exists:
|
||||
item.dest_error = _('The anchor %s does not exist in file %s')%(
|
||||
item.frag, name)
|
||||
|
||||
def get_toc(container, verify_destinations=True):
|
||||
toc = container.opf_xpath('//opf:spine/@toc')
|
||||
if toc:
|
||||
toc = container.manifest_id_map.get(toc[0], None)
|
||||
@ -79,6 +116,9 @@ def get_toc(container):
|
||||
toc = container.manifest_type_map.get(ncx, [None])[0]
|
||||
if not toc:
|
||||
return None
|
||||
return parse_ncx(container, toc)
|
||||
ans = parse_ncx(container, toc)
|
||||
if verify_destinations:
|
||||
verify_toc_destinations(container, ans)
|
||||
return ans
|
||||
|
||||
|
||||
|
@ -21,6 +21,8 @@ from calibre.gui2 import Application
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||
from calibre.utils.logging import GUILog
|
||||
|
||||
ICON_SIZE = 24
|
||||
|
||||
class ItemView(QFrame): # {{{
|
||||
|
||||
add_new_item = pyqtSignal(object, object)
|
||||
@ -38,9 +40,12 @@ class ItemView(QFrame): # {{{
|
||||
s.addWidget(rp)
|
||||
s.addWidget(ip)
|
||||
|
||||
self.l1 = la = QLabel(_(
|
||||
self.l1 = la = QLabel('<p>'+_(
|
||||
'You can edit existing entries in the Table of Contents by clicking them'
|
||||
' in the panel to the left.'))
|
||||
' in the panel to the left.')+'<p>'+_(
|
||||
'Entries with a green tick next to them point to a location that has '
|
||||
'been verified to exist. Entries with a red dot are broken and may need'
|
||||
' to be fixed.'))
|
||||
la.setWordWrap(True)
|
||||
l = QVBoxLayout()
|
||||
rp.setLayout(l)
|
||||
@ -70,15 +75,14 @@ class TOCView(QWidget): # {{{
|
||||
self.setLayout(l)
|
||||
self.tocw = t = QTreeWidget(self)
|
||||
t.setHeaderLabel(_('Table of Contents'))
|
||||
icon_size = 32
|
||||
t.setIconSize(QSize(icon_size, icon_size))
|
||||
t.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
|
||||
t.setDragEnabled(True)
|
||||
t.setSelectionMode(t.ExtendedSelection)
|
||||
t.viewport().setAcceptDrops(True)
|
||||
t.setDropIndicatorShown(True)
|
||||
t.setDragDropMode(t.InternalMove)
|
||||
t.setAutoScroll(True)
|
||||
t.setAutoScrollMargin(icon_size*2)
|
||||
t.setAutoScrollMargin(ICON_SIZE*2)
|
||||
t.setDefaultDropAction(Qt.MoveAction)
|
||||
t.setAutoExpandDelay(1000)
|
||||
t.setAnimated(True)
|
||||
@ -86,18 +90,21 @@ class TOCView(QWidget): # {{{
|
||||
l.addWidget(t, 0, 0, 5, 3)
|
||||
self.up_button = b = QToolButton(self)
|
||||
b.setIcon(QIcon(I('arrow-up.png')))
|
||||
b.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
|
||||
l.addWidget(b, 0, 3)
|
||||
b.setToolTip(_('Move current item up'))
|
||||
b.setToolTip(_('Move current entry up'))
|
||||
b.clicked.connect(self.move_up)
|
||||
self.del_button = b = QToolButton(self)
|
||||
b.setIcon(QIcon(I('trash.png')))
|
||||
b.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
|
||||
l.addWidget(b, 2, 3)
|
||||
b.setToolTip(_('Remove all selected items'))
|
||||
b.setToolTip(_('Remove all selected entries'))
|
||||
b.clicked.connect(self.del_items)
|
||||
self.down_button = b = QToolButton(self)
|
||||
b.setIcon(QIcon(I('arrow-down.png')))
|
||||
b.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
|
||||
l.addWidget(b, 4, 3)
|
||||
b.setToolTip(_('Move current item down'))
|
||||
b.setToolTip(_('Move current entry down'))
|
||||
b.clicked.connect(self.move_down)
|
||||
self.expand_all_button = b = QPushButton(_('&Expand all'))
|
||||
col = 5
|
||||
@ -129,14 +136,17 @@ class TOCView(QWidget): # {{{
|
||||
p = item.parent() or self.root
|
||||
p.removeChild(item)
|
||||
|
||||
def highlight_item(self, item):
|
||||
self.tocw.setCurrentItem(item, 0, QItemSelectionModel.ClearAndSelect)
|
||||
self.tocw.scrollToItem(item)
|
||||
|
||||
def move_down(self):
|
||||
item = self.tocw.currentItem()
|
||||
if item is None:
|
||||
if self.root.childCount() == 0:
|
||||
return
|
||||
item = self.root.child(0)
|
||||
self.tocw.setCurrentItem(item, 0, QItemSelectionModel.ClearAndSelect)
|
||||
self.tocw.scrollToItem(item)
|
||||
self.highlight_item(item)
|
||||
return
|
||||
parent = item.parent() or self.root
|
||||
idx = parent.indexOfChild(item)
|
||||
@ -151,8 +161,7 @@ class TOCView(QWidget): # {{{
|
||||
sibling = parent.child(idx+1)
|
||||
parent.removeChild(item)
|
||||
sibling.insertChild(0, item)
|
||||
self.tocw.setCurrentItem(item, 0, QItemSelectionModel.ClearAndSelect)
|
||||
self.tocw.scrollToItem(item)
|
||||
self.highlight_item(item)
|
||||
|
||||
def move_up(self):
|
||||
item = self.tocw.currentItem()
|
||||
@ -160,8 +169,7 @@ class TOCView(QWidget): # {{{
|
||||
if self.root.childCount() == 0:
|
||||
return
|
||||
item = self.root.child(self.root.childCount()-1)
|
||||
self.tocw.setCurrentItem(item, 0, QItemSelectionModel.ClearAndSelect)
|
||||
self.tocw.scrollToItem(item)
|
||||
self.highlight_item(item)
|
||||
return
|
||||
parent = item.parent() or self.root
|
||||
idx = parent.indexOfChild(item)
|
||||
@ -176,8 +184,7 @@ class TOCView(QWidget): # {{{
|
||||
sibling = parent.child(idx-1)
|
||||
parent.removeChild(item)
|
||||
sibling.addChild(item)
|
||||
self.tocw.setCurrentItem(item, 0, QItemSelectionModel.ClearAndSelect)
|
||||
self.tocw.scrollToItem(item)
|
||||
self.highlight_item(item)
|
||||
|
||||
def update_status_tip(self, item):
|
||||
c = item.data(0, Qt.UserRole).toPyObject()
|
||||
@ -200,6 +207,9 @@ class TOCView(QWidget): # {{{
|
||||
self.ebook = ebook
|
||||
self.toc = get_toc(self.ebook)
|
||||
blank = self.blank = QIcon(I('blank.png'))
|
||||
ok = self.ok = QIcon(I('ok.png'))
|
||||
err = self.err = QIcon(I('dot_red.png'))
|
||||
icon_map = {None:blank, True:ok, False:err}
|
||||
|
||||
def process_item(node, parent):
|
||||
for child in node:
|
||||
@ -208,7 +218,12 @@ class TOCView(QWidget): # {{{
|
||||
c.setData(0, Qt.UserRole, child)
|
||||
c.setFlags(Qt.ItemIsDragEnabled|Qt.ItemIsEditable|Qt.ItemIsEnabled|
|
||||
Qt.ItemIsSelectable|Qt.ItemIsDropEnabled)
|
||||
c.setData(0, Qt.DecorationRole, blank)
|
||||
c.setData(0, Qt.DecorationRole, icon_map[child.dest_exists])
|
||||
if child.dest_exists is False:
|
||||
c.setData(0, Qt.ToolTipRole, _(
|
||||
'The location this entry point to does not exist:\n%s')
|
||||
%child.dest_error)
|
||||
|
||||
self.update_status_tip(c)
|
||||
process_item(child, c)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user