web-dev-qa-db-fra.com

Comment créer une chaîne à partir de UTF8 dans Swift?

Nous savons que nous pouvons imprimer chaque caractère en unités de code UTF8? Ensuite, si nous avons des unités de code de ces caractères, comment créer une chaîne avec eux?

15
jxwho

Il est possible de convertir les points de code UTF8 en chaîne Swift de manière idiomatique à l'aide de la classe UTF8 Swift. Bien qu'il soit beaucoup plus facile de convertir de String en UTF8!

import Foundation

public class UTF8Encoding {
  public static func encode(bytes: Array<UInt8>) -> String {
    var encodedString = ""
    var decoder = UTF8()
    var generator = bytes.generate()
    var finished: Bool = false
    do {
      let decodingResult = decoder.decode(&generator)
      switch decodingResult {
      case .Result(let char):
        encodedString.append(char)
      case .EmptyInput:
        finished = true
      /* ignore errors and unexpected values */
      case .Error:
        finished = true
      default:
        finished = true
      }
    } while (!finished)
    return encodedString
  }

  public static func decode(str: String) -> Array<UInt8> {
    var decodedBytes = Array<UInt8>()
    for b in str.utf8 {
      decodedBytes.append(b)
    }
    return decodedBytes
  }
}

func testUTF8Encoding() {
  let testString = "A UTF8 String With Special Characters: ????????"
  let decodedArray = UTF8Encoding.decode(testString)
  let encodedString = UTF8Encoding.encode(decodedArray)
  XCTAssert(encodedString == testString, "UTF8Encoding is lossless: \(encodedString) != \(testString)")
}

Parmi les autres alternatives proposées:

  • Utiliser NSString appelle le pont Objective-C;

  • Utiliser UnicodeScalar est sujet aux erreurs car il convertit directement UnicodeScalars en caractères, en ignorant les grappes de graphèmes complexes; et

  • Utiliser String.fromCString est potentiellement dangereux car il utilise des pointeurs.

13
Tim WB

améliorer la réponse de Martin R

import AppKit

let utf8 : CChar[] = [65, 66, 67, 0]
let str = NSString(bytes: utf8, length: utf8.count, encoding: NSUTF8StringEncoding)
println(str) // Output: ABC

import AppKit

let utf8 : UInt8[] = [0xE2, 0x82, 0xAC, 0]
let str = NSString(bytes: utf8, length: utf8.count, encoding: NSUTF8StringEncoding)
println(str) // Output: €

Ce qui s’est passé est que Array peut être converti automatiquement en CConstVoidPointer qui peut être utilisé pour créer une chaîne avec NSSString(bytes: CConstVoidPointer, length len: Int, encoding: Uint)

5
Bryan Chen

Je cherchais moi-même une réponse complète concernant la manipulation de cordes à Swift. S'appuyer sur le casting de et vers NSString et d'autres magies de pointeur non sécurisées ne le faisaient tout simplement pas pour moi. Voici une alternative sûre:

D'abord, nous voudrons étendre UInt8. C'est le type primitif derrière CodeUnit.

extension UInt8 {
    var character: Character {
        return Character(UnicodeScalar(self))
    }
}

Cela nous permettra de faire quelque chose comme ceci:

let codeUnits: [UInt8] = [
    72, 69, 76, 76, 79
]

let characters = codeUnits.map { $0.character }
let string     = String(characters)

// string prints "HELLO"

Equipé de cette extension, nous pouvons maintenant modifier des chaînes.

let string = "ABCDEFGHIJKLMONP"

var modifiedCharacters = [Character]()
for (index, utf8unit) in string.utf8.enumerate() {

    // Insert a "-" every 4 characters
    if index > 0 && index % 4 == 0 {
        let separator: UInt8 = 45 // "-" in ASCII
        modifiedCharacters.append(separator.character)
    }
    modifiedCharacters.append(utf8unit.character)
}

let modifiedString = String(modifiedCharacters)

// modified string == "ABCD-EFGH-IJKL-MONP"
2
dbart

Voici une solution possible (mise à jour pour Swift 2 ):

let utf8 : [CChar] = [65, 66, 67, 0]
if let str = utf8.withUnsafeBufferPointer( { String.fromCString($0.baseAddress) }) {
    print(str) // Output: ABC
} else {
    print("Not a valid UTF-8 string") 
}

