класс 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 или строки, из которых читается или в которые записывается данные. Ваши данные никогда не преобразуются (если вы не попросите Ruby их преобразовать) и будут буквально обработаны в кодировке, в которой они находятся. Таким образом, 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
-
- DateMatcher
-
Регулярное выражение (Regexp), используемое для поиска и преобразования некоторых распространённых форматов дат (Date).
- DateTimeMatcher
-
Регулярное выражение (Regexp), используемое для поиска и преобразования некоторых распространённых форматов даты и времени (DateTime).
- FieldInfo
-
FieldInfo — это структура (Struct), содержащая информацию о позиции поля в источнике данных, из которого оно было прочитано. CSV передаёт эту структуру в некоторые блоки, которые принимают решения на основе структуры поля. См. #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 1077
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 ($/).
# File lib/csv.rb, line 1118
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, чтобы данные были транскодированы при чтении. Например, encoding:
"UTF-32BE:UTF-8" будет читать данные UTF-32BE из файла, но транскодировать их в UTF-8 перед парсингом CSV.
# File lib/csv.rb, line 1143
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 = ""
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 , когда не передаётся строка, чтобы установить базовую кодировку Encoding для вывода. CSV нуждается в этом указании, если вы планируете выводить данные, несовместимые с ASCII.
# File lib/csv.rb, line 1173
def self.generate_line(row, options = Hash.new)
options = {row_sep: $INPUT_RECORD_SEPARATOR}.merge(options)
encoding = options.delete(:encoding)
str = ""
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 для установки базовой кодировки Encoding для вывода. Этот метод попытается угадать вашу кодировку Encoding из первого поля, не являющегося nil в row, если это возможно, но вам может потребоваться использовать этот параметр как резервный вариант.
Параметр :row_sep option по умолчанию $INPUT_RECORD_SEPARATOR ($/).
# File lib/csv.rb, line 1037
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.
Если указан блок, экземпляр передаётся в блок, и возвращаемое значение становится возвращаемым значением блока.
# File lib/csv.rb, line 1498
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) 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.) Если вы передадите строку для 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, или проверку fields.compact.empty? каждой строки. -
:force_quotes -
При установке значения
true, CSV будет цитировать все поля CSV, которые он создаёт. -
:skip_lines -
При установке объекта, реагирующего на
match, любая строка, соответствующая ему, считается комментарием и игнорируется во время обработки. Если установлено значение в виде строки, строка сначала преобразуется в Regexp. Если установлено значениеnil, ни одна строка не считается комментарием. Если переданный объект не отвечает наmatch, выбрасываетсяArgumentError.
См. CSV::DEFAULT_OPTIONS для значений по умолчанию.
Параметры не могут быть переопределены в методах экземпляра по причинам производительности, поэтому убедитесь, что вы установили нужные вам значения здесь.
# File lib/csv.rb, line 1248
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.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 1293
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 для чтения, и необязательный options словарь, содержащий всё, что понимает ::new.
# File lib/csv.rb, line 1313 def self.parse_line(line, options = Hash.new) new(line, options).shift end
Этот метод является сокращением для преобразования одной строки CSV строки в массив. Обратите внимание, что если line содержит несколько строк, всё, что идёт за первой строкой, игнорируется.
Параметр options может быть любым, что понимает ::new.
# File lib/csv.rb, line 1328
def self.read(path, *options)
open(path, *options) { |csv| csv.read }
end Используйте для чтения файла CSV в массив массивов. Передайте path в файл и любые options ::new понимает. Этот метод также понимает дополнительный параметр :encoding, который вы можете использовать для указания кодировки данных в читаемом файле. Вы должны предоставить его, если ваши данные не в Encoding.default_external. CSV будет использовать это для определения того, как обработать данные. Вы можете предоставить вторую кодировку Encoding, чтобы данные были преобразованы во время чтения. Например, encoding:
"UTF-32BE:UTF-8" будет читать данные UTF-32BE из файла, но перекодировать их в UTF-8, прежде чем CSV обработает их.
# File lib/csv.rb, line 1333 def self.readlines(*args) read(*args) end
Псевдоним для ::read.
# File lib/csv.rb, line 1344
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) )
Методы публичного экземпляра
# File lib/csv.rb, line 1655
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().
Источник данных должен быть открыт для записи.
# File lib/csv.rb, line 1704 def convert(name = nil, &converter) add_converter(:converters, self.class::Converters, name, &converter) end
Этот метод можно использовать для установки встроенных конвертеров CSV::Converters или для указания блока, обрабатывающего пользовательскую конверсию.
Если вы предоставите блок, принимающий один аргумент, ему будет передано поле, и он должен вернуть преобразованное значение или само поле. Если ваш блок принимает два аргумента, ему также будет передана информация о поле CSV::FieldInfo, содержащая подробности о поле. Опять же, блок должен вернуть преобразованное поле или само поле.
# File lib/csv.rb, line 1571
def converters
@converters.map do |converter|
name = Converters.rassoc(converter)
name ? name.first : converter
end
end Возвращает текущий список действующих конвертеров. Подробнее см. ::new. Встроенные конвертеры будут возвращены по имени, а другие — как есть.
# File lib/csv.rb, line 1735
def each
if block_given?
while row = shift
yield row
end
else
to_enum
end
end Поочередно возвращает каждую строку источника данных.
Поддержка Enumerable.
Источник данных должен быть открыт для чтения.
# File lib/csv.rb, line 1614 def force_quotes?() @force_quotes end
Возвращает true если все выходные поля заключены в кавычки. Подробнее см. ::new.
# File lib/csv.rb, line 1719
def header_convert(name = nil, &converter)
add_converter( :header_converters,
self.class::HeaderConverters,
name,
&converter )
end Идентично #convert, но для заголовков строк.
Обратите внимание, что этот метод должен быть вызван до чтения заголовков строк, чтобы иметь какой-либо эффект.
# File lib/csv.rb, line 1602
def header_converters
@header_converters.map do |converter|
name = HeaderConverters.rassoc(converter)
name ? name.first : converter
end
end Возвращает текущий список конвертеров, используемых для заголовков. Подробнее см. ::new. Встроенные конвертеры будут возвращены по имени, а другие — как есть.
# File lib/csv.rb, line 1761 def header_row? @use_headers and @headers.nil? end
Возвращает true если следующая считанная строка будет строкой заголовка.
# File lib/csv.rb, line 1587 def headers @headers || true if @use_headers end
Возвращает nil если заголовки не будут использоваться, true если они будут, но еще не были прочитаны, или фактические заголовки после их чтения. Подробнее см. ::new.
# File lib/csv.rb, line 1922
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 ].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 1750
def read
rows = to_a
if @use_headers
Table.new(rows)
else
rows
end
end Считывает оставшиеся строки и возвращает массив массивов.
Источник данных должен быть открыт для чтения.
# File lib/csv.rb, line 1594 def return_headers?() @return_headers end
Возвращает true если заголовки будут возвращены как строка результатов. Подробнее см. ::new.
# File lib/csv.rb, line 1639 def rewind @headers = nil @lineno = 0 @io.rewind end
Перематывает объект ввода-вывода и сбрасывает счётчик строк CSV.
# File lib/csv.rb, line 1772
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.last << part[0..-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)
in_extended_col = false
else
csv.last << part
csv.last << @col_sep
end
elsif part[0] == @quote_char
# If we are staring a new quoted column
if part[-1] != @quote_char || part.count(@quote_char) % 2 != 0
# start an extended column
csv << part[1..-1]
csv.last << @col_sep
in_extended_col = true
else
# 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)
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
raise MalformedCSVError, "Illegal quoting in line #{lineno + 1}."
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 1612 def skip_blanks?() @skip_blanks end
Возвращает true пустые строки пропускаются анализатором. Подробнее см. ::new.
# File lib/csv.rb, line 1581 def unconverted_fields?() @unconverted_fields end
Возвращает true если unconverted_fields() будут преобразованы в результаты. Подробнее см. ::new.
# File lib/csv.rb, line 1596 def write_headers?() @write_headers end
Возвращает true если заголовки записываются в выводе. Подробнее см. ::new.
Методы частного экземпляра
# File lib/csv.rb, line 2161
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 2248
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.
# File lib/csv.rb, line 2184
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, возвращая преобразованный набор полей. Любой преобразователь, который изменяет поле на что-то отличное от String, прерывает цепочку преобразований для этого поля. Это в первую очередь оптимизация.
# File lib/csv.rb, line 2271 def encode_re(*chunks) Regexp.new(encode_str(*chunks)) end
Создает регулярное выражение в @encoding. Все chunks будут преобразованы в эту кодировку.
# File lib/csv.rb, line 2279
def encode_str(*chunks)
chunks.map { |chunk| chunk.encode(@encoding.name) }.join('')
end Создаёт строку в @encoding. Все chunks будут преобразованы в эту кодировку.
# File lib/csv.rb, line 2263
def escape_re(str)
str.gsub(@re_chars) {|c| @re_esc + c}
end Этот метод является безопасной по отношению к кодировке версией Regexp.escape. Он будет экранировать любые символы, которые изменили бы значение регулярного выражения в кодировке str. Символы регулярных выражений, которые нельзя преобразовать в целевую кодировку, будут пропущены, и экранирование не будет выполнено, если обратный слэш нельзя преобразовать.
# File lib/csv.rb, line 2146
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
# File lib/csv.rb, line 2099
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, если запрошено.
# File lib/csv.rb, line 2129 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
Хранит параметры строки заголовка и загружает преобразователи заголовков, если необходимо.
# File lib/csv.rb, line 2068
def init_parsers(options)
# store the parser behaviors
@skip_blanks = options.delete(:skip_blanks)
@field_size_limit = options.delete(:field_size_limit)
# 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 1969
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 вывода.
# File lib/csv.rb, line 2213
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 2289
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.