utf-8 - 通过 octets->string->unpack 解压二进制文件失败:signed int `#(243 0)` is invalid UTF8
问题描述
我正在解析一个混合了字符、浮点数、整数和短裤的二进制文件(nifti)(使用PDL::IO::Nifti cpan 模块作为参考)。
我很幸运地将八位字节序列解析为字符串,以便将它们传递给cl-pack:unpack
. 这很复杂,但便于使用 perl 模块作为参考进行移植。
此策略在读取#(243 0)
为二进制文件时失败
(setf my-problem (make-array 2
:element-type '(unsigned-byte 8)
:initial-contents #(243 0)))
(babel:octets-to-string my-problem)
非法 :UTF-8 字符从位置 0 开始
并且,当尝试将文件读取为char*
八位字节序列#(243 0 1 0) 无法解码。
我希望有一个我还没有弄清楚的简单编码问题。尝试反向(打包243
和获取八位字节)会给出一个长度为 3 的向量,而我期望它是 2。
(babel:string-to-octets (cl-pack:pack "s" 243))
; yields #(195 179 0) expect #(243 0)
完整的上下文
;; can read up to position 40. at which we expect 8 signed ints.
;; 4th int is value "243" but octet cannot be parsed
(setq fid-bin (open "test.nii" :direction :input :element-type 'unsigned-byte))
(file-position fid-bin 40)
(setf seq (make-array (* 2 8) :element-type '(unsigned-byte 8)))
(read-sequence seq fid-bin)
; seq: #(3 0 0 1 44 1 243 0 1 0 1 0 1 0 1 0)
(babel:octets-to-string seq) ; Illegal :UTF-8 character starting at position 6.
(sb-ext:octets-to-string seq) ; Illegal ....
;; first 3 are as expected
(cl-pack:unpack "s3" (babel:octets-to-string (subseq seq 0 6)))
; 3 256 300
(setf my-problem (subseq seq 6 8)) ; #(243 0)
(babel:octets-to-string my-problem) ; Illegal :UTF-8 character starting at position 0.
;; checking the reverse direction
;; 243 gets represented as 3 bytes!?
(babel:string-to-octets (cl-pack:pack "s3" 3 256 300)) ; #(3 0 0 1 44 1)
(babel:string-to-octets (cl-pack:pack "s4" 3 256 300 243)) ; #(3 0 0 1 44 1 195 179 0)
(setq fid-str (open "test.nii" :direction :input))
(setf char-seq (make-array (* 2 8) :initial-element nil :element-type 'char*))
(file-position fid-str 40)
(read-sequence char-seq fid-str)
;; :UTF-8 stream decoding error on #<SB-SYS:FD-STREAM ....
;; the octet sequence #(243 0 1 0) cannot be decoded.
perl 等价物
open my $f, "test.nii";
seek $f, 46, 0;
read $f,my $b, 2;
print(unpack "s", $b); # 243
解决方案
问题是您使用的函数试图将某些八位字节序列视为字符序列(或某些 Unicode 事物:我认为除了 Unicode 中的字符之外还有其他事物)的编码表示。特别是,在您的情况下,您正在使用的函数将八位字节序列视为某些字符串的 UTF-8 编码。好吧,并不是所有的八位字节序列都是合法的 UTF-8,所以这些函数正确地在一个非法的八位字节序列上呕吐。
但那是因为你没有做正确的事情:你想做的是获取一个八位字节序列并创建一个字符串,其char-code
s 是那些八位字节。您不想使用任何愚蠢的编码大字符在小整数中的垃圾,因为您永远不会看到任何大字符。你想要类似这些函数的东西(两者都有点误称,因为除非你是,否则它们不会对整个八位字节的事情大惊小怪)。
(defun stringify-octets (octets &key
(element-type 'character)
(into (make-string (length octets)
:element-type element-type)))
;; Smash a sequence of octets into a string.
(map-into into #'code-char octets))
(defun octetify-string (string &key
(element-type `(integer 0 (,char-code-limit)))
(into (make-array (length string)
:element-type element-type)))
;; smash a string into an array of 'octets' (not actually octets)
(map-into into #'char-code string))
现在您可以检查一切是否正常:
> (octetify-string (pack "s" 243))
#(243 0)
> (unpack "s" (stringify-octets (octetify-string (pack "s" 243))))
243
等等。给定您的示例序列:
> (unpack "s8" (stringify-octets #(3 0 0 1 44 1 243 0 1 0 1 0 1 0 1 0)))
3
256
300
243
1
1
1
1
一个更好的方法是让打包和解包函数简单地处理八位字节序列。但我怀疑这是一个失败的原因。一种可怕但比将八位字节序列转换为字符更可怕的临时方法是将文件作为文本读取,但使用根本不翻译的外部格式。如何做到这一点取决于实现(但基于 latin-1 的东西将是一个好的开始)。
推荐阅读
- laravel - Google Pub/Sub:用户在尝试提取消息时无权执行此操作
- c++ - 尝试将对象添加到向量并显示结果,但出现段错误但无法正常工作
- reactjs - TypeError:无法使用 React 将未定义转换为对象
- makefile - 多个目录上的 Makefile 模式匹配
- powershell - 运行 Add-LocalGroupMember 的 PowerShell 错误
- reactjs - 在材料 UI ASC 和 DESC 中的表格上添加默认排序正在工作,但未按原始顺序排序
- regex - 正则表达式匹配除
- xml - 将 PDF 转换为 XML 结构
- google-apps-script - Google Scripts onEdit 显示 API 调用错误
- sql-server - 仅启用了 Azure Active Directory 身份验证的 Azure 弹性作业代理