File size: 2,435 Bytes
4fe5320 db715e0 b301995 db715e0 83b23d4 b301995 83b23d4 b301995 56b871f 9f1fdcf 56b871f f358579 56b871f e2cfeee 56b871f e2cfeee 56b871f b301995 56b871f b301995 83b23d4 2b6b332 4fe5320 b301995 4fe5320 56b871f b301995 56b871f b301995 56b871f 4fe5320 56b871f b301995 56b871f 4fe5320 2b6b332 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | import difflib
import re
from typing import List, Tuple
def preprocess(text: str) -> List[str]:
return re.sub(r"[^\w\s]", "", text.lower()).split()
def tokenize_raw_text(text: str) -> List[str]:
return re.findall(r"\b\w+(?:'\w+)?\b|[^\w\s]", text)
def extract_segment_with_punct(raw_words: List[str], start_index: int, word_count: int) -> Tuple[
List[Tuple[str, bool]], int]:
segment = []
word_seen = 0
i = start_index
while word_seen < word_count and i < len(raw_words):
word = raw_words[i]
if re.match(r"\w+", word): # це слово
punct = ""
i += 1
while i < len(raw_words) and not re.match(r"\w+", raw_words[i]):
punct += raw_words[i]
i += 1
segment.append((word + punct, True))
word_seen += 1
else:
segment.append((word, False))
i += 1
return segment, i
def style_word(word: str, correct: bool) -> str:
color = "#e6ffe6" if correct else "#ffe6e6"
text_color = "#006600" if correct else "#990000"
content = word if correct else "..."
return (
f"<span style='background-color:{color}; color:{text_color}; padding:4px 10px; "
f"margin:4px; border-radius:999px; font-weight:500; display:inline-block;'>{content}</span>"
)
def highlight_fuzzy_diff(user_text: str, original_text: str) -> Tuple[str, int]:
original_words_raw = tokenize_raw_text(original_text)
original_words_clean = preprocess(original_text)
user_words_clean = preprocess(user_text)
matcher = difflib.SequenceMatcher(None, user_words_clean, original_words_clean)
highlighted = []
correct_count = 0
total_count = 0
original_pointer = 0
for op, i1, i2, j1, j2 in matcher.get_opcodes():
word_count = j2 - j1
segment_raw, original_pointer = extract_segment_with_punct(original_words_raw, original_pointer, word_count)
for token, is_word in segment_raw:
if is_word:
total_count += 1
is_correct = op == "equal"
if is_correct:
correct_count += 1
highlighted.append(style_word(token, is_correct))
else:
highlighted.append(token)
score_percent = round((correct_count / total_count) * 100) if total_count > 0 else 0
return " ".join(highlighted), score_percent
|