forked from baskbuddy/zhangshun
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
557 lines
19 KiB
557 lines
19 KiB
"""
|
|
职来产品开发方案 Word 格式化脚本
|
|
将原始 docx 转为规范的 A4 企业级文档:封面、目录、多级标题、正文排版、表格、页眉页脚
|
|
"""
|
|
|
|
import copy
|
|
from docx import Document
|
|
from docx.shared import Pt, Cm, RGBColor, Emu
|
|
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
|
|
from docx.enum.table import WD_TABLE_ALIGNMENT
|
|
from docx.enum.section import WD_ORIENT
|
|
from docx.oxml.ns import qn, nsdecls
|
|
from docx.oxml import parse_xml
|
|
|
|
# ⚠️ 历史遗留脚本:原依赖青核项目(collegeCli)目录下的 Word 文档
|
|
# 青核项目已独立为 C:\Users\15642\Desktop\青核\collegeCli
|
|
# 若输入文件不存在,请手动指定 SRC 路径后运行
|
|
SRC = r"C:\Users\15642\Desktop\青核\collegeCli\职来(mydreamjob)产品开发方案-v1.docx"
|
|
OUT = r"C:\Users\15642\Desktop\青核\collegeCli\职来(mydreamjob)产品开发方案-v1-格式化.docx"
|
|
|
|
# ── style mapping ──
|
|
HEADING_STYLE_MAP = {"Heading 1": "Heading 1", "Heading 2": "Heading 2", "Heading 3": "Heading 3"}
|
|
MONO_FONTS = {"JetBrains Mono", "Consolas", "Courier New", "Fira Code", "Source Code Pro"}
|
|
|
|
|
|
def setup_page(doc):
|
|
"""A4, margins"""
|
|
for sec in doc.sections:
|
|
sec.page_width = Cm(21.0)
|
|
sec.page_height = Cm(29.7)
|
|
sec.top_margin = Cm(2.54)
|
|
sec.bottom_margin = Cm(2.54)
|
|
sec.left_margin = Cm(3.18)
|
|
sec.right_margin = Cm(3.18)
|
|
|
|
|
|
def setup_styles(doc):
|
|
"""Define heading & body styles"""
|
|
# ── Normal (body text) ──
|
|
style = doc.styles["Normal"]
|
|
style.font.name = "宋体"
|
|
style.font.size = Pt(12) # 小四
|
|
style.font.color.rgb = RGBColor(0x00, 0x00, 0x00)
|
|
style.paragraph_format.line_spacing = 1.5
|
|
style.paragraph_format.space_before = Pt(0)
|
|
style.paragraph_format.space_after = Pt(0)
|
|
style.paragraph_format.first_line_indent = Pt(24) # 2 chars ≈ 24pt
|
|
# Set east-asian font
|
|
rPr = style.element.get_or_add_rPr()
|
|
rFonts = rPr.find(qn("w:rFonts"))
|
|
if rFonts is None:
|
|
rFonts = parse_xml(f'<w:rFonts {nsdecls("w")} w:eastAsia="宋体"/>')
|
|
rPr.append(rFonts)
|
|
else:
|
|
rFonts.set(qn("w:eastAsia"), "宋体")
|
|
|
|
# ── Heading 1 ──
|
|
h1 = doc.styles["Heading 1"]
|
|
h1.font.name = "黑体"
|
|
h1.font.size = Pt(18) # 小二
|
|
h1.font.bold = True
|
|
h1.font.color.rgb = RGBColor(0x00, 0x00, 0x00)
|
|
h1.paragraph_format.space_before = Pt(18)
|
|
h1.paragraph_format.space_after = Pt(12)
|
|
h1.paragraph_format.line_spacing = 1.5
|
|
h1.paragraph_format.first_line_indent = Pt(0)
|
|
h1.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
rPr = h1.element.get_or_add_rPr()
|
|
rFonts = rPr.find(qn("w:rFonts"))
|
|
if rFonts is None:
|
|
rFonts = parse_xml(f'<w:rFonts {nsdecls("w")} w:eastAsia="黑体"/>')
|
|
rPr.append(rFonts)
|
|
else:
|
|
rFonts.set(qn("w:eastAsia"), "黑体")
|
|
|
|
# ── Heading 2 ──
|
|
h2 = doc.styles["Heading 2"]
|
|
h2.font.name = "黑体"
|
|
h2.font.size = Pt(15) # 小三
|
|
h2.font.bold = True
|
|
h2.font.color.rgb = RGBColor(0x00, 0x00, 0x00)
|
|
h2.paragraph_format.space_before = Pt(12)
|
|
h2.paragraph_format.space_after = Pt(6)
|
|
h2.paragraph_format.line_spacing = 1.5
|
|
h2.paragraph_format.first_line_indent = Pt(0)
|
|
h2.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
rPr = h2.element.get_or_add_rPr()
|
|
rFonts = rPr.find(qn("w:rFonts"))
|
|
if rFonts is None:
|
|
rFonts = parse_xml(f'<w:rFonts {nsdecls("w")} w:eastAsia="黑体"/>')
|
|
rPr.append(rFonts)
|
|
else:
|
|
rFonts.set(qn("w:eastAsia"), "黑体")
|
|
|
|
# ── Heading 3 ──
|
|
h3 = doc.styles["Heading 3"]
|
|
h3.font.name = "黑体"
|
|
h3.font.size = Pt(14) # 四号
|
|
h3.font.bold = True
|
|
h3.font.color.rgb = RGBColor(0x00, 0x00, 0x00)
|
|
h3.paragraph_format.space_before = Pt(8)
|
|
h3.paragraph_format.space_after = Pt(4)
|
|
h3.paragraph_format.line_spacing = 1.5
|
|
h3.paragraph_format.first_line_indent = Pt(0)
|
|
h3.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
rPr = h3.element.get_or_add_rPr()
|
|
rFonts = rPr.find(qn("w:rFonts"))
|
|
if rFonts is None:
|
|
rFonts = parse_xml(f'<w:rFonts {nsdecls("w")} w:eastAsia="黑体"/>')
|
|
rPr.append(rFonts)
|
|
else:
|
|
rFonts.set(qn("w:eastAsia"), "黑体")
|
|
|
|
|
|
def setup_numbering(doc):
|
|
"""Multi-level heading auto-numbering: 1 / 1.1 / 1.1.1"""
|
|
# Create a numbering part if it doesn't exist
|
|
numbering_part = doc.part.numbering_part
|
|
numbering = numbering_part.element
|
|
|
|
# Abstract numbering
|
|
abstract_num_id = 0
|
|
abstract_num = parse_xml(
|
|
f'<w:abstractNum {nsdecls("w")} w:abstractNumId="{abstract_num_id}">'
|
|
f' <w:multiLevelType w:val="hybridMultilevel"/>'
|
|
f' <w:lvl w:ilvl="0">'
|
|
f' <w:start w:val="1"/>'
|
|
f' <w:numFmt w:val="decimal"/>'
|
|
f' <w:lvlText w:val="%1."/>'
|
|
f' <w:lvlJc w:val="left"/>'
|
|
f' <w:pPr><w:ind w:left="0" w:hanging="420"/></w:pPr>'
|
|
f' </w:lvl>'
|
|
f' <w:lvl w:ilvl="1">'
|
|
f' <w:start w:val="1"/>'
|
|
f' <w:numFmt w:val="decimal"/>'
|
|
f' <w:lvlText w:val="%1.%2"/>'
|
|
f' <w:lvlJc w:val="left"/>'
|
|
f' <w:pPr><w:ind w:left="420" w:hanging="420"/></w:pPr>'
|
|
f' </w:lvl>'
|
|
f' <w:lvl w:ilvl="2">'
|
|
f' <w:start w:val="1"/>'
|
|
f' <w:numFmt w:val="decimal"/>'
|
|
f' <w:lvlText w:val="%1.%2.%3"/>'
|
|
f' <w:lvlJc w:val="left"/>'
|
|
f' <w:pPr><w:ind w:left="840" w:hanging="420"/></w:pPr>'
|
|
f' </w:lvl>'
|
|
f'</w:abstractNum>'
|
|
)
|
|
numbering.insert(0, abstract_num)
|
|
|
|
# Numbering instance
|
|
num_id = 1
|
|
num = parse_xml(
|
|
f'<w:num {nsdecls("w")} w:numId="{num_id}">'
|
|
f' <w:abstractNumId w:val="{abstract_num_id}"/>'
|
|
f'</w:num>'
|
|
)
|
|
numbering.append(num)
|
|
|
|
# Link numbering to heading styles
|
|
for level, style_name in [(0, "Heading 1"), (1, "Heading 2"), (2, "Heading 3")]:
|
|
style = doc.styles[style_name]
|
|
pPr = style.element.get_or_add_pPr()
|
|
numPr = pPr.find(qn("w:numPr"))
|
|
if numPr is not None:
|
|
pPr.remove(numPr)
|
|
numPr = parse_xml(
|
|
f'<w:numPr {nsdecls("w")}>'
|
|
f' <w:ilvl w:val="{level}"/>'
|
|
f' <w:numId w:val="{num_id}"/>'
|
|
f'</w:numPr>'
|
|
)
|
|
pPr.append(numPr)
|
|
|
|
|
|
def add_cover_page(doc):
|
|
"""Professional cover page"""
|
|
for _ in range(6):
|
|
doc.add_paragraph("")
|
|
|
|
title_p = doc.add_paragraph("")
|
|
title_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
title_run = title_p.add_run("职来(mydreamjob)产品开发方案")
|
|
title_run.font.name = "黑体"
|
|
title_run.font.size = Pt(26)
|
|
title_run.font.bold = True
|
|
title_run.font.color.rgb = RGBColor(0x00, 0x00, 0x00)
|
|
rPr = title_run._r.get_or_add_rPr()
|
|
rFonts = parse_xml(f'<w:rFonts {nsdecls("w")} w:eastAsia="黑体"/>')
|
|
rPr.append(rFonts)
|
|
|
|
doc.add_paragraph("")
|
|
|
|
subtitle_p = doc.add_paragraph("")
|
|
subtitle_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
sub_run = subtitle_p.add_run("面向大学生与应届生的可信求职平台")
|
|
sub_run.font.name = "宋体"
|
|
sub_run.font.size = Pt(16)
|
|
sub_run.font.color.rgb = RGBColor(0x44, 0x44, 0x44)
|
|
|
|
for _ in range(8):
|
|
doc.add_paragraph("")
|
|
|
|
meta_items = [
|
|
("文档版本:", "v1.0"),
|
|
("编制日期:", "2026 年 7 月 30 日"),
|
|
("密级:", "内部"),
|
|
("编制:", "项目组"),
|
|
("送阅对象:", "公司负责人 / 投资人"),
|
|
]
|
|
for label, value in meta_items:
|
|
mp = doc.add_paragraph("")
|
|
mp.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
lr = mp.add_run(label)
|
|
lr.font.name = "宋体"
|
|
lr.font.size = Pt(12)
|
|
vr = mp.add_run(value)
|
|
vr.font.name = "宋体"
|
|
vr.font.size = Pt(12)
|
|
vr.font.bold = True
|
|
|
|
doc.add_page_break()
|
|
|
|
|
|
def add_toc(doc):
|
|
"""Insert TOC field (populated when opened in Word)"""
|
|
toc_heading = doc.add_paragraph("目 录")
|
|
toc_heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
toc_heading.style = doc.styles["Normal"]
|
|
toc_heading.paragraph_format.first_line_indent = Pt(0)
|
|
toc_heading.paragraph_format.space_after = Pt(12)
|
|
for run in toc_heading.runs:
|
|
run.font.name = "黑体"
|
|
run.font.size = Pt(18)
|
|
run.font.bold = True
|
|
|
|
# Insert TOC field
|
|
paragraph = doc.add_paragraph("")
|
|
paragraph.paragraph_format.first_line_indent = Pt(0)
|
|
run = paragraph.add_run()
|
|
fldChar1 = parse_xml(f'<w:fldChar {nsdecls("w")} w:fldCharType="begin"/>')
|
|
run._r.append(fldChar1)
|
|
|
|
run2 = paragraph.add_run()
|
|
instrText = parse_xml(
|
|
f'<w:instrText {nsdecls("w")} xml:space="preserve"> TOC \\o "1-3" \\h \\z \\u </w:instrText>'
|
|
)
|
|
run2._r.append(instrText)
|
|
|
|
run3 = paragraph.add_run()
|
|
fldChar2 = parse_xml(f'<w:fldChar {nsdecls("w")} w:fldCharType="separate"/>')
|
|
run3._r.append(fldChar2)
|
|
|
|
run4 = paragraph.add_run('(请在 Word 中右键此处,选择"更新域"以生成目录)')
|
|
run4.font.name = "宋体"
|
|
run4.font.size = Pt(10)
|
|
run4.font.color.rgb = RGBColor(0x99, 0x99, 0x99)
|
|
|
|
run5 = paragraph.add_run()
|
|
fldChar3 = parse_xml(f'<w:fldChar {nsdecls("w")} w:fldCharType="end"/>')
|
|
run5._r.append(fldChar3)
|
|
|
|
|
|
def add_header_footer(doc):
|
|
"""Header with doc title, footer with page number"""
|
|
for sec in doc.sections:
|
|
# Header
|
|
header = sec.header
|
|
header.is_linked_to_previous = False
|
|
hp = header.paragraphs[0] if header.paragraphs else header.add_paragraph()
|
|
hp.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
hp.paragraph_format.first_line_indent = Pt(0)
|
|
hr = hp.add_run("职来(mydreamjob)产品开发方案")
|
|
hr.font.name = "宋体"
|
|
hr.font.size = Pt(9)
|
|
hr.font.color.rgb = RGBColor(0x88, 0x88, 0x88)
|
|
# Add bottom border
|
|
pPr = hp._p.get_or_add_pPr()
|
|
pBdr = parse_xml(
|
|
f'<w:pBdr {nsdecls("w")}>'
|
|
f' <w:bottom w:val="single" w:sz="4" w:space="1" w:color="CCCCCC"/>'
|
|
f'</w:pBdr>'
|
|
)
|
|
pPr.append(pBdr)
|
|
|
|
# Footer
|
|
footer = sec.footer
|
|
footer.is_linked_to_previous = False
|
|
fp = footer.paragraphs[0] if footer.paragraphs else footer.add_paragraph()
|
|
fp.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
fp.paragraph_format.first_line_indent = Pt(0)
|
|
# Page number field
|
|
fr1 = fp.add_run()
|
|
fldChar1 = parse_xml(f'<w:fldChar {nsdecls("w")} w:fldCharType="begin"/>')
|
|
fr1._r.append(fldChar1)
|
|
fr2 = fp.add_run()
|
|
instrText = parse_xml(f'<w:instrText {nsdecls("w")} xml:space="preserve"> PAGE </w:instrText>')
|
|
fr2._r.append(instrText)
|
|
fr3 = fp.add_run()
|
|
fldChar2 = parse_xml(f'<w:fldChar {nsdecls("w")} w:fldCharType="separate"/>')
|
|
fr3._r.append(fldChar2)
|
|
fr4 = fp.add_run("1")
|
|
fr4.font.size = Pt(9)
|
|
fr5 = fp.add_run()
|
|
fldChar3 = parse_xml(f'<w:fldChar {nsdecls("w")} w:fldCharType="end"/>')
|
|
fr5._r.append(fldChar3)
|
|
|
|
|
|
def is_code_paragraph(p):
|
|
"""Detect code/monospace paragraphs"""
|
|
if not p.runs:
|
|
return False
|
|
for r in p.runs:
|
|
if r.font.name and r.font.name in MONO_FONTS:
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_separator(text):
|
|
"""Detect separator lines"""
|
|
s = text.strip()
|
|
if not s:
|
|
return False
|
|
# Check if mostly dashes or special separator chars
|
|
dash_chars = {"─", "—", "-", "=", "━", "┅", "―"}
|
|
total = len(s)
|
|
dash_count = sum(1 for c in s if c in dash_chars)
|
|
return dash_count / total > 0.6 and total > 10
|
|
|
|
|
|
def fixup_font_eastasia(run, font_name):
|
|
"""Set east-Asian font on a run"""
|
|
rPr = run._r.get_or_add_rPr()
|
|
rFonts_elem = rPr.find(qn("w:rFonts"))
|
|
if rFonts_elem is None:
|
|
rFonts_elem = parse_xml(f'<w:rFonts {nsdecls("w")} w:eastAsia="{font_name}"/>')
|
|
rPr.append(rFonts_elem)
|
|
else:
|
|
rFonts_elem.set(qn("w:eastAsia"), font_name)
|
|
|
|
|
|
def copy_paragraph(doc, src_p, heading_counter):
|
|
"""Copy a paragraph with style-aware formatting and heading auto-numbering"""
|
|
src_style = src_p.style.name if src_p.style else "Normal"
|
|
text = src_p.text.strip()
|
|
|
|
# Determine target style
|
|
if src_style in HEADING_STYLE_MAP:
|
|
target_style = HEADING_STYLE_MAP[src_style]
|
|
# Increment heading counter
|
|
if src_style == "Heading 1":
|
|
heading_counter[0] += 1
|
|
heading_counter[1] = 0
|
|
heading_counter[2] = 0
|
|
elif src_style == "Heading 2":
|
|
heading_counter[1] += 1
|
|
heading_counter[2] = 0
|
|
elif src_style == "Heading 3":
|
|
heading_counter[2] += 1
|
|
new_p = doc.add_paragraph(style=target_style)
|
|
else:
|
|
new_p = doc.add_paragraph(style="Normal")
|
|
|
|
# Handle separator
|
|
if is_separator(text):
|
|
new_p.paragraph_format.first_line_indent = Pt(0)
|
|
new_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
run = new_p.add_run("· · ·")
|
|
run.font.name = "宋体"
|
|
run.font.size = Pt(10)
|
|
run.font.color.rgb = RGBColor(0xCC, 0xCC, 0xCC)
|
|
fixup_font_eastasia(run, "宋体")
|
|
return
|
|
|
|
# Reset first-line indent for non-body paragraphs
|
|
if src_style in HEADING_STYLE_MAP or src_style == "List Paragraph":
|
|
new_p.paragraph_format.first_line_indent = Pt(0)
|
|
|
|
# Handle List Paragraph style
|
|
if src_style == "List Paragraph":
|
|
new_p.paragraph_format.left_indent = Cm(1.0)
|
|
new_p.paragraph_format.first_line_indent = Cm(-0.5)
|
|
|
|
# Check for code paragraphs
|
|
is_code = is_code_paragraph(src_p)
|
|
|
|
# Copy runs
|
|
for src_run in src_p.runs:
|
|
new_run = new_p.add_run(src_run.text)
|
|
|
|
# Determine font
|
|
if is_code:
|
|
new_run.font.name = "Consolas"
|
|
new_run.font.size = Pt(9.5)
|
|
elif src_style in HEADING_STYLE_MAP:
|
|
# Heading font handled by style
|
|
pass
|
|
else:
|
|
new_run.font.name = "宋体"
|
|
new_run.font.size = Pt(12)
|
|
fixup_font_eastasia(new_run, "宋体")
|
|
|
|
# Copy bold
|
|
if src_run.bold is not None:
|
|
new_run.bold = src_run.bold
|
|
|
|
# Copy italic
|
|
if src_run.italic is not None:
|
|
new_run.italic = src_run.italic
|
|
|
|
return new_p
|
|
|
|
|
|
def format_table(new_tbl):
|
|
"""Apply uniform formatting to a table"""
|
|
new_tbl.alignment = WD_TABLE_ALIGNMENT.CENTER
|
|
# Table borders
|
|
tbl = new_tbl._tbl
|
|
tblPr = tbl.tblPr if tbl.tblPr is not None else parse_xml(f'<w:tblPr {nsdecls("w")}/>')
|
|
borders = parse_xml(
|
|
f'<w:tblBorders {nsdecls("w")}>'
|
|
f' <w:top w:val="single" w:sz="4" w:space="0" w:color="333333"/>'
|
|
f' <w:left w:val="single" w:sz="4" w:space="0" w:color="333333"/>'
|
|
f' <w:bottom w:val="single" w:sz="4" w:space="0" w:color="333333"/>'
|
|
f' <w:right w:val="single" w:sz="4" w:space="0" w:color="333333"/>'
|
|
f' <w:insideH w:val="single" w:sz="4" w:space="0" w:color="CCCCCC"/>'
|
|
f' <w:insideV w:val="single" w:sz="4" w:space="0" w:color="CCCCCC"/>'
|
|
f'</w:tblBorders>'
|
|
)
|
|
existing = tblPr.find(qn("w:tblBorders"))
|
|
if existing is not None:
|
|
tblPr.remove(existing)
|
|
tblPr.append(borders)
|
|
|
|
# Format header row (first row)
|
|
for ri, row in enumerate(new_tbl.rows):
|
|
for cell in row.cells:
|
|
for p in cell.paragraphs:
|
|
p.paragraph_format.space_before = Pt(2)
|
|
p.paragraph_format.space_after = Pt(2)
|
|
p.paragraph_format.line_spacing = 1.15
|
|
p.paragraph_format.first_line_indent = Pt(0)
|
|
for run in p.runs:
|
|
if ri == 0:
|
|
run.font.name = "黑体"
|
|
run.font.size = Pt(10.5)
|
|
run.font.bold = True
|
|
fixup_font_eastasia(run, "黑体")
|
|
else:
|
|
run.font.name = "宋体"
|
|
run.font.size = Pt(10.5)
|
|
fixup_font_eastasia(run, "宋体")
|
|
|
|
# Set column widths based on column count
|
|
ncols = len(new_tbl.columns)
|
|
if ncols > 0:
|
|
col_width = Cm(14.0 / ncols) # approx usable width
|
|
for col in new_tbl.columns:
|
|
for cell in col.cells:
|
|
cell.width = col_width
|
|
|
|
|
|
def copy_table(doc, src_tbl, table_index):
|
|
"""Copy a table from source to target document with formatting"""
|
|
# Add table caption
|
|
caption_p = doc.add_paragraph("")
|
|
caption_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
caption_p.paragraph_format.first_line_indent = Pt(0)
|
|
caption_p.paragraph_format.space_before = Pt(12)
|
|
caption_p.paragraph_format.space_after = Pt(4)
|
|
cr = caption_p.add_run(f"表 {table_index + 1}")
|
|
cr.font.name = "黑体"
|
|
cr.font.size = Pt(10.5)
|
|
cr.font.bold = True
|
|
fixup_font_eastasia(cr, "黑体")
|
|
|
|
# Create new table
|
|
nrows = len(src_tbl.rows)
|
|
ncols = len(src_tbl.columns)
|
|
new_tbl = doc.add_table(rows=nrows, cols=ncols)
|
|
|
|
# Copy cell contents
|
|
for ri, src_row in enumerate(src_tbl.rows):
|
|
for ci, src_cell in enumerate(src_row.cells):
|
|
new_cell = new_tbl.rows[ri].cells[ci]
|
|
# Clear default paragraph
|
|
new_cell.paragraphs[0].clear()
|
|
# Copy paragraphs from source
|
|
for sp in src_cell.paragraphs:
|
|
np = new_cell.add_paragraph(sp.text) if sp.text.strip() else new_cell.paragraphs[0]
|
|
if not sp.text.strip() and new_cell.paragraphs[0].text == "":
|
|
continue
|
|
for sr in sp.runs:
|
|
nr = np.add_run(sr.text) if np != new_cell.paragraphs[0] else new_cell.paragraphs[0].add_run(sr.text)
|
|
if ri == 0:
|
|
nr.font.bold = True
|
|
|
|
format_table(new_tbl)
|
|
return new_tbl
|
|
|
|
|
|
def main():
|
|
import os
|
|
if not os.path.exists(SRC):
|
|
print(f"❌ 输入文件不存在: {SRC}")
|
|
print(" 请手动指定 SRC 路径后重试(本脚本为历史遗留脚本,依赖青核项目目录)")
|
|
return
|
|
print("Reading source document...")
|
|
src = Document(SRC)
|
|
print(f" {len(src.paragraphs)} paragraphs, {len(src.tables)} tables")
|
|
|
|
print("Creating new document...")
|
|
doc = Document()
|
|
setup_page(doc)
|
|
setup_styles(doc)
|
|
# 原文档已有一、二、三...手工编号,不叠自动编号以避免冲突
|
|
# setup_numbering(doc)
|
|
|
|
print("Adding cover page...")
|
|
add_cover_page(doc)
|
|
|
|
print("Adding TOC...")
|
|
add_toc(doc)
|
|
|
|
print("Copying content...")
|
|
heading_counter = [0, 0, 0] # [H1, H2, H3]
|
|
|
|
# Walk through body elements in original order
|
|
body = src.element.body
|
|
para_idx = 0
|
|
tbl_idx = 0
|
|
|
|
for child in body:
|
|
tag = child.tag.split("}")[-1] if "}" in child.tag else child.tag
|
|
if tag == "p":
|
|
# Paragraph
|
|
if para_idx < len(src.paragraphs):
|
|
copy_paragraph(doc, src.paragraphs[para_idx], heading_counter)
|
|
para_idx += 1
|
|
elif tag == "tbl":
|
|
# Table
|
|
if tbl_idx < len(src.tables):
|
|
copy_table(doc, src.tables[tbl_idx], tbl_idx)
|
|
tbl_idx += 1
|
|
|
|
print(f" Processed {para_idx} paragraphs, {tbl_idx} tables")
|
|
|
|
print("Adding header/footer...")
|
|
add_header_footer(doc)
|
|
|
|
print(f"Saving to {OUT}...")
|
|
doc.save(OUT)
|
|
print("Done!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|