ruby 1.9,force_encoding,但检查

时间:2012-04-17 23:12:47

标签: ruby character-encoding

我有一个我从某种输入中读过的字符串。

据我所知,它是UTF8。好:

string.force_encoding("utf8")

但是如果这个字符串中包含的字节实际上不是合法的UTF8,我现在想知道并采取行动。

通常,如果遇到这样的字节,force_encoding(“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中的坏字节,并用其他东西替换em。但我不想希望转换为不同的编码,我想保持utf8 - 但如果那里有一个无效的字节,我可能想要提高,或者我可能想要替换无效的字节替换字符。

有没有办法让红宝石这样做?

更新我相信这最终已经添加到2.1中的ruby中,在2.1预览版本中使用String#scrub来执行此操作。所以寻找它!

9 个答案:

答案 0 :(得分: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"
是的,这正是我想要的。事实证明这个IS内置于1.9 stdlib,它只是没有文档,很少有人知道它(或者很少有人说英语知道它?)。虽然我看到这些论点在某个地方的博客上以这种方式使用,所以其他人都知道它!

答案 1 :(得分:6)

在ruby 2.1中,stdlib最终支持scrub

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

答案 2 :(得分: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"
编辑:这里有一个适用于任意编码的解决方案,第一个只编码坏字符,第二个只替换为?

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

答案 3 :(得分: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

答案 4 :(得分:0)

关于我能想到的唯一一件事就是转码到一些不会在往返途中损坏字符串的东西:

string.force_encoding("UTF-8").encode("UTF-32LE").encode("UTF-8")
但是,这似乎相当浪费。

答案 5 :(得分: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/

更多的咆哮

答案 6 :(得分:0)

如果您正在为“真实”用例执行此操作 - 例如,用于解析用户输入的不同字符串,而不仅仅是为了能够“解码”可以由其组成的完全随机文件尽可能多的编码,然后我想你至少可以假设每个字符串的所有字符具有相同的编码。

然后,在这种情况下,你会怎么看?

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“专家”,所以请原谅我的解决方案是错误的还是有点天真......

我只是试着回馈我能做的事情,这就是我所要做的,而我(我还在)正在研究这个用于任意编码字符串的小解析器,我正在为一个研究项目做

虽然我发布这篇文章,但我必须承认我甚至没有对它进行过全面测试..我......只是得到了一些“积极”的结果,但我觉得很可能找到了我正在努力的事情我一直觉得有必要尽可能快地分享它,并希望它可以帮助节省一些时间来寻找这个的人。只要我一直......如果它按预期工作:)

答案 7 :(得分:0)

引发异常的一种简单方法似乎是:

untrusted_string.match /./

答案 8 :(得分:0)

以下是两种常见情况以及如何在 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