""" 职来产品开发方案 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'') 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'') 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'') 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'') 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'' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f' ' f'' ) numbering.insert(0, abstract_num) # Numbering instance num_id = 1 num = parse_xml( f'' f' ' f'' ) 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'' f' ' f' ' f'' ) 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'') 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'') run._r.append(fldChar1) run2 = paragraph.add_run() instrText = parse_xml( f' TOC \\o "1-3" \\h \\z \\u ' ) run2._r.append(instrText) run3 = paragraph.add_run() fldChar2 = parse_xml(f'') 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'') 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'' f' ' f'' ) 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'') fr1._r.append(fldChar1) fr2 = fp.add_run() instrText = parse_xml(f' PAGE ') fr2._r.append(instrText) fr3 = fp.add_run() fldChar2 = parse_xml(f'') fr3._r.append(fldChar2) fr4 = fp.add_run("1") fr4.font.size = Pt(9) fr5 = fp.add_run() fldChar3 = parse_xml(f'') 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'') 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'') borders = parse_xml( f'' f' ' f' ' f' ' f' ' f' ' f' ' f'' ) 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()