28. Include Directive

The include directive provides a way to import content from another file into the current document. The include directive must be placed on a line by itself with the following syntax, which is covered in detail in the next section.

include::path[<attrlist>]

When the document is processed, the include directive is replaced by the contents of the include file. Think of the include directive like a file expander.

This directive is useful if you want to:

  • partition a large document into smaller files (for better organization and to make restructuring simpler),

    • (always separate consecutive include directives by a blank line unless your intent is for the included lines to run together)

  • insert snippets of source code (so your examples are kept up-to-date with the latest source files),

  • populate tables with output from other programs (e.g., CSV data),

  • create document variants by combining the include directive with preprocessor conditionals (e.g., ifdef), and

  • reuse fragments and boilerplate content multiple times within the same document.

You can use the include directive as many times as you want in the same document.

The include directive is disabled when Asciidoctor is run in secure mode. In this mode, the include directive is converted to a link in the output document. To learn more about secure mode, refer to the section Running Asciidoctor Securely.

28.1. Anatomy

The include directive has the following anatomy:

include::path[leveloffset=offset,lines=ranges,tag(s)=name(s),indent=depth,opts=optional]

The leveloffset, lines, tag(s), indent, and opts attributes are optional, making the simplest case look like:

include::content.adoc[]

The sections that follow go into detail about when the include is processed, how the include file is resolved, and how each attribute is used.

28.2. Processing

Although the include directive looks like a block macro, it’s not a macro and therefore not processed like one. Instead, it’s a preprocessor directive, just like ifdef and ifeval. It’s important to understand the distinction.

A preprocessor directive is processed when the lines are read, but before the document structure is parsed. Therefore, it’s not aware of the surrounding document structure. A preprocessor directive merely adds lines to the reader or takes lines away. The include directive is a preprocessor directive that always adds lines.

The best way to think of the include directive is to imagine that it is being replaced by the lines from the include file (i.e., the imported lines). Only then does the parser read and interpret those lines. That’s also why it’s important to surround the include directive by blank lines if it imports in a discrete structure. You only want to place include files directly adjacent to one another if the imported content should be directly adjacent.

If you don’t want the include directive to be processed, you must escape it using a backslash.

\include::just-an-example.ext[]

Escaping the directive is necessary even if it appears in a verbatim block since it’s not aware of the surrounding document structure.

28.3. File resolution

The path used in an include directive may be either relative or absolute.

If the path relative, the processor resolves the path using the following rules:

  • If the include directive is used in the main (top-level) document, relative paths are resolved relative to the base directory. (The base directory defaults to the directory of the main document and can be overridden from the CLI or API).

  • If the include directive is used in a file that has itself been included, the path is resolved relative to the including (i.e., current) file.

These defaults makes it easy to reason about how the path to the include file is resolved.

If the processor cannot locate the file (perhaps because you mistyped the path), you’ll still be able to convert the document. However, you will get the following warning message during conversion:

asciidoctor: WARNING: master.adoc: line 3: include file not found: /.../content.adoc

The following message will also be inserted into the output:

Unresolved directive in master.adoc - include::content.adoc[]

To fix the problem, edit the file path and run the converter again.

If you want Asciidoctor not to trigger a warning when the target is missing, set the optional option on the include directive (e.g., opts=optional).

If you store your AsciiDoc files in nested folders at different levels, relative file paths can quickly become awkward and inflexible. A common pattern to help here is to define the paths in attributes defined in the header, then prefix all include paths with a reference to one of these attributes:

:includedir: _includes
:sourcedir: ../src/main/java

include::{includedir}/fragment1.adoc[]

[source,java]
----
include::{sourcedir}/org/asciidoctor/Asciidoctor.java[]
----

Keep in mind that no matter how Asciidoctor resolves the path to the file, access to that file is limited by the safe mode setting under which Asciidoctor is run. If a path violates the security restrictions, it may be truncated.

28.4. Partitioning large documents and using leveloffset

When your document gets large, you can split it up into subsections for easier editing as follows:

= My book

include::chapter01.adoc[]

include::chapter02.adoc[]

