# File lib/asciidoctor/document.rb, line 170
  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
      @references = parent_doc.references.inject({}) do |accum, (key,ref)|
        if key == :footnotes
          accum[:footnotes] = []
        else
          accum[key] = ref
        end
        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
      attr_overrides = parent_doc.attributes.dup
      ['doctype', 'compat-mode', 'toc', 'toc-placement', 'toc-position'].each do |key|
        attr_overrides.delete key
      end
      @attribute_overrides = attr_overrides
      @safe = parent_doc.safe
      @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
      @references = {
        :ids => {},
        :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 ::Fixnum === 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.const_get(safe_mode.to_s.upcase)
        rescue
          @safe = SafeMode::SECURE
        end
      end
      @sourcemap = options[:sourcemap]
      @compat_mode = false
      @converter = nil
      initialize_extensions = defined? ::Asciidoctor::Extensions
      @extensions = nil # initialize furthur down
    end

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

    attrs = @attributes
    #attrs['encoding'] = 'UTF-8'
    attrs['sectids'] = ''
    attrs['notitle'] = '' unless header_footer
    attrs['toc-placement'] = 'auto'
    attrs['stylesheet'] = ''
    attrs['webfonts'] = ''
    attrs['copycss'] = '' if header_footer
    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['appendix-caption'] = 'Appendix'
    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['untitled-label'] = 'Untitled'
    attrs['version-label'] = 'Version'
    attrs['last-update-label'] = 'Last updated'

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

    safe_mode_name = SafeMode.constants.detect {|l| SafeMode.const_get(l) == @safe }.to_s.downcase
    attr_overrides['safe-mode-name'] = safe_mode_name
    attr_overrides["safe-mode-#{safe_mode_name}"] = ''
    attr_overrides['safe-mode-level'] = @safe

    # sync the embedded attribute w/ the value of options...do not allow override
    attr_overrides['embedded'] = header_footer ? nil : ''

    # the only way to set the max-include-depth attribute is via the document options
    # 64 is the AsciiDoc default
    attr_overrides['max-include-depth'] ||= 64

    # the only way to enable uri reads is via the document options, disabled by default
    unless !attr_overrides['allow-uri-read'].nil?
      attr_overrides['allow-uri-read'] = nil
    end

    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 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]
      @base_dir = attr_overrides['docdir'] = ::File.expand_path(options[:base_dir])
    else
      if attr_overrides['docdir']
        @base_dir = attr_overrides['docdir'] = ::File.expand_path(attr_overrides['docdir'])
      else
        #warn 'asciidoctor: WARNING: setting base_dir is recommended when working with string documents' unless nested?
        @base_dir = attr_overrides['docdir'] = ::File.expand_path(::Dir.pwd)
      end
    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})
    end

    if (doctype_val = options[:doctype])
      attr_overrides['doctype'] = %(#{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
        # 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

    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

    @compat_mode = true if attrs.key? 'compat-mode'

    if parent_doc
      # setup default doctype (backend is fixed)
      attrs['doctype'] ||= DEFAULT_DOCTYPE

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

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

      # dynamic intrinstic attribute values
      now = ::Time.now
      localdate = (attrs['localdate'] ||= now.strftime('%Y-%m-%d'))
      unless (localtime = attrs['localtime'])
        begin
          localtime = attrs['localtime'] = now.strftime('%H:%M:%S %Z')
        rescue
          localtime = attrs['localtime'] = now.strftime('%H:%M:%S')
        end
      end
      attrs['localdatetime'] ||= %(#{localdate} #{localtime})

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

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

      if initialize_extensions
        if (registry = options[:extensions_registry])
          if Extensions::Registry === registry || (::RUBY_ENGINE_JRUBY &&
              ::AsciidoctorJ::Extensions::ExtensionRegistry === registry)
            # take it as it is
          else
            registry = Extensions::Registry.new
          end
        elsif ::Proc === (ext_block = options[:extensions])
          registry = Extensions.build_registry(&ext_block)
        else
          registry = Extensions::Registry.new
        end
        @extensions = registry.activate self
      end

      @reader = PreprocessorReader.new self, data, Reader::Cursor.new(attrs['docfile'], @base_dir)
    end
  end