ロゴ ロゴ

Swift で Windows 31J ( CP932 ) を扱うのに困った話

はじめに

筆者がSwiftのバージョンが4の時に文字列処理でぶちぎれたという話です

そもそもSwiftとは

詳しいことはWikiにお任せしますが、Apple製品上で動くアプリを書くときに使うプログラミング言語です

もともとObjective-CというSmartTalkベースのC言語をクッソ読みづらくしたゴミ言語があったのですが、2014年にこれを置き換える目的で登場した言語です

モダンな言語らしく、次の点でいままでもより開発のしやすい言語として設計されています

・ Null安全

変数にNull(nil)が割り当てられているところにアクセスしようとしても、言語仕様的にできなくする仕組み(Nullになりそうな型の変数には強制的にアクセスするか、nilかどうか聞かないとアクセスできない)がある

・ 総称型が使える

何を入れるかを使うときに決められる(それまではTとか置いて変数を宣言できる)

・タプル

一つの変数に何種類もの型をつっこめる

なんでこんなに怒っているのか

そもそも発端は、ある匿名掲示板を見るためのブラウザーを開発していたときのことでした

その掲示板は文字列をShit-JIS(とみせかけたCP932)で返すのですが、次のようなコードで文字列型の変数に値を格納しようとしても失敗することが何回かありました

let threadBodyLinesData:Data = getHTTP("http://9ch.net/~~~~.html")//String型でHTMLをとってくる

//文字列がShift-JISなのでデータ型を文字列型にShit-JISで変換する
let threadBodyLines:String = String(data: threadBodyLinesData, encoding: .shiftJIS)

if threadBodyLines == nil {
    print("nil")
}else{
    print("Not nil")
}
//nil
//なぜかnilと出てしまう場合がある

これはShit-JISで定められている文字コードの外に定義されている文字が変換しようとしている文字列に含まれているとデコードエラーとしてnilを返してしまうようです

結局どうしたのか

方針として、1文字分バイト列を1回1回読み取っていき、Shit-JISの範囲を超えるようなバイト列の場合は�で置き換えていくというコードを書きました

    public func getStringDataWithCP932(data:NSData)->String{
        //
        let SJISMultiCheck :(UInt8)->Bool = {c in
            if(((c>=0x81)&&(c=0xe0)&&(c<=0xfc))){
                return true
            }else{
                return false
            }
        }

        //完成した文字列を格納する
        var result = ""

        var byte = [UInt8](repeating: 0, count: 1)

        var temp = [UInt8]()

        let length = data.count / MemoryLayout.size

        var count = 0
        while count<length {

            var flag = true

            while flag {
                //初期化
                temp = []
                //byteに1文字分のバイト列を格納する
                data.getBytes(&byte, range: NSRange(location: count, length: MemoryLayout.size))
                //1バイトは確実に変換できる(はず)
                temp.append(byte[0])
                //問題は2バイト目が範囲外かどうか
                if(SJISMultiCheck(temp[0])){
                    //下の行が実行された段階でbyteの中身は更新される
                    data.getBytes(&byte, range: NSRange(location: count+1, length: MemoryLayout.size))
                    temp.append(byte[0])
                    count += MemoryLayout.size
                }
                //置き換える
                let line = String(bytes: temp, encoding: String.Encoding.shiftJIS) ?? "�"

                //もし変換できてたら
                if(line.count > 0){
                    flag = false
                    result += line
                    //print(line)
                }

                count += MemoryLayout.size
            }
        }
        return result
    }

感想

Swiftは主に文字列処理ではUnicodeに力をいれているようですが、こういう古い文字コードはバッサリ切り捨てるあたりがアップルらしいですね()


とりあえず、こういう形でなんとか対処できたので、メモ的な感じで残しておきます

コメント入力

関連サイト