summaryrefslogtreecommitdiff
path: root/docs/modules/api
diff options
context:
space:
mode:
authorDan Allen <dan.j.allen@gmail.com>2023-04-23 23:59:02 -0600
committerDan Allen <dan.j.allen@gmail.com>2023-04-24 03:48:31 -0600
commit36b4d1862dc8b276e480da28cdb61c3d55919015 (patch)
treebf7f41c9cb17301b4c5eb0415338a9a376ca1f6b /docs/modules/api
parent756455747b21b62c4da045a8579e44a5584f55ac (diff)
document the find_by method on blocks
Diffstat (limited to 'docs/modules/api')
-rw-r--r--docs/modules/api/nav.adoc1
-rw-r--r--docs/modules/api/pages/find-blocks.adoc198
2 files changed, 199 insertions, 0 deletions
diff --git a/docs/modules/api/nav.adoc b/docs/modules/api/nav.adoc
index 8354bed0..dbdd72cb 100644
--- a/docs/modules/api/nav.adoc
+++ b/docs/modules/api/nav.adoc
@@ -5,4 +5,5 @@
** xref:set-safe-mode.adoc[]
** xref:sourcemap.adoc[]
** xref:catalog-assets.adoc[]
+** xref:find-blocks.adoc[]
** xref:options.adoc[]
diff --git a/docs/modules/api/pages/find-blocks.adoc b/docs/modules/api/pages/find-blocks.adoc
new file mode 100644
index 00000000..0908c396
--- /dev/null
+++ b/docs/modules/api/pages/find-blocks.adoc
@@ -0,0 +1,198 @@
+= Find Blocks
+
+Once the document has been loaded (or partially loaded), you can traverse the document to find block nodes.
+There are two ways to look for block nodes.
+One way is to start walking down the tree starting from the Document object.
+All blocks can be reached from the Document object.
+However, a much quicker way to find blocks is to use the `find_by` method, which does the walking for you.
+We'll start there, then look at how to use the custom traversal approach.
+
+== find_by
+
+Every block node (a parsed block), including the Document object, provides the {url-api-gems}/asciidoctor/{release-version}/Asciidoctor/AbstractBlock#find_by-instance_method[find_by] method.
+The purpose of this method is to help you quickly find descendant blocks.
+Since some blocks have different models, this method can help you navigate the document without having to worry about those nuances.
+
+IMPORTANT: The `find_by` method only finds block nodes.
+It does not find inline nodes.
+
+If you want to look for any block in the parsed document, call the `find_by` method on the Document object.
+Otherwise, you can look for blocks in a specific area of the document by calling it on the relevant ancestor of those blocks.
+
+The return value of this method is a flat array of blocks in document order which were matched.
+The relationship between those blocks is only preserved by way of their own model.
+If no blocks are matched, the method returns an empty array.
+
+=== All blocks
+
+If not called with any arguments, the `find_by` method will return all blocks starting from the block on which it was called.
+If called on the Document object, it will return all blocks in the document (except for blocks in AsciiDoc table cells), including the document itself.
+Here's an example:
+
+[,ruby]
+----
+require 'asciidoctor'
+
+doc = Asciidoctor.load_file 'input.adoc', safe: :safe
+puts doc.find_by
+----
+
+Here's an example of how to find all the blocks in the first section:
+
+[,ruby]
+----
+doc.sections.first.find_by
+----
+
+Notice that the `find_by` method always returns the block that you start with as the first result (assuming it also matches the provided selector, covered later).
+If you want to exclude that block, slice it off from the results:
+
+[,ruby]
+----
+puts doc.find_by.slice 1..-1
+----
+
+If youre just looking for the first result, you can pluck it from the result array:
+
+[,ruby]
+----
+puts doc.find_by.first
+----
+
+By default, and for backwards compatibility, the `find_by` method does not traverse into AsciiDoc table cells.
+If you want it to look in these cells for blocks, set the `:traverse_documents` key on the selector Hash to true.
+
+[,ruby]
+----
+all_blocks = doc.find_by traverse_documents: true
+----
+
+The next section will look at how to filter the blocks that are returned.
+
+=== Filter blocks
+
+When using the `find_by` method, you're probably looking for specific blocks.
+The method accepts an optional selector (a Hash) and an optional block filter (a Ruby proc).
+The method will walk the entire tree (including in AsciiDoc table cells if `:traverse_documents` is `true`) to find blocks.
+By default, it will descend into a block which does not match, though this behavior can be controlled using the block filter.
+
+The simplest way to match blocks is to use the selector.
+The selector is a Hash that accepts four predefined symbol keys:
+
+:context:: A single block xref:convert:contexts-ref.adoc[context] (i.e., block name), such as `:paragraph`.
+:style:: A single block style, such as `source`.
+:id:: An ID.
+:role:: A single role.
+
+If an `:id` is specified, the method will never return more than one block since an ID is, by natural, globally unique.
+Here's an example of how to find a block by ID using the `:id` selector:
+
+[,ruby]
+----
+match = (doc.find_by id: 'prerequisites').first
+----
+
+Now let's assume we want to match all listing blocks that are source blocks.
+We can do so by combining the `:context` and `:style` selectors:
+
+[,ruby]
+----
+some_source_blocks = doc.find_by context: :listing, style: 'source'
+----
+
+Since literal blocks can also be source blocks, if we want all source blocks, we'd need to leave off the `:context` selector:
+
+[,ruby]
+----
+all_source_blocks = doc.find_by style: 'source'
+----
+
+If we want all blocks marked with a specific role, we can find them using the `:role` selector:
+
+[,ruby]
+----
+blocks_with_role = doc.find_by role: 'try-it'
+----
+
+The selector Hash is intentionally simple to make it easy to find blocks.
+If the blocks you're looking for cannot be described using that selector, then you'll want to use a block filter instead.
+
+A block filter is a Ruby proc that runs on each block visited.
+It accepts the candidate block as the sole argument (i.e., the candidate block is yielded to the proc).
+If the proc returns true, then the candidate is considered matched.
+
+Here's an example of using the block filter to find all top-level sections:
+
+[,ruby]
+----
+top_level_sections = doc.find_by {|block| block.context == :section && block.level == 1 }
+----
+
+We can make this slightly more efficient by combining it with a selector:
+
+[,ruby]
+----
+top_level_sections = doc.find_by(context: :section) {|section| section.level == 1 }
+----
+
+If a Ruby block is given, it's applied as a supplemental filter to the selector.
+In other words, the candidate block must match the selector and the filter.
+
+=== Control the traversal
+
+The benefit of the block filter is that it also allows you to control the traversal.
+The filter method can return any of the following keywords:
+
+true::
+:accept::
+The block is accepted and the traversal continues.
+
+false::
+:skip::
+The block is skipped but its children are traversed.
+
+:reject::
+The block is rejected and its children are not traversed.
+
+:prune::
+The block is accepted, but its descendants are not traversed.
+
+Here's an efficient way to match all sidebars that are not contained within another block.
+
+[,ruby]
+----
+top_level_sidebars = doc.find_by do |block|
+ if block == block.document
+ :skip
+ elsif block.context == :sidebar
+ :prune
+ else
+ :reject
+ end
+end
+----
+
+The filter has to return `:skip` instead of `:reject` for the document object or else no blocks will be traversed.
+
+If you combine the selector and the block filter, you will have less control over which nodes are traversed.
+Therefore, if you're going to be using the block filter to control the traversal, it's best to do all logic in that filter.
+
+== Custom traversal
+
+Another way to find blocks is to traverse the tree explicitly.
+Starting at the document object, you can access its children by calling the `blocks` method.
+
+[,ruby]
+----
+doc.blocks.each do |block|
+ puts block
+end
+----
+
+CAUTION: Not all blocks have the same model.
+For example, each item in a description list is an array of two nodes.
+And tables have a very different model from other blocks.
+These differences are important to be aware of when traversing the document model.
+
+If the block or blocks you're looking for are close at hand or in a known location, it may be more efficient to use a custom traversal.
+However, if you aren't sure where the block is located in the document tree, you'd be much better off using the `find_by` method to locate it.