1
\$\begingroup\$

In Swift, I have a string like this

http://mnc-hdqp.oss-cn-shanghai.aliyuncs.com/user%2Fheat%2Fdefault.jpg?Signature=2BI%2BauSvy&Expires=1568682491&OSSAccessKeyId=LTAIQ8Lif1HHVkXd

Need to extract 2BI%2BauSvy, the value of key Signature

here is code: regex match, then use range to subtract the key ahead.

let key = "Signature"
let signatures = icon.matches(for: "\(key)[^&]+")
guard !signatures.isEmpty else{
     return
}
if let range = signatures[0].range(of: "\(key)="){
      let signature = String(signatures[0][range.upperBound...])
      print(signature)
}

Any way to implement it conveniently?


PS: func matches(for:)

extension String{

    func matches(for regex: String) -> [String] {

        do {
            let regex = try NSRegularExpression(pattern: regex)
            let results = regex.matches(in: self,
                                        range: NSRange(self.startIndex..., in: self))
            return results.map {
                String(self[Range($0.range, in: self)!])
            }
        } catch let error {
            print("invalid regex: \(error.localizedDescription)")
            return []
        }
    }
\$\endgroup\$
  • 2
    \$\begingroup\$ (?<=Signature=)[^&]+, is exactly what I want \$\endgroup\$ – dengApro Sep 17 '19 at 2:52
  • 2
    \$\begingroup\$ [&?]Signature=([^&#?]+) is probably closer, using a capture group, or [&?]Signature=(?<sig>[^&#?]+) to use a named capture group. \$\endgroup\$ – Oh My Goodness Sep 17 '19 at 4:15
  • \$\begingroup\$ @Oh My Goodness, you can give an answer \$\endgroup\$ – dengApro Sep 17 '19 at 7:16
4
\$\begingroup\$

Simplify the code

The guard statement and the following if let can be combined into a single statement:

let key = "Signature"
let signatures = iconURL.matches(for: "\(key)[^&]+")
if let firstSignature = signatures.first,
    let range = firstSignature.range(of: "\(key)=") {
    let signature = String(firstSignature[range.upperBound...])
    print(signature)
}

Improve the regex pattern

Your method is fragile because the key "Signature" may occur in the host part of the URL. Here is an example where it fails:

let iconURL = "http://Signature.com/?a=b&Signature=sig&c=d"

As mentioned in the comments, you can use a positive look-behind which includes the "=" character:

let key = "Signature"
let signatures = iconURL.matches(for: "(?<=\(key)=)[^&]+")
if let signature = signatures.first {
    print(signature)
}

However, this would still fail for

let iconURL = "http://foo.com/Signature=bar?a=b&Signature=sig&c=d"

because the "=" character is valid in the path part of an URL.

You also must ensure that the key does not contain any characters which have a special meaning in a regex pattern.

And now for something completely different

The Foundation framework has a dedicated URLComponents type to parse URLs into their parts. It does exactly what you need here:

let iconURL = "http://Signature.com/Signature=foo?a=b&Signature=2BI%2BauSvy&c=d"
let key = "Signature"

if let urlComponents = URLComponents(string: iconURL),
    let queryItems = urlComponents.queryItems {
    for queryItem in queryItems where queryItem.name == key {
        if let value = queryItem.value {
            print(value) // 2BI+auSvy
        }
    }
}

In addition, the value is already percent-decoded.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.