2015-08-08 15 views
6

Tôi đang viết một chương trình trong Common Lisp để chỉnh sửa tập tin nhị phân được tạo ra bởi Minecraft mà sử dụng định dạng NBT, tài liệu ở đây: http://minecraft.gamepedia.com/NBT_format?cookieSetup=true (I nhận thức được rằng các công cụ như vậy tồn tại, chẳng hạn như NBTEditor và MCEdit, nhưng không được viết bằng Common Lisp, và tôi nghĩ dự án này sẽ tạo ra một bài tập học tập xuất sắc).Parsing UTF-8 chuỗi có độ dài được biết đến trong Common Lisp một byte tại một thời điểm

Cho đến nay, một trong những điều duy nhất mà tôi chưa quản lý để tự thực hiện là chức năng đọc chuỗi UTF-8 có độ dài đã biết có chứa các ký tự được biểu diễn bằng nhiều hơn một octet -ASCII ký tự). Trong định dạng NBT, mỗi chuỗi được mã hóa UTF-8 và được đặt trước bởi một số nguyên ngắn (hai octet) n biểu thị độ dài của chuỗi. Vì vậy, giả định rằng chỉ các ký tự ASCII có mặt trong chuỗi, tôi có thể chỉ đơn giản là đọc một chuỗi các n octet từ stream và chuyển đổi nó thành một chuỗi sử dụng như sau:

(defun read-utf-8-string (string-length byte-stream) 
    (let ((seq (make-array string-length :element-type '(unsigned-byte 8) 
             :fill-pointer t))) 
    (setf (fill-pointer seq) (read-sequence seq byte-stream)) 
    (flexi-streams:octets-to-string seq :external-format :utf-8))) 

Nhưng nếu một hoặc nhiều ký tự có một mã ký tự lớn hơn 255, nó được mã hóa trong hai hoặc nhiều byte, như đã thấy trong ví dụ này:

(flexi-streams:string-to-octets "wife" :external-format :utf-8) 
==> #(119 105 102 101) 

(flexi-streams:string-to-octets "жена" :external-format :utf-8) 
==> #(208 182 208 181 208 189 208 176) 

Cả hai chuỗi có độ dài như nhau, nhưng mỗi nhân vật của từ Nga được mã hóa trong đôi số octet, vì vậy tổng kích thước của s tring gấp đôi so với tiếng Anh. Vì vậy, biết chiều dài chuỗi không giúp đỡ nếu sử dụng chuỗi đọc. Ngay cả khi kích thước của chuỗi (tức là số octet cần mã hóa nó) đã được biết, vẫn không có cách nào biết được octet nào chuyển đổi thành dạng ký tự riêng lẻ và nhóm nào để nhóm lại với nhau để chuyển đổi. Vì vậy, thay vì lăn chức năng của riêng tôi, tôi đã cố gắng tìm một cách để có được hoặc thực hiện (Clozure CL) hoặc một thư viện bên ngoài làm công việc cho tôi. Thật không may này quá đã có vấn đề, bởi vì phân tích cú pháp của tôi dựa vào việc sử dụng các dòng tập tin tương tự cho tất cả các chức năng đọc, như thế này:

