시나리오 내용 반영
This commit is contained in:
1 parent
d9287de729
commit
350bb92bf9
270 files changed
+198867
No files matched your search
@@ -0,0 +1,220 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from docx import Document
|
||||
from docx.oxml.text.paragraph import CT_P
|
||||
from docx.oxml.table import CT_Tbl
|
||||
from docx.text.paragraph import Paragraph
|
||||
from docx.table import Table
|
||||
|
||||
def clean_cell_text(cell):
|
||||
"""Clean and format table cell text into markdown-compatible single line."""
|
||||
# Process cell paragraphs
|
||||
lines = []
|
||||
for p in cell.paragraphs:
|
||||
p_text = format_paragraph(p).strip()
|
||||
if p_text:
|
||||
lines.append(p_text)
|
||||
full_text = "<br>".join(lines)
|
||||
# Escape pipes
|
||||
full_text = full_text.replace("|", r"\|")
|
||||
# Clean excessive whitespace
|
||||
full_text = re.sub(r"[ \t]+", " ", full_text).strip()
|
||||
return full_text
|
||||
|
||||
def format_paragraph(p):
|
||||
"""Format paragraph runs into markdown text with bold/italic."""
|
||||
runs_text = []
|
||||
for r in p.runs:
|
||||
text = r.text
|
||||
if not text:
|
||||
continue
|
||||
# Check formatting
|
||||
is_bold = bool(r.bold)
|
||||
is_italic = bool(r.italic)
|
||||
|
||||
# Don't format pure whitespace runs
|
||||
if text.strip() == "":
|
||||
runs_text.append(text)
|
||||
continue
|
||||
|
||||
prefix_space = " " if text.startswith(" ") else ""
|
||||
suffix_space = " " if text.endswith(" ") else ""
|
||||
core_text = text.strip()
|
||||
|
||||
if is_bold and is_italic:
|
||||
core_text = f"***{core_text}***"
|
||||
elif is_bold:
|
||||
core_text = f"**{core_text}**"
|
||||
elif is_italic:
|
||||
core_text = f"*{core_text}*"
|
||||
|
||||
runs_text.append(prefix_space + core_text + suffix_space)
|
||||
|
||||
res = "".join(runs_text)
|
||||
if not res.strip() and p.text.strip():
|
||||
# Fallback if runs were empty or weird
|
||||
res = p.text.strip()
|
||||
return res
|
||||
|
||||
def convert_paragraph_to_md(p):
|
||||
"""Convert a docx Paragraph to Markdown string."""
|
||||
text = format_paragraph(p).strip()
|
||||
if not text:
|
||||
return ""
|
||||
|
||||
style_name = p.style.name if p.style and p.style.name else ""
|
||||
style_lower = style_name.lower()
|
||||
|
||||
# Check heading styles
|
||||
if "heading 1" in style_lower or style_name in ["제목 1", "Title", "제목"]:
|
||||
return f"# {text}\n"
|
||||
elif "heading 2" in style_lower or style_name in ["제목 2"]:
|
||||
return f"## {text}\n"
|
||||
elif "heading 3" in style_lower or style_name in ["제목 3", "Subtitle", "부제목"]:
|
||||
return f"### {text}\n"
|
||||
elif "heading 4" in style_lower or style_name in ["제목 4"]:
|
||||
return f"#### {text}\n"
|
||||
elif "heading 5" in style_lower or style_name in ["제목 5"]:
|
||||
return f"##### {text}\n"
|
||||
elif "heading 6" in style_lower or style_name in ["제목 6"]:
|
||||
return f"###### {text}\n"
|
||||
|
||||
# Check list styles
|
||||
if "bullet" in style_lower or "글머리" in style_lower:
|
||||
return f"- {text}"
|
||||
if "number" in style_lower or "번호" in style_lower:
|
||||
return f"1. {text}"
|
||||
|
||||
return text
|
||||
|
||||
def convert_table_to_md(table):
|
||||
"""Convert a docx Table to Markdown table format."""
|
||||
if not table.rows:
|
||||
return ""
|
||||
|
||||
rows_data = []
|
||||
max_cols = 0
|
||||
for row in table.rows:
|
||||
row_cells = []
|
||||
for cell in row.cells:
|
||||
cell_text = clean_cell_text(cell)
|
||||
row_cells.append(cell_text)
|
||||
max_cols = max(max_cols, len(row_cells))
|
||||
rows_data.append(row_cells)
|
||||
|
||||
if max_cols == 0:
|
||||
return ""
|
||||
|
||||
# Standardize column count
|
||||
norm_rows = []
|
||||
for row in rows_data:
|
||||
if len(row) < max_cols:
|
||||
row.extend([""] * (max_cols - len(row)))
|
||||
elif len(row) > max_cols:
|
||||
row = row[:max_cols]
|
||||
norm_rows.append(row)
|
||||
|
||||
if not norm_rows:
|
||||
return ""
|
||||
|
||||
md_lines = []
|
||||
# If table has only 1 row, format as key-value or 1-row table with header
|
||||
if len(norm_rows) == 1:
|
||||
# 1-row table: row 0 as header or data row
|
||||
header = norm_rows[0]
|
||||
md_lines.append("| " + " | ".join(header) + " |")
|
||||
md_lines.append("| " + " | ".join(["---"] * max_cols) + " |")
|
||||
else:
|
||||
# Header row
|
||||
header = norm_rows[0]
|
||||
md_lines.append("| " + " | ".join(header) + " |")
|
||||
md_lines.append("| " + " | ".join(["---"] * max_cols) + " |")
|
||||
# Data rows
|
||||
for row in norm_rows[1:]:
|
||||
md_lines.append("| " + " | ".join(row) + " |")
|
||||
|
||||
return "\n".join(md_lines) + "\n"
|
||||
|
||||
def convert_docx_file(docx_path):
|
||||
"""Read a docx file and convert its contents to markdown."""
|
||||
doc = Document(docx_path)
|
||||
md_blocks = []
|
||||
|
||||
for block in doc.element.body:
|
||||
if isinstance(block, CT_P):
|
||||
p = Paragraph(block, doc)
|
||||
md_text = convert_paragraph_to_md(p)
|
||||
if md_text:
|
||||
md_blocks.append(md_text)
|
||||
elif isinstance(block, CT_Tbl):
|
||||
t = Table(block, doc)
|
||||
md_table = convert_table_to_md(t)
|
||||
if md_table:
|
||||
md_blocks.append(md_table)
|
||||
|
||||
content = "\n\n".join(md_blocks)
|
||||
# Normalize consecutive empty lines
|
||||
content = re.sub(r"\n{3,}", "\n\n", content).strip() + "\n"
|
||||
return content
|
||||
|
||||
def run_conversion(src_dir="scenario_docx", dst_dir="scenario_md"):
|
||||
"""Convert all docx files in src_dir to md files in dst_dir, copying existing md files."""
|
||||
src_path = Path(src_dir)
|
||||
dst_path = Path(dst_dir)
|
||||
|
||||
dst_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
converted_count = 0
|
||||
copied_count = 0
|
||||
error_count = 0
|
||||
|
||||
for root, dirs, files in os.walk(src_path):
|
||||
rel_dir = Path(root).relative_to(src_path)
|
||||
target_dir = dst_path / rel_dir
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for file_name in files:
|
||||
src_file = Path(root) / file_name
|
||||
ext = src_file.suffix.lower()
|
||||
|
||||
if ext == ".docx":
|
||||
target_file_name = src_file.stem + ".md"
|
||||
target_file = target_dir / target_file_name
|
||||
try:
|
||||
md_content = convert_docx_file(str(src_file))
|
||||
with open(target_file, "w", encoding="utf-8") as f:
|
||||
f.write(md_content)
|
||||
converted_count += 1
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to convert {src_file}: {e}", file=sys.stderr)
|
||||
error_count += 1
|
||||
elif ext == ".md":
|
||||
target_file = target_dir / file_name
|
||||
try:
|
||||
shutil.copy2(src_file, target_file)
|
||||
copied_count += 1
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to copy {src_file}: {e}", file=sys.stderr)
|
||||
error_count += 1
|
||||
else:
|
||||
# Other files (e.g. assets, images)
|
||||
target_file = target_dir / file_name
|
||||
try:
|
||||
shutil.copy2(src_file, target_file)
|
||||
copied_count += 1
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to copy {src_file}: {e}", file=sys.stderr)
|
||||
error_count += 1
|
||||
|
||||
print("=" * 60)
|
||||
print("Conversion Summary:")
|
||||
print(f" Converted .docx -> .md : {converted_count}")
|
||||
print(f" Copied .md / others : {copied_count}")
|
||||
print(f" Errors : {error_count}")
|
||||
print("=" * 60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_conversion()
|
||||
Reference in new issue
Block a user