⚡

Excel转Word保留原格式Skill excel-to-word

将 Excel 工作表/工作簿转换为 Word 原生表格,并尽量保留 Excel 自身格式(中文字体、字号、颜色、底纹、边框、对齐、合并、行高列宽、数字格式),支持打印区域、隐藏行列、一表多表拆分、纸型与固定页边距、根据窗口调整表格、数字不换行及逐格回读校验,适合工程概预算、估算、概算、工程量清单等交付件排版。关键词:Excel转Word、保留原格式、Word原生表格、工程概预算、概算表、工程量清单、打印区域、固定页边距、纸型自动、逐格校验、openpyxl、python-docx。

工程造价 0 次安装 0 次浏览 更新于 11/10/2026
名称 excel-to-word
版本 2.3.3
描述 “把 Excel 工作表/工作簿转换为 Word 原生表格,同时保留电子表格自身的字体(包括中文 eastAsia 字体)、字号、颜色、单元格底纹、边框、对齐、合并、行高、列宽和数字格式。单一 Python 入口(openpyxl + python-docx),不走 VBA/宏路线:带按工作表分节的 CLI、打印区域检测、纸张大小自动选择、按方向固定的页边距(横向 上2.5/下2.0/左2.0/右2.0cm,纵向 上2.0/下2.0/左2.5/右2.0cm)、表格根据窗口调整、列加宽使数字单元格永不换行,以及用于回读检查的 verify/diag 配对。包含初设概算(模板)参考文档的实测版式规范:A4 纵向 + A4 横向分节、宋体 9-10pt、每单元格 0.5pt 边框、atLeast 行高、重复表头行、全宽合并标题行。用于工程概预算/估算/概算/工程量清单 Excel 转 Word 交付件、保留原格式、固定页边距、根据窗口调整表格、Excel 表格转 Word 排版、多方案表拆分、按打印区域导出、逐格校验。区别于 excel-to-word-template,后者复现的是参考 Word 文档的版式,而不是电子表格自身的外观。” agent_created: true

Excel → Word(保留 Excel 自身格式)v2.3.3

把 Excel 表转成 Word 原生表格,字体、字号、底纹、边框、对齐、合并、列宽行高、数字格式全部保留。 不去套 Word 模板——要套模板用 excel-to-word-template。

详细操作手册见 使用说明.md(给使用者);本文件是给 agent 的实现约定与决策依据。

唯一路线:Python CLI

本技能只走 Python 路线,不提供 VBA 宏。原因(都是实测得出的):

VBA 宏的硬伤 造价表的真实情况
不认打印区域 作者常把「差额」「上一版」列设在打印区外,宏会一起导
不处理隐藏行列 概算表普遍藏几十到几百行辅助计算行
不能一表拆两段 方案一/方案二上下排在同一个工作表里是常态
不能控纸型与缩放 19 列表在 A4 横向要缩到 7.5pt,需要判 A3
不能重跑/批处理 改一版就要重新点一遍按钮
数字依赖剪贴板 无法逐格回读校验,交付前没法机器验收

Python 路线用同一份 openpyxl 数据既写 docx 又做校验,改完能重跑、交付前能自检。

干什么 命令
看结构(先跑这个) python scripts/excel_to_word.py 概算.xlsx --diag-only
转换 python scripts/excel_to_word.py 概算.xlsx
交付前校验 python scripts/verify_docx.py 概算.xlsx 概算.docx

依赖:pip install openpyxl python-docx。旧版 VBA 宏留在 _backup/ 仅作历史存档,不要再用。

Excel 侧前提(转之前先确认,否则一定多东西或少东西)

转换是「如实搬运」,不替用户做取舍。三个信号决定结果,命令行不会报错:

信号 决定的 没设好的后果
打印区域 取哪一块 打印区外的「差额」「上一版」「辅助列」不会导出;整表没设打印区域则退回整表使用区域,草稿列一起进来
工作表隐藏 转哪几张 明细、底稿、参考资料全部转进 Word(--include-hidden 才导隐藏表)
隐藏行 / 隐藏列 辅助数据走不走 几百行辅助计算全部进版心

另外两件顺手事:Excel 里设「打印标题 → 顶端标题行」可控制跨页重复表头(不设则自动猜,易偏); 首行不是表名行时 Word 标题取工作表标签名,标签叫「控」标题就是「控」。

