news 2026/10/17 2:33:41

CKEditor 4 pastefromword:IE 环境下 Word 列表粘贴回归(Trac 16826)复现、修复原理与验证机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CKEditor 4 pastefromword:IE 环境下 Word 列表粘贴回归(Trac 16826)复现、修复原理与验证机制
  • 前端
  • UI组件

【免费下载链接】ckeditor4

The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.

项目地址:https://gitcode.com/gh_mirrors/ck/ckeditor4
点击查看免费下载

本文围绕 CKEditor 4 仓库中针对 Trac 16826 缺陷的回归测试文档展开:在 IE 浏览器中从 Microsoft Word 粘贴多行列表时,列表项<li>内部被错误地嵌套了段落<p>元素。读完后你将掌握该缺陷的完整复现步骤、pastefromword过滤链中针对 IE 嵌套段落的具体修复实现,以及仓库中配套的 bender 回归测试与 fixture 组织方式。

一、缺陷复现步骤(原回归测试文档内容)

仓库中 tests/tickets/16826/ielists.md 是一份典型的 CKEditor 4 "ticket 回归测试"描述文档,其 bender 标签声明了缺陷属性与所需插件:

@bender-tags: bug, 4.7.0, trac16826, pastefromword @bender-ui: collapsed @bender-ckeditor-plugins: wysiwygarea, toolbar, undo, pastefromword, sourcearea, elementspath, list

复现步骤原文如下:

  1. 使用 Microsoft Word 打开或创建一个包含列表的文档(仓库提供的样例文档为 Ordered_list_multiple.docx,这是一个多级有序列表:1. this/2. is/3. a/a. multi/i. level/b. ordered/4. list)。
  2. 全选整个列表。
  3. 将其粘贴进 CKEditor 实例。

