Spec-Zone.ru › Ruby 2.4

класс CSV

Родитель:
Объект
Включенные модули:
Enumerable

Этот класс предоставляет полный интерфейс для файлов CSV и данных. Он предлагает инструменты, которые позволяют вам читать и записывать данные из строк или объектов IO, по мере необходимости.

Чтение

Из файла

Строка за строкой

CSV.foreach("path/to/file.csv") do |row|
  # use row here...
end

Все сразу

arr_of_arrs = CSV.read("path/to/file.csv")

Из строки

Строка за строкой

CSV.parse("CSV,data,String") do |row|
  # use row here...
end

Все сразу

arr_of_arrs = CSV.parse("CSV,data,String")

Запись

В файл

CSV.open("path/to/file.csv", "wb") do |csv|
  csv << ["row", "of", "CSV", "data"]
  csv << ["another", "row"]
  # ...
end

В строку

csv_string = CSV.generate do |csv|
  csv << ["row", "of", "CSV", "data"]
  csv << ["another", "row"]
  # ...
end

Преобразование одной строки

csv_string = ["CSV", "data"].to_csv   # to CSV
csv_array  = "CSV,String".parse_csv   # from CSV

Интерфейс сокращений

CSV             { |csv_out| csv_out << %w{my data here} }  # to $stdout
CSV(csv = "")   { |csv_str| csv_str << %w{my data here} }  # to a String
CSV($stderr)    { |csv_err| csv_err << %w{my data here} }  # to $stderr
CSV($stdin)     { |csv_in|  csv_in.each { |row| p row } }  # from $stdin

Расширенное использование

Оборачивание объекта IO Объект

csv = CSV.new(io, options)
# ... read (with gets() or each()) from and write (with <<) to csv here ...

CSV и кодировки символов (M17n или многоязычие)

Этот новый парсер CSV умеет работать с кодировками. Парсер работает в кодировке объекта IO или строки, из которой читается или в которую записывается информация. Ваши данные никогда не транскодируются (если вы не попросите Руби это сделать), и они будут буквально обработаны в кодировке, в которой они хранятся. Таким образом, CSV вернёт массивы или строки строк в кодировке ваших данных. Это достигается путём транскодирования самого парсера в вашу кодировку.

Некоторая транскодировка, конечно, должна происходить, чтобы обеспечить поддержку множества кодировок. Например, :col_sep, :row_sep, и :quote_char должны быть транскодированы для соответствия вашим данным. Надеюсь, это делает весь процесс прозрачным, так как значения по умолчанию CSV должны просто автоматически работать с вашими данными. Однако вы можете вручную задать эти значения в целевой кодировке, чтобы избежать перевода.

Также важно отметить, что, хотя весь основной парсер CSV теперь независим от кодировки, некоторые функции таковыми не являются. Например, встроенные преобразователи попытаются транскодировать данные в UTF-8 перед выполнением преобразований. Опять же, вы можете предоставить пользовательские преобразователи, которые понимают ваши кодировки, чтобы избежать этого перевода. Для меня слишком сложно поддерживать родственные преобразования во всех кодировках Ruby.

В любом случае, практическая сторона этого проста: убедитесь, что объекты IO и строки, передаваемые в CSV, имеют установленную правильную кодировку, и всё должно работать. Методы CSV, которые позволяют вам открывать объекты IO (CSV::foreach(), ::open, ::read и ::readlines), позволяют вам указать кодировку.

Одно небольшое исключение возникает при генерации CSV в строку с кодировкой, которая несовместима с ASCII. Для CSV нет существующих данных, которые можно было бы использовать для подготовки, поэтому, вероятно, вам придётся вручную указать желаемую кодировку для большинства таких случаев. Однако он попытается угадать, используя поля в строке вывода, когда используется ::generate_line или Array#to_csv().

Я стараюсь указывать любые другие проблемы с кодировкой в документации методов по мере их появления.

Это было протестировано по мере моих возможностей со всеми не «фиктивными» кодировками, которые поставляются с Ruby. Однако это новый смелый код, и в нём могут быть ошибки. Пожалуйста, не стесняйтесь сообщать о любых проблемах, которые вы найдёте.

Константы

ConverterEncoding

Кодировка, используемая всеми преобразователями.

Converters

Этот хеш содержит встроенные преобразователи CSV, доступные по имени. Вы можете выбрать преобразователи с помощью #convert или через переданный options хеш в ::new.

:integer

Преобразует любой тип поля, который принимает Integer().

:float

Преобразует любое поле, которое принимает Float().

:numeric

Комбинация :integer и :float.

:date

Преобразует любое поле, которое принимает Date.parse.

:date_time

Преобразует любое поле, которое принимает DateTime.parse.

:all

Все встроенные преобразователи. Комбинация :date_time и :numeric.

Все встроенные преобразователи транскодируют данные поля в UTF-8 перед выполнением преобразования. Если данные не могут быть транскодированы в UTF-8, преобразование завершится неудачно, и поле останется неизменным.

Этот хеш намеренно оставлен не замороженным, и пользователи могут добавлять в него значения, доступные для всех объектов CSV.

Для добавления комбинированного поля значение должно быть массивом имён. Комбинированные поля могут быть вложены друг в друга.

DEFAULT_OPTIONS

Параметры, используемые, когда вызывающий код не предоставляет альтернативные значения. Они следующие:

:col_sep

","

:row_sep

:auto

:quote_char

'"'

:field_size_limit

nil

:converters

nil

:unconverted_fields

nil

:headers

false

:return_headers

false

:header_converters

nil

:skip_blanks

false

:force_quotes

false

:skip_lines

nil

:liberal_parsing

false

DateMatcher

Регулярное выражение (Regexp), используемое для поиска и преобразования некоторых распространённых форматов дат (Date).

DateTimeMatcher

Регулярное выражение (Regexp), используемое для поиска и преобразования некоторых распространённых форматов DateTime.

FieldInfo

Структура Struct FieldInfo содержит данные о позиции поля в источнике данных, из которого оно было прочитано. CSV передаст эту структуру Struct в некоторые блоки, которые принимают решения на основе структуры поля. См. #convert_fields для примера.

index

Индекс поля в строке (нумерация с нуля).

line

Строка источника данных, из которой взята эта строка.

header

Заголовок столбца, если он доступен.

HeaderConverters

