ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Joplin HtmlToMd 转换管线实战:一个真实网站 `<figure><picture>` 片段如何被测试套件“钉死”为干净 Markdown

Joplin HtmlToMd 转换管线实战:一个真实网站 `<figure><picture>` 片段如何被测试套件“钉死”为干净 Markdown Joplin HtmlToMd 转换管线实战一个真实网站figurepicture片段如何被测试套件“钉死”为干净 Markdown【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplin这篇指南以 Joplin 仓库中的一个真实 HTML-to-Markdown 测试用例为核心讲清三件事网页上常见的figurepicture多分辨率图片标记会被 Joplin 的HtmlToMd引擎转换成什么样的 Markdown这套转换由packages/lib中的 Turndown 封装驱动关键选项如何配置以及packages/app-cli/tests/html_to_md/目录下“HTML 输入 Markdown 期望输出”成对出现的 golden file 测试是如何把转换行为逐字节锁定的。读完后你可以独立分析任意一个html_to_md测试对的转换结果并按同样的方式新增回归用例。一、关联文档本体一个“黄金文件”对指定文档 picture.md 只有 3 行但它是 Joplin HTML 转 Markdown 回归测试的一部分。该目录下packages/app-cli/tests/html_to_md/存在大量成对文件每个xxx.html是一个“输入样本”同名xxx.md是该样本被转换后必须逐字节匹配的期望输出。本对样本为输入picture.html —— 取自真实新闻网站页面的一整段图片标记期望输出picture.md驱动测试tests/HtmlToMd.ts。期望输出全文如下含一个空行[![A blood moon](https://i.guim.co.uk/img/media/75583fcfe2eb74f1e89ea320355ff4156f4ade7b/0_49_3904_2342/master/3904.jpg?width300quality85autoformatfitmaxs1e9b643d2c109a1e271f50046eac1324)](#img-1) A blood moon last occurred in July 2018, though clouds largely obscured the celestial phenomenon in the UK. Photograph: JM F Almeida/Getty Images从这份期望输出可以直接读出该测试“钉死”了四条转换行为多分辨率picture塌缩为单一![]()图片输入里有 8 个带media/sizes/srcset的source分支按 980px、740px、660px、480px、0px 断点提供 1240w/620w/700w/645w/465w 等档位输出中它们全部消失只保留img元素自身的src注意 URL 参数是width300与img标签上的属性一致而不是source的srcset档位。包裹图片的a href#img-1被保留为锚点链接语法输出是...(#img-1)形式的嵌套——图片整体作为链接文本链接目标是#img-1正好对应输入figure ... idimg-1的 id即“点击大图打开 Lightbox”这类站内锚点被无损映射为 Markdown 内部锚点。figcaption图注退化为普通段落图注文本“A blood moon last occurred in July 2018...Photograph: JM F Almeida/Getty Images”被拼成一行、置于图片之后作为独立段落输出。页面装饰性标记全部被剥离输入中的meta itemprop结构化数据、!--[if IE 9]条件注释、图注内嵌的 3 个装饰性svg图标、label/span包裹结构在期望输出里都没有踪迹。值得注意的细节期望输出里图注两段文字说明 摄影师署名被合并为同一行说明转换器对块级文本内部的分行做了归并而figure与图注文字之间恰好保留一个空行符合“段落之间一个空行”的 Markdown 排版约定。二、输入样本剖析真实网站的“脏 HTML”长什么样对照 picture.html可以看到这类图片标记的典型复杂度figure itempropassociatedMedia image itemscope itemtypehttp://schema.org/ImageObject >// packages/lib/HtmlToMd.ts节选 export interface ParseOptions { anchorNames?: string[]; preserveImageTagsWithSize?: boolean; preserveNestedTables?: boolean; preserveTableStyles?: boolean; preserveColorStyles?: boolean; baseUrl?: string; disableEscapeContent?: boolean; convertEmbeddedPdfsToLinks?: boolean; tightLists?: boolean; collapseMultipleBlankLines?: boolean; } export default class HtmlToMd { public parse(html: string|HTMLElement, options: ParseOptions {}) { const turndownOpts: Recordstring, unknown { headingStyle: atx, anchorNames: options.anchorNames ? options.anchorNames.map(n n.trim().toLowerCase()) : [], codeBlockStyle: fenced, preserveImageTagsWithSize: !!options.preserveImageTagsWithSize, preserveNestedTables: !!options.preserveNestedTables, preserveTableStyles: !!options.preserveTableStyles, preserveColorStyles: !!options.preserveColorStyles, bulletListMarker: -, emDelimiter: *, strongDelimiter: **, allowResourcePlaceholders: true, br: , // 软换行行尾补两个空格对应 Joplin issue #8430 disableEscapeContent: disableEscapeContent in options ? options.disableEscapeContent : false, tightLists: !!options.tightLists, collapseMultipleBlankLines: !!options.collapseMultipleBlankLines, }; ... const turndown new TurndownService(turndownOpts); turndown.use(turndownPluginGfm); turndown.remove(script); turndown.remove(style); ... } }与picture用例直接相关的配置事实anchorNames可选的文档锚点名称列表解析前统一trim().toLowerCase()。picture.html的a href#img-1恰好命中容器自身idimg-1输出保留了(#img-1)内部锚点preserveImageTagsWithSize默认关闭图片一律转成![]()只有显式开启才保留带尺寸的原生img标签image_preserve_size_*用例即验证此开关remove(script)/remove(style)无条件剥离脚本与样式节点保证网页噪声不进入笔记同目录下 skip_script.md、skip_style.md 分别验证br: 把br渲染为 Markdown 软换行行尾双空格这是 Joplin 笔记渲染约定的一部分GFM 插件joplin/turndown-plugin-gfm补齐表格等 GitHub 风格扩展语法html_to_md目录中 30 余个table_*用例就依赖它。另外convertEmbeddedPdfsToLinks开启时引擎会注册一条embed/object规则把指向.pdf的内嵌对象改写为embedded_pdf链接并用blankReplacement兜底处理空的object这与本用例无关但体现了同一封装对不同导入场景的分支能力。四、测试驱动器golden file 逐字节比对所有html_to_md样本由 packages/app-cli/tests/HtmlToMd.ts 中的should convert from Html to Markdown统一驱动其工作方式为const basePath ${__dirname}/html_to_md; const files await shim.fsDriver().readDirStats(basePath); const htmlToMd new HtmlToMd(); for (let i 0; i files.length; i) { const htmlFilename files[i].path; if (htmlFilename.indexOf(.html) 0) continue; const htmlPath ${basePath}/${htmlFilename}; const mdPath ${basePath}/${filename(htmlFilename)}.md; const htmlToMdOptions: ParseOptions {}; // 按文件名前缀为特定样本附加选项 // anchor_local.html - anchorNames: [first, second, fourth] // image_preserve_size* - preserveImageTagsWithSize: true // preserve_nested_tables*- preserveNestedTables: true // text_color* - preserveColorStyles: true // table_with*/table_default* - preserveTableStyles: true const html await readFile(htmlPath, utf8); let expectedMd await readFile(mdPath, utf8); let actualMd await htmlToMd.parse(div${html}/div, htmlToMdOptions); // Windows CRLF 归一后做全等比较 if (actualMd ! expectedMd) { /* 打印 Got/Expected 全量行对比并断言失败 */ } }要点说明样本零配置默认值picture.html不命中任何特殊前缀因此它验证的是ParseOptions全空纯默认配置下的图片转换行为——这正是剪藏普通图文页面的默认路径统一包一层div输入被写成htmlToMd.parse(div html /div)模拟“片段粘入容器”的真实入口全等比较 换行归一期望与结果做!全量比对仅当os.EOL \r\n时先把两侧\r\n归一为\n。这意味着图片 URL 的查询串、alt 文本、段落空行位置都必须完全一致任何“差不多”的输出都会让测试失败并打印逐行引号包裹的 Got/Expected 对照失败即回归一旦picture对不通过测试会在输出中精确定位是哪个样本、哪一行偏差适合 CI 直接消费。五、生产调用链同一引擎如何被命令系统复用测试中使用的HtmlToMd与产品内命令共用同一实现。packages/lib/commands/convertHtmlToMarkdown.ts 声明了名为convertHtmlToMarkdown的命令执行体与测试几乎同构但固定了三项生产选项const markdown await htmlToMdParser.parse(div${html}/div, { baseUrl: , anchorNames: [], convertEmbeddedPdfsToLinks: true, });convertEmbeddedPdfsToLinks: true面向导入场景把 HTML 中内嵌 PDF 对象转成可下载资源链接其余保持默认。结合 HtmlToMd.ts 的封装可知桌面/移动/CLI 各端凡是“富文本转 Markdown”的路径最终都收敛到这条 Turndown 管线因此html_to_md目录的每一个.html/.md对实际上是对三端共用行为的一份跨端回归契约。六、如何验证与扩展在本地复现该用例的验证方式仓库只读只需运行测试在packages/app-cli下运行 jest过滤该套件即可重放全部html_to_md样本测试入口为 packages/app-cli/tests/HtmlToMd.ts可参考 packages/app-cli/jest.config.js 的配置若想在本地快速核对单个样本按测试驱动逻辑手工执行即可读取html_to_md/picture.html以HtmlToMd默认选项解析div${html}/div输出必须与 picture.md 全等新增用例的规范做法在同目录放入my_case.html与my_case.md一对文件。若该用例需要非默认ParseOptions还需在 tests/HtmlToMd.ts 的按文件名分支中补上对应开关如preserveImageTagsWithSize。七、小结package:app-cli/tests/html_to_md/picture.md 这个看似只有三行的文件实质是 Joplin 对“新闻站点figurepicture响应式图片标记”这一高频脏输入定义的逐字节转换契约多档srcset丢弃、取img src为唯一图片 URL、保留alt与内部锚点链接、figcaption降为图注段落、结构化meta/条件注释/装饰 SVG 全部剥离。它背后由 packages/lib/HtmlToMd.ts 的 Turndown 封装驱动、由 packages/app-cli/tests/HtmlToMd.ts 的 golden file 全等比对守护并通过 convertHtmlToMarkdown 命令复用为产品级能力——这正是 Joplin 剪藏与导入功能能把任意网页图文稳定落成干净 Markdown 笔记的底层保证。【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表