include::chapter03.adoc[]
Take note of the blank lines between the include directives. The blank line between include directives prevents the first and last lines of the included files from being adjoined. This practice is strongly encouraged when combining document parts. If you don’t include these blank lines, you might find that the AsciiDoc processor swallows section titles. This happens because the leading section title can get interpreted as the last line of the final paragraph in the preceding include. Only place include directives on consecutive lines if the intent is for the includes to run together (such as in a listing block).

The leveloffset attribute can help here by pushing all headings in the included document down by the specified number of levels. This allows you to publish each chapter as a standalone document (complete with a document title), but still be able to include the chapters into a master document (which has its own document title).

You can easily assemble your book so that the chapter document titles become level 1 headings using:

= My Book

include::chapter01.adoc[leveloffset=+1]

include::chapter02.adoc[leveloffset=+1]

include::chapter03.adoc[leveloffset=+1]

Because the leveloffset is relative (it begins with + or -), this works even if the included document has its own includes and leveloffsets.

If you have lots of chapters to include and want them all to have the same offset, you can save some typing by setting leveloffset around the includes:

= My book

:leveloffset: +1

include::chapter01.adoc[]

include::chapter02.adoc[]

include::chapter03.adoc[]

:leveloffset: -1

The final line returns the leveloffset to 0.

Alternatively, you could use absolute levels:

:leveloffset: 1

//includes

:leveloffset: 0

Relative levels are preferred. Absolute levels become awkward when you have nested includes since they aren’t context aware.

28.5. AsciiDoc vs non-AsciiDoc files

The include directive performs a simple file merge, so it works with any text file. The content of all included content is normalized. This means that the encoding is forced to UTF-8 (or converted from UTF-16 to UTF-8 if the file contains a BOM) and trailing whitespace and endlines are removed from each line and replaced with a Unix line feed. This normalization is important to how Asciidoctor works.

If the file is recognized as an AsciiDoc file (i.e., it has one of the following extensions: .asciidoc, .adoc, .ad, .asc, or .txt), Asciidoctor runs the preprocessor on the lines, looking for and interpreting the following directives:

  • includes

  • preprocessor conditionals (e.g., ifdef)

This allows includes to be nested, and provides lot of flexibility in constructing radically different documents with a single master document and a few command line attributes.

Including non-AsciiDoc files is normally done to merge output from other programs or populate table data:

.2016 Sales Results
,===
include::sales/2016/results.csv[]
,===

In this case, the include directive does not do any processing of AsciiDoc directives. The content is inserted as is (after being normalized).

28.6. Select Portions of a Document to Include

The include directive enables you to select portions of a file to include instead of including the whole file. Use the lines attribute to include individual lines or a range of lines (by line number). Use the tags attribute (or tag attribute for the singular case) to select lines that fall between regions marked with user-defined tags.

When including multiple line ranges or multiple tags, each entry in the list must be separated by either a comma or a semi-colon. If commas are used, the entire value must be enclosed in quotes. You can eliminate this requirement by using the semi-colon as the data separator instead.

28.6.1. By tagged regions

Tags are useful when you want to identify specific regions of a file to include. You can then select the lines between the boundaries of the include tag/end directives to include using the tags attribute.

If the target file has tagged lines, and you just want to ignore those lines, use the tags attribute to filter them out. See Tag filtering for details.

The example below shows how you tag a region of content inside a file containing multiple code examples.

Tagged code snippets in a file named core.rb
# tag::timings[]  (1) (2)
if timings
  timings.record :read
  timings.start :parse
end
# end::timings[]  (3) (4)
# tag::parse[] (5)
doc = (options[:parse] == false ? (Document.new lines, options) :
    (Document.new lines,options).parse)
