Ruby 1.9, force_encoding, но проверьте
У меня есть строка, которую я прочитал из какого-то ввода.
Насколько мне известно, это UTF8. Хорошо:
string.force_encoding("utf8")
Но если в этой строке есть байты, которые на самом деле не являются законными UTF8, я хочу знать сейчас и принять меры.
Как правило, будет принудительно повышаться ( "utf8" ), если он встречает такие байты? Я верю, что этого не произойдет.
Если я делал # encode, я мог бы выбрать из удобных опций, что делать с символами, которые недействительны в исходной кодировке ( или кодирование адресата).
Но я не делаю #encode, я делаю #force_encoding. У этого нет таких вариантов.
Будет ли смысл
string.force_encoding("utf8").encode("utf8")
получить исключение сразу? Обычно кодировка с utf8 на utf8 не имеет никакого смысла. Но может быть, это способ заставить его сразу поднять, если есть недопустимые байты? Или используйте параметр :replace
и т.д., Чтобы сделать что-то другое с недопустимыми байтами?
Но нет, похоже, тоже не может сделать эту работу.
Кто-нибудь знает?
1.9.3-p0 :032 > a = "bad: \xc3\x28 okay".force_encoding("utf-8")
=> "bad: \xC3( okay"
1.9.3-p0 :033 > a.valid_encoding?
=> false
Хорошо, но как мне найти и устранить эти плохие байты? Как ни странно, это НЕ поднимает:
1.9.3-p0 :035 > a.encode("utf-8")
=> "bad: \xC3( okay"
Если бы я конвертировал в другую кодировку, это было бы!
1.9.3-p0 :039 > a.encode("ISO-8859-1")
Encoding::InvalidByteSequenceError: "\xC3" followed by "(" on UTF-8
Или, если бы я сказал это, он заменил бы его на "?" = >
1.9.3-p0 :040 > a.encode("ISO-8859-1", :invalid => :replace)
=> "bad: ?( okay"
Итак, Ruby получил умения, чтобы узнать, что такое плохие байты в utf-8, и заменить их чем-то другим - при преобразовании в другую кодировку. Но я не хочу конвертировать в другую кодировку, я хочу остаться utf8 - но я могу захотеть поднять, если там есть недопустимый байт, или я могу заменить недействительные байты на заменяющие символы.
Нет ли способа получить рубин для этого?
update. Полагаю, что это, наконец, было добавлено в ruby в версии 2.1, с чипом String #, присутствующим в версии предварительного просмотра 2.1, для этого. Так что ищите!
Ответы
Ответ 1
(обновление: см. https://github.com/jrochkind/scrub_rb)
Итак, я закодировал решение того, что мне здесь нужно: https://github.com/jrochkind/ensure_valid_encoding/blob/master/lib/ensure_valid_encoding.rb
Но только совсем недавно я понял, что это действительно встроено в stdlib, вам просто нужно несколько интуитивно пропустить "двоичный" как "исходную кодировку":
a = "bad: \xc3\x28 okay".force_encoding("utf-8")
a.encode("utf-8", "binary", :undef => :replace)
=> "bad: �( okay"
Да, именно это я и хотел. Так получается, что он построен в 1.9 stdlib, он просто недокументирован, и мало кто знает об этом (или, может быть, мало кто говорит по-английски об этом?). Хотя я видел, как эти аргументы использовались таким образом в блоге где-то, так что кто-то знал это!
Ответ 2
В ruby 2.1, stdlib, наконец, поддерживает это с помощью scrub
.
http://ruby-doc.org/core-2.1.0/String.html#method-i-scrub
Ответ 3
убедитесь, что сам файл сценария сохранен как UTF8 и попробуйте выполнить
# encoding: UTF-8
p [a = "bad: \xc3\x28 okay", a.valid_encoding?]
p [a.force_encoding("utf-8"), a.valid_encoding?]
p [a.encode!("ISO-8859-1", :invalid => :replace), a.valid_encoding?]
Это дает моей системе windows7 следующие
["bad: \xC3( okay", false]
["bad: \xC3( okay", false]
["bad: ?( okay", true]
Итак, ваш плохой char заменен, вы можете сделать это сразу:
a = "bad: \xc3\x28 okay".encode!("ISO-8859-1", :invalid => :replace)
=> "bad: ?( okay"
EDIT: здесь решение, которое работает на любом произвольном кодировании, первое кодирует только плохие символы, второе просто заменяет на?
def validate_encoding(str)
str.chars.collect do |c|
(c.valid_encoding?) ? c:c.encode!(Encoding.locale_charmap, :invalid => :replace)
end.join
end
def validate_encoding2(str)
str.chars.collect do |c|
(c.valid_encoding?) ? c:'?'
end.join
end
a = "bad: \xc3\x28 okay"
puts validate_encoding(a) #=>bad: ?( okay
puts validate_encoding(a).valid_encoding? #=>true
puts validate_encoding2(a) #=>bad: ?( okay
puts validate_encoding2(a).valid_encoding? #=>true
Ответ 4
Чтобы проверить, что строка не имеет недопустимых последовательностей, попробуйте преобразовать ее в двоичную кодировку:
# Returns true if the string has only valid sequences
def valid_encoding?(string)
string.encode('binary', :undef => :replace)
true
rescue Encoding::InvalidByteSequenceError => e
false
end
p valid_encoding?("\xc0".force_encoding('iso-8859-1')) # true
p valid_encoding?("\u1111") # true
p valid_encoding?("\xc0".force_encoding('utf-8')) # false
Этот код заменяет символы undefined, потому что нам все равно, есть ли допустимые последовательности, которые не могут быть представлены в двоичном формате. Нам все равно, если есть недопустимые последовательности.
Небольшая модификация этого кода возвращает фактическую ошибку, которая имеет ценную информацию о неправильном кодировании:
# Returns the encoding error, or nil if there isn't one.
def encoding_error(string)
string.encode('binary', :undef => :replace)
nil
rescue Encoding::InvalidByteSequenceError => e
e.to_s
end
# Returns truthy if the string has only valid sequences
def valid_encoding?(string)
!encoding_error(string)
end
puts encoding_error("\xc0".force_encoding('iso-8859-1')) # nil
puts encoding_error("\u1111") # nil
puts encoding_error("\xc0".force_encoding('utf-8')) # "\xC0" on UTF-8
Ответ 5
О единственном, о чем я могу думать, это перекодировать что-то и обратно, что не повредит строку в кругообороте:
string.force_encoding("UTF-8").encode("UTF-32LE").encode("UTF-8")
Кажется довольно расточительным, однако.
Ответ 6
Хорошо, здесь действительно хромой чистый рубиновый способ сделать это. Я понял себя. Вероятно, он работает на дерьмо. что за черт, рубин? На данный момент я не выбираю свой собственный ответ, надеясь, что кто-то еще появится и даст нам что-то лучшее.
# Pass in a string, will raise an Encoding::InvalidByteSequenceError
# if it contains an invalid byte for it encoding; otherwise
# returns an equivalent string.
#
# OR, like String#encode, pass in option `:invalid => :replace`
# to replace invalid bytes with a replacement string in the
# returned string. Pass in the
# char you'd like with option `:replace`, or will, like String#encode
# use the unicode replacement char if it thinks it a unicode encoding,
# else ascii '?'.
#
# in any case, method will raise, or return a new string
# that is #valid_encoding?
def validate_encoding(str, options = {})
str.chars.collect do |c|
if c.valid_encoding?
c
else
unless options[:invalid] == :replace
# it ought to be filled out with all the metadata
# this exception usually has, but what a pain!
raise Encoding::InvalidByteSequenceError.new
else
options[:replace] || (
# surely there a better way to tell if
# an encoding is a 'Unicode encoding form'
# than this? What wrong with you ruby 1.9?
str.encoding.name.start_with?('UTF') ?
"\uFFFD" :
"?" )
end
end
end.join
end
Больше разглагольствования на http://bibwild.wordpress.com/2012/04/17/checkingfixing-bad-bytes-in-ruby-1-9-char-encoding/
Ответ 7
Если вы делаете это для "реального" варианта использования - например, для разбора разных строк, введенных пользователями, а не только для того, чтобы "декодировать" полностью случайный файл, который можно было бы сделать из сколько угодно кодировок, тогда, я думаю, вы могли бы по крайней мере предположить, что все символы для каждой строки имеют одинаковую кодировку.
Тогда, в этом случае, что бы вы подумали об этом?
strings = [ "UTF-8 string with some utf8 chars \xC3\xB2 \xC3\x93",
"ISO-8859-1 string with some iso-8859-1 chars \xE0 \xE8", "..." ]
strings.each { |s|
s.force_encoding "utf-8"
if s.valid_encoding?
next
else
while s.valid_encoding? == false
s.force_encoding "ISO-8859-1"
s.force_encoding "..."
end
s.encode!("utf-8")
end
}
Я никоим образом не являюсь "про" Ruby, поэтому, пожалуйста, простите, если мое решение неверно или даже немного наивно.
Я просто пытаюсь отдать все, что могу, и это то, к чему я пришел, пока я был (до сих пор), работая над этим маленьким парсером для произвольно закодированных строк, что я делаю для учебного проекта.
Пока я публикую это, я должен признать, что я даже не полностью его протестировал.. Я... получил пару "положительных" результатов, но я был так взволнован, возможно, обнаружил, что я борелся найти (и все время, когда я читал об этом на SO..), я просто почувствовал необходимость делиться им как можно быстрее, надеясь, что это может помочь сэкономить время любому, кто искал это для как я был..... если он работает так, как ожидалось:)
Ответ 8
Простой способ вызвать исключение выглядит следующим образом:
untrusted_string.match/./
Ответ 9
Вот две распространенные ситуации и способы борьбы с ними в Ruby 2.1 +. Я знаю, вопрос относится к Ruby v1.9, но, возможно, это полезно для других, которые могут найти этот вопрос через Google.
Ситуация 1
У вас есть строка UTF-8, возможно, несколько недопустимых байтов
Удалите недопустимые байты:
str = "Partly valid\xE4 UTF-8 encoding: äöüß"
str.scrub('')
# => "Partly valid UTF-8 encoding: äöüß"
Ситуация 2
У вас есть строка, которая может быть в кодировке UTF-8 или ISO-8859-1
Проверьте, какая кодировка есть и преобразуется в UTF-8 (если необходимо):
str = "String in ISO-8859-1 encoding: \xE4\xF6\xFC\xDF"
unless str.valid_encoding?
str.encode!( 'UTF-8', 'ISO-8859-1', invalid: :replace, undef: :replace, replace: '?' )
end #unless
# => "String in ISO-8859-1 encoding: äöüß"
Примечания
-
В приведенных выше фрагментах кода предполагается, что Ruby кодирует все ваши строки в UTF-8
по умолчанию. Хотя это почти всегда так, вы можете убедиться в этом, запустив свои скрипты с помощью # encoding: UTF-8
.
-
Если это неверно, программно можно обнаружить большинство многобайтовых кодировок, например UTF-8
(в Ruby, см. #valid_encoding?
). Тем не менее, НЕ (легко) можно программно обнаружить недействительность однобайтовых кодировок, таких как ISO-8859-1
. Таким образом, приведенный выше фрагмент кода не работает наоборот, то есть обнаруживает, является ли строка правильной кодировкой ISO-8859-1
.
-
Несмотря на то, что UTF-8
становится все более популярным как кодировка по умолчанию в Интернете, ISO-8859-1
и другие Latin1
вкусы по-прежнему очень популярны в западных странах, особенно в Северной Америке. Имейте в виду, что существует несколько однобайтовых кодировок, которые очень похожи, но немного отличаются от ISO-8859-1. Примеры: CP1252
(a.k.a. Windows-1252
), ISO-8859-15