清理jruby中输入数据的编码错误

0lvr5msh  于 12个月前  发布在  Ruby
关注(0)|答案(2)|浏览(118)

在我的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端接收到的字符串,我当然可以通过执行

string = iso_string.encode(Encoding::UTF_8)

但是我怎么能在这里抓到非法的人物呢?根据我对encode方法的Ruby语法的理解,可以在目标编码之后声明的选项不提供 converts 键。

更新

下面是一个简单的例子来演示这个问题:
(1)良好情况(无错误)

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)错误情况

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

如预期的那样引发 UndefinedConversionError:“"\x8A””从ASCII-8BIT到UTF-8
我尝试了什么(虽然没有记录):

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

有趣的是,这摆脱了异常,但尽管如此,转换没有成功。如果我

t.encoding

ASCII-8BIT的缩写。似乎什么都没有改变。我希望看到非法字符被删除,即。在这种情况下,t是空字符串。

mbskvtky

mbskvtky1#

不幸的是,有时会出现编码错误:数据偶尔包含无效的字节ISO_8859_1
这很奇怪,因为没有这样的事情。ISO 8859-1字符编码涵盖了所有256个可能的8位字节,并且没有一个是无效的。它们也可以全部转换为Unicode,因为最低的256个Unicode码位与ISO 8859-1中的256个字符1:1对应。
(It有65个不可打印的“控制字符”Map到字节0-31和127-159,但这些都包含在Unicode中。这些控制字符包括一些相当常见的字符,如制表符、换行符和回车符,还有many other rarely used ones
你的实际问题似乎是Ruby将你的字节字符串标记为默认的ASCII_8BIT编码,* 而不是 * ISO_8859_1。这是一种特殊的编码,它允许一个字符串包含所有256个8位字节,但只为其中的前128个字节定义Unicode字符值,这些字节对应于7位ASCII字符编码。引用Ruby documentation
Encoding::ASCII_8BIT是一种特殊的编码,通常用于字节字符串,而不是字符串。但正如其名称所坚持的那样,它在ASCII范围内的字符被认为是ASCII字符。当您将ASCII-8BIT字符与其他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(十六进制0x 8A,在inspect输出中表示为\x8A)已成功转换为Unicode等效字符U+008 A(\u008A)。

**PS.**也有可能你的输入数据实际上 * 不是 * ISO 8859-1编码,而是其他一些相关的编码,比如Windows-1252,它与ISO 8859-1的区别只在于它用各种额外的符号和重音字母替换了65个不可打印的控制字符中的32个(确切地说,C1块由128到159的字节组成)。

如果是这种情况(您应该能够通过尝试将某些数据解码为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编码中所表示的。

vlf7wbxs

vlf7wbxs2#

我发现下面的方法是可行的(就我问题中的例子而言):

t = x.encode(Encoding::UTF_8, undef: :replace, replace: '')

相关问题