Remove percent-escaped characters from string - ios

How do I remove, not decode, percent-escaped characters from a string using Swift. For instance:
"hello%20there"
should become
"hellothere"
EDIT:
I would like to replace multiple percent-escaped characters in a string. So:
"hello%20there%0Dperson"
should become
"hellothereperson"

let string = originalString.replacingOccurrences(of: "%[0-9a-fA-F]{2}",
with: "",
options: .regularExpression,
range: nil)

You can use the method "removingPercentEncoding"
let precentEncodedString = "hello%20there%0Dperson"
let decodedString = precentEncodedString.removingPercentEncoding ?? ""

You can use regex for that matching % followed by two numbers: %[0-9a-fA-F]{2}
let myString = "hello%20there%0D%24person"
let regex = try! NSRegularExpression(pattern: "%[0-9a-fA-F]{2}", options: [])
let range = NSMakeRange(0, myString.characters.count)
let modString = regex.stringByReplacingMatchesInString(myString,
options: [],
range: range,
withTemplate: "")
print(modString)

let input:String = "hello%20there%0Dperson"
guard let output = input.stringByRemovingPercentEncoding else{
NSLog("failed to remove percent encoding")
return
}
NSLog(output)
and the result is
hello there
person
then you can just remove the spaces
or you can remove it by regex
"%([0-9a-fA-F]{2})"

Related

How to do Inverse Regex match [regex negation] in Swift?

Is there a way to do inverse regular expression match and retrieve the un-matching string as return value in iOS Swift?
Let's say,
Input string: "232#$%4lion"
Regex Pattern: "[a-z]{4}"
Normal match Output: "lion"
Inverse match output: "232#$%4" (Expected result)
Please find the normal regex matching swift code below.
func regexMatch() {
let str = "232#$%4lion"
let regex = try! NSRegularExpression(pattern: "[a-z]{4}", options: .caseInsensitive)
let firstMatch = regex.firstMatch(in: str, options: .reportProgress, range: NSMakeRange(0, str.count))
guard let matches = firstMatch else {
print("No Match")
return
}
if matches.numberOfRanges > 0 {
let outputRange = matches.range(at: 0)
let startIndex = str.index(str.startIndex, offsetBy: outputRange.lowerBound)
let endIndex = str.index(str.startIndex, offsetBy: outputRange.upperBound)
print("Matched String: \(str[startIndex..<endIndex])")
}
}
I can somehow manipulate the matching result and then, can retrieve the inverse matching string by manipulating range operations. Instead of doing that,
I want to know if inverse matching can be done using regex pattern itself in Swift and directly retrieve un-matching pattern from the input string.
Above input and output values are for reference purpose only. Just to keep the question simple. Actual values in real time cases can be complex. Please answer with a generic solution.
You may use your regex to remove (replace with an empty string) the matches found:
let result = str.replacingOccurrences(of: "[a-z]{4}", with: "", options: .regularExpression)
Swift test:
let str = "232#$%4lion"
let result = str.replacingOccurrences(of: "[a-z]{4}", with: "", options: .regularExpression)
print(result)
Output: 232#$%4.
There is no need to use a regular expression for that. You can just use filter.
RangeReplaceableCollection has a filter instance method that returns Self, String conforms to RangeReplaceableCollection, so filter when used with a String returns another String.
You can combine it with the new Character property isLetter (Swift5) and create a predicate negating that property.
let str = "232#$%4lion"
let result = str.filter { !$0.isLetter }
print(result) // "232#$%4"
Either use this pattern, however it doesn't invert {4} because it's not predictable
"[^a-z]+"
or create a new mutable string from str and remove the found range
func invertedRegexMatch() {
let str = "232#$%4lion"
let regex = try! NSRegularExpression(pattern: "[a-z]{4}", options: .caseInsensitive)
let firstMatch = regex.firstMatch(in: str, options: .reportProgress, range: NSRange(str.startIndex..., in: str))
guard let matches = firstMatch else {
print("No Match")
return
}
if matches.numberOfRanges > 0 {
let outputRange = Range(matches.range(at: 0), in: str)!
var invertedString = str
invertedString.removeSubrange(outputRange)
print("Matched String: \(invertedString)")
}
}
Note: Use always the dedicated API to create NSRange from Range<String.Index> and vice versa
But if you just want to remove all letters there is a much simpler way
var str = "232#$%4lion"
str.removeAll{$0.isLetter}

