Swiftで16進数をShift-JIS表示へとするにはどうしたら良いでしょうか

投稿者: Anonymous

題名の通り、Swiftで文字列をShift-JIS表示へと変更する事は可能でしょうか。

let str = “あいうえお“

for scalar in str.unicodeScalars {
     let aStr = String(scaler.value, radix:16) // 3042 3044 3046 3948 304Aとなる
...
}

Unicode スカラ表現を16進数とすると、3042 3044 3046 3948 304A が出ます。その数値を最終的な結果としてShift-JIS表示となる B0D0F0H0J0 へと変換したいと考えています。どの様な方法がありますでしょうか。

解決

コメントによると、ご質問内容は誤った変換を繰り返した結果「あいうえお」が「B0D0F0H0J0」となってしまうという挙動をSwiftで再現したいと言うことのようです。

「B0D0F0H0J0」と言う結果は「Shift-JIS表示」でも何でもなく、間違ったエンコーディングでデータを読み取って文字化けした結果、です。

Swiftで再現するコードは以下のような感じになります。
(別のエンコーディングを試しやすいようにコードを修正しました。)

let str = "あいうえお"
let encodedBytes = str.data(using: .utf16LittleEndian) ?? Data()
let forcedReinterpretedStr = String(data: encodedBytes, encoding: .shiftJIS) ?? "**not a valid string**"
print(forcedReinterpretedStr)

strの中身を色々変えて試してみるとわかりますが、文字列のUTF-16表現というのはシフトJISとして解釈できるバイト列になるとは限らないため、**not a valid string**の方が表示されるかもしれません。

(質問の書き方というのも難しいかもしれませんが、元ネタがリンクで示せるなら、そのリンクを含めていただいていれば、もっと早く上記のコードをお示しできたと思います。)


修正
以下の記述は質問者さんの意図が掴みきれないときに書いたものですが、SwiftのString型の挙動を理解するために有用と思われるため、そのまま残しておきます。

コメントに書いたように「Shift-JIS表示へと変更」と言うのがよくわからないのですが、Swiftの文字列では文字列側に「Shift_JIS」といったエンコーディング情報は保存されませんし、Swiftの内部では常にシステム側で決められた内部表現(現在はUTF-8)で文字列を表現しています。

したがって、出来ることは「文字列」から「その文字列をシフトJISで表現した時のバイト列(バイナリーデータ)を得ること」になります。

import Foundation

let str = "あいうえお"

let sjisData = str.data(using: .shiftJIS)!
print(type(of: sjisData)) //-> Data
print(sjisData as NSData) //-> {length = 10, bytes = 0x82a082a282a482a682a8}

日本の技術者が「シフトJIS」と言った場合、Windowsの拡張文字も含めた文字セット(IANAの"Windows-31J")を表すことが多いのですが、SwiftのString.Encoding.shiftJISという定数はそちらを表すようです。IANA規定の"Shift_JIS"を表したいのであれば、少々面倒なことをしないといけないのですが、実用的には必要になることは滅多にないでしょう。

以上のコードでsjisDataは「シフトJIS表現のバイト列」を保持するData型の値になります。


この変換結果を使いたいときには、さらにその用途によってDataから変換することが必要になるかもしれません。

例えば「シフトJISの16進表現を表す文字列」(デバッグ用途を除けば、こんなものが必要になることは滅多にありませんが)が欲しいのであれば、こんな感じになります。

let sjisHex = sjisData.map {String(format: "%02X", $0)}
                .joined()
print(type(of: sjisHex)) //-> String
print(sjisHex) //-> 82A082A282A482A682A8

「結果をファイルに保存したい」と言うコメントをいただいたので、そのためのコード例を示しておきます。上のsjisHexの部分は無視して、変換結果のDataをファイルに書き込めば良いだけなので、いくらでも方法はありますが、以下は例の1つとして。

let url = URL(fileURLWithPath: "/path/to/file")
do {
    try sjisData.write(to: url)
} catch {
    print(error)
}

再度のコメントで「outputとして string型の B0D0F0H0J0 という結果を得たい」とありますが、その例は上のsjisHexの部分を見てください。「ファイルに保存」するにしても、「文字列を得たい」にしても、「そのファイルを後でどう使うのか」「その文字列を後でどう使うのか」までわかれば、「もっと簡単にこう書ける」と言う場合があります。

まだしっくりこなければ、そこら辺の情報をいただければ何かお手伝いできるかもしれません。


まだ、話が噛み合わないような気がするので、少し一般的になってしまいますが、Swiftの文字列について説明しておきます。

Swiftでは、String型において、文字列をUnicode準拠の方法で格納すると規定されており、また、Unicodeベースのいくつかのデータ表現については簡単に変換できるようになっています。

let str = "あいうえお?"
//Unicodeスカラー
print(str.unicodeScalars.map{String(format: "U+%04X", $0.value)}.joined(separator: " "))
//->U+3042 U+3044 U+3046 U+3048 U+304A U+1F600

//UTF-16コード単位
print(str.utf16.map{String(format: "0x%04X", $0)}.joined(separator: " "))
//->0x3042 0x3044 0x3046 0x3048 0x304A 0xD83D 0xDE00

//UTF-8コード単位
print(str.utf8.map{String(format: "0x%02X", $0)}.joined(separator: " "))
//->0xE3 0x81 0x82 0xE3 0x81 0x84 0xE3 0x81 0x86 0xE3 0x81 0x88 0xE3 0x81 0x8A 0xF0 0x9F 0x98 0x80

このうち、現在のSwiftでは3つめのUTF-8コード単位でデータが格納されており、これをシフトJISに変換することはできません

また、古いSwiftやObjective-C(のNSString)では、2つ目のUTF-16コード単位でデータが格納されています。これをシフトJISに変換することはできません

Unicodeスカラーにしろ、UTF-16にしろUTF-8にしろUnicode規格で規定されたデータ表現であり、シフトJISと言うのはUnicodeではありませんから、何をどうやっても上記のメソッド(と言うかプロパティですが、unicodeScalars, utf16, utf8)からシフトJISのバイト列が得られることは絶対にありません


Swiftの他、Objective-C, C#, Java, JavaScriptなんかも、同じように文字列の内部表現は固定であり、その「内部表現をシフトJISに変換すること」は決してできません。

これに対して、例えばPHPの「文字列」と言うのは単なるバイト列であり、

"他の文字列あいうえお他の文字列"と言った文字列があるときに"他の文字列" + ("あいうえお"をシフトJIS変換したもの) + "他の文字列"といった「文字列」を作ることも可能です。

PHPで上記のような処理をしている部分をSwiftでどのように行えば良いのかは、実際に出来上がった「文字列」をどう使っているのかを見ないと、何とも言えません。

結果の「文字列」をどう使いたいのか、具体的にお示しください。

回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *