class Asciidoctor::Document
noheader - The header block (h1 heading, author, revision info) should not be shown
notitle - The h1 heading should not be shown
Keep in mind that you’ll want to honor these document settings:
header.title - title of section level 0
first_section.title - title of first section in document, if present
title - value of the title attribute, or nil if not present
name - an alias of doctitle
otherwise nil
otherwise title of first section in document, if present
doctitle - value of title attribute, if assigned and non-empty,
There are several strategies for getting the title of the document:
using erb templates.
Public: Methods for parsing Asciidoc documents and rendering them
def apply_attribute_value_subs(value)
value - The String attribute value on which to perform substitutions
apply the verbatim substitutions to the value.
apply the substitutions defined on the macro to the text. Otherwise,
If the value is an inline passthrough macro (e.g., pass:[text]), then
Internal: Apply substitutions to the attribute value
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
def attribute_locked?(name)
key - The attribute key to check
Public: Determine if the attribute has been locked by being assigned in document options
def attribute_locked?(name) @attribute_overrides.has_key?(name) || @attribute_overrides.has_key?("#{name}!") end
def author
Public: Convenience method to retrieve the document attribute 'author'
def author @attributes['author'] end
def backend
def backend @attributes['backend'] end
def clear_playback_attributes(attributes)
def clear_playback_attributes(attributes) attributes.delete(:attribute_entries) end
def content
def content # per AsciiDoc-spec, remove the title after rendering the header @attributes.delete('title') @blocks.map {|b| b.render }.join end
def counter(name, seed = nil)
seed - the initial value as a String or Integer
name - the String name of the counter
Public: Get the named counter and take the next number in the sequence.
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
def delete_attribute(name)
name - the String attribute name
If the attribute is locked, false is returned. Otherwise, the attribute is deleted.
Public: Delete the specified attribute from the document if the name is not locked
def delete_attribute(name) if attribute_locked?(name) false else @attributes.delete(name) true end end
def doctitle
def doctitle if !(title = @attributes.fetch('title', '')).empty? title elsif !(sect = first_section).nil? && sect.title? sect.title else nil end end
def doctype
def doctype @attributes['doctype'] end
def first_section
def first_section has_header? ? @header : (@blocks || []).detect{|e| e.is_a? Section} end
def footnotes
def footnotes @references[:footnotes] end
def footnotes?
def footnotes? not @references[:footnotes].empty? end
def has_header?
def has_header? !@header.nil? end
def initialize(data = [], options = {}, &block)
doc = Asciidoctor::Document.new(data)
data = File.readlines(filename)
Examples
data to include in this document.
block - A block that can be used to retrieve external Asciidoc
(default: {})
suppressing the header/footer (:header_footer) and attribute overrides (:attributes)
options - A Hash of options to control processing, such as setting the safe mode (:safe),
data - The Array of Strings holding the Asciidoc source document. (default: [])
Public: Initialize an Asciidoc object.
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
def nested?
def nested? !@parent_document.nil? end
def nextval(current)
current - the value to increment as a String or Integer
Handles both integer and character sequences.
Internal: Get the next value in the sequence.
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
def noheader
def noheader @attributes.has_key? 'noheader' end
def notitle
def notitle @attributes.has_key? 'notitle' end
def playback_attributes(block_attributes)
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
def register(type, value)
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
def render(opts = {})
or a template is missing, the renderer will fall back to
loaded by Renderer. If a :template_dir is not specified,
Public: Render the Asciidoc document using the templates
def render(opts = {}) restore_attributes r = renderer(opts) @options.merge(opts)[:header_footer] ? r.render('document', self).strip : r.render('embedded', self) end
def renderer(opts = {})
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
def restore_attributes
def restore_attributes @attributes = @original_attributes end
def revdate
Public: Convenience method to retrieve the document attribute 'revdate'
def revdate @attributes['revdate'] end
def save_attributes
Internal: Branch the attributes so that the original state can be restored
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
def set_attribute(name, value)
value - the String attribute value
name - the String attribute name
value of backend-related attributes are updated.
substitutions on the value. If the attribute name is 'backend', then the
assigned to the attribute name after first performing attribute
If the attribute is locked, false is returned. Otherwise, the value is
Public: Set the specified attribute on the document if the name is not locked
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
def source
def source @reader.source.join if @reader end
def source_lines
def source_lines @reader.source if @reader end
def splain
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
def title
def title @attributes['title'] end
def title=(title)
def title=(title) @header ||= Section.new self @header.title = title end
def to_s
def to_s %[#{super.to_s} - #{doctitle}] end
def update_backend_attributes()
def update_backend_attributes() backend = @attributes['backend'] if BACKEND_ALIASES.has_key? backend backend = @attributes['backend'] = BACKEND_ALIASES[backend] end basebackend = backend.sub(/[[: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