Save data from textfile in a array and/or dictionary - ios

I hang on a programming step. I hope you can help me.
I got in a textfile the following rows:
#Objekt
Objektnr; 1000000;
Filialname; Dresden;
Filialeemail; email#email.com;
#Baustelle
Anschrift1;;
Anschrift2;Juwelier Schubert;
Strasse;Theresienstrafle 7;
Land;DE;
Ort;TheTown;
PLZ;12345;
....
I have the following function for bring the file-data to an array or an dictionary. In another function i will save the data to the local CoreData-Database.
func startImportTextfile(fileName: String, fileDir: String) -> Bool {
var filePath : String = folderDocuments.stringByAppendingPathComponent(fileDir)
var fileNameWithPath = filePath.stringByAppendingPathComponent(fileName)
var fullImportContent = String(contentsOfFile: fileNameWithPath, encoding: NSUTF8StringEncoding, error: nil)
if(fullImportContent != "")
{
var stringArray = fullImportContent!.componentsSeparatedByString("\n")
var stringArrayCompleteData = Dictionary<String, Array<Any>>()
var arrIndexSection : String = "NoHeader"
for singleRow in stringArray
{
if(singleRow != "")
{
switch singleRow {
case "#Header":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Objekt":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Baustelle":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Auftraggeber":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Architekt":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Vermittler":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Regulierer":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Versicherung":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Kontaktstellen":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Dateien":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
default:
//Here the multiple array would be filled
var arrSingleRow = singleRow.componentsSeparatedByString(";")
if( arrSingleRow.count > 0 )
{
if( arrIndexSection == "Kontaktstellen" )
{
//TODO: Kontaktstellen einlesen
//#Kontaktstellen
//Baustelle;0;348873;;;;0
//Baustelle;0;381263;;Albrecht;0815;0
//Regulierer/SV;0;171979;Josef;Eder;08546/911055;0
println( "Kontaktstellendaten" )
println( singleRow )
}
else if( arrIndexSection == "Dateien" )
{
//TODO: Dateien einlesen
//#Dateien
//11022015090007_BEmail_INNNUE_21102014141534.pdf; 99; Email an asdfasdf#sdf.de
println( "Dateiendaten" )
println( singleRow )
}
else
{
stringArrayCompleteData[arrIndexSection] = [arrSingleRow[0]: arrSingleRow[1]]
}
}
}
}
}
for key in stringArrayCompleteData {
println("Key: \(key)")
}
return true
}
else
{
return false
}
}
The aim is that I can open the data like this:
println(stringArrayCompleteData["Objekt"].Objektnr)
But I dont know how i have to declare the stringArrayCompleteData.
Maybe i have to change this decleration
var stringArrayCompleteData = Dictionary<String, Array<Any>>()
to
var stringArrayCompleteData = Array<String, Dictionary<String, Any>>()
Thanks for every little help