一个工作表竖着堆多张表是常态(「水工估」第 1 行是炸礁表、第 21 行又是水工建筑工程表)。 转换器会自动按表名行切开,每张表独立成节、独立表名、独立跨页表头—— 不切的话第二张表的表名行和列标题行会落在表格中间,看起来就是「标题行重复」。

所以拿到文件先跑 --diag-only,看 区A1:M24 可见 24行×13列 是否等于用户想导的范围,再动手转。 打印区域不方便改时,用 --sheets "名!A1:H86@4" 白名单绕开(但它不持久,改版重跑要重带)。

Python 路线

三条命令覆盖 90% 场景

# 1) 先诊断:列出每张表的取数区域、可见行列、宽度、建议纸型、缩放后字号
python scripts/excel_to_word.py 概算.xlsx --diag-only

# 2) 直接转(默认按打印区域取数、每表一节、纸型自动、页码自动)
python scripts/excel_to_word.py 概算.xlsx

# 3) 一表两段 + 指定表头行数(方案一 / 方案二在同一个工作表里上下排列时)
python scripts/excel_to_word.py 概算.xlsx -o 概算.docx \
  --sheets "汇总,水工!A1:I86@4,水工!A88:I163@4,建!A1:G10@4,建!A12:G18@4"

参数

参数 默认 说明
-o/--out 源文件同名 .docx 输出路径
--sheets 全部可见表 名1,名2!A1:H86@4;!区域 在打印区域内再收窄(不能扩大),@N 指定该段表头行数
--area auto auto=有打印区域就用+裁尾部空列空行;print=照打印区域不裁;used=整个使用区域(唯一能突破打印区域的开关)
--orient auto auto 按宽度选纵向/横向;也可强制 portrait/landscape;excel=严格照 Excel 页面设置的方向,不按表宽自动改
--no-scale 关 字号严格照 Excel,不做等比缩放;边距未固定时先压左右边距吸收,边距固定时改压列宽(见下)
--margin-preset cost cost=横/纵各一套固定边距(横 2.5/2.0/2.0/2.0,纵 2.0/2.0/2.5/2.0 cm,顺序上/下/左/右,不随表宽变动);sym=用 --margin/--margin-v
--margin-min 0.8 仅 --margin-preset sym + --no-scale 时有效:左右边距可压到的最小值 cm
--no-fit-window 关 不设「根据窗口调整表格」。默认设:每表 tblW=100% + tblLayout=autofit,列宽等比归一到版心宽
--no-widen 关 禁止「为放不下的数字加宽列」(默认会加宽,见下)
--paper auto A3 = 放不下时允许升 A3 兜底(不是强制 A3)
--min-font 7.5 缩放后正文字号低于此值告警(不阻断)
--repeat-header 0(自动) >0 强制行数;0 时按 打印标题行 > 冻结窗格 > 「整行无数字」识别
--margin / --margin-v 2.5 左右 / 上下页边距 cm,仅 --margin-preset sym 时生效
--row-height atleast atleast 按内容撑高不裁字;exact 复刻 Excel 固定行高(会裁字)
--sheet-title auto 首行已是合并表名行时不另加标题段
--page-number / --no-page-number 开 页脚居中连续页码
--header "项目名" 空 页眉文字
--include-hidden 关 连隐藏工作表一起导(默认跳过;隐藏表即使被 --sheets 点名也不导)
--cant-split 关 禁止数据行跨页断开(样张不设)
--diag-only — 只打印结构诊断,不生成 docx
--exact-row-height — 等同 --row-height exact(兼容 v1 写法)

输出怎么读

[ 4] 水工    A4L 行  163 列 9 宽   675pt 缩放100.0% 表头4行 缩字0格 区1-163
      ↑纸型   ↑行数 ↑列数 ↑原表宽   ↑等比缩放   ↑重复表头 ↑为保单行缩了字的格数
  • 缩放 < 85% 或提示「字号 < --min-font」→ 考虑 --paper A3 或拆表
  • 表头N行 明显偏大/偏小 → 用 @N 指定(从表头首行起算)
  • 表头N行 大到接近整表(如 71 行)→ v2.3.1 已修(冻结窗格会被当成表头提示), 若仍出现请回报该表名
  • 区a-b 与预期不符 → 检查 Excel 打印区域,或加 --area used

处理流程(v2)

诊断区域(打印区∩有值范围) → 按表名行切块(一表多表) → 定纸型/缩放系数 s
  → 逐格写内容+样式(noWrap 按 Excel 的 wrap_text)
  → 合并 → 清空段落 → 整表等比缩放(列宽/行高/字号 ×s)
  → 内边距自适应 → 不换行格缩字保单行
  → 表名行拆成无框表前标题块 → 表头竖合并展平 → 连续标记重复表头 → 分节+页码

