mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Diff view: Add option to pretty print HTML/XML before calculating changes
This commit is contained in:
parent
cae211974b
commit
ec7d64195f
@ -173,9 +173,10 @@ def pretty_html_tree(container, root):
|
||||
for body in root.findall('h:body', namespaces=XPNSMAP):
|
||||
pretty_block(body)
|
||||
|
||||
# Handle <script> and <style> tags
|
||||
for child in root.xpath('//*[local-name()="script" or local-name()="style"]'):
|
||||
pretty_script_or_style(container, child)
|
||||
if container is not None:
|
||||
# Handle <script> and <style> tags
|
||||
for child in root.xpath('//*[local-name()="script" or local-name()="style"]'):
|
||||
pretty_script_or_style(container, child)
|
||||
|
||||
def fix_html(container, raw):
|
||||
root = container.parse_xhtml(raw)
|
||||
|
@ -182,6 +182,7 @@ class Diff(Dialog):
|
||||
|
||||
def __init__(self, revert_button_msg=None, parent=None, show_open_in_editor=False):
|
||||
self.context = 3
|
||||
self.beautify = False
|
||||
self.apply_diff_calls = []
|
||||
self.show_open_in_editor = show_open_in_editor
|
||||
self.revert_button_msg = revert_button_msg
|
||||
@ -247,14 +248,17 @@ class Diff(Dialog):
|
||||
b.setChecked(True)
|
||||
self.pb = b = QToolButton(self)
|
||||
b.setIcon(QIcon(I('config.png')))
|
||||
b.setText(_('&Context')), b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
|
||||
b.setToolTip(_('Change the amount of context shown around the changes'))
|
||||
b.setText(_('&Options')), b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
|
||||
b.setToolTip(_('Change how the differences are displayed'))
|
||||
b.setPopupMode(b.InstantPopup)
|
||||
m = QMenu(b)
|
||||
b.setMenu(m)
|
||||
cm = self.cm = QMenu(_('Lines of context around each change'))
|
||||
for i in (3, 5, 10, 50):
|
||||
m.addAction(_('Show %d lines of context around changes') % i, partial(self.change_context, i))
|
||||
m.addAction(_('Show all text'), partial(self.change_context, None))
|
||||
cm.addAction(_('Show %d lines of context') % i, partial(self.change_context, i))
|
||||
cm.addAction(_('Show all text'), partial(self.change_context, None))
|
||||
m.addAction(_('Beautify HTML/XML files before comparing them'), partial(self.change_beautify, True))
|
||||
m.addMenu(cm)
|
||||
l.addWidget(b, l.rowCount() - 1, l.columnCount(), 1, 1)
|
||||
|
||||
self.bb.setStandardButtons(self.bb.Close)
|
||||
@ -286,13 +290,23 @@ class Diff(Dialog):
|
||||
if context == self.context:
|
||||
return
|
||||
self.context = context
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
with self:
|
||||
self.view.clear()
|
||||
for args, kwargs in self.apply_diff_calls:
|
||||
kwargs['context'] = self.context
|
||||
kwargs['beautify'] = self.beautify
|
||||
self.view.add_diff(*args, **kwargs)
|
||||
self.view.finalize()
|
||||
|
||||
def change_beautify(self, beautify):
|
||||
if beautify == self.beautify:
|
||||
return
|
||||
self.beautify = beautify
|
||||
self.refresh()
|
||||
|
||||
def __enter__(self):
|
||||
self.stacks.setCurrentIndex(0)
|
||||
self.busy.pi.startAnimation()
|
||||
@ -343,31 +357,28 @@ class Diff(Dialog):
|
||||
info_dialog(self, _('No changes found'), identical_msg, show=True)
|
||||
return True
|
||||
|
||||
kwargs = lambda name: {'context':self.context, 'beautify':self.beautify, 'syntax':syntax_map.get(name, None)}
|
||||
|
||||
if isinstance(changed_names, dict):
|
||||
for name, other_name in sorted(changed_names.iteritems(), key=lambda x:numeric_sort_key(x[0])):
|
||||
args = (name, other_name, cache.left(name), cache.right(other_name))
|
||||
kwargs = {'syntax':syntax_map.get(name, None), 'context':self.context}
|
||||
add(args, kwargs)
|
||||
add(args, kwargs(name))
|
||||
else:
|
||||
for name in sorted(changed_names, key=numeric_sort_key):
|
||||
args = (name, name, cache.left(name), cache.right(name))
|
||||
kwargs = {'syntax':syntax_map.get(name, None), 'context':self.context}
|
||||
add(args, kwargs)
|
||||
add(args, kwargs(name))
|
||||
|
||||
for name in sorted(added_names, key=numeric_sort_key):
|
||||
args = (_('[%s was added]') % name, name, None, cache.right(name))
|
||||
kwargs = {'syntax':syntax_map.get(name, None), 'context':self.context}
|
||||
add(args, kwargs)
|
||||
add(args, kwargs(name))
|
||||
|
||||
for name in sorted(removed_names, key=numeric_sort_key):
|
||||
args = (name, _('[%s was removed]') % name, cache.left(name), None)
|
||||
kwargs = {'syntax':syntax_map.get(name, None), 'context':self.context}
|
||||
add(args, kwargs)
|
||||
add(args, kwargs(name))
|
||||
|
||||
for name, new_name in sorted(renamed_names.iteritems(), key=lambda x:numeric_sort_key(x[0])):
|
||||
args = (name, new_name, None, None)
|
||||
kwargs = {'syntax':syntax_map.get(name, None), 'context':self.context}
|
||||
add(args, kwargs)
|
||||
add(args, kwargs(name))
|
||||
|
||||
def keyPressEvent(self, ev):
|
||||
if not self.view.handle_key(ev):
|
||||
|
@ -45,6 +45,20 @@ def get_theme():
|
||||
theme = THEMES[default_theme()]
|
||||
return theme
|
||||
|
||||
def beautify_text(raw, syntax):
|
||||
from lxml import etree
|
||||
from calibre.ebooks.oeb.polish.parsing import parse
|
||||
from calibre.ebooks.oeb.polish.pretty import pretty_xml_tree, pretty_html_tree
|
||||
from calibre.ebooks.chardet import strip_encoding_declarations
|
||||
if syntax == 'xml':
|
||||
root = etree.fromstring(strip_encoding_declarations(raw))
|
||||
pretty_xml_tree(root)
|
||||
else:
|
||||
root = parse(raw, line_numbers=False)
|
||||
pretty_html_tree(None, root)
|
||||
return etree.tostring(root, encoding=unicode)
|
||||
|
||||
|
||||
class LineNumberMap(dict): # {{{
|
||||
|
||||
'Map line numbers and keep track of the maximum width of the line numbers'
|
||||
@ -518,7 +532,7 @@ class DiffSplit(QSplitter): # {{{
|
||||
v.setTextCursor(c)
|
||||
self.update()
|
||||
|
||||
def add_diff(self, left_name, right_name, left_text, right_text, context=None, syntax=None):
|
||||
def add_diff(self, left_name, right_name, left_text, right_text, context=None, syntax=None, beautify=False):
|
||||
left_text, right_text = left_text or '', right_text or ''
|
||||
is_identical = len(left_text) == len(right_text) and left_text == right_text and left_name == right_name
|
||||
is_text = isinstance(left_text, type('')) and isinstance(right_text, type(''))
|
||||
@ -542,7 +556,7 @@ class DiffSplit(QSplitter): # {{{
|
||||
for v in (self.left, self.right):
|
||||
v.appendPlainText('\n')
|
||||
elif is_text:
|
||||
self.add_text_diff(left_text, right_text, context, syntax)
|
||||
self.add_text_diff(left_text, right_text, context, syntax, beautify=beautify)
|
||||
elif syntax == 'raster_image':
|
||||
self.add_image_diff(left_text, right_text)
|
||||
else:
|
||||
@ -651,9 +665,11 @@ class DiffSplit(QSplitter): # {{{
|
||||
# }}}
|
||||
|
||||
# text diffs {{{
|
||||
def add_text_diff(self, left_text, right_text, context, syntax):
|
||||
def add_text_diff(self, left_text, right_text, context, syntax, beautify=False):
|
||||
left_text = unicodedata.normalize('NFC', left_text)
|
||||
right_text = unicodedata.normalize('NFC', right_text)
|
||||
if beautify and syntax in {'xml', 'html'}:
|
||||
left_text, right_text = beautify_text(left_text, syntax), beautify_text(right_text, syntax)
|
||||
left_lines = self.left_lines = left_text.splitlines()
|
||||
right_lines = self.right_lines = right_text.splitlines()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user