Using a switch statement to construct a Regular Expression in Swift 3 - ios

I've created a function that generates a RegularExpression in Swift 3.0. I'm close to what I want, but the backslash is causing me a lot of trouble.
I've looked at Swift Documentation and I thought changing the "\" to \u{005C} or u{005C} would resolve the issue, but it doesn't.
Here's the array I'm feeding my regex generation function:
var letterArray = ["a","","a","","","","","","",""]
Here's the relevant portion of my method:
var outputString = String()
// getMinimumWordLength returns 3
let minimumWordLength = getMinimumWordLength(letterArray: letterArray)
// for the array above, maximumWordLength returns 10
let maximumWordLength = letterArray.count
var index = 0
for letter in letterArray {
if index < minimumWordLength {
if letter as! String != "" {
outputString = outputString + letter.lowercased
} else {
// this puts an extra \ in my regex
outputString = outputString + "\\w" // first \ is an escape character, 2nd one gets read
// this puts an extra backslash in, too
// outputString = outputString + "\u{005C}w"
}
}
index += 1
}
outputString = outputString + ("{\(minimumWordLength),\(maximumWordLength)}$/")
return outputString
My desired output is:
a\wa{3,10}$/
My actual output is:
a\\wa{3,10}$/
If anyone has suggestions what I'm fouling up, I welcome them. Thank you for reading.

When string is printed in debugger, escape character will be displayed. When it is displayed for user, it will not.

Related

Replace part of string with lower case letters - Swift

I have a Swift based iOS app and one of the features allows you to comment on a post. Anyway, users can add "#mentions" in their posts to tag other people. However I want to stop the user from adding a username with a capital letter.
Is there anyway I can convert a string, so that the #usernames are all in lowercase?
For example:
I really enjoy sightseeing with #uSerABC (not allowed)
I really enjoy sightseeing with #userabc (allowed)
I know there is a property for the string in swift called .lowercaseString - but the problem with that, is that it makes the entire string lowercase and thats not what I want. I only want the #username to be in lower case.
Is there any way around this with having to use the .lowercase property.
Thanks for your time, Dan.
This comes from a code I use to detect hashtags, I've modified to detect mentions:
func detectMentionsInText(text: String) -> [NSRange]? {
let mentionsDetector = try? NSRegularExpression(pattern: "#(\\w+)", options: NSRegularExpressionOptions.CaseInsensitive)
let results = mentionsDetector?.matchesInString(text, options: NSMatchingOptions.WithoutAnchoringBounds, range: NSMakeRange(0, text.utf16.count)).map { $0 }
return results?.map{$0.rangeAtIndex(0)}
}
It detects all the mentions in a string by using a regex and returns an NSRange array, by using a range you have the beginning and the end of the "mention" and you can easily replace them with a lower case version.
Split the string into two using the following command -
let arr = myString.componentsSeparatedByString("#")
//Convert arr[1] to lower case
//Append to arr[0]
//Enjoy
Thanks to everyone for their help. In the end I couldn't get any of the solutions to work and after a lot of testing, I came up with this solution:
func correctStringWithUsernames(inputString: String, completion: (correctString: String) -> Void) {
// Create the final string and get all
// the seperate strings from the data.
var finalString: String!
var commentSegments: NSArray!
commentSegments = inputString.componentsSeparatedByString(" ")
if (commentSegments.count > 0) {
for (var loop = 0; loop < commentSegments.count; loop++) {
// Check the username to ensure that there
// are no capital letters in the string.
let currentString = commentSegments[loop] as! String
let capitalLetterRegEx = ".*[A-Z]+.*"
let textData = NSPredicate(format:"SELF MATCHES %#", capitalLetterRegEx)
let capitalResult = textData.evaluateWithObject(currentString)
// Check if the current loop string
// is a #user mention string or not.
if (currentString.containsString("#")) {
// If we are in the first loop then set the
// string otherwise concatenate the string.
if (loop == 0) {
if (capitalResult == true) {
// The username contains capital letters
// so change it to a lower case version.
finalString = currentString.lowercaseString
}
else {
// The username does not contain capital letters.
finalString = currentString
}
}
else {
if (capitalResult == true) {
// The username contains capital letters
// so change it to a lower case version.
finalString = "\(finalString) \(currentString.lowercaseString)"
}
else {
// The username does not contain capital letters.
finalString = "\(finalString) \(currentString)"
}
}
}
else {
// The current string is NOT a #user mention
// so simply set or concatenate the finalString.
if (loop == 0) {
finalString = currentString
}
else {
finalString = "\(finalString) \(currentString)"
}
}
}
}
else {
// No issues pass back the string.
finalString = inputString
}
// Pass back the correct username string.
completion(correctString: finalString)
}
Its certainly not the most elegant or efficient solution around but it does work. If there are any ways of improving it, please leave a comment.

