class Asciidoctor::AttributeList - asciidoctor-1.5.6.2 Documentation

# class Asciidoctor::AttributeList

Public: Handles parsing AsciiDoc attribute lists into a Hash of key/value pairs. By default, attributes must each be separated by a comma and quotes may be used around the value. If a key is not detected, the value is assigned to a 1-based positional key, The positional attributes can be “rekeyed” when given a posattrs array either during parsing or after the fact.

Examples

    attrlist = Asciidoctor::AttributeList.new('astyle')

    attrlist.parse
    => {0 => 'astyle'}

    attrlist.rekey(['style'])
    => {'style' => 'astyle'}

    attrlist = Asciidoctor::AttributeList.new('quote, Famous Person, Famous Book (2001)')

    attrlist.parse(['style', 'attribution', 'citetitle'])
    => {'style' => 'quote', 'attribution' => 'Famous Person', 'citetitle' => 'Famous Book (2001)'}

### Constants

BACKSLASH
BlankRx
BoundaryRxs

  Public: Regular expressions for detecting the boundary of a value

EscapedQuotes

  Public: Regular expressions for unescaping quoted characters

NameRx

  Public: A regular expression for an attribute name (approx. name token from XML) TODO named attributes cannot contain dash characters

SkipRxs

  Public: Regular expressions for skipping blanks and delimiters

### Public Class Methods

new(source, block = nil, delimiter = ',') click to toggle source

    # File lib/asciidoctor/attribute_list.rb, line 51
    def initialize source, block = nil, delimiter = ','
      @scanner = ::StringScanner.new source
      @block = block
      @delimiter = delimiter
      @delimiter_skip_pattern = SkipRxs[delimiter]
      @delimiter_boundary_pattern = BoundaryRxs[delimiter]
      @attributes = nil
    end

rekey(attributes, pos_attrs) click to toggle source

    # File lib/asciidoctor/attribute_list.rb, line 86
    def self.rekey attributes, pos_attrs
      pos_attrs.each_with_index do |key, index|
        next unless key
        pos = index + 1
        if (val = attributes[pos])
          # QUESTION should we delete the positional key?
          attributes[key] = val
        end
      end

      attributes
    end

### Public Instance Methods

parse(posattrs = []) click to toggle source

    # File lib/asciidoctor/attribute_list.rb, line 64
    def parse posattrs = []
      # return if already parsed
      return @attributes if @attributes

      @attributes = {}
      # QUESTION do we want to store the attribute list as the zero-index attribute?
      #attributes[0] = @scanner.string
      index = 0

      while parse_attribute index, posattrs
        break if @scanner.eos?
        skip_delimiter
        index += 1
      end

      @attributes
    end

parse_attribute(index = 0, pos_attrs = []) click to toggle source

    # File lib/asciidoctor/attribute_list.rb, line 99
    def parse_attribute index = 0, pos_attrs = []
      single_quoted_value = false
      skip_blank
      # example: "quote"
      if (first = @scanner.peek(1)) == '"'
        name = parse_attribute_value @scanner.get_byte
        value = nil
      # example: 'quote'
      elsif first == '\'
        name = parse_attribute_value @scanner.get_byte
        value = nil
        single_quoted_value = true
      else
        name = scan_name

        skipped = 0
        c = nil
        if @scanner.eos?
          return false unless name
        else
          skipped = skip_blank || 0
          c = @scanner.get_byte
        end

        # example: quote
        if !c || c == @delimiter
          value = nil
        # example: Sherlock Holmes || =foo=
        elsif c != '=' || !name
          name = %Q(#{name}#{' ' * skipped}#{c}#{scan_to_delimiter})
          value = nil
        else
          skip_blank
          if @scanner.peek(1)
            # example: foo="bar" || foo="ba\"zaar"
            if (c = @scanner.get_byte) == '"'
              value = parse_attribute_value c
            # example: foo='bar' || foo='ba\'zaar' || foo='ba"zaar'
            elsif c == '\'
              value = parse_attribute_value c
              single_quoted_value = true
            # example: foo=,
            elsif c == @delimiter
              value = nil
            # example: foo=bar (all spaces ignored)
            else
              value = %Q(#{c}#{scan_to_delimiter})
              return true if value == 'None'
            end
          end
        end
      end

      if value
        # example: options="opt1,opt2,opt3"
        # opts is an alias for options
        case name
        when 'options', 'opts'
          if value.include? ','
            value = value.delete ' ' if value.include? ' '
            (value.split ',').each {|opt| @attributes[%Q(#{opt}-option)] = '' unless opt.empty? }
          else
            @attributes[%Q(#{value = value.strip}-option)] = ''
          end
          @attributes['options'] = value
        else
          if single_quoted_value && @block
            case name
            when 'title', 'reftext'
              @attributes[name] = value
            else
              @attributes[name] = @block.apply_subs value
            end
          else
            @attributes[name] = value
          end
        end
      else
        resolved_name = single_quoted_value && @block ? (@block.apply_subs name) : name
        if (pos_name = pos_attrs[index])
          @attributes[pos_name] = resolved_name
        end
        # QUESTION should we always assign the positional key?
        @attributes[index + 1] = resolved_name
        # QUESTION should we assign the resolved name as an attribute?
        #@attributes[resolved_name] = nil
      end

      true
    end

parse_attribute_value(quote) click to toggle source

    # File lib/asciidoctor/attribute_list.rb, line 190
    def parse_attribute_value quote
      # empty quoted value
      if @scanner.peek(1) == quote
        @scanner.get_byte
        return ''
      end

      if (value = scan_to_quote quote)
        @scanner.get_byte
        if value.include? BACKSLASH
          value.gsub EscapedQuotes[quote], quote
        else
          value
        end
      else
        %Q(#{quote}#{scan_to_delimiter})
      end
    end

parse_into(attributes, posattrs = []) click to toggle source

    # File lib/asciidoctor/attribute_list.rb, line 60
    def parse_into attributes, posattrs = []
      attributes.update(parse posattrs)
    end

rekey(posattrs) click to toggle source

    # File lib/asciidoctor/attribute_list.rb, line 82
    def rekey posattrs
      AttributeList.rekey @attributes, posattrs
    end

scan_name() click to toggle source

    # File lib/asciidoctor/attribute_list.rb, line 217
    def scan_name
      @scanner.scan NameRx
    end

scan_to_delimiter() click to toggle source

    # File lib/asciidoctor/attribute_list.rb, line 221
    def scan_to_delimiter
      @scanner.scan @delimiter_boundary_pattern
    end

scan_to_quote(quote) click to toggle source

    # File lib/asciidoctor/attribute_list.rb, line 225
    def scan_to_quote quote
      @scanner.scan BoundaryRxs[quote]
    end

skip_blank() click to toggle source

    # File lib/asciidoctor/attribute_list.rb, line 209
    def skip_blank
      @scanner.skip BlankRx
    end

skip_delimiter() click to toggle source

    # File lib/asciidoctor/attribute_list.rb, line 213
    def skip_delimiter
      @scanner.skip @delimiter_skip_pattern
    end