在我的JRuby应用程序中,我从两个来源获得输入:

  • 外部文件
  • 一个Java程序,它调用我的JRuby代码并向我传递数据

一些外部数据被(假设)编码为ISO_8859_1,而我在内部将其处理为UTF_8,并生成UTF_8作为输出.

遗憾的是,有时会出现编码错误:数据偶尔包含无效的ISO_8859_1字节,这将无法修复.该规范要求简单地丢弃那些非法输入的字节.

对于文件,我使用以下命令读取该文件

string = File.new(filename, {external_encoding: Encoding::ISO_8859_1, internal_encoding: Encoding::UTF_8, converters: UTF8_CONVERTER})

converts子句负责跳过非法输入的字节.

对于从Java端接收的字符串,我当然可以将它们转换为UTF_8,方法是执行

string = iso_string.encode(Encoding::UTF_8)

但我怎么才能抓到这里的非法人物呢?根据我对Ruby Docs中encode方法的理解,可以在目的地编码之后声明的选项没有提供converts键.

UPDATE

下面是一个简单的例子来演示这个问题:

(1)好 case (无错误)

s = [49, 67].pack('C*')
put s
puts s.encoding
u = s.encode(Encoding::UTF_8)
puts u
puts u.encoding

这是打印的

1C    
ASCII-8BIT
1C
UTF-8

(2)错误 case

x = [49, 138, 67].pack('C*')
x.encode(Encoding::UTF_8)

不出所料,加薪UndefinedConversionError: ""\x8A"" from ASCII-8BIT to UTF-8

我try 了什么(尽管没有记录):

t = x.encode(external_encoding: Encoding::ISO_8859_1, internal_encoding: Encoding::UTF_8, converters: UTF8_CONVERTER)

有趣的是,这消除了例外,但转换并未成功.如果我做一个

t.encoding

我还是能看到ASCII-8BIT.似乎什么都没有被转换.我希望删除非法字符,即在本例中t是空字符串.

推荐答案

遗憾的是,有时会出现编码错误:数据偶尔包含无效的ISO_8859_1字节

这很奇怪,因为根本没有这回事.ISO 8859-1字符编码涵盖了所有256个可能的8位字节,并且没有一个是无效的.它们也都可以转换为Unicode--很简单,因为最低的256个Unicode码位对应于ISO 8859-1中的256个字符.

(它确实有65个不可打印的"控制字符"映射到字节0-31和127-159,但这些字符也都包含在Unicode中.这些控制字符包括一些相当常见的字符,如制表符、换行符和回车符,但也有many other rarely used ones个.)

您的实际问题似乎是,Ruby已经将您的字节字符串标记为具有默认的ASCII_8BIT编码,即not ISO_8859_1.这是一种特殊的编码,允许字符串包含所有256个8位字节,但只为其中前128个字节定义与7位ASCII字符编码对应的Unicode字符值.引用Ruby documentation的话:

Encoding::ASCII_8BIT是一种特殊编码,通常用于字节字符串,而不是字符串.但顾名思义,它在ASCII范围内的字符被认为是ASCII字符.当您将ASCII-8位字符与其他ASCII兼容字符一起使用时,这很有用.

无论如何,在您的情况下,解决方案只是使用String#force_encoding方法(它就地修改字符串,尽管由于某些原因缺少常规的感叹号!),以将字节字符串的编码更改为其应有的形式,即在您的情况下为Encoding::ISO_8859_1,如下所示:

x = [49, 138, 67].pack('C*')
puts "x = #{x.inspect} has encoding #{x.encoding}"
x.force_encoding(Encoding::ISO_8859_1)
puts "x = #{x.inspect} now has encoding #{x.encoding}"
u = x.encode(Encoding::UTF_8)
puts "u = #{u.inspect} has encoding #{u.encoding}"

它将打印:

x = "1\x8AC" has encoding ASCII-8BIT
x = "1\x8AC" now has encoding ISO-8859-1
u = "1\u008AC" has encoding UTF-8

如您所见,ISO 8859-1控制字符138(十六进制0x8A,在inspect输出中表示为\x8A)已成功转换为其Unicode等效项U+008A(\u008A).


Ps.也有可能您的输入数据实际上是ISO 8859-1编码中的not,但采用了其他相关编码,例如Windows-1252,这与ISO 8859-1的区别只在于它用各种附加符号和重音字母替换了65个不可打印的控制字符中的32个(确切地说,由128到159个字节组成的C1块).

如果是这种情况(通过try 将一些数据解码为Windows-1252并查看结果是否有意义,您应该能够相当容易地进行测试),您应该使用Encoding::WINDOWS_1252而不是Encoding::ISO_8859_1.例如:

x = [49, 138, 67].pack('C*')
puts "x = #{x.inspect} has encoding #{x.encoding}"
x.force_encoding(Encoding::WINDOWS_1252)
puts "x = #{x.inspect} now has encoding #{x.encoding}"
u = x.encode(Encoding::UTF_8)
puts "u = #{u.inspect} has encoding #{u.encoding}"

将打印:

x = "1\x8AC" has encoding ASCII-8BIT
x = "1\x8AC" now has encoding Windows-1252
u = "1ŠC" has encoding UTF-8

请注意,\x8A字节现在已被转换为重音字母Š,这是它在Windows-1252编码中所表示的.

Ruby相关问答推荐

为什么非数字在Ruby中被转换为integer?

Ruby Case语句和固定,不适用于attr_reader

有没有办法把条件语句写得更干净?

在 Ruby 中,如何从派生类中重写同一方法的不同方法调用基类方法?

如何返回此 OOP Ruby 代码最后一行中的变量?

在 Ruby 中为类添加实例变量

Ruby 相当于 Groovy 的 Elvis (?:) 运算符?

给url添加参数

为什么 ruby​​ 在 Windows 上这么慢?

我应该签入.ruby-gemset和/或.ruby-version吗?

Rails 类 << self

将元素添加到 ruby​​ 数组返回新数组

hash['key'] 到 Ruby 中的 hash.key

Ruby 的排序方法使用哪种算法?

Ruby 哈希默认值行为

仅针对特定参数的 Rspec 存根方法

为什么在 ruby​​ / rails / activerecord 中并不总是需要 self ?

在 Rspec 测试中运行 Rake 任务

如何判断变量是数字还是字符串?

Ruby哈希中的条件键/值