Parsing strings: String before character - ios

Input:
chris#mydomain.com
steve#mydomain.com
Output:
chris
steve
I'm looking to get the substring before the # character. In Python, I would use something like: myString = myString[:myString.find("#")] but I think Swift's version is very complex, at least from what I've been reading. Would it be better to bridge to Obj-C in this case, since Swift's indexOf/find function looks something like this mess: Finding index of character in Swift String ?
Something like this, even though it's using arrays, seems to be the simpliest route:
contact = contact.componentsSeparatedByString("#")[0]

In Swift, Python myString[:myString.find("#")] is equivalent to:
myString[myString.startIndex ..< find(myString, "#")!]
But this causes a runtime error unless myString contains "#".
This is safer.
if let found = find(myString, "#") {
myString = myString[myString.startIndex ..< found]
}
Note that, find() searches Character only, not substring. so you cannot:
find(myString, "#mydomain")

This may help you
let arr = split(contact, { $0 == "#"}, maxSplit: Int.max, allowEmptySlices: false)
println(arr[0])

I believe you have hit the nail on the head. Another option is to use regular expressions to find it. I'm still learning swift, but the objective-c version of things is as follows:
-(NSString*) parseEmailPrefix(NSString*)email{
NSError *error = nil;
NSString *pattern = #"([A-Z0-9a-z._%+-]+)#[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:pattern
options:0
error:&error];
NSTextCheckingResult *result = [expression firstMatchInString:email
options:0
range:NSMakeRange(0, email.length)];
return [string substringWithRange:[result rangeAtIndex:1]];
}
This is in my opinion harder to read, but it has the added benefit of validating the email as you go.
Regex credit: Regex for email address

