一旦文档被加载(或部分加载),你可以遍历文档来寻找块节点。寻找块节点有两种方法。一种方法是从Document对象开始向下遍历树。所有的块都可以从Document对象访问到。然而,一个更快的寻找块的方法是使用`find_by`方法,它会为你完成遍历。我们将从这里开始,然后看看如何使用自定义遍历方法。

查找

每个块节点(解析后的块),包括Document对象,都提供了{url-api-gems}/asciidoctor/{release-version}/Asciidoctor/AbstractBlock#find_by-instance_method[find_by]方法。该方法的目的是为了帮助您快速找到子代块。由于某些块有不同的模型,这个方法可以帮助您在不必担心这些细微差别的情况下导航文档。

Important
find_by` 方法只查找块级节点。它不查找行内节点。

如果您想在解析的文档中查找任何块,可以在Document对象上调用`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 表格单元格。如果你希望它在这些单元格中查找块,请在选择器散列上设置 :traverse_documents 键为 true。

all_blocks = doc.find_by traverse_documents: true

下一节将讨论如何筛选返回的区块。

筛选块

当使用 find_by 方法时,你可能在寻找特定的块。该方法接受一个可选的选择器(一个哈希)和一个可选的块过滤器(一个Ruby进程)。该方法将遍历整个树(包括在AsciiDoc表格单元格中,如果 :traverse_documents 设置为 true),以寻找块。默认情况下,它会深入到不匹配的块中,尽管这种行为可以通过块过滤器来控制。

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

上下文

一个单独的块引用 上下文(即,块名称),例如 :paragraph

样式

一个单独的块状样式,比如`source`。

标识符

一个身份证件。

角色

一个角色。

如果指定了`: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过程。它接受候选块作为唯一参数(即,候选块被传递给过程)。如果过程返回真值,那么候选块就被视为匹配成功。

这里有一个使用块过滤器找到所有顶级部分的例子:

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块,它将作为选择器的补充过滤器应用。换句话说,候选块必须匹配选择器和过滤器。

控制遍历

块过滤器的好处是它还允许你控制遍历。filter方法可以返回以下任何关键词:

:accept

该块被接受,遍历继续进行。

: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`方法来访问其子元素。

ruby
doc.blocks.each do |block|
  puts block
end
Caution
不是所有的块都有相同的模型。例如,描述列表中的每个项都是由两个节点组成的数组。而表格的模型与其他块的模型大相径庭。在遍历文档模型时,意识到这些差异是很重要的。

如果你要找的块或块都在手边或在一个已知的位置,使用自定义遍历可能更有效。然而,如果你不确定块在文档树中的位置,使用 find_by 方法来定位它会更好。