商用HTTP代理选型避坑指南:从IP池陷阱到实战测试方法
2026/9/6 9:10:16
# -*- coding: utf-8 -*-"""将软考模拟试卷 Markdown 转为排版良好的 Word 文档。 用法: python md2docx.py <input.md> [output.docx] 支持: 标题层级、表格、列表、引用、代码块、粗体/行内代码、水平线。 """importsysimportosimportioimportre sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding="utf-8",errors="replace")importdocxfromdocximportDocumentfromdocx.sharedimportPt,Cm,RGBColorfromdocx.enum.textimportWD_ALIGN_PARAGRAPH,WD_LINE_SPACINGfromdocx.enum.tableimportWD_TABLE_ALIGNMENTfromdocx.oxml.nsimportqnfromdocx.oxmlimportOxmlElementdefset_font(run,name="宋体",ascii_font=None,size=None,bold=None,color=None):"""统一设置中文字体。"""run.font.name=ascii_fontorname r=run._element.rPrifrisnotNone:rFonts=r.find(qn("w:rFonts"))ifrFontsisNone:rFonts=OxmlElement("w:rFonts")r.append(rFonts)rFonts.set(qn("w:eastAsia"),name)ifascii_font:rFonts.set(qn("w:ascii"),ascii_font)rFonts.set(qn("w:hAnsi"),ascii_font)ifsizeisnotNone:run.font.size=sizeifboldisnotNone:run.font.bold=boldifcolorisnotNone:run.font.color.rgb=colordefadd_runs(par,text,base_font="宋体",ascii_font=None,size=None,base_bold=False,color=None):"""解析行内 **粗体** 与 `代码` 标记并写入段落。"""tokens=re.split(r"(\*\*.+?\*\*|`[^`]+`)",text)fortokintokens:ifnottok:continueiftok.startswith("**")andtok.endswith("**")andlen(tok)>4:run=par.add_run(tok[2:-2])set_font(run,base_font,ascii_font,size,True,color)eliftok.startswith("`")andtok.endswith("`")andlen(tok)>2:run=par.add_run(tok[1:-1])set_font(run,base_font,"Consolas",size,base_bold,color)else:run=par.add_run(tok)set_font(run,base_font,ascii_font,size,base_bold,color)defadd_hr(doc):p=doc.add_paragraph()pPr=p._p.get_or_add_pPr()pBdr=OxmlElement("w:pBdr")bottom=OxmlElement("w:bottom")bottom.set(qn("w:val"),"single")bottom.set(qn("w:sz"),"6")bottom.set(qn("w:space"),"1")bottom.set(qn("w:color"),"888888")pBdr.append(bottom)pPr.append(pBdr)defset_cell_bg(cell,hexcolor):shd=OxmlElement("w:shd")shd.set(qn("w:val"),"clear")shd.set(qn("w:fill"),hexcolor)cell._tc.get_or_add_tcPr().append(shd)defadd_table(doc,rows):n_cols=max(len(r)forrinrows)table=doc.add_table(rows=len(rows),cols=n_cols)table.style="Table Grid"table.alignment=WD_TABLE_ALIGNMENT.CENTERfori,rowinenumerate(rows):forjinrange(n_cols):cell=table.cell(i,j)text=row[j]ifj<len(row)else""cell.text=""par=cell.paragraphs[0]par.alignment=WD_ALIGN_PARAGRAPH.CENTERifi==0elseWD_ALIGN_PARAGRAPH.LEFT add_runs(par,text,size=Pt(9))forruninpar.runs:ifi==0:run.font.bold=Trueifi==0:set_cell_bg(cell,"D9E2F3")doc.add_paragraph()defadd_code_block(doc,lines):fori,lineinenumerate(lines):par=doc.add_paragraph()pf=par.paragraph_format pf.space_before=Pt(0)pf.space_after=Pt(0)pf.line_spacing_rule=WD_LINE_SPACING.SINGLE pf.left_indent=Cm(0.5)run=par.add_run(lineiflineelse" ")set_font(run,"Consolas","Consolas",Pt(9))INLINE_CODE_RE=re.compile(r"`([^`]*)`")defconvert(md_path,docx_path):withopen(md_path,"r",encoding="utf-8")asf:lines=f.read().splitlines()doc=Document()# 页面设置: A4sec=doc.sections[0]sec.page_width=Cm(21.0)sec.page_height=Cm(29.7)sec.left_margin=Cm(2.5)sec.right_margin=Cm(2.5)sec.top_margin=Cm(2.5)sec.bottom_margin=Cm(2.5)# 正文默认字体style=doc.styles["Normal"]style.font.name="宋体"style.font.size=Pt(10.5)style.element.rPr.rFonts.set(qn("w:eastAsia"),"宋体")i=0n=len(lines)whilei<n:line=lines[i]stripped=line.strip()# 代码块ifstripped.startswith("```"):code_lines=[]i+=1whilei<nandnotlines[i].strip().startswith("```"):code_lines.append(lines[i])i+=1i+=1# skip closing fenceadd_code_block(doc,code_lines)continue# 表格 (| ... |)ifstripped.startswith("|")andi+1<nandre.match(r"^\s*\|[\s:|-]+\|\s*$",lines[i+1]):rows=[]header=[c.strip()forcinstripped.strip("|").split("|")]rows.append(header)i+=2whilei<nandlines[i].strip().startswith("|"):rows.append([c.strip()forcinlines[i].strip().strip("|").split("|")])i+=1add_table(doc,rows)continue# 水平线ifre.match(r"^-{3,}\s*$",stripped)orre.match(r"^\*{3,}\s*$",stripped):add_hr(doc)i+=1continue# 标题m=re.match(r"^(#{1,6})\s+(.*)$",stripped)ifm:level=len(m.group(1))text=m.group(2).strip()text=INLINE_CODE_RE.sub(r"\1",text)text=text.replace("**","")iflevel==1:par=doc.add_paragraph()par.alignment=WD_ALIGN_PARAGRAPH.CENTER run=par.add_run(text)set_font(run,"黑体","Times New Roman",Pt(16),True)par.paragraph_format.space_after=Pt(12)eliflevel==2:par=doc.add_paragraph()par.alignment=WD_ALIGN_PARAGRAPH.CENTER run=par.add_run(text)set_font(run,"黑体","Times New Roman",Pt(14),True)par.paragraph_format.space_after=Pt(10)else:par=doc.add_paragraph()run=par.add_run(text)set_font(run,"黑体","Times New Roman",Pt(12),True)par.paragraph_format.space_before=Pt(8)par.paragraph_format.space_after=Pt(6)i+=1continue# 引用块ifstripped.startswith(">"):quote_lines=[]whilei<nandlines[i].strip().startswith(">"):quote_lines.append(lines[i].strip().lstrip(">").strip())i+=1par=doc.add_paragraph()pf=par.paragraph_format pf.left_indent=Cm(0.75)pf.space_before=Pt(4)pf.space_after=Pt(4)add_runs(par," ".join(quote_lines),size=Pt(9.5),color=RGBColor(0x33,0x33,0x33))continue# 列表(- / * / 数字.)m=re.match(r"^(\s*)([-*]|\d+\.)\s+(.*)$",line)ifmandnotstripped.startswith("**"):indent_level=len(m.group(1))//2marker=m.group(2)text=m.group(3)is_num=marker.rstrip(".")!=marker# "1." 形式par=doc.add_paragraph()pf=par.paragraph_format pf.left_indent=Cm(0.75+0.75*indent_level)pf.first_line_indent=Cm(-0.35)pf.space_before=Pt(0)pf.space_after=Pt(3)prefix=marker+" "ifis_numelse"• "run=par.add_run(prefix)set_font(run,"宋体",None,Pt(10.5),True)add_runs(par,text)i+=1continue# 空行ifnotstripped:i+=1continue# 普通段落par=doc.add_paragraph()pf=par.paragraph_format pf.space_before=Pt(2)pf.space_after=Pt(4)pf.line_spacing=1.3# 试卷题干常见 "**N.** xxx" 或行首加粗编号, add_runs 处理粗体add_runs(par,stripped)i+=1doc.save(docx_path)print("OK:",docx_path)if__name__=="__main__":iflen(sys.argv)<2:print("usage: python md2docx.py <input.md> [output.docx]")sys.exit(1)src=sys.argv[1]dst=sys.argv[2]iflen(sys.argv)>2elseos.path.splitext(src)[0]+".docx"convert(src,dst)