How to remove specific characters or words from a string in swift?

var myString = "43321 This is example hahaha 4356-13"
And I want result that
var resultString = "This is example"
In other words, I want to erase certain words and numbers.
How is it possible?
I've already done a search, But I could not find exactly what I wanted.
Please answer me. Thanks
Okay, To be precise, I want to erase all numbers and erase only certain words I want.
I made a big mistake. I am so sorry. To be precise, it is correct to erase numbers and certain words. However, I should not always delete all digits, but keep them if number's are next to a string.
example)
let testString1 = "123123123123"
let testString2 = "apple 65456876"
let testString3 = "apple banana3 horse5"
let testString4 = "44 apple banana1banana 5horse"
let testString5 = "123 banana123 999"
And I want remove words "apple".
So result is
let resultString1 = ""
let resultString2 = ""
let resultString3 = "banana3 horse5"
let resultString4 = "banana1banana 5horse"
let resultString5 = "banana123"
You can try applying below code,
var sentence = "43321 This is example hahaha 4356-13"
sentence = (sentence.components(separatedBy: NSCharacterSet.decimalDigits) as NSArray).componentsJoined(by: "")
let wordToRemove = "hahaha"
if let range = sentence.range(of: wordToRemove) {
sentence.removeSubrange(range)
}
print(sentence) // This is example -
I think this will be faster with a regex solution:
//use NSMutableString so the regex.replaceMatches later will work
var myString:NSMutableString = "43321 This is example hahaha 4356-13"
//add more words to match by using | operator e.g. "[0-9]{1,}|apple|orange"
//[0-9]{1,} simply removes all numbers but is more efficient than [0-9]
let regexPattern = "[0-9]{1,}|apple"
//caseInsensitive or the regex will be much longer
let regex = try NSRegularExpression(pattern: regexPattern, options: .caseInsensitive)
var matches = regex.matches(in: myString as String, options: .withoutAnchoringBounds, range: range)
regex.replaceMatches(in: myString, options: .withoutAnchoringBounds, range: range, withTemplate: "")
print(myString) // This is example hahaha -
Subsequent Strings
var testString3: NSMutableString = "apple banana horse"
matches = regex.matches(in: testString3 as String, options: .withoutAnchoringBounds, range: range)
regex.replaceMatches(in: testString3, options: .withoutAnchoringBounds, range: range, withTemplate: "")
print(testString3) // banana horse
You can try this :
let myString = "43321 This is example hahaha 4356-13"
let stringToReplace = "43321"
let outputStr = myString.replacingOccurrences(of: stringToReplace, with: "")
print(outputStr.trimmingCharacters(in: NSCharacterSet.whitespaces))
//output: This is example hahaha 4356-13
Use this string extension .
extension String
{
func removeNumbersAString(str:String)->String
{
var aa = self.components(separatedBy: CharacterSet.decimalDigits).joined(separator: " ")
let regexp = " \\d* "
var present = self
while present.range(of:regexp, options: .regularExpression) != nil {
if let range = present.range(of:regexp, options: .regularExpression) {
let result = present.substring(with:range)
present = present.replacingOccurrences(of: result, with: "")
// print("before \(result)")
}
}
return present
}
}
A test
var str = "dsghdsghdghdhgsdghghdghds 12233 apple"
print("before \(str)") /// before dsghdsghdghdhgsdghghdghds 12233 apple
print("after \(str.removeNumbersAString(str: "apple"))") /// after dsghdsghdghdhgsdghghdghds
Try bellow code:
var myString = "43321 This is example hahaha 4356-13"
//to remove numbers
myString = (myString.components(separatedBy: NSCharacterSet.decimalDigits) as NSArray).componentsJoined(by: "")
// to remove specific word
if let range = myString.range(of: "hahaha") {
myString.removeSubrange(range)
}
print(myString)

Searching for strings starting with \n\n in Swift

