class Asciidoctor::Document

Public: Methods for parsing Asciidoc documents and rendering them using erb templates.

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

Constants

AttributeEntry
Footnote

Attributes

base_dir[R]

Public: Base directory for rendering 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

counters[R]

Public: Get the Hash of document counters

header[R]

Public: The section level 0 block

parent_document[R]

Public: A reference to the parent document of this nested document.

references[R]

Public: Get the Hash of document references

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

A value of 10 (SERVER) disallows the document from setting attributes that would affect the rendering 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::[] macro 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)!

Public Class Methods

new(data = [], options = {}, &block) click to toggle source

Public: Initialize an Asciidoc object.

data - The Array of Strings holding the Asciidoc source document. (default: []) options - A Hash of options to control processing, such as setting the safe mode (:safe),

suppressing the header/footer (:header_footer) and attribute overrides (:attributes)
(default: {})

block - A block that can be used to retrieve external Asciidoc

data to include in this document.

Examples

data = File.readlines(filename)
doc  = Asciidoctor::Document.new(data)
puts doc.render
# File lib/asciidoctor/document.rb, line 101
def initialize(data = [], options = {}, &block)
  super(self, :document)
  @renderer = nil

  if options[:parent]
    @parent_document = options.delete(:parent)
    # should we dup here?
    options[:attributes] = @parent_document.attributes
    options[:safe] ||= @parent_document.safe
    options[:base_dir] ||= @parent_document.base_dir
    @renderer = @parent_document.renderer
  else
    @parent_document = nil
  end

  @header = nil
  @references = {
    :ids => {},
    :footnotes => [],
    :links => [],
    :images => [],
    :indexterms => []
  }
  @counters = {}
  @callouts = Callouts.new
  @options = options
  @safe = @options.fetch(:safe, SafeMode::SECURE).to_i
  @options[:header_footer] = @options.fetch(:header_footer, false)

  @attributes['asciidoctor'] = ''
  @attributes['asciidoctor-version'] = VERSION
  @attributes['sectids'] = ''
  @attributes['encoding'] = 'UTF-8'
  @attributes['notitle'] = '' if !@options[:header_footer]

  # language strings
  # TODO load these based on language settings
  @attributes['caution-caption'] = 'Caution'
  @attributes['important-caption'] = 'Important'
  @attributes['note-caption'] = 'Note'
  @attributes['tip-caption'] = 'Tip'
  @attributes['warning-caption'] = 'Warning'
  @attributes['appendix-caption'] = 'Appendix'
  @attributes['example-caption'] = 'Example'
  @attributes['figure-caption'] = 'Figure'
  @attributes['table-caption'] = 'Table'
  @attributes['toc-title'] = 'Table of Contents'

  @attribute_overrides = options[:attributes] || {}

  # the only way to set the include-depth attribute is via the document options
  # 10 is the AsciiDoc default, though currently Asciidoctor only supports 1 level
  @attribute_overrides['include-depth'] ||= 10

  # if the base_dir option is specified, it overrides docdir as the root for relative paths
  # otherwise, the base_dir is the directory of the source file (docdir) or the current
  # directory of the input is a string
  if options[:base_dir].nil?
    if @attribute_overrides['docdir']
      @base_dir = @attribute_overrides['docdir'] = File.expand_path(@attribute_overrides['docdir'])
    else
      # perhaps issue a warning here?
      @base_dir = @attribute_overrides['docdir'] = Dir.pwd
    end
  else
    @base_dir = @attribute_overrides['docdir'] = File.expand_path(options[:base_dir])
  end

  # allow common attributes backend and doctype to be set using options hash
  unless @options[:backend].nil?
    @attribute_overrides['backend'] = @options[:backend]
  end

  unless @options[:doctype].nil?
    @attribute_overrides['doctype'] = @options[:doctype]
  end

  if @safe >= SafeMode::SERVER
    # restrict document from setting source-highlighter and backend
    @attribute_overrides['source-highlighter'] ||= nil
    @attribute_overrides['backend'] ||= DEFAULT_BACKEND
    # restrict document from seeing the docdir and trim docfile to relative path
    if @attribute_overrides.has_key?('docfile') && @parent_document.nil?
      @attribute_overrides['docfile'] = @attribute_overrides['docfile'][(@attribute_overrides['docdir'].length + 1)..-1]
    end
    @attribute_overrides['docdir'] = ''
    # restrict document from enabling icons
    if @safe >= SafeMode::SECURE
      @attribute_overrides['icons'] ||= nil
    end
  end
  
  @attribute_overrides.delete_if {|key, val|
    verdict = false
    # a nil or negative key undefines the attribute 
    if val.nil? || key[-1..-1] == '!'
      @attributes.delete(key.chomp '!')
    # otherwise it's an attribute assignment
    else
      # a value ending in @ indicates this attribute does not override
      # an attribute with the same key in the document souce
      if val.is_a?(String) && val.end_with?('@')
        val.chop!
        verdict = true
      end
      @attributes[key] = val
    end
    verdict
  }

  @attributes['backend'] ||= DEFAULT_BACKEND
  @attributes['doctype'] ||= DEFAULT_DOCTYPE
  update_backend_attributes

  if !@parent_document.nil?
    # don't need to do the extra processing within our own document
    @reader = Reader.new(data)
  else
    @reader = Reader.new(data, self, true, &block)
  end

  # dynamic intrinstic attribute values
  now = Time.new
  @attributes['localdate'] ||= now.strftime('%Y-%m-%d')
  @attributes['localtime'] ||= now.strftime('%H:%M:%S %Z')
  @attributes['localdatetime'] ||= [@attributes['localdate'], @attributes['localtime']] * ' '
  
  # docdate, doctime and docdatetime should default to
  # localdate, localtime and localdatetime if not otherwise set
  @attributes['docdate'] ||= @attributes['localdate']
  @attributes['doctime'] ||= @attributes['localtime']
  @attributes['docdatetime'] ||= @attributes['localdatetime']
  
  @attributes['iconsdir'] ||= File.join(@attributes.fetch('imagesdir', 'images'), 'icons')

  # Now parse the lines in the reader into blocks
  Lexer.parse(@reader, self, :header_only => @options.fetch(:parse_header_only, false)) 

  @callouts.rewind

  Debug.debug {
    msg = []
    msg << "Found #{@blocks.size} blocks in this document:"
    @blocks.each {|b|
      msg << b
    }
    msg * "\n"
  }
