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

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4 

5from __future__ import annotations 

6 

7from diff_match_patch import diff_match_patch 

8from django.utils.html import format_html 

9 

10from weblate.utils.unicodechars import COMPOSITING_CHARS 

11 

12 

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 

17 

18 def __init__(self) -> None: 

19 self.dmp = diff_match_patch() 

20 

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 

28 

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 

48 

49 previous_chars = previous_block[-merged:] 

50 

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 

75 

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)