Swift Get string between 2 strings in a string

IosStringSwiftSubstring

Ios Problem Overview


I am getting a string from html parse that is;

string = "javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"

my code is something like

var startIndex = text.rangeOfString("'")
var endIndex = text.rangeOfString("',")
var range2 = startIndex2...endIndex
substr= string.substringWithRange(range)

i am not sure if my second splitting string should be "'" or "',"

i want my outcome as

substr = "Info/99/something"

Ios Solutions


Solution 1 - Ios

extension String {
    
    func slice(from: String, to: String) -> String? {
        return (range(of: from)?.upperBound).flatMap { substringFrom in
            (range(of: to, range: substringFrom..<endIndex)?.lowerBound).map { substringTo in
                String(self[substringFrom..<substringTo])
            }
        }
    }
}

"javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
  .sliceFrom("'", to: "',")

Solution 2 - Ios

I'd use a regular expression to extract substrings from complex input like this.

Swift 3.1:

let test = "javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"

if let match = test.range(of: "(?<=')[^']+", options: .regularExpression) {
    print(test.substring(with: match))
}

// Prints: Info/99/something

Swift 2.0:

let test = "javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"

if let match = test.rangeOfString("(?<=')[^']+", options: .RegularExpressionSearch) {
    print(test.substringWithRange(match))
}

// Prints: Info/99/something

Solution 3 - Ios

I rewrote one of the top Swift answers to understand what it was doing with map. I prefer a version using guard, IMO.

extension String {
    
    func slice(from: String, to: String) -> String? {
        guard let rangeFrom = range(of: from)?.upperBound else { return nil }
        guard let rangeTo = self[rangeFrom...].range(of: to)?.lowerBound else { return nil }
        return String(self[rangeFrom..<rangeTo])
    }
    
}

behavior:

let test1 =   "a[b]c".slice(from: "[", to: "]") // "b"
let test2 =     "abc".slice(from: "[", to: "]") // nil
let test3 =   "a]b[c".slice(from: "[", to: "]") // nil
let test4 = "[a[b]c]".slice(from: "[", to: "]") // "a[b"

Solution 4 - Ios

To find all substrings that are between a starting string and an ending string:

extension String {
    func sliceMultipleTimes(from: String, to: String) -> [String] {
        components(separatedBy: from).dropFirst().compactMap { sub in
            (sub.range(of: to)?.lowerBound).flatMap { endRange in
                String(sub[sub.startIndex ..< endRange])
            }
        }
    }
}

let str = "start A end ... start B end"
str.sliceMultipleTimes(from: "start", to: "end")    // ["A", "B"]

Solution 5 - Ios

This works if it is always the second split:

let subString = split(string, isSeparator: "'")[1]

Solution 6 - Ios

You can use var arr = str.componentsSeparatedByString(",") as your second split which will return you array

Solution 7 - Ios

Swift 4.2:

extension String {

    //right is the first encountered string after left
    func between(_ left: String, _ right: String) -> String? {
        guard let leftRange = range(of: left), let rightRange = range(of: right, options: .backwards)
            ,leftRange.upperBound <= rightRange.lowerBound else { return nil }
        
        let sub = self[leftRange.upperBound...]
        let closestToLeftRange = sub.range(of: right)!
        return String(sub[..<closestToLeftRange.lowerBound])
    }

}

Solution 8 - Ios

Consider using a regular expression to match everything between single quotes.

let string = "javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"

let pattern = "'(.+?)'"
let regex = NSRegularExpression(pattern: pattern, options: nil, error: nil)
let results = regex!.matchesInString(string, options: nil, range: NSMakeRange(0, count(string)))  as! [NSTextCheckingResult]

let nsstring = string as NSString
let matches = results.map { result in return nsstring.substringWithRange(result.range)}

// First match
println(matches[0])

Solution 9 - Ios

In Swift 4 or later you can create an extension method on StringProtocol to support substrings as well. You can just return a Substring instead of a new String:

edit/update: Swift 5 or later

