查找块

文档加载(或部分加载)后,您可以遍历文档以查找块节点。有两种查找块节点的方法。一种方法是从 Document 对象开始向下遍历树。所有块都可以从 Document 对象访问。但是,查找块的更快捷的方法是使用 find_by 方法,该方法会自动为您进行遍历。我们将从这里开始,然后查看如何使用自定义遍历方法。

find_by

每个块节点(已解析的块),包括 Document 对象,都提供 find_by 方法。此方法旨在帮助您快速查找后代块。由于某些块具有不同的模型,因此此方法可以帮助您导航文档,而无需担心这些细微差别。

find_by 方法仅查找块节点。它不查找内联节点。

如果您想查找已解析文档中的任何块,请在 Document 对象上调用 find_by 方法。否则,您可以调用相关块的祖先上的 find_by 方法来查找文档特定区域中的块。

此方法的返回值是一个扁平的块数组,按文档顺序排列,并且已匹配。这些块之间的关系仅通过它们各自的模型来保留。如果没有匹配的块,则方法返回一个空数组。

所有块

如果未提供任何参数,find_by 方法将返回从调用它的块开始的所有块。如果调用 Document 对象,它将返回文档中的所有块(不包括 AsciiDoc 表格单元格中的块),包括文档本身。这是一个例子

require 'asciidoctor'

doc = Asciidoctor.load_file 'input.adoc', safe: :safe
puts doc.find_by

这是一个查找第一个节中所有块的示例

doc.sections.first.find_by

请注意,find_by 方法始终将您开始的块作为第一个结果返回(假设它也匹配稍后介绍的提供的选择器)。如果要排除该块,请将其从结果中切片掉。

puts doc.find_by.slice 1..-1

如果您只查找第一个结果,可以从结果数组中提取它。

puts doc.find_by.first

默认情况下,并且为了向后兼容,find_by 方法不会深入遍历 AsciiDoc 表格单元格。如果您希望它在这些单元格中查找块,请将选择器 Hash 中的 :traverse_documents 键设置为 true。

all_blocks = doc.find_by traverse_documents: true

下一节将介绍如何过滤返回的块。

过滤块

使用 find_by 方法时,您可能正在查找特定的块。该方法接受一个可选的选择器(一个 Hash)和一个可选的块过滤器(一个 Ruby proc)。该方法将遍历整个树(如果 :traverse_documentstrue,则包括 AsciiDoc 表格单元格)来查找块。默认情况下,它会向下遍历不匹配的块,尽管可以使用块过滤器控制此行为。

匹配块的最简单方法是使用选择器。选择器是一个 Hash,它接受四个预定义的符号键。

:context

单个块 上下文(即块名称),例如 :paragraph

:style

单个块样式,例如 source

:id

ID。

:role

单个角色。

如果指定了 :id,则该方法永远不会返回超过一个块,因为 ID 本身是全局唯一的。以下是如何使用 :id 选择器按 ID 查找块的示例。

match = (doc.find_by id: 'prerequisites').first

现在,假设我们要匹配所有是源块的列表块。我们可以通过组合 :context:style 选择器来实现。

some_source_blocks = doc.find_by context: :listing, style: 'source'

由于字面块也可以是源块,如果我们想要所有源块,我们需要省略 :context 选择器。

all_source_blocks = doc.find_by style: 'source'

如果我们想要所有带有特定角色的块,我们可以使用 :role 选择器来查找它们。

blocks_with_role = doc.find_by role: 'try-it'

选择器 Hash 的设计有意简单,以便于查找块。如果您要查找的块无法使用该选择器进行描述,那么您将需要使用块过滤器。

块过滤器是一个 Ruby proc,它会在访问的每个块上运行。它接受候选块作为唯一参数(即,将候选块传递给 proc)。如果 proc 返回 true,则认为候选块已匹配。

以下是使用块过滤器查找所有顶层节的示例。

top_level_sections = doc.find_by {|block| block.context == :section && block.level == 1 }

通过将其与选择器结合使用,我们可以使其更有效率。

top_level_sections = doc.find_by(context: :section) {|section| section.level == 1 }

如果提供了 Ruby 块,则它将作为附加过滤器应用于选择器。换句话说,候选块必须同时匹配选择器和过滤器。

控制遍历

块过滤器的优势在于它还允许您控制遍历。过滤器方法可以返回以下任何关键字。

true
:accept

接受块,继续遍历。

false
:skip

跳过块,但遍历其子项。

:reject

拒绝块,并且不遍历其子项。

:prune

接受块,但不遍历其后代。

以下是匹配所有未包含在另一个块内的侧边栏的高效方法。

top_level_sidebars = doc.find_by do |block|
  if block == block.document
    :skip
  elsif block.context == :sidebar
    :prune
  else
    :reject
  end
end

过滤器必须返回 :skip 而不是 :reject 来处理文档对象,否则将不会遍历任何块。

如果组合使用选择器和块过滤器,您将对遍历哪些节点的可控性会降低。因此,如果您将使用块过滤器来控制遍历,最好将所有逻辑都放在该过滤器中。

自定义遍历

查找块的另一种方法是显式遍历树。从文档对象开始,您可以通过调用 blocks 方法来访问其子项。

doc.blocks.each do |block|
  puts block
end
并非所有块都具有相同的模型。例如,描述列表中的每个项都是两个节点的数组。并且表格与其他块的模型截然不同。在遍历文档模型时,要注意这些差异很重要。

如果您要查找的块或块近在咫尺或位于已知位置,则使用自定义遍历可能更有效。但是,如果您不确定块在文档树中的位置,使用 find_by 方法来定位它会更好。