Hi Roland – My suggestion would be to divide up the text into the different sections and put each into a struct. For example, you would define a Objekt struct that would contain 3 properties: objektnr, filialname, and filialeemail.
You could make a tree of structs, for example, with a root Person struct. The Person struct would contain properties corresponding to an Objekt struct instance, a Bastille struct instance, etc.
Here's some example code to define the structs:
struct Person:Printable {
init() {
}
var objektStruct:Objekt?
var baustelleStruct:Baustelle?
var description:String {
get {
let objektStructDescription = objektStruct != nil ? objektStruct!.description : ""
let baustelleStructDescription = baustelleStruct != nil ? baustelleStruct!.description : ""
return "\(objektStructDescription)\n\n\(baustelleStructDescription)"
}
}
}
struct Objekt:Printable {
var objektnr:Int = 0
var filialname:String = ""
var filialeemail:String = ""
init(text:String) {
// Divide the text into an array of lines
let textLinesArray:[String] = text.componentsSeparatedByString("\n")
// Loop through each line and set the corresponding property
for textLine:String in textLinesArray {
// Separate the components of the line at each ";" character
var components:[String] = textLine.componentsSeparatedByString(";")
components = components.map({ (componentString:String) -> String in
return componentString.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
})
// Get the key and value for the line.
let key:String = components.count > 0 ? components[0] : ""
let value:String = components.count > 1 ? components[1] : ""
// Based on the key, set the appropriate property value
switch key {
case "Objektnr":
if let valueAsInt = value.toInt() {
objektnr = valueAsInt
}
case "Filialname":
filialname = value
case "Filialeemail":
filialeemail = value
default:
break
}
}
}
var description:String {
get {
return "*Objekt*\n Objektnr = \(objektnr)\n Filialname = \(filialname)\n Filialeemail = \(filialeemail)"
}
}
}
struct Baustelle:Printable {
var anschrift1:String = ""
var anschrift2:String = ""
var strasse:String = ""
var land:String = ""
var ort:String = ""
var plz:Int = 0
init(text:String) {
// Divide the text into an array of lines
let textLinesArray:[String] = text.componentsSeparatedByString("\n")
// Loop through each line and set the corresponding property
for textLine:String in textLinesArray {
// Separate the components of the line at each ";" character
var components:[String] = textLine.componentsSeparatedByString(";")
components = components.map({ (componentString:String) -> String in
return componentString.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
})
// Get the key and value for the line.
let key:String = components.count > 0 ? components[0] : ""
let value:String = components.count > 1 ? components[1] : ""
// Based on the key, set the appropriate property value
switch key {
case "Anschrift1":
anschrift1 = value
case "Anschrift2":
anschrift2 = value
case "Strasse":
strasse = value
case "Land":
land = value
case "Ort":
ort = value
case "PLZ":
if let valueAsInt = value.toInt() {
plz = valueAsInt
}
default:
break
}
}
}
var description:String {
get {
return "*Baustelle*\n Anschrift1 = \(anschrift1)\n Anschrift2 = \(anschrift2)\n Strasse = \(strasse)\n Land = \(land)\n Ort = \(ort)\n PLZ = \(plz)"
}
}
}
I wrote the init methods for the Objekt and Baustelle so that the whole text section can be supplied, and the struct takes care of parsing it into the different properties.
Here's some sample code to use the above structs:
let text = String(contentsOfFile: "/Users/markstone/Downloads/textdata.txt")!
// Divide the text at each double newline into multiline sections.
// Eg., the arrayOfTextSections[0] will be the multiline string:
// #Objekt
// Objektnr; 1000000;
// Filialname; Dresden;
// Filialeemail; email#
let arrayOfTextSections = text.componentsSeparatedByString("\n\n")
// Create an empty Person struct instance. We'll fill it in the loop below
var person = Person()
for textSection in arrayOfTextSections {
// For each text section, find out the heading (eg., #Objekt or #Baustelle).
let sectionStringArray = textSection.componentsSeparatedByString("\n")
let sectionHeading = sectionStringArray[0]
switch sectionHeading {
case "#Objekt":
// Create a new instance of the Objekt using the multiline text in this section, and set this instance to the person.objektStruct property.
person.objektStruct = Objekt(text: textSection)
case "#Baustelle":
// Create a new instance of the Baustelle using the multiline text in this section, and set this instance to the person.baustelleStruct property.
person.baustelleStruct = Baustelle(text: textSection)
default:
break
}
}
print(person)
The line print(person) prints the following output:
*Objekt*
Objektnr = 0
Filialname = Dresden
Filialeemail = email#email.com
*Baustelle*
Anschrift1 =
Anschrift2 = Juwelier Schubert
Strasse = Theresienstrafle 7
Land = DE
Ort = TheTown
PLZ = 12345
With this sort of approach, it's quite easy to then turn the struct instances into managed objects for a core data store.

