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 []
}
}
1 Answer 1
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.
(?<=Signature=)[^&]+
, is exactly what I want \$\endgroup\$[&?]Signature=([^&#?]+)
is probably closer, using a capture group, or[&?]Signature=(?<sig>[^&#?]+)
to use a named capture group. \$\endgroup\$