Dans la fermeture, $0 est un UnsafeBufferPointer<CChar> pointant vers le stockage contigu du tableau. À partir de cela, une String rapide peut être créée.

Sinon, si vous préférez l’entrée sous forme de unsigned octets:

let utf8 : [UInt8] = [0xE2, 0x82, 0xAC, 0]
if let str = utf8.withUnsafeBufferPointer( { String.fromCString(UnsafePointer($0.baseAddress)) }) {
    print(str) // Output: €
} else {
    print("Not a valid UTF-8 string")
}
1
Martin R
// Swift4
var units = [UTF8.CodeUnit]()
//
// update units
//
let str = String(decoding: units, as: UTF8.self)
1
xohozu

Si vous commencez avec un tampon brut, tel que celui provenant de l'objet Data renvoyé par un descripteur de fichier (dans ce cas, extrait d'un objet Pipe):

let data = pipe.fileHandleForReading.readDataToEndOfFile()
var unsafePointer = UnsafeMutablePointer<UInt8>.allocate(capacity: data.count)

data.copyBytes(to: unsafePointer, count: data.count)

let output = String(cString: unsafePointer)
1
johnkzin

Je ferais quelque chose comme ça, ce n'est peut-être pas si élégant que de travailler avec des "pointeurs", mais ça fait bien le travail, il s'agit plutôt d'un groupe de nouveaux opérateurs += pour String comme:

@infix func += (inout lhs: String, rhs: (unit1: UInt8)) {
    lhs += Character(UnicodeScalar(UInt32(rhs.unit1)))
}

@infix func += (inout lhs: String, rhs: (unit1: UInt8, unit2: UInt8)) {
    lhs += Character(UnicodeScalar(UInt32(rhs.unit1) << 8 | UInt32(rhs.unit2)))
}

@infix func += (inout lhs: String, rhs: (unit1: UInt8, unit2: UInt8, unit3: UInt8, unit4: UInt8)) {
    lhs += Character(UnicodeScalar(UInt32(rhs.unit1) << 24 | UInt32(rhs.unit2) << 16 | UInt32(rhs.unit3) << 8 | UInt32(rhs.unit4)))
}

REMARQUE: vous pouvez également étendre la liste des opérateurs pris en charge avec l'opérateur de substitution +, en définissant la liste des opérateurs entièrement commutatifs pour String.


et maintenant vous pouvez ajouter une String avec un caractère unicode (UTF-8, UTF-16 ou UTF-32) comme par exemple:

var string: String = "signs of the Zodiac: "
string += (0x0, 0x0, 0x26, 0x4b)
string += (38)
string += (0x26, 76)
1
holex

Avec Swift 5, vous pouvez choisir l’une des méthodes suivantes pour convertir une collection d’unités de code UTF-8 en chaîne.


#1. Utilisation de String 's init(_:) initializer

Si vous avez une instance String.UTF8View (c'est-à-dire une collection d'unités de code UTF-8) et souhaitez la convertir en chaîne, vous pouvez utiliser l'initialiseur init(_:). init(_:) a la déclaration suivante:

init(_ utf8: String.UTF8View)

Crée une chaîne correspondant à la séquence donnée des unités de code UTF-8.

L'exemple de code Playground ci-dessous montre comment utiliser init(_:):

let string = "Café ????????"
let utf8View: String.UTF8View = string.utf8

let newString = String(utf8View)
print(newString) // prints: Café ????????

# 2. Utilisation de Swift 's init(decoding:as:) initializer

init(decoding:as:) crée une chaîne à partir de la collection d'unités de code Unicode donnée dans le codage spécifié:

let string = "Café ????????"
let codeUnits: [Unicode.UTF8.CodeUnit] = Array(string.utf8)

let newString = String(decoding: codeUnits, as: UTF8.self)
print(newString) // prints: Café ????????

Notez que init(decoding:as:) fonctionne également avec le paramètre String.UTF8View:

let string = "Café ????????"
let utf8View: String.UTF8View = string.utf8

let newString = String(decoding: utf8View, as: UTF8.self)
print(newString) // prints: Café ????????

# 3. Utilisation de transcode(_:from:to:stoppingOnError:into:) function

L'exemple suivant convertit la représentation UTF-8 d'une chaîne initiale en valeurs scalaires Unicode (unités de code UTF-32) pouvant être utilisées pour créer une nouvelle chaîne:

