Swift regex for searching format specifiers in a string - ios

I need help to write the swift regex to find any format specifier in a string.
Eg.
"I am %#. My age is %d and my height is %.02f."
I need to find all sub-strings in bold and replace them with 'MATCH'
Below is my code
var description = "I am %#. My age is %d and my height is %.02f. (%#)"
let pattern = "(%[#df])"
let regex = try NSRegularExpression(pattern: pattern, options: [])
let nsrange = NSRange(description.startIndex..<description.endIndex, in: description)
while let match = regex.firstMatch(in: description, options: [], range: nsrange) {
description = (description as NSString).replacingCharacters(in: match.range, with: "MATCH")
}
print(description)
and output
I am MATCH. My age is MATCH and my height is %.02f. (%#)
It did not find %.02f and last %# with paranthesis.
Thanks in advance!

First of all you have to replace the matches reversed otherwise you will run into index trouble.
A possible pattern is
%([.0-9]+)?[#df]
it considers also the (optional) decimal places specifier.
var description = "I am %#. My age is %d and my height is %.02f. (%#)"
let pattern = "%([.0-9]+)?[#df]"
let regex = try NSRegularExpression(pattern: pattern)
let nsrange = NSRange(description.startIndex..., in: description)
for match in regex.matches(in: description, range: nsrange).reversed() {
let range = Range(match.range, in: description)!
description.replaceSubrange(range, with: "MATCH")
}
print(description)

Related

Swift regular expression for arabic decimals in arabic text

I have some Arabic text which has some decimals as well.
for example this text
"بِسۡمِ اللّٰہِ الرَّحۡمٰنِ الرَّحِیۡمِ ﴿۱﴾"
"وَاِذَا قِیۡلَ لَہُمۡ اٰمِنُوۡا کَمَاۤ اٰمَنَ النَّاسُ قَالُوۡۤا اَنُؤۡمِنُ کَمَاۤ اٰمَنَ السُّفَہَآءُ ؕ اَلَاۤ اِنَّہُمۡ ہُمُ السُّفَہَآءُ وَلٰکِنۡ لَّا یَعۡلَمُوۡنَ ﴿۱۴﴾"
This text has verse numbers as Arabic digits in the end.
I wanted to find out all the matches for the verse numbers in these verses.
In swift I am tring to use the regular expression but somehow i am not coming up with the correct regex.
Here is my code:
func getRegex() {
// unicode for the arabic digits
let regexStr = "[\u{0660}-\u{0669}]+"
//let regexStr = "[\\p{N}]+"
//let regexStr = "[۹۸۷۶۵۴۳۲۱۰]+"
do {
let regex = try NSRegularExpression(pattern: regexStr, options: .caseInsensitive)
let matches = regex.matches(in: self.arabicText, options: .anchored, range: NSRange(location: 0, length: self.arabicText.count))
print("Matches count : \(matches.count)")
} catch {
print(error)
}
}
Can somebody guide me on how I can get the matches for the Arabic digits in the example Arabic text?
The .anchored argument makes the pattern only match at the start of string, so you need to remove it.
Also, as your string is not ASCII, you need to use self.arabicText.utf16.count string property rather than accessing the self.arabicText.count directly.
So, you can use
let regexStr = "[۹۸۷۶۵۴۳۲۱۰]+"
and then
let matches = regex.matches(in: self.arabicText, options: [], range: NSRange(location: 0, length: self.arabicText.utf16.count))

Replace matches using regex by modifying matched string in swift

Im trying to replace matched strings using regex in swift, my requirement is as below
originalString = "It is live now at Germany(DE)"
i want the string within the (" ") i.eDE to be separated by space i.e. "D E"
so replacedString should be "It is live now at Germany(D E)"
i tried below code
var value: NSMutableString = "It is live now at Germany(DE)"
let pattern = "(\\([A-Za-z ]+\\))"
let regex = try? NSRegularExpression(pattern: pattern)
regex?.replaceMatches(in: value, options: .reportProgress, range:
NSRange(location: 0,length: value.length), withTemplate: " $1 ")
print(value)
output is It is live now at Germany (DE), i know it's not what is required.
here it is based on the template where we cannot modify based on matched string value. Is there any way to achieve this ?
Thanks in advance
You may use
var value: NSMutableString = "It is live now at Germany(DE) or (SOFE)"
let pattern = "(?<=\\G(?<!\\A)|\\()[A-Za-z](?=[A-Za-z]+\\))"
let regex = try? NSRegularExpression(pattern: pattern)
regex?.replaceMatches(in: value, options: .reportProgress, range: NSRange(location: 0,length: value.length), withTemplate: "$0 ")
print(value)
Or just
let val = "It is live now at Germany(DE) or (SOFE)"
let pattern = "(?<=\\G(?<!\\A)|\\()[A-Za-z](?=[A-Za-z]+\\))"
print( val.replacingOccurrences(of: pattern, with: "$0 ", options: .regularExpression, range: nil) )
Output: It is live now at Germany(D E) or (S O F E)
Pattern details
(?<=\\G(?<!\\A)|\\() - a positive lookbehind that matches a location right after ( or at the end of the preceding successful match
[A-Za-z] - matches and consumes any ASCII letter
(?=[A-Za-z]+\\)) - a positive lookahead that matches a location that is immediately followed with 1+ ASCII letters and then a ) char.
The $0 in the replacement inserts the whole match value back into the resulting string.

Exclude pattern in NSRegularExpression

Imagine the following sentence:
The **quick** brown **fox** ...
If I run the following regex
let boldPattern = "\\*{2}([\\w ]+)\\*{2}"
let boldRegex = try NSRegularExpression(pattern: boldPattern)
let str = "The **quick** brown **fox** ..."
let results = regex.matches(in: str, range: NSRange(str.startIndex..., in: str))
results.forEach {
print("$0")
}
I'm able to get all the words between the **.
I have read about negative regex, which returns every words except the ones we're trying to "avoid".
Given the above sentence, is there a way to use a negative regex to get words that doesn't match the boldPattern, so I would get The brown ..., avoiding the **words** ??
Edit
I'm looking for something around this pattern ((?!\\*{2}([\\w ]+)\\*{2}).*) , something that searches for words that doesn't start with ** word **
One way to do it is to use NSRegularExpression's stringByReplacingMatches method.
Example:
let boldPattern = "\\*{2}([\\w ]+)\\*{2}"
let boldRegex = try NSRegularExpression(pattern: boldPattern)
let str = "The **quick** brown **fox** ..."
let unmatchedString = boldRegex.stringByReplacingMatches(in: str, options: [], range: NSMakeRange(0, str.count), withTemplate: "")
print(unmatchedString)
// prints: The brown ...

Replace regex match with attributed string and text

Our app Api returns a field with custom format for user mentions just like:
"this is a text with mention for #(steve|user_id)".
So before display it on UITextView, need to process the text, find the pattern and replace with something more user friendly.
Final result would be "this is a text with mention for #steve" where #steve should have a link attribute with user_id. Basically the same functionality as Facebook.
First I've created an UITextView extension, with a match function for the regex pattern.
extension UITextView {
func processText(pattern: String) {
let inString = self.text
let regex = try? NSRegularExpression(pattern: pattern, options: [])
let range = NSMakeRange(0, inString.characters.count)
let matches = (regex?.matchesInString(inString, options: [], range: range))! as [NSTextCheckingResult]
let attrString = NSMutableAttributedString(string: inString, attributes:attrs)
//Iterate over regex matches
for match in matches {
//Properly print match range
print(match.range)
//A basic idea to add a link attribute on regex match range
attrString.addAttribute(NSLinkAttributeName, value: "\(schemeMap["#"]):\(must_be_user_id)", range: match.range)
//Still text it's in format #(steve|user_id) how could replace it by #steve keeping the link attribute ?
}
}
}
//To use it
let regex = ""\\#\\(([\\w\\s?]*)\\|([a-zA-Z0-9]{24})\\)""
myTextView.processText(regex)
This is what I have right now, but I'm stucked trying to get final result
Thanks a lot !
I changed your regex a bit, but got a pretty good result. Modified the code a little as well, so you can test it directly in Playgrounds.
func processText() -> NSAttributedString {
let pattern = "(#\\(([^|]*)([^#]*)\\))"
let inString = "this is a text with mention for #(steve|user_id1) and #(alan|user_id2)."
let regex = try? NSRegularExpression(pattern: pattern, options: [])
let range = NSMakeRange(0, inString.characters.count)
let matches = (regex?.matchesInString(inString, options: [], range: range))!
let attrString = NSMutableAttributedString(string: inString, attributes:nil)
print(matches.count)
//Iterate over regex matches
for match in matches.reverse() {
//Properly print match range
print(match.range)
//Get username and userid
let userName = attrString.attributedSubstringFromRange(match.rangeAtIndex(2)).string
let userId = attrString.attributedSubstringFromRange(match.rangeAtIndex(3)).string
//A basic idea to add a link attribute on regex match range
attrString.addAttribute(NSLinkAttributeName, value: "\(userId)", range: match.rangeAtIndex(1))
//Still text it's in format #(steve|user_id) how could replace it by #steve keeping the link attribute ?
attrString.replaceCharactersInRange(match.rangeAtIndex(1), withString: "#\(userName)")
}
return attrString
}

How to get value from point(a b) swift

I have this kind of string: "POINT(101.650577657408 3.1653186153213)".
Anyone know how can I get the first and second value of POINT from this String?
You can easily spit the string using componentsSeparatedByString function.
let myStr = "POINT(101.650577657408 3.1653186153213)"
let characterSet = NSCharacterSet(charactersInString: "( )")
var splitString = myStr.componentsSeparatedByCharactersInSet(characterSet)
print(splitString[0])
print(splitString[1])
print(splitString[2])
The above only works if you have the complete one String.
Although the NSCharacterSet solution is correct but here is another solution using the most powerful regex.
var error: NSError?
// Initialise the regex for a float value
let regex: NSRegularExpression = NSRegularExpression(pattern: "(\\d*\\.\\d*)", options: NSRegularExpressionOptions.CaseInsensitive, error: &error)!
// Matches array contains all the match found for float in given string
let matches: NSArray = regex.matchesInString(str as String, options: NSMatchingOptions.ReportProgress, range: NSMakeRange(0, str.length))
// You can easily get all values by enumeration
for match in matches {
println(str.substringWithRange(match.range))
}
The benefit of this solution is it will scan all the float values and will also work in case of pattern got changed.
Try this, this will work for your string
let myStr = "POINT(101.650577657408 3.1653186153213)"
let strWithout_POINT_openingBrace = myStr.stringByReplacingOccurrencesOfString("POINT(", withString: "")//"101.650577657408 3.1653186153213)"
let strWithout_closingBrace = strWithout_POINT_openingBrace.stringByReplacingOccurrencesOfString(")", withString: "")//"101.650577657408 3.1653186153213"
//now you have only space between two values
//so split string by space
let arrStringValues = strWithout_closingBrace.componentsSeparatedByString(" ");//["101.650577657408","3.1653186153213"]
print(arrStringValues[0]);//first value "101.650577657408"
print(arrStringValues[1]);//second value "3.1653186153213"

Resources