Spaces:
Sleeping
Sleeping
unknown commited on
Commit ·
8dbfbab
1
Parent(s): de7fe63
updated
Browse files- utils/markdown_sanitizer.py +42 -6
utils/markdown_sanitizer.py
CHANGED
|
@@ -1,12 +1,17 @@
|
|
| 1 |
-
# utils/markdown_sanitizer.py
|
| 2 |
import re
|
| 3 |
|
|
|
|
| 4 |
_WS_WEIRD = re.compile(r"[\u00A0\u2000-\u200B\u2060\u3000]")
|
| 5 |
|
|
|
|
| 6 |
ROW_RE = re.compile(r"^\s*\|.*\|\s*$")
|
| 7 |
SEP_RE = re.compile(r"^\s*\|?\s*:?-{2,}\s*(\|\s*:?-{2,}\s*)+\|?\s*$")
|
| 8 |
FENCE_RE = re.compile(r"^\s*(```|~~~)")
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
def _split_cells(line: str) -> list[str]:
|
| 11 |
return [c.strip() for c in line.strip().strip("|").split("|")]
|
| 12 |
|
|
@@ -31,6 +36,10 @@ def _balance_columns(block: list[str]) -> list[str]:
|
|
| 31 |
fixed.append(_join_cells(cells))
|
| 32 |
return fixed
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
def _unindent_headings(line: str) -> str:
|
| 35 |
m = re.match(r"^(\s{4,})(#{1,6}\s+.*)$", line)
|
| 36 |
if m:
|
|
@@ -52,8 +61,12 @@ def _escape_leading_pipe(line: str) -> str:
|
|
| 52 |
return line[:i] + r"\|" + line[i+1:]
|
| 53 |
return line
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
def _normalize_math_brackets(lines: list[str]) -> list[str]:
|
| 56 |
-
"""Chuyển [ ... ] → $$ … $$ ; và $$…$$-trên-1-dòng → khối $$
|
| 57 |
out = []
|
| 58 |
i, n = 0, len(lines)
|
| 59 |
in_fence = False
|
|
@@ -68,7 +81,7 @@ def _normalize_math_brackets(lines: list[str]) -> list[str]:
|
|
| 68 |
if not in_fence:
|
| 69 |
t = ln.strip()
|
| 70 |
|
| 71 |
-
# $$ ... $$ trên
|
| 72 |
m = re.match(r"^\s*\$\$(.+)\$\$\s*$", ln)
|
| 73 |
if m:
|
| 74 |
content = m.group(1).strip()
|
|
@@ -100,15 +113,20 @@ def _normalize_math_brackets(lines: list[str]) -> list[str]:
|
|
| 100 |
|
| 101 |
return out
|
| 102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
def normalize_markdown_for_pandoc(md: str) -> str:
|
|
|
|
| 104 |
md = md.replace("\r\n", "\n").replace("\r", "\n")
|
| 105 |
md = _WS_WEIRD.sub(" ", md)
|
| 106 |
|
| 107 |
-
# Pass 1:
|
| 108 |
lines = md.split("\n")
|
| 109 |
lines = _normalize_math_brackets(lines)
|
| 110 |
|
| 111 |
-
# Pass 2:
|
| 112 |
out = []
|
| 113 |
i, n = 0, len(lines)
|
| 114 |
in_fence = False
|
|
@@ -149,4 +167,22 @@ def normalize_markdown_for_pandoc(md: str) -> str:
|
|
| 149 |
|
| 150 |
out.append(ln); i += 1
|
| 151 |
|
| 152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import re
|
| 2 |
|
| 3 |
+
# Xử lý ký tự khoảng trắng đặc biệt
|
| 4 |
_WS_WEIRD = re.compile(r"[\u00A0\u2000-\u200B\u2060\u3000]")
|
| 5 |
|
| 6 |
+
# Nhận diện bảng, code block, toán học
|
| 7 |
ROW_RE = re.compile(r"^\s*\|.*\|\s*$")
|
| 8 |
SEP_RE = re.compile(r"^\s*\|?\s*:?-{2,}\s*(\|\s*:?-{2,}\s*)+\|?\s*$")
|
| 9 |
FENCE_RE = re.compile(r"^\s*(```|~~~)")
|
| 10 |
|
| 11 |
+
# ------------------------- #
|
| 12 |
+
# HÀM HỖ TRỢ XỬ LÝ BẢNG #
|
| 13 |
+
# ------------------------- #
|
| 14 |
+
|
| 15 |
def _split_cells(line: str) -> list[str]:
|
| 16 |
return [c.strip() for c in line.strip().strip("|").split("|")]
|
| 17 |
|
|
|
|
| 36 |
fixed.append(_join_cells(cells))
|
| 37 |
return fixed
|
| 38 |
|
| 39 |
+
# ------------------------- #
|
| 40 |
+
# HỖ TRỢ XỬ LÝ KHÁC #
|
| 41 |
+
# ------------------------- #
|
| 42 |
+
|
| 43 |
def _unindent_headings(line: str) -> str:
|
| 44 |
m = re.match(r"^(\s{4,})(#{1,6}\s+.*)$", line)
|
| 45 |
if m:
|
|
|
|
| 61 |
return line[:i] + r"\|" + line[i+1:]
|
| 62 |
return line
|
| 63 |
|
| 64 |
+
# ------------------------- #
|
| 65 |
+
# XỬ LÝ CÔNG THỨC TOÁN #
|
| 66 |
+
# ------------------------- #
|
| 67 |
+
|
| 68 |
def _normalize_math_brackets(lines: list[str]) -> list[str]:
|
| 69 |
+
"""Chuyển [ ... ] → $$ … $$ ; và $$…$$-trên-1-dòng → khối $$ riêng."""
|
| 70 |
out = []
|
| 71 |
i, n = 0, len(lines)
|
| 72 |
in_fence = False
|
|
|
|
| 81 |
if not in_fence:
|
| 82 |
t = ln.strip()
|
| 83 |
|
| 84 |
+
# $$ ... $$ trên 1 dòng → khối riêng
|
| 85 |
m = re.match(r"^\s*\$\$(.+)\$\$\s*$", ln)
|
| 86 |
if m:
|
| 87 |
content = m.group(1).strip()
|
|
|
|
| 113 |
|
| 114 |
return out
|
| 115 |
|
| 116 |
+
# ------------------------- #
|
| 117 |
+
# CHUẨN HÓA TOÀN BỘ MARKDOWN #
|
| 118 |
+
# ------------------------- #
|
| 119 |
+
|
| 120 |
def normalize_markdown_for_pandoc(md: str) -> str:
|
| 121 |
+
"""Chuẩn hóa Markdown trước khi đưa vào Pandoc."""
|
| 122 |
md = md.replace("\r\n", "\n").replace("\r", "\n")
|
| 123 |
md = _WS_WEIRD.sub(" ", md)
|
| 124 |
|
| 125 |
+
# Pass 1: Chuẩn hóa toán học
|
| 126 |
lines = md.split("\n")
|
| 127 |
lines = _normalize_math_brackets(lines)
|
| 128 |
|
| 129 |
+
# Pass 2: Bảng + escape ký tự
|
| 130 |
out = []
|
| 131 |
i, n = 0, len(lines)
|
| 132 |
in_fence = False
|
|
|
|
| 167 |
|
| 168 |
out.append(ln); i += 1
|
| 169 |
|
| 170 |
+
# Pass 3: Ép xuống dòng hợp lý (fix Word export)
|
| 171 |
+
final_lines = []
|
| 172 |
+
for j, ln in enumerate(out):
|
| 173 |
+
final_lines.append(ln)
|
| 174 |
+
# Nếu dòng hiện tại và dòng kế tiếp đều không trống và không thuộc bảng / code / heading
|
| 175 |
+
if (
|
| 176 |
+
j + 1 < len(out)
|
| 177 |
+
and out[j].strip() != ""
|
| 178 |
+
and out[j + 1].strip() != ""
|
| 179 |
+
and not ROW_RE.match(out[j])
|
| 180 |
+
and not FENCE_RE.match(out[j])
|
| 181 |
+
and not out[j].strip().startswith("|")
|
| 182 |
+
and not out[j + 1].strip().startswith("|")
|
| 183 |
+
and not out[j].strip().startswith("#")
|
| 184 |
+
and not out[j + 1].strip().startswith("#")
|
| 185 |
+
):
|
| 186 |
+
final_lines[-1] = final_lines[-1].rstrip() + " " # ép xuống dòng Markdown
|
| 187 |
+
|
| 188 |
+
return "\n".join(final_lines)
|