整表等比缩放是关键决策:等价 Excel「调整为 1 页宽」,列宽/行高/字号同比例缩, 版面比例不变形。不再单独压列宽(v1 那样压列宽会把数字挤成两行)。

交付件要求「字号一分不让」时:--orient excel --no-scale

造价所审图常要求字号与 Excel 完全一致、纸张方向也照原表。此时用:

python scripts/excel_to_word.py 概算.xlsx --orient excel --no-scale

三件事会变,其余不变:

  1. 方向取 Excel 页面设置(ws.page_setup.orientation),不再按表宽自动翻。
  2. 不缩放,字号/列宽/行高都是 Excel 原值。表比版心宽时:
    • 边距未固定(--margin-preset sym)→ 先压左右边距吸收(压到 --margin-min);
    • 边距固定(默认 cost)→ 改压列宽:squeeze_columns 按各列余量分摊挤掉的量, 数字/单元符号列守住单行最小宽度,只压项目名称、特征描述这类文字列; 连数字单行都保不住时才退化为等比缩并告警。
  3. 数字不再被拆行:Excel 金额列常设「自动换行 + 行高锁死」,数字实际是截断显示的; 搬到 Word 会变成 490/5572/.14 竖排。现在会把该列补宽到数字单行所需(余量从页面 空处腾),并打上 noWrap——宁可列宽/行高变大,也不挤数字。 短单元符号(万m3)同理,否则一行变两行会把整表高度顶超一页。
  4. 单表差一两行就排满一页时,按比例压行高(不低于 0.7,atLeast 仍保证不裁字), 避免「工程费用合计」被甩到孤零零的第二页。压的是行高不是字号。
  5. 表格设为「根据窗口调整表格」:tblW=100% + tblLayout=autofit, 列宽先等比归一到版心宽,Word/WPS 拿到的是「已经刚好铺满」的列宽,不会再二次分配 (否则刚加宽的金额列会被压回两行)。 例外:压过列宽的表(squeeze_columns 生效)改用固定布局 tblW=版心宽(dxa) + tblLayout=fixed。autofit 下 Word 会按内容重新分配宽度,把「序号」「计量单位」 这类窄列压成竖排、把 030402009001 和 134613.4 拆成两行(南沙项目电2 真实事故)。

--no-scale 下 shrink_nowrap 只放开换行、绝不缩字。想恢复旧行为就去掉这两个开关。

交付件要求固定页边距(横向/纵向各一套)

python scripts/excel_to_word.py 概算.xlsx --orient excel --no-scale   # 默认即 --margin-preset cost
方向 上 下 左 右 版心(A4)
横向 2.5 2.0 2.0 2.0 宽 25.7cm ≈ 728.5pt
纵向 2.0 2.0 2.5 2.0 宽 16.5cm ≈ 467.7pt
  • 四个值分别写,不假设左右对称;页眉/页脚距自动压到 min(0.8, 边距-0.4), 否则页脚距 > 下边距时 Word 会把版心底边卡在页脚位置(下边距形同虚设)。
  • 「固定」的确切含义:表比版心宽时绝不动边距,只压列宽或整表缩放。 --margin-preset cost 下的 --margin-min 不生效(那是 sym 的口子)。
  • 横纵版心差了 5.3cm,横向能铺下的表换到纵向就会超宽。用 --diag-only 先看 表宽 / 版心,超出的表会打「从列宽压掉 Npt」提示,属预期。

坑(WPS 渲染):tblW=100% 在 WPS 里按内容盒算宽,会把两侧单元格内边距算进去, 表格比版心宽 2×内边距,居中后左右各外溢一份。5.4pt 内边距 → 外溢 2.2mm;2pt → 0.8mm。 所以 --fit-window 打开时强制把内边距压到 2pt(set_cell_margins(40))。 Word 按边框盒算,本就是精确版心宽——两边的差异只在这 1mm 内。

版式判定

可见列宽合计 W,版心 U(p) = 纸宽 − 左 − 右(边距按 `page_margins(args, p)` 取,横纵各一套)
1) 列数 > --wide-col-limit(10)        → A4 横向
2) W ≤ U(A4纵) × 1.04                 → A4 纵向   ← 4% 容差,别为省几磅就翻页向
3) W ≤ U(A4横)                        → A4 横向
4) W ≤ U(A3横) 且 --paper A3          → A3 横向
5) 仍超                               → A4 横向 + 等比缩到版心宽

