映射块的源位置

由于 Asciidoctor 的主要重点是高效地转换文档,因此它默认不尝试在解析时跟踪块的源位置。然而,这些信息对于从源文档中提取信息、改进错误消息以及供扩展使用非常有用。因此,Asciidoctor 提供了一个映射块源位置的标志,称为 sourcemap。本页将举例说明如何启用 sourcemap 以及如何利用它提供的信息。

sourcemap 提供什么?

sourcemap 为解析文档中的所有块提供行和文件信息。具体来说,它提供了关于每个块开始位置的信息。块的开始位置不包括块上方的任何块元数据(块锚点和块属性)。

sourcemap 还跟踪每个预处理器条件指令的开始行,因此如果指令未关闭,可以报告其位置。如果未启用 sourcemap,则使用文档末尾的位置。

sourcemap 仅将源位置信息添加到块。它不跟踪内联元素(如格式化文本或内联图像)或属性条目的源位置。

sourcemap 信息可在块的 source_location 属性上找到。启用 sourcemap 时,此属性的值是 Cursor 对象。Cursor 对象包含以下属性:

file

块开始的源文件的绝对文件名(如果输入是字符串,则值为 nil

dir

块开始的源文件的绝对目录(如果输入是字符串,则值为 base dir)

lineno

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

path

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

linenofile 属性可以直接作为块本身的同名属性进行访问。

sourcemap 并不完美。某些边缘情况,例如块跨越多个文件,或块开始和结束在 include 文件的最后一行,sourcemap 可能会报告错误的文件或行信息。如果您正在编写依赖于 sourcemap 的处理器,最好验证光标处的行是否是您期望找到的行,然后进行相应调整。

使用 :sourcemap 选项启用

sourcemap 功能可以通过 API 使用 :sourcemap 选项进行控制。此选项的值是布尔值。如果值为 false(默认),则 sourcemap 未启用。如果值为 true,则 sourcemap 已启用。:sourcemap 选项被所有 入口点方法(例如,Asciidoctor#load_file)接受。

以下是如何使用 API 启用 sourcemap 的示例

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

从扩展启用

您可以使用 Asciidoctor 预处理器扩展来启用 sourcemap。如果您需要访问块的源位置的扩展,但又不想要求用户向 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 已启用,您的扩展可以访问解析文档中块元素的源位置。

使用 sourcemap

启用 sourcemap 后,解析器将在解析文档的每个块上的 source_location 属性上存储源信息。让我们看一个例子。

首先,创建以下名为 doc.adoc 的 AsciiDoc 文件。

示例 1. doc.adoc
= Document Title

== Section

Paragraph.

Another paragraph.

现在,使用 Asciidoctor 加载此文件,并启用 :sourcemap 选项

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

您在这里看到的是光标的字符串值。如果您将 puts 替换为 pp,您会看到更多信息。

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

由于 file 和 lineno 是最有用的属性,它们可以直接从块访问

puts first_paragraph.file
puts first_paragraph.lineno

如果将节的源移至 include 文件,如下所示

示例 2. doc.adoc
= Document Title

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">

如果块具有元数据行,则在报告块的开始位置时会跳过这些行。例如,假设段落定义如下

[#p1]
Paragraph.

现在,源位置中段落的 lineno 比之前大一。

#<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),这至少应该能让您接近元素的位置。