mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
83c5359df1
commit
56848d80f4
@ -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
|
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
|
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
|
an editor.
|
||||||
use the bulk renaming facility of the Files Browser, described above, to give
|
|
||||||
the split off files sensible names.
|
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
|
Miscellaneous Tools
|
||||||
|
@ -222,6 +222,34 @@ def split(container, name, loc_or_xpath, before=True):
|
|||||||
container.dirty(container.opf_name)
|
container.dirty(container.opf_name)
|
||||||
return bottom_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):
|
class MergeLinkReplacer(object):
|
||||||
|
|
||||||
def __init__(self, base, anchor_map, master, container):
|
def __init__(self, base, anchor_map, master, container):
|
||||||
|
@ -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.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.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.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.toc import remove_names_from_toc, find_existing_toc
|
||||||
from calibre.ebooks.oeb.polish.utils import link_stylesheets
|
from calibre.ebooks.oeb.polish.utils import link_stylesheets
|
||||||
from calibre.gui2 import error_dialog, choose_files, question_dialog, info_dialog, choose_save_file
|
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 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.editor.insert_resource import get_resource_data, NewBook
|
||||||
from calibre.gui2.tweak_book.preferences import Preferences
|
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):
|
def get_container(*args, **kwargs):
|
||||||
kwargs['tweak_mode'] = True
|
kwargs['tweak_mode'] = True
|
||||||
@ -821,6 +821,29 @@ class Boss(QObject):
|
|||||||
self.apply_container_update_to_gui()
|
self.apply_container_update_to_gui()
|
||||||
self.edit_file(bottom_name, 'html')
|
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
|
@in_thread_job
|
||||||
def link_clicked(self, name, anchor):
|
def link_clicked(self, name, anchor):
|
||||||
if not name:
|
if not name:
|
||||||
|
@ -268,6 +268,8 @@ class Editor(QMainWindow):
|
|||||||
m.addSeparator()
|
m.addSeparator()
|
||||||
m.addAction(_('&Select all'), self.editor.select_all)
|
m.addAction(_('&Select all'), self.editor.select_all)
|
||||||
m.addAction(actions['mark-selected-text'])
|
m.addAction(actions['mark-selected-text'])
|
||||||
|
if self.syntax == 'html':
|
||||||
|
m.addAction(actions['multisplit'])
|
||||||
m.exec_(self.editor.mapToGlobal(pos))
|
m.exec_(self.editor.mapToGlobal(pos))
|
||||||
|
|
||||||
|
|
||||||
|
@ -403,6 +403,9 @@ class Main(MainWindow):
|
|||||||
self.action_browse_images = reg(
|
self.action_browse_images = reg(
|
||||||
'view-image.png', _('&Browse images in book'), self.boss.browse_images, 'browse-images', (), _(
|
'view-image.png', _('&Browse images in book'), self.boss.browse_images, 'browse-images', (), _(
|
||||||
'Browse images in the books visually'))
|
'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):
|
def create_menubar(self):
|
||||||
p, q = self.create_application_menubar()
|
p, q = self.create_application_menubar()
|
||||||
|
@ -6,8 +6,10 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
__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
|
from calibre.gui2.tweak_book import tprefs
|
||||||
|
|
||||||
class Dialog(QDialog):
|
class Dialog(QDialog):
|
||||||
@ -46,7 +48,7 @@ class Dialog(QDialog):
|
|||||||
def setup_ui(self):
|
def setup_ui(self):
|
||||||
raise NotImplementedError('You must implement this method in Dialog subclasses')
|
raise NotImplementedError('You must implement this method in Dialog subclasses')
|
||||||
|
|
||||||
class RationalizeFolders(Dialog):
|
class RationalizeFolders(Dialog): # {{{
|
||||||
|
|
||||||
TYPE_MAP = (
|
TYPE_MAP = (
|
||||||
('text', _('Text (HTML) files')),
|
('text', _('Text (HTML) files')),
|
||||||
@ -103,3 +105,38 @@ class RationalizeFolders(Dialog):
|
|||||||
def accept(self):
|
def accept(self):
|
||||||
tprefs['folders_for_types'] = self.folder_map
|
tprefs['folders_for_types'] = self.folder_map
|
||||||
return Dialog.accept(self)
|
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
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user