Skip to content

Commit

Permalink
Merge ff527ad into ba59ce5
Browse files Browse the repository at this point in the history
  • Loading branch information
regebro authored Sep 12, 2018
2 parents ba59ce5 + ff527ad commit 9fa4abc
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 19 deletions.
2 changes: 1 addition & 1 deletion tests/test_data/complex-text-update.expected.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<body xmlns:diff="http://namespaces.shoobx.com/diff">
<div id="id">
<p><diff:insert>Let's see. </diff:insert>This is some simple text demonstrating the features of the <b diff:delete-formatting=""><i diff:insert-formatting="">human text differ</i></b>. This <u diff:delete-formatting="">feature</u> attempts to make changelog readable for humans. The <i diff:insert-formatting="">human text differ</i> uses sentences as its first order matching. <diff:delete>Let's see.</diff:delete><invalid><diff:insert>It should handle unknown tags just fine.</diff:insert></invalid></p>
<p><diff:insert>Let's see. </diff:insert>This is some simple text demonstrating the features of the <b diff:delete-formatting=""><i diff:insert-formatting="">human text differ</i></b>. This <u diff:delete-formatting="">feature</u> attempts to make changelog readable for humans.<br diff:insert=""/> The <i diff:insert-formatting="">human text differ</i> uses sentences as its first order matching. <diff:delete>Let's see.</diff:delete><invalid><diff:insert>It should handle unknown tags just fine.</diff:insert></invalid></p>
</div>
</body>
2 changes: 1 addition & 1 deletion tests/test_data/complex-text-update.right.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<p>
Let's see. This is some simple text demonstrating the features of the
<i>human text differ</i>. This feature attempts to make changelog
readable for humans. The <i>human text differ</i> uses sentences as its
readable for humans.<br/> The <i>human text differ</i> uses sentences as its
first order matching. <invalid>It should handle unknown tags just fine.</invalid>
</p>

Expand Down
133 changes: 123 additions & 10 deletions tests/test_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
DeleteNode, UpdateAttrib, InsertAttrib, RenameAttrib,
DeleteAttrib, UpdateTextAfter, RenameNode)

from .testing import compare_elements


class APITests(unittest.TestCase):
left = u"<document><p>Text</p><p>More</p></document>"
Expand Down Expand Up @@ -828,6 +830,7 @@ def _diff(self, left, right):
differ = Differ()
differ.set_trees(left_tree, right_tree)
editscript = list(differ.diff())
compare_elements(differ.left, differ.right)
return editscript

def test_process(self):
Expand Down Expand Up @@ -867,8 +870,8 @@ def test_process(self):
InsertAttrib('/document/story/section[2]', 'single-ref', '4'),
MoveNode('/document/story/section[1]/para[3]',
'/document/story/section[2]', 0),
InsertNode('/document/story/section[2]', 'para', 0),
UpdateTextIn('/document/story/section[2]/para[1]',
InsertNode('/document/story/section[2]', 'para', 1),
UpdateTextIn('/document/story/section[2]/para[2]',
'Fourth paragraph'),
DeleteNode('/document/story/deleteme/para[1]'),
DeleteNode('/document/story/deleteme[1]'),
Expand All @@ -895,7 +898,7 @@ def test_no_root_match(self):
self.assertEqual(
result,
[
DeleteAttrib(node='/root[1]', name='attr'),
DeleteAttrib('/root[1]', 'attr'),
MoveNode('/root/n[1]', '/root[1]', 1),
MoveNode('/root/n[2]/p[2]', '/root/n[1]', 0),
]
Expand Down Expand Up @@ -1040,6 +1043,115 @@ def test_rmldoc(self):
]
)

def test_sbt_template(self):
here = os.path.split(__file__)[0]
lfile = os.path.join(here, 'test_data', 'sbt_template.left.xml')
rfile = os.path.join(here, 'test_data', 'sbt_template.right.xml')
with open(lfile, 'rt', encoding='utf8') as infile:
left = infile.read()
with open(rfile, 'rt', encoding='utf8') as infile:
right = infile.read()

