ruby 1.9,force_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 – 但如果那里有一个无效字节,我可能想要提高,或者我可能想用替换字符替换无效字节。
是不是有办法让ruby这样做?
更新我相信这最终已经添加到2.1中的ruby中,在2.1预览版本中使用String#scrub来执行此操作。 所以寻找它!
(更新:请参阅https://github.com/jrochkind/scrub_rb )
所以我编写了一个我需要的解决方案: https : //github.com/jrochkind/ensure_valid_encoding/blob/master/lib/ensure_valid_encoding.rb
但是最近我才意识到这实际上是内置于stdlib中,你只需要在某种程度上反直觉地将’binary’作为“源代码编译”:
a = "bad: \xc3\x28 okay".force_encoding("utf-8") a.encode("utf-8", "binary", :undef => :replace) => "bad: ( okay"
是的,这正是我想要的。 事实certificate这个IS内置于1.9 stdlib,它只是没有文档,很少有人知道它(或者很少有人说英语知道它?)。 虽然我看到这些论点在某个地方的博客上以这种方式使用,所以其他人都知道它!
在ruby 2.1中,stdlib最终使用scrub
支持。
确保您的脚本文件本身保存为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
要检查字符串是否没有无效序列,请尝试将其转换为二进制编码:
# 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
关于我能想到的唯一的事情是转码到一些东西并且不会损坏往返中的字符串:
string.force_encoding("UTF-8").encode("UTF-32LE").encode("UTF-8")
但是,这似乎相当浪费。
好吧,这是一个非常蹩脚的纯ruby方式,我想出了自己。 它可能表现为垃圾。 什么,ruby? 暂时没有选择我自己的答案,希望别人出现并给我们更好的东西。
# 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/更多的咆哮
如果您正在为“真实”用例执行此操作 – 例如,用于解析用户输入的不同字符串,而不仅仅是为了能够“解码”可以由尽可能多的编码组成的完全随机文件如你所愿,那么我猜你至少可以假设每个字符串的所有字符都具有相同的编码。
那么,在这种情况下,您会怎么看待这个?
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“专家”,所以请原谅我的解决方案是错误的还是有点天真…
我只是尝试回馈我能做的,这就是我所要做的,而我(我仍然)正在研究这个用于任意编码字符串的小解析器,我正在为一个研究项目做。
虽然我发布了这篇文章,但我必须承认我甚至没有对它进行过全面的测试。我……得到了一些“积极”的结果,但我觉得很可能找到了我正在努力寻找的东西(我一直都在阅读这篇文章,因为我觉得有必要尽可能快地分享它,希望它可以帮助节省一些时间,只要我一直在寻找这个已经……如果按预期工作:)
引发exception的一种简单方法似乎是:
untrusted_string.match /./
以下是两种常见情况以及如何在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
等单字节编码的无效性。 因此,上面的代码片段不起作用,即检测String是否是有效的ISO-8859-1
编码。 -
尽管
UTF-8
作为网络中的默认编码越来越受欢迎,但ISO-8859-1
和其他Latin1
种口味在西方国家仍然非常流行,特别是在北美。 请注意,有几个单字节编码非常相似,但与ISO-8859-1略有不同。 示例:CP1252
(又名Windows-1252
),ISO-8859-15