class Asciidoctor::Document - asciidoctor-1.5.6.2 Documentation

# class Asciidoctor::Document

Public: Methods for parsing and converting AsciiDoc documents.

There are several strategies for getting the title of the document:

doctitle - value of title attribute, if assigned and non-empty,

    otherwise title of first section in document, if present
    otherwise nil

name - an alias of doctitle title - value of the title attribute, or nil if not present first_section.title - title of first section in document, if present header.title - title of section level 0

Keep in mind that you'll want to honor these document settings:

notitle - The h1 heading should not be shown noheader - The header block (h1 heading, author, revision info) should not be shown nofooter - the footer block should not be shown

### Constants

Footnote

### 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.

callouts[R]

Public: Get the Hash of callouts

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

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.

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 value disallows changing the backend or the source-highlighter using an attribute defined in the source document. This is the most fundamental level of security for server-side 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-side 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

### 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),

    header/footer toggle (:header_footer), 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 179
    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
        @catalog = parent_doc.catalog.inject({}) do |accum, (key, table)|
          accum[key] = (key == :footnotes ? [] : table)
          accum
        end
        @callouts = parent_doc.callouts
        # QUESTION should we support setting attribute in parent document from nested document?
        # NOTE we must dup or else all the assignments to the overrides clobbers the real attributes
        @attribute_overrides = attr_overrides = parent_doc.attributes.dup
        parent_doctype = attr_overrides.delete 'doctype'
        attr_overrides.delete 'compat-mode'
        attr_overrides.delete 'toc'
        attr_overrides.delete 'toc-placement'
        attr_overrides.delete 'toc-position'
        @safe = parent_doc.safe
        @attributes['compat-mode'] = '' if (@compat_mode = parent_doc.compat_mode)
        @sourcemap = parent_doc.sourcemap
        @converter = parent_doc.converter
        initialize_extensions = false
        @extensions = parent_doc.extensions
      else
        @parent_document = nil
        @catalog = {
          :ids => {},
          :refs => {},
          :footnotes => [],
          :links => [],
          :images => [],
          :indexterms => [],
          :includes => ::Set.new,
        }
        @callouts = Callouts.new
        # 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
        attr_overrides = {}
        (options[:attributes] || {}).each do |key, value|
          if key.start_with? '!'
            key = key[1..-1]
            value = nil
          elsif key.end_with? '!'
            key = key.chop
            value = nil
          end
          attr_overrides[key.downcase] = value
        end
        @attribute_overrides = attr_overrides
        # 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
          # NOTE: not using infix rescue for performance reasons, see https://github.com/jruby/jruby/issues/1816
          begin
            @safe = SafeMode.value_for_name safe_mode.to_s
          rescue
            @safe = SafeMode::SECURE
          end
        end
        @compat_mode = attr_overrides.key? 'compat-mode'
        @sourcemap = options[:sourcemap]
        @converter = nil
        initialize_extensions = defined? ::Asciidoctor::Extensions
        @extensions = nil # initialize furthur down
      end

      @parsed = false
      @header = nil
      @counters = {}
      @attributes_modified = ::Set.new
      @docinfo_processor_extensions = {}
      header_footer = (options[:header_footer] ||= false)
      (@options = options).freeze

      attrs = @attributes
      #attrs['encoding'] = 'UTF-8'
      attrs['sectids'] = ''
      attrs['toc-placement'] = 'auto'
      if header_footer
        attrs['copycss'] = ''
        # sync embedded attribute with :header_footer option value
        attr_overrides['embedded'] = nil
      else
        attrs['notitle'] = ''
        # sync embedded attribute with :header_footer option value
        attr_overrides['embedded'] = ''
      end
      attrs['stylesheet'] = ''
      attrs['webfonts'] = ''
      attrs['prewrap'] = ''
      attrs['attribute-undefined'] = Compliance.attribute_undefined
      attrs['attribute-missing'] = Compliance.attribute_missing
      attrs['iconfont-remote'] = ''

      # language strings
      # TODO load these based on language settings
      attrs['caution-caption'] = 'Caution'
      attrs['important-caption'] = 'Important'
      attrs['note-caption'] = 'Note'
      attrs['tip-caption'] = 'Tip'
      attrs['warning-caption'] = 'Warning'
      attrs['example-caption'] = 'Example'
      attrs['figure-caption'] = 'Figure'
      #attrs['listing-caption'] = 'Listing'
      attrs['table-caption'] = 'Table'
      attrs['toc-title'] = 'Table of Contents'
      #attrs['preface-title'] = 'Preface'
      attrs['manname-title'] = 'NAME'
      attrs['section-refsig'] = 'Section'
      #attrs['part-refsig'] = 'Part'
      attrs['chapter-refsig'] = 'Chapter'
      attrs['appendix-caption'] = attrs['appendix-refsig'] = 'Appendix'
      attrs['untitled-label'] = 'Untitled'
      attrs['version-label'] = 'Version'
      attrs['last-update-label'] = 'Last updated'

      attr_overrides['asciidoctor'] = ''
      attr_overrides['asciidoctor-version'] = 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 Python
      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

      attr_overrides['user-home'] = USER_HOME

      # legacy support for numbered attribute
      attr_overrides['sectnums'] = attr_overrides.delete 'numbered' if attr_overrides.key? 'numbered'

      # 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
        #warn 'asciidoctor: WARNING: 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'] = %Q(#{backend_val})
      end

      if (doctype_val = options[:doctype])
        attr_overrides['doctype'] = %Q(#{doctype_val})
      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
          # effectively the same has "has key 'linkcss' and value == nil"
          unless attr_overrides.fetch('linkcss', '').nil?
            attr_overrides['linkcss'] = ''
          end
          # restrict document from enabling icons
          attr_overrides['icons'] ||= nil
        end
      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|
        verdict = false
        # a nil value undefines the attribute
        if val.nil?
          attrs.delete(key)
        else
          # a value ending in @ indicates this attribute does not override
          # an attribute with the same key in the document souce
          if ::String === val && (val.end_with? '@')
            val = val.chop
            verdict = true
          end
          attrs[key] = val
        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]

        # 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 (attrs['backend'] ||= DEFAULT_BACKEND) == 'manpage'
          @doctype = attrs['doctype'] = attr_overrides['doctype'] = 'manpage'
        else
          @doctype = (attrs['doctype'] ||= DEFAULT_DOCTYPE)
        end
        update_backend_attributes attrs['backend'], true

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

        # dynamic intrinstic attribute values

        # See https://reproducible-builds.org/specs/source-date-epoch/
        # NOTE Opal can't call key? on ENV
        now = ::ENV['SOURCE_DATE_EPOCH'] ? ::Time.at(Integer ::ENV['SOURCE_DATE_EPOCH']).utc : ::Time.now
        if (localdate = attrs['localdate'])
          localyear = (attrs['localyear'] ||= ((localdate.index '-') == 4 ? (localdate.slice 0, 4) : nil))
        else
          localdate = attrs['localdate'] = (now.strftime '%Y-%m-%d')
          localyear = (attrs['localyear'] ||= now.year.to_s)
        end
        localtime = (attrs['localtime'] ||= begin
            now.strftime '%H:%M:%S %Z'
          rescue # Asciidoctor.js fails if timezone string has characters outside basic Latin (see asciidoctor.js#23)
            now.strftime '%H:%M:%S %z'
          end)
        attrs['localdatetime'] ||= %Q(#{localdate} #{localtime})

        # docdate, doctime and docdatetime should default to
        # localdate, localtime and localdatetime if not otherwise set
        attrs['docdate'] ||= localdate
        attrs['docyear'] ||= localyear
        attrs['doctime'] ||= localtime
        attrs['docdatetime'] ||= %Q(#{localdate} #{localtime})

        # fallback directories
        attrs['stylesdir'] ||= '.'
        attrs['iconsdir'] ||= ::File.join(attrs.fetch('imagesdir', './images'), 'icons')

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

        @reader = PreprocessorReader.new self, data, (Reader::Cursor.new attrs['docfile'], @base_dir), :normalize => true
      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 705
    def << block
      enumerate_section block if block.context == :section
      super
    end

apply_attribute_value_subs(value) click to toggle source

Internal: Apply substitutions to the attribute value

If the value is an inline passthrough macro (e.g., pass:), apply the substitutions defined in <subs> to the value, or leave the value unmodified if no substitutions are specified. If the value is not an inline passthrough macro, apply header substitutions to the value.

value - The String attribute value on which to perform substitutions

Returns The String value with substitutions performed

    # File lib/asciidoctor/document.rb, line 897
    def apply_attribute_value_subs value
      if AttributeEntryPassMacroRx =~ value
        $1 ? (apply_subs $2, (resolve_pass_subs $1)) : $2
      else
        apply_header_subs value
      end
    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 883
    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 666
    def author
      @attributes['author']
    end

basebackend?(base) click to toggle source

    # File lib/asciidoctor/document.rb, line 607
    def basebackend? base
      @attributes['basebackend'] == base
    end

clear_playback_attributes(attributes) click to toggle source

Internal: Delete any attributes stored for playback

    # File lib/asciidoctor/document.rb, line 808
    def clear_playback_attributes(attributes)
      attributes.delete(:attribute_entries)
    end

content() click to toggle source

def convert_to target, opts = {}

    start = ::Time.now.to_f if (monitor = opts[:monitor])
    output = (r = converter opts).convert
    monitor[:convert] = ::Time.now.to_f - start if monitor

    unless target.respond_to? :write
      @attributes['outfile'] = target = ::File.expand_path target
      @attributes['outdir'] = ::File.dirname target
    end

    start = ::Time.now.to_f if monitor
    r.write output, target
    monitor[:write] = ::Time.now.to_f - start if monitor

    output

end

Calls superclass method Asciidoctor::AbstractBlock#content

    # File lib/asciidoctor/document.rb, line 1114
    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 1037
    def convert opts = {}
      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])
          if block.content_model == :compound || block.content_model == :empty
            warn %Q(asciidoctor: WARNING: no inline candidate; use the inline doctype to convert a single paragragh, verbatim, or raw block)
          else
            output = block.content
          end
        end
      else
        transform = ((opts.key? :header_footer) ? opts[:header_footer] : @options[:header_footer]) ? 'document' : 'embedded'
        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

      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 516
    def counter name, seed = nil
      return @parent_document.counter name, seed if @parent_document
      if (attr_seed = !(attr_val = @attributes[name]).nil_or_empty?) && (@counters.key? name)
        @attributes[name] = @counters[name] = (nextval attr_val)
      elsif seed
        @attributes[name] = @counters[name] = (seed == seed.to_i.to_s ? seed.to_i : seed)
      else
        @attributes[name] = @counters[name] = nextval(attr_seed ? attr_val : 0)
      end
    end