result = self._diff(left, right)

# Most lines get too long and flake8 complains because of this part:
bm_bm_bm = '/metal:block/metal:block/metal:block'
self.assertEqual(
result,
[
InsertNode(
bm_bm_bm + '[1]',
'{http://namespaces.shoobx.com/application}section',
0),
InsertAttrib(
bm_bm_bm + '/app:section[1]',
'allowCustom',
'False'),
InsertAttrib(
bm_bm_bm + '/app:section[1]',
'hidden',
"advisor.payment_type == 'none'"),
InsertAttrib(
bm_bm_bm + '/app:section[1]',
'name',
'payment'),
InsertAttrib(
bm_bm_bm + '/app:section[1]',
'title',
'Payment'),
InsertNode(
bm_bm_bm + '/app:section[1]',
'{http://xml.zope.org/namespaces/tal}if',
0),
InsertAttrib(
bm_bm_bm + '/app:section[1]/tal:if[1]',
'condition',
"python: advisor.payment_type == 'stock_award'"),
InsertNode(
bm_bm_bm + '/app:section[1]',
'{http://xml.zope.org/namespaces/tal}if',
1),
InsertAttrib(
bm_bm_bm + '/app:section[1]/tal:if[2]',
'condition',
"python: advisor.payment_type == 'cash'"),
InsertNode(
bm_bm_bm + '/app:section[1]',
'{http://xml.zope.org/namespaces/tal}if',
2),
InsertAttrib(
bm_bm_bm + '/app:section[1]/tal:if[3]',
'condition',
"python: advisor.payment_type == 'stock_award_and_cash'"),
InsertNode(
bm_bm_bm + '/app:section[1]/tal:if[1]',
'para',
0),
UpdateTextIn(
bm_bm_bm + '/app:section[1]/tal:if[1]/para[1]',
'\n\tA '),
InsertNode(
bm_bm_bm + '/app:section[1]/tal:if[1]/para[1]',
'i',
0),
UpdateTextIn(
bm_bm_bm + '/app:section[1]/tal:if[1]/para/i[1]',
'whole'),
UpdateTextAfter(
bm_bm_bm + '/app:section[1]/tal:if[1]/para/i[1]',
' load of formatted text and '),
InsertNode(
bm_bm_bm + '/app:section[1]/tal:if[1]/para[1]',
'br',
1),
UpdateTextAfter(
bm_bm_bm + '/app:section[1]/tal:if[1]/para/br[1]',
' other stuff.\n '),
InsertNode(
bm_bm_bm + '/app:section[1]/tal:if[2]',
'para',
0),
UpdateTextIn(
bm_bm_bm + '/app:section[1]/tal:if[2]/para[1]',
'\n\tMore text for diffing purposes\n '),
InsertNode(
bm_bm_bm + '/app:section[1]/tal:if[3]',
'para',
0),
UpdateTextIn(
bm_bm_bm + '/app:section[1]/tal:if[3]/para[1]',
'\n\tLorem hipster ipso facto\n '),
DeleteNode(
bm_bm_bm + '/app:section[2]/tal:if/para/b[1]'),
DeleteNode(
bm_bm_bm + '/app:section[2]/tal:if/para[1]'),
DeleteNode(
bm_bm_bm + '/app:section[2]/tal:if[1]'),
DeleteNode(
bm_bm_bm + '/app:section[2]')
]
)