期望结果(Expected):列表项内部不包含段落元素(List items doesn't have paragraphs in it)。

非期望结果(Unexpected):<li>元素内存在段落(Paragraphs are present inlielements)。

配套的测试页面 tests/tickets/16826/ielists.html 中有一个关键守卫:

if ( !CKEDITOR.env.ie ) { bender.ignore(); }

也就是说该用例只在 IE 浏览器中执行——这正是缺陷只与 IE 的粘贴行为相关的原因。

二、为什么只有 IE 会在<li>内嵌套<p>

对比 fixture 目录下不同浏览器的原始粘贴数据即可看出差异。以 word2013 的 IE11 原始数据 为例,其结构为:

<ol style="list-style-type: decimal; direction: ltr;"> <li style="font-style: normal; font-weight: normal;"> <p style="... mso-list: l0 level1 lfo1;">This</p> </li> <li style='...'> <p style='... mso-list: l0 level1 lfo1;'>Is</p> </li> <li style='...'> <p style='... mso-list: l0 level1 lfo1;'>A</p> <ol style="list-style-type: lower-alpha;"> <li style='...'><p style='... mso-list: l0 level2 lfo1; ...'>Multi</p>...</li> </ol> </li> ... </ol>

每个<li>内都包着一层带mso-list: l0 levelN lfo1样式的<p>;而同一 Word 版本下 Chrome/Firefox 的 fixture(如 word2013/chrome.html)中<li>直接包含内联内容,没有这层段落包裹。源码注释也直接点明了这一点(见 plugins/pastefromword/filter/default.js#L160-L162):

IE pastes nested paragraphs in list items, which is different from other browsers. (https://dev.ckeditor.com/ticket/16826)

这带来两个具体问题:一是语义上不干净的<li><p>…</p></li>结构;二是 IE 把列表层级信息写在<li>内部<p>的mso-list样式上,过滤器需要到嵌套段落中才能取到层级数据。

三、过滤链中的 IE 专门修复

修复逻辑位于 Word 粘贴过滤器的段落处理规则中,见 plugins/pastefromword/filter/default.js#L146-L201 的p元素规则。处理流程分两支:

分支一:该段落本身是一个列表项(List.thisIsAListItem判定为真,见 L484-L502),执行 16826 的核心修复:

// IE pastes nested paragraphs in list items, which is different from other browsers. (https://dev.ckeditor.com/ticket/16826) // There's a possibility that list item will contain multiple paragraphs, in that case we want // to split them with BR. tools.array.reduce( element.children, function( paragraphsReplaced, node ) { if ( node.name === 'p' ) { // If there were already paragraphs replaced, put a br before this paragraph, so that // it's inline children are displayed in a next line. if ( paragraphsReplaced > 0 ) { var br = new CKEDITOR.htmlParser.element( 'br' ); br.insertBefore( node ); } node.replaceWithChildren(); paragraphsReplaced += 1; } return paragraphsReplaced; }, 0 );

要点:

  • 逐个检查列表项(此处已被List.convertToFakeListItem转换为cke:li的伪列表项)的子节点,发现内嵌<p>时调用replaceWithChildren()将段落"展开"为其内联子节点,消除<li>内的段落层;
  • 如果一个列表项包含多个段落,则从第二个段落起在其前插入<br>,保证原有换行呈现不被丢失。

分支二:该段落不是列表项,但其祖先<ul>/<ol>尚未携带层级信息(L179-L189)。注释写道:"In IE list level information is stored in<p>elements inside<li>elements",因此从<p>的mso-list: l0 level2 lfo1样式中解析出level数字,写入祖先列表容器的cke-list-level属性,供后续createLists构建嵌套结构使用。

此外,ul/ol规则(L262-L301)还处理了 IE11 下列表跳级时<ol>被隐式包进<li>的边界情况,给外层<li>打上list-style-type: none,随后统一走List.dissolveList溶解。

四、列表重建流水线(修复所处位置)

理解上述修复需要看它在整条过滤流水线中的位置。过滤规则入口plug.rules中,根节点规则先完成全部过滤,再把所有"伪列表项"重建成真实列表树(L72-L76):

root: function( element ) { element.filterChildren( filter ); CKEDITOR.plugins.pastefromword.lists.cleanup( List.createLists( element, editor ) ); }

从源码结构看,完整链路为:

  1. Word 识别:msoListsDetected通过正则mso-list:\s*l\d+\s+level\d+\s+lfo\d+确认粘贴内容确属 Word(L69),避免把普通 HTML 列表误当 Word 列表处理;
  2. 溶解现有列表:ul/ol规则调用 List.dissolveList,按list-style-type为每个<li>计算符号(cke-symbol)与层级(cke-list-level),并把<li>改名为<p>打cke-dissolved标记;
  3. 伪列表项转换:thisIsAListItem→convertToFakeListItem(L513-L577),元素改名为cke:li,同时处理margin-left缩进校正;16826 的嵌套段落剥离就发生在这一阶段之内;
  4. 层级信息解析:List.getListItemInfo 从mso-list属性解析出列表id与level;
  5. 重建嵌套树:List.createLists(L865)先将cke:li批量转回真实<li>(convertToRealListItems),再按cke-list-level用容器栈生成ol > li > ol > li之类的嵌套结构,设置list-style-type、start、li[value]等;
  6. 清理临时属性:List.cleanup(L1051-L1067)删除cke-list-level、cke-symbol、cke-dissolved等辅助属性。

16826 的修复正是第 3 步中针对 IE 粘贴特征(<li>内嵌<p>)的归一化,使 IE 的数据与 Chrome/Firefox 的扁平结构对齐后再进入统一的重建逻辑。

五、期望输出对照与自动化回归

fixture 目录 tests/plugins/pastefromword/generated/_fixtures/Ordered_list_multiple/ 按 Word 版本与浏览器组织数据:word2007/、word2013/、word2016/各自存放原始粘贴 HTML 与expected_*.html期望输出,根目录另有 expected.html 作为基线。修复后的目标输出是一个无段落嵌套的干净有序列表:

<ol> <li>this</li> <li>is</li> <li>a <ol style="list-style-type:lower-alpha"> <li>multi <ol style="list-style-type:lower-roman"> <li>level</li> </ol> </li> <li>ordered</li> </ol> </li> <li>list</li> </ol>

而 IE11 专属期望 expected_ie11.html 保留了 IE 粘贴的字体样式信息,但同样验证了<li>内不再有<p>、且lower-alpha/lower-roman嵌套层级正确。

自动化回归则由生成式测试套件驱动:

  • tests/plugins/pastefromword/generated/tickets6.js:在 chrome/edge + word2016 组合下将Ordered_list_multiple等 fixture 纳入测试集(compareRawData: true做原始数据比对),并注明ignoreAll: ( CKEDITOR.env.ie && CKEDITOR.env.version <= 11 ) || bender.tools.env.mobile,即 IE 场景交给人工/环境限定用例;
  • tests/plugins/pastefromword/generated/tickets7.js:在pasteFromWord_heuristicsEdgeList = false配置下验证No_heuristics/Ordered_list_multiple变体;
  • tests/plugins/pastefromword/generated/generic.js:通用套件中同样启用Ordered_list_multiple与Ordered_list_multiple_edgy。

结合 ielists.html 中仅 IE 生效的守卫,形成了"自动生成套件覆盖主流浏览器 + 独立 ticket 用例钉死 IE 行为"的双层回归保障。

六、运行与验证方式

本仓库测试基于 bender 框架,入口配置见 bender.js 与 bender-runner.config.json。验证该缺陷修复时:

  1. 在支持的环境中安装依赖并启动 bender runner(以仓库根目录的package.json脚本与 bender 配置为准);
  2. 定位到tests/tickets/16826/对应用例(ielists),在 IE 环境(或具备 IE 的 CI 矩阵)中执行:从 Word 打开样例Ordered_list_multiple.docx→ 全选列表 → 粘贴 → 检查源码视图中<li>内是否还有<p>;
  3. 交叉运行tests/plugins/pastefromword/generated/下的tickets6.js、tickets7.js、generic.js,确认Ordered_list_multiple各变体在 word2007/2013/2016 × 多浏览器矩阵下均通过。

适用前提与限制:该用例依赖真实 IE 浏览器环境,ielists.html在非 IE 环境会自动bender.ignore();fixture 的期望输出与具体 Word 版本强绑定(目录中按word2007/word2013/word2016分列),更换 Word 版本重新生成粘贴数据时需同步更新对应expected_*.html。

七、小结

要素位置
回归测试描述与复现步骤tests/tickets/16826/ielists.md
IE 限定测试页tests/tickets/16826/ielists.html
样例 Word 文档Ordered_list_multiple.docx
IE 修复核心代码(嵌套<p>剥离 +<br>拆分)plugins/pastefromword/filter/default.js#L160-L177
IE 层级信息回写cke-list-levelplugins/pastefromword/filter/default.js#L179-L189
列表重建与清理plugins/pastefromword/filter/default.js#L865、#L1051-L1067
自动化回归套件tickets6.js、tickets7.js、generic.js

16826 虽然只是一个"列表项内不应出现段落"的小缺陷,但它暴露了跨浏览器粘贴 HTML 结构差异这一普遍问题:pastefromword过滤器通过"识别来源 → 溶解归一 → 伪列表项 → 重建嵌套树 → 清理临时属性"的流水线,把 IE 特有的嵌套段落归一化后汇入统一重建逻辑,并以 fixture 期望文件 + 自动生成测试套件的方式把回归钉死在 CI 可复验的形态上。

  • 前端
  • UI组件

【免费下载链接】ckeditor4

The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.

项目地址:https://gitcode.com/gh_mirrors/ck/ckeditor4
点击查看免费下载

相关推荐

上一篇:Kener 状态页监控嵌入完全指南:JavaScript 脚本与 iframe 两种方案的实战手册
下一篇:OpenOcta八爪鱼完全指南:中国首个开源桌面级AI智能体,双击安装30秒上手

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 2:33:15

双节锂电池主动均衡系统设计:MP2642GR-Z与TM4C129XKCZAD协同实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 2:31:12

Java Socket文件传输实战:从基础到断点续传与完整性校验

1. 为什么文件传输这件事&#xff0c;绕不开Socket很多人第一次接触网络编程&#xff0c;都是从"传个文件"开始的。听起来简单&#xff0c;但真动手写的时候&#xff0c;问题一个接一个&#xff1a;文件传过去少了几KB、大文件传到一半卡死、中文文件名变成乱码、服务…

作者头像 李华
网站建设 2026/10/17 2:31:00

纯CSS家族树:零依赖实现树形结构布局的全流程解析

简介&#xff1a;纯CSS实现的可无限扩展族谱树形结构&#xff0c;专为Web前端、HTML5与PHP开发场景打造&#xff0c;可直观呈现家谱、组织架构、目录树等层次化数据&#xff0c;也能用于决策树与流程图的导航展示。压缩包内仅有1个HTML文件&#xff0c;体积仅2KB&#xff0c;无…

作者头像 李华
网站建设 2026/10/17 2:30:49

十一、内部类

一、概念1.内部类&#xff1a;定义在一个类内部的类// 外部类 class Outer{// 内部类class Inner{} }2.内部类编译后会生成独立的.class文件&#xff0c;命名格式&#xff1a;外部类名&#xff1a;外部类名$内部类名.class3.内部类可以直接使用外部类私有成员&#xff0c;不破坏…

作者头像 李华
网站建设 2026/10/17 2:30:36

两节串联电池主动均衡方案:MP2642GR-Z与PIC32MZ2048EFH144实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华