InkMark:一种轻量级的标记语言

2021年8月25日 // InkMark 标记语言

1 概述

InkMark 是一种用于文档撰写的轻量级标记语言。它的语法规定了如何利用带简单标记的纯文本,为文档添加结构、语义和格式。通过特定的 InkMark 解析器,可以将纯文本形式的 InkMark 文档转换为规范的 HTML 或其他格式的文档。InkMark 尤其适用于学术类文档的网络书写与展示。

1.1 为什么要设计 InkMark?

Web 技术在当今被广泛应用,其中许多信息主要以 HTML 格式在浏览器或手机应用中展示。然而,对于大多数人而言,要编写格式和结构良好的网页内容相比于使用 MicroSoft Office Word 编写一个文档要困难得多。许多网站要求用户从浏览器中在线输入内容,并提供了简单的内容编辑器。大部分情况,这样的编辑器已基本够用了。但有时候我们会有更高的要求,如在维基百科上编辑“调和矩阵”这样一个包含大量公式且结构较为复杂的数学条目,或是直接在浏览器中书写并发表自己的学术论文,甚至直接在浏览器中和他人协作编写一本专著。通常这些学术文档篇幅巨大、结构复杂,尤其是其中包含大量的图像、表格、公式和参考文献,需要对这些内容分别标注自动编号的标题,然后在文档其他位置进行交叉引用和索引。不得不说,要在浏览器文本输入框中编写这类内容几乎是一场梦魇,即便对当今如 Google Docs 这样强大的在线文档编辑器来说也是如此。

为了能在浏览器中轻松编写并正确地展示格式复杂的学术内容,我们需要做一些折中。即不完全依赖所见即所得的 HTML 编辑器,而是让用户以纯文本的形式输入内容,纯文本中包含一些简单的标记,用于标示文档的各种元素及其属性,之后通过编译过程将这些被标记的文本转化为网页内容(HTML 格式的文本)。当然,这些标记规则要容易记忆、便于输入、可读性好。基于最新的 Web 技术,这些标记文本被编译后生成的网页看起来完全可以像正规的出版物一样,甚至在交互性操作、多种尺寸屏幕上的自动调整版式显示还优于以往常用的便携式文档格式(PDF 格式)或 MicroSoft Office Word 文档。

这种标记方法以及如何将其转换为 HTML 格式文本的约定构成了 InkMark 这种轻量级标记语言的规范。实际上,目前已有许多轻量级标记语言被设计出来了,如非常流行的 Markdown维基百科网站使用的 MediaWiki 标记语法。但为了保持简单,这些语言的表达能力都非常有限,尤其是缺少对学术内容的表达能力,因此科研人员很少用轻量级标记语言撰写学术类文档。InkMark 语言试图通过在简单性和表达力强之间找到一个平衡点,构建一套用纯文本表征学术类内容的方法体系,方便学术类内容的网络书写和展示。

1.2 特性

相对于其他各种轻量级标记语言,InkMark 具有以下特性:

  • 具有几乎与 HTML 相匹配的文档内容描述能力。理论上来说,InkMark 可以表示 HTML 中所有类型的元素,以及为元素添加任意属性。
  • 很好地在简单和功能强大之间取得平衡。InkMark 的元素标记法包括完整模式和简写模式,其完整模式像 HTML 一样功能强大,而简写模式则像 Markdown 一样简单,且可以按统一的规则由完整模式过渡到简写模式。
  • 简写模式使用独特的“标记符号加方括号对接龙”形式的语法。例如,在其他轻量级标记语言中,常用 **着重强调** 表示要 着重强调 的文本,Inkmark 中却使用 *[着重强调] 达到同样的效果。
  • 能够用于科学技术报告、学术论文、书籍的书写。通常这些学术文档篇幅巨大,结构复杂,含有大量的图像、表格、公式和参考文献,需要对这些内容标注题注、自动编号,并在文档其他位置进行交叉引用和索引。
  • 具有灵活的文档组织能力。各个文档可独立编写和呈现,之后不必改变就可以合并在一起形成更长的主文档。无论是论坛帖子、博客文章、期刊论文、报告或书籍,其书写体验基本一致。
  • 注重语义的表述,不直接指定显示格式。如该语言关心你文本中的一行文字是普通段落、标题还是引文,但却不关心你的段落对齐方式,以及首行是否缩进等。
  • 尽可能保持语法的一致性和对称性。使用户能根据一定的规则来理解和学习其语法,避免产生歧义,降低语法学习和解析器实现的难度。
  • 良好的中文支持。其语法在换行、空格及标点处理,东西方文字混排等方面都更符合中文用户的使用习惯。
  • 当使用相对高级的功能时,所生成 HTML 文本的正确显示需要依赖特定的层叠样式表(CSS)甚至 JavaScript 代码。如通常要借助 MathJax 这类 JavaScript 数学显示引擎才能正确地显示文档中的公式,带编号公式的合理显示布局(通常公式和编号显示在同一行内,公式居中对齐,编号右对齐)也需要借助额外的 CSS 实现,而一些自定义类独有的显示方式也需要借助 CSS 实现。
  • 更加复杂。功能强大必然带来复杂性的提升,因此针对其全部功能的学习及解析器的实现将非常困难。InkMark 的复杂性主要表现于两种元素标记模式的融合共存,自动编号、引用、索引相关功能的实现,以及灵活的文档组织。

InkMark 不像 Markdown 几乎为每种常用元素定义了简单且直观的标记方法,它很“死板”,几乎是为所有元素定义了相同的标记规则。因此 InkMark 不如 Markdown 那么直观,也不如 Markdown 那样特别适合编写程序开发文档。但 InkMark 的标记规则相对更容易学习,并且其表达能力要强得多。其元素内容的边界范围也更加明确,不容易产生歧义。当只使用常用的标记功能时,它几乎和 Markdown 一样简单。

和 HTML 相比,InkMark 的完整模式几乎具有和 HTML 相匹配的描述能力,但元素边界不如 HTML 那样通过起始和结束标签项明晰标示。而 InkMark 的简写模式则要比 HTML 简单得多,适合手工书写,且源代码也更易读。InkMark 能做到在完整模式和简单模式间无缝切换,很好地在简单和功能强大间取得了平衡;不过两者进行切换的规则需要额外的学习成本。另外,针对学术文档的书写这一特殊目的,InkMark 通过加入一些自定义元素大大增强了其表达能力。

相对于 LaTeX 这种专业的学术论文排版系统,InkMark 主要专注于为文本添加常用的结构和语义,而其他更多的功能,如文本和页面样式,则大多交给其他程序(如和网页关联的层叠样式表 CSS)来完成。因此,其功能要远远少于 LaTeX,但也简单得多。

1.3 名称的由来

在给此语言命名的时候,首先考虑给它一个雅致的中文名称。既然和文字有关,那就与墨(Ink)有关。而此语言又是一种标记(Mark)语言,那就叫 “InkMark” 吧。“Ink Mark” 一词在中文中可翻译为“墨迹”或“墨痕”,他们表示笔墨在纸上留下的痕迹,或前人的诗文书画,甚至也可理解为用墨斗标记的直线痕迹。因此用 “InkMark” 为此标记语言命名是再贴切不过了。

另外,“墨斗”翻译为英文就是 “Ink Marker”,这是旧时木匠在待创作的木料上标记直线常用的工具。我们不妨将 InkMark 解析器看做是一个墨斗,并希望每一个使用此工具进行书写的人,都别具匠心,创作出美的作品。

1.4 是谁设计的 InkMark?

InkMark 最初是由金洪伟设计,并于 2021 年 8 月初次公开发布。金洪伟自身并不在 IT 领域工作,因此设计 InkMark 纯粹是出于兴趣爱好。目前,作者只以 HTML 格式写出了 InkMark 的最初设计文档,也就是你正在看的这个文档。未来该语言的进一步完善,以及解析器的实现,都需要更多人的参与。InkMark 是开放的,欢迎更多人员参与 InkMark 的生态建设。

1.5 关于此文档

此文档并不是语言规范,而是本语言的设计和论证说明。即其内容不光是说明该语言的基本语法,同时还讨论了为什么要这样设计。该文档假定读者已经熟悉 HTML 语法,并至少掌握一种轻量级标记语言。希望以此建立一个和协作者讨论改进此语言的基点,感兴趣的读者可基于此文档理解本语言中各项设计的出发点,并提出改进意见。

2 元素标记法概述

2.1 元素

InkMark 文档由元素组成,这些元素被组织成树状的层次结构。元素具有类型、内容和属性,InkMark 的语法主要规定了如何标记不同类型的元素内容及其属性。

InkMark 元素主要用于表征文档的内容,这些元素多数与 HTML 中 body 元素内的流式内容元素相对应,也有少数 InkMark 自定义的元素。常见的元素类型有段落、标题、列表、表格、单元格、图像、图像标题、链接、强调的文字,等等。在 HTML 中,这些不同的元素类型用不同的标签(tag)名称表示,如段落的标签名称为 p,表格的标签名称为 table,着重强调的文字的标签名称为 strong,InkMark 同样如此。

与 HTML 一样,InkMark 中的元素主要可分为两大类:

  • 块级元素:内部可以包含行内元素或其他块级元素的元素。它是比行内元素更“大型”的结构。通常大多数块级元素开始时会新起一行。常见的块级元素有段落、标题、表格、表格行、单元格、列表、列表项等。
  • 行内元素:又称为内联元素,只能包含内容文本或其他行内元素的元素。通常不会以新行开始。常见的行内元素有着重强调(通常显示为粗体)、强调(通常显示为斜体)、删除(通常显示为带删除线文字)、插入(通常显示为带下划线文字)、链接、上标、下标、变量、行内公式、代码等。

元素内容是指被标签标记的文本,或元素中包括的子元素。如对于指向维基百科网站的链接,其中 维基百科 四个字组成的文本就是元素内容。再如,对于列表元素,其中的各个列表项是其子元素,他们就是列表元素的内容。并非所有元素都有内容,如用于表示在文本中生成一个换行符号的元素(对应于 HTML 中的 <br> 元素)就不包含内容。有内容的元素被称为容器元素,无内容的元素被称为空元素

元素属性给出了元素的行为或额外的性质。元素属性通常以 名称=值 的形式给出,其中名称为属性名称标识,值为该属性对应的值。如对于链接维基百科,就有一个名称为 href 的属性,其值为 https://wikipedia.org/,用于给出链接指向的 URL 或 URL 片段。

为了在简单和功能强大之间取得平衡,InkMark 元素的标记语法包括完整模式简写模式。完整模式的书写繁琐,但却能清晰和全面地表示元素的类型、内容和属性,其功能与 HTML 相匹配。简写模式则是按一定规则规定了常见元素及其常见属性的简化表示法。完整模式和简写模式并不是相互隔离的,他们在内容及属性表示方面符合大致相同的规则,可以混用两种模式。多数情况下只需要使用简写模式,并且也应该尽量使用简写模式。

2.2 完整模式

完整模式实际上是对 HTML 元素标记方法的改造,其所表示的内容本质上与 HTML 元素是相同的。如对于如下 HTML 链接元素:

<a href="https://wikipedia.org/" target="_blank">维基百科</a>

图 1 是对其元素结构的分析。

HTML 元素结构
图 1 HTML 元素结构

若用 InkMark 完整模式表述,其形式为:

[=a][维基百科][href="https://wikipedia.org/" target="_blank"]

图 2 是对该 InkMark 元素结构的分析。

HTML 元素结构
图 2 InkMark 元素结构

可以看出,InkMark 完整模式的元素结构形式为:

[=标签名称][元素内容][属性1="值 1" 属性2="值 2"][属性3="值 3"]

这是一种可称之为方括号对接龙的标记法。其中第一对方括号称为标签项,它通过标签名称标明了元素的类型,标签名称前方的等号 = 用于和余下的各个方括号对项目区分。第二对方括号通常为内容项,它包含元素的内容。余下的零个或多个方括号都称为属性项,每个属性项内部包含元素的一个或多个属性。每个方括号对外围的方括号只能有一层,即应该是 [ ] 的形式,而不能是 [[ ]] 的形式。对于内容项,若内容外围的方括号有多层,则除了最外层的方括号作为内容定界符之外,其内层成对出现的方括号都应作为正常的文本字面。如 [=sup][[2]] 中,内层黄色背景的方括号对是文本字面而不是标记定界符。

当元素为行内元素时,要求各个方括号项必须紧密连接,即其间不能有空白。当元素为块级元素时,该元素必须单独占一行或多行(这一点与 HTML 不一样),各个方括号项之间可以有零个或多个水平空白,但不能有非水平空白。

标签项中的标签名称与 HTML 元素的标签名称大致相同,不过 InkMark 还额外定义了一些标签。这里列出少数几个元素标签名称:p(段落)、h1(一级标题)、h2(二级标题)、h3(三级标题)、uli(无序列表项,InkMark 自定义)、oli(编号列表项,InkMark 自定义)、table(表格)、td(普通单元格)、img(图像)、a(链接)、strong(着重强调)、code(代码),等等。

内容项可以写在一行内,也可以跨行书写。例如,通常用预格式化文本标签 pre 标记代码块,并用等宽字体显示,以下是一段 JavaScript 代码的标记方法:

[=pre][
class User {
	constructor(name) {
		this.name = name;
	}

	sayHi() {
		alert(this.name);
	}
}
]

对于容器元素,即便因某种原因没有给出内容,也应该在标签项后面放置空的方括号对表示内容项,如 [=strong][]。对于空元素,由于不具备任何内容,在表示时也不应包含内容项。如对于换行符元素,可以将其表示为 [=br];若给定一个字符串 [=br][],则后面的空方括号 [] 并不属于该换行符元素,而属于正常的文本字面。

属性项中对属性名称和值的要求与 HTML 语法完全相同。属性值通常用一对单引号 ' 或一对双引号 " 包括起来的字符串。当属性值中不包括空白时,可以省略这些引号。另外,InkMark 规定,当元素有多个属性时,各个属性可以放在一个方括号对中,中间用一个或多个水平空白分隔;或者将不同的属性分别放在不同的方括号对中。例如,上述链接元素也可以表示为:

