Update app.py
Browse files
app.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import re
|
| 2 |
|
| 3 |
HYPHENS = set(['\u002D','\uFF0D','\u2010','\u2011','\u2013','\u2014'])
|
|
@@ -98,22 +99,109 @@ def build_active_text_and_map(original, flags):
|
|
| 98 |
index_map = [i for i, _ in active_chars]
|
| 99 |
return active_text, index_map
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import difflib
|
| 2 |
import re
|
| 3 |
|
| 4 |
HYPHENS = set(['\u002D','\uFF0D','\u2010','\u2011','\u2013','\u2014'])
|
|
|
|
| 99 |
index_map = [i for i, _ in active_chars]
|
| 100 |
return active_text, index_map
|
| 101 |
|
| 102 |
+
def build_removed_text_and_map(original, flags):
|
| 103 |
+
removed_chars = [(i, ch) for i, ch in enumerate(original) if not flags[i]]
|
| 104 |
+
removed_text = ''.join(ch for _, ch in removed_chars)
|
| 105 |
+
index_map = [i for i, _ in removed_chars]
|
| 106 |
+
return removed_text, index_map
|
| 107 |
+
|
| 108 |
+
def merge_ranges(ranges):
|
| 109 |
+
merged = []
|
| 110 |
+
for s, e in sorted(ranges):
|
| 111 |
+
if s >= e: continue
|
| 112 |
+
if merged and s <= merged[-1][1]:
|
| 113 |
+
merged[-1] = (merged[-1][0], max(merged[-1][1], e))
|
| 114 |
+
else:
|
| 115 |
+
merged.append([s, e])
|
| 116 |
+
return [(s, e) for s, e in merged]
|
| 117 |
+
|
| 118 |
+
def compare(src, dst, settings):
|
| 119 |
+
src_flags = build_active_flags(src, settings)
|
| 120 |
+
dst_flags = build_active_flags(dst, settings)
|
| 121 |
+
|
| 122 |
+
src_active, src_map = build_active_text_and_map(src, src_flags)
|
| 123 |
+
dst_active, dst_map = build_active_text_and_map(dst, dst_flags)
|
| 124 |
+
|
| 125 |
+
src_diff_ranges = []
|
| 126 |
+
dst_diff_ranges = []
|
| 127 |
+
|
| 128 |
+
# 有効文字の比較(両方空なら差分なし)
|
| 129 |
+
if src_active != '' or dst_active != '':
|
| 130 |
+
matcher = difflib.SequenceMatcher(None, src_active, dst_active, autojunk=False)
|
| 131 |
+
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
| 132 |
+
if tag in ('replace', 'delete') and i2 > i1:
|
| 133 |
+
src_diff_ranges.append((src_map[i1], src_map[i2-1] + 1))
|
| 134 |
+
if tag in ('replace', 'insert') and j2 > j1:
|
| 135 |
+
dst_diff_ranges.append((dst_map[j1], dst_map[j2-1] + 1))
|
| 136 |
+
|
| 137 |
+
# 除去文字の比較
|
| 138 |
+
src_removed, src_removed_map = build_removed_text_and_map(src, src_flags)
|
| 139 |
+
dst_removed, dst_removed_map = build_removed_text_and_map(dst, dst_flags)
|
| 140 |
+
|
| 141 |
+
if src_removed != dst_removed and (src_active != '' or dst_active != ''):
|
| 142 |
+
# 除去文字同士をdifflibで比較
|
| 143 |
+
rm_matcher = difflib.SequenceMatcher(None, src_removed, dst_removed, autojunk=False)
|
| 144 |
+
for tag, i1, i2, j1, j2 in rm_matcher.get_opcodes():
|
| 145 |
+
if tag in ('replace', 'delete') and i2 > i1:
|
| 146 |
+
src_diff_ranges.append((src_removed_map[i1], src_removed_map[i2-1] + 1))
|
| 147 |
+
if tag in ('replace', 'insert') and j2 > j1:
|
| 148 |
+
dst_diff_ranges.append((dst_removed_map[j1], dst_removed_map[j2-1] + 1))
|
| 149 |
+
|
| 150 |
+
return merge_ranges(src_diff_ranges), merge_ranges(dst_diff_ranges)
|
| 151 |
+
|
| 152 |
+
def apply_highlight(text, ranges):
|
| 153 |
+
result = []
|
| 154 |
+
prev = 0
|
| 155 |
+
for s, e in ranges:
|
| 156 |
+
result.append(text[prev:s])
|
| 157 |
+
result.append('"' + text[s:e] + '"')
|
| 158 |
+
prev = e
|
| 159 |
+
result.append(text[prev:])
|
| 160 |
+
return ''.join(result)
|
| 161 |
+
|
| 162 |
+
test_cases = [
|
| 163 |
+
("TEST-01", "吾輩は猫である。名前はまだない。", "吾輩は猫である。名前はまだない。", {}),
|
| 164 |
+
("TEST-02", "吾輩は猫である。", "吾輩は犬である。", {}),
|
| 165 |
+
("TEST-03", "abcdef", "abcXYZdef", {}),
|
| 166 |
+
("TEST-04", "abcXYZdef", "abcdef", {}),
|
| 167 |
+
("TEST-08", "吾輩は、猫である。「名前」は(まだ)ない!", "吾輩は猫である名前はまだない",
|
| 168 |
+
{'ignore_yakumono': True}),
|
| 169 |
+
("TEST-09", "(abc)テスト", "abcテスト",
|
| 170 |
+
{'ignore_yakumono': True}),
|
| 171 |
+
("TEST-19", "abc-def-ghi‐jkl‑mno–pqr—stu", "abcdefghijklmnopqrstu",
|
| 172 |
+
{'ignore_hyphen': True}),
|
| 173 |
+
("TEST-21", "abc def ghi", "abcdefghi",
|
| 174 |
+
{'ignore_space': True}),
|
| 175 |
+
("TEST-22", "abc\ndef\nghi", "abcdefghi",
|
| 176 |
+
{'ignore_newline': True}),
|
| 177 |
+
("TEST-23", "吾輩は(猫)である。名前は【まだ】ない!", "吾輩はである。名前はない!",
|
| 178 |
+
{'ignore_yakumono': True, 'ignore_brackets_with_content': True}),
|
| 179 |
+
("TEST-25",
|
| 180 |
+
"abc(def)123アイウ〔ghi〕fi⼀zero-test \nabc\n(def)123アイウ〔ghi〕fi⼀zero-test \nabc",
|
| 181 |
+
"xyz(www)789カキクケコ〔jkl〕fl⼆one+test \nxyz\n(www)789カキクケコ〔jkl〕fl⼆one+test \nxyz",
|
| 182 |
+
{'ignore_alpha': True, 'ignore_digit': True, 'ignore_kana': True, 'ignore_yakumono': True,
|
| 183 |
+
'ignore_ligature': True, 'ignore_kangxi': True, 'ignore_brackets_with_content': True,
|
| 184 |
+
'ignore_kanjinum': True, 'ignore_hyphen': True, 'ignore_kumimoji': True,
|
| 185 |
+
'ignore_space': True, 'ignore_newline': True}),
|
| 186 |
+
("TEST-30", "abc def", "abcdef", {}),
|
| 187 |
+
("TEST-35", "abcXYZdef", "abcdef", {}),
|
| 188 |
+
("TEST-37", "abc-def-ghi‐jkl‑mno–pqr—stu", "abcdefghijklmnopqrstu",
|
| 189 |
+
{'ignore_hyphen': True}),
|
| 190 |
+
# 比較元と比較先を入れ替えたケース
|
| 191 |
+
("TEST-08-Rev", "吾輩は猫である名前はまだない", "吾輩は、猫である。「名前」は(まだ)ない!",
|
| 192 |
+
{'ignore_yakumono': True}),
|
| 193 |
+
("TEST-19-Rev", "abcdefghijklmnopqrstu", "abc-def-ghi‐jkl‑mno–pqr—stu",
|
| 194 |
+
{'ignore_hyphen': True}),
|
| 195 |
+
("TEST-21-Rev", "abcdefghi", "abc def ghi",
|
| 196 |
+
{'ignore_space': True}),
|
| 197 |
+
("TEST-22-Rev", "abcdefghi", "abc\ndef\nghi",
|
| 198 |
+
{'ignore_newline': True}),
|
| 199 |
+
("TEST-23-Rev", "吾輩はである。名前はない!", "吾輩は(猫)である。名前は【まだ】ない!",
|
| 200 |
+
{'ignore_yakumono': True, 'ignore_brackets_with_content': True}),
|
| 201 |
+
]
|
| 202 |
+
|
| 203 |
+
for name, src, dst, settings in test_cases:
|
| 204 |
+
src_ranges, dst_ranges = compare(src, dst, settings)
|
| 205 |
+
print(f"\n=== {name} ===")
|
| 206 |
+
print(f" 比較元: {apply_highlight(src, src_ranges)}")
|
| 207 |
+
print(f" 比較先: {apply_highlight(dst, dst_ranges)}")
|