在线 Word 编辑模型设计
1. 目标
设计一个面向在线 Word 编辑器的简化模型,用于:
- 表达页眉、正文、页脚三段结构
- 表达段落、标题、列表、表格、图片、分页、分隔线、超链接等常见编辑能力
- 支持模板变量、数据集绑定、动态表格、图表和二维码占位替换
- 输出
docx - 支持从
docx回读到简化模型
模型设计参考 nop-excel 的对象模型和 ExcelTemplate 的输出方式,但不直接照搬 Excel 语义。
2. 能力范围
在线 Word 编辑模型至少覆盖:
- 页面宽高、方向、页边距、水印
- 首页是否显示页眉页脚
- 页眉、正文、页脚
- 文本、标题、列表、表格、图片、超链接、分页、分隔线
- 字体、字号、颜色、粗体、斜体、下划线、删除线、高亮、上下标
- 行对齐、行距
- 表格宽度、列宽、行高、单元格背景色、纵向对齐、跨行跨列
- 图表、条码、二维码占位与绑定
- 数据集驱动的纵向、横向、分组表格展开
3. 现有基础
仓库里已经有三类基础设施可以复用:
nop-excel:模型生成方式、样式列表、clone/freeze/init 模式nop-ooxml-docx:docxpackage 抽象、模板解析与输出nop-report-docx:Word 表格与报表展开算法的桥接
结论:
- 静态 Word 文档结构需要新建 Word 模型
- 动态表格不需要从零实现,应复用现有
nop-report-docx
4. 模型分层
这里需要补充一个关键约束:
- 参考 Nop 已内置的
WordTemplateParser/XptWordTemplateParser机制,以及workbook.xdef的设计方式,模板设计不能做成外挂的独立 settings 对象 - Word 模型本身既要表达静态文档结构,也要表达模板生成语义
- 因此模板语义应通过各层节点内嵌的
model子节点来承载,而不是再设计一套 springreport 风格的外部 JSON 配置表
建议顶层结构:
OfficeDocModel
- props
- styles
- assets
- pages
- model
其中:
pages/page/header/body/footer负责静态文档结构doc.model/page.model/block.model/run.model/table.model/cell.model负责模板设计- 产品管理类字段,例如模板名称、分类、发布状态,不进入文档模型本身
建议核心对象:
WordDocumentWordDocTemplateModelWordPageTemplateModelWordStylesWordSectionWordBlockWordBlockTemplateModelWordInlineWordRunTemplateModelWordTableWordTableTemplateModelWordChartBindingWordCodeBinding
5. 内容结构
块级对象:
ParagraphBlockTitleBlockListBlockTableBlockImageBlockHyperlinkBlockSeparatorBlockPageBreakBlock
行内对象:
TextRunTabRunHyperlinkRunSuperscriptRunSubscriptRunBreakRun
6. 样式策略
不建议直接复用 ExcelStyle 作为 Word 样式。
推荐拆分为:
WordRunStyleWordParagraphStyleWordCellStyleWordTableStyle
原因:
ExcelStyle是单元格样式,不适合作为段落/文本/表格统一样式- Word 需要高亮、行距、标题级别、列表样式等 Excel 不具备的语义
- 直接复用会把 Excel 模型污染成 Office 通用垃圾桶
7. 通用共享模型拆分
为了减少 Excel/Word 重复定义,推荐抽出共享基础模型层。
7.1 模块名和位置
推荐新增模块:
- 模块名:
nop-office-model - 位置:
nop-format/nop-office-model - 主包名:
io.nop.office.model
7.2 抽取原则
只抽“真正共享的叶子模型”,不把整个 Excel 模型上提。
当前优先抽取:
OfficeFont- 字体相关枚举
- 颜色辅助类
后续可继续评估:
- 通用边框样式
- 通用对齐枚举
- 页边距和页面方向
8. 本次实施的结构调整
本次已经按推荐方式完成第一步基础重构:
- 新增模块
nop-format/nop-office-model - 新增共享字体模型
io.nop.office.model.OfficeFont - 新增字体相关枚举:
OfficeFontFamilyOfficeFontUnderlineOfficeFontVerticalAlign
- 新增共享颜色辅助类
OfficeColorHelper - 新增元模型
/nop/schema/office/font.xdef - 新模块增加了
precompile/gen-office-xdsl.xgen - 新模块
pom.xml已增加exec-maven-plugin nop-format/pom.xml已注册nop-office-modelnop-bom/pom.xml已加入nop-office-model- nop-excel/pom.xml 已增加对
nop-office-model的依赖 nop-excel/precompile/gen-excel-xdsl.xgen已移除对excel/font.xdef的直接生成
9. 元模型调整
共享字体元模型已从 Excel 语义抽出:
- 新位置:
/nop/schema/office/font.xdef - 新生成类:
io.nop.office.model.OfficeFont
旧的 /nop/schema/excel/font.xdef 保留为兼容入口,但语义上已经切换为复用 Office 字体定义。
这一步的目的:
- 新增 Word 模型时不再引入
ExcelFont命名污染 - 后续 Excel 与 Word 可以共用同一份字体元模型
- 代码生成链路仍然通过
precompile自动执行
10. docx 生成设计
参考 ExcelTemplate,建议新增:
WordDocumentTemplate extends AbstractOfficeTemplateWordDocumentParserWordModelToDocxTransformer
生成链路:
WordDocument
-> normalize/init
-> resolve styles/assets/bindings
-> render to WordOfficePackage
-> zip to docx
动态表格策略:
- 静态表格直接渲染
<w:tbl> - 动态表格复用
nop-report-docx - 图表和二维码沿用“图片占位 + 绑定定义 + 运行时替换”模式
10.1 与 Nop 内置 Word 模板机制对齐
当前 Nop 已经内置了两套非常关键的 Word 模板能力:
WordTemplateParserXptWordTemplateParser
它们分别对应:
- 通过
expr:/xpl:超链接将 docx 编译为 XPL 模板 - 通过
xpt:table标注把 Word 表格接入 NopReport
因此新的 Word 模型设计不应该绕开这两套能力,而应该把它们模型化。
推荐映射关系:
doc.model对应当前XplGenConfig的内嵌版,承载importLibs、beforeGen、afterGen、dump等能力page.model对应节/页面级的testExpr、循环与生命周期钩子run.model对应当前超链接模板中的expr/tpl_expr/ 常见样式表达式image.model对应图片替换场景中的dataExpr、testExprtable.model对应当前xpt:table标记与未来结构化表格模板能力row.model/cell.model对齐excel-table.xdef中XptRowModel/XptCellModel的做法,内嵌测试、取值、格式化、样式等规则
一句话:
Word 模型不是替代 Nop 内置 WordTemplate 机制,而是把它结构化、类型化,并内嵌到文档模型中。
10.2 参考 workbook.xdef 的内嵌方式
workbook.xdef 的关键经验不是“再加一个 settings 对象”,而是:
- 在 workbook 顶层通过
<model>表达工作簿级模板行为 - 在 sheet 级通过
<model>表达循环、测试、生命周期 - 在 row/cell/image/chart 等节点上就近放置模板语义
Word 侧推荐采用相同策略。
示意结构如下:
<doc>
<pages>...</pages>
<model xdef:name="WordDocTemplateModel"
loopVarName="var-name"
loopIndexName="var-name"
loopItemsName="var-name"
dump="boolean"
dumpFile="string">
<importLibs>csv-set</importLibs>
<testExpr>xpl-predicate</testExpr>
<beginLoop>xpl</beginLoop>
<endLoop>xpl</endLoop>
<beforeGen>xpl</beforeGen>
<afterGen>xpl</afterGen>
</model>
</doc>
<table>
<rows>...</rows>
<model xdef:name="WordTableTemplateModel" renderType="string">
<testExpr>xpl-predicate</testExpr>
<styleIdExpr>report-expr</styleIdExpr>
<expandExpr>xpl</expandExpr>
<beforeRender>xpl</beforeRender>
<afterRender>xpl</afterRender>
</model>
</table>
<cell>
<value>string</value>
<model xdef:name="WordCellTemplateModel">
<valueExpr>report-expr</valueExpr>
<formatExpr>report-expr</formatExpr>
<styleIdExpr>report-expr</styleIdExpr>
<testExpr>xpl-predicate</testExpr>
</model>
</cell>
这里的字段名不一定最终逐字照抄,但建模原则应该保持一致。
10.2.1 当前已落地的第一版结构
本轮已经把第一版模板模型直接落到 Word schema 和 Java 模型中:
doc.model -> OfficeDocTemplateModelpage.model -> OfficeDocPageTemplateModelp.model -> OfficeParagraphTemplateModelr.model -> OfficeRunTemplateModeltable.model -> WordTableTemplateModelcol.model -> WordTableColumnTemplateModelrow.model -> WordTableRowTemplateModelcell.model -> WordTableCellTemplateModel
当前第一版字段范围是保守设计:
- 文档级:
dump、dumpFile、normalizeQuote、deleteAllAfterConfigTable、importLibs、testExpr、beginLoop、endLoop、beforeGen、afterGen - 页面级:
testExpr、beginLoop、endLoop、beforeRender、afterRender - 段落级:
testExpr、visibleExpr、beforeRender、afterRender - run 级:
testExpr、valueExpr、formatExpr、linkExpr、templateExpr、beforeRender、afterRender - table 级:
testExpr、dataExpr、expandExpr、styleIdExpr、beforeRender、afterRender - col/row/cell 级:宽度、高度、样式、可见性、取值、格式化、处理表达式等
同时补充了 makeModel() 便捷方法,和 ExcelSheet/ExcelCell/ExcelRow 的体验保持一致。
这意味着当前 Word 模型已经不只是“静态文档结构”,而是具备了与 workbook.xdef 同构的第一版模板承载能力。
10.3 保留高级逃生口
Nop 当前 Word 模板机制的优势在于它支持完整 xpl 标签,而不仅是简单表达式。
因此结构化模型除了提供常见的 valueExpr/testExpr/styleIdExpr 之外,还应该保留高级逃生口:
- 支持节点级扩展属性
- 必要时支持内嵌原始
xpl片段 - 对暂时无法结构化表达的 OOXML 细节允许保留原始
XNode或等价扩展信息
否则会丢掉当前 WordTemplateParser 最大的表达能力优势。
11. 后续实施顺序
建议按下面顺序继续推进:
- 在
doc.xdef、word-table.xdef中增加model子节点,先补齐文档级、页面级、表格级模板模型 - 将当前
XplGenConfig语义迁移为doc.model的内嵌结构 - 将
xpt:table语义迁移为table.model的结构化表达 - 继续扩展 paragraph/run/image/cell 级模板模型
- 建立
WordDocumentModel -> WordTemplate/XPL的编译链路 - 打通静态
docx输出与动态模板输出的统一出口 - 实现
docx template -> WordDocumentModel的回读映射,兼容现有超链接/注释标注方式
12. 结论
推荐路线是:
- Word 模型独立建立
- 共享基础模型逐步抽到
nop-office-model - 模板设计内嵌在 Word 模型本身,方式参考
workbook.xdef docx输出和动态模板编译复用现有nop-ooxml-docx和nop-report-docx- 不把模板能力再拆成外挂的
WordTemplateSettings
一句话:
Word 模型独立建设,共享叶子模型逐步沉淀到 nop-office-model,模板设计则像 workbook.xdef 一样内嵌在模型本身。
13. 与 springreport 的分层对比结论
补充对 springreport 的实际实现分析后,可以把它的能力拆成三层:
- 文档结构层
- 模板配置层
- 产品化编辑层
13.1 文档结构层
springreport 的 DocTplSettings.header/footer/main 本质上保存的是一组 JSON 块数据,而不是强类型文档对象模型。
从 uploadDocx 和 parseTable/parseParagraph 的实现看,它能表达的核心结构包括:
- 页眉、正文、页脚
- 普通段落、标题、列表
- 文本 run 样式:字体、字号、颜色、粗斜体、下划线、删除线、高亮、上下标
- 图片、超链接、分页、分隔线
- 表格、列宽、行高、背景色、纵向对齐、跨行跨列
这部分能力与我们当前已经落地的 OfficeDocModel + OfficeParagraphModel + OfficeRunModel + WordTable 基本处于同一层,说明:
- 当前 nop Word 模型对于 springreport 的基础文档结构表达已经基本够用
- 不需要为了对齐 springreport 再退回到“前端 JSON DTO 即最终模型”的做法
13.2 模板配置层
这里需要修正一个表述:
- 从 springreport 观察到的“模板配置能力”不能简单等价为“应该存在一个外挂配置对象”
- 参考
workbook.xdef,真正参与文档生成的模板语义,应该尽量进入 Word 模型本身 - 只有模板名称、分类、发布、权限、预览缓存等产品管理信息,才应该留在模型外部
换句话说,springreport 真正提示我们的不是“再加一个 settings 表”,而是 Word 模型除了静态结构之外,还需要承载模板行为。
其中值得吸收到模型内的能力包括:
DocTpl.firstpageHeaderFooterShowDocTplSettings.watermarkDocTplChartsDocTplCodes- 数据集驱动的 vertical/horizontal/group 扩展策略
这些能力多数属于:
- 页面设置
- 运行时绑定定义
- 模板级业务配置
它们不应该强塞进 WordRunStyle、WordParagraphStyle 这种纯样式对象里,但应通过 doc.model/page.model/table.model/... 内嵌到文档模型。
paramMerge 这类主要影响预览查询参数拼装的策略,更接近报表运行控制,可根据最终实现决定放在文档级 model 还是外层报表定义中,但不应退化成 springreport 式的散乱外部 JSON。
13.3 产品化编辑层
springreport 前端还提供:
- 分类管理
- 模板上传/导入
- 设计器交互
- 模板预览与导出
- 图表/二维码/条码配置面板
这些是产品功能和编辑器工作流,不等同于底层文档模型能力。
因此评估“模型是否够用”时,不能把:
- 文档结构表达能力
- 模板配置能力
- 编辑器产品能力
混成一个问题。
13.4 对当前 nop 方案的直接结论
如果对比目标是 springreport 的基础 Word 文档结构表达:
- 当前 nop Word 模型已经基本足够
如果对比目标是 springreport 的完整模板产品能力:
- 当前 nop 还不完整
- 但短板主要在“模板语义尚未内嵌到模型”,而不是 Word 结构对象本身方向错了
13.5 后续补充方向
下一步如果继续补齐 springreport 风格能力,优先应补:
doc.model,承载当前XplGenConfig与文档级模板生命周期能力page.model,承载页面级条件、循环与页眉页脚行为table.model/row.model/cell.model,承载 XPT 表格展开和结构化取值能力run.model/image.model,承载expr/xpl/dataExpr/testExpr等常见模板语义- 文档模型上的扩展属性与高级
xpl逃生口
不建议继续扩大底层 Word 结构模型的职责,更不建议重新依赖 excel-table.xdef。