# File lib/asciidoctor/parser.rb, line 2107
  def self.store_attribute(name, value, doc = nil, attrs = nil)
    # TODO move processing of attribute value to utility method
    if name.end_with?('!')
      # a nil value signals the attribute should be deleted (undefined)
      value = nil
      name = name.chop
    elsif name.start_with?('!')
      # a nil value signals the attribute should be deleted (undefined)
      value = nil
      name = name[1..-1]
    end

    name = sanitize_attribute_name(name)
    accessible = true
    if doc
      # alias numbered attribute to sectnums
      if name == 'numbered'
        name = 'sectnums'
      # support relative leveloffset values
      elsif name == 'leveloffset'
        if value
          case value.chr
          when '+'
            value = ((doc.attr 'leveloffset', 0).to_i + (value[1..-1] || 0).to_i).to_s
          when '-'
            value = ((doc.attr 'leveloffset', 0).to_i - (value[1..-1] || 0).to_i).to_s
          end
        end
      end
      accessible = value ? doc.set_attribute(name, value) : doc.delete_attribute(name)
    end

    if accessible && attrs
      # NOTE lookup resolved value (resolution occurs inside set_attribute)
      value = doc.attributes[name] if value
      Document::AttributeEntry.new(name, value).save_to(attrs)
    end

    [name, value]
  end