Mit Scanner
in einigen Fällen ist eine sehr bequeme Art und Weise Zahlen der Extraktion aus einem String. Und es ist fast so leistungsfähig wie NumberFormatter
beim Dekodieren und Behandeln verschiedener Zahlenformate und Gebietsschemas. Es kann Zahlen und Währungen mit unterschiedlichen Dezimal- und Gruppentrennzeichen extrahieren.
import Foundation
// The code below includes manual fix for whitespaces (for French case)
let strings = ["en_US": "My salary is $9,999.99",
"fr_FR": "Mon salaire est 9 999,99€",
"de_DE": "Mein Gehalt ist 9999,99€",
"en_GB": "My salary is £9,999.99" ]
// Just for referce
let allPossibleDecimalSeparators = Set(Locale.availableIdentifiers.compactMap({ Locale(identifier: $0).decimalSeparator}))
print(allPossibleDecimalSeparators)
for str in strings {
let locale = Locale(identifier: str.key)
let valStr = str.value.filter{!($0.isWhitespace || $0 == Character(locale.groupingSeparator ?? ""))}
print("Value String", valStr)
let sc = Scanner(string: valStr)
// we could do this more reliably with `filter` as well
sc.charactersToBeSkipped = CharacterSet.decimalDigits.inverted
sc.locale = locale
print("Locale \(locale.identifier) grouping separator: |\(locale.groupingSeparator ?? "")| . Decimal separator: \(locale.decimalSeparator ?? "")")
while !(sc.isAtEnd) {
if let val = sc.scanDouble() {
print(val)
}
}
}
Es gibt jedoch Probleme mit Trennzeichen, die als Worttrennzeichen aufgefasst werden könnten.
// This doesn't work. `Scanner` just ignores grouping separators because scanner tends to seek for multiple values
// It just refuses to ignore spaces or commas for example.
let strings = ["en_US": "$9,999.99", "fr_FR": "9999,99€", "de_DE": "9999,99€", "en_GB": "£9,999.99" ]
for str in strings {
let locale = Locale(identifier: str.key)
let sc = Scanner(string: str.value)
sc.charactersToBeSkipped = CharacterSet.decimalDigits.inverted.union(CharacterSet(charactersIn: locale.groupingSeparator ?? ""))
sc.locale = locale
print("Locale \(locale.identifier) grouping separator: \(locale.groupingSeparator ?? "") . Decimal separator: \(locale.decimalSeparator ?? "")")
while !(sc.isAtEnd) {
if let val = sc.scanDouble() {
print(val)
}
}
}
// sc.scanDouble(representation: Scanner.NumberRepresentation) could help if there were .currency case
Es ist kein Problem, das Gebietsschema automatisch zu erkennen. Beachten Sie, dass groupingSeparator im französischen Gebietsschema in der Zeichenfolge "Mon salaire est 9 999,99 €" kein Leerzeichen ist, obwohl es möglicherweise genau als Leerzeichen gerendert wird (hier nicht). Deshalb funktioniert der folgende Code einwandfrei, ohne dass !$0.isWhitespace
Zeichen herausgefiltert werden.
let stringsArr = ["My salary is $9,999.99",
"Mon salaire est 9 999,99€",
"Mein Gehalt ist 9.999,99€",
"My salary is £9,999.99" ]
let tagger = NSLinguisticTagger(tagSchemes: [.language], options: Int(NSLinguisticTagger.Options.init().rawValue))
for str in stringsArr {
tagger.string = str
let locale = Locale(identifier: tagger.dominantLanguage ?? "en")
let valStr = str.filter{!($0 == Character(locale.groupingSeparator ?? ""))}
print("Value String", valStr)
let sc = Scanner(string: valStr)
// we could do this more reliably with `filter` as well
sc.charactersToBeSkipped = CharacterSet.decimalDigits.inverted
sc.locale = locale
print("Locale \(locale.identifier) grouping separator: |\(locale.groupingSeparator ?? "")| . Decimal separator: \(locale.decimalSeparator ?? "")")
while !(sc.isAtEnd) {
if let val = sc.scanDouble() {
print(val)
}
}
}
// Also will fail if groupingSeparator == decimalSeparator (but don't think it's possible)