Quarkdown 行内图片语法全解:从 `...` 到尺寸标注与自定义引用(源码与测试双重视角)
2026/9/14 13:05:53 网站建设 项目流程

Quarkdown 行内图片语法全解:从...到尺寸标注与自定义引用(源码与测试双重视角)

【免费下载链接】quarkdown🪐 Markdown with superpowers: from ideas to papers, presentations, websites, books, and knowledge bases.项目地址: https://gitcode.com/GitHub_Trending/qu/quarkdown

本文以 Quarkdown 核心解析模块中的图片解析测试夹具 image.md 为主体,完整梳理 Quarkdown 行内图片(Inline Image)的全部语法形态——标准 Markdown 图片、标题、Quarkdown 扩展的尺寸标注!(WxH)与自定义引用 ID{#custom-id}——并结合 正则定义、解析器实现 与 AST 节点,逐条对照测试断言说明每种写法的实际解析结果,帮助读者准确掌握图片语法的书写规范与底层解析链路。

测试夹具全文:15 组图片语法用例

parsing/inline/image.md 是 InlineParserTest 的输入素材,其image()测试方法通过inlineIterator<Image>(readSource("/parsing/inline/image.md"))逐节点消费解析结果。文件全文如下(按用例顺序编号,后文逐组讲解):

foo /* 1 */ foo /* 2 */ foo /* 3 */ !(150x100)foo) /* 4 */ !(150x_)foo /* 5 */ !(_x100)foo /* 6 */ !(_x_)foo /* 7 */ !(140)foo /* 8 */ !(2cm*4.2in)foo /* 9 */ !(20mm*3cm)foo /* 10 */ !(2px*3)foo /* 11 */ !(50%*5%)foo /* 12 */ !(50%x5%)foo /* 13 */ !(50% 5%)foo /* 14 */ !(70%)foo /* 15 */ foo {#custom-id} /* 16 */ !(150x100)foo {#custom-id} /* 17 */

这 17 行用例覆盖了四个维度:基础形态(1–3)、尺寸标注(4–15)、自定义引用 ID(16–17),以及尺寸标注与标题、引用 ID 的组合(4、17)。

标准 Markdown 图片语法(用例 1–3)

前三个用例验证的是 Quarkdown 对 CommonMark 标准图片语法的兼容性:

  1. foo:最基础形态。测试断言 label 的第一个节点是Text("foo")link.url/imgtitlewidthheightreferenceId全部为null

  2. foo:单引号标题。解析后link.title为节点序列Text("Title"),尺寸与引用 ID 仍为空。

  3. 多行带双引号标题

    foo

    URL 与标题之间允许任意空白、甚至换行,测试断言其结果与用例 2 完全一致(同一断言块以repeat(2)复用了两次)。

这三条用例确立了图片的基本解析规则:!前缀触发图片解析,括号内为「URL + 可选标题」,标题支持单引号、双引号两种定界符,且标题内容本身按行内节点序列(而非裸字符串)存储。

Quarkdown 扩展:!(WxH)尺寸标注(用例 4–15)

Quarkdown 在标准语法之上扩展了一个可选的!(WxH)前缀(放在!之后、[label]之前),用于直接约束图片的宽(W)与高(H)。从 BaseMarkdownInlineTokenRegexPatterns.kt 的image模式定义可以看出其结构:

!(?:\(imgsize\))?linkcustomid? imgsize = (?<imgwidth>.+?)(?:sizedivider(?<imgheight>.+?))? sizedivider = IMAGE_SIZE_DIVIDER_HELPER link = 标准链接模式 customid = PatternHelpers.customId("img")

即:尺寸段整体可选,宽、高各捕获为命名分组imgwidth/imgheight,中间的分隔符由sizedivider辅助模式匹配。据此可以从测试用例归纳出以下书写规则:

分隔符的三种等价写法

用例 12–14 验证了三种分隔符写法解析结果完全相同(均为宽 50%、高 5%):

写法分隔符
!(50%*5%)星号*
!(50%x5%)字母x
!(50% 5%)空格

测试中以repeat(3)对三个用例套用同一断言(assertEquals(50.percent, width); assertEquals(5.percent, height)),确认三者语义等价。

_占位符表示“自适应”

  • !(150x_)(用例 5):宽 150px、高自适应(heightnull);
  • !(_x100)(用例 6):宽自适应、高 100px;
  • !(_x_)(用例 7):宽高均不约束,解析结果与不写尺寸段等价(两者null)。

缺省与单位规则

  • 只写宽度!(140)(用例 8)解析为宽 140px、高null!(70%)(用例 15)解析为宽 70%、高null
  • 支持物理单位!(2cm*4.2in)得到2.0.cm4.2.inch(用例 9);!(20mm*3cm)得到20.0.mm3.0.cm(用例 10)。
  • 纯数字缺省为 px!(2px*3)(用例 11)中第二个值3未带单位,测试断言其解析为3.px,与2.px成对。
  • 百分比:以%结尾的值解析为percent尺寸(用例 12–14、15)。

测试文件顶部的导入(com.quarkdown.core.document.size包下的cminchmmpercentpx扩展)印证了Size类型对这几类单位的原生支持。

尺寸标注与标题的组合

用例 4!(150x100)foo)验证了尺寸段、方括号 label、括号 URL 与标题三者可同时存在:宽 150px、高 100px、标题Text("Title")

