- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
本文围绕 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复现步骤原文如下:
- 使用 Microsoft Word 打开或创建一个包含列表的文档(仓库提供的样例文档为 Ordered_list_multiple.docx,这是一个多级有序列表:
1. this/2. is/3. a/a. multi/i. level/b. ordered/4. list)。 - 全选整个列表。
- 将其粘贴进 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 ) ); }从源码结构看,完整链路为:
- Word 识别:
msoListsDetected通过正则mso-list:\s*l\d+\s+level\d+\s+lfo\d+确认粘贴内容确属 Word(L69),避免把普通 HTML 列表误当 Word 列表处理; - 溶解现有列表:
ul/ol规则调用 List.dissolveList,按list-style-type为每个<li>计算符号(cke-symbol)与层级(cke-list-level),并把<li>改名为<p>打cke-dissolved标记; - 伪列表项转换:
thisIsAListItem→convertToFakeListItem(L513-L577),元素改名为cke:li,同时处理margin-left缩进校正;16826 的嵌套段落剥离就发生在这一阶段之内; - 层级信息解析:List.getListItemInfo 从
mso-list属性解析出列表id与level; - 重建嵌套树:
List.createLists(L865)先将cke:li批量转回真实<li>(convertToRealListItems),再按cke-list-level用容器栈生成ol > li > ol > li之类的嵌套结构,设置list-style-type、start、li[value]等; - 清理临时属性:
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。验证该缺陷修复时:
- 在支持的环境中安装依赖并启动 bender runner(以仓库根目录的
package.json脚本与 bender 配置为准); - 定位到
tests/tickets/16826/对应用例(ielists),在 IE 环境(或具备 IE 的 CI 矩阵)中执行:从 Word 打开样例Ordered_list_multiple.docx→ 全选列表 → 粘贴 → 检查源码视图中<li>内是否还有<p>; - 交叉运行
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-level | plugins/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.
相关推荐
Respect Validation Extension 验证器实战指南:PHP 文件扩展名校验原理、模板定制与测试用例解析
Respect Validation Extension 验证器实战指南:PHP 文件扩展名校验原理、模板定制与测试用例解析 本文是 Respect\Valid
前端UI组件CKEditor 4 粘贴部分列表项失败的修复原理与手动回归测试指南
CKEditor 4 粘贴部分列表项失败的修复原理与手动回归测试指南 导读 本文以 CKEditor 4 仓库中的手动回归测试 pastepartiallist
前端UI组件CKEditor 4 从 Word 粘贴“特殊字符项目符号”无序列表崩溃(877):问题复现、修复原理与回归测试指南
CKEditor 4 从 Word 粘贴“特殊字符项目符号”无序列表崩溃( 877):问题复现、修复原理与回归测试指南 本文围绕 CKEditor 4 仓库中
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考