由于Asciidoctor的主要关注点是高效地转换文档,所以默认情况下它并不尝试在解析时追踪块的源位置。然而,这样的信息对于从源文档提取信息、改进错误信息以及在扩展中使用可能是有用的。因此,Asciidoctor提供了一个标志以映射块的源位置,称为源码映射(sourcemap)。本页面例举了如何启用源码映射以及如何利用它所提供的信息。

源映射提供了什么?

源映射为解析文档中的所有块提供行和文件信息。特别是,它提供了每个块开始的信息。块的开头不包括位于块上方的任何块元数据(块锚点和块属性)。

源映射只适用于块元素。它并不追踪内联元素的源位置,比如格式化文本或内联图像,也不追踪属性条目的源位置。

源映射信息可通过块的`source_location`属性获取。当启用源映射时,该属性的值是一个`Cursor`对象。`Cursor`对象包含以下属性:

文件

代码块开始位置的源文件的绝对文件名(如果输入是一个字符串,该值为`nil`)

目录

块开始的源文件的绝对目录(如果输入是一个字符串,这个值是基础目录)

行号

源文件中块开始的行号(在任何空行或块元数据行之后)

路径

区块开始处源文件(相对于docdir的)的相对路径(如果输入是字符串,则值为`<stdin>`)

lineno` 和 file 属性可以作为块本身的同名属性进行访问。

Important
源映射并不完美。在某些边界情况下,例如当块跨越多个文件分割或者块在一个包含文件的最后一行开始和结束时,源映射可能会报告错误的文件或行信息。如果你正在编写一个依赖源映射的处理器,那么确认光标所在行是你期望找到的那一行是个好主意,然后相应地进行调整。

启用使用 :sourcemap 选项

源映射功能可以通过API中的`:sourcemap`选项来控制。此选项的值是一个布尔值。如果值为`false`(默认值),则不启用源映射。如果值为`true`,则启用源映射。`:sourcemap`选项被所有的入口方法(例如,Asciidoctor#load_file)所接受。

以下是一个使用API启用源映射的示例:

doc = Asciidoctor.load_file 'doc.adoc', safe: :safe, sourcemap: true

从扩展启用

你可以使用Asciidoctor预处理器扩展来启用源映射。如果你的扩展需要访问块的源位置,但你不想要求用户向Asciidoctor传递额外的选项,这种技术是非常有用的。

Asciidoctor::Document.prepend (Module.new do
  attr_writer :sourcemap
end) unless Asciidoctor::Document.method_defined? :sourcemap=

# A preprocessor that enables the sourcemap feature if not already enabled via the API.
Asciidoctor::Extensions.register do
  preprocessor do
    process do |doc, reader|
      doc.sourcemap = true
      nil
    end
  end
end

现在 sourcemap 已经启用,您的扩展可以访问解析文档中块元素的源位置。

使用源映射

当启用源映射时,解析器将在解析文档中的每个块上的`source_location`属性上存储源信息。让我们来看一个例子。

从创建以下名为[.path]_doc.adoc_的AsciiDoc文件开始。

doc.adoc
= 文件标题

== 章节

段落。

另一个段落。

现在,使用启用了 :sourcemap 选项的 Asciidoctor 加载这个文件:

doc = Asciidoctor.load_file 'doc.adoc', safe: :safe, sourcemap: true

让我们找到文档中的第一个段落并检查其源位置:

first_paragraph = (doc.find_by context: :paragraph)[0]
puts first_paragraph.source_location

你将看到类似于下面所示的输出:

doc.adoc: line 5

你在这里看到的是游标的字符串值。如果你用`pp`替换`puts`,你可以看到更多信息。

#<Asciidoctor::Reader::Cursor
 @dir="/path/to/docdir",
 @file="/path/to/docdir/doc.adoc",
 @lineno=5,
 @path="doc.adoc">

由于文件和行号是最有用的属性,它们可以直接从块中访问:

puts first_paragraph.file
puts first_paragraph.lineno

如果你将段落的来源移动到包含文件中,如下所示:

doc.adoc
= 文件标题

include::partials/section.adoc[]

那么源位置将跟随该段落进入那个文件:

#<Asciidoctor::Reader::Cursor
 @dir="/path/to/docdir/partials",
 @file="/path/to/docdir/partials/section.adoc",
 @lineno=3,
 @path="partials/section.adoc">

如果代码块有元数据行,那么在报告块的开始位置时会跳过这些行。例如,假设段落是这样定义的:

段落。

源位置中段落的行号现在比之前大了一。

#<Asciidoctor::Reader::Cursor
 @dir="/path/to/docdir/partials",
 @file="/path/to/docdir/partials/section.adoc",
 @lineno=4,
 @path="partials/section.adoc">

如果您正在编写自定义转换器,内联元素无法获取源位置。然而,您可以访问父元素的源位置(例如,node.parent.source_location),这至少可以让你接近元素的位置。