I placed a text file containing your data and saved in a text file in Document's Directory and passed the text filename and the directory path to the function and the functions returns the dictionary with the data into it in dictionary format.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let fileDirectory : [String] = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as [String]
var fileDir = fileDirectory[0]
var returnedDictionary : Dictionary = self.startImportTextfile("textdata", fileDir: fileDir)
println(returnedDictionary)
var keyDictionary : Dictionary! = returnedDictionary["Objekt"]
println(keyDictionary["Objektnr"] as String!)
}
func startImportTextfile(fileName: String, fileDir: String) -> Dictionary<String,Dictionary<String,String>> {
let fileNameWithPath :String! = fileDir.stringByAppendingPathComponent(fileName)
var err: NSError?
var currentKey : String!
let fullImportContent :String! = String.stringWithContentsOfFile(fileNameWithPath!, encoding: NSUTF8StringEncoding, error: &err)
var dataDictionary = [String : Dictionary <String,String>]()
if(fullImportContent != "")
{
var singleLineArray = fullImportContent!.componentsSeparatedByString("\n")
for singleRow in singleLineArray
{
if(singleRow != "")
{
switch singleRow
{
case "#Header","#Objekt","#Baustelle","#Auftraggeber","#Architekt","#Vermittler","#Regulierer","#Versicherung","#Kontaktstellen","#Dateien":
currentKey = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
dataDictionary[currentKey] = Dictionary <String,String>()
default:
var arrayForSingleRow = singleRow.componentsSeparatedByString(";")
if( arrayForSingleRow.count > 0 )
{
var sampledict : Dictionary! = dataDictionary[currentKey]
sampledict[arrayForSingleRow[0]] = arrayForSingleRow[1]
dataDictionary[currentKey] = sampledict
}
}
}
}
}
return dataDictionary
}
}
After executing it i get the following output :
[Objekt: [Filialname: Dresden, Objektnr: 1000000, Filialeemail: email#], Baustelle: [PLZ: 12345, Ort: TheTown, Land: DE, Anschrift1: , Strasse: Theresienstrafle 7, Anschrift2: Juwelier Schubert]]
1000000
where Objekt is the key and it contains a dictionary value and from that dictionary you would be able to access Objektnr as i have done in viewDidLoad.
Link to Text file : https://www.dropbox.com/s/oyzbo128zr3jd6h/textdata?dl=0

It looks like your guess is correct and what you mean to declare stringArrayCompleteData as is indeed a dictionary of dictionaries, with Any as the value type for the dictionary. By the way, you can declare that type a bit more neatly as [String: [String:Any]]
Though, do you just want to store the details as strings? Or do you plan to parse numbers as integers? If you want some stronger-typed set of values, as an alternative to using Any as the data type, you might want to look into using an enum with associated values.
Here’s a version of your code that will store up the data records and insert them under the key. This could be neatened up a lot, but also uses a few Swift features you might find useful.
import Foundation
let folderDocuments = "/"
// rather than return true/false, you could return the data as an optional
func startImportTextfile(fileName: String, fileDir: String) -> [String: [String:String]]? {
let filePath: String = folderDocuments.stringByAppendingPathComponent(fileDir)
let fileNameWithPath = filePath.stringByAppendingPathComponent(fileName)
var error: NSError?
let fullImportContent = String(contentsOfFile: fileNameWithPath, encoding: NSUTF8StringEncoding, error: &error)
if let data = fullImportContent where !data.isEmpty {
let stringArray = split(data) { $0 == "\n" }
var completeData: [String: [String:String]] = [:]
var sectionEntries: [String:String] = [:]
var arrIndexSection: String? = nil
for singleRow in stringArray {
// first is an easy and safe way to check if first character
// has a specific value, whilst handling empty strings
if first(singleRow) == "#" {
// insert records from previous section
if let header = arrIndexSection {
completeData[header] = sectionEntries
}
// start a new section
// dropFirst removes the first "#"
arrIndexSection = dropFirst(singleRow)
sectionEntries = [:]
}
else {
let arrSingleRow = split(singleRow) { $0 == ";" }
if( arrSingleRow.count > 0 ) {
switch arrIndexSection {
case .Some("Kontaktstellen"):
//TODO: Kontaktstellen einlesen
println( "Kontaktstellendaten" )
println( singleRow )
case .Some("Dateien"):
//TODO: Dateien einlesen
println( "Dateiendaten" )
println( singleRow )
default:
if let label = first(arrSingleRow) {
sectionEntries[label] = first(dropFirst(arrSingleRow)) ?? ""
}
}
}
}
}
if let header = arrIndexSection {
completeData[header] = sectionEntries
}
return completeData
}
println("fail: \(error)")
return nil
}
if let data = startImportTextfile(Process.arguments[1], Process.arguments[2]) {
for (key,value) in data {
println("Key: \(key)\nData: \(value)")
}
}

Related

String interpretation in Swift

I have a struct that looks like this:
struct colorShapeSize{
let color: String!
let shape: String!
let size: UIImage!
}
and I have a string that looks something like this:
"color:{Blue}shape:{round}size:{medium}"
All of the strings will be in the same format (i.e. color will always come first, shape second, and size third).
How would I extract the data from the string and put it into a colorShapeSize struct?
How about this?
struct ColorShapeSize {
let color: String
let shape: String
let size: String
init(rawValue: String) {
var dictionary: [String: String] = [:]
var sorted = rawValue.components(separatedBy: "}").filter({ return $0.components(separatedBy: ":{").count == 2 })
for s in sorted {
let kv = s.components(separatedBy: ":{")
let key = kv[0]
let value = kv[1]
dictionary[key] = value
}
color = dictionary["color"] ?? ""
shape = dictionary["shape"] ?? ""
size = dictionary["size"] ?? ""
}
}
let str = "color:{Blue}shape:{round}size:{medium}"
let css = ColorShapeSize(rawValue: str)
print(css.color, css.shape, css.size)
try this, it will extract the string in array, then you can do what you want with the value
func test() {
let givenString = "color:{Blue}shape:{round}size:{medium}"
var results = [String]()
do {
let regex = try NSRegularExpression(pattern: "\\{(.*?)\\}", options: [])
let tempString = givenString as NSString
regex.enumerateMatches(in: givenString, options: [], range: NSMakeRange(0, givenString.characters.count), using: { (result, flag, stop) in
if let range = result?.rangeAt(1) {
let number = tempString.substring(with: range)
results.append(number)
}
})
print(results) //["Blue", "round", "medium"] (Here you can initialize your struct with the values)
}
catch(let error) {
print("Unable to extract string : \(error.localizedDescription)")
}
}

Delete characters in range of string

I have the following string I would like to edit:
var someString = "I wan't this text {something I don't want}"
I would like to remove all the text contained in the two braces, no matter how long that text is. I have been using the follow code to remove a section of a String when I know the range:
extension String {
mutating func deleteCharactersInRange(range: NSRange) {
let mutableSelf = NSMutableString(string: self)
mutableSelf.deleteCharactersInRange(range)
self = mutableSelf
}
}
However, I do not know the range in my problem. Any ideas?
Working with strings and ranges can be quite challenging when mixing NSString and NSRange with Swift's String and Range.
Here is a pure Swift solution.
var someString = "I wan't this text {something I don't want}"
let rangeOpenCurl = someString.rangeOfString("{")
let rangeCloseCurl = someString.rangeOfString("}")
if let startLocation = rangeOpenCurl?.startIndex,
let endLocation = rangeCloseCurl?.endIndex {
someString.replaceRange(startLocation ..< endLocation, with: "")
}
With a RegEx pattern to match anything enclosed with curly brackets:
var sourceString: String = "I wan\'t this text {something I don't want}"
let destinationString = sourceString.stringByReplacingOccurrencesOfString("\\{(.*?)\\}", withString: "", options: .RegularExpressionSearch)
print(destinationString)
This will print "I wan't this text " without the double quotes.
extension String {
func getCurlyBraceRanges() -> [NSRange] {
var results = [NSRange]()
var leftCurlyBrace = -1
for index in 0..<self.characters.count {
let char = self[self.startIndex.advancedBy(index)]
if char == Character("{") {
leftCurlyBrace = index
} else if char == Character("}") {
if leftCurlyBrace != -1 {
results.append(NSRange(location: leftCurlyBrace, length: index - leftCurlyBrace + 1))
leftCurlyBrace = -1
}
}
}
return results
}
mutating func deleteCharactersInRange(range: NSRange) {
let mutableSelf = NSMutableString(string: self)
mutableSelf.deleteCharactersInRange(range)
self = String(mutableSelf)
}
mutating func deleteCharactersInRanges(ranges: [NSRange]) {
var tmpString = self
for i in (0..<ranges.count).reverse() {
tmpString.deleteCharactersInRange(ranges[i])
print(tmpString)
}
self = tmpString
}
}
var testString = "I wan't this text {something I don't want}"
testString.deleteCharactersInRanges(testString.getCurlyBraceRanges())
Output: "I wan't this text "

Ignore a letter in swift which starts with a Lower Case

Here's what I am trying to do :
let courseName = "Bachelor of Tourism Administration(B.T.A)".condensedWhitespace
let upperCaseCourseName = courseName.uppercaseString
let extrctCourseName = upperCaseCourseName.componentsSeparatedByString(" ").reduce("") { $0.0 + String($0.1.characters.first!) }
let upperCasecourseFirstCharcters = extrctCourseName
print(upperCasecourseFirstCharcters) // output : "BOTA" but i want "BTA"
as you see that my outPut of "Bachelor of Tourism Administration(B.T.A)" is BOTA but the desired output is BTA because word of is starting from a lowerCase and i want to ignore that word in my this method , how am gonna do that any idea ?
let courseName = "Bachelor of Tourism Administration(B.T.A)" //.condensedWhitespace
var newString = ""
let array : NSArray = courseName.componentsSeparatedByString(" ")
for chr in array {
let str = chr as! NSString
if str.lowercaseString != str{
if newString.characters.count > 0{
newString = newString.stringByAppendingString(" "+(str as String))
continue
}
newString = newString.stringByAppendingString((str as String))
}
}
let upperCaseCourseName = newString.uppercaseString
let extrctCourseName = upperCaseCourseName.componentsSeparatedByString(" ").reduce("") { $0.0 + String($0.1.characters.first!) }
let upperCasecourseFirstCharcters = extrctCourseName
print(upperCasecourseFirstCharcters)
//This will defiantly meet to your problem/. Let me know if it works for u or not
You can paste this into a playground:
extension String {
func array() -> [String] {
return self.componentsSeparatedByString(" ")
}
func abbreviate() -> String {
var output = ""
let array = self.array()
for word in array {
let index = word.startIndex.advancedBy(0)
let str = String(word[index])
if str.lowercaseString != str {
output += str
}
}
return output
}
}
let courseName = "Bachelor of Tourism Administration(B.T.A)".abbreviate()
print(courseName) // prints BTA
A clean approach would be:
extension Character
{
public func isUpper() -> Bool
{
let characterString = String(self)
return (characterString == characterString.uppercaseString) && (characterString != characterString.lowercaseString)
}
}
let courseName = "Bachelor of Tourism Administration(B.T.A)"
let upperCaseCourseName = courseName
let extrctCourseName = upperCaseCourseName.componentsSeparatedByString(" ").reduce("") {
if($0.1.characters.first!.isUpper()) {
return $0.0 + String($0.1.characters.first!)
}else {
return $0.0
}
}

Splitting a string in swift using multiple delimiters

I am trying to split (or explode) a string in Swift (1.2) using multiple delimiters, or seperators as Apple calls them.
My string looks like this:
KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value
I have formatted it for easy reading:
KEY1=subKey1=value&subkey2=value
KEY2=subkey1=value&subkey2=value
KEY3=subKey1=value&subkey3=value
The uppercase "KEY" are predefined names.
I was trying to do this using:
var splittedString = string.componentsSeparatedByString("KEY1")
But as you can see, I can only do this with one KEY as the separator, so I am looking for something like this:
var splittedString = string.componentsSeperatedByStrings(["KEY1", "KEY2", "KEY3"])
So the result would be:
[
"KEY1" => "subKey1=value&subkey2=value",
"KEY2" => "subkey1=value&subkey2=value",
"KEY3" => "subkey1=value&subkey2=value"
]
Is there anything built into Swift 1.2 that I can use?
Or is there some kind of extension/library that can do this easily?
Thanks for your time, and have a great day!
One can also use the following approach to split a string with multiple delimiters in case keys are single characters:
//swift 4+
let stringData = "K01L02M03"
let res = stringData.components(separatedBy: CharacterSet(charactersIn: "KLM"))
//older swift syntax
let res = stringData.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: "KLM"));
res will contain ["01", "02", "03"]
If anyone knows any kind of special syntax to extend the approach to multiple characters per key you are welcome to suggest and to improve this answer
Swift 4.2 update to #vir us's answer:
let string = "dots.and-hyphens"
let array = string.components(separatedBy: CharacterSet(charactersIn: ".-"))
This isn't very efficient, but it should do the job:
import Foundation
extension String {
func componentsSeperatedByStrings(ss: [String]) -> [String] {
let inds = ss.flatMap { s in
self.rangeOfString(s).map { r in [r.startIndex, r.endIndex] } ?? []
}
let ended = [startIndex] + inds + [endIndex]
let chunks = stride(from: 0, to: ended.count, by: 2)
let bounds = map(chunks) { i in (ended[i], ended[i+1]) }
return bounds
.map { (s, e) in self[s..<e] }
.filter { sl in !sl.isEmpty }
}
}
"KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value".componentsSeperatedByStrings(["KEY1", "KEY2", "KEY3"])
// ["=subKey1=value&subkey2=value", "=subkey1=value&subkey2=value", "=subKey1=value&subkey3=value"]
Or, if you wanted it in dictionary form:
import Foundation
extension String {
func componentsSeperatedByStrings(ss: [String]) -> [String:String] {
let maybeRanges = ss.map { s in self.rangeOfString(s) }
let inds = maybeRanges.flatMap { $0.map { r in [r.startIndex, r.endIndex] } ?? [] }
let ended = [startIndex] + inds + [endIndex]
let chunks = stride(from: 0, to: ended.count, by: 2)
let bounds = map(chunks) { i in (ended[i], ended[i+1]) }
let values = bounds
.map { (s, e) in self[s..<e] }
.filter { sl in !sl.isEmpty }
let keys = filter(zip(maybeRanges, ss)) { (r, _) in r != nil }
var result: [String:String] = [:]
for ((_, k), v) in zip(keys, values) { result[k] = v }
return result
}
}
"KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value".componentsSeperatedByStrings(["KEY1", "KEY2", "KEY3"])
// ["KEY3": "=subKey1=value&subkey3=value", "KEY2": "=subkey1=value&subkey2=value", "KEY1": "=subKey1=value&subkey2=value"]
For Swift 2:
import Foundation
extension String {
func componentsSeperatedByStrings(ss: [String]) -> [String] {
let unshifted = ss
.flatMap { s in rangeOfString(s) }
.flatMap { r in [r.startIndex, r.endIndex] }
let inds = [startIndex] + unshifted + [endIndex]
return inds.startIndex
.stride(to: inds.endIndex, by: 2)
.map { i in (inds[i], inds[i+1]) }
.flatMap { (s, e) in s == e ? nil : self[s..<e] }
}
}
Swift 5:
extension String {
func components<T>(separatedBy separators: [T]) -> [String] where T : StringProtocol {
var result = [self]
for separator in separators {
result = result
.map { $0.components(separatedBy: separator)}
.flatMap { $0 }
}
return result
}
}
It's for the sack of nice and neat code, don't use it if you need something efficiently
Swift 2 for forward compatibility
Using a regular expression:
let string = "KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value"
let nsString :NSString = string
let stringRange = NSMakeRange(0, string.utf16.count)
let pattern = "(KEY\\d)=([^=]+=[^&]+[^=]+?=[^K]+)"
var results = [String:String]()
do {
var regEx = try NSRegularExpression(pattern:pattern, options:[])
regEx.enumerateMatchesInString(string, options: [], range: stringRange) {
(result : NSTextCheckingResult?, _, _) in
if let result = result {
if result.numberOfRanges == 3 {
let key = nsString.substringWithRange(result.rangeAtIndex(1))
let value = nsString.substringWithRange(result.rangeAtIndex(2))
results[key] = value
}
}
}
}
catch {
print("Bad Pattern")
}
results: ["KEY3": "subKey1=value&subkey3=value", "KEY2": "subkey1=value&subkey2=value", "KEY1": "subKey1=value&subkey2=value"]
You could do it with regular expressions. The below snippet is a bit clumsy and not really fail-safe but it should give you an idea.
let string = "KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value"
let re = NSRegularExpression(pattern: "(KEY1|KEY2|KEY3)=", options: nil, error: nil)!
let matches = re.matchesInString(string, options: nil,
range: NSMakeRange(0, count(string)))
var dict = [String: String]()
for (index, match) in enumerate(matches) {
let key = (string as NSString).substringWithRange(
NSMakeRange(match.range.location, match.range.length - 1))
let valueStart = match.range.location + match.range.length
let valueEnd = index < matches.count - 1 ? matches[index + 1].range.location
: count(string)
let value = (string as NSString).substringWithRange(
NSMakeRange(valueStart, valueEnd - valueStart))
dict[key] = value
}
The final value of dict is
[KEY3: subKey1=value&subkey3=value,
KEY2: subkey1=value&subkey2=value,
KEY1: subKey1=value&subkey2=value]

