# File lib/asciidoctor/parser.rb, line 2248
  def self.next_table(table_reader, parent, attributes)
    table = Table.new(parent, attributes)
    if attributes.key? 'title'
      table.title = attributes.delete 'title'
      table.assign_caption(attributes.delete 'caption')
    end

    if (attributes.key? 'cols') && !(colspecs = parse_colspecs attributes['cols']).empty?
      table.create_columns colspecs
      explicit_colspecs = true
    end

    skipped = table_reader.skip_blank_lines || 0
    parser_ctx = Table::ParserContext.new table_reader, table, attributes
    format, loop_idx, implicit_header_boundary = parser_ctx.format, -1, nil
    implicit_header = true unless skipped > 0 || (attributes.key? 'header-option') || (attributes.key? 'noheader-option')

    while (line = table_reader.read_line)
      if (loop_idx += 1) > 0 && line.empty?
        line = nil
        implicit_header_boundary += 1 if implicit_header_boundary
      elsif format == 'psv'
        if parser_ctx.starts_with_delimiter? line
          line = line.slice 1, line.length
          # push empty cell spec if cell boundary appears at start of line
          parser_ctx.close_open_cell
          implicit_header_boundary = nil if implicit_header_boundary
        else
          next_cellspec, line = parse_cellspec line, :start, parser_ctx.delimiter
          # if cellspec is not nil, we're at a cell boundary
          if next_cellspec
            parser_ctx.close_open_cell next_cellspec
            implicit_header_boundary = nil if implicit_header_boundary
          # otherwise, the cell continues from previous line
          elsif implicit_header_boundary && implicit_header_boundary == loop_idx
            implicit_header, implicit_header_boundary = false, nil
          end
        end
      end

      # NOTE implicit header is offset by at least one blank line; implicit_header_boundary tracks size of gap
      if loop_idx == 0 && implicit_header
        if table_reader.has_more_lines? && table_reader.peek_line.empty?
          implicit_header_boundary = 1
        else
          implicit_header = false
        end
      end

      # this loop is used for flow control; internal logic controls how many times it executes
      while true
        if line && (m = parser_ctx.match_delimiter line)
          case format
          when 'csv'
            if parser_ctx.buffer_has_unclosed_quotes? m.pre_match
              break if (line = parser_ctx.skip_past_delimiter m).empty?
              redo
            end
            parser_ctx.buffer = %(#{parser_ctx.buffer}#{m.pre_match})
          when 'dsv'
            if m.pre_match.end_with? '\\'
              if (line = parser_ctx.skip_past_escaped_delimiter m).empty?
                parser_ctx.buffer = %(#{parser_ctx.buffer}#{LF})
                parser_ctx.keep_cell_open
                break
              end
              redo
            end
            parser_ctx.buffer = %(#{parser_ctx.buffer}#{m.pre_match})
          else # psv
            if m.pre_match.end_with? '\\'
              if (line = parser_ctx.skip_past_escaped_delimiter m).empty?
                parser_ctx.buffer = %(#{parser_ctx.buffer}#{LF})
                parser_ctx.keep_cell_open
                break
              end
              redo
            end
            next_cellspec, cell_text = parse_cellspec m.pre_match
            parser_ctx.push_cellspec next_cellspec
            parser_ctx.buffer = %(#{parser_ctx.buffer}#{cell_text})
          end
          # don't break if empty to preserve empty cell found at end of line (see issue #1106)
          line = nil if (line = m.post_match).empty?
          parser_ctx.close_cell
        else
          # no other delimiters to see here; suck up this line into the buffer and move on
          parser_ctx.buffer = %(#{parser_ctx.buffer}#{line}#{LF})
          case format
          when 'csv'
            # QUESTION make stripping endlines in csv data an option? (unwrap-option?)
            parser_ctx.buffer = %(#{parser_ctx.buffer.rstrip} )
            if parser_ctx.buffer_has_unclosed_quotes?
              implicit_header, implicit_header_boundary = false, nil if implicit_header_boundary && loop_idx == 0
              parser_ctx.keep_cell_open
            else
              parser_ctx.close_cell true
            end
          when 'dsv'
            parser_ctx.close_cell true
          else # psv
            parser_ctx.keep_cell_open
          end
          break
        end
      end

      # NOTE cell may already be closed if table format is csv or dsv
      if parser_ctx.cell_open?
        parser_ctx.close_cell true unless table_reader.has_more_lines?
      else
        table_reader.skip_blank_lines || break
      end
    end

    unless (table.attributes['colcount'] ||= table.columns.size) == 0 || explicit_colspecs
      table.assign_column_widths
    end

    if implicit_header
      table.has_header_option = true
      attributes['header-option'] = ''
      attributes['options'] = (attributes.key? 'options') ? %(#{attributes['options']},header) : 'header'
    end

    table.partition_header_footer attributes

    table
  end