26

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

据我所知,它是 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 来执行此操作。所以找那个!

4

9 回答 9

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 中的,它只是没有记录,很少有人知道(或者可能很少有人会说英语?)。虽然我在某处的博客上看到这些论点以这种方式使用,但其他人也知道!

于 2013-03-12T01:49:46.840 回答
6

在 ruby​​ 2.1 中,stdlib 最终通过scrub.

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

于 2014-06-10T12:06:13.607 回答
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
于 2012-04-18T11:28:59.490 回答
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
于 2014-02-10T20:13:47.737 回答
0

关于我唯一能想到的是将代码转换为不会损坏往返字符串的内容并返回:

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

不过,似乎相当浪费。

于 2012-04-18T01:17:33.370 回答
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/

于 2012-04-18T02:37:28.403 回答
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 “专业人士”,所以如果我的解决方案有误甚至有点幼稚,请原谅。

我只是尽量回馈我所能做的,这就是我的目标,而我正在(我仍然在)研究这个用于任意编码字符串的小解析器,我正在为一个研究项目做这个。

当我发布这篇文章时,我必须承认我什至还没有完全测试过它.. 我.. 刚刚得到了几个“积极”的结果,但我对可能找到我正在努力寻找的东西感到非常兴奋(并且我花了所有时间在 SO..) 上阅读此内容,我只是觉得有必要尽快分享它,希望它可以帮助任何人节省一些时间一直......如果它按预期工作:)

于 2013-03-11T01:35:10.500 回答
0

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

untrusted_string.match /./

于 2013-11-09T11:57:42.287 回答
0

这里有 2 种常见情况以及如何在Ruby 2.1+中处理它们。我知道,这个问题是指 Ruby v1.9,但也许这有助于其他人通过谷歌找到这个问题。

情况一

您有一个 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: äöüß"

笔记

  • 上面的代码片段假定 RubyUTF-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

于 2016-02-18T20:35:07.117 回答