Этот хеш содержит встроенные преобразователи заголовков CSV, доступные по имени. Вы можете выбрать HeaderConverters с помощью #header_convert или через options хеш в ::new.

:downcase

Вызывает downcase() для строкового заголовка.

:symbol

Удаляет ведущие и хвостовые пробелы, преобразует строку в нижний регистр, заменяет оставшиеся пробелы подчёркиваниями, удаляет небуквенно-цифровые символы и, наконец, вызывает to_sym().

Все встроенные преобразователи заголовков транскодируют данные заголовков в UTF-8 перед выполнением преобразования. Если данные не могут быть транскодированы в UTF-8, преобразование завершится неудачно, и заголовок останется неизменным.

Этот хеш намеренно оставлен не замороженным, и пользователи могут добавлять в него значения, доступные для всех объектов CSV.

Для добавления комбинированного поля значение должно быть массивом имён. Комбинированные поля могут быть вложены друг в друга.

VERSION

Версия установленной библиотеки.

Атрибуты

col_sep[R]

Кодированный :col_sep, используемый при разборе и записи. Подробнее см. ::new.

encoding[R]

Кодировка Encoding, в которой CSV выполняет разбор или запись. Это кодировка, в которой вы получаете данные после разбора и/или кодировка, в которой будут записаны данные.

field_size_limit[R]

Предел размера поля, если он задан. Подробнее см. ::new.

lineno[R]

Номер строки последней прочитанной строки из этого файла. Поля с вложенными символами конца строки не повлияют на этот счётчик.

quote_char[R]

Кодированный :quote_char, используемый при разборе и записи. Подробнее см. ::new.

row_sep[R]

Кодированный :row_sep, используемый при разборе и записи. Подробнее см. ::new.

skip_lines[R]

Регулярное выражение, определяющее строку как комментарий. Подробности см. в ::new.

END_OF_DOCUMENT_MARKER

Методы публичного класса

filter( options = Hash.new ) { |row| ... } Показать исходный код
filter( input, options = Hash.new ) { |row| ... }
filter( input, output, options = Hash.new ) { |row| ... }
# File lib/csv.rb, line 1102
def self.filter(*args)
  # parse options for input, output, or both
  in_options, out_options = Hash.new, {row_sep: $INPUT_RECORD_SEPARATOR}
  if args.last.is_a? Hash
    args.pop.each do |key, value|
      case key.to_s
      when /\Ain(?:put)?_(.+)\Z/
        in_options[$1.to_sym] = value
      when /\Aout(?:put)?_(.+)\Z/
        out_options[$1.to_sym] = value
      else
        in_options[key]  = value
        out_options[key] = value
      end
    end
  end
  # build input and output wrappers
  input  = new(args.shift || ARGF,    in_options)
  output = new(args.shift || $stdout, out_options)

  # read, yield, write
  input.each do |row|
    yield row
    output << row
  end
end

Этот метод предоставляет удобный способ создания фильтров в стиле Unix для данных CSV. Каждая строка передаётся в предоставленный блок, который может её изменить по мере необходимости. После возвращения блока, строка добавляется к output изменённой или нет.

Аргументы input и output могут быть любыми, которые принимает ::new (обычно объекты String или IO). Если не указаны, они по умолчанию равны ARGF и $stdout.

Параметр options также отфильтровывается ::new после некоторых хитроумных операций с разбором ключей. Любой ключ, начинающийся с :in_ или :input_ будет иметь этот префикс удалён и будет использован только в options Hash для объекта input. Ключи, начинающиеся с :out_ или :output_ влияют только на output. Все остальные ключи назначаются обоим объектам.

Значение :output_row_sep option по умолчанию равно $INPUT_RECORD_SEPARATOR ($/).

foreach(path, options = Hash.new, &block) Показать исходный код
# File lib/csv.rb, line 1143
def self.foreach(path, options = Hash.new, &block)
  return to_enum(__method__, path, options) unless block
  open(path, options) do |csv|
    csv.each(&block)
  end
end

Этот метод предназначен в качестве основного интерфейса для чтения файлов CSV. Вы передаёте path и любые options параметры, которые вы хотите установить для чтения. Каждая строка файла будет передана в предоставленный block по очереди.

Параметр options может быть любым, что понимает ::new. Этот метод также понимает дополнительный параметр :encoding, который вы можете использовать для указания кодировки данных в читаемом файле. Вы должны указать его, если ваши данные не в формате Encoding.default_external. CSV будет использовать его для определения способа парсинга данных. Вы можете указать вторую кодировку, чтобы данные были транскодированы по мере их чтения. Например, encoding: "UTF-32BE:UTF-8" будет читать данные UTF-32BE из файла, но транскодировать их в UTF-8 перед тем, как CSV их распарсит.

generate( str, options = Hash.new ) { |csv| ... } Показать исходный код
generate( options = Hash.new ) { |csv| ... }
# File lib/csv.rb, line 1168
def self.generate(*args)
  # add a default empty String, if none was given
  if args.first.is_a? String
    io = StringIO.new(args.shift)
    io.seek(0, IO::SEEK_END)
    args.unshift(io)
  else
    encoding = args[-1][:encoding] if args.last.is_a?(Hash)
    str      = String.new
    str.force_encoding(encoding) if encoding
    args.unshift(str)
  end
  csv = new(*args)  # wrap
  yield csv         # yield for appending
  csv.string        # return final String
end

Этот метод оборачивает переданную вами строку, или по умолчанию пустую строку, в объект CSV, который передаётся в предоставленный блок. Вы можете использовать блок для добавления строк CSV в строку, и по выходе из блока, возвращаемая строка будет возвращена.

Обратите внимание, что переданная строка модифицируется этим методом. Вызовите dup() перед передачей, если вам нужна новая строка.

Параметр options может быть любым, что понимает ::new. Этот метод понимает дополнительный параметр :encoding , когда не передаётся строка, для установки базовой кодировки для вывода. CSV нуждается в этой подсказке, если вы планируете выводить данные, несовместимые с ASCII.

generate_line(row, options = Hash.new) Показать исходный код
# File lib/csv.rb, line 1198
def self.generate_line(row, options = Hash.new)
  options  = {row_sep: $INPUT_RECORD_SEPARATOR}.merge(options)
  encoding = options.delete(:encoding)
  str      = String.new
  if encoding
    str.force_encoding(encoding)
  elsif field = row.find { |f| not f.nil? }
    str.force_encoding(String(field).encoding)
  end
  (new(str, options) << row).string
