67

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"
2
  • Is there always the same length (for example the 1) - or is that different? Is the 'Info/..." always the same? Pls share some more Strings, to find out the best way to get the String. Commented Jul 30, 2015 at 13:48
  • javascript:getInfo(1,'Info/123/somethingelse', 'City2 hall3',456,789); Commented Jul 30, 2015 at 13:59

12 Answers 12

134
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: "',")
Sign up to request clarification or add additional context in comments.

4 Comments

@oisdk does it work if we need to slice from a string to the end of the string ? Ex : "blabla popo titi toto" -> slice(from "popo", to : endOfString) ?
In Swift 4 you can also compute the second range as self[substringFrom...].range(of: to), that saves some keystrokes and the mention of endIndex.
I found this return flatmap.().map() code pretty hard to grok for the function; so I tried rewriting a version using guard.
@Makaille see this answer
34

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"

Comments

30

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

1 Comment

For more detail on the regex, see stackoverflow.com/questions/6109882/…
16

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"]

3 Comments

Hi, I love this answer, how can we slightly change the returned results to be the string between the 2 strings but also include the from and to not just return what's in between? Thanks.
@Wael Just change the last line to: from + String(sub[sub.startIndex ..< endRange]) + to
Thank you. I did work it out Thanks for responding.
7

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"

Comments

6

This works if it is always the second split:

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

1 Comment

let subString = split(text, "'") ; return subString[1]; in this code it gives error "Missing argument for parameter "isSeparator" in call"
5

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

1 Comment

im doing this substring process in a a function, when i call return subString[1] gives error "cannot subscript a value of type '[String]' with an index of type ()." if i call subString[0] it works as expected
5

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])
    }

}

1 Comment

Remove options: .backwards if you want [a[b]c] to return a[b instead of a[b]c
5

Swift 5

extension String {
    /// Returns an array of substrings between the specified left and right strings.
    /// Returns an empty array when there are no matches.
    func substring(from left: String, to right: String) -> [String] {
        // Escape special characters in the left and right strings for use in a regular expression
        let leftEscaped = NSRegularExpression.escapedPattern(for: left)
        let rightEscaped = NSRegularExpression.escapedPattern(for: right)

        // Create a regular expression pattern to match content between the last occurrence of the left string
        // and the right string
        let pattern = "\(leftEscaped).*(?<=\(leftEscaped))(.*?)(?=\(rightEscaped))"

        // Create a regular expression object with the pattern
        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
            return []
        }

        // Find matches in the current string
        let matches = regex.matches(in: self, options: [], range: NSRange(startIndex..., in: self))

        // Extract the substrings from the matches and return them in an array
        return matches.compactMap { match in
            guard let range = Range(match.range(at: 1), in: self) else { return nil }
            return String(self[range])
        }
    }
}

// Example usage
let result = "cat cat dog rat".substring(from: "cat", to: "rat")
print(result) // Output: [" dog "]

Comments

4

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])

Comments

2

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])
    } 
}

Comments

1

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
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.