# File lib/asciidoctor/substitutors.rb, line 447
  def sub_attributes data, opts = {}
    return data if data.nil_or_empty?

    # normalizes data type to an array (string becomes single-element array)
    if (string_data = ::String === data)
      data = [data]
    end

    doc_attrs = @document.attributes
    attribute_missing = nil
    result = []
    data.each do |line|
      reject = false
      reject_if_empty = false
      line = line.gsub(AttributeReferenceRx) {
        # alias match for Ruby 1.8.7 compat
        m = $~
        # escaped attribute, return unescaped
        if m[1] == '\\' || m[4] == '\\'
          %({#{m[2]}})
        elsif !m[3].nil_or_empty?
          offset = (directive = m[3]).length + 1
          expr = m[2][offset..-1]
          case directive
          when 'set'
            args = expr.split(':')
            _, value = Parser.store_attribute(args[0], args[1] || '', @document)
            unless value
              # since this is an assignment, only drop-line applies here (skip and drop imply the same result)
              if doc_attrs.fetch('attribute-undefined', Compliance.attribute_undefined) == 'drop-line'
                reject = true
                break ''
              end
            end
            reject_if_empty = true
            ''
          when 'counter', 'counter2'
            args = expr.split(':')
            val = @document.counter(args[0], args[1])
            if directive == 'counter2'
              reject_if_empty = true
              ''
            else
              val
            end
          else
            # if we get here, our AttributeReference regex is too loose
            warn %(asciidoctor: WARNING: illegal attribute directive: #{m[3]})
            m[0]
          end
        elsif doc_attrs.key?(key = m[2].downcase)
          doc_attrs[key]
        elsif INTRINSIC_ATTRIBUTES.key? key
          INTRINSIC_ATTRIBUTES[key]
        else
          case (attribute_missing ||= (opts[:attribute_missing] || doc_attrs.fetch('attribute-missing', Compliance.attribute_missing)))
          when 'skip'
            m[0]
          when 'drop-line'
            warn %(asciidoctor: WARNING: dropping line containing reference to missing attribute: #{key})
            reject = true
            break ''
          when 'warn'
            warn %(asciidoctor: WARNING: skipping reference to missing attribute: #{key})
            m[0]
          else # 'drop'
            # QUESTION should we warn in this case?
            reject_if_empty = true
            ''
          end
        end
      } if line.include? '{'

      result << line unless reject || (reject_if_empty && line.empty?)
    end

    string_data ? result * EOL : result
  end