# File lib/asciidoctor/substitutors.rb, line 460
  def sub_attributes data, opts = {}
    # normalizes data type to an array (string becomes single-element array)
    data = [data] if (input_is_string = ::String === data)
    doc_attrs, result = @document.attributes, []
    data.each do |line|
      reject = reject_if_empty = false
      line = line.gsub(AttributeReferenceRx) {
        # escaped attribute, return unescaped
        if $1 == RS || $4 == RS
          %({#{$2}})
        elsif $3
          case (args = $2.split ':', 3).shift
          when 'set'
            _, value = Parser.store_attribute args[0], args[1] || '', @document
            # 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 unless value
            reject_if_empty = true
            ''
          when 'counter2'
            @document.counter(*args)
            reject_if_empty = true
            ''
          else # 'counter'
            @document.counter(*args)
          end
        elsif doc_attrs.key?(key = $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 'drop'
            # QUESTION should we warn in this case?
            reject_if_empty = true
            ''
          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})
            $&
          else # 'skip'
            $&
          end
        end
      } if line.include? ATTR_REF_HEAD

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

    input_is_string ? result * LF : result
  end