# -*- coding: utf-8 -*- """보관된 법령 XML 기준으로 기존 md 의 누락분만 보충한다 (네트워크 미사용). 기존 md 에는 수동·스크립트 후처리(박스표 → 마크다운 표, 수식, 이미지)가 적용돼 있어 전체 재렌더링은 그 결과를 파괴한다. 따라서 **추가만** 한다. 보충 대상 (2026-08-14 감사에서 확인된 변환 누락 2종): 1. 부칙 절단 — 과거 collect_law.py 가 `부칙단위[:20]` 만 출력해 최신 부칙이 통째로 빠졌다. md 에 없는 부칙단위를 뒤에 이어붙인다. 2. 목(가·나·다…) 미출력 — 목이 호가 아니라 항의 직속 자식으로 오는 구조를 처리하지 못해 누락됐다. 부모 호 줄 뒤에 들여쓰기 4칸으로 삽입한다. 사용: python patch_law_md.py --dry # 변경량만 출력 python patch_law_md.py # 저장 """ import re import sys import xml.etree.ElementTree as ET from collect_law import ROOT, T, clean 분류목록 = ("법률", "행정규칙") STEM = re.compile(r"^(현행|교본시점)_\d{8}$") # 괄호형 타법개정 헤더('부칙(통계법 시행령) <제20331호,…>')도 매칭 — 괄호형을 # 놓치면 키가 비어 매 실행마다 같은 부칙을 재추가한다 (2026-08-15 멱등성 결함 정정). 부칙헤더 = re.compile(r"부칙\s*(?:\([^)\n]*\))?\s*<[^>]*>") def 부칙키(text): """'부칙 <제20903호,2025.4.2>' → 정규화 키.""" m = 부칙헤더.search(text or "") return re.sub(r"\s+", "", m.group(0)) if m else None def 목텍스트(mo): return clean(T(mo, "목내용")) or clean(" ".join(x for x in mo.itertext() if x)) def patch(md_path, xml_path): """(부칙 추가수, 목 추가수, 목 앵커실패수)""" root = ET.parse(xml_path).getroot() text = md_path.read_text(encoding="utf-8") lines = text.splitlines() 부칙추가, 목추가, 앵커실패 = 0, 0, 0 # ── 1) 목 삽입 (뒤에서부터 삽입해 인덱스 밀림 방지) ── 조문 = root.find("조문") 작업 = [] if 조문 is not None: for j in 조문.findall("조문단위"): for h in j.findall("항"): 앵커 = None # 직전 호 내용 for kid in h: if kid.tag == "호": 앵커 = clean(T(kid, "호내용")) for mo in kid.findall("목"): 작업.append((앵커, 목텍스트(mo))) elif kid.tag == "목": 작업.append((앵커, 목텍스트(kid))) for ho in j.findall("호"): 앵커 = clean(T(ho, "호내용")) for mo in ho.findall("목"): 작업.append((앵커, 목텍스트(mo))) # 앵커별로 묶어 한 번에 삽입 묶음 = {} for 앵커, mv in 작업: if not mv or mv in text: # 이미 있으면 건너뜀 continue if not 앵커: 앵커실패 += 1 continue 묶음.setdefault(앵커, []).append(mv) for 앵커, 목들 in 묶음.items(): idx = next((i for i, l in enumerate(lines) if l.strip() == 앵커.strip()), None) if idx is None: idx = next((i for i, l in enumerate(lines) if 앵커.strip() and 앵커.strip() in l), None) if idx is None: 앵커실패 += len(목들) continue lines[idx + 1 : idx + 1] = [f" {mv}" for mv in 목들] 목추가 += len(목들) # ── 2) 부칙 보충 ── 부칙 = root.find("부칙") if 부칙 is not None: # 법령(law)=부칙단위 래퍼 / 행정규칙(admrul)=부칙내용 직속 (2026-08-15 결함 정정 # — 직속형 미처리로 행정규칙 md 22건에 부칙이 통째로 빠져 있었다) units = 부칙.findall("부칙단위") or 부칙.findall("부칙내용") 본문 = "\n".join(lines) 기존키 = {re.sub(r"\s+", "", m) for m in 부칙헤더.findall(본문)} def 본문텍스트(b): return T(b, "부칙내용") if b.tag == "부칙단위" else "".join(b.itertext()) 누락 = [b for b in units if (부칙키(본문텍스트(b)) or "") not in 기존키] if 누락: if "## 부칙" not in 본문: lines += ["", "## 부칙", ""] add = [] for b in 누락: body = clean(본문텍스트(b)) if not body.strip(): continue for ln in body.split("\n"): add.append(f"> {ln}" if ln.strip() else ">") add.append("") 부칙추가 += 1 # 첨부파일 절이 뒤에 있으면 그 앞에, 없으면 끝에 pos = next((i for i, l in enumerate(lines) if l.startswith("## 첨부파일")), len(lines)) lines[pos:pos] = add if 부칙추가 or 목추가: md_path.write_text("\n".join(lines) + "\n", encoding="utf-8") return 부칙추가, 목추가, 앵커실패 def main(dry=False): 합계 = [0, 0, 0] 파일 = 0 for 분류 in 분류목록: base = ROOT / 분류 if not base.is_dir(): continue for folder in sorted(p for p in base.iterdir() if p.is_dir()): for xml_path in sorted(folder.glob("*.xml")): if not STEM.match(xml_path.stem): continue md_path = xml_path.with_suffix(".md") if not md_path.exists(): continue if dry: orig = md_path.read_text(encoding="utf-8") b, m, f = patch(md_path, xml_path) if dry: md_path.write_text(orig, encoding="utf-8") if b or m or f: 파일 += 1 print( f" {분류}/{folder.name}/{md_path.name}: 부칙+{b} 목+{m}" + (f" (앵커실패 {f})" if f else "") ) 합계[0] += b 합계[1] += m 합계[2] += f print( f"\n파일 {파일}건 / 부칙 +{합계[0]} · 목 +{합계[1]}" + (f" · 앵커실패 {합계[2]}" if 합계[2] else "") + (" (dry-run — 원복함)" if dry else " 저장 완료") ) if __name__ == "__main__": main(dry="--dry" in sys.argv)