Replace substrings with characters in Cocoa - objective-c

I'm quite new to Cocoa programming and I'm tiring my best to create a program which will have the user input text into a text field and then press a button. When the button is pressed the text is supposed to replace certain substrings to certain characters. None of the substrings are longer than 2 characters, though some are a single character long. After the replacement has been performed the newly acquired text is to be put into another textfield.
Examples of substring replacements may be that "n" is supposed to be changed to "5", "nj" is supposed to be changed to "g" and "ng" is to be changed to "s". So the text "Inject the syringe now!" would be changed to "Igect the syrise 5ow!"
How can I achieve this in a simple and elegant way? I have tried the following code but it doesn't seem to work.
- (IBAction)convert:(id)sender {
NSMutableString *x;
[x setString:[self.input stringValue]];
NSMutableString *output1;
[output1 setString:#""];
NSMutableString *middle;
middle = [[NSMutableString alloc] init];
int s;
unsigned long length = [x length];
for (s = 0; s < length; s = s + 1) {
if (s + 2 <= length) { // if more than or equal to two characters left
[middle setString:[x substringWithRange:NSMakeRange(s, 2)]];
if ([middle isEqualToString:#"nj"]) {
[output1 appendToString:#"g"];
s = s+1;
} else if ([middle isEqualToString:#"ng"]) {
[output1 appendToString:#"s"];
s = s+1;
} else { // if no two-character sequence matched
[middle setString:[x substringWithRange:NSMakeRange(s, 1)]];
if ([middle isEqualToString:#"n"]) {
[output1 appendString:#"5"];
}
}
} else { // if less than two characters left
[middle setString:[x substringWithRange:NSMakeRange(s, 1)]];
if ([middle isEqualToString:#"n"]) {
[output1 appendString:#"5"];
}
}
}
[self.output setStringValue:output1];
}
Here, *x is where the text from input goes, *output1 is where the result is stored, *middle consists of the piece of text being tested, and input and output are the NSTextFields.

I guess you could achieve what you want with a quite a few different methods. Here is a simple one:
Define a map for values/replacements
Sort them by length (largest length first)
Match and replace
Something like this perhaps:
#import <Foundation/Foundation.h>
NSString * matchAndReplace(NSString *input, NSDictionary *map){
NSMutableString *_input = [input mutableCopy];
// Get all keys sorted by greatest length
NSArray *keys = [map.allKeys sortedArrayUsingComparator: ^(NSString *key1, NSString *key2) {
return [#(key2.length) compare:#(key1.length)];
}];
for (NSString *key in keys) {
[_input replaceOccurrencesOfString:key
withString:map[key]
options:NSLiteralSearch
range:NSMakeRange(0,_input.length)];
}
return [_input copy];
};
int main(int argc, char *argv[]) {
#autoreleasepool {
NSDictionary *mapping = #{
#"n": #"5",
#"nj": #"g",
#"ng": #"s"
};
NSString *input = #"Inject the syringe now!";
NSLog(#"Output: %#", matchAndReplace(input, mapping));
}
}
Which will produce:
Output: Igect the syrise 5ow!
Note: This is an over-simplified way to achieve what you want (obviously) and maybe requires a few adjustments to cover every edge case, but it's simpler than your version and I hope that will be helpful to you.

Related

In my macOS application, I am working with UserDefaults dictionaryRepresentation. Sometimes I get strings with unknown encoding. Any suggesition?

I am working with a Objective-C Application, specifically I am gathering the dictionary representation of NSUserDefaults with this code:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *userDefaultsDict = [defaults dictionaryRepresentation];
While enumerating keys and objects of the resulting dict, sometimes I find a kind of opaque string that you can see in the following picture:
So it seems like an encoding problem.
If I try to print description of the string, the debugger correctly prints:
Printing description of obj:
tsuqsx
However, if I try to write obj to a file, or use it in any other way, I get an unreadable output like this:
What I would like to achieve is the following:
Detect in some way that the string has the encoding problem.
Convert the string to UTF8 encoding to use it in the rest of the program.
Any help is greatly appreciated. Thanks
EDIT: Very Hacky possible Solution that helps explaining what I am trying to do.
After trying all possible solutions based on dataUsingEncoding and back, I ended up with the following solution, absolutely weird, but I post it here, in the hope that it can help somebody to guess the encoding and what to do with unprintable characters:
- (BOOL)isProblematicString:(NSString *)candidateString {
BOOL returnValue = YES;
if ([candidateString length] <= 2) {
return NO;
}
const char *temp = [candidateString UTF8String];
long length = temp[0];
char *dest = malloc(length + 1);
long ctr = 1;
long usefulCounter = 0;
for (ctr = 1;ctr <= length;ctr++) {
if ((ctr - 1) % 3 == 0) {
memcpy(&dest[ctr - usefulCounter - 1],&temp[ctr],1);
} else {
if (ctr != 1 && ctr < [candidateString length]) {
if (temp[ctr] < 0x10 || temp[ctr] > 0x1F) {
returnValue = NO;
}
}
usefulCounter += 1;
}
}
memset(&dest[length],0,1);
free(dest);
return returnValue;
}
- (NSString *)utf8StringFromUnknownEncodedString:(NSString*)originalUnknownString {
const char *temp = [originalUnknownString UTF8String];
long length = temp[0];
char *dest = malloc(length + 1);
long ctr = 1;
long usefulCounter = 0;
for (ctr = 1;ctr <= length;ctr++) {
if ((ctr - 1) % 3 == 0) {
memcpy(&dest[ctr - usefulCounter - 1],&temp[ctr],1);
} else {
usefulCounter += 1;
}
}
memset(&dest[length],0,1);
NSString *returnValue = [[NSString alloc] initWithUTF8String:dest];
free(dest);
return returnValue;
}
This returns me a string that I can use to build a full UTF8 string. I am looking for a clean solution. Any help is greatly appreciated. Thanks
We're talking about a string which comes from the /Library/Preferences/.GlobalPreferences.plist
(key com.apple.preferences.timezone.new.selected_city).
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:#"com.apple.preferences.timezone.new.selected_city"];
NSLog(#"%#", city); // \^Zt\^\\^]s\^]\^\u\^V\^_q\^]\^[s\^W\^Zx\^P
(lldb) p [city description]
(__NSCFString *) $1 = 0x0000600003f6c240 #"\x1at\x1c\x1ds\x1d\x1cu\x16\x1fq\x1d\x1bs\x17\x1ax\x10"
What I would like to achieve is the following:
Detect in some way that the string has the encoding problem.
Convert the string to UTF8 encoding to use it in the rest of the program.
&
After trying all possible solutions based on dataUsingEncoding and back.
This string has no encoding problem and characters like \x1a, \x1c, ... are valid characters.
You can call dataUsingEncoding: with ASCII, UTF-8, ... but all these characters will still be
present. They're called control characters (or non-printing characters). The linked Wikipedia page explains what these characters are and how they're defined in ASCII, extended ASCII and unicode.
What you're looking for is a way how to remove control characters from a string.
Remove control characters
We can create a category for our new method:
#interface NSString (ControlCharacters)
- (NSString *)stringByRemovingControlCharacters;
#end
#implementation NSString (ControlCharacters)
- (NSString *)stringByRemovingControlCharacters {
// TODO Remove control characters
return self;
}
#end
In all examples below, the city variable is created in this way ...
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:#"com.apple.preferences.timezone.new.selected_city"];
... and contains #"\x1at\x1c\x1ds\x1d\x1cu\x16\x1fq\x1d\x1bs\x17\x1ax\x10". Also all
examples below were tested with the following code:
NSString *cityWithoutCC = [city stringByRemovingControlCharacters];
// tsuqsx
NSLog(#"%#", cityWithoutCC);
// {length = 6, bytes = 0x747375717378}
NSLog(#"%#", [cityWithoutCC dataUsingEncoding:NSUTF8StringEncoding]);
Split & join
One way is to utilize the NSCharacterSet.controlCharacterSet.
There's a stringByTrimmingCharactersInSet:
method (NSString), but it removes these characters from the beginning/end only,
which is not what you're looking for. There's a trick you can use:
- (NSString *)stringByRemovingControlCharacters {
NSArray<NSString *> *components = [self componentsSeparatedByCharactersInSet:NSCharacterSet.controlCharacterSet];
return [components componentsJoinedByString:#""];
}
It splits the string by control characters and then joins these components back. Not a very efficient way, but it works.
ICU transform
Another way is to use ICU transform (see ICU User Guide).
There's a stringByApplyingTransform:reverse:
method (NSString), but it only accepts predefined constants. Documentation says:
The constants defined by the NSStringTransform type offer a subset of the functionality provided by the underlying ICU transform functionality. To apply an ICU transform defined in the ICU User Guide that doesn't have a corresponding NSStringTransform constant, create an instance of NSMutableString and call the applyTransform:reverse:range:updatedRange: method instead.
Let's update our implementation:
- (NSString *)stringByRemovingControlCharacters {
NSMutableString *result = [self mutableCopy];
[result applyTransform:#"[[:Cc:] [:Cf:]] Remove"
reverse:NO
range:NSMakeRange(0, self.length)
updatedRange:nil];
return result;
}
[:Cc:] represents control characters, [:Cf:] represents format characters. Both represents the same character set as the already mentioned NSCharacterSet.controlCharacterSet. Documentation:
A character set containing the characters in Unicode General Category Cc and Cf.
Iterate over characters
NSCharacterSet also offers the characterIsMember: method. Here we need to iterate over characters (unichar) and check if it's a control character or not.
Let's update our implementation:
- (NSString *)stringByRemovingControlCharacters {
if (self.length == 0) {
return self;
}
NSUInteger length = self.length;
unichar characters[length];
[self getCharacters:characters];
NSUInteger resultLength = 0;
unichar result[length];
NSCharacterSet *controlCharacterSet = NSCharacterSet.controlCharacterSet;
for (NSUInteger i = 0 ; i < length ; i++) {
if ([controlCharacterSet characterIsMember:characters[i]] == NO) {
result[resultLength++] = characters[i];
}
}
return [NSString stringWithCharacters:result length:resultLength];
}
Here we filter out all characters (unichar) which belong to the controlCharacterSet.
Other ways
There're other ways how to iterate over characters - for example - Most efficient way to iterate over all the chars in an NSString.
BBEdit & others
Let's write this string to a file:
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:#"com.apple.preferences.timezone.new.selected_city"];
[city writeToFile:#"/Users/zrzka/city.txt"
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
It's up to the editor how all these controls characters are handled/displayed. Here's en example - Visual Studio Code.
View - Render Control Characters off:
View - Render Control Characters on:
BBEdit displays question marks (upside down), but I'm sure there's a way how to
toggle control characters rendering. Don't have BBEdit installed to verify it.

Matching strings, consider some characters are the same

please help me with this problem.
I want to check if the targetString match the keyword or not. Consider some character may different, but should still return true.
Example:
targetString = #"#ß<"
keyword = #"abc", #"∂B(", #"#Aß<"
result: all must return true.
(Matched.targetString and all keyword are the same.)
Consider me have an array, contains list of character set that can be the same:
NSArray *variants = [NSArray arrayWithObjects:#"aA#∂", #"bBß", #"c©C<(", nil]
So that when matching, with this rule, it can match as the example above.
Here is what i've done so far (using recursion):
- (BOOL) test:(NSString*)aString include:(NSString*) keyWord doTrim:(BOOL)doTrim {
// break recursion.
if([aString length] < [keyWord length]) return false;
// First, loop through each keyword's character
for (NSUInteger i = 0; i < [keyWord length]; i++) {
// Get #"aA#∂", #"bBß", #"c©C<(" or only the character itself.
// like, if the keyword's character is A, return the string #"aA#∂".
// If the character is not in the variants set, eg. P, return #"P"
char c = [keyWord characterAtIndex:i];
NSString *rs = [self variantsWithChar:c];
// Check if rs (#"aA#∂" or #"P") contains aString[i] character
if([rs rangeOfString:[NSString stringWithCharacters:[aString characterAtIndex:i] length:1]].location == NSNotFound) {
// If not the same char, remove first char in targetString (aString), recursion to match again.
return [self test:[aString substringFromIndex:1] include:keyWord doTrim:NO];
}
}
// If all match with keyword, return true.
return true;
}
- (NSString *) variantsWithChar:(char) c {
for (NSString *s in self.variants) {
if ([s rangeOfString:[NSString stringWithFormat:#"%c",c]].location != NSNotFound) {
return s;
}
}
return [NSString stringWithFormat:#"%c", c];
}
The main problem is, variantsWithChar: doesn't return the correct string. I don't know which datatype and which function should I use here. Please help.
For thou who know ruby, here's the example in ruby. It work super fine!
require 'test/unit/assertions'
include Test::Unit::Assertions
class String
def matching?(keyword)
length >= keyword.length && (keyword.chars.zip(chars).all? { |cs| variants(cs[0]).include?(cs[1]) } || slice(1, length - 1).matching?(keyword))
end
private
VARIANTS = ["aA#∂", "bBß", "c©C<("]
def variants(c)
VARIANTS.find { |cs| cs.include?(c) } || c
end
end
assert "abc".matching?("#ß<")
PS: The fact is, it's containt a japanese character set that sounds the same (like あア, いイ... for thou who know japanese)
PS 2: Please feel free to edit this Question, since my engrish is sooo bad. I may not tell all my thought.
PS 3: And, maybe some may comment about the performance. Like, search about 10,000 target words, with nearly 100 variants, each variant have at most 4 more same characters.
So first off, ignore comments about ASCII and stop using char. NSString and CFString use unichar
If what you really want to do is transpose hiragana and katakana you can do that with CFStringTransform()
It wraps the ICU libraries included in OS X and iOS.
It makes it very simple.
Search for that function and you will find examples of how to use it.
After a while (a day) working on the code above, I finally get it through. But don't know about the performance. Someone comment and help me improve about performance, please. Thanks.
- (BOOL) test:(NSString*)aString include:(NSString*) keyWord doTrim:(BOOL)doTrim {
// break recursion.
if([aString length] < [keyWord length]) return false;
// First, loop through each keyword's character
for (NSUInteger i = 0; i < [keyWord length]; i++) {
// Get #"aA#∂", #"bBß", #"c©C<(" or only the character itself.
// like, if the keyword's character is A, return the string #"aA#∂".
// If the character is not in the variants set, eg. P, return #"P"
NSString* c = [NSString stringWithFormat:#"%C", [keyWord characterAtIndex:i]];
NSString *rs = [self variantsWithChar:c];
NSString *theTargetChar = [NSString stringWithFormat:#"%C", [aString characterAtIndex:i]];
// Check if rs (#"aA#∂" or #"P") contains aString[i] character
if([rs rangeOfString:theTargetChar].location == NSNotFound) {
// If not the same char, remove first char in targetString (aString), recursion to match again.
return [self test:[aString substringFromIndex:1] include:keyWord doTrim:NO];
}
}
// If all match with keyword, return true.
return true;
}
If you remove all comment, it'll be pretty short...
////////////////////////////////////////
- (NSString *) variantsWithChar:(NSString *) c{
for (NSString *s in self.variants) {
if ([s rangeOfString:c].location != NSNotFound) {
return s;
}
}
return c;
}
You could try comparing ascii values of the japanese characters in the variants's each character's ascii value. These japanese characters aren't treated like usual characters or string. Hence, string functions like rangeOfString won't work on them.
to be more precise: have a look at the following code.
it will search for "∂" in the string "aA#∂"
NSString *string = #"aA#∂";
NSMutableSet *listOfAsciiValuesOfString = [self getListOfAsciiValuesForString:string]; //method definition given below
NSString *charToSearch = #"∂";
NSNumber *ascii = [NSNumber numberWithInt:[charToSearch characterAtIndex:0]];
int countBeforeAdding = [listOfAsciiValuesOfString count],countAfterAdding = 0;
[listOfAsciiValuesOfString addObject:ascii];
countAfterAdding = [listOfAsciiValuesOfString count];
if(countAfterAdding == countBeforeAdding){ //element found
NSLog(#"element exists"); //return string
}else{
NSLog(#"Doesnt exists"); //return char
}
===================================
-(NSMutableSet*)getListOfAsciiValuesForString:(NSString*)string{
NSMutableSet *set = [[NSMutableSet alloc] init];
for(int i=0;i<[string length];i++){
NSNumber *ascii = [NSNumber numberWithInt:[string characterAtIndex:i]];
[set addObject:ascii];
}
return set;
}

Scanning for doublets in an array after equating upper and lower case letters

I'm reading in a file, containing words and names, as a string. Then I'm breaking it into an array of strings. What I want to do is to print out the names that are also words. The words are spelled with only lower case letters and the names has a capital first letter. Thus, I want to order upper and lower cases the same so that Ii then can scan the array and receive the duplicates.
So what I have in my main.m file looks like this now:
int main(int argc, const char * argv[])
{
#autoreleasepool {
// insert code here...
NSString *wordString = [NSString stringWithContentsOfFile:#"/usr/share/dict/words"
encoding:NSUTF8StringEncoding
error:NULL];
NSArray *words = [wordString componentsSeparatedByString:#"\n"];
Everywhere it says I should use a caseIntensiveCompare method, but I don't understand how it works, or how to use it in this particularly case.. When I search for it on google all I get is this:
NSString *aString = #"ABC";
NSString *bString = #"abc";
if ([aString caseInsesitiveCompare: bString]) == NSOrderedSame)
{
//The strings are ordered equal
}
It seems wrong, firstly because I only have the one string, and secondly I want it to actually order them the letters the same, not to check if they are ordered the same..
If someone could give me a hint of how to do this I would be VERY thankful!
Thanks in advance // Bjoern
Not sure whether i have understand your question properly. But how much i understood is you need to first store array string in mutable set then on the basis of that you can compare the existing one to the new one as represent below code. So that like that you can filter your array as well as identify duplicate both words and names. Below assuming words is you array which contains string values. So on the basis of that processing the further code.
NSMutableSet* existing = [NSMutableSet set];
NSMutableArray* newArray = [NSMutableArray
array];
for (id object in words) {
if (![existing containsObject:[[object
name]lowercaseString]) {
[existing addObject:[[object
name]lowercaseString];
[newArray addObject:object];
}
else
{
NSLog(#"duplicate name=%#", [object name]);
}
}
You could try something like this (explanation in the comments):
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
#autoreleasepool {
NSString *wordString = [NSString stringWithContentsOfFile:#"/usr/share/dict/words"
encoding:NSUTF8StringEncoding
error:NULL];
// Get all the words by separating on newlines & convert to lowercase
// Note: Assuming that the list doesn't contain duplicate strings
// (i.e. the same word or name twice)
// If it does, you should separate/add_to_set/get_all_objects/lowercase instead
NSArray *words = [[wordString componentsSeparatedByString:#"\n"]
valueForKey:#"lowercaseString"];
// Create a counted set to keep track of duplicate strings
NSCountedSet *bag = [[NSCountedSet alloc] initWithArray:words];
// Create a mutable set to add only duplicates
NSMutableSet *duplicates = [NSMutableSet setWithCapacity:0];
// Iterate and add words that appear more than once in the counted set
for (NSString *word in bag) {
if ([bag countForObject:word] > 1) {
[duplicates addObject:word];
}
}
NSLog(#"Words: %lu | Unique words: %lu | Duplicates: %lu", words.count, bag.count, duplicates.count);
// Output => Words: 235887 | Unique words: 234372 | Duplicates: 1515
}
}
Now duplicates is a Set of the strings that are both words & names (as per your requirement i.e. they only differ in the capitalization). You can get an array of the words by sending [duplicates allObjects].

How to convert text to camel case in Objective-C?

I'm making little utility to help me generate code for an app I'm making. I like to have constants for my NSUserDefaults settings, so that my code is more readable and easier to maintain. The problem is, that making constants for everything takes some time, so I'm trying to write a utility to generate code for me. I'd like to be able to enter a string and have it converted to camel case, like so:
- (NSString *)camelCaseFromString:(NSString *)input{
return inputAsCamelCase;
}
Now, the input string might be composed of multiple words. I'm assuming that I need some sort of regular expression here, or perhaps there is another way to do it. I'd like to input something like this:
#"scrolling direction"
or this:
#"speed of scrolling"
and get back something like this:
kScrollingDirection
or this:
kSpeedOfScrolling
How would you go about removing spaces and replacing the character following the space with the uppercase version?
- (NSString *)camelCaseFromString:(NSString *)input {
return [#"k" stringByAppendingString:[[input capitalizedString] stringByReplacingOccurrencesOfString:#" " withString:#""]];
}
Capitalize each word.
Remove whitespace.
Insert "k" at the beginning. (Not literally, but a simplification using stringByAppendingString.)
The currently accepted answer has a bug. (also pointed out by #jaydee3)
Words already having proper camelCasing, PascalCasing, or capitalized TLA acronyms will have their correct casing "destroyed" by having non-first characters lowercased via the call to capitalizedString.
So "I prefer camelCasing to PascalCasing for variables" would look like "iPreferCamelcasingToPascalcasingForVariables" but according to the question, should be "iPreferCamelCasingToPascalCasingForVariables"
Use the below category on NSString to create non-destructive camel and pascal casing. It also properly leaves ALL_CAPS words/acronyms in place, although that wasn't really part of the orig question.
An acronym as a first word for a camelCased string would be weird. "WTH" becomes "wTH" which looks weird. Anyway, that's an edge case, and not addressed. However, since question asker is prefixing with "k" then it "k IBM" becomes "kIBM" which looks ok to me, but would look be "kIbm" with currently accepted answer.
Use:
NSString *str = #"K computer manufacturer IBM";
NSLog(#"constant: %#", str.pascalCased);
// "constant: kComputerManufacturerIBM"
Category (class extension) code.
#implementation NSString (MixedCasing)
- (NSString *)camelCased {
NSMutableString *result = [NSMutableString new];
NSArray *words = [self componentsSeparatedByString: #" "];
for (uint i = 0; i < words.count; i++) {
if (i==0) {
[result appendString:((NSString *) words[i]).withLowercasedFirstChar];
}
else {
[result appendString:((NSString *)words[i]).withUppercasedFirstChar];
}
}
return result;
}
- (NSString *)pascalCased {
NSMutableString *result = [NSMutableString new];
NSArray *words = [self componentsSeparatedByString: #" "];
for (NSString *word in words) {
[result appendString:word.withUppercasedFirstChar];
}
return result;
}
- (NSString *)withUppercasedFirstChar {
if (self.length <= 1) {
return self.uppercaseString;
} else {
return [NSString stringWithFormat:#"%#%#",[[self substringToIndex:1] uppercaseString],[self substringFromIndex:1]];
}
}
- (NSString *)withLowercasedFirstChar {
if (self.length <= 1) {
return self.lowercaseString;
} else {
return [NSString stringWithFormat:#"%#%#",[[self substringToIndex:1] lowercaseString],[self substringFromIndex:1]];
}
}
#end
Just use: #"This is a sentence".capitalizedString;
Becomes: >> "This Is A Sentence"
Replace spaces and manipulate...
Uppercases first char of word, and lowers the other letters for each word.

Character replacing in a string

suppose I have string like "this str1ng for test" now i want to check if character at position [i-1] and [i+1] are both alphabet but character at [i] is number, like this example in word "str1ng" then character at position [i] replaced by appropriate alphabet.
or vice versa.
I need this for post processing for output of OCR. TQ
You might have an easier time using Regular Expressions.
NSString are immutable, so you'll have to create a new NSMutableString from it, and mutate this copy, or to allocate a unichar* buffer, copy data from the NSString, perform the correction, and then recreate a new NSString from the result. Once you're working on a mutable copy of the string, you can use whatever algorithm you want.
So you'll need to have a function like that:
- (NSString*)correctOCRErrors:(NSString*)string
{
BOOL hasError = NO;
for (int i = 0; i < [string length]; ++ i)
{
if (isIncorrect([string characterAtIndex:i]))
{
hasError = YES;
break;
}
}
if (hasError)
{
unichar* buffer = (unichar*)malloc([string length]);
for (int i = 0; i < [string length]; ++ i)
{
unichar chr = [string characterAtIndex:i];
if (isIncorrect(chr))
chr = correctChar(chr);
buffer[i] = chr;
}
string = [[[NSString alloc] initWithCharactersNoCopy:buffer length:[string length] freeWhenDone:YES] autorelease];
}
return string;
}
You can access character in a NSString by passing a message charAtIndex:(NSUInteger)index.
And now you can get the ascii value at the particular index you are interested in and change it according to your requirement.
NSString Ref
Hope this is helpful !