Use Regex with NSPredicate - ios

Aim
Using NSPredicate I would like to use Regex to match all strings beginning with "Test"
I specifically want to use Regex and NSPredicate.
Questions
What mistake am I making?
What is the right way to use Regex to achieve what I am trying to do.
Code (My attempt, doesn't work)
let tests = ["Testhello", "Car", "a#b.com", "Test", "Test 123"]
let pattern = "^Test"
let predicate = NSPredicate(format: "SELF MATCHES %#", pattern)
for test in tests {
let eval = predicate.evaluate(with: test)
print("\(test) - \(eval)")
}
Output
Testhello - false
Car - false
a#b.com - false
Test - true
Test 123 - false

The regex used with NSPRedicate and MATCHES must match the whole string, so you need to use
let pattern = "Test.*"
Or - if there can be mutliple lines in the input string:
let pattern = "(?s)Test.*"
to let the .* consume the rest of the string.
If the string must end with Test, use
let pattern = "(?s).*Test"
You do not even need the ^ or $ anchors here since they are implicit here.
If the string must contain a Test substring, use
let pattern = "(?s).*Test.*"
Note that this is not efficient though due to high backtracking caused by the first .*.

You try an exact match.... try with MATCHES, try LIKE or CONTAINS instead.
let predicate = NSPredicate(format: "SELF CONTAINS %#", pattern)
See Here as you need

Related

Generics method with predicated core data search won’t return correct result

I’m trying to have a generic method that search entities for a chosen string inside an attribute to see how many such entities are there, which then will decide what number or lack of number to attach to the string as it was returned. I’ve shown it in the picture. The entity type, the search string, and the attribute are all determined by input parameters. The problem is I cannot seem to make this generic method work consistently. On some projects it works, on others it doesn’t and the predicated search either returns an empty string or incomplete result. Can anyone help me figure it out? I’ve been scratching my head since last month.
Code:
private func getDefaultNameFor<T>Centityt T, defaultString: String, attribute: String)—>String{
var count = 0
var defaultName = defaultString
let type = T.self
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: “\(type)”)
let pred = NSPredicate(format: “%# CONTAINS[cd] %#", attribute, defaultString)
fetchRequest.predicate = pred
var untitledEntities: [NSManagedObject] = []
do {
untitledEntities = try context.fetch(fetchRequest)
}catch{
print("error fetching existing default names: \(error)”
}
count = untitledEntities.count
print("pred rEturns: " untitledEntities.description)
if count !=0 {
defaultName = defaultString + String(count)
}
print("default name: " defaultName)
return defaultName
}
Keypaths are substituted with %K instead of %#. (format: “%K CONTAINS[cd] %#", attribute, defaultString) worked.
Nothing wrong with using the generic, although I think not using it might still be preferable. This was a very basic sort of error I hope I had caught earlier by reviewing the predicate formatting guide.
Here’s how I solved it:
1: as suggested, I changed it from generic to taking a simple string as entity name, but the search still returns empty. I checked again at the pred but I don’t think it was formatted wrong. Am I missing something?
2: okay so I just adjusted the pred to be a simple format: “name CONTAINS[cd] ‘untitled’” and it worked fine. So why is it not working in (format: “%# CONTAINS[cd] %#", attribute, defaultString) ? I even tried to add ‘’ around the second %# but result was still empty.
3: (format: “name CONTAINS[cd] %#", defaultString) is working fine. So I guess the problem is the first substitute not functioning as keypath in the predicate format. So how to make it function as so?
4: Solved it! Keypaths are substituted with %K instead of %#. (format: “%K CONTAINS[cd] %#", attribute, defaultString) worked. This is such a basic error. I cannot believe I’ve been bugged with this for a month almost.

How to validate NSPredicate before calling fetch

I create a search NSPredicate using a custom format string. Sometimes the syntax of this string is wrong and when I execute a fetch using a NSFetchRequest with this predicate I get a NSInvalidArgumentException, for example when the key-path is incorrect. I would much rather prefer to validate this predicate (call some method that returns YES if format is ok), than have an exception thrown that crashes my app. What can I do?
To rephrase the inquiry into a simple question:
Can I validate the predicate without potentially crashing the app?
Here is example code in Swift:
let text = "price > 0.1" // ok, no syntax error
let text = "price > 'abc'" // error
let predicate = NSPredicate.init(format: text, argumentArray: [])
let request = NSFetchRequest<Fruit>.init(entityName: "Fruit")
request.predicate = predicate
request.fetchLimit = 1
let fruit = try myContext.fetch(request) as [Fruit] // exception thrown here that crashes app
// sometimes the exception can be caught with an obj-c try/catch
// sometimes it can't be caught, and causes program to terminate
// CAN WE HAVE A SOLUTION THAT NEVER CAUSES A CRASH ?
There's no built-in way to validate that a predicate format string is a valid predicate. With format strings, you really need to validate the predicates by (a) testing them during development and (b) verifying that substitution values in predicates are at least the right data type.
If that's a problem, you might find it better to build your predicates in other ways, instead of with format strings. For example, use NSComparisonPredicate and NSCompoundPredicate to build the predicates from instances of NSExpression. The code will be much more verbose, but it will be easier to ensure that your predicate is valid by checking each part of it as you construct it.
You can make use of evaluate(with:) to validate NSPredicate's format,
Returns a Boolean value indicating whether the specified object matches the conditions specified by the predicate.
Swift 4.2:
For example,
let dataArray = [
"Amazon",
"Flipkart",
"Snapdeal",
"eBay",
"Jabong",
"Myntra",
"Bestbuy",
"Alibaba",
"Shopclue"
]
let searchString = "buy"
let predicate = NSPredicate(format: "SELF contains %#", searchString)
let searchResult = dataArray.filter { predicate.evaluate(with: $0) }
print(searchDataSource)
if !searchResult.isEmpty {
//success
} else {
//evaluation failed
}
To avoid an exception, you may want to use try-catch block

Parsing UUID in NSPredicate

I am trying to get data from the Realm database. I am using NSPredicate. And it was working well. But today I have to get data from Object who has string Id. This Id is in UUID. So when ever I try to get the value using UUID(the String ID), it gives me error like so
nable to parse the format string "Id == BD1698EE-C57D-4B8D-9D54-1D4403B2136F"'
This is the error statement. Whereas I have the following line in the code.
let resultPredicateShoppingListDetail = NSPredicate(format: "Id == \(shoppingListModel.Id)")
It does not make sense to me. Why this is happening?
Don't use string interpolation to build a predicate format, it is very
difficult to get the necessary quoting correct. As an example, this would work (note the additional
single quotes):
let uuid = "BD1698EE-C57D-4B8D-9D54-1D4403B2136F"
print(NSPredicate(format: "id == '\(uuid)'"))
// id == "BD1698EE-C57D-4B8D-9D54-1D4403B2136F"
but also fail if the uuid string contains a single quote.
Better use the %# var arg substitution for strings:
let uuid = "BD1698EE-C57D-4B8D-9D54-1D4403B2136F"
print(NSPredicate(format: "id == %#", uuid))
// id == "BD1698EE-C57D-4B8D-9D54-1D4403B2136F"
In your case (assuming that shoppingListModel.Id is a String or NSString):
let resultPredicateShoppingListDetail = NSPredicate(format: "Id == %#", shoppingListModel.Id)
Even better, use the %K keypath var arg substitution and the #keyPath
compiler directive to insert the correct key path:
let resultPredicateShoppingListDetail = NSPredicate(format: "%K == %#",
#keyPath(ShoppingList.Id), shoppingListModel.Id)
For more information about %K and %# in predicates, see
“Predicate Format String Syntax” in the “Predicate Programming Guide.”

How to avoid an error due to special characters in search string when using Regex for a simple search in Swift?

I'm using Regex to search for a word in a textView. I implemented a textField and two switch as options (Whole words and Match case). All work fine when you enter a plain word in the search filed but I get an error when I enter a special character like \ or *.
The error I get is like this one:
Error Domain=NSCocoaErrorDomain Code=2048 "The value “*” is invalid." UserInfo={NSInvalidValue=*}
Is there a way to avoid this problem and have the code handle all the text like plain text?
Because I would like to search also for special characters, I would like to prefer to not interdict to enter them. At the beginning I though to programmatically add an escape backslash to all special character before to perform a search, but maybe there are some more smart approaches?
Here is the code I'm using (based on this tutorial: NSRegularExpression Tutorial: Getting Started)
struct SearchOptions {
let searchString: String
var replacementString: String
let matchCase: Bool
let wholeWords: Bool
}
extension NSRegularExpression {
convenience init?(options: SearchOptions) {
let searchString = options.searchString
let isCaseSensitive = options.matchCase
let isWholeWords = options.wholeWords
// handle case sensitive option
var regexOption: NSRegularExpressionOptions = .CaseInsensitive
if isCaseSensitive { // if it is match case remove case sensitive option
regexOption = []
}
// put the search string in the pattern
var pattern = searchString
// if it's whole word put the string between word boundary \b
if isWholeWords {
pattern = "\\b\(searchString)\\b" // the second \ is used as escape
}
do {
try self.init(pattern: pattern, options: regexOption)
} catch {
print(error)
}
}
}
You may use NSRegularExpression.escapedPatternForString:
Returns a string by adding backslash escapes as necessary to protect any characters that would match as pattern metacharacters.
Thus, you need
var pattern = NSRegularExpression.escapedPatternForString(searchString)
Also, note that this piece:
if isWholeWords {
pattern = "\\b\(searchString)\\b"
might fail if a user inputs (text) and wishes to search for it as a whole word. The best way to match whole words is by means of lookarounds disallowing word chars on both ends of the search word:
if isWholeWords {
pattern = "(?<!\\w)" + NSRegularExpression.escapedPatternForString(searchString) + "(?!\\w)"

Using regex in NSPredicate to perform a NSFetchRequest

I try to perform an NSFetchRequest with this NSPredicate:
let searchString: NSString = "приш[её]л"
let predicate = NSPredicate(format: "text MATCHES[cd] %#", searchString)
let fetchRequest = NSFetchRequest(entityName: "Data")
fetchRequest.predicate = predicate
do {
let objects = try context!.executeFetchRequest(fetchRequest) as! [NSManagedObject]
return objects
} catch {
print("Error")
}
But no results, though actually 'text' contains "пришёл"
I've found answer in this qustion: Setting up a CoreData regular expression predicate for a comma-separated list
let searchString = "приш[её]л"
let format = "(text MATCHES %#)"
let pattern = NSString(format: ".*(\\b%#\\b).*", searchString)
let predicate = NSPredicate(format: format, pattern)
You need to be aware that using MATCHES[cd] to perform regex (regular expression) matching is very expensive if you have a lot of data in your database.
You should try to avoid regular expressions if you can. And in this particular case it seems like using normalized text would allow you to use a predicate of the form %K BEGINWITH[n] %# or %K ==[n] %# wich is very fast.
To use normalized strings, you’d have both an attribute text and a normalized version of it, e.g. textNormalized which you could update in the setter for text. You’d then use textNormalized in the [n] predicates.
You can normalize text with
extension String {
public var normalizedForSearch: String {
let transformed = applyingTransform(
StringTransform("Any-Latin; Latin-ASCII; Lower"),
reverse: false)
return transformed as String? ?? ""
}
}
Be sure to check the chapter on Text in our book: https://www.objc.io/books/core-data/ — it describes how to use normalized versions of your text to make sure searches like these are efficient.

Resources