class Asciidoctor::Document

Public: The Document class represents a parsed AsciiDoc document.

Document is the root node of a parsed AsciiDoc document. It provides an abstract syntax tree (AST) that represents the structure of the AsciiDoc document from which the Document object was parsed.

Although the constructor can be used to create an empty document object, more commonly, you’ll load the document object from AsciiDoc source using the primary API methods, {Asciidoctor.load} or {Asciidoctor.load_file}. When using one of these APIs, you almost always want to set the safe mode to :safe (or :unsafe) to enable all of Asciidoctor’s features.

Asciidoctor.load '= Hello, AsciiDoc!', safe: :safe
# => Asciidoctor::Document { doctype: "article", doctitle: "Hello, AsciiDoc!", blocks: 0 }

Instances of this class can be used to extract information from the document or alter its structure. As such, the Document object is most often used in extensions and by integrations.

The most basic usage of the Document object is to retrieve the document’s title.

source = '= Document Title'
document = Asciidoctor.load source, safe: :safe
document.doctitle
# => 'Document Title'

If the document has no title, the {Document#doctitle} method returns the title of the first section. If that check falls through, you can have the method return a fallback value (the value of the untitled-label attribute).

Asciidoctor.load('no doctitle', safe: :safe).doctitle use_fallback: true
# => "Untitled"

You can also use the Document object to access document attributes defined in the header, such as the author and doctype.

source = '= Document Title
Author Name
:doctype: book'
document = Asciidoctor.load source, safe: :safe
document.author
# => 'Author Name'
document.doctype
# => 'book'

You can retrieve arbitrary document attributes defined in the header using {Document#attr} or check for the existence of one using {Document#attr?}:

source = '= Asciidoctor
:uri-project: https://asciidoctor.org'
document = Asciidoctor.load source, safe: :safe
document.attr 'uri-project'
# => 'https://asciidoctor.org'
document.attr? 'icons'
# => false

Starting at the Document object, you can begin walking the document tree using the {Document#blocks} method:

source = 'paragraph contents

[sidebar]
sidebar contents'
doc = Asciidoctor.load source, safe: :safe
doc.blocks.map {|block| block.context }
# => [:paragraph, :sidebar]

You can discover block nodes at any depth in the tree using the {AbstractBlock#find_by} method.

source = '****
paragraph in sidebar
****'
doc = Asciidoctor.load source, safe: :safe
doc.find_by(context: :paragraph).map {|block| block.context }
# => [:paragraph]

Loading a document object is the first step in the conversion process. You can take the process to completion by calling the {Document#convert} method.

Constants

Author

Public: The Author class represents information about an author extracted from document attributes

Footnote
ImageReference

Attributes

backend[R]

Public: Get the cached value of the backend attribute for this document

base_dir[R]

Public: Get the String base directory for converting this document.

Defaults to directory of the source file. If the source is a string, defaults to the current directory.

catalog[R]

Public: Get the document catalog Hash

compat_mode[R]

Public: Get the Boolean AsciiDoc compatibility mode

enabling this attribute activates the following syntax changes:

* single quotes as constrained emphasis formatting marks
* single backticks parsed as inline literal, formatted as monospace
* single plus parsed as constrained, monospaced inline formatting
* double plus parsed as constrained, monospaced inline formatting
converter[R]

Public: Get the Converter associated with this document

counters[R]

Public: Get the Hash of document counters

doctype[R]

Public: Get the cached value of the doctype attribute for this document

extensions[R]

Public: Get the activated Extensions::Registry associated with this document.

header[R]

Public: Get the level-0 Section (i.e., doctitle). (Only stores the title, not the header attributes).

options[R]

Public: Get the Hash of resolved options used to initialize this Document

outfilesuffix[R]

Public: Get the outfilesuffix defined at the end of the header.

parent_document[R]

Public: Get a reference to the parent Document of this nested document.

path_resolver[R]

Public: Get/Set the PathResolver instance used to resolve paths in this Document.

reader[R]

Public: Get the Reader associated with this document

references[R]

Public: Get the document catalog Hash

safe[R]

Public A read-only integer value indicating the level of security that should be enforced while processing this document. The value must be set in the Document constructor using the :safe option.

A value of 0 (UNSAFE) disables any of the security features enforced by Asciidoctor (Ruby is still subject to its own restrictions).

A value of 1 (SAFE) closely parallels safe mode in AsciiDoc. In particular, it prevents access to files which reside outside of the parent directory of the source file and disables any macro other than the include directive.

A value of 10 (SERVER) disallows the document from setting attributes that would affect the conversion of the document, in addition to all the security features of SafeMode::SAFE. For instance, this level forbids changing the backend or source-highlighter using an attribute defined in the source document header. This is the most fundamental level of security for server deployments (hence the name).

A value of 20 (SECURE) disallows the document from attempting to read files from the file system and including the contents of them into the document, in addition to all the security features of SafeMode::SECURE. In particular, it disallows use of the include::[] directive and the embedding of binary content (data uri), stylesheets and JavaScripts referenced by the document. (Asciidoctor and trusted extensions may still be allowed to embed trusted content into the document).

Since Asciidoctor is aiming for wide adoption, 20 (SECURE) is the default value and is recommended for server deployments.

A value of 100 (PARANOID) is planned to disallow the use of passthrough macros and prevents the document from setting any known attributes in addition to all the security features of SafeMode::SECURE. Please note that this level is not currently implemented (and therefore not enforced)!

sourcemap[RW]

Public: Get or set the Boolean flag that indicates whether source map information should be tracked by the parser

syntax_highlighter[R]

Public: Get the SyntaxHighlighter associated with this document

Public Class Methods

new(data = nil, options = {}) click to toggle source

Public: Initialize a {Document} object.

data - The AsciiDoc source data as a String or String Array. (default: nil) options - A Hash of options to control processing (e.g., safe mode value (:safe), backend (:backend),

standalone enclosure (:standalone), custom attributes (:attributes)). (default: {})

Duplication of the options Hash is handled in the enclosing API.

Examples

data = File.read filename
doc = Asciidoctor::Document.new data
puts doc.convert
Calls superclass method Asciidoctor::AbstractBlock::new
# File lib/asciidoctor/document.rb, line 253
def initialize data = nil, options = {}
  super self, :document

  if (parent_doc = options.delete :parent)
    @parent_document = parent_doc
    options[:base_dir] ||= parent_doc.base_dir
    options[:catalog_assets] = true if parent_doc.options[:catalog_assets]
    options[:to_dir] = parent_doc.options[:to_dir] if parent_doc.options[:to_dir]
    @catalog = parent_doc.catalog.merge footnotes: []
    # QUESTION should we support setting attribute in parent document from nested document?
    @attribute_overrides = attr_overrides = (parent_doc.instance_variable_get :@attribute_overrides).merge parent_doc.attributes
    attr_overrides.delete 'compat-mode'
    parent_doctype = attr_overrides.delete 'doctype'
    attr_overrides.delete 'notitle'
    attr_overrides.delete 'showtitle'
    # QUESTION if toc is hard unset in parent document, should it be hard unset in nested document?
    attr_overrides.delete 'toc'
    @attributes['toc-placement'] = (attr_overrides.delete 'toc-placement') || 'auto'
    attr_overrides.delete 'toc-position'
    @safe = parent_doc.safe
    @attributes['compat-mode'] = '' if (@compat_mode = parent_doc.compat_mode)
    @outfilesuffix = parent_doc.outfilesuffix
    @sourcemap = parent_doc.sourcemap
    @timings = nil
    @path_resolver = parent_doc.path_resolver
    @converter = parent_doc.converter
    initialize_extensions = nil
    @extensions = parent_doc.extensions
    @syntax_highlighter = parent_doc.syntax_highlighter
  else
    @parent_document = nil
    @catalog = {
      ids: {}, # deprecated; kept for backwards compatibility with converters
      refs: {},
      footnotes: [],
      links: [],
      images: [],
      callouts: Callouts.new,
      includes: {},
    }
    # copy attributes map and normalize keys
    # attribute overrides are attributes that can only be set from the commandline
    # a direct assignment effectively makes the attribute a constant
    # a nil value or name with leading or trailing ! will result in the attribute being unassigned
    @attribute_overrides = attr_overrides = {}
    (options[:attributes] || {}).each do |key, val|
      if key.end_with? '@'
        if key.start_with? '!'
          key, val = (key.slice 1, key.length - 2), false
        elsif key.end_with? '!@'
          key, val = (key.slice 0, key.length - 2), false
        else
          key, val = key.chop, %(#{val}@)
        end
      elsif key.start_with? '!'
        key, val = (key.slice 1, key.length), val == '@' ? false : nil
      elsif key.end_with? '!'
        key, val = key.chop, val == '@' ? false : nil
      end
      attr_overrides[key.downcase] = val
    end
    if ::String === (to_file = options[:to_file])
      attr_overrides['outfilesuffix'] = Helpers.extname to_file
    end
    # safely resolve the safe mode from const, int or string
    if !(safe_mode = options[:safe])
      @safe = SafeMode::SECURE
    elsif ::Integer === safe_mode
      # be permissive in case API user wants to define new levels
      @safe = safe_mode
    else
      @safe = (SafeMode.value_for_name safe_mode) rescue SafeMode::SECURE
    end
    input_mtime = options.delete :input_mtime
    @compat_mode = attr_overrides.key? 'compat-mode'
    @sourcemap = options[:sourcemap]
    @timings = options.delete :timings
    @path_resolver = PathResolver.new
    initialize_extensions = (defined? ::Asciidoctor::Extensions) || (options.key? :extensions) ? ::Asciidoctor::Extensions : nil
    @extensions = nil # initialize further down if initialize_extensions is true
    options[:standalone] = options[:header_footer] if (options.key? :header_footer) && !(options.key? :standalone)
  end

  @parsed = @reftexts = @header = @header_attributes = nil
  @counters = {}
  @attributes_modified = ::Set.new
  @docinfo_processor_extensions = {}
  standalone = options[:standalone]
  (@options = options).freeze

  attrs = @attributes
  unless parent_doc
    attrs['attribute-undefined'] = Compliance.attribute_undefined
    attrs['attribute-missing'] = Compliance.attribute_missing
    attrs.update DEFAULT_ATTRIBUTES
    # TODO if lang attribute is set, @safe mode < SafeMode::SERVER, and !parent_doc,
    # load attributes from data/locale/attributes-<lang>.adoc
  end

  if standalone
    # sync embedded attribute with :standalone option value
    attr_overrides['embedded'] = nil
    attrs['copycss'] = ''
    attrs['iconfont-remote'] = ''
    attrs['stylesheet'] = ''
    attrs['webfonts'] = ''
  else
    # sync embedded attribute with :standalone option value
    attr_overrides['embedded'] = ''
    if (attr_overrides.key? 'showtitle') && (attr_overrides.keys & %w(notitle showtitle))[-1] == 'showtitle'
      attr_overrides['notitle'] = { nil => '', false => '@', '@' => false }[attr_overrides['showtitle']]
    elsif attr_overrides.key? 'notitle'
      attr_overrides['showtitle'] = { nil => '', false => '@', '@' => false }[attr_overrides['notitle']]
    else
      attrs['notitle'] = ''
    end
  end

  attr_overrides['asciidoctor'] = ''
  attr_overrides['asciidoctor-version'] = ::Asciidoctor::VERSION

  attr_overrides['safe-mode-name'] = (safe_mode_name = SafeMode.name_for_value @safe)
  attr_overrides[%(safe-mode-#{safe_mode_name})] = ''
  attr_overrides['safe-mode-level'] = @safe

  # the only way to set the max-include-depth attribute is via the API; default to 64 like AsciiDoc.py
  attr_overrides['max-include-depth'] ||= 64

  # the only way to set the allow-uri-read attribute is via the API; disabled by default
  attr_overrides['allow-uri-read'] ||= nil

  # remap legacy attribute names
  attr_overrides['sectnums'] = attr_overrides.delete 'numbered' if attr_overrides.key? 'numbered'
  attr_overrides['hardbreaks-option'] = attr_overrides.delete 'hardbreaks' if attr_overrides.key? 'hardbreaks'

  # If the base_dir option is specified, it overrides docdir and is used as the root for relative
  # paths. Otherwise, the base_dir is the directory of the source file (docdir), if set, otherwise
  # the current directory.
  if (base_dir_val = options[:base_dir])
    @base_dir = (attr_overrides['docdir'] = ::File.expand_path base_dir_val)
  elsif attr_overrides['docdir']
    @base_dir = attr_overrides['docdir']
  else
    #logger.warn 'setting base_dir is recommended when working with string documents' unless nested?
    @base_dir = attr_overrides['docdir'] = ::Dir.pwd
  end

  # allow common attributes backend and doctype to be set using options hash, coerce values to string
  if (backend_val = options[:backend])
    attr_overrides['backend'] = backend_val.to_s
  end

  if (doctype_val = options[:doctype])
    attr_overrides['doctype'] = doctype_val.to_s
  end

  if @safe >= SafeMode::SERVER
    # restrict document from setting copycss, source-highlighter and backend
    attr_overrides['copycss'] ||= nil
    attr_overrides['source-highlighter'] ||= nil
    attr_overrides['backend'] ||= DEFAULT_BACKEND
    # restrict document from seeing the docdir and trim docfile to relative path
    if !parent_doc && attr_overrides.key?('docfile')
      attr_overrides['docfile'] = attr_overrides['docfile'][(attr_overrides['docdir'].length + 1)..-1]
    end
    attr_overrides['docdir'] = ''
    attr_overrides['user-home'] ||= '.'
    if @safe >= SafeMode::SECURE
      attr_overrides['max-attribute-value-size'] = 4096 unless attr_overrides.key? 'max-attribute-value-size'
      # assign linkcss (preventing css embedding) unless explicitly disabled from the commandline or API
      #attr_overrides['linkcss'] = (attr_overrides.fetch 'linkcss', '') || nil
      attr_overrides['linkcss'] = '' unless attr_overrides.key? 'linkcss'
      # restrict document from enabling icons
      attr_overrides['icons'] ||= nil
    end
  else
    attr_overrides['user-home'] ||= USER_HOME
  end

  # the only way to set the max-attribute-value-size attribute is via the API; disabled by default
  @max_attribute_value_size = (size = (attr_overrides['max-attribute-value-size'] ||= nil)) ? size.to_i.abs : nil

  attr_overrides.delete_if do |key, val|
    if val
      # a value ending in @ allows document to override value
      if ::String === val && (val.end_with? '@')
        val, verdict = val.chop, true
      end
      attrs[key] = val
    else
      # a nil or false value both unset the attribute; only a nil value locks it
      attrs.delete key
      verdict = val == false
    end
    verdict
  end

  if parent_doc
    @backend = attrs['backend']
    # reset doctype unless it matches the default value
    unless (@doctype = attrs['doctype'] = parent_doctype) == DEFAULT_DOCTYPE
      update_doctype_attributes DEFAULT_DOCTYPE
    end

    # don't need to do the extra processing within our own document
    # FIXME line info isn't reported correctly within include files in nested document
    @reader = Reader.new data, options[:cursor]
    @source_location = @reader.cursor if @sourcemap

    # Now parse the lines in the reader into blocks
    # Eagerly parse (for now) since a subdocument is not a publicly accessible object
    Parser.parse @reader, self

    # should we call some sort of post-parse function?
    restore_attributes
    @parsed = true
  else
    # setup default backend and doctype
    @backend = nil
    if (initial_backend = attrs['backend'] || DEFAULT_BACKEND) == 'manpage'
      @doctype = attrs['doctype'] = attr_overrides['doctype'] = 'manpage'
    else
      @doctype = (attrs['doctype'] ||= DEFAULT_DOCTYPE)
    end
    update_backend_attributes initial_backend, true

    # dynamic intrinstic attribute values

    #attrs['indir'] = attrs['docdir']
    #attrs['infile'] = attrs['docfile']

    # fallback directories
    attrs['stylesdir'] ||= '.'
    attrs['iconsdir'] ||= %(#{attrs.fetch 'imagesdir', './images'}/icons)

    fill_datetime_attributes attrs, input_mtime

    if initialize_extensions
      if (ext_registry = options[:extension_registry])
        # QUESTION should we warn if the value type of this option is not a registry
        if Extensions::Registry === ext_registry || ((defined? ::AsciidoctorJ::Extensions::ExtensionRegistry) &&
            ::AsciidoctorJ::Extensions::ExtensionRegistry === ext_registry)
          @extensions = ext_registry.activate self
        end
      elsif (ext_block = options[:extensions]).nil?
        @extensions = Extensions::Registry.new.activate self unless Extensions.groups.empty?
      elsif ::Proc === ext_block
        @extensions = Extensions.create(&ext_block).activate self
      end
    end

    @reader = PreprocessorReader.new self, data, (Reader::Cursor.new attrs['docfile'], @base_dir), normalize: true
    @source_location = @reader.cursor if @sourcemap
  end
end

Public Instance Methods

<<(block) click to toggle source

Public: Append a content Block to this Document.

If the child block is a Section, assign an index to it.

block - The child Block to append to this parent Block

Returns The parent Block

Calls superclass method Asciidoctor::AbstractBlock#<<
# File lib/asciidoctor/document.rb, line 807
def << block
  assign_numeral block if block.context == :section
  super
end
attribute_locked?(name) click to toggle source

Public: Determine if the attribute has been locked by being assigned in document options

key - The attribute key to check

Returns true if the attribute is locked, false otherwise

# File lib/asciidoctor/document.rb, line 900
def attribute_locked?(name)
  @attribute_overrides.key?(name)
end
author() click to toggle source

Public: Convenience method to retrieve the document attribute ‘author’

returns the full name of the author as a String

# File lib/asciidoctor/document.rb, line 747
def author
  @attributes['author']
end
authors() click to toggle source

Public: Convenience method to retrieve the authors of this document as an Array of Author objects.

This method is backed by the author-related attributes on the document.

returns the authors of this document as an Array

# File lib/asciidoctor/document.rb, line 756
def authors
  if (attrs = @attributes).key? 'author'
    authors = [(Author.new attrs['author'], attrs['firstname'], attrs['middlename'], attrs['lastname'], attrs['authorinitials'], attrs['email'])]
    if (num_authors = attrs['authorcount'] || 0) > 1
      idx = 1
      while idx < num_authors
        idx += 1
        authors << (Author.new attrs[%(author_#{idx})], attrs[%(firstname_#{idx})], attrs[%(middlename_#{idx})], attrs[%(lastname_#{idx})], attrs[%(authorinitials_#{idx})], attrs[%(email_#{idx})])
      end
    end
    authors
  else
    []
  end
end
basebackend?(base) click to toggle source
# File lib/asciidoctor/document.rb, line 676
def basebackend? base
  @attributes['basebackend'] == base
end
callouts() click to toggle source
# File lib/asciidoctor/document.rb, line 650
def callouts
  @catalog[:callouts]
end
content() click to toggle source
Calls superclass method Asciidoctor::AbstractBlock#content
# File lib/asciidoctor/document.rb, line 1009
def content
  # NOTE per AsciiDoc-spec, remove the title before converting the body
  @attributes.delete('title')
  super
end
convert(opts = {}) click to toggle source

Public: Convert the AsciiDoc document using the templates loaded by the Converter. If a :template_dir is not specified, or a template is missing, the converter will fall back to using the appropriate built-in template.

# File lib/asciidoctor/document.rb, line 929
def convert opts = {}
  @timings.start :convert if @timings
  parse unless @parsed
  unless @safe >= SafeMode::SERVER || opts.empty?
    # QUESTION should we store these on the Document object?
    @attributes.delete 'outfile' unless (@attributes['outfile'] = opts['outfile'])
    @attributes.delete 'outdir' unless (@attributes['outdir'] = opts['outdir'])
  end

  # QUESTION should we add extensions that execute before conversion begins?

  if doctype == 'inline'
    if (block = @blocks[0] || @header)
      if block.content_model == :compound || block.content_model == :empty
        logger.warn 'no inline candidate; use the inline doctype to convert a single paragragh, verbatim, or raw block'
      else
        output = block.content
      end
    end
  else
    if opts.key? :standalone
      transform = opts[:standalone] ? 'document' : 'embedded'
    elsif opts.key? :header_footer
      transform = opts[:header_footer] ? 'document' : 'embedded'
    else
      transform = @options[:standalone] ? 'document' : 'embedded'
    end
    output = @converter.convert self, transform
  end

  unless @parent_document
    if (exts = @extensions) && exts.postprocessors?
      exts.postprocessors.each do |ext|
        output = ext.process_method[self, output]
      end
    end
  end

  @timings.record :convert if @timings
  output
end
Also aliased as: render
counter(name, seed = nil) click to toggle source

Public: Get the named counter and take the next number in the sequence.

name - the String name of the counter seed - the initial value as a String or Integer

returns the next number in the sequence for the specified counter

# File lib/asciidoctor/document.rb, line 567
def counter name, seed = nil
  return @parent_document.counter name, seed if @parent_document
  if ((locked = attribute_locked? name) && (curr_val = @counters[name])) || !(curr_val = @attributes[name]).nil_or_empty?
    next_val = @counters[name] = Helpers.nextval curr_val
  elsif seed
    next_val = @counters[name] = seed == seed.to_i.to_s ? seed.to_i : seed
  else
    next_val = @counters[name] = 1
  end
  @attributes[name] = next_val unless locked
  next_val
end
counter_increment(counter_name, block)

Deprecated: Map old counter_increment method to increment_counter for backwards compatibility

delete_attribute(name) click to toggle source

Public: Delete the specified attribute from the document if the name is not locked

If the attribute is locked, false is returned. Otherwise, the attribute is deleted.

name - the String attribute name

returns true if the attribute was deleted, false if it was not because it’s locked

# File lib/asciidoctor/document.rb, line 885
def delete_attribute(name)
  if attribute_locked?(name)
    false
  else
    @attributes.delete(name)
    @attributes_modified << name
    true
  end
end
docinfo(location = :head, suffix = nil) click to toggle source

Public: Read the docinfo file(s) for inclusion in the document template

If the docinfo1 attribute is set, read the docinfo.ext file. If the docinfo attribute is set, read the doc-name.docinfo.ext file. If the docinfo2 attribute is set, read both files in that order.

location - The Symbol location of the docinfo (e.g., :head, :footer, etc). (default: :head) suffix - The suffix of the docinfo file(s). If not set, the extension

will be set to the outfilesuffix. (default: nil)

returns The contents of the docinfo file(s) or empty string if no files are found or the safe mode is secure or greater.

# File lib/asciidoctor/document.rb, line 1027
def docinfo location = :head, suffix = nil
  if safe < SafeMode::SECURE
    qualifier = %(-#{location}) unless location == :head
    suffix ||= @outfilesuffix

    if (docinfo = @attributes['docinfo']).nil_or_empty?
      if @attributes.key? 'docinfo2'
        docinfo = ['private', 'shared']
      elsif @attributes.key? 'docinfo1'
        docinfo = ['shared']
      else
        docinfo = docinfo ? ['private'] : nil
      end
    else
      docinfo = docinfo.split(',').map {|it| it.strip }
    end

    if docinfo
      content = []
      docinfo_file, docinfo_dir, docinfo_subs = %(docinfo#{qualifier}#{suffix}), @attributes['docinfodir'], resolve_docinfo_subs
      unless (docinfo & ['shared', %(shared-#{location})]).empty?
        docinfo_path = normalize_system_path docinfo_file, docinfo_dir
        # NOTE normalizing the lines is essential if we're performing substitutions
        if (shared_docinfo = read_asset docinfo_path, normalize: true)
          content << (apply_subs shared_docinfo, docinfo_subs)
        end
      end

      unless @attributes['docname'].nil_or_empty? || (docinfo & ['private', %(private-#{location})]).empty?
        docinfo_path = normalize_system_path %(#{@attributes['docname']}-#{docinfo_file}), docinfo_dir
        # NOTE normalizing the lines is essential if we're performing substitutions
        if (private_docinfo = read_asset docinfo_path, normalize: true)
          content << (apply_subs private_docinfo, docinfo_subs)
        end
      end
    end
  end

  # TODO allow document to control whether extension docinfo is contributed
  if @extensions && (docinfo_processors? location)
    ((content || []).concat @docinfo_processor_extensions[location].map {|ext| ext.process_method[self] }.compact).join LF
  elsif content
    content.join LF
  else
    ''
  end
end
docinfo_processors?(location = :head) click to toggle source
# File lib/asciidoctor/document.rb, line 1075
def docinfo_processors?(location = :head)
  if @docinfo_processor_extensions.key?(location)
    # false means we already performed a lookup and didn't find any
    @docinfo_processor_extensions[location] != false
  elsif @extensions && @document.extensions.docinfo_processors?(location)
    !!(@docinfo_processor_extensions[location] = @document.extensions.docinfo_processors(location))
  else
    @docinfo_processor_extensions[location] = false
  end
end
doctitle(opts = {}) click to toggle source

Public: Resolves the primary title for the document

Searches the locations to find the first non-empty value:

* document-level attribute named title
* header title (known as the document title)
* title of the first section
* document-level attribute named untitled-label (if :use_fallback option is set)

If no value can be resolved, nil is returned.

If the :partition attribute is specified, the value is parsed into an Document::Title object. If the :sanitize attribute is specified, XML elements are removed from the value.

TODO separate sanitization by type (:cdata for HTML/XML, :plain_text for non-SGML, false for none)

Returns the resolved title as a [Title] if the :partition option is passed or a [String] if not or nil if no value can be resolved.

# File lib/asciidoctor/document.rb, line 721
def doctitle opts = {}
  unless (val = @attributes['title'])
    if (sect = first_section)
      val = sect.title
    elsif !(opts[:use_fallback] && (val = @attributes['untitled-label']))
      return
    end
  end

  if (separator = opts[:partition])
    Title.new val, opts.merge({ separator: (separator == true ? @attributes['title-separator'] : separator) })
  elsif opts[:sanitize] && val.include?('<')
    val.gsub(XmlSanitizeRx, '').squeeze(' ').strip
  else
    val
  end
end
Also aliased as: name
embedded?() click to toggle source
# File lib/asciidoctor/document.rb, line 658
def embedded?
  @attributes.key? 'embedded'
end
extensions?() click to toggle source
# File lib/asciidoctor/document.rb, line 662
def extensions?
  @extensions ? true : false
end
finalize_header(unrooted_attributes, header_valid = true) click to toggle source

Internal: Called by the parser after parsing the header and before parsing the body, even if no header is found.

# File lib/asciidoctor/document.rb, line 817
def finalize_header unrooted_attributes, header_valid = true
  clear_playback_attributes unrooted_attributes
  save_attributes
  unrooted_attributes['invalid-header'] = true unless header_valid
  unrooted_attributes
end
first_section() click to toggle source
# File lib/asciidoctor/document.rb, line 791
def first_section
  @header || @blocks.find {|e| e.context == :section }
end
footnotes() click to toggle source
# File lib/asciidoctor/document.rb, line 646
def footnotes
  @catalog[:footnotes]
end
footnotes?() click to toggle source
# File lib/asciidoctor/document.rb, line 642
def footnotes?
  @catalog[:footnotes].empty? ? false : true
end
has_header?()
Alias for: header?
header?() click to toggle source
# File lib/asciidoctor/document.rb, line 795
def header?
  @header ? true : false
end
Also aliased as: has_header?
increment_and_store_counter(counter_name, block) click to toggle source

Public: Increment the specified counter and store it in the block’s attributes

counter_name - the String name of the counter attribute block - the Block on which to save the counter

returns the next number in the sequence for the specified counter

# File lib/asciidoctor/document.rb, line 586
def increment_and_store_counter counter_name, block
  ((AttributeEntry.new counter_name, (counter counter_name)).save_to block.attributes).value
end
Also aliased as: counter_increment
name(opts = {})
Alias for: doctitle
nested?() click to toggle source
# File lib/asciidoctor/document.rb, line 654
def nested?
  @parent_document ? true : false
end
nofooter() click to toggle source
# File lib/asciidoctor/document.rb, line 787
def nofooter
  @attributes.key? 'nofooter'
end
noheader() click to toggle source
# File lib/asciidoctor/document.rb, line 783
def noheader
  @attributes.key? 'noheader'
end
notitle() click to toggle source
# File lib/asciidoctor/document.rb, line 779
def notitle
  @attributes.key? 'notitle'
end
parse(data = nil) click to toggle source

Public: Parse the AsciiDoc source stored in the {Reader} into an abstract syntax tree.

If the data parameter is not nil, create a new {PreprocessorReader} and assigned it to the reader property of this object. Otherwise, continue with the reader that was created in {#initialize}. Pass the reader to {Parser.parse} to parse the source data into an abstract syntax tree.

If parsing has already been performed, this method returns without performing any processing.

data - The optional replacement AsciiDoc source data as a String or String Array. (default: nil)

Returns this [Document]

# File lib/asciidoctor/document.rb, line 520
def parse data = nil
  if @parsed
    self
  else
    doc = self
    # create reader if data is provided (used when data is not known at the time the Document object is created)
    if data
      @reader = PreprocessorReader.new doc, data, (Reader::Cursor.new @attributes['docfile'], @base_dir), normalize: true
      @source_location = @reader.cursor if @sourcemap
    end

    if (exts = @parent_document ? nil : @extensions) && exts.preprocessors?
      exts.preprocessors.each do |ext|
        @reader = ext.process_method[doc, @reader] || @reader
      end
    end

    # Now parse the lines in the reader into blocks
    Parser.parse @reader, doc, header_only: @options[:parse_header_only]

    # should we call sort of post-parse function?
    restore_attributes

    if exts && exts.tree_processors?
      exts.tree_processors.each do |ext|
        if (result = ext.process_method[doc]) && Document === result && result != doc
          doc = result
        end
      end
    end

    @parsed = true
    doc
  end
end
parsed?() click to toggle source

Public: Returns whether the source lines of the document have been parsed.

# File lib/asciidoctor/document.rb, line 557
def parsed?
  @parsed
end
playback_attributes(block_attributes) click to toggle source

Public: Replay attribute assignments at the block level

# File lib/asciidoctor/document.rb, line 825
def playback_attributes(block_attributes)
  if block_attributes.key? :attribute_entries
    block_attributes[:attribute_entries].each do |entry|
      name = entry.name
      if entry.negate
        @attributes.delete name
        @compat_mode = false if name == 'compat-mode'
      else
        @attributes[name] = entry.value
        @compat_mode = true if name == 'compat-mode'
      end
    end
  end
end
register(type, value) click to toggle source

Public: Register a reference in the document catalog

# File lib/asciidoctor/document.rb, line 593
def register type, value
  case type
  when :ids # deprecated
    register :refs, [(id = value[0]), (Inline.new self, :anchor, value[1], type: :ref, id: id)]
  when :refs
    @catalog[:refs][value[0]] ||= (ref = value[1])
    ref
  when :footnotes
    @catalog[type] << value
  else
    @catalog[type] << (type == :images ? (ImageReference.new value, @attributes['imagesdir']) : value) if @options[:catalog_assets]
  end
end
render(opts = {})

Deprecated: Use {Document#convert} instead.

Alias for: convert
resolve_id(text) click to toggle source

Public: Scan registered references and return the ID of the first reference that matches the specified reference text.

text - The String reference text to compare to the converted reference text of each registered reference.

Returns the String ID of the first reference with matching reference text or nothing if no reference is found.

# File lib/asciidoctor/document.rb, line 612
def resolve_id text
  if @reftexts
    @reftexts[text]
  elsif @parsed
    # @reftexts is set eagerly to prevent nested lazy init
    (@reftexts = {}).tap {|accum| @catalog[:refs].each {|id, ref| accum[ref.xreftext] ||= id } }[text]
  else
    resolved_id = nil
    # @reftexts is set eagerly to prevent nested lazy init
    @reftexts = accum = {}
    @catalog[:refs].each do |id, ref|
      # NOTE short-circuit early since we're throwing away this table anyway
      if (xreftext = ref.xreftext) == text
        resolved_id = id
        break
      end
      accum[xreftext] ||= id
    end
    @reftexts = nil
    resolved_id
  end
end
restore_attributes() click to toggle source

Public: Restore the attributes to the previously saved state (attributes in header)

# File lib/asciidoctor/document.rb, line 841
def restore_attributes
  @catalog[:callouts].rewind unless @parent_document
  @attributes.replace @header_attributes
end
revdate() click to toggle source

Public: Convenience method to retrieve the document attribute ‘revdate’

returns the date of last revision for the document as a String

# File lib/asciidoctor/document.rb, line 775
def revdate
  @attributes['revdate']
end
sections?() click to toggle source

Public: Check whether this Document has any child Section objects.

Returns A [Boolean] to indicate whether this Document has child Section objects

# File lib/asciidoctor/document.rb, line 638
def sections?
  @next_section_index > 0
end
set_attribute(name, value = '') click to toggle source

Public: Set the specified attribute on the document if the name is not locked

If the attribute is locked, false is returned. Otherwise, the value is assigned to the attribute name after first performing attribute substitutions on the value. If the attribute name is ‘backend’ or ‘doctype’, then the value of backend-related attributes are updated.

name - the String attribute name value - the String attribute value; must not be nil (optional, default: ”)

Returns the substituted value if the attribute was set or nil if it was not because it’s locked.

# File lib/asciidoctor/document.rb, line 857
def set_attribute name, value = ''
  unless attribute_locked? name
    value = apply_attribute_value_subs value unless value.empty?
    # NOTE if @header_attributes is set, we're beyond the document header
    if @header_attributes
      @attributes[name] = value
    else
      case name
      when 'backend'
        update_backend_attributes value, (@attributes_modified.delete? 'htmlsyntax') && value == @backend
      when 'doctype'
        update_doctype_attributes value
      else
        @attributes[name] = value
      end
      @attributes_modified << name
    end
    value
  end
end
set_header_attribute(name, value = '', overwrite = true) click to toggle source

Public: Assign a value to the specified attribute in the document header.

The assignment will be visible when the header attributes are restored, typically between processor phases (e.g., between parse and convert).

name - The String attribute name to assign value - The Object value to assign to the attribute (default: ”) overwrite - A Boolean indicating whether to assign the attribute

if already present in the attributes Hash (default: true)

Returns a [Boolean] indicating whether the assignment was performed

# File lib/asciidoctor/document.rb, line 915
def set_header_attribute name, value = '', overwrite = true
  attrs = @header_attributes || @attributes
  if overwrite == false && (attrs.key? name)
    false
  else
    attrs[name] = value
    true
  end
end
source() click to toggle source

Make the raw source for the Document available.

# File lib/asciidoctor/document.rb, line 667
def source
  @reader.source if @reader
end
source_lines() click to toggle source

Make the raw source lines for the Document available.

# File lib/asciidoctor/document.rb, line 672
def source_lines
  @reader.source_lines if @reader
end
title() click to toggle source

Public: Return the doctitle as a String

Returns the resolved doctitle as a [String] or nil if a doctitle cannot be resolved

# File lib/asciidoctor/document.rb, line 683
def title
  doctitle
end
title=(title) click to toggle source

Public: Set the title on the document header

Set the title of the document header to the specified value. If the header does not exist, it is first created.

title - the String title to assign as the title of the document header

Returns the specified [String] title

# File lib/asciidoctor/document.rb, line 695
def title= title
  unless (sect = @header)
    (sect = (@header = Section.new self, 0)).sectname = 'header'
  end
  sect.title = title
end
to_s() click to toggle source
# File lib/asciidoctor/document.rb, line 1086
def to_s
  %(#<#{self.class}@#{object_id} {doctype: #{doctype.inspect}, doctitle: #{(@header && @header.title).inspect}, blocks: #{@blocks.size}}>)
end
write(output, target) click to toggle source

Public: Write the output to the specified file

If the converter responds to :write, delegate the work of writing the output to that method. Otherwise, write the output to the specified file. In the latter case, this method ensures the output has a trailing newline if the target responds to write and the output is not empty.

output - The output to write. Unless the converter responds to write, this

object is expected to be a String.

target - The file to write, either a File object or a String path.

Returns nothing

# File lib/asciidoctor/document.rb, line 986
def write output, target
  @timings.start :write if @timings
  if Writer === @converter
    @converter.write output, target
  else
    if target.respond_to? :write
      # QUESTION should we set encoding using target.set_encoding?
      unless output.nil_or_empty?
        target.write output.chomp
        # ensure there's a trailing endline
        target.write LF
      end
    else
      ::File.write target, output, mode: FILE_WRITE_MODE
    end
    if @backend == 'manpage' && ::String === target && (@converter.class.respond_to? :write_alternate_pages)
      @converter.class.write_alternate_pages @attributes['mannames'], @attributes['manvolnum'], target
    end
  end
  @timings.record :write if @timings
  nil
end
xreftext(xrefstyle = nil) click to toggle source
# File lib/asciidoctor/document.rb, line 740
def xreftext xrefstyle = nil
  (val = reftext) && !val.empty? ? val : title
end