前阵子被一段“从Word复制进百度UE编辑器后整篇乱掉”的稿子折腾了半宿。用户用的是百度UE,也就是大家熟知的UEditor,从Word 2019复制了一篇带三级列表、两张表格、若干截图的技术文档,粘贴进编辑器点击保存,再从前端页面打开,三级标题全变成普通段落,表格边框全线丢失,图片倒是还在,但所有列表都变成了带着诡异缩进的纯文本。更麻烦的是,编辑部同事用的Office版本五花八门,Word 2010、2016、WPS、Office 365都有,每台机器复制出来的“脏HTML”还都不一样。
排查到最后,问题基本锁定在“Word文档生成的那套HTML结构”和“UEditor自带的过滤规则”之间缺少一层翻译。这篇文章不打算铺垫太多背景,直接把我在实际项目里做的一层Word版本兼容扩展展开讲。如果你正在给UEditor写二次开发,或者需要处理“从不同版本Word/WPS复制粘贴进富文本编辑器后格式错乱”的问题,下面这套组件化扩展的思路、步骤和踩坑记录应该能帮你少走不少弯路。
1. 从编辑器粘贴事故说起:Word兼容问题的真正形态
1.1 一个让我决定改扩展的线上案例
先说那个让我决定动手的案例。当时用户反馈的是“复制进来以后内容本身没丢,但格式完全不是Word里的样子”,比如一个三级列表,在Word里看起来是“1.1.1 标题”这种自动编号,粘贴后却变成“1. 1.1 1.1.1”,或者序号全没了。再比如Word里带底纹的表格,粘到编辑器后底纹丢失,只剩一层不完整的border。
我第一反应是UEditor的默认过滤把样式吃了,于是调高了白名单,加了一堆allowClass、allowStyle配置,结果问题反而更严重了。后来把编辑器里的HTML复制出来,才看到里面残留了大量类似下面的内容:
html复制<!--[if gte mso 9]><xml><w:WordDocument><w:View>Normal</w:View><w:Zoom>0</w:Zoom>
<w:TrackMoves>false</w:TrackMoves><w:TrackFormatting></w:TrackFormatting>
<w:PunctuationKerning/><w:DrawingGridVerticalSpacing>7.8 磅</w:DrawingGridVerticalSpacing>
<w:DisplayHorizontalDrawingGridEvery>0</w:DisplayHorizontalDrawingGridEvery>
</w:WordDocument></xml><![endif]-->
这些是Word复制到剪贴板时自带的条件注释、命名空间声明和MSO样式。不同Office版本不会老老实实地给你一套标准HTML,而是带着各种自己的私有协议。UEditor就算内置了过滤,也挡不住这些结构在到达编辑器之前已经把样式层级弄乱了。
1.2 不同版本Word“穿”到剪贴板里的衣服
Word文档本身是二进制或者Office Open XML格式,但做“复制粘贴”时,Office会额外生成一份HTML放在系统剪贴板的text/html字段里。这份HTML不是简单地把内容转成<p>、<span>,而是夹杂着大量不同版本特有的“包装层”。
我拿实际复制出来的一段内容统计过,不同来源的HTML特征差异非常明显,见下表:
| 文档来源 | HTML常见特征 | 典型问题 |
|---|---|---|
| Word 2003 / 早期 .doc | 大量<xml>块、<o:p>空段落、VML图形描述 |
条件注释进入正文,段落间出现大量空行 |
| Word 2007/2010 (.docx) | xmlns:w="urn:schemas-microsoft-com:office:word",<style>里定义了大量.MsoNormal、.MsoListParagraph |
UEditor会丢弃<style>,导致样式依赖全部失效 |
| Word 2013/2016+ | 更强的mso-list段落模拟列表,class名称带MsoListParagraphCxSpFirst/Middle/Last |
三段为一个列表项,清洗后列表错乱严重 |
| Office 365 网页版 | 开始转向标准<ul>/<ol>,但style里仍带mso-私有属性,部分内联样式冲突 |
直接插进去之后,列表外观和编辑器主题样式互相打架 |
| WPS / 国产Office | 兼容Windows版Word产物,但同时会生成data-自定义属性和自己的一套字体回退 |
如果按“微软家族”去白名单,会漏掉很多属性 |
真正的问题不是文字编码或者中文字符,而是Word在“版本A”里用<p mso-list>模拟列表,在“版本B”里改用真正的<ul><li>,在“版本C”里又是<span>加手动编号。如果不做版本识别和结构归一化,只靠UEditor默认的filterWord,做一百遍都是一个结果——不同版本粘贴后表现不一样。
这也是为什么标题里的“版本兼容”四个字,本质上不是去兼容Word二进制格式,而是要兼容“Word通过剪贴板生成的那几种HTML方言”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 剖析UEditor的粘贴链路,找到最合适的扩展点
2.1 UEditor本身就是一套“过滤器链”
UEditor不像市面上那些轻量编辑器那样直接execCommand('insertHTML')就完事,它在把外部HTML插入编辑区域前,会经过一系列处理。这个过程大体是:
- 接收剪贴板里的
text/html; - 调用
UE.filterWord等工具对Word特征做初步过滤; - 经过XSS白名单过滤规则,把不在白名单里的标签和属性移除;
- 调用浏览器的
insertHTML能力把清理后的HTML插进可编辑区域; - 最后触发
contentchange,把内容同步到隐藏的textarea。
这里面最容易出问题的,是第3步。XSS过滤规则为了安全,默认会删除<style>、<xml>、<o:p>,还会把形如mso-list开头的内联样式当作罕见样式直接剥掉。结果就是,Word里那套依赖class="MsoListParagraph"和<xml>声明的列表逻辑,到了编辑器里变成了一堆没有层级关系的<p>标签。
所以,如果只依靠UEditor自带的处理,不同Word版本的产物基本都会被“一刀切”,但切完之后能不能恢复到Word里的视觉层级,就是纯运气了。正确做法是:在UEditor的默认过滤之前,先做一轮“版本归一化”,把各种Word方言翻译成UEditor认识的标准HTML,然后再让UEdit
