Swift: Checking Prefix with Regex - swift

I am attempting to check if a string begins with a text that follows a regex pattern. Is there a way to do so using string.hasPrefix()?
My implementation so far:
let pattern = "[Ff][Yy][Ii](.)?"
let regex = try? NSRegularExpression(pattern: pattern, options: [])
if firstRowText.hasPrefix(regex) { //Cannot convert value of type NSRegularExpression to String
}

You should use a regex using range(of:options:range:locale:) passing the .regularExpression option with .anchored option:
if firstRowText.range(of: "[Ff][Yy][Ii]", options: [.regularExpression, .anchored]) != nil {...}
The .anchored option makes the regex engine search for a match at the start of the string only.
To make your regex match in a case insensitive way, you may pass another option alongside the current ones, .caseInsensitive, and use a shorter regex, like "FYI":
if firstRowText.range(of: "FYI", options: [.regularExpression, .anchored, .caseInsensitive]) != nil {
See the Swift online demo.
Note that you may also use an inline modifier option (?i) to set case insensitivity:
"(?i)FYI"

Related

Regex for Ronin wallet address

I created a regex to identify the following string example:
ronin:50460c4cd74094cd591f454cad457e99c4ab8bf1
The regex doesn't recognize it. This is it:
let roninWalletPattern = #"ronin:[a-fA-F0-9]{46}"#
// Checks regex
let result = walletAddress.range(
of: roninWalletPattern,
options: .regularExpression
)
let validAddress = (result != nil)
so if it's nil is not valid.
What am I missing on that regex?
There are only 40 characters after the ronin: part, not 46. The {46} only applies to the previous token, which is [a-fA-F0-9].
Change {46} to {40}.

Getting a empty Range<String.Index> in Swift

I am very new to Swift and have trying to use regular expressions, but getting the match from the string seems to be an insurmountable task.
This is my current approach.
print(data.substring(with: (data.range(of: "[a-zA-Z]at", options: .regularExpression))))
This doesn't work because
Value of optional type 'Range<String.Index>?' must be unwrapped to a value of type 'Range<String.Index>'
I guess this has something to do with it possibly being null, so now i want to provide it with an alternative using the ?? operator.
print(data.substring(with: (data.range(of: "[a-zA-Z]at", options: .regularExpression) ?? Range<String.Index>())))
What i want to do is to provide it with an empty range object but it seems to be impossible to create an empty object of the type required.
Any suggestions?
There is simply no argument-less initialiser for Range<String.Index>.
One way you can create an empty range of String.Index is to use:
data.startIndex..<data.startIndex
Remember that you shouldn't use integers here, because we are dealing with indices of a string. See this if you don't understand why.
So:
print(data.substring(with: (data.range(of: "[a-zA-Z]at", options: .regularExpression) ?? data.startIndex..<data.startIndex)))
But substring(with:) is deprecated. You are recommended to use the subscript:
print(data[data.range(of: "[a-zA-Z]at", options: .regularExpression) ?? data.startIndex..<data.startIndex])
Instead of trying to create an empty range, I would suggest creating an empty Substring in case there was no match. Range can be quite error-prone, so using this approach you can save yourself a lot of headaches.
let match: Substring
if let range = data.range(of: "[a-zA-Z]at", options: .regularExpression) {
match = data[range]
} else {
match = ""
}
print(match)
You can create such constructor with a simple extension to Range type. Like this:
extension Range where Bound == String.Index {
static var empty: Range<Bound> {
"".startIndex..<"".startIndex
}
init() {
self = Range<Bound>.empty
}
}
Then it can be used like this:
let str = "kukukukuku"
let substr1 = str[str.range(of: "abc", options: .regularExpression) ?? Range<String.Index>()]
let substr2 = str[str.range(of: "abc", options: .regularExpression) ?? Range<String.Index>.empty]

NSRegularExpressions - Non Capture Group not working

Hello I am having trouble using the Non-Capture group feature of regex in NSRegularExpressions
Heres some code to capture matches:
func matches(for regex: String, in text: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex);
let results = regex.matches(in: text,
range: NSRange(text.startIndex..., in: text));
return results.map {
String(text[Range($0.range, in: text)!]);
};
} catch let error {
print("invalid regex: \(error.localizedDescription)")
return [];
};
};
So now moving onto the regex, I have a string of text that is in the form workcenter:WDO-POLD should be very easy to make this work but the regex string ((?:workcenter:)(.{0,20})) does not return what I need
I get no errors on running but I get a return of the same string that I input - I am trying to retrieve the value that would be after workcenter: which is (.{0,20})
The first problem is with your regular expression. You do not want the outer capture group. Change your regular expression to:
(?:workcenter:)(.{0,20}) <-- outer capture group removed
The next problem is with how you are doing the mapping. You are accessing the full range of the match and not the desired capture group. Since you have a generalized function for handling any regular expression, it's hard to deal with all possibilities but the following change solves your immediate example and should work with regular expressions that have no capture group as well as those with one capture group.
Update your mapping line to:
return results.map {
regex.numberOfCaptureGroups == 0 ?
String(text[Range($0.range, in: text)!]) :
String(text[Range($0.range(at: 1), in: text)!])
}
This checks how many capture groups are in your regular expression. If none, it returns the full match. But if there is 1 or more, it returns just the value of the first capture group.
You can also get your original mapping to work if you change your regular expression to:
(?<=workcenter:)(.{0,20})
There's a much simpler solution here.
You have a lot of extra groups. Remove the outermost and no need for the non-capture group. Just use workcenter:(.{0,20}). Then you can reference the desired capture group with $1.
And no need for NSRegularExpression in this case. Use a simple string replacement.
let str = "workcenter:WDO-POLD"
let res = str.replacingOccurrences(of: "workcenter:(.{0,20})", with: "$1", options: .regularExpression)
This gives WDO-POLD.

