ruby 1.9, force_encoding, но проверьте

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

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

<code>string.force_encoding("utf8")
</code>

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

Обычно, вызывается force_encoding (& quot; utf8 & quot;), если он встречает такие байты? яbelieve Я не буду.

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

Но я не делаю #encode, я делаю #force_encoding. У него нет таких вариантов.

Будет ли смысл

<code>string.force_encoding("utf8").encode("utf8")
</code>

получить исключение сразу? Обычно кодированиеfrom utf8to utf8 не имеет никакого смысла. Но, может быть, это способ получить его сразу же, если есть недействительные байты? Или используйте:replace вариант и т.д., чтобы сделать что-то другое с недопустимыми байтами?

Но нет, похоже, это тоже не сработает.

Кто-нибудь знает?

<code>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
</code>

Хорошо, но как мне найти и устранить эти плохие байты? Как ни странно, это НЕ вызывает:

<code>1.9.3-p0 :035 > a.encode("utf-8")
 => "bad: \xC3( okay"
</code>

Если бы я конвертировал в другую кодировку, это было бы!

<code>1.9.3-p0 :039 > a.encode("ISO-8859-1")
Encoding::InvalidByteSequenceError: "\xC3" followed by "(" on UTF-8
</code>

Или, если бы я сказал это, он заменил бы его на "?" = & GT;

<code>1.9.3-p0 :040 > a.encode("ISO-8859-1", :invalid => :replace)
=> "bad: ?( okay"
</code>

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

Есть ли какой-нибудь способ получить рубин, чтобы сделать это?

update Я полагаю, что это наконец-то было добавлено в ruby в версии 2.1, и String # scrub присутствует в предварительной версии 2.1 для этого. Ищите это!

Ответы на вопрос(9)

Простой способ спровоцировать исключение выглядит так:

untrusted_string.match /./

 jrochkind10 нояб. 2013 г., 15:59
Если вы просто хотите исключение для недопустимых строк, вы можете просто сделать:raise Exception.new unless string.valid_encoding?  Он заменяет плохие байты заменяемыми символами, что является более сложной задачей.

Хорошо, вот действительно неудачный, чистый рубиновый способ сделать это, я сам понял. Это, вероятно, выполняет для дерьма. какого черта, рубин? Пока не выбираю мой собственный ответ, надеясь, что кто-то еще появится и даст нам что-то лучшее.

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

Вот 2 общие ситуации и как с ними справиться вRuby 2.1+, Я знаю, что этот вопрос относится к Ruby v1.9, но, возможно, это полезно для других, которые находят этот вопрос через Google.

Situation 1

You have an UTF-8 string with possibly a few invalid bytes
Удалить недействительные байты:

str = "Partly valid\xE4 UTF-8 encoding: äöüß"

str.scrub('')
 # => "Partly valid UTF-8 encoding: äöüß"
Situation 2

You have a string that could be in either UTF-8 or ISO-8859-1 encoding
Проверьте, какая это кодировка и конвертируйте в 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

The above code snippets assume that Ruby encodes all your strings in UTF-8 by default. Even though, this is almost always the case, you can make sure of this by starting your scripts with # encoding: UTF-8.

