класс CSV
Этот класс предоставляет полный интерфейс для файлов 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, используемый при разборе и записи. См. ::new для подробностей.
Предел размера поля, если таковой имеется. См. ::new для подробностей.
Номер строки последней прочитанной строки из этого файла. Поля с вложенными символами конца строки не повлияют на этот счёт.
Номер строки последней прочитанной строки из этого файла. Поля с вложенными символами конца строки не повлияют на этот счёт.
Кодированный :quote_char, используемый при разборе и записи. См. ::new для подробностей.
Кодированный :row_sep, используемый при разборе и записи. См. ::new для подробностей.
Регулярное выражение, отмечающее строку как комментарий. См. ::new для подробностей.
Методы публичного класса
# File lib/csv.rb, line 1098
def self.filter(input=nil, output=nil, **options)
# parse options for input, output, or both
in_options, out_options = Hash.new, {row_sep: $INPUT_RECORD_SEPARATOR}
options.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
# build input and output wrappers
input = new(input || ARGF, in_options)
output = new(output || $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 ($/).
# File lib/csv.rb, line 1137
def self.foreach(path, **options, &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.
# File lib/csv.rb, line 1162
def self.generate(str=nil, **options)
# add a default empty String, if none was given
if str
io = StringIO.new(str)
io.seek(0, IO::SEEK_END)
else
encoding = options[:encoding]
str = String.new
str.force_encoding(encoding) if encoding
end
csv = new(str, options) # wrap
yield csv # yield for appending
csv.string # return final String
end Этот метод заключает предоставленную вами строку, или пустую строку по умолчанию, в объект CSV, который передаётся в предоставленный блок. Вы можете использовать блок для добавления строк CSV в строку, и когда блок завершит работу, будет возвращена окончательная строка.
Обратите внимание, что переданная строка изменяется этим методом. Используйте метод dup() перед передачей, если вам нужна новая строка.
Параметр options может быть любым, что понимает ::new. Этот метод понимает дополнительный параметр :encoding, который можно использовать для установки базовой кодировки вывода, если не передана строка. CSV нуждается в этом указании, если вы планируете выводить данные, несовместимые с ASCII.
# File lib/csv.rb, line 1190
def self.generate_line(row, **options)
options = {row_sep: $INPUT_RECORD_SEPARATOR}.merge(options)
str = String.new
if options[:encoding]
str.force_encoding(options[: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 ($/) при вызове этого метода.
# File lib/csv.rb, line 1058
def self.instance(data = $stdout, **options)
# 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.
Если указан блок, экземпляр передаётся в блок, и возвращаемое значение блока становится возвращаемым значением.
# File lib/csv.rb, line 1517
def initialize(data, col_sep: ",", row_sep: :auto, quote_char: '"', field_size_limit: nil,
converters: nil, unconverted_fields: nil, headers: false, return_headers: false,
write_headers: nil, header_converters: nil, skip_blanks: false, force_quotes: false,
skip_lines: nil, liberal_parsing: false, internal_encoding: nil, external_encoding: nil, encoding: nil)
raise ArgumentError.new("Cannot parse nil as CSV") if data.nil?
# 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
internal_encoding = Encoding.find(internal_encoding) if internal_encoding
external_encoding = Encoding.find(external_encoding) if external_encoding
if encoding
encoding, = encoding.split(":", 2) if encoding.is_a?(String)
encoding = Encoding.find(encoding)
end
@encoding = raw_encoding(nil) || internal_encoding || encoding ||
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)}/
@unconverted_fields = unconverted_fields
# Stores header row settings and loads header converters, if needed.
@use_headers = headers
@return_headers = return_headers
@write_headers = write_headers
# headers must be delayed until shift(), in case they need a row of content
@headers = nil
init_separators(col_sep, row_sep, quote_char, force_quotes)
init_parsers(skip_blanks, field_size_limit, liberal_parsing)
init_converters(converters, :@converters, :convert)
init_converters(header_converters, :@header_converters, :header_convert)
init_comments(skip_lines)
@force_encoding = !!encoding
# track our own lineno since IO gets confused about line-ends is CSV fields
@lineno = 0
# make sure headers have been assigned
if header_row? and [Array, String].include? @use_headers.class and @write_headers
parse_headers # won't read data for Array or String
self << @headers
end
end Этот конструктор будет оборачивать объект String или IO, переданный data для чтения и/или записи. В дополнение к методам экземпляра класса CSV, делегированы несколько методов IO. (См. ::open для полного списка.) Если вы передадите строку для data, вы сможете позже извлечь её (например, после записи в неё) с помощью CSV.string().
Обратите внимание, что обернутая строка будет расположена в начале (для чтения). Если вы хотите расположить её в конце (для записи), используйте ::generate. Если вам нужно другое расположение, передайте предварительно заданный объект StringIO вместо этого.
Вы можете установить любые предпочтения для чтения и/или записи в options Hash. Доступные параметры:
-
:col_sep -
Строка, вставляемая между каждым полем. Эта строка будет преобразована в кодировку данных перед анализом.
-
:row_sep -
Строка, добавляемая в конец каждой строки. Это может быть установлено в специальное значение
:auto, которое запрашивает, чтобы CSV автоматически определил его из данных. Автоопределение считывает данные вперёд, ища следующую"\r\n","\n", или"\r"последовательность. Последовательность будет выбрана даже если она встречается в цитируемом поле, предполагая, что у вас будет тот же разделитель строк. Если ни одна из этих последовательностей не найдена,data—ARGF,STDIN,STDOUT, илиSTDERR, или поток доступен только для вывода, используется значение по умолчанию$INPUT_RECORD_SEPARATOR($/). Очевидно, определение занимает некоторое время. Ручное задание, если скорость важна. Также обратите внимание, что объекты IO должны быть открыты в двоичном режиме в Windows, если будет использована эта функция, так как перевод разделителей строк может вызвать проблемы с возвращением позиции документа к предыдущему состоянию после предвосхищающего считывания. Эта строка будет преобразована в кодировку данных перед анализом. -
:quote_char -
Символ, используемый для цитирования полей. Это должна быть строка из одного символа. Это полезно для приложений, которые неправильно используют
'в качестве символа кавычек вместо правильного". CSV всегда будет считать двойную последовательность этого символа как экранированную кавычку. Эта строка будет преобразована в кодировку данных перед анализом. -
:field_size_limit -
Это максимальный размер, который CSV будет считывать вперёд, ища закрывающую кавычку поля. (На самом деле, он считывает до первой новой строки за пределами этого размера.) Если кавычка не найдена в пределах лимита, CSV вызовет MalformedCSVError, предполагая, что данные некорректны. Вы можете использовать этот лимит для предотвращения, по сути, атак типа DoS на парсер. Однако этот лимит может привести к ошибке при легитимном анализе, и поэтому по умолчанию он установлен на
nilили выключен. -
:converters -
Массив имён из Converters Hash и/или лямбд, которые обрабатывают пользовательские преобразования. Один преобразователь не обязательно должен быть в массиве. Все встроенные преобразователи пытаются преобразовать поля в UTF-8 перед преобразованием. Преобразование завершится ошибкой, если данные не могут быть преобразованы, оставив поле неизменным.
-
:unconverted_fields -
Если установлено значение
true, в возвращаемые строки (массив или CSV::Row) будет добавлен метод unconverted_fields(), который вернёт поля в том виде, в котором они были до преобразования. Обратите внимание, что: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, или проверьте, пуст ли массив полей .compact каждой строки. -
:force_quotes -
При установке в значение
true, CSV цитирует все поля CSV, которые он создаёт. -
:skip_lines -
При установке в объект, реагирующий на
match, каждая строка, соответствующая ему, считается комментарием и игнорируется во время анализа. При установке в строку она сначала преобразуется в Regexp. При установке вnilни одна строка не считается комментарием. Если переданный объект не отвечает наmatch, выбрасываетсяArgumentError. -
:liberal_parsing -
При установке в значение
true, CSV попытается проанализировать входные данные, не соответствующие RFC 4180, такие как двойные кавычки в нецитируемых полях.
См. CSV::DEFAULT_OPTIONS для значений по умолчанию.
Параметры не могут быть переопределены в методах экземпляра по причинам производительности, поэтому убедитесь, что вы установили желаемые значения здесь.
# File lib/csv.rb, line 1264
def self.open(filename, mode="r", **options)
# wrap a File opened with the remaining +args+ with no newline
# decorator
file_opts = {universal_newline: false}.merge(options)
begin
f = File.open(filename, mode, file_opts)
rescue ArgumentError => e
raise unless /needs binmode/ =~ e.message and mode == "r"
mode = "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.default_external. CSV проверит кодировку базового объекта IO (установленного через mode , который вы передаёте), чтобы определить, как анализировать данные. Вы можете указать вторую кодировку, чтобы данные были преобразованы по мере чтения, так же, как и при обычном вызове 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?()
# File lib/csv.rb, line 1308
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 содержащий всё, что понимает ::new.
# File lib/csv.rb, line 1328 def self.parse_line(line, **options) new(line, options).shift end
Этот метод — это сокращение для преобразования одной строки CSV строки в массив. Обратите внимание, что если line содержит несколько строк, всё, что выходит за рамки первой строки, игнорируется.
Параметр options может быть любым, что понимает ::new.
# File lib/csv.rb, line 1343
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 их проанализирует.
# File lib/csv.rb, line 1348 def self.readlines(*args) read(*args) end
Псевдоним для ::read.
# File lib/csv.rb, line 1359
def self.table(path, **options)
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
# File lib/csv.rb, line 1680
def <<(row)
# make sure headers have been assigned
if header_row? and [Array, String].include? @use_headers.class and !@write_headers
parse_headers # won't read data for Array or String
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() добавляются в вывод.
Источник данных должен быть открыт для записи.
# File lib/csv.rb, line 1728 def convert(name = nil, &converter) add_converter(:@converters, self.class::Converters, name, &converter) end
Вы можете использовать этот метод для установки встроенных CSV::Converters или для предоставления блока, обрабатывающего пользовательское преобразование.
Если вы предоставляете блок, принимающий один аргумент, он получит поле и должен вернуть преобразованное значение или само поле. Если ваш блок принимает два аргумента, он также получит CSV::FieldInfo Struct, содержащий подробности о поле. Опять же, блок должен вернуть преобразованное поле или само поле.
# File lib/csv.rb, line 1594
def converters
@converters.map do |converter|
name = Converters.rassoc(converter)
name ? name.first : converter
end
end Возвращает текущий список конвертеров. Подробности см. в ::new. Встроенные конвертеры будут возвращены по имени, а другие — как есть.
# File lib/csv.rb, line 1759
def each
if block_given?
while row = shift
yield row
end
else
to_enum
end
end Поочередно возвращает каждую строку источника данных.
Поддержка Enumerable.
Источник данных должен быть открыт для чтения.
# File lib/csv.rb, line 1637 def force_quotes?() @force_quotes end
Возвращает true , если все выходные поля цитируются. Подробности см. в ::new.
# File lib/csv.rb, line 1743
def header_convert(name = nil, &converter)
add_converter( :@header_converters,
self.class::HeaderConverters,
name,
&converter )
end Идентично #convert, но для заголовочных строк.
Обратите внимание, что этот метод необходимо вызвать до чтения заголовочных строк, чтобы он подействовал.
# File lib/csv.rb, line 1625
def header_converters
@header_converters.map do |converter|
name = HeaderConverters.rassoc(converter)
name ? name.first : converter
end
end Возвращает текущий список конвертеров, действующих для заголовков. Подробности см. в ::new. Встроенные конвертеры будут возвращены по имени, а другие — как есть.
# File lib/csv.rb, line 1785 def header_row? @use_headers and @headers.nil? end
Возвращает true , если следующая считываемая строка будет заголовочной строкой.
# File lib/csv.rb, line 1610 def headers @headers || true if @use_headers end
Возвращает nil , если заголовки не будут использоваться, true , если они будут, но ещё не были прочитаны, или фактические заголовки после их чтения. Подробности см. в ::new.
# File lib/csv.rb, line 1959
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 строке.
# File lib/csv.rb, line 1639 def liberal_parsing?() @liberal_parsing end
Возвращает true , если некорректный ввод обрабатывается. Подробности см. в ::new.
# File lib/csv.rb, line 1774
def read
rows = to_a
if @use_headers
Table.new(rows)
else
rows
end
end Считывает оставшиеся строки и возвращает массив массивов.
Источник данных должен быть открыт для чтения.
# File lib/csv.rb, line 1617 def return_headers?() @return_headers end
Возвращает true , если заголовки будут возвращены как строка результатов. Подробности см. в ::new.
# File lib/csv.rb, line 1664 def rewind @headers = nil @lineno = 0 @io.rewind end
Перематывает объект IO и сбрасывает счётчик строк CSV.
# File lib/csv.rb, line 1796
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
if in_extended_col
@line.concat(parse)
else
@line = parse.clone
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.end_with?(@quote_char) && part.count(@quote_char) % 2 != 0
# extended column ends
csv.last << part[0..-2]
if csv.last =~ @parsers[:stray_quote]
raise MalformedCSVError,
"Missing or stray quote in line #{lineno + 1}"
end
csv.last.gsub!(@double_quote_char, @quote_char)
in_extended_col = false
else
csv.last << part << @col_sep
end
elsif part.start_with?(@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.end_with?(@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!(@double_quote_char, @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.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 (при использовании заголовочных строк).
Источник данных должен быть открыт для чтения.
# File lib/csv.rb, line 1635 def skip_blanks?() @skip_blanks end
Возвращает true , если пустые строки пропускаются анализатором. Подробности см. в ::new.
# File lib/csv.rb, line 1604 def unconverted_fields?() @unconverted_fields end
Возвращает true , если unconverted_fields() преобразованы в результаты парсинга. Подробности см. в ::new.
# File lib/csv.rb, line 1619 def write_headers?() @write_headers end
Возвращает true если заголовки записаны в выходной поток. Подробности см. в ::new.
Приватные методы экземпляров
# File lib/csv.rb, line 2176
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.
# File lib/csv.rb, line 2263
def add_unconverted_fields(row, fields)
class << row
attr_reader :unconverted_fields
end
row.instance_variable_set(:@unconverted_fields, fields)
row
end Этот метод вставляет экземпляр переменной unconverted_fields в row и метод доступа к row с именем unconverted_fields(). Переменная устанавливается в содержимое fields.
# File lib/csv.rb, line 2199
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 headers && 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, возвращая преобразованный набор полей. Любой преобразователь, изменяющий поле на что-то отличное от строки, прерывает цепочку преобразований для этого поля. Это в основном служит для повышения эффективности.
# File lib/csv.rb, line 2286 def encode_re(*chunks) Regexp.new(encode_str(*chunks)) end
Создаёт регулярное выражение в @encoding. Все chunks будут перекодированы в указанную кодировку.
# File lib/csv.rb, line 2294
def encode_str(*chunks)
chunks.map { |chunk| chunk.encode(@encoding.name) }.join('')
end Создаёт строку в @encoding. Все chunks будут перекодированы в указанную кодировку.
# File lib/csv.rb, line 2278
def escape_re(str)
str.gsub(@re_chars) {|c| @re_esc + c}
end Этот метод является кодировочно-безопасной версией Regexp.escape. Он будет экранировать любые символы, которые могут изменить значение регулярного выражения в кодировке str. Символы регулярных выражений, которые нельзя перекодировать в целевую кодировку, будут пропущены, и экранирование не будет выполнено, если обратный слэш нельзя перекодировать.
# File lib/csv.rb, line 2161
def init_comments(skip_lines)
@skip_lines = skip_lines
@skip_lines = Regexp.new(Regexp.escape(@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
# File lib/csv.rb, line 2136
def init_converters(converters, ivar_name, convert_method)
converters = case converters
when nil then []
when Array then converters
else [converters]
end
instance_variable_set(ivar_name, [])
convert = method(convert_method)
# load converters
converters.each do |converter|
if converter.is_a? Proc # custom code block
convert.call(&converter)
else # by name
convert.call(converter)
end
end
end Загружает запрошенные во время создания преобразователи.
Если field_name установлено :converters (по умолчанию) устанавливаются преобразователи полей. Когда field_name равно :header_converters добавляются преобразователи заголовков.
Опция :unconverted_fields также активируется для :converters вызовов, если запрошено.
# File lib/csv.rb, line 2104
def init_parsers(skip_blanks, field_size_limit, liberal_parsing)
# store the parser behaviors
@skip_blanks = skip_blanks
@field_size_limit = field_size_limit
@liberal_parsing = 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 Предварительно компилирует парсеры и сохраняет их по имени для доступа во время чтения.
# File lib/csv.rb, line 2006
def init_separators(col_sep, row_sep, quote_char, force_quotes)
# store the selected separators
@col_sep = col_sep.to_s.encode(@encoding)
@row_sep = row_sep # encode after resolving :auto
@quote_char = quote_char.to_s.encode(@encoding)
@double_quote_char = @quote_char * 2
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 = 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 вывода.
# File lib/csv.rb, line 2228
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 предполагается как строка заголовков, не основанная на фактической строке потока.
# File lib/csv.rb, line 2302
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.