extension StringProtocol  {
    func substring<S: StringProtocol>(from start: S, options: String.CompareOptions = []) -> SubSequence? {
        guard let lower = range(of: start, options: options)?.upperBound
        else { return nil }
        return self[lower...]
    }
    func substring<S: StringProtocol>(through end: S, options: String.CompareOptions = []) -> SubSequence? {
        guard let upper = range(of: end, options: options)?.upperBound
        else { return nil }
        return self[..<upper]
    }
    func substring<S: StringProtocol>(upTo end: S, options: String.CompareOptions = []) -> SubSequence? {
        guard let upper = range(of: end, options: options)?.lowerBound
        else { return nil }
        return self[..<upper]
    }
    func substring<S: StringProtocol, T: StringProtocol>(from start: S, upTo end: T, options: String.CompareOptions = []) -> SubSequence? {
        guard let lower = range(of: start, options: options)?.upperBound,
            let upper = self[lower...].range(of: end, options: options)?.lowerBound
        else { return nil }
        return self[lower..<upper]
    }
    func substring<S: StringProtocol, T: StringProtocol>(from start: S, through end: T, options: String.CompareOptions = []) -> SubSequence? {
        guard let lower = range(of: start, options: options)?.upperBound,
            let upper = self[lower...].range(of: end, options: options)?.upperBound
        else { return nil }
        return self[lower..<upper]
    }
}

Usage:

let string = "javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
let substr = string.substring(from: "'")                   // "Info/99/something', 'City Hall',1, 99);"
let through = string.substring(through: "Info")  // "javascript:getInfo"
let upTo = string.substring(upTo: "Info")  // "javascript:get"
let fromUpTo = string.substring(from: "'", upTo: "',")  // "Info/99/something"
let fromThrough = string.substring(from: "'", through: "',")  // "Info/99/something',"

let fromUpToCaseInsensitive = string.substring(from: "'info/", upTo: "/something", options: .caseInsensitive)  // "99"

Solution 10 - Ios

Swift 5

extension String {
    
    ///Returns an empty string when there is no path.
    func substring(from left: String, to right: String) -> String {
        if let match = range(of: "(?<=\(left))[^\(right)]+", options: .regularExpression) {
            return String(self[match])
        }
        return ""
    }
}

Solution 11 - Ios

If you want to support also from the start or end of the string

extension String {
    func slice(from: String, to: String) -> String? {
        return (from.isEmpty ? startIndex..<startIndex : range(of: from)).flatMap { fromRange in
            (to.isEmpty ? endIndex..<endIndex : range(of: to, range: fromRange.upperBound..<endIndex)).map({ toRange in
                String(self[fromRange.upperBound..<toRange.lowerBound])
            })
        }
    }
}

"javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
  .slice(from: "'", to: "',") // "Info/99/something"

"javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
  .slice(from: "", to: ":") // "javascript"

"javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
  .slice(from: ":", to: "") // "getInfo(1,'Info/99/something', 'City Hall',1, 99);"

"javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
  .slice(from: "", to: "") // "javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"

if you want another syntax, maybe more readable

extension String {
    func slice(from: String, to: String) -> String? {
        guard let fromRange = from.isEmpty ? startIndex..<startIndex : range(of: from) else { return nil }
        guard let toRange = to.isEmpty ? endIndex..<endIndex : range(of: to, range: fromRange.upperBound..<endIndex) else { return nil }
        
        return String(self[fromRange.upperBound..<toRange.lowerBound])
    } 
}

Solution 12 - Ios

Swift 4 version of @litso. To find all values in text

func find(inText text: String, pattern: String) -> [String]? {
    do {
        let regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
        let result = regex.matches(in: text, options: .init(rawValue: 0), range: NSRange(location: 0, length: text.count))

        let matches = result.map { result in
            return (text as NSString).substring(with: result.range)
        }

        return matches
    } catch {
        print(error)
    }
    return nil
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlpView Question on Stackoverflow
Solution 1 - IosoisdkView Answer on Stackoverflow
Solution 2 - IossebView Answer on Stackoverflow
Solution 3 - IospkambView Answer on Stackoverflow
Solution 4 - IosShaked SayagView Answer on Stackoverflow
Solution 5 - IosQbyteView Answer on Stackoverflow
Solution 6 - IosiAnuragView Answer on Stackoverflow
Solution 7 - IosDevansh VyasView Answer on Stackoverflow
Solution 8 - IoslitsoView Answer on Stackoverflow
Solution 9 - IosLeo DabusView Answer on Stackoverflow
Solution 10 - IosScottyBladesView Answer on Stackoverflow
Solution 11 - IosMendyView Answer on Stackoverflow
Solution 12 - IosArekView Answer on Stackoverflow