def test_namespace(self):
# Test changing nodes and attributes with namespaces
left = u"""<document xmlns:app="someuri">
Expand Down Expand Up @@ -1094,8 +1206,9 @@ def test_namespace(self):
RenameNode(
'/document/story/app:section/foo:para[1]',
'{someuri}para'),
InsertAttrib('/document/story/app:section/app:para[3]',
'{someuri}attrib', 'value'),
InsertAttrib(
'/document/story/app:section/app:para[3]',
'{someuri}attrib', 'value'),
]
)

Expand All @@ -1120,10 +1233,10 @@ def test_multiple_tag_deletes(self):
result = self._diff(left, right)
self.assertEqual(
result,
[UpdateTextIn(node='/document/story[1]', text='\n '),
DeleteNode(node='/document/story/ul/li[3]'),
DeleteNode(node='/document/story/ul/li[2]'),
DeleteNode(node='/document/story/ul/li[1]'),
DeleteNode(node='/document/story/ul[1]'),
[UpdateTextIn('/document/story[1]', '\n '),
DeleteNode('/document/story/ul/li[3]'),
DeleteNode('/document/story/ul/li[2]'),
DeleteNode('/document/story/ul/li[1]'),
DeleteNode('/document/story/ul[1]'),
]
)
2 changes: 2 additions & 0 deletions tests/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ class XMLFormatterFileTests(FormatterFileTests):
class HTMLFormatterFileTests(FormatterFileTests):

# We use the HTMLFormatter for the placeholder tests
# <br/> is missing from this intentionally, to test an edge case
# with empty non-formatting tags in text.
formatter = formatting.XMLFormatter(
normalize=formatting.WS_BOTH,
pretty_print=True,
Expand Down
11 changes: 11 additions & 0 deletions tests/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,14 @@ def generate_filebased_cases(data_dir, test_class, suffix='xml', ignore=()):
function_name = os.path.split(left_filename)[-1].replace('.', '-')
test_name = 'test_' + function_name
setattr(test_class, test_name, test_function)


def compare_elements(left, right):
path = left.getroottree().getpath(left)
assert left.text == right.text, "Texts differ: %s" % path
assert left.tail == right.tail, "Tails differ: %s" % path
assert left.attrib == right.attrib, "Attributes differ: %s" % path
# We intentionally do NOT compare namespaces, they are allowed to differ
assert len(left) == len(right), "Children differ" % path
for l, r in zip(left.getchildren(), right.getchildren()):
compare_elements(l, r)
11 changes: 6 additions & 5 deletions xmldiff/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ def update_node_tag(self, left, right):

def update_node_attr(self, left, right):
left_xpath = utils.getpath(left)
right_xpath = utils.getpath(right)

# Update: Look for differences in attributes

Expand Down Expand Up @@ -238,7 +237,7 @@ def update_node_attr(self, left, right):

# Insert: Find new attributes
for key in sorted(new_keys):
yield InsertAttrib(right_xpath, key, right.attrib[key])
yield InsertAttrib(left_xpath, key, right.attrib[key])
left.attrib[key] = right.attrib[key]

# Delete: remove removed attributes
Expand Down Expand Up @@ -270,13 +269,13 @@ def find_pos(self, child):
# deals with the case of no child being in order.

# Find the last sibling before the child that is in order
i = parent.index(child) - 1
while i >= 0:
i = parent.index(child)
while i >= 1:
i -= 1
sibling = parent[i]
if sibling in self._inorder:
# That's it
break
i -= 1
else:
# No previous sibling in order.
return 0
Expand Down Expand Up @@ -387,6 +386,8 @@ def diff(self, left=None, right=None):
# Move the node from current parent to target
lparent.remove(lnode)
ltarget.insert(pos, lnode)
self._inorder.add(lnode)
self._inorder.add(rnode)

# (d) Align
for action in self.align_children(lnode, rnode):
Expand Down
7 changes: 5 additions & 2 deletions xmldiff/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,12 @@ def mark_diff(self, ph, action):
# Formatting element, add a diff attribute
action += '-formatting'
elem.attrib['{%s}%s' % (DIFF_NS, action)] = ''
else:
# Not formatting, wrap content
elif elem.text:
# Not formatting, but it has content, wrap content
elem.text = self.wrap_diff(elem.text, action)
else:
# No content, not formatting
elem.attrib['{%s}%s' % (DIFF_NS, action)] = ''

# And make a new placeholder for this new entry:
return self.get_placeholder(elem, entry.ttype, entry.close_ph)
Expand Down

0 comments on commit 9fa4abc

Please sign in to comment.