leilaghomashchi commited on
Commit
21b2872
·
verified ·
1 Parent(s): 26ca0c1

Upload text_anonymizer (2).py

Browse files
Files changed (1) hide show
  1. text_anonymizer (2).py +1057 -0
text_anonymizer (2).py ADDED
@@ -0,0 +1,1057 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import re
4
+ from collections import defaultdict
5
+ import io
6
+
7
+ class TextAnonymizer:
8
+ def __init__(self):
9
+ self.person_counter = 0
10
+ self.company_counter = 0
11
+ self.amount_counter = 0
12
+ self.percent_counter = 0
13
+
14
+ # دیکشنری برای نگه‌داری تبدیل‌ها
15
+ self.person_mapping = {}
16
+ self.company_mapping = {}
17
+ self.amount_mapping = {}
18
+ self.percent_mapping = {}
19
+
20
+ def reset_counters(self):
21
+ """بازنشانی شمارنده‌ها برای پردازش جدید"""
22
+ self.person_counter = 0
23
+ self.company_counter = 0
24
+ self.amount_counter = 0
25
+ self.percent_counter = 0
26
+ self.person_mapping.clear()
27
+ self.company_mapping.clear()
28
+ self.amount_mapping.clear()
29
+ self.percent_mapping.clear()
30
+
31
+ def detect_financial_amounts(self, text):
32
+ """تشخیص مبالغ مالی (فارسی و انگلیسی)"""
33
+ patterns = [
34
+ # الگوهای انگلیسی
35
+ r'\$[\d,]+(?:\.\d{2})?', # $1,000.00
36
+ r'[\d,]+\s*(?:dollars?|USD|usd|Dollars?)', # 1000 dollars
37
+ r'[\d,]+\s*(?:million|billion|thousand|Million|Billion|Thousand)', # 1 million
38
+ r'[\d,]+(?:\.\d+)?\s*(?:M|B|K|m|b|k)', # 1.5M, 2B, 500K
39
+ r'€[\d,]+(?:\.\d{2})?', # €1,000.00
40
+ r'£[\d,]+(?:\.\d{2})?', # £1,000.00
41
+
42
+ # الگوهای فارسی - ارقام فارسی
43
+ r'[\u06F0-\u06F9\u06F۰-\u06F۹\d,\u060C]+\s*(?:هزار|میلیون|میلیارد|تریلیون)\s*(?:و\s*[\u06F0-\u06F9\u06F۰-\u06F۹\d,\u060C]+\s*(?:هزار|میلیون|میلیارد)?)?\s*(?:تومان|ریال|دلار|یورو|درهم)',
44
+ r'[\u06F0-\u06F9\u06F۰-\u06F۹\d,\u060C]+\s*(?:همت)', # ۳۷ همت
45
+ r'[\u06F0-\u06F9\u06F۰-\u06F۹\d,\u060C]+\s*(?:هزار|میلیون|میلیارد)\s*(?:تومان|ریال|دلار)',
46
+
47
+ # الگوهای ترکیبی
48
+ r'[\u06F0-\u06F9\u06F۰-\u06F۹\d]+[\u06F0-\u06F9\u06F۰-\u06F۹\d,\u060C]*\s*(?:هزار|میلیون|میلیارد)',
49
+ r'بیش\s*از\s*[\u06F0-\u06F9\u06F۰-\u06F۹\d,\u060C]+\s*(?:هزار|میلیون|میلیارد|همت)',
50
+ r'حدود\s*[\u06F0-\u06F9\u06F۰-\u06F۹\d,\u060C]+\s*(?:هزار|میلیون|میلیارد|همت)',
51
+ r'نزدیک\s*به\s*[\u06F0-\u06F9\u06F۰-\u06F۹\d,\u060C]+\s*(?:هزار|میلیون|میلیارد|همت)',
52
+ ]
53
+
54
+ amounts = []
55
+ for pattern in patterns:
56
+ matches = re.finditer(pattern, text, re.IGNORECASE)
57
+ for match in matches:
58
+ amounts.append((match.start(), match.end(), match.group()))
59
+
60
+ return amounts
61
+
62
+ def detect_percentages(self, text):
63
+ """تشخیص درصدها (فارسی و انگلیسی)"""
64
+ patterns = [
65
+ r'\d+(?:\.\d+)?%', # انگلیسی: 15%, 18.5%
66
+ r'[\u06F0-\u06F9\u06F۰-\u06F۹]+(?:\.[\u06F0-\u06F9\u06F۰-\u06F۹]+)?\s*درصد', # فارسی: ۱۵ درصد
67
+ r'[\u06F0-\u06F9\u06F۰-\u06F۹]+(?:\.[\u06F0-\u06F9\u06F۰-\u06F۹]+)?%', # ترکیبی: ۱۵%
68
+ ]
69
+ percentages = []
70
+ for pattern in patterns:
71
+ matches = re.finditer(pattern, text)
72
+ for match in matches:
73
+ percentages.append((match.start(), match.end(), match.group()))
74
+
75
+ return percentages
76
+
77
+ def detect_names_regex(self, text):
78
+ """تشخیص اسامی اشخاص (فارسی و انگلیسی)"""
79
+ patterns = [
80
+ # الگوهای انگلیسی
81
+ r'\b[A-Z][a-z]+ [A-Z][a-z]+\b', # John Smith
82
+ r'\b[A-Z][a-z]+ [A-Z]\. [A-Z][a-z]+\b', # John M. Smith
83
+ r'\b[A-Z][a-z]+ [A-Z][a-z]+ [A-Z][a-z]+\b', # John Michael Smith
84
+ r'\bMr\. [A-Z][a-z]+\b', # Mr. Smith
85
+ r'\bMs\. [A-Z][a-z]+\b', # Ms. Johnson
86
+ r'\bDr\. [A-Z][a-z]+\b', # Dr. Brown
87
+
88
+ # الگوهای فارسی - نام‌های کامل
89
+ r'[\u0600-\u06FF]+\s+[\u0600-\u06FF]+(?:\s+[\u0600-\u06FF]+)?(?:\s+[\u0600-\u06FF]+)?', # محمد ایروانی، جواد زارع‌پور
90
+
91
+ # الگوهای خاص فارسی با عناوین
92
+ r'(?:دکتر|آقای|خانم|مهندس|استاد)\s+[\u0600-\u06FF]+(?:\s+[\u0600-\u06FF]+){1,3}',
93
+
94
+ # الگوی مدیرعامل و سمت‌ها
95
+ r'[\u0600-\u06FF]+(?:\s+[\u0600-\u06FF]+){1,3}،\s*مدیرعامل',
96
+ r'[\u0600-\u06FF]+(?:\s+[\u0600-\u06FF]+){1,3}،\s*(?:مدیر|رئیس|نایب)',
97
+
98
+ # نام‌های بین عبارت‌ها
99
+ r'(?:با\s+(?:حضور|سکانداری)\s+)[\u0600-\u06FF]+(?:\s+[\u0600-\u06FF]+){1,3}',
100
+ r'(?:امضای\s+(?:مشترک\s+)?(?:«)?)[\u0600-\u06FF]+(?:\s+[\u0600-\u06FF]+){1,3}(?:»)?',
101
+ ]
102
+
103
+ names = []
104
+ processed_spans = set() # برای جلوگیری از تداخل
105
+
106
+ for pattern in patterns:
107
+ matches = re.finditer(pattern, text)
108
+ for match in matches:
109
+ start, end = match.start(), match.end()
110
+ name = match.group().strip()
111
+
112
+ # پاک کردن عناوین و کلمات اضافی
113
+ name = re.sub(r'^(?:دکتر|آقای|خانم|مهندس|استاد)\s+', '', name)
114
+ name = re.sub(r'،\s*(?:مدیرعامل|مدیر|رئیس|نایب).*
115
+
116
+ def detect_companies_regex(self, text):
117
+ """تشخیص شرکت‌ها (فارسی و انگلیسی)"""
118
+ # الگوهای عمومی انگلیسی
119
+ general_patterns = [
120
+ r'\b[A-Z][a-z]+ (?:Inc|Corp|LLC|Ltd|Company|Co|Corporation|Group|Technologies|Tech|Systems|Solutions|Services|International|Global|Enterprises)\.?\b',
121
+ r'\b[A-Z][A-Z]+ (?:Inc|Corp|LLC|Ltd|Company|Co|Corporation)\.?\b', # مثل IBM Corp
122
+ r'\b[A-Z][a-z]+ [A-Z][a-z]+ (?:Inc|Corp|LLC|Ltd|Company|Co|Corporation)\.?\b', # مثل Apple Inc
123
+ ]
124
+
125
+ # شرکت‌های مشهور انگلیسی
126
+ tech_companies = r'\b(?:Apple|Microsoft|Google|Amazon|Facebook|Meta|Netflix|Tesla|Oracle|IBM|Intel|Cisco|Adobe|Salesforce|PayPal|Uber|Airbnb|Twitter|LinkedIn|NVIDIA|AMD|Zoom|Slack|Dropbox|Spotify)\b'
127
+ auto_companies = r'\b(?:Toyota|Honda|Ford|BMW|Mercedes|Audi|Volkswagen|Nissan|Hyundai|Kia|Mazda|Subaru|Volvo|Porsche|Ferrari|Lamborghini)\b'
128
+ finance_companies = r'\b(?:JPMorgan|Goldman Sachs|Morgan Stanley|Bank of America|Wells Fargo|Chase|Citibank|American Express|Visa|Mastercard|PayPal)\b'
129
+ retail_companies = r'\b(?:Walmart|Target|Costco|Amazon|eBay|Alibaba|Nike|Adidas|Zara|H&M|IKEA|Starbucks|McDonalds|KFC|Subway)\b'
130
+
131
+ # الگوهای فارسی - شرکت‌ها و سازمان‌ها
132
+ persian_company_patterns = [
133
+ # الگوی کلی شرکت
134
+ r'شرکت\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
135
+ r'گروه\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
136
+ r'هلدینگ\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
137
+ r'بانک\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
138
+ r'بیمه\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
139
+ r'پتروشیمی\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
140
+ r'صنایع\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
141
+ r'فولاد\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
142
+ r'سازمان\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
143
+ r'موسسه\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
144
+
145
+ # شرکت‌های خاص از نمونه‌ها
146
+ r'ایران\s*خودرو',
147
+ r'همراه\s*اول',
148
+ r'فولاد\s*مبارکه(?:\s+اصفهان)?',
149
+ r'بانک\s+(?:ملت|پاسارگاد|سرمایه|مرکزی|کشاورزی)',
150
+ r'بیمه\s+(?:پارسیان|سامان)',
151
+ r'پتروشیمی\s+(?:پارس|بوعلی\s*سینا|اروند)',
152
+
153
+ # الگوی با مخفف
154
+ r'[\u0600-\u06FF\s]+\s*\([\u0600-\u06FF\s]+\)', # مثل تأمین (تیپیکو)
155
+
156
+ # الگوی نام‌های مرکب
157
+ r'[\u0600-\u06FF]+(?:\s+[\u0600-\u06FF]+){1,4}(?:\s+(?:شرکت|گروه|بانک|بیمه|صنایع))?',
158
+ ]
159
+
160
+ # ترکیب تمام الگوها
161
+ all_patterns = general_patterns + [
162
+ tech_companies,
163
+ auto_companies,
164
+ finance_companies,
165
+ retail_companies
166
+ ] + persian_company_patterns
167
+
168
+ companies = []
169
+ processed_spans = set() # برای جلوگیری از تداخل
170
+
171
+ for pattern in all_patterns:
172
+ matches = re.finditer(pattern, text, re.IGNORECASE)
173
+ for match in matches:
174
+ start, end = match.start(), match.end()
175
+ company = match.group().strip()
176
+
177
+ # فیلتر کردن نتایج خیلی کوتاه یا طولانی
178
+ if (len(company) > 2 and len(company) < 100 and
179
+ not any(start < existing_end and end > existing_start
180
+ for existing_start, existing_end in processed_spans)):
181
+
182
+ companies.append((start, end, company))
183
+ processed_spans.add((start, end))
184
+
185
+ return companies
186
+
187
+ def anonymize_text(self, text):
188
+ """ناشناس‌سازی متن با regex"""
189
+ if not text or pd.isna(text):
190
+ return text
191
+
192
+ replacements = []
193
+
194
+ # تشخیص اسامی اشخاص
195
+ names = self.detect_names_regex(text)
196
+ for start, end, name in names:
197
+ if name not in self.person_mapping:
198
+ self.person_counter += 1
199
+ self.person_mapping[name] = f"person-{self.person_counter:02d}"
200
+ replacements.append((start, end, self.person_mapping[name]))
201
+
202
+ # تشخیص شرکت‌ها
203
+ companies = self.detect_companies_regex(text)
204
+ for start, end, company in companies:
205
+ if company not in self.company_mapping:
206
+ self.company_counter += 1
207
+ self.company_mapping[company] = f"company-{self.company_counter:02d}"
208
+ replacements.append((start, end, self.company_mapping[company]))
209
+
210
+ # تشخیص مبالغ مالی
211
+ amounts = self.detect_financial_amounts(text)
212
+ for start, end, amount in amounts:
213
+ if amount not in self.amount_mapping:
214
+ self.amount_counter += 1
215
+ self.amount_mapping[amount] = f"amount-{self.amount_counter:02d}"
216
+ replacements.append((start, end, self.amount_mapping[amount]))
217
+
218
+ # تشخیص درصدها
219
+ percentages = self.detect_percentages(text)
220
+ for start, end, percent in percentages:
221
+ if percent not in self.percent_mapping:
222
+ self.percent_counter += 1
223
+ self.percent_mapping[percent] = f"percent-{self.percent_counter:02d}"
224
+ replacements.append((start, end, self.percent_mapping[percent]))
225
+
226
+ # حذف تداخل‌ها و مرتب‌سازی
227
+ replacements = self.remove_overlaps(replacements)
228
+ replacements.sort(key=lambda x: x[0], reverse=True)
229
+
230
+ # اعمال جایگزینی‌ها
231
+ result = text
232
+ for start, end, replacement in replacements:
233
+ result = result[:start] + replacement + result[end:]
234
+
235
+ return result
236
+
237
+ def remove_overlaps(self, replacements):
238
+ """حذف تداخل‌ها در جایگزینی‌ها"""
239
+ if not replacements:
240
+ return []
241
+
242
+ # مرتب‌سازی بر اساس موقعیت شروع
243
+ replacements.sort(key=lambda x: x[0])
244
+
245
+ filtered = []
246
+ for start, end, replacement in replacements:
247
+ # بررسی تداخل با آخرین جایگزینی اضافه شده
248
+ if not filtered or start >= filtered[-1][1]:
249
+ filtered.append((start, end, replacement))
250
+
251
+ return filtered
252
+
253
+ def get_mapping_summary(self):
254
+ """خلاصه‌ای از تبدیل‌های انجام شده"""
255
+ summary = []
256
+
257
+ if self.person_mapping:
258
+ summary.append("**اسامی اشخاص:**")
259
+ for original, anonymized in self.person_mapping.items():
260
+ summary.append(f"- {original} → {anonymized}")
261
+ summary.append("")
262
+
263
+ if self.company_mapping:
264
+ summary.append("**نام شرکت‌ها:**")
265
+ for original, anonymized in self.company_mapping.items():
266
+ summary.append(f"- {original} → {anonymized}")
267
+ summary.append("")
268
+
269
+ if self.amount_mapping:
270
+ summary.append("**مبالغ مالی:**")
271
+ for original, anonymized in self.amount_mapping.items():
272
+ summary.append(f"- {original} → {anonymized}")
273
+ summary.append("")
274
+
275
+ if self.percent_mapping:
276
+ summary.append("**درصدها:**")
277
+ for original, anonymized in self.percent_mapping.items():
278
+ summary.append(f"- {original} → {anonymized}")
279
+
280
+ return "\n".join(summary) if summary else "هیچ موجودیت حساسی یافت نشد."
281
+
282
+ # ایجاد نمونه از کلاس ناشناس‌ساز
283
+ anonymizer = TextAnonymizer()
284
+
285
+ def process_csv(file):
286
+ """پردازش فایل CSV"""
287
+ try:
288
+ # بازنشانی شمارنده‌ها
289
+ anonymizer.reset_counters()
290
+
291
+ # بررسی فایل
292
+ if file is None:
293
+ return None, "لطفاً فایل CSV آپلود کنید.", "", None
294
+
295
+ # خواندن فایل CSV
296
+ if file.name.endswith('.csv'):
297
+ df = pd.read_csv(file.name)
298
+ else:
299
+ return None, "لطفاً فایل CSV آپلود کنید.", "", None
300
+
301
+ # بررسی وجود ستون original_text
302
+ if 'original_text' not in df.columns:
303
+ available_columns = ', '.join(df.columns.tolist())
304
+ return None, f"ستون 'original_text' در فایل یافت نشد. ستون‌های موجود: {available_columns}", "", None
305
+
306
+ # ایجاد کپی از دیتافریم
307
+ result_df = df.copy()
308
+
309
+ # ناشناس‌سازی متن‌های ستون original_text
310
+ result_df['anonymized_text'] = df['original_text'].apply(anonymizer.anonymize_text)
311
+
312
+ # تبدیل به CSV برای دانلود
313
+ output = io.StringIO()
314
+ result_df.to_csv(output, index=False, encoding='utf-8')
315
+ csv_content = output.getvalue()
316
+
317
+ # ایجاد فایل CSV برای دانلود
318
+ output_file = "anonymized_data.csv"
319
+ with open(output_file, 'w', encoding='utf-8') as f:
320
+ f.write(csv_content)
321
+
322
+ # نمایش نمونه از نتایج
323
+ sample_df = result_df[['original_text', 'anonymized_text']].head(10)
324
+
325
+ # خلاصه تبدیل‌ها
326
+ mapping_summary = anonymizer.get_mapping_summary()
327
+
328
+ return output_file, f"✅ پردازش کامل شد! {len(df)} ردیف پردازش شد.", mapping_summary, sample_df
329
+
330
+ except Exception as e:
331
+ return None, f"❌ خطا در پردازش فایل: {str(e)}", "", None
332
+
333
+ def process_single_text(text):
334
+ """پردازش تک متن"""
335
+ if not text.strip():
336
+ return "", "لطفاً متنی وارد کنید."
337
+
338
+ anonymizer.reset_counters()
339
+ anonymized = anonymizer.anonymize_text(text)
340
+ mapping_summary = anonymizer.get_mapping_summary()
341
+
342
+ return anonymized, mapping_summary
343
+
344
+ # ایجاد رابط کاربری Gradio
345
+ with gr.Blocks(title="ناشناس‌سازی متن", theme=gr.themes.Soft()) as demo:
346
+ gr.Markdown("""
347
+ # 🔒 برنامه ناشناس‌سازی متن (نسخه Regex)
348
+
349
+ ⚡ **وضعیت:** حالت سریع - بدون نیاز به spaCy
350
+
351
+ این برنامه متن‌های شما را ناشناس می‌کند و اطلاعات حساس زیر را جایگزین می‌کند:
352
+ - 👤 **اسامی اشخاص** → person-01, person-02, ...
353
+ - 🏢 **نام شرکت‌ها** → company-01, company-02, ...
354
+ - 💰 **مبالغ مالی** → amount-01, amount-02, ...
355
+ - 📊 **درصدها** → percent-01, percent-02, ...
356
+
357
+ **نسخه ۱:** آدرس‌ها، مکان‌ها و تاریخ‌ها ناشناس‌سازی نمی‌شوند.
358
+ """)
359
+
360
+ with gr.Tabs():
361
+ # تب پردازش فایل CSV
362
+ with gr.TabItem("📁 پردازش فایل CSV"):
363
+ gr.Markdown("### آپلود فایل CSV با ستون 'original_text'")
364
+
365
+ with gr.Row():
366
+ with gr.Column():
367
+ file_input = gr.File(
368
+ label="فایل CSV را انتخاب کنید",
369
+ file_types=[".csv"],
370
+ type="filepath"
371
+ )
372
+ process_btn = gr.Button("🚀 شروع پردازش", variant="primary")
373
+
374
+ with gr.Column():
375
+ status_output = gr.Textbox(
376
+ label="وضعیت",
377
+ interactive=False
378
+ )
379
+ download_file = gr.File(
380
+ label="دانلود فایل ناشناس‌سازی شده",
381
+ interactive=False
382
+ )
383
+
384
+ with gr.Row():
385
+ with gr.Column():
386
+ mapping_output = gr.Markdown(
387
+ label="خلاصه تبدیل‌ها",
388
+ value="خلاصه تبدیل‌ها اینجا نمایش داده می‌شود..."
389
+ )
390
+
391
+ with gr.Column():
392
+ sample_output = gr.Dataframe(
393
+ label="نمونه نتایج (۱۰ ردیف اول)",
394
+ interactive=False
395
+ )
396
+
397
+ # تب تست تک متن
398
+ with gr.TabItem("📝 تست تک متن"):
399
+ gr.Markdown("### تست ناشناس‌سازی روی یک متن")
400
+
401
+ with gr.Row():
402
+ with gr.Column():
403
+ text_input = gr.Textbox(
404
+ label="متن اصلی",
405
+ placeholder="متن خود را اینجا وارد کنید...",
406
+ lines=5
407
+ )
408
+ test_btn = gr.Button("🔍 ناشناس‌سازی", variant="primary")
409
+
410
+ with gr.Column():
411
+ text_output = gr.Textbox(
412
+ label="متن ناشناس‌سازی شده",
413
+ lines=5,
414
+ interactive=False
415
+ )
416
+ text_mapping = gr.Markdown(
417
+ label="تبدیل‌های انجام شده"
418
+ )
419
+
420
+ # اتصال رویدادها
421
+ process_btn.click(
422
+ fn=process_csv,
423
+ inputs=[file_input],
424
+ outputs=[download_file, status_output, mapping_output, sample_output]
425
+ )
426
+
427
+ test_btn.click(
428
+ fn=process_single_text,
429
+ inputs=[text_input],
430
+ outputs=[text_output, text_mapping]
431
+ )
432
+
433
+ # مثال در بخش تست
434
+ gr.Examples(
435
+ examples=[
436
+ ["John Smith works at Microsoft and earned $50,000 with a 15% bonus."],
437
+ ["Sarah Johnson from Google Inc. reported revenues of $2.5 million, representing a 25% increase."],
438
+ ["The CEO of Apple, Tim Cook, announced profits of $1.2B with 18.5% growth rate."],
439
+ ["Dr. Michael Brown from IBM Corp. received €75,000 salary increase of 12%."],
440
+ ["Ms. Lisa Wilson at Amazon reported quarterly results of £500K with 8.7% margin."]
441
+ ],
442
+ inputs=[text_input],
443
+ label="نمونه متن‌ها"
444
+ )
445
+
446
+ # اجرای برنامه
447
+ if __name__ == "__main__":
448
+ demo.launch(), '', name)
449
+ name = re.sub(r'^(?:با\s+(?:حضور|سکانداری)\s+)', '', name)
450
+ name = re.sub(r'^(?:امضای\s+(?:مشترک\s+)?(?:«)?)', '', name)
451
+ name = re.sub(r'(?:»)?
452
+
453
+ def detect_companies_regex(self, text):
454
+ """تشخیص شرکت‌ها با regex"""
455
+ # الگوهای عمومی شرکت‌ها
456
+ general_patterns = [
457
+ r'\b[A-Z][a-z]+ (?:Inc|Corp|LLC|Ltd|Company|Co|Corporation|Group|Technologies|Tech|Systems|Solutions|Services|International|Global|Enterprises)\.?\b',
458
+ r'\b[A-Z][A-Z]+ (?:Inc|Corp|LLC|Ltd|Company|Co|Corporation)\.?\b', # مثل IBM Corp
459
+ r'\b[A-Z][a-z]+ [A-Z][a-z]+ (?:Inc|Corp|LLC|Ltd|Company|Co|Corporation)\.?\b', # مثل Apple Inc
460
+ ]
461
+
462
+ # شرکت‌های مشهور (تقسیم شده به گروه‌های کوچک‌تر)
463
+ tech_companies = r'\b(?:Apple|Microsoft|Google|Amazon|Facebook|Meta|Netflix|Tesla|Oracle|IBM|Intel|Cisco|Adobe|Salesforce|PayPal|Uber|Airbnb|Twitter|LinkedIn|NVIDIA|AMD|Zoom|Slack|Dropbox|Spotify)\b'
464
+
465
+ auto_companies = r'\b(?:Toyota|Honda|Ford|BMW|Mercedes|Audi|Volkswagen|Nissan|Hyundai|Kia|Mazda|Subaru|Volvo|Porsche|Ferrari|Lamborghini)\b'
466
+
467
+ finance_companies = r'\b(?:JPMorgan|Goldman Sachs|Morgan Stanley|Bank of America|Wells Fargo|Chase|Citibank|American Express|Visa|Mastercard|PayPal)\b'
468
+
469
+ retail_companies = r'\b(?:Walmart|Target|Costco|Amazon|eBay|Alibaba|Nike|Adidas|Zara|H&M|IKEA|Starbucks|McDonalds|KFC|Subway)\b'
470
+
471
+ # ترکیب تمام الگوها
472
+ company_patterns = general_patterns + [
473
+ tech_companies,
474
+ auto_companies,
475
+ finance_companies,
476
+ retail_companies
477
+ ]
478
+
479
+ companies = []
480
+ for pattern in company_patterns:
481
+ matches = re.finditer(pattern, text, re.IGNORECASE)
482
+ for match in matches:
483
+ companies.append((match.start(), match.end(), match.group()))
484
+
485
+ return companies
486
+
487
+ def anonymize_text(self, text):
488
+ """ناشناس‌سازی متن با regex"""
489
+ if not text or pd.isna(text):
490
+ return text
491
+
492
+ replacements = []
493
+
494
+ # تشخیص اسامی اشخاص
495
+ names = self.detect_names_regex(text)
496
+ for start, end, name in names:
497
+ if name not in self.person_mapping:
498
+ self.person_counter += 1
499
+ self.person_mapping[name] = f"person-{self.person_counter:02d}"
500
+ replacements.append((start, end, self.person_mapping[name]))
501
+
502
+ # تشخیص شرکت‌ها
503
+ companies = self.detect_companies_regex(text)
504
+ for start, end, company in companies:
505
+ if company not in self.company_mapping:
506
+ self.company_counter += 1
507
+ self.company_mapping[company] = f"company-{self.company_counter:02d}"
508
+ replacements.append((start, end, self.company_mapping[company]))
509
+
510
+ # تشخیص مبالغ مالی
511
+ amounts = self.detect_financial_amounts(text)
512
+ for start, end, amount in amounts:
513
+ if amount not in self.amount_mapping:
514
+ self.amount_counter += 1
515
+ self.amount_mapping[amount] = f"amount-{self.amount_counter:02d}"
516
+ replacements.append((start, end, self.amount_mapping[amount]))
517
+
518
+ # تشخیص درصدها
519
+ percentages = self.detect_percentages(text)
520
+ for start, end, percent in percentages:
521
+ if percent not in self.percent_mapping:
522
+ self.percent_counter += 1
523
+ self.percent_mapping[percent] = f"percent-{self.percent_counter:02d}"
524
+ replacements.append((start, end, self.percent_mapping[percent]))
525
+
526
+ # حذف تداخل‌ها و مرتب‌سازی
527
+ replacements = self.remove_overlaps(replacements)
528
+ replacements.sort(key=lambda x: x[0], reverse=True)
529
+
530
+ # اعمال جایگزینی‌ها
531
+ result = text
532
+ for start, end, replacement in replacements:
533
+ result = result[:start] + replacement + result[end:]
534
+
535
+ return result
536
+
537
+ def remove_overlaps(self, replacements):
538
+ """حذف تداخل‌ها در جایگزینی‌ها"""
539
+ if not replacements:
540
+ return []
541
+
542
+ # مرتب‌سازی بر اساس موقعیت شروع
543
+ replacements.sort(key=lambda x: x[0])
544
+
545
+ filtered = []
546
+ for start, end, replacement in replacements:
547
+ # بررسی تداخل با آخرین جایگزینی اضافه شده
548
+ if not filtered or start >= filtered[-1][1]:
549
+ filtered.append((start, end, replacement))
550
+
551
+ return filtered
552
+
553
+ def get_mapping_summary(self):
554
+ """خلاصه‌ای از تبدیل‌های انجام شده"""
555
+ summary = []
556
+
557
+ if self.person_mapping:
558
+ summary.append("**اسامی اشخاص:**")
559
+ for original, anonymized in self.person_mapping.items():
560
+ summary.append(f"- {original} → {anonymized}")
561
+ summary.append("")
562
+
563
+ if self.company_mapping:
564
+ summary.append("**نام شرکت‌ها:**")
565
+ for original, anonymized in self.company_mapping.items():
566
+ summary.append(f"- {original} → {anonymized}")
567
+ summary.append("")
568
+
569
+ if self.amount_mapping:
570
+ summary.append("**مبالغ مالی:**")
571
+ for original, anonymized in self.amount_mapping.items():
572
+ summary.append(f"- {original} → {anonymized}")
573
+ summary.append("")
574
+
575
+ if self.percent_mapping:
576
+ summary.append("**درصدها:**")
577
+ for original, anonymized in self.percent_mapping.items():
578
+ summary.append(f"- {original} → {anonymized}")
579
+
580
+ return "\n".join(summary) if summary else "هیچ موجودیت حساسی یافت نشد."
581
+
582
+ # ایجاد نمونه از کلاس ناشناس‌ساز
583
+ anonymizer = TextAnonymizer()
584
+
585
+ def process_csv(file):
586
+ """پردازش فایل CSV"""
587
+ try:
588
+ # بازنشانی شمارنده‌ها
589
+ anonymizer.reset_counters()
590
+
591
+ # بررسی فایل
592
+ if file is None:
593
+ return None, "لطفاً فایل CSV آپلود کنید.", "", None
594
+
595
+ # خواندن فایل CSV
596
+ if file.name.endswith('.csv'):
597
+ df = pd.read_csv(file.name)
598
+ else:
599
+ return None, "لطفاً فایل CSV آپلود کنید.", "", None
600
+
601
+ # بررسی وجود ستون original_text
602
+ if 'original_text' not in df.columns:
603
+ available_columns = ', '.join(df.columns.tolist())
604
+ return None, f"ستون 'original_text' در فایل یافت نشد. ستون‌های موجود: {available_columns}", "", None
605
+
606
+ # ایجاد کپی از دیتافریم
607
+ result_df = df.copy()
608
+
609
+ # ناشناس‌سازی متن‌های ستون original_text
610
+ result_df['anonymized_text'] = df['original_text'].apply(anonymizer.anonymize_text)
611
+
612
+ # تبدیل به CSV برای دانلود
613
+ output = io.StringIO()
614
+ result_df.to_csv(output, index=False, encoding='utf-8')
615
+ csv_content = output.getvalue()
616
+
617
+ # ایجاد فایل CSV برای دانلود
618
+ output_file = "anonymized_data.csv"
619
+ with open(output_file, 'w', encoding='utf-8') as f:
620
+ f.write(csv_content)
621
+
622
+ # نمایش نمونه از نتایج
623
+ sample_df = result_df[['original_text', 'anonymized_text']].head(10)
624
+
625
+ # خلاصه تبدیل‌ها
626
+ mapping_summary = anonymizer.get_mapping_summary()
627
+
628
+ return output_file, f"✅ پردازش کامل شد! {len(df)} ردیف پردازش شد.", mapping_summary, sample_df
629
+
630
+ except Exception as e:
631
+ return None, f"❌ خطا در پردازش فایل: {str(e)}", "", None
632
+
633
+ def process_single_text(text):
634
+ """پردازش تک متن"""
635
+ if not text.strip():
636
+ return "", "لطفاً متنی وارد کنید."
637
+
638
+ anonymizer.reset_counters()
639
+ anonymized = anonymizer.anonymize_text(text)
640
+ mapping_summary = anonymizer.get_mapping_summary()
641
+
642
+ return anonymized, mapping_summary
643
+
644
+ # ایجاد رابط کاربری Gradio
645
+ with gr.Blocks(title="ناشناس‌سازی متن", theme=gr.themes.Soft()) as demo:
646
+ gr.Markdown("""
647
+ # 🔒 برنامه ناشناس‌سازی متن (نسخه Regex)
648
+
649
+ ⚡ **وضعیت:** حالت سریع - بدون نیاز به spaCy
650
+
651
+ این برنامه متن‌های شما را ناشناس می‌کند و اطلاعات حساس زیر را جایگزین می‌کند:
652
+ - 👤 **اسامی اشخاص** → person-01, person-02, ...
653
+ - 🏢 **نام شرکت‌ها** → company-01, company-02, ...
654
+ - 💰 **مبالغ مالی** → amount-01, amount-02, ...
655
+ - 📊 **درصدها** → percent-01, percent-02, ...
656
+
657
+ **نسخه ۱:** آدرس‌ها، مکان‌ها و تاریخ‌ها ناشناس‌سازی نمی‌شوند.
658
+ """)
659
+
660
+ with gr.Tabs():
661
+ # تب پردازش فایل CSV
662
+ with gr.TabItem("📁 پردازش فایل CSV"):
663
+ gr.Markdown("### آپلود فایل CSV با ستون 'original_text'")
664
+
665
+ with gr.Row():
666
+ with gr.Column():
667
+ file_input = gr.File(
668
+ label="فایل CSV را انتخاب کنید",
669
+ file_types=[".csv"],
670
+ type="filepath"
671
+ )
672
+ process_btn = gr.Button("🚀 شروع پردازش", variant="primary")
673
+
674
+ with gr.Column():
675
+ status_output = gr.Textbox(
676
+ label="وضعیت",
677
+ interactive=False
678
+ )
679
+ download_file = gr.File(
680
+ label="دانلود فایل ناشناس‌سازی شده",
681
+ interactive=False
682
+ )
683
+
684
+ with gr.Row():
685
+ with gr.Column():
686
+ mapping_output = gr.Markdown(
687
+ label="خلاصه تبدیل‌ها",
688
+ value="خلاصه تبدیل‌ها اینجا نمایش داده می‌شود..."
689
+ )
690
+
691
+ with gr.Column():
692
+ sample_output = gr.Dataframe(
693
+ label="نمونه نتایج (۱۰ ردیف اول)",
694
+ interactive=False
695
+ )
696
+
697
+ # تب تست تک متن
698
+ with gr.TabItem("📝 تست تک متن"):
699
+ gr.Markdown("### تست ناشناس‌سازی روی یک متن")
700
+
701
+ with gr.Row():
702
+ with gr.Column():
703
+ text_input = gr.Textbox(
704
+ label="متن اصلی",
705
+ placeholder="متن خود را اینجا وارد کنید...",
706
+ lines=5
707
+ )
708
+ test_btn = gr.Button("🔍 ناشناس‌سازی", variant="primary")
709
+
710
+ with gr.Column():
711
+ text_output = gr.Textbox(
712
+ label="متن ناشناس‌سازی شده",
713
+ lines=5,
714
+ interactive=False
715
+ )
716
+ text_mapping = gr.Markdown(
717
+ label="تبدیل‌های انجام شده"
718
+ )
719
+
720
+ # اتصال رویدادها
721
+ process_btn.click(
722
+ fn=process_csv,
723
+ inputs=[file_input],
724
+ outputs=[download_file, status_output, mapping_output, sample_output]
725
+ )
726
+
727
+ test_btn.click(
728
+ fn=process_single_text,
729
+ inputs=[text_input],
730
+ outputs=[text_output, text_mapping]
731
+ )
732
+
733
+ # مثال در بخش تست
734
+ gr.Examples(
735
+ examples=[
736
+ ["John Smith works at Microsoft and earned $50,000 with a 15% bonus."],
737
+ ["Sarah Johnson from Google Inc. reported revenues of $2.5 million, representing a 25% increase."],
738
+ ["The CEO of Apple, Tim Cook, announced profits of $1.2B with 18.5% growth rate."],
739
+ ["Dr. Michael Brown from IBM Corp. received €75,000 salary increase of 12%."],
740
+ ["Ms. Lisa Wilson at Amazon reported quarterly results of £500K with 8.7% margin."]
741
+ ],
742
+ inputs=[text_input],
743
+ label="نمونه متن‌ها"
744
+ )
745
+
746
+ # اجرای برنامه
747
+ if __name__ == "__main__":
748
+ demo.launch(), '', name)
749
+ name = name.strip('،» ()')
750
+
751
+ # بررسی طول نام و عدم تداخل
752
+ if (len(name.split()) >= 2 and
753
+ len(name) > 3 and
754
+ not any(start < existing_end and end > existing_start
755
+ for existing_start, existing_end in processed_spans)):
756
+
757
+ names.append((start, end, name))
758
+ processed_spans.add((start, end))
759
+
760
+ return names
761
+
762
+ def detect_companies_regex(self, text):
763
+ """تشخیص شرکت‌ها با regex"""
764
+ # الگوهای عمومی شرکت‌ها
765
+ general_patterns = [
766
+ r'\b[A-Z][a-z]+ (?:Inc|Corp|LLC|Ltd|Company|Co|Corporation|Group|Technologies|Tech|Systems|Solutions|Services|International|Global|Enterprises)\.?\b',
767
+ r'\b[A-Z][A-Z]+ (?:Inc|Corp|LLC|Ltd|Company|Co|Corporation)\.?\b', # مثل IBM Corp
768
+ r'\b[A-Z][a-z]+ [A-Z][a-z]+ (?:Inc|Corp|LLC|Ltd|Company|Co|Corporation)\.?\b', # مثل Apple Inc
769
+ ]
770
+
771
+ # شرکت‌های مشهور (تقسیم شده به گروه‌های کوچک‌تر)
772
+ tech_companies = r'\b(?:Apple|Microsoft|Google|Amazon|Facebook|Meta|Netflix|Tesla|Oracle|IBM|Intel|Cisco|Adobe|Salesforce|PayPal|Uber|Airbnb|Twitter|LinkedIn|NVIDIA|AMD|Zoom|Slack|Dropbox|Spotify)\b'
773
+
774
+ auto_companies = r'\b(?:Toyota|Honda|Ford|BMW|Mercedes|Audi|Volkswagen|Nissan|Hyundai|Kia|Mazda|Subaru|Volvo|Porsche|Ferrari|Lamborghini)\b'
775
+
776
+ finance_companies = r'\b(?:JPMorgan|Goldman Sachs|Morgan Stanley|Bank of America|Wells Fargo|Chase|Citibank|American Express|Visa|Mastercard|PayPal)\b'
777
+
778
+ retail_companies = r'\b(?:Walmart|Target|Costco|Amazon|eBay|Alibaba|Nike|Adidas|Zara|H&M|IKEA|Starbucks|McDonalds|KFC|Subway)\b'
779
+
780
+ # ترکیب تمام الگوها
781
+ company_patterns = general_patterns + [
782
+ tech_companies,
783
+ auto_companies,
784
+ finance_companies,
785
+ retail_companies
786
+ ]
787
+
788
+ companies = []
789
+ for pattern in company_patterns:
790
+ matches = re.finditer(pattern, text, re.IGNORECASE)
791
+ for match in matches:
792
+ companies.append((match.start(), match.end(), match.group()))
793
+
794
+ return companies
795
+
796
+ def anonymize_text(self, text):
797
+ """ناشناس‌سازی متن با regex"""
798
+ if not text or pd.isna(text):
799
+ return text
800
+
801
+ replacements = []
802
+
803
+ # تشخیص اسامی اشخاص
804
+ names = self.detect_names_regex(text)
805
+ for start, end, name in names:
806
+ if name not in self.person_mapping:
807
+ self.person_counter += 1
808
+ self.person_mapping[name] = f"person-{self.person_counter:02d}"
809
+ replacements.append((start, end, self.person_mapping[name]))
810
+
811
+ # تشخیص شرکت‌ها
812
+ companies = self.detect_companies_regex(text)
813
+ for start, end, company in companies:
814
+ if company not in self.company_mapping:
815
+ self.company_counter += 1
816
+ self.company_mapping[company] = f"company-{self.company_counter:02d}"
817
+ replacements.append((start, end, self.company_mapping[company]))
818
+
819
+ # تشخیص مبالغ مالی
820
+ amounts = self.detect_financial_amounts(text)
821
+ for start, end, amount in amounts:
822
+ if amount not in self.amount_mapping:
823
+ self.amount_counter += 1
824
+ self.amount_mapping[amount] = f"amount-{self.amount_counter:02d}"
825
+ replacements.append((start, end, self.amount_mapping[amount]))
826
+
827
+ # تشخیص درصدها
828
+ percentages = self.detect_percentages(text)
829
+ for start, end, percent in percentages:
830
+ if percent not in self.percent_mapping:
831
+ self.percent_counter += 1
832
+ self.percent_mapping[percent] = f"percent-{self.percent_counter:02d}"
833
+ replacements.append((start, end, self.percent_mapping[percent]))
834
+
835
+ # حذف تداخل‌ها و مرتب‌سازی
836
+ replacements = self.remove_overlaps(replacements)
837
+ replacements.sort(key=lambda x: x[0], reverse=True)
838
+
839
+ # اعمال جایگزینی‌ها
840
+ result = text
841
+ for start, end, replacement in replacements:
842
+ result = result[:start] + replacement + result[end:]
843
+
844
+ return result
845
+
846
+ def remove_overlaps(self, replacements):
847
+ """حذف تداخل‌ها در جایگزینی‌ها"""
848
+ if not replacements:
849
+ return []
850
+
851
+ # مرتب‌سازی بر اساس موقعیت شروع
852
+ replacements.sort(key=lambda x: x[0])
853
+
854
+ filtered = []
855
+ for start, end, replacement in replacements:
856
+ # بررسی تداخل با آخرین جایگزینی اضافه شده
857
+ if not filtered or start >= filtered[-1][1]:
858
+ filtered.append((start, end, replacement))
859
+
860
+ return filtered
861
+
862
+ def get_mapping_summary(self):
863
+ """خلاصه‌ای از تبدیل‌های انجام شده"""
864
+ summary = []
865
+
866
+ if self.person_mapping:
867
+ summary.append("**اسامی اشخاص:**")
868
+ for original, anonymized in self.person_mapping.items():
869
+ summary.append(f"- {original} → {anonymized}")
870
+ summary.append("")
871
+
872
+ if self.company_mapping:
873
+ summary.append("**نام شرکت‌ها:**")
874
+ for original, anonymized in self.company_mapping.items():
875
+ summary.append(f"- {original} → {anonymized}")
876
+ summary.append("")
877
+
878
+ if self.amount_mapping:
879
+ summary.append("**مبالغ مالی:**")
880
+ for original, anonymized in self.amount_mapping.items():
881
+ summary.append(f"- {original} → {anonymized}")
882
+ summary.append("")
883
+
884
+ if self.percent_mapping:
885
+ summary.append("**درصدها:**")
886
+ for original, anonymized in self.percent_mapping.items():
887
+ summary.append(f"- {original} → {anonymized}")
888
+
889
+ return "\n".join(summary) if summary else "هیچ موجودی�� حساسی یافت نشد."
890
+
891
+ # ایجاد نمونه از کلاس ناشناس‌ساز
892
+ anonymizer = TextAnonymizer()
893
+
894
+ def process_csv(file):
895
+ """پردازش فایل CSV"""
896
+ try:
897
+ # بازنشانی شمارنده‌ها
898
+ anonymizer.reset_counters()
899
+
900
+ # بررسی فایل
901
+ if file is None:
902
+ return None, "لطفاً فایل CSV آپلود کنید.", "", None
903
+
904
+ # خواندن فایل CSV
905
+ if file.name.endswith('.csv'):
906
+ df = pd.read_csv(file.name)
907
+ else:
908
+ return None, "لطفاً فایل CSV آپلود کنید.", "", None
909
+
910
+ # بررسی وجود ستون original_text
911
+ if 'original_text' not in df.columns:
912
+ available_columns = ', '.join(df.columns.tolist())
913
+ return None, f"ستون 'original_text' در فایل یافت نشد. ستون‌های موجود: {available_columns}", "", None
914
+
915
+ # ایجاد کپی از دیتافریم
916
+ result_df = df.copy()
917
+
918
+ # ناشناس‌سازی متن‌های ستون original_text
919
+ result_df['anonymized_text'] = df['original_text'].apply(anonymizer.anonymize_text)
920
+
921
+ # تبدیل به CSV برای دانلود
922
+ output = io.StringIO()
923
+ result_df.to_csv(output, index=False, encoding='utf-8')
924
+ csv_content = output.getvalue()
925
+
926
+ # ایجاد فایل CSV برای دانلود
927
+ output_file = "anonymized_data.csv"
928
+ with open(output_file, 'w', encoding='utf-8') as f:
929
+ f.write(csv_content)
930
+
931
+ # نمایش نمونه از نتایج
932
+ sample_df = result_df[['original_text', 'anonymized_text']].head(10)
933
+
934
+ # خلاصه تبدیل‌ها
935
+ mapping_summary = anonymizer.get_mapping_summary()
936
+
937
+ return output_file, f"✅ پردازش کامل شد! {len(df)} ردیف پردازش شد.", mapping_summary, sample_df
938
+
939
+ except Exception as e:
940
+ return None, f"❌ خطا در پردازش فایل: {str(e)}", "", None
941
+
942
+ def process_single_text(text):
943
+ """پردازش تک متن"""
944
+ if not text.strip():
945
+ return "", "لطفاً متنی وارد کنید."
946
+
947
+ anonymizer.reset_counters()
948
+ anonymized = anonymizer.anonymize_text(text)
949
+ mapping_summary = anonymizer.get_mapping_summary()
950
+
951
+ return anonymized, mapping_summary
952
+
953
+ # ایجاد رابط کاربری Gradio
954
+ with gr.Blocks(title="ناشناس‌سازی متن", theme=gr.themes.Soft()) as demo:
955
+ gr.Markdown("""
956
+ # 🔒 برنامه ناشناس‌سازی متن (نسخه Regex)
957
+
958
+ ⚡ **وضعیت:** حالت سریع - بدون نیاز به spaCy
959
+
960
+ این برنامه متن‌های شما را ناشناس می‌کند و اطلاعات حساس زیر را جایگزین می‌کند:
961
+ - 👤 **اسامی اشخاص** → person-01, person-02, ...
962
+ - 🏢 **نام شرکت‌ها** → company-01, company-02, ...
963
+ - 💰 **مبالغ مالی** → amount-01, amount-02, ...
964
+ - 📊 **درصدها** → percent-01, percent-02, ...
965
+
966
+ **نسخه ۱:** آدرس‌ها، مکان‌ها و تاریخ‌ها ناشناس‌سازی نمی‌شوند.
967
+ """)
968
+
969
+ with gr.Tabs():
970
+ # تب پردازش فایل CSV
971
+ with gr.TabItem("📁 پردازش فایل CSV"):
972
+ gr.Markdown("### آپلود فایل CSV با ستون 'original_text'")
973
+
974
+ with gr.Row():
975
+ with gr.Column():
976
+ file_input = gr.File(
977
+ label="فایل CSV را انتخاب کنید",
978
+ file_types=[".csv"],
979
+ type="filepath"
980
+ )
981
+ process_btn = gr.Button("🚀 شروع پردازش", variant="primary")
982
+
983
+ with gr.Column():
984
+ status_output = gr.Textbox(
985
+ label="وضعیت",
986
+ interactive=False
987
+ )
988
+ download_file = gr.File(
989
+ label="دانلود فایل ناشناس‌سازی شده",
990
+ interactive=False
991
+ )
992
+
993
+ with gr.Row():
994
+ with gr.Column():
995
+ mapping_output = gr.Markdown(
996
+ label="خلاصه تبدیل‌ها",
997
+ value="خلاصه تبدیل‌ها اینجا نمایش داده می‌شود..."
998
+ )
999
+
1000
+ with gr.Column():
1001
+ sample_output = gr.Dataframe(
1002
+ label="نمونه نتایج (۱۰ ردیف اول)",
1003
+ interactive=False
1004
+ )
1005
+
1006
+ # تب تست تک متن
1007
+ with gr.TabItem("📝 تست تک متن"):
1008
+ gr.Markdown("### تست ناشناس‌سازی روی یک متن")
1009
+
1010
+ with gr.Row():
1011
+ with gr.Column():
1012
+ text_input = gr.Textbox(
1013
+ label="متن اصلی",
1014
+ placeholder="متن خود را اینجا وارد کنید...",
1015
+ lines=5
1016
+ )
1017
+ test_btn = gr.Button("🔍 ناشناس‌سازی", variant="primary")
1018
+
1019
+ with gr.Column():
1020
+ text_output = gr.Textbox(
1021
+ label="متن ناشناس‌سازی شده",
1022
+ lines=5,
1023
+ interactive=False
1024
+ )
1025
+ text_mapping = gr.Markdown(
1026
+ label="تبدیل‌های انجام شده"
1027
+ )
1028
+
1029
+ # اتصال رویدادها
1030
+ process_btn.click(
1031
+ fn=process_csv,
1032
+ inputs=[file_input],
1033
+ outputs=[download_file, status_output, mapping_output, sample_output]
1034
+ )
1035
+
1036
+ test_btn.click(
1037
+ fn=process_single_text,
1038
+ inputs=[text_input],
1039
+ outputs=[text_output, text_mapping]
1040
+ )
1041
+
1042
+ # مثال در بخش تست
1043
+ gr.Examples(
1044
+ examples=[
1045
+ ["John Smith works at Microsoft and earned $50,000 with a 15% bonus."],
1046
+ ["Sarah Johnson from Google Inc. reported revenues of $2.5 million, representing a 25% increase."],
1047
+ ["The CEO of Apple, Tim Cook, announced profits of $1.2B with 18.5% growth rate."],
1048
+ ["Dr. Michael Brown from IBM Corp. received €75,000 salary increase of 12%."],
1049
+ ["Ms. Lisa Wilson at Amazon reported quarterly results of £500K with 8.7% margin."]
1050
+ ],
1051
+ inputs=[text_input],
1052
+ label="نمونه متن‌ها"
1053
+ )
1054
+
1055
+ # اجرای برنامه
1056
+ if __name__ == "__main__":
1057
+ demo.launch()