可用宽速查:cost 预设下 A4 纵 481.9pt(17cm)/ A4 横 773.9pt(27.3cm); cost(默认)版心:A4 横 728.5pt / A4 纵 467.7pt。 sym 2.5cm 对称边距下 A4 纵 453.5pt / A4 横 700.2pt / A3 横 1048.8pt。

每张表一个节(下一页分节符),所以同一份文档里 A4 纵的小表和 A4 横的宽表可以混排—— 参考样张就是这个结构。页码只在第 1 节设置,其余节链接到前一节。

硬规则(改代码前必读)

  1. 中文字体必须写 w:eastAsia,只设 font.name 中文不生效。
  2. General 格式按 10 位有效数字输出,绝不能 int() 取整(10.1→10、6.25→6 是真实事故)。
  3. 金额用 Decimal ROUND_HALF_UP,round() 银行家舍入会差 1 分。
  4. 行高规则属性是 w:hRule,写成 w:h-rule 会被 Word 忽略、exact 失效。
  5. w:tcPr / w:trPr / w:tblPr 子元素必须按 schema 顺序插入,乱序会让 Word 报文档损坏。
  6. 合并锚点判定用 (min_row,min_col)==(r,c);合并前先把锚点样式刷到区内所有格。
  7. 合并区必须裁剪到「窗口 ∩ 可见列」,否则 pos_c() 抛 ValueError。
  8. 合并后清掉锚点里多余的空段落,否则行高虚高。
  9. 列宽无论 s 是否为 1 都要写——python-docx 建表默认等宽。
  10. w:tblHeader 只认从表格首行起连续标记的行。想「只重复第 3、4 行」做不到—— Word 会连同前两行一起重复。做法是把表名行/工程信息行拆出表格(无框表 + 1pt 过渡段), 让列标题行成为新表的第 1 行。
  11. 表头里的竖合并(「序号」跨两行这类)原样保留,不要拆。第 10 条已经把表名行移出 表格,表头只剩列标题行,实测 Word/WPS 跨页重复时 w:vMerge 完整保留,续页表头仍是合并好 的两行。反过来把它们拆成「上格去下框、下格去上框」的无框拼接,在 Word 里看就是两个空格子 ——视觉上勉强连续,但用户能点中、能看出没合并(v2.3.3 真实返工)。
  12. 表前标题块要跟表格锁在同一页:w:cantSplit 只管行内不跨页,「与下段同页」必须在 块内所有单元格段落上加 w:keepNext,否则表名会孤零零留在上页页底。
  13. 单元格值里的 \r 要先归一成 ,否则 Word 里变双换行。
  14. 表间空段落与分节符寄生的空段落都要压到 0.1pt(w:line="2" exact)。1pt 不够—— 表格排到版心底只剩 0.2pt 时仍会被挤出一张空白页(南沙五期真实事故)。
  15. 不要引入 Office COM / VBA 作为主路径——交付件必须能被 verify_docx.py 逐格回读校验。

更多「症状 → 根因 → 修法」见 references/踩坑清单.md。

验收(交付前必过)

python scripts/verify_docx.py 概算.xlsx 概算.docx          # 逐格回读 + 结构校验

检查项:表格数 = 期望表数;逐格内容差异 0(含 10.1、6.25 这类小数序号/数量); 表宽 ≤ 版心;每节纸型与预期一致;表头已连续重复;页脚有页码;无多余空白页。

需要肉眼确认时,用 WPS 表格把 xlsx 直接导成 PDF 做基准对比页数与列宽:

app = win32com.client.DispatchEx("KET.Application")
wb = app.Workbooks.Open(xlsx, ReadOnly=True)
wb.ExportAsFixedFormat(Type=0, Filename=pdf)

页数预期:Word 页数通常多于 Excel 打印稿(Excel 把行高锁死、超长文字被裁)。 珠海项目实测 96 页(Excel) → 179 页(Word 不裁字);南沙五期 145 页 → 199 页。 差额来自「文字显示完整」,不是 bug。

空白页自查(verify_docx.py 查不出来,必须过一遍 PDF):

import pymupdf
d = pymupdf.open(pdf)
for i, p in enumerate(d):
    t = p.get_text().strip().replace(str(i + 1), "", 1).strip()
    if len(t) < 6 and len(p.get_drawings()) < 3:
        print("空白页", i + 1)