Cannot find Substring "n't"

I am trying to determine whether an input string contains "n't" or "not".
For example, if the input were:
let part = "Hi, I can't be found!"
I want to find the presence of the negation.
I have tried input.contains, .range, and NSRegularExpression. All of these succeed in finding "not", but fail to find "n't". I have tried escaping the character as well.
'//REGEX:
let negationPattern = "(?:n't|[Nn]ot)"
do {
let regex = try NSRegularExpression(pattern: negationPattern)
let results = regex.matches(in: text,range: NSRange(part.startIndex..., in: part))
print("results are \(results)")
negation = (results.count > 0)
} catch let error {
print("invalid regex: \(error.localizedDescription)")
}
//.CONTAINS
if part.contains("not") || part.contains("n't"){
print("negation present in part")
negation = true
}
//.RANGE (showing .regex option; also tried without)
if part.lowercased().range(of:"not", options: .regularExpression) != nil || part.lowercased().range(of:"n't", options: .regularExpression) != nil {
print("negation present in part")
negation = true
}
Here is a picture:
This is a bit tricky, and the screenshot is actually what gives it away: your regex pattern has a plain single quote in it, but the input text has a "smart" or "curly" apostrophe in it. The difference is subtle:
Regular: '
Smart: ’
Lots of text fields will automatically replace regular single quotes with "smart" apostrophes when they think it's appropriate. Your regex, however, only matches the plain single quote, as evidenced by this tiny test:
func isNegation(input text: String) -> Bool {
let negationPattern = "(?:n't|[Nn]ot)"
let regex = try! NSRegularExpression(pattern: negationPattern)
let matches = regex.matches(in: text,range: NSRange(text.startIndex..., in: text))
return matches.count > 0
}
for input in ["not", "n't", "n’t"] {
print("\"\(input)\" is negation: \(isNegation(input: input) ? "YES" : "NO")")
}
This prints:
"not" is negation: YES
"n't" is negation: YES
"n’t" is negation: NO
If you want to continue using a regex for this problem, you'll need to modify it to match this kind of punctuation character, and avoid assuming all your input text includes "plain" single quotes.

Swift 3 replacingOccurrences of multiple strings

I have JavaScript code that looks like the following:
foo.replace(/(\r\n|\n|\r)/gm, "");
Basically this strips any return carriages and new lines from my string. I'd like to do something similar in Swift 3.
I see func replacingOccurrences(of target: String, with replacement: String, options: CompareOptions = default, range searchRange: Range<Index>? = default) -> String is available. The problem is, this only takes one string in the of parameter.
Does this mean I need to call the method multiple times for each of my instances above, once for \r\n, once for \n\ and once for \r? Is there anyway to potentially accomplish something closer to what the regex is doing instead of calling replacingOccurrences three times?
Use replacingOccurrences with the option set to regularExpression.
let updated = foo.replacingOccurrences(of: "\r\n|\n|\r", with: "", options: .regularExpression)
To replace non-digits you can use "\D" as a regular expression
let numbers = "+1(202)-505-71-17".replacingOccurrences(of: "\\D", with: "", options: .regularExpression)
You can find how to use other regular expressions here
This one is pretty easy, it's all in the documentation.
let noNewlines = "a\nb\r\nc\r".replacingOccurrences(of: "\r\n|\n|\r", with: "", options: .regularExpression)