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 []
 }
 }
Martin R
24.2k2 gold badges37 silver badges95 bronze badges
asked Sep 17, 2019 at 1:09
\$\endgroup\$
3
  • 2
    \$\begingroup\$ (?<=Signature=)[^&]+, is exactly what I want \$\endgroup\$ Commented Sep 17, 2019 at 2:52
  • 2
    \$\begingroup\$ [&?]Signature=([^&#?]+) is probably closer, using a capture group, or [&?]Signature=(?<sig>[^&#?]+) to use a named capture group. \$\endgroup\$ Commented Sep 17, 2019 at 4:15
  • \$\begingroup\$ @Oh My Goodness, you can give an answer \$\endgroup\$ Commented Sep 17, 2019 at 7:16

1 Answer 1

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.

answered Sep 17, 2019 at 7:16
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.