timings.record :parse if timings
doc
# end::parse[] (6)
1 To indicate the start of a tagged region, insert a comment line in the code.
2 Assign a unique name to the tag directive. In this example the tag is called timings.
3 Insert another comment line where you want the tagged region to end.
4 Assign the name of the region you want to terminate to the end directive.
5 This is the start a tagged snippet named parse.
6 This is the end of the tagged snippet named parse.
The tag::[] and end::[] directives should be placed after a line comment as defined by the language of the source file. The directives must also appear at the end of the line. In the previous example, we choose to prefix the lines with a hash (#) because that’s the start of a line comment in Ruby.
For languages that only have circumfix comments, such as XML, you can enclose the tag and end directives in the respective circumfix comment markers. For example, in XML files, you can use <!-- tag::name[] --> and <!-- end::name[] --> (the spaces around the tag are required).

In the next example, the tagged region named parse is selected by the include directive.

Selecting the parse code snippet from a document
[source,ruby]
----
include::core.rb[tag=parse] (1)
----
1 In the directive’s brackets, set the tag attribute and assign it the unique name of the code snippet you tagged in your code file.

You can include multiple tags from the same file.

Selecting the timings and the parse code snippets from a document
[source,ruby]
----
include::core.rb[tags=timings;parse]
----

It is also possible to have fine-grained tagged regions inside larger tagged regions.

For example, if your include file has the following content:

// tag::snippets[]
// tag::snippet-a[]
snippet a
// end::snippet-a[]

// tag::snippet-b[]
snippet b
// end::snippet-b[]
// end::snippets[]

And you include this file using the following include directive:

include::file-with-snippets.adoc[tag=snippets]

The following lines will be selected and displayed:

snippet a

snippet b

Notice that none of the lines with the tag directives are displayed.

28.6.2. Tag filtering

The previous section showed how to select tagged regions explicitly, but you can also use wildcards and exclusions. These expressions give you the ability to include or exclude tags in bulk. Even if you’re not including by tag, you can use the double wildcard (**) to ignore all lines with an include tag/end directive.

The modifiers you can use for filtering are as follows:

*

Select all tagged regions.

**

Select all the lines in the document except for lines with an include tag/end directive. Use this symbol if you want to include a file that has tag/end directives, but you want to ignore all those lines.

!

Negate the match.

The wildcards are applied first, then the exclusions are applied. Technically, the order you list them doesn’t matter, but it’s customary to list them in this order.

Here are some of the permutations that you can use:

  • ** — selects all the lines in the document; matches the default behavior of the include directive, except lines containing a tag directive; shorthand for **;* when used alone

  • * — selects all tagged regions in the document.

  • **;* — selects all the lines outside and inside of tagged regions, but not the lines containing a tag directive.

  • foo;!bar — selects regions tagged foo, but excludes any nested regions tagged bar.

  • *;!foo — selects all tagged regions, but excludes any regions tagged foo (nested or otherwise).

  • **;!foo — selects all the lines of the document except for regions tagged foo.

  • **;!* — selects only the regions of the document outside of tags (i.e., non-tagged regions).

There are also some shorthands if only exclusions are specified:

  • !foo — equivalent to **;!foo; an exclusion without an inclusion implicitly starts by selecting all the lines.

  • !* — equivalent to **;!*; only selects regions that are not tagged.

28.6.3. By line ranges

To include content by line range, assign a starting line number and an ending line number separated by a pair of dots (e.g., lines=1..5) to the lines attribute.

include::filename.txt[lines=5..10]

You can specify multiple ranges by separating each range by a comma. Since commas are normally used to separate individual attributes, you must quote the comma-separated list of ranges.

include::filename.txt[lines="1..10,15..20"]

To avoid having to quote the list of ranges, you can instead separate them using semi-colons.

include::filename.txt[lines=7;14..25;28..43]

If you don’t know the number of lines in the document, or you don’t want to couple the range to the length of the file, you can refer to the last line of the document using the value -1.

include::filename.txt[lines=12..-1]

28.7. Normalize Block Indentation

Source code snippets from external files are often padded with a leading block indent. This leading block indent is relevant in its original context. However, once inside the documentation, this leading block indent is no longer needed.

The attribute indent allows the leading block indent to be stripped and, optionally, a new block indent to be set for blocks with verbatim content (listing, literal, source, verse, etc.).

  • When indent is 0, the leading block indent is stripped

  • When indent is > 0, the leading block indent is first stripped, then the content is indented by the number of columns equal to this value.

If the tabsize attribute is set on the block or the document, tabs are also replaced with the number of spaces specified by that attribute, regardless of whether the indent attribute is set.

For example, this AsciiDoc source:

[source,ruby,indent=0]
----
    def names
      @name.split ' '
    end
----

Produces:

def names
  @name.split ' '
end

This AsciiDoc source:

[indent=2]
----
    def names
      @name.split ' '
    end
----

Produces:

  def names
    @name.split ' '
  end

28.8. Include Content from a URI

The include directive recognizes when the target is a URI and can include the content referenced by that URI.

Including content from a URI

This example demonstrates how to include an AsciiDoc file from a GitHub repo directly into your document.

include::https://raw.githubusercontent.com/asciidoctor/asciidoctor/master/README.adoc[]

For security reasons, this capability is not enabled by default. To allow content to be read from a URI, you must enable the URI read permission by:

  1. running Asciidoctor in SERVER mode or less and

  2. setting the allow-uri-read attribute securely (i.e., from the CLI or API).

Here’s an example that shows how to run Asciidoctor from the console so it can read content from a URI:

$ asciidoctor -a allow-uri-read filename.adoc

Remember that Asciidoctor executes in UNSAFE mode by default when run from the command line.

Here’s an example that shows how to run Asciidoctor from the API so it can read content from a URI:

Asciidoctor.convert_file 'filename.adoc', safe: :safe, attributes: { 'allow-uri-read' => '' }
Including content from sources outside your control carries certain risks, including the potential to introduce malicious behavior into your documentation. Because allow-uri-read is a potentially dangerous feature, it is forcefully disabled when the safe mode is SECURE or higher.
URI vs URL

URI stands for Uniform Resource Identifier. When we talk about a URI, we’re usually talking about a URL, or Uniform Resource Locator. A URL is simply a URI that points to a resource over a network, or web address.

As far as Asciidoctor is concerned, all URIs share the same restriction, whether or not it’s actually local or remote, or whether it points to a web address (http or https prefix), FTP address (ftp prefix), or some other addressing scheme.

The same restriction described in this section applies when embedding an image referenced from a URI, such as when data-uri is set or when converting to PDF using Asciidoctor PDF.

28.9. Caching URI Content

Reading content from a URI is obviously much slower than reading it from a local file.

Asciidoctor provides a way for the content read from a URI to be cached, which is highly recommended.

To enable the built-in cache, you must:

  1. Install the open-uri-cached gem.

  2. Set the cache-uri attribute in the document.

When these two conditions are satisfied, Asciidoctor caches content read from a URI according the to HTTP caching recommendations.

28.10. Include a File Multiple Times in the Same Document

A document can include the same file any number of times. The problem comes if there are IDs in the included file; the output document (HTML or DocBook) will then have duplicate IDs which will make it not well-formed. To fix this, you can reference a dynamic variable from the main document in the ID.

For example, let’s say you want to include the same subsection describing a bike chain in both the operation and maintenance chapters:

= Bike Manual

:chapter: operation
== Operation

include::fragment-chain.adoc[]

:chapter: maintenance
== Maintenance

include::fragment-chain.adoc[]

Write fragment-chain.adoc as:

[id='chain-{chapter}']
=== Chain

See xref:chain-{chapter}[].

The first time the fragment-chain.adoc file is included, the ID of the included section resolves to chain-operation. The second time the file included, the ID resolves to chain-maintenance.

In order for this to work, you must use the long-hand forms of both the ID assignment and the cross-reference. The single quotes around the variable name in the assignment are required to force variable substitution (aka interpolation).

28.11. Using an Include in a List Item

You can use the include directive to include the content of a list item from another file, but there are some things you need to be aware of.

Recall that the include directive must be defined on a line by itself. This presents a challenge with lists since each list item must begin with the list marker. We can solve this by using the built-in blank attribute to initiate the list item, then follow that line with the include directive to bring in the actual content.

Here’s an example of how to use the blank attribute and the include directive to define a list item, then include the primary text from another file:

* {blank}
include::item-text.adoc[]

This technique works well if you control the contents of the included file and can ensure it only contains adjacent lines of text. If a list item does not contain adjacent lines, the list may be terminated. So we need a bit more syntax.

If you can’t guarantee that all the included lines will be adjacent, you’ll want to tuck the include directive inside an open block. This keeps all the include lines together, enclosed inside the boundaries of the block. You then attach this block to the list item using a list continuation (i.e., +).

Here’s an example of how to include complex content from another file into a list item:

* {blank}
+
--
include::complex-list-item.adoc[]
--

See Dropping the principal text for another example of this technique.