And then there is this Obj-C snippet, which addresses more specifically the data given in the question. (Forgive that I have not translated to Swift, still working on that skill.)
NSString* email = #"chuck#norris.net";
NSURL* theURL = [NSURL URLWithString:[NSString stringWithFormat:#"mailto://%#", email]];
NSLog(#"%# at %#", theURL.user, theURL.host); // yields "chuck at norris.net"

This works:
var str: String = "Hello#World"
let a = str.componentsSeparatedByString("#")
println("\(a[0])")
Output is "Hello"

Related

iOS - NSString regex match

I have a string for example:
NSString *str = #"Strängnäs"
Then I use a method for replace scandinavian letters with *, so it would be:
NSString *strReplaced = #"Str*ngn*s"
I need a function to match str with strReplaced. In other words, the * should be treated as any character ( * should match with any character).
How can I achieve this?
Strängnäs should be equal to Str*ngn*s
EDIT:
Maybe I wasn't clear enough. I want * to be treated as any character. So when doing [#"Strängnäs" isEqualToString:#"Str*ngn*s"] it should return YES
I think the following regex pattern will match all non-ASCII text considering that Scandinavian letters are not ASCII:
[^ -~]
Treat each line separately to avoid matching the newline character and replace the matches with *.
Demo: https://regex101.com/r/dI6zN5/1
Edit:
Here's an optimized pattern based on the above one:
[^\000-~]
Demo: https://regex101.com/r/lO0bE9/1
Edit 1: As per your comment, you need a UDF (User defined function) that:
takes in the Scandinavian string
converts all of its Scandinavian letters to *
takes in the string with the asterisks
compares the two strings
return True if the two strings match, else false.
You can then use the UDF like CompareString(ScanStr,AsteriskStr).
I have created a code example using the regex posted by JLILI Amen
Code
NSString *string = #"Strängnäs";
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"[^ -~]" options:NSRegularExpressionCaseInsensitive error:&error];
NSString *modifiedString = [regex stringByReplacingMatchesInString:string options:0 range:NSMakeRange(0, [string length]) withTemplate:#"*"];
NSLog(#"%#", modifiedString);
Output
Str*ngn*s
Not sure exactly what you are after, but maybe this will help.
The regular expression pattern which matches anything is. (dot), so you can create a pattern from your strReplaced by replacing the *'s with .'s:
NSString *pattern = [strReplaced stringByReplacingOccurencesOfString:#"*" withString:"."];
Now using NSRegularExpression you can construct a regular expression from pattern and then see if str matches it - see the documentation for the required methods.

How can i trim a blank(empty) line in NSString?

I have textView where user can add text in new line. but when user enter multiple new line and not enter a any text then i want skip all that line and just use only one new line.
I have String like below.
Hello,
How r u?
I want a string like this
Hello
How r u?
I have tried this but not working
strContects=[strContects stringByReplacingOccurrencesOfString:#"\n\n" withString:#"\n"];
How can i do this?
Hope u will understand?
You can replace multiple occurrence of omit multiple newline characters with single one by following regular expressions code
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"\n+" options:0 error:NULL];
NSString *newString = [regex stringByReplacingMatchesInString:myString options:0 range:NSMakeRange(0, [myString length]) withTemplate:#"\n"];
this will print
Hello,
How r u? //in new line(all \n omitted with single \n)
If the goal here is removing all blank lines - not just consolidating multiple newlines - then it is worth noting the accepted answer wont remove an initial blank line in the string; eg "\nHello..."
A bit more involved, but try this category:
- (NSString*)stringByRemovingBlankLines
{
NSScanner *scan = [NSScanner scannerWithString:self];
NSMutableString *string = NSMutableString.new;
while (!scan.isAtEnd) {
[scan scanCharactersFromSet:NSCharacterSet.newlineCharacterSet intoString:NULL];
NSString *line = nil;
[scan scanUpToCharactersFromSet:NSCharacterSet.newlineCharacterSet intoString:&line];
if (line) [string appendFormat:#"%#\n",line];
}
if (string.length) [string deleteCharactersInRange:(NSRange){string.length-1,1}]; // drop last '\n'
return string;
}
(BTW - this can also handle other types of 'newline' characters which the accepted answer does not. This wasn't asked for, but it came up in the comments)

String search with Turkish dotless i

When searching the text Çınaraltı Café for the text Ci using the code
NSStringCompareOptions options =
NSCaseInsensitiveSearch |
NSDiacriticInsensitiveSearch |
NSWidthInsensitiveSearch;
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:#"tr"];
NSRange range = [haystack rangeOfString:needle
options:options
range:NSMakeRange(o, haystack.length)
locale:locale];
I get range.location equals NSNotFound.
It's not to do with the diacritic on the initial Ç because I get the same result searching for alti where the only odd character is the ı. I also get a valid match searching for Cafe which contains a diacritic (the é).
The apple docs mention this situation as notes on the locale parameter and I think I'm following them. Though I guess I'm not because it's not working.
How can I get a search for 'i' to match both 'i' and 'ı'?
I don't know whether this helps as an answer, but perhaps explains why it's happening.
I should point out I'm not an expert in this matter, but I've been looking into this for my own purposes and been doing some research.
Looking at the Unicode collation chart for latin, the equivalent characters to ASCII "i" (\u0069) do not include "ı" (\u0131), whereas all the other letters in your example string are as you expect, i.e.:
"c" (\u0063) does include "Ç" (\u00c7)
"e" (\u0065) does include "é" (\u00e9)
The ı character is listed separately as being of primary difference to i. That might not make sense to a Turkish speaker (I'm not one) but it's what Unicode have to say about it, and it does fit the logic of the problem you describe.
In Chrome you can see this in action with an in-page search. Searching in the page for ASCII i highlights all the characters in its block and does not match ı. Searching for ı does the opposite.
By contrast, MySQL's utf8_general_ci collation table maps uppercase ASCII I to ı as you want.
So, without knowing anything about iOS, I'm assuming it's using the Unicode standard and normalising all characters to latin by this table.
As to how you match Çınaraltı with Ci - if you can't override the collation table then perhaps you can just replace i in your search strings with a regular expression, so you search on Ç[iı] instead.
I wrote a simple extension in Swift 3 for Turkish string search.
let turkishSentence = "Türkçe ya da Türk dili, batıda Balkanlar’dan başlayıp doğuda Hazar Denizi sahasına kadar konuşulan Altay dillerinden biridir."
let turkishWannabe = "basLayip"
let shouldBeTrue = turkishSentence.contains(turkishString: turkishWannabe, caseSensitive: false)
let shouldBeFalse = turkishSentence.contains(turkishString: turkishWannabe, caseSensitive: true)
You can check it out from https://github.com/alpkeser/swift_turkish_string_search/blob/master/TurkishTextSearch.playground/Contents.swift
I did this and seems to work well for me.. hope it helps!
NSString *cleanedHaystack = [haystack stringByReplacingOccurrencesOfString:#"ı"
withString:#"i"];
cleanedHaystack = [cleanedHaystack stringByReplacingOccurrencesOfString:#"İ"
withString:#"I"];
NSString *cleanedNeedle = [needle stringByReplacingOccurrencesOfString:#"ı"
withString:#"i"];
cleanedNeedle = [cleanedNeedle stringByReplacingOccurrencesOfString:#"İ"
withString:#"I"];
NSUInteger options = (NSDiacriticInsensitiveSearch |
NSCaseInsensitiveSearch |
NSWidthInsensitiveSearch);
NSRange range = [cleanedHaystack rangeOfString:cleanedNeedle
options:options];
As Tim mentions, we can use regular expression to match text containing i or ı. I also didn't want to add a new field or change the source data as the search looks up huge amounts of string. So I ended up a solution using regular expressions and NSPredicate.
Create NSString category and copy this method. It returns basic or matching pattern. You can use it with any method that accepts regular expression pattern.
- (NSString *)zst_regexForTurkishLettersWithCaseSensitive:(BOOL)caseSensitive
{
NSMutableString *filterWordRegex = [NSMutableString string];
for (NSUInteger i = 0; i < self.length; i++) {
NSString *letter = [self substringWithRange:NSMakeRange(i, 1)];
if (caseSensitive) {
if ([letter isEqualToString:#"ı"] || [letter isEqualToString:#"i"]) {
letter = #"[ıi]";
} else if ([letter isEqualToString:#"I"] || [letter isEqualToString:#"İ"]) {
letter = #"[Iİ]";
}
} else {
if ([letter isEqualToString:#"ı"] || [letter isEqualToString:#"i"] ||
[letter isEqualToString:#"I"] || [letter isEqualToString:#"İ"]) {
letter = #"[ıiIİ]";
}
}
[filterWordRegex appendString:letter];
}
return filterWordRegex;
}
So if the search word is Şırnak, it creates Ş[ıi]rnak for case sensitive and Ş[ıiIİ]rnak for case insensitive search.
And here are the possible usages.
NSString *testString = #"Şırnak";
// First create your search regular expression.
NSString *searchWord = #"şır";
NSString *searchPattern = [searchWord zst_regexForTurkishLettersWithCaseSensitive:NO];
// Then create your matching pattern.
NSString *pattern = searchPattern; // Direct match
// NSString *pattern = [NSString stringWithFormat:#".*%#.*", searchPattern]; // Contains
// NSString *pattern = [NSString stringWithFormat:#"\\b%#.*", searchPattern]; // Begins with
// NSPredicate
// c for case insensitive, d for diacritic insensitive
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"self matches[cd] %#", pattern];
if ([predicate evaluateWithObject:testString]) {
// Matches
}
// If you want to filter an array of objects
NSArray *matchedCities = [allAirports filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:#"city matches[cd] %#", pattern]];
You can also use NSRegularExpression but I think using case and diacritic insensitive search with NSPredicate is much more simpler.

What characters are allowed in a iOS file name?

I'm looking for a way to make sure a string can be used as a file name under iOS. I'm currently in the section of the code that deletes incompatible characters. I'm wondering if I'm doing it right.
NSString *filename = #"A file name";
fileName = [fileName stringByTrimmingCharactersInSet: [NSCharacterSet controlCharacterSet]];
fileName = [fileName stringByTrimmingCharactersInSet: [NSCharacterSet newlineCharacterSet]];
I'm also wondering if there's already a method that validates a string as a file name.
Thank you for your advice!
Use RegEx:
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"[^a-zA-Z0-9_]+" options:0 error:nil];
filename = [regex stringByReplacingMatchesInString:filename options:0 range:NSMakeRange(0, filename.length) withTemplate:#"-"];
I find this to be cleaner and probably much more performant. This is based on Angel Naydenov's solution, but first constructing Character set with all invalid characters and then calling components(separatedBy:) just once.
Swift 3 & 4
var invalidCharacters = CharacterSet(charactersIn: ":/")
invalidCharacters.formUnion(.newlines)
invalidCharacters.formUnion(.illegalCharacters)
invalidCharacters.formUnion(.controlCharacters)
let newFilename = originalFilename
.components(separatedBy: invalidCharacters)
.joined(separator: "")
Swift 2
let invalidCharacters = NSMutableCharacterSet(charactersInString: ":/")
invalidCharacters.formUnionWithCharacterSet(NSCharacterSet.newlineCharacterSet())
invalidCharacters.formUnionWithCharacterSet(NSCharacterSet.illegalCharacterSet())
invalidCharacters.formUnionWithCharacterSet(NSCharacterSet.controlCharacterSet())
let filename = originalFilename
.componentsSeparatedByCharactersInSet(invalidCharacters)
.joinWithSeparator("")
First of all, you're using the wrong method. Trimming the string will only remove characters in the beginning and the end of the string.
What you're looking for is something more like:
fileName = [fileName stringByReplacingOccurrencesOfString:#"/" withString:#"_"];
However, that's a suboptimal solution, since you'll have to do that for every character you want to exclude, so maybe you want to keep looking or write you're own method for manipulating the string.
iOS is UNIX based and as such I suppose it supports almost any characters in filenames. UNIX allows white spaces, <, >, |, \, :, (, ), &, ;, as well as wildcards such as ? and *, to be quoted or escaped using \ symbol. However I wouldn't use any of those characters in my filenames. In fact, I would restrict the characters in my filenames to 'a'-'z', '0'-'9', '_' and '.'.
As I did not see a list with allowed characters in this question but the question wanted a list with such characters I am adding a bit more details on this topic.
First we need to know what is the file system that iOS devices use. Using multiple online sources this seems to be HFSX which is the HFS+ case sensitive version. And including one link here for reference: https://apple.stackexchange.com/questions/83671/what-filesystem-does-ios-use
Now that we know what the file system is we can look for what characters are not allowed. And these seem to be: colon (:) and slash (/). Here is a link for reference: http://www.comentum.com/File-Systems-HFS-FAT-UFS.html
Having this information and what others have written in this thread my personal preference for removing not allowed characters from file names is the following Swift code:
filename = "-".join(filename.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()))
filename = "-".join(filename.componentsSeparatedByCharactersInSet(NSCharacterSet.illegalCharacterSet()))
filename = "-".join(filename.componentsSeparatedByCharactersInSet(NSCharacterSet.controlCharacterSet()))
filename = "-".join(filename.componentsSeparatedByString(":"))
filename = "-".join(filename.componentsSeparatedByString("/"))
The reason I am not preferring the RegEx approach is that it seems too restrictive to me. I do not want to restrict my users only to Latin characters. They may as well wish to use some Chinese, Cyrillic or whatever else they like.
Happy coding!
I've had to save remote files locally with filenames containing other characters than basic alpha-numeric characters. I use the method below to strip out potential invalid characters, ensuring it's a valid filename for the filesystem when generating a NSURL using URLWithString:
filename = [[filename componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsJoinedByString:#"" ];
filename = [[filename componentsSeparatedByCharactersInSet:[NSCharacterSet illegalCharacterSet]] componentsJoinedByString:#"" ];
filename = [[filename componentsSeparatedByCharactersInSet:[NSCharacterSet symbolCharacterSet]] componentsJoinedByString:#"" ];
fileURLString = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
fileURL = [NSURL URLWithString:fileURLString];
You may also want to test for collision errors first using:
[[NSFileManager defaultManager] fileExistsAtPath:[fileURL absoluteString]]
This String extension (Swift 4.2) will help convert an invalid iOS file name to a valid iOS file name.
extension String {
func convertToValidFileName() -> String {
let invalidFileNameCharactersRegex = "[^a-zA-Z0-9_]+"
let fullRange = startIndex..<endIndex
let validName = replacingOccurrences(of: invalidFileNameCharactersRegex,
with: "-",
options: .regularExpression,
range: fullRange)
return validName
}
}
For example
"name.name?/!!23$$#1asd".convertToValudFileName() // "name-name-23-1asd"
"!Hello.312,^%-0//\r\r".convertToValidFileName() // "-Hello-312-0-"
"/foo/bar/pop?soda=yes|please".convertToValidFileName() // "-foo-bar-pop-soda-yes-please"
I'm pretty happy with this solution:
NSString *testString = #"This*is::/legal.😀,?縦書き 123";
NSString *result = [[[testString componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"length > 0"]] componentsJoinedByString:#"-"];
Output:
"This-is-legal-縦書き-123"
What is this sorcery?
Let me break it up into multiple lines so it's clear what's going on:
NSString *testString = #"This*is::/legal.😀,?縦書き 123";
// Get a character set for everything that's NOT alphanumeric.
NSCharacterSet *nonAlphanumericCharacterSet = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
// Split the string on each non-alphanumeric character, thus removing them.
NSArray *cleanedUpComponentsWithBlanks = [testString componentsSeparatedByCharactersInSet:nonAlphanumericCharacterSet];
// Filter out empty strings ("length" is a KVO-compliant property that the predicate can call on each NSString in the array).
NSArray *cleanedUpComponentsWithoutBlanks = [cleanedUpComponentsWithBlanks filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"length > 0"]];
// Put the components back together and join them with a "-".
NSString *result = [cleanedUpComponentsWithoutBlanks componentsJoinedByString:#"-"];
Enjoy!
Swift 4 Version
Added by john-pang on 2021-09-01 with Swift version:
let testString = "This*is::/legal.😀,?縦書き 123"
// Get a character set for everything that's NOT alphanumeric.
let nonAlphanumericCharacterSet = CharacterSet.alphanumerics.inverted
// Split the string on each non-alphanumeric character, thus removing them.
let cleanedUpComponentsWithBlanks = testString.components(separatedBy: nonAlphanumericCharacterSet)
// Filter out empty strings ("length" is a KVO-compliant property that the predicate can call on each NSString in the array).
let cleanedUpComponentsWithoutBlanks = cleanedUpComponentsWithBlanks.filter { $0.length > 0 }
// Put the components back together and join them with a "-".
let result = cleanedUpComponentsWithoutBlanks.joined(separator: "_")
I came up with the following solution. Works nice so far.
import Foundation
extension String {
func removeUnsupportedCharactersForFileName() -> String {
var cleanString = self
["?", "/", "\\", "*"].forEach {
cleanString = cleanString.replacingOccurrences(of: $0, with: "-")
}
return cleanString
}
}
let a = "***???foo.png"
let validString = a.removeUnsupportedCharactersForFileName()
Base on Marian Answers, here is a string extension to remove any unwanted characters.
extension String {
func stripCharacters() -> String {
var invalidCharacters = CharacterSet(charactersIn: ":/")
invalidCharacters.formUnion(.newlines)
invalidCharacters.formUnion(.illegalCharacters)
invalidCharacters.formUnion(.controlCharacters)
let newString = self
.components(separatedBy: invalidCharacters)
.joined(separator: "_")
return newString
}
}
Example:
let fileName = "Man(lop23/45"
let newFileName = fileName.stripCharacters()
print(newFileName)
Swift 5 extension:
I wanted to remove emojis as well and in windows \ is also an invalid character. So I added symbols charset and backslash \ as well.
extension String {
var validFilename: String {
let invalidCharsets = CharacterSet(charactersIn: ":/\\")
.union(.illegalCharacters)
.union(.controlCharacters)
.union(.symbols)
.union(.newlines)
return self.components(separatedBy: invalidCharsets).joined()
}
}

How to validate a phone number with + symbol in objective c?

I am so confused about the regex methods. My requirement is to validate a phone number that may contains + symbol in its prefix. Then all the charactors should be numerals only. For this, how can i create a regular expression in objective c.
I'm late answering, but I found an interesting solution when I recently have had the same problem. It uses the built-in cocoa methods instead of custom regex.
- (BOOL)validatePhoneNumberWithString:(NSString *)string {
if (nil == string || ([string length] < 2 ) )
return NO;
NSError *error;
NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypePhoneNumber error:&error];
NSArray *matches = [detector matchesInString:string options:0 range:NSMakeRange(0, [string length])];
for (NSTextCheckingResult *match in matches) {
if ([match resultType] == NSTextCheckingTypePhoneNumber) {
NSString *phoneNumber = [match phoneNumber];
if ([string isEqualToString:phoneNumber]) {
return YES;
}
}
}
return NO;
}
I wouldn't say this is a definitive answer but it should give you a start.
^\x2b[0-9]+
Will match any string that starts with a '+' and then any amount of numbers greater than 0.
For instance:
+441312002000 - Full phone number matched.
+4413120c2000 - +4413120 is matched.
++441312002000 - No match
441312002000 - No Match
If there are further constraints on length etc then specifiy and I can update the regex. I agree with other poster about using RegexKitLite.
Use RegexKitLite, check the following http://regexkit.sourceforge.net/RegexKitLite/
^\+?[0-9]*$
should do:
^ # start of string
\+? # match zero or one + characters
[0-9]* # match any number of digits
$ # end of string
To use the regex in a string, you'll need to double the backslashes: #"^\\+?[0-9]*$" should work according to other regex examples I've seen, but I don't know Objective-C and may be wrong about this.
This post nicely explains the regex -- http://blog.stevenlevithan.com/archives/validate-phone-number. You have to use "\" instead of "\" to prevent the Objective C preprocessor from interpreting regex escape codes as character string escape codes.
Here is the NSString you would use for the requested match
NSString *northAmRegexWithOptionalLeadingOne = #"^(?:\\+?1[-. ]?)?\\(?([2-9][0-8][0-9])\\)?[-. ]?([2-9][0-9]{2})[-. ]?([0-9]{4})$";
+*[0-9]{length of phone}. Should work.

Resources