CKEditor 4 粘贴 LibreOffice 文档:页眉页脚图片提取规则详解与手动测试指南
CKEditor 4 粘贴 LibreOffice 文档:页眉页脚图片提取规则详解与手动测试指南
本篇技术指南围绕 CKEditor 4 的 pastefromlibreoffice 插件及其配套手动测试用例展开,重点解读"从 LibreOffice 文档粘贴时,正文中的图片应被正确粘贴,而页眉/页脚(header/footer)中的图片不应被提取"这一核心行为。读者将掌握:该插件处理粘贴图片的完整数据流、RTF 内容中页眉页脚分组的过滤机制、如何运行并判读 imagesinheaderfooter 手动测试,以及相关自动化测试夹具的组织方式。
一、测试文档概览:验证什么、预期什么
关联文档 imagesinheaderfooter.md 是 pastefromlibreoffice 插件的一组手动测试(manual test) 之一,归属于 bender 测试框架。该文档通过 bender 元标签声明了测试属性与所需插件:
@bender-tags: feature, 4.16.0, 2800, pastefromword
@bender-ui: collapsed
@bender-ckeditor-plugins: wysiwygarea, toolbar, undo, pastefromlibreoffice, image, pagebreak, autogrow
feature:这是一个功能特性类测试;4.16.0:该特性随 CKEditor 4.16.0 引入;2800:对应的内部工单(ticket)编号,围绕图片提取(images extraction)主题,同一编号还关联了imagesduplicated、imagesunsupported两个手动测试(见 tests/plugins/pastefromlibreoffice/manual 目录下的同名.md/.html文件);bender-ckeditor-plugins:运行本用例时编辑器需加载的插件集合,包括核心的pastefromlibreoffice、image(图片渲染)与pagebreak(分页符渲染,因为测试夹具文档中含分页符)。
配套的 HTML 宿主文件 imagesinheaderfooter.html 展示了用例运行环境:
<div id="editor"></div>
<script>
if ( bender.tools.env.mobile || CKEDITOR.env.ie || CKEDITOR.env.safari ) {
bender.ignore();
}
CKEDITOR.replace( 'editor' );
</script>
即在移动端、IE 与 Safari 环境下用例会被 bender.ignore() 忽略——这与 plugin.js 中 isSupportedEnvironment() 的定义保持一致(Safari 与 IE≤11 不支持该插件)。
操作步骤:将 InHeaderFooterComplex.odt 的内容复制并粘贴到编辑器。
预期结果(Expected):
- 正文(main content)中的所有图片与其他内容一起被正确粘贴。
非预期结果(Unexpected):
- 正文中的图片没有被正确粘贴;
- 页眉/页脚中的图片被粘贴进了编辑器。
简言之,这是一条"页眉页脚图片不应被提取、正文图片必须被提取"的过滤规则测试。
二、测试夹具:InHeaderFooterComplex 文档结构
测试文档使用的夹具位于 InHeaderFooterComplex 目录,包含:
InHeaderFooterComplex.odt:LibreOffice 源文档(含复杂页眉页脚);expected.html:期望的粘贴输出(正文两张图片 + 分页符,不含页眉页脚图片);libreoffice7/子目录:分别记录 Chrome/Firefox 浏览器在 LibreOffice 7 环境下复制时的chrome.html/chrome.rtf与firefox.html/firefox.rtf剪贴板数据,作为自动化对比测试的基准输入。
从 expected.html 可以直观看到期望结果:粘贴后仅包含正文中的两张图片(以 data:image/png;base64,... Data URL 形式内联),以及两个由 page-break-after: always 样式生成的 cke_pagebreak 分页符;没有任何来自页眉/页脚的图片。
而 chrome.html(LibreOffice 7 在 Chrome 中复制得到的 HTML)只含正文的 <img> 标签,图片 src 为本地临时文件路径(file:///Users/.../lu224761cox90.tmp/...png);页眉页脚内容存在于 RTF 而非 HTML 中。
ImagesExtraction 夹具家族
InHeaderFooterComplex 仅是 ImagesExtraction 夹具组之一,同组还有 AnimatedGif、DrawnObject、DuplicatedImage、InFooter、InHeader、InHeaderFooter、InHeaderFooterCurlyBraces、UnsupportedFormats、WrappedImages 共 10 个场景,共同覆盖图片提取的各种边界情况(动图、绘制对象、重复图片、仅页眉/仅页脚/页眉页脚并存、花括号文件名、不支持格式、文字环绕等)。详见 tests/plugins/pastefromlibreoffice/generated/_fixtures/ImagesExtraction。
三、手动测试的判读要点
执行粘贴后按以下规则判定结果:
| 表现 | 判定 |
|---|---|
| 正文图片全部随内容粘贴,页眉页脚图片不出现 | ✅ 符合预期 |
| 正文图片丢失或渲染异常 | ❌ 不符合预期 |
| 页眉/页脚图片出现在编辑器中 | ❌ 不符合预期 |
配套的手动测试 imagesduplicated.md 与 imagesunsupported.md 还补充了同编号特性(2800)的另外两个检查点:重复图片是否都能正确渲染、控制台是否有 pastetools 相关报错;不支持格式(如 EMF/WMF)图片是否被跳过并产生 pastetools-unsupported-image 控制台错误。
四、底层实现:图片为什么不会被从页眉页脚提取
要理解该手动测试背后的原理,需要沿着粘贴数据流阅读源码。
4.1 插件注册与分发
plugins/pastefromlibreoffice/plugin.js 中,插件依赖 pastetools,并通过 editor.pasteTools.register() 注册一个优先级为 100(在 Paste from Word 之后)的处理器,依次加载三个过滤器:
filters: [
CKEDITOR.getUrl( pasteToolsPath + 'filter/common.js' ),
CKEDITOR.getUrl( pasteToolsPath + 'filter/image.js' ),
CKEDITOR.getUrl( path + 'filter/default.js' )
]
处理器的 canHandle() 通过 CKEDITOR.plugins.pastetools.getContentGeneratorName( textHtml ) 识别剪贴板 HTML 是否由 LibreOffice 生成;handle() 中先调用 CKEDITOR.pasteFilters.image( clipboardHtml, editor, rtf ) 做图片提取,再调用 CKEDITOR.pasteFilters.libreoffice 做样式规范化,最后把处理结果写回 data.dataValue。
4.2 RTF 图片提取的核心过滤逻辑
图片提取的核心在 plugins/pastetools/filter/image.js 的 extractFromRtf() 中。关键的一步是:
// Remove headers, footers, non-Word images and drawn objects.
// Headers and footers are in \header* and \footer* groups,
// non-Word images are inside \nonshp groups.
// Drawn objects are inside \shprslt and could be e.g. image alignment.
rtfContent = filter.removeGroups( rtfContent, '(?:(?:header|footer)[lrf]?|nonshppict|shprslt)' );
wholeImages = filter.getGroups( rtfContent, 'pict' );
即:在解析 \pict 图片组之前,先用正则把 RTF 中的 \header、\footer 及其变体(\headerl/r/f、\footerl/r/f)分组整体剔除,同时也剔除非 Word 图片组 \nonshppict 与绘制对象组 \shprslt,然后才从剩余的 \pict 组中提取图片。这正是"页眉页脚图片不会被粘贴"的实现依据——它们所在的 RTF 分组在提取前已被移除。
在 chrome.rtf 中可以看到页眉内容实际存放于 \headerf 等分组中(例如首行页眉图片出现在 {\*\shppict{\pict{...}\pngblip ...}} 结构内),与上述过滤注释描述的 \header*/\footer* 组形态一致。
4.3 图片去重、格式识别与错误上报
extractFromRtf() 还处理了多种边界情况:
- 重复图片去重:通过
\blipuid或\bliptag提取图片 ID,同一图片被多次插入时复用同一份数据; - 备用格式跳过:同一图片紧跟原始图以另一种格式出现时(
isAlternateFormat)跳过,避免重复提取; - WordArt 形状过滤:含
\defshp的图片组被跳过; - 格式识别:通过
\pngblip、\jpegblip、\emfblip、\wmetafile标记识别 PNG/JPEG/EMF/WMF 类型(见recognizableImageTypes),仅image/png、image/jpeg、image/gif(supportedImageTypes)会被转换为 Base64 Data URL; - 数量不匹配报错:若 HTML 中
<img>数量与 RTF 中提取的图片数量不一致,抛出pastetools-failed-image-extraction错误;若图片类型不支持,抛出pastetools-unsupported-image错误。
4.4 最终写入编辑器
提取出的十六进制图片数据经 createSrcWithBase64() 转为 data:image/png;base64,... Data URL,并通过正则将 HTML 中 file:// 形式的临时路径替换为新地址,从而让正文图片以可渲染的内联数据形式进入编辑器(见 expected.html 中的 <img src="data:image/png;base64,...">)。
五、自动化测试:同一场景的双重保障
该手动测试对应的自动化用例在 generated/imagesextraction.js 中。它以 createTestSuite 驱动,覆盖 Chrome 与 Firefox 两个浏览器、LibreOffice 7 一个版本,并将 ImagesExtraction/InHeaderFooterComplex 等 10 个夹具场景全部纳入断言(includeRTF: true 表示同时使用 RTF 剪贴板数据作为输入)。测试通过 _helpers/config.js(tests/plugins/pastefromlibreoffice/generated/_helpers/config.js)注入 pfloConfig 公共配置,并用自定义过滤器剥离 data-cke-saved-src 属性后与 expected.html 逐字节对比。
因此,本手动测试与 generated/imagesextraction.js 自动化用例互为印证:自动化测试保证回归、手动测试提供真实浏览器环境下的可视化验证,两者共享同一套 ImagesExtraction 夹具。
六、如何运行与扩展该测试
- 手动测试:使用 Bender 运行器打开 manual 目录 下的
imagesinheaderfooter.html(或在 bender 测试面板中定位该用例),在打开的编辑器中粘贴 InHeaderFooterComplex.odt 并核对预期/非预期清单。 - 环境前提:需在非 Safari、非 IE 的桌面浏览器(如 Chrome/Firefox)中运行,因为 plugin.js 的
isSupportedEnvironment()明确排除了这两类环境。 - 扩展新场景:如需新增页眉页脚图片的变体用例,可参照
ImagesExtraction目录结构,在 generated/_fixtures/ImagesExtraction 下新增夹具目录(含.odt源文件、expected.html期望输出以及libreoffice7/{chrome,firefox}.{html,rtf}剪贴板基准),并在 generated/imagesextraction.js 的tests对象中登记对应键名。
七、小结
通过 imagesinheaderfooter 手动测试及其源码证据,可以确认 CKEditor 4 处理 LibreOffice 粘贴图片时的完整规则链:RTF 中的页眉/页脚分组(\header*/\footer*)在图片提取前被整体剔除,正文 \pict 组中的 PNG/JPEG/GIF 图片被提取并转换为 Base64 Data URL 内联粘贴,重复图片与备用格式会被去重,EMF/WMF 等不支持格式会被跳过并触发控制台错误。这套机制同时由 generated/imagesextraction.js 自动化用例保障,开发者可以借助上述夹具结构快速新增或回归验证页眉页脚相关粘贴场景。