下载一个 gbk 编码的 xml,用 openuri 默认方式:text=URI.open(link, header).read,得出来的编码是 ascii-8bit。用以下代码转成 utf-8,偶尔提示:ascii-8bit 不能转换成 gbk: "\x???\x???"(???是具体的字符)。但是先把 text 写入到文件,然后再以 File.open(filename, 'r:gbk') 方式读取文件,又能读取到 gbk 文本。这让我郁闷不已。
encode('utf-8', 'gbk', {:invalid => :replace, :undef => :replace, :replace => '?'})
openuri 的文档也没讲怎么怎么指定编码。偶尔发现 URI.open(link, header).class=StringIO,也就是说 openuri 可能 不是先下载二进制流,然后才转成文本的。所以我全盘搜索 openuri,找到 openuri.rb。从 URI.open 跳到 Kernel.open,里面有个 uri.buffer。我知道 OpenURI 是扩展了 URI 模块,所以直接跳到 OpenURI.open_uri,发现第 148 行开始:
if /\Arb?(?:\Z|:([^:]+))/ =~ mode
encoding, = $1,Encoding.find($1) if $1
mode = nil
end
if options.has_key? :encoding
if !encoding.nil?
raise ArgumentError, "encoding specified twice"
end
encoding = Encoding.find(options[:encoding])
end
结合上下文,发现这个 options 就是 header,只不过我一开始把 URI.open() 的第二参数理解为发送给服务器的 header,却没想到 OpenURI 还从这个第二参数获取指定编码的值。于是我就在 http 的 header 里指定:
header[:encoding]='gbk'
然后重试,终于成功了。OpenURI.open(link, header).read 返回的是 gbk 文本,然后再用开头提到的 encode() 函数,转成了 utf-8,然后不管是在内存里比较还是先写入文件再比较,都能得到正确的结果。