自定义引用 ID{#custom-id}(用例 16–17)

图片末尾可追加{#custom-id}形式的自定义 ID,解析为referenceId字段:

  • 用例 16foo {#custom-id}referenceId"custom-id",尺寸与标题均为null
  • 用例 17!(150x100)foo {#custom-id}:尺寸(宽 150px、高 100px)、标题(Text("Title"))、引用 ID 三者全部生效。

从 Image.kt 的类定义可知,Image节点实现了CrossReferenceableNode接口,KDoc 明确referenceId是「可通过CrossReference交叉引用的可选 ID」——也就是说,{#custom-id}使图片获得了被其他节点通过交叉引用指向的能力,这一点与仓库文档 docs/cross-references.qd 所描述的交叉引用机制一脉相承。

源码实现链路:正则 → 词法 token → AST 节点

词法层:正则与命名分组

BaseMarkdownInlineTokenRegexPatterns.kt 中,image模式通过RegexBuilder拼装,最终输出三个命名分组:imgwidthimgheightimgcustomid(见groupNames声明),分别对应尺寸宽、尺寸高与自定义 ID。词法层将命中结果封装为 ImageToken——其注释示例正是Label

语法层:分组取值与容错

InlineTokenParser.kt 的visit(token: ImageToken)方法是三个关键步骤:

  1. 复用链接解析visit(LinkToken(token.data))把去掉!前缀后的部分按标准链接解析,得到LinkNode(含 label、url、title);
  2. 提取尺寸extractImageSize("img", token.data)(见 L255-L269)从命名分组取出imgwidth/imgheight的原始字符串,交给ValueFactory::size解析为Size若解析抛出IllegalRawValueException则降级为null,即_或非法值都不会中断解析,而是表现为「该方向不约束」——这正是用例 5–7 中_占位符的行为来源;
  3. 提取引用 IDimgcustomid分组trim()后作为referenceId,最终构造Image(link, width, height, referenceId)

AST 层:Image 节点的契约

Image 节点携带五个数据成员:

  • link: LinkNode—— 指向资源的核心链接(错误会经override var error by link::error上浮到图片自身,满足ErrorCapableNode);
  • width: Size?/height: Size?—— 可选宽高约束;
  • referenceId: String?—— 交叉引用 ID(CrossReferenceableNode);
  • usesMediaStorage: Boolean = true—— 是否注册进媒体存储(由MediaStorerHook在上下文启用时处理,对应 docs/media-storage.qd 的主题)。

作为PrimitiveFunctionBackedNodeImagebackingFunctionName"image"toFunctionCallArguments()把节点还原为函数调用参数:urllabeltitlewidthheightrefmediastoragefigure。从源码结构看,行内图片语法因此可以等价地由image(...)原语函数调用表达,二者共享同一 AST 节点——这也解释了为什么尺寸、引用 ID 等语法特性在函数调用形态下同样成立。

顺带一提,参考式图片![label][ref]走的是平行的referenceImage模式与 ReferenceImage 节点,其正则同样带imgsize尺寸段与refimgcustomid分组,由独立的测试夹具 refimage.md 覆盖,本文不展开。

测试断言对照表

InlineParserTest.kt 的image()方法与 17 行夹具一一对应,以下为完整断言结果(“自适应/null”表示对应字段为null):

用例语法widthheighttitlereferenceId
1foonullnullnullnull
2foonullnullTitlenull
3foonullnullTitlenull
4!(150x100)foo)150px100pxTitlenull
5!(150x_)foo150pxnullnull
6!(_x100)foonull100pxnull
7!(_x_)foonullnullnull
8!(140)foo140pxnullnull
9!(2cm*4.2in)foo2cm4.2innull
10!(20mm*3cm)foo20mm3cmnull
11!(2px*3)foo2px3pxnull
12!(50%*5%)foo50%5%null
13!(50%x5%)foo50%5%null
14!(50% 5%)foo50%5%null
15!(70%)foo70%nullnull
16foo {#custom-id}nullnullnullcustom-id
17!(150x100)foo {#custom-id}150px100pxTitlecustom-id

表中 “—” 表示该用例的断言块未单独校验 title 字段(其取值与相邻用例一致,由同一解析路径保证)。

实践建议与延伸阅读

  • 书写尺寸时:优先使用%相对尺寸(随版心缩放),跨页排版场景用cm/mm/in物理单位,仅约束单方向时用_占位保持纵横比自适应;
  • 需要被引用时:始终附带{#custom-id},使图片成为交叉引用的合法目标;
  • 尺寸语义的用户侧文档可进一步参考 docs/image-size.qd 与 docs/figure.qd(行内图片的“图片版”是块级figure,由ImageFigure节点承载,见 Figure.kt);
  • 想在本地复现全部断言,可直接运行 quarkdown-core/src/test/kotlin/com/quarkdown/core/InlineParserTest.kt 中的image()测试方法,输入即本文开头的 image.md 夹具文件。

【免费下载链接】quarkdown🪐 Markdown with superpowers: from ideas to papers, presentations, websites, books, and knowledge bases.项目地址: https://gitcode.com/GitHub_Trending/qu/quarkdown

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询