参考样张版式(实测值,见 references/参考样张-初设概算.md)

项 值
页边距 上下左右 2.5cm;页眉 1.5 / 页脚 1.75cm
表宽 100% 可用宽,tblInd=0,表居中
表级边框 全 none;边框画在单元格上
单元格边框 单线 黑 0.5pt(sz=4)
单元格边距 左右 5.4pt(108tw),上下 0;密集宽表压到 2pt
垂直对齐 全部 center
行高 hRule=atLeast,255–480tw
cantSplit 0 处(不禁止行跨页断开)
跨页表头 2–4 行(12 列表 3 行,9 列表 4 行,8 列表 2 行)
底纹 无彩色填充
表内第 1 行 跨全列合并的表名行,故不要另加标题段
字体 ascii/hAnsi/eastAsia/cs 全「宋体」,表内 9–10pt,表名行 12–16pt

格式映射表(逐项都踩过坑)

Excel openpyxl Word OOXML 坑
中文字体 font.name w:rFonts w:eastAsia 只设 font.name 中文不生效
字号 font.sz w:sz 半磅,10.5pt → 21
粗/斜/下划线/删除线 b/i/u/strike w:b / w:i / w:u / w:strike
字体色 font.color.rgb w:color theme 色取不到 RGB → 兜底黑
底纹 fill.fgColor w:shd w:fill 渐变填充读不到,只拿纯色
边框 border.X.style/color w:tcBorders/w:X 没有的边必须写 val="nil"
水平/垂直对齐 alignment.h/v w:jc / w:vAlign general 按类型推断
自动换行 alignment.wrap_text 天然换行 关的时候要写 w:noWrap 并缩字保单行
合并 merged_cells.ranges w:gridSpan / w:vMerge 锚点判定、界外裁剪、空段落
列宽 column_dimensions[X].width w:tblGrid + 每格 w:tcW 1 字符 ≈ 5.25pt;gridSpan 要累加
行高 row_dimensions[r].height w:trHeight + hRule 属性名是 hRule
数字格式 cell.number_format 渲染成字符串 General 单独处理;Decimal ROUND_HALF_UP;¥/万元 前后缀
公式 data_only=True 取缓存值 未用 Excel 存过则缓存空 → 必须告警
隐藏行列 hidden 跳过 常藏辅助计算列;隐藏表的锚点要重锚
打印区域 ws.print_area 取数区域 可能是 str 或 list;常出现 A1:IV18 拉满整行

文件清单

SKILL.md                      本文件(实现约定)
使用说明.md                    给使用者的完整操作手册
CHANGELOG.md                  版本记录
scripts/excel_to_word.py      主程序 v2.1.2(唯一路线)
scripts/verify_docx.py        逐格回读校验
references/参考样张-初设概算.md  版式基准实测数据
references/踩坑清单.md         症状→根因→修法
excel-to-word-离线版.html      浏览器离线版(单文件,双击即用,见下)
_backup/                      历史存档(v1.0 脚本、v1 VBA 宏),不要使用

离线网页版(excel-to-word-离线版.html)

把本技能完整搬进浏览器:单个 HTML 文件,约 113 KB,无任何外链、无网络请求, 双击用 Chrome / Edge 打开即用。适合把工具直接发给不用命令行、不装 Python 的同事。

  • 交互:上传 Excel 表 按钮 → 自动解析并列出各表纸型/表宽/压缩率 → 生成 Word → 下载 Word
  • 实现:zip.js(ZIP 读写,DecompressionStream('deflate-raw'))+ xlsx.js(自研 xlsx 解析, 口径对齐 openpyxl)+ ooxml.js(OOXML 元素树 + docx 打包)+ convert.js (逐函数移植 excel_to_word.py v2.2.0 的排版决策),四段按序内联在同一个 <script> 里
  • 与 CLI 等价性:同一份 xlsx,浏览器产物与 Python 产物逐字节一致(16 表 / 7050 格 / 48 页 / verify_docx.py 零差异),仅耗时不同
  • 参数差异:网页默认「保留 Excel 字号 + 列宽口径=兼容(openpyxl)」,与 CLI 的 --orient excel --no-scale 加同一 colWidthMode 等价;换口径时诊断数字会跟着变
  • 构建:源码在项目 build/src/,node build/bundle.mjs 把四个引擎文件内联进 app.template.html 的 /*__ENGINE__*/ 占位,产出单文件