Hey I have a requirement to increase the spacing in my UILables for double spaced line breaks. I want to search my string and find all the strings starting with \n\n. For example "Hello world\nI am on the next line\n\nNow I'm on the next line and it's spaced more than before\nNow I'm back to normal spacing". I'm having trouble trying to figure out the regex for this. I am trying:
let regExRule = "^\n\n*"
and passing it into this function:
func matchesForRegexInText(regex: String, text: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex, options: [])
let nsString = text as NSString
let results = regex.matchesInString(text,
options: [], range: NSMakeRange(0, nsString.length))
return results.map { nsString.substringWithRange($0.range)}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
However I am getting an empty array. Not really sure how to construct the regex pattern for this. Any pointers would be really appreciated. Thanks!
The primary issue I see is the regex pattern should include a capture group to select the multiple strings needed.
func matchesForRegexInText(regex : String, text: String) -> [String] {
var captured = [String]()
let exp = try! NSRegularExpression(pattern: regex, options: [])
let matches = exp.matchesInString(text, options:[], range: NSMakeRange(0, text.characters.count))
for match in matches {
let c = (text as NSString).substringWithRange(match.rangeAtIndex(1))
captured.append(c)
}
return captured
}
let re = "\\n\\n([\\w\\\\s,']+)"; // selection with (...)
// ["Alpha", "Bravo", "Charlie"]
let strResults = matchesForRegexInText(re, text: "\n\nAlpha\n\nBravo\n\nCharlie\n\n")

Convert unicode scalar emoji to String in Swift

I'm getting unicode scalar for emojis in a text string that I get from a server, which fail to show up as emojis when I print them in a UILabel. This is the format in which I get my string from server:
let string = "Hi, I'm lily U+1F609"
This doesn't work unless it's changed to
let string = "Hi, I'm lily \u{1F609}"
Is there anyway I can convert the string to the required format?
I don't want to use a regex to determine occurrences of U+<HEX_CODE> and then converting them to \u{HEX_CODE}. There has to be a better way of doing this.
This is the very kind of problems that regex was created for. If there's a simpler non-regex solution, I'll delete this answer:
func replaceWithEmoji(str: String) -> String {
var result = str
let regex = try! NSRegularExpression(pattern: "(U\\+([0-9A-F]+))", options: [.CaseInsensitive])
let matches = regex.matchesInString(result, options: [], range: NSMakeRange(0, result.characters.count))
for m in matches.reverse() {
let range1 = m.rangeAtIndex(1)
let range2 = m.rangeAtIndex(2)
if let codePoint = Int(result[range2], radix: 16) {
let emoji = String(UnicodeScalar(codePoint))
let startIndex = result.startIndex.advancedBy(range1.location)
let endIndex = startIndex.advancedBy(range1.length)
result.replaceRange(startIndex..<endIndex, with: emoji)
}
}
return result
}

Swift replace substring regex

