fix: HWP 추출 오류 진단 강화 + openstream 문자열 경로로 변경
Browse files- _ext_hwp: list 대신 문자열 경로 사용 (BodyText/SectionN)
- _ext_hwp: OLE 항목 대소문자 무시 비교
- _ext_hwp: 각 단계별 명시적 오류 메시지 + print 로그
- _ext_hwp: zlib 실패 시 일반 zlib 재시도 후 raw 폴백
- /extract: str(e) 빈 문자열이면 type name 대신 사용
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
app.py
CHANGED
|
@@ -290,32 +290,55 @@ def _ext_hwp(path: Path) -> str:
|
|
| 290 |
try:
|
| 291 |
import olefile
|
| 292 |
except ImportError:
|
| 293 |
-
raise RuntimeError("olefile
|
| 294 |
|
| 295 |
-
|
|
|
|
|
|
|
|
|
|
| 296 |
|
| 297 |
compressed = True
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 309 |
if not sections:
|
| 310 |
-
|
|
|
|
| 311 |
|
| 312 |
lines: list[str] = []
|
| 313 |
for sec_name in sections:
|
| 314 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
try:
|
| 316 |
data = zlib.decompress(raw, -15) if compressed else raw
|
| 317 |
except zlib.error:
|
| 318 |
-
|
|
|
|
|
|
|
|
|
|
| 319 |
|
| 320 |
pos = 0
|
| 321 |
while pos + 4 <= len(data):
|
|
@@ -337,7 +360,7 @@ def _ext_hwp(path: Path) -> str:
|
|
| 337 |
if cp == 0x0D:
|
| 338 |
lines.append("".join(chars)); chars = []
|
| 339 |
elif 0x01 <= cp <= 0x0C:
|
| 340 |
-
t += 8
|
| 341 |
elif cp >= 0x20:
|
| 342 |
chars.append(chr(cp))
|
| 343 |
if chars:
|
|
@@ -428,7 +451,8 @@ async def extract_file(file: UploadFile = File(...)):
|
|
| 428 |
else:
|
| 429 |
text = _ext_txt(tmp_path)
|
| 430 |
except Exception as e:
|
| 431 |
-
|
|
|
|
| 432 |
finally:
|
| 433 |
tmp_path.unlink(missing_ok=True)
|
| 434 |
|
|
|
|
| 290 |
try:
|
| 291 |
import olefile
|
| 292 |
except ImportError:
|
| 293 |
+
raise RuntimeError("olefile 미설치 (pip install olefile)")
|
| 294 |
|
| 295 |
+
try:
|
| 296 |
+
ole = olefile.OleFileIO(str(path))
|
| 297 |
+
except Exception as exc:
|
| 298 |
+
raise RuntimeError(f"HWP OLE 열기 실패 [{type(exc).__name__}]: {exc or 'no detail'}")
|
| 299 |
|
| 300 |
compressed = True
|
| 301 |
+
try:
|
| 302 |
+
if ole.exists("FileHeader"):
|
| 303 |
+
hdr = ole.openstream("FileHeader").read()
|
| 304 |
+
if len(hdr) >= 40:
|
| 305 |
+
compressed = bool(struct.unpack_from("<I", hdr, 36)[0] & 0x1)
|
| 306 |
+
except Exception:
|
| 307 |
+
pass
|
| 308 |
+
|
| 309 |
+
# Discover sections: case-insensitive, any naming convention
|
| 310 |
+
try:
|
| 311 |
+
all_entries = ole.listdir()
|
| 312 |
+
print(f"[HWP] OLE entries: {all_entries[:20]}", flush=True)
|
| 313 |
+
sections = sorted(
|
| 314 |
+
(e[1] for e in all_entries
|
| 315 |
+
if isinstance(e, (list, tuple)) and len(e) >= 2
|
| 316 |
+
and str(e[0]).lower() == "bodytext"
|
| 317 |
+
and str(e[1]).lower().startswith("section")),
|
| 318 |
+
key=lambda s: int(re.sub(r"\D", "", s) or "0"),
|
| 319 |
+
)
|
| 320 |
+
except Exception as exc:
|
| 321 |
+
raise RuntimeError(f"섹션 목록 오류 [{type(exc).__name__}]: {exc or 'no detail'}")
|
| 322 |
+
|
| 323 |
if not sections:
|
| 324 |
+
entry_dump = str([str(e) for e in all_entries[:20]])
|
| 325 |
+
raise RuntimeError(f"BodyText/Section* 없음. OLE 항목: {entry_dump}")
|
| 326 |
|
| 327 |
lines: list[str] = []
|
| 328 |
for sec_name in sections:
|
| 329 |
+
try:
|
| 330 |
+
raw = ole.openstream(f"BodyText/{sec_name}").read()
|
| 331 |
+
except Exception as exc:
|
| 332 |
+
print(f"[HWP] openstream {sec_name} 오류: {exc}", flush=True)
|
| 333 |
+
continue
|
| 334 |
+
|
| 335 |
try:
|
| 336 |
data = zlib.decompress(raw, -15) if compressed else raw
|
| 337 |
except zlib.error:
|
| 338 |
+
try:
|
| 339 |
+
data = zlib.decompress(raw)
|
| 340 |
+
except zlib.error:
|
| 341 |
+
data = raw
|
| 342 |
|
| 343 |
pos = 0
|
| 344 |
while pos + 4 <= len(data):
|
|
|
|
| 360 |
if cp == 0x0D:
|
| 361 |
lines.append("".join(chars)); chars = []
|
| 362 |
elif 0x01 <= cp <= 0x0C:
|
| 363 |
+
t += 8
|
| 364 |
elif cp >= 0x20:
|
| 365 |
chars.append(chr(cp))
|
| 366 |
if chars:
|
|
|
|
| 451 |
else:
|
| 452 |
text = _ext_txt(tmp_path)
|
| 453 |
except Exception as e:
|
| 454 |
+
detail = str(e).strip() or type(e).__name__
|
| 455 |
+
raise HTTPException(500, detail)
|
| 456 |
finally:
|
| 457 |
tmp_path.unlink(missing_ok=True)
|
| 458 |
|