# File lib/asciidoctor/reader.rb, line 823
  def preprocess_include_directive raw_target, raw_attributes
    if ((target = raw_target).include? ATTR_REF_HEAD) &&
        (target = @document.sub_attributes raw_target, :attribute_missing => 'drop-line').empty?
      shift
      if @document.attributes.fetch('attribute-missing', Compliance.attribute_missing) == 'skip'
        unshift %(Unresolved directive in #{@path} - include::#{raw_target}[#{raw_attributes}])
      end
      true
    elsif include_processors? && (ext = @include_processor_extensions.find {|candidate| candidate.instance.handles? target })
      shift
      # FIXME parse attributes only if requested by extension
      ext.process_method[@document, self, target, AttributeList.new(raw_attributes).parse]
      true
    # if running in SafeMode::SECURE or greater, don't process this directive
    # however, be friendly and at least make it a link to the source document
    elsif @document.safe >= SafeMode::SECURE
      # FIXME we don't want to use a link macro if we are in a verbatim context
      replace_next_line %(link:#{target}[])
      true
    elsif (abs_maxdepth = @maxdepth[:abs]) > 0
      if @include_stack.size >= abs_maxdepth
        warn %(asciidoctor: ERROR: #{line_info}: maximum include depth of #{@maxdepth[:rel]} exceeded)
        return false
      end
      if ::RUBY_ENGINE_OPAL && ::JAVASCRIPT_IO_MODULE == 'xmlhttprequest'
        # NOTE resolves uri relative to currently loaded document
        # NOTE we defer checking if file exists and catch the 404 error if it does not
        target_type = :file
        inc_path = relpath = @include_stack.empty? && ::Dir.pwd == @document.base_dir ? target : (::File.join @dir, target)
      elsif Helpers.uriish? target
        unless @document.attributes.key? 'allow-uri-read'
          replace_next_line %(link:#{target}[])
          return true
        end

        target_type = :uri
        inc_path = relpath = target
        if @document.attributes.key? 'cache-uri'
          # caching requires the open-uri-cached gem to be installed
          # processing will be automatically aborted if these libraries can't be opened
          Helpers.require_library 'open-uri/cached', 'open-uri-cached' unless defined? ::OpenURI::Cache
        elsif !::RUBY_ENGINE_OPAL
          # autoload open-uri
          ::OpenURI
        end
      else
        target_type = :file
        # include file is resolved relative to dir of current include, or base_dir if within original docfile
        inc_path = @document.normalize_system_path target, @dir, nil, :target_name => 'include file'
        unless ::File.file? inc_path
          warn %(asciidoctor: WARNING: #{line_info}: include file not found: #{inc_path})
          replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
          return true
        end
        # NOTE relpath is the path relative to the root document (or base_dir, if set)
        # QUESTION should we move relative_path method to Document
        relpath = (@path_resolver ||= PathResolver.new).relative_path inc_path, @document.base_dir
      end

      inc_linenos, inc_tags, attributes = nil, nil, {}
      unless raw_attributes.empty?
        # QUESTION should we use @document.parse_attribues?
        attributes = AttributeList.new(raw_attributes).parse
        if attributes.key? 'lines'
          inc_linenos = []
          attributes['lines'].split(DataDelimiterRx).each do |linedef|
            if linedef.include?('..')
              from, to = linedef.split('..', 2).map {|it| it.to_i }
              if to == -1
                inc_linenos << from
                inc_linenos << 1.0/0.0
              else
                inc_linenos.concat ::Range.new(from, to).to_a
              end
            else
              inc_linenos << linedef.to_i
            end
          end
          inc_linenos = inc_linenos.empty? ? nil : inc_linenos.sort.uniq
        elsif attributes.key? 'tag'
          unless (tag = attributes['tag']).empty?
            if tag.start_with? '!'
              inc_tags = { (tag.slice 1, tag.length) => false } unless tag == '!'
            else
              inc_tags = { tag => true }
            end
          end
        elsif attributes.key? 'tags'
          inc_tags = {}
          attributes['tags'].split(DataDelimiterRx).each do |tagdef|
            if tagdef.start_with? '!'
              inc_tags[tagdef.slice 1, tagdef.length] = false unless tagdef == '!'
            else
              inc_tags[tagdef] = true
            end unless tagdef.empty?
          end
          inc_tags = nil if inc_tags.empty?
        end
      end

      if inc_linenos
        inc_lines, inc_offset, inc_lineno = [], nil, 0
        begin
          open(inc_path, 'r') do |f|
            f.each_line do |l|
              inc_lineno += 1
              select = inc_linenos[0]
              if ::Float === select && select.infinite?
                # NOTE record line where we started selecting
                inc_offset ||= inc_lineno
                inc_lines << l
              else
                if inc_lineno == select
                  # NOTE record line where we started selecting
                  inc_offset ||= inc_lineno
                  inc_lines << l
                  inc_linenos.shift
                end
                break if inc_linenos.empty?
              end
            end
          end
        rescue
          warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{inc_path})
          replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
          return true
        end
        shift
        # FIXME not accounting for skipped lines in reader line numbering
        push_include inc_lines, inc_path, relpath, inc_offset, attributes if inc_offset
      elsif inc_tags
        inc_lines, inc_offset, inc_lineno, tag_stack, tags_used, active_tag = [], nil, 0, [], ::Set.new, nil
        if inc_tags.key? '**'
          if inc_tags.key? '*'
            select = base_select = (inc_tags.delete '**')
            wildcard = inc_tags.delete '*'
          else
            select = base_select = wildcard = (inc_tags.delete '**')
          end
        else
          select = base_select = !(inc_tags.value? true)
          wildcard = inc_tags.delete '*'
        end
        if (ext_idx = inc_path.rindex '.') && (circ_cmt = CIRCUMFIX_COMMENTS[inc_path.slice ext_idx, inc_path.length])
          cmt_suffix_len = (tag_suffix = %([] #{circ_cmt[:suffix]})).length - 2
        end
        begin
          open(inc_path, 'r') do |f|
            f.each_line do |l|
              inc_lineno += 1
              # must force encoding since we're performing String operations on line
              l.force_encoding ::Encoding::UTF_8 if FORCE_ENCODING
              if (((tl = l.chomp).end_with? '[]') ||
                  (tag_suffix && (tl.end_with? tag_suffix) && (tl = tl.slice 0, tl.length - cmt_suffix_len))) &&
                  TagDirectiveRx =~ tl
                if $1 # end tag
                  if (this_tag = $2) == active_tag
                    tag_stack.pop
                    active_tag, select = tag_stack.empty? ? [nil, base_select] : tag_stack[-1]
                  elsif inc_tags.key? this_tag
                    if (idx = tag_stack.rindex {|key, _| key == this_tag })
                      idx == 0 ? tag_stack.shift : (tag_stack.delete_at idx)
                      warn %(asciidoctor: WARNING: #{target}: line #{inc_lineno}: mismatched end tag in include: expected #{active_tag}, found #{this_tag})
                    else
                      warn %(asciidoctor: WARNING: #{target}: line #{inc_lineno}: unexpected end tag in include: #{this_tag})
                    end
                  end
                elsif inc_tags.key?(this_tag = $2)
                  tags_used << this_tag
                  # QUESTION should we prevent tag from being selected when enclosing tag is excluded?
                  tag_stack << [(active_tag = this_tag), (select = inc_tags[this_tag])]
                elsif !wildcard.nil?
                  select = active_tag && !select ? false : wildcard
                  tag_stack << [(active_tag = this_tag), select]
                end
              elsif select
                # NOTE record the line where we started selecting
                inc_offset ||= inc_lineno
                inc_lines << l
              end
            end
          end
        rescue
          warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{inc_path})
          replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
          return true
        end
        unless (missing_tags = inc_tags.keys.to_a - tags_used.to_a).empty?
          warn %(asciidoctor: WARNING: #{line_info}: tag#{missing_tags.size > 1 ? 's' : nil} '#{missing_tags * ','}' not found in include #{target_type}: #{inc_path})
        end
        shift
        # FIXME not accounting for skipped lines in reader line numbering
        push_include inc_lines, inc_path, relpath, inc_offset, attributes if inc_offset
      else
        begin
          # NOTE read content first so that we only advance cursor if IO operation succeeds
          inc_content = target_type == :file ? (::IO.read inc_path) : open(inc_path, 'r') {|f| f.read }
          shift
          push_include inc_content, inc_path, relpath, 1, attributes
        rescue
          warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{inc_path})
          replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
          return true
        end
      end
      true
    else
      false
    end
  end