aiaiteam commited on
Commit
3f4dcc3
·
1 Parent(s): 22e0fb1

Simplify hwpx: plain UTF-8 text with .hwpx extension

Browse files
Files changed (1) hide show
  1. app.py +5 -83
app.py CHANGED
@@ -306,99 +306,21 @@ def generate(req: GenerateRequest):
306
 
307
  @app.post("/download/hwpx")
308
  def download_hwpx(req: HwpxRequest):
309
- """텍스트를 HWPX(한글과컴퓨터) 파일로 변환해 다운로드."""
310
- import io
311
  import re
312
- import zipfile
313
  from urllib.parse import quote
314
- from xml.sax.saxutils import escape as xe
315
  from fastapi.responses import Response
316
 
317
- lines = req.text.split("\n")
318
-
319
- CONTAINER = (
320
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
321
- '<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container">\n'
322
- ' <rootfiles>\n'
323
- ' <rootfile full-path="Contents/content.hpf" media-type="application/hwp+zip"/>\n'
324
- ' </rootfiles>\n'
325
- '</container>'
326
- )
327
- HPF = (
328
- '<?xml version="1.0" encoding="UTF-8"?>\n'
329
- '<opf:package xmlns:opf="http://www.idpf.org/2007/opf" version="2.0">\n'
330
- ' <opf:metadata>'
331
- '<dc:title xmlns:dc="http://purl.org/dc/elements/1.1/"/>'
332
- '<dc:language xmlns:dc="http://purl.org/dc/elements/1.1/">ko</dc:language>'
333
- '</opf:metadata>\n'
334
- ' <opf:manifest>\n'
335
- ' <opf:item id="header" href="header.xml" media-type="application/xml"/>\n'
336
- ' <opf:item id="section0" href="section0.xml" media-type="application/xml"/>\n'
337
- ' </opf:manifest>\n'
338
- ' <opf:spine><opf:itemref idref="section0"/></opf:spine>\n'
339
- '</opf:package>'
340
- )
341
- HEADER = (
342
- '<?xml version="1.0" encoding="UTF-8"?>\n'
343
- '<hh:Head xmlns:hh="http://www.hancom.co.kr/hwpml/2011/head"\n'
344
- ' xmlns:hf="http://www.hancom.co.kr/hwpml/2011/charshape"\n'
345
- ' xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph">\n'
346
- ' <hh:RefList>\n'
347
- ' <hh:CharShapes>\n'
348
- ' <hf:CharShape Id="0" Height="1000" TextColor="0" BoldWeight="400"\n'
349
- ' FaceNameHangul="맑은 고딕" FaceNameLatin="맑은 고딕"\n'
350
- ' FaceNameHanja="맑은 고딕" Ratio="100" Spacing="0"/>\n'
351
- ' </hh:CharShapes>\n'
352
- ' <hh:ParaShapes>\n'
353
- ' <hp:ParaShape Id="0" AlignType="Justify"\n'
354
- ' LineSpacing="160" LineSpacingType="PercentsOfLine"\n'
355
- ' SpaceBefore="0" SpaceAfter="85"/>\n'
356
- ' </hh:ParaShapes>\n'
357
- ' <hh:Styles>\n'
358
- ' <hh:Style Id="0" EngName="Normal" HangulName="바탕글"\n'
359
- ' CharPrIDRef="0" ParaPrIDRef="0"/>\n'
360
- ' </hh:Styles>\n'
361
- ' </hh:RefList>\n'
362
- '</hh:Head>'
363
- )
364
-
365
- def _para(pid, line):
366
- txt = xe(line)
367
- run = (f'<hp:Run><hp:RPr CharPrIDRef="0"/><hp:T>{txt}</hp:T></hp:Run>'
368
- if txt else "")
369
- return (f' <hp:P Id="{pid}">'
370
- f'<hp:PPr ParaPrIDRef="0"/>'
371
- f'{run}'
372
- f'</hp:P>')
373
-
374
- body = "\n".join(_para(i, l) for i, l in enumerate(lines))
375
- SECTION = (
376
- '<?xml version="1.0" encoding="UTF-8"?>\n'
377
- '<hh:SEC xmlns:hh="http://www.hancom.co.kr/hwpml/2011/section"\n'
378
- ' xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph">\n'
379
- f'{body}\n'
380
- '</hh:SEC>'
381
- )
382
-
383
- buf = io.BytesIO()
384
- with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
385
- mi = zipfile.ZipInfo("mimetype")
386
- mi.compress_type = zipfile.ZIP_STORED
387
- zf.writestr(mi, "application/hwp+zip")
388
- zf.writestr("META-INF/container.xml", CONTAINER.encode("utf-8"))
389
- zf.writestr("Contents/content.hpf", HPF.encode("utf-8"))
390
- zf.writestr("Contents/header.xml", HEADER.encode("utf-8"))
391
- zf.writestr("Contents/section0.xml", SECTION.encode("utf-8"))
392
-
393
- # 파일명: 출력에서 제목 추출, 없으면 "문서"
394
  m = re.search(r"^제\s*목\s+(.+)$", req.text, re.MULTILINE)
395
  title = m.group(1).strip() if m else "문서"
396
  safe = re.sub(r'[\\/:*?"<>|]', "_", title)[:50]
397
  encoded = quote(safe, safe="")
398
 
 
 
399
  return Response(
400
- content=buf.getvalue(),
401
- media_type="application/hwp+zip",
402
  headers={
403
  "Content-Disposition": f"attachment; filename*=UTF-8''{encoded}.hwpx",
404
  },
 
306
 
307
  @app.post("/download/hwpx")
308
  def download_hwpx(req: HwpxRequest):
309
+ """텍스트를 .hwpx 파일로 다운로드 (UTF-8 텍스트, 확장자 .hwpx)."""
 
310
  import re
 
311
  from urllib.parse import quote
 
312
  from fastapi.responses import Response
313
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  m = re.search(r"^제\s*목\s+(.+)$", req.text, re.MULTILINE)
315
  title = m.group(1).strip() if m else "문서"
316
  safe = re.sub(r'[\\/:*?"<>|]', "_", title)[:50]
317
  encoded = quote(safe, safe="")
318
 
319
+ content = req.text.encode("utf-8-sig") # UTF-8 with BOM
320
+
321
  return Response(
322
+ content=content,
323
+ media_type="application/octet-stream",
324
  headers={
325
  "Content-Disposition": f"attachment; filename*=UTF-8''{encoded}.hwpx",
326
  },