[=a][维基百科][href=https://wikipedia.org/][target=_blank]

InkMark 这种完整模式的元素标记法和 HTML 相比只是形式不同,他们的表达能力基本相同。由于不像 HTML 那样在元素结束标签中也包含标签名称,导致难以一眼看清 InkMark 元素各个方括号项的相互关系。在表示多层嵌套的元素,或者许多挤在一起没有很好排版的元素时,其结构还不如 HTML 更加清晰。为了解决这种问题,还额外要求行内元素各个方括号项之间不能有空白,而块级元素各个方括号项之间虽然可以有水平空白,但每个块级元素必须单独占一行或多行,不能出现多个同级块级元素同时出现在一行的情景(表格单元格除外)。完整模式虽然繁琐,但却可以表示更多类型的元素,做到和 HTML 具有相匹配的表达能力。另外,完整模式也可以轻易地和简写模式相互转换。

在设计完整模式时,之所以采用方括号 [ ] 作为标签项、内容项和属性项的定界符而不是其他括号类型,是因为方括号输入时不需要同时按键盘上的 Shift 键,更方便手工输入。在开启中文输入法时,左右方括号往往被替换为 ,但实际上这两个符号并不常用,因此完全可以设置中文输入法不进行此类替换,以方便左右英文方括号的输入。之所以要求标签项的标签名称前面有一个等号 =,是为了更加明确地标明这是一个标记开头,避免与其他非标记项的方括号混淆。= 在键盘上距离 [ ] 最近,输入时同样不需要按 Shift 键,方便手工输入。最后,在开启中文输入法时,= 符号通常并不被转换为其他中文标点,非常方便输入。

2.3 简写模式

为了简化书写,提高可读性,可通过以下规则,将完整模式逐渐转换为简写模式。

简写规则 1:对于一些常见的元素类型,可以用键盘上出现的各种标点符号代替完整模式中开头的标签项。

如用句点 . 代替段落标签项 [=p],则段落元素可分别按如下两种方式书写(后面还有更简化的段落书写方式):

[=p][这是一个段落。]

. [这也是一个段落。] 

这种用标点符号代替 [=标签名称] 形式的标签项的标记法称为符号标记法

在采用符号标记法时,若元素存在内容项或属性项,规定行内元素的标记符号后面必须紧跟方括号,而块级元素的标记符号后面必须有一个或多个水平空白,再跟内容项或属性项。这样做的原因是考虑到键盘上的标点符号毕竟有限,经常会用同一个标点分别标记不同的行内元素和块级元素,这种要求就可以根据元素后方有无水平空白辨别其为行内元素或块级元素。从而可以利用键盘上有限的标点符号,标记更多类型的元素了。

例如,对于着重强调 [=strong] 这一行内元素标签项,可以用星号 * 代替;同样,对于无序列表项 [=uli] 这一块级元素标签项,也可以使用星号 * 代替。对于如下两行,第一行表示一个段落,段落中包含一个行内的着重强调元素;第二行是一个无序列表,其中包含一个块级的列表项。其判断依据就是看 * 后方有无水平空白。

*[着重强调]

* [无序列表项]

当用符号标记法表示空元素,且没有属性项时,用单独一个标记符号显然是不行的,规定这时必须重复书写其标记符号 2 次及以上,且块级元素必须单独占一行,块级元素后方同一行内不能有任何其他内容。如单个换行符 [=br] 可以使用标记符号 ;;;;;;;;; 表示。如果要表示连续的两个换行符,则可以用 [=br][=br];; ;; 表示,不过后者和前者略有不同,因为后者在两个换行符之间插入了一个空格。主题转换元素(水平线) [=hr] 可以使用独占一行的 ---------- 表示。不过,同样单独占一行的 --- -- 却不行,其原因是空格后方存在非空白字符,表示这不是空元素,从而不能被识别为 [=hr] 元素。

上面提到了用连字符 - 标记的 hr 元素,实际上,- 还可用于标记删除的文本(del),这是一个行内元素。两者都用 - 标记,区别是 hr 元素必须没有内容项,必须单独占一行;而标记 del 元素的 - 后面必须紧跟内容项。实际上,- 还可以用于标记表格行,这只出现在 table 元素中,我们将在表格部分进一步介绍。

在使用符号标记法时,若标记符号作用于行内元素,且标记符号后面有内容项或属性项时,则要求标记符号只能书写一次,且后面紧跟内容或属性项的左方括号。如 **[着重强调] 将被转换为 *<strong>着重强调</strong>,而 * [着重强调] 将还是 * [着重强调]。而当标记符号作用于块级元素,则标记符号可以重写多次,且符号与后面的左方括号之间必须有水平空白,以此使标记更加醒目。如规定可以使用双引号 " 代替块级引用标签项 [=blockquote],则以下是一个合法的块级引用元素:

"""""""""" [
	学而时习之,不亦说乎?有朋自远方来,不亦乐乎?人不知而不愠,不亦君子乎?
]

对于部分块级元素,需要通过行首标记符号重复的次数来判断其类型或等级。如对于各级标题,其行首标记元素重复的次数就是该标题的等级:

= [ 一级标题 ]
== [ 二级标题 ]
=== [ 三级标题 ]

简写规则 2:对于单独占一行的块级元素,其内容项两侧的方括号可以省略。

这种仅在行首放标签项,不用方括号界定标记内容的标记方法称为行首标记法。同时使用行首标记法和符号标记法时,又称为行首符号标记法

例如,对于上述引文,可以使用行首标记法的形式表示(紧跟 [=blockquote] 后面的空格是可有可无的):

[=blockquote] 学而时习之,不亦说乎?有朋自远方来,不亦乐乎?人不知而不愠,不亦君子乎?

或使用行首符号标记法表示(" 后面必须至少有一个水平空白):

" 学而时习之,不亦说乎?有朋自远方来,不亦乐乎?人不知而不愠,不亦君子乎?

又如,对上述的三个标题,可表示为:

= 一级标题
== 二级标题
=== 三级标题

在采用行首标记法时,如果该元素包含属性项,则属性项必须放在行尾,其前方与内容之间最好有(可以没有)一个或多个水平空白,其后方不能有除了空白字符之外的其他内容。有时,为了将该块级元素的属性与其内部行内元素的属性区分开来,必须在该块级元素的属性项前面加入空白。如下所示:

= 一级标题 [class="title"]
== 二级标题 [class="subtitle"]

. 这是一个带 class 属性的段落,其内包含一个带 id 属性的*[行内着重强调元素][id="id-name"] [class="highlight"]

简写规则 3:对于部分元素的重要属性,可以在单独的一个方括号对中只写其属性值,而省略其前方的属性名称以及等号 =,这些属性的名称可根据其在方括号对接龙中出现的顺序判定。

例如,对于链接元素 ahref 就是一个非常重要的属性,规定该属性名称可以省略。如果内容项后方紧跟一个无属性名称的方括号对,则该方括号对中的内容就是 href 属性的值。由于链接元素的标签 [=a] 可用 @ 代替,则如下两个链接元素是等同的:

[=a][维基百科][href="https://wikipedia.org/"]

@[维基百科][https://wikipedia.org/]

再如,图像元素 img(可用 & 符号标记)是一个空元素,它必须包含一个 src 属性,另外一个重要但不是必须的属性是 alt。则规定标签项后方第一个出现的无属性名称属性就是其 src 属性的值,第二个无属性名称的属性就是其 alt 属性的值。如下是两个图像元素是等同的:

[=img][src="http://inkmark.org/images/image.png" alt="替换文字"]

&[http://inkmark.org/images/image.png][替换文字]

省略属性名称的方括号对必须出现在各个各个属性项方括号对的最前方。若前面已经存在未省略属性名称的属性项,其后方的所有属性项都不应该再省略属性名称。如以下元素:

&[http://inkmark.org/images/image.png][class="thumb"][替换文字]

由于前面已经出现了 [class="thumb"] 属性项,后面的 [替换文字] 将不被看作是属性的一部分,而被看作是常规的文本字面。

简写规则 4:InkMark 自定义了一系列元素,用于简化常用的复合类型元素的表示。

例如,在文档撰写时经常用到图像框,图像框中包含一个图像,带有一个标题,以及一段描述文字。一般常用 figure 元素表示这种内容,在完整模式下的表述方法如下:

[=figure][
	[=img][src="http://inkmark.org/images/image.png" alt="替换文字"]
	[=figcaption][图像标题]
	[=p][用一个段落对图像信息进行描述。][class="description"]
]

这有点繁琐,为了简化表述,InkMark 中专门定义了一个新的元素 imgbox,该元素只适用于 figure 元素内包含一个图像、一个标题和一段描述的情况。其完整模式为:

[=imgbox][src="http://inkmark.org/images/image.png"][caption="图像标题"][alt="替换文字"][description="用一个段落对图像信息进行描述。"]

以上代码可转化为如下简写模式:

& [http://inkmark.org/images/image.png][图像标题][alt="替换文字"][description="用一个段落对图像信息进行描述。"]

注意该简写模式与前面的 img 示例元素相比,其标记符号 & 后必须至少有一个水平空白。仅仅这些变化,就使 & 符号所标记的元素由行内元素 img 变为块级元素 imgbox 了。

又如,对于无序列表元素 ul,其内部会包含多个列表项 li,其完整模式下的标记方法如下所示:

[=ul] [
	[=li] [列表项 1]
	[=li] [列表项 2]
	[=li] [列表项 3]
]

在转化为简写模式时,可以不再标示其外围的 ul 标记,而只标示列表项标签。如下所示:

* 列表项 1
* 列表项 2
* 列表项 3

同样地,以上 * 代表的无序列表项标记对应于 InkMark 自定义的标签 uli

简写规则 5:当确定一项内容属于块级元素,但没有给出任何标签以标示其元素类型时,该元素默认就是段落。

段落在文档中是如此常见,以致我们都懒得对它进行标记了。由于规定凡是没有标示元素类型的块级元素都是段落,因此就不用往每个段落的开头放一个 .[=p] 标记了。这使我们的标记工作量大大减少,且文档更加美观。以下两行分别表示两个段落:

这是一个段落。

这也是一个段落。

2.4 拥有两种模式的好处

InkMark 在完全使用完整模式时并不轻量,只有使用简写模式时才算作一种轻量级的标记语言。其他的轻量级标记语言都不像 InkMark 这样内建两种书写模式。这导致这些轻量级标记语言通常只能书写简单的文档内容,当书写复杂结构的文档时,大多需要混合使用 HTML,或依赖预先写好的内容模板,从而增大写作难度,且导致文档不够美观。InkMark 则允许我们在简写模式和完整模式间无缝地切换,不仅做到了使文档便于输入、可读性好,并且对各种元素类型及属性的支持使文档的表述能力具有无限的扩展空间。

为方便输入和阅读,应该尽可能地使用简写模式。只能在简写模式无法满足要求时,才使用完整模式。

3 更多的元素标记规则

本节将完整介绍有关元素标记的更多通用规则。如果你想要快速了解 InkMark 语法,可以暂时跳过本节,先阅读余下的内容。

3.1 空白的处理

在 InkMark 中,空白字符特指空格(Unicode 编码为 U+0020)、水平制表符(U+0009)、回车符(U+000D)和换行符(U+000A),其中空格和水平制表符又称为水平空白。空白虽然不可见,但常常被用来分隔和美化文本。InkMark 元素内容中的空白都按如下规则处理:

  • 所有处在行首或行尾的水平空白都被忽略;
  • 元素内容开头和结尾的所有空白都被忽略;
  • 连续出现的多个水平空白的任意组合都被看作是一个空格;
  • 行尾单个的换行符、回车符以及连续出现的回车和换行符的组合都被当作是一个换行符;
  • 如果一行中仅包括空白字符,则该行称为空行
  • 连续的多个空行都被看作是一个空行。

以上对空白的处理有两个例外,就是预格式化元素 pre 以及 InkMark 自定义的预格式化代码元素 precode,这两个元素中的文本通常会保留空格和换行符。

InkMark 对行首、行尾水平空白的处理与 Markdown 有很大的不同。Markdown 经常依靠行首和行尾的水平空白进行标记,而 InkMark 则完全忽略行首和行尾的水平空白。他们各有优缺点,InkMark 的这种处理方法使我们可以自由地用水平空白缩进以美化排版,且不必纠结每级缩进用 2 个空格、4 个空格或者是水平制表符的问题。但在 InkMark 中,无法简单地通过缩进文本来标记代码块,或者标记单个列表项中的多段内容。

除了以最简化的不加任何标记物表示的段落外,其他块级元素都有明确的边界范围,因此不需要在其上方或下方添加一个空行以便和上下方的同级块级元素区分。但有时候我们仍然会在块级引用、标题、代码块上下方各添加一个空行,这纯粹是出于美化排版的需要。

3.2 适用于所有元素的规则

  • InkMark 的元素可以包括标签项、内容项和属性项,三者依次出现。
  • 当内容项以方括号包围时,其内容可以包含一行或多行,但标签项和内容项的左方括号必须出现在同一行中;内容项的右方括号和各个属性项也必须出现在同一行中。
  • 所有方括号项中的左方括号、被包括内容、右方括号两两之间可以有任意数目的水平空白。
  • 除了段落元素的标签项可以省略外,其他元素的标签项都不可以省略。
  • 所有元素的标签项都可以用 [=标签名称] 的形式表示,部分常用元素可以使用符号标记法,即其标签项可以用一次或多次重复出现的标点符号代替。
  • 标签项以 [=标签名称] 的形式表示时,其左右方括号必须处在同一行上,其中 = 和标签名称之间可以有任意个数的水平空白。
  • 标签项以多次重复出现的标点符号代替时,这些标点符号之间不能有空白,如行首出现的 = = 并不是一个有效的二级标题标签项。
  • 所有标签都有名称,标签名称包括 HTML 中的流式内容元素的标签名称,也包括 InkMark 自定义的标签名称。
  • 所有容器元素必须包含内容项(即便是空的也应写成 [] 的形式),所有空元素都不能包含内容项。
  • 当空元素的标签项用标记符号代替,且不保括属性项时,则必须重复书写其标记符号 2 次及以上,且块级元素必须单独占一行。
  • 除了仅包含一行的块级容器类元素在采用行首标记法时可不用方括号包括其内容之外,其余容器类元素内容项都必须用一对方括号包括起来。
  • 元素可以包含多个属性,各个属性以 名称=值 的形式出现。
  • 多个属性可以出现在一个方括号对中,也可以出现在多个方括号对中。
  • 属性名称通常与 HTML 的属性名称相同,或使用任意自定义的属性名称。
  • 属性值一般同时用单引号 ' 或双引号 " 包括起来,属性值也可能是一个列表,中间用空格分开,如 class="title paragraph"。当属性值中间不包含空格时,可以省略外围的引号。
  • 在 HTML 中,一些布尔属性,其名称形如 requiredreadonlydisabled,他们的值只有 truefalse 两种,可以只给出属性名称,如果存在该名称,则表示其值为 true,否则为 false。这种属性表示法和 InkMark 中可省略属性名称的表示法相冲突,因此 InkMark 不支持这种属性表示法。InkMark 的所有属性必须以 名称=值 的形式给出,即要求用 required=true 而不是 required

3.3 行内元素标记规则

  • 行内元素的标签项、内容项、各个属性项之间必须紧密相连,不能有任何空白。
  • 行内元素内容里面可以包含换行符,如以下元素形式都是正确的:

    该段中包含一个*[需要着重
    强调的文本],该着重强调元素的内容可以处在两行中,但这两行中间不能有空行。
  • 当采用符号标记法时,且具有内容项或属性项时,行内元素的标记符号只有一个。
  • 容器类行内元素内容项的定界方括号不能被省略,如 *[] 表示 <strong></strong> 元素,而行内单独出现的 *** 仅仅是一个常规的文本字面。
  • 除了链接元素 a,其他行内元素的内容项内只可以包括行内元素,而不能再包含块级元素。

3.4 块级元素标记规则

  • 块级元素的标签项、内容项、各个属性之间不必像行内元素那样必须紧密相连,可以有任意数目的水平空白分隔。
  • 当采用符号标记法时,块级元素的标记符号可以重复书写,这时这些符号必须紧密相连,之间不能有空白。
  • 除了表格单元格(tdth)外,其他块级元素都不能和其兄弟块级元素共占一行。即块级元素的必须新起一行书写,其块级元素标记项前方不能有任何非空白字符;若标记项前方有非空白字符,将不会将其识别为 InkMark 块级元素。块级元素结束处同一行内不能有任意其他内容;如果有,这些内容将被忽略。
  • 当采用符号标记法时,块级元素的标记项和内容项之间必须有一个或多个水平空白。
  • 当采用行首标记法时,只有本行的内容才是被标记内容,即便下一行和该行紧连,也应被看作是下一个同级块级元素
  • 当采用行首标记法时,若元素同时具有属性项,则属性项必须放在行尾,其后方不能有除了空白字符之外的其他内容
  • 块级元素的内容项可以包含其他块级元素或行内元素。
  • 当块级元素内包含多个块级元素时,外层的块级元素不能使用行首标记法,即必须用方括号将该块级元素的内容包括起来。

3.5 转义

InkMark 特殊的标记法使其较难发生常规的正文标点符号与标记符号混淆的情形。如参考文献常用形如 [3] 的上标形式出现,这里的左右方括号是标记内容文本字面而不是标记定界符。以下这两种标记法都是合法的:

  • ^[ [3] ]:该标记中,上标标记内容的左右定界符和标记内容 [3] 之间各有一个空格,将定界符和标记内容区分开来。
  • ^[[3]]:该标记中,由于元素内容外围只能用一对方括号包括,因此其内层的方括号就只能是内容文本字面了。

这时估计有人会产生疑问:在 ^[ [3] ] 中,为什么不是第一个出现的左方括号和第一个出现的右方括号配对?如 ^[ [3] ] 中的黄色高亮显示的那样。这是因为 InkMark 规定:若标记内容中的方括号成对出现,则这些方括号应优先配对且被当作是文本字面。由于实际文本中往往成对出现方括号而非单个出现,这时按常规书写方括号并不会产生歧义。

现在,考虑一些特殊情形,假如我们希望被标记的内容显示为 3] 而不是 [3],用 ^[ 3] ] 的形式标记就会出现问题。这时第一个出现的左方括号正好和第一个出现的右方括号配对,而第二个出现的右方括号将被看作是标记元素后方的常规文本字面。在这种情况下,就需要对第一个出现的右方括号进行转义。即用特定的字符实体代替各个单一字符。

InkMark 字符实体的表示与 HTML 完全相同。它包括三部分:第一部分是一个 & 符号;第二部分是实体名字或者是 # 加上实体编号;第三部分是一个分号。此链接网页列出了所有定义的字符实体。表 1 列出了 InkMark 中常用的字符实体。

表 1 常用字符实体
字符 描述 实体名称形式 实体编号形式
  空格 &nbsp; &#160;
[ 左方括号 &lsqb;&lbrack; &#91;
] 右方括号 &rsqb;&rbrack; &#93;
& 与和符 &amp; &#38;
" 双引号 &quot; &#34;
' 单引号 &apos; &#39;

因此,以上所述的只有半边方括号的上标元素,应表示为 ^[ 3&rsqb; ]^[ 3&rbrack; ]^[ 3&#93; ]

同样地,假如我们希望被标记的内容显示为 [3,也需要对内容中第一个左方括号进行转义,否则原本作为右定界符的右方括号就可能被认为与内容中的左方括号配对了。正确的书写方法可以是 ^[ &lsqb;3 ]^[ &lbrack;3 ]^[ &#91;3 ]。也就是说,凡是在元素内容、属性值中非成对出现的方括号,都应该进行转义

除了防止正文中出现的标点符号与标记符号相混淆之外,字符实体还有另外一个作用,即可以用于输入一些较难输入,或者在编辑器中无法正确显示的字符。如输入 &copy; 将显示字符 ©

有些轻量级标记语言,还额外定义了一些更加轻便的转义方法,通常是用反斜线 \ 紧跟一个符号,将显示该符号。如 \[ 将显示 [。InkMark 并没有这样做,这主要是因为 InkMark 中需要转义的场合并不多,而引入 \ 加符号转义法就使得我们不得不在许多场合对 \ 本身进行转义,反而使事情变得更加复杂。另外,许多 LaTeX 数学公式引擎要求在网页中嵌入块级 LaTeX 公式时,公式要用 \[ ... \] 包围,这正好也与这种转义方法相冲突。

4 元素列表

4.1 HTML 元素列表

InkMark 中的元素包括在 HTML 中所有的流式内容(Flow content)元素,他们的语义与 HTML 规范也完全相同,可以在 HTML 规范MDN 网站查看这些元素的定义。此文档将不再一一列出。(TODO:未来应该逐一列出这些元素,说明其中文名称和语义,是行内元素还是块级元素,以及是否应该被 InkMark 支持等)

4.2 自定义元素列表

除了已有的 HTML 元素之外,为了进一步增强表达能力,InkMark 额外添加了一些自定义元素。如表 2 所示。

表 2 InkMark 的自定义元素
标签名称 元素名称 元素类型 标记符号 说明
anc 自动编号内容 行内 # 一个非常灵活的元素,图、表、公式、参考文献的标题编号、交叉引用和索引都基于此元素实现。
baton 接力棒 块级 / 用于无序或有序列表环境,允许用行首标记法一个列表项中包含多个段落的情况进行标记。
blockcomment 注释块 块级 ? 支持为文档添加块级注释,该元素的内容将不显示。
blockformula 块级公式 块级 $ 单独占一行或多行的公式,一般居中显示,有时右侧还有自动编号。
blocknomark 非标记文本块 块级 ~ 其内部的文本不被当做 InkMark 文本,通过设置文档生成选型,其内部可包含 HTML 代码。
comment 行内注释 行内 ? 嵌入行内的注释文本,该元素的内容将不显示。
dld 定义列表描述 块级 : 表示定义列表 dl 中的 dd 元素。
dlt 定义列表术语 块级 ; 表示定义列表 dl 中的 dt 元素。
i1 ~ i6 导入 块级 > i1, i2, ..., i6 分别对应一级导入、二级导入直至六级导入,对应的标记符号分别是 >, >>, ..., >>>>>>
imgbox 单图框 块级 & 复合元素,外部是 figure 元素,内部包含一个图像元素 img,一个可选的图像标题元素 figcaption,以及一段可选的描述文字的。
formula 公式 行内 $ 嵌入文本流中的公式,其内容可以是 InkMark 文本、LaTeX 公式文本、图像元素 img 等。
loanc 编号索引 块级 生成编号索引,必须给定其 label 属性,将会以列表形式显示文档中所有 label 属性值相同的 anc 元素编号及内容。
nobr 不换行 行内 不进行硬换行,必须放置在行尾。
nomark 无标记文本 行内 ~ 其内部的文本不被当做 InkMark 文本,通过设置文档生成选型,其内部可包含 HTML 代码。
oli 编号列表项 块级 # 表示编号列表 ol 内的列表项元素 li
precode 预格式化代码块 块级 ` 用于表示单独占一行或多行的代码块文本。
roanc 自动编号内容引用 行内 # 引用特定的 anc 元素,必须具有 ref 属性和 label 属性。其生成内容为一链接,链接的 href 属性指向 label 属性及内容与给定值相同的 anc 元素,链接内容默认与该 anc 元素的编号或内容相同。
roh 标题引用 行内 = 引用特定的标题元素(h1h6),必须具有 ref 属性。其生成内容为一链接,链接的 href 属性指向内容与给定值相同的标题元素,链接内容默认与该标题元素的编号或内容相同。
tbr 表格主体行 块级 - 表示表格主体元素 tbody 内的表格行元素 tr,其父元素必须是表格元素 table
tfr 表格尾=部行 块级 _ 表示表格尾部元素 tfoot 内的表格行元素 tr,其父元素必须是表格元素 table
thr 表格头部行 块级 ^ 表示表格头部元素 thead 内的表格行元素 tr,其父元素必须是表格元素 table
tmtl 向左合并单元格 块级 < 放置于表格单元格 tdth 内,或表格列 col 内,用于表示该元素将向左合并到左侧的单元格或列。由于是空元素,用符号标记法时,应使用 << 的形式。
tmtu 向上合并单元格 块级 ^ 放置于表格单元格 tdth 内,用于表示该元素将向上合并到上方的单元格。由于是空元素,用符号标记法时,应使用 ^^ 的形式。
uli 无序列表项 块级 * 表示无序列表 ul 内的列表项元素 li
toc 目录 块级 生成目录,目录是一个多级无序或有序列表,其各列表项是各个标题的编号和内容的组合。

从上表可以看出,许多自定义元素的类型名称都因语义不显著或过长而难于记忆,但他们大多具有对应的标记符号。实际应用中,通常都会使用符号标记法,而不不要记忆这些元素的名称。之所以仍然定义了其元素类型名称,是为了和前面的 HTML 元素标记法保持一致,即做到所有元素都有名称,都可以使用完整标记法。另外可以看出,其中有部分元素,如 commentformulanomark,他们的标记名称并不会出现在生成的 HTML 中,似乎可以分别用同一个名称同时标示行内元素和块级元素,但实际上还是分别定义了他们对应的块级元素 blockcommentblockformulablocknomark。这是因为在使用完整标记法时,无法用同一名称的元素分别标记内元素和块级元素。如注释元素,它不生成任何 HTML 代码,但以下两行是不同的:

[=comment][行内注释]

[=blockcomment][块级注释。]

第一行生成的 HTML 代码为 <p></p>,表示一个不包含任何内容的段落。因为该行内注释虽然什么也不生成,但它必然在某个块级元素内,该块级元素就是段落。第二行则什么也不生成,这是因为该注释本身就是块级元素,不必再把它放入其他块级元素内了。以上两行用符号标记法标记时也是不一样的:

?[行内注释]

? [块级注释。]

4.3 标记符号

InkMark 几乎使用了 QWERTY 键盘上所有的标点符号来标记元素。且同一个标点符号,用在不同的上下文中,可能表示不同的含义。InkMark 中使用的标记符号及其标记内容见表 3 所示。

表 3 键盘上的标点符号及对应的标记
符号 名称 行内元素 块级元素
~ 波浪字符,代字号 非标记文本,nomark;在 template 属性中以 {{~}} 的形式指代元素内容 非标记文本块,blocknomark;表格中的 colgroup 元素;在 template 属性中以 {{~}} 的形式指代元素内容
` 反引号 代码文本,code 预格式化的代码块,precode
! 惊叹号 表格中的表头单元格,th
@ 爱特,小老鼠 链接,a 链接,a
# 井号 自动编号内容,anc;自动编号内容引用,roanc;任意元素的 id 属性 编号列表项,oli;编号列表元素 ol 内的列表项,li;任意元素的 id 属性
$ 美元符 行内公式,formula 块级公式,blockformula
% 百分号
^ 脱字符 上标文本,sup 表格头部行,thr;表格中 thead 元素内的表格行,tr
& 与和符 图像,img;用于转义的字符实体的第一个符号,如 &lt; 图像框,imgbox
* 星号 着重强调的文本,strong 无编号列表项,uli;无编号列表元素 ul 内的列表项,li
- 连字符 删除的文本,del 主题转换(水平线)元素,hr;表格主体行,tbr;表格中 tbody 元素内的表格行,tr
_ 下划线 下标文本,sub 表格尾部行,tfr;表格中 tfoot 元素内的表格行,tr
+ 加号 插入的文本,ins 表格,table
= 等号 行内元素标签名称前导符号 块级元素标签名称前导符号;正文中各级标题,h1, h2, ..., h6
() 小括号,圆括号
[] 中括号,方括号 标记项定界符 标记项定界符
{} 大括号,花括号 numbertemplate 属性中的模板变量定界符,用双大括号表示 numbertemplate 属性中的模板变量定界符,用双大括号表示
<> 尖括号 右尖括号 > 表示各级导入外部文档元素,i1, i2, ..., i6
: 冒号 定义列表中的术语描述,dlddd
; 分号 换行符,br 定义列表中的术语定义,dltdt
" 双引号 引用的文本,cite;包括元素属性值的定界符 块级引用,blockquote;包括元素属性值的定界符
' 单引号 变量文本,var;包括元素属性值的定界符 包括元素属性值的定界符
, 逗号
. 句号,点 元素的 class 属性 段落(可省略),p;元素的 class 属性
? 问号 行内注释,comment 块级注释,blockcomment
/ 斜线 强调文本,em 接力棒,baton
\ 反斜线
| 竖线 表格中的普通单元格,td

5 常见属性

支持为元素添加属性是 InkMark 的重要特色,这大大增加了 InkMark 的表达能力,但同时也大大增加了其复杂度。除了极少数自定义属性(如自动编号内容元素 ancnumberlabel 属性)外,InkMark 的大部分属性与 HTML 元素的属性含义相同。当然,也可以像 HTML 那样,为 InkMark 元素添加任意名称的属性。

应尽量有节制地使用各种属性,以降低文档的复杂度,提高安全性。甚至建议解析器主动设置可用属性的白名单或黑名单,以避免属性被滥用。例如,全局属性 style 可直接包含应用于元素的 CSS 样式声明,为了使文档内容和样式分离,可以考虑禁止使用此属性。

5.1 全局属性

全局属性是所有 InkMark 元素共有的属性;它们可以用于所有元素,即使属性可能对某些元素不起作用。同 HTML 一样,InkMark 支持的全局属性主要包括: accesskeyautocapitalizeclasscontenteditabledata-*dirdraggabledropzoneexportpartshiddenidinputmodeisitemiditempropitemrefitemscopeitemtypelangpartslotspellcheckstyletabindextitletranslate。可以通过此网页查看这些全局属性的含义。

5.2 classid 属性

classid 都是最常见的全局属性。他们允许 CSS 或 JavaScript 选择和访问特定的元素,从而大大提高了 InkMark 的表达能力和功能性,通常允许使用这两个属性。若按常规的方法为元素添加 classid 属性,其形式如下:

一段包含重要内容的段落。 [class="important highlight" id="paragraph1"]

以上给出了一个段落,该段落具有两个 class 属性,以空格分开,同时还具有一个 id 属性。

由于 classid 属性是如此常见且有用,InkMark 专门为这两种属性定义了简化的定义方法。规定 class 属性可在属性项中以 .class-name-1.class-name-2 的方式给出,即在每个类名称前面添加句点 . 表示这是一个 class。又规定 id 属性可在属性项中以 #id-name 的方式给出,即在 ID 名称前面添加井号 # 表示这是一个 id。其方法与 CSS 中类或 ID 选择器的描述方法一样。因此,以上段落元素又可表示为:

一段包含重要内容的段落。 [.important.highlight#paragraph1]

每个元素最多只能有一个 id 属性,且该属性值相对于文档中所有元素的 id 属性值应该是唯一的。对于标题、自动编号元素,InkMark 会按一定的规则隐式地给定 id 属性,但当显式指定 id 时,将使用该显式指定的 id 而不是隐式生成的 id

5.3 template 属性

对于绝大多数元素,其内容与生成的 HTML 元素内容是相同的,但有时我们会希望根据元素的属性或上下文稍微改变一下元素的显示内容,这时就要用到 templatetemplate 属性是 InkMark 中特别定义的全局属性,它进一步规定了在将 InkMark 元素转换为 HTML 元素时,HTML 元素的内容是什么。该属性仅对容器类元素有用,而空元素则忽略该属性。它主要用于这些元素:标题(从 h1h6)、标题引用 roh、目录 toc、自动编号内容 anc、自动编号内容引用 roanc 和自动编号索引 loanc。其他元素也可以借助该属性实现一些特殊的功能。但很多时候,为了不把事情弄得太复杂,在其他元素中应尽量避免使用此属性。

template 属性值为一个模板字符串,其中包含一些形如 {{属性名称}} 的模板变量。该模板字符串将替换元素原有内容出现在生成的 HTML 元素内容中,并且其中的模板变量将会被替换为该元素具有的相同属性名称的属性值。如以下链接元素:

@[维基百科][https://wikipedia.org/][template="{{href}}"]

将生成如下 HTML 代码:

<a href="https://wikipedia.org/">https://wikipedia.org/</a>

template 属性值中,元素原有的内容可以用 {{content}} 表示,{{content}} 可进一步简写为 {{~}}。因此,如下链接元素:

@[维基百科][https://wikipedia.org/][template="{{~}}({{href}})"]

将生成如下 HTML 代码:

<a href="https://wikipedia.org/">维基百科(https://wikipedia.org/)</a>

有关 template 属性的应用将进一步在标题自动编号部分介绍。

6 段落和换行

6.1 段落

段落是最常见的元素。其标签名称为 p,标记符号为句点 .。由于太过常见,我们索性给其一定的“特权”,规定当使用行首标记符号标记法时(段落一般都使用行首标记法),行首的标签项可以省略。或者说,当一个块级元素的类型不确定时,它默认就是段落。但如果前后两个段落都采用这种纯内容的标记法,则必须在两者之间添加一个空行以进行分隔。而当段落和其他块级元素相邻时,由于这些块级元素必须显式地标注,因此他们之间可以不添加空行。但通常在各个相邻块级元素间添加空行以美化排版。以下给出了一些段落示例(实际并不建议这样写):

## 二级标题 1
这是一个段落。

这是另外一个段落。
## 二级标题 2

这还是一个段落,
该行和上一行同属一个段落。
. 这是一个新的段落。

由于段落标记常被嵌入到其他块级元素内,因此在使用行首标记法标记这些元素时,可以将段落标记符 . 紧跟其他行首标记符放置,表示该单行块级标记的内容包含一个段落。如下所示:

". 学而时习之,不亦说乎?有朋自远方来,不亦乐乎?人不知而不愠,不亦君子乎?

以上标记对应的 HTML 文本为:

<blockquote><p>学而时习之,不亦说乎?有朋自远方来,不亦乐乎?人不知而不愠,不亦君子乎?</p></blockquote>

只有段落标记符 . 能被用于这种特殊的组合行首标记符。

6.2 换行

有些时候,我们需要将同一段落或其他块级元素内的内容,分隔成多行显示,其方法有多种。最直观的方法就是在输入文字时,直接在需要换行的地方按回车键,然后紧接着书写下一行。如下所示:

远远的街灯明了,
好像闪着无数的明星。
天上的明星现了,
好像点着无数的街灯。

这将生成如下 HTML 代码:

<p>远远的街灯明了,<br>
好像闪着无数的明星。<br>
天上的明星现了,<br>
好像点着无数的街灯。</p>

即在文本中的每一个断行处自动插入 HTML 换行符元素 <br>。这是一种硬换行(hard wrap)的换行方式,该方式对中文书写更为适用。因为在中文书写时,一般不将一个段落分成多行书写,也不像英文那样在断行的地方插入一个空格。

除了以上直接以多行形式输入多行文本外,也可以将多行文本书写为一行,在中间显式地插入换行符元素 [=br]。其形式如下:

远远的街灯明了,[=br]好像闪着无数的明星。[=br]天上的明星现了,[=br]好像点着无数的街灯。

这种显式输入换行符的方法可以使我们利用行首标记法输入多行文本,在对表格单元格内容进行排版时尤其有用。上述换行符元素 [=br] 对应的标记符号是 ;,由于是空元素,需要将该符号重复书写至少两次,因此以上的诗句也可以写成:

远远的街灯明了,;;好像闪着无数的明星。;;天上的明星现了,;;好像点着无数的街灯。

还有一些情况,我们在书写 InkMark 源文件时,为了美化排版,需要将其分行书写,但实际显示时,则需要将他们显示为一行。这时可以使用 InkMark 自定义的 [=nobr] 标记,该标记仅在行尾时有效。如下示例代码:

&[./path/to/image1.png][图像 1]
&[./path/to/image2.png][图像 2]

将会生成以下 HTML 代码:

<p><img src="./path/to/image1.png" alt="图像 1"><br>
<img src="./path/to/image2.png" alt="图像 2"></p>

注意上述代码会自动在两个图像中插入 <br>,从而使两个图像分别显示在两行。但我们的本意可能是想要两个尺寸较小的图像显示在一行,这需要消除其中的 <br>。当然,将两个图像元素放在一行可以解决这种需求:

&[./path/to/image1.png][图像 1] &[./path/to/image2.png][图像 2]

或者我们就既希望将他们放在两行,又希望两个图像显示在一行,这就需要在第一个图像元素行的最后方插入 [=nobr]

&[./path/to/image1.png][图像 1][=nobr]
&[./path/to/image2.png][图像 2]

注意,以上并不会自动在第一行的行尾插入空格,要实现在第一行的行尾插入空格,请手动在 [=nobr] 的前方添加空格。[=nobr] 只在少数场合有用,因此没有定义对应的标记符号。

7 标题

7.1 标题的表示

InkMark 的标题元素包括 h1h2h3h4h5h6。主要依靠各级标题表示文档的结构,以及生成文档的大纲。所有这 6 级标题对应的标记符号都是等号 =,不过在书写时,要通过符号的个数来判断标题的级别。其形式如下:

= 一级标题
== 二级标题
=== 三级标题
==== 四级标题
===== 五级标题
====== 六级标题

这将生成如下 HTML 代码:

<h1 id="一级标题">一级标题</h1>
<h2 id="二级标题">二级标题</h2>
<h3 id="三级标题">三级标题</h3>
<h4 id="四级标题">四级标题</h4>
<h5 id="五级标题">五级标题</h5>
<h6 id="六级标题">六级标题</h6>

如果一个标题包含多行,则以下两种方法都是可以的:

= [将标题内容用方括号包括起来
构成多行标题]

= 在标题中插入换行符;;也可构成多行标题

在 InkMark 中,单个文档可以由多个相对独立的文件或文本片段组合而成,而书写这些相对独立的文件或文本片段时,都建议将一个一级标题作为该文件或文本片段的总标题,该一级标题下方依次是其他等级的标题。在按层级结构组合这些文件或文本片段时,需要按照上下文再次确定各个标题的等级。这就意味值,以 == 标记的标题,在最终生成的文档中并不一定就是二级标题,而有可能是更下级标题。具体规则将在文档组织部分详细介绍。

7.2 自动生成 id

在将各级标题转化为 HTML 时,会自动为所生成的 HTML 元素添加一个 id 属性,以使该标题可以被其他元素所引用。该 id 属性的值是根据元素的内容,按如下规则生成:

  • 元素内容中所有空白字符被转换为一个下划线 _,多个连续的空白字符仅生成一个下划线;
  • 若元素内容中有其他 InkMark 元素,则生成的 id 属性值中只包含标记的内容,且内容前后和其他内容之间应该有一个下划线 _ 分隔;
  • 若同时显式指定元素的 id 属性值,则生成元素的 id 是该显式指定的值,而非自动生成的值;
  • 若标题元素中没有内容,且没有显式指定 id,则不自动添加 id 属性。

根据上述规则,对于如下一系列示例:

== 中间带有一个空格 的*[标题]

== [跨行的
标题]

== 包含+[连续]-[多个]行内元素的*[标题]

== 显式指定 `[id] 属性的标题 [#the-specifice-id-name]

== []

将分别生成如下 HTML:

<h2 id="中间带有一个空格_的_标题">中间带有一个空格 的<strong>标题</strong></h2>
<h2 id="跨行的_标题">跨行的<br>标题</h2>
<h2 id="包含_连续_多个_行内元素的_标题">包含<ins>连续</ins><del>多个</del>行内元素的<strong>标题</strong></h2>
<h2 id="the-specifice-id-name">显式指定 <code>id</code> 属性的标题</h2>
<h2></h2>

7.3 标题的编号

当标题数目较多时,经常需要对各级标题进行编号,以便清晰地显示标题间的次序和层级关系。常见的编号形式如:1、1.2、1.2.1、第 1 章、第 1 节、一,等等。默认情况下,InkMark 的编号以阿拉伯数字的形式给出,各级标题之间用句点 . 分隔,编号最后没有句点,形如 1、1.1、1.1.1、1.1.1.1 等。InkMark 并不总是生成标题编号,这可以通过设置文档生成选型来决定是否自动生成标题编号,如可以设定仅当一级或二级标题的个数大于等于 3 个时,才自动生成标题编号。在生成标题编号时,根据具体的情况不同,一级标题(有可能是文档的总标题)可没有编号,这时二级标题的编号为 1, 2, 3, ...;也可能有编号,这时二级标题的编号为 1.1, 1.2, 2.1, ... 之类。

InkMark 也可以自定义标题编号的形式,需要通过给定标题的 number 属性进行自定义。number 属性有点复杂,其属性值像前面的 template 属性一样,是一个模板字符串,不过对于标题元素,其内部的模板变量只能是 {{=}}, {{==}}, {{===}}, {{====}}, {{=====}}{{======}} 这 6 个值。他们分别指代该标题所隶属的上级标题或该标题本身的编号数字,其中的每一项不是 1.2.1 这样的复合数字形式,而只是 1、2、3 这样的单个数字形式。示例如下:

= [一级标题][number="第 {{=}} 章"]
== [二级标题][number="第 {{==}} 节"]
=== [三级标题][number="{{=}}.{{==}}.{{===}}"]

以上三行代码将分别生成形如“第 1 章”、“第 1 节”、“1.2.1”形式的标题编号。

如果不想用阿拉伯数字作为标题编号,而改用 A、ⅰ、一、① 之类的编号,这时可在 number 属性值各模板变量中的最后一个 = 后面添加一个序号字符来显式指定编号类型,如 {{=A}}{{=一}}。能添加的序号字符应是各种编号类型序列中的第一个编号,其具体值可以是 1、ⅰ、Ⅰ、①、❶、a、A、⑴、㈠、㊀、一、壹、甲等,而 {{=2}}{{=C}}=六 这些值将不被识别为模板变量。因此,如果要使一个三级标题的编号以“一、”、“二、”、“三、”的形式显示,则可进行如下定义:

=== [三级标题][number="{{===一}}、"]

只需要在各级标题第一次出现的时候指定该级标题的 number 属性,后续的标题一般不指定该属性。若不指定,则默认的 number 属性值等于前面最后一次指定的 number。若重新指定 number,则自此标题开始,标题的 number 属性值变为该新指定的值。各级标题的编号值总是从 1 开始,并连续递增;若要重新指定某标题的编号值,请为该标题添加一个 start 属性,其属性值为一个阿拉伯数字,后续同级标题的编号值将在此数值上累加。对于如下示例代码:

= [一级标题 1][number="第 {{=}} 章"]
= 一级标题 2
= 一级标题 3
= [参考文献][number=""]
= 致谢
= [附件 1 名称][number="附件 {{=Ⅰ}}"][start="1"]
= 附件 2 名称

其中前面三个标题编号分别是“第 1 章”、“第 2 章”和“第 3 章”;接下来两个标题由于重新指定了空的 number 值,因此将没有标题编号;最后两个标题同时重新指定了 numberstart 属性,他们的编号分别是“附件 Ⅰ”和“附件 Ⅱ”。

number 属性值中包含最多个 = 的模板变量中 = 的个数不应大于该标题的实际等级,否则该模板变量将被完全忽略。另外,由于在将多个文件合成为单个文档后,原来标记项所标示的等级数可能会小于最终生成的标题等级,在给定 number 属性值时,应以最终生成的标题等级进行标注。例如,如果主文档内部引入一个次级文件,且该次级文件内包含一个一级标题,该标题最终将被转换为二级标题,则应按如下形式定义该标题的编号:

= [次级文档的总标题][number="第 {{=}}.{{==}} 节"]

还有一点需要注意,即便给定了标题的 number 属性值,也并不代表生成的标题中就一定会包含编号,这还要取决于整体的文档生成选型。而要显式地指定是否生成标题编号,可通过给定 template 属性实现。一旦给定 template 属性,将完全按该属性值规定的方式生成元素内容。在 template 中,模板变量 {{#}} 指代该元素的编号,也即根据 number 属性生成的内容。 如下所示:

= [一级标题][number="第 {{=}} 章"][template="{{#}}&nbsp;&nbsp;{{~}}"]

这将生成如下 HTML 代码:

<h1 id="一级标题">第 1 章&nbsp;&nbsp;一级标题</h1>

number 属性一样,只需要在各级标题第一次出现的时候指定该级标题的 template 属性,后续的标题一般不指定该属性。若不指定,则默认的 template 属性值等于前面最后一次指定的 template。若重新指定 template,则此标题的 template 属性值将等于该新指定的属性值。

7.4 引用标题

标题引用是生成一个链接,其链接文字与被引用的标题相关,且其 href 属性值指向被链接的标题。例如,对于如下标题:

== 示例标题

由于其所生成的 HTML 元素中包含自动生成的 id="示例标题" 属性名称/值对,因此我们可以简单地用一个链接元素来引用此标题,形如 @[示例标题][#示例标题]。但是,假如以上标题对应的编号是“第 2 节”,我们想在引用中标出其编号而不是内容,这时候直接使用 @[第 2 节][#示例标题] 这样的链接就不太好了。因为随着对文档的修改,该标题的编号很可能自动从“第 2 节”变为“第 3 节”或其他,而链接的内容却不会自动更新。对于这种需求,我们一般使用标题引用元素,其类型名称是 roh,它是“reference of heading”的缩写。

roh 元素使我们能更加便捷地生成对标题的引用。该元素是一个行内空元素,其对应的标记符号同样是 =。因此,当 = 符号标记的元素为块级容器元素时,其元素类型为各级标题;当为行内空元素时,其元素类型就是标题引用了。标题引用并不分等级,即对所有级别的标题的引用都是相同的。roh 元素必须具有一个 ref 属性,其值是被引用标题的内容。当显式地给定了被引用标题的 id 时,ref 值应该是该显式指定的 id 值。因此,对于以上“示例标题”,可以简单地用以下方式进行引用:

=[ref="示例标题"]

该元素生成的 HTML 类似如下形式:

<a href="#示例标题">2.1 示例标题</a>

当被引用标题显式指定了 id 值时,ref 属性值应该是该显式指定的 id 值。否则,ref 属性值应该和标题内容大致相同。其内部可以包含水平空白,以及其他各种行内元素,但不能包含多行。在根据 ref 属性值生成链接的 href 属性值时,将按照与自动生成标题的 id 属性值相似的规则得到属性值字符串。若原始的标题内容由多行组成,在 ref 中,其换行符应该用空格或换行符元素 ;; 代替。

默认情况下,所生成链接元素的内容与被引用标题最终生成的 HTML 元素内容完全相同,相当于 roh 元素的 template 属性与被引用标题的 template 属性完全相同。但有时候我们并不想这么做,比如我们只想部分地引用标题的编号或内容。这时可以通过重新给定该元素的 template 属性实现此功能。例如,假如我们只想引用以上标题的编号,可通过如下方式实现:

=[ref="示例标题"][template="{{#}}"]

该元素生成的 HTML 类似如下形式:

<a href="#示例标题">2.1</a>

同各级标题元素的 template 属性一样,并不需要在每次引用标题时都指定其 template 属性,而只需要在第一次引用该级别的标题时指定其 template 属性,后续对该标题的引用一般不指定该属性。若不指定,则默认的 template 属性值等于前面最后一次指定的 template。若重新指定 template,则此标题引用的 template 属性值将等于该新指定的属性值。由于 roh 元素并没有标示被引用标题的等级,解析器应该做更多智能化的工作,即根据实际被引用标题的等级来分类确定 template 属性值。

7.5 生成目录

要在文档中合适的位置插入目录,通常只需要插入如下块级空元素即可:

[=toc]

toc 元素是目录元素,它是“table of contents”的缩写。所生成的目录是一个多级无序列表,其各个叶子列表项的内容与各个标题对应相同,且具有指向这些标题的链接。同样可以为该元素指定 template 属性值。不过在大部分情况下,不必要专门设置 template 属性值。若设定 template,设定值将同时作用于所有各级目录项,一般可借此设定是否在目录中显示各个标题的编号,以及编号和内容之间的空白。如下示例:

[=toc] [template="{{#}}&nbsp;&nbsp;{{~}}"]

根据上述代码,不管正文中的标题是否显示编号,在目录中的标题都将显示编号。

8 简单行内元素

表 5 列出一些最常用到的简单行内元素,这些元素一般不用给定属性,且能够使用符号标记法标记。

表 5 常见的简单行内元素
元素类型 示例 生成的 HTML 显示效果 备注
着重强调 *[示例文本] <strong>示例文本</strong> 示例文本 表示重要的文字,一般用粗体显示。
强调 /[示例文本] <em>示例文本</em> 示例文本 表示较重要的文字,一般用斜体显示。
插入 +[示例文本] <ins>示例文本</ins> 示例文本 表示已经被插入文档中的文本,一般显示为带下划线文本。
删除 -[示例文本] <del>示例文本</del> 示例文本 表示被从文档中删除的文本,一般显示为带删除线的文本。
上标 a^[2] a<sup>2</sup> a2 上标文本。
下标 a_[2] a<sub>2</sub> a2 下标文本。
引用 "[示例文本] <cite>呼啸山庄</cite> 呼啸山庄 表示引用他人作品的标题。
变量 '[f]_[1] <var>f</var><sub>1</sub> f1 表示变量,变量通常用拉丁字母表示,以斜体显示。
代码 `[func] <code>func</func> func 表示代码文本。

从该表可以看出,其中许多标点符号的日常应用场景与元素的语义具有一定的关联性。如最常使用的 4 个行内元素着重强调、强调、插入和删除的标记符号分别是 */+-,这些符号又可称为乘号、除号、加号和减号。着重强调对应乘号,大多轻量级标记语言都这样用;强调常显示为斜体,因此对应斜线形式的除号;插入文字就是向文本中添加文字,对应的符号就是加号;相反,删除就对应减号。这种理解虽然有点牵强,但确实能帮助记忆。

上标 sup 和下标 sub 分别用 ^_ 符号标记,这和 LaTeX 中对上下标的表示是一致的。

引用 cite 用双引号 " 进行标记,这与我们在引述他人话语时常用双引号括起来的做法相似。不过请注意,这里的 cite 常用于表示他人作品的名称,而不是具体的话语,其意义类似于给标题加书名号。该元素也常常被嵌入到块级引用元素 blockquote 中使用。

变量元素 var 用于标记一个数学表达式或程序中的变量,其显示效果与强调元素 em 类似,即以斜体显示。但对于学术文档,建议通过 CSS 额外地将其显示字体指定为衬线体,如 “Times New Roman”、“Nimbus Roman No9 L”、“Linux Libertine”,等。在学术内容写作时,经常需要在文档中插入变量,因此专门为此元素指定了一个标记符号,即为单引号 '。该符号在显示时比较不显眼,从而更能突出其所标记的变量文本。在实际使用时,该元素内部通常不应包括汉字、阿拉伯数字,因为习惯上并不使用这些字符表示变量,即便作为变量的下标,也不以斜体显示。

行内的代码元素 code 以反引号 ` 表示,这与 Markdown 相一致。在编程开发相关的文档中,该元素常被用到,并常以等宽字体显示。

9 非标记文本

9.1 非标记文本元素

有些时候,主要是撰写 InkMark 的说明文档的时候,并不希望所有合规书写的 InkMark 文本都被解释为 InkMark 文本,这时候可以使用 InkMark 中特有的非标记文本元素 nomark,其对应的标记符号是波浪字符(或称代字符)~。例如,以下是一段介绍 InkMark 语法的 InkMark 文本:

Inkmark 使用 ~[*[着重强调]] 表示要 *[着重强调] 的文本。

可以看出第一个出现的 着重强调 四个字外围有两层标记。内层标记 *[] 很显然是 strong 标记;外层标记 ~[]nomark 标记,它告诉解析器,不应将其内部的标记看作是 InkMark 标记,而只是常规的文本字面。以上文字的显示效果为:

Inkmark 使用 *[着重强调] 表示要 着重强调 的文本。

InkMark 文本也属于代码文本,最好同时将其标记为 code 元素,这时需要这么写:

Inkmark 使用 `[~[*[着重强调]]] 表示要 *[着重强调] 的文本。

着重强调 外围有三层标记,有些繁琐,但没有更好的办法。其显示效果为:

Inkmark 使用 *[着重强调] 表示要 着重强调 的文本。

在 Markdown 中,用 ` 标记的行内或块级代码文本中的任何内容将不被解析为 Markdown 元素。InkMark 却不是这样,InkMark 中用 ` 标记的行内或块级代码文本内部的 InkMark 元素仍然有效。之所以这样,是因为考虑到 HTML 中的 codepre 元素中可以包含其他 HTML 元素,InkMark 的功能是与 HTML 基本对等的,因此才有这种设定。但为了在介绍 InkMark 语法时,能轻易地显示原始的 InkMark 代码,而不用对所有标记符号和方括号转义,就定义了 nomark 元素。以上三层行内标记的示例确实有点繁琐,但好在这种应用场景是较少见的。

nomark 是行内元素。因此,对于以下单独占一行的 nomark 元素:

~[*[着重强调]]

将生成如下 HTML 代码:

<p>*[着重强调]</p>

除此之外,InkMark 还专门定义了一个块级的非标记文本元素 blocknomark,其对应的标记符号依然是 ~。对于以下代码:

~ *[着重强调]

将生成如下 HTML:

*[着重强调]

该元素不再是一个段落了,这种结果可能并不是你所想要的,因此 blocknomark 在正常的 InkMark 文本中很少用到。当要实现和 HTML 混排时,则根据场景不同分别使用 nomarkblocknomark

9.2 与 HTML 混排

由于 InkMark 几乎具有与 HTML 相匹配的表达能力,因此,不必要将 InkMark 源文本与 HTML 源文本混排,InkMark 也不支持两者直接混排。这意味着,所有出现的 HTML 源文本将会被转义。例如,以下一段 InkMark 文本:

Inkmark 使用 `[~[*[着重强调]]] 表示要 <strong>着重强调</strong> 的文本。

将会被转换为下面的 HTML 文本:

<p>Inkmark 使用 <code>*[着重强调]</code> 表示要 &lt;strong&gt;着重强调&lt;/strong&gt; 的文本。</p>

其中的 <strong>着重强调</strong> 并不被当做 HTML 元素,而是被转义了。

个别时候,我们可能希望通过配置解析器选项,允许 InkMark 源文本与 HTML 源文本混排。即便这时也必须只允许 HTML 源文本出现在 nomarkblocknomark 元素内部,从而仍使 InkMark 与 HTML 保持一定程度上的隔离。例如,以下代码都实现 InkMark 和 HTML 的混排:

Inkmark 使用 `[~[*[着重强调]]] 表示要 ~[<strong>着重强调</strong>] 的文本。
	
这是一段正常的 @[InkMark][https://inkmari.org] 文本。下面是一个 SVG 图像:

~~~~~ [
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24">
    <path d="M12,11.5A2.5,2.5 0 0,1 9.5,9A2.5,2.5 0 0,1 12,6.5A2.5,2.5 0 0,1 14.5,9A2.5,2.5 0 0,1 12,11.5M12,2A7,7 0 0,0 5,9C5,14.25 12,22 12,22C12,22 19,14.25 19,9A7,7 0 0,0 12,2Z" />
</svg>
]

默认情况下,以上 SVG 源文本将被转义,从而无法正常显示。只有在解析器选项中允许与 HTML 混排时才不进行转义。

10 注释

注释是对文档的解释和说明,包含行内注释 comment 和块级注释 blockcomment,对应的标记符号都是 ?。默认情况下,注释什么也不生成,不会被转换为 HTML 注释(但也可通过解析器配置选项将其转换为 HTML 注释)。以下是注释的使用示例:

这是一个正常的段落。?[此注释将不会显示]

? [
	这是一个块级注释。此注释也将被忽略。

	这是注释的第二行。
]

这是另外一个段落。

以上代码生成的 HTML 为:

<p>这是一个正常的段落。</p>
<p>这是另外一个段落。</p>

11 代码

代码分为行内代码和块级代码。行内代码的标签名称是 code,其对应的标记符号是 `。行内代码中并不会保留文本内容和左右定界符方括号间的空白,代码中多个连续的空白也将被转换为一个空格。例如,所有以下各项都各等价的,他们都将被转换为 <code>package main</code>

  • `[package main]
  • `[ package main ]
  • `[    package main]
  • `[package     main]

在 Markdown 中,可以简单地通过将文本块段缩进 4 个空格来表示代码块,相对来说,InkMark 没有这么方便。块级代码的元素名称是 precode,它是 InkMark 自定义的元素,其实际上是由 precode 两元素嵌套起来构成的复合元素,块级代码对应的标记符号仍然是 `。以下两个代码块是等价的:

[=pre] [
[=code][
package main

import "fmt"

func main() {
	fmt.Printf("Hello, 世界!\n")
}
]
]
[=precode] [
package main

import "fmt"

func main() {
	fmt.Printf("Hello, 世界!\n")
}
]

同 Markdown 一样,也可以指定块级代码的编程语言名称。规定通过 plang 属性指定语言名称(注意不是 lang,因为 lang 属性是定义元素语言的全局属性)。又规定该属性名称是可以省略的,这时必须将此属性放置在内容项之后。如下所示为一段 Go 语言代码块:

` [
package main

import "fmt"

func main() {
	fmt.Printf("Hello, 世界!\n")
}
] [Go]

precode 与 HTML 中的 pre 元素稍微不太一样:在 precode 元素中,所有处在开始和结尾的空行都会被删除。因此,以上代码的开头虽然有一个空行,但该空行会被删除,从而生成如下 HTML 代码:

<pre><code>package main

import "fmt"

func main() {
	fmt.Printf("Hello, 世界!\n")
}
</code></pre>

当代码块只有一行时,可以使用行首标记法。如下所示:

` fmt.Printf("Hello, 世界!\n")

12 链接

链接元素 a 是指向其他网页、文件、电子邮件、同一页面内的位置或任何其他 URL 的链接。必须通过元素的 href 属性给定链接,即链接的目标。InkMark 对 HTML 链接的功能进行了扩充,从而能非常方便地为各种内容添加链接。

12.1 普通链接

普通链接的功能与 HTML 链接功能完全对应,即通过 a 元素及其 href 属性为内容添加链接。其中 href 属性可省略属性名称。以下各行都是一些有效的链接:

@[示例标题][http://example.com/]
@[示例标题][https://example.com/]
@[链接到相对地址][example.html]
@[链接到相对地址][./example.html]
@[链接到相对地址][../example.html]
@[链接到本页面中的一个锚点][#本页面中的一个锚点]
@[链接到其他网页中的一个锚点][https://example.com/example-page.html#该网页中的一个锚点]
@[链接到邮箱][myname@gmail.com]
@[链接到邮箱][mailto:myname@gmail.com]
@[链接到图像][https://example.com/images/avatar.png]

根据具体的应用场景,解析器可以对链接元素的解析规则进行适当的修改,如可以将如下类型的连接看作是内部链接(Wiki 类网站中常用的链接到同一网站另一个条目的链接):

@[链接文本][条目名称]

大部分时候,我们都将链接作为一个行内元素,其内部也只是包含文本字面或其他行内元素。但其实该元素内部也可以包含块级元素,这时必须以块级元素的形式表示链接元素。如以下的链接内容就包含了两个段落:

@ [
	第一个段落。

	第二个段落。
][https://example.com/]

以上文本将会被转换成如下 HTML 代码:

<a href="https://example.com/">
	<p>第一个段落。</p>
	<p>第二个段落。</p>
</a>

12.2 为任意元素添加链接

链接的应用场景非常多,并且往往和其他元素嵌套使用,这些嵌套常常会造成元素中包含太多的方括号,难以识别各元素的边界。如下代码中的链接元素内包含了一个图像框:

@ [
	& [./path/to/image-thumb.png][图像的标题]
][./path/to/image.png]

以上嵌套还不算复杂,但已经很难做到一眼就看清元素结构了。为了解决此问题,InkMark 支持通过为任意元素添加 href 属性(这时该属性名称不能省略),从而实现为任意元素添加链接。如以上代码的简化形式为:

& [./path/to/image-thumb.png][图像的标题][href="./path/to/image.png"]

这将生成如下 HTML 代码:

<a href="./path/to/image.png">
	<figure>
		<img src="./path/to/image-thumb.png" alt="图像标题">
		<figcaption>图像标题</figcaption>
	</figure>
</a>

而下面 InkMark 文本:

这是一个段落。 [href="https://example.com/"]

将生成如下 HTML 代码:

<p><a href="https://example.com/">这是一个段落。</a></p>

以上代码都是通过为单个元素添加 href 属性而使其变为链接,但生成 HTML 的形式并不一样。即有时将 a 标签放在元素外,有时则放在元素内。这是因为 figure 元素内常常直接嵌入 imgfigcaption 元素,如果将 a 标签放在 figure 元素内,则有可能会破坏该元素常规的嵌套关系,且改变其行为。对于 figureimgboxpreprecodeulol 元素,其嵌套规则同样如此,将用 a 标签包括元素。而对于 ph1h2liuliolispandiv 这类元素,由于其内部嵌套的元素类型本来就是不确定的,因此常常将 a 标签放在这些元素的内部。

13 列表

列表包括无序列表有序列表定义列表

13.1 无序列表

无序列表是一个没有特定顺序的列表项的集合,也称为项目列表。无序列表的元素名称为 ul,其内部包含一系列 li 元素。完整模式下的无序列表形式为:

[=ul] [
	[=li] [列表项 1]
	[=li] [列表项 2
		[=ul] [
			[=li] [列表项 2-1]
			[=li] [列表项 2-2]
			[=li] [列表项 2-3
				[=ul] [
					[=li] [列表项 2-3-1]
					[=li] [列表项 2-3-2]
				]
			]
			[=li] [列表项 2-4]
		]
	]
	[=li] [列表项 3]
]

这种表示方法非常繁琐,实际更常用其简化模式。简化模式并不会给出外层的 ul 标签,而只给出其各个 li 元素列表项。为了区分无序列表和有序列表的列表项,将无序列表的列表项重新命名为 uli 元素,并可用符号 * 表示。当使用符号标记法时,对于一级列表,其列表项用一个 * 标记,若某个列表项中包含下级列表,则下级列表中的列表项用 ** 标记;每增加一级列表,则其列表项前会多加一个星号 *。以上多级列表可以以下简化表示法表示:

* 列表项 1
* 列表项 2
	** 列表项 2-1
	** 列表项 2-2
	** 列表项 2-3
		*** 列表项 2-3-1
		*** 列表项 2-3-2
	** 列表项 2-4
* 列表项 3

上述各个列表项前面的空白并不起标记作用,而只是为了美化排版。

另外,即便各个列表项之间包含空行,这些用简化表示法表示的列表项也应该被看作是同属于一个列表,且不对生成的 HTML 有任何影响。

有些轻量级标记语言,会自动在每个列表项中的内容表示为段落,即转换为 <li><p>列表项内容</p></li> 的形式。InkMark 默认不在列表项内容外加 p 标签,但在三种情况下,会将列表项内容转换为段落。

第一种情况是通过在列表标签项后面添加一个段落标记符号 .,从而显式地将列表项表示为段落。如下所示:

*. 列表项 1
*. 列表项 2
*. 列表项 3

该列表将被转换为如下 HTML 代码:

<ul>
	<li><p>列表项 1</p></li>
	<li><p>列表项 2</p></li>
	<li><p>列表项 3</p></li>
</ul>

第二种情况是当列表项中显式包含多个段落的情况。如下所示:

* [
	列表项 1 中的第一个段落。

	列表项 1 中的第二个段落。
* [
	列表项 2 中的第一个段落。

	" 列表项 2 中的一个块级引用。
]

<ul>
	<li>
		<p>列表项 1 中的第一个段落。</p>
		<p>列表项 1 中的第二个段落。</p>
	</li>
	<li>
		<p>列表项 2 中的第一个段落。</p>
		<blockquote>列表项 2 中的一个块级引用。</blockquote>
	</li>
</ul>

第三种情况是只要有一个列表项中包含段落元素,考虑到各个列表项是并列的,则同级的其他不显式指定块级标记的列表项内容也将是段落。如下所示:

* 列表项 1
* [
	列表项 2 中的第一个段落。

	列表项 2 中的第二个段落。
]
* 列表项 3

因第二个列表项中包含两个段落,因此所有的列表项内容都包含段落,其生成的 HTML 代码为:

<ul>
	<li>列表项 1</li>
	<li>
		<p>列表项 2 中的第一个段落。</p>
		<p>列表项 2 中的第二个段落。</p>
	</li>
	<li>列表项 3</li>
</ul>

以下是另外一个示例:

* 列表项 1
* 列表项 2
*. 列表项 3

因第三个列表项中包含段落,因此所有的列表项内容都包含段落。其生成的 HTML 代码为:

<ul>
	<li><p>列表项 1</p></li>
	<li><p>列表项 2</p></li>
	<li><p>列表项 3</p></li>
</ul>

根据行首标记符的规则,如果一个行首的 [=uli]* 后面没有任何非空白内容,则该行将不被当做一个列表项。如下代码:

* 列表项 1
*
* 列表项 3

将会生成如下 HTML:

<ul>
	<li>列表项 1</li>
</ul>
<p>*</p>
<ul>
	<li>列表项 3</li>
</ul>

对这种某个列表项暂时没有内容的情况,要生成单个列表,应明确用空的方括号标示内容。如下所示:

* 列表项 1
* []
* 列表项 3

其所生成的 HTML 代码为:

<ul>
	<li>列表项 1</li>
	<li></li>
	<li>列表项 3</li>
</ul>

不能用一个简单的空行将一个列表分成多个列表。如下所示:

* 列表项 1
* 列表项 2

* 列表项 3

将生成如下 HTML:

<ul>
	<li>列表项 1</li>
	<li>列表项 2</li>
	<li>列表项 3</li>
</ul>

这里提供一个分隔列表的小技巧,即使用块级的注释元素来分隔列表。如下所示:

* 列表项 1
* 列表项 2
? []
* 列表项 3

其所生成的 HTML 代码为:

<ul>
	<li>列表项 1</li>
	<li>列表项 2</li>
</ul>
<ul>
	<li>列表项 3</li>
</ul>

有时,我们需要为整个列表添加一些属性,这时就不得不显式地写出其外围的 ul 元素,而在 ul 元素的内部,仍然可以用 * 表示列表项 li。如下所示:

[=ul][
	* 列表项 1
	* 列表项 2
	* 列表项 3
][.conclusion]

13.2 有序列表

有序列表在显示时,每个列表项前面通常有一个带编号的数字。有序列表的标记方法基本上与无序列表相同,但其简化模式的列表项用 oli 标记,对应的标记符号是 #。如下所示为一个有序列表:

# 列表项 1
# 列表项 2
	## 列表项 2-1
	## 列表项 2-2
	## 列表项 2-3
		### 列表项 2-3-1
		### 列表项 2-3-2
# 列表项 3
	** 列表项 2-1
	** 列表项 2-2
	** 列表项 2-3

该列表的第三个列表项内又包含一个无序列表,注意该无序列表项的标记符号是 **,而不是 #*

有序列表的其他表示规则与无序列表相同,在此不再赘述。

在 Markdown 中,常常使用处在行首的 1.2.3. 之类的数字标记有序列表,这种标记方法非常直观。InkMark 整体的标记规则不允许采用 Markdown 这种标记方法,因此其可读性要稍差。

13.3 定义列表

定义列表 dl 不像前面无序列表或有序列表那样,内部包含一个个并列的列表项元素 li,而是包含两种元素:术语 dt 和术语的描述 dd。InkMark 简化模式同样不需要写出外层的 dl,而只需分别写出 dtdd。为了和原来标签有所区分,InkMark 新定义了两种元素:定义列表术语 dlt,其对应的标记符号为冒号 ;,以及定义列表描述 dld,其对应的标记符号为分号 :。这两个标记符号位于键盘的同一个按键上,很容易将他们关联在一起。以下是一个定义列表示例:

; HTML
: 超文本标记语言,延伸自 SGML。
; XHTML
: 可扩展超文本标记语言,是 HTML 4.0 的重写,符合XML规则。
; HTML5
: HTML 标准的最新版本。

该定义列表将被转换为如下 HTML 代码:

<dl>
    <dt>HTML</dt>
    <dd>超文本标记语言,延伸自 SGML。</dd>
    <dt>XHTML</dt>
    <dd>可扩展超文本标记语言,是 HTML 4.0 的重写,符合XML规则。</dd>
    <dt>HTML5</dt>
    <dd>HTML 标准的最新版本。</dd>
</dl>

为了能给定外层的 dl 元素某种属性,同样可以显式地写出 dl 元素,并在其内部用 ; 表示 dt 元素,用 : 表示 dd 元素,如下所示:

[=dl][
	; HTML
	: 超文本标记语言,延伸自 SGML。
	; XHTML
	: 可扩展超文本标记语言,是 HTML 4.0 的重写,符合XML规则。
	; HTML5
	: HTML 标准的最新版本。
][.term]

另外,许多轻量级标记语言都通过扩展支持任务列表,但任务列表并不对应任何 HTML 元素,InkMark 也没有相应的任务列表元素。如果有扩展需要支持,可通过给定无序或有序列表项特定的 class 属性支持。

13.4 接力棒

当一个列表项中包含多个段落时,其做法是用方括号将这些多个段落内容包括起来,如下所示:

* 列表项 1
* [
	列表项 2 中的第一个段落。

	列表项 2 中的第二个段落。
]
* 列表项 3

但在 Markdown 的许多扩展语法中,可以通过适当的缩进,使一个列表项后续的段落也被当作是该列表项的内容。如下所示:

* 列表项 1
* 列表项 2 中的第一个段落。

  列表项 2 中的第二个段落。
* 列表项 3

显然,Markdown 的这种标记方法要更加美观一点,不过为了区分代码行和列表项中的段落,Markdown 也制定量比较复杂的规则。InkMark 不将行首的水平空白作为标记依据,因此它不能实现这种标记法。但这种标记法相比总是加方括号的标记法的确要更加美观,因此 InkMark 规定了一种不同的方法实现了类似的功能。InkMark 通过接力棒元素 baton 元素实现这些功能。接力棒 baton 是一个块级容器元素,其标记符号是斜线 /,它只用于无序列表和有序列表行首符号标记法的场景。该元素表示其中的内容与前一个块级元素的内容是同级同类型内容(一般为段落),就相当于用一对方括号将所有这些内容同时包括起来,并且之间用空行分隔开来。如下所示:

* 列表项 1
* 列表项 2 中的第一个段落。
/ 列表项 2 中的第二个段落。
/ 列表项 2 中的第三个段落。
* 列表项 3

这里的 / 表明“列表项 2 中的第二个段落。”与“列表项 2 中的第一个段落。”属于同类内容,又因为每一行都是块级内容,因此他们都被认为是段落。其所生成的 HTML 为:

<ul>
	<li>
		<p>列表项 1</p>
	</li>
	<li>
		<p>列表项 2 中的第一个段落。</p>
		<p>列表项 2 中的第二个段落。</p>
		<p>列表项 2 中的第三个段落。</p>
	</li>
	<li>列表项 3</li>
</ul>

另外,还可以通过 / 的个数来确定与之匹配的列表项。如下所示:

* 列表项 1
* 列表项 2
	** 列表项 2-1
	// 列表项 2-1 中的第二个段落
	** 列表项 2-2
/ 列表项 2 中的第二个段落
* 列表项 3

其中“列表项 2-1 中的第二个段落”与“列表项 2-1”相匹配,而“列表项 2 中的第二个段落”与“列表项 2”相匹配,这将生成如下 HTML:

<ul>
	<li>
		<p>列表项 1</p>
	</li>
	<li>
		<p>列表项 2</p>
		<ul>
			<li>
				<p>列表项 2-1</p>
				<p>列表项 2-1 中的第二个段落</p>
			</li>
			<li>
				<p>列表项 2-2</p>
			</li>
		</ul>
		<p>列表项 2 中的第二个段落</p>
	</li>
	<li>
		<p>列表项 3</p>
	</li>
</ul>

14 自动编号

14.1 自动编号内容元素

自动编号是大型文档编写时最常用到的功能。学术文档中的图像、表格、公式和参考文献,通常都带有具有自动编号的标题(或称题注),或者只是带一个自动编号,以便对这些内容进行区分,并在其他地方引用和索引。为此,InkMark 特别定义了一个自动编号内容元素 anc(automatically numbering content),该元素可根据其名称为编号标签的 label 属性对自动编号元素进行分类,并分别为不同标记类型的自动编号内容自动生成编号。anc 也可用标记符号 # 代替。以下示例是图像的带自动编号标题:

#[图的标题文本][label="图"]

默认情况下,该自动编号内容将会被转换为如下 HTML 代码:

<span id="图_图的标题文本" label="图">图 1 图的标题文本</span>

注意,标记元素类型名称的英文单词为 tag,通常将其翻译为“标签”或“标记”,而 label 也翻译为“标签”或“标记”,很难通过中文分辨他们之间的区别。因此,在翻译 label 时,一般将其称为“编号标签”。

anc 元素的 label 属性是必备属性,因此规定它是可以省略的。类似如下形式:

#[图的标题文本][图]

自动编号内容是一个行内元素,在 InkMark 中,应将要编号的内容(如图或表的标题,参考文献)作为元素内容。该元素所生成的 HTML 代码是一个用 span 标签包括的以类似“图 1 图的标题文本”、“图 3.2 图的标题文本”、“表 1 表的标题文本”这样的文本,该文本由“编号标签 + 空格 + 编号数值 + 空格 + 编号内容”构成。生成的 HTML 中的 span 元素具有两个属性 labelid,其 label 属性值和原始给定值相同,id 属性的值为 “编号标签” + “下划线” + “元素内容”,其中的元素内容按由标题内容生成标题 id 相同的方法生成 id 字符串片段。当显式给定 anc 元素的 id 属性时,则 id 属性值变为此显式指定的值。如以下编号内容元素:

#[表的标题][表][#a_unique_id_value]

将生成如下 HTML 代码:

<span id="a_unique_id_value" label="表">表 1 表的标题</span>

对于篇幅较长的文字,如果使用自动生成 id 的方法,则生成的 HTML 元素的 id 值也显得过长,这将使文档变得十分冗长。这种情况下通常可以显式指定 anc 元素的 id 属性。

有些时候,我们只是想生成一个自动编号,并不需要在其他地方引用该编号,或生成自动编号内容的索引列表,这时可以只给出一个空的自动编号内容项方括号达到此目的。例如:

#[][示例]

这将生成如下 HTML:

<span>示例 1</span>

由于该元素没有内容,因此也不自动生成其 id 属性(但可以显式指定)。

14.2 设置编号样式

anc 所生成编号的默认形式是“编号标签 + 空格 + 编号”。有时我们想展示的内容和默认形式不一样。如对于图和表的标题,经常也使用形如 图 1.1 形式的编号,其中第一个数字是章序号,第二个数字是在本章内该编号类型的序号。对于参考文献,经常使用形如 [1] 形式的编号,且不在正文中显示编号内容(具体的参考文献)及编号标签。对于公式,常常在公式右侧使用形如 (1)(1.1) 形式的编号,也不显示标记值。

对于这类需求,同样可以和自定义标题的编号一样,通过指定 number 属性来自定义编号。不过,该属性值中可使用的模板变量除了 {{=}}{{======}} 这 6 个值外,还可以额外使用一个模板变量 {{#}}{{#}} 指代该编号项在所处区域的编号值,具体值根据 number 中出现的 {{=}}{{======}} 的等级动态确定。若最大等级为 {{=}},则 {{#}} 的值在本章中从 1 开始编号;若最大等级为 {{=}},则 {{#}} 的值在本节中从 1 开始编号;依次类推。以下是一些编号样式定义示例:

  • 图 1:#[图的标题文本][图][number="{{label}} {{#}}"]
  • 图 1.1:#[图的标题文本][fig][number="图 {{=}}.{{#}}"]
  • 图 1-1-1:#[图的标题文本][fig][number="图 {{=}}-{{==}}-{{#}}"]
  • (1):#[][formula][number="({{#}})"]
  • (1-1):#[][formula][number="({{=}}-{{#}})"]

同标题的 template 属性一样,同样也经常为 anc 指定 template 属性。可以按如下方式定义参考文献的显示样式:

#[张三, 李四, 王老五, 等. 示例文章标题 [J]. 示例期刊名称, 2021, 33(8): 335-341.][reference][number="[{{#}}]"][template="^[ {{#}} ]"]

其中的 number 属性值表示以 [1] 的样式显示文献编号,而 template 属性值则进一步定义以上标的形式只显示文献编号,且不显示文献内容。numbertemplate 的值中都出现了 {{#}},当 {{#}} 出现在 number 中时,它指代具体的编号值;当 {{#}} 出现在 template 中时,它指代由 number 定义的模板生成的内容。以上代码将生成如下 HTML:

<span id="reference_张三,_李四,_王老五,_等._示例文章标题_[J]._示例期刊名称,_2021,_33(8):_335-341." label="reference"><sup>[1]</sup></span>

同样可以在 number 属性值,通过在 =# 后附加单个起始编号字符的方法,指定具体的编号序列。例如,我国的许多法规文档,一般使用“第一百八十三条”这样的中文编号标记各个法条,可以通过如下方式定义这种编号:

#[][label="法条"][number="第{{#一}}条"]

该编号项的内容部分为空,这是因为法条内容往往较多,甚至还包含多个段落,因而没必要将内容包括进来。我们只是简单地生成一个编号,而不打算生成所有法条的索引列表。

同标题一样,对于每一个具体的 label 值,同样只需要该编号标签项的 anc 第一次出现时,指定其 numbertemplate,后续同编号标签项的编号及内容样式,将自动继承上一个项目的相应属性值。

即便后来重新定义了同一编号标签的 anc 元素的的 number,其实际的编号仍然接续前面的编号,这一点与标题的编号不一样。这是因为如果想要从 1 开始编号,应该通过重新定义一个具有不同编号标签的 anc 元素,而不是重新指定 number 实现。

14.3 引用编号项

在定义了自动编号之后,可以通过 roanc (reference of anc)引用该编号,同样可以用符号 # 来简化标记。用符号标记时,roancanc 两元素的区别是前者为行内空元素,后者为行内容器元素。roanc 元素的用法与标题引用元素 roh 大致相同,不过其 label 属性名称是不可省略的。如下所示:

#[label="图"][ref="图的标题文本"]

默认情况下,该元素所生成的内容与被引用的编号元素的内容完全相同,它就像被引用元素的一个影子,只不过带有一个指向被引用元素的链接。如果被引用元素存在,以上代码将生成如下 HTML:

<a href="#图_图的标题文本">图 1 图的标题文本</a>

同样可以重新给定 roanc 元素的 template 属性值,以自定义所生成的内容:

#[label="编号标签"][ref="编号内容"][template="{{#}}"]

这将生成如下 HTML:

<a href="#图_图的标题文本">图 1</a>

14.4 生成编号索引

可以通过 loanc(list of anc)元素在文档的适当位置插入某类编号标签项的索引列表,这是一个块级元素,其用法与 toc 元素相似。示例如下:

[=loanc][label="图"]

这将生成一个无限列表,各个列表项的内容就是对应的编号内容,并且具有一个指向被索引编号的链接。同样可以为此元素强制指定 template 属性,以下编号列表将强制显示带标签编号及编号内容。

[=loanc][label="图"][template="{{#}} {{~}}"]

15 图像

15.1 常规图像

可通过 img 元素插入图像,其标记符号是 &。该元素是一个行内空元素,一般必须给定其 src 属性,其 alt 是常见的可选属性,这两个属性都可以省略属性名称,当省略时, src 属性值必须放在 alt 之前。如下两行是同等的:

&[src="https://example.com/path/to/picture.png"][alt="替换文字"]
&[https://example.com/path/to/picture.png][替换文字]

由于 img 是行内元素,当其单独占一行时,该元素将被置于段落元素内部,如下所示:

<p><img src="https://example.com/path/to/picture.png" alt="替换文字"></p>

img 元素所插入的图像完全处于行内,因此一般用于在行内插入小图像,如下所示:

HTML 通过标签 &[tag.gif][标签] 标记元素的类型。

其显示效果为:

HTML 通过标签 标签 标记元素的类型。

也可以为 img 元素指定其他属性,常见的如 widthheight,这两个属性的单位可以是像素(px),或者是百分比(%)。如下所示:

&[https://example.com/path/to/picture.png][替换文字][width="400px"]

&[https://example.com/path/to/picture.png][替换文字][width="400px" height="300px"]

&[https://example.com/path/to/picture.png][替换文字][width="50%" height="50%"]

&[https://example.com/path/to/picture.png][替换文字][width="50%" height="250px"]

15.2 图像框

雏菊
雏菊

雏菊是菊科雏菊属的一种,别名长命菊、延命菊,原产于欧洲,原种被视为丛生的杂草,开花期在春季。

在学术文档中,所插入的较大幅或大幅图像通常带有一个标题,有时还有一小段描述文字。当文档页面宽度大于图像宽度时,还经常将图像浮动显示在文字的左侧或右侧,如右图所示。我们将这种方式显示的图像框

当使用 HTML 时,通常有多种方法插入图像框,但最常见的形式是使用 figure 元素,其内部进一步包括 imgfigcaption 等元素。使用 InkMark 的完整模式表示时,其形式如下:

[=figure][
	[=img][src="http://inkmark.org/images/image.png" alt="替换文字"]
	[=figcaption][图像标题]
	[=p][用一个段落对图像信息进行描述。][.description]
]

这种插入图像框的方法有点繁琐了,InkMark 专门针对这种包含单个图像的图像框,定义了一个 imgbox 元素来简化输入,该元素对应的标记符号仍然是 &,不过它是一个块级空元素。以上图像框可以表示为:

& [src="https://inkmark.org/path/to/picture.png"][caption="图像标题"][alt="替换文字"][description="用一个段落对图像信息进行描述。"]

其实 imgbox 元素的属性是把 figureimgfigcaptionp 三个元素的内容和属性整合在一起了。该元素的 srcalt 定义了其中 img 元素的两个属性,其 caption 属性定义了其中 figcaption 元素的内容,其 description 属性定义了其中 p 元素的内容。当没有给定 alt 属性时,其中 img 元素的 alt 属性值默认等于 caption 属性值。同样地,若为 imgbox 元素指定 widthheight 属性,它将作用到 img 元素上。

实际上,在学术文档中,还经常为图像的标题指定一个编号,这也可以通过为上述 imgbox 元素指定 label 属性实现。指定 label 属性后,其中 figcaption 元素的内容将变为一个自动编号内容元素 anc。进一步地,还可以为 imgbox 元素指定 numbertemplate 属性,这些属性也都将作用于 anc 元素。如下所示:

& [src="https://inkmark.org/path/to/picture.png"][caption="图像标题"][label="图"][description="用一个段落对图像信息进行描述。"]

这将生成如下 HTML 代码:

<figure>
	<img src="https://inkmark.org/path/to/picture.png" alt="图像标题">
	<figcaption><span id="图_图像标题" label="图">图 1 图像标题</span></figcaption>
	<p class="description">用一个段落对图像信息进行描述。</p>
</figure>

在给定了 imgbox 元素的 label 属性后,可以进一步通过 roanc 元素引用图像的标题,用 loanc 元素生成图像标题索引。

imgbox 元素的 srccaption 属性是两个最常用的属性,因此规定可以省略属性名称。当两者都省略属性名称时, src 必须出现在 caption 的前面。对于如上图像框,可以进一步简化为:

& [https://inkmark.org/path/to/picture.png][图像标题][label="图"][description="用一个段落对图像信息进行描述。"]

16 表格

16.1 表格元素

表格是一种相对较为复杂的元素,一些轻量级标记语言对表格提供了有限的支持。然而,学术文档经常会出现十分复杂的表格。例如,一个单元格中可能包含多个段落,或者包含图片框、列表、代码块等复杂的块级元素。InkMark 也支持表格,并且基本做到了当表格简单时,能以相对整齐美观的形式显示表格;而对于复杂的表格,同样具有表征能力。

先来看看 HTML 中,一个完整的表格有哪些元素:

<table>
	<caption>表格标题</caption>
	<colgroup>
		<col class="column1" style="background-color: #e0f7fa;">
		<col span="2" class="columns2-3">
	</colgroup>
	<thead>
		<tr>
			<th>第 1 行第 1 列</th>
			<th>第 1 行第 2 列</th>
			<th>第 1 行第 3 列</th>
		</tr>
	</thead>
	<tfoot>
		<tr>
			<td colspan="3">汇总行。</td>
		</tr>
	</tfoot>
	<tbody>
		<tr>
			<th>第 2 行第 1 列</th>
			<td rowspan="2">第 2 行第 2 列</td>
			<td>第 2 行第 3 列</td>
		</tr>
		<tr>
			<th>第 3 行第 1 列</th>
			<td>第 3 行第 3 列</td>
		</tr>
		<tr>
			<th>第 4 行第 1 列</th>
			<td colspan="2">第 4 行第 2 列</td>
		</tr>
	</tbody>
</table>

在 HTML 中,涉及表格的元素主要有表格 table、表格标题 caption、表格列组 colgroup、表格列 col、表格头 thead、表格主体 tbody、表格尾 tfoot、表格行 tr、表头单元格 th 和表格单元格 td,这些元素都是块级容器元素。有关这些元素的意义,请参见 HTML 表格入门table 元素,在此不再赘述。以上表格代码的显示样式如下所示:

表格标题
第 1 行第 1 列 第 1 行第 2 列 第 1 行第 3 列
汇总行。
第 2 行第 1 列 第 2 行第 2 列 第 2 行第 3 列
第 3 行第 1 列 第 3 行第 3 列
第 4 行第 1 列 第 4 行第 2 列

注意以上表格并非表格的默认显示样式,本文档已通过层叠样式表对表格进行了美化。以下代码是在 InkMark 中表示此表格的方法,可以看出其代码量已大大减少了,并且可读性更好。

+++ [
~| [.column1][style="background-color: #e0f7fa;"] | [span="2"][.columns2-3]
^! 第 1 行第 1 列 ! 第 1 行第 2 列 ! 第 1 行第 3 列
_| 汇总行。       | <<      | <<
-! 第 2 行第 1 列 | 第 2 行第 2 列 | 第 2 行第 3 列
-! 第 3 行第 1 列 | ^^            | 第 3 行第 3 列
-! 第 4 行第 1 列 | 第 4 行第 2 列 | <<
][表格标题]

以上表格有点复杂,为了能说明表格的表示方法,先从较简单的示例说起:

++ [
^! 第 1 行第 1 列 ! 第 1 行第 2 列 ! 第 1 行第 3 列
_! 第 4 行第 1 列 | 第 4 行第 2 列 | 第 4 行第 3 列
-! 第 2 行第 1 列 | 第 2 行第 2 列 | 第 2 行第 3 列
-! 第 3 行第 1 列 | 第 3 行第 2 列 | 第 3 行第 3 列
]

最外层的加号 + 代表 [=table]thead 元素内的每个表格行 tr 以行首的 ^ 标记,tfoot 元素内的每个表格行 tr 以行首的 _ 标记,tbody 元素内的每个表格行 tr 以行首的 - 标记,标题单元格 th 以感叹号 ! 标记,普通单元格 td 以竖线 | 标记。

与前面列表元素中 uliolili 的区别相似,分别在 theadtfoottbody 中的 tr 元素也应该有一个新的名称,我们分别称之为 thrtfrtbr。这是为了使完整模式始终可用,进而保持语法的一致性才起的名字,我们并不需要去记住他们,而只需要记住他们的标记符号 ^_- 就行了。

在前面,我们已经将符号 ^_- 用作其他标记用途了,这里为什么还要用作标记表格行?没错,我们重新定义了这些符号的标记作用,但只限于在表格上下文中。更确切地说,只限于这些元素的父元素是 table 时才行。除了这三个符号外,另外还有 ~ 符号也是这样,我们稍后将讲到。

为了美观,表格的标记法和 InkMark 常规的块级容器元素标记规则稍有不同,主要表现在:

  • 按照常规,表格行的标记符号 ^_- 后必须有一个或多个水平空白,但由于他们的后方必须是单元格标记符号 !|,就特别规定 ^_- 后方的水平空白可以省略。后面讲到的 ~ 符号符合这种规则。
  • 按照常规,块级元素必须新起一行书写,但规定标题单元格 th 的标记符号 ! 和普通单元格 td 的标记符号 | 是例外。即当表格行 tr 以行首标记法标记,且单元格采用符号标记法标记时,单元格标记符号 !| 可以不新起一行书写,但这时单元格不能跨行,且单元格标记符号 !| 与单元格内容之间必须以一个或多个水平空白分隔。
  • 按照常规,表格行包含多个块级元素,并且还经常占多行,其内容应该用方括号对 [ ] 包括起来,但由于在表格环境中,很容易识别各个表格行的范围,因此特别规定表格行的内容可以不用方括号包括起来。

在生成 HTML 时,会将在多个连续的 thr 元素放在 thead 元素内,将在多个连续的 tfr 元素放在 tfoot 元素内,将在多个连续的 tbr 元素放在 tbody 元素内。以上 InkMark 表格将生成如下 HTML 代码:

<table>
	<thead>
		<tr>
			<th>第 1 行第 1 列</th>
			<th>第 1 行第 2 列</th>
			<th>第 1 行第 3 列</th>
		</tr>
	</thead>
	<tfoot>
		<tr>
			<th>第 4 行第 1 列</th>
			<th>第 4 行第 2 列</th>
			<th>第 4 行第 3 列</th>
		</tr>
	</tfoot>
	<tbody>
		<tr>
			<th>第 2 行第 1 列</th>
			<td>第 2 行第 2 列</td>
			<td>第 2 行第 3 列</td>
		</tr>
		<tr>
			<th>第 3 行第 1 列</th>
			<td>第 3 行第 2 列</td>
			<td>第 3 行第 3 列</td>
		</tr>
	</tbody>
</table>

如果单元格的内容稍多,不便在一行内书写所有表格行内容时,也完全可以使每个单元格占一行。以下是一个示例:

++ [
^! 第 1 行第 1 列
 ! 第 1 行第 2 列
 ! 第 1 行第 3 列
_! 第 4 行第 1 列
 | 第 4 行第 2 列
 | 第 4 行第 3 列
-! 第 2 行第 1 列
 | 第 2 行第 2 列
 | 第 2 行第 3 列
-! 第 3 行第 1 列
 | 第 3 行第 2 列
 | 第 3 行第 3 列
]

上面为了对齐单元格标记符号 !|,在他们前方插入了一个空格。

当单元格内包含复杂的块级内容时,或者仅仅是为了给单元格添加属性,就必须使该单元格单独占一行,且用方括号对 [ ] 包括起来。如下所示:

++ [
^! 第 1 行第 1 列
 ! 第 1 行第 2 列
 ! 第 1 行第 3 列
-! 第 2 行第 1 列
 | [
 第 2 行第 2 列

 一个单元格内可以有多个段落。
 ]
 | [第 2 行第 3 列][.unverified]
-! 第 3 行第 1 列
 | 第 3 行第 2 列
 | 第 3 行第 3 列
]

16.2 合并单元格

在 HTML 中,可以通过指定单元格的 colspanrowspan 属性分别使该单元格与其右侧或下方给定数目的单元格合并到此单元格。InkMark 也可以通过给定这些属性进行单元格合并,但为了美观,一般通过一个更直观的方法进行单元格合并。该方法是通过将右侧或下方被合并的单元格内容声明为 tmtltmtu 元素来声明单元格合并方式。这两个元素都是行内空元素。tmtl 是“table merge to left”的缩写,其对应的标记符号是 <,由于是空元素,且不包含属性,要将该符号书写二次及以上以上,即 <<。同样,tmtu 是“table merge to upper”的缩写,其对应的标记符号是 ^,同样要将该符号书写二次及以上,即 ^^。当一个单元格将被合并到左侧或上方单元格时,其内容应该只包含该 tmtltmtu,一旦包含其他非空白内容,则这两个元素的作用将失效。单元格的合并还具有传递性,假如一个表格行的第 2 列被声明为合并的第 1 列,而第 3 列被声明为合并到第 2 列,实际上是该第 2、3 列都将被合并到第 1 列。如下是一个合并单元格的示例:

++ [
-! 第 1 行第 1 列 ! 第 1 行第 2 列 ! 第 1 行第 3 列 ! 第 1 行第 4 列
-! 第 2 行第 1 列 | 第 2 行第 2 列 | <<      | <<
-! ^^            | 第 3 行第 2 列 | 第 3 行第 3 列 | 第 3 行第 4 列
]

该表格的显示效果为:

第 1 行第 1 列 第 1 行第 2 列 第 1 行第 3 列 第 1 行第 4 列
第 2 行第 1 列 第 2 行第 2 列
第 2 行第 2 列 第 2 行第 3 列 第 2 行第 4 列

16.3 多主体表格

一般每个表格中 theadtfoot 元素的个数各自不超过一个,但 tbody 元素却可以有多个,每个 tbody 代表表格内容中相对独立的一部分,且可以有自己的标题行。为了能分切连续的多个表格行,使他们处在不同的 tbody 内,我们使用自定义的 thr 元素,这是一个块级空元素,其标记符号是 --。这和主题转换元素(水平线) hr 的标记方法是一样的,但它只用在表格环境中,称之为表格主体分隔元素。以下是 thr 元素的使用示例:

+++ [
^! 学生 ID ! 姓名
-! 计算机科学 ! <<<
-| 20210045  | 张三
-| 20212178  | 李四
-| 20210928  | 王老五
-----
-! 英语      ! <<<
-| 20216381  | 赵六
-----
-! 政治      ! <<<
-| 20210916  | 公孙七
-| 20210659  | 慕容八
]

该表格的将生成如下 HTML:

<table>
	<thead>
		<tr>
			<th>学生 ID</th>
			<th>姓名</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<th colspan="2">计算机科学</th>
		</tr>
		<tr>
			<td>20210045</td>
			<td>张三</td>
		</tr>
		<tr>
			<td>20212178</td>
			<td>李四</td>
		</tr>
		<tr>
			<td>20210928</td>
			<td>王老五</td>
		</tr>
	</tbody>
	<tbody>
		<tr>
			<th colspan="2">英语</th>
		</tr>
		<tr>
			<td>20216381</td>
			<td>赵六</td>
		</tr>
	</tbody>
	<tbody>
		<tr>
			<th colspan="2">政治</th>
		</tr>
		<tr>
			<td>20210916</td>
			<td>公孙七</td>
		</tr>
		<tr>
			<td>20210659</td>
			<td>慕容八</td>
		</tr>
	</tbody>
</table>

其显示效果如下:

学生 ID 姓名
计算机科学
20210045 张三
20212178 李四
20210928 王老五
英语
20216381 赵六
政治
20210916 公孙七
20210659 慕容八

16.4 表格标题

同前面的图像框元素 imgbox 一样,也可以为表格指定 caption 属性,以及标题的 labelnumbertemplate 属性。caption 属性是表格的常见属性,规定可以省略其属性名称。如下表格:

++ [
-! 第 1 行第 1 列 ! 第 1 行第 2 列 ! 第 1 行第 3 列
-! 第 2 行第 1 列 | 第 2 行第 2 列 | 第 2 行第 3 列
-! 第 3 行第 1 列 | 第 3 行第 2 列 | 第 3 行第 3 列
][表格标题][label="table"][number="表 {{=}}.{{#}}"]

这将生成如下 HTML 代码:

<table>
	<caption label="table" id="table_表格标题">表 16.1 表格标题</caption>
	<tbody>
		<tr>
			<th>第 1 行第 1 列</th>
			<th>第 1 行第 2 列</th>
			<th>第 1 行第 3 列</th>
		</tr>
		<tr>
			<th>第 2 行第 1 列</th>
			<td>第 2 行第 2 列</td>
			<td>第 2 行第 3 列</td>
		</tr>
		<tr>
			<th>第 3 行第 1 列</th>
			<td>第 3 行第 2 列</td>
			<td>第 3 行第 3 列</td>
		</tr>
	</tbody>
</table>

在给定了 table 元素的 label 属性后,可以进一步通过 roanc 元素引用表格的标题,用 loanc 元素生成表格标题索引。

16.5 设置行和列的属性

同 HTML 一样,InkMark 支持设置特定行或列的属性。其中设置行的属性与 InkMark 中设置元素的属性方法是一致的,以下是一个示例:

++ [
^ [! 第 1 行第 1 列 ! 第 1 行第 2 列 ! 第 1 行第 3 列][style="color: blue;"]
-! 第 2 行第 1 列 | 第 2 行第 2 列 | 第 2 行第 3 列
- [! 第 3 行第 1 列 | 第 3 行第 2 列 | 第 3 行第 3 列][style="font-style: italic;"]
]

在 HTML 中,可通过 colgroupcol 元素批量设置列的属性,在 InkMark 中,colgroup 可用符号 ~ 标记,而其内部的 col 仍用符号 | 标记,不过其父元素必须是 colgroupcol 元素在 InkMark 中是一个块级空元素,因此当其包含属性时,可用 | [属性名=属性值] 的形式;当其不包含属性时,则必须连续书写 | 两次及两次以上。同单元格标记符号一样,这里的多个 col 元素仍可在一行内书写。如下是使用 colgroupcol 的示例:

++ [
~|  |  | [span="2"][.content][style="width: 300px;"]
^! 第 1 行第 1 列 ! 第 1 行第 2 列 ! 第 1 行第 3 列
-! 第 2 行第 1 列 | 第 2 行第 2 列 | 第 2 行第 3 列
-! 第 3 行第 1 列 | 第 3 行第 2 列 | 第 3 行第 3 列
]

虽然 col 是一个空元素,但我们仍特别规定其内容可以是 tmtl 元素,该内容是虚拟的,仅表示该元素的属性与左侧元素的属性相同,从而可以省略其左侧 col 元素的 span 属性。如下代码与以上代码的效果完全相同:

++ [
~|   |  | [.content][style="width: 300px;"] | <<
^! 第 1 行第 1 列 ! 第 1 行第 2 列 ! 第 1 行第 3 列
-! 第 2 行第 1 列 | 第 2 行第 2 列 | 第 2 行第 3 列
-! 第 3 行第 1 列 | 第 3 行第 2 列 | 第 3 行第 3 列
]

以上代码将生成如下 HTML:

<table>
	<colgroup>
		<col>
		<col span="2" class="content" style="width: 300px;">
	</colgroup>
	<thead>
		<tr>
			<th>第 1 行第 1 列</th>
			<th>第 1 行第 2 列</th>
			<th>第 1 行第 3 列</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<th>第 2 行第 1 列</th>
			<td>第 2 行第 2 列</td>
			<td>第 2 行第 3 列</td>
		</tr>
		<tr>
			<th>第 3 行第 1 列</th>
			<td>第 3 行第 2 列</td>
			<td>第 3 行第 3 列</td>
		</tr>
	</tbody>
</table>

实际使用中,应主要通过 colgroupcol 为各列指定 class 属性,再通过内容之外的 CSS 设置该对应列的显示样式,做到内容和样式分离,而不应该像上面那样直接设置各列的 style 属性。

17 公式

一直以来,在浏览器中输入和显示公式都是一件很困难的事情,这也是阻碍在浏览器中编辑和显示学术内容的最大障碍。所幸的是到目前为止,这其中的许多问题都有了解决方案,只是这些解决方案相互差异较大,对非专业人员仍显得复杂。InkMark 为数学公式的排版显示提供了一个初级的大杂烩式的解决方案,并且经常需要依赖特定的数学公式显示引擎。为了把问题交代清楚,先介绍以下三个知识点:

  • 数学标记语言:泛指那些基于数学符号的计算机符号,用于表示数学公式。这里并不特指 MathML。有关各种数学标记语言的介绍可见于这个维基百科页面。最常用的数学标记语言是 TeX / LaTeXMathML,前两者非常简洁,可通过人工输入,后者很繁琐,很难通过人工输入,主要用于计算机程序间的数据交换。
  • 公式编辑器:对于大多数人来说,要手工以某种数学标记语言编写公式都是一件非常困难的事,这时就要借助公式编辑器。现有的许多公式编辑器可以使我们通过鼠标和键盘,以所见即所得的方式编写公式,并将公式导出为特定数学公式语言描述的文本。目前最好用的公式编辑器都是商业化的,如 Micorsoft Office 自带的公式编辑器、MathTypeAxMath 等。LibreOffice / OpenOffice 自带的公式编辑器也相对好用,但其公式使用 OpenDocument 格式却很难用于网页,需要专门的插件才能导出为 LaTeX 格式。
  • 公式显示引擎:要在浏览器中正确地显示公式,往往需要浏览器插件或引入 JavaScript 库,或者在服务器端将公式渲染为图片。Mozilla Firefox 浏览器包含对 MathML 的较完整支持,但其他多数浏览器都不支持。维基百科系列网站中最主流的做法是将公式渲染为位图图片。还有其他许多网站主要借助 MathJaxKaTeX 这类 JavaScript 库进行公式渲染,这些引擎一般基于现代 CSS、Web 字体、SVG 技术渲染公式。

17.1 简单公式

由于有关公式的描述、输入与显示的技术方案有多种,且比较复杂,无法在 InkMark 指定具体该使用什么技术。因此,InkMark 只是提供了两个简单的包装公式的元素,这两个元素分别是行内公式 formula 和块级公式 blockformula,他们对应的标记符号都是美元符 $。并不需要强制使用这两个元素,应该根据公式显示引擎的具体要求以及文档编排需求输入公式。

先来看看 formula 元素都做了什么。如对于以下纯 InkMark 文本:

在公式 $[y = ax^[2] + bx + c] 中。

将生成 HTML:

<p>在公式 <span class="formula">y = ax<sup>2</sup> + bx + c</span> 中。</p>

其显示效果为:在公式 y = ax2 + bx + c 中。

formula 元素只是做到在生成的 HTML 中将其内容放置在 span 标签元素内,并给该元素一个 formula 类,仅此而已。另外,一般会通过 CSS,专门设置类选择器 .formula 的显示效果,如设置其字体为某种数学公式字体(实际考虑到并非所有的操作系统都有同样的数学公式字体,一般将字体设置为 “Times New Roman” 或其他衬线字体就基本达到美观要求了)。

在公式中,一般将变量设置为斜体显示,以上代码显然没有做到。以往经常用强调元素 em 表示变量,但这里建议使用语义更贴切的变量元素 var 而不是 em。如下所示:

在公式 $['[y] = '[a] '[x] ^[2] + '[b] '[x] + '[c]] 中。

其所生成的 HTML 为:

<p>在公式 <span class="formula"><var>y</var> = <var>a</var> <var>x</var><sup>2</sup> + <var>b</var> <var>x</var> + <var>c</var></span> 中。</p>

其显示效果为:在公式 y = a x2 + b x + c 中。

可以看出,只需要对类选择器 .formula 设置少量的样式,就能得到较好的显示效果。

再来看看 blockformula 元素。如下代码:

$ '[y] = '[a] '[x] ^[2] + '[b] '[x] + '[c]

将生成 HTML:

<p class="blockformula">
	<span class="formula"><var>y</var> = <var>a</var> <var>x</var><sup>2</sup> + <var>b</var> <var>x</var> + <var>c</var></span>
</p>

其显示效果为:

y = a x2 + b x + c

另外,同图像框 imgbox、表格 table 元素一样,可以为 blockformula 元素添加带编号的标题。如下所示:

$ [
	'[y] = '[a] '[x] ^[2] + '[b] '[x] + '[c]
][label="formula"][caption="抛物线公式"][number="({{#}})"][template="{{#}}"]

将生成如下 HTML:

<p class="blockformula">
	<span class="formula"><var>y</var> = <var>a</var> <var>x</var><sup>2</sup> + <var>b</var> <var>x</var> + <var>c</var></span>
	<span class="number" id="formula_抛物线公式" label="formula">(1)</span>
</p>

通过设置 CSS,以上 HTML 代码可按如下方式显示:

y = a x2 + b x + c (1)

以上定义带编号的 blockformula 时,需要定义其 4 个属性,这比较繁琐。如果不打算在其他地方引用公式,也不打算生成公式索引,完全可以省略其 caption 属性,进而可省略其 template 属性。如下所示:

$ [
	'[y] = '[a] '[x] ^[2] + '[b] '[x] + '[c]
][label="formula"][number="({{#}})"]

这将生成如下 HTML:

<p class="blockformula">
	<span class="formula"><var>y</var> = <var>a</var> <var>x</var><sup>2</sup> + <var>b</var> <var>x</var> + <var>c</var></span>
	<span class="number" id="formula_1" label="formula">(1)</span>
</p>

上面没有指定 caption 属性,相当于公式标题的编号内容为空,这时生成的编号的 id 值就是“编号标签 + _ + 编号值”了。

$ [
	'[y] = '[a] '[x] ^[2] + '[b] '[x] + '[c]
][label="formula"][number="({{#}})"]

有些时候,仅仅需要一个变量,这时直接用 var 元素就好了,没必要一定要用 formula 元素,如 '[x] 将显示为 x。但如果是 '[x]_[1],为了让下标 1 和变量 x 的字体一致,最好还是将他们都放在 formula 元素内,即以 $['[x]_[1]] 的形式标注。还有一些时候,对于稍微复杂的公式,我们会使用图片代替,对于行内公式,直接用 img 元素就好了,也没必要使用 formula 元素。如 &[sqrt.png][平方根公式] 将显示 平方根公式。但对于单独一行显示的公式,为了应用一些固有的样式,或者为了

$ [
	&[parabolic-formula.png]
][label="formula"][caption="抛物线公式2"]

前面已经有一个具有属性值 label="formula" 的块级公式了,因此这里不需要再次指定其 numbertemplate 属性。此公式将生成如下 HTML:

<p class="blockformula">
	<span class="formula"><img src="parabolic-formula.png" alt="抛物线公式2"></span>
	<span class="number" id="formula_抛物线公式2" label="formula">(2)</span>
</p>

请注意此处 blockformulacaption 属性值,被自动地赋值给其中 img 元素的 alt 属性了。以上代码的其显示效果为:

抛物线公式2 (2)

17.2 LaTeX 公式

当文档中公式很多,且对公式的输入及显示要求较高时,可以考虑采用在文档中输入 LaTeX 公式,这时一般会同时用到 MathJax 或 KaTeX 这类数学公式显示引擎。这两个公式显示引擎一般都要求将行内公式放置在\(  \)$  $ 之间,将块级公式放置在 \[  \]$$  $$ 之间。这些符号都不会被当做 InkMark 标记符号,因此就依照这些公式引擎的要求做就好了,他们将会原样生成 HTML,从而被这些引擎辨识并渲染。如在文本之间输入 $\sqrt{x}$ 将会显示为 $\sqrt{x}$。而单独占一行输入的:

$$x=\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$

将显示为:

$$x=\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$

当然,我们仍然可以使用 blockformula 元素包括这些公式,如下所示:

$ $$x=\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$

这将生成 HTML:

<p class="blockformula">
	<span class="formula">$$x=\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$</span>
</p>

其显示效果和不用 blockformula 时是一样的。

如果要给公式添加编号,就必须使用 blockformula 元素包括公式了。如下所示:

$ [
	$$x=\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$
][label="formula"][caption="一元二次方程的根1"]

其显示效果为:

$$x=\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$ (3)

17.3 MathML 公式

MathML 的全称是 “Mathematical Markup Language”,它是一种基于 XML 的标准,用来描述数学符号和公式。它的目标是把数学公式集成到万维网和其他文档中。从 2015 年开始,MathML 成为了 HTML5 的一部分和 ISO 标准。因此,从理论上来说,InkMark 应该首先考虑能够集成 MathML,不过实际上支持 MathML 的浏览器有限,又过于繁琐,致使其广泛应用仍存在较大的困难。

InkMark 解析器应该根据实际的应用需求,决定是否支持 MathML。由于在正常的 InkMark 文本中,所有的 HTML 元素标记都会被转义,意味着这种基于 XML 的 MathML 也会被转义。因此,如果 InkMark 解析器选择支持 MathML,则在遇到 <math> </math> 标签对时,应不对该标签对本身以及其中的内容进行转义。而该标签对之中可能会被置入非 MathML 内容,从而带来一定的安全隐患。以下示例说明了如何在正常的段落文本中置入 MathML 公式:

这是抛物线公式:
<math>
	<mrow>
		<mi>y</mi><mo>=</mo>
		<mi>a</mi><mo>&InvisibleTimes;</mo><msup><mi>x</mi><mn>2</mn></msup>
		<mo>+</mo><mi>b</mi><mo>&InvisibleTimes;</mo><mi>x</mi>
		<mo>+</mo><mi>c</mi>
	</mrow>
</math>

其显示效果为:

这是抛物线公式: y= ax2 +bx +c

如果上一段公式显示不正常,就说明你的浏览器不支持 MathML。

同样可以使用 formulablockformula 元素标记 MathML 公式。formula 标记的作用其实并不大。当要使公式以特定样式显示(如居中),或给公式添加编号时,blockformula 还是有点用的。如下所示:

$ [
<math template='block'>
    <mrow>
        <mi>x</mi><mo>=</mo>
        <mfrac>
            <mrow>
                <mo>&#x2212;</mo><mi>b</mi><mo>&#x00B1;</mo>
                <msqrt>
                    <mrow>
                        <msup><mi>b</mi><mn>2</mn></msup>
                        <mo>&#x2212;</mo><mn>4</mn><mi>a</mi><mi>c</mi>
                    </mrow>
                </msqrt>
            </mrow>
            <mrow>
                <mn>2</mn><mi>a</mi>
            </mrow>
        </mfrac>
    </mrow>
</math>
][label="formula"][caption="一元二次方程的根2"]

其显示效果为:

x= b± b2 4ac 2a (4)

18 参考文献

InkMark 完全依靠自动编号内容元素 anc 实现对参考文献的支持。如在文本流中插入:

#[赵六, 公孙七, 慕容八, 等. 书籍标题 [M]. 北京: 清华大学出版社, 2021.][reference][number="[{{#}}]"][template="^[ {{#}} ]"]

以上代码将在文本流中显示一个 [2] 形式的上标数字。其对应的 HTML 代码为:

<span id="reference_赵六,_公孙七,_慕容八,_等._书籍标题_[M]._北京:_清华大学出版社,_2021." label="reference"><sup>[2]</sup></span>

如果要再次引用这一相同的参考文献,并且通过在正文中重复输入 #[赵六, 公孙七, 慕容八, 等. 书籍标题 [M]. 北京: 清华大学出版社, 2021.][reference][number="[{{#}}]"][template="^[ {{#}} ]"] 的方式实现,将会导致生成的文献索引列表中重复出现该文献。为了避免这一问题,应该使用 roanc 元素而非 anc 元素,即通过引用已有的参考文献的方式实现,其方法为:

#[ref="赵六, 公孙七, 慕容八, 等. 书籍标题 [M]. 北京: 清华大学出版社, 2021."][reference]

由于前面没有显式指定文献的 id,这里必须使 ref 属性值等于被引用文献的全部内容。以上代码将在正文中显示为 [2],其对应的 HTML 代码为:

<a href="#reference_赵六,_公孙七,_慕容八,_等._书籍标题_[M]._北京:_清华大学出版社,_2021." label="reference"><sup>[2]</sup></a>

要生成参考文献列表,一般可通过如下方式实现:

[=loanc][reference][template="{{#}} {{~}}"]

目前 Markdown 已经可以结合其他第三方的文献管理软件自动插入参考文献,从而实现诸多高级功能。如对文献不同的引用方式和排序方式。针对 Zotero 文献管理软件,其插件列表页面列出了一些的与 Markdown 相关的插件。同样地,预计 InkMark 也完全可以实现与 Zotero 的集成。

19 文档组织

大型文档往往由多个部分组成。这些文档组成部分可能是多个文件,也可能是网站的一个页面。这里只讨论以文件存储文档各个部分的情况,并假定各个文件的后缀名为 .ink

InkMark 从设计上尽可能保证各个文件可以独立撰写。例如,假设一个文档由如下文件构成:

index.ink
ch01.ink
ch02.ink
……

各个文件的可能形式为:

index.ink 文件:

= 文档标题

关于该文档的前言性介绍。

> ch01.ink
> ch02.ink

……

ch01.ink 文件:

= 第 1 章标题
== 1.1 节标题

……

ch02.ink 文件:

= 第 2 章标题
== 2.1 节标题

……

请注意各章文件 ch01.inkch02.ink 等开头的标题是一级标题 h1 而非二级标题 h2。最终合成的 HTML 文档内容如下所示:

<h1 id="文档标题">文档标题</h1>

<p>关于该文档的前言性介绍。</p>

<section>
	<h2 id="第_1_章标题">第 1 章标题</h2>
	<h3 id="1.1_节标题">1.1 节标题</h3>

	……
</section>

<section>
	<h2 id="第_2_章标题">第 2 章标题</h2>
	<h3 id="2.1_节标题">2.1 节标题</h3>

	……
</section>

……

可以看到原来在 ch01.inkch02.ink 中的一级标题在总文档中都变成二级标题,而二级标题在总文档中都变成三级标题了,依次类推。

这里约定 index.ink 文件为总文档的入口(你也可以规定为其他文件),该文件中类似 > ch01.ink 的行表示包含文件 ch01.ink 的内容,其中的大于号 > 是 InkMark 自定义元素一级包含 i1 对应的标记符号。共有 6 个这样的标记符号,分别是 i1i2i3i4i5i6,他们对应的标记符号分别是 >>>>>>>>>>>>>>>>>>>>>,可以分别称这些元素为一级包含、二级包含、三级包含,等等,他们都是块级元素。这种基于标记符号 > 的个数来判断标记等级的方法与标题 h1h6 的标记方法相似。

当使用一级包含 > 引入被包含文件时,该被引入文件所有标题的等级在引入文件中将降低 1 级;当使用二级包含 >> 引入被包含文件时,该被引入文件所有标题的等级在引入文件中将降低 2 级;当使用三级包含 >>> 引入被包含文件时,该被引入文件所有标题的等级在引入文件中将降低 3 级;依次类推。另外,一般会在被引入的文件内容外围用 section 元素包含。

在以上 ch01.ink 文件中还可以继续包含其他文件,如下所示:

= 第 1 章标题
== 1.1 节标题

……

> sec02.ink

sec02.ink 文件所表示的节内容最终也将被包含到总文档中。

应杜绝对文件的循环引用,比如文件 a.ink 引用文件 b.ink,而文件 b.ink 又引用文件 a.ink 是不行的。解析器应该对文件的循环引用进行检查,当存在这种问题时,显示错误信息并停止解析。

也可以一个文件内包含对所有文件的引用,从而使文档结构更加清晰。如在 index.ink 文件中引用所有章和节的内容:

> ch01/preface.ink
	>> ch01/sec1.ink
	>> ch01/sec2.ink
	>> ch01/sec3.ink
> ch02/preface.ink
	>> ch02/sec1.ink
	>> ch02/sec2.ink
	>> ch02/sec3.ink
	>> ch02/sec4.ink
> ch03/preface.ink
	>> ch03/sec1.ink
	>> ch03/sec2.ink
	>> ch03/sec3.ink

基于以上文档组织方法,在编写 InkMark 文档时,可以把其中的章、节等分散到多个文件夹和文件中编写。每个章、节的编写基本上像编写一个独立文档一样:每个 InkMark 文件的最高等级标题总是一级标题,从而不用关心各个标题在总文档中的等级。当然,仍然并部分因素使我们不得不考虑其他文档中的内容:如果通过标题的 number 属性指定其编号格式,就需要确切知道该标题在总文档中的等级;如果引用其他标题或自动编号内容,当这些被引用内容在其他文档时,也需要确保这些被引用内容是存在的。

20 中文支持

本节讨论的内容都是建议实现的内容,这些内容可以大大降低中文用户使用 InkMark 的门槛。这些内容包括:

  1. 使用中文名称

    使用完整模式时,InkMark 的元素类型名称是英文的,尽管 InkMark 可以使用标点符号代替许多常用元素的标记项,但还有一些不常用元素无法使用符号标记法。另外,元素的属性名称也都是英文的。对于部分中文用户,要记住这些英文名称可能并不那么容易。因此,InkMark 可以允许使用中文的标签名和属性名进行元素标记。如下示例:

    $ [
    	'[y] = '[a] '[x] ^[2] + '[b] '[x] + '[c]
    ][标签="公式"][编号="({{#}})"]
    

    不过,要给所有标签、属性名寻找一个相对“标准”的中文名称,也是一项不小的工作,这方面留待以后慢慢完成。

  2. 中英文混排

    在中英文混排时,一般要求在中英文之间、中文与数字之间添加空格,但许多用户往往忘记这么做,建议解析器将这方面的工作自动化。

  3. 使用英文标点

    在编写 InkMark 文档中,由于大量使用各种英文标点作为标记符号,而中文文本内却要使用不同的中文标点,这会导致需要不停地进行输入法切换。为了避免反复的输入法切换,提高用户输入体验,可以允许用户在输入中文时使用英文标点,而编译后自动转换为中文标点。如当检测到标点符号前面的句子中包含中文时,行内的 .␣ 以及行尾的 . 都转换为 (这里的 代表空格),行内的 ,␣转换为 ,成对出现的 ␣( )␣ 转换为 ( )␣< >␣ 转换为 《》␣' '␣ 转换为 ‘ ’␣" "␣ 转换为 “ ”,三个句点 ... 转换为省略号的一半 ,行内的三个连字符 --- 转换为破折号的一半 ,由于英文中没有顿号 ,可将其用双写的逗号 ,, 代替,凡此种种。

  4. 支持中文编号

    如前所述,可以在标题的编号、自动编号内容的编号中使用中文编号,如一、壹、㈠、㊀、甲等。

21 发布许可

知识共享许可协议
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。