end

Public Instance Methods

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:), then apply the substitutions defined on the macro to the text. Otherwise, apply the verbatim 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 482
def apply_attribute_value_subs(value)
  if value.match(REGEXP[:pass_macro_basic])
    # copy match for Ruby 1.8.7 compat
    m = $~
    subs = []
    if !m[1].empty?
      subs = resolve_subs(m[1])
    end
    if !subs.empty?
      apply_subs(m[2], subs)
    else
      m[2]
    end
  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 469
def attribute_locked?(name)
  @attribute_overrides.has_key?(name) || @attribute_overrides.has_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 364
def author
  @attributes['author']
end
backend() click to toggle source
# File lib/asciidoctor/document.rb, line 335
def backend
  @attributes['backend']
end
clear_playback_attributes(attributes) click to toggle source

Internal: Delete any attributes stored for playback

# File lib/asciidoctor/document.rb, line 408
def clear_playback_attributes(attributes)
  attributes.delete(:attribute_entries)
end
content() click to toggle source
# File lib/asciidoctor/document.rb, line 578
def content
  # per AsciiDoc-spec, remove the title after rendering the header
  @attributes.delete('title')
  @blocks.map {|b| b.render }.join
end
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 257
def counter(name, seed = nil)
  if !@counters.has_key? name
    if seed.nil?
      seed = nextval(@attributes.has_key?(name) ? @attributes[name] : 0)
    elsif seed.to_i.to_s == seed
      seed = seed.to_i
    end
    @counters[name] = seed
  else
    @counters[name] = nextval(@counters[name])
  end

  (@attributes[name] = @counters[name])
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 455
def delete_attribute(name)
  if attribute_locked?(name)
    false
  else
    @attributes.delete(name)
    true
  end
end
doctitle() click to toggle source

We need to be able to return some semblance of a title

# File lib/asciidoctor/document.rb, line 350
def doctitle
  if !(title = @attributes.fetch('title', '')).empty?
    title
  elsif !(sect = first_section).nil? && sect.title?
    sect.title
  else
    nil
  end
end
Also aliased as: name
doctype() click to toggle source
# File lib/asciidoctor/document.rb, line 331
def doctype
  @attributes['doctype']
end
first_section() click to toggle source

QUESTION move to AbstractBlock?

# File lib/asciidoctor/document.rb, line 384
def first_section
  has_header? ? @header : (@blocks || []).detect{|e| e.is_a? Section}
end
footnotes() click to toggle source
# File lib/asciidoctor/document.rb, line 313
def footnotes
  @references[:footnotes]
end
footnotes?() click to toggle source
# File lib/asciidoctor/document.rb, line 309
def footnotes?
  not @references[:footnotes].empty?
end
has_header?() click to toggle source
# File lib/asciidoctor/document.rb, line 388
def has_header?
  !@header.nil?
end
name() click to toggle source
Alias for: doctitle
nested?() click to toggle source
# File lib/asciidoctor/document.rb, line 317
def nested?
  !@parent_document.nil?
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 279
def nextval(current)
  if current.is_a?(Integer)
    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
noheader() click to toggle source
# File lib/asciidoctor/document.rb, line 379
def noheader
  @attributes.has_key? 'noheader'
end
notitle() click to toggle source
# File lib/asciidoctor/document.rb, line 375
def notitle
  @attributes.has_key? 'notitle'
end
playback_attributes(block_attributes) click to toggle source

Internal: Replay attribute assignments at the block level

# File lib/asciidoctor/document.rb, line 413
def playback_attributes(block_attributes)
  if block_attributes.has_key? :attribute_entries
    block_attributes[:attribute_entries].each do |entry|
      if entry.negate
        @attributes.delete(entry.name)
      else
        @attributes[entry.name] = entry.value
      end
    end
  end