counter_increment(counter_name, block)

Deprecated: Map old #counter_increment method to increment_counter for backwards compatibility

Alias for: increment_and_store_counter
create_converter() click to toggle source

TODO document me

    # File lib/asciidoctor/document.rb, line 1005
    def create_converter
      converter_opts = {}
      converter_opts[:htmlsyntax] = @attributes['htmlsyntax']
      template_dirs = if (template_dir = @options[:template_dir])
        converter_opts[:template_dirs] = [template_dir]
      elsif (template_dirs = @options[:template_dirs])
        converter_opts[:template_dirs] = template_dirs
      end
      if template_dirs
        converter_opts[:template_cache] = @options.fetch :template_cache, true
        converter_opts[:template_engine] = @options[:template_engine]
        converter_opts[:template_engine_options] = @options[:template_engine_options]
        converter_opts[:eruby] = @options[:eruby]
        converter_opts[:safe] = @safe
      end
      if (converter = @options[:converter])
        converter_factory = Converter::Factory.new ::Hash[backend, converter]
      else
        converter_factory = Converter::Factory.default false
      end
      # QUESTION should we honor the convert_opts?
      # QUESTION should we pass through all options and attributes too?
      #converter_opts.update opts
      converter_factory.create backend, converter_opts
    end

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 868
    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 1132
    def docinfo location = :head, suffix = nil
      if safe >= SafeMode::SECURE
        ''
      else
        content = []
        qualifier = %Q(-#{location}) unless location == :head
        suffix = @outfilesuffix unless suffix

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

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

        # TODO allow document to control whether extension docinfo is contributed
        if @extensions && (docinfo_processors? location)
          content += @docinfo_processor_extensions[location].map {|ext| ext.process_method[self] }.compact
        end

        content * LF
      end
    end

docinfo_processors?(location = :head) click to toggle source

    # File lib/asciidoctor/document.rb, line 1191
    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 642
    def doctitle opts = {}
      if !(val = @attributes['title'].nil_or_empty?)
        val = title
      elsif (sect = first_section)
        val = sect.title
      elsif opts[:use_fallback] && (val = @attributes['untitled-label'])
        # use val set in condition
      else
        return
      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 589
    def embedded?
      @attributes.key? 'embedded'
    end

extensions?() click to toggle source

    # File lib/asciidoctor/document.rb, line 593
    def extensions?
      @extensions ? true : false
    end

finalize_header(unrooted_attributes, header_valid = true) click to toggle source

Internal: called after the header has been parsed and before the content will be parsed.

    # File lib/asciidoctor/document.rb, line 715
    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 689
    def first_section
      @header || @blocks.find {|e| e.context == :section }
    end

footnotes() click to toggle source

    # File lib/asciidoctor/document.rb, line 581
    def footnotes
      @catalog[:footnotes]
    end

footnotes?() click to toggle source

    # File lib/asciidoctor/document.rb, line 577
    def footnotes?
      @catalog[:footnotes].empty? ? false : true
    end

has_header?() click to toggle source

    # File lib/asciidoctor/document.rb, line 693
    def has_header?
      @header ? true : false
    end

Also aliased as: header?
header?()
Alias for: 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 533
    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 585
    def nested?
      @parent_document ? true : false
    end

nextval(current) click to toggle source

Internal: Get the next value in the sequence.

Handles both integer and character sequences.

current - the value to increment as a String or Integer

returns the next value in the sequence according to the current value's type

    # File lib/asciidoctor/document.rb, line 546
    def nextval(current)
      if ::Integer === current
        current + 1
      else
        intval = current.to_i
        if intval.to_s != current.to_s
          (current[0].ord + 1).chr
        else
          intval + 1
        end
      end
    end

nofooter() click to toggle source

    # File lib/asciidoctor/document.rb, line 685
    def nofooter
      @attributes.key? 'nofooter'
    end

noheader() click to toggle source

    # File lib/asciidoctor/document.rb, line 681
    def noheader
      @attributes.key? 'noheader'
    end

notitle() click to toggle source

    # File lib/asciidoctor/document.rb, line 677
    def notitle
      !@attributes.key?('showtitle') && @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 475
    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
        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

playback_attributes(block_attributes) click to toggle source

Internal: Replay attribute assignments at the block level

    # File lib/asciidoctor/document.rb, line 813
    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

    # File lib/asciidoctor/document.rb, line 559
    def register type, value
      case type
      when :ids # deprecated
        id, reftext = value
        @catalog[:ids][id] ||= reftext || ('[' + id + ']')
      when :refs
        id, ref, reftext = value
        unless (refs = @catalog[:refs]).key? id
          @catalog[:ids][id] = reftext || ('[' + id + ']')
          refs[id] = ref
        end
      when :footnotes, :indexterms
        @catalog[type] << value
      else
        @catalog[type] << value if @options[:catalog_assets]
      end
    end

render(opts = {})

Alias render to convert to maintain backwards compatibility

Alias for: convert
resolve_docinfo_subs() click to toggle source

Internal: Resolve the list of comma-delimited subs to apply to docinfo files.

Resolve the list of substitutions from the value of the docinfosubs document attribute, if specified. Otherwise, return an Array containing the Symbol :attributes.

Returns an [Array] of substitution [Symbol]s

    # File lib/asciidoctor/document.rb, line 1187
    def resolve_docinfo_subs
      (@attributes.key? 'docinfosubs') ? (resolve_subs @attributes['docinfosubs'], :block, nil, 'docinfo') : [:attributes]
    end

restore_attributes() click to toggle source

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

    # File lib/asciidoctor/document.rb, line 801
    def restore_attributes
      @callouts.rewind unless @parent_document
      # QUESTION shouldn't this be a dup in case we convert again?
      @attributes = @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 673
    def revdate
      @attributes['revdate']
    end

save_attributes() click to toggle source

Internal: Branch the attributes so that the original state can be restored at a future time.

    # File lib/asciidoctor/document.rb, line 724
    def save_attributes
      # enable toc and sectnums (i.e., numbered) by default in DocBook backend
      # NOTE the attributes_modified should go away once we have a proper attribute storage & tracking facility
      if (attrs = @attributes)['basebackend'] == 'docbook'
        attrs['toc'] = '' unless attribute_locked?('toc') || @attributes_modified.include?('toc')
        attrs['sectnums'] = '' unless attribute_locked?('sectnums') || @attributes_modified.include?('sectnums')
      end

      unless attrs.key?('doctitle') || !(val = doctitle)
        attrs['doctitle'] = val
      end

      # css-signature cannot be updated after header attributes are processed
      @id = attrs['css-signature'] unless @id

      toc_position_val = if (toc_val = (attrs.delete('toc2') ? 'left' : attrs['toc']))
        # toc-placement allows us to separate position from using fitted slot vs macro
        (toc_placement = attrs.fetch('toc-placement', 'macro')) && toc_placement != 'auto' ? toc_placement : attrs['toc-position']
      else
        nil
      end

      if toc_val && (!toc_val.empty? || !toc_position_val.nil_or_empty?)
        default_toc_position = 'left'
        # TODO rename toc2 to aside-toc
        default_toc_class = 'toc2'
        if !toc_position_val.nil_or_empty?
          position = toc_position_val
        elsif !toc_val.empty?
          position = toc_val
        else
          position = default_toc_position
        end
        attrs['toc'] = ''
        attrs['toc-placement'] = 'auto'
        case position
        when 'left', '<', '&lt;'
          attrs['toc-position'] = 'left'
        when 'right', '>', '&gt;'
          attrs['toc-position'] = 'right'
        when 'top', '^'
          attrs['toc-position'] = 'top'
        when 'bottom', 'v'
          attrs['toc-position'] = 'bottom'
        when 'preamble', 'macro'
          attrs['toc-position'] = 'content'
          attrs['toc-placement'] = position
          default_toc_class = nil
        else
          attrs.delete 'toc-position'
          default_toc_class = nil
        end
        attrs['toc-class'] ||= default_toc_class if default_toc_class
      end

      if (@compat_mode = attrs.key? 'compat-mode')
        attrs['source-language'] = attrs['language'] if attrs.key? 'language'
      end

      # NOTE pin the outfilesuffix after the header is parsed
      @outfilesuffix = attrs['outfilesuffix']

      @header_attributes = attrs.dup

      # unfreeze "flexible" attributes
      unless @parent_document
        FLEXIBLE_ATTRIBUTES.each do |name|
          # turning a flexible attribute off should be permanent
          # (we may need more config if that's not always the case)
          if @attribute_overrides.key?(name) && @attribute_overrides[name]
            @attribute_overrides.delete(name)
          end
        end
      end
    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 (default: '')

Returns the resolved value if the attribute was set or false if it was not because it's locked.

    # File lib/asciidoctor/document.rb, line 839
    def set_attribute name, value = ''
      if attribute_locked? name
        false
      else
        if @max_attribute_value_size
          resolved_value = (apply_attribute_value_subs value).limit_bytesize @max_attribute_value_size
        else
          resolved_value = apply_attribute_value_subs value
        end
        case name
        when 'backend'
          update_backend_attributes resolved_value, (@attributes_modified.delete? 'htmlsyntax')
        when 'doctype'
          update_doctype_attributes resolved_value
        else
          @attributes[name] = resolved_value
        end
        @attributes_modified << name
        resolved_value
      end
    end

source() click to toggle source

Make the raw source for the Document available.

    # File lib/asciidoctor/document.rb, line 598
    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 603
    def source_lines
      @reader.source_lines if @reader
    end

title() click to toggle source

The title explicitly defined in the document attributes

    # File lib/asciidoctor/document.rb, line 612
    def title
      @attributes['title']
    end

title=(title) click to toggle source

    # File lib/asciidoctor/document.rb, line 616
    def title= title
      unless (sect = @header)
        (sect = (@header = Section.new self, 0, false)).sectname = 'header'
      end
      sect.title = title
    end

to_s() click to toggle source

    # File lib/asciidoctor/document.rb, line 1202
    def to_s
      %Q(#<#{self.class}@#{object_id} {doctype: #{doctype.inspect}, doctitle: #{(@header != nil ? @header.title : nil).inspect}, blocks: #{@blocks.size}}>)
    end

update_backend_attributes(new_backend, force = nil) click to toggle source

Public: Update the backend attributes to reflect a change in the active backend.

This method also handles updating the related doctype attributes if the doctype attribute is assigned at the time this method is called.

Returns the resolved String backend if updated, nothing otherwise.

    # File lib/asciidoctor/document.rb, line 911
    def update_backend_attributes new_backend, force = nil
      if force || (new_backend && new_backend != @backend)
        current_backend, current_basebackend, current_doctype = @backend, (attrs = @attributes)['basebackend'], @doctype
        if new_backend.start_with? 'xhtml'
          attrs['htmlsyntax'] = 'xml'
          new_backend = new_backend[1..-1]
        elsif new_backend.start_with? 'html'
          attrs['htmlsyntax'] = 'html' unless attrs['htmlsyntax'] == 'xml'
        end
        if (resolved_backend = BACKEND_ALIASES[new_backend])
          new_backend = resolved_backend
        end
        if current_doctype
          if current_backend
            attrs.delete %Q(backend-#{current_backend})
            attrs.delete %Q(backend-#{current_backend}-doctype-#{current_doctype})
          end
          attrs[%Q(backend-#{new_backend}-doctype-#{current_doctype})] = ''
          attrs[%Q(doctype-#{current_doctype})] = ''
        elsif current_backend
          attrs.delete %Q(backend-#{current_backend})
        end
        attrs[%Q(backend-#{new_backend})] = ''
        @backend = attrs['backend'] = new_backend
        # (re)initialize converter
        if Converter::BackendInfo === (@converter = create_converter)
          new_basebackend = @converter.basebackend
          attrs['outfilesuffix'] = @converter.outfilesuffix unless attribute_locked? 'outfilesuffix'
          new_filetype = @converter.filetype
        elsif @converter
          new_basebackend = new_backend.sub TrailingDigitsRx, ''
          if (new_outfilesuffix = DEFAULT_EXTENSIONS[new_basebackend])
            new_filetype = new_outfilesuffix[1..-1]
          else
            new_outfilesuffix, new_basebackend, new_filetype = '.html', 'html', 'html'
          end
          attrs['outfilesuffix'] = new_outfilesuffix unless attribute_locked? 'outfilesuffix'
        else
          # NOTE ideally we shouldn't need the converter before the converter phase, but we do
          raise ::NotImplementedError, %Q(asciidoctor: FAILED: missing converter for backend '#{new_backend}'. Processing aborted.)
        end
        if (current_filetype = attrs['filetype'])
          attrs.delete %Q(filetype-#{current_filetype})
        end
        attrs['filetype'] = new_filetype
        attrs[%Q(filetype-#{new_filetype})] = ''
        if (page_width = DEFAULT_PAGE_WIDTHS[new_basebackend])
          attrs['pagewidth'] = page_width
        else
          attrs.delete 'pagewidth'
        end
        if new_basebackend != current_basebackend
          if current_doctype
            if current_basebackend
              attrs.delete %Q(basebackend-#{current_basebackend})
              attrs.delete %Q(basebackend-#{current_basebackend}-doctype-#{current_doctype})
            end
            attrs[%Q(basebackend-#{new_basebackend}-doctype-#{current_doctype})] = ''
          elsif current_basebackend
            attrs.delete %Q(basebackend-#{current_basebackend})
          end
          attrs[%Q(basebackend-#{new_basebackend})] = ''
          attrs['basebackend'] = new_basebackend
        end
        return new_backend
      end
    end

update_doctype_attributes(new_doctype) click to toggle source

TODO document me

Returns the String doctype if updated, nothing otherwise.

    # File lib/asciidoctor/document.rb, line 982
    def update_doctype_attributes new_doctype
      if new_doctype && new_doctype != @doctype
        current_backend, current_basebackend, current_doctype = @backend, (attrs = @attributes)['basebackend'], @doctype
        if current_doctype
          attrs.delete %Q(doctype-#{current_doctype})
          if current_backend
            attrs.delete %Q(backend-#{current_backend}-doctype-#{current_doctype})
            attrs[%Q(backend-#{current_backend}-doctype-#{new_doctype})] = ''
          end
          if current_basebackend
            attrs.delete %Q(basebackend-#{current_basebackend}-doctype-#{current_doctype})
            attrs[%Q(basebackend-#{current_basebackend}-doctype-#{new_doctype})] = ''
          end
        else
          attrs[%Q(backend-#{current_backend}-doctype-#{new_doctype})] = '' if current_backend
          attrs[%Q(basebackend-#{current_basebackend}-doctype-#{new_doctype})] = '' if current_basebackend
        end
        attrs[%Q(doctype-#{new_doctype})] = ''
        return @doctype = attrs['doctype'] = new_doctype
      end
    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 file to that method. Otherwise, write the output the specified file.

    # File lib/asciidoctor/document.rb, line 1078
    def write output, target
      if Writer === @converter
        @converter.write output, target
      else
        if target.respond_to? :write
          unless output.nil_or_empty?
            target.write output.chomp
            # ensure there's a trailing endline
            target.write LF
          end
        else
          ::IO.write target, output
        end
        nil
      end
    end