2012-04-17 2 views
25

У меня есть строка, которую я прочитал из какого-то ввода.ruby ​​1.9, force_encoding, но проверьте

Насколько мне известно, это UTF8. Хорошо:

string.force_encoding("utf8") 

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

Как правило, будет принудительно повышаться («utf8»), если он встречает такие байты? I считаю это не будет.

Если бы я делал #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" 

Так рубин получил ум, чтобы знать, что плохие байт в кодировке UTF-8, и заменить им, с чем-то еще - при преобразовании в другой кодировке. Но я не хочу хочу, чтобы преобразовать в другую кодировку, я хочу остаться utf8 - но я могу поднять, если там есть недопустимый байт, или я могу заменить недействительные байты на заменяющие символы.

Нет ли способа получить рубин для этого?

обновление Я считаю, что это, наконец, было добавлено к рубину в 2.1, с стиранием String #, представленным в предварительном выпуске 2.1, для этого. Так что ищите!

ответ

16

(обновление: см 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, он просто недокументирован, и мало кто знает об этом (или, может быть, мало кто говорит по-английски об этом?). Хотя я видел, как эти аргументы использовались таким образом в блоге где-то, так что кто-то знал это!

+0

Используя Ruby 1.9.3-p484, этот ошибочно помеченный байтом \ xc0 в файле iso-8859-1 как ненадлежащее кодирование. Я обнаружил, что для моих нескольких тестовых случаев 'encode ('binary',: undef =>: replace)' похоже работает: iso-8859-1 проходит, но файл UTF-8 с неправильной последовательностью пойманы. –

+0

См. [Этот новый ответ] (http://stackoverflow.com/a/21686992/238886) для кода, который не страдает от проблемы, о которой я упоминал выше. –

0

О единственное, что я могу думать о том, чтобы перекодировать к чему-то и обратно, что не повредит строку в редиректа:

string.force_encoding("UTF-8").encode("UTF-32LE").encode("UTF-8") 

кажется довольно расточительно, хотя.

+0

тьфу. Помимо расточительности, вам необходимо убедиться, что вы знаете, какие кодировки будут круглыми, не теряя ничего. Я бы хотел использовать решение общего назначения, которое будет работать на любом произвольном входном кодировании. Рубин знает, как это сделать с любой кодировкой, когда на самом деле транскодирование, почему он не может это сделать для меня? Раздражает. – jrochkind

+2

Вы всегда можете совершать круговое путешествие между любыми UTF; Unicode - это Unicode, независимо от того, как вы его представляете. Только когда вы выходите из Unicode, вы можете потерять что-то в переводе. –

+0

Право, я хочу решение, которое не предполагает unicode. – jrochkind

0

Хорошо, это действительно хромой чистый рубиновый способ сделать это Я сам понял. Вероятно, он работает на дерьмо. что за черт, рубин? На данный момент я не выбираю свой собственный ответ, надеясь, что кто-то еще появится и даст нам что-то лучшее.

# Pass in a string, will raise an Encoding::InvalidByteSequenceError 
# if it contains an invalid byte for it's 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's 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's a better way to tell if 
      # an encoding is a 'Unicode encoding form' 
      # than this? What's 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/

4

убедитесь, что сама ваша файл_сценария сохраняется как 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] 

Итак, ваш плохой символ заменен, вы можете сделать это сразу

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 
+0

Я не хочу менять кодировки по ISO-8859-1. Я хочу оставить его в исходной кодировке. Теперь вы скажете «хорошо, а затем перекодируйте до 8859 1, а затем снова». Я хочу решение, которое будет работать на любой произвольной кодировке; вы не можете перекодировать до 8859 и обратно без потерь для любой произвольной кодировки. – jrochkind

+0

ok, только что отредактировал мой ответ – peter

+0

Спасибо. Я самостоятельно пришел к чему-то подобному, но вы можете объяснить, что это делает: 'c.encode! (Encoding.locale_charmap,: invalid =>: replace)'? Это перекод? Я не хочу перекодировать (изменять кодировку) строки, независимо от того, какая кодировка начинается и какова моя локальная кодировка по умолчанию. Но я думаю, что я уже пришел к тому, что вы в конечном итоге тоже учтете это, посмотрите на мой ответ на этот вопрос. – jrochkind

0

Если вы делаете это для «реальной жизни» прецедент - например, для разбора различных строк, вводимых пользователями, а не только ради того, чтобы быть в состоянии «декодировать» абсолютно случайный файл, который может быть сделано из множества кодировок, как вам угодно, тогда я думаю, вы могли бы по крайней мере предположить, что все charcters для каждой строки имеют одинаковую кодировку.

Тогда, в этом случае, что бы вы подумали об этом?

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 
} 