I am attempting to use regular expression to replace all occurrences of UK car registrations within a string.
The following swift code works perfectly for a when the string matches the regex exactly as below.
var myString = "DD11 AAA"
var stringlength = countElements(myString)
var ierror: NSError?
var regex:NSRegularExpression = NSRegularExpression(pattern: "^([A-HK-PRSVWY][A-HJ-PR-Y])\\s?([0][2-9]|[1-9][0-9])\\s?[A-HJ-PR-Z]{3}$", options: NSRegularExpressionOptions.CaseInsensitive, error: &ierror)!
var modString = regex.stringByReplacingMatchesInString(myString, options: nil, range: NSMakeRange(0, stringlength), withTemplate: "XX")
print(modString)
The result is XX
However, the following does not work and the string is not modifed
var myString = "my car reg 1 - DD11 AAA my car reg 2 - AA22 BBB"
var stringlength = countElements(myString)
var ierror: NSError?
var regex:NSRegularExpression = NSRegularExpression(pattern: "^([A-HK-PRSVWY][A-HJ-PR-Y])\\s?([0][2-9]|[1-9][0-9])\\s?[A-HJ-PR-Z]{3}$", options: NSRegularExpressionOptions.CaseInsensitive, error: &ierror)!
var modString = regex.stringByReplacingMatchesInString(myString, options: nil, range: NSMakeRange(0, stringlength), withTemplate: "XX")
print(modString)
The result is my car reg 1 - DD11 AAA my car reg 2 - AA22 BBB
Can anyone give me any pointers?
You need to remove the ^ and $ anchors.
The ^ means start of string and $ means end of string (or line, depending on the options). That's why your first example works: in the first test string, the start of the string is really followed by your pattern and ends with it.
In the second test string, the pattern is found in the middle of the string, thus the ^... can't apply. If you would just remove the ^, the $ would apply on the second occurrence of the registration number and the output would be my car reg 1 - DD11 AAA my car reg 2 - XX.
let myString = "my car reg 1 - DD11 AAA my car reg 2 - AA22 BBB"
let regex = try! NSRegularExpression(pattern: "([A-HK-PRSVWY][A-HJ-PR-Y])\\s?([0][2-9]|[1-9][0-9])\\s?[A-HJ-PR-Z]{3}", options: NSRegularExpression.Options.caseInsensitive)
let range = NSMakeRange(0, myString.count)
let modString = regex.stringByReplacingMatches(in: myString, options: [], range: range, withTemplate: "XX")
print(modString)
// Output: "my car reg 1 - XX my car reg 2 - XX"
Let's use a class extension to wrap this up in Swift 3 syntax:
extension String {
mutating func removingRegexMatches(pattern: String, replaceWith: String = "") {
do {
let regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
let range = NSRange(location: 0, length: count)
self = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith)
} catch { return }
}
}
var phoneNumber = "+1 07777777777"
phoneNumber.removingRegexMatches(pattern: "\\+\\d{1,4} (0)?")
Results in 7777777777 (thus removing country code from phone number)
Swift 4.2 Updated
let myString = "my car reg 1 - DD11 AAA my car reg 2 - AA22 BBB"
if let regex = try? NSRegularExpression(pattern: "([A-HK-PRSVWY][A-HJ-PR-Y])\\s?([0][2-9]|[1-9][0-9])\\s?[A-HJ-PR-Z]{3}", options: .caseInsensitive) {
let modString = regex.stringByReplacingMatches(in: myString, options: [], range: NSRange(location: 0, length: myString.count), withTemplate: "XX")
print(modString)
}
Update for Swift 2.1:
var myString = "my car reg 1 - DD11 AAA my car reg 2 - AA22 BBB"
if let regex = try? NSRegularExpression(pattern: "([A-HK-PRSVWY][A-HJ-PR-Y])\\s?([0][2-9]|[1-9][0-9])\\s?[A-HJ-PR-Z]{3}", options: .CaseInsensitive) {
let modString = regex.stringByReplacingMatchesInString(myString, options: .WithTransparentBounds, range: NSMakeRange(0, myString.characters.count), withTemplate: "XX")
print(modString)
}
Warning
Do not use NSRange(location: 0, length: myString.count) as all examples above quoted.
Use NSRange(myString.startIndex..., in: myString) instead!
.count will count newline characters like \r\n as one character - this may result in a shortened, thus invalid, NSRange that does not match the whole string.
(.length should work)
With pattern: "^ ... $" you have specified that the pattern is anchored
to the start and end of the string, in other words, the entire string
must match the pattern. Just remove ^ and $ from the pattern
and you'll get the expected result.
Simple extension:
extension String {
func replacingRegex(
matching pattern: String,
findingOptions: NSRegularExpression.Options = .caseInsensitive,
replacingOptions: NSRegularExpression.MatchingOptions = [],
with template: String
) throws -> String {
let regex = try NSRegularExpression(pattern: pattern, options: findingOptions)
let range = NSRange(startIndex..., in: self)
return regex.stringByReplacingMatches(in: self, options: replacingOptions, range: range, withTemplate: template)
}
}
✅ Advantages to other answers
Exposed throwing error to the caller
Exposed finding options to the caller with default for the ease of use
Exposed replacing options to the caller with default for the ease of use
Fixed the range BUG 🐞 in the original answer
A notice to all answers that uses .count in their answers:
This will cause problems in cases that the operating target range has surrogate-paired characters.
Please fix your answers by using .utf16.count instead.
Here's Ryan Brodie 's answer with this fix. It works with Swift 5.5.
private extension String {
mutating func regReplace(pattern: String, replaceWith: String = "") {
do {
let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines])
let range = NSRange(location: 0, length: self.utf16.count)
self = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith)
} catch { return }
}
}
Update: If considering #coyer 's concerns:
private extension String {
mutating func regReplace(pattern: String, replaceWith: String = "") {
do {
let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines])
let range = NSRange(self.startIndex..., in: self)
self = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith)
} catch { return }
}
}
Also: to #Martin R' :
It is okay to use ^ and $ in Regex as long as you have enabled the ".anchorsMatchLines" in the Regex options. I already applied this option in the codeblocks above.

Resources