Coverage for app/venv/lib/python3.14/site-packages/weblate/utils/diff.py: 20%
50 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
1# Copyright © Michal Čihař <michal@weblate.org>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
5from __future__ import annotations
7from diff_match_patch import diff_match_patch
8from django.utils.html import format_html
10from weblate.utils.unicodechars import COMPOSITING_CHARS
13class Differ:
14 DIFF_DELETE = diff_match_patch.DIFF_DELETE
15 DIFF_INSERT = diff_match_patch.DIFF_INSERT
16 DIFF_EQUAL = diff_match_patch.DIFF_EQUAL
18 def __init__(self) -> None:
19 self.dmp = diff_match_patch()
21 def compare(self, new: str, old: str) -> list[tuple[str, str]]:
22 dmp = self.dmp
23 diffs = dmp.diff_main(old, new)
24 dmp.diff_cleanupSemantic(diffs)
25 dmp.diff_cleanupEfficiency(diffs)
26 self.cleanup_unicode(diffs)
27 return diffs
29 def cleanup_unicode(self, diffs: list[tuple[str, str]]) -> None:
30 """Merge Unicode characters."""
31 pointer = 0
32 while pointer < len(diffs):
33 if (
34 diffs[pointer][0] != self.DIFF_EQUAL
35 and diffs[pointer][1]
36 and diffs[pointer][1][0] in COMPOSITING_CHARS
37 and pointer > 0
38 and diffs[pointer - 1][0] == self.DIFF_EQUAL
39 ):
40 # Merge previous characters up to anything else than non spacing mark to current diff
41 previous_block = diffs[pointer - 1][1]
42 merged = 1
43 while (
44 merged < len(previous_block)
45 and previous_block[-merged] in COMPOSITING_CHARS
46 ):
47 merged += 1
49 previous_chars = previous_block[-merged:]
51 current_operation = diffs[pointer][0]
52 diffs[pointer] = (
53 current_operation,
54 f"{previous_chars}{diffs[pointer][1]}",
55 )
56 new_operation = (
57 self.DIFF_DELETE
58 if current_operation == self.DIFF_INSERT
59 else self.DIFF_INSERT
60 )
61 if len(previous_block) == merged:
62 diffs[pointer - 1] = (new_operation, previous_chars)
63 else:
64 # Remove extracted char
65 diffs[pointer - 1] = (
66 diffs[pointer - 1][0],
67 previous_block[:-merged],
68 )
69 # Build new diff entry
70 new_diff = (new_operation, previous_chars)
71 # Extend diff list
72 diffs.insert(pointer, new_diff)
73 pointer += 1
74 pointer += 1
76 def highlight(self, new: str, old: str) -> str:
77 diff = self.compare(new, old)
78 output = []
79 for op, data in diff:
80 if op == self.DIFF_DELETE:
81 template = "<del>{}</del>"
82 elif op == self.DIFF_INSERT:
83 template = "<ins>{}</ins>"
84 elif op == self.DIFF_EQUAL:
85 template = "{}"
86 else:
87 msg = f"Unsuppoorted operation: {op}"
88 raise ValueError(msg)
89 output.append(format_html(template, data))
90 return "".join(output)