let string = "Café ????????"
let bytes = Array(string.utf8)

var newString = ""
_ = transcode(bytes.makeIterator(), from: UTF8.self, to: UTF32.self, stoppingOnError: true, into: {
    newString.append(String(Unicode.Scalar($0)!))
})
print(newString) // prints: Café ????????

# 4. Utilisation de Array 's withUnsafeBufferPointer(_:) method et de String' s init(cString:) initializer

init(cString:) a la déclaration suivante:

init(cString: UnsafePointer<CChar>)

Crée une nouvelle chaîne en copiant les données UTF-8 à zéro terminal référencées par le pointeur donné.

L'exemple suivant montre comment utiliser init(cString:) avec un pointeur sur le contenu d'un tableau CChar (c'est-à-dire une séquence d'unités de code UTF-8 bien formée) afin de créer une chaîne à partir de celui-ci:

let bytes: [CChar] = [67, 97, 102, -61, -87, 32, -16, -97, -121, -85, -16, -97, -121, -73, 0]

let newString = bytes.withUnsafeBufferPointer({ (bufferPointer: UnsafeBufferPointer<CChar>)in
    return String(cString: bufferPointer.baseAddress!)
})
print(newString) // prints: Café ????????

# 5. Utilisation de Unicode.UTF8 's decode(_:) method

Pour décoder une séquence d'unités de code, appelez decode(_:) à plusieurs reprises jusqu'à ce qu'il renvoie UnicodeDecodingResult.emptyInput:

let string = "Café ????????"
let codeUnits = Array(string.utf8)

var codeUnitIterator = codeUnits.makeIterator()
var utf8Decoder = Unicode.UTF8()
var newString = ""

Decode: while true {
    switch utf8Decoder.decode(&codeUnitIterator) {
    case .scalarValue(let value):
        newString.append(Character(Unicode.Scalar(value)))
    case .emptyInput:
        break Decode
    case .error:
        print("Decoding error")
        break Decode
    }
}

print(newString) // prints: Café ????????

# 6. Utilisation de String 's init(bytes:encoding:) initializer

Foundation fournit à String un initialiseur init(bytes:encoding:) que vous pouvez utiliser comme indiqué dans l'exemple de code ci-dessous de Playground:

import Foundation

let string = "Café ????????"
let bytes: [Unicode.UTF8.CodeUnit] = Array(string.utf8)

let newString = String(bytes: bytes, encoding: String.Encoding.utf8)
print(String(describing: newString)) // prints: Optional("Café ????????")
0
Imanou Petit

Il existe une version Swift 3.0 de Martin R answer

public class UTF8Encoding {
  public static func encode(bytes: Array<UInt8>) -> String {
    var encodedString = ""
    var decoder = UTF8()
    var generator = bytes.makeIterator()
    var finished: Bool = false
    repeat {
      let decodingResult = decoder.decode(&generator)
      switch decodingResult {
      case .scalarValue(let char):
        encodedString += "\(char)"
      case .emptyInput:
        finished = true
      case .error:
        finished = true
      }
    } while (!finished)
    return encodedString
  }
  public static func decode(str: String) -> Array<UInt8> {
    var decodedBytes = Array<UInt8>()
    for b in str.utf8 {
      decodedBytes.append(b)
    }
    return decodedBytes
  }
}

Si vous souhaitez afficher emoji à partir de la chaîne UTF-8, utilisez uniquement la méthode utilisateur convertEmojiCodesToString ci-dessous. Il fonctionne correctement pour des chaînes telles que "U + 1F52B" (emoji) ou "U + 1F1E6 U + 1F1F1" (pays emoji).

class EmojiConverter {
  static func convertEmojiCodesToString(_ emojiCodesString: String) -> String {
    let emojies = emojiCodesString.components(separatedBy: " ")
    var resultString = ""
    for emoji in emojies {
      var formattedCode = emoji
      formattedCode.slice(from: 2, to: emoji.length)
      formattedCode = formattedCode.lowercased()
      if let charCode = UInt32(formattedCode, radix: 16),
        let unicode = UnicodeScalar(charCode) {
        let str = String(unicode)
        resultString += "\(str)"
      }
    }
    return resultString
  }
}
0
Alex Shoshiashvili