end

Этот метод — сокращение для преобразования одной строки (массива) в строку CSV.

Параметр options может быть любым, что понимает ::new. Этот метод понимает дополнительный параметр :encoding для установки базовой кодировки вывода. Этот метод попытается угадать вашу кодировку по первому не-nil полю в row, если это возможно, но вам может потребоваться использовать этот параметр как запасной вариант.

Значение :row_sep option по умолчанию равно $INPUT_RECORD_SEPARATOR ($/).

instance(data = $stdout, options = Hash.new) { |instance| ... } Показать исходный код
# File lib/csv.rb, line 1062
def self.instance(data = $stdout, options = Hash.new)
  # create a _signature_ for this method call, data object and options
  sig = [data.object_id] +
        options.values_at(*DEFAULT_OPTIONS.keys.sort_by { |sym| sym.to_s })

  # fetch or create the instance for this signature
  @@instances ||= Hash.new
  instance    =   (@@instances[sig] ||= new(data, options))

  if block_given?
    yield instance  # run block, if given, returning result
  else
    instance        # or return the instance
  end
end

Этот метод вернёт экземпляр CSV, как и ::new, но экземпляр будет кэширован и возвращён для всех последующих вызовов этого метода для того же объекта data (проверяется с помощью Object#object_id) с теми же options.

Если указан блок, экземпляр передаётся в блок, и возвращаемое значение блока становится возвращаемым значением.

new(data, options = Hash.new) Показать исходный код
# File lib/csv.rb, line 1527
def initialize(data, options = Hash.new)
  if data.nil?
    raise ArgumentError.new("Cannot parse nil as CSV")
  end

  # build the options for this read/write
  options = DEFAULT_OPTIONS.merge(options)

  # create the IO object we will read from
  @io       = data.is_a?(String) ? StringIO.new(data) : data
  # honor the IO encoding if we can, otherwise default to ASCII-8BIT
  @encoding = raw_encoding(nil) ||
              ( if encoding = options.delete(:internal_encoding)
                  case encoding
                  when Encoding; encoding
                  else Encoding.find(encoding)
                  end
                end ) ||
              ( case encoding = options.delete(:encoding)
                when Encoding; encoding
                when /\A[^:]+/; Encoding.find($&)
                end ) ||
              Encoding.default_internal || Encoding.default_external
  #
  # prepare for building safe regular expressions in the target encoding,
  # if we can transcode the needed characters
  #
  @re_esc   =   "\\".encode(@encoding).freeze rescue ""
  @re_chars =   /#{%"[-\\]\\[\\.^$?*+{}()|# \r\n\t\f\v]".encode(@encoding)}/

  init_separators(options)
  init_parsers(options)
  init_converters(options)
  init_headers(options)
  init_comments(options)

  @force_encoding = !!(encoding || options.delete(:encoding))
  options.delete(:internal_encoding)
  options.delete(:external_encoding)
  unless options.empty?
    raise ArgumentError, "Unknown options:  #{options.keys.join(', ')}."
  end

  # track our own lineno since IO gets confused about line-ends is CSV fields
  @lineno = 0
end

Этот конструктор будет оборачивать объект String или IO, переданный в data для чтения и/или записи. В дополнение к методам экземпляра CSV, делегируются несколько методов IO. (См. ::open для полного списка.) Если вы передадите String для data, вы можете позже получить его (например, после записи в него) с помощью CSV.string().

Обратите внимание, что обернутая строка будет расположена в начале (для чтения). Если вы хотите расположить её в конце (для записи), используйте ::generate. Если вам нужно другое расположение, передайте предварительно заданный объект StringIO.

Вы можете установить любые параметры чтения и/или записи в options Hash. Доступные параметры:

:col_sep

Строка, размещаемая между каждым полем. Эта строка будет преобразована в кодировку данных Encoding перед разбором.

:row_sep

Строка, добавляемая в конец каждой строки. Это может быть установлено в специальное значение :auto, которое запрашивает, чтобы CSV автоматически обнаружил его из данных. Автообнаружение читает вперёд в данных, ища следующий "\r\n", "\n", или "\r" последовательность. Последовательность будет выбрана даже если она встречается в цитируемом поле, предполагая, что у вас там будут одинаковые символы конца строки. Если ни одна из этих последовательностей не найдена, data это ARGF, STDIN, STDOUT, или STDERR, или поток доступен только для вывода, используется значение по умолчанию $INPUT_RECORD_SEPARATOR ($/). Очевидно, обнаружение занимает немного времени. Ручная установка с помощью Set важна, если скорость имеет значение. Также обратите внимание, что объекты IO на Windows должны быть открыты в двоичном режиме, если эта функция будет использоваться, так как перевод символов конца строки может вызвать проблемы с восстановлением позиции документа до той, которая была до предварительного чтения. Эта строка будет преобразована в кодировку данных Encoding перед разбором.

:quote_char

Символ, используемый для цитирования полей. Это должна быть строка с одним символом. Это полезно для приложений, которые неправильно используют ' в качестве символа цитирования вместо правильного ". CSV всегда будет рассматривать двойное повторение этого символа как экранированный символ цитирования. Эта строка будет преобразована в кодировку данных Encoding перед разбором.

:field_size_limit

Это максимальный размер, до которого CSV будет читать вперёд, ища закрывающий символ цитирования для поля. (На самом деле, он читает до первой строки, идущей за этим размером.) Если символ цитирования не может быть найден в пределах лимита, CSV выбросит исключение MalformedCSVError, предполагая, что данные некорректны. Вы можете использовать этот лимит для предотвращения атак типа DoS на парсер. Однако этот лимит может привести к отказу в легитимном разборе, и поэтому по умолчанию установлен на nil или выключен.

:converters

Массив имён из Converters Hash и/или лямбда-функций, которые обрабатывают пользовательское преобразование. Один преобразователь не обязательно должен быть в массиве. Все встроенные преобразователи пытаются преобразовать поля в UTF-8 перед преобразованием. Преобразование завершится неудачей, если данные не могут быть преобразованы, оставив поле неизменным.

:unconverted_fields

Если установлено в true, методу unconverted_fields() будет добавлен ко всем возвращаемым строкам (массив или CSV::Row), который вернёт поля в том виде, в котором они были до преобразования. Обратите внимание, что :headers, предоставленные массивом или строкой, не были полями документа, и поэтому к ним будет прикреплён пустой массив.

:headers

Если установлено в :first_row или true, начальная строка файла CSV будет обрабатываться как строка заголовков. Если установлено в массив, содержимое будет использоваться как заголовки. Если установлено в строку, строка передаётся в вызов ::parse_line с теми же :col_sep, :row_sep, и :quote_char, что и в этом экземпляре, чтобы получить массив заголовков. Это настройка вызывает #shift возвращать строки как объекты CSV::Row вместо массивов и #read возвращать объекты CSV::Table вместо массива массивов.

:return_headers

Когда false, строки заголовков молча игнорируются. Если установлено в true, строки заголовков возвращаются в объекте CSV::Row с идентичными заголовками и полями (за исключением того, что поля не проходят через преобразователи).

:write_headers

Когда true и :headers установлены, в выходные данные будет добавлен заголовок.

:header_converters

Идентично по функциональности :converters, за исключением того, что преобразования производятся только для строк заголовков. Все встроенные преобразователи пытаются преобразовать заголовки в UTF-8 перед преобразованием. Преобразование завершится неудачей, если данные не могут быть преобразованы, оставив заголовок неизменным.

:skip_blanks

Когда установлено в значение true, CSV будет пропускать пустые строки. Обратите внимание, что эта настройка не будет пропускать строки, содержащие разделители столбцов, даже если в строках нет фактических данных. Если вы хотите пропустить строки, содержащие разделители, но без содержимого, рассмотрите использование :skip_lines, или проверку пустоты массива полей каждой строки с помощью fields.compact.empty?.

:force_quotes

Когда установлено в значение true, CSV будет заключать все созданные поля CSV в кавычки.

:skip_lines

Когда установлено в объект, реагирующий на match, каждая строка, соответствующая ему, считается комментарием и игнорируется во время разбора. Когда установлено в строку, она сначала преобразуется в Regexp. Когда установлено в nil, ни одна строка не считается комментарием. Если переданный объект не реагирует на match, выбрасывается ArgumentError.

:liberal_parsing

Когда установлено в значение true, CSV попытается проанализировать входные данные, не соответствующие RFC 4180, такие как двойные кавычки в нецитируемых полях.

См. CSV::DEFAULT_OPTIONS для значений по умолчанию.

Параметры не могут быть переопределены в методах экземпляра по причинам производительности, поэтому убедитесь, что вы установили нужные значения здесь.

open( filename, mode = "rb", options = Hash.new ) { |faster_csv| ... } Показать исходный код
open( filename, options = Hash.new ) { |faster_csv| ... }
open( filename, mode = "rb", options = Hash.new )
open( filename, options = Hash.new )
# File lib/csv.rb, line 1273
def self.open(*args)
  # find the +options+ Hash
  options = if args.last.is_a? Hash then args.pop else Hash.new end
  # wrap a File opened with the remaining +args+ with no newline
  # decorator
  file_opts = {universal_newline: false}.merge(options)
  begin
    f = File.open(*args, file_opts)
  rescue ArgumentError => e
    raise unless /needs binmode/ =~ e.message and args.size == 1
    args << "rb"
    file_opts = {encoding: Encoding.default_external}.merge(file_opts)
    retry
  end
  begin
    csv = new(f, options)
  rescue Exception
    f.close
    raise
  end

  # handle blocks like Ruby's open(), not like the CSV library
  if block_given?
    begin
      yield csv
    ensure
      csv.close
    end
  else
    csv
  end
end

Этот метод открывает объект IO и оборачивает его с помощью CSV. Это предназначено как основной интерфейс для записи файла CSV.

Вы должны передать filename и можете по желанию добавить mode для Ruby's open(). Вы также можете передать необязательный Hash, содержащий любые options, которые понимает ::new в качестве последнего аргумента.

Этот метод работает так же, как вызов Ruby's open(), в том смысле, что он передаёт объект CSV в предоставленный блок и закрывает его, когда блок завершается, или возвращает объект CSV, если блок не предоставлен. (Примечание: Это отличается от библиотеки Ruby 1.8 CSV, которая передавала строки в блок. Используйте ::foreach для такого поведения.)

Вы должны предоставить mode с встроенным указателем кодировки Encoding, если ваши данные не в Encoding.default_external. CSV будет проверять кодировку Encoding базового объекта IO (установленную переданным вами mode) для определения того, как проанализировать данные. Вы можете указать вторую кодировку Encoding, чтобы данные были преобразованы при чтении, как вы можете сделать с обычным вызовом IO.open. Например, "rb:UTF-32BE:UTF-8" будет читать данные UTF-32BE из файла, но преобразовывать их в UTF-8 перед разбором CSV.

Открытый объект CSV будет делегировать многие методы IO для удобства. Вы можете вызвать:

  • binmode()

  • binmode?()

  • close()

  • close_read()

  • close_write()

  • closed?()

  • eof()

  • eof?()

  • external_encoding()

  • fcntl()

  • fileno()

  • flock()

  • flush()

  • fsync()

  • internal_encoding()

  • ioctl()

  • isatty()

  • path()

  • pid()

  • pos()

  • pos=()

  • reopen()

  • seek()

  • stat()

  • sync()

  • sync=()

  • tell()

  • to_i()

  • to_io()

  • truncate()

  • tty?()

parse( str, options = Hash.new ) { |row| ... } Показать исходный код
parse( str, options = Hash.new )
# File lib/csv.rb, line 1318
def self.parse(*args, &block)
  csv = new(*args)
  if block.nil?  # slurp contents, if no block is given
    begin
      csv.read
    ensure
      csv.close
    end
  else           # or pass each row to a provided block
    csv.each(&block)
  end
end

Этот метод можно использовать для удобного разбора CSV данных из строки. Вы можете либо предоставить block, который будет вызываться для каждой строки строки по очереди, либо просто использовать возвращённый массив массивов (если block не указан).

Вы передаёте строку, из которой нужно читать, и необязательный str Hash, содержащий всё, что понимает ::new.

parse_line(line, options = Hash.new) Показать исходный код
# File lib/csv.rb, line 1338
def self.parse_line(line, options = Hash.new)
  new(line, options).shift
end

Этот метод — сокращение для преобразования одной строки строки CSV в массив. Обратите внимание, что если line содержит несколько строк, то всё, что идёт после первой строки, игнорируется.

Параметр options может быть любым, что понимает ::new.

read(path, *options) Показать исходный код
# File lib/csv.rb, line 1353
def self.read(path, *options)
  open(path, *options) { |csv| csv.read }
end

Используйте для считывания файла CSV в массив массивов. Передайте path в файл и любые options ::new понимает. Этот метод также понимает дополнительный параметр :encoding, который можно использовать для указания кодировки данных в считываемом файле. Его необходимо предоставить, если ваши данные не в Encoding.default_external. CSV будет использовать его для определения того, как обрабатывать данные. Вы можете указать вторую кодировку, чтобы данные были транскодированы при чтении. Например, encoding: "UTF-32BE:UTF-8" считывает данные UTF-32BE из файла, но транскодирует их в UTF-8 перед разбором CSV.

readlines(*args) Показать исходный код
# File lib/csv.rb, line 1358
def self.readlines(*args)
  read(*args)
end

Псевдоним для ::read.

table(path, options = Hash.new) Показать исходный код
# File lib/csv.rb, line 1369
def self.table(path, options = Hash.new)
  read( path, { headers:           true,
                converters:        :numeric,
                header_converters: :symbol }.merge(options) )
end

Сокращение для:

CSV.read( path, { headers:           true,
                  converters:        :numeric,
                  header_converters: :symbol }.merge(options) )

Методы экземпляра public

<<(row) Показать исходный код
# File lib/csv.rb, line 1686
def <<(row)
  # make sure headers have been assigned
  if header_row? and [Array, String].include? @use_headers.class
    parse_headers  # won't read data for Array or String
    self << @headers if @write_headers
  end

  # handle CSV::Row objects and Hashes
  row = case row
        when self.class::Row then row.fields
        when Hash            then @headers.map { |header| row[header] }
        else                      row
        end

  @headers =  row if header_row?
  @lineno  += 1

  output = row.map(&@quote).join(@col_sep) + @row_sep  # quote and separate
  if @io.is_a?(StringIO)             and
     output.encoding != (encoding = raw_encoding)
    if @force_encoding
      output = output.encode(encoding)
    elsif (compatible_encoding = Encoding.compatible?(@io.string, output))
      @io.set_encoding(compatible_encoding)
      @io.seek(0, IO::SEEK_END)
    end
  end
  @io << output

  self  # for chaining
end

Основной метод записи для обернутых строк и ввода-вывода, row (массив или CSV::Row) преобразуется в CSV и добавляется к источнику данных. Когда передаётся CSV::Row, только поля row's fields() добавляются в вывод.

Источник данных должен быть открыт для записи.

Также алиасы: add_row, puts
add_row(row)
Псевдоним для: <<
convert( name ) Показать исходный код
convert { |field| ... }
convert { |field, field_info| ... }
# File lib/csv.rb, line 1735
def convert(name = nil, &converter)
  add_converter(:converters, self.class::Converters, name, &converter)
end

Вы можете использовать этот метод для установки встроенных CSV::Converters или для предоставления блока, обрабатывающего пользовательское преобразование.

Если вы предоставляете блок, принимающий один аргумент, он получит поле и должен вернуть преобразованное значение или само поле. Если ваш блок принимает два аргумента, он также получит CSV::FieldInfo Struct, содержащий подробности о поле. Опять же, блок должен вернуть преобразованное поле или само поле.

converters() Показать исходный код
# File lib/csv.rb, line 1600
def converters
  @converters.map do |converter|
    name = Converters.rassoc(converter)
    name ? name.first : converter
  end
end

Возвращает текущий список конвертеров. Подробности см. в ::new. Встроенные конвертеры будут возвращены по имени, а другие — как есть.

each() { |row| ... } Показать исходный код
# File lib/csv.rb, line 1766
def each
  if block_given?
    while row = shift
      yield row
    end
  else
    to_enum
  end
end

Поочередно возвращает каждую строку источника данных.

Поддержка Enumerable.

Источник данных должен быть открыт для чтения.

force_quotes?() Показать исходный код
# File lib/csv.rb, line 1643
def force_quotes?()       @force_quotes       end

Возвращает true , если все выходные поля цитируются. Подробности см. в ::new.

gets()
Псевдоним для: shift
header_convert( name ) Показать исходный код
header_convert { |field| ... }
header_convert { |field, field_info| ... }
# File lib/csv.rb, line 1750
def header_convert(name = nil, &converter)
  add_converter( :header_converters,
                 self.class::HeaderConverters,
                 name,
                 &converter )
end

Идентично #convert, но для заголовочных строк.

Обратите внимание, что этот метод необходимо вызвать до чтения заголовочных строк, чтобы он подействовал.

header_converters() Показать исходный код
# File lib/csv.rb, line 1631
def header_converters
  @header_converters.map do |converter|
    name = HeaderConverters.rassoc(converter)
    name ? name.first : converter
  end
end

Возвращает текущий список конвертеров, действующих для заголовков. Подробности см. в ::new. Встроенные конвертеры будут возвращены по имени, а другие — как есть.

header_row?() Показать исходный код
# File lib/csv.rb, line 1792
def header_row?
  @use_headers and @headers.nil?
end

Возвращает true , если следующая считываемая строка будет заголовочной строкой.

headers() Показать исходный код
# File lib/csv.rb, line 1616
def headers
  @headers || true if @use_headers
end

Возвращает nil , если заголовки не будут использоваться, true , если они будут, но ещё не были прочитаны, или фактические заголовки после их чтения. Подробности см. в ::new.

inspect() Показать исходный код
# File lib/csv.rb, line 1960
def inspect
  str = ["<#", self.class.to_s, " io_type:"]
  # show type of wrapped IO
  if    @io == $stdout then str << "$stdout"
  elsif @io == $stdin  then str << "$stdin"
  elsif @io == $stderr then str << "$stderr"
  else                      str << @io.class.to_s
  end
  # show IO.path(), if available
  if @io.respond_to?(:path) and (p = @io.path)
    str << " io_path:" << p.inspect
  end
  # show encoding
  str << " encoding:" << @encoding.name
  # show other attributes
  %w[ lineno     col_sep     row_sep
      quote_char skip_blanks liberal_parsing ].each do |attr_name|
    if a = instance_variable_get("@#{attr_name}")
      str << " " << attr_name << ":" << a.inspect
    end
  end
  if @use_headers
    str << " headers:" << headers.inspect
  end
  str << ">"
  begin
    str.join('')
  rescue  # any encoding error
    str.map do |s|
      e = Encoding::Converter.asciicompat_encoding(s.encoding)
      e ? s.encode(e) : s.force_encoding("ASCII-8BIT")
    end.join('')
  end
end

Возвращает упрощённое описание ключевых атрибутов CSV в совместимой с ASCII строке.

liberal_parsing?() Показать исходный код
# File lib/csv.rb, line 1645
def liberal_parsing?()    @liberal_parsing    end

Возвращает true , если некорректный ввод обрабатывается. Подробности см. в ::new.

puts(row)
Псевдоним для: <<
read() Показать исходный код
# File lib/csv.rb, line 1781
def read
  rows = to_a
  if @use_headers
    Table.new(rows)
  else
    rows
  end
end

Считывает оставшиеся строки и возвращает массив массивов.

Источник данных должен быть открыт для чтения.

Также алиасы: readlines
readline()
Псевдоним для: shift
readlines()
Псевдоним для: read
return_headers?() Показать исходный код
# File lib/csv.rb, line 1623
def return_headers?()     @return_headers     end

Возвращает true , если заголовки будут возвращены как строка результатов. Подробности см. в ::new.

rewind() Показать исходный код
# File lib/csv.rb, line 1670
def rewind
  @headers = nil
  @lineno  = 0

  @io.rewind
end

Перематывает объект IO и сбрасывает счётчик строк CSV.

shift() Показать исходный код
# File lib/csv.rb, line 1803
def shift
  #########################################################################
  ### This method is purposefully kept a bit long as simple conditional ###
  ### checks are faster than numerous (expensive) method calls.         ###
  #########################################################################

  # handle headers not based on document content
  if header_row? and @return_headers and
     [Array, String].include? @use_headers.class
    if @unconverted_fields
      return add_unconverted_fields(parse_headers, Array.new)
    else
      return parse_headers
    end
  end

  #
  # it can take multiple calls to <tt>@io.gets()</tt> to get a full line,
  # because of \r and/or \n characters embedded in quoted fields
  #
  in_extended_col = false
  csv             = Array.new

  loop do
    # add another read to the line
    unless parse = @io.gets(@row_sep)
      return nil
    end

    parse.sub!(@parsers[:line_end], "")

    if csv.empty?
      #
      # I believe a blank line should be an <tt>Array.new</tt>, not Ruby 1.8
      # CSV's <tt>[nil]</tt>
      #
      if parse.empty?
        @lineno += 1
        if @skip_blanks
          next
        elsif @unconverted_fields
          return add_unconverted_fields(Array.new, Array.new)
        elsif @use_headers
          return self.class::Row.new(Array.new, Array.new)
        else
          return Array.new
        end
      end
    end

    next if @skip_lines and @skip_lines.match parse

    parts =  parse.split(@col_sep, -1)
    if parts.empty?
      if in_extended_col
        csv[-1] << @col_sep   # will be replaced with a @row_sep after the parts.each loop
      else
        csv << nil
      end
    end

    # This loop is the hot path of csv parsing. Some things may be non-dry
    # for a reason. Make sure to benchmark when refactoring.
    parts.each do |part|
      if in_extended_col
        # If we are continuing a previous column
        if part[-1] == @quote_char && part.count(@quote_char) % 2 != 0
          # extended column ends
          csv[-1] = csv[-1].push(part[0..-2]).join("")
          if csv.last =~ @parsers[:stray_quote]
            raise MalformedCSVError,
                  "Missing or stray quote in line #{lineno + 1}"
          end
          csv.last.gsub!(@quote_char * 2, @quote_char)
          in_extended_col = false
        else
          csv.last.push(part, @col_sep)
        end
      elsif part[0] == @quote_char
        # If we are starting a new quoted column
        if part.count(@quote_char) % 2 != 0
          # start an extended column
          csv << [part[1..-1], @col_sep]
          in_extended_col =  true
        elsif part[-1] == @quote_char
          # regular quoted column
          csv << part[1..-2]
          if csv.last =~ @parsers[:stray_quote]
            raise MalformedCSVError,
                  "Missing or stray quote in line #{lineno + 1}"
          end
          csv.last.gsub!(@quote_char * 2, @quote_char)
        elsif @liberal_parsing
          csv << part
        else
          raise MalformedCSVError,
                "Missing or stray quote in line #{lineno + 1}"
        end
      elsif part =~ @parsers[:quote_or_nl]
        # Unquoted field with bad characters.
        if part =~ @parsers[:nl_or_lf]
          raise MalformedCSVError, "Unquoted fields do not allow " +
                                   "\\r or \\n (line #{lineno + 1})."
        else
          if @liberal_parsing
            csv << part
          else
            raise MalformedCSVError, "Illegal quoting in line #{lineno + 1}."
          end
        end
      else
        # Regular ole unquoted field.
        csv << (part.empty? ? nil : part)
      end
    end

    # Replace tacked on @col_sep with @row_sep if we are still in an extended
    # column.
    csv[-1][-1] = @row_sep if in_extended_col

    if in_extended_col
      # if we're at eof?(), a quoted field wasn't closed...
      if @io.eof?
        raise MalformedCSVError,
              "Unclosed quoted field on line #{lineno + 1}."
      elsif @field_size_limit and csv.last.sum(&:size) >= @field_size_limit
        raise MalformedCSVError, "Field size exceeded on line #{lineno + 1}."
      end
      # otherwise, we need to loop and pull some more data to complete the row
    else
      @lineno += 1

      # save fields unconverted fields, if needed...
      unconverted = csv.dup if @unconverted_fields

      # convert fields, if needed...
      csv = convert_fields(csv) unless @use_headers or @converters.empty?
      # parse out header rows and handle CSV::Row conversions...
      csv = parse_headers(csv)  if     @use_headers

      # inject unconverted fields and accessor, if requested...
      if @unconverted_fields and not csv.respond_to? :unconverted_fields
        add_unconverted_fields(csv, unconverted)
      end

      # return the results
      break csv
    end
  end
end

Основной метод чтения для обернутых строк и ввода-вывода, одна строка извлекается из источника данных, анализируется и возвращается в виде массива полей (если заголовочные строки не используются) или CSV::Row (при использовании заголовочных строк).

Источник данных должен быть открыт для чтения.

Также алиасы: gets, readline
skip_blanks?() Показать исходный код
# File lib/csv.rb, line 1641
def skip_blanks?()        @skip_blanks        end

Возвращает true , если пустые строки пропускаются анализатором. Подробности см. в ::new.

unconverted_fields?() Показать исходный код
# File lib/csv.rb, line 1610
def unconverted_fields?() @unconverted_fields end

Возвращает true , если unconverted_fields() преобразованы в результаты парсинга. Подробности см. в ::new.

write_headers?() Показать исходный код
# File lib/csv.rb, line 1625
def write_headers?()      @write_headers      end

Возвращает true если заголовки записаны в выходной поток. Подробности см. в ::new.

Приватные методы экземпляра

add_converter(var_name, const, name = nil, &converter) Показать исходный код
# File lib/csv.rb, line 2200
def add_converter(var_name, const, name = nil, &converter)
  if name.nil?  # custom converter
    instance_variable_get("@#{var_name}") << converter
  else          # named converter
    combo = const[name]
    case combo
    when Array  # combo converter
      combo.each do |converter_name|
        add_converter(var_name, const, converter_name)
      end
    else        # individual named converter
      instance_variable_get("@#{var_name}") << combo
    end
  end
end

Фактический метод добавления преобразователей, используемый как #convert, так и #header_convert.

Этот метод требует var_name переменной экземпляра для размещения преобразователей, const Hash для поиска именованных преобразователей, а также стандартные параметры методов #convert и #header_convert.

add_unconverted_fields(row, fields) Показать исходный код
# File lib/csv.rb, line 2287
def add_unconverted_fields(row, fields)
  class << row
    attr_reader :unconverted_fields
  end
  row.instance_eval { @unconverted_fields = fields }
  row
end

Этот метод вставляет переменную экземпляра unconverted_fields в row и метод доступа к row с именем unconverted_fields(). Переменная устанавливается в содержимое fields.

convert_fields(fields, headers = false) Показать исходный код
# File lib/csv.rb, line 2223
def convert_fields(fields, headers = false)
  # see if we are converting headers or fields
  converters = headers ? @header_converters : @converters

  fields.map.with_index do |field, index|
    converters.each do |converter|
      break if field.nil?
      field = if converter.arity == 1  # straight field converter
        converter[field]
      else                             # FieldInfo converter
        header = @use_headers && !headers ? @headers[index] : nil
        converter[field, FieldInfo.new(index, lineno, header)]
      end
      break unless field.is_a? String  # short-circuit pipeline for speed
    end
    field  # final state of each field, converted or original
  end
end

Обрабатывает fields с помощью @converters, или @header_converters, если headers передаётся как true, возвращая преобразованный набор полей. Любой преобразователь, изменяющий поле на что-то, кроме строки, останавливает цепочку преобразований для этого поля. В основном это оптимизация.

encode_re(*chunks) Показать исходный код
# File lib/csv.rb, line 2310
def encode_re(*chunks)
  Regexp.new(encode_str(*chunks))
end

Создаёт регулярное выражение в @encoding. Все chunks будут преобразованы в указанное кодирование.

encode_str(*chunks) Показать исходный код
# File lib/csv.rb, line 2318
def encode_str(*chunks)
  chunks.map { |chunk| chunk.encode(@encoding.name) }.join('')
end

Создаёт строку в @encoding. Все chunks будут преобразованы в указанное кодирование.

escape_re(str) Показать исходный код
# File lib/csv.rb, line 2302
def escape_re(str)
  str.gsub(@re_chars) {|c| @re_esc + c}
end

Это безопасная по отношению к кодировке версия метода Regexp.escape. Она экранирует все символы, которые могли бы изменить смысл регулярного выражения в кодировке str. Символы регулярных выражений, которые нельзя преобразовать в целевую кодировку, будут пропущены, и экранирование не будет выполнено, если обратный слэш нельзя преобразовать.

init_comments(options) Показать исходный код
# File lib/csv.rb, line 2185
def init_comments(options)
  @skip_lines = options.delete(:skip_lines)
  @skip_lines = Regexp.new(@skip_lines) if @skip_lines.is_a? String
  if @skip_lines and not @skip_lines.respond_to?(:match)
    raise ArgumentError, ":skip_lines has to respond to matches"
  end
end

Хранит шаблон комментариев для пропуска из предоставленных опций.

Шаблон должен отвечать на .match, в противном случае поднимается исключение ArgumentError. Строки преобразуются в Regexp.

См. также ::new.

init_converters(options, field_name = :converters) Показать исходный код
# File lib/csv.rb, line 2138
def init_converters(options, field_name = :converters)
  if field_name == :converters
    @unconverted_fields = options.delete(:unconverted_fields)
  end

  instance_variable_set("@#{field_name}", Array.new)

  # find the correct method to add the converters
  convert = method(field_name.to_s.sub(/ers\Z/, ""))

  # load converters
  unless options[field_name].nil?
    # allow a single converter not wrapped in an Array
    unless options[field_name].is_a? Array
      options[field_name] = [options[field_name]]
    end
    # load each converter...
    options[field_name].each do |converter|
      if converter.is_a? Proc  # custom code block
        convert.call(&converter)
      else                     # by name
        convert.call(converter)
      end
    end
  end

  options.delete(field_name)
end

Загружает запрошенные преобразователи во время создания.

Если field_name установлено :converters (значение по умолчанию), устанавливаются преобразователи полей. При field_name :header_converters добавляются преобразователи заголовков вместо этого.

Опция :unconverted_fields также активирована для :converters вызовов, если запрошено.

init_headers(options) Показать исходный код
# File lib/csv.rb, line 2168
def init_headers(options)
  @use_headers    = options.delete(:headers)
  @return_headers = options.delete(:return_headers)
  @write_headers  = options.delete(:write_headers)

  # headers must be delayed until shift(), in case they need a row of content
  @headers = nil

  init_converters(options, :header_converters)
end

Хранит настройки строки заголовков и загружает преобразователи заголовков при необходимости.

init_parsers(options) Показать исходный код
# File lib/csv.rb, line 2106
def init_parsers(options)
  # store the parser behaviors
  @skip_blanks      = options.delete(:skip_blanks)
  @field_size_limit = options.delete(:field_size_limit)
  @liberal_parsing  = options.delete(:liberal_parsing)

  # prebuild Regexps for faster parsing
  esc_row_sep = escape_re(@row_sep)
  esc_quote   = escape_re(@quote_char)
  @parsers = {
    # for detecting parse errors
    quote_or_nl:    encode_re("[", esc_quote, "\r\n]"),
    nl_or_lf:       encode_re("[\r\n]"),
    stray_quote:    encode_re( "[^", esc_quote, "]", esc_quote,
                               "[^", esc_quote, "]" ),
    # safer than chomp!()
    line_end:       encode_re(esc_row_sep, "\\z"),
    # illegal unquoted characters
    return_newline: encode_str("\r\n")
  }
end

Прекомпилирует парсеры и сохраняет их по имени для доступа во время чтения.

init_separators(options) Показать исходный код
# File lib/csv.rb, line 2007
def init_separators(options)
  # store the selected separators
  @col_sep    = options.delete(:col_sep).to_s.encode(@encoding)
  @row_sep    = options.delete(:row_sep)  # encode after resolving :auto
  @quote_char = options.delete(:quote_char).to_s.encode(@encoding)

  if @quote_char.length != 1
    raise ArgumentError, ":quote_char has to be a single character String"
  end

  #
  # automatically discover row separator when requested
  # (not fully encoding safe)
  #
  if @row_sep == :auto
    if [ARGF, STDIN, STDOUT, STDERR].include?(@io) or
       (defined?(Zlib) and @io.class == Zlib::GzipWriter)
      @row_sep = $INPUT_RECORD_SEPARATOR
    else
      begin
        #
        # remember where we were (pos() will raise an exception if @io is pipe
        # or not opened for reading)
        #
        saved_pos = @io.pos
        while @row_sep == :auto
          #
          # if we run out of data, it's probably a single line
          # (ensure will set default value)
          #
          break unless sample = @io.gets(nil, 1024)
          # extend sample if we're unsure of the line ending
          if sample.end_with? encode_str("\r")
            sample << (@io.gets(nil, 1) || "")
          end

          # try to find a standard separator
          if sample =~ encode_re("\r\n?|\n")
            @row_sep = $&
            break
          end
        end

        # tricky seek() clone to work around GzipReader's lack of seek()
        @io.rewind
        # reset back to the remembered position
        while saved_pos > 1024  # avoid loading a lot of data into memory
          @io.read(1024)
          saved_pos -= 1024
        end
        @io.read(saved_pos) if saved_pos.nonzero?
      rescue IOError         # not opened for reading
        # do nothing:  ensure will set default
      rescue NoMethodError   # Zlib::GzipWriter doesn't have some IO methods
        # do nothing:  ensure will set default
      rescue SystemCallError # pipe
        # do nothing:  ensure will set default
      ensure
        #
        # set default if we failed to detect
        # (stream not opened for reading, a pipe, or a single line of data)
        #
        @row_sep = $INPUT_RECORD_SEPARATOR if @row_sep == :auto
      end
    end
  end
  @row_sep = @row_sep.to_s.encode(@encoding)

  # establish quoting rules
  @force_quotes   = options.delete(:force_quotes)
  do_quote        = lambda do |field|
    field         = String(field)
    encoded_quote = @quote_char.encode(field.encoding)
    encoded_quote                                +
    field.gsub(encoded_quote, encoded_quote * 2) +
    encoded_quote
  end
  quotable_chars = encode_str("\r\n", @col_sep, @quote_char)
  @quote         = if @force_quotes
    do_quote
  else
    lambda do |field|
      if field.nil?  # represent +nil+ fields as empty unquoted fields
        ""
      else
        field = String(field)  # Stringify fields
        # represent empty fields as empty quoted fields
        if field.empty? or
           field.count(quotable_chars).nonzero?
          do_quote.call(field)
        else
          field  # unquoted field
        end
      end
    end
  end
end

Хранит указанные разделители для последующего использования.

Если обнаружение по умолчанию было запрошено для @row_sep, этот метод прочитает вперёд в @io и попытается найти один. ARGF, STDIN, STDOUT, STDERR и любой открытый только для записи поток с параметром по умолчанию @row_sep $INPUT_RECORD_SEPARATOR ($/).

Этот метод также устанавливает правила цитирования, используемые для CSV вывода.

parse_headers(row = nil) Показать исходный код
# File lib/csv.rb, line 2252
def parse_headers(row = nil)
  if @headers.nil?                # header row
    @headers = case @use_headers  # save headers
               # Array of headers
               when Array then @use_headers
               # CSV header String
               when String
                 self.class.parse_line( @use_headers,
                                        col_sep:    @col_sep,
                                        row_sep:    @row_sep,
                                        quote_char: @quote_char )
               # first row is headers
               else            row
               end

    # prepare converted and unconverted copies
    row      = @headers                       if row.nil?
    @headers = convert_fields(@headers, true)
    @headers.each { |h| h.freeze if h.is_a? String }

    if @return_headers                                     # return headers
      return self.class::Row.new(@headers, row, true)
    elsif not [Array, String].include? @use_headers.class  # skip to field row
      return shift
    end
  end

  self.class::Row.new(@headers, convert_fields(row))  # field row
end

Этот метод используется для преобразования завершённой row в CSV::Row. Строки заголовков также обрабатываются здесь, либо возвращая CSV::Row с идентичными заголовками и полями (за исключением того, что поля не проходят через преобразователи), либо считывая их дальше, чтобы вернуть строку полей. Заголовки также сохраняются в @headers для использования в будущих строках.

Если nil, row предполагается строкой заголовков, не основанной на фактической строке потока.

raw_encoding(default = Encoding::ASCII_8BIT) Показать исходный код
# File lib/csv.rb, line 2328
def raw_encoding(default = Encoding::ASCII_8BIT)
  if @io.respond_to? :internal_encoding
    @io.internal_encoding || @io.external_encoding
  elsif @io.is_a? StringIO
    @io.string.encoding
  elsif @io.respond_to? :encoding
    @io.encoding
  else
    default
  end
end

Возвращает кодировку внутреннего объекта IO или default если кодировку определить нельзя.

Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API