Edit book: Add a tool to split HTML files at multiple locations automatically.

To use it, right click in the file and choose "Split at multiple
locations".  Useful if you want to split at all heading tags or all tags
having a certain class and so on.
This commit is contained in:
Kovid Goyal 2014-01-22 12:08:34 +05:30
parent 83c5359df1
commit 56848d80f4
6 changed files with 103 additions and 7 deletions

View File

@ -433,9 +433,12 @@ the location you want, simply click and the split will be performed.
Splitting the file will automatically update all links and references that
pointed into the bottom half of the file and will open the newly split file in
an editor. If you want to repeatedly split a file, you can do that, and then
use the bulk renaming facility of the Files Browser, described above, to give
the split off files sensible names.
an editor.
You can also split a single HTML file at multiple locations automatically, by
right clicking inside the file in the editor and choosing :guilabel:`Split at
multiple locations`. This will allow you to easily split a large file at all
heading tags or all tags having a certain class and so on.
Miscellaneous Tools

View File

@ -222,6 +222,34 @@ def split(container, name, loc_or_xpath, before=True):
container.dirty(container.opf_name)
return bottom_name
def multisplit(container, name, xpath, before=True):
root = container.parsed(name)
nodes = root.xpath(xpath, namespaces=XPNSMAP)
if not nodes:
raise AbortError(_('The expression %s did not match any nodes') % xpath)
for split_point in nodes:
if in_table(split_point):
raise AbortError('Cannot split inside tables')
if split_point.tag.endswith('}body'):
raise AbortError('Cannot split on the <body> tag')
for i, tag in enumerate(nodes):
tag.set('calibre-split-point', str(i))
current = name
all_names = [name]
for i in xrange(len(nodes)):
current = split(container, current, '//*[@calibre-split-point="%d"]' % i, before=before)
all_names.append(current)
for x in all_names:
for tag in container.parsed(x).xpath('//*[@calibre-split-point]'):
tag.attrib.pop('calibre-split-point')
container.dirty(x)
return all_names[1:]
class MergeLinkReplacer(object):
def __init__(self, base, anchor_map, master, container):

View File

@ -22,7 +22,7 @@ from calibre.ebooks.oeb.polish.container import get_container as _gc, clone_cont
from calibre.ebooks.oeb.polish.cover import mark_as_cover, mark_as_titlepage
from calibre.ebooks.oeb.polish.pretty import fix_all_html, pretty_all
from calibre.ebooks.oeb.polish.replace import rename_files, replace_file, get_recommended_folders, rationalize_folders
from calibre.ebooks.oeb.polish.split import split, merge, AbortError
from calibre.ebooks.oeb.polish.split import split, merge, AbortError, multisplit
from calibre.ebooks.oeb.polish.toc import remove_names_from_toc, find_existing_toc
from calibre.ebooks.oeb.polish.utils import link_stylesheets
from calibre.gui2 import error_dialog, choose_files, question_dialog, info_dialog, choose_save_file
@ -36,7 +36,7 @@ from calibre.gui2.tweak_book.toc import TOCEditor
from calibre.gui2.tweak_book.editor import editor_from_syntax, syntax_from_mime
from calibre.gui2.tweak_book.editor.insert_resource import get_resource_data, NewBook
from calibre.gui2.tweak_book.preferences import Preferences
from calibre.gui2.tweak_book.widgets import RationalizeFolders
from calibre.gui2.tweak_book.widgets import RationalizeFolders, MultiSplit
def get_container(*args, **kwargs):
kwargs['tweak_mode'] = True
@ -821,6 +821,29 @@ class Boss(QObject):
self.apply_container_update_to_gui()
self.edit_file(bottom_name, 'html')
def multisplit(self):
ed = self.gui.central.current_editor
if ed.syntax != 'html':
return
name = None
for n, x in editors.iteritems():
if ed is x:
name = n
break
if name is None:
return
d = MultiSplit(self.gui)
if d.exec_() == d.Accepted:
with BusyCursor():
self.commit_all_editors_to_container()
self.add_savepoint(_('Split %s') % self.gui.elided_text(name))
try:
multisplit(current_container(), name, d.xpath)
except AbortError:
self.rewind_savepoint()
raise
self.apply_container_update_to_gui()
@in_thread_job
def link_clicked(self, name, anchor):
if not name:

View File

@ -268,6 +268,8 @@ class Editor(QMainWindow):
m.addSeparator()
m.addAction(_('&Select all'), self.editor.select_all)
m.addAction(actions['mark-selected-text'])
if self.syntax == 'html':
m.addAction(actions['multisplit'])
m.exec_(self.editor.mapToGlobal(pos))

View File

@ -403,6 +403,9 @@ class Main(MainWindow):
self.action_browse_images = reg(
'view-image.png', _('&Browse images in book'), self.boss.browse_images, 'browse-images', (), _(
'Browse images in the books visually'))
self.action_multiple_split = reg(
'auto_author_sort.png', _('&Split at multiple locations'), self.boss.multisplit, 'multisplit', (), _(
'Split HTML file at multiple locations'))
def create_menubar(self):
p, q = self.create_application_menubar()

View File

@ -6,8 +6,10 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
from PyQt4.Qt import (QDialog, QDialogButtonBox, QGridLayout, QLabel, QLineEdit)
from PyQt4.Qt import (
QDialog, QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QVBoxLayout)
from calibre.gui2 import error_dialog
from calibre.gui2.tweak_book import tprefs
class Dialog(QDialog):
@ -46,7 +48,7 @@ class Dialog(QDialog):
def setup_ui(self):
raise NotImplementedError('You must implement this method in Dialog subclasses')
class RationalizeFolders(Dialog):
class RationalizeFolders(Dialog): # {{{
TYPE_MAP = (
('text', _('Text (HTML) files')),
@ -103,3 +105,38 @@ class RationalizeFolders(Dialog):
def accept(self):
tprefs['folders_for_types'] = self.folder_map
return Dialog.accept(self)
# }}}
class MultiSplit(Dialog): # {{{
def __init__(self, parent=None):
Dialog.__init__(self, _('Specify locations to split at'), 'multisplit-xpath', parent=parent)
def setup_ui(self):
from calibre.gui2.convert.xpath_wizard import XPathEdit
self.l = l = QVBoxLayout(self)
self.setLayout(l)
self.la = la = QLabel(_(
'Specify the locations to split at, using an XPath expression (click'
' the wizard button for help with generating XPath expressions).'))
la.setWordWrap(True)
l.addWidget(la)
self._xpath = xp = XPathEdit(self)
xp.set_msg(_('&XPath expression:'))
xp.setObjectName('editor-multisplit-xpath-edit')
l.addWidget(xp)
l.addWidget(self.bb)
def accept(self):
if not self._xpath.check():
return error_dialog(self, _('Invalid XPath expression'), _(
'The XPath expression %s is invalid.') % self.xpath)
return Dialog.accept(self)
@property
def xpath(self):
return self._xpath.xpath
# }}}