File size: 8,809 Bytes
d4abe4b |
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 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
import os
import re
import unicodedata
from pathlib import Path
import pdfplumber
# ===============================================
# CONFIG
# ===============================================
PDF_PATH = Path("data/Huong-dan-chan-doan-dieu-tri-Da-lieu.pdf")
SPECIALTY_FOLDER = "Da liễu" # folder chuyên khoa (config)
ENABLE_DEBUG = False
# ===============================================
# TOC STRUCTURE (CHAPTERS + DISEASES)
# ===============================================
CHAPTERS = [
("CHƯƠNG 1. BỆNH DA NHIỄM KHUẨN", 8),
("CHƯƠNG 2. BỆNH DA DO KÝ SINH TRÙNG – CÔN TRÙNG", 40),
("CHƯƠNG 3. BỆNH DA DO VI RÚT", 67),
("CHƯƠNG 4. BỆNH DA TỰ MIỄN", 81),
("CHƯƠNG 5. BỆNH DA DỊ ỨNG – MIỄN DỊCH", 114),
("CHƯƠNG 6. BỆNH ĐỎ DA CÓ VẢY", 154),
("CHƯƠNG 7. BỆNH LÂY TRUYỀN QUA ĐƯỜNG TÌNH DỤC", 185),
("CHƯƠNG 8. U DA", 221),
("CHƯƠNG 9. CÁC BỆNH DA DI TRUYỀN", 241),
("CHƯƠNG 10. RỐI LOẠN SẮC TỐ", 281),
("CHƯƠNG 11. CÁC BỆNH DA KHÁC", 293),
]
# BỆNH cuối cùng có end_page riêng
DISEASES = [
("1. BỆNH CHỐC", 8),
("2. NHỌT", 13),
("3. VIÊM NANG LÔNG", 16),
("4. HỘI CHỨNG BONG VẢY DA DO TỤ CẦU", 20),
("5. TRỨNG CÁ", 23),
("6. BỆNH LAO DA", 28),
("7. BỆNH PHONG", 34),
("8. BỆNH GHẺ", 40),
("9. LANG BEN", 43),
("10. BỆNH DA DO NẤM SỢI", 46),
("11. BỆNH DA VÀ NIÊM MẠC DO CANDIDA", 50),
("12. NẤM TÓC", 55),
("13. NẤM MÓNG", 60),
("14. VIÊM DA TIẾP XÚC DO CÔN TRÙNG", 64),
("15. BỆNH ZONA", 67),
("16. BỆNH HẠT CƠM", 72),
("17. U MỀM LÂY", 77),
("18. BỆNH LUPUS BAN ĐỎ", 81),
("19. VIÊM BÌ CƠ", 88),
("20. PEMPHIGUS", 92),
("21. BỌNG NƯỚC DẠNG PEMPHIGUS", 98),
("22. BỆNH VIÊM DA DẠNG HERPES CỦA DUHRING-BROCQ", 103),
("23. HỘI CHỨNG RAYNAUD", 107),
("24. VIÊM DA CƠ ĐỊA", 114),
("25. VIÊM DA TIẾP XÚC DỊ ỨNG", 119),
("26. HỘI CHỨNG DRESS", 123),
("27. HỒNG BAN ĐA DẠNG", 127),
("28. HỘI CHỨNG STEVENS- JOHNSON", 133),
("29. HỘI CHỨNG LYELL", 139),
("30. SẨN NGỨA", 145),
("31. BỆNH MÀY ĐAY", 149),
("32. VIÊM DA DẦU", 154),
("33. VẢY PHẤN HỒNG GIBERT", 157),
("34. BỆNH VẢY NẾN", 161),
("35. Á VẢY NẾN VÀ VẢY PHẤN DẠNG LICHEN", 167),
("36. ĐỎ DA TOÀN THÂN", 173),
("37. BỆNH LICHEN PHẲNG", 180),
("38. BỆNH GIANG MAI", 185),
("39. BỆNH LẬU", 194),
("40. VIÊM ÂM HỘ-ÂM ĐẠO DO NẤM CANDIDA", 198),
("41. HERPES SINH DỤC", 202),
("42. NHIỄM CHLAMYDIA TRACHOMATIS SINH DỤC-TIẾT NIỆU", 205),
("43. VIÊM ÂM ĐẠO DO TRÙNG ROI", 211),
("44. BỆNH SÙI MÀO GÀ SINH DỤC", 215),
("45. UNG THƯ TẾ BÀO ĐÁY", 221),
("46. UNG THƯ TẾ BÀO VẢY", 226),
("47. UNG THƯ TẾ BÀO HẮC TỐ", 232),
("48. U ỐNG TUYẾN MỒ HÔI", 238),
("49. DÀY SỪNG LÒNG BÀN TAY, BÀN CHÂN DI TRUYỀN", 241),
("50. LY THƯỢNG BÌ BỌNG NƯỚC BẨM SINH", 244),
("51. BỆNH VẢY PHẤN ĐỎ NANG LÔNG", 250),
("52. U XƠ THẦN KINH", 255),
("53. BỆNH GAI ĐEN", 259),
("54. DỊ SỪNG NANG LÔNG", 264),
("55. BỆNH VẢY CÁ", 267),
("56. VIÊM DA ĐẦU CHI- RUỘT", 274),
("57. SARCOIDOSIS", 277),
("58. BỆNH BẠCH BIẾN", 281),
("59. SẠM DA", 285),
("60. RÁM MÁ", 289),
("61. BỆNH APTHOSE", 293),
("62. BỆNH DA DO ÁNH SÁNG", 297),
("63. BỆNH PORPHYRIN DA", 301),
("64. BỆNH DA NGHỀ NGHIỆP", 305),
("65. BỆNH PELLAGRA", 312, 315),
]
# ===============================================
# TEXT NORMALIZATION (FIX PDF ENCODING ERRORS)
# ===============================================
REPLACEMENTS = {
"Ƣ": "Ư",
"ƣ": "ư",
"PHÕNG": "PHÒNG",
"PHÕM": "PHÒM",
}
PAGE_NUMBER_PATTERN = re.compile(r"^\s*\d+\s*$")
def normalize_text(s: str) -> str:
s = unicodedata.normalize("NFC", s)
for wrong, right in REPLACEMENTS.items():
s = s.replace(wrong, right)
return s
# ===============================================
# HELPERS
# ===============================================
def safe_slug(text: str) -> str:
s = normalize_text(text)
# Replace underscores/double underscores with spaces
s = s.replace("__", " ")
s = s.replace("_", " ")
# Normalize spaces
s = re.sub(r"\s+", " ", s.strip())
# Replace slashes with hyphen to keep filesystem safe
s = s.replace("/", "-").replace("\\", "-")
# Remove characters outside word chars, spaces, hyphen, parentheses
s = re.sub(r"[^\w\s\-\(\)]+", "", s)
return s.strip()
def clean_heading_title(text: str, remove_chapter_keyword: bool = False) -> str:
s = normalize_text(text)
if remove_chapter_keyword:
s = re.sub(r"^(CH(U|Ư)ƠNG)\s*\d+[\.\-:\s]*", "", s, flags=re.IGNORECASE)
s = re.sub(r"^\d+[\.\-:\s]+", "", s)
return s.strip(" .:-_")
def remove_page_number_lines(text: str) -> str:
cleaned_lines = []
for line in text.splitlines():
if PAGE_NUMBER_PATTERN.match(line.strip()):
continue
cleaned_lines.append(line)
return "\n".join(cleaned_lines)
def find_chapter(page: int):
chosen = None
for chapter, start in CHAPTERS:
if start <= page:
chosen = chapter
else:
break
return chosen or "CHƯƠNG_KHÁC"
# ===============================================
# 1. SPLIT PDF → DISEASE SECTIONS (only used as input)
# ===============================================
def extract_disease_sections(pdf_path: Path):
if SPECIALTY_FOLDER:
specialty = SPECIALTY_FOLDER
else:
specialty = safe_slug(pdf_path.stem.split("-")[-1])
root = pdf_path.parent / specialty
root.mkdir(exist_ok=True)
with pdfplumber.open(str(pdf_path)) as pdf:
total = len(pdf.pages)
for i, entry in enumerate(DISEASES):
title = entry[0]
start = entry[1]
end = entry[2] if len(entry) > 2 else (
DISEASES[i + 1][1] - 1 if i + 1 < len(DISEASES) else start
)
start_idx = start
end_idx = min(end, total - 1)
chapter = find_chapter(start)
chapter_clean = clean_heading_title(chapter, remove_chapter_keyword=True) or chapter
chapter_dir = root / safe_slug(chapter_clean)
chapter_dir.mkdir(parents=True, exist_ok=True)
disease_clean = clean_heading_title(title) or title
disease_dir = chapter_dir / safe_slug(disease_clean)
disease_dir.mkdir(parents=True, exist_ok=True)
txt_raw = ""
for p in range(start_idx, end_idx + 1):
txt_raw += (pdf.pages[p].extract_text() or "") + "\n"
txt_raw = remove_page_number_lines(txt_raw)
# Save temporary disease text (not final output)
(disease_dir / "_raw.txt").write_text(normalize_text(txt_raw), encoding="utf-8")
return root
# ===============================================
# 2. SPLIT EACH DISEASE INTO SUBSECTIONS
# ===============================================
def split_into_fields(root_dir: Path):
for raw_file in root_dir.rglob("_raw.txt"):
text = raw_file.read_text(encoding="utf-8")
lines = text.splitlines(keepends=True)
headings = []
for i, line in enumerate(lines):
m = re.match(r"^\s*(\d+)\.\s+(.+)$", line)
if m:
num = m.group(1)
title = normalize_text(m.group(2))
headings.append((i, num, title))
if len(headings) < 1:
continue
disease_dir = raw_file.parent
for idx, (start_idx, num, title) in enumerate(headings):
end_idx = headings[idx + 1][0] if idx + 1 < len(headings) else len(lines)
section_text = "".join(lines[start_idx:end_idx]).strip()
if not section_text:
continue
section_clean = clean_heading_title(title) or title
section_slug = safe_slug(section_clean) or f"section_{num}"
out_file = disease_dir / f"{section_slug}.txt"
if out_file.exists():
out_file = disease_dir / f"{section_slug}_{num}.txt"
out_file.write_text(section_text, encoding="utf-8")
raw_file.unlink() # remove raw file
# ===============================================
# MAIN
# ===============================================
if __name__ == "__main__":
root = extract_disease_sections(PDF_PATH)
split_into_fields(root) |