SamitF commited on
Commit
df9c352
Β·
verified Β·
1 Parent(s): 2ef65c2

create app.py

Browse files
Files changed (1) hide show
  1. app.py +816 -0
app.py ADDED
@@ -0,0 +1,816 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import math
3
+ import json
4
+ import string
5
+ import tempfile
6
+ import os
7
+ from collections import Counter
8
+ from html.parser import HTMLParser
9
+ from typing import Dict, List, Tuple, Any, Optional
10
+ import xml.etree.ElementTree as ET
11
+ import gradio as gr
12
+
13
+ # =====================================================================
14
+ # CUSTOM CORE PARSERS & HELPERS (NO EXTERNAL NLP LIBRARIES)
15
+ # =====================================================================
16
+
17
+ class SimpleHTMLInspector(HTMLParser):
18
+ """
19
+ A lightweight, pure-Python HTML inspector utilizing standard HTMLParser.
20
+ Fulfills educational HTML analysis without external BeautifulSoup or library dependencies.
21
+ """
22
+ def __init__(self):
23
+ super().__init__()
24
+ self.tags: List[str] = []
25
+ self.content_map: List[Tuple[str, str]] = []
26
+ self.tag_stack: List[str] = []
27
+
28
+ def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]):
29
+ self.tags.append(tag)
30
+ self.tag_stack.append(tag)
31
+
32
+ def handle_data(self, data: str):
33
+ cleaned = data.strip()
34
+ if cleaned:
35
+ current_tag = self.tag_stack[-1] if self.tag_stack else "text"
36
+ # Ignore style/script data for clean text preview
37
+ if current_tag not in ["script", "style"]:
38
+ self.content_map.append((current_tag, cleaned))
39
+
40
+ def handle_endtag(self, tag: str):
41
+ if self.tag_stack and self.tag_stack[-1] == tag:
42
+ self.tag_stack.pop()
43
+
44
+
45
+ def parse_csv_custom(text: str, delimiter: str = ",") -> Tuple[List[str], List[List[str]], int, int]:
46
+ """
47
+ A custom state-machine CSV parser built from scratch.
48
+ Complies with rules: No import csv, manages quote boundaries, escaped quotes, and newlines correctly.
49
+ """
50
+ lines = text.splitlines()
51
+ if not lines:
52
+ return [], [], 0, 0
53
+
54
+ parsed_rows: List[List[str]] = []
55
+
56
+ for line in lines:
57
+ if not line.strip():
58
+ continue
59
+ row_fields = []
60
+ current_field = []
61
+ in_quotes = False
62
+ i = 0
63
+ n = len(line)
64
+
65
+ while i < n:
66
+ char = line[i]
67
+ if in_quotes:
68
+ if char == '"':
69
+ # Lookahead for escaped quote
70
+ if i + 1 < n and line[i+1] == '"':
71
+ current_field.append('"')
72
+ i += 2
73
+ continue
74
+ else:
75
+ in_quotes = False
76
+ else:
77
+ current_field.append(char)
78
+ else:
79
+ if char == '"':
80
+ in_quotes = True
81
+ elif char == delimiter:
82
+ row_fields.append("".join(current_field))
83
+ current_field = []
84
+ else:
85
+ current_field.append(char)
86
+ i += 1
87
+ row_fields.append("".join(current_field))
88
+ parsed_rows.append(row_fields)
89
+
90
+ if not parsed_rows:
91
+ return [], [], 0, 0
92
+
93
+ headers = parsed_rows[0]
94
+ data_rows = parsed_rows[1:] if len(parsed_rows) > 1 else []
95
+
96
+ col_count = len(headers)
97
+ row_count = len(parsed_rows)
98
+
99
+ return headers, data_rows, row_count, col_count
100
+
101
+
102
+ def detect_dominant_delimiter(text: str) -> str:
103
+ """
104
+ Analyzes lines to guess if the delimiter is comma, semicolon, or tab.
105
+ Looks for the highest count with consistency across initial lines.
106
+ """
107
+ candidates = [",", ";", "\t"]
108
+ sample_lines = [l for l in text.splitlines()[:5] if l.strip()]
109
+ if not sample_lines:
110
+ return ","
111
+
112
+ best_delim = ","
113
+ best_score = -1
114
+
115
+ for delim in candidates:
116
+ counts = [line.count(delim) for line in sample_lines]
117
+ avg_count = sum(counts) / len(counts)
118
+ # Minimize standard deviation for structural consistency
119
+ variance = sum((c - avg_count)**2 for c in counts) / len(counts)
120
+
121
+ if avg_count > 0.5:
122
+ score = avg_count / (1.0 + variance) # high frequency, low variation
123
+ if score > best_score:
124
+ best_score = score
125
+ best_delim = delim
126
+
127
+ return best_delim
128
+
129
+ # =====================================================================
130
+ # TEXT ANALYZER CORE LOGIC
131
+ # =====================================================================
132
+
133
+ class TextAnalyzer:
134
+ """
135
+ The monolithic analysis processor wrapping all character,
136
+ unicode, word, sentence, regex, and structured analyses.
137
+ """
138
+ def __init__(self, text: str):
139
+ self.text = text
140
+ self.bytes_data = text.encode("utf-8")
141
+
142
+ # Simple structural tokenizer
143
+ self.lines = text.splitlines()
144
+
145
+ # Word extraction without NLP: lowercase for counts, standard regex boundary matches
146
+ self.words_raw = re.findall(r'\b[a-zA-Z0-9_\'-]+\b', text)
147
+ self.words = [w.strip() for w in self.words_raw if w.strip()]
148
+
149
+ # Sentence segmentation heuristic (split on . ! ? followed by space/line bounds)
150
+ self.sentences = [s.strip() for s in re.split(r'[.!?]+(?=\s|$)', text) if s.strip()]
151
+
152
+ def get_overview(self) -> Dict[str, Any]:
153
+ """Module 1: General document measurements."""
154
+ char_count = len(self.text)
155
+ word_count = len(self.words)
156
+ sentence_count = max(1 if word_count > 0 and not self.sentences else 0, len(self.sentences))
157
+ line_count = len(self.lines)
158
+
159
+ # Paragraphs: split by multiple empty lines
160
+ paragraphs = [p for p in re.split(r'\n\s*\n', self.text) if p.strip()]
161
+ paragraph_count = len(paragraphs)
162
+
163
+ byte_count = len(self.bytes_data)
164
+
165
+ avg_word_length = sum(len(w) for w in self.words) / word_count if word_count > 0 else 0.0
166
+ avg_sentence_length = word_count / sentence_count if sentence_count > 0 else 0.0
167
+
168
+ return {
169
+ "char_count": char_count,
170
+ "word_count": word_count,
171
+ "sentence_count": sentence_count,
172
+ "line_count": line_count,
173
+ "paragraph_count": paragraph_count,
174
+ "byte_count": byte_count,
175
+ "avg_word_length": round(avg_word_length, 2),
176
+ "avg_sentence_length": round(avg_sentence_length, 2),
177
+ }
178
+
179
+ def generate_encoding_table(self, max_chars: int = 150) -> List[List[str]]:
180
+ """
181
+ Module 2: Technical character encodings mappings.
182
+ Limits rows to avoid crashing browser windows with massive datasets.
183
+ """
184
+ rows = []
185
+ for char in self.text[:max_chars]:
186
+ dec = ord(char)
187
+ hex_val = f"0x{dec:X}"
188
+ bin_val = f"{dec:08b}"
189
+
190
+ # ASCII validation
191
+ ascii_repr = char if dec < 128 else "N/A"
192
+ if dec < 32 or dec == 127:
193
+ ascii_repr = "Control Char" if dec != 10 and dec != 9 else ("[LF]" if dec == 10 else "[TAB]")
194
+
195
+ unicode_point = f"U+{dec:04X}"
196
+
197
+ # UTF-8 details
198
+ utf8_bytes = char.encode("utf-8")
199
+ utf8_len = len(utf8_bytes)
200
+ utf8_bytes_str = " ".join(f"{b:02X}" for b in utf8_bytes)
201
+
202
+ rows.append([
203
+ char if dec >= 32 else " ",
204
+ ascii_repr,
205
+ unicode_point,
206
+ str(dec),
207
+ hex_val,
208
+ bin_val,
209
+ f"{utf8_len} byte(s)",
210
+ utf8_bytes_str
211
+ ])
212
+ return rows
213
+
214
+ def generate_unicode_explorer(self, max_chars: int = 100) -> str:
215
+ """
216
+ Module 3: Educational breakdown highlighting the prefix bit structure of UTF-8.
217
+ Provides highly descriptive, interactive visual representation of multibyte UTF-8.
218
+ """
219
+ html = ['<div class="space-y-4">']
220
+
221
+ limit_text = self.text[:max_chars]
222
+ for idx, char in enumerate(limit_text):
223
+ dec_val = ord(char)
224
+ cp = f"U+{dec_val:04X}"
225
+ utf8_b = char.encode("utf-8")
226
+
227
+ html.append('<div class="p-3 rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 flex items-center justify-between shadow-sm hover:shadow-md transition">')
228
+
229
+ # Character Display Panel
230
+ char_display = char if dec_val >= 32 else f"<span class='text-xs text-amber-500 font-mono'>CTRL({dec_val})</span>"
231
+ html.append(f'<div class="flex items-center space-x-4">')
232
+ html.append(f' <div class="w-12 h-12 bg-teal-50 dark:bg-teal-950 text-teal-700 dark:text-teal-300 rounded-lg flex items-center justify-center font-bold text-2xl border border-teal-200">{char_display}</div>')
233
+ html.append(f' <div>')
234
+ html.append(f' <div class="text-sm font-bold text-slate-800 dark:text-slate-100">Character #{idx + 1}</div>')
235
+ html.append(f' <div class="text-xs font-mono text-slate-500">{cp}</div>')
236
+ html.append(f' </div>')
237
+ html.append(f'</div>')
238
+
239
+ # Binary byte representation detail
240
+ html.append('<div class="text-right font-mono text-xs">')
241
+ html.append('<div class="text-slate-400 mb-1">UTF-8 Bit Structure:</div>')
242
+
243
+ for b in utf8_b:
244
+ bin_str = f"{b:08b}"
245
+ # Format markers with color spans according to UTF-8 rule
246
+ if len(utf8_b) == 1:
247
+ # Single Byte (ASCII): 0xxxxxxx
248
+ formatted_bin = f"<span class='text-green-600 dark:text-green-400 font-bold'>0</span>{bin_str[1:]}"
249
+ lbl = "ASCII Pattern"
250
+ elif len(utf8_b) == 2:
251
+ if b == utf8_b[0]:
252
+ formatted_bin = f"<span class='text-blue-600 dark:text-blue-400 font-bold'>110</span>{bin_str[3:]}"
253
+ lbl = "2-Byte Header"
254
+ else:
255
+ formatted_bin = f"<span class='text-purple-600 dark:text-purple-400 font-bold'>10</span>{bin_str[2:]}"
256
+ lbl = "Data Byte"
257
+ elif len(utf8_b) == 3:
258
+ if b == utf8_b[0]:
259
+ formatted_bin = f"<span class='text-orange-600 dark:text-orange-400 font-bold'>1110</span>{bin_str[4:]}"
260
+ lbl = "3-Byte Header"
261
+ else:
262
+ formatted_bin = f"<span class='text-purple-600 dark:text-purple-400 font-bold'>10</span>{bin_str[2:]}"
263
+ lbl = "Data Byte"
264
+ else: # 4-byte sequences
265
+ if b == utf8_b[0]:
266
+ formatted_bin = f"<span class='text-red-600 dark:text-red-400 font-bold'>11110</span>{bin_str[5:]}"
267
+ lbl = "4-Byte Header (Emoji/Rare)"
268
+ else:
269
+ formatted_bin = f"<span class='text-purple-600 dark:text-purple-400 font-bold'>10</span>{bin_str[2:]}"
270
+ lbl = "Data Byte"
271
+
272
+ html.append(f'<div class="flex items-center justify-end space-x-2">')
273
+ html.append(f' <span class="text-[10px] text-slate-400 italic">({lbl})</span>')
274
+ html.append(f' <span class="bg-slate-100 dark:bg-slate-900 px-1.5 py-0.5 rounded tracking-widest">{formatted_bin}</span>')
275
+ html.append(f'</div>')
276
+
277
+ html.append('</div>') # end byte display
278
+ html.append('</div>') # end card
279
+
280
+ html.append('</div>')
281
+
282
+ if len(self.text) > max_chars:
283
+ html.append(f'<div class="p-3 text-center text-xs text-amber-600 bg-amber-50 rounded border border-amber-200 mt-2">Displaying first {max_chars} characters. Your input has {len(self.text)} total chars.</div>')
284
+
285
+ return "\n".join(html)
286
+
287
+ def get_char_frequencies(self) -> List[Tuple[str, int, float]]:
288
+ """Module 4: Sort and measure character densities."""
289
+ total = len(self.text)
290
+ if total == 0:
291
+ return []
292
+ cnt = Counter(self.text)
293
+ sorted_chars = cnt.most_common()
294
+ return [(c, count, round((count / total) * 100, 2)) for c, count in sorted_chars]
295
+
296
+ def get_word_frequencies(self) -> List[Tuple[str, int]]:
297
+ """Module 5: Tokenize, clean and compute high vocabulary counts."""
298
+ if not self.words:
299
+ return []
300
+ normalized_words = [w.lower() for w in self.words]
301
+ cnt = Counter(normalized_words)
302
+ return cnt.most_common()
303
+
304
+ def get_string_statistics(self) -> Dict[str, Any]:
305
+ """Module 6: Text measurements and distributions."""
306
+ unique_words = len(set(w.lower() for w in self.words))
307
+ unique_chars = len(set(self.text))
308
+ whitespace_count = sum(1 for c in self.text if c.isspace())
309
+ tab_count = self.text.count('\t')
310
+ newline_count = self.text.count('\n')
311
+ digit_count = sum(1 for c in self.text if c.isdigit())
312
+ uppercase_count = sum(1 for c in self.text if c.isupper())
313
+ lowercase_count = sum(1 for c in self.text if c.islower())
314
+
315
+ # Punctuation set (Standard English keys + Unicode counterparts)
316
+ punct_set = set(string.punctuation) | {'β€œ', '”', 'β€˜', '’', 'β€”', '–', '…', 'ΒΏ', 'Β‘'}
317
+ punctuation_count = sum(1 for c in self.text if c in punct_set)
318
+
319
+ longest_word = max(self.words, key=len) if self.words else ""
320
+ shortest_word = min(self.words, key=len) if self.words else ""
321
+
322
+ return {
323
+ "longest_word": longest_word,
324
+ "shortest_word": shortest_word,
325
+ "unique_words": unique_words,
326
+ "unique_characters": unique_chars,
327
+ "whitespace_count": whitespace_count,
328
+ "tab_count": tab_count,
329
+ "newline_count": newline_count,
330
+ "digit_count": digit_count,
331
+ "uppercase_count": uppercase_count,
332
+ "lowercase_count": lowercase_count,
333
+ "punctuation_count": punctuation_count
334
+ }
335
+
336
+ def run_regex_explorer(self) -> Dict[str, List[str]]:
337
+ """
338
+ Module 7: Matches semantic groups matching pre-defined structural regex rules.
339
+ """
340
+ patterns = {
341
+ "Emails": r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
342
+ "URLs": r'https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)',
343
+ "Phone numbers": r'\+?[0-9]{1,4}?[-.\s]?(?:\([0-9]{1,3}\)|[0-9]{1,3})[-.\s]?[0-9]{1,4}[-.\s]?[0-9]{1,4}[-.\s]?[0-9]{1,9}',
344
+ "Dates": r'\d{4}[-/.]\d{1,2}[-/.]\d{1,2}|\d{1,2}[-/.]\d{1,2}[-/.]\d{2,4}',
345
+ "Times": r'\b(?:[01]?\d|2[0-3]):[0-5]\d(?::[0-5]\d)?\s?(?:AM|PM|am|pm)?\b',
346
+ "Numbers": r'\b\d+(?:\.\d+)?\b',
347
+ "Currency values": r'(?:\$|€|Β£|Β₯|β‚Ή)\s?\d+(?:,\d{3})*(?:\.\d+)?\b|\b\d+(?:\.\d+)?\s?(?:USD|EUR|GBP|JPY|INR)\b',
348
+ "Hashtags": r'#[a-zA-Z0-9_]+',
349
+ "Mentions": r'@[a-zA-Z0-9_]+',
350
+ "IP addresses": r'\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b'
351
+ }
352
+
353
+ matches = {}
354
+ for title, pattern in patterns.items():
355
+ found = re.findall(pattern, self.text)
356
+ # Remove empty strings or simple fragments
357
+ matches[title] = [f.strip() for f in found if f.strip()]
358
+ return matches
359
+
360
+ def detect_format(self) -> Tuple[str, str]:
361
+ """
362
+ Module 8: Text Format Detection.
363
+ Evaluates criteria and supplies natural explaining logic for findings.
364
+ """
365
+ txt = self.text.strip()
366
+ if not txt:
367
+ return "Empty Input", "No text provided to detect structural formats."
368
+
369
+ # JSON detection
370
+ try:
371
+ json.loads(txt)
372
+ return "JSON (Valid)", "Parsed cleanly into key-value trees or arrays using standard python serialization (json.loads)."
373
+ except json.JSONDecodeError:
374
+ if (txt.startswith('{') and txt.endswith('}')) or (txt.startswith('[') and txt.endswith(']')):
375
+ return "JSON (Malformed)", "Begins/ends with brackets ({}, []), but contains syntactic errors like misplaced commas or string literals."
376
+
377
+ # XML detection
378
+ try:
379
+ ET.fromstring(txt)
380
+ return "XML (Valid)", "Parsed successfully using standard ElementTree XML architecture."
381
+ except ET.ParseError:
382
+ if re.search(r'<\?xml', txt, re.I) or (txt.startswith('<') and txt.endswith('>')):
383
+ return "XML (Malformed)", "Starts/ends with tags or contains XML headers, but fails parser schemas."
384
+
385
+ # HTML detection
386
+ html_sig_tags = r'<!DOCTYPE html|<html|<body|<div\s+|<span\s+|<p\s+|<a\s+href'
387
+ tags_found = re.findall(r'<[a-zA-Z1-6]+(?:\s+[^>]*)*>', txt)
388
+ if re.search(html_sig_tags, txt, re.I) or len(tags_found) > 4:
389
+ return "HTML", f"Contains signature tags (matched {len(tags_found)} raw HTML elements) or document structures like '<!DOCTYPE>'."
390
+
391
+ # Markdown detection
392
+ md_points = 0
393
+ reasons = []
394
+ if re.search(r'^(?:#|##|###|####|#####|######)\s+.+', txt, re.M):
395
+ md_points += 2
396
+ reasons.append("Contains structural section indicators (# Header)")
397
+ if re.search(r'\[.+?\]\(https?://.+?\)', txt):
398
+ md_points += 2
399
+ reasons.append("Identified Markdown link structures: [text](URL)")
400
+ if re.search(r'^[*-]\s+\w+', txt, re.M):
401
+ md_points += 1
402
+ reasons.append("Identified bullet lists (- or *)")
403
+ if re.search(r'^```\w*\n', txt, re.M):
404
+ md_points += 3
405
+ reasons.append("Identified structural code fencing tags (```)")
406
+
407
+ if md_points >= 3:
408
+ return "Markdown", f"Identified markdown layout markers: {', '.join(reasons)}."
409
+
410
+ # CSV/TSV detection
411
+ delim = detect_dominant_delimiter(self.text)
412
+ lines_with_delim = [l for l in self.text.splitlines() if delim in l]
413
+ if len(lines_with_delim) >= 2:
414
+ # Check consistency of delimiter count
415
+ counts = [l.count(delim) for l in lines_with_delim[:5]]
416
+ avg_count = sum(counts) / len(counts)
417
+ variance = sum((c - avg_count)**2 for c in counts) / len(counts)
418
+ if avg_count > 0 and variance < 1.5:
419
+ delim_name = "Comma" if delim == "," else ("Semicolon" if delim == ";" else "Tab")
420
+ return f"CSV / Delimited Text", f"Structured grid properties detected using consistently spaced delimiter: '{delim_name}' (average spacing density: {avg_count:.1f} per line)."
421
+
422
+ return "Plain Text (General)", "Default categorization. Contains no specialized programmatic structure, markup schemas, or clear delimiters."
423
+
424
+
425
+ # =====================================================================
426
+ # UI GENERATION HELPER FUNCTIONS
427
+ # =====================================================================
428
+
429
+ def analyze_all_inputs(text: str) -> List[Any]:
430
+ """
431
+ Main callback updating all visual components in Gradio blocks concurrently.
432
+ """
433
+ if not text or not text.strip():
434
+ # Fallback values
435
+ empty_overview_html = "<div class='text-center text-slate-500 py-6'>Please supply valid text or load a sample.</div>"
436
+ empty_tbl = []
437
+ return [
438
+ empty_overview_html, empty_tbl, empty_overview_html, empty_tbl, empty_tbl,
439
+ empty_overview_html, empty_overview_html, empty_overview_html, empty_overview_html, ""
440
+ ]
441
+
442
+ analyzer = TextAnalyzer(text)
443
+
444
+ # Overview
445
+ ov = analyzer.get_overview()
446
+ ov_html = f"""
447
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
448
+ <div class="bg-indigo-50 dark:bg-indigo-950 p-4 rounded-xl border border-indigo-100 text-center">
449
+ <span class="block text-indigo-500 text-xs font-semibold uppercase tracking-wider mb-1">Characters</span>
450
+ <span class="text-3xl font-extrabold text-indigo-900 dark:text-indigo-100">{ov['char_count']:,}</span>
451
+ </div>
452
+ <div class="bg-blue-50 dark:bg-blue-950 p-4 rounded-xl border border-blue-100 text-center">
453
+ <span class="block text-blue-500 text-xs font-semibold uppercase tracking-wider mb-1">Words</span>
454
+ <span class="text-3xl font-extrabold text-blue-900 dark:text-blue-100">{ov['word_count']:,}</span>
455
+ </div>
456
+ <div class="bg-emerald-50 dark:bg-emerald-950 p-4 rounded-xl border border-emerald-100 text-center">
457
+ <span class="block text-emerald-500 text-xs font-semibold uppercase tracking-wider mb-1">Sentences</span>
458
+ <span class="text-3xl font-extrabold text-emerald-900 dark:text-emerald-100">{ov['sentence_count']:,}</span>
459
+ </div>
460
+ <div class="bg-purple-50 dark:bg-purple-950 p-4 rounded-xl border border-purple-100 text-center">
461
+ <span class="block text-purple-500 text-xs font-semibold uppercase tracking-wider mb-1">Bytes (UTF-8)</span>
462
+ <span class="text-3xl font-extrabold text-purple-900 dark:text-purple-100">{ov['byte_count']:,}</span>
463
+ </div>
464
+ </div>
465
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
466
+ <div class="bg-slate-50 dark:bg-slate-900 p-4 rounded-xl border border-slate-100 text-center">
467
+ <span class="block text-slate-500 text-xs font-semibold uppercase tracking-wider mb-1">Lines</span>
468
+ <span class="text-xl font-bold text-slate-800 dark:text-slate-200">{ov['line_count']:,}</span>
469
+ </div>
470
+ <div class="bg-slate-50 dark:bg-slate-900 p-4 rounded-xl border border-slate-100 text-center">
471
+ <span class="block text-slate-500 text-xs font-semibold uppercase tracking-wider mb-1">Paragraphs</span>
472
+ <span class="text-xl font-bold text-slate-800 dark:text-slate-200">{ov['paragraph_count']:,}</span>
473
+ </div>
474
+ <div class="bg-slate-50 dark:bg-slate-900 p-4 rounded-xl border border-slate-100 text-center">
475
+ <span class="block text-slate-500 text-xs font-semibold uppercase tracking-wider mb-1">Avg Word Length</span>
476
+ <span class="text-xl font-bold text-slate-800 dark:text-slate-200">{ov['avg_word_length']} <small class="text-xs text-slate-400">chars</small></span>
477
+ </div>
478
+ <div class="bg-slate-50 dark:bg-slate-900 p-4 rounded-xl border border-slate-100 text-center">
479
+ <span class="block text-slate-500 text-xs font-semibold uppercase tracking-wider mb-1">Avg Sentence Length</span>
480
+ <span class="text-xl font-bold text-slate-800 dark:text-slate-200">{ov['avg_sentence_length']} <small class="text-xs text-slate-400">words</small></span>
481
+ </div>
482
+ </div>
483
+ """
484
+
485
+ # Visual Charts using Tailwind (Avoid high load graphics libraries)
486
+ chars_stat = analyzer.get_string_statistics()
487
+ total_measurable = max(1, chars_stat["uppercase_count"] + chars_stat["lowercase_count"] + chars_stat["digit_count"] + chars_stat["punctuation_count"] + chars_stat["whitespace_count"])
488
+
489
+ def pct(val):
490
+ return (val / total_measurable) * 100
491
+
492
+ overview_chart_html = f"""
493
+ <div class="mt-6 p-4 rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800">
494
+ <h4 class="text-sm font-bold text-slate-800 dark:text-slate-200 mb-3">Character Class Distribution Ratio:</h4>
495
+ <div class="w-full flex h-6 rounded-lg overflow-hidden border border-slate-300 dark:border-slate-600 mb-3">
496
+ <div style="width: {pct(chars_stat['lowercase_count'])}%" class="bg-emerald-500 hover:opacity-90" title="Lowercase ({chars_stat['lowercase_count']})"></div>
497
+ <div style="width: {pct(chars_stat['uppercase_count'])}%" class="bg-blue-500 hover:opacity-90" title="Uppercase ({chars_stat['uppercase_count']})"></div>
498
+ <div style="width: {pct(chars_stat['digit_count'])}%" class="bg-amber-500 hover:opacity-90" title="Digits ({chars_stat['digit_count']})"></div>
499
+ <div style="width: {pct(chars_stat['punctuation_count'])}%" class="bg-purple-500 hover:opacity-90" title="Punctuation ({chars_stat['punctuation_count']})"></div>
500
+ <div style="width: {pct(chars_stat['whitespace_count'])}%" class="bg-slate-400 hover:opacity-90" title="Whitespace ({chars_stat['whitespace_count']})"></div>
501
+ </div>
502
+ <div class="grid grid-cols-2 sm:grid-cols-5 gap-2 text-xs">
503
+ <div class="flex items-center space-x-1.5"><span class="w-3 h-3 bg-emerald-500 rounded-sm"></span> <span>Lowercase ({chars_stat['lowercase_count']})</span></div>
504
+ <div class="flex items-center space-x-1.5"><span class="w-3 h-3 bg-blue-500 rounded-sm"></span> <span>Uppercase ({chars_stat['uppercase_count']})</span></div>
505
+ <div class="flex items-center space-x-1.5"><span class="w-3 h-3 bg-amber-500 rounded-sm"></span> <span>Digits ({chars_stat['digit_count']})</span></div>
506
+ <div class="flex items-center space-x-1.5"><span class="w-3 h-3 bg-purple-500 rounded-sm"></span> <span>Punctuation ({chars_stat['punctuation_count']})</span></div>
507
+ <div class="flex items-center space-x-1.5"><span class="w-3 h-3 bg-slate-400 rounded-sm"></span> <span>Whitespace ({chars_stat['whitespace_count']})</span></div>
508
+ </div>
509
+ </div>
510
+ """
511
+
512
+ # Encodings Table
513
+ encodings_data = analyzer.generate_encoding_table()
514
+
515
+ # Unicode Inspector
516
+ unicode_html = analyzer.generate_unicode_explorer()
517
+
518
+ # Frequencies
519
+ char_freqs = [[repr(c)[1:-1] if c != '\n' else '[LF]', count, f"{pct:.1f}%"] for c, count, pct in analyzer.get_char_frequencies()]
520
+ word_freqs = [[word, count] for word, count in analyzer.get_word_frequencies()[:100]]
521
+
522
+ # Detailed statistics mappings
523
+ stats_data = [
524
+ ["Property Metric", "Value", "Educational Context"],
525
+ ["Longest word length", f"{len(chars_stat['longest_word'])} chars ('{chars_stat['longest_word']}')" if chars_stat['longest_word'] else "N/A", "Useful for finding outlier anomalies or unspaced strings."],
526
+ ["Shortest word length", f"{len(chars_stat['shortest_word'])} chars ('{chars_stat['shortest_word']}')" if chars_stat['shortest_word'] else "N/A", "Averages lower for basic grammar prepositions."],
527
+ ["Unique vocabulary (Words)", str(chars_stat['unique_words']), "Vocabulary density indicator before lemmatization / stemming."],
528
+ ["Unique character keys", str(chars_stat['unique_characters']), "Alphabet size of current document schema."],
529
+ ["Whitespace instances", str(chars_stat['whitespace_count']), "Sum of space, newline, tab occurrences."],
530
+ ["Tab character counts", str(chars_stat['tab_count']), "Important identifier for tab-separated grids or indent properties."],
531
+ ["Line feeds (Newlines)", str(chars_stat['newline_count']), "Tells us structural grouping spacing parameters."],
532
+ ["Number instances", str(chars_stat['digit_count']), "Shows the count of raw numerals."],
533
+ ["Uppercase keys", str(chars_stat['uppercase_count']), "Casing properties indicating structural sentences or nouns."],
534
+ ["Lowercase keys", str(chars_stat['lowercase_count']), "Standard core text body."],
535
+ ["Punctuation marks", str(chars_stat['punctuation_count']), "Sentence partitions and code punctuation keys."]
536
+ ]
537
+
538
+ # Regex Matches HTML Builder
539
+ regex_matches = analyzer.run_regex_explorer()
540
+ regex_html = ['<div class="space-y-4">']
541
+ for category, list_matches in regex_matches.items():
542
+ count = len(list_matches)
543
+ color = "emerald" if count > 0 else "slate"
544
+ badge_class = f"bg-{color}-100 text-{color}-800 dark:bg-{color}-950 dark:text-{color}-300"
545
+
546
+ regex_html.append(f'<div class="p-4 rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 shadow-sm">')
547
+ regex_html.append(f' <div class="flex items-center justify-between border-b border-slate-100 dark:border-slate-700 pb-2 mb-2">')
548
+ regex_html.append(f' <h4 class="text-sm font-bold text-slate-800 dark:text-slate-100">{category}</h4>')
549
+ regex_html.append(f' <span class="px-2 py-0.5 text-xs font-semibold rounded-full {badge_class}">{count} matches</span>')
550
+ regex_html.append(f' </div>')
551
+
552
+ if count > 0:
553
+ # Highlight items in tags
554
+ items_html = " ".join(f'<span class="inline-block px-2.5 py-1 bg-slate-100 dark:bg-slate-900 text-slate-800 dark:text-slate-200 rounded font-mono text-xs m-1 border border-slate-200 dark:border-slate-800">{m}</span>' for m in set(list_matches[:30]))
555
+ if len(list_matches) > 30:
556
+ items_html += f' <span class="text-xs text-slate-400 italic">...and {len(list_matches)-30} more</span>'
557
+ regex_html.append(f' <div class="flex flex-wrap">{items_html}</div>')
558
+ else:
559
+ regex_html.append(f' <p class="text-xs text-slate-400 italic">No matches detected in this pattern configuration.</p>')
560
+
561
+ regex_html.append('</div>')
562
+ regex_html.append('</div>')
563
+ regex_html_str = "\n".join(regex_html)
564
+
565
+ # Text Formats Detection
566
+ format_type, format_desc = analyzer.detect_format()
567
+ format_html_str = f"""
568
+ <div class="p-6 rounded-xl border border-indigo-100 dark:border-indigo-900/50 bg-indigo-50/50 dark:bg-indigo-950/30">
569
+ <span class="text-[10px] uppercase font-extrabold tracking-widest text-indigo-500 block mb-1">Identified Schema Format</span>
570
+ <h3 class="text-2xl font-black text-indigo-900 dark:text-indigo-100 mb-2">{format_type}</h3>
571
+ <p class="text-sm text-indigo-700 dark:text-indigo-300 font-medium">{format_desc}</p>
572
+ </div>
573
+ """
574
+
575
+ # Sub-Inspectors (CSV, HTML)
576
+ csv_html_str = "<p class='text-xs text-slate-400 italic'>CSV structures not detected. This explorer will display formatting tables if text looks tabular.</p>"
577
+ if "CSV" in format_type or "," in text or ";" in text or "\t" in text:
578
+ delim = detect_dominant_delimiter(text)
579
+ try:
580
+ headers, data_rows, r_cnt, c_cnt = parse_csv_custom(text, delim)
581
+ if r_cnt > 0:
582
+ # Limit row views
583
+ preview_rows = data_rows[:10]
584
+ rows_html = "".join(f"<tr class='border-b border-slate-100 dark:border-slate-800 text-slate-600 dark:text-slate-300'>" + "".join(f"<td class='px-3 py-2 text-xs'>{col}</td>" for col in row) + "</tr>" for row in preview_rows)
585
+ headers_html = "".join(f"<th class='px-3 py-2 bg-slate-100 dark:bg-slate-900 text-left text-xs font-bold text-slate-700 dark:text-slate-300'>{h}</th>" for h in headers)
586
+
587
+ csv_html_str = f"""
588
+ <div class="mt-4 border border-slate-200 dark:border-slate-700 rounded-lg overflow-hidden bg-white dark:bg-slate-800">
589
+ <div class="p-3 bg-slate-50 dark:bg-slate-900 border-b border-slate-200 dark:border-slate-700 flex justify-between items-center text-xs">
590
+ <span class="font-bold text-slate-700 dark:text-slate-300">Parser Output: {r_cnt} Rows Γ— {c_cnt} Columns</span>
591
+ <span class="px-2 py-0.5 bg-slate-200 dark:bg-slate-800 rounded font-mono text-slate-600 dark:text-slate-400">Delimiter: '{delim}'</span>
592
+ </div>
593
+ <div class="overflow-x-auto">
594
+ <table class="w-full text-left border-collapse">
595
+ <thead><tr>{headers_html}</tr></thead>
596
+ <tbody>{rows_html}</tbody>
597
+ </table>
598
+ </div>
599
+ {f'<div class="p-2 text-center text-[11px] text-slate-400 bg-slate-50 border-t border-slate-200">Showing top 10 preview rows</div>' if len(data_rows) > 10 else ''}
600
+ </div>
601
+ """
602
+ except Exception as e:
603
+ csv_html_str = f"<p class='text-xs text-red-500 italic'>Failed standard CSV matrix processing: {str(e)}</p>"
604
+
605
+ # HTML parsing extraction using SimpleHTMLInspector
606
+ html_details_str = "<p class='text-xs text-slate-400 italic'>HTML markup pattern matches not found.</p>"
607
+ if "HTML" in format_type or "<" in text:
608
+ try:
609
+ parser = SimpleHTMLInspector()
610
+ parser.feed(text)
611
+
612
+ tag_counts = Counter(parser.tags)
613
+ tags_stat_html = " ".join(f"<span class='inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-800 dark:bg-blue-950 dark:text-blue-300 m-1'>&lt;{tag}&gt; ({cnt})</span>" for tag, cnt in tag_counts.items())
614
+
615
+ # Content mapping lists
616
+ content_preview_html = "".join(f"<div class='p-2 bg-slate-50 dark:bg-slate-900 border-b border-slate-100 dark:border-slate-800 flex justify-between items-start text-xs'><span class='font-mono text-teal-600 font-bold'>&lt;{tag}&gt;</span><span class='text-slate-600 dark:text-slate-300 text-right w-2/3'>{data}</span></div>" for tag, data in parser.content_map[:15])
617
+
618
+ html_details_str = f"""
619
+ <div class="mt-4 space-y-4">
620
+ <div class="p-3 border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 rounded-lg">
621
+ <h5 class="text-xs font-bold text-slate-700 dark:text-slate-300 mb-2">Tag Counts (Structure Streams)</h5>
622
+ <div class="flex flex-wrap">{tags_stat_html if tags_stat_html else '<span class="text-xs text-slate-400 italic">No tag keys found.</span>'}</div>
623
+ </div>
624
+
625
+ <div class="border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 rounded-lg overflow-hidden">
626
+ <div class="p-2.5 bg-slate-50 dark:bg-slate-900 border-b border-slate-200 text-xs font-bold text-slate-700 dark:text-slate-300">
627
+ Tag Text Extraction Preview
628
+ </div>
629
+ <div>
630
+ {content_preview_html if content_preview_html else '<div class="p-3 text-xs italic text-slate-400">No clean text stream mapped inside tags.</div>'}
631
+ </div>
632
+ {f'<div class="p-2 text-center text-[11px] text-slate-400 bg-slate-50 border-t border-slate-200">Displaying first 15 mapped nodes</div>' if len(parser.content_map) > 15 else ''}
633
+ </div>
634
+ </div>
635
+ """
636
+ except Exception as e:
637
+ html_details_str = f"<p class='text-xs text-red-500 italic'>HTML parser trace error: {str(e)}</p>"
638
+
639
+ return [
640
+ ov_html,
641
+ encodings_data,
642
+ unicode_html,
643
+ char_freqs,
644
+ word_freqs,
645
+ stats_data,
646
+ regex_html_str,
647
+ format_html_str,
648
+ csv_html_str,
649
+ html_details_str,
650
+ overview_chart_html
651
+ ]
652
+
653
+
654
+ def handle_file_upload(file_obj) -> str:
655
+ """
656
+ Reads the file path securely and returns decoded text.
657
+ Handles decoding failures gracefully by falling back to errors or latin-1.
658
+ """
659
+ if file_obj is None:
660
+ return ""
661
+ try:
662
+ # File object passed is a tempfile wrapper path in Gradio
663
+ with open(file_obj.name, "rb") as f:
664
+ bytes_content = f.read()
665
+ try:
666
+ return bytes_content.decode("utf-8")
667
+ except UnicodeDecodeError:
668
+ # Fallback to Latin-1 encoding to preserve characters cleanly
669
+ return bytes_content.decode("latin-1")
670
+ except Exception as e:
671
+ return f"File load error: {str(e)}"
672
+
673
+
674
+ def generate_report(text: str) -> str:
675
+ """
676
+ Fulfills Module 11: Export downloadable analysis reports.
677
+ Formats analysis sections into a beautifully structured Markdown report file.
678
+ """
679
+ if not text or not text.strip():
680
+ # Fallback empty path
681
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".md")
682
+ temp_file.write("# No analysis data available\nPlease submit text first.".encode('utf-8'))
683
+ temp_file.close()
684
+ return temp_file.name
685
+
686
+ analyzer = TextAnalyzer(text)
687
+ ov = analyzer.get_overview()
688
+ chars_stat = analyzer.get_string_statistics()
689
+ fmt, desc = analyzer.detect_format()
690
+ regex_matches = analyzer.run_regex_explorer()
691
+ char_freqs = analyzer.get_char_frequencies()[:20]
692
+ word_freqs = analyzer.get_word_frequencies()[:20]
693
+
694
+ report = []
695
+ report.append("# Text Explorer Analysis Report")
696
+ report.append(f"Generated on: 2026-07-05 (System Analysis)\n")
697
+ report.append("---")
698
+
699
+ report.append("## 1. Document Overview Statistics")
700
+ report.append(f"- **Characters (Count)**: {ov['char_count']}")
701
+ report.append(f"- **Words (Count)**: {ov['word_count']}")
702
+ report.append(f"- **Sentences**: {ov['sentence_count']}")
703
+ report.append(f"- **Line count**: {ov['line_count']}")
704
+ report.append(f"- **Paragraphs**: {ov['paragraph_count']}")
705
+ report.append(f"- **Bytes (Size)**: {ov['byte_count']} bytes")
706
+ report.append(f"- **Average Word Length**: {ov['avg_word_length']} characters")
707
+ report.append(f"- **Average Sentence Length**: {ov['avg_sentence_length']} words\n")
708
+
709
+ report.append("## 2. Text Format Classification")
710
+ report.append(f"- **Detected Structure Format**: {fmt}")
711
+ report.append(f"- **Deduction Reason**: {desc}\n")
712
+
713
+ report.append("## 3. String & Lexical Statistics")
714
+ report.append(f"- **Longest word**: '{chars_stat['longest_word']}'")
715
+ report.append(f"- **Shortest word**: '{chars_stat['shortest_word']}'")
716
+ report.append(f"- **Unique words**: {chars_stat['unique_words']}")
717
+ report.append(f"- **Unique characters**: {chars_stat['unique_characters']}")
718
+ report.append(f"- **Whitespaces total**: {chars_stat['whitespace_count']}")
719
+ report.append(f"- **Tabs**: {chars_stat['tab_count']}")
720
+ report.append(f"- **Newlines**: {chars_stat['newline_count']}")
721
+ report.append(f"- **Numerics (Digits)**: {chars_stat['digit_count']}")
722
+ report.append(f"- **Uppercase characters**: {chars_stat['uppercase_count']}")
723
+ report.append(f"- **Lowercase characters**: {chars_stat['lowercase_count']}")
724
+ report.append(f"- **Punctuation keys**: {chars_stat['punctuation_count']}\n")
725
+
726
+ report.append("## 4. Top 20 Character Frequencies")
727
+ report.append("| Character | Count | Percentage |")
728
+ report.append("| :--- | :--- | :--- |")
729
+ for char, cnt, pct in char_freqs:
730
+ c_repr = repr(char)[1:-1] if char != '\n' else '[LF]'
731
+ report.append(f"| `{c_repr}` | {cnt} | {pct:.1f}% |")
732
+ report.append("\n")
733
+
734
+ report.append("## 5. Top 20 Vocabulary Word Frequencies")
735
+ report.append("| Word | Count |")
736
+ report.append("| :--- | :--- |")
737
+ for word, cnt in word_freqs:
738
+ report.append(f"| {word} | {cnt} |")
739
+ report.append("\n")
740
+
741
+ report.append("## 6. Regex Inspector Discoveries")
742
+ for key, items in regex_matches.items():
743
+ if items:
744
+ report.append(f"- **{key}** ({len(items)} found): {', '.join(set(items[:15]))}")
745
+ else:
746
+ report.append(f"- **{key}**: 0 instances detected.")
747
+
748
+ report.append("\n---\n*Report generated by Text Explorer Space (Pure Python Educational Analyzer).*")
749
+
750
+ # Write out to temporary local file path
751
+ temp_dir = tempfile.gettempdir()
752
+ file_path = os.path.join(temp_dir, "text_explorer_report.md")
753
+ with open(file_path, "w", encoding="utf-8") as f:
754
+ f.write("\n".join(report))
755
+
756
+ return file_path
757
+
758
+
759
+ # =====================================================================
760
+ # CUSTOM CSS STYLE DEFINITIONS FOR THE GRADIO THEME
761
+ # =====================================================================
762
+
763
+ CSS = """
764
+ body {
765
+ background-color: #f8fafc;
766
+ }
767
+ .gradio-container {
768
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
769
+ }
770
+ .header-hero {
771
+ background: linear-gradient(135deg, #4f46e5 0%, #2563eb 100%);
772
+ color: white !important;
773
+ border-radius: 1rem;
774
+ padding: 2.5rem 2rem;
775
+ margin-bottom: 2rem;
776
+ text-align: center;
777
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
778
+ }
779
+ .header-hero h1 {
780
+ color: white !important;
781
+ font-weight: 900 !important;
782
+ letter-spacing: -0.025em;
783
+ font-size: 2.25rem !important;
784
+ margin-bottom: 0.5rem;
785
+ }
786
+ .header-hero p {
787
+ color: #e0e7ff !important;
788
+ font-size: 1rem;
789
+ }
790
+ .educational-card {
791
+ background-color: #f0fdfa;
792
+ border: 1px solid #ccfbf1;
793
+ border-radius: 0.75rem;
794
+ padding: 1rem;
795
+ margin-bottom: 1rem;
796
+ }
797
+ .educational-card h4 {
798
+ color: #0f766e !important;
799
+ margin-top: 0 !important;
800
+ }
801
+ .educational-card p, .educational-card li {
802
+ color: #115e59 !important;
803
+ font-size: 0.875rem !important;
804
+ }
805
+ """
806
+
807
+ # =====================================================================
808
+ # SAMPLE TEXTSETS FOR CONVENIENT USER EXPERIENCES
809
+ # =====================================================================
810
+
811
+ SAMPLES = {
812
+ "Simple English": "Hello World! Feel free to paste any text here to test the NLP Text Explorer system. It works immediately without external libraries.",
813
+ "Multilingual & Emoji": "Multilingual test: Bonjour, こんにけは, μ•ˆλ…•ν•˜μ„Έμš”! Here is an emoji breakdown to inspect UTF-8 bytes: 😊 πŸš€ πŸ•. What is their representation in binary memory space?",
814
+ "HTML document sample": '<!DOCTYPE html>\n<html>\n<head>\n <title>Sample Sandbox</title>\n</head>\n<body>\n <div class="content">\n <h1>Welcome to Text Explorer!</h1>\n <p>This is standard markup designed to verify our pure HTML-Parser extraction utilities.</p>\n <a href="https://example.com">Visit our project</a>\n </div>\n</body>\n</html>',
815
+ "Tabular CSV format": "username,email,role,joined_date\nsmith_john,john.smith@gmail.com,Administrator,2024-03-24\nelizabeth_k,k.elizabeth@yahoo.com,Contributor,2025-05-15\ntech_support,support@domain.org,User,2026-01-10",
816
+ "Markdown document": "# Markdown Document Guide\n\nWelcome to this structural text parsing review. Here are some key attributes:\n\n- Support lists\n- Supports link items: [Hugging Face Space](https://huggingface.co/spaces)\n\n## Sub-headers\n