File size: 7,176 Bytes
7cb8aac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
"""Shared rule engine for the humanize-text model.

Conservative bilingual editing rules derived from the reference projects:
- spaces/humanizer-lite (phrase replacements + protected spans)
- trust-fixes/humanizer.py (multi-method pipeline philosophy)

The rules generate the supervised (ai_text -> human_text) pairs used to
fine-tune the seq2seq model, and can also run as a standalone fallback.
"""

import re

# --------------------------------------------------------------------------
# Protected spans: code, URLs, paths, numbers, quoted strings.
# --------------------------------------------------------------------------

PROTECTED_PATTERN = re.compile(
    r"(`[^`]+`|https?://[^\s\u4e00-\u9fff,。!?;:、]+|(?:[A-Za-z]:)?(?:[/\\][\w.\-]+)+|"
    r"\b\d+(?:\.\d+)?(?:%|[A-Za-z]+)?\b|\"[^\"]*\"|'[^']*')"
)


CJK_RE = re.compile(r"[\u4e00-\u9fff]")


def protect(text: str, lang: str = "auto"):
    """Replace protected spans with language-aware placeholders:
    EN ``PROTECTED_n``, ZH ``【保护n】`` (both tokenizer-friendly)."""
    protected = []
    zh = (
        lang == "zh"
        or (lang == "auto" and len(CJK_RE.findall(text)) > 0)
    )

    def replace(match):
        token = f"【保护{len(protected)}】" if zh else f"PROTECTED_{len(protected)}"
        protected.append(match.group(0))
        return token

    return PROTECTED_PATTERN.sub(replace, text), protected


def restore(text: str, protected):
    """Restore placeholders (either format, tolerant of inserted spaces)."""
    for index, value in enumerate(protected):
        text = re.sub(rf"PROTECTED_{index}\b", lambda m: value, text)
        text = re.sub(
            rf"【\s*保\s*护\s*{index}\s*】", lambda m: value, text
        )
    return text


# --------------------------------------------------------------------------
# Formulaic (AI) phrases, English and Chinese.
# --------------------------------------------------------------------------

EN_PHRASES = {
    "in today's rapidly evolving world": "today",
    "in today's fast-paced world": "now",
    "it is important to note that": "",
    "it is worth noting that": "",
    "it can be seen that": "",
    "serves as a testament to": "shows",
    "a testament to": "proof of",
    "delve into": "look at",
    "leverage": "use",
    "leveraging": "using",
    "seamless": "smooth",
    "robust": "reliable",
    "game-changer": "big improvement",
    "groundbreaking": "new",
    "cutting-edge": "modern",
    "state-of-the-art": "modern",
    "plays a crucial role in": "matters for",
    "plays a crucial role": "matters",
    "in the realm of": "in",
    "in conclusion": "",
    "moreover": "also",
    "furthermore": "also",
    "additionally": "also",
    "it is essential to": "we need to",
    "comprehensive approach": "broad approach",
    "foster": "support",
    "fostering": "supporting",
    "undoubtedly": "",
    "no longer optional but essential": "necessary",
    "revolutionize": "change",
    "revolutionizing": "changing",
    "holistic": "full",
}

ZH_PHRASES = {
    "值得注意的是": "",
    "在当今快速发展的时代": "现在",
    "综上所述": "",
    "总而言之": "",
    "由此可见": "",
    "赋能": "帮助",
    "助力": "帮助",
    "降本增效": "省钱提效",
    "闭环": "完整流程",
    "无缝": "顺畅",
    "发挥着重要作用": "很有用",
    "发挥着至关重要的作用": "很重要",
    "数字化转型": "数字化",
    "必然趋势": "趋势",
    "积极拥抱新技术": "用新技术",
    "不断提升": "提高",
    "持续优化": "改进",
    "实现": "做到",
    "进一步": "",
}

EN_OPENERS = ["Moreover,", "Furthermore,", "Additionally,", "In conclusion,"]

EN_EXTRA = {
    "very": "",
    "really": "",
    "extremely": "",
    "highly": "",
}

CONTRACTIONS = {
    "it is": "it's",
    "it will": "it'll",
    "we are": "we're",
    "we will": "we'll",
    "you are": "you're",
    "you will": "you'll",
    "do not": "don't",
    "does not": "doesn't",
    "cannot": "can't",
    "I am": "I'm",
    "I will": "I'll",
    "that is": "that's",
    "there is": "there's",
    "will not": "won't",
    "it is not": "it isn't",
}

ZH_PARTICLES_END = ["了", "吧", "呢"]
ZH_PARTICLES_START = ["其实", "说实话", "我总觉得"]

# --------------------------------------------------------------------------
# Core rule application (single pass, deterministic).
# --------------------------------------------------------------------------


def humanize_rules(text: str, lang: str = "auto") -> str:
    """Apply conservative editing rules to a draft. Deterministic."""
    protected_text, protected = protect(text, lang)

    if lang in ("auto", "en"):
        low = protected_text.lower()
        for phrase, replacement in EN_PHRASES.items():
            protected_text = re.sub(re.escape(phrase), replacement, protected_text, flags=re.I)
        # Only apply opener removals when they start a sentence boundary.
        for opener in EN_OPENERS:
            protected_text = re.sub(
                re.escape(opener) + r"\s*", "", protected_text, flags=re.I
            )
        for word, replacement in EN_EXTRA.items():
            protected_text = re.sub(r"\b" + re.escape(word) + r"\s+", "", protected_text, flags=re.I)
        for phrase, replacement in CONTRACTIONS.items():
            protected_text = re.sub(
                r"\b" + re.escape(phrase) + r"\b", replacement, protected_text, flags=re.I
            )

    if lang in ("auto", "zh"):
        for phrase, replacement in ZH_PHRASES.items():
            protected_text = protected_text.replace(phrase, replacement)
        protected_text = re.sub(r",{2,}", ",", protected_text)

    protected_text = re.sub(r"[ \t]{2,}", " ", protected_text)
    protected_text = re.sub(r"\s+([,.;:!?,。;:!?])", r"\1", protected_text)
    protected_text = re.sub(r"[,;]\s*\.", ".", protected_text)
    protected_text = re.sub(r"\.\s*[,;]", ".", protected_text)
    protected_text = re.sub(r"[,,]\s*(?=[。!?.!?])", "", protected_text)
    protected_text = re.sub(r"\.\s+([a-z])", lambda m: ". " + m.group(1).upper(), protected_text)
    protected_text = re.sub(r"([.!?。!?])\s*\1+", r"\1", protected_text)
    protected_text = re.sub(r"\n{3,}", "\n\n", protected_text).strip(" ,,")
    return restore(protected_text, protected)


def zh_add_colloquial(text: str) -> str:
    """Add light colloquial markers (used for data augmentation only)."""
    if re.search(r"[\u4e00-\u9fff]", text):
        text = re.sub(r"[。!?]", lambda m: m.group(0) + "呢", text, count=1)
        text = re.sub(r"^", "其实,", text, count=1)
        text = text.replace("今天", "今天吧")
        text = text.replace("我觉得", "我总觉得")
    return text


def en_add_colloquial(text: str) -> str:
    """Add light colloquial markers (used for data augmentation only)."""
    text = re.sub(r"(^|\. )(I|we|he|she|they)\b", r"\1Honestly, \2", text, count=1)
    text = text.replace("The ", "The ", 1)
    if " quite " not in text:
        text = re.sub(r"(\w+)\.$", r"\1, honestly.", text, count=1)
    return text