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