This commit is contained in:
2026-03-01 01:13:16 +08:00
parent 37cdfe2b68
commit 31e0ddf1f5
81 changed files with 4801 additions and 0 deletions

View File

@@ -0,0 +1,307 @@
# 2026届网络工程毕业设计
# 论文撰写规范(应用开发类)
中文摘要 (3-5 个关键词)
# 英文摘要
# 目录
# 第1章绪论
1.1 课题来源及意义
1.2 课题研究现状
1.3当前存在的问题
1.4 课题研究目标
# 第2章主要技术和框架
2.1 主要技术 (开发系统或平台所需相关技术的介绍)
# 2.2 框架和开发模式(开发系统或平台所使用的框架和开发模式的介绍)
# 第3章XXXXX的系统分析
# 3.1需求分析(包括功能需求分析和性能需求分析)
3.2可行性分析(包括技术可行性、操作可行性、经济可行性,社会可行性等)
3.3 用例图(根据系统角色划分用例图,若有两个角色就有两个用例图)
3.3.1 用户用例图(需先对用例图进行描述,其实也就是对该系统的用户角色进行权限分析,再画出具体用户用例图,用例图如下所示)
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/fedda2aecd9c17cf7f678bd9dfce3c4005225a98e76f2b3c551c72ab00f31ab0.jpg)
图3.1用户用例图
3.3.2 管理员用例图(需先对用例图进行描述,其实也就是对该系统的用户角色进行权限分析,再画出具体用户用例图,用例图如下所示)
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/85b57ee2a2135507c52a9eaab015e3bbf5218d259fefa8256bf6efc10fa0b42a.jpg)
图3.2管理员用例图
3.4 用例描述(用例描述是对用例图中用例的详细说明,它详细阐述了用例的功
能实现过程、输入输出、前置条件、后置条件以及可能的异常情况等,是对用例图
的补充和细化使系统功能更加清晰具体。需分别对每个用例列举3个左右的用
例描述。)
1用户管理地址用例描述如下表3.1所示。
表 3.1 用户管理地址用例描述
<table><tr><td>用例名称:管理地址</td></tr><tr><td>执行者:用户</td></tr><tr><td>简要说明:用户对收货地址进行添加、修改、删除等管理操作</td></tr><tr><td>基本事件流:
1.用户登录平台,进入地址管理界面
2.系统显示已有地址列表
3.用户选择添加新地址,输入详细地址信息,验证通过后存储地址
4.若修改地址,系统更新后提示成功,若删除地址,确认操作后数据库移除该地址记录</td></tr><tr><td>(2)用户查看商品用例描述如下表3.2所示。
表3.2用户查看商品用例</td></tr><tr><td>用例名称:查看商品</td></tr><tr><td>执行者:用户</td></tr><tr><td>简要说明:用户分类浏览平台上的闲置商品</td></tr><tr><td>基本事件流:
1.用户登录平台,进入查看商品界面
2.选择商品分类,如闲置书籍、衣服、手机等;
3.系统加载并显示该分类下的商品列表
4.用户可点击具体商品,查看详细信息,如介绍、图片、价格等</td></tr></table>
# 3.5 系统性能分析
# 第4章XXXXX的系统设计
4.1系统功能设计(先总体概述一段该系统的主要角色有哪几个,分别包含什么主
要功能,再给出功能结构图。)
具体如图4.1和图4.2所示。
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/eac455914323329b18745df5545040771c017cdc8a0d678f5569589c30bf1f4e.jpg)
图4.1用户功能结构图
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/18177e942ba24fd2a163cebfaf31c07e24ad1f0c3c3bb57deb63dff73bc44a22.jpg)
图4.2管理员功能结构图
4.2 类图(根据给定的初步类图,详细描述每个类的属性、方法以及类之间的具体关系。)
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/e677faab545e5c9b75f847b7d786df1e0c2e764b6475056e4ed0f2b769dcf25f.jpg)
图4.3系统类图
4.3 序列图(展示了对象之间随时间顺序发生的消息传递,通常用于描述用例的实现过程或系统中某个功能的动态行为。至少需列举系统角色各一个序列图,在给出序列图之前还需将过程的详细步骤写出。如:用户登录序列图、管理员删除账号序列图。)
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/b707fad3bc4d4d52a6b9da497d9bfdc7c365eac728f93991ea0c2488b2ccb653.jpg)
图4.4用户购买商品功能序列图
4.4 活动图(活动图可以清晰地描述系统的动态行为和工作流程。例如,在描述一个在线购物系统的订单处理流程时,可以通过活动图展示从用户下单到订单完成的各个步骤。论文中至少需描述各个角色各一个活动的活动图,如用户更新个人信息活动图,管理员删除用户信息活动图。)
# 4.4.1 用户填写个人信息活动图
用户填写个人信息过程可分为以下几步:
(1) 用户输入验证账号密码。
(2) 数据库查询用户数据,并判断用户是否存在。
(3) 登录成功后,用户填写个人信息并提交。
(4) 系统接收提交的个人信息后,将其发送至数据库进行更新保存。
(5) 用户确认信息更新无误后,更新界面。
用户填写个人信息活动图如下图4.7所示。
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/1167b27131dd2f56dcf570a6d1f2ad3888a8cb31390877f532d47161d49aaabc.jpg)
图4.7 用户填写个人信息活动图
# 4.5 数据库设计
撰写说明:
本章是重点章节很多同学搞不清概念设计、逻辑设计和物理设计的关系请参见另外一个文档《2026届网络工程毕业论文第4章-数据库设计撰写规范(应用开发类补充说明).docx》
4.5.1 概念设计概念设计包括两部分实体属性图不少于8个实体每个实体
标明属性图的主码和总体E-R图
# (1) 管理员实体
管理员实体属性图如图4.9所示。
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/f0139dd7d92cc9b47066ec9de102f1667c1ce1e48206094d7a2bdef274205425.jpg)
图4.9管理员实体属性图
# (2用户实体
用户实体属性图如图4.10所示。
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/6d6a6cb8e810b97076d80cd79891215ff2119fc737339c35edd15ad884bba839.jpg)
图4.10用户实体属性图
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/0f95a66d6d06fcc3c2727444cdea2f4793ac489748efa04d7bfa75c3578d1d9c.jpg)
图4.系统E-R图标明各个实体之间的关联
4.5.2 逻辑设计将E-R图中的实体关系转换为逻辑结构设计即关系模式。
其中,一个实体转换为 1 个单独的关系模式。
实体间联系有3种1:11:nn:m。
1:n 联系要将 1 端实体如用户的主码加入到 n 端如评论实体的关系模式中。
n:m 联系要将联系如上图中的购买转换成一个单独的关系模式,并将两端的主
码放入该关系模式作为属性。)关系模式中需标明主外码
上图转换为逻辑结构如下所示:
(1) 用户表:(用户 id, 用户账号, 密码, 用户姓名, 性别, 联系电话, 头像, 积分,余额)
(2) 管理员表: (管理员 id, 创建时间, 管理员名称, 密码, 管理员类型)
(3) 收货地址表收货地址id用户id地址收货人电话是否为默认地址
(4) 购物车表购物车id商品表名商品商品名称图片购买数量单价会员价商品类型
(5) 订单表订单id订单编号用户id规格上架时间商品详情团购价拼团人数
(6) 团购商品表团购商品id购买数量价格折扣价总价商品类型id支付类型物流购物车id
(7) 商品类型表:(商品类型 id创建时间类型数量
(8) 评论表评论id用户id头像用户名评论内容回复内容
(9) 公告表公告id创建时间标题简介图片内容管理员id
(10) 购买表:(用户 id团购商品 id购买日期购买时间购买表的外键为
用户id团购商品id)
4.5.3 物理设计将上述关系模式转换为一个个物理表结构一个关系模式转换为一个物理表至少包含8张物理表
1系统根据MySQL数据库数据存储的特性设计数据库关系表
表 4.1 管理员表 (admin 表)
<table><tr><td>字段名称</td><td>字段意义</td><td>数据类型</td><td>长度</td><td>完整性约束</td></tr><tr><td>admin_id</td><td>管理员 id</td><td>int</td><td>10</td><td>主键</td></tr><tr><td>admin_name</td><td>管理员姓名</td><td>varchar</td><td>10</td><td>非空</td></tr><tr><td>admin_password</td><td>管理员密码</td><td>varchar</td><td>20</td><td>非空</td></tr><tr><td>admin_email</td><td>管理员邮箱</td><td>varchar</td><td>20</td><td>非空</td></tr><tr><td>admin_phone</td><td>管理员手机号</td><td>varchar</td><td>11</td><td>非空</td></tr></table>
表 4.2 用户表 (user 表)
<table><tr><td>字段名称</td><td>字段意义</td><td>数据类型</td><td>长度</td><td>完整性约束</td></tr><tr><td>user_id</td><td>用户id</td><td>int</td><td>10</td><td>主键</td></tr><tr><td>user_name</td><td>用户昵称</td><td>varchar</td><td>10</td><td>非空</td></tr><tr><td>user_password</td><td>用户密码</td><td>varchar</td><td>20</td><td>非空</td></tr><tr><td>user/mobile</td><td>用户电话</td><td>varchar</td><td>11</td><td>非空</td></tr><tr><td>user_realname</td><td>用户真实姓名</td><td>varchar</td><td>10</td><td>非空</td></tr><tr><td>user_score</td><td>信誉分</td><td>int</td><td>4</td><td>非空</td></tr></table>
表4.3商品表 (product表)
<table><tr><td>字段名称</td><td>字段意义</td><td>数据类型</td><td>长度</td><td>完整性约束</td></tr><tr><td>product_id</td><td>商品id</td><td>int</td><td>10</td><td>主键</td></tr><tr><td>product_name</td><td>商品名称</td><td>varchar</td><td>10</td><td>非空</td></tr><tr><td>product_title</td><td>商品概要</td><td>varchar</td><td>50</td><td>非空</td></tr><tr><td>product_intro</td><td>商品详情</td><td>varchar</td><td>100</td><td>非空</td></tr></table>
4.6图形界面设计实现某一个具体功能的界面设计至少系统中各角色各列举1
# 个功能界面设计)
如:删除购物车界面设计
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-30/cb195f07-5e0a-4b42-8211-f649b1c1b2df/5f2eaca38a994385483bc816a9956ee51e2a41baf561d9e08f613b9938c1de46.jpg)
图4. 删除购物车界面设计图
# 第5章XXXXX的系统实现
只写出主要功能的实现结果即可,并给出运行截图(不写用户注册、登录),
运行截图中需输入数据的地方要有数据输入,且输入的数据必须真实,不可输入
类似 1111 类数据。可按系统角色进行实现,再进行角色详细功能实现。如:
# 5.1 用户功能模块
# 5.1.1 购物车
# 5.1.2 我的订单
··
# 5.2 管理员功能模块
# 5.2.1 账号管理
# 5.2.2 订单管理
··
# 第6章XXXXX的系统测试
# 6.1 系统测试概述
6.1.1 测试的背景
6.1.2 测试的意义
6.1.3 测试的环境
# 6.2 系统测试用例设计
6.2.1XXXX功能测试
6.2.2XXXX功能测试
6.2.3XXXX功能测试
6.2.4 安全性测试
6.2.5 兼容性测试
# 第7章结论
参考文献
致谢
附录

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,341 @@
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Cm, Pt, RGBColor
import re
SRC = r"/Users/apple/code/bs/mying/example/萌贝母婴商城毕业论文初稿-2026版.docx"
DST = r"/Users/apple/code/bs/mying/example/萌贝母婴商城毕业论文初稿-2026版-排版.docx"
def set_style_font(
style,
east_asia_font: str,
size_pt: float,
bold: bool | None = None,
west_font: str = "Times New Roman",
):
font = style.font
font.name = west_font
font.size = Pt(size_pt)
if bold is not None:
font.bold = bold
font.color.rgb = RGBColor(0, 0, 0)
rfonts = style.element.get_or_add_rPr().get_or_add_rFonts()
rfonts.set(qn("w:ascii"), west_font)
rfonts.set(qn("w:hAnsi"), west_font)
rfonts.set(qn("w:eastAsia"), east_asia_font)
def set_runs_font(
paragraph,
east_asia_font: str,
size_pt: float,
bold: bool | None = None,
west_font: str = "Times New Roman",
):
for run in paragraph.runs:
run.font.name = west_font
run.font.size = Pt(size_pt)
if bold is not None:
run.font.bold = bold
run.font.color.rgb = RGBColor(0, 0, 0)
rpr = run._element.get_or_add_rPr()
rfonts = rpr.get_or_add_rFonts()
rfonts.set(qn("w:ascii"), west_font)
rfonts.set(qn("w:hAnsi"), west_font)
rfonts.set(qn("w:eastAsia"), east_asia_font)
def set_runs_common(paragraph, italic: bool | None = None, color_black: bool = True):
for run in paragraph.runs:
if italic is not None:
run.font.italic = italic
if color_black:
run.font.color.rgb = RGBColor(0, 0, 0)
def is_numbered_paragraph(paragraph) -> bool:
ppr = paragraph._p.pPr
if ppr is None:
return False
return ppr.numPr is not None
def iter_table_paragraphs(table):
for row in table.rows:
for cell in row.cells:
for p in cell.paragraphs:
yield p
for t in cell.tables:
yield from iter_table_paragraphs(t)
def format_table_paragraph(p, bold: bool = False):
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
fmt = p.paragraph_format
fmt.line_spacing = 1.0
fmt.space_before = Pt(0)
fmt.space_after = Pt(0)
fmt.first_line_indent = Pt(0)
set_runs_font(p, "宋体", 10.5, bold=bold)
set_runs_common(p, italic=False, color_black=True)
def set_table_style_like_template(table):
tbl = table._tbl
tbl_pr = tbl.tblPr
if tbl_pr is None:
tbl_pr = OxmlElement("w:tblPr")
tbl.insert(0, tbl_pr)
tbl_style = tbl_pr.find(qn("w:tblStyle"))
if tbl_style is None:
tbl_style = OxmlElement("w:tblStyle")
tbl_pr.append(tbl_style)
tbl_style.set(qn("w:val"), "Table Grid")
tbl_w = tbl_pr.find(qn("w:tblW"))
if tbl_w is None:
tbl_w = OxmlElement("w:tblW")
tbl_pr.append(tbl_w)
tbl_w.set(qn("w:type"), "pct")
tbl_w.set(qn("w:w"), "4997")
tbl_jc = tbl_pr.find(qn("w:jc"))
if tbl_jc is None:
tbl_jc = OxmlElement("w:jc")
tbl_pr.append(tbl_jc)
tbl_jc.set(qn("w:val"), "center")
tbl_cell_mar = tbl_pr.find(qn("w:tblCellMar"))
if tbl_cell_mar is None:
tbl_cell_mar = OxmlElement("w:tblCellMar")
tbl_pr.append(tbl_cell_mar)
for edge, width in (("top", "120"), ("bottom", "120"), ("left", "140"), ("right", "140")):
elem = tbl_cell_mar.find(qn(f"w:{edge}"))
if elem is None:
elem = OxmlElement(f"w:{edge}")
tbl_cell_mar.append(elem)
elem.set(qn("w:w"), width)
elem.set(qn("w:type"), "dxa")
tbl_borders = tbl_pr.find(qn("w:tblBorders"))
if tbl_borders is None:
tbl_borders = OxmlElement("w:tblBorders")
tbl_pr.append(tbl_borders)
for edge in ("top", "left", "bottom", "right", "insideH", "insideV"):
elem = tbl_borders.find(qn(f"w:{edge}"))
if elem is None:
elem = OxmlElement(f"w:{edge}")
tbl_borders.append(elem)
elem.set(qn("w:val"), "single")
elem.set(qn("w:sz"), "4")
elem.set(qn("w:color"), "auto")
elem.set(qn("w:space"), "0")
for row in table.rows:
tr_pr = row._tr.get_or_add_trPr()
tr_height = tr_pr.find(qn("w:trHeight"))
if tr_height is None:
tr_height = OxmlElement("w:trHeight")
tr_pr.append(tr_height)
tr_height.set(qn("w:val"), "620")
tr_height.set(qn("w:hRule"), "atLeast")
for cell in row.cells:
tc_pr = cell._tc.get_or_add_tcPr()
v_align = tc_pr.find(qn("w:vAlign"))
if v_align is None:
v_align = OxmlElement("w:vAlign")
tc_pr.append(v_align)
v_align.set(qn("w:val"), "center")
tc_borders = tc_pr.find(qn("w:tcBorders"))
if tc_borders is None:
tc_borders = OxmlElement("w:tcBorders")
tc_pr.append(tc_borders)
for edge in ("top", "left", "bottom", "right"):
elem = tc_borders.find(qn(f"w:{edge}"))
if elem is None:
elem = OxmlElement(f"w:{edge}")
tc_borders.append(elem)
elem.set(qn("w:val"), "single")
elem.set(qn("w:sz"), "4")
elem.set(qn("w:color"), "auto")
elem.set(qn("w:space"), "0")
def set_table_header_gray(table):
if not table.rows:
return
for cell in table.rows[0].cells:
tc_pr = cell._tc.get_or_add_tcPr()
shd = tc_pr.find(qn("w:shd"))
if shd is None:
shd = OxmlElement("w:shd")
tc_pr.append(shd)
shd.set(qn("w:val"), "clear")
shd.set(qn("w:color"), "auto")
shd.set(qn("w:fill"), "D9D9D9")
def cleanup_paragraph_spaces(paragraph):
runs = paragraph.runs
if not runs:
return
for run in runs:
if run.text:
run.text = re.sub(r"[ \t]{2,}", " ", run.text)
runs[0].text = runs[0].text.lstrip(" \t\u3000")
runs[-1].text = runs[-1].text.rstrip(" \t\u3000")
def remove_redundant_blank_paragraphs(doc):
prev_blank = False
for p in list(doc.paragraphs):
text = p.text.replace("\u3000", " ").strip()
is_blank = text == ""
if is_blank and prev_blank:
p._element.getparent().remove(p._element)
continue
prev_blank = is_blank
def add_page_break_between_chapters(doc):
chapter_pattern = re.compile(r"^第\s*\d+\s*章")
chapter_paragraphs = []
for p in list(doc.paragraphs):
text = p.text.replace("\u3000", " ").strip()
if not text or not chapter_pattern.match(text):
continue
chapter_paragraphs.append(p)
for index, p in enumerate(chapter_paragraphs):
if index == 0:
continue
prev = p._element.getprevious()
has_page_break = False
if prev is not None:
for br in prev.findall('.//w:br', {'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}):
if br.attrib.get('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}type') == 'page':
has_page_break = True
break
if not has_page_break:
break_paragraph = p.insert_paragraph_before("")
break_paragraph.add_run().add_break(WD_BREAK.PAGE)
def set_first_line_two_chars(paragraph, twips: int = 420, chars: int = 200):
ppr = paragraph._p.get_or_add_pPr()
ind = ppr.find(qn("w:ind"))
if ind is None:
ind = OxmlElement("w:ind")
ppr.append(ind)
ind.set(qn("w:firstLine"), str(twips))
ind.set(qn("w:firstLineChars"), str(chars))
def apply_para_format(paragraph, line_spacing: float, first_line_pt: float | None = None, align=None):
fmt = paragraph.paragraph_format
fmt.line_spacing = line_spacing
fmt.space_before = Pt(0)
fmt.space_after = Pt(0)
if first_line_pt is not None:
fmt.first_line_indent = Pt(first_line_pt)
set_first_line_two_chars(paragraph)
if align is not None:
paragraph.alignment = align
def format_paragraph(p):
style_name = p.style.name if p.style is not None else ""
if style_name == "Heading 1":
apply_para_format(p, 1.5, 0, WD_ALIGN_PARAGRAPH.CENTER)
set_runs_font(p, "黑体", 22, True)
elif style_name == "Heading 2":
apply_para_format(p, 1.5, 32)
set_runs_font(p, "黑体", 16, True)
elif style_name == "Heading 3":
apply_para_format(p, 1.5, 28)
set_runs_font(p, "黑体", 14, True)
elif style_name == "Heading 4":
apply_para_format(p, 1.5, 24)
set_runs_font(p, "黑体", 14, True)
set_runs_common(p, italic=False, color_black=True)
elif is_numbered_paragraph(p) or style_name.startswith("List Number"):
p.paragraph_format.line_spacing = 1.5
set_runs_font(p, "宋体", 12)
set_runs_common(p, color_black=True)
else:
apply_para_format(p, 1.5, 24)
set_runs_font(p, "宋体", 10.5)
set_runs_common(p, color_black=True)
def set_page_layout(doc):
for section in doc.sections:
section.page_width = Cm(21.0)
section.page_height = Cm(29.7)
section.top_margin = Cm(2.5)
section.bottom_margin = Cm(2.5)
section.left_margin = Cm(2.5)
section.right_margin = Cm(2.5)
section.header_distance = Cm(1.5)
section.footer_distance = Cm(1.75)
def main():
doc = Document(SRC)
normal = doc.styles["Normal"]
h1 = doc.styles["Heading 1"]
h2 = doc.styles["Heading 2"]
h3 = doc.styles["Heading 3"]
h4 = doc.styles["Heading 4"]
set_style_font(normal, "宋体", 10.5)
normal.paragraph_format.line_spacing = 1.5
normal.paragraph_format.first_line_indent = Pt(21)
set_style_font(h1, "黑体", 22, True)
h1.paragraph_format.line_spacing = 1.5
h1.paragraph_format.first_line_indent = Pt(0)
set_style_font(h2, "黑体", 16, True)
h2.paragraph_format.line_spacing = 1.5
h2.paragraph_format.first_line_indent = Pt(32)
set_style_font(h3, "黑体", 14, True)
h3.paragraph_format.line_spacing = 1.5
h3.paragraph_format.first_line_indent = Pt(28)
set_style_font(h4, "黑体", 14, True)
h4.font.italic = False
h4.paragraph_format.line_spacing = 1.5
h4.paragraph_format.first_line_indent = Pt(24)
set_page_layout(doc)
for p in doc.paragraphs:
format_paragraph(p)
cleanup_paragraph_spaces(p)
for t in doc.tables:
set_table_style_like_template(t)
set_table_header_gray(t)
for row_index, row in enumerate(t.rows):
for cell in row.cells:
for p in cell.paragraphs:
format_table_paragraph(p, bold=(row_index == 0))
cleanup_paragraph_spaces(p)
remove_redundant_blank_paragraphs(doc)
add_page_break_between_chapters(doc)
doc.save(DST)
print(DST)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,97 @@
from __future__ import annotations
from pathlib import Path
import re
from docx import Document
INPUT_MD = Path("/Users/apple/code/bs/mying/example/萌贝母婴商城毕业论文初稿-2026版.md")
OUTPUT_DOCX = Path("/Users/apple/code/bs/mying/example/萌贝母婴商城毕业论文初稿-2026版.docx")
def is_table_separator(line: str) -> bool:
stripped = line.strip()
if not stripped.startswith("|"):
return False
core = stripped.strip("|").replace(" ", "")
return bool(core) and all(ch in "-:|" for ch in core)
def split_table_row(line: str) -> list[str]:
raw = line.strip().strip("|")
return [cell.strip() for cell in raw.split("|")]
def convert_markdown_to_docx(md_text: str, doc: Document) -> None:
lines = md_text.splitlines()
i = 0
while i < len(lines):
line = lines[i]
stripped = line.strip()
if not stripped:
doc.add_paragraph("")
i += 1
continue
# Table block
if stripped.startswith("|") and i + 1 < len(lines) and is_table_separator(lines[i + 1]):
headers = split_table_row(lines[i])
i += 2
rows: list[list[str]] = []
while i < len(lines):
row_line = lines[i].strip()
if not row_line.startswith("|"):
break
rows.append(split_table_row(lines[i]))
i += 1
cols = max(1, len(headers))
table = doc.add_table(rows=1, cols=cols)
for c in range(cols):
table.cell(0, c).text = headers[c] if c < len(headers) else ""
for row in rows:
cells = table.add_row().cells
for c in range(cols):
cells[c].text = row[c] if c < len(row) else ""
continue
# Heading
heading_match = re.match(r"^(#{1,6})\s+(.*)$", stripped)
if heading_match:
level = min(4, len(heading_match.group(1)))
text = heading_match.group(2).strip()
doc.add_heading(text, level=level)
i += 1
continue
# Ordered list
if re.match(r"^\d+\.\s+", stripped):
text = re.sub(r"^\d+\.\s+", "", stripped)
doc.add_paragraph(text, style="List Number")
i += 1
continue
# Unordered list
if stripped.startswith("- "):
doc.add_paragraph(stripped[2:].strip(), style="List Bullet")
i += 1
continue
# Plain paragraph
doc.add_paragraph(stripped)
i += 1
def main() -> None:
md_text = INPUT_MD.read_text(encoding="utf-8")
doc = Document()
convert_markdown_to_docx(md_text, doc)
doc.save(OUTPUT_DOCX)
print(OUTPUT_DOCX)
if __name__ == "__main__":
main()