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 标准图片语法的兼容性:
foo:最基础形态。测试断言 label 的第一个节点是Text("foo"),link.url为/img,title、width、height、referenceId全部为null。foo:单引号标题。解析后link.title为节点序列Text("Title"),尺寸与引用 ID 仍为空。多行带双引号标题:
fooURL 与标题之间允许任意空白、甚至换行,测试断言其结果与用例 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、高自适应(height为null);!(_x100)(用例 6):宽自适应、高 100px;!(_x_)(用例 7):宽高均不约束,解析结果与不写尺寸段等价(两者null)。
缺省与单位规则
- 只写宽度:
!(140)(用例 8)解析为宽 140px、高null;!(70%)(用例 15)解析为宽 70%、高null。 - 支持物理单位:
!(2cm*4.2in)得到2.0.cm与4.2.inch(用例 9);!(20mm*3cm)得到20.0.mm与3.0.cm(用例 10)。 - 纯数字缺省为 px:
!(2px*3)(用例 11)中第二个值3未带单位,测试断言其解析为3.px,与2.px成对。 - 百分比:以
%结尾的值解析为percent尺寸(用例 12–14、15)。
测试文件顶部的导入(com.quarkdown.core.document.size包下的cm、inch、mm、percent、px扩展)印证了Size类型对这几类单位的原生支持。
尺寸标注与标题的组合
用例 4!(150x100)foo)验证了尺寸段、方括号 label、括号 URL 与标题三者可同时存在:宽 150px、高 100px、标题Text("Title")。
自定义引用 ID{#custom-id}(用例 16–17)
图片末尾可追加{#custom-id}形式的自定义 ID,解析为referenceId字段:
- 用例 16
foo {#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拼装,最终输出三个命名分组:imgwidth、imgheight、imgcustomid(见groupNames声明),分别对应尺寸宽、尺寸高与自定义 ID。词法层将命中结果封装为 ImageToken——其注释示例正是Label。
语法层:分组取值与容错
InlineTokenParser.kt 的visit(token: ImageToken)方法是三个关键步骤:
- 复用链接解析:
visit(LinkToken(token.data))把去掉!前缀后的部分按标准链接解析,得到LinkNode(含 label、url、title); - 提取尺寸:
extractImageSize("img", token.data)(见 L255-L269)从命名分组取出imgwidth/imgheight的原始字符串,交给ValueFactory::size解析为Size;若解析抛出IllegalRawValueException则降级为null,即_或非法值都不会中断解析,而是表现为「该方向不约束」——这正是用例 5–7 中_占位符的行为来源; - 提取引用 ID:
imgcustomid分组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 的主题)。
作为PrimitiveFunctionBackedNode,Image的backingFunctionName为"image",toFunctionCallArguments()把节点还原为函数调用参数:url、label、title、width、height、ref、mediastorage、figure。从源码结构看,行内图片语法因此可以等价地由image(...)原语函数调用表达,二者共享同一 AST 节点——这也解释了为什么尺寸、引用 ID 等语法特性在函数调用形态下同样成立。
顺带一提,参考式图片![label][ref]走的是平行的referenceImage模式与 ReferenceImage 节点,其正则同样带imgsize尺寸段与refimgcustomid分组,由独立的测试夹具 refimage.md 覆盖,本文不展开。
测试断言对照表
InlineParserTest.kt 的image()方法与 17 行夹具一一对应,以下为完整断言结果(“自适应/null”表示对应字段为null):
| 用例 | 语法 | width | height | title | referenceId |
|---|---|---|---|---|---|
| 1 | foo | null | null | null | null |
| 2 | foo | null | null | Title | null |
| 3 | foo | null | null | Title | null |
| 4 | !(150x100)foo) | 150px | 100px | Title | null |
| 5 | !(150x_)foo | 150px | null | — | null |
| 6 | !(_x100)foo | null | 100px | — | null |
| 7 | !(_x_)foo | null | null | — | null |
| 8 | !(140)foo | 140px | null | — | null |
| 9 | !(2cm*4.2in)foo | 2cm | 4.2in | — | null |
| 10 | !(20mm*3cm)foo | 20mm | 3cm | — | null |
| 11 | !(2px*3)foo | 2px | 3px | — | null |
| 12 | !(50%*5%)foo | 50% | 5% | — | null |
| 13 | !(50%x5%)foo | 50% | 5% | — | null |
| 14 | !(50% 5%)foo | 50% | 5% | — | null |
| 15 | !(70%)foo | 70% | null | — | null |
| 16 | foo {#custom-id} | null | null | null | custom-id |
| 17 | !(150x100)foo {#custom-id} | 150px | 100px | Title | custom-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),仅供参考