0

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,

snaggs
  • 5,543
  • 17
  • 64
  • 127
  • 1
    Why do you work with the object's `description` at all? There must be a better solution. – Martin R Apr 11 '15 at 10:22
  • @MartinR Well, this is workaround because today its problematic to fetch emails from old iCloud accounts but if I fail with this method I will use `if let email:String = item.URL?.resourceSpecifier?.lowercaseString { ..}` – snaggs Apr 11 '15 at 10:24
  • 1
    Perhaps you can add some more information about the actual problem (and where the description string comes from) to your question. – Reconstruction an object from its description is always error prone. What if any value contains an semicolon or equal sign? And is the description of an EKParticipant object documented and fixed? – Martin R Apr 11 '15 at 10:36
  • @MartinR it comes from this problem: http://stackoverflow.com/questions/13642786/how-to-get-ekevent-ekparticipant-email. Today iCloud event attendee email returns me type like `/xyzxyzxyzxyz.../principal` and not email. So the flow is: 1) get email by legal way, if email validation fails => 2) parse string description to key-value, if email key fails => return – snaggs Apr 11 '15 at 10:45
  • I cannot really help because I have no experience with this topic, I am just expressing my doubts if you are on the right path. The thread that you linked to mentions: *"I was cautioned NOT to use the description field because that may change at any time."* – Martin R Apr 11 '15 at 10:52
  • If you have a description, then you have an object. Ask the object for things. Parsing the description is ridiculous, unstable, not going to work for strings other than the one you are trying, and bound to break in the future. – gnasher729 Apr 11 '15 at 11:03
  • @gnasher729 please read comments above. I have an object but for icloud the behaviour is different – snaggs Apr 11 '15 at 11:05
  • Martin and gnasher are right. Trying to parse an object's description in order to extract information from it is a very bad idea. Don't do it. – Duncan C Apr 11 '15 at 11:37

3 Answers3

1

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"
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
0

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
    }
}
snaggs
  • 5,543
  • 17
  • 64
  • 127
0

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
}
Rengers
  • 14,911
  • 1
  • 36
  • 54