end
register(type, value) click to toggle source
# File lib/asciidoctor/document.rb, line 292
def register(type, value)
  case type
  when :ids
    if value.is_a?(Array)
      @references[:ids][value[0]] = (value[1] || '[' + value[0] + ']')
    else
      @references[:ids][value] = '[' + value + ']'
    end
  when :footnotes, :indexterms
    @references[type] << value
  else
    if @options[:catalog_assets]
      @references[type] << value
    end
  end
end
render(opts = {}) click to toggle source

Public: Render the Asciidoc document using the templates loaded by Renderer. If a :template_dir is not specified, or a template is missing, the renderer will fall back to using the appropriate built-in template.

# File lib/asciidoctor/document.rb, line 572
def render(opts = {})
  restore_attributes
  r = renderer(opts)
  @options.merge(opts)[:header_footer] ? r.render('document', self).strip : r.render('embedded', self)
end
renderer(opts = {}) click to toggle source
# File lib/asciidoctor/document.rb, line 547
def renderer(opts = {})
  return @renderer if @renderer
  
  render_options = {}

  # Load up relevant Document @options
  if @options.has_key? :template_dir
    render_options[:template_dir] = @options[:template_dir]
  end
  
  render_options[:backend] = @attributes.fetch('backend', 'html5')
  render_options[:template_engine] = @options[:template_engine]
  render_options[:eruby] = @options.fetch(:eruby, 'erb')
  render_options[:compact] = @options.fetch(:compact, false)
  
  # Override Document @option settings with options passed in
  render_options.merge! opts

  @renderer = Renderer.new(render_options)
end
restore_attributes() click to toggle source

Internal: Restore the attributes to the previously saved state

# File lib/asciidoctor/document.rb, line 403
def restore_attributes
  @attributes = @original_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 371
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 394
def save_attributes
  # css-signature cannot be updated after header attributes are processed
  if @id.nil? && @attributes.has_key?('css-signature')
    @id = @attributes['css-signature']
  end
  @original_attributes = @attributes.dup
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', then the value of backend-related attributes are updated.

name - the String attribute name value - the String attribute value

returns true if the attribute was set, false if it was not set because it's locked

# File lib/asciidoctor/document.rb, line 436
def set_attribute(name, value)
  if attribute_locked?(name)
    false
  else
    @attributes[name] = apply_attribute_value_subs(value)
    if name == 'backend'
      update_backend_attributes()
    end
    true
  end
end
source() click to toggle source

Make the raw source for the Document available.

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

Make the raw source lines for the Document available.

# File lib/asciidoctor/document.rb, line 327
def source_lines
  @reader.source if @reader
end
splain() click to toggle source
# File lib/asciidoctor/document.rb, line 526
def splain
  Debug.debug {
    msg = ''
    if @header
      msg = "Header is #{@header}"
    else
      msg = "No header"
    end

    msg += "I have #{@blocks.count} blocks"
    @blocks.each_with_index do |block, i|
      msg += "v" * 60
      msg += "Block ##{i} is a #{block.class}"
      msg += "Name is #{block.title rescue 'n/a'}"
      block.splain(0) if block.respond_to? :splain
      msg += "^" * 60
    end
  }
  nil
end
title() click to toggle source

The title explicitly defined in the document attributes

# File lib/asciidoctor/document.rb, line 340
def title
  @attributes['title']
end
title=(title) click to toggle source
# File lib/asciidoctor/document.rb, line 344
def title=(title)
  @header ||= Section.new self
  @header.title = title
end
to_s() click to toggle source
# File lib/asciidoctor/document.rb, line 584
def to_s
  %Q[#{super.to_s} - #{doctitle}]  
end
update_backend_attributes() click to toggle source

Public: Update the backend attributes to reflect a change in the selected backend

# File lib/asciidoctor/document.rb, line 501
def update_backend_attributes()
  backend = @attributes['backend']
  if BACKEND_ALIASES.has_key? backend
    backend = @attributes['backend'] = BACKEND_ALIASES[backend]
  end
  basebackend = backend.sub(%r[[:digit:]]+$/, '')
  page_width = DEFAULT_PAGE_WIDTHS[basebackend]
  if page_width
    @attributes['pagewidth'] = page_width
  else
    @attributes.delete('pagewidth')
  end
  @attributes["backend-#{backend}"] = ''
  @attributes['basebackend'] = basebackend
  @attributes["basebackend-#{basebackend}"] = ''
  # REVIEW cases for the next two assignments
  @attributes["#{backend}-#{@attributes['doctype']}"] = ''
  @attributes["#{basebackend}-#{@attributes['doctype']}"] = ''
  ext = DEFAULT_EXTENSIONS[basebackend] || '.html'
  @attributes['outfilesuffix'] = ext
  file_type = ext[1..-1]
  @attributes['filetype'] = file_type
  @attributes["filetype-#{file_type}"] = ''
end