How to fetch safety value from String in Swift iOS?

I have EKParticipant object with description looks like:
item description: EKAttendee <0x1c0b7d90> {UUID = 116B99AB9-41AC-4741-A288-B67172298625; name = Snaggy Snags; email = snaggy#gmail.com; status = 4; role = 1; type = 1}
How to safety split this string to Dictionary in order to fetch after email key value?
This is what I did so far:
extension String {
func split(splitter: String) -> Array<String> {
let regEx = NSRegularExpression(pattern: splitter, options: NSRegularExpressionOptions(), error: nil)!
let stop = "SomeStringThatYouDoNotExpectToOccurInSelf"
let modifiedString = regEx.stringByReplacingMatchesInString(self, options: NSMatchingOptions(), range: NSMakeRange(0, countElements(self)), withTemplate: stop)
return modifiedString.componentsSeparatedByString(stop)
}
func removeCharsFromEnd(count:Int) -> String{
let stringLength = countElements(self)
let substringIndex = (stringLength < count) ? 0 : stringLength - count
return self.substringToIndex(advance(self.startIndex, substringIndex))
}
}
var str = "item description: EKAttendee <0x1c0b7d90> {UUID = 16B99AB9-41AC-4742-A288-B67172299625; name = Snaggy Snags; email = snaggy#gmail.com; status = 4; role = 1; type = 1}"
var newStr = str.split("\\{")[1]
newStr = newStr.removeCharsFromEnd(1)
So now newStr equals:
UUID = 16B99AB9-41AC-4741-A288-B67172298625; name = Snaggy Snags; email = snaggy#gmail.com; status = 4; role = 1; type = 1
What next?
Thanks,
You can use componentsSeparatedByString method to extract your elements as follow:
extension String {
var elements:(udid: String, name: String, email: String, status: Int, role: Int, type: Int) {
let components = componentsSeparatedByString("; ")
if components.count == 6 {
let udid = components[0].componentsSeparatedByString(" = ").last ?? ""
let name = components[1].componentsSeparatedByString(" = ").last ?? ""
let email = components[2].componentsSeparatedByString(" = ").last ?? ""
let status = components[3].componentsSeparatedByString(" = ").last ?? ""
let role = components[4].componentsSeparatedByString(" = ").last ?? ""
let type = components[5].componentsSeparatedByString(" = ").last ?? ""
return (udid, name, email, (status as NSString).integerValue, (role as NSString).integerValue, (type as NSString).integerValue)
}
return ("","","",0,0,0)
}
}
let input = "UUID = 16B99AB9-41AC-4741-A288-B67172298625; name = Snaggy Snags; email = snaggy#gmail.com; status = 4; role = 1; type = 1"
let result = input.elements // (.0 "16B99AB9-41AC-4741-A288-B67172298625", .1 "Snaggy Snags", .2 "snaggy#gmail.com", .3 4, .4 1, .5 1, .6 "UUID = 16B99AB9-41AC-4741-A288-B67172298625; name = Snaggy Snags; email = snaggy#gmail.com; status = 4; role = 1; type = 1")
println(result.udid) // "16B99AB9-41AC-4741-A288-B67172298625"
println(result.name) // "Snaggy Snags"
println(result.email) // "snaggy#gmail.com"
println(result.status.description) // "4"
println(result.role.description) // "1"
println(result.type.description) // "1"
You can also use String's method hasPrefix to make sure you are grabbing the right info from your elements even if they return unordered as follow:
extension String {
var elements:(udid: String, name: String, email: String, status: Int, role: Int, type: Int) {
let components = componentsSeparatedByString("; ")
var udid = "", name = "", email = "", status = 0, role = 0, type = 0
for item in components {
println(item)
if item.hasPrefix("UUID = "){
udid = item.substringWithRange(Range(start: advance(item.startIndex, 7), end: item.endIndex))
}
if item.hasPrefix("name = "){
name = item.substringWithRange(Range(start: advance(item.startIndex, 7), end: item.endIndex))
}
if item.hasPrefix("email = "){
email = item.substringWithRange(Range(start: advance(item.startIndex, 8), end: item.endIndex))
}
if item.hasPrefix("status = "){
status = (item.substringWithRange(Range(start: advance(item.startIndex, 9), end: item.endIndex)) as NSString).integerValue
}
if item.hasPrefix("role = "){
role = (item.substringWithRange(Range(start: advance(item.startIndex, 7), end: item.endIndex)) as NSString).integerValue
}
if item.hasPrefix("type = "){
type = (item.substringWithRange(Range(start: advance(item.startIndex, 7), end: item.endIndex)) as NSString).integerValue
}
}
return (udid, name, email, status, role, type)
}
}
let input = "UUID = 16B99AB9-41AC-4741-A288-B67172298625; name = Snaggy Snags; email = snaggy#gmail.com; status = 4; role = 1; type = 1"
let elements = input.elements // (.0 "16B99AB9-41AC-4741-A288-B67172298625", .1 "Snaggy Snags", .2 "snaggy#gmail.com", .3 4, .4 1, .5 1)
let udid = elements.udid // "16B99AB9-41AC-4741-A288-B67172298625"
let name = elements.name // "Snaggy Snags"
let email = elements.email // "snaggy#gmail.com"
let status = elements.status.description // "4"
let role = elements.role.description // "1"
let type = elements.type.description // "1"
Here is method that fetches email or returns nil if something goes wrong:
func fetchEmailIfExists(str:String) -> String?{
var email:String?
for item:String in str.split(";"){
if item.contains("email"){
var emailPart = item.trim()
if emailPart.componentsSeparatedByString("=").first?.trim() == "email" {
if let temp:String = emailPart.componentsSeparatedByString("=").last?.trim(){
return temp
}
}
}
}
return email
}
Helpers
extension String {
func split(splitter: String) -> Array<String> {
let regEx = NSRegularExpression(pattern: splitter, options: NSRegularExpressionOptions(), error: nil)!
let stop = "SomeStringThatYouDoNotExpectToOccurInSelf"
let modifiedString = regEx.stringByReplacingMatchesInString(self, options: NSMatchingOptions(), range: NSMakeRange(0, countElements(self)), withTemplate: stop)
return modifiedString.componentsSeparatedByString(stop)
}
func trim() -> String {
return self.stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet())
}
func contains(find: String) -> Bool{
return self.rangeOfString(find) != nil
}
}
This a perfect use case for NSScanner. The stringToDictionary takes a String like yours and returns a dictionary of [String: String]. You can use it for any string in your format, regardless of the amount of key/value pairs. However it will break down when the values contain semicolons or equal signs.
let string = "item description: EKAttendee <0x1c0b7d90> {UUID = 116B99AB9-41AC-4741-A288-B67172298625; name = Snaggy Snags; email = snaggy#gmail.com; status = 4; role = 1; type = 1}"
var dictionary = stringToDictionary(string)
func stringToDictionary(input: String) -> [String: String] {
var output = [String: String]()
let scanner = NSScanner(string: input)
let separatingCharacters = NSCharacterSet(charactersInString: ";}")
scanner.scanUpToString("{", intoString: nil)
scanner.scanString("{", intoString: nil)
var key: NSString?, value: NSString?
while !scanner.atEnd {
scanner.scanUpToString(" =", intoString: &key)
scanner.scanString("= ", intoString: nil)
scanner.scanUpToCharactersFromSet(separatingCharacters, intoString: &value)
scanner.scanCharactersFromSet(separatingCharacters, intoString: nil)
if let key = key as? String, value = value as? String {
output[key] = value
}
}
return output
}

Resources