Reputation: 105
I'm programming in swift 5 a search routine and I want to highlight in a string if the search is contained in this string (e.g. if I search for "bcd" in a string like "äbcdef" the result should look like "äbcdef". In doing so I wrote an extension for String to split a String into the substring before the match with the search string (="before") , the match with the search string (="match") and the substring afterwards (="after").
extension String {
func findSubstring(forSearchStr search: String, caseSensitive sensitive: Bool) -> (before: String, match: String, after: String) {
var before = self
var searchStr = search
if !sensitive {
before = before.lowercased()
searchStr = searchStr.lowercased()
}
var match = ""
var after = ""
let totalStringlength = before.count
let searchStringlength = searchStr.count
var startpos = self.startIndex
var endpos = self.endIndex
for id in 0 ... (totalStringlength - searchStringlength) {
startpos = self.index(self.startIndex, offsetBy: id)
endpos = self.index(startpos, offsetBy: searchStringlength)
if searchStr == String(before[startpos ..< endpos]) {
before = String(self[self.startIndex ..< startpos])
match = String(self[startpos ..< endpos])
if id < totalStringlength - searchStringlength - 1 {
startpos = self.index(startpos, offsetBy: searchStringlength)
after = String(self[startpos ..< self.endIndex])
}
break
}
}
return (before, match, after)
} // end findSubstring()
}
My problem is, that this routine works well for all strings without special characters like the German Umlaute "ä, ö, ü" or "ß". If a string contains one of these characters the returned substrings "match" and "after" are shifted one sign to the right. In the example above the result for the search "bcd" is in this case "äbcdef"
My question is, what do I have to do to handle this characters properly as well?
By the way: is there a simplier solution than mine to split a string as described than what I have programmed (which seems to me to be rather complex :) )
Thanks for your valuable support
Upvotes: 0
Views: 198
Reputation: 274835
String comparison is a complicated issue, and is something you would not want to handle yourself unless you are studying this.
Just use String.range(of:options:)
:
extension String {
func findSubstring(forSearchStr search: String, caseSensitive sensitive: Bool) -> (before: String, match: String, after: String)? {
if let substringRange = range(of: search, options: sensitive ? [] : [.caseInsensitive], locale: nil) {
return (String(self[startIndex..<substringRange.lowerBound]),
String(self[substringRange]),
String(self[substringRange.upperBound..<self.endIndex]))
} else {
return nil
}
}
}
// (before: "ä", match: "bcd", after: "e")
print("äbcde".findSubstring(forSearchStr: "bcd", caseSensitive: true)!)
Note that this is not a literal comparison. For example:
// prints (before: "", match: "ß", after: "")
print("ß".findSubstring(forSearchStr: "ss", caseSensitive: false)!)
If you want a literal comparison, use the literal
option:
range(of: search, options: sensitive ? [.literal] : [.caseInsensitive, .literal])
Upvotes: 3