If invalid, it is programmatically possible to detect most multi-byte encodings like UTF-8 (in Ruby, see: #valid_encoding?). However, it is NOT (easily) possible to programmatically detect invalidity of single-byte-encodings like ISO-8859-1. Thus the above code snippet does not work the other way around, i.e. detecting if a String is valid ISO-8859-1 encoding.

Even though UTF-8 has become increasingly popular as the default encoding in the web, ISO-8859-1 and other Latin1 flavors are still very popular in the Western countries, especially in North America. Be aware that there a several single-byte encodings out there that are very similar, but slightly vary from ISO-8859-1. Examples: CP1252 (a.k.a. Windows-1252), ISO-8859-15

 jrochkind18 февр. 2016 г., 22:25
Я бы не передавал параметр для очистки, хотя я быwant плохие байты отображаются как символ замены юникода (& # xFFFD;), но не стираются полностью. Я думаю, что по умолчанию правильное поведение по умолчанию.
 jrochkind20 февр. 2016 г., 03:26
Я уверен, что есть случаи, которые уместны, но они имеют специальное назначение. Независимо от того, сколько гигабайт данных, я не думаю, что когда-либо захочу неправильно закодировать Macap & # xE1; (город в Бразилии) превращается в Macap (место в Индонезии) вместо Macap & # xFFFD ;. В качестве общего предложения по умолчанию, не зная чьего-либо особого варианта использования, по умолчанию подпрограмма, использующая заменяющий символ Unicode, является подходящей - эти люди Unicode знали, что они делали.
 20 февр. 2016 г., 00:19
@jrochkind: я согласен, что для разных приложений вы хотите иметь разное поведение. Если человек посмотрит на преобразованную строку (и), то, скорее всего, вы захотите заменить плохие байты символом замены по умолчанию (& # xFFFD;). Однако есть и другие ситуации. В качестве примера: откуда я пришел, мы работаем с гигабайтами потоков данных с ненадежными кодировками. Мы хотим фильтровать только определенную информацию. Для правильной работы нам нужны правильные строки UTF-8, но мы не заботимся о плохих байтах. В таких случаях я рекомендую удалять плохие байты.

В ruby 2.1, stdlib наконец поддерживает этоscrub.

http://ruby-doc.org/core-2.1.0/String.html#method-i-scrub

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

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

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

 jrochkind12 мар. 2013 г., 02:42
Вот что я в итоге сделал:github.com/jrochkind/ensure_valid_encoding/blob/master/lib/…  Главное, что я знаю, что это за строкаsupposed быть закодированным как, но это может иметь плохие байты в нем. Ваше решение больше пытается угадать, что кодировка "на самом деле" есть, это другая проблема.
 jrochkind07 нояб. 2014 г., 16:00
Если в нем были плохие байты, то вы, очевидно, ошибались, будучи на 100% уверены в кодировке! По определению это была не та кодировка. До того, как scrub был в stdlib, вы должны были выяснить, как реализовать scrub самостоятельно в чистом рубине, что я и сделал в итоге - а не просматривая кодировки один за другим, а это не то, что я искал - что скраб делает то, что я искал. Достигаете ли вы в целом большого успеха в stackoverflow, пытаясь сказать людям, что их требования глупы, и они глупы, и они не должны задавать вопрос, который они задают?
 jrochkind06 нояб. 2014 г., 21:25
Спасибо за ответ, пытаясь убедить меня, что глупо делать то, что мне нужно, но, очевидно, многие другие не согласны, так как ruby добавил его в stdlib с помощью String # scrub в ruby 2.1! На самом деле, я понимаю, что я делаю, и во многих случаях имеет смысл сделать это (пытались ли вы проверить, что в этом случае делает vim или другой ваш любимый редактор?), Но смысл этот билет не должен был убедить вас в этом факте.
 06 нояб. 2014 г., 21:51
#scrub предназначен для случаев, когда вы УВЕРЕНЫ в строковом кодировании, иначе вы потеряете данные. Вы сказали в первом комментарии выше, что вы знаете, что это за строкаSUPPOSED быть закодированным как & quot ;. Это на английском языке не означает, что вы на 100% УВЕРЕНЫ в кодировке, но это ДОЛЖНО быть так. Так что не жалуйтесь. Если бы вы не имели в виду это, то вы могли бы просто сказать с самого начала, что вы УВЕРЕНЫ в кодировке и что вы хотели только удалить плохие байты. И, кстати, перед #scrub вам пришлось вручную проходить различные кодировки, чтобы сделать это. Что я и посоветовал.
 24 мар. 2013 г., 19:01
Напомним: 1) у вас либо плохо закодированные символы, либо повреждение данных (из обоснования на вашем github вы предполагаете, что обе вещи могут быть причиной проблемы), 2) вам кажется, что вас не заботит неправильная кодировка, потому что вы хотите только сохранить действительные символы utf-8 (вы не должны проверять, являются ли неверные данные действительными с другой кодировкой) - Люди советуют преобразовывать в другую кодировку в качестве средства проверки на недопустимые байты, но тогда вы боитесь возможной потери некоторых данных. Какой смысл в том, что если вы не проверяете достоверность предполагаемой кодировки в первую очередь? (Так что, возможно, потеря данных в любом случае?)

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

# 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
Решение Вопроса

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

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

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

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

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

 jrochkind19 апр. 2012 г., 14:14
Хорошо, я хочу решение, которое не предполагает Unicode.
 19 апр. 2012 г., 17:15
А? Вы уже предполагаете, что вводом является UTF-8, что означает, что вы уже принимаете Unicode. Я не понимаю это возражение.
 jrochkind20 апр. 2012 г., 17:11
Я пишу код библиотеки, где я должен иметь возможность "проверять и восстанавливать правильность кодирования" для любой произвольной кодировки символов. В данном случае это был UTF-8, но он должен быть библиотечной функцией, которая может иметь дело с любой произвольной кодировкой ruby 1.9 в качестве входного параметра.
 jrochkind18 апр. 2012 г., 03:42
тьфу. Помимо того, что вы расточительны, вам нужно быть уверенным в том, что вы знаете, какие кодировки могут быть использованы в любом случае, не теряя ничего. Мне нравится решение общего назначения, которое будет работать с любой произвольной входной кодировкой - ruby знает, как сделать это с любой кодировкой при фактическом транскодировании, почему он не может сделать это для меня? Раздражает.
 19 апр. 2012 г., 02:29
Вы всегда можете совершить поездку туда и обратно между любыми UTF-ами; Unicode - это Unicode, независимо от того, как вы его представляете. Только когда вы выходите из Юникода, вы можете потерять что-то в переводе.

убедитесь, что сам скрипт-файл сохранен как 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"

РЕДАКТИРОВАТЬ: здесь решение, которое работает на любую произвольную кодировку, первая кодирует только плохие символы, вторая просто заменяет на?

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
 19 апр. 2012 г., 17:57
хорошо, только что отредактировал мой ответ
 jrochkind20 апр. 2012 г., 17:13
Благодарю. Я независимо пришел к чему-то похожему, но вы можете объяснить, что это делает:c.encode!(Encoding.locale_charmap, :invalid => :replace)? Это транскод? Я не хочу транскодировать (изменять кодировку) строки независимо от того, в какой кодировке она начинается и какова моя кодировка локали по умолчанию. Но я думаю, что я уже достиг того места, где вы в конечном итоге тоже это учтете, см. Мой ответ на этот вопрос.
 jrochkind19 апр. 2012 г., 14:15
Я не хочу менять кодировки на ISO-8859-1. Я хочу оставить его в оригинальной кодировке. Теперь вы скажете "хорошо", затем перекодируйте в 8859 1 и затем вернитесь снова. " Я хочу решение, которое будет работать с любой произвольной кодировкой; Вы не можете перекодировать в 8859 и обратно без потерь для любого произвольного кодирования.
 20 апр. 2012 г., 20:54
я не изменяю кодировку, поскольку она совпадает с кодировкой входной строки, в любом случае, у вас есть решение, которое имеет значение

Ваш ответ на вопрос