|
|
import os |
|
|
import re |
|
|
import unicodedata |
|
|
from pathlib import Path |
|
|
import pdfplumber |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PDF_PATH = Path("data/Huong-dan-chan-doan-dieu-tri-Da-lieu.pdf") |
|
|
SPECIALTY_FOLDER = "Da liễu" |
|
|
ENABLE_DEBUG = False |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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), |
|
|
] |
|
|
|
|
|
|
|
|
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), |
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def safe_slug(text: str) -> str: |
|
|
s = normalize_text(text) |
|
|
|
|
|
s = s.replace("__", " ") |
|
|
s = s.replace("_", " ") |
|
|
|
|
|
s = re.sub(r"\s+", " ", s.strip()) |
|
|
|
|
|
s = s.replace("/", "-").replace("\\", "-") |
|
|
|
|
|
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" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
(disease_dir / "_raw.txt").write_text(normalize_text(txt_raw), encoding="utf-8") |
|
|
|
|
|
return root |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
root = extract_disease_sections(PDF_PATH) |
|
|
split_into_fields(root) |