| | import os |
| | import re |
| | import unicodedata |
| | from pathlib import Path |
| | import pdfplumber |
| |
|
| | |
| | |
| | |
| |
|
| | PDF_PATH = Path("data/Rang_Ham_Mat.pdf") |
| | SPECIALTY_FOLDER = "Răng Hàm Mặt" |
| | 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) |