Diff view: Add option to pretty print HTML/XML before calculating changes

This commit is contained in:
Kovid Goyal 2014-01-30 11:54:03 +05:30
parent cae211974b
commit ec7d64195f
3 changed files with 48 additions and 20 deletions

View File

@ -173,9 +173,10 @@ def pretty_html_tree(container, root):
for body in root.findall('h:body', namespaces=XPNSMAP): for body in root.findall('h:body', namespaces=XPNSMAP):
pretty_block(body) pretty_block(body)
# Handle <script> and <style> tags if container is not None:
for child in root.xpath('//*[local-name()="script" or local-name()="style"]'): # Handle <script> and <style> tags
pretty_script_or_style(container, child) for child in root.xpath('//*[local-name()="script" or local-name()="style"]'):
pretty_script_or_style(container, child)
def fix_html(container, raw): def fix_html(container, raw):
root = container.parse_xhtml(raw) root = container.parse_xhtml(raw)

View File

@ -182,6 +182,7 @@ class Diff(Dialog):
def __init__(self, revert_button_msg=None, parent=None, show_open_in_editor=False): def __init__(self, revert_button_msg=None, parent=None, show_open_in_editor=False):
self.context = 3 self.context = 3
self.beautify = False
self.apply_diff_calls = [] self.apply_diff_calls = []
self.show_open_in_editor = show_open_in_editor self.show_open_in_editor = show_open_in_editor
self.revert_button_msg = revert_button_msg self.revert_button_msg = revert_button_msg
@ -247,14 +248,17 @@ class Diff(Dialog):
b.setChecked(True) b.setChecked(True)
self.pb = b = QToolButton(self) self.pb = b = QToolButton(self)
b.setIcon(QIcon(I('config.png'))) b.setIcon(QIcon(I('config.png')))
b.setText(_('&Context')), b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) b.setText(_('&Options')), b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
b.setToolTip(_('Change the amount of context shown around the changes')) b.setToolTip(_('Change how the differences are displayed'))
b.setPopupMode(b.InstantPopup) b.setPopupMode(b.InstantPopup)
m = QMenu(b) m = QMenu(b)
b.setMenu(m) b.setMenu(m)
cm = self.cm = QMenu(_('Lines of context around each change'))
for i in (3, 5, 10, 50): for i in (3, 5, 10, 50):
m.addAction(_('Show %d lines of context around changes') % i, partial(self.change_context, i)) cm.addAction(_('Show %d lines of context') % i, partial(self.change_context, i))
m.addAction(_('Show all text'), partial(self.change_context, None)) 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) l.addWidget(b, l.rowCount() - 1, l.columnCount(), 1, 1)
self.bb.setStandardButtons(self.bb.Close) self.bb.setStandardButtons(self.bb.Close)
@ -286,13 +290,23 @@ class Diff(Dialog):
if context == self.context: if context == self.context:
return return
self.context = context self.context = context
self.refresh()
def refresh(self):
with self: with self:
self.view.clear() self.view.clear()
for args, kwargs in self.apply_diff_calls: for args, kwargs in self.apply_diff_calls:
kwargs['context'] = self.context kwargs['context'] = self.context
kwargs['beautify'] = self.beautify
self.view.add_diff(*args, **kwargs) self.view.add_diff(*args, **kwargs)
self.view.finalize() self.view.finalize()
def change_beautify(self, beautify):
if beautify == self.beautify:
return
self.beautify = beautify
self.refresh()
def __enter__(self): def __enter__(self):
self.stacks.setCurrentIndex(0) self.stacks.setCurrentIndex(0)
self.busy.pi.startAnimation() self.busy.pi.startAnimation()
@ -343,31 +357,28 @@ class Diff(Dialog):
info_dialog(self, _('No changes found'), identical_msg, show=True) info_dialog(self, _('No changes found'), identical_msg, show=True)
return True return True
kwargs = lambda name: {'context':self.context, 'beautify':self.beautify, 'syntax':syntax_map.get(name, None)}
if isinstance(changed_names, dict): if isinstance(changed_names, dict):
for name, other_name in sorted(changed_names.iteritems(), key=lambda x:numeric_sort_key(x[0])): 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)) args = (name, other_name, cache.left(name), cache.right(other_name))
kwargs = {'syntax':syntax_map.get(name, None), 'context':self.context} add(args, kwargs(name))
add(args, kwargs)
else: else:
for name in sorted(changed_names, key=numeric_sort_key): for name in sorted(changed_names, key=numeric_sort_key):
args = (name, name, cache.left(name), cache.right(name)) args = (name, name, cache.left(name), cache.right(name))
kwargs = {'syntax':syntax_map.get(name, None), 'context':self.context} add(args, kwargs(name))
add(args, kwargs)
for name in sorted(added_names, key=numeric_sort_key): for name in sorted(added_names, key=numeric_sort_key):
args = (_('[%s was added]') % name, name, None, cache.right(name)) args = (_('[%s was added]') % name, name, None, cache.right(name))
kwargs = {'syntax':syntax_map.get(name, None), 'context':self.context} add(args, kwargs(name))
add(args, kwargs)
for name in sorted(removed_names, key=numeric_sort_key): for name in sorted(removed_names, key=numeric_sort_key):
args = (name, _('[%s was removed]') % name, cache.left(name), None) args = (name, _('[%s was removed]') % name, cache.left(name), None)
kwargs = {'syntax':syntax_map.get(name, None), 'context':self.context} add(args, kwargs(name))
add(args, kwargs)
for name, new_name in sorted(renamed_names.iteritems(), key=lambda x:numeric_sort_key(x[0])): for name, new_name in sorted(renamed_names.iteritems(), key=lambda x:numeric_sort_key(x[0])):
args = (name, new_name, None, None) args = (name, new_name, None, None)
kwargs = {'syntax':syntax_map.get(name, None), 'context':self.context} add(args, kwargs(name))
add(args, kwargs)
def keyPressEvent(self, ev): def keyPressEvent(self, ev):
if not self.view.handle_key(ev): if not self.view.handle_key(ev):

View File

@ -45,6 +45,20 @@ def get_theme():
theme = THEMES[default_theme()] theme = THEMES[default_theme()]
return 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): # {{{ class LineNumberMap(dict): # {{{
'Map line numbers and keep track of the maximum width of the line numbers' 'Map line numbers and keep track of the maximum width of the line numbers'
@ -518,7 +532,7 @@ class DiffSplit(QSplitter): # {{{
v.setTextCursor(c) v.setTextCursor(c)
self.update() 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 '' 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_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('')) 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): for v in (self.left, self.right):
v.appendPlainText('\n') v.appendPlainText('\n')
elif is_text: 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': elif syntax == 'raster_image':
self.add_image_diff(left_text, right_text) self.add_image_diff(left_text, right_text)
else: else:
@ -651,9 +665,11 @@ class DiffSplit(QSplitter): # {{{
# }}} # }}}
# text diffs {{{ # 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) left_text = unicodedata.normalize('NFC', left_text)
right_text = unicodedata.normalize('NFC', right_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() left_lines = self.left_lines = left_text.splitlines()
right_lines = self.right_lines = right_text.splitlines() right_lines = self.right_lines = right_text.splitlines()