Подтвердить что ты не робот

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, для этого. Так что ищите!

4b9b3361

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

Ответ 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