(with-open-file (stream "test.dat" :direction :input 
            :element-type '(unsigned-byte 8)) 
    ;;Read entire contents of NBT file from stream here) 

làm hạn chế tôi đến :element-type '(unsigned-byte 8) và do đó ngăn cấm tôi từ cách xác định một mã hóa ký tự và sử dụng đọc -char (hoặc tương đương) như thế này:

(with-open-file (stream "test.dat" :external-format :utf-8) 
    ...) 

các :element-type phải là '(unsigned-byte 8) để tôi có thể đọc và ghi các số nguyên và nổi các kích cỡ khác nhau. Để tránh phải chuyển đổi chuỗi octet thành chuỗi theo cách thủ công, trước hết tôi tự hỏi liệu có cách nào để thay đổi loại phần tử thành ký tự trong khi tệp đang mở, dẫn tôi đến cuộc thảo luận này tại đây: https://groups.google.com/forum/#!searchin/comp.lang.lisp/binary $ 20write $ 20 Đọc/comp.lang.lisp/N0IESNPSPCU/​​Qmcvtk0HkC0J Rõ ràng một số triển khai CL như SBCL sử dụng các luồng hai hóa trị theo mặc định, vì vậy cả hai byte đọc và đọc-char có thể được sử dụng trên cùng một luồng; nếu tôi sử dụng phương pháp này, tôi vẫn cần có thể chỉ định :external-format cho luồng (:utf-8), mặc dù định dạng này chỉ nên áp dụng khi đọc các ký tự chứ không phải khi đọc byte thô.

Tôi đã sử dụng một vài chức năng từ các dòng flexi trong các ví dụ trên vì lợi ích ngắn gọn, nhưng mã của tôi chỉ sử dụng các loại luồng tích hợp và tôi vẫn chưa sử dụng chính dòng flexi. Vấn đề này có phải là một ứng cử viên tốt cho các dòng linh hoạt không? Có một lớp trừu tượng bổ sung cho phép tôi đọc các byte thô và các ký tự UTF-8 thay thế cho nhau từ cùng một luồng sẽ là lý tưởng.

Bất kỳ lời khuyên nào từ những người quen thuộc với các luồng linh hoạt (hoặc các cách tiếp cận có liên quan khác) sẽ được đánh giá rất nhiều.

Cảm ơn bạn.

+1

AIUI, tiền tố độ dài cho độ dài của chuỗi byte mà bạn có để giải mã sau. Vì vậy, chức năng đầu tiên của bạn nên được hoàn toàn đầy đủ. – angus

+0

Khi nó quay ra, bạn là chính xác! Tôi đã hiểu sai một chút về đặc điểm kỹ thuật vào thời điểm đó. Vì vậy, trước đây trong mã của tôi, tôi đã tính toán giá trị 'string-length' bằng cách sử dụng' (length mystring) ', tôi chỉ cần viết' (length (flexi-stream: string-to-octets mystring: external-format: utf-8)) 'thay thế. Điều này tiết kiệm phải tính toán số octet trong đó mỗi ký tự phải được mã hóa, như trong câu trả lời của Rainer. –

Trả lời

4

Đây là nội dung tôi đã viết:

Trước tiên, chúng tôi muốn biết mã hóa cho một số ký tự thực sự là bao lâu, cho byte đầu tiên.

(defun utf-8-number-of-bytes (first-byte) 
    "returns the length of the utf-8 code in number of bytes, based on the first byte. 
The length can be a number between 1 and 4." 
    (declare (fixnum first-byte)) 
    (cond ((=  0 (ldb (byte 1 7) first-byte)) 1) 
     ((= #b110 (ldb (byte 3 5) first-byte)) 2) 
     ((= #b1110 (ldb (byte 4 4) first-byte)) 3) 
     ((= #b11110 (ldb (byte 5 3) first-byte)) 4) 
     (t (error "unknown number of utf-8 bytes for ~a" first-byte)))) 

Sau đó chúng ta giải mã:

(defun utf-8-decode-unicode-character-code-from-stream (stream) 
    "Decodes byte values, from a binary byte stream, which describe a character 
encoded using UTF-8. 
Returns the character code and the number of bytes read." 
    (let* ((first-byte (read-byte stream)) 
     (number-of-bytes (utf-8-number-of-bytes first-byte))) 
    (declare (fixnum first-byte number-of-bytes)) 
    (ecase number-of-bytes 
     (1 (values (ldb (byte 7 0) first-byte) 
       1)) 
     (2 (values (logior (ash (ldb (byte 5 0) first-byte) 6) 
         (ldb (byte 6 0) (read-byte stream))) 
       2)) 
     (3 (values (logior (ash (ldb (byte 5 0) first-byte) 12) 
         (ash (ldb (byte 6 0) (read-byte stream)) 6) 
         (ldb (byte 6 0) (read-byte stream))) 
       3)) 
     (4 (values (logior (ash (ldb (byte 3 0) first-byte) 18) 
         (ash (ldb (byte 6 0) (read-byte stream)) 12) 
         (ash (ldb (byte 6 0) (read-byte stream)) 6) 
         (ldb (byte 6 0) (read-byte stream))) 
       4)) 
     (t (error "wrong UTF-8 encoding for file position ~a of stream ~s" 
       (file-position stream) 
       stream))))) 

Bạn biết có bao nhiêu ký tự có. N ký tự. Bạn có thể phân bổ một chuỗi có khả năng unicode cho N ký tự. Vì vậy, bạn gọi hàm N lần. Sau đó, đối với mỗi kết quả, bạn chuyển đổi kết quả thành một ký tự và đặt nó vào trong chuỗi.

+0

Rất thông minh! Tôi sẽ xem xét điều này một cách chi tiết vào ngày mai và xem liệu tôi có thể kết hợp nó vào một giải pháp làm việc hay không. Cảm ơn bạn một lần nữa, Rainer, cho câu trả lời nhanh chóng và hữu ích của bạn. –

Các vấn đề liên quan