Я не Рубин «про» в любом случае, поэтому, пожалуйста, простите, если мое решение является неправильным или даже немного наивный ..

Я просто пытаюсь вернуть то, что я могу, и это это то, к чему я пришел, пока я был (до сих пор), работая над этим маленьким парсером для произвольно закодированных строк, которые я делаю для учебного проекта.

В то время как я отправляю это, я должен признать, что я даже не полностью его протестировал .. Я .. получил пару «положительных» результатов, но я был так взволнован, возможно, обнаружив, что я пытаясь найти (и все время, что я читал об этом на SO ..), я просто почувствовал необходимость делиться им как можно быстрее, надеясь, что это может помочь сэкономить время любому, кто искал это для до тех пор, как я был ... .. если он работает, как ожидалось :)

+0

Вот что я в итоге сделал: https://github.com/jrochkind/ensure_valid_encoding/ blob/master/lib/secure_valid_encoding.rb Главное, что я знаю, что строка _supposed_ должна быть закодирована как, но в ней могут быть плохие байты. Ваше решение больше пытается угадать, что такое кодировка «действительно», что является другой проблемой. – jrochkind

+0

Напомним: 1) у вас либо есть плохие закодированные символы, либо повреждение данных (из соображений в вашем github вы предполагаете, что обе вещи могут быть причиной проблемы), 2) вы, похоже, не заботитесь о неправильном кодировании, потому что вы только хотите сохраняйте допустимые символы utf-8 (вы не проверяете, недействительны ли плохие данные при использовании другой кодировки). Люди советуют конвертировать в другую кодировку как средство для проверки недействительных байтов, но тогда вы боитесь, возможно, потерять некоторые данные , В чем смысл в том, что если вы не проверяете правильность предполагаемой кодировки в первую очередь? (Так что, возможно, все равно теряете данные?) –

+0

Спасибо за ответ, пытаясь убедить меня, что глупо делать то, что мне нужно сделать, но, видимо, многие другие не согласны с тех пор, как рубин добавил его в stdlib с помощью String # scrub в ruby ​​2.1! На самом деле, я понимаю, что я делаю, и есть много случаев, когда имеет смысл это делать (попробовали ли вы проверить, что делает vim или ваш другой любимый редактор в этом случае?), Но точка этот билет не должен был убедить вас в этом факте. – jrochkind

0

Простой способ вызвать исключение, кажется:

untrusted_string.match /./

+1

Если вам просто нужно исключение для недействительных строк, вы можете просто выполнить: 'raise Exception.new, если string.valid_encoding?' Он заменяет неправильные байты на заменяющие символы, что является более сложным. – jrochkind

3

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

# 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 

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

Небольшая модификация этого кода возвращает фактическую ошибку, которая имеет ценную информацию о неправильной кодировке:

# 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 
0

Вот 2 общие ситуации, и как с ними бороться в Руби 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: äöüß" 

Notes

  • Приведенные выше фрагменты кода Предположим, что рубин кодирует все строки в 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 (а.к.а. Windows-1252), ISO-8859-15

+0

Я бы не пропустил параметр для скрапа, хотя я бы сказал, что неудачные байты отображаются как символ замены unicode (), а не полностью стираются. Я считаю, что по умолчанию правильное правильное поведение по умолчанию. – jrochkind

+0

@jrochkind: Я согласен, что для разных приложений вы хотите иметь другое поведение. Если человек посмотрит на преобразованную строку (строки), то, скорее всего, вы захотите заменить плохие байты символом замены по умолчанию (). Однако есть и другие ситуации. Чтобы привести пример: откуда я пришел, мы работаем с гигабайтами потоков данных с ненадежными кодировками. Мы хотим отфильтровать только определенные данные. Для правильной работы нам нужны правильные строки UTF-8, но нам не нужны плохие байты. В таких сценариях я рекомендую удалить лишние байты. –

+0

Я уверен, что есть случаи, которые подходят, но они предназначены для особых целей. Независимо от того, сколько гигабайт данных, я не думаю, что когда-нибудь захочет неправильно закодированный Macapá (город в Бразилии), превратившийся в Macap (место в Индонезии) вместо Macap . В качестве общего предложения по умолчанию, не зная чей-то специальный вариант использования, по умолчанию процедура, использующая символ замены юникода, является подходящей - эти люди из Юникода знали, что они делают. – jrochkind