32640


Swift3にて、Shift-JISでURLをエンコードする必要が出てきたのですが、  
`String.addingPercentEscapes`が非推奨になっており、代わりに`String.addingPercentEncoding`を使用するように警告が出てしまいました。

いろいろ調べてみたのですが、`String.addingPercentEscapes`を使用せずにShift-
JISでURLエンコードを行う方法が分からなかったため、ご教授いただきたいです。


CC BY-SA 3.0
2017-02-13T11:59:25.370
32636
2018-01-05T10:08:30.560
20635
question
swift swift3 encoding shift-jis
Swift3でSJISを使ったURLエンコードをしたい


本来なら、アプリ側をSJISに対応させるのではなく、サーバー側をUTF-8に対応させるのが筋だと思うのですが…。

AppleがSJIS対応のURLエンコード機能をiOSやSwiftの標準ライブラリーに再度付け加える可能性は極めて低いでしょうから、全部自前で処理してやる必要があります。

例えばこんな感じになるでしょう。

[code]

    extension CharacterSet {
        static let rfc3986Unreserved = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~")
    }
    
    extension String.Encoding {
        static let windows31j = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.dosJapanese.rawValue)))
    }
    
    extension String {
        func addingPercentEncoding(withAllowedCharacters characterSet: CharacterSet, using encoding: String.Encoding) -> String {
            let stringData = self.data(using: encoding, allowLossyConversion: true) ?? Data()
            let percentEscaped = stringData.map {byte->String in
                if characterSet.contains(UnicodeScalar(byte)) {
                    return String(UnicodeScalar(byte))
                } else if byte == UInt8(ascii: " ") {
                    return "+"
                } else {
                    return String(format: "%%%02X", byte)
                }
            }.joined()
            return percentEscaped
        }
    
        var sjisPercentEncoded: String {
            return self.addingPercentEncoding(withAllowedCharacters: .rfc3986Unreserved,  using: .windows31j)
        }
    }
    
    let escaped = "今時SJISのシステムなんて…".sjisPercentEncoded
    print(escaped) //->%8D%A1%8E%9ESJIS%82%CC%83V%83X%83e%83%80%82%C8%82%F1%82%C4%81c
    
[/code]

通常SJISと名乗っているシステムは、Windows拡張文字も受け取れることが多いので、上記では`String.Encoding.shiftJIS`ではなく、Windows-31J相当のエンコーディングを使用しています。

実際にこれがデコードできるかどうかは、サーバーサイドが、どうパーセントエンコーディングを処理しているかによりますので、果たして上記のコードが無事に動くかどうかはなんとも言えません。


CC BY-SA 3.0
2017-02-13T14:10:23.193
32640
2017-02-13T14:10:23.193
13972
32636
answer


deprecatedの関数を使いますが、一応以下でSJISのURLエンコードができます。

[code]

    func sjisUrlEncode(_ str: String) -> String? {
    
        let raw: NSString = str as NSString
        // iOS9からdeprecatedだが他にShift_JISでエスケープできる関数がない
        if var encoded = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
                                                                 raw,
                                                                 nil,
                                                                 ":/?&=;+!@#$()',*" as CFString,
                                                                 0x0A01) as String?
        {
            encoded = encoded.replacingOccurrences(of: "~", with: "%7E") // チルダのエンコード
            encoded = encoded.replacingOccurrences(of: "%20", with: "+") // スペースは%20ではなく+にする
            return encoded
        }
    
        return nil
    }
    
[/code]

OOPerさんのコードを参考に、deprecatedの関数を使わず上記と同じ出力となる（たぶん）ものを作りました。

[code]

    import UIKit
    
    class SJISURLEncoder {
        private static let windows31j = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.dosJapanese.rawValue)))
    
        private static let rfc3986Unreserved = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
    
        private static let specialConversionMap: [Character: String] = [
            " ":"+",
            "¥":"%5C",
            "\\":"%81%5F", // バックスラッシュは全角バックスラッシュに変換
            "~":"%7E" // チルダは古いシステムではエスケープが必要な場合がある
        ]
    
        // SJISでURLエンコードする
        class func encode(_ src: String) -> String {
    
            let percentEscaped = src.map { c -> String in
    
                if let converted = specialConversionMap[c] {
                    return converted
                } else if rfc3986Unreserved.contains(c) {
                    return String(c)
                }
    
                let stringData = String(c).data(using: windows31j, allowLossyConversion: true) ?? Data()
                return stringData.map {byte -> String in
                    return String(format: "%%%02X", byte)
                }.joined()
    
            }.joined()
    
            return percentEscaped
        }
    }
    
[/code]

※Swift3だとsrc.mapはsrc.characters.mapとなるかもしれません。


CC BY-SA 3.0
2018-01-05T10:08:30.560
40753
2018-01-05T10:08:30.560
8148
32636
answer