Escaping Strings in Swift

I am going to create a CSV file programmatically and would like to properly escape my strings before writing them.
I assume I'll need to escape commas and probably need to surround each value in single or double quotes (and thus will need to escape those too). Plus any carriage return / new line constants.
I was going to write it all myself but then found this in Objective-C and said why not just convert it, as it looks quite thorough:
-(NSString *)escapeString:(NSString *)s
{
NSString * escapedString = s;
BOOL containsSeperator = !NSEqualRanges([s rangeOfString:#","], NSMakeRange(NSNotFound, 0));
BOOL containsQuotes = !NSEqualRanges([s rangeOfString:#"\""], NSMakeRange(NSNotFound, 0));
BOOL containsLineBreak = !NSEqualRanges([s rangeOfString:#"\n"], NSMakeRange(NSNotFound, 0));
if (containsQuotes) {
escapedString = [escapedString stringByReplacingOccurrencesOfString:#"\"" withString:#"\"\""];
}
if (containsSeperator || containsLineBreak) {
escapedString = [NSString stringWithFormat:#"\"%#\"", escapedString];
}
return escapedString;
}
Before I go and convert this, however, I wanted to ask the community if there is an easier way now that we're in Swift 2. Have any interesting/new changes occurred for strings that I might want to consider in favor of "Swiftifying" the above code? I did some Googling but nothing jumped out at me and I want to really make sure I do a good job here. :-)
Thanks!
You could reduce your code and save it as a String extension:
extension String {
func escapeString() -> String {
var newString = self.stringByReplacingOccurrencesOfString("\"", withString: "\"\"")
if newString.containsString(",") || newString.containsString("\n") {
newString = String(format: "\"%#\"", newString)
}
return newString
}
}
Also few tests:
var test1 = String("Test")
test1.escapeString() // produces Test
var test2 = String("Test\n")
test2.escapeString() // produces "Test\n"
var test3 = String("Test, Test2")
test3.escapeString() // produces "Test, Test2"

Add string to beginning of another string

Basic question
I have 2 strings. I want to add one string to another? Here's an example:
var secondString= "is your name."
var firstString = "Mike, "
Here I have 2 strings. I want to add firstString to secondString, NOT vice versa. (Which would be: firstString += secondString.)
More detail
I have 5 string
let first = "7898"
let second = "00"
let third = "5481"
let fourth = "4782"
var fullString = "\(third):\(fourth)"
I know for sure that third and fourth will be in fullString, but I don't know about first and second.
So I will make an if statement checking if second has 00. If it does, first and second won't go in fullString. If it doesn't, second will go intofullString`.
Then I will check if first has 00. If it does, then first won't go inside of fullString, and if not, it will go.
The thing is, I need them in the same order: first, second, third fourth. So in the if statement, I need a way to potentially add first and second at the beginning of fullString.
Re. your basic question:
secondString = "\(firstString)\(secondString)"
or
secondString = firstString + secondString
Here is a way to insert string at the beginning "without resetting" per your comment (first at front of second):
let range = second.startIndex..<second.startIndex
second.replaceRange(range, with: first)
Re. your "more detail" question:
var fullString: String
if second == "00" {
fullString = third + fourth
} else if first == "00" {
fullString = second + third + fourth
} else {
fullString = first + second + third + fourth
}
From the Apple documentation:
String values can be added together (or concatenated) with the addition operator (+) to create a new String value:
let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2
// welcome now equals "hello there"
You can also append a String value to an existing String variable with the addition assignment operator (+=):
var instruction = "look over"
instruction += string2
// instruction now equals "look over there"
You can append a Character value to a String variable with the String type’s append() method:
let exclamationMark: Character = "!"
welcome.append(exclamationMark)
// welcome now equals "hello there!"
So you are pretty much free to add these in any way shape or form.
Which includes
secondstring += firststring
Edit to accommodate the new information:
Strings in Swift are mutable which means you can always add to a string in-place without recreating any objects.
Something like (pseudo-code)
if(second != "00")
{
fullstring = second + fullstring
//only do something with first if second != 00
if(first != "00")
{
fullstring = first + fullstring
}
}

Unicode Character Conversion

I am attempting to use the unicode character U+00AE in a UITextView. If I use the code \u{00AE} using the below:
textView.text = "THING AND STUFF TrademarkedThing\u{00AE}"
However, if I pull some text from another location (this is technically coming from an API call, but that shouldn't matter), and assign it to the textView, I do not get the unicode character:
var apiText = "TrademarkedThing\u{00AE}" //Pulled from API call as text and saved into text variable
textView.text = "THING AND STUFF " + apiText
So the in the code below, the first unicode character does not show, but the second does.
var apiText = "TrademarkedThing\u{00AE}" //Pulled from API call as text and saved into text variable
textView.text = "THING AND STUFF " + apiText + " \u{00AE}"
Why won't the unicode from that text show?
The Unicode conversion (from \u{...}) only happens for string literals. You can see the problem if you compare these two:
let t1 = "Thing\u{00AE}"
// Thing®
let t2 = "Thing\\u{00" + "AE}"
// Thing\u{00AE}
Since your string is coming from another source, it acts like the second one. The Swift language book has a section on Unicode characters in string literals.
If you want to interpret those Unicode sequences after the fact, here's a String extension:
extension String {
func indexOfSubstring(str: String, fromIndex: String.Index? = nil) -> String.Index? {
var index = fromIndex ?? startIndex
while index < endIndex {
if self[Range(start: index, end: endIndex)].hasPrefix(str) {
return index
}
index = index.successor()
}
return nil
}
func convertedUnicodeSequences() -> String {
if let index = indexOfSubstring("\\u{") {
if let nextIndex = indexOfSubstring("}", fromIndex: index) {
let substr = self[Range(start: advance(index, 3), end: nextIndex)]
let scalar = UnicodeScalar(UInt32(strtoul(substr, nil, 16)))
return self[Range(start: startIndex, end: index)] +
String(scalar) +
self[Range(start: nextIndex.successor(), end: endIndex)].convertedUnicodeSequences()
}
}
return self
}
}

NSRange in Strings having dialects

I was working on an app, which takes input in a language called "Tamil". So in order to find the range of any particular charater in the string i have used the below code.
var range = originalWord.rangeOfString("\(character)")
println("\(range.location)")
So this works fine except for some cases.
there are some characters like this -> í , ó . // am just saying an example.
So like this combination, in other languages there are several vowel diacritcs are there.
If i have this word "alv`in"
// which is alvin , but i used "v" with a dialect.
If i print the unicde value of these characters in xcode, i will get each unicode. But for "v`" there will be two unicode values but its considered as a single character.
So if i check this character in the above mentioned code. i get the folowing result. Which gives errors in my program.
range.location // 2147483647 , its not a single digit.? why.?
But for other characters its just prints the correct Int Value. // Single digit like "3"
Anybody have any idea of how to get this done.? How can i achieve this if i use characters with dialets
.?
code given below
// userInput = "இல்லம்"
var originalWord : NSString = ("இல்லம்")
var originalArray = Array("இல்லம்")
var userInputWord = Array(String(userInput))
// -------------------------------------------
for character in String(userInput)
{
switch character
{
case originalArray[0] :
// here matches first character of the userinput to the original word first character
// the character exists at the 0th index
var range = originalWord.rangeOfString("\(character)")
if range.location == 0
{
// same character in the same index
// correctValue increase by one (cow Value)
cowValue += 1
}
else
{
// same character but in the different index
// Wrong value increase by one (bull Value)
bullValue += 1
}
case originalArray[1] :
// here matches first character of the userinput to the original word first character
// the character exists at the 1th index
var range = originalWord.rangeOfString("\(character)")
println("\(range.location)") // here i get he long Int Value instead of single digit
if range.location == 1
{
// same character in the same index
// correctValue increase by one (cow Value)
cowValue += 1
}
else
{
// same character but in the different index
// Wrong value increase by one (bull Value)
bullValue += 1
}
You should use Swift strings instead of NSString, because Swift strings have
full Unicode support including composed character sequences, (extended) grapheme clusters etc.
For Swift strings, rangeOfString() returns an optional Range<String.Index>
which is a bit more complicated to handle. You can also use find() instead to
find the position of a character. This might help as a starting point:
var cowValue = 0
var bullValue = 0
let userInput = "இல்லம்"
let originalWord = "இல்லம்"
let originalArray = Array("இல்லம்")
for character in userInput {
switch character {
case originalArray[0] :
if let pos = find(originalWord, character) {
// Character found in string
println(pos)
if pos == originalWord.startIndex {
// At position 0
cowValue += 1
} else {
// At a different position
bullValue += 1
}
} else {
// Character not found in string
}
case originalArray[1] :
if let pos = find(originalWord, character) {
// Character found in string
println(pos)
if pos == advance(originalWord.startIndex, 1) {
// At position 1
cowValue += 1
} else {
// At a different position
bullValue += 1
}
} else {
// Character not found in string
}
default:
println("What ?")
}
}
Check out the documentation for NSString's rangeOfComposedCharacterSequenceAtIndex: and rangeOfComposedCharacterSequencesForRange:
